diff --git a/404.html b/404.html new file mode 100644 index 000000000..b2bb3f6ec --- /dev/null +++ b/404.html @@ -0,0 +1,192 @@ +页面没有找到 | The Blog + + + + + + + + + +
Page not found

404

Page Not Found
\ No newline at end of file diff --git a/archives/2023/03/index.html b/archives/2023/03/index.html new file mode 100644 index 000000000..4cd08ed18 --- /dev/null +++ b/archives/2023/03/index.html @@ -0,0 +1,192 @@ +三月 2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/03/page/2/index.html b/archives/2023/03/page/2/index.html new file mode 100644 index 000000000..318503c67 --- /dev/null +++ b/archives/2023/03/page/2/index.html @@ -0,0 +1,192 @@ +三月 2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/04/index.html b/archives/2023/04/index.html new file mode 100644 index 000000000..895c4eaa1 --- /dev/null +++ b/archives/2023/04/index.html @@ -0,0 +1,192 @@ +四月 2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/04/page/2/index.html b/archives/2023/04/page/2/index.html new file mode 100644 index 000000000..7844bc2e5 --- /dev/null +++ b/archives/2023/04/page/2/index.html @@ -0,0 +1,192 @@ +四月 2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/05/index.html b/archives/2023/05/index.html new file mode 100644 index 000000000..51e7acc3c --- /dev/null +++ b/archives/2023/05/index.html @@ -0,0 +1,192 @@ +五月 2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/05/page/2/index.html b/archives/2023/05/page/2/index.html new file mode 100644 index 000000000..04a1cd122 --- /dev/null +++ b/archives/2023/05/page/2/index.html @@ -0,0 +1,192 @@ +五月 2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/06/index.html b/archives/2023/06/index.html new file mode 100644 index 000000000..d5484a87a --- /dev/null +++ b/archives/2023/06/index.html @@ -0,0 +1,192 @@ +六月 2023 | The Blog + + + + + + + + + +
文章总览 - 4
2023
ElasticSearch
ElasticSearch
数据校验
数据校验
Linux中开发环境的搭建
Linux中开发环境的搭建
开发环境的搭建
开发环境的搭建
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/07/index.html b/archives/2023/07/index.html new file mode 100644 index 000000000..e3ab180fe --- /dev/null +++ b/archives/2023/07/index.html @@ -0,0 +1,192 @@ +七月 2023 | The Blog + + + + + + + + + +
文章总览 - 3
2023
SpringBoot入门教程
SpringBoot入门教程
压力测试与性能监控
压力测试与性能监控
Thymeleaf教程
Thymeleaf教程
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html new file mode 100644 index 000000000..196be3085 --- /dev/null +++ b/archives/2023/08/index.html @@ -0,0 +1,192 @@ +八月 2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/09/index.html b/archives/2023/09/index.html new file mode 100644 index 000000000..6ed314897 --- /dev/null +++ b/archives/2023/09/index.html @@ -0,0 +1,192 @@ +九月 2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 000000000..831cef15d --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1,192 @@ +2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/page/2/index.html b/archives/2023/page/2/index.html new file mode 100644 index 000000000..1ce5b0e60 --- /dev/null +++ b/archives/2023/page/2/index.html @@ -0,0 +1,192 @@ +2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/page/3/index.html b/archives/2023/page/3/index.html new file mode 100644 index 000000000..cb46e4bc9 --- /dev/null +++ b/archives/2023/page/3/index.html @@ -0,0 +1,192 @@ +2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/page/4/index.html b/archives/2023/page/4/index.html new file mode 100644 index 000000000..f60bc2529 --- /dev/null +++ b/archives/2023/page/4/index.html @@ -0,0 +1,192 @@ +2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/page/5/index.html b/archives/2023/page/5/index.html new file mode 100644 index 000000000..36eff716c --- /dev/null +++ b/archives/2023/page/5/index.html @@ -0,0 +1,192 @@ +2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/page/6/index.html b/archives/2023/page/6/index.html new file mode 100644 index 000000000..283cb50ce --- /dev/null +++ b/archives/2023/page/6/index.html @@ -0,0 +1,192 @@ +2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/2023/page/7/index.html b/archives/2023/page/7/index.html new file mode 100644 index 000000000..a1a267b55 --- /dev/null +++ b/archives/2023/page/7/index.html @@ -0,0 +1,192 @@ +2023 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 000000000..eb6488ed8 --- /dev/null +++ b/archives/index.html @@ -0,0 +1,192 @@ +归档 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 000000000..1d0706c92 --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,192 @@ +归档 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/page/3/index.html b/archives/page/3/index.html new file mode 100644 index 000000000..f3a944a13 --- /dev/null +++ b/archives/page/3/index.html @@ -0,0 +1,192 @@ +归档 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/page/4/index.html b/archives/page/4/index.html new file mode 100644 index 000000000..572e09475 --- /dev/null +++ b/archives/page/4/index.html @@ -0,0 +1,192 @@ +归档 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/page/5/index.html b/archives/page/5/index.html new file mode 100644 index 000000000..53a846db2 --- /dev/null +++ b/archives/page/5/index.html @@ -0,0 +1,192 @@ +归档 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/page/6/index.html b/archives/page/6/index.html new file mode 100644 index 000000000..33c140e6f --- /dev/null +++ b/archives/page/6/index.html @@ -0,0 +1,192 @@ +归档 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/archives/page/7/index.html b/archives/page/7/index.html new file mode 100644 index 000000000..e4ac624f6 --- /dev/null +++ b/archives/page/7/index.html @@ -0,0 +1,192 @@ +归档 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/baidusitemap.xml b/baidusitemap.xml new file mode 100644 index 000000000..4a4453aad --- /dev/null +++ b/baidusitemap.xml @@ -0,0 +1,283 @@ + + + + https://jasonsgong.gitee.io/posts/32696.html + 2023-09-22 + + + https://jasonsgong.gitee.io/posts/24183.html + 2023-09-22 + + + https://jasonsgong.gitee.io/posts/8957.html + 2023-09-21 + + + https://jasonsgong.gitee.io/posts/29985.html + 2023-09-21 + + + https://jasonsgong.gitee.io/posts/25154.html + 2023-09-19 + + + https://jasonsgong.gitee.io/posts/39654.html + 2023-09-18 + + + https://jasonsgong.gitee.io/posts/53088.html + 2023-09-14 + + + https://jasonsgong.gitee.io/posts/64695.html + 2023-09-14 + + + https://jasonsgong.gitee.io/posts/5727.html + 2023-09-12 + + + https://jasonsgong.gitee.io/posts/19306.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/63724.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/60685.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/18459.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/28118.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/63587.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/20683.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/48020.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/21883.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/17259.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/24637.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/53306.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/50465.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/24606.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/73.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/19270.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/29367.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/50908.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/62439.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/26768.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/22654.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/56742.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/32679.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/32246.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/855.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/12929.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/38823.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/64205.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/47407.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/13813.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/54835.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/60780.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/30127.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/6932.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/1530.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/1416.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/13579.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/47003.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/51007.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/14438.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/17772.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/63333.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/1727.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/36397.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/45572.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/432.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/28687.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/60684.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/3661.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/35630.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/27166.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/46306.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/22202.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/7353.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/29250.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/31385.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/11844.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/6319.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/46317.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/40445.html + 2023-09-11 + + + https://jasonsgong.gitee.io/posts/45726.html + 2023-09-11 + + \ No newline at end of file diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 000000000..d6340a0e6 --- /dev/null +++ b/categories/index.html @@ -0,0 +1,194 @@ +分类 | The Blog + + + + + + + + + + + +
\ No newline at end of file diff --git a/categories/个人/index.html b/categories/个人/index.html new file mode 100644 index 000000000..7877ab585 --- /dev/null +++ b/categories/个人/index.html @@ -0,0 +1,192 @@ +分类: 个人 | The Blog + + + + + + + + + +
分类 - 个人
2023
免费域名注册教程
免费域名注册教程
代码注释模板
代码注释模板
常用网站及网址信息
常用网站及网址信息
Blog
Blog
任务进度
任务进度
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/前端/index.html b/categories/前端/index.html new file mode 100644 index 000000000..4fb957f54 --- /dev/null +++ b/categories/前端/index.html @@ -0,0 +1,192 @@ +分类: 前端 | The Blog + + + + + + + + + +
分类 - 前端
2023
FreeMarker模板引擎
FreeMarker模板引擎
Thymeleaf教程
Thymeleaf教程
ElementUI使用示例
ElementUI使用示例
前端基础知识
前端基础知识
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/后端/index.html b/categories/后端/index.html new file mode 100644 index 000000000..6d9886008 --- /dev/null +++ b/categories/后端/index.html @@ -0,0 +1,192 @@ +分类: 后端 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/后端/page/2/index.html b/categories/后端/page/2/index.html new file mode 100644 index 000000000..b5b70d2f1 --- /dev/null +++ b/categories/后端/page/2/index.html @@ -0,0 +1,192 @@ +分类: 后端 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/后端/page/3/index.html b/categories/后端/page/3/index.html new file mode 100644 index 000000000..6c065844f --- /dev/null +++ b/categories/后端/page/3/index.html @@ -0,0 +1,192 @@ +分类: 后端 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/后端/page/4/index.html b/categories/后端/page/4/index.html new file mode 100644 index 000000000..b964f8a5d --- /dev/null +++ b/categories/后端/page/4/index.html @@ -0,0 +1,192 @@ +分类: 后端 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/后端/page/5/index.html b/categories/后端/page/5/index.html new file mode 100644 index 000000000..c52e6ef5d --- /dev/null +++ b/categories/后端/page/5/index.html @@ -0,0 +1,192 @@ +分类: 后端 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/后端/page/6/index.html b/categories/后端/page/6/index.html new file mode 100644 index 000000000..aabf8d66d --- /dev/null +++ b/categories/后端/page/6/index.html @@ -0,0 +1,192 @@ +分类: 后端 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/测试/index.html b/categories/测试/index.html new file mode 100644 index 000000000..cd1c9f900 --- /dev/null +++ b/categories/测试/index.html @@ -0,0 +1,192 @@ +分类: 测试 | The Blog + + + + + + + + + +
分类 - 测试
2023
压力测试与性能监控
压力测试与性能监控
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/运维/index.html b/categories/运维/index.html new file mode 100644 index 000000000..caa11b116 --- /dev/null +++ b/categories/运维/index.html @@ -0,0 +1,192 @@ +分类: 运维 | The Blog + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/categories/面试/index.html b/categories/面试/index.html new file mode 100644 index 000000000..0dff66eae --- /dev/null +++ b/categories/面试/index.html @@ -0,0 +1,192 @@ +分类: 面试 | The Blog + + + + + + + + + +
分类 - 面试
2023
面试题集锦
面试题集锦
简历模板
简历模板
面试专题
面试专题
个人简历
个人简历
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/css/hbe.style.css b/css/hbe.style.css new file mode 100644 index 000000000..060f1f83b --- /dev/null +++ b/css/hbe.style.css @@ -0,0 +1,749 @@ +.hbe, +.hbe:after, +.hbe:before { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.hbe-container{ + margin: 0 auto; + overflow: hidden; +} +.hbe-content { + text-align: center; + font-size: 150%; + padding: 1em 0; +} + +.hbe-input { + position: relative; + z-index: 1; + display: inline-block; + margin: 1em; + width: 80%; + min-width: 200px; + vertical-align: top; +} + +.hbe-input-field { + line-height: normal; + font-size: 100%; + margin: 0; + position: relative; + display: block; + float: right; + padding: 0.8em; + width: 60%; + border: none; + border-radius: 0; + background: #f0f0f0; + color: #aaa; + font-weight: 400; + font-family: "Avenir Next", "Helvetica Neue", Helvetica, Arial, sans-serif; + -webkit-appearance: none; /* for box shadows to show on iOS */ +} + +.hbe-input-field:focus { + outline: none; +} + +.hbe-input-label { + display: inline-block; + float: right; + padding: 0 1em; + width: 40%; + color: #696969; + font-weight: bold; + font-size: 70.25%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.hbe-input-label-content { + position: relative; + display: block; + padding: 1.6em 0; + width: 100%; +} + +.hbe-graphic { + position: absolute; + top: 0; + left: 0; + fill: none; +} + +/* hbe button in post page */ +.hbe-button { + width: 130px; + height: 40px; + background: linear-gradient(to bottom, #4eb5e5 0%,#389ed5 100%); /* W3C */ + border: none; + border-radius: 5px; + position: relative; + border-bottom: 4px solid #2b8bc6; + color: #fbfbfb; + font-weight: 600; + font-family: 'Open Sans', sans-serif; + text-shadow: 1px 1px 1px rgba(0,0,0,.4); + font-size: 15px; + text-align: left; + text-indent: 5px; + box-shadow: 0px 3px 0px 0px rgba(0,0,0,.2); + cursor: pointer; + + display: block; + margin: 0 auto; + margin-bottom: 20px; +} + +.hbe-button:active { + box-shadow: 0px 2px 0px 0px rgba(0,0,0,.2); + top: 1px; +} + +.hbe-button:after { + content: ""; + width: 0; + height: 0; + display: block; + border-top: 20px solid #187dbc; + border-bottom: 20px solid #187dbc; + border-left: 16px solid transparent; + border-right: 20px solid #187dbc; + position: absolute; + opacity: 0.6; + right: 0; + top: 0; + border-radius: 0 5px 5px 0; +} +/* hbe button in post page */ + +/* default theme {{{ */ +.hbe-input-default { + overflow: hidden; +} + +.hbe-input-field-default { + width: 100%; + background: transparent; + padding: 0.5em; + margin-bottom: 2em; + color: #f9f7f6; + z-index: 100; + opacity: 0; +} + +.hbe-input-label-default { + width: 100%; + position: absolute; + text-align: left; + padding: 0.5em 0; + pointer-events: none; + font-size: 1em; +} + +.hbe-input-label-default::before, +.hbe-input-label-default::after { + content: ''; + position: absolute; + width: 100%; + left: 0; +} + +.hbe-input-label-default::before { + height: 100%; + background: #666666; + top: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + -webkit-transition: -webkit-transform 0.2s; + transition: transform 0.2s; +} + +.hbe-input-label-default::after { + height: 2px; + background: #666666; + top: 100%; + -webkit-transition: opacity 0.2s; + transition: opacity 0.2s; +} + +.hbe-input-label-content-default { + padding: 0; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transition: -webkit-transform 0.2s, color 0.2s; + transition: transform 0.2s, color 0.2s; +} + +.hbe-input-field-default:focus, +.hbe-input--filled .hbe-input-field-default { + opacity: 1; + -webkit-transition: opacity 0s 0.2s; + transition: opacity 0s 0.2s; +} + +.hbe-input-label-default::before, +.hbe-input-label-default::after, +.hbe-input-label-content-default, +.hbe-input-field-default:focus, +.hbe-input--filled .hbe-input-field-default { + -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); + transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); +} + +.hbe-input-field-default:focus + .hbe-input-label-default::before, +.hbe-input--filled .hbe-input-label-default::before { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} + +.hbe-input-field-default:focus + .hbe-input-label-default::after, +.hbe-input--filled .hbe-input-label-default::after { + opacity: 0; +} + +.hbe-input-field-default:focus + .hbe-input-label-default .hbe-input-label-content-default, +.hbe-input--filled .hbe-input-label-default .hbe-input-label-content-default { + color: #555555; + -webkit-transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1); + transform: translate3d(0, 2.1em, 0) scale3d(0.65, 0.65, 1); +} +/* default theme }}} */ + +/* up theme {{{ */ +.hbe-input-up { + overflow: hidden; + padding-top: 2em; +} + +.hbe-input-field-up { + width: 100%; + background: transparent; + opacity: 0; + padding: 0.35em; + z-index: 100; + color: #837482; +} + +.hbe-input-label-up { + width: 100%; + bottom: 0; + position: absolute; + pointer-events: none; + text-align: left; + color: #8E9191; + padding: 0 0.5em; +} + +.hbe-input-label-up::before { + content: ''; + position: absolute; + width: 100%; + height: 4em; + top: 100%; + left: 0; + background: #fff; + border-top: 4px solid #9B9F9F; + -webkit-transform: translate3d(0, -3px, 0); + transform: translate3d(0, -3px, 0); + -webkit-transition: -webkit-transform 0.4s; + transition: transform 0.4s; + -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); + transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); +} + +.hbe-input-label-content-up { + padding: 0.5em 0; + -webkit-transform-origin: 0% 100%; + transform-origin: 0% 100%; + -webkit-transition: -webkit-transform 0.4s, color 0.4s; + transition: transform 0.4s, color 0.4s; + -webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); + transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1); +} + +.hbe-input-field-up:focus, +.input--filled .hbe-input-field-up { + cursor: text; + opacity: 1; + -webkit-transition: opacity 0s 0.4s; + transition: opacity 0s 0.4s; +} + +.hbe-input-field-up:focus + .hbe-input-label-up::before, +.input--filled .hbe-input-label-up::before { + -webkit-transition-delay: 0.05s; + transition-delay: 0.05s; + -webkit-transform: translate3d(0, -3.3em, 0); + transform: translate3d(0, -3.3em, 0); +} + +.hbe-input-field-up:focus + .hbe-input-label-up .hbe-input-label-content-up, +.input--filled .hbe-input-label-content-up { + color: #6B6E6E; + -webkit-transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1); + transform: translate3d(0, -3.3em, 0) scale3d(0.81, 0.81, 1); +} +/* up theme }}} */ + +/* wave theme {{{ */ +.hbe-input-wave { + overflow: hidden; + padding-top: 1em; +} + +.hbe-input-field-wave { + padding: 0.5em 0em 0.25em; + width: 100%; + background: transparent; + color: #9da8b2; + font-size: 1.25em; +} + +.hbe-input-label-wave { + position: absolute; + top: 0.95em; + font-size: 0.85em; + left: 0; + display: block; + width: 100%; + text-align: left; + padding: 0em; + pointer-events: none; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transition: -webkit-transform 0.2s 0.15s, color 1s; + transition: transform 0.2s 0.15s, color 1s; + -webkit-transition-timing-function: ease-out; + transition-timing-function: ease-out; +} + +.hbe-graphic-wave { + stroke: #92989e; + pointer-events: none; + -webkit-transition: -webkit-transform 0.7s, stroke 0.7s; + transition: transform 0.7s, stroke 0.7s; + -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); + transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); +} + +.hbe-input-field-wave:focus + .hbe-input-label-wave, +.input--filled .hbe-input-label-wave { + color: #333; + -webkit-transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1); + transform: translate3d(0, -1.25em, 0) scale3d(0.75, 0.75, 1); +} + +.hbe-input-field-wave:focus ~ .hbe-graphic-wave, +.input--filled .graphic-wave { + stroke: #333; + -webkit-transform: translate3d(-66.6%, 0, 0); + transform: translate3d(-66.6%, 0, 0); +} +/* wave theme }}} */ + +/* flip theme {{{ */ +.hbe-input-field-flip { + width: 100%; + background-color: #d0d1d0; + border: 2px solid transparent; + -webkit-transition: background-color 0.25s, border-color 0.25s; + transition: background-color 0.25s, border-color 0.25s; +} + +.hbe-input-label-flip { + width: 100%; + text-align: left; + position: absolute; + bottom: 100%; + pointer-events: none; + overflow: hidden; + padding: 0 1.25em; + -webkit-transform: translate3d(0, 3em, 0); + transform: translate3d(0, 3em, 0); + -webkit-transition: -webkit-transform 0.25s; + transition: transform 0.25s ; + -webkit-transition-timing-function: ease-in-out; + transition-timing-function: ease-in-out; +} + +.hbe-input-label-content-flip { + color: #8B8C8B; + padding: 0.25em 0; + -webkit-transition: -webkit-transform 0.25s; + transition: transform 0.25s; + -webkit-transition-timing-function: ease-in-out; + transition-timing-function: ease-in-out; +} + +.hbe-input-label-content-flip::after { + content: attr(data-content); + position: absolute; + font-weight: 800; + bottom: 100%; + left: 0; + height: 100%; + width: 100%; + color: #666666; + padding: 0.25em 0; + letter-spacing: 1px; + font-size: 1em; +} + +.hbe-input-field-flip:focus + .hbe-input-label-flip, +.input--filled .hbe-input-label-flip { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} + +.hbe-input-field-flip:focus + .hbe-input-label-flip .hbe-input-label-content-flip, +.input--filled .hbe-input-label-content-flip { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); +} + +.hbe-input-field-flip:focus + .hbe-input-field-flip, +.input--filled .hbe-input-field-flip { + background-color: transparent; + border-color: #666666; +} +/* flip theme }}} */ + +/* xray theme {{{ */ +.hbe-input-xray { + overflow: hidden; + padding-bottom: 2.5em; +} + +.hbe-input-field-xray { + padding: 0; + margin-top: 1.2em; + width: 100%; + background: transparent; + color: #84AF9B ; + font-size: 1.55em; +} + +.hbe-input-label-xray { + position: absolute; + top: 2em; + left: 0; + display: block; + width: 100%; + text-align: left; + padding: 0em; + letter-spacing: 1px; + color: #84AF9B ; + pointer-events: none; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transition: -webkit-transform 0.2s 0.1s, color 0.3s; + transition: transform 0.2s 0.1s, color 0.3s; + -webkit-transition-timing-function: ease-out; + transition-timing-function: ease-out; +} + +.hbe-graphic-xray { + stroke: #84AF9B ; + pointer-events: none; + stroke-width: 2px; + top: 1.25em; + bottom: 0px; + height: 3.275em; + -webkit-transition: -webkit-transform 0.7s, stroke 0.7s; + transition: transform 0.7s, stroke 0.7s; + -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); + transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); +} + +.hbe-input-field-xray:focus + .hbe-input-label-xray, +.input--filled .hbe-input-label-xray { + color: #84AF9B ; + -webkit-transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1); + transform: translate3d(0, 3.5em, 0) scale3d(0.85, 0.85, 1); +} + +.hbe-input-field-xray:focus ~ .hbe-graphic-xray, +.input--filled .graphic-xray { + stroke: #84AF9B ; + -webkit-transform: translate3d(-66.6%, 0, 0); + transform: translate3d(-66.6%, 0, 0); +} +/* xray theme }}} */ + +/* blink theme {{{ */ +.hbe-input-blink { + padding-top: 1em; +} + +.hbe-input-field-blink { + width: 100%; + padding: 0.8em 0.5em; + background: transparent; + border: 2px solid; + color: #8781bd; + -webkit-transition: border-color 0.25s; + transition: border-color 0.25s; +} + +.hbe-input-label-blink { + width: 100%; + position: absolute; + top: 0; + text-align: left; + overflow: hidden; + padding: 0; + pointer-events: none; + -webkit-transform: translate3d(0, 3em, 0); + transform: translate3d(0, 3em, 0); +} + +.hbe-input-label-content-blink { + padding: 0 1em; + font-weight: 400; + color: #b5b5b5; +} + +.hbe-input-label-content-blink::after { + content: attr(data-content); + position: absolute; + top: -200%; + left: 0; + color: #8781bd ; + font-weight: 800; +} + +.hbe-input-field-blink:focus, +.input--filled .hbe-input-field-blink { + border-color: #8781bd ; +} + +.hbe-input-field-blink:focus + .hbe-input-label-blink, +.input--filled .hbe-input-label-blink { + -webkit-animation: anim-blink-1 0.25s forwards; + animation: anim-blink-1 0.25s forwards; +} + +.hbe-input-field-blink:focus + .hbe-input-label-blink .hbe-input-label-content-blink, +.input--filled .hbe-input-label-content-blink { + -webkit-animation: anim-blink-2 0.25s forwards ease-in; + animation: anim-blink-2 0.25s forwards ease-in; +} + +@-webkit-keyframes anim-blink-1 { + 0%, 70% { + -webkit-transform: translate3d(0, 3em, 0); + transform: translate3d(0, 3em, 0); + } + 71%, 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@-webkit-keyframes anim-blink-2 { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + 70%, 71% { + -webkit-transform: translate3d(0, 125%, 0); + transform: translate3d(0, 125%, 0); + opacity: 0; + -webkit-animation-timing-function: ease-out; + } + 100% { + color: transparent; + -webkit-transform: translate3d(0, 200%, 0); + transform: translate3d(0, 200%, 0); + } +} + +@keyframes anim-blink-1 { + 0%, 70% { + -webkit-transform: translate3d(0, 3em, 0); + transform: translate3d(0, 3em, 0); + } + 71%, 100% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes anim-blink-2 { + 0% { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + 70%, 71% { + -webkit-transform: translate3d(0, 125%, 0); + transform: translate3d(0, 125%, 0); + opacity: 0; + -webkit-animation-timing-function: ease-out; + } + 100% { + color: transparent; + -webkit-transform: translate3d(0, 200%, 0); + transform: translate3d(0, 200%, 0); + } +} +/* blink theme }}} */ + +/* surge theme {{{ */ +.hbe-input-surge { + overflow: hidden; + padding-bottom: 1em; +} + +.hbe-input-field-surge { + padding: 0.25em 0.5em; + margin-top: 1.25em; + width: 100%; + background: transparent; + color: #D0D0D0; + font-size: 1.55em; + opacity: 0; +} + +.hbe-input-label-surge { + width: 100%; + text-align: left; + position: absolute; + top: 1em; + pointer-events: none; + overflow: hidden; + padding: 0 0.25em; + -webkit-transform: translate3d(1em, 2.75em, 0); + transform: translate3d(1em, 2.75em, 0); + -webkit-transition: -webkit-transform 0.3s; + transition: transform 0.3s; +} + +.hbe-input-label-content-surge { + color: #A4A5A6; + padding: 0.4em 0 0.25em; + -webkit-transition: -webkit-transform 0.3s; + transition: transform 0.3s; +} + +.hbe-input-label-content-surge::after { + content: attr(data-content); + position: absolute; + font-weight: 800; + top: 100%; + left: 0; + height: 100%; + width: 100%; + color: #2C3E50; + padding: 0.25em 0; + letter-spacing: 1px; + font-size: 0.85em; +} + +.hbe-graphic-surge { + fill: #2C3E50; + pointer-events: none; + top: 1em; + bottom: 0px; + height: 4.5em; + z-index: -1; + -webkit-transition: -webkit-transform 0.7s, fill 0.7s; + transition: transform 0.7s, fill 0.7s; + -webkit-transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); + transition-timing-function: cubic-bezier(0, 0.25, 0.5, 1); +} + +.hbe-input-field-surge:focus, +.input--filled .hbe-input-field-surge { + -webkit-transition: opacity 0s 0.35s; + transition: opacity 0s 0.35s; + opacity: 1; +} + +.hbe-input-field-surge:focus + .hbe-input-label-surge, +.input--filled .hbe-input-label-surge { + -webkit-transition-delay: 0.15s; + transition-delay: 0.15s; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} + +.hbe-input-field-surge:focus + .hbe-input-label-surge .hbe-input-label-content-surge, +.input--filled .hbe-input-label-content-surge { + -webkit-transition-delay: 0.15s; + transition-delay: 0.15s; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); +} + +.hbe-input-field-surge:focus ~ .hbe-graphic-surge, +.input--filled .graphic-surge { + fill: #2C3E50; + -webkit-transform: translate3d(-66.6%, 0, 0); + transform: translate3d(-66.6%, 0, 0); +} +/* surge theme }}} */ + +/* shrink theme {{{ */ +.hbe-input-field-shrink { + width: 100%; + background: transparent; + padding: 0.5em 0; + margin-bottom: 2em; + color: #2C3E50; +} + +.hbe-input-label-shrink { + width: 100%; + position: absolute; + text-align: left; + font-size: 1em; + padding: 10px 0 5px; + pointer-events: none; +} + +.hbe-input-label-shrink::after { + content: ''; + position: absolute; + width: 100%; + height: 7px; + background: #B7C3AC; + left: 0; + top: 100%; + -webkit-transform-origin: 50% 100%; + transform-origin: 50% 100%; + -webkit-transition: -webkit-transform 0.3s, background-color 0.3s; + transition: transform 0.3s, background-color 0.3s; +} + +.hbe-input-label-content-shrink { + padding: 0; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transition: -webkit-transform 0.3s, color 0.3s; + transition: transform 0.3s, color 0.3s; +} + +.hbe-input-field-shrink:focus + .hbe-input-label-shrink::after, +.input--filled .hbe-input-label-shrink::after { + background: #84AF9B; + -webkit-transform: scale3d(1, 0.25, 1); + transform: scale3d(1, 0.25, 1); +} + +.hbe-input-field-shrink:focus + .hbe-input-label-shrink .hbe-input-label-content-shrink, +.input--filled .hbe-input-label-shrink .hbe-input-label-content-shrink { + color: #84AF9B; + -webkit-transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1); + transform: translate3d(0, 2em, 0) scale3d(0.655, 0.655, 1); +} +/* shrink theme }}} */ diff --git a/css/index.css b/css/index.css new file mode 100644 index 000000000..9e6a3db85 --- /dev/null +++ b/css/index.css @@ -0,0 +1,5924 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ +html { + line-height: 1.15; + -webkit-text-size-adjust: 100% +} + +body { + margin: 0 +} + +main { + display: block +} + +h1 { + font-size: 2em; + margin: .67em 0 +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible +} + +pre { + font-family: monospace, monospace; + font-size: 1em +} + +a { + background-color: transparent +} + +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted +} + +b, +strong { + font-weight: bolder +} + +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em +} + +small { + font-size: 80% +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sub { + bottom: -.25em +} + +sup { + top: -.5em +} + +img { + border-style: none +} + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + font-size: 100%; + line-height: 1.15; + margin: 0 +} + +button, +input { + overflow: visible +} + +button, +select { + text-transform: none +} + +[type=button], +[type=reset], +[type=submit], +button { + -webkit-appearance: button +} + +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner, +button::-moz-focus-inner { + border-style: none; + padding: 0 +} + +[type=button]:-moz-focusring, +[type=reset]:-moz-focusring, +[type=submit]:-moz-focusring, +button:-moz-focusring { + outline: 1px dotted ButtonText +} + +fieldset { + padding: .35em .75em .625em +} + +legend { + box-sizing: border-box; + color: inherit; + display: table; + max-width: 100%; + padding: 0; + white-space: normal +} + +progress { + vertical-align: baseline +} + +textarea { + overflow: auto +} + +[type=checkbox], +[type=radio] { + box-sizing: border-box; + padding: 0 +} + +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { + height: auto +} + +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px +} + +[type=search]::-webkit-search-decoration { + -webkit-appearance: none +} + +::-webkit-file-upload-button { + -webkit-appearance: button; + font: inherit +} + +details { + display: block +} + +summary { + display: list-item +} + +template { + display: none +} + +[hidden] { + display: none +} +.limit-one-line, +#article-container .flink .flink-item-name, +#article-container .flink .flink-item-desc, +#aside-content .card-archives ul.card-archive-list > .card-archive-list-item a span, +#aside-content .card-categories ul.card-category-list > .card-category-list-item a span, +.site-data > a .headline, +#nav #blog-info, +#pagination .prev_info, +#pagination .next_info, +#sidebar #sidebar-menus .menus_items .site-page { + overflow: hidden; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + white-space: nowrap; +} +.limit-more-line, +.error404 #error-wrap .error-content .error-info .error_subtitle, +.article-sort-item-title, +#recent-posts > .recent-post-item >.recent-post-info > .article-title, +#recent-posts > .recent-post-item >.recent-post-info > .content, +#aside-content .aside-list > .aside-list-item .content > .name, +#aside-content .aside-list > .aside-list-item .content > .title, +#aside-content .aside-list > .aside-list-item .content > .comment, +#post-info .post-title, +.relatedPosts > .relatedPosts-list .content .title, +#article-container figure.gallery-group p, +#article-container figure.gallery-group .gallery-group-name { + display: -webkit-box; + overflow: hidden; + -webkit-box-orient: vertical; +} +.fontawesomeIcon, +.custom-hr:before, +#post .post-copyright:before, +#post .post-outdate-notice:before, +.note:not(.no-icon)::before, +.search-dialog hr:before { + display: inline-block; + font-weight: 600; + font-family: 'Font Awesome 6 Free'; + text-rendering: auto; + -webkit-font-smoothing: antialiased; +} +.cardHover, +.error404 #error-wrap .error-content, +.layout > div:first-child:not(.recent-posts), +#recent-posts > .recent-post-item, +#aside-content .card-widget, +.layout > .recent-posts .pagination > *:not(.space) { + border-radius: 8px; + background: var(--card-bg); + -webkit-box-shadow: var(--card-box-shadow); + box-shadow: var(--card-box-shadow); + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; +} +.cardHover:hover, +.error404 #error-wrap .error-content:hover, +.layout > div:first-child:not(.recent-posts):hover, +#recent-posts > .recent-post-item:hover, +#aside-content .card-widget:hover, +.layout > .recent-posts .pagination > *:not(.space):hover { + -webkit-box-shadow: var(--card-hover-box-shadow); + box-shadow: var(--card-hover-box-shadow); +} +.imgHover, +.error404 #error-wrap .error-content .error-img img, +.article-sort-item-img :first-child, +#recent-posts > .recent-post-item .post_cover .post-bg, +#aside-content .aside-list > .aside-list-item .thumbnail :first-child { + width: 100%; + height: 100%; + -webkit-transition: filter 375ms ease-in 0.2s, -webkit-transform 0.6s; + -moz-transition: filter 375ms ease-in 0.2s, -moz-transform 0.6s; + -o-transition: filter 375ms ease-in 0.2s, -o-transform 0.6s; + -ms-transition: filter 375ms ease-in 0.2s, -ms-transform 0.6s; + transition: filter 375ms ease-in 0.2s, transform 0.6s; + object-fit: cover; +} +.imgHover:hover, +.error404 #error-wrap .error-content .error-img img:hover, +.article-sort-item-img :first-child:hover, +#recent-posts > .recent-post-item .post_cover .post-bg:hover, +#aside-content .aside-list > .aside-list-item .thumbnail :first-child:hover { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -o-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); +} +.postImgHover:hover .cover, +#pagination .prev-post:hover .cover, +#pagination .next-post:hover .cover, +.relatedPosts > .relatedPosts-list > div:hover .cover { + opacity: 0.8; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; + filter: alpha(opacity=80); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -o-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); +} +.postImgHover .cover, +#pagination .prev-post .cover, +#pagination .next-post .cover, +.relatedPosts > .relatedPosts-list > div .cover { + position: absolute; + width: 100%; + height: 100%; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + -webkit-transition: all 0.6s, filter 375ms ease-in 0.2s; + -moz-transition: all 0.6s, filter 375ms ease-in 0.2s; + -o-transition: all 0.6s, filter 375ms ease-in 0.2s; + -ms-transition: all 0.6s, filter 375ms ease-in 0.2s; + transition: all 0.6s, filter 375ms ease-in 0.2s; + object-fit: cover; +} +.list-beauty, +.category-lists ul { + list-style: none; +} +.list-beauty li, +.category-lists ul li { + position: relative; + padding: 0.12em 0.4em 0.12em 1.4em; +} +.list-beauty li:hover:before, +.category-lists ul li:hover:before { + border-color: var(--pseudo-hover); +} +.list-beauty li:before, +.category-lists ul li:before { + position: absolute; + top: 0.67em; + left: 0; + width: 0.43em; + height: 0.43em; + border: 0.215em solid #008080; + border-radius: 0.43em; + background: transparent; + content: ''; + cursor: pointer; + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} +.custom-hr, +.search-dialog hr { + position: relative; + margin: 40px auto; + border: 2px dashed var(--hr-border); + width: calc(100% - 4px); +} +.custom-hr:hover:before, +.search-dialog hr:hover:before { + left: calc(95% - 20px); +} +.custom-hr:before, +.search-dialog hr:before { + position: absolute; + top: -10px; + left: 5%; + z-index: 1; + color: var(--hr-before-color); + content: '\f0c4'; + font-size: 20px; + line-height: 1; + -webkit-transition: all 1s ease-in-out; + -moz-transition: all 1s ease-in-out; + -o-transition: all 1s ease-in-out; + -ms-transition: all 1s ease-in-out; + transition: all 1s ease-in-out; +} +#content-inner, +#footer { + -webkit-animation: bottom-top 1s; + -moz-animation: bottom-top 1s; + -o-animation: bottom-top 1s; + -ms-animation: bottom-top 1s; + animation: bottom-top 1s; +} +#page-header { + -webkit-animation: header-effect 1s; + -moz-animation: header-effect 1s; + -o-animation: header-effect 1s; + -ms-animation: header-effect 1s; + animation: header-effect 1s; +} +#site-title, +#site-subtitle { + -webkit-animation: titleScale 1s; + -moz-animation: titleScale 1s; + -o-animation: titleScale 1s; + -ms-animation: titleScale 1s; + animation: titleScale 1s; +} +#nav.show { + -webkit-animation: headerNoOpacity 1s; + -moz-animation: headerNoOpacity 1s; + -o-animation: headerNoOpacity 1s; + -ms-animation: headerNoOpacity 1s; + animation: headerNoOpacity 1s; +} +canvas:not(#ribbon-canvas), +#web_bg { + -webkit-animation: to_show 4s; + -moz-animation: to_show 4s; + -o-animation: to_show 4s; + -ms-animation: to_show 4s; + animation: to_show 4s; +} +#ribbon-canvas { + -webkit-animation: ribbon_to_show 4s; + -moz-animation: ribbon_to_show 4s; + -o-animation: ribbon_to_show 4s; + -ms-animation: ribbon_to_show 4s; + animation: ribbon_to_show 4s; +} +#sidebar-menus.open > :nth-child(1) { + -webkit-animation: sidebarItem 0.2s; + -moz-animation: sidebarItem 0.2s; + -o-animation: sidebarItem 0.2s; + -ms-animation: sidebarItem 0.2s; + animation: sidebarItem 0.2s; +} +#sidebar-menus.open > :nth-child(2) { + -webkit-animation: sidebarItem 0.4s; + -moz-animation: sidebarItem 0.4s; + -o-animation: sidebarItem 0.4s; + -ms-animation: sidebarItem 0.4s; + animation: sidebarItem 0.4s; +} +#sidebar-menus.open > :nth-child(3) { + -webkit-animation: sidebarItem 0.6s; + -moz-animation: sidebarItem 0.6s; + -o-animation: sidebarItem 0.6s; + -ms-animation: sidebarItem 0.6s; + animation: sidebarItem 0.6s; +} +#sidebar-menus.open > :nth-child(4) { + -webkit-animation: sidebarItem 0.8s; + -moz-animation: sidebarItem 0.8s; + -o-animation: sidebarItem 0.8s; + -ms-animation: sidebarItem 0.8s; + animation: sidebarItem 0.8s; +} +.scroll-down-effects { + -webkit-animation: scroll-down-effect 1.5s infinite; + -moz-animation: scroll-down-effect 1.5s infinite; + -o-animation: scroll-down-effect 1.5s infinite; + -ms-animation: scroll-down-effect 1.5s infinite; + animation: scroll-down-effect 1.5s infinite; +} +.reward-main { + -webkit-animation: donate_effcet 0.3s 0.1s ease both; + -moz-animation: donate_effcet 0.3s 0.1s ease both; + -o-animation: donate_effcet 0.3s 0.1s ease both; + -ms-animation: donate_effcet 0.3s 0.1s ease both; + animation: donate_effcet 0.3s 0.1s ease both; +} +@-moz-keyframes scroll-down-effect { + 0% { + top: 0; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + } + 50% { + top: -16px; + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + top: 0; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + } +} +@-webkit-keyframes scroll-down-effect { + 0% { + top: 0; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + } + 50% { + top: -16px; + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + top: 0; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + } +} +@-o-keyframes scroll-down-effect { + 0% { + top: 0; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + } + 50% { + top: -16px; + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + top: 0; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + } +} +@keyframes scroll-down-effect { + 0% { + top: 0; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + } + 50% { + top: -16px; + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + top: 0; + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + } +} +@-moz-keyframes header-effect { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(-50px); + -moz-transform: translateY(-50px); + -o-transform: translateY(-50px); + -ms-transform: translateY(-50px); + transform: translateY(-50px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-webkit-keyframes header-effect { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(-50px); + -moz-transform: translateY(-50px); + -o-transform: translateY(-50px); + -ms-transform: translateY(-50px); + transform: translateY(-50px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-o-keyframes header-effect { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(-50px); + -moz-transform: translateY(-50px); + -o-transform: translateY(-50px); + -ms-transform: translateY(-50px); + transform: translateY(-50px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@keyframes header-effect { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(-50px); + -moz-transform: translateY(-50px); + -o-transform: translateY(-50px); + -ms-transform: translateY(-50px); + transform: translateY(-50px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-moz-keyframes headerNoOpacity { + 0% { + -webkit-transform: translateY(-50px); + -moz-transform: translateY(-50px); + -o-transform: translateY(-50px); + -ms-transform: translateY(-50px); + transform: translateY(-50px); + } + 100% { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-webkit-keyframes headerNoOpacity { + 0% { + -webkit-transform: translateY(-50px); + -moz-transform: translateY(-50px); + -o-transform: translateY(-50px); + -ms-transform: translateY(-50px); + transform: translateY(-50px); + } + 100% { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-o-keyframes headerNoOpacity { + 0% { + -webkit-transform: translateY(-50px); + -moz-transform: translateY(-50px); + -o-transform: translateY(-50px); + -ms-transform: translateY(-50px); + transform: translateY(-50px); + } + 100% { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@keyframes headerNoOpacity { + 0% { + -webkit-transform: translateY(-50px); + -moz-transform: translateY(-50px); + -o-transform: translateY(-50px); + -ms-transform: translateY(-50px); + transform: translateY(-50px); + } + 100% { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-moz-keyframes bottom-top { + 0% { + margin-top: 50px; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + margin-top: 0; + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@-webkit-keyframes bottom-top { + 0% { + margin-top: 50px; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + margin-top: 0; + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@-o-keyframes bottom-top { + 0% { + margin-top: 50px; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + margin-top: 0; + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@keyframes bottom-top { + 0% { + margin-top: 50px; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + margin-top: 0; + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@-moz-keyframes titleScale { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} +@-webkit-keyframes titleScale { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} +@-o-keyframes titleScale { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} +@keyframes titleScale { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} +@-moz-keyframes search_close { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } +} +@-webkit-keyframes search_close { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } +} +@-o-keyframes search_close { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } +} +@keyframes search_close { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } +} +@-moz-keyframes to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@-webkit-keyframes to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@-o-keyframes to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@keyframes to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + } +} +@-moz-keyframes to_hide { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } +} +@-webkit-keyframes to_hide { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } +} +@-o-keyframes to_hide { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } +} +@keyframes to_hide { + 0% { + opacity: 1; + -ms-filter: none; + filter: none; + } + 100% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } +} +@-moz-keyframes ribbon_to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +@-webkit-keyframes ribbon_to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +@-o-keyframes ribbon_to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +@keyframes ribbon_to_show { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +@-moz-keyframes avatar_turn_around { + from { + -webkit-transform: rotate(0); + -moz-transform: rotate(0); + -o-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + } + to { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@-webkit-keyframes avatar_turn_around { + from { + -webkit-transform: rotate(0); + -moz-transform: rotate(0); + -o-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + } + to { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@-o-keyframes avatar_turn_around { + from { + -webkit-transform: rotate(0); + -moz-transform: rotate(0); + -o-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + } + to { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes avatar_turn_around { + from { + -webkit-transform: rotate(0); + -moz-transform: rotate(0); + -o-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); + } + to { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@-moz-keyframes sub_menus { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(10px); + -moz-transform: translateY(10px); + -o-transform: translateY(10px); + -ms-transform: translateY(10px); + transform: translateY(10px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-webkit-keyframes sub_menus { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(10px); + -moz-transform: translateY(10px); + -o-transform: translateY(10px); + -ms-transform: translateY(10px); + transform: translateY(10px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-o-keyframes sub_menus { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(10px); + -moz-transform: translateY(10px); + -o-transform: translateY(10px); + -ms-transform: translateY(10px); + transform: translateY(10px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@keyframes sub_menus { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(10px); + -moz-transform: translateY(10px); + -o-transform: translateY(10px); + -ms-transform: translateY(10px); + transform: translateY(10px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-moz-keyframes donate_effcet { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(-20px); + -moz-transform: translateY(-20px); + -o-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-webkit-keyframes donate_effcet { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(-20px); + -moz-transform: translateY(-20px); + -o-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-o-keyframes donate_effcet { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(-20px); + -moz-transform: translateY(-20px); + -o-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@keyframes donate_effcet { + 0% { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transform: translateY(-20px); + -moz-transform: translateY(-20px); + -o-transform: translateY(-20px); + -ms-transform: translateY(-20px); + transform: translateY(-20px); + } + 100% { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-moz-keyframes sidebarItem { + 0% { + -webkit-transform: translateX(200px); + -moz-transform: translateX(200px); + -o-transform: translateX(200px); + -ms-transform: translateX(200px); + transform: translateX(200px); + } + 100% { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + -o-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} +@-webkit-keyframes sidebarItem { + 0% { + -webkit-transform: translateX(200px); + -moz-transform: translateX(200px); + -o-transform: translateX(200px); + -ms-transform: translateX(200px); + transform: translateX(200px); + } + 100% { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + -o-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} +@-o-keyframes sidebarItem { + 0% { + -webkit-transform: translateX(200px); + -moz-transform: translateX(200px); + -o-transform: translateX(200px); + -ms-transform: translateX(200px); + transform: translateX(200px); + } + 100% { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + -o-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} +@keyframes sidebarItem { + 0% { + -webkit-transform: translateX(200px); + -moz-transform: translateX(200px); + -o-transform: translateX(200px); + -ms-transform: translateX(200px); + transform: translateX(200px); + } + 100% { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + -o-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } +} +:root { + --global-font-size: 14px; + --global-bg: #fff; + --font-color: #4c4948; + --hr-border: #a4d8fa; + --hr-before-color: #80c8f8; + --search-bg: #f6f8fa; + --search-input-color: #4c4948; + --search-a-color: #4c4948; + --preloader-bg: #37474f; + --preloader-color: #fff; + --tab-border-color: #f0f0f0; + --tab-botton-bg: #f0f0f0; + --tab-botton-color: #1f2d3d; + --tab-button-hover-bg: #dcdcdc; + --tab-button-active-bg: #fff; + --card-bg: #fff; + --sidebar-bg: #f6f8fa; + --btn-hover-color: #ff7242; + --btn-color: #fff; + --btn-bg: #008080; + --text-bg-hover: rgba(0,128,128,0.7); + --light-grey: #eee; + --dark-grey: #cacaca; + --white: #fff; + --text-highlight-color: #1f2d3d; + --blockquote-color: #6a737d; + --blockquote-bg: rgba(73,177,245,0.1); + --reward-pop: #f5f5f5; + --toc-link-color: #666261; + --card-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.05); + --card-hover-box-shadow: 0 3px 8px 6px rgba(7,17,27,0.09); + --pseudo-hover: #ff7242; + --headline-presudo: #a0a0a0; + --scrollbar-color: #008080; + --default-bg-color: #008080; + --zoom-bg: #fff; + --mark-bg: rgba(0,0,0,0.3); +} +body { + position: relative; + min-height: 100%; + background: var(--global-bg); + color: var(--font-color); + font-size: var(--global-font-size); + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif; + line-height: 2; + -webkit-tap-highlight-color: rgba(0,0,0,0); +} +*::-webkit-scrollbar { + width: 5px; + height: 5px; +} +*::-webkit-scrollbar-thumb { + background: var(--scrollbar-color); +} +*::-webkit-scrollbar-track { + background-color: transparent; +} +* { + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-color) transparent; +} +input::placeholder { + color: var(--font-color); +} +h1, +h2, +h3, +h4, +h5, +h6 { + position: relative; + margin: 20px 0 14px; + color: var(--text-highlight-color); + font-weight: bold; +} +h1 code, +h2 code, +h3 code, +h4 code, +h5 code, +h6 code { + font-size: inherit !important; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.table-wrap { + overflow-x: scroll; + margin: 0 0 20px; +} +table { + display: table; + width: 100%; + border-spacing: 0; + border-collapse: collapse; + empty-cells: show; +} +table thead { + background: rgba(153,169,191,0.1); +} +table th, +table td { + padding: 6px 12px; + border: 1px solid var(--light-grey); + vertical-align: middle; +} +*::selection { + background: #00c4b6; + color: #f7f7f7; +} +button { + padding: 0; + outline: 0; + border: none; + background: none; + cursor: pointer; + touch-action: manipulation; +} +a { + color: #99a9bf; + text-decoration: none; + word-wrap: break-word; + -webkit-transition: all 0.2s; + -moz-transition: all 0.2s; + -o-transition: all 0.2s; + -ms-transition: all 0.2s; + transition: all 0.2s; + overflow-wrap: break-word; +} +a:hover { + color: #008080; +} +.is-center { + text-align: center; +} +.pull-left { + float: left; +} +.pull-right { + float: right; +} +img[src=''], +img:not([src]) { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +.img-alt { + margin: -10px 0 10px; + color: #858585; +} +.img-alt:hover { + text-decoration: none !important; +} +blockquote { + margin: 0 0 20px; + padding: 12px 15px; + border-left: 3px solid #49b1f5; + background-color: var(--blockquote-bg); + color: var(--blockquote-color); +} +blockquote footer cite:before { + padding: 0 5px; + content: '—'; +} +blockquote > :last-child { + margin-bottom: 0 !important; +} +:root { + --hl-color: #90a4ae; + --hl-bg: #f6f8fa; + --hltools-bg: #e6ebf1; + --hltools-color: #90a4ae; + --hlnumber-bg: #f6f8fa; + --hlnumber-color: rgba(144,164,174,0.5); + --hlscrollbar-bg: #dce4eb; + --hlexpand-bg: linear-gradient(180deg, rgba(246,248,250,0.6), rgba(246,248,250,0.9)); +} +figure.highlight table { + scrollbar-color: var(--hlscrollbar-bg) transparent; +} +figure.highlight table::-webkit-scrollbar-thumb { + background: var(--hlscrollbar-bg); +} +figure.highlight pre .deletion { + color: #bf42bf; +} +figure.highlight pre .addition { + color: #105ede; +} +figure.highlight pre .meta { + color: #7c4dff; +} +figure.highlight pre .comment { + color: rgba(149,165,166,0.8); +} +figure.highlight pre .variable, +figure.highlight pre .attribute, +figure.highlight pre .regexp, +figure.highlight pre .ruby .constant, +figure.highlight pre .xml .tag .title, +figure.highlight pre .xml .pi, +figure.highlight pre .xml .doctype, +figure.highlight pre .html .doctype, +figure.highlight pre .css .id, +figure.highlight pre .tag .name, +figure.highlight pre .css .class, +figure.highlight pre .css .pseudo { + color: #e53935; +} +figure.highlight pre .tag { + color: #39adb5; +} +figure.highlight pre .number, +figure.highlight pre .preprocessor, +figure.highlight pre .literal, +figure.highlight pre .params, +figure.highlight pre .constant, +figure.highlight pre .command { + color: #f76d47; +} +figure.highlight pre .built_in { + color: #ffb62c; +} +figure.highlight pre .ruby .class .title, +figure.highlight pre .css .rules .attribute, +figure.highlight pre .string, +figure.highlight pre .value, +figure.highlight pre .inheritance, +figure.highlight pre .header, +figure.highlight pre .ruby .symbol, +figure.highlight pre .xml .cdata, +figure.highlight pre .special, +figure.highlight pre .number, +figure.highlight pre .formula { + color: #91b859; +} +figure.highlight pre .keyword, +figure.highlight pre .title, +figure.highlight pre .css .hexcolor { + color: #39adb5; +} +figure.highlight pre .function, +figure.highlight pre .python .decorator, +figure.highlight pre .python .title, +figure.highlight pre .ruby .function .title, +figure.highlight pre .ruby .title .keyword, +figure.highlight pre .perl .sub, +figure.highlight pre .javascript .title, +figure.highlight pre .coffeescript .title { + color: #6182b8; +} +figure.highlight pre .tag .attr, +figure.highlight pre .javascript .function { + color: #7c4dff; +} +#article-container figure.highlight .line.marked { + background-color: rgba(128,203,196,0.251); +} +#article-container figure.highlight table { + display: block; + overflow: auto; + border: none; +} +#article-container figure.highlight table td { + padding: 0; + border: none; +} +#article-container figure.highlight .gutter pre { + padding-right: 10px; + padding-left: 10px; + background-color: var(--hlnumber-bg); + color: var(--hlnumber-color); + text-align: right; +} +#article-container figure.highlight .code pre { + padding-right: 10px; + padding-left: 10px; + width: 100%; +} +#article-container pre, +#article-container figure.highlight { + overflow: auto; + margin: 0 0 20px; + padding: 0; + background: var(--hl-bg); + color: var(--hl-color); + line-height: 1.6; +} +#article-container pre, +#article-container code { + font-size: var(--global-font-size); + font-family: consolas, Menlo, 'PingFang SC', 'Microsoft YaHei', sans-serif !important; +} +#article-container code { + padding: 2px 4px; + background: rgba(27,31,35,0.05); + color: #f47466; +} +#article-container pre { + padding: 10px 20px; +} +#article-container pre code { + padding: 0; + background: none; + color: var(--hl-color); + text-shadow: none; +} +#article-container figure.highlight { + position: relative; +} +#article-container figure.highlight pre { + margin: 0; + padding: 8px 0; + border: none; +} +#article-container figure.highlight figcaption, +#article-container figure.highlight .caption { + padding: 6px 0 2px 14px; + font-size: var(--global-font-size); + line-height: 1em; +} +#article-container figure.highlight figcaption a, +#article-container figure.highlight .caption a { + float: right; + padding-right: 10px; + color: var(--hl-color); +} +#article-container figure.highlight figcaption a:hover, +#article-container figure.highlight .caption a:hover { + border-bottom-color: var(--hl-color); +} +#article-container figure.highlight.copy-true { + -webkit-user-select: all; + -moz-user-select: all; + -ms-user-select: all; + user-select: all; +} +#article-container figure.highlight.copy-true > table, +#article-container figure.highlight.copy-true > pre { + display: block !important; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +#article-container .highlight-tools { + position: relative; + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-align: center; + -moz-box-align: center; + -o-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + overflow: hidden; + min-height: 24px; + height: 2.15em; + background: var(--hltools-bg); + color: var(--hltools-color); + font-size: var(--global-font-size); +} +#article-container .highlight-tools.closed ~ * { + display: none; +} +#article-container .highlight-tools .expand { + position: absolute; + padding: 0.57em 0.7em; + cursor: pointer; + -webkit-transition: -webkit-transform 0.3s; + -moz-transition: -moz-transform 0.3s; + -o-transition: -o-transform 0.3s; + -ms-transition: -ms-transform 0.3s; + transition: transform 0.3s; +} +#article-container .highlight-tools .expand + .code-lang { + left: 1.7em; +} +#article-container .highlight-tools .expand.closed { + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; + -webkit-transform: rotate(-90deg) !important; + -moz-transform: rotate(-90deg) !important; + -o-transform: rotate(-90deg) !important; + -ms-transform: rotate(-90deg) !important; + transform: rotate(-90deg) !important; +} +#article-container .highlight-tools .code-lang { + position: absolute; + left: 14px; + text-transform: uppercase; + font-weight: bold; + font-size: 1.15em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +#article-container .highlight-tools .copy-notice { + position: absolute; + right: 2.4em; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transition: opacity 0.4s; + -moz-transition: opacity 0.4s; + -o-transition: opacity 0.4s; + -ms-transition: opacity 0.4s; + transition: opacity 0.4s; +} +#article-container .highlight-tools .copy-button { + position: absolute; + right: 14px; + cursor: pointer; + -webkit-transition: color 0.2s; + -moz-transition: color 0.2s; + -o-transition: color 0.2s; + -ms-transition: color 0.2s; + transition: color 0.2s; +} +#article-container .highlight-tools .copy-button:hover { + color: #008080; +} +#article-container .gutter { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +#article-container .gist table { + width: auto; +} +#article-container .gist table td { + border: none; +} +#article-container figure.highlight { + margin: 0 0 24px; + border-radius: 7px; + -webkit-box-shadow: 0 5px 10px 0 rgba(144,164,174,0.4); + box-shadow: 0 5px 10px 0 rgba(144,164,174,0.4); + -webkit-transform: translateZ(0); +} +#article-container figure.highlight .highlight-tools:after { + position: absolute; + left: 14px; + width: 12px; + height: 12px; + border-radius: 50%; + background: #fc625d; + -webkit-box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b; + box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b; + content: ' '; +} +#article-container figure.highlight .highlight-tools .expand { + right: 0; +} +#article-container figure.highlight .highlight-tools .expand.closed { + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; + -webkit-transform: rotate(90deg) !important; + -moz-transform: rotate(90deg) !important; + -o-transform: rotate(90deg) !important; + -ms-transform: rotate(90deg) !important; + transform: rotate(90deg) !important; +} +#article-container figure.highlight .highlight-tools .expand ~ .copy-notice { + right: 3.45em; +} +#article-container figure.highlight .highlight-tools .expand ~ .copy-button { + right: 2.1em; +} +#article-container figure.highlight .highlight-tools .code-lang { + left: 75px; +} +#article-container .code-expand-btn { + position: absolute; + bottom: 0; + z-index: 10; + width: 100%; + background: var(--hlexpand-bg); + text-align: center; + font-size: var(--global-font-size); + cursor: pointer; +} +#article-container .code-expand-btn i { + padding: 6px 0; + color: var(--hlnumber-color); + -webkit-animation: code-expand-key 1.2s infinite; + -moz-animation: code-expand-key 1.2s infinite; + -o-animation: code-expand-key 1.2s infinite; + -ms-animation: code-expand-key 1.2s infinite; + animation: code-expand-key 1.2s infinite; +} +#article-container .code-expand-btn.expand-done > i { + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -o-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +#article-container .code-expand-btn.expand-done + table, +#article-container .code-expand-btn.expand-done + pre { + margin-bottom: 1.8em; +} +#article-container .code-expand-btn:not(.expand-done) ~ table, +#article-container .code-expand-btn:not(.expand-done) ~ pre { + overflow: hidden; + height: 400px; +} +@-moz-keyframes code-expand-key { + 0% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } + 50% { + opacity: 0.1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)"; + filter: alpha(opacity=10); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +@-webkit-keyframes code-expand-key { + 0% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } + 50% { + opacity: 0.1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)"; + filter: alpha(opacity=10); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +@-o-keyframes code-expand-key { + 0% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } + 50% { + opacity: 0.1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)"; + filter: alpha(opacity=10); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +@keyframes code-expand-key { + 0% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } + 50% { + opacity: 0.1; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)"; + filter: alpha(opacity=10); + } + 100% { + opacity: 0.6; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=60)"; + filter: alpha(opacity=60); + } +} +.error404 #error-wrap { + position: absolute; + top: 50%; + right: 0; + left: 0; + margin: 0 auto; + padding: 60px 20px 0; + max-width: 1000px; + -webkit-transform: translate(0, -50%); + -moz-transform: translate(0, -50%); + -o-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); +} +.error404 #error-wrap .error-content { + overflow: hidden; + margin: 0 20px; + height: 360px; +} +@media screen and (max-width: 768px) { + .error404 #error-wrap .error-content { + margin: 0; + height: 500px; + } +} +.error404 #error-wrap .error-content .error-img { + display: inline-block; + overflow: hidden; + width: 50%; + height: 100%; +} +@media screen and (max-width: 768px) { + .error404 #error-wrap .error-content .error-img { + width: 100%; + height: 45%; + } +} +.error404 #error-wrap .error-content .error-img img { + background-color: #008080; +} +.error404 #error-wrap .error-content .error-info { + display: -webkit-inline-box; + display: -moz-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-box; + display: inline-flex; + -webkit-box-orient: vertical; + -moz-box-orient: vertical; + -o-box-orient: vertical; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-pack: center; + -moz-box-pack: center; + -o-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + -ms-flex-line-pack: center; + -webkit-align-content: center; + align-content: center; + width: 50%; + height: 100%; + vertical-align: top; + text-align: center; +} +@media screen and (max-width: 768px) { + .error404 #error-wrap .error-content .error-info { + width: 100%; + height: 55%; + } +} +.error404 #error-wrap .error-content .error-info .error_title { + margin-top: -0.6em; + font-size: 9em; +} +@media screen and (max-width: 768px) { + .error404 #error-wrap .error-content .error-info .error_title { + font-size: 8em; + } +} +.error404 #error-wrap .error-content .error-info .error_subtitle { + margin-top: -3em; + word-break: break-word; + font-size: 1.6em; + -webkit-line-clamp: 2; +} +.error404 + #rightside { + display: none; +} +.article-sort { + margin-left: 10px; + padding-left: 20px; + border-left: 2px solid #00e6e6; +} +.article-sort-title { + position: relative; + margin-left: 10px; + padding-bottom: 20px; + padding-left: 20px; + font-size: 1.72em; +} +.article-sort-title:hover:before { + border-color: var(--pseudo-hover); +} +.article-sort-title:before { + position: absolute; + top: calc(((100% - 36px) / 2)); + left: -9px; + z-index: 1; + width: 10px; + height: 10px; + border: 5px solid #008080; + border-radius: 10px; + background: var(--card-bg); + content: ''; + line-height: 10px; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + -ms-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.article-sort-title:after { + position: absolute; + bottom: 0; + left: 0; + z-index: 0; + width: 2px; + height: 1.5em; + background: #00e6e6; + content: ''; +} +.article-sort-item { + position: relative; + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-align: center; + -moz-box-align: center; + -o-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + margin: 0 0 20px 10px; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + -ms-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.article-sort-item:hover:before { + border-color: var(--pseudo-hover); +} +.article-sort-item:before { + position: absolute; + left: calc(-20px - 17px); + width: 6px; + height: 6px; + border: 3px solid #008080; + border-radius: 6px; + background: var(--card-bg); + content: ''; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + -ms-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +.article-sort-item.no-article-cover { + height: 80px; +} +.article-sort-item.no-article-cover .article-sort-item-info { + padding: 0; +} +.article-sort-item.year { + font-size: 1.43em; +} +.article-sort-item.year:hover:before { + border-color: #008080; +} +.article-sort-item.year:before { + border-color: var(--pseudo-hover); +} +.article-sort-item-time { + color: #858585; + font-size: 95%; +} +.article-sort-item-time time { + padding-left: 6px; + cursor: default; +} +.article-sort-item-title { + color: var(--font-color); + font-size: 1.1em; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; + -webkit-line-clamp: 2; +} +.article-sort-item-title:hover { + color: #008080; + -webkit-transform: translateX(10px); + -moz-transform: translateX(10px); + -o-transform: translateX(10px); + -ms-transform: translateX(10px); + transform: translateX(10px); +} +.article-sort-item-img { + overflow: hidden; + width: 80px; + height: 80px; +} +.article-sort-item-info { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -o-box-flex: 1; + box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + padding: 0 16px; +} +.category-lists .category-title { + font-size: 2.57em; +} +@media screen and (max-width: 768px) { + .category-lists .category-title { + font-size: 2em; + } +} +.category-lists .category-list { + margin-bottom: 0; +} +.category-lists .category-list a { + color: var(--font-color); +} +.category-lists .category-list a:hover { + color: #008080; +} +.category-lists .category-list .category-list-count { + margin-left: 8px; + color: #858585; +} +.category-lists .category-list .category-list-count:before { + content: '('; +} +.category-lists .category-list .category-list-count:after { + content: ')'; +} +.category-lists ul { + padding: 0 0 0 20px; +} +.category-lists ul ul { + padding-left: 4px; +} +.category-lists ul li { + position: relative; + margin: 6px 0; + padding: 0.12em 0.4em 0.12em 1.4em; +} +#body-wrap { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-orient: vertical; + -moz-box-orient: vertical; + -o-box-orient: vertical; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + min-height: 100vh; +} +.layout { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-flex: 1; + -moz-box-flex: 1; + -o-box-flex: 1; + box-flex: 1; + -webkit-flex: 1 auto; + -ms-flex: 1 auto; + flex: 1 auto; + margin: 0 auto; + padding: 40px 15px; + max-width: 1200px; + width: 100%; +} +@media screen and (max-width: 900px) { + .layout { + -webkit-box-orient: vertical; + -moz-box-orient: vertical; + -o-box-orient: vertical; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + } +} +@media screen and (max-width: 768px) { + .layout { + padding: 20px 5px; + } +} +@media screen and (min-width: 2000px) { + .layout { + max-width: 70%; + } +} +.layout > div:first-child:not(.recent-posts) { + -webkit-align-self: flex-start; + align-self: flex-start; + -ms-flex-item-align: start; + padding: 50px 40px; +} +@media screen and (max-width: 768px) { + .layout > div:first-child:not(.recent-posts) { + padding: 36px 14px; + } +} +.layout > div:first-child { + width: 74%; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; +} +@media screen and (max-width: 900px) { + .layout > div:first-child { + width: 100% !important; + } +} +.layout.hide-aside { + max-width: 1000px; +} +@media screen and (min-width: 2000px) { + .layout.hide-aside { + max-width: 1300px; + } +} +.layout.hide-aside > div { + width: 100% !important; +} +.apple #page-header.full_page { + background-attachment: scroll !important; +} +.apple .recent-post-item, +.apple .avatar-img, +.apple .flink-item-icon { + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -o-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); +} +#article-container .flink { + margin-bottom: 20px; +} +#article-container .flink .flink-list { + overflow: auto; + padding: 10px 10px 0; + text-align: center; +} +#article-container .flink .flink-list > .flink-list-item { + position: relative; + float: left; + overflow: hidden; + margin: 15px 7px; + width: calc(100% / 3 - 15px); + height: 90px; + border-radius: 8px; + line-height: 17px; + -webkit-transform: translateZ(0); +} +@media screen and (max-width: 1024px) { + #article-container .flink .flink-list > .flink-list-item { + width: calc(50% - 15px) !important; + } +} +@media screen and (max-width: 600px) { + #article-container .flink .flink-list > .flink-list-item { + width: calc(100% - 15px) !important; + } +} +#article-container .flink .flink-list > .flink-list-item:hover .flink-item-icon { + margin-left: -10px; + width: 0; +} +#article-container .flink .flink-list > .flink-list-item:before { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -1; + background: var(--text-bg-hover); + content: ''; + -webkit-transition: -webkit-transform 0.3s ease-out; + -moz-transition: -moz-transform 0.3s ease-out; + -o-transition: -o-transform 0.3s ease-out; + -ms-transition: -ms-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + -webkit-transform: scale(0); + -moz-transform: scale(0); + -o-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); +} +#article-container .flink .flink-list > .flink-list-item:hover:before, +#article-container .flink .flink-list > .flink-list-item:focus:before, +#article-container .flink .flink-list > .flink-list-item:active:before { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); +} +#article-container .flink .flink-list > .flink-list-item a { + color: var(--font-color); + text-decoration: none; +} +#article-container .flink .flink-list > .flink-list-item a .flink-item-icon { + float: left; + overflow: hidden; + margin: 15px 10px; + width: 60px; + height: 60px; + border-radius: 35px; + -webkit-transition: width 0.3s ease-out; + -moz-transition: width 0.3s ease-out; + -o-transition: width 0.3s ease-out; + -ms-transition: width 0.3s ease-out; + transition: width 0.3s ease-out; +} +#article-container .flink .flink-list > .flink-list-item a .flink-item-icon img { + width: 100%; + height: 100%; + -webkit-transition: filter 375ms ease-in 0.2s, -webkit-transform 0.3s; + -moz-transition: filter 375ms ease-in 0.2s, -moz-transform 0.3s; + -o-transition: filter 375ms ease-in 0.2s, -o-transform 0.3s; + -ms-transition: filter 375ms ease-in 0.2s, -ms-transform 0.3s; + transition: filter 375ms ease-in 0.2s, transform 0.3s; + object-fit: cover; +} +#article-container .flink .flink-list > .flink-list-item a .img-alt { + display: none; +} +#article-container .flink .flink-item-name { + padding: 16px 10px 0 0; + height: 40px; + font-weight: bold; + font-size: 1.43em; +} +#article-container .flink .flink-item-desc { + padding: 16px 10px 16px 0; + height: 50px; + font-size: 0.93em; +} +#article-container .flink .flink-name { + margin-bottom: 5px; + font-weight: bold; + font-size: 1.5em; +} +#recent-posts > .recent-post-item:not(:first-child) { + margin-top: 20px; +} +#recent-posts > .recent-post-item { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-orient: horizontal; + -moz-box-orient: horizontal; + -o-box-orient: horizontal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-align: center; + -moz-box-align: center; + -o-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + overflow: hidden; + height: 16.8em; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item { + -webkit-box-orient: vertical; + -moz-box-orient: vertical; + -o-box-orient: vertical; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + height: auto; + } +} +#recent-posts > .recent-post-item:hover img.post-bg { + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -o-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); +} +#recent-posts > .recent-post-item.ads-wrap { + display: block !important; + height: auto !important; +} +#recent-posts > .recent-post-item .post_cover { + overflow: hidden; + width: 42%; + height: 100%; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item .post_cover { + width: 100%; + height: 230px; + } +} +#recent-posts > .recent-post-item .post_cover.right { + -webkit-box-ordinal-group: 1; + -moz-box-ordinal-group: 1; + -o-box-ordinal-group: 1; + -ms-flex-order: 1; + -webkit-order: 1; + order: 1; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item .post_cover.right { + -webkit-box-ordinal-group: 0; + -moz-box-ordinal-group: 0; + -o-box-ordinal-group: 0; + -ms-flex-order: 0; + -webkit-order: 0; + order: 0; + } +} +#recent-posts > .recent-post-item >.recent-post-info { + padding: 0 40px; + width: 58%; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item >.recent-post-info { + padding: 20px 20px 30px; + width: 100%; + } +} +#recent-posts > .recent-post-item >.recent-post-info.no-cover { + width: 100%; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item >.recent-post-info.no-cover { + padding: 30px 20px; + } +} +#recent-posts > .recent-post-item >.recent-post-info > .article-title { + color: var(--text-highlight-color); + font-size: 1.55em; + line-height: 1.4; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + -ms-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; + -webkit-line-clamp: 2; +} +@media screen and (max-width: 768px) { + #recent-posts > .recent-post-item >.recent-post-info > .article-title { + font-size: 1.43em; + } +} +#recent-posts > .recent-post-item >.recent-post-info > .article-title:hover { + color: #008080; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap { + margin: 6px 0; + color: #858585; + font-size: 0.9em; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap > .post-meta-date { + cursor: default; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .sticky { + color: #ff7242; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap i { + margin: 0 4px 0 0; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .fa-spinner { + margin: 0; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .article-meta-label { + padding-right: 4px; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .article-meta-separator { + margin: 0 6px; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap .article-meta-link { + margin: 0 4px; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap time { + display: none; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap a { + color: #858585; +} +#recent-posts > .recent-post-item >.recent-post-info > .article-meta-wrap a:hover { + color: #008080; + text-decoration: underline; +} +#recent-posts > .recent-post-item >.recent-post-info > .content { + -webkit-line-clamp: 2; +} +.tag-cloud-list a { + display: inline-block; + padding: 0 8px; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; +} +.tag-cloud-list a:hover { + color: #008080 !important; + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -o-transform: scale(1.1); + -ms-transform: scale(1.1); + transform: scale(1.1); +} +@media screen and (max-width: 768px) { + .tag-cloud-list a { + zoom: 0.85; + } +} +.tag-cloud-title { + font-size: 2.57em; +} +@media screen and (max-width: 768px) { + .tag-cloud-title { + font-size: 2em; + } +} +h1.page-title + .tag-cloud-list { + text-align: left; +} +#aside-content { + width: 26%; +} +@media screen and (min-width: 900px) { + #aside-content { + padding-left: 15px; + } +} +@media screen and (max-width: 900px) { + #aside-content { + width: 100%; + } +} +#aside-content > .card-widget:first-child { + margin-top: 0; +} +@media screen and (max-width: 900px) { + #aside-content > .card-widget:first-child { + margin-top: 20px; + } +} +#aside-content .card-widget { + position: relative; + overflow: hidden; + margin-top: 20px; + padding: 20px 24px; +} +#aside-content .card-info .author-info__name { + font-weight: 500; + font-size: 1.57em; +} +#aside-content .card-info .author-info__description { + margin-top: -0.42em; +} +#aside-content .card-info .card-info-data { + margin: 14px 0 4px; +} +#aside-content .card-info .card-info-social-icons { + margin: 6px 0 -6px; +} +#aside-content .card-info .card-info-social-icons .social-icon { + margin: 0 10px; + color: var(--font-color); + font-size: 1.4em; +} +#aside-content .card-info .card-info-social-icons i { + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; +} +#aside-content .card-info .card-info-social-icons i:hover { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); +} +#aside-content .card-info #card-info-btn { + display: block; + margin-top: 14px; + background-color: var(--btn-bg); + color: var(--btn-color); + text-align: center; + line-height: 2.4; +} +#aside-content .card-info #card-info-btn:hover { + background-color: var(--btn-hover-color); +} +#aside-content .card-info #card-info-btn span { + padding-left: 10px; +} +#aside-content .item-headline { + padding-bottom: 6px; + font-size: 1.2em; +} +#aside-content .item-headline span { + margin-left: 6px; +} +@media screen and (min-width: 900px) { + #aside-content .sticky_layout { + position: sticky; + position: -webkit-sticky; + top: 20px; + -webkit-transition: top 0.3s; + -moz-transition: top 0.3s; + -o-transition: top 0.3s; + -ms-transition: top 0.3s; + transition: top 0.3s; + } +} +#aside-content .card-tag-cloud a { + display: inline-block; + padding: 0 4px; +} +#aside-content .card-tag-cloud a:hover { + color: #008080 !important; +} +#aside-content .aside-list > span { + display: block; + margin-bottom: 10px; + text-align: center; +} +#aside-content .aside-list > .aside-list-item { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-align: center; + -moz-box-align: center; + -o-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + padding: 6px 0; +} +#aside-content .aside-list > .aside-list-item:first-child { + padding-top: 0; +} +#aside-content .aside-list > .aside-list-item:not(:last-child) { + border-bottom: 1px dashed #f5f5f5; +} +#aside-content .aside-list > .aside-list-item:last-child { + padding-bottom: 0; +} +#aside-content .aside-list > .aside-list-item .thumbnail { + overflow: hidden; + width: 4.2em; + height: 4.2em; +} +#aside-content .aside-list > .aside-list-item .content { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -o-box-flex: 1; + box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + padding-left: 10px; + word-break: break-all; +} +#aside-content .aside-list > .aside-list-item .content > .name { + -webkit-line-clamp: 1; +} +#aside-content .aside-list > .aside-list-item .content > time, +#aside-content .aside-list > .aside-list-item .content > .name { + display: block; + color: #858585; + font-size: 85%; +} +#aside-content .aside-list > .aside-list-item .content > .title, +#aside-content .aside-list > .aside-list-item .content > .comment { + color: var(--font-color); + font-size: 95%; + line-height: 1.5; + -webkit-line-clamp: 2; +} +#aside-content .aside-list > .aside-list-item .content > .title:hover, +#aside-content .aside-list > .aside-list-item .content > .comment:hover { + color: #008080; +} +#aside-content .aside-list > .aside-list-item.no-cover { + min-height: 4.4em; +} +#aside-content .card-archives ul.card-archive-list, +#aside-content .card-categories ul.card-category-list { + margin: 0; + padding: 0; + list-style: none; +} +#aside-content .card-archives ul.card-archive-list > .card-archive-list-item a, +#aside-content .card-categories ul.card-category-list > .card-category-list-item a { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-orient: horizontal; + -moz-box-orient: horizontal; + -o-box-orient: horizontal; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + padding: 3px 10px; + color: var(--font-color); + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -o-transition: all 0.4s; + -ms-transition: all 0.4s; + transition: all 0.4s; +} +#aside-content .card-archives ul.card-archive-list > .card-archive-list-item a:hover, +#aside-content .card-categories ul.card-category-list > .card-category-list-item a:hover { + padding: 3px 17px; + background-color: var(--text-bg-hover); +} +#aside-content .card-archives ul.card-archive-list > .card-archive-list-item a span:first-child, +#aside-content .card-categories ul.card-category-list > .card-category-list-item a span:first-child { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -o-box-flex: 1; + box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +#aside-content .card-categories .card-category-list.child { + padding: 0 0 0 16px; +} +#aside-content .card-categories .card-category-list > .parent > a .card-category-list-name { + width: 70% !important; +} +#aside-content .card-categories .card-category-list > .parent > a .card-category-list-count { + width: calc(100% - 70% - 20px); + text-align: right; +} +#aside-content .card-categories .card-category-list > .parent i { + float: right; + margin-right: -0.5em; + padding: 0.5em; + -webkit-transition: -webkit-transform 0.3s; + -moz-transition: -moz-transform 0.3s; + -o-transition: -o-transform 0.3s; + -ms-transition: -ms-transform 0.3s; + transition: transform 0.3s; + -webkit-transform: rotate(0); + -moz-transform: rotate(0); + -o-transform: rotate(0); + -ms-transform: rotate(0); + transform: rotate(0); +} +#aside-content .card-categories .card-category-list > .parent i.expand { + -webkit-transform: rotate(-90deg); + -moz-transform: rotate(-90deg); + -o-transform: rotate(-90deg); + -ms-transform: rotate(-90deg); + transform: rotate(-90deg); +} +#aside-content .card-webinfo .webinfo .webinfo-item { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-align: center; + -moz-box-align: center; + -o-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + padding: 2px 10px 0; +} +#aside-content .card-webinfo .webinfo .webinfo-item div:first-child { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -o-box-flex: 1; + box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + padding-right: 20px; +} +@media screen and (min-width: 901px) { + #aside-content #card-toc { + right: 0 !important; + } +} +@media screen and (max-width: 900px) { + #aside-content #card-toc { + position: fixed; + right: -100%; + bottom: 30px; + z-index: 100; + max-width: 380px; + max-height: calc(100% - 60px); + width: calc(100% - 80px); + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transition: initial; + -moz-transition: initial; + -o-transition: initial; + -ms-transition: initial; + transition: initial; + -webkit-transform-origin: right bottom; + -moz-transform-origin: right bottom; + -o-transform-origin: right bottom; + -ms-transform-origin: right bottom; + transform-origin: right bottom; + } +} +#aside-content #card-toc .toc-percentage { + float: right; + margin-top: -9px; + color: #a9a9a9; + font-style: italic; + font-size: 140%; +} +#aside-content #card-toc .toc-content { + overflow-y: scroll; + overflow-y: overlay; + margin: 0 -24px; + max-height: calc(100vh - 120px); +} +@media screen and (max-width: 900px) { + #aside-content #card-toc .toc-content { + max-height: calc(100vh - 140px); + } +} +#aside-content #card-toc .toc-content > * { + margin: 0 20px !important; +} +#aside-content #card-toc .toc-content > * > .toc-item > .toc-child { + margin-left: 10px; + padding-left: 10px; + border-left: 1px solid var(--dark-grey); +} +#aside-content #card-toc .toc-content:not(.is-expand) .toc-child { + display: none; +} +@media screen and (max-width: 900px) { + #aside-content #card-toc .toc-content:not(.is-expand) .toc-child { + display: block !important; + } +} +#aside-content #card-toc .toc-content:not(.is-expand) .toc-item.active .toc-child { + display: block; +} +#aside-content #card-toc .toc-content ol, +#aside-content #card-toc .toc-content li { + list-style: none; +} +#aside-content #card-toc .toc-content > ol { + padding: 0 !important; +} +#aside-content #card-toc .toc-content ol { + margin: 0; + padding-left: 18px; +} +#aside-content #card-toc .toc-content .toc-link { + display: block; + margin: 4px 0; + padding: 1px 6px; + color: var(--toc-link-color); + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + -ms-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +#aside-content #card-toc .toc-content .toc-link:hover { + color: #008080; +} +#aside-content #card-toc .toc-content .toc-link.active { + background: #00c4b6; + color: #fff; +} +#aside-content .sticky_layout:only-child > :first-child { + margin-top: 0; +} +#aside-content .card-more-btn { + float: right; + color: inherit; +} +#aside-content .card-more-btn:hover { + -webkit-animation: more-btn-move 1s infinite; + -moz-animation: more-btn-move 1s infinite; + -o-animation: more-btn-move 1s infinite; + -ms-animation: more-btn-move 1s infinite; + animation: more-btn-move 1s infinite; +} +#aside-content .card-announcement .item-headline i { + color: #f00; +} +.avatar-img { + overflow: hidden; + margin: 0 auto; + width: 110px; + height: 110px; + border-radius: 70px; +} +.avatar-img img { + width: 100%; + height: 100%; + -webkit-transition: filter 375ms ease-in 0.2s, -webkit-transform 0.3s; + -moz-transition: filter 375ms ease-in 0.2s, -moz-transform 0.3s; + -o-transition: filter 375ms ease-in 0.2s, -o-transform 0.3s; + -ms-transition: filter 375ms ease-in 0.2s, -ms-transform 0.3s; + transition: filter 375ms ease-in 0.2s, transform 0.3s; + object-fit: cover; +} +.avatar-img img:hover { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); +} +.site-data { + display: table; + width: 100%; + table-layout: fixed; +} +.site-data > a { + display: table-cell; +} +.site-data > a div { + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; +} +.site-data > a:hover div { + color: #008080 !important; +} +.site-data > a .headline { + color: var(--font-color); +} +.site-data > a .length-num { + margin-top: -0.32em; + color: var(--text-highlight-color); + font-size: 1.4em; +} +@media screen and (min-width: 900px) { + html.hide-aside .layout { + -webkit-box-pack: center; + -moz-box-pack: center; + -o-box-pack: center; + -ms-flex-pack: center; + -webkit-justify-content: center; + justify-content: center; + } + html.hide-aside .layout > .aside-content { + display: none; + } + html.hide-aside .layout > div:first-child { + width: 80%; + } +} +.page .sticky_layout { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-orient: vertical; + -moz-box-orient: vertical; + -o-box-orient: vertical; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} +@-moz-keyframes more-btn-move { + 0%, 100% { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + -o-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + 50% { + -webkit-transform: translateX(3px); + -moz-transform: translateX(3px); + -o-transform: translateX(3px); + -ms-transform: translateX(3px); + transform: translateX(3px); + } +} +@-webkit-keyframes more-btn-move { + 0%, 100% { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + -o-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + 50% { + -webkit-transform: translateX(3px); + -moz-transform: translateX(3px); + -o-transform: translateX(3px); + -ms-transform: translateX(3px); + transform: translateX(3px); + } +} +@-o-keyframes more-btn-move { + 0%, 100% { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + -o-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + 50% { + -webkit-transform: translateX(3px); + -moz-transform: translateX(3px); + -o-transform: translateX(3px); + -ms-transform: translateX(3px); + transform: translateX(3px); + } +} +@keyframes more-btn-move { + 0%, 100% { + -webkit-transform: translateX(0); + -moz-transform: translateX(0); + -o-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + 50% { + -webkit-transform: translateX(3px); + -moz-transform: translateX(3px); + -o-transform: translateX(3px); + -ms-transform: translateX(3px); + transform: translateX(3px); + } +} +@-moz-keyframes toc-open { + 0% { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } + 100% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} +@-webkit-keyframes toc-open { + 0% { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } + 100% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} +@-o-keyframes toc-open { + 0% { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } + 100% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} +@keyframes toc-open { + 0% { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } + 100% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } +} +@-moz-keyframes toc-close { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + 100% { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } +} +@-webkit-keyframes toc-close { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + 100% { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } +} +@-o-keyframes toc-close { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + 100% { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } +} +@keyframes toc-close { + 0% { + -webkit-transform: scale(1); + -moz-transform: scale(1); + -o-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + } + 100% { + -webkit-transform: scale(0.7); + -moz-transform: scale(0.7); + -o-transform: scale(0.7); + -ms-transform: scale(0.7); + transform: scale(0.7); + } +} +#post-comment .comment-head { + margin-bottom: 20px; +} +#post-comment .comment-head .comment-headline { + display: inline-block; + vertical-align: middle; + font-weight: 700; + font-size: 1.43em; +} +#post-comment .comment-head #comment-switch { + display: inline-block; + float: right; + margin: 2px auto 0; + padding: 4px 16px; + width: max-content; + border-radius: 8px; + background: #f6f8fa; +} +#post-comment .comment-head #comment-switch .first-comment { + color: #49b1f5; +} +#post-comment .comment-head #comment-switch .second-comment { + color: #ff7242; +} +#post-comment .comment-head #comment-switch .switch-btn { + position: relative; + display: inline-block; + margin: -4px 8px 0; + width: 42px; + height: 22px; + border-radius: 34px; + background-color: #49b1f5; + vertical-align: middle; + cursor: pointer; + -webkit-transition: 0.4s; + -moz-transition: 0.4s; + -o-transition: 0.4s; + -ms-transition: 0.4s; + transition: 0.4s; +} +#post-comment .comment-head #comment-switch .switch-btn:before { + position: absolute; + bottom: 4px; + left: 4px; + width: 14px; + height: 14px; + border-radius: 50%; + background-color: #fff; + content: ''; + -webkit-transition: 0.4s; + -moz-transition: 0.4s; + -o-transition: 0.4s; + -ms-transition: 0.4s; + transition: 0.4s; +} +#post-comment .comment-head #comment-switch .switch-btn.move { + background-color: #ff7242; +} +#post-comment .comment-head #comment-switch .switch-btn.move:before { + -webkit-transform: translateX(20px); + -moz-transform: translateX(20px); + -o-transform: translateX(20px); + -ms-transform: translateX(20px); + transform: translateX(20px); +} +#post-comment .comment-wrap > div:nth-child(2) { + display: none; +} +#footer { + position: relative; + background-color: #008080; + background-attachment: scroll; + background-position: bottom; + background-size: cover; +} +#footer-wrap { + position: relative; + padding: 40px 20px; + color: var(--light-grey); + text-align: center; +} +#footer-wrap a { + color: var(--light-grey); +} +#footer-wrap a:hover { + text-decoration: underline; +} +#footer-wrap .footer-separator { + margin: 0 4px; +} +#footer-wrap .icp-icon { + padding: 0 4px; + max-height: 1.4em; + width: auto; + vertical-align: text-bottom; +} +#page-header { + position: relative; + width: 100%; + background-color: #008080; + background-position: center center; + background-size: cover; + background-repeat: no-repeat; + -webkit-transition: all 0.5s; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -ms-transition: all 0.5s; + transition: all 0.5s; +} +#page-header:not(.not-top-img):before { + position: absolute; + width: 100%; + height: 100%; + background-color: var(--mark-bg); + content: ''; +} +#page-header.full_page { + height: 100vh; + background-attachment: fixed; +} +#page-header.full_page #site-info { + position: absolute; + top: 43%; + padding: 0 10px; + width: 100%; +} +#page-header #site-title, +#page-header #site-subtitle, +#page-header #scroll-down .scroll-down-effects { + text-align: center; + text-shadow: 2px 2px 4px rgba(0,0,0,0.15); + line-height: 1.5; +} +#page-header #site-title { + margin: 0; + color: var(--white); + font-size: 1.85em; +} +@media screen and (min-width: 768px) { + #page-header #site-title { + font-size: 2.85em; + } +} +#page-header #site-subtitle { + color: var(--light-grey); + font-size: 1.15em; +} +@media screen and (min-width: 768px) { + #page-header #site-subtitle { + font-size: 1.72em; + } +} +#page-header #site_social_icons { + display: none; + margin: 0 auto; + width: 300px; + text-align: center; +} +@media screen and (max-width: 768px) { + #page-header #site_social_icons { + display: block; + } +} +#page-header #site_social_icons .social-icon { + margin: 0 10px; + color: var(--light-grey); + text-shadow: 2px 2px 4px rgba(0,0,0,0.15); + font-size: 1.43em; +} +#page-header #scroll-down { + position: absolute; + bottom: 0; + width: 100%; + cursor: pointer; +} +#page-header #scroll-down .scroll-down-effects { + position: relative; + width: 100%; + color: var(--light-grey); + font-size: 30px; +} +#page-header.not-home-page { + height: 400px; +} +@media screen and (max-width: 768px) { + #page-header.not-home-page { + height: 280px; + } +} +#page-header #page-site-info { + position: absolute; + top: 200px; + padding: 0 10px; + width: 100%; +} +@media screen and (max-width: 768px) { + #page-header #page-site-info { + top: 140px; + } +} +#page-header.post-bg { + height: 400px; +} +@media screen and (max-width: 768px) { + #page-header.post-bg { + height: 360px; + } +} +#page-header #post-info { + position: absolute; + bottom: 100px; + padding: 0 8%; + width: 100%; + text-align: center; +} +@media screen and (max-width: 900px) { + #page-header #post-info { + bottom: 30px; + text-align: left; + } +} +@media screen and (max-width: 768px) { + #page-header #post-info { + bottom: 22px; + padding: 0 22px; + } +} +#page-header.not-top-img { + margin-bottom: 10px; + height: 60px; + background: 0; +} +#page-header.not-top-img #nav { + background: rgba(255,255,255,0.8); + -webkit-box-shadow: 0 5px 6px -5px rgba(133,133,133,0.6); + box-shadow: 0 5px 6px -5px rgba(133,133,133,0.6); +} +#page-header.not-top-img #nav a, +#page-header.not-top-img #nav .site-name { + color: var(--font-color); + text-shadow: none; +} +#page-header.nav-fixed #nav { + position: fixed; + top: -60px; + z-index: 91; + background: rgba(255,255,255,0.8); + -webkit-box-shadow: 0 5px 6px -5px rgba(133,133,133,0.6); + box-shadow: 0 5px 6px -5px rgba(133,133,133,0.6); + -webkit-transition: -webkit-transform 0.2s ease-in-out, opacity 0.2s ease-in-out; + -moz-transition: -moz-transform 0.2s ease-in-out, opacity 0.2s ease-in-out; + -o-transition: -o-transform 0.2s ease-in-out, opacity 0.2s ease-in-out; + -ms-transition: -ms-transform 0.2s ease-in-out, opacity 0.2s ease-in-out; + transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out; +} +#page-header.nav-fixed #nav #blog-info { + color: var(--font-color); +} +#page-header.nav-fixed #nav #blog-info:hover { + color: #008080; +} +#page-header.nav-fixed #nav #blog-info .site-name { + text-shadow: none; +} +#page-header.nav-fixed #nav a, +#page-header.nav-fixed #nav #toggle-menu { + color: var(--font-color); + text-shadow: none; +} +#page-header.nav-fixed #nav a:hover, +#page-header.nav-fixed #nav #toggle-menu:hover { + color: #008080; +} +#page-header.nav-fixed.fixed #nav { + top: 0; + -webkit-transition: all 0.5s; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -ms-transition: all 0.5s; + transition: all 0.5s; +} +#page-header.nav-visible:not(.fixed) #nav { + -webkit-transition: all 0.5s; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -ms-transition: all 0.5s; + transition: all 0.5s; + -webkit-transform: translate3d(0, 100%, 0); + -moz-transform: translate3d(0, 100%, 0); + -o-transform: translate3d(0, 100%, 0); + -ms-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); +} +#page-header.nav-visible:not(.fixed) + .layout > .aside-content > .sticky_layout { + top: 70px; + -webkit-transition: top 0.5s; + -moz-transition: top 0.5s; + -o-transition: top 0.5s; + -ms-transition: top 0.5s; + transition: top 0.5s; +} +#page-header.fixed #nav { + position: fixed; +} +#page-header.fixed + .layout > .aside-content > .sticky_layout { + top: 70px; + -webkit-transition: top 0.5s; + -moz-transition: top 0.5s; + -o-transition: top 0.5s; + -ms-transition: top 0.5s; + transition: top 0.5s; +} +#page-header.fixed + .layout #card-toc .toc-content { + max-height: calc(100vh - 170px); +} +#page h1.page-title { + margin: 8px 0 20px; +} +#post > #post-info { + margin-bottom: 30px; +} +#post > #post-info .post-title { + padding-bottom: 4px; + border-bottom: 1px solid var(--light-grey); + color: var(--text-highlight-color); +} +#post > #post-info .post-title .post-edit-link { + float: right; +} +#post > #post-info #post-meta, +#post > #post-info #post-meta a { + color: #78818a; +} +#post-info .post-title { + margin-bottom: 8px; + color: var(--white); + font-weight: normal; + font-size: 2.5em; + line-height: 1.5; + -webkit-line-clamp: 3; +} +@media screen and (max-width: 768px) { + #post-info .post-title { + font-size: 2.1em; + } +} +#post-info .post-title .post-edit-link { + padding-left: 10px; +} +#post-info #post-meta { + color: var(--light-grey); + font-size: 95%; +} +@media screen and (min-width: 768px) { + #post-info #post-meta > .meta-secondline > span:first-child { + display: none; + } +} +@media screen and (max-width: 768px) { + #post-info #post-meta { + font-size: 90%; + } + #post-info #post-meta > .meta-firstline, + #post-info #post-meta > .meta-secondline { + display: inline; + } +} +#post-info #post-meta .post-meta-separator { + margin: 0 5px; +} +#post-info #post-meta .post-meta-icon { + margin-right: 4px; +} +#post-info #post-meta .post-meta-label { + margin-right: 4px; +} +#post-info #post-meta a { + color: var(--light-grey); + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + -ms-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} +#post-info #post-meta a:hover { + color: #008080; + text-decoration: underline; +} +#post-info #post-meta time { + display: none; +} +#nav { + position: absolute; + top: 0; + z-index: 90; + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-align: center; + -moz-box-align: center; + -o-box-align: center; + -ms-flex-align: center; + -webkit-align-items: center; + align-items: center; + padding: 0 36px; + width: 100%; + height: 60px; + font-size: 1.3em; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transition: all 0.5s; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -ms-transition: all 0.5s; + transition: all 0.5s; +} +@media screen and (max-width: 768px) { + #nav { + padding: 0 16px; + } +} +#nav.show { + opacity: 1; + -ms-filter: none; + filter: none; +} +#nav #blog-info { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -o-box-flex: 1; + box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + color: var(--light-grey); +} +#nav #blog-info .site-icon { + margin-right: 6px; + height: 36px; + vertical-align: middle; +} +#nav #toggle-menu { + display: none; + padding: 2px 0 0 6px; + vertical-align: top; +} +#nav #toggle-menu:hover { + color: var(--white); +} +#nav a { + color: var(--light-grey); +} +#nav a:hover { + color: var(--white); +} +#nav .site-name { + text-shadow: 2px 2px 4px rgba(0,0,0,0.15); + font-weight: bold; +} +#nav .menus_items { + display: inline; +} +#nav .menus_items .menus_item { + position: relative; + display: inline-block; + padding: 0 0 0 14px; +} +#nav .menus_items .menus_item:hover .menus_item_child { + display: block; +} +#nav .menus_items .menus_item:hover > a > i:last-child { + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -o-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +#nav .menus_items .menus_item > a > i:last-child { + padding: 4px; + -webkit-transition: -webkit-transform 0.3s; + -moz-transition: -moz-transform 0.3s; + -o-transition: -o-transform 0.3s; + -ms-transition: -ms-transform 0.3s; + transition: transform 0.3s; +} +#nav .menus_items .menus_item .menus_item_child { + position: absolute; + right: 0; + display: none; + margin-top: 8px; + padding: 0; + width: max-content; + border-radius: 5px; + background-color: var(--sidebar-bg); + -webkit-box-shadow: 0 5px 20px -4px rgba(0,0,0,0.5); + box-shadow: 0 5px 20px -4px rgba(0,0,0,0.5); + -webkit-animation: sub_menus 0.3s 0.1s ease both; + -moz-animation: sub_menus 0.3s 0.1s ease both; + -o-animation: sub_menus 0.3s 0.1s ease both; + -ms-animation: sub_menus 0.3s 0.1s ease both; + animation: sub_menus 0.3s 0.1s ease both; +} +#nav .menus_items .menus_item .menus_item_child:before { + position: absolute; + top: -8px; + left: 0; + width: 100%; + height: 20px; + content: ''; +} +#nav .menus_items .menus_item .menus_item_child li { + list-style: none; +} +#nav .menus_items .menus_item .menus_item_child li:hover { + background: var(--text-bg-hover); +} +#nav .menus_items .menus_item .menus_item_child li:first-child { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} +#nav .menus_items .menus_item .menus_item_child li:last-child { + border-bottom-right-radius: 5px; + border-bottom-left-radius: 5px; +} +#nav .menus_items .menus_item .menus_item_child li a { + display: inline-block; + padding: 8px 16px; + width: 100%; + color: var(--font-color) !important; + text-shadow: none !important; +} +#nav.hide-menu #toggle-menu { + display: inline-block !important; +} +#nav.hide-menu #toggle-menu .site-page { + font-size: inherit; +} +#nav.hide-menu .menus_items { + display: none; +} +#nav.hide-menu #search-button span { + display: none; +} +#nav #search-button { + display: inline; + padding: 0 0 0 14px; +} +#nav .site-page { + position: relative; + padding-bottom: 6px; + text-shadow: 1px 1px 2px rgba(0,0,0,0.3); + font-size: 0.78em; + cursor: pointer; +} +#nav .site-page:not(.child):after { + position: absolute; + bottom: 0; + left: 0; + z-index: -1; + width: 0; + height: 3px; + background-color: #00f3f3; + content: ''; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + transition: all 0.3s ease-in-out; +} +#nav .site-page:not(.child):hover:after { + width: 100%; +} +#pagination .pagination { + margin-top: 20px; + text-align: center; +} +#pagination .page-number.current { + background: #00c4b6; + color: var(--white); +} +#pagination .pagination-info { + position: absolute; + top: 50%; + padding: 20px 40px; + width: 100%; + -webkit-transform: translate(0, -50%); + -moz-transform: translate(0, -50%); + -o-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); +} +#pagination .prev_info, +#pagination .next_info { + color: var(--white); + font-weight: 500; +} +#pagination .next-post .pagination-info { + text-align: right; +} +#pagination .pull-full { + width: 100% !important; +} +#pagination .prev-post .label, +#pagination .next-post .label { + color: var(--light-grey); + text-transform: uppercase; + font-size: 90%; +} +#pagination .prev-post, +#pagination .next-post { + width: 50%; +} +@media screen and (max-width: 768px) { + #pagination .prev-post, + #pagination .next-post { + width: 100%; + } +} +#pagination .prev-post a, +#pagination .next-post a { + position: relative; + display: block; + overflow: hidden; + height: 150px; +} +#pagination.pagination-post { + overflow: hidden; + margin-top: 40px; + width: 100%; + background: #000; +} +.layout > .recent-posts .pagination > * { + display: inline-block; + margin: 0 6px; + width: 2.5em; + height: 2.5em; + line-height: 2.5em; +} +.layout > .recent-posts .pagination > *:not(.space):hover { + background: var(--btn-hover-color); + color: var(--btn-color); +} +.layout > div:not(.recent-posts) .pagination .page-number { + display: inline-block; + margin: 0 4px; + min-width: 24px; + height: 24px; + text-align: center; + line-height: 24px; + cursor: pointer; +} +#article-container { + word-wrap: break-word; + overflow-wrap: break-word; +} +#article-container a { + color: #49b1f5; +} +#article-container a:hover { + text-decoration: underline; +} +#article-container img { + display: block; + margin: 0 auto 20px; + max-width: 100%; + -webkit-transition: filter 375ms ease-in 0.2s; + -moz-transition: filter 375ms ease-in 0.2s; + -o-transition: filter 375ms ease-in 0.2s; + -ms-transition: filter 375ms ease-in 0.2s; + transition: filter 375ms ease-in 0.2s; +} +#article-container p { + margin: 0 0 16px; +} +#article-container iframe { + margin: 0 0 20px; +} +#article-container kbd { + margin: 0 3px; + padding: 3px 5px; + border: 1px solid #b4b4b4; + border-radius: 3px; + background-color: #f8f8f8; + -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.25), 0 2px 1px 0 rgba(255,255,255,0.6) inset; + box-shadow: 0 1px 3px rgba(0,0,0,0.25), 0 2px 1px 0 rgba(255,255,255,0.6) inset; + color: #34495e; + white-space: nowrap; + font-weight: 600; + font-size: 0.9em; + font-family: Monaco, 'Ubuntu Mono', monospace; + line-height: 1em; +} +#article-container ol ol, +#article-container ul ol, +#article-container ol ul, +#article-container ul ul { + padding-left: 20px; +} +#article-container ol li, +#article-container ul li { + margin: 4px 0; +} +#article-container ol p, +#article-container ul p { + margin: 0 0 8px; +} +#article-container > :last-child { + margin-bottom: 0 !important; +} +#article-container hr { + margin: 20px 0; +} +#post .tag_share:after { + display: block; + clear: both; + content: ''; +} +#post .tag_share .post-meta__tag-list { + display: inline-block; +} +#post .tag_share .post-meta__tags { + display: inline-block; + margin: 8px 8px 8px 0; + padding: 0 12px; + width: fit-content; + border: 1px solid #008080; + border-radius: 12px; + color: #008080; + font-size: 0.85em; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + -ms-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +#post .tag_share .post-meta__tags:hover { + background: #008080; + color: var(--white); +} +#post .tag_share .post_share { + display: inline-block; + float: right; + margin: 8px 0 0; + width: fit-content; +} +#post .tag_share .post_share .social-share { + font-size: 0.85em; +} +#post .tag_share .post_share .social-share .social-share-icon { + margin: 0 4px; + width: 1.85em; + height: 1.85em; + font-size: 1.2em; + line-height: 1.85em; +} +#post .post-copyright { + position: relative; + margin: 40px 0 10px; + padding: 10px 16px; + border: 1px solid var(--light-grey); + -webkit-transition: box-shadow 0.3s ease-in-out; + -moz-transition: box-shadow 0.3s ease-in-out; + -o-transition: box-shadow 0.3s ease-in-out; + -ms-transition: box-shadow 0.3s ease-in-out; + transition: box-shadow 0.3s ease-in-out; +} +#post .post-copyright:before { + position: absolute; + top: 2px; + right: 12px; + color: #008080; + content: '\f1f9'; + font-size: 1.3em; +} +#post .post-copyright:hover { + -webkit-box-shadow: 0 0 8px 0 rgba(232,237,250,0.6), 0 2px 4px 0 rgba(232,237,250,0.5); + box-shadow: 0 0 8px 0 rgba(232,237,250,0.6), 0 2px 4px 0 rgba(232,237,250,0.5); +} +#post .post-copyright .post-copyright-meta { + color: #008080; + font-weight: bold; +} +#post .post-copyright .post-copyright-info { + padding-left: 6px; +} +#post .post-copyright .post-copyright-info a { + text-decoration: underline; + word-break: break-word; +} +#post .post-copyright .post-copyright-info a:hover { + text-decoration: none; +} +#post .post-outdate-notice { + position: relative; + margin: 0 0 20px; + padding: 0.5em 1.2em; + border-radius: 3px; + background-color: #ffe6e6; + color: #f66; + padding: 0.5em 1em 0.5em 2.6em; + border-left: 5px solid #ff8080; +} +#post .post-outdate-notice:before { + position: absolute; + top: 50%; + left: 0.9em; + color: #ff8080; + content: '\f071'; + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -o-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} +#post .ads-wrap { + margin: 40px 0; +} +.relatedPosts { + margin-top: 40px; +} +.relatedPosts > .headline { + margin-bottom: 5px; + font-weight: 700; + font-size: 1.43em; +} +.relatedPosts > .relatedPosts-list > div { + position: relative; + display: inline-block; + overflow: hidden; + margin: 3px; + width: calc(33.333% - 6px); + height: 200px; + background: #000; + vertical-align: bottom; +} +@media screen and (max-width: 768px) { + .relatedPosts > .relatedPosts-list > div { + margin: 2px; + width: calc(50% - 4px); + height: 150px; + } +} +@media screen and (max-width: 600px) { + .relatedPosts > .relatedPosts-list > div { + width: calc(100% - 4px); + } +} +.relatedPosts > .relatedPosts-list .content { + position: absolute; + top: 50%; + padding: 0 20px; + width: 100%; + -webkit-transform: translate(0, -50%); + -moz-transform: translate(0, -50%); + -o-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); +} +.relatedPosts > .relatedPosts-list .content .date { + color: var(--light-grey); + font-size: 90%; +} +.relatedPosts > .relatedPosts-list .content .title { + color: var(--white); + -webkit-line-clamp: 2; +} +.post-reward { + position: relative; + margin-top: 80px; + width: 100%; + text-align: center; + pointer-events: none; +} +.post-reward > * { + pointer-events: auto; +} +.post-reward .reward-button { + display: inline-block; + padding: 4px 24px; + background: var(--btn-bg); + color: var(--btn-color); + cursor: pointer; +} +.post-reward:hover .reward-button { + background: var(--btn-hover-color); +} +.post-reward:hover > .reward-main { + display: block; +} +.post-reward .reward-main { + position: absolute; + bottom: 40px; + left: 0; + z-index: 100; + display: none; + padding: 0 0 15px; + width: 100%; +} +.post-reward .reward-main .reward-all { + display: inline-block; + margin: 0; + padding: 20px 10px; + border-radius: 4px; + background: var(--reward-pop); +} +.post-reward .reward-main .reward-all:before { + position: absolute; + bottom: -10px; + left: 0; + width: 100%; + height: 20px; + content: ''; +} +.post-reward .reward-main .reward-all:after { + position: absolute; + right: 0; + bottom: 2px; + left: 0; + margin: 0 auto; + width: 0; + height: 0; + border-top: 13px solid var(--reward-pop); + border-right: 13px solid transparent; + border-left: 13px solid transparent; + content: ''; +} +.post-reward .reward-main .reward-all .reward-item { + display: inline-block; + padding: 0 8px; + list-style-type: none; + vertical-align: top; +} +.post-reward .reward-main .reward-all .reward-item img { + width: 130px; + height: 130px; +} +.post-reward .reward-main .reward-all .reward-item .post-qr-code-desc { + width: 130px; + color: #858585; +} +#rightside { + position: fixed; + right: -48px; + bottom: 40px; + z-index: 100; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transition: all 0.5s; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -ms-transition: all 0.5s; + transition: all 0.5s; +} +#rightside #rightside-config-hide { + height: 0; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transition: -webkit-transform 0.4s; + -moz-transition: -moz-transform 0.4s; + -o-transition: -o-transform 0.4s; + -ms-transition: -ms-transform 0.4s; + transition: transform 0.4s; + -webkit-transform: translate(45px, 0); + -moz-transform: translate(45px, 0); + -o-transform: translate(45px, 0); + -ms-transform: translate(45px, 0); + transform: translate(45px, 0); +} +#rightside #rightside-config-hide.show { + height: auto; + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translate(0, 0); + -moz-transform: translate(0, 0); + -o-transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); +} +#rightside #rightside-config-hide.status { + height: auto; + opacity: 1; + -ms-filter: none; + filter: none; +} +#rightside > div > button, +#rightside > div > a { + display: block; + margin-bottom: 5px; + width: 35px; + height: 35px; + border-radius: 5px; + background-color: var(--btn-bg); + color: var(--btn-color); + text-align: center; + font-size: 16px; + line-height: 35px; +} +#rightside > div > button:hover, +#rightside > div > a:hover { + background-color: var(--btn-hover-color); +} +#rightside #mobile-toc-button { + display: none; +} +@media screen and (max-width: 900px) { + #rightside #mobile-toc-button { + display: block; + } +} +@media screen and (max-width: 900px) { + #rightside #hide-aside-btn { + display: none; + } +} +#sidebar #menu-mask { + position: fixed; + z-index: 102; + display: none; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.8); +} +#sidebar #sidebar-menus { + position: fixed; + top: 0; + right: -300px; + z-index: 103; + overflow-x: hidden; + overflow-y: auto; + width: 300px; + height: 100%; + background: var(--sidebar-bg); + -webkit-transition: all 0.5s; + -moz-transition: all 0.5s; + -o-transition: all 0.5s; + -ms-transition: all 0.5s; + transition: all 0.5s; +} +#sidebar #sidebar-menus.open { + -webkit-transform: translate3d(-100%, 0, 0); + -moz-transform: translate3d(-100%, 0, 0); + -o-transform: translate3d(-100%, 0, 0); + -ms-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); +} +#sidebar #sidebar-menus > .avatar-img { + margin: 20px auto; +} +#sidebar #sidebar-menus .sidebar-site-data { + padding: 0 10px; +} +#sidebar #sidebar-menus hr { + margin: 20px auto; +} +#sidebar #sidebar-menus .menus_items { + padding: 0 10px 40px; +} +#sidebar #sidebar-menus .menus_items .site-page { + position: relative; + display: block; + padding: 6px 30px 6px 22px; + color: var(--font-color); + font-size: 1.15em; +} +#sidebar #sidebar-menus .menus_items .site-page:hover { + background: var(--text-bg-hover); +} +#sidebar #sidebar-menus .menus_items .site-page i:first-child { + width: 15%; + text-align: left; +} +#sidebar #sidebar-menus .menus_items .site-page.group > i:last-child { + position: absolute; + top: 0.78em; + right: 18px; + -webkit-transition: -webkit-transform 0.3s; + -moz-transition: -moz-transform 0.3s; + -o-transition: -o-transform 0.3s; + -ms-transition: -ms-transform 0.3s; + transition: transform 0.3s; +} +#sidebar #sidebar-menus .menus_items .site-page.group.hide > i:last-child { + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +#sidebar #sidebar-menus .menus_items .site-page.group.hide + .menus_item_child { + display: none; +} +#sidebar #sidebar-menus .menus_items .menus_item_child { + margin: 0; + list-style: none; +} +#vcomment { + font-size: 1.1em; +} +#vcomment .vbtn { + border: none; + background: var(--btn-bg); + color: var(--btn-color); +} +#vcomment .vbtn:hover { + background: var(--btn-hover-color); +} +#vcomment .vimg { + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; +} +#vcomment .vimg:hover { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + transform: rotate(360deg); +} +#vcomment .vcards .vcard .vcontent.expand:before, +#vcomment .vcards .vcard .vcontent.expand:after { + z-index: 22; +} +#waline-wrap { + --waline-font-size: 1.1em; + --waline-theme-color: #008080; + --waline-active-color: #ff7242; +} +#waline-wrap .wl-comment-actions > button:not(last-child) { + padding-right: 4px; +} +.fireworks { + position: fixed; + top: 0; + left: 0; + z-index: 9999; + pointer-events: none; +} +.medium-zoom-image--opened { + z-index: 99999 !important; + margin: 0 !important; +} +.medium-zoom-overlay { + z-index: 99999 !important; +} +.mermaid-wrap { + margin: 0 0 20px; + text-align: center; +} +.mermaid-wrap > svg { + height: 100%; +} +.utterances, +.fb-comments iframe { + width: 100% !important; +} +#gitalk-container .gt-meta { + margin: 0 0 0.8em; + padding: 6px 0 16px; +} +.katex-wrap { + overflow: auto; +} +.katex-wrap::-webkit-scrollbar { + display: none; +} +mjx-container { + overflow-x: auto; + overflow-y: hidden; + padding-bottom: 4px; + max-width: 100%; +} +mjx-container[display] { + display: block !important; + min-width: auto !important; +} +mjx-container:not([display]) { + display: inline-grid !important; +} +mjx-assistive-mml { + right: 0; + bottom: 0; +} +.aplayer { + color: #4c4948; +} +#article-container .aplayer { + margin: 0 0 20px; +} +.snackbar-css { + border-radius: 5px !important; +} +.abc-music-sheet { + margin: 0 0 20px; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transition: opacity 0.3s; + -moz-transition: opacity 0.3s; + -o-transition: opacity 0.3s; + -ms-transition: opacity 0.3s; + transition: opacity 0.3s; +} +.abc-music-sheet.abcjs-container { + opacity: 1; + -ms-filter: none; + filter: none; +} +@media screen and (max-width: 768px) { + .fancybox__toolbar__column.is-middle { + display: none; + } +} +#article-container .btn-center { + margin: 0 0 20px; + text-align: center; +} +#article-container .btn-beautify { + display: inline-block; + margin: 0 4px 6px; + padding: 0 15px; + background-color: var(--btn-beautify-color, #777); + color: #fff; + line-height: 2; +} +#article-container .btn-beautify.blue { + --btn-beautify-color: #428bca; +} +#article-container .btn-beautify.pink { + --btn-beautify-color: #ff69b4; +} +#article-container .btn-beautify.red { + --btn-beautify-color: #f00; +} +#article-container .btn-beautify.purple { + --btn-beautify-color: #6f42c1; +} +#article-container .btn-beautify.orange { + --btn-beautify-color: #ff8c00; +} +#article-container .btn-beautify.green { + --btn-beautify-color: #5cb85c; +} +#article-container .btn-beautify:hover { + background-color: var(--btn-hover-color); +} +#article-container .btn-beautify i + span { + margin-left: 6px; +} +#article-container .btn-beautify:not(.block) + .btn-beautify:not(.block) { + margin: 0 4px 20px; +} +#article-container .btn-beautify.block { + display: block; + margin: 0 0 20px; + width: fit-content; + width: -moz-fit-content; +} +#article-container .btn-beautify.block.center { + margin: 0 auto 20px; +} +#article-container .btn-beautify.block.right { + margin: 0 0 20px auto; +} +#article-container .btn-beautify.larger { + padding: 6px 15px; +} +#article-container .btn-beautify:hover { + text-decoration: none; +} +#article-container .btn-beautify.outline { + border: 1px solid transparent; + border-color: var(--btn-beautify-color, #777); + background-color: transparent; + color: var(--btn-beautify-color, #777); +} +#article-container .btn-beautify.outline:hover { + background-color: var(--btn-beautify-color, #777); +} +#article-container .btn-beautify.outline:hover { + color: #fff !important; +} +#article-container figure.gallery-group { + position: relative; + float: left; + overflow: hidden; + margin: 6px 4px; + width: calc(50% - 8px); + height: 250px; + border-radius: 8px; + background: #000; + -webkit-transform: translate3d(0, 0, 0); +} +@media screen and (max-width: 600px) { + #article-container figure.gallery-group { + width: calc(100% - 8px); + } +} +#article-container figure.gallery-group:hover img { + opacity: 0.4; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=40)"; + filter: alpha(opacity=40); + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + -ms-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +#article-container figure.gallery-group:hover .gallery-group-name::after { + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + -ms-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +#article-container figure.gallery-group:hover p { + opacity: 1; + -ms-filter: none; + filter: none; + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + -ms-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); +} +#article-container figure.gallery-group img { + position: relative; + margin: 0; + max-width: none; + width: calc(100% + 20px); + height: 250px; + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + opacity: 0.8; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)"; + filter: alpha(opacity=80); + -webkit-transition: all 0.3s, filter 375ms ease-in 0.2s; + -moz-transition: all 0.3s, filter 375ms ease-in 0.2s; + -o-transition: all 0.3s, filter 375ms ease-in 0.2s; + -ms-transition: all 0.3s, filter 375ms ease-in 0.2s; + transition: all 0.3s, filter 375ms ease-in 0.2s; + -webkit-transform: translate3d(-10px, 0, 0); + -moz-transform: translate3d(-10px, 0, 0); + -o-transform: translate3d(-10px, 0, 0); + -ms-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + object-fit: cover; +} +#article-container figure.gallery-group figcaption { + position: absolute; + top: 0; + left: 0; + padding: 30px; + width: 100%; + height: 100%; + color: #fff; + text-transform: uppercase; + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; +} +#article-container figure.gallery-group figcaption > a { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1000; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +#article-container figure.gallery-group p { + margin: 0; + padding: 8px 0 0; + letter-spacing: 1px; + font-size: 1.1em; + line-height: 1.5; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); + -webkit-transition: opacity 0.35s, -webkit-transform 0.35s; + -moz-transition: opacity 0.35s, -moz-transform 0.35s; + -o-transition: opacity 0.35s, -o-transform 0.35s; + -ms-transition: opacity 0.35s, -ms-transform 0.35s; + transition: opacity 0.35s, transform 0.35s; + -webkit-transform: translate3d(100%, 0, 0); + -moz-transform: translate3d(100%, 0, 0); + -o-transform: translate3d(100%, 0, 0); + -ms-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + -webkit-line-clamp: 4; +} +#article-container figure.gallery-group .gallery-group-name { + position: relative; + margin: 0; + padding: 8px 0; + font-weight: bold; + font-size: 1.65em; + line-height: 1.5; + -webkit-line-clamp: 2; +} +#article-container figure.gallery-group .gallery-group-name:after { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 2px; + background: #fff; + content: ''; + -webkit-transition: -webkit-transform 0.35s; + -moz-transition: -moz-transform 0.35s; + -o-transition: -o-transform 0.35s; + -ms-transition: -ms-transform 0.35s; + transition: transform 0.35s; + -webkit-transform: translate3d(-100%, 0, 0); + -moz-transform: translate3d(-100%, 0, 0); + -o-transform: translate3d(-100%, 0, 0); + -ms-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); +} +#article-container .gallery-group-main { + overflow: auto; + padding: 0 0 16px; +} +#article-container .gallery { + margin: 0 0 16px; + text-align: center; +} +#article-container .gallery .fj-gallery { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +#article-container .gallery .fj-gallery .img-alt { + display: none; +} +#article-container .gallery .fj-gallery.lazyload + button { + display: inline-block; +} +#article-container .gallery .fj-gallery .gallery-data { + display: none; +} +#article-container .gallery button { + display: none; + margin-top: 25px; + padding: 10px; + width: 9em; + border-radius: 5px; + background: var(--btn-bg); + color: var(--btn-color); + font-weight: bold; + font-size: 1.1em; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; +} +#article-container .gallery button > * { + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -o-transition: all 0.4s; + -ms-transition: all 0.4s; + transition: all 0.4s; +} +#article-container .gallery button i { + width: 0; + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +#article-container .gallery button:hover { + background: var(--btn-hover-color); +} +#article-container .gallery button:hover i { + margin-left: 2px; + width: 20px; + opacity: 1; + -ms-filter: none; + filter: none; +} +blockquote.pullquote { + position: relative; + max-width: 45%; + font-size: 110%; +} +blockquote.pullquote.left { + float: left; + margin: 1em 0.5em 0 0; +} +blockquote.pullquote.right { + float: right; + margin: 1em 0 0 0.5em; +} +.video-container { + position: relative; + overflow: hidden; + margin-bottom: 16px; + padding-top: 56.25%; + height: 0; +} +.video-container iframe { + position: absolute; + top: 0; + left: 0; + margin-top: 0; + width: 100%; + height: 100%; +} +.hide-inline > .hide-button, +.hide-block > .hide-button { + display: inline-block; + padding: 5px 18px; + background: #008080; + color: var(--white); +} +.hide-inline > .hide-button:hover, +.hide-block > .hide-button:hover { + background-color: var(--btn-hover-color); +} +.hide-inline > .hide-button.open, +.hide-block > .hide-button.open { + display: none; +} +.hide-inline > .hide-button.open + div, +.hide-block > .hide-button.open + div { + display: block; +} +.hide-inline > .hide-button.open + span, +.hide-block > .hide-button.open + span { + display: inline; +} +.hide-inline > .hide-content, +.hide-block > .hide-content { + display: none; +} +.hide-inline > .hide-button { + margin: 0 6px; +} +.hide-inline > .hide-content { + margin: 0 6px; +} +.hide-block { + margin: 0 0 16px; +} +.toggle { + margin-bottom: 20px; + border: 1px solid #f0f0f0; +} +.toggle > .toggle-button { + padding: 6px 15px; + background: #f0f0f0; + color: #1f2d3d; + cursor: pointer; +} +.toggle > .toggle-content { + margin: 30px 24px; +} +#article-container .inline-img { + display: inline; + margin: 0 3px; + height: 1.1em; + vertical-align: text-bottom; +} +.hl-label { + padding: 2px 4px; + border-radius: 3px; + color: #fff; +} +.hl-label.default { + background-color: #777; +} +.hl-label.blue { + background-color: #428bca; +} +.hl-label.pink { + background-color: #ff69b4; +} +.hl-label.red { + background-color: #f00; +} +.hl-label.purple { + background-color: #6f42c1; +} +.hl-label.orange { + background-color: #ff8c00; +} +.hl-label.green { + background-color: #5cb85c; +} +.note { + position: relative; + margin: 0 0 20px; + padding: 15px; + border-radius: 3px; +} +.note.icon-padding { + padding-left: 3em; +} +.note > .note-icon { + position: absolute; + top: calc(50% - 0.5em); + left: 0.8em; + font-size: larger; +} +.note.blue:not(.disabled) { + border-left-color: #428bca !important; +} +.note.blue:not(.disabled).modern { + border-left-color: transparent !important; + color: #428bca; +} +.note.blue:not(.disabled):not(.simple) { + background: #e3eef7 !important; +} +.note.blue > .note-icon { + color: #428bca; +} +.note.pink:not(.disabled) { + border-left-color: #ff69b4 !important; +} +.note.pink:not(.disabled).modern { + border-left-color: transparent !important; + color: #ff69b4; +} +.note.pink:not(.disabled):not(.simple) { + background: #ffe9f4 !important; +} +.note.pink > .note-icon { + color: #ff69b4; +} +.note.red:not(.disabled) { + border-left-color: #f00 !important; +} +.note.red:not(.disabled).modern { + border-left-color: transparent !important; + color: #f00; +} +.note.red:not(.disabled):not(.simple) { + background: #ffd9d9 !important; +} +.note.red > .note-icon { + color: #f00; +} +.note.purple:not(.disabled) { + border-left-color: #6f42c1 !important; +} +.note.purple:not(.disabled).modern { + border-left-color: transparent !important; + color: #6f42c1; +} +.note.purple:not(.disabled):not(.simple) { + background: #e9e3f6 !important; +} +.note.purple > .note-icon { + color: #6f42c1; +} +.note.orange:not(.disabled) { + border-left-color: #ff8c00 !important; +} +.note.orange:not(.disabled).modern { + border-left-color: transparent !important; + color: #ff8c00; +} +.note.orange:not(.disabled):not(.simple) { + background: #ffeed9 !important; +} +.note.orange > .note-icon { + color: #ff8c00; +} +.note.green:not(.disabled) { + border-left-color: #5cb85c !important; +} +.note.green:not(.disabled).modern { + border-left-color: transparent !important; + color: #5cb85c; +} +.note.green:not(.disabled):not(.simple) { + background: #e7f4e7 !important; +} +.note.green > .note-icon { + color: #5cb85c; +} +.note.simple { + border: 1px solid #eee; + border-left-width: 5px; +} +.note.modern { + border: 1px solid transparent !important; + background-color: #f5f5f5; + color: #4c4948; +} +.note.flat { + border: initial; + border-left: 5px solid #eee; + background-color: #f9f9f9; + color: #4c4948; +} +.note h2, +.note h3, +.note h4, +.note h5, +.note h6 { + margin-top: 3px; + margin-bottom: 0; + padding-top: 0 !important; + border-bottom: initial; +} +.note p:first-child, +.note ul:first-child, +.note ol:first-child, +.note table:first-child, +.note pre:first-child, +.note blockquote:first-child, +.note img:first-child { + margin-top: 0 !important; +} +.note p:last-child, +.note ul:last-child, +.note ol:last-child, +.note table:last-child, +.note pre:last-child, +.note blockquote:last-child, +.note img:last-child { + margin-bottom: 0 !important; +} +.note:not(.no-icon) { + padding-left: 3em; +} +.note:not(.no-icon)::before { + position: absolute; + top: calc(50% - 0.95em); + left: 0.8em; + font-size: larger; +} +.note.default.flat { + background: #f7f7f7; +} +.note.default.modern { + border-color: #e1e1e1; + background: #f3f3f3; + color: #666; +} +.note.default.modern a:not(.btn) { + color: #666; +} +.note.default.modern a:not(.btn):hover { + color: #454545; +} +.note.default:not(.modern) { + border-left-color: #777; +} +.note.default:not(.modern) h2, +.note.default:not(.modern) h3, +.note.default:not(.modern) h4, +.note.default:not(.modern) h5, +.note.default:not(.modern) h6 { + color: #777; +} +.note.default:not(.no-icon)::before { + content: '\f0a9'; +} +.note.default:not(.no-icon):not(.modern)::before { + color: #777; +} +.note.primary.flat { + background: #f5f0fa; +} +.note.primary.modern { + border-color: #e1c2ff; + background: #f3daff; + color: #6f42c1; +} +.note.primary.modern a:not(.btn) { + color: #6f42c1; +} +.note.primary.modern a:not(.btn):hover { + color: #453298; +} +.note.primary:not(.modern) { + border-left-color: #6f42c1; +} +.note.primary:not(.modern) h2, +.note.primary:not(.modern) h3, +.note.primary:not(.modern) h4, +.note.primary:not(.modern) h5, +.note.primary:not(.modern) h6 { + color: #6f42c1; +} +.note.primary:not(.no-icon)::before { + content: '\f055'; +} +.note.primary:not(.no-icon):not(.modern)::before { + color: #6f42c1; +} +.note.info.flat { + background: #eef7fa; +} +.note.info.modern { + border-color: #b3e5ef; + background: #d9edf7; + color: #31708f; +} +.note.info.modern a:not(.btn) { + color: #31708f; +} +.note.info.modern a:not(.btn):hover { + color: #215761; +} +.note.info:not(.modern) { + border-left-color: #428bca; +} +.note.info:not(.modern) h2, +.note.info:not(.modern) h3, +.note.info:not(.modern) h4, +.note.info:not(.modern) h5, +.note.info:not(.modern) h6 { + color: #428bca; +} +.note.info:not(.no-icon)::before { + content: '\f05a'; +} +.note.info:not(.no-icon):not(.modern)::before { + color: #428bca; +} +.note.success.flat { + background: #eff8f0; +} +.note.success.modern { + border-color: #d0e6be; + background: #dff0d8; + color: #3c763d; +} +.note.success.modern a:not(.btn) { + color: #3c763d; +} +.note.success.modern a:not(.btn):hover { + color: #32562c; +} +.note.success:not(.modern) { + border-left-color: #5cb85c; +} +.note.success:not(.modern) h2, +.note.success:not(.modern) h3, +.note.success:not(.modern) h4, +.note.success:not(.modern) h5, +.note.success:not(.modern) h6 { + color: #5cb85c; +} +.note.success:not(.no-icon)::before { + content: '\f058'; +} +.note.success:not(.no-icon):not(.modern)::before { + color: #5cb85c; +} +.note.warning.flat { + background: #fdf8ea; +} +.note.warning.modern { + border-color: #fae4cd; + background: #fcf4e3; + color: #8a6d3b; +} +.note.warning.modern a:not(.btn) { + color: #8a6d3b; +} +.note.warning.modern a:not(.btn):hover { + color: #714f30; +} +.note.warning:not(.modern) { + border-left-color: #f0ad4e; +} +.note.warning:not(.modern) h2, +.note.warning:not(.modern) h3, +.note.warning:not(.modern) h4, +.note.warning:not(.modern) h5, +.note.warning:not(.modern) h6 { + color: #f0ad4e; +} +.note.warning:not(.no-icon)::before { + content: '\f06a'; +} +.note.warning:not(.no-icon):not(.modern)::before { + color: #f0ad4e; +} +.note.danger.flat { + background: #fcf1f2; +} +.note.danger.modern { + border-color: #ebcdd2; + background: #f2dfdf; + color: #a94442; +} +.note.danger.modern a:not(.btn) { + color: #a94442; +} +.note.danger.modern a:not(.btn):hover { + color: #84333f; +} +.note.danger:not(.modern) { + border-left-color: #d9534f; +} +.note.danger:not(.modern) h2, +.note.danger:not(.modern) h3, +.note.danger:not(.modern) h4, +.note.danger:not(.modern) h5, +.note.danger:not(.modern) h6 { + color: #d9534f; +} +.note.danger:not(.no-icon)::before { + content: '\f056'; +} +.note.danger:not(.no-icon):not(.modern)::before { + color: #d9534f; +} +#article-container .tabs { + position: relative; + margin: 0 0 20px; + border-right: 1px solid var(--tab-border-color); + border-bottom: 1px solid var(--tab-border-color); + border-left: 1px solid var(--tab-border-color); +} +#article-container .tabs > .nav-tabs { + display: -webkit-box; + display: -moz-box; + display: -webkit-flex; + display: -ms-flexbox; + display: box; + display: flex; + -webkit-box-lines: multiple; + -moz-box-lines: multiple; + -o-box-lines: multiple; + -webkit-flex-wrap: wrap; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin: 0; + padding: 0; + background: var(--tab-botton-bg); +} +#article-container .tabs > .nav-tabs > .tab { + margin: 0; + padding: 0; + list-style: none; +} +@media screen and (max-width: 768px) { + #article-container .tabs > .nav-tabs > .tab { + -webkit-box-flex: 1; + -moz-box-flex: 1; + -o-box-flex: 1; + -ms-box-flex: 1; + box-flex: 1; + -webkit-flex-grow: 1; + flex-grow: 1; + } +} +#article-container .tabs > .nav-tabs > .tab button { + display: block; + padding: 8px 18px; + width: 100%; + border-top: 2px solid var(--tab-border-color); + background: var(--tab-botton-bg); + color: var(--tab-botton-color); + line-height: 2; + -webkit-transition: all 0.4s; + -moz-transition: all 0.4s; + -o-transition: all 0.4s; + -ms-transition: all 0.4s; + transition: all 0.4s; +} +#article-container .tabs > .nav-tabs > .tab button i { + width: 1.5em; +} +#article-container .tabs > .nav-tabs > .tab.active button { + border-top: 2px solid #008080; + background: var(--tab-button-active-bg); + cursor: default; +} +#article-container .tabs > .nav-tabs > .tab:not(.active) button:hover { + border-top: 2px solid var(--tab-button-hover-bg); + background: var(--tab-button-hover-bg); +} +#article-container .tabs > .tab-contents .tab-item-content { + position: relative; + display: none; + padding: 36px 24px; +} +@media screen and (max-width: 768px) { + #article-container .tabs > .tab-contents .tab-item-content { + padding: 24px 14px; + } +} +#article-container .tabs > .tab-contents .tab-item-content.active { + display: block; + -webkit-animation: tabshow 0.5s; + -moz-animation: tabshow 0.5s; + -o-animation: tabshow 0.5s; + -ms-animation: tabshow 0.5s; + animation: tabshow 0.5s; +} +#article-container .tabs .tab-to-top { + position: relative; + display: block; + margin: 0 0 0 auto; + color: #99a9bf; +} +@-moz-keyframes tabshow { + 0% { + -webkit-transform: translateY(15px); + -moz-transform: translateY(15px); + -o-transform: translateY(15px); + -ms-transform: translateY(15px); + transform: translateY(15px); + } + 100% { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-webkit-keyframes tabshow { + 0% { + -webkit-transform: translateY(15px); + -moz-transform: translateY(15px); + -o-transform: translateY(15px); + -ms-transform: translateY(15px); + transform: translateY(15px); + } + 100% { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@-o-keyframes tabshow { + 0% { + -webkit-transform: translateY(15px); + -moz-transform: translateY(15px); + -o-transform: translateY(15px); + -ms-transform: translateY(15px); + transform: translateY(15px); + } + 100% { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +@keyframes tabshow { + 0% { + -webkit-transform: translateY(15px); + -moz-transform: translateY(15px); + -o-transform: translateY(15px); + -ms-transform: translateY(15px); + transform: translateY(15px); + } + 100% { + -webkit-transform: translateY(0); + -moz-transform: translateY(0); + -o-transform: translateY(0); + -ms-transform: translateY(0); + transform: translateY(0); + } +} +#article-container .timeline { + margin: 0 0 20px 10px; + padding: 14px 20px 5px; + border-left: 2px solid var(--timeline-color, #008080); +} +#article-container .timeline.blue { + --timeline-color: #428bca; + --timeline-bg: rgba(66,139,202, 0.2); +} +#article-container .timeline.pink { + --timeline-color: #ff69b4; + --timeline-bg: rgba(255,105,180, 0.2); +} +#article-container .timeline.red { + --timeline-color: #f00; + --timeline-bg: rgba(255,0,0, 0.2); +} +#article-container .timeline.purple { + --timeline-color: #6f42c1; + --timeline-bg: rgba(111,66,193, 0.2); +} +#article-container .timeline.orange { + --timeline-color: #ff8c00; + --timeline-bg: rgba(255,140,0, 0.2); +} +#article-container .timeline.green { + --timeline-color: #5cb85c; + --timeline-bg: rgba(92,184,92, 0.2); +} +#article-container .timeline .timeline-item { + margin: 0 0 15px; +} +#article-container .timeline .timeline-item:hover .item-circle:before { + border-color: var(--timeline-color, #008080); +} +#article-container .timeline .timeline-item.headline .timeline-item-title .item-circle > p { + font-weight: 600; + font-size: 1.2em; +} +#article-container .timeline .timeline-item.headline .timeline-item-title .item-circle:before { + left: -28px; + border: 4px solid var(--timeline-color, #008080); +} +#article-container .timeline .timeline-item.headline:hover .item-circle:before { + border-color: var(--pseudo-hover); +} +#article-container .timeline .timeline-item .timeline-item-title { + position: relative; +} +#article-container .timeline .timeline-item .item-circle:before { + position: absolute; + top: 50%; + left: -27px; + width: 6px; + height: 6px; + border: 3px solid var(--pseudo-hover); + border-radius: 50%; + background: var(--card-bg); + content: ''; + -webkit-transition: all 0.3s; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -ms-transition: all 0.3s; + transition: all 0.3s; + -webkit-transform: translate(0, -50%); + -moz-transform: translate(0, -50%); + -o-transform: translate(0, -50%); + -ms-transform: translate(0, -50%); + transform: translate(0, -50%); +} +#article-container .timeline .timeline-item .item-circle > p { + margin: 0 0 8px; + font-weight: 500; +} +#article-container .timeline .timeline-item .timeline-item-content { + position: relative; + padding: 12px 15px; + border-radius: 8px; + background: var(--timeline-bg, #c6ffff); + font-size: 0.93em; +} +#article-container .timeline .timeline-item .timeline-item-content > :last-child { + margin-bottom: 0; +} +#article-container .timeline + .timeline { + margin-top: -20px; +} +[data-theme='dark'] { + --global-bg: #0d0d0d; + --font-color: rgba(255,255,255,0.7); + --hr-border: rgba(255,255,255,0.4); + --hr-before-color: rgba(255,255,255,0.7); + --search-bg: #121212; + --search-input-color: rgba(255,255,255,0.7); + --search-a-color: rgba(255,255,255,0.7); + --preloader-bg: #0d0d0d; + --preloader-color: rgba(255,255,255,0.7); + --tab-border-color: #2c2c2c; + --tab-botton-bg: #2c2c2c; + --tab-botton-color: rgba(255,255,255,0.7); + --tab-button-hover-bg: #383838; + --tab-button-active-bg: #121212; + --card-bg: #121212; + --sidebar-bg: #121212; + --btn-hover-color: #787878; + --btn-color: rgba(255,255,255,0.7); + --btn-bg: #1f1f1f; + --text-bg-hover: #383838; + --light-grey: rgba(255,255,255,0.7); + --dark-grey: rgba(255,255,255,0.2); + --white: rgba(255,255,255,0.9); + --text-highlight-color: rgba(255,255,255,0.9); + --blockquote-color: rgba(255,255,255,0.7); + --blockquote-bg: #2c2c2c; + --reward-pop: #2c2c2c; + --toc-link-color: rgba(255,255,255,0.6); + --hl-color: rgba(255,255,255,0.7); + --hl-bg: #171717; + --hltools-bg: #1a1a1a; + --hltools-color: #90a4ae; + --hlnumber-bg: #171717; + --hlnumber-color: rgba(255,255,255,0.4); + --hlscrollbar-bg: #1f1f1f; + --hlexpand-bg: linear-gradient(180deg, rgba(23,23,23,0.6), rgba(23,23,23,0.9)); + --scrollbar-color: #1f1f1f; + --timeline-bg: #1f1f1f; + --zoom-bg: #121212; + --mark-bg: rgba(0,0,0,0.6); +} +[data-theme='dark'] #web_bg:before { + position: absolute; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.7); + content: ''; +} +[data-theme='dark'] #article-container code { + background: #2c2c2c; +} +[data-theme='dark'] #article-container pre > code { + background: #171717; +} +[data-theme='dark'] #article-container figure.highlight { + -webkit-box-shadow: none; + box-shadow: none; +} +[data-theme='dark'] #article-container .note code { + background: rgba(27,31,35,0.05); +} +[data-theme='dark'] #article-container .aplayer { + filter: brightness(0.8); +} +[data-theme='dark'] #article-container kbd { + border-color: #696969; + background-color: #525252; + color: #e2f1ff; +} +[data-theme='dark'] #page-header.nav-fixed > #nav, +[data-theme='dark'] #page-header.not-top-img > #nav { + background: rgba(18,18,18,0.8); + -webkit-box-shadow: 0 5px 6px -5px rgba(133,133,133,0); + box-shadow: 0 5px 6px -5px rgba(133,133,133,0); +} +[data-theme='dark'] #post-comment #comment-switch { + background: #2c2c2c !important; +} +[data-theme='dark'] #post-comment #comment-switch .switch-btn { + filter: brightness(0.8); +} +[data-theme='dark'] .note { + filter: brightness(0.8); +} +[data-theme='dark'] .hide-button, +[data-theme='dark'] .btn-beautify, +[data-theme='dark'] .hl-label, +[data-theme='dark'] .post-outdate-notice, +[data-theme='dark'] .error-img, +[data-theme='dark'] #article-container iframe, +[data-theme='dark'] .gist, +[data-theme='dark'] .ads-wrap { + filter: brightness(0.8); +} +[data-theme='dark'] img { + filter: brightness(0.8); +} +[data-theme='dark'] #aside-content .aside-list > .aside-list-item:not(:last-child) { + border-bottom: 1px dashed rgba(255,255,255,0.1); +} +[data-theme='dark'] #gitalk-container { + filter: brightness(0.8); +} +[data-theme='dark'] #gitalk-container svg { + fill: rgba(255,255,255,0.9) !important; +} +[data-theme='dark'] #disqusjs #dsqjs:hover, +[data-theme='dark'] #disqusjs #dsqjs:focus, +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-tab-active, +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-no-comment { + color: rgba(255,255,255,0.7); +} +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-order-label { + background-color: #1f1f1f; +} +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-post-body { + color: rgba(255,255,255,0.7); +} +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-post-body code, +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-post-body pre { + background: #2c2c2c; +} +[data-theme='dark'] #disqusjs #dsqjs .dsqjs-post-body blockquote { + color: rgba(255,255,255,0.7); +} +[data-theme='dark'] #artitalk_main #lazy { + background: #121212; +} +[data-theme='dark'] #operare_artitalk .c2 { + background: #121212; +} +@media screen and (max-width: 900px) { + [data-theme='dark'] #card-toc { + background: #1f1f1f; + } +} +.read-mode { + --font-color: #4c4948; + --readmode-light-color: #fff; + --white: #4c4948; + --light-grey: #4c4948; + --gray: #d6dbdf; + --hr-border: #d6dbdf; + --hr-before-color: #b9c2c9; + --highlight-bg: #f7f7f7; + --exit-btn-bg: #c0c0c0; + --exit-btn-color: #fff; + --exit-btn-hover: #8d8d8d; + --pseudo-hover: none; +} +[data-theme='dark'] .read-mode { + --font-color: rgba(255,255,255,0.7); + --readmode-light-color: #0d0d0d; + --white: rgba(255,255,255,0.9); + --light-grey: rgba(255,255,255,0.7); + --gray: rgba(255,255,255,0.7); + --hr-border: rgba(255,255,255,0.5); + --hr-before-color: rgba(255,255,255,0.7); + --highlight-bg: #171717; + --exit-btn-bg: #1f1f1f; + --exit-btn-color: rgba(255,255,255,0.9); + --exit-btn-hover: #525252; +} +.read-mode { + background: var(--readmode-light-color); +} +.read-mode .exit-readmode { + position: fixed; + top: 30px; + right: 30px; + z-index: 100; + width: 40px; + height: 40px; + border-radius: 8px; + background: var(--exit-btn-bg); + color: var(--exit-btn-color); + font-size: 16px; + -webkit-transition: background 0.3s; + -moz-transition: background 0.3s; + -o-transition: background 0.3s; + -ms-transition: background 0.3s; + transition: background 0.3s; +} +@media screen and (max-width: 768px) { + .read-mode .exit-readmode { + top: initial; + bottom: 30px; + } +} +.read-mode .exit-readmode:hover { + background: var(--exit-btn-hover); +} +.read-mode #aside-content { + display: none; +} +.read-mode #page-header.post-bg { + background: none !important; +} +.read-mode #page-header.post-bg:before { + opacity: 0; + -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; + filter: alpha(opacity=0); +} +.read-mode #page-header.post-bg > #post-info { + text-align: center; +} +.read-mode #post { + margin: 0 auto; + background: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.read-mode #post:hover { + -webkit-box-shadow: none; + box-shadow: none; +} +.read-mode > canvas { + display: none !important; +} +.read-mode .highlight-tools, +.read-mode #footer, +.read-mode #post > *:not(#post-info):not(.post-content), +.read-mode #nav, +.read-mode .post-outdate-notice, +.read-mode #web_bg, +.read-mode #rightside, +.read-mode .not-top-img { + display: none !important; +} +.read-mode #article-container a { + color: #99a9bf; +} +.read-mode #article-container pre, +.read-mode #article-container .highlight:not(.js-file-line-container) { + background: var(--highlight-bg) !important; +} +.read-mode #article-container pre *, +.read-mode #article-container .highlight:not(.js-file-line-container) * { + color: var(--font-color) !important; +} +.read-mode #article-container figure.highlight { + border-radius: 0 !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; +} +.read-mode #article-container figure.highlight > :not(.highlight-tools) { + display: block !important; +} +.read-mode #article-container figure.highlight .line:before { + color: var(--font-color) !important; +} +.read-mode #article-container figure.highlight .hljs { + background: var(--highlight-bg) !important; +} +.read-mode #article-container h1, +.read-mode #article-container h2, +.read-mode #article-container h3, +.read-mode #article-container h4, +.read-mode #article-container h5, +.read-mode #article-container h6 { + padding: 0; +} +.read-mode #article-container h1:before, +.read-mode #article-container h2:before, +.read-mode #article-container h3:before, +.read-mode #article-container h4:before, +.read-mode #article-container h5:before, +.read-mode #article-container h6:before { + content: ''; +} +.read-mode #article-container h1:hover, +.read-mode #article-container h2:hover, +.read-mode #article-container h3:hover, +.read-mode #article-container h4:hover, +.read-mode #article-container h5:hover, +.read-mode #article-container h6:hover { + padding: 0; +} +.read-mode #article-container ul:hover:before, +.read-mode #article-container li:hover:before, +.read-mode #article-container ol:hover:before { + -webkit-transform: none !important; + -moz-transform: none !important; + -o-transform: none !important; + -ms-transform: none !important; + transform: none !important; +} +.read-mode #article-container ol:before, +.read-mode #article-container li:before { + background: transparent !important; + color: var(--font-color) !important; +} +.read-mode #article-container ul >li:before { + border-color: var(--gray) !important; +} +.read-mode #article-container .tabs { + border: 2px solid var(--tab-border-color); +} +.read-mode #article-container .tabs > .nav-tabs { + background: transparent; +} +.read-mode #article-container .tabs > .nav-tabs > .tab { + border-bottom: 0; +} +.read-mode #article-container .tabs > .nav-tabs > .tab button { + border-top: none !important; + background: transparent; +} +.read-mode #article-container .tabs > .nav-tabs > .tab button:hover { + background: none !important; +} +.read-mode #article-container .tabs > .nav-tabs > .tab.active button { + text-decoration: underline; +} +.read-mode #article-container .tabs > .tab-contents .tab-item-content.active { + -webkit-animation: none; + -moz-animation: none; + -o-animation: none; + -ms-animation: none; + animation: none; +} +.read-mode #article-container code { + color: var(--font-color); +} +.read-mode #article-container blockquote { + border-color: var(--gray); + background-color: var(--readmode-light-color); +} +.read-mode #article-container kbd { + border: 1px solid var(--gray); + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; + color: var(--font-color); +} +.read-mode #article-container .hide-toggle { + border: 1px solid var(--gray) !important; +} +.read-mode #article-container .hide-button, +.read-mode #article-container .btn-beautify, +.read-mode #article-container .hl-label { + border: 1px solid var(--gray) !important; + background: var(--readmode-light-color) !important; + color: var(--font-color) !important; +} +.read-mode #article-container .note { + border: 2px solid var(--gray); + border-left-color: var(--gray) !important; + filter: none; + background-color: var(--readmode-light-color) !important; + color: var(--font-color); +} +.read-mode #article-container .note:before, +.read-mode #article-container .note .note-icon { + color: var(--font-color); +} +.search-dialog { + position: fixed; + top: 10%; + left: 50%; + z-index: 1001; + display: none; + margin-left: -300px; + padding: 20px; + width: 600px; + border-radius: 8px; + background: var(--search-bg); + --search-height: 100vh; +} +@media screen and (max-width: 768px) { + .search-dialog { + top: 0; + left: 0; + margin: 0; + width: 100%; + height: 100%; + border-radius: 0; + } +} +.search-dialog hr { + margin: 20px auto; +} +.search-dialog .search-nav { + margin: 0 0 14px; + color: #008080; + font-size: 1.4em; + line-height: 1; +} +.search-dialog .search-nav .search-dialog-title { + margin-right: 10px; +} +.search-dialog .search-nav .search-close-button { + float: right; + color: #858585; + -webkit-transition: color 0.2s ease-in-out; + -moz-transition: color 0.2s ease-in-out; + -o-transition: color 0.2s ease-in-out; + -ms-transition: color 0.2s ease-in-out; + transition: color 0.2s ease-in-out; +} +.search-dialog .search-nav .search-close-button:hover { + color: #008080; +} +.search-dialog hr { + margin: 20px auto; +} +#search-mask { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1000; + display: none; + background: rgba(0,0,0,0.6); +} +#local-search .search-dialog .local-search-box { + margin: 0 auto; + max-width: 100%; + width: 100%; +} +#local-search .search-dialog .local-search-box input { + padding: 5px 14px; + width: 100%; + outline: none; + border: 2px solid #008080; + border-radius: 40px; + background: var(--search-bg); + color: var(--search-input-color); + -webkit-appearance: none; +} +#local-search .search-dialog .search-wrap { + display: none; +} +#local-search .search-dialog .local-search-hit-item { + position: relative; + padding-left: 24px; + line-height: 1.7; +} +#local-search .search-dialog .local-search-hit-item:hover:before { + border-color: var(--pseudo-hover); +} +#local-search .search-dialog .local-search-hit-item:before { + position: absolute; + top: 0.45em; + left: 0; + width: 0.5em; + height: 0.5em; + border: 3px solid #008080; + border-radius: 0.5em; + background: transparent; + content: ''; + line-height: 0.5em; + -webkit-transition: all 0.2s ease-in-out; + -moz-transition: all 0.2s ease-in-out; + -o-transition: all 0.2s ease-in-out; + -ms-transition: all 0.2s ease-in-out; + transition: all 0.2s ease-in-out; +} +#local-search .search-dialog .local-search-hit-item a { + display: block; + color: var(--search-a-color); +} +#local-search .search-dialog .local-search-hit-item a:hover { + color: #008080; +} +#local-search .search-dialog .local-search-hit-item .search-result-title { + font-weight: 600; +} +#local-search .search-dialog .local-search-hit-item .search-result { + margin: 0 0 8px; + word-break: break-word; +} +#local-search .search-dialog .search-result-list { + overflow-y: overlay; + margin: 0 -20px; + padding: 0 22px; + max-height: calc(80vh - 200px); +} +@media screen and (max-width: 768px) { + #local-search .search-dialog .search-result-list { + max-height: calc(var(--search-height) - 220px) !important; + } +} +#local-search .search-dialog .no-result + #local-search-stats-wrap { + display: none; +} +.search-keyword { + background: transparent; + color: #f47466; + font-weight: bold; +} diff --git a/placeholder b/css/var.css similarity index 100% rename from placeholder rename to css/var.css diff --git a/img/1.jpg b/img/1.jpg new file mode 100644 index 000000000..32a5eaf2e Binary files /dev/null and b/img/1.jpg differ diff --git a/img/2.jpg b/img/2.jpg new file mode 100644 index 000000000..c9dc9c7d6 Binary files /dev/null and b/img/2.jpg differ diff --git a/img/3.jpg b/img/3.jpg new file mode 100644 index 000000000..b2d7843eb Binary files /dev/null and b/img/3.jpg differ diff --git a/img/4.jpg b/img/4.jpg new file mode 100644 index 000000000..4606135de Binary files /dev/null and b/img/4.jpg differ diff --git a/img/404.jpg b/img/404.jpg new file mode 100644 index 000000000..6f6362e0c Binary files /dev/null and b/img/404.jpg differ diff --git a/img/5.jpg b/img/5.jpg new file mode 100644 index 000000000..129ad721c Binary files /dev/null and b/img/5.jpg differ diff --git a/img/avatar.jpg b/img/avatar.jpg new file mode 100644 index 000000000..76058ebf2 Binary files /dev/null and b/img/avatar.jpg differ diff --git a/img/favicon.png b/img/favicon.png new file mode 100644 index 000000000..862ebe858 Binary files /dev/null and b/img/favicon.png differ diff --git a/img/loading.gif b/img/loading.gif new file mode 100644 index 000000000..ad7dd907d Binary files /dev/null and b/img/loading.gif differ diff --git a/img/图标.png b/img/图标.png new file mode 100644 index 000000000..3de731b1b Binary files /dev/null and b/img/图标.png differ diff --git a/index.html b/index.html new file mode 100644 index 000000000..81fd4130e --- /dev/null +++ b/index.html @@ -0,0 +1,355 @@ +The Blog + + + + + + + + + +
Windows10的Linux子系统WSL的安装和使用
Linux从入门到进阶
免费域名注册教程
代码注释模板
面试题集锦
使用Robot类编写自动化脚本
Java生成二维码
MySql进阶教程
对象存储服务MinIO
FreeMarker模板引擎
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 000000000..303300d20 --- /dev/null +++ b/js/main.js @@ -0,0 +1,827 @@ +document.addEventListener('DOMContentLoaded', function () { + let headerContentWidth, $nav + let mobileSidebarOpen = false + + const adjustMenu = init => { + const getAllWidth = ele => { + let width = 0 + ele.length && Array.from(ele).forEach(i => { width += i.offsetWidth }) + return width + } + + if (init) { + const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children) + const menusWidth = getAllWidth(document.getElementById('menus').children) + headerContentWidth = blogInfoWidth + menusWidth + $nav = document.getElementById('nav') + } + + let hideMenuIndex = '' + if (window.innerWidth <= 768) hideMenuIndex = true + else hideMenuIndex = headerContentWidth > $nav.offsetWidth - 120 + + if (hideMenuIndex) { + $nav.classList.add('hide-menu') + } else { + $nav.classList.remove('hide-menu') + } + } + + // 初始化header + const initAdjust = () => { + adjustMenu(true) + $nav.classList.add('show') + } + + // sidebar menus + const sidebarFn = { + open: () => { + btf.sidebarPaddingR() + document.body.style.overflow = 'hidden' + btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s') + document.getElementById('sidebar-menus').classList.add('open') + mobileSidebarOpen = true + }, + close: () => { + const $body = document.body + $body.style.overflow = '' + $body.style.paddingRight = '' + btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s') + document.getElementById('sidebar-menus').classList.remove('open') + mobileSidebarOpen = false + } + } + + /** + * 首頁top_img底下的箭頭 + */ + const scrollDownInIndex = () => { + const $scrollDownEle = document.getElementById('scroll-down') + $scrollDownEle && $scrollDownEle.addEventListener('click', function () { + btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300) + }) + } + + /** + * 代碼 + * 只適用於Hexo默認的代碼渲染 + */ + const addHighlightTool = function () { + const highLight = GLOBAL_CONFIG.highlight + if (!highLight) return + + const { highlightCopy, highlightLang, highlightHeightLimit, plugin } = highLight + const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink + const isShowTool = highlightCopy || highlightLang || isHighlightShrink !== undefined + const $figureHighlight = plugin === 'highlighjs' ? document.querySelectorAll('figure.highlight') : document.querySelectorAll('pre[class*="language-"]') + + if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return + + const isPrismjs = plugin === 'prismjs' + const highlightShrinkClass = isHighlightShrink === true ? 'closed' : '' + const highlightShrinkEle = isHighlightShrink !== undefined ? `` : '' + const highlightCopyEle = highlightCopy ? '
' : '' + + const copy = (text, ctx) => { + if (document.queryCommandSupported && document.queryCommandSupported('copy')) { + document.execCommand('copy') + if (GLOBAL_CONFIG.Snackbar !== undefined) { + btf.snackbarShow(GLOBAL_CONFIG.copy.success) + } else { + const prevEle = ctx.previousElementSibling + prevEle.textContent = GLOBAL_CONFIG.copy.success + prevEle.style.opacity = 1 + setTimeout(() => { prevEle.style.opacity = 0 }, 700) + } + } else { + if (GLOBAL_CONFIG.Snackbar !== undefined) { + btf.snackbarShow(GLOBAL_CONFIG.copy.noSupport) + } else { + ctx.previousElementSibling.textContent = GLOBAL_CONFIG.copy.noSupport + } + } + } + + // click events + const highlightCopyFn = (ele) => { + const $buttonParent = ele.parentNode + $buttonParent.classList.add('copy-true') + const selection = window.getSelection() + const range = document.createRange() + const preCodeSelector = isPrismjs ? 'pre code' : 'table .code pre' + range.selectNodeContents($buttonParent.querySelectorAll(`${preCodeSelector}`)[0]) + selection.removeAllRanges() + selection.addRange(range) + const text = selection.toString() + copy(text, ele.lastChild) + selection.removeAllRanges() + $buttonParent.classList.remove('copy-true') + } + + const highlightShrinkFn = (ele) => { + const $nextEle = [...ele.parentNode.children].slice(1) + ele.firstChild.classList.toggle('closed') + if (btf.isHidden($nextEle[$nextEle.length - 1])) { + $nextEle.forEach(e => { e.style.display = 'block' }) + } else { + $nextEle.forEach(e => { e.style.display = 'none' }) + } + } + + const highlightToolsFn = function (e) { + const $target = e.target.classList + if ($target.contains('expand')) highlightShrinkFn(this) + else if ($target.contains('copy-button')) highlightCopyFn(this) + } + + const expandCode = function () { + this.classList.toggle('expand-done') + } + + function createEle (lang, item, service) { + const fragment = document.createDocumentFragment() + + if (isShowTool) { + const hlTools = document.createElement('div') + hlTools.className = `highlight-tools ${highlightShrinkClass}` + hlTools.innerHTML = highlightShrinkEle + lang + highlightCopyEle + hlTools.addEventListener('click', highlightToolsFn) + fragment.appendChild(hlTools) + } + + if (highlightHeightLimit && item.offsetHeight > highlightHeightLimit + 30) { + const ele = document.createElement('div') + ele.className = 'code-expand-btn' + ele.innerHTML = '' + ele.addEventListener('click', expandCode) + fragment.appendChild(ele) + } + + if (service === 'hl') { + item.insertBefore(fragment, item.firstChild) + } else { + item.parentNode.insertBefore(fragment, item) + } + } + + if (isPrismjs) { + $figureHighlight.forEach(item => { + if (highlightLang) { + const langName = item.getAttribute('data-language') || 'Code' + const highlightLangEle = `
${langName}
` + btf.wrap(item, 'figure', { class: 'highlight' }) + createEle(highlightLangEle, item) + } else { + btf.wrap(item, 'figure', { class: 'highlight' }) + createEle('', item) + } + }) + } else { + $figureHighlight.forEach(function (item) { + if (highlightLang) { + let langName = item.getAttribute('class').split(' ')[1] + if (langName === 'plain' || langName === undefined) langName = 'Code' + const highlightLangEle = `
${langName}
` + createEle(highlightLangEle, item, 'hl') + } else { + createEle('', item, 'hl') + } + }) + } + } + + /** + * PhotoFigcaption + */ + function addPhotoFigcaption () { + document.querySelectorAll('#article-container img').forEach(function (item) { + const parentEle = item.parentNode + const altValue = item.title || item.alt + if (altValue && !parentEle.parentNode.classList.contains('justified-gallery')) { + const ele = document.createElement('div') + ele.className = 'img-alt is-center' + ele.textContent = altValue + parentEle.insertBefore(ele, item.nextSibling) + } + }) + } + + /** + * Lightbox + */ + const runLightbox = () => { + btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) + } + + /** + * justified-gallery 圖庫排版 + */ + const runJustifiedGallery = function (ele) { + const htmlStr = arr => { + let str = '' + const replaceDq = str => str.replace(/"/g, '"') // replace double quotes to " + arr.forEach(i => { + const alt = i.alt ? `alt="${replaceDq(i.alt)}"` : '' + const title = i.title ? `title="${replaceDq(i.title)}"` : '' + str += `` + }) + return str + } + + const lazyloadFn = (i, arr, limit) => { + const loadItem = limit + const arrLength = arr.length + if (arrLength > loadItem) i.insertAdjacentHTML('beforeend', htmlStr(arr.splice(0, loadItem))) + else { + i.insertAdjacentHTML('beforeend', htmlStr(arr)) + i.classList.remove('lazyload') + } + return arrLength > loadItem ? loadItem : arrLength + } + + const fetchUrl = async (url) => { + const response = await fetch(url) + return await response.json() + } + + const runJustifiedGallery = (item, arr) => { + if (!item.classList.contains('lazyload')) item.innerHTML = htmlStr(arr) + else { + const limit = item.getAttribute('data-limit') + lazyloadFn(item, arr, limit) + const clickBtnFn = () => { + const lastItemLength = lazyloadFn(item, arr, limit) + fjGallery(item, 'appendImages', item.querySelectorAll(`.fj-gallery-item:nth-last-child(-n+${lastItemLength})`)) + btf.loadLightbox(item.querySelectorAll('img')) + lastItemLength < limit && item.nextElementSibling.removeEventListener('click', clickBtnFn) + } + item.nextElementSibling.addEventListener('click', clickBtnFn) + } + btf.initJustifiedGallery(item) + btf.loadLightbox(item.querySelectorAll('img')) + } + + const addJustifiedGallery = () => { + ele.forEach(item => { + item.classList.contains('url') + ? fetchUrl(item.textContent).then(res => { runJustifiedGallery(item, res) }) + : runJustifiedGallery(item, JSON.parse(item.textContent)) + }) + } + + if (window.fjGallery) { + addJustifiedGallery() + return + } + + getCSS(`${GLOBAL_CONFIG.source.justifiedGallery.css}`) + getScript(`${GLOBAL_CONFIG.source.justifiedGallery.js}`).then(addJustifiedGallery) + } + + /** + * rightside scroll percent + */ + const rightsideScrollPercent = currentTop => { + const perNum = btf.getScrollPercent(currentTop, document.body) + const $goUp = document.getElementById('go-up') + if (perNum < 95) { + $goUp.classList.add('show-percent') + $goUp.querySelector('.scroll-percent').textContent = perNum + } else { + $goUp.classList.remove('show-percent') + } + } + + /** + * 滾動處理 + */ + const scrollFn = function () { + const $rightside = document.getElementById('rightside') + const innerHeight = window.innerHeight + 56 + let initTop = 0 + let isChatShow = true + const $header = document.getElementById('page-header') + const isChatBtn = typeof chatBtn !== 'undefined' + const isShowPercent = GLOBAL_CONFIG.percent.rightside + + // 當滾動條小于 56 的時候 + if (document.body.scrollHeight <= innerHeight) { + $rightside.style.cssText = 'opacity: 1; transform: translateX(-58px)' + return + } + + // find the scroll direction + const scrollDirection = currentTop => { + const result = currentTop > initTop // true is down & false is up + initTop = currentTop + return result + } + + const scrollTask = btf.throttle(() => { + const currentTop = window.scrollY || document.documentElement.scrollTop + const isDown = scrollDirection(currentTop) + if (currentTop > 56) { + if (isDown) { + if ($header.classList.contains('nav-visible')) $header.classList.remove('nav-visible') + if (isChatBtn && isChatShow === true) { + window.chatBtn.hide() + isChatShow = false + } + } else { + if (!$header.classList.contains('nav-visible')) $header.classList.add('nav-visible') + if (isChatBtn && isChatShow === false) { + window.chatBtn.show() + isChatShow = true + } + } + $header.classList.add('nav-fixed') + if (window.getComputedStyle($rightside).getPropertyValue('opacity') === '0') { + $rightside.style.cssText = 'opacity: 0.8; transform: translateX(-58px)' + } + } else { + if (currentTop === 0) { + $header.classList.remove('nav-fixed', 'nav-visible') + } + $rightside.style.cssText = "opacity: ''; transform: ''" + } + + isShowPercent && rightsideScrollPercent(currentTop) + + if (document.body.scrollHeight <= innerHeight) { + $rightside.style.cssText = 'opacity: 0.8; transform: translateX(-58px)' + } + }, 200) + + window.scrollCollect = scrollTask + + window.addEventListener('scroll', scrollCollect) + } + + /** + * toc,anchor + */ + const scrollFnToDo = function () { + const isToc = GLOBAL_CONFIG_SITE.isToc + const isAnchor = GLOBAL_CONFIG.isAnchor + const $article = document.getElementById('article-container') + + if (!($article && (isToc || isAnchor))) return + + let $tocLink, $cardToc, autoScrollToc, $tocPercentage, isExpand + + if (isToc) { + const $cardTocLayout = document.getElementById('card-toc') + $cardToc = $cardTocLayout.getElementsByClassName('toc-content')[0] + $tocLink = $cardToc.querySelectorAll('.toc-link') + $tocPercentage = $cardTocLayout.querySelector('.toc-percentage') + isExpand = $cardToc.classList.contains('is-expand') + + window.mobileToc = { + open: () => { + $cardTocLayout.style.cssText = 'animation: toc-open .3s; opacity: 1; right: 55px' + }, + + close: () => { + $cardTocLayout.style.animation = 'toc-close .2s' + setTimeout(() => { + $cardTocLayout.style.cssText = "opacity:''; animation: ''; right: ''" + }, 100) + } + } + + // toc元素點擊 + $cardToc.addEventListener('click', e => { + e.preventDefault() + const target = e.target.classList + if (target.contains('toc-content')) return + const $target = target.contains('toc-link') + ? e.target + : e.target.parentElement + btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI($target.getAttribute('href')).replace('#', ''))), 300) + if (window.innerWidth < 900) { + window.mobileToc.close() + } + }) + + autoScrollToc = item => { + const activePosition = item.getBoundingClientRect().top + const sidebarScrollTop = $cardToc.scrollTop + if (activePosition > (document.documentElement.clientHeight - 100)) { + $cardToc.scrollTop = sidebarScrollTop + 150 + } + if (activePosition < 100) { + $cardToc.scrollTop = sidebarScrollTop - 150 + } + } + } + + // find head position & add active class + const list = $article.querySelectorAll('h1,h2,h3,h4,h5,h6') + let detectItem = '' + const findHeadPosition = function (top) { + if (top === 0) { + return false + } + + let currentId = '' + let currentIndex = '' + + list.forEach(function (ele, index) { + if (top > btf.getEleTop(ele) - 80) { + const id = ele.id + currentId = id ? '#' + encodeURI(id) : '' + currentIndex = index + } + }) + + if (detectItem === currentIndex) return + + if (isAnchor) btf.updateAnchor(currentId) + + detectItem = currentIndex + + if (isToc) { + $cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') }) + + if (currentId === '') { + return + } + + const currentActive = $tocLink[currentIndex] + currentActive.classList.add('active') + + setTimeout(() => { + autoScrollToc(currentActive) + }, 0) + + if (isExpand) return + let parent = currentActive.parentNode + + for (; !parent.matches('.toc'); parent = parent.parentNode) { + if (parent.matches('li')) parent.classList.add('active') + } + } + } + + // main of scroll + window.tocScrollFn = btf.throttle(() => { + const currentTop = window.scrollY || document.documentElement.scrollTop + if (isToc && GLOBAL_CONFIG.percent.toc) { + $tocPercentage.textContent = btf.getScrollPercent(currentTop, $article) + } + findHeadPosition(currentTop) + }, 100) + + window.addEventListener('scroll', tocScrollFn) + } + + const modeChangeFn = mode => { + if (!window.themeChange) { + return + } + + const turnMode = item => window.themeChange[item](mode) + + Object.keys(window.themeChange).forEach(item => { + if (['disqus', 'disqusjs'].includes(item)) { + setTimeout(() => turnMode(item), 300) + } else { + turnMode(item) + } + }) + } + + /** + * Rightside + */ + const rightSideFn = { + switchReadMode: () => { // read-mode + const $body = document.body + $body.classList.add('read-mode') + const newEle = document.createElement('button') + newEle.type = 'button' + newEle.className = 'fas fa-sign-out-alt exit-readmode' + $body.appendChild(newEle) + + const clickFn = () => { + $body.classList.remove('read-mode') + newEle.remove() + newEle.removeEventListener('click', clickFn) + } + + newEle.addEventListener('click', clickFn) + }, + switchDarkMode: () => { // Switch Between Light And Dark Mode + const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark' + if (willChangeMode === 'dark') { + activateDarkMode() + saveToLocal.set('theme', 'dark', 2) + GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night) + } else { + activateLightMode() + saveToLocal.set('theme', 'light', 2) + GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day) + } + modeChangeFn(willChangeMode) + }, + showOrHideBtn: (e) => { // rightside 點擊設置 按鈕 展開 + const rightsideHideClassList = document.getElementById('rightside-config-hide').classList + rightsideHideClassList.toggle('show') + if (e.classList.contains('show')) { + rightsideHideClassList.add('status') + setTimeout(() => { + rightsideHideClassList.remove('status') + }, 300) + } + e.classList.toggle('show') + }, + scrollToTop: () => { // Back to top + btf.scrollToDest(0, 500) + }, + hideAsideBtn: () => { // Hide aside + const $htmlDom = document.documentElement.classList + const saveStatus = $htmlDom.contains('hide-aside') ? 'show' : 'hide' + saveToLocal.set('aside-status', saveStatus, 2) + $htmlDom.toggle('hide-aside') + }, + runMobileToc: () => { + if (window.getComputedStyle(document.getElementById('card-toc')).getPropertyValue('opacity') === '0') window.mobileToc.open() + else window.mobileToc.close() + }, + toggleChatDisplay: () => { + window.chatBtnFn() + } + } + + document.getElementById('rightside').addEventListener('click', function (e) { + const $target = e.target.id ? e.target : e.target.parentNode + switch ($target.id) { + case 'go-up': + rightSideFn.scrollToTop() + break + case 'rightside_config': + rightSideFn.showOrHideBtn($target) + break + case 'mobile-toc-button': + rightSideFn.runMobileToc() + break + case 'readmode': + rightSideFn.switchReadMode() + break + case 'darkmode': + rightSideFn.switchDarkMode() + break + case 'hide-aside-btn': + rightSideFn.hideAsideBtn() + break + case 'chat-btn': + rightSideFn.toggleChatDisplay() + break + default: + break + } + }) + + /** + * menu + * 側邊欄sub-menu 展開/收縮 + */ + const clickFnOfSubMenu = () => { + document.querySelectorAll('#sidebar-menus .site-page.group').forEach(function (item) { + item.addEventListener('click', function () { + this.classList.toggle('hide') + }) + }) + } + + /** + * 複製時加上版權信息 + */ + const addCopyright = () => { + const copyright = GLOBAL_CONFIG.copyright + document.body.oncopy = (e) => { + e.preventDefault() + const copyFont = window.getSelection(0).toString() + let textFont = copyFont + if (copyFont.length > copyright.limitCount) { + textFont = `${copyFont}\n\n\n${copyright.languages.author}\n${copyright.languages.link}${window.location.href}\n${copyright.languages.source}\n${copyright.languages.info}` + } + if (e.clipboardData) { + return e.clipboardData.setData('text', textFont) + } else { + return window.clipboardData.setData('text', textFont) + } + } + } + + /** + * 網頁運行時間 + */ + const addRuntime = () => { + const $runtimeCount = document.getElementById('runtimeshow') + if ($runtimeCount) { + const publishDate = $runtimeCount.getAttribute('data-publishDate') + $runtimeCount.textContent = `${btf.diffDate(publishDate)} ${GLOBAL_CONFIG.runtime}` + } + } + + /** + * 最後一次更新時間 + */ + const addLastPushDate = () => { + const $lastPushDateItem = document.getElementById('last-push-date') + if ($lastPushDateItem) { + const lastPushDate = $lastPushDateItem.getAttribute('data-lastPushDate') + $lastPushDateItem.textContent = btf.diffDate(lastPushDate, true) + } + } + + /** + * table overflow + */ + const addTableWrap = () => { + const $table = document.querySelectorAll('#article-container :not(.highlight) > table, #article-container > table') + if ($table.length) { + $table.forEach(item => { + btf.wrap(item, 'div', { class: 'table-wrap' }) + }) + } + } + + /** + * tag-hide + */ + const clickFnOfTagHide = function () { + const $hideInline = document.querySelectorAll('#article-container .hide-button') + if ($hideInline.length) { + $hideInline.forEach(function (item) { + item.addEventListener('click', function (e) { + const $this = this + $this.classList.add('open') + const $fjGallery = $this.nextElementSibling.querySelectorAll('.fj-gallery') + $fjGallery.length && btf.initJustifiedGallery($fjGallery) + }) + }) + } + } + + const tabsFn = { + clickFnOfTabs: function () { + document.querySelectorAll('#article-container .tab > button').forEach(function (item) { + item.addEventListener('click', function (e) { + const $this = this + const $tabItem = $this.parentNode + + if (!$tabItem.classList.contains('active')) { + const $tabContent = $tabItem.parentNode.nextElementSibling + const $siblings = btf.siblings($tabItem, '.active')[0] + $siblings && $siblings.classList.remove('active') + $tabItem.classList.add('active') + const tabId = $this.getAttribute('data-href').replace('#', '') + const childList = [...$tabContent.children] + childList.forEach(item => { + if (item.id === tabId) item.classList.add('active') + else item.classList.remove('active') + }) + const $isTabJustifiedGallery = $tabContent.querySelectorAll(`#${tabId} .fj-gallery`) + if ($isTabJustifiedGallery.length > 0) { + btf.initJustifiedGallery($isTabJustifiedGallery) + } + } + }) + }) + }, + backToTop: () => { + document.querySelectorAll('#article-container .tabs .tab-to-top').forEach(function (item) { + item.addEventListener('click', function () { + btf.scrollToDest(btf.getEleTop(btf.getParents(this, '.tabs')), 300) + }) + }) + } + } + + const toggleCardCategory = function () { + const $cardCategory = document.querySelectorAll('#aside-cat-list .card-category-list-item.parent i') + if ($cardCategory.length) { + $cardCategory.forEach(function (item) { + item.addEventListener('click', function (e) { + e.preventDefault() + const $this = this + $this.classList.toggle('expand') + const $parentEle = $this.parentNode.nextElementSibling + if (btf.isHidden($parentEle)) { + $parentEle.style.display = 'block' + } else { + $parentEle.style.display = 'none' + } + }) + }) + } + } + + const switchComments = function () { + let switchDone = false + const $switchBtn = document.querySelector('#comment-switch > .switch-btn') + $switchBtn && $switchBtn.addEventListener('click', function () { + this.classList.toggle('move') + document.querySelectorAll('#post-comment > .comment-wrap > div').forEach(function (item) { + if (btf.isHidden(item)) { + item.style.cssText = 'display: block;animation: tabshow .5s' + } else { + item.style.cssText = "display: none;animation: ''" + } + }) + + if (!switchDone && typeof loadOtherComment === 'function') { + switchDone = true + loadOtherComment() + } + }) + } + + const addPostOutdateNotice = function () { + const data = GLOBAL_CONFIG.noticeOutdate + const diffDay = btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate) + if (diffDay >= data.limitDay) { + const ele = document.createElement('div') + ele.className = 'post-outdate-notice' + ele.textContent = data.messagePrev + ' ' + diffDay + ' ' + data.messageNext + const $targetEle = document.getElementById('article-container') + if (data.position === 'top') { + $targetEle.insertBefore(ele, $targetEle.firstChild) + } else { + $targetEle.appendChild(ele) + } + } + } + + const lazyloadImg = () => { + window.lazyLoadInstance = new LazyLoad({ + elements_selector: 'img', + threshold: 0, + data_src: 'lazy-src' + }) + } + + const relativeDate = function (selector) { + selector.forEach(item => { + const timeVal = item.getAttribute('datetime') + item.textContent = btf.diffDate(timeVal, true) + item.style.display = 'inline' + }) + } + + const unRefreshFn = function () { + window.addEventListener('resize', () => { + adjustMenu(false) + btf.isHidden(document.getElementById('toggle-menu')) && mobileSidebarOpen && sidebarFn.close() + }) + + document.getElementById('menu-mask').addEventListener('click', e => { sidebarFn.close() }) + + clickFnOfSubMenu() + GLOBAL_CONFIG.islazyload && lazyloadImg() + GLOBAL_CONFIG.copyright !== undefined && addCopyright() + + if (GLOBAL_CONFIG.autoDarkmode) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { + if (saveToLocal.get('theme') !== undefined) return + e.matches ? modeChangeFn('dark') : modeChangeFn('light') + }) + } + } + + window.refreshFn = function () { + initAdjust() + + if (GLOBAL_CONFIG_SITE.isPost) { + GLOBAL_CONFIG.noticeOutdate !== undefined && addPostOutdateNotice() + GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time')) + } else { + GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time')) + GLOBAL_CONFIG.runtime && addRuntime() + addLastPushDate() + toggleCardCategory() + } + + scrollFnToDo() + GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex() + addHighlightTool() + GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption() + scrollFn() + + const $jgEle = document.querySelectorAll('#article-container .fj-gallery') + $jgEle.length && runJustifiedGallery($jgEle) + + runLightbox() + addTableWrap() + clickFnOfTagHide() + tabsFn.clickFnOfTabs() + tabsFn.backToTop() + switchComments() + document.getElementById('toggle-menu').addEventListener('click', () => { sidebarFn.open() }) + } + + refreshFn() + unRefreshFn() +}) diff --git a/js/search/algolia.js b/js/search/algolia.js new file mode 100644 index 000000000..9ac9f94ea --- /dev/null +++ b/js/search/algolia.js @@ -0,0 +1,177 @@ +window.addEventListener('load', () => { + const $searchMask = document.getElementById('search-mask') + const $searchDialog = document.querySelector('#algolia-search .search-dialog') + + const openSearch = () => { + const bodyStyle = document.body.style + bodyStyle.width = '100%' + bodyStyle.overflow = 'hidden' + btf.animateIn($searchMask, 'to_show 0.5s') + btf.animateIn($searchDialog, 'titleScale 0.5s') + setTimeout(() => { document.querySelector('#algolia-search .ais-SearchBox-input').focus() }, 100) + + // shortcut: ESC + document.addEventListener('keydown', function f (event) { + if (event.code === 'Escape') { + closeSearch() + document.removeEventListener('keydown', f) + } + }) + + fixSafariHeight() + window.addEventListener('resize', fixSafariHeight) + } + + const closeSearch = () => { + const bodyStyle = document.body.style + bodyStyle.width = '' + bodyStyle.overflow = '' + btf.animateOut($searchDialog, 'search_close .5s') + btf.animateOut($searchMask, 'to_hide 0.5s') + window.removeEventListener('resize', fixSafariHeight) + } + + // fix safari + const fixSafariHeight = () => { + if (window.innerWidth < 768) { + $searchDialog.style.setProperty('--search-height', window.innerHeight + 'px') + } + } + + const searchClickFn = () => { + document.querySelector('#search-button > .search').addEventListener('click', openSearch) + } + + const searchFnOnce = () => { + $searchMask.addEventListener('click', closeSearch) + document.querySelector('#algolia-search .search-close-button').addEventListener('click', closeSearch) + } + + const cutContent = content => { + if (content === '') return '' + + const firstOccur = content.indexOf('') + + let start = firstOccur - 30 + let end = firstOccur + 120 + let pre = '' + let post = '' + + if (start <= 0) { + start = 0 + end = 140 + } else { + pre = '...' + } + + if (end > content.length) { + end = content.length + } else { + post = '...' + } + + const matchContent = pre + content.substring(start, end) + post + return matchContent + } + + const algolia = GLOBAL_CONFIG.algolia + const isAlgoliaValid = algolia.appId && algolia.apiKey && algolia.indexName + if (!isAlgoliaValid) { + return console.error('Algolia setting is invalid!') + } + + const search = instantsearch({ + indexName: algolia.indexName, + /* global algoliasearch */ + searchClient: algoliasearch(algolia.appId, algolia.apiKey), + searchFunction (helper) { + helper.state.query && helper.search() + } + }) + + const configure = instantsearch.widgets.configure({ + hitsPerPage: 5 + }) + + const searchBox = instantsearch.widgets.searchBox({ + container: '#algolia-search-input', + showReset: false, + showSubmit: false, + placeholder: GLOBAL_CONFIG.algolia.languages.input_placeholder, + showLoadingIndicator: true + }) + + const hits = instantsearch.widgets.hits({ + container: '#algolia-hits', + templates: { + item (data) { + const link = data.permalink ? data.permalink : (GLOBAL_CONFIG.root + data.path) + const result = data._highlightResult + const content = result.contentStripTruncate + ? cutContent(result.contentStripTruncate.value) + : result.contentStrip + ? cutContent(result.contentStrip.value) + : result.content + ? cutContent(result.content.value) + : '' + return ` + + ${result.title.value || 'no-title'} +

${content}

+
` + }, + empty: function (data) { + return ( + '
' + + GLOBAL_CONFIG.algolia.languages.hits_empty.replace(/\$\{query}/, data.query) + + '
' + ) + } + } + }) + + const stats = instantsearch.widgets.stats({ + container: '#algolia-info > .algolia-stats', + templates: { + text: function (data) { + const stats = GLOBAL_CONFIG.algolia.languages.hits_stats + .replace(/\$\{hits}/, data.nbHits) + .replace(/\$\{time}/, data.processingTimeMS) + return ( + `
${stats}` + ) + } + } + }) + + const powerBy = instantsearch.widgets.poweredBy({ + container: '#algolia-info > .algolia-poweredBy' + }) + + const pagination = instantsearch.widgets.pagination({ + container: '#algolia-pagination', + totalPages: 5, + templates: { + first: '', + last: '', + previous: '', + next: '' + } + }) + + search.addWidgets([configure, searchBox, hits, stats, powerBy, pagination]) // add the widgets to the instantsearch instance + + search.start() + + searchClickFn() + searchFnOnce() + + window.addEventListener('pjax:complete', () => { + !btf.isHidden($searchMask) && closeSearch() + searchClickFn() + }) + + window.pjax && search.on('render', () => { + window.pjax.refresh(document.getElementById('algolia-hits')) + }) +}) diff --git a/js/search/local-search.js b/js/search/local-search.js new file mode 100644 index 000000000..7a22a0766 --- /dev/null +++ b/js/search/local-search.js @@ -0,0 +1,360 @@ +/** + * Refer to hexo-generator-searchdb + * https://github.com/next-theme/hexo-generator-searchdb/blob/main/dist/search.js + * Modified by hexo-theme-butterfly + */ + +class LocalSearch { + constructor ({ + path = '', + unescape = false, + top_n_per_article = 1 + }) { + this.path = path + this.unescape = unescape + this.top_n_per_article = top_n_per_article + this.isfetched = false + this.datas = null + } + + getIndexByWord (words, text, caseSensitive = false) { + const index = [] + const included = new Set() + + if (!caseSensitive) { + text = text.toLowerCase() + } + words.forEach(word => { + if (this.unescape) { + const div = document.createElement('div') + div.innerText = word + word = div.innerHTML + } + const wordLen = word.length + if (wordLen === 0) return + let startPosition = 0 + let position = -1 + if (!caseSensitive) { + word = word.toLowerCase() + } + while ((position = text.indexOf(word, startPosition)) > -1) { + index.push({ position, word }) + included.add(word) + startPosition = position + wordLen + } + }) + // Sort index by position of keyword + index.sort((left, right) => { + if (left.position !== right.position) { + return left.position - right.position + } + return right.word.length - left.word.length + }) + return [index, included] + } + + // Merge hits into slices + mergeIntoSlice (start, end, index) { + let item = index[0] + let { position, word } = item + const hits = [] + const count = new Set() + while (position + word.length <= end && index.length !== 0) { + count.add(word) + hits.push({ + position, + length: word.length + }) + const wordEnd = position + word.length + + // Move to next position of hit + index.shift() + while (index.length !== 0) { + item = index[0] + position = item.position + word = item.word + if (wordEnd > position) { + index.shift() + } else { + break + } + } + } + return { + hits, + start, + end, + count: count.size + } + } + + // Highlight title and content + highlightKeyword (val, slice) { + let result = '' + let index = slice.start + for (const { position, length } of slice.hits) { + result += val.substring(index, position) + index = position + length + result += `${val.substr(position, length)}` + } + result += val.substring(index, slice.end) + return result + } + + getResultItems (keywords) { + const resultItems = [] + this.datas.forEach(({ title, content, url }) => { + // The number of different keywords included in the article. + const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title) + const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content) + const includedCount = new Set([...keysOfTitle, ...keysOfContent]).size + + // Show search results + const hitCount = indexOfTitle.length + indexOfContent.length + if (hitCount === 0) return + + const slicesOfTitle = [] + if (indexOfTitle.length !== 0) { + slicesOfTitle.push(this.mergeIntoSlice(0, title.length, indexOfTitle)) + } + + let slicesOfContent = [] + while (indexOfContent.length !== 0) { + const item = indexOfContent[0] + const { position } = item + // Cut out 120 characters. The maxlength of .search-input is 80. + const start = Math.max(0, position - 20) + const end = Math.min(content.length, position + 100) + slicesOfContent.push(this.mergeIntoSlice(start, end, indexOfContent)) + } + + // Sort slices in content by included keywords' count and hits' count + slicesOfContent.sort((left, right) => { + if (left.count !== right.count) { + return right.count - left.count + } else if (left.hits.length !== right.hits.length) { + return right.hits.length - left.hits.length + } + return left.start - right.start + }) + + // Select top N slices in content + const upperBound = parseInt(this.top_n_per_article, 10) + if (upperBound >= 0) { + slicesOfContent = slicesOfContent.slice(0, upperBound) + } + + let resultItem = '' + + url = new URL(url, location.origin) + url.searchParams.append('highlight', keywords.join(' ')) + + if (slicesOfTitle.length !== 0) { + resultItem += `
${this.highlightKeyword(title, slicesOfTitle[0])}` + } else { + resultItem += `' + resultItems.push({ + item: resultItem, + id: resultItems.length, + hitCount, + includedCount + }) + }) + return resultItems + } + + fetchData () { + const isXml = !this.path.endsWith('json') + fetch(this.path) + .then(response => response.text()) + .then(res => { + // Get the contents from search data + this.isfetched = true + this.datas = isXml + ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => ({ + title: element.querySelector('title').textContent, + content: element.querySelector('content').textContent, + url: element.querySelector('url').textContent + })) + : JSON.parse(res) + // Only match articles with non-empty titles + this.datas = this.datas.filter(data => data.title).map(data => { + data.title = data.title.trim() + data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '' + data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/') + return data + }) + // Remove loading animation + window.dispatchEvent(new Event('search:loaded')) + }) + } + + // Highlight by wrapping node in mark elements with the given class name + highlightText (node, slice, className) { + const val = node.nodeValue + let index = slice.start + const children = [] + for (const { position, length } of slice.hits) { + const text = document.createTextNode(val.substring(index, position)) + index = position + length + const mark = document.createElement('mark') + mark.className = className + mark.appendChild(document.createTextNode(val.substr(position, length))) + children.push(text, mark) + } + node.nodeValue = val.substring(index, slice.end) + children.forEach(element => { + node.parentNode.insertBefore(element, node) + }) + } + + // Highlight the search words provided in the url in the text + highlightSearchWords (body) { + const params = new URL(location.href).searchParams.get('highlight') + const keywords = params ? params.split(' ') : [] + if (!keywords.length || !body) return + const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null) + const allNodes = [] + while (walk.nextNode()) { + if (!walk.currentNode.parentNode.matches('button, select, textarea, .mermaid')) allNodes.push(walk.currentNode) + } + allNodes.forEach(node => { + const [indexOfNode] = this.getIndexByWord(keywords, node.nodeValue) + if (!indexOfNode.length) return + const slice = this.mergeIntoSlice(0, node.nodeValue.length, indexOfNode) + this.highlightText(node, slice, 'search-keyword') + }) + } +} + +window.addEventListener('load', () => { +// Search + const { path, top_n_per_article, unescape, languages } = GLOBAL_CONFIG.localSearch + const localSearch = new LocalSearch({ + path, + top_n_per_article, + unescape + }) + + const input = document.querySelector('#local-search-input input') + const statsItem = document.getElementById('local-search-stats-wrap') + const $loadingStatus = document.getElementById('loading-status') + + const inputEventFunction = () => { + if (!localSearch.isfetched) return + const searchText = input.value.trim().toLowerCase() + if (searchText !== '') $loadingStatus.innerHTML = '' + const keywords = searchText.split(/[-\s]+/) + const container = document.getElementById('local-search-results') + let resultItems = [] + if (searchText.length > 0) { + // Perform local searching + resultItems = localSearch.getResultItems(keywords) + } + if (keywords.length === 1 && keywords[0] === '') { + container.classList.add('no-result') + container.textContent = '' + } else if (resultItems.length === 0) { + container.textContent = '' + statsItem.innerHTML = `
${languages.hits_empty.replace(/\$\{query}/, searchText)}
` + } else { + resultItems.sort((left, right) => { + if (left.includedCount !== right.includedCount) { + return right.includedCount - left.includedCount + } else if (left.hitCount !== right.hitCount) { + return right.hitCount - left.hitCount + } + return right.id - left.id + }) + + const stats = languages.hits_stats.replace(/\$\{hits}/, resultItems.length) + + container.classList.remove('no-result') + container.innerHTML = `
${resultItems.map(result => result.item).join('')}
` + statsItem.innerHTML = `
${stats}
` + window.pjax && window.pjax.refresh(container) + } + + $loadingStatus.textContent = '' + } + + let loadFlag = false + const $searchMask = document.getElementById('search-mask') + const $searchDialog = document.querySelector('#local-search .search-dialog') + + // fix safari + const fixSafariHeight = () => { + if (window.innerWidth < 768) { + $searchDialog.style.setProperty('--search-height', window.innerHeight + 'px') + } + } + + const openSearch = () => { + const bodyStyle = document.body.style + bodyStyle.width = '100%' + bodyStyle.overflow = 'hidden' + btf.animateIn($searchMask, 'to_show 0.5s') + btf.animateIn($searchDialog, 'titleScale 0.5s') + setTimeout(() => { input.focus() }, 300) + if (!loadFlag) { + !localSearch.isfetched && localSearch.fetchData() + input.addEventListener('input', inputEventFunction) + loadFlag = true + } + // shortcut: ESC + document.addEventListener('keydown', function f (event) { + if (event.code === 'Escape') { + closeSearch() + document.removeEventListener('keydown', f) + } + }) + + fixSafariHeight() + window.addEventListener('resize', fixSafariHeight) + } + + const closeSearch = () => { + const bodyStyle = document.body.style + bodyStyle.width = '' + bodyStyle.overflow = '' + btf.animateOut($searchDialog, 'search_close .5s') + btf.animateOut($searchMask, 'to_hide 0.5s') + window.removeEventListener('resize', fixSafariHeight) + } + + const searchClickFn = () => { + document.querySelector('#search-button > .search').addEventListener('click', openSearch) + } + + const searchFnOnce = () => { + document.querySelector('#local-search .search-close-button').addEventListener('click', closeSearch) + $searchMask.addEventListener('click', closeSearch) + if (GLOBAL_CONFIG.localSearch.preload) { + localSearch.fetchData() + } + localSearch.highlightSearchWords(document.getElementById('article-container')) + } + + window.addEventListener('search:loaded', () => { + const $loadDataItem = document.getElementById('loading-database') + $loadDataItem.nextElementSibling.style.display = 'block' + $loadDataItem.remove() + }) + + searchClickFn() + searchFnOnce() + + // pjax + window.addEventListener('pjax:complete', () => { + !btf.isHidden($searchMask) && closeSearch() + localSearch.highlightSearchWords(document.getElementById('article-container')) + searchClickFn() + }) +}) diff --git a/js/tw_cn.js b/js/tw_cn.js new file mode 100644 index 000000000..c27db8f2d --- /dev/null +++ b/js/tw_cn.js @@ -0,0 +1,115 @@ +document.addEventListener('DOMContentLoaded', function () { + const { defaultEncoding, translateDelay, msgToTraditionalChinese, msgToSimplifiedChinese } = GLOBAL_CONFIG.translate + const snackbarData = GLOBAL_CONFIG.Snackbar + let currentEncoding = defaultEncoding + const targetEncodingCookie = 'translate-chn-cht' + let targetEncoding = + saveToLocal.get(targetEncodingCookie) === undefined + ? defaultEncoding + : Number(saveToLocal.get('translate-chn-cht')) + let translateButtonObject + const isSnackbar = snackbarData !== undefined + + function setLang () { + document.documentElement.lang = targetEncoding === 1 ? 'zh-TW' : 'zh-CN' + } + + function translateText (txt) { + if (txt === '' || txt == null) return '' + if (currentEncoding === 1 && targetEncoding === 2) return Simplized(txt) + else if (currentEncoding === 2 && targetEncoding === 1) { + return Traditionalized(txt) + } else return txt + } + function translateBody (fobj) { + let objs + if (typeof fobj === 'object') objs = fobj.childNodes + else objs = document.body.childNodes + for (let i = 0; i < objs.length; i++) { + const obj = objs.item(i) + if ( + '||BR|HR|'.indexOf('|' + obj.tagName + '|') > 0 || + obj === translateButtonObject + ) { + continue + } + if (obj.title !== '' && obj.title != null) { + obj.title = translateText(obj.title) + } + if (obj.alt !== '' && obj.alt != null) obj.alt = translateText(obj.alt) + if (obj.placeholder !== '' && obj.placeholder != null) { obj.placeholder = translateText(obj.placeholder) } + if ( + obj.tagName === 'INPUT' && + obj.value !== '' && + obj.type !== 'text' && + obj.type !== 'hidden' + ) { + obj.value = translateText(obj.value) + } + if (obj.nodeType === 3) obj.data = translateText(obj.data) + else translateBody(obj) + } + } + function translatePage () { + if (targetEncoding === 1) { + currentEncoding = 1 + targetEncoding = 2 + translateButtonObject.textContent = msgToTraditionalChinese + isSnackbar && btf.snackbarShow(snackbarData.cht_to_chs) + } else if (targetEncoding === 2) { + currentEncoding = 2 + targetEncoding = 1 + translateButtonObject.textContent = msgToSimplifiedChinese + isSnackbar && btf.snackbarShow(snackbarData.chs_to_cht) + } + saveToLocal.set(targetEncodingCookie, targetEncoding, 2) + setLang() + translateBody() + } + + function JTPYStr () { + return '万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗姜娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岳岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪漤潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊犟状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾龙历志制一台皋准复猛钟注范签' + } + function FTPYStr () { + return '萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳曆厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備複夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍薑婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺嶽崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦濫瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋裏钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽龍歷誌製壹臺臯準復勐鐘註範籤' + } + function Traditionalized (cc) { + let str = '' + const ss = JTPYStr() + const tt = FTPYStr() + for (let i = 0; i < cc.length; i++) { + if (cc.charCodeAt(i) > 10000 && ss.indexOf(cc.charAt(i)) !== -1) { + str += tt.charAt(ss.indexOf(cc.charAt(i))) + } else str += cc.charAt(i) + } + return str + } + function Simplized (cc) { + let str = '' + const ss = JTPYStr() + const tt = FTPYStr() + for (let i = 0; i < cc.length; i++) { + if (cc.charCodeAt(i) > 10000 && tt.indexOf(cc.charAt(i)) !== -1) { + str += ss.charAt(tt.indexOf(cc.charAt(i))) + } else str += cc.charAt(i) + } + return str + } + + function translateInitialization () { + translateButtonObject = document.getElementById('translateLink') + if (translateButtonObject) { + if (currentEncoding !== targetEncoding) { + translateButtonObject.textContent = + targetEncoding === 1 + ? msgToSimplifiedChinese + : msgToTraditionalChinese + setLang() + setTimeout(translateBody, translateDelay) + } + translateButtonObject.addEventListener('click', translatePage, false) + } + } + translateInitialization() + document.addEventListener('pjax:complete', translateInitialization) +}) diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 000000000..adff39ca8 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,307 @@ +const btf = { + debounce: function (func, wait, immediate) { + let timeout + return function () { + const context = this + const args = arguments + const later = function () { + timeout = null + if (!immediate) func.apply(context, args) + } + const callNow = immediate && !timeout + clearTimeout(timeout) + timeout = setTimeout(later, wait) + if (callNow) func.apply(context, args) + } + }, + + throttle: function (func, wait, options) { + let timeout, context, args + let previous = 0 + if (!options) options = {} + + const later = function () { + previous = options.leading === false ? 0 : new Date().getTime() + timeout = null + func.apply(context, args) + if (!timeout) context = args = null + } + + const throttled = function () { + const now = new Date().getTime() + if (!previous && options.leading === false) previous = now + const remaining = wait - (now - previous) + context = this + args = arguments + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout) + timeout = null + } + previous = now + func.apply(context, args) + if (!timeout) context = args = null + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining) + } + } + + return throttled + }, + + sidebarPaddingR: () => { + const innerWidth = window.innerWidth + const clientWidth = document.body.clientWidth + const paddingRight = innerWidth - clientWidth + if (innerWidth !== clientWidth) { + document.body.style.paddingRight = paddingRight + 'px' + } + }, + + snackbarShow: (text, showAction = false, duration = 2000) => { + const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar + const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark + Snackbar.show({ + text, + backgroundColor: bg, + showAction, + duration, + pos: position, + customClass: 'snackbar-css' + }) + }, + + diffDate: (d, more = false) => { + const dateNow = new Date() + const datePost = new Date(d) + const dateDiff = dateNow.getTime() - datePost.getTime() + const minute = 1000 * 60 + const hour = minute * 60 + const day = hour * 24 + const month = day * 30 + const { dateSuffix } = GLOBAL_CONFIG + + if (!more) return parseInt(dateDiff / day) + + const monthCount = dateDiff / month + const dayCount = dateDiff / day + const hourCount = dateDiff / hour + const minuteCount = dateDiff / minute + + if (monthCount > 12) return datePost.toISOString().slice(0, 10) + if (monthCount >= 1) return `${parseInt(monthCount)} ${dateSuffix.month}` + if (dayCount >= 1) return `${parseInt(dayCount)} ${dateSuffix.day}` + if (hourCount >= 1) return `${parseInt(hourCount)} ${dateSuffix.hour}` + if (minuteCount >= 1) return `${parseInt(minuteCount)} ${dateSuffix.min}` + return dateSuffix.just + }, + + loadComment: (dom, callback) => { + if ('IntersectionObserver' in window) { + const observerItem = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + callback() + observerItem.disconnect() + } + }, { threshold: [0] }) + observerItem.observe(dom) + } else { + callback() + } + }, + + scrollToDest: (pos, time = 500) => { + const currentPos = window.pageYOffset + const isNavFixed = document.getElementById('page-header').classList.contains('fixed') + if (currentPos > pos || isNavFixed) pos = pos - 70 + + if ('scrollBehavior' in document.documentElement.style) { + window.scrollTo({ + top: pos, + behavior: 'smooth' + }) + return + } + + let start = null + pos = +pos + window.requestAnimationFrame(function step (currentTime) { + start = !start ? currentTime : start + const progress = currentTime - start + if (currentPos < pos) { + window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos) + } else { + window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time)) + } + if (progress < time) { + window.requestAnimationFrame(step) + } else { + window.scrollTo(0, pos) + } + }) + }, + + animateIn: (ele, text) => { + ele.style.display = 'block' + ele.style.animation = text + }, + + animateOut: (ele, text) => { + ele.addEventListener('animationend', function f () { + ele.style.display = '' + ele.style.animation = '' + ele.removeEventListener('animationend', f) + }) + ele.style.animation = text + }, + + getParents: (elem, selector) => { + for (; elem && elem !== document; elem = elem.parentNode) { + if (elem.matches(selector)) return elem + } + return null + }, + + siblings: (ele, selector) => { + return [...ele.parentNode.children].filter((child) => { + if (selector) { + return child !== ele && child.matches(selector) + } + return child !== ele + }) + }, + + /** + * @param {*} selector + * @param {*} eleType the type of create element + * @param {*} options object key: value + */ + wrap: (selector, eleType, options) => { + const createEle = document.createElement(eleType) + for (const [key, value] of Object.entries(options)) { + createEle.setAttribute(key, value) + } + selector.parentNode.insertBefore(createEle, selector) + createEle.appendChild(selector) + }, + + unwrap: el => { + const parent = el.parentNode + if (parent && parent !== document.body) { + parent.replaceChild(el, parent) + } + }, + + isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0, + + getEleTop: ele => { + let actualTop = ele.offsetTop + let current = ele.offsetParent + + while (current !== null) { + actualTop += current.offsetTop + current = current.offsetParent + } + + return actualTop + }, + + loadLightbox: ele => { + const service = GLOBAL_CONFIG.lightbox + + if (service === 'mediumZoom') { + mediumZoom(ele, { background: 'var(--zoom-bg)' }) + } + + if (service === 'fancybox') { + ele.forEach(i => { + if (i.parentNode.tagName !== 'A') { + const dataSrc = i.dataset.lazySrc || i.src + const dataCaption = i.title || i.alt || '' + btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb': dataSrc }) + } + }) + + if (!window.fancyboxRun) { + Fancybox.bind('[data-fancybox]', { + Hash: false, + Thumbs: { + showOnStart: false + }, + Images: { + Panzoom: { + maxScale: 4 + } + }, + Carousel: { + transition: 'slide' + }, + Toolbar: { + display: { + left: ['infobar'], + middle: [ + 'zoomIn', + 'zoomOut', + 'toggle1to1', + 'rotateCCW', + 'rotateCW', + 'flipX', + 'flipY' + ], + right: ['slideshow', 'thumbs', 'close'] + } + } + }) + window.fancyboxRun = true + } + } + }, + + initJustifiedGallery: function (selector) { + const runJustifiedGallery = i => { + if (!btf.isHidden(i)) { + fjGallery(i, { + itemSelector: '.fj-gallery-item', + rowHeight: i.getAttribute('data-rowHeight'), + gutter: 4, + onJustify: function () { + this.$container.style.opacity = '1' + } + }) + } + } + + if (Array.from(selector).length === 0) runJustifiedGallery(selector) + else selector.forEach(i => { runJustifiedGallery(i) }) + }, + + updateAnchor: (anchor) => { + if (anchor !== window.location.hash) { + if (!anchor) anchor = location.pathname + const title = GLOBAL_CONFIG_SITE.title + window.history.replaceState({ + url: location.href, + title + }, title, anchor) + } + }, + + getScrollPercent: (currentTop, ele) => { + const docHeight = ele.clientHeight + const winHeight = document.documentElement.clientHeight + const headerHeight = ele.offsetTop + const contentMath = (docHeight > winHeight) ? (docHeight - winHeight) : (document.documentElement.scrollHeight - winHeight) + const scrollPercent = (currentTop - headerHeight) / (contentMath) + const scrollPercentRounded = Math.round(scrollPercent * 100) + const percentage = (scrollPercentRounded > 100) ? 100 : (scrollPercentRounded <= 0) ? 0 : scrollPercentRounded + return percentage + }, + + addModeChange: (name, fn) => { + if (window.themeChange && window.themeChange[name]) return + window.themeChange = { + ...window.themeChange, + [name]: fn + } + } +} diff --git a/lib/hbe.js b/lib/hbe.js new file mode 100644 index 000000000..71205dd75 --- /dev/null +++ b/lib/hbe.js @@ -0,0 +1,297 @@ +(() => { + 'use strict'; + + const cryptoObj = window.crypto || window.msCrypto; + const storage = window.localStorage; + + const storageName = 'hexo-blog-encrypt:#' + window.location.pathname; + const keySalt = textToArray('hexo-blog-encrypt的作者们都是大帅比!'); + const ivSalt = textToArray('hexo-blog-encrypt是地表最强Hexo加密插件!'); + +// As we can't detect the wrong password with AES-CBC, +// so adding an empty div and check it when decrption. +const knownPrefix = ""; + + const mainElement = document.getElementById('hexo-blog-encrypt'); + const wrongPassMessage = mainElement.dataset['wpm']; + const wrongHashMessage = mainElement.dataset['whm']; + const dataElement = mainElement.getElementsByTagName('script')['hbeData']; + const encryptedData = dataElement.innerText; + const HmacDigist = dataElement.dataset['hmacdigest']; + + function hexToArray(s) { + return new Uint8Array(s.match(/[\da-f]{2}/gi).map((h => { + return parseInt(h, 16); + }))); + } + + function textToArray(s) { + var i = s.length; + var n = 0; + var ba = new Array() + + for (var j = 0; j < i;) { + var c = s.codePointAt(j); + if (c < 128) { + ba[n++] = c; + j++; + } else if ((c > 127) && (c < 2048)) { + ba[n++] = (c >> 6) | 192; + ba[n++] = (c & 63) | 128; + j++; + } else if ((c > 2047) && (c < 65536)) { + ba[n++] = (c >> 12) | 224; + ba[n++] = ((c >> 6) & 63) | 128; + ba[n++] = (c & 63) | 128; + j++; + } else { + ba[n++] = (c >> 18) | 240; + ba[n++] = ((c >> 12) & 63) | 128; + ba[n++] = ((c >> 6) & 63) | 128; + ba[n++] = (c & 63) | 128; + j += 2; + } + } + return new Uint8Array(ba); + } + + function arrayBufferToHex(arrayBuffer) { + if (typeof arrayBuffer !== 'object' || arrayBuffer === null || typeof arrayBuffer.byteLength !== 'number') { + throw new TypeError('Expected input to be an ArrayBuffer') + } + + var view = new Uint8Array(arrayBuffer) + var result = '' + var value + + for (var i = 0; i < view.length; i++) { + value = view[i].toString(16) + result += (value.length === 1 ? '0' + value : value) + } + + return result + } + + async function getExecutableScript(oldElem) { + let out = document.createElement('script'); + const attList = ['type', 'text', 'src', 'crossorigin', 'defer', 'referrerpolicy']; + attList.forEach((att) => { + if (oldElem[att]) + out[att] = oldElem[att]; + }) + + return out; + } + + async function convertHTMLToElement(content) { + let out = document.createElement('div'); + out.innerHTML = content; + out.querySelectorAll('script').forEach(async (elem) => { + elem.replaceWith(await getExecutableScript(elem)); + }); + + return out; + } + + function getKeyMaterial(password) { + let encoder = new TextEncoder(); + return cryptoObj.subtle.importKey( + 'raw', + encoder.encode(password), + { + 'name': 'PBKDF2', + }, + false, + [ + 'deriveKey', + 'deriveBits', + ] + ); + } + + function getHmacKey(keyMaterial) { + return cryptoObj.subtle.deriveKey({ + 'name': 'PBKDF2', + 'hash': 'SHA-256', + 'salt': keySalt.buffer, + 'iterations': 1024 + }, keyMaterial, { + 'name': 'HMAC', + 'hash': 'SHA-256', + 'length': 256, + }, true, [ + 'verify', + ]); + } + + function getDecryptKey(keyMaterial) { + return cryptoObj.subtle.deriveKey({ + 'name': 'PBKDF2', + 'hash': 'SHA-256', + 'salt': keySalt.buffer, + 'iterations': 1024, + }, keyMaterial, { + 'name': 'AES-CBC', + 'length': 256, + }, true, [ + 'decrypt', + ]); + } + + function getIv(keyMaterial) { + return cryptoObj.subtle.deriveBits({ + 'name': 'PBKDF2', + 'hash': 'SHA-256', + 'salt': ivSalt.buffer, + 'iterations': 512, + }, keyMaterial, 16 * 8); + } + + async function verifyContent(key, content) { + const encoder = new TextEncoder(); + const encoded = encoder.encode(content); + + let signature = hexToArray(HmacDigist); + + const result = await cryptoObj.subtle.verify({ + 'name': 'HMAC', + 'hash': 'SHA-256', + }, key, signature, encoded); + console.log(`Verification result: ${result}`); + if (!result) { + alert(wrongHashMessage); + console.log(`${wrongHashMessage}, got `, signature, ` but proved wrong.`); + } + return result; + } + + async function decrypt(decryptKey, iv, hmacKey) { + let typedArray = hexToArray(encryptedData); + + const result = await cryptoObj.subtle.decrypt({ + 'name': 'AES-CBC', + 'iv': iv, + }, decryptKey, typedArray.buffer).then(async (result) => { + const decoder = new TextDecoder(); + const decoded = decoder.decode(result); + + // check the prefix, if not then we can sure here is wrong password. + if (!decoded.startsWith(knownPrefix)) { + throw "Decode successfully but not start with KnownPrefix."; + } + + const hideButton = document.createElement('button'); + hideButton.textContent = 'Encrypt again'; + hideButton.type = 'button'; + hideButton.classList.add("hbe-button"); + hideButton.addEventListener('click', () => { + window.localStorage.removeItem(storageName); + window.location.reload(); + }); + + document.getElementById('hexo-blog-encrypt').style.display = 'inline'; + document.getElementById('hexo-blog-encrypt').innerHTML = ''; + document.getElementById('hexo-blog-encrypt').appendChild(await convertHTMLToElement(decoded)); + document.getElementById('hexo-blog-encrypt').appendChild(hideButton); + + // support html5 lazyload functionality. + document.querySelectorAll('img').forEach((elem) => { + if (elem.getAttribute("data-src") && !elem.src) { + elem.src = elem.getAttribute('data-src'); + } + }); + + // support theme-next refresh + window.NexT && NexT.boot && typeof NexT.boot.refresh === 'function' && NexT.boot.refresh(); + + // TOC part + var tocDiv = document.getElementById("toc-div"); + if (tocDiv) { + tocDiv.style.display = 'inline'; + } + + var tocDivs = document.getElementsByClassName('toc-div-class'); + if (tocDivs && tocDivs.length > 0) { + for (var idx = 0; idx < tocDivs.length; idx++) { + tocDivs[idx].style.display = 'inline'; + } + } + + // trigger event + var event = new Event('hexo-blog-decrypt'); + window.dispatchEvent(event); + + return await verifyContent(hmacKey, decoded); + }).catch((e) => { + alert(wrongPassMessage); + console.log(e); + return false; + }); + + return result; + + } + + function hbeLoader() { + + const oldStorageData = JSON.parse(storage.getItem(storageName)); + + if (oldStorageData) { + console.log(`Password got from localStorage(${storageName}): `, oldStorageData); + + const sIv = hexToArray(oldStorageData.iv).buffer; + const sDk = oldStorageData.dk; + const sHmk = oldStorageData.hmk; + + cryptoObj.subtle.importKey('jwk', sDk, { + 'name': 'AES-CBC', + 'length': 256, + }, true, [ + 'decrypt', + ]).then((dkCK) => { + cryptoObj.subtle.importKey('jwk', sHmk, { + 'name': 'HMAC', + 'hash': 'SHA-256', + 'length': 256, + }, true, [ + 'verify', + ]).then((hmkCK) => { + decrypt(dkCK, sIv, hmkCK).then((result) => { + if (!result) { + storage.removeItem(storageName); + } + }); + }); + }); + } + + mainElement.addEventListener('keydown', async (event) => { + if (event.isComposing || event.keyCode === 13) { + const password = document.getElementById('hbePass').value; + const keyMaterial = await getKeyMaterial(password); + const hmacKey = await getHmacKey(keyMaterial); + const decryptKey = await getDecryptKey(keyMaterial); + const iv = await getIv(keyMaterial); + + decrypt(decryptKey, iv, hmacKey).then((result) => { + console.log(`Decrypt result: ${result}`); + if (result) { + cryptoObj.subtle.exportKey('jwk', decryptKey).then((dk) => { + cryptoObj.subtle.exportKey('jwk', hmacKey).then((hmk) => { + const newStorageData = { + 'dk': dk, + 'iv': arrayBufferToHex(iv), + 'hmk': hmk, + }; + storage.setItem(storageName, JSON.stringify(newStorageData)); + }); + }); + } + }); + } + }); + } + + hbeLoader(); + +})(); diff --git a/notice/index.html b/notice/index.html new file mode 100644 index 000000000..52d9ae9a9 --- /dev/null +++ b/notice/index.html @@ -0,0 +1,204 @@ +公告 | The Blog + + + + + + + + + + + +

公告

2023

+

03-1

+

建立博客

+

09-10

+

改进博客中图片的存储方案

+

09-11

+

博客增加百度访问统计功能

+

09-12

+

博客中增加公告页

+
+
\ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 000000000..8aebb6786 --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,315 @@ +The Blog + + + + + + + + + +
接口测试工具
项目实战-黑马头条
SpringBoot入门教程
压力测试与性能监控
Thymeleaf教程
ElasticSearch
数据校验
Linux中开发环境的搭建
开发环境的搭建
ElementUI使用示例
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 000000000..a479e81fc --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,403 @@ +The Blog + + + + + + + + + +
Mybatis-Plus的使用教程
Git命令速查
项目实战-谷粒商城
力扣(LeetCode)算法刷题
Linux设置静态IP
VMWare虚拟机安装Linux教程
Maven配置文件settings.xml
Java爬虫
简历模板
面试专题
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/page/4/index.html b/page/4/index.html new file mode 100644 index 000000000..22a2e4fdc --- /dev/null +++ b/page/4/index.html @@ -0,0 +1,276 @@ +The Blog + + + + + + + + + +
前端基础知识
统一异常处理
常见的问题及解决方法
SpringBoot整合Knife4j
技术书籍-Linux指令大全
常用网站及网址信息
细节知识
阿里云对象存储OSS
Docker容器化技术
微信登录
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/page/5/index.html b/page/5/index.html new file mode 100644 index 000000000..6f2681547 --- /dev/null +++ b/page/5/index.html @@ -0,0 +1,271 @@ +The Blog + + + + + + + + + +
JWT-token生成工具
Map集合的遍历
SpringBoot整合MongoDB
SpringBoot整合Redis
SpringBoot整合EasyExcel
SpringBoot整合Logback日志
SpringBoot中整合Swagger2
内网穿透
技术书籍-java8实战
SSM框架基础知识及整合
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/page/6/index.html b/page/6/index.html new file mode 100644 index 000000000..01bf36e82 --- /dev/null +++ b/page/6/index.html @@ -0,0 +1,307 @@ +The Blog + + + + + + + + + +
Java程序设计基础
MySql基础进阶运维篇PDF笔记
SpringBoot中使用定时任务
验证码服务
MybatisX插件的使用
xShell自定义配色方案
数据结构与算法
MySQL5.7安装教程
gitHub上的优质课设项目
信息系统分析与设计项目运行环境的搭建
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/page/7/index.html b/page/7/index.html new file mode 100644 index 000000000..6044fdd7e --- /dev/null +++ b/page/7/index.html @@ -0,0 +1,235 @@ +The Blog + + + + + + + + + +
常用的DOS命令
SSM常用配置
常用类及其方法
常用正则表达式大全
IDEA常用快捷键
谷粒学苑课程分类列表显示前后端代码
Blog
Java学习路线
个人简历
任务进度
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
天气
\ No newline at end of file diff --git a/pdf/2023最新Java面试题全集.pdf b/pdf/2023最新Java面试题全集.pdf new file mode 100644 index 000000000..0b84f094f Binary files /dev/null and b/pdf/2023最新Java面试题全集.pdf differ diff --git a/pdf/JAVA开发_李传播_5年.pdf b/pdf/JAVA开发_李传播_5年.pdf new file mode 100644 index 000000000..acf73ee54 Binary files /dev/null and b/pdf/JAVA开发_李传播_5年.pdf differ diff --git a/pdf/Java8实战.pdf b/pdf/Java8实战.pdf new file mode 100644 index 000000000..76790cc39 Binary files /dev/null and b/pdf/Java8实战.pdf differ diff --git a/pdf/Java开发_AAA_N年.pdf b/pdf/Java开发_AAA_N年.pdf new file mode 100644 index 000000000..0519908a8 Binary files /dev/null and b/pdf/Java开发_AAA_N年.pdf differ diff --git a/pdf/Linux命令行与shell脚本编程大全.pdf b/pdf/Linux命令行与shell脚本编程大全.pdf new file mode 100644 index 000000000..321643970 Binary files /dev/null and b/pdf/Linux命令行与shell脚本编程大全.pdf differ diff --git a/pdf/MyBatis.pdf b/pdf/MyBatis.pdf new file mode 100644 index 000000000..fdd3e57af Binary files /dev/null and b/pdf/MyBatis.pdf differ diff --git a/pdf/MySQL-基础篇.pdf b/pdf/MySQL-基础篇.pdf new file mode 100644 index 000000000..bd1da38ec Binary files /dev/null and b/pdf/MySQL-基础篇.pdf differ diff --git a/pdf/MySQL-运维篇.pdf b/pdf/MySQL-运维篇.pdf new file mode 100644 index 000000000..5b7773142 Binary files /dev/null and b/pdf/MySQL-运维篇.pdf differ diff --git a/pdf/MySQL-进阶篇.pdf b/pdf/MySQL-进阶篇.pdf new file mode 100644 index 000000000..a612b3519 Binary files /dev/null and b/pdf/MySQL-进阶篇.pdf differ diff --git a/pdf/MySQL5.7.19安装.pdf b/pdf/MySQL5.7.19安装.pdf new file mode 100644 index 000000000..65df1368e Binary files /dev/null and b/pdf/MySQL5.7.19安装.pdf differ diff --git a/pdf/MySQL面试题-参考回答.pdf b/pdf/MySQL面试题-参考回答.pdf new file mode 100644 index 000000000..d53f0043a Binary files /dev/null and b/pdf/MySQL面试题-参考回答.pdf differ diff --git a/pdf/Redis面试题-参考回答.pdf b/pdf/Redis面试题-参考回答.pdf new file mode 100644 index 000000000..7171f6c41 Binary files /dev/null and b/pdf/Redis面试题-参考回答.pdf differ diff --git a/pdf/SSM整合.pdf b/pdf/SSM整合.pdf new file mode 100644 index 000000000..b24cb559d Binary files /dev/null and b/pdf/SSM整合.pdf differ diff --git a/pdf/Thymeleaf中文参考文档.pdf b/pdf/Thymeleaf中文参考文档.pdf new file mode 100644 index 000000000..e73b4c693 Binary files /dev/null and b/pdf/Thymeleaf中文参考文档.pdf differ diff --git a/pdf/【参考】1年_本科.pdf b/pdf/【参考】1年_本科.pdf new file mode 100644 index 000000000..0bd7722bb Binary files /dev/null and b/pdf/【参考】1年_本科.pdf differ diff --git a/pdf/【必看】面试参考话术.pdf b/pdf/【必看】面试参考话术.pdf new file mode 100644 index 000000000..f8b452b1d Binary files /dev/null and b/pdf/【必看】面试参考话术.pdf differ diff --git a/pdf/个人简历(大四实习).pdf b/pdf/个人简历(大四实习).pdf new file mode 100644 index 000000000..01730ef05 Binary files /dev/null and b/pdf/个人简历(大四实习).pdf differ diff --git a/pdf/尚硅谷图解Java数据结构和算法.pdf b/pdf/尚硅谷图解Java数据结构和算法.pdf new file mode 100644 index 000000000..e03af5c0e Binary files /dev/null and b/pdf/尚硅谷图解Java数据结构和算法.pdf differ diff --git a/pdf/微服务面试题-参考回答.pdf b/pdf/微服务面试题-参考回答.pdf new file mode 100644 index 000000000..a9b3fb37c Binary files /dev/null and b/pdf/微服务面试题-参考回答.pdf differ diff --git a/pdf/时尚线条简历模板.pdf b/pdf/时尚线条简历模板.pdf new file mode 100644 index 000000000..0375d3bcb Binary files /dev/null and b/pdf/时尚线条简历模板.pdf differ diff --git a/pdf/框架篇面试题-参考回答.pdf b/pdf/框架篇面试题-参考回答.pdf new file mode 100644 index 000000000..8482ed225 Binary files /dev/null and b/pdf/框架篇面试题-参考回答.pdf differ diff --git a/pdf/消息中间件面试题-参考回答.pdf b/pdf/消息中间件面试题-参考回答.pdf new file mode 100644 index 000000000..b24752e72 Binary files /dev/null and b/pdf/消息中间件面试题-参考回答.pdf differ diff --git a/pdf/灰色大气简约简历模板.pdf b/pdf/灰色大气简约简历模板.pdf new file mode 100644 index 000000000..6acb26af7 Binary files /dev/null and b/pdf/灰色大气简约简历模板.pdf differ diff --git a/pdf/灰蓝色色时尚简历模板.pdf b/pdf/灰蓝色色时尚简历模板.pdf new file mode 100644 index 000000000..36aefe7ca Binary files /dev/null and b/pdf/灰蓝色色时尚简历模板.pdf differ diff --git a/pdf/科技版简历模板.pdf b/pdf/科技版简历模板.pdf new file mode 100644 index 000000000..993fee20a Binary files /dev/null and b/pdf/科技版简历模板.pdf differ diff --git a/pdf/简历.pdf b/pdf/简历.pdf new file mode 100644 index 000000000..68001da91 Binary files /dev/null and b/pdf/简历.pdf differ diff --git a/pdf/简历模板_CN.pdf b/pdf/简历模板_CN.pdf new file mode 100644 index 000000000..f13749582 Binary files /dev/null and b/pdf/简历模板_CN.pdf differ diff --git a/pdf/简约大气橙色简历模板.pdf b/pdf/简约大气橙色简历模板.pdf new file mode 100644 index 000000000..b22f42af7 Binary files /dev/null and b/pdf/简约大气橙色简历模板.pdf differ diff --git a/pdf/精通Linux.第2版.pdf b/pdf/精通Linux.第2版.pdf new file mode 100644 index 000000000..caae42d41 Binary files /dev/null and b/pdf/精通Linux.第2版.pdf differ diff --git a/pdf/经典风格简历模板.pdf b/pdf/经典风格简历模板.pdf new file mode 100644 index 000000000..a6f9ca16b Binary files /dev/null and b/pdf/经典风格简历模板.pdf differ diff --git a/pdf/韩顺平_循序渐进学Java零基础【完整笔记】.pdf b/pdf/韩顺平_循序渐进学Java零基础【完整笔记】.pdf new file mode 100644 index 000000000..3f7f59766 Binary files /dev/null and b/pdf/韩顺平_循序渐进学Java零基础【完整笔记】.pdf differ diff --git a/pdf/韩顺平教育安装CentOS7.6.pdf b/pdf/韩顺平教育安装CentOS7.6.pdf new file mode 100644 index 000000000..31b8e800a Binary files /dev/null and b/pdf/韩顺平教育安装CentOS7.6.pdf differ diff --git a/pdf/韩顺平教育安装CentOS8.1.pdf b/pdf/韩顺平教育安装CentOS8.1.pdf new file mode 100644 index 000000000..d2791a641 Binary files /dev/null and b/pdf/韩顺平教育安装CentOS8.1.pdf differ diff --git a/pictures/1573636765632.png b/pictures/1573636765632.png new file mode 100644 index 000000000..6b16a24bf Binary files /dev/null and b/pictures/1573636765632.png differ diff --git a/pictures/1573649804623.png b/pictures/1573649804623.png new file mode 100644 index 000000000..4628c0fa6 Binary files /dev/null and b/pictures/1573649804623.png differ diff --git a/pictures/1573652396669.png b/pictures/1573652396669.png new file mode 100644 index 000000000..0d18aad1d Binary files /dev/null and b/pictures/1573652396669.png differ diff --git a/pictures/20221027215841.png b/pictures/20221027215841.png new file mode 100644 index 000000000..4b0e90b8c Binary files /dev/null and b/pictures/20221027215841.png differ diff --git a/pictures/20221027215846.png b/pictures/20221027215846.png new file mode 100644 index 000000000..d444aea89 Binary files /dev/null and b/pictures/20221027215846.png differ diff --git a/pictures/20221027215849.png b/pictures/20221027215849.png new file mode 100644 index 000000000..75f659da0 Binary files /dev/null and b/pictures/20221027215849.png differ diff --git a/pictures/20221027215858.png b/pictures/20221027215858.png new file mode 100644 index 000000000..067566976 Binary files /dev/null and b/pictures/20221027215858.png differ diff --git a/pictures/20221027220005.png b/pictures/20221027220005.png new file mode 100644 index 000000000..9a5beeca8 Binary files /dev/null and b/pictures/20221027220005.png differ diff --git a/pictures/20221027220010.png b/pictures/20221027220010.png new file mode 100644 index 000000000..821672bb0 Binary files /dev/null and b/pictures/20221027220010.png differ diff --git a/pictures/20221027220429.png b/pictures/20221027220429.png new file mode 100644 index 000000000..ea0b9ecc7 Binary files /dev/null and b/pictures/20221027220429.png differ diff --git a/pictures/20221027220449.png b/pictures/20221027220449.png new file mode 100644 index 000000000..47b866585 Binary files /dev/null and b/pictures/20221027220449.png differ diff --git a/pictures/20221027220514.png b/pictures/20221027220514.png new file mode 100644 index 000000000..36368a529 Binary files /dev/null and b/pictures/20221027220514.png differ diff --git a/pictures/20221027220525.png b/pictures/20221027220525.png new file mode 100644 index 000000000..3fe49d67c Binary files /dev/null and b/pictures/20221027220525.png differ diff --git a/pictures/20221027220554.png b/pictures/20221027220554.png new file mode 100644 index 000000000..2867537bc Binary files /dev/null and b/pictures/20221027220554.png differ diff --git a/pictures/20221027221129.png b/pictures/20221027221129.png new file mode 100644 index 000000000..8faae3585 Binary files /dev/null and b/pictures/20221027221129.png differ diff --git a/pictures/20221027221149.png b/pictures/20221027221149.png new file mode 100644 index 000000000..ad81852b0 Binary files /dev/null and b/pictures/20221027221149.png differ diff --git a/pictures/20221027221201.png b/pictures/20221027221201.png new file mode 100644 index 000000000..1182ec14a Binary files /dev/null and b/pictures/20221027221201.png differ diff --git a/pictures/20221027221210.png b/pictures/20221027221210.png new file mode 100644 index 000000000..285cc8492 Binary files /dev/null and b/pictures/20221027221210.png differ diff --git a/pictures/20221027221241.png b/pictures/20221027221241.png new file mode 100644 index 000000000..23943763d Binary files /dev/null and b/pictures/20221027221241.png differ diff --git a/pictures/20221027221303.png b/pictures/20221027221303.png new file mode 100644 index 000000000..192951a33 Binary files /dev/null and b/pictures/20221027221303.png differ diff --git a/pictures/20221027221340.png b/pictures/20221027221340.png new file mode 100644 index 000000000..caa2c2ffd Binary files /dev/null and b/pictures/20221027221340.png differ diff --git a/pictures/20221027221354.png b/pictures/20221027221354.png new file mode 100644 index 000000000..d59e70407 Binary files /dev/null and b/pictures/20221027221354.png differ diff --git a/pictures/20221027221413.png b/pictures/20221027221413.png new file mode 100644 index 000000000..4c1d37886 Binary files /dev/null and b/pictures/20221027221413.png differ diff --git a/pictures/20221027221440.png b/pictures/20221027221440.png new file mode 100644 index 000000000..dcd6be31b Binary files /dev/null and b/pictures/20221027221440.png differ diff --git a/pictures/20221027221514.png b/pictures/20221027221514.png new file mode 100644 index 000000000..d19ed0c34 Binary files /dev/null and b/pictures/20221027221514.png differ diff --git a/pictures/20221027221545.png b/pictures/20221027221545.png new file mode 100644 index 000000000..c11424af9 Binary files /dev/null and b/pictures/20221027221545.png differ diff --git a/pictures/20221027221906.png b/pictures/20221027221906.png new file mode 100644 index 000000000..ee6083736 Binary files /dev/null and b/pictures/20221027221906.png differ diff --git a/pictures/20221027221939.png b/pictures/20221027221939.png new file mode 100644 index 000000000..53eebf2c8 Binary files /dev/null and b/pictures/20221027221939.png differ diff --git a/pictures/20221027222021.png b/pictures/20221027222021.png new file mode 100644 index 000000000..2bcce6a0c Binary files /dev/null and b/pictures/20221027222021.png differ diff --git a/pictures/20221027222035.png b/pictures/20221027222035.png new file mode 100644 index 000000000..adb8349b2 Binary files /dev/null and b/pictures/20221027222035.png differ diff --git a/pictures/20221027222157.png b/pictures/20221027222157.png new file mode 100644 index 000000000..9f61d6b5c Binary files /dev/null and b/pictures/20221027222157.png differ diff --git a/pictures/20221027222326.png b/pictures/20221027222326.png new file mode 100644 index 000000000..255397440 Binary files /dev/null and b/pictures/20221027222326.png differ diff --git a/pictures/20221027222354.png b/pictures/20221027222354.png new file mode 100644 index 000000000..4802e8d4f Binary files /dev/null and b/pictures/20221027222354.png differ diff --git a/pictures/20221027222407.png b/pictures/20221027222407.png new file mode 100644 index 000000000..1d8932a35 Binary files /dev/null and b/pictures/20221027222407.png differ diff --git a/pictures/20221027222446.png b/pictures/20221027222446.png new file mode 100644 index 000000000..8fbad548e Binary files /dev/null and b/pictures/20221027222446.png differ diff --git a/pictures/20221027222512.png b/pictures/20221027222512.png new file mode 100644 index 000000000..eb4f48e29 Binary files /dev/null and b/pictures/20221027222512.png differ diff --git a/pictures/20230321180350.png b/pictures/20230321180350.png new file mode 100644 index 000000000..802882125 Binary files /dev/null and b/pictures/20230321180350.png differ diff --git a/pictures/20230321180405.png b/pictures/20230321180405.png new file mode 100644 index 000000000..ad7e14f9b Binary files /dev/null and b/pictures/20230321180405.png differ diff --git a/pictures/20230321180420.png b/pictures/20230321180420.png new file mode 100644 index 000000000..619f4909e Binary files /dev/null and b/pictures/20230321180420.png differ diff --git a/pictures/Sorting_shellsort_anim.gif b/pictures/Sorting_shellsort_anim.gif new file mode 100644 index 000000000..08820ad7f Binary files /dev/null and b/pictures/Sorting_shellsort_anim.gif differ diff --git a/pictures/b25e8bc25036aa854b3c4a33c9537f43.png b/pictures/b25e8bc25036aa854b3c4a33c9537f43.png new file mode 100644 index 000000000..bd9fc37df Binary files /dev/null and b/pictures/b25e8bc25036aa854b3c4a33c9537f43.png differ diff --git a/pictures/clip_image002.gif b/pictures/clip_image002.gif new file mode 100644 index 000000000..e9962627d Binary files /dev/null and b/pictures/clip_image002.gif differ diff --git a/pictures/clip_image002.jpg b/pictures/clip_image002.jpg new file mode 100644 index 000000000..2118e6e5e Binary files /dev/null and b/pictures/clip_image002.jpg differ diff --git a/pictures/clip_image004.jpg b/pictures/clip_image004.jpg new file mode 100644 index 000000000..55b538415 Binary files /dev/null and b/pictures/clip_image004.jpg differ diff --git a/pictures/clip_image006.jpg b/pictures/clip_image006.jpg new file mode 100644 index 000000000..02dd59f78 Binary files /dev/null and b/pictures/clip_image006.jpg differ diff --git a/pictures/clip_image008.jpg b/pictures/clip_image008.jpg new file mode 100644 index 000000000..32b3c8f31 Binary files /dev/null and b/pictures/clip_image008.jpg differ diff --git a/pictures/clip_image010.jpg b/pictures/clip_image010.jpg new file mode 100644 index 000000000..846f862b0 Binary files /dev/null and b/pictures/clip_image010.jpg differ diff --git a/pictures/clip_image012.jpg b/pictures/clip_image012.jpg new file mode 100644 index 000000000..35f34dbbc Binary files /dev/null and b/pictures/clip_image012.jpg differ diff --git a/pictures/clip_image014.jpg b/pictures/clip_image014.jpg new file mode 100644 index 000000000..b550ec705 Binary files /dev/null and b/pictures/clip_image014.jpg differ diff --git a/pictures/clip_image016.jpg b/pictures/clip_image016.jpg new file mode 100644 index 000000000..b2efb7549 Binary files /dev/null and b/pictures/clip_image016.jpg differ diff --git a/pictures/clip_image018.jpg b/pictures/clip_image018.jpg new file mode 100644 index 000000000..ab3607292 Binary files /dev/null and b/pictures/clip_image018.jpg differ diff --git a/pictures/docker01.png b/pictures/docker01.png new file mode 100644 index 000000000..4b837e8c4 Binary files /dev/null and b/pictures/docker01.png differ diff --git a/pictures/es6-tutorial.jpg b/pictures/es6-tutorial.jpg new file mode 100644 index 000000000..b78f306ee Binary files /dev/null and b/pictures/es6-tutorial.jpg differ diff --git a/pictures/image-20230310102929346.png b/pictures/image-20230310102929346.png new file mode 100644 index 000000000..7412ac53e Binary files /dev/null and b/pictures/image-20230310102929346.png differ diff --git a/pictures/image-20230310103126938.png b/pictures/image-20230310103126938.png new file mode 100644 index 000000000..8c93d156b Binary files /dev/null and b/pictures/image-20230310103126938.png differ diff --git a/pictures/image-20230310103351729.png b/pictures/image-20230310103351729.png new file mode 100644 index 000000000..715f0df78 Binary files /dev/null and b/pictures/image-20230310103351729.png differ diff --git a/pictures/image-20230310103430906.png b/pictures/image-20230310103430906.png new file mode 100644 index 000000000..6874b7827 Binary files /dev/null and b/pictures/image-20230310103430906.png differ diff --git a/pictures/image-20230311104034350.png b/pictures/image-20230311104034350.png new file mode 100644 index 000000000..8642a5f4f Binary files /dev/null and b/pictures/image-20230311104034350.png differ diff --git a/pictures/image-20230311111916307.png b/pictures/image-20230311111916307.png new file mode 100644 index 000000000..3757e8c6d Binary files /dev/null and b/pictures/image-20230311111916307.png differ diff --git a/pictures/image-20230311113425453.png b/pictures/image-20230311113425453.png new file mode 100644 index 000000000..6a27671e8 Binary files /dev/null and b/pictures/image-20230311113425453.png differ diff --git a/pictures/image-20230311113536220.png b/pictures/image-20230311113536220.png new file mode 100644 index 000000000..baeac6dfa Binary files /dev/null and b/pictures/image-20230311113536220.png differ diff --git a/pictures/image-20230311113653032.png b/pictures/image-20230311113653032.png new file mode 100644 index 000000000..4bdb0994c Binary files /dev/null and b/pictures/image-20230311113653032.png differ diff --git a/pictures/image-20230311113841997.png b/pictures/image-20230311113841997.png new file mode 100644 index 000000000..8a16c2749 Binary files /dev/null and b/pictures/image-20230311113841997.png differ diff --git a/pictures/image-20230311114451668.png b/pictures/image-20230311114451668.png new file mode 100644 index 000000000..5568a7303 Binary files /dev/null and b/pictures/image-20230311114451668.png differ diff --git a/pictures/image-20230311114537761.png b/pictures/image-20230311114537761.png new file mode 100644 index 000000000..138f8b2e3 Binary files /dev/null and b/pictures/image-20230311114537761.png differ diff --git a/pictures/image-20230311114653791.png b/pictures/image-20230311114653791.png new file mode 100644 index 000000000..050a8cf19 Binary files /dev/null and b/pictures/image-20230311114653791.png differ diff --git a/pictures/image-20230311114849263.png b/pictures/image-20230311114849263.png new file mode 100644 index 000000000..03ceff1bd Binary files /dev/null and b/pictures/image-20230311114849263.png differ diff --git a/pictures/image-20230311115057351.png b/pictures/image-20230311115057351.png new file mode 100644 index 000000000..b0afa7c02 Binary files /dev/null and b/pictures/image-20230311115057351.png differ diff --git a/pictures/image-20230311115223537.png b/pictures/image-20230311115223537.png new file mode 100644 index 000000000..226daaed6 Binary files /dev/null and b/pictures/image-20230311115223537.png differ diff --git a/pictures/image-20230311120902633.png b/pictures/image-20230311120902633.png new file mode 100644 index 000000000..639fd01fd Binary files /dev/null and b/pictures/image-20230311120902633.png differ diff --git a/pictures/image-20230311121529641.png b/pictures/image-20230311121529641.png new file mode 100644 index 000000000..03492f519 Binary files /dev/null and b/pictures/image-20230311121529641.png differ diff --git a/pictures/image-20230312152323079.png b/pictures/image-20230312152323079.png new file mode 100644 index 000000000..624f781d7 Binary files /dev/null and b/pictures/image-20230312152323079.png differ diff --git a/pictures/image-20230312211636578.png b/pictures/image-20230312211636578.png new file mode 100644 index 000000000..1df9d8c74 Binary files /dev/null and b/pictures/image-20230312211636578.png differ diff --git a/pictures/image-20230312211901702.png b/pictures/image-20230312211901702.png new file mode 100644 index 000000000..a3d156033 Binary files /dev/null and b/pictures/image-20230312211901702.png differ diff --git a/pictures/image-20230313124552408.png b/pictures/image-20230313124552408.png new file mode 100644 index 000000000..745049b6a Binary files /dev/null and b/pictures/image-20230313124552408.png differ diff --git a/pictures/image-20230313160224878.png b/pictures/image-20230313160224878.png new file mode 100644 index 000000000..210f62bbf Binary files /dev/null and b/pictures/image-20230313160224878.png differ diff --git a/pictures/image-20230318100138197.png b/pictures/image-20230318100138197.png new file mode 100644 index 000000000..02009dd25 Binary files /dev/null and b/pictures/image-20230318100138197.png differ diff --git a/pictures/image-20230321181437910.png b/pictures/image-20230321181437910.png new file mode 100644 index 000000000..aeec6827d Binary files /dev/null and b/pictures/image-20230321181437910.png differ diff --git a/pictures/image-20230321181452909.png b/pictures/image-20230321181452909.png new file mode 100644 index 000000000..abb6e3b51 Binary files /dev/null and b/pictures/image-20230321181452909.png differ diff --git a/pictures/image-20230329184540977.png b/pictures/image-20230329184540977.png new file mode 100644 index 000000000..5326c214f Binary files /dev/null and b/pictures/image-20230329184540977.png differ diff --git a/pictures/image-20230329184618098.png b/pictures/image-20230329184618098.png new file mode 100644 index 000000000..798f6efe7 Binary files /dev/null and b/pictures/image-20230329184618098.png differ diff --git a/pictures/image-20230409221503708.png b/pictures/image-20230409221503708.png new file mode 100644 index 000000000..b207b5279 Binary files /dev/null and b/pictures/image-20230409221503708.png differ diff --git a/pictures/image-20230409221827414.png b/pictures/image-20230409221827414.png new file mode 100644 index 000000000..da1046f6b Binary files /dev/null and b/pictures/image-20230409221827414.png differ diff --git a/pictures/image-20230409221954792.png b/pictures/image-20230409221954792.png new file mode 100644 index 000000000..eb05b5c43 Binary files /dev/null and b/pictures/image-20230409221954792.png differ diff --git a/pictures/image-20230409222457135.png b/pictures/image-20230409222457135.png new file mode 100644 index 000000000..f92c21204 Binary files /dev/null and b/pictures/image-20230409222457135.png differ diff --git a/pictures/image-20230409222628425.png b/pictures/image-20230409222628425.png new file mode 100644 index 000000000..457010af4 Binary files /dev/null and b/pictures/image-20230409222628425.png differ diff --git a/pictures/image-20230409222923480.png b/pictures/image-20230409222923480.png new file mode 100644 index 000000000..9b386eda7 Binary files /dev/null and b/pictures/image-20230409222923480.png differ diff --git a/pictures/image-20230412141010237.png b/pictures/image-20230412141010237.png new file mode 100644 index 000000000..47e1e953f Binary files /dev/null and b/pictures/image-20230412141010237.png differ diff --git a/pictures/image-20230412171350769.png b/pictures/image-20230412171350769.png new file mode 100644 index 000000000..db40cc23d Binary files /dev/null and b/pictures/image-20230412171350769.png differ diff --git a/pictures/image-20230414131020923.png b/pictures/image-20230414131020923.png new file mode 100644 index 000000000..34753cf43 Binary files /dev/null and b/pictures/image-20230414131020923.png differ diff --git a/pictures/image-20230414152035347.png b/pictures/image-20230414152035347.png new file mode 100644 index 000000000..30811a909 Binary files /dev/null and b/pictures/image-20230414152035347.png differ diff --git a/pictures/image-20230420102523288.png b/pictures/image-20230420102523288.png new file mode 100644 index 000000000..1f31ffb3b Binary files /dev/null and b/pictures/image-20230420102523288.png differ diff --git a/pictures/image-20230420103234232.png b/pictures/image-20230420103234232.png new file mode 100644 index 000000000..3b9d984f0 Binary files /dev/null and b/pictures/image-20230420103234232.png differ diff --git a/pictures/image-20230420110340570.png b/pictures/image-20230420110340570.png new file mode 100644 index 000000000..fbbd8a32f Binary files /dev/null and b/pictures/image-20230420110340570.png differ diff --git a/pictures/image-20230421103035776.png b/pictures/image-20230421103035776.png new file mode 100644 index 000000000..ecbb3bd1b Binary files /dev/null and b/pictures/image-20230421103035776.png differ diff --git a/pictures/image-20230421103610184.png b/pictures/image-20230421103610184.png new file mode 100644 index 000000000..718995e36 Binary files /dev/null and b/pictures/image-20230421103610184.png differ diff --git a/pictures/image-20230421103755984.png b/pictures/image-20230421103755984.png new file mode 100644 index 000000000..5cad2b8c1 Binary files /dev/null and b/pictures/image-20230421103755984.png differ diff --git a/pictures/image-20230421104103726.png b/pictures/image-20230421104103726.png new file mode 100644 index 000000000..cbd358911 Binary files /dev/null and b/pictures/image-20230421104103726.png differ diff --git a/pictures/image-20230421110034731.png b/pictures/image-20230421110034731.png new file mode 100644 index 000000000..ffe710e8b Binary files /dev/null and b/pictures/image-20230421110034731.png differ diff --git a/pictures/image-20230421203717902.png b/pictures/image-20230421203717902.png new file mode 100644 index 000000000..fc65e7969 Binary files /dev/null and b/pictures/image-20230421203717902.png differ diff --git a/pictures/image-20230421205258647.png b/pictures/image-20230421205258647.png new file mode 100644 index 000000000..7e15feb78 Binary files /dev/null and b/pictures/image-20230421205258647.png differ diff --git a/pictures/image-20230421210454720.png b/pictures/image-20230421210454720.png new file mode 100644 index 000000000..c767bbce1 Binary files /dev/null and b/pictures/image-20230421210454720.png differ diff --git a/pictures/image-20230422210404204.png b/pictures/image-20230422210404204.png new file mode 100644 index 000000000..3005b5171 Binary files /dev/null and b/pictures/image-20230422210404204.png differ diff --git a/pictures/image-20230422211448747.png b/pictures/image-20230422211448747.png new file mode 100644 index 000000000..ea736002c Binary files /dev/null and b/pictures/image-20230422211448747.png differ diff --git a/pictures/image-20230422211940485.png b/pictures/image-20230422211940485.png new file mode 100644 index 000000000..9cca78296 Binary files /dev/null and b/pictures/image-20230422211940485.png differ diff --git a/pictures/image-20230422212508029.png b/pictures/image-20230422212508029.png new file mode 100644 index 000000000..c3f216c68 Binary files /dev/null and b/pictures/image-20230422212508029.png differ diff --git a/pictures/image-20230428124659873.png b/pictures/image-20230428124659873.png new file mode 100644 index 000000000..c4a2eacd0 Binary files /dev/null and b/pictures/image-20230428124659873.png differ diff --git a/pictures/image-20230428134234636.png b/pictures/image-20230428134234636.png new file mode 100644 index 000000000..d4b45ed0d Binary files /dev/null and b/pictures/image-20230428134234636.png differ diff --git a/pictures/image-20230428135032883.png b/pictures/image-20230428135032883.png new file mode 100644 index 000000000..27fe217b9 Binary files /dev/null and b/pictures/image-20230428135032883.png differ diff --git a/pictures/image-20230505222154471.png b/pictures/image-20230505222154471.png new file mode 100644 index 000000000..e62c47222 Binary files /dev/null and b/pictures/image-20230505222154471.png differ diff --git a/pictures/image-20230505223331221.png b/pictures/image-20230505223331221.png new file mode 100644 index 000000000..cc6ac256b Binary files /dev/null and b/pictures/image-20230505223331221.png differ diff --git a/pictures/image-20230505223433369.png b/pictures/image-20230505223433369.png new file mode 100644 index 000000000..1b0db5c26 Binary files /dev/null and b/pictures/image-20230505223433369.png differ diff --git a/pictures/image-20230505223651097.png b/pictures/image-20230505223651097.png new file mode 100644 index 000000000..1c429cd12 Binary files /dev/null and b/pictures/image-20230505223651097.png differ diff --git a/pictures/image-20230506105453269.png b/pictures/image-20230506105453269.png new file mode 100644 index 000000000..b3715233b Binary files /dev/null and b/pictures/image-20230506105453269.png differ diff --git a/pictures/image-20230506130238222.png b/pictures/image-20230506130238222.png new file mode 100644 index 000000000..6f30d7340 Binary files /dev/null and b/pictures/image-20230506130238222.png differ diff --git a/pictures/image-20230506153955544.png b/pictures/image-20230506153955544.png new file mode 100644 index 000000000..4edd370f3 Binary files /dev/null and b/pictures/image-20230506153955544.png differ diff --git a/pictures/image-20230506154605339.png b/pictures/image-20230506154605339.png new file mode 100644 index 000000000..e9bd9ce3a Binary files /dev/null and b/pictures/image-20230506154605339.png differ diff --git a/pictures/image-20230507100730848.png b/pictures/image-20230507100730848.png new file mode 100644 index 000000000..55eef0253 Binary files /dev/null and b/pictures/image-20230507100730848.png differ diff --git a/pictures/image-20230508104207815.png b/pictures/image-20230508104207815.png new file mode 100644 index 000000000..918b83138 Binary files /dev/null and b/pictures/image-20230508104207815.png differ diff --git a/pictures/image-20230508121934697.png b/pictures/image-20230508121934697.png new file mode 100644 index 000000000..043ab4b47 Binary files /dev/null and b/pictures/image-20230508121934697.png differ diff --git a/pictures/image-20230508235133469.png b/pictures/image-20230508235133469.png new file mode 100644 index 000000000..b467a361e Binary files /dev/null and b/pictures/image-20230508235133469.png differ diff --git a/pictures/image-20230509161845558.png b/pictures/image-20230509161845558.png new file mode 100644 index 000000000..f88902016 Binary files /dev/null and b/pictures/image-20230509161845558.png differ diff --git a/pictures/image-20230509161907139.png b/pictures/image-20230509161907139.png new file mode 100644 index 000000000..55ec3c77c Binary files /dev/null and b/pictures/image-20230509161907139.png differ diff --git a/pictures/image-20230509180334969.png b/pictures/image-20230509180334969.png new file mode 100644 index 000000000..413905532 Binary files /dev/null and b/pictures/image-20230509180334969.png differ diff --git a/pictures/image-20230509180435467.png b/pictures/image-20230509180435467.png new file mode 100644 index 000000000..e99bf917a Binary files /dev/null and b/pictures/image-20230509180435467.png differ diff --git a/pictures/image-20230509180547905.png b/pictures/image-20230509180547905.png new file mode 100644 index 000000000..7184639ea Binary files /dev/null and b/pictures/image-20230509180547905.png differ diff --git a/pictures/image-20230509233922789.png b/pictures/image-20230509233922789.png new file mode 100644 index 000000000..2b4435857 Binary files /dev/null and b/pictures/image-20230509233922789.png differ diff --git a/pictures/image-20230509234019249.png b/pictures/image-20230509234019249.png new file mode 100644 index 000000000..d63ee35f6 Binary files /dev/null and b/pictures/image-20230509234019249.png differ diff --git a/pictures/image-20230510101713914.png b/pictures/image-20230510101713914.png new file mode 100644 index 000000000..76c574a7b Binary files /dev/null and b/pictures/image-20230510101713914.png differ diff --git a/pictures/image-20230510101722707.png b/pictures/image-20230510101722707.png new file mode 100644 index 000000000..e8d2e0e3d Binary files /dev/null and b/pictures/image-20230510101722707.png differ diff --git a/pictures/image-20230510101916230.png b/pictures/image-20230510101916230.png new file mode 100644 index 000000000..968e44dc3 Binary files /dev/null and b/pictures/image-20230510101916230.png differ diff --git a/pictures/image-20230510105251076.png b/pictures/image-20230510105251076.png new file mode 100644 index 000000000..3c4a4a81e Binary files /dev/null and b/pictures/image-20230510105251076.png differ diff --git a/pictures/image-20230510110403055.png b/pictures/image-20230510110403055.png new file mode 100644 index 000000000..33951d4f8 Binary files /dev/null and b/pictures/image-20230510110403055.png differ diff --git a/pictures/image-20230510222210848.png b/pictures/image-20230510222210848.png new file mode 100644 index 000000000..c9db54ff4 Binary files /dev/null and b/pictures/image-20230510222210848.png differ diff --git a/pictures/image-20230510224239950.png b/pictures/image-20230510224239950.png new file mode 100644 index 000000000..c86ec977a Binary files /dev/null and b/pictures/image-20230510224239950.png differ diff --git a/pictures/image-20230510225825430.png b/pictures/image-20230510225825430.png new file mode 100644 index 000000000..0daaaa52e Binary files /dev/null and b/pictures/image-20230510225825430.png differ diff --git a/pictures/image-20230510230114550.png b/pictures/image-20230510230114550.png new file mode 100644 index 000000000..269dc1db0 Binary files /dev/null and b/pictures/image-20230510230114550.png differ diff --git a/pictures/image-20230510230758405.png b/pictures/image-20230510230758405.png new file mode 100644 index 000000000..469e77476 Binary files /dev/null and b/pictures/image-20230510230758405.png differ diff --git a/pictures/image-20230510230937843.png b/pictures/image-20230510230937843.png new file mode 100644 index 000000000..a3268fb17 Binary files /dev/null and b/pictures/image-20230510230937843.png differ diff --git a/pictures/image-20230510231329208.png b/pictures/image-20230510231329208.png new file mode 100644 index 000000000..888c5b9f9 Binary files /dev/null and b/pictures/image-20230510231329208.png differ diff --git a/pictures/image-20230510231432103.png b/pictures/image-20230510231432103.png new file mode 100644 index 000000000..04056c74b Binary files /dev/null and b/pictures/image-20230510231432103.png differ diff --git a/pictures/image-20230510231607314.png b/pictures/image-20230510231607314.png new file mode 100644 index 000000000..8361ea550 Binary files /dev/null and b/pictures/image-20230510231607314.png differ diff --git a/pictures/image-20230510232029231.png b/pictures/image-20230510232029231.png new file mode 100644 index 000000000..a0c50ee66 Binary files /dev/null and b/pictures/image-20230510232029231.png differ diff --git a/pictures/image-20230511094011546.png b/pictures/image-20230511094011546.png new file mode 100644 index 000000000..d095ec6ff Binary files /dev/null and b/pictures/image-20230511094011546.png differ diff --git a/pictures/image-20230511123245408.png b/pictures/image-20230511123245408.png new file mode 100644 index 000000000..25af2d5dc Binary files /dev/null and b/pictures/image-20230511123245408.png differ diff --git a/pictures/image-20230511123527496.png b/pictures/image-20230511123527496.png new file mode 100644 index 000000000..ff2e0810a Binary files /dev/null and b/pictures/image-20230511123527496.png differ diff --git a/pictures/image-20230511172226172.png b/pictures/image-20230511172226172.png new file mode 100644 index 000000000..68049b30c Binary files /dev/null and b/pictures/image-20230511172226172.png differ diff --git a/pictures/image-20230511172259204.png b/pictures/image-20230511172259204.png new file mode 100644 index 000000000..0b898090d Binary files /dev/null and b/pictures/image-20230511172259204.png differ diff --git a/pictures/image-20230511230406172.png b/pictures/image-20230511230406172.png new file mode 100644 index 000000000..010561bdf Binary files /dev/null and b/pictures/image-20230511230406172.png differ diff --git a/pictures/image-20230511233324998.png b/pictures/image-20230511233324998.png new file mode 100644 index 000000000..c63e79489 Binary files /dev/null and b/pictures/image-20230511233324998.png differ diff --git a/pictures/image-20230511233838595.png b/pictures/image-20230511233838595.png new file mode 100644 index 000000000..8a4159d23 Binary files /dev/null and b/pictures/image-20230511233838595.png differ diff --git a/pictures/image-20230511234154523.png b/pictures/image-20230511234154523.png new file mode 100644 index 000000000..906b652d2 Binary files /dev/null and b/pictures/image-20230511234154523.png differ diff --git a/pictures/image-20230511235621228.png b/pictures/image-20230511235621228.png new file mode 100644 index 000000000..a93db1b1a Binary files /dev/null and b/pictures/image-20230511235621228.png differ diff --git a/pictures/image-20230512092144712.png b/pictures/image-20230512092144712.png new file mode 100644 index 000000000..e6ff4c768 Binary files /dev/null and b/pictures/image-20230512092144712.png differ diff --git a/pictures/image-20230512112050729.png b/pictures/image-20230512112050729.png new file mode 100644 index 000000000..21a1da9d7 Binary files /dev/null and b/pictures/image-20230512112050729.png differ diff --git a/pictures/image-20230512112456731.png b/pictures/image-20230512112456731.png new file mode 100644 index 000000000..221dcdb35 Binary files /dev/null and b/pictures/image-20230512112456731.png differ diff --git a/pictures/image-20230513115319799.png b/pictures/image-20230513115319799.png new file mode 100644 index 000000000..a3a8e6467 Binary files /dev/null and b/pictures/image-20230513115319799.png differ diff --git a/pictures/image-20230513115606521.png b/pictures/image-20230513115606521.png new file mode 100644 index 000000000..a871a7501 Binary files /dev/null and b/pictures/image-20230513115606521.png differ diff --git a/pictures/image-20230513134435330.png b/pictures/image-20230513134435330.png new file mode 100644 index 000000000..d2e4d0a6b Binary files /dev/null and b/pictures/image-20230513134435330.png differ diff --git a/pictures/image-20230515093520587.png b/pictures/image-20230515093520587.png new file mode 100644 index 000000000..3746e35c0 Binary files /dev/null and b/pictures/image-20230515093520587.png differ diff --git a/pictures/image-20230515093753893.png b/pictures/image-20230515093753893.png new file mode 100644 index 000000000..2bb128dff Binary files /dev/null and b/pictures/image-20230515093753893.png differ diff --git a/pictures/image-20230515095247319.png b/pictures/image-20230515095247319.png new file mode 100644 index 000000000..e5accca6a Binary files /dev/null and b/pictures/image-20230515095247319.png differ diff --git a/pictures/image-20230515100345339.png b/pictures/image-20230515100345339.png new file mode 100644 index 000000000..4e225b135 Binary files /dev/null and b/pictures/image-20230515100345339.png differ diff --git a/pictures/image-20230515100754827.png b/pictures/image-20230515100754827.png new file mode 100644 index 000000000..3cfcc3aa0 Binary files /dev/null and b/pictures/image-20230515100754827.png differ diff --git a/pictures/image-20230515105359552.png b/pictures/image-20230515105359552.png new file mode 100644 index 000000000..5d271187c Binary files /dev/null and b/pictures/image-20230515105359552.png differ diff --git a/pictures/image-20230516083148055.png b/pictures/image-20230516083148055.png new file mode 100644 index 000000000..e44284c19 Binary files /dev/null and b/pictures/image-20230516083148055.png differ diff --git a/pictures/image-20230516083738747.png b/pictures/image-20230516083738747.png new file mode 100644 index 000000000..96ab1e27f Binary files /dev/null and b/pictures/image-20230516083738747.png differ diff --git a/pictures/image-20230516091218716.png b/pictures/image-20230516091218716.png new file mode 100644 index 000000000..4b43c61a2 Binary files /dev/null and b/pictures/image-20230516091218716.png differ diff --git a/pictures/image-20230516093143656.png b/pictures/image-20230516093143656.png new file mode 100644 index 000000000..b8a9713b1 Binary files /dev/null and b/pictures/image-20230516093143656.png differ diff --git a/pictures/image-20230516100256880.png b/pictures/image-20230516100256880.png new file mode 100644 index 000000000..47f9fefec Binary files /dev/null and b/pictures/image-20230516100256880.png differ diff --git a/pictures/image-20230516105517026.png b/pictures/image-20230516105517026.png new file mode 100644 index 000000000..b129a9fc6 Binary files /dev/null and b/pictures/image-20230516105517026.png differ diff --git a/pictures/image-20230517091741355.png b/pictures/image-20230517091741355.png new file mode 100644 index 000000000..eecfa5920 Binary files /dev/null and b/pictures/image-20230517091741355.png differ diff --git a/pictures/image-20230517091841366.png b/pictures/image-20230517091841366.png new file mode 100644 index 000000000..9dd6f19ff Binary files /dev/null and b/pictures/image-20230517091841366.png differ diff --git a/pictures/image-20230517102649272.png b/pictures/image-20230517102649272.png new file mode 100644 index 000000000..9fc744929 Binary files /dev/null and b/pictures/image-20230517102649272.png differ diff --git a/pictures/image-20230517103139900.png b/pictures/image-20230517103139900.png new file mode 100644 index 000000000..6708c127d Binary files /dev/null and b/pictures/image-20230517103139900.png differ diff --git a/pictures/image-20230518000727005.png b/pictures/image-20230518000727005.png new file mode 100644 index 000000000..8a101d8c6 Binary files /dev/null and b/pictures/image-20230518000727005.png differ diff --git a/pictures/image-20230518010847966.png b/pictures/image-20230518010847966.png new file mode 100644 index 000000000..9feea9b5a Binary files /dev/null and b/pictures/image-20230518010847966.png differ diff --git a/pictures/image-20230518011030343.png b/pictures/image-20230518011030343.png new file mode 100644 index 000000000..bc2fdca81 Binary files /dev/null and b/pictures/image-20230518011030343.png differ diff --git a/pictures/image-20230518012211042.png b/pictures/image-20230518012211042.png new file mode 100644 index 000000000..27068d98c Binary files /dev/null and b/pictures/image-20230518012211042.png differ diff --git a/pictures/image-20230518012538349.png b/pictures/image-20230518012538349.png new file mode 100644 index 000000000..98c95a2ce Binary files /dev/null and b/pictures/image-20230518012538349.png differ diff --git a/pictures/image-20230518013254404.png b/pictures/image-20230518013254404.png new file mode 100644 index 000000000..82288a62b Binary files /dev/null and b/pictures/image-20230518013254404.png differ diff --git a/pictures/image-20230518013624395.png b/pictures/image-20230518013624395.png new file mode 100644 index 000000000..62de2faf9 Binary files /dev/null and b/pictures/image-20230518013624395.png differ diff --git a/pictures/image-20230518014732955.png b/pictures/image-20230518014732955.png new file mode 100644 index 000000000..a0219b489 Binary files /dev/null and b/pictures/image-20230518014732955.png differ diff --git a/pictures/image-20230518015831413.png b/pictures/image-20230518015831413.png new file mode 100644 index 000000000..d467a69fb Binary files /dev/null and b/pictures/image-20230518015831413.png differ diff --git a/pictures/image-20230518111233009.png b/pictures/image-20230518111233009.png new file mode 100644 index 000000000..4cce72129 Binary files /dev/null and b/pictures/image-20230518111233009.png differ diff --git a/pictures/image-20230518111454775.png b/pictures/image-20230518111454775.png new file mode 100644 index 000000000..54eb21ffb Binary files /dev/null and b/pictures/image-20230518111454775.png differ diff --git a/pictures/image-20230518111536026.png b/pictures/image-20230518111536026.png new file mode 100644 index 000000000..c8d92e7a1 Binary files /dev/null and b/pictures/image-20230518111536026.png differ diff --git a/pictures/image-20230518141429712.png b/pictures/image-20230518141429712.png new file mode 100644 index 000000000..70ef809ec Binary files /dev/null and b/pictures/image-20230518141429712.png differ diff --git a/pictures/image-20230518145456236.png b/pictures/image-20230518145456236.png new file mode 100644 index 000000000..ca993527d Binary files /dev/null and b/pictures/image-20230518145456236.png differ diff --git a/pictures/image-20230518155308167.png b/pictures/image-20230518155308167.png new file mode 100644 index 000000000..7f5357d9d Binary files /dev/null and b/pictures/image-20230518155308167.png differ diff --git a/pictures/image-20230518160444927.png b/pictures/image-20230518160444927.png new file mode 100644 index 000000000..5f7ea564d Binary files /dev/null and b/pictures/image-20230518160444927.png differ diff --git a/pictures/image-20230518161145808.png b/pictures/image-20230518161145808.png new file mode 100644 index 000000000..a2e9b65d9 Binary files /dev/null and b/pictures/image-20230518161145808.png differ diff --git a/pictures/image-20230518163703370.png b/pictures/image-20230518163703370.png new file mode 100644 index 000000000..38766da65 Binary files /dev/null and b/pictures/image-20230518163703370.png differ diff --git a/pictures/image-20230518163838162.png b/pictures/image-20230518163838162.png new file mode 100644 index 000000000..0a3caae8e Binary files /dev/null and b/pictures/image-20230518163838162.png differ diff --git a/pictures/image-20230518164005690.png b/pictures/image-20230518164005690.png new file mode 100644 index 000000000..0488313df Binary files /dev/null and b/pictures/image-20230518164005690.png differ diff --git a/pictures/image-20230518164217773.png b/pictures/image-20230518164217773.png new file mode 100644 index 000000000..77c46283d Binary files /dev/null and b/pictures/image-20230518164217773.png differ diff --git a/pictures/image-20230518171355588.png b/pictures/image-20230518171355588.png new file mode 100644 index 000000000..83c2355dc Binary files /dev/null and b/pictures/image-20230518171355588.png differ diff --git a/pictures/image-20230518171546236.png b/pictures/image-20230518171546236.png new file mode 100644 index 000000000..8df3f3021 Binary files /dev/null and b/pictures/image-20230518171546236.png differ diff --git a/pictures/image-20230518173203605.png b/pictures/image-20230518173203605.png new file mode 100644 index 000000000..803ae9d30 Binary files /dev/null and b/pictures/image-20230518173203605.png differ diff --git a/pictures/image-20230518221222845.png b/pictures/image-20230518221222845.png new file mode 100644 index 000000000..9bdf0b18b Binary files /dev/null and b/pictures/image-20230518221222845.png differ diff --git a/pictures/image-20230518221433314.png b/pictures/image-20230518221433314.png new file mode 100644 index 000000000..0b0ef2bad Binary files /dev/null and b/pictures/image-20230518221433314.png differ diff --git a/pictures/image-20230518223021928.png b/pictures/image-20230518223021928.png new file mode 100644 index 000000000..938712661 Binary files /dev/null and b/pictures/image-20230518223021928.png differ diff --git a/pictures/image-20230518223525151.png b/pictures/image-20230518223525151.png new file mode 100644 index 000000000..dae25f865 Binary files /dev/null and b/pictures/image-20230518223525151.png differ diff --git a/pictures/image-20230518223921179.png b/pictures/image-20230518223921179.png new file mode 100644 index 000000000..d34e2389b Binary files /dev/null and b/pictures/image-20230518223921179.png differ diff --git a/pictures/image-20230518230016421.png b/pictures/image-20230518230016421.png new file mode 100644 index 000000000..d5ff3b711 Binary files /dev/null and b/pictures/image-20230518230016421.png differ diff --git a/pictures/image-20230518234030568.png b/pictures/image-20230518234030568.png new file mode 100644 index 000000000..fa01aa7e5 Binary files /dev/null and b/pictures/image-20230518234030568.png differ diff --git a/pictures/image-20230518234311272.png b/pictures/image-20230518234311272.png new file mode 100644 index 000000000..2588a448c Binary files /dev/null and b/pictures/image-20230518234311272.png differ diff --git a/pictures/image-20230518234922087.png b/pictures/image-20230518234922087.png new file mode 100644 index 000000000..44dd1fddd Binary files /dev/null and b/pictures/image-20230518234922087.png differ diff --git a/pictures/image-20230518234941961.png b/pictures/image-20230518234941961.png new file mode 100644 index 000000000..c6e53868c Binary files /dev/null and b/pictures/image-20230518234941961.png differ diff --git a/pictures/image-20230519002146272.png b/pictures/image-20230519002146272.png new file mode 100644 index 000000000..012bae501 Binary files /dev/null and b/pictures/image-20230519002146272.png differ diff --git a/pictures/image-20230519005131468.png b/pictures/image-20230519005131468.png new file mode 100644 index 000000000..2442ad69a Binary files /dev/null and b/pictures/image-20230519005131468.png differ diff --git a/pictures/image-20230519161707857.png b/pictures/image-20230519161707857.png new file mode 100644 index 000000000..8452e42c1 Binary files /dev/null and b/pictures/image-20230519161707857.png differ diff --git a/pictures/image-20230519162859782.png b/pictures/image-20230519162859782.png new file mode 100644 index 000000000..cd71bfd07 Binary files /dev/null and b/pictures/image-20230519162859782.png differ diff --git a/pictures/image-20230519164012950.png b/pictures/image-20230519164012950.png new file mode 100644 index 000000000..7503d1f55 Binary files /dev/null and b/pictures/image-20230519164012950.png differ diff --git a/pictures/image-20230519165606784.png b/pictures/image-20230519165606784.png new file mode 100644 index 000000000..6ceaf043d Binary files /dev/null and b/pictures/image-20230519165606784.png differ diff --git a/pictures/image-20230519210917423.png b/pictures/image-20230519210917423.png new file mode 100644 index 000000000..172cdcf71 Binary files /dev/null and b/pictures/image-20230519210917423.png differ diff --git a/pictures/image-20230519213945320.png b/pictures/image-20230519213945320.png new file mode 100644 index 000000000..9f81acb5c Binary files /dev/null and b/pictures/image-20230519213945320.png differ diff --git a/pictures/image-20230519215232782.png b/pictures/image-20230519215232782.png new file mode 100644 index 000000000..21091bda9 Binary files /dev/null and b/pictures/image-20230519215232782.png differ diff --git a/pictures/image-20230519215420623.png b/pictures/image-20230519215420623.png new file mode 100644 index 000000000..3a15e8bc9 Binary files /dev/null and b/pictures/image-20230519215420623.png differ diff --git a/pictures/image-20230519220357933.png b/pictures/image-20230519220357933.png new file mode 100644 index 000000000..ef7c60051 Binary files /dev/null and b/pictures/image-20230519220357933.png differ diff --git a/pictures/image-20230519220922701.png b/pictures/image-20230519220922701.png new file mode 100644 index 000000000..600f32b7f Binary files /dev/null and b/pictures/image-20230519220922701.png differ diff --git a/pictures/image-20230519223403139.png b/pictures/image-20230519223403139.png new file mode 100644 index 000000000..afd2f574d Binary files /dev/null and b/pictures/image-20230519223403139.png differ diff --git a/pictures/image-20230519225637848.png b/pictures/image-20230519225637848.png new file mode 100644 index 000000000..ab6041ef9 Binary files /dev/null and b/pictures/image-20230519225637848.png differ diff --git a/pictures/image-20230519233741029.png b/pictures/image-20230519233741029.png new file mode 100644 index 000000000..0065b9e9f Binary files /dev/null and b/pictures/image-20230519233741029.png differ diff --git a/pictures/image-20230520173824247.png b/pictures/image-20230520173824247.png new file mode 100644 index 000000000..00afb816e Binary files /dev/null and b/pictures/image-20230520173824247.png differ diff --git a/pictures/image-20230520205015056.png b/pictures/image-20230520205015056.png new file mode 100644 index 000000000..897202224 Binary files /dev/null and b/pictures/image-20230520205015056.png differ diff --git a/pictures/image-20230520212001874.png b/pictures/image-20230520212001874.png new file mode 100644 index 000000000..282893c6b Binary files /dev/null and b/pictures/image-20230520212001874.png differ diff --git a/pictures/image-20230520212351174.png b/pictures/image-20230520212351174.png new file mode 100644 index 000000000..98675accd Binary files /dev/null and b/pictures/image-20230520212351174.png differ diff --git a/pictures/image-20230521233651126.png b/pictures/image-20230521233651126.png new file mode 100644 index 000000000..2396a7779 Binary files /dev/null and b/pictures/image-20230521233651126.png differ diff --git a/pictures/image-20230525191246045.png b/pictures/image-20230525191246045.png new file mode 100644 index 000000000..e07b8ad43 Binary files /dev/null and b/pictures/image-20230525191246045.png differ diff --git a/pictures/image-20230525192244158.png b/pictures/image-20230525192244158.png new file mode 100644 index 000000000..1706b400d Binary files /dev/null and b/pictures/image-20230525192244158.png differ diff --git a/pictures/image-20230525195008102.png b/pictures/image-20230525195008102.png new file mode 100644 index 000000000..e9d15efb3 Binary files /dev/null and b/pictures/image-20230525195008102.png differ diff --git a/pictures/image-20230526094633377.png b/pictures/image-20230526094633377.png new file mode 100644 index 000000000..38c7ad4c7 Binary files /dev/null and b/pictures/image-20230526094633377.png differ diff --git a/pictures/image-20230526094945578.png b/pictures/image-20230526094945578.png new file mode 100644 index 000000000..28bb99ab9 Binary files /dev/null and b/pictures/image-20230526094945578.png differ diff --git a/pictures/image-20230526113739081.png b/pictures/image-20230526113739081.png new file mode 100644 index 000000000..9fea28b11 Binary files /dev/null and b/pictures/image-20230526113739081.png differ diff --git a/pictures/image-20230526114127402.png b/pictures/image-20230526114127402.png new file mode 100644 index 000000000..226cb04cd Binary files /dev/null and b/pictures/image-20230526114127402.png differ diff --git a/pictures/image-20230526115027518.png b/pictures/image-20230526115027518.png new file mode 100644 index 000000000..52f1dfae9 Binary files /dev/null and b/pictures/image-20230526115027518.png differ diff --git a/pictures/image-20230526164858205.png b/pictures/image-20230526164858205.png new file mode 100644 index 000000000..5956a12a7 Binary files /dev/null and b/pictures/image-20230526164858205.png differ diff --git a/pictures/image-20230526170222378.png b/pictures/image-20230526170222378.png new file mode 100644 index 000000000..9584c208e Binary files /dev/null and b/pictures/image-20230526170222378.png differ diff --git a/pictures/image-20230526172228532.png b/pictures/image-20230526172228532.png new file mode 100644 index 000000000..ebc672ff9 Binary files /dev/null and b/pictures/image-20230526172228532.png differ diff --git a/pictures/image-20230527215736955.png b/pictures/image-20230527215736955.png new file mode 100644 index 000000000..7da161449 Binary files /dev/null and b/pictures/image-20230527215736955.png differ diff --git a/pictures/image-20230527220905922.png b/pictures/image-20230527220905922.png new file mode 100644 index 000000000..e186a8d85 Binary files /dev/null and b/pictures/image-20230527220905922.png differ diff --git a/pictures/image-20230527221756442.png b/pictures/image-20230527221756442.png new file mode 100644 index 000000000..d03e198d0 Binary files /dev/null and b/pictures/image-20230527221756442.png differ diff --git a/pictures/image-20230529104112317.png b/pictures/image-20230529104112317.png new file mode 100644 index 000000000..a9d390276 Binary files /dev/null and b/pictures/image-20230529104112317.png differ diff --git a/pictures/image-20230529160903551.png b/pictures/image-20230529160903551.png new file mode 100644 index 000000000..2091ce7bc Binary files /dev/null and b/pictures/image-20230529160903551.png differ diff --git a/pictures/image-20230529161308671.png b/pictures/image-20230529161308671.png new file mode 100644 index 000000000..2091ce7bc Binary files /dev/null and b/pictures/image-20230529161308671.png differ diff --git a/pictures/image-20230529161317694.png b/pictures/image-20230529161317694.png new file mode 100644 index 000000000..8b04cc97c Binary files /dev/null and b/pictures/image-20230529161317694.png differ diff --git a/pictures/image-20230529161648257.png b/pictures/image-20230529161648257.png new file mode 100644 index 000000000..3893b0947 Binary files /dev/null and b/pictures/image-20230529161648257.png differ diff --git a/pictures/image-20230529162235482.png b/pictures/image-20230529162235482.png new file mode 100644 index 000000000..4f7e8ee0e Binary files /dev/null and b/pictures/image-20230529162235482.png differ diff --git a/pictures/image-20230529162946584.png b/pictures/image-20230529162946584.png new file mode 100644 index 000000000..f971daf6d Binary files /dev/null and b/pictures/image-20230529162946584.png differ diff --git a/pictures/image-20230529170149102.png b/pictures/image-20230529170149102.png new file mode 100644 index 000000000..0079b6b27 Binary files /dev/null and b/pictures/image-20230529170149102.png differ diff --git a/pictures/image-20230529170214769.png b/pictures/image-20230529170214769.png new file mode 100644 index 000000000..e88b8b037 Binary files /dev/null and b/pictures/image-20230529170214769.png differ diff --git a/pictures/image-20230530101020552.png b/pictures/image-20230530101020552.png new file mode 100644 index 000000000..b4827939c Binary files /dev/null and b/pictures/image-20230530101020552.png differ diff --git a/pictures/image-20230530101803392.png b/pictures/image-20230530101803392.png new file mode 100644 index 000000000..9e4ab4485 Binary files /dev/null and b/pictures/image-20230530101803392.png differ diff --git a/pictures/image-20230530105652529.png b/pictures/image-20230530105652529.png new file mode 100644 index 000000000..b443d591e Binary files /dev/null and b/pictures/image-20230530105652529.png differ diff --git a/pictures/image-20230530110145048.png b/pictures/image-20230530110145048.png new file mode 100644 index 000000000..897b6d85f Binary files /dev/null and b/pictures/image-20230530110145048.png differ diff --git a/pictures/image-20230530110511769.png b/pictures/image-20230530110511769.png new file mode 100644 index 000000000..a626ed625 Binary files /dev/null and b/pictures/image-20230530110511769.png differ diff --git a/pictures/image-20230530111856491.png b/pictures/image-20230530111856491.png new file mode 100644 index 000000000..3ccd8ccb2 Binary files /dev/null and b/pictures/image-20230530111856491.png differ diff --git a/pictures/image-20230530112115261.png b/pictures/image-20230530112115261.png new file mode 100644 index 000000000..dd3492302 Binary files /dev/null and b/pictures/image-20230530112115261.png differ diff --git a/pictures/image-20230530112630678.png b/pictures/image-20230530112630678.png new file mode 100644 index 000000000..2c0638b8a Binary files /dev/null and b/pictures/image-20230530112630678.png differ diff --git a/pictures/image-20230530172846989.png b/pictures/image-20230530172846989.png new file mode 100644 index 000000000..5af03078b Binary files /dev/null and b/pictures/image-20230530172846989.png differ diff --git a/pictures/image-20230530173658950.png b/pictures/image-20230530173658950.png new file mode 100644 index 000000000..593564f0a Binary files /dev/null and b/pictures/image-20230530173658950.png differ diff --git a/pictures/image-20230530173801824.png b/pictures/image-20230530173801824.png new file mode 100644 index 000000000..0d11c1702 Binary files /dev/null and b/pictures/image-20230530173801824.png differ diff --git a/pictures/image-20230530174911409.png b/pictures/image-20230530174911409.png new file mode 100644 index 000000000..ec6a0ea0c Binary files /dev/null and b/pictures/image-20230530174911409.png differ diff --git a/pictures/image-20230530181102832.png b/pictures/image-20230530181102832.png new file mode 100644 index 000000000..d0108bbf9 Binary files /dev/null and b/pictures/image-20230530181102832.png differ diff --git a/pictures/image-20230603154356433.png b/pictures/image-20230603154356433.png new file mode 100644 index 000000000..b0b13dff8 Binary files /dev/null and b/pictures/image-20230603154356433.png differ diff --git a/pictures/image-20230603154531129.png b/pictures/image-20230603154531129.png new file mode 100644 index 000000000..ae5e5a838 Binary files /dev/null and b/pictures/image-20230603154531129.png differ diff --git a/pictures/image-20230603154622726.png b/pictures/image-20230603154622726.png new file mode 100644 index 000000000..8ab032972 Binary files /dev/null and b/pictures/image-20230603154622726.png differ diff --git a/pictures/image-20230603193632659.png b/pictures/image-20230603193632659.png new file mode 100644 index 000000000..4ff8c3326 Binary files /dev/null and b/pictures/image-20230603193632659.png differ diff --git a/pictures/image-20230603202245269.png b/pictures/image-20230603202245269.png new file mode 100644 index 000000000..fbe77a9d7 Binary files /dev/null and b/pictures/image-20230603202245269.png differ diff --git a/pictures/image-20230604120307280.png b/pictures/image-20230604120307280.png new file mode 100644 index 000000000..b918573c8 Binary files /dev/null and b/pictures/image-20230604120307280.png differ diff --git a/pictures/image-20230604120436139.png b/pictures/image-20230604120436139.png new file mode 100644 index 000000000..3d68a4d70 Binary files /dev/null and b/pictures/image-20230604120436139.png differ diff --git a/pictures/image-20230604120610791.png b/pictures/image-20230604120610791.png new file mode 100644 index 000000000..ef69b7d70 Binary files /dev/null and b/pictures/image-20230604120610791.png differ diff --git a/pictures/image-20230604120654078.png b/pictures/image-20230604120654078.png new file mode 100644 index 000000000..d0f45806a Binary files /dev/null and b/pictures/image-20230604120654078.png differ diff --git a/pictures/image-20230604121126549.png b/pictures/image-20230604121126549.png new file mode 100644 index 000000000..c8e3767cc Binary files /dev/null and b/pictures/image-20230604121126549.png differ diff --git a/pictures/image-20230604151530534.png b/pictures/image-20230604151530534.png new file mode 100644 index 000000000..c837ad704 Binary files /dev/null and b/pictures/image-20230604151530534.png differ diff --git a/pictures/image-20230605145343384.png b/pictures/image-20230605145343384.png new file mode 100644 index 000000000..a9421dd43 Binary files /dev/null and b/pictures/image-20230605145343384.png differ diff --git a/pictures/image-20230605145433594.png b/pictures/image-20230605145433594.png new file mode 100644 index 000000000..c4c33fcc3 Binary files /dev/null and b/pictures/image-20230605145433594.png differ diff --git a/pictures/image-20230605153935967.png b/pictures/image-20230605153935967.png new file mode 100644 index 000000000..a08089be0 Binary files /dev/null and b/pictures/image-20230605153935967.png differ diff --git a/pictures/image-20230605154629290.png b/pictures/image-20230605154629290.png new file mode 100644 index 000000000..d43b5d852 Binary files /dev/null and b/pictures/image-20230605154629290.png differ diff --git a/pictures/image-20230605163004850.png b/pictures/image-20230605163004850.png new file mode 100644 index 000000000..6bae49221 Binary files /dev/null and b/pictures/image-20230605163004850.png differ diff --git a/pictures/image-20230609220809366.png b/pictures/image-20230609220809366.png new file mode 100644 index 000000000..d5eb21387 Binary files /dev/null and b/pictures/image-20230609220809366.png differ diff --git a/pictures/image-20230609221040152.png b/pictures/image-20230609221040152.png new file mode 100644 index 000000000..68564aff2 Binary files /dev/null and b/pictures/image-20230609221040152.png differ diff --git a/pictures/image-20230611111440999.png b/pictures/image-20230611111440999.png new file mode 100644 index 000000000..ff5fd8e09 Binary files /dev/null and b/pictures/image-20230611111440999.png differ diff --git a/pictures/image-20230611153418426.png b/pictures/image-20230611153418426.png new file mode 100644 index 000000000..029495f79 Binary files /dev/null and b/pictures/image-20230611153418426.png differ diff --git a/pictures/image-20230611211438017.png b/pictures/image-20230611211438017.png new file mode 100644 index 000000000..ca52edfb8 Binary files /dev/null and b/pictures/image-20230611211438017.png differ diff --git a/pictures/image-20230611214209712.png b/pictures/image-20230611214209712.png new file mode 100644 index 000000000..47bb32c4b Binary files /dev/null and b/pictures/image-20230611214209712.png differ diff --git a/pictures/image-20230612111805518.png b/pictures/image-20230612111805518.png new file mode 100644 index 000000000..7fd87e0ba Binary files /dev/null and b/pictures/image-20230612111805518.png differ diff --git a/pictures/image-20230612114238391.png b/pictures/image-20230612114238391.png new file mode 100644 index 000000000..57e4183cd Binary files /dev/null and b/pictures/image-20230612114238391.png differ diff --git a/pictures/image-20230612114314757.png b/pictures/image-20230612114314757.png new file mode 100644 index 000000000..d9d2e9e76 Binary files /dev/null and b/pictures/image-20230612114314757.png differ diff --git a/pictures/image-20230612165021780.png b/pictures/image-20230612165021780.png new file mode 100644 index 000000000..25b0a98c0 Binary files /dev/null and b/pictures/image-20230612165021780.png differ diff --git a/pictures/image-20230612170704286.png b/pictures/image-20230612170704286.png new file mode 100644 index 000000000..817cec74d Binary files /dev/null and b/pictures/image-20230612170704286.png differ diff --git a/pictures/image-20230614105450858.png b/pictures/image-20230614105450858.png new file mode 100644 index 000000000..6760dd75c Binary files /dev/null and b/pictures/image-20230614105450858.png differ diff --git a/pictures/image-20230614111024292.png b/pictures/image-20230614111024292.png new file mode 100644 index 000000000..a0dda9482 Binary files /dev/null and b/pictures/image-20230614111024292.png differ diff --git a/pictures/image-20230615151144311.png b/pictures/image-20230615151144311.png new file mode 100644 index 000000000..cc2fceeec Binary files /dev/null and b/pictures/image-20230615151144311.png differ diff --git a/pictures/image-20230615152331597.png b/pictures/image-20230615152331597.png new file mode 100644 index 000000000..351eaa973 Binary files /dev/null and b/pictures/image-20230615152331597.png differ diff --git a/pictures/image-20230616152846339.png b/pictures/image-20230616152846339.png new file mode 100644 index 000000000..1424960cd Binary files /dev/null and b/pictures/image-20230616152846339.png differ diff --git a/pictures/image-20230616153304953.png b/pictures/image-20230616153304953.png new file mode 100644 index 000000000..377a575cb Binary files /dev/null and b/pictures/image-20230616153304953.png differ diff --git a/pictures/image-20230617173759895.png b/pictures/image-20230617173759895.png new file mode 100644 index 000000000..33da14fe7 Binary files /dev/null and b/pictures/image-20230617173759895.png differ diff --git a/pictures/image-20230618232437618.png b/pictures/image-20230618232437618.png new file mode 100644 index 000000000..29ab80269 Binary files /dev/null and b/pictures/image-20230618232437618.png differ diff --git a/pictures/image-20230619111338153.png b/pictures/image-20230619111338153.png new file mode 100644 index 000000000..0ea8f58aa Binary files /dev/null and b/pictures/image-20230619111338153.png differ diff --git a/pictures/image-20230619111515367.png b/pictures/image-20230619111515367.png new file mode 100644 index 000000000..9c3e16242 Binary files /dev/null and b/pictures/image-20230619111515367.png differ diff --git a/pictures/image-20230620092919922.png b/pictures/image-20230620092919922.png new file mode 100644 index 000000000..ba21879be Binary files /dev/null and b/pictures/image-20230620092919922.png differ diff --git a/pictures/image-20230620093420150.png b/pictures/image-20230620093420150.png new file mode 100644 index 000000000..b4a2cd45e Binary files /dev/null and b/pictures/image-20230620093420150.png differ diff --git a/pictures/image-20230620101533445.png b/pictures/image-20230620101533445.png new file mode 100644 index 000000000..55d3ade98 Binary files /dev/null and b/pictures/image-20230620101533445.png differ diff --git a/pictures/image-20230624192906124.png b/pictures/image-20230624192906124.png new file mode 100644 index 000000000..bfa283b45 Binary files /dev/null and b/pictures/image-20230624192906124.png differ diff --git a/pictures/image-20230625090037616.png b/pictures/image-20230625090037616.png new file mode 100644 index 000000000..2b5010dfe Binary files /dev/null and b/pictures/image-20230625090037616.png differ diff --git a/pictures/image-20230625090118059.png b/pictures/image-20230625090118059.png new file mode 100644 index 000000000..44cd11ac1 Binary files /dev/null and b/pictures/image-20230625090118059.png differ diff --git a/pictures/image-20230626101113941.png b/pictures/image-20230626101113941.png new file mode 100644 index 000000000..eb73194dc Binary files /dev/null and b/pictures/image-20230626101113941.png differ diff --git a/pictures/image-20230626142854091.png b/pictures/image-20230626142854091.png new file mode 100644 index 000000000..723b733c1 Binary files /dev/null and b/pictures/image-20230626142854091.png differ diff --git a/pictures/image-20230626143051042.png b/pictures/image-20230626143051042.png new file mode 100644 index 000000000..f4a25f8c2 Binary files /dev/null and b/pictures/image-20230626143051042.png differ diff --git a/pictures/image-20230626143228035.png b/pictures/image-20230626143228035.png new file mode 100644 index 000000000..44d6f4ef7 Binary files /dev/null and b/pictures/image-20230626143228035.png differ diff --git a/pictures/image-20230626182832804.png b/pictures/image-20230626182832804.png new file mode 100644 index 000000000..b053cf647 Binary files /dev/null and b/pictures/image-20230626182832804.png differ diff --git a/pictures/image-20230626184303186.png b/pictures/image-20230626184303186.png new file mode 100644 index 000000000..d277dec89 Binary files /dev/null and b/pictures/image-20230626184303186.png differ diff --git a/pictures/image-20230627102918192.png b/pictures/image-20230627102918192.png new file mode 100644 index 000000000..bca90ff93 Binary files /dev/null and b/pictures/image-20230627102918192.png differ diff --git a/pictures/image-20230627112218894.png b/pictures/image-20230627112218894.png new file mode 100644 index 000000000..cb7c2ed25 Binary files /dev/null and b/pictures/image-20230627112218894.png differ diff --git a/pictures/image-20230627153518692.png b/pictures/image-20230627153518692.png new file mode 100644 index 000000000..dd70cf83a Binary files /dev/null and b/pictures/image-20230627153518692.png differ diff --git a/pictures/image-20230627160605717.png b/pictures/image-20230627160605717.png new file mode 100644 index 000000000..dc0f1c77c Binary files /dev/null and b/pictures/image-20230627160605717.png differ diff --git a/pictures/image-20230627161955351.png b/pictures/image-20230627161955351.png new file mode 100644 index 000000000..8cc707665 Binary files /dev/null and b/pictures/image-20230627161955351.png differ diff --git a/pictures/image-20230627162707377.png b/pictures/image-20230627162707377.png new file mode 100644 index 000000000..739141681 Binary files /dev/null and b/pictures/image-20230627162707377.png differ diff --git a/pictures/image-20230628165931139.png b/pictures/image-20230628165931139.png new file mode 100644 index 000000000..a45c3bb0d Binary files /dev/null and b/pictures/image-20230628165931139.png differ diff --git a/pictures/image-20230628170218776.png b/pictures/image-20230628170218776.png new file mode 100644 index 000000000..80d085adc Binary files /dev/null and b/pictures/image-20230628170218776.png differ diff --git a/pictures/image-20230628171558364.png b/pictures/image-20230628171558364.png new file mode 100644 index 000000000..c3f686ebd Binary files /dev/null and b/pictures/image-20230628171558364.png differ diff --git a/pictures/image-20230628180000365.png b/pictures/image-20230628180000365.png new file mode 100644 index 000000000..ed22efef8 Binary files /dev/null and b/pictures/image-20230628180000365.png differ diff --git a/pictures/image-20230628191936254.png b/pictures/image-20230628191936254.png new file mode 100644 index 000000000..df06a6239 Binary files /dev/null and b/pictures/image-20230628191936254.png differ diff --git a/pictures/image-20230701104616032.png b/pictures/image-20230701104616032.png new file mode 100644 index 000000000..ee5099450 Binary files /dev/null and b/pictures/image-20230701104616032.png differ diff --git a/pictures/image-20230701144040577.png b/pictures/image-20230701144040577.png new file mode 100644 index 000000000..d47469d74 Binary files /dev/null and b/pictures/image-20230701144040577.png differ diff --git a/pictures/image-20230701145518348.png b/pictures/image-20230701145518348.png new file mode 100644 index 000000000..7b2ce8f9e Binary files /dev/null and b/pictures/image-20230701145518348.png differ diff --git a/pictures/image-20230701151445190.png b/pictures/image-20230701151445190.png new file mode 100644 index 000000000..3393d5ae7 Binary files /dev/null and b/pictures/image-20230701151445190.png differ diff --git a/pictures/image-20230701152501989.png b/pictures/image-20230701152501989.png new file mode 100644 index 000000000..f213ebd20 Binary files /dev/null and b/pictures/image-20230701152501989.png differ diff --git a/pictures/image-20230701152648756.png b/pictures/image-20230701152648756.png new file mode 100644 index 000000000..58ed3ca01 Binary files /dev/null and b/pictures/image-20230701152648756.png differ diff --git a/pictures/image-20230701153754438.png b/pictures/image-20230701153754438.png new file mode 100644 index 000000000..3c3623835 Binary files /dev/null and b/pictures/image-20230701153754438.png differ diff --git a/pictures/image-20230701154217181.png b/pictures/image-20230701154217181.png new file mode 100644 index 000000000..2b86c8d17 Binary files /dev/null and b/pictures/image-20230701154217181.png differ diff --git a/pictures/image-20230701154839574.png b/pictures/image-20230701154839574.png new file mode 100644 index 000000000..4c48be2cb Binary files /dev/null and b/pictures/image-20230701154839574.png differ diff --git a/pictures/image-20230701155812781.png b/pictures/image-20230701155812781.png new file mode 100644 index 000000000..df07c6e21 Binary files /dev/null and b/pictures/image-20230701155812781.png differ diff --git a/pictures/image-20230701162247192.png b/pictures/image-20230701162247192.png new file mode 100644 index 000000000..8996bd407 Binary files /dev/null and b/pictures/image-20230701162247192.png differ diff --git a/pictures/image-20230701163210869.png b/pictures/image-20230701163210869.png new file mode 100644 index 000000000..5b4303b5d Binary files /dev/null and b/pictures/image-20230701163210869.png differ diff --git a/pictures/image-20230702111451660.png b/pictures/image-20230702111451660.png new file mode 100644 index 000000000..2c8132c97 Binary files /dev/null and b/pictures/image-20230702111451660.png differ diff --git a/pictures/image-20230702111554667.png b/pictures/image-20230702111554667.png new file mode 100644 index 000000000..345058baa Binary files /dev/null and b/pictures/image-20230702111554667.png differ diff --git a/pictures/image-20230703101958163.png b/pictures/image-20230703101958163.png new file mode 100644 index 000000000..03c0ab7b4 Binary files /dev/null and b/pictures/image-20230703101958163.png differ diff --git a/pictures/image-20230703102805047.png b/pictures/image-20230703102805047.png new file mode 100644 index 000000000..1852f0d43 Binary files /dev/null and b/pictures/image-20230703102805047.png differ diff --git a/pictures/image-20230703105247964.png b/pictures/image-20230703105247964.png new file mode 100644 index 000000000..813237e81 Binary files /dev/null and b/pictures/image-20230703105247964.png differ diff --git a/pictures/image-20230703112342368.png b/pictures/image-20230703112342368.png new file mode 100644 index 000000000..955fcd807 Binary files /dev/null and b/pictures/image-20230703112342368.png differ diff --git a/pictures/image-20230703113121234.png b/pictures/image-20230703113121234.png new file mode 100644 index 000000000..03405e6b3 Binary files /dev/null and b/pictures/image-20230703113121234.png differ diff --git a/pictures/image-20230703115140810.png b/pictures/image-20230703115140810.png new file mode 100644 index 000000000..fcf10abc9 Binary files /dev/null and b/pictures/image-20230703115140810.png differ diff --git a/pictures/image-20230703152110823.png b/pictures/image-20230703152110823.png new file mode 100644 index 000000000..eaeb42574 Binary files /dev/null and b/pictures/image-20230703152110823.png differ diff --git a/pictures/image-20230703170719714.png b/pictures/image-20230703170719714.png new file mode 100644 index 000000000..fd3a925f1 Binary files /dev/null and b/pictures/image-20230703170719714.png differ diff --git a/pictures/image-20230703171420471.png b/pictures/image-20230703171420471.png new file mode 100644 index 000000000..44c15e5cd Binary files /dev/null and b/pictures/image-20230703171420471.png differ diff --git a/pictures/image-20230706112521327.png b/pictures/image-20230706112521327.png new file mode 100644 index 000000000..d8c6f9086 Binary files /dev/null and b/pictures/image-20230706112521327.png differ diff --git a/pictures/image-20230706114304695.png b/pictures/image-20230706114304695.png new file mode 100644 index 000000000..85e26c8d4 Binary files /dev/null and b/pictures/image-20230706114304695.png differ diff --git a/pictures/image-20230706145050684.png b/pictures/image-20230706145050684.png new file mode 100644 index 000000000..d52bf831a Binary files /dev/null and b/pictures/image-20230706145050684.png differ diff --git a/pictures/image-20230706152329777.png b/pictures/image-20230706152329777.png new file mode 100644 index 000000000..57af61a0e Binary files /dev/null and b/pictures/image-20230706152329777.png differ diff --git a/pictures/image-20230706154112026.png b/pictures/image-20230706154112026.png new file mode 100644 index 000000000..23848eed0 Binary files /dev/null and b/pictures/image-20230706154112026.png differ diff --git a/pictures/image-20230706161401528.png b/pictures/image-20230706161401528.png new file mode 100644 index 000000000..37bcf7ebd Binary files /dev/null and b/pictures/image-20230706161401528.png differ diff --git a/pictures/image-20230707151505310.png b/pictures/image-20230707151505310.png new file mode 100644 index 000000000..069f8ba9a Binary files /dev/null and b/pictures/image-20230707151505310.png differ diff --git a/pictures/image-20230707151543593.png b/pictures/image-20230707151543593.png new file mode 100644 index 000000000..2d36b673f Binary files /dev/null and b/pictures/image-20230707151543593.png differ diff --git a/pictures/image-20230707153628394.png b/pictures/image-20230707153628394.png new file mode 100644 index 000000000..8686970f3 Binary files /dev/null and b/pictures/image-20230707153628394.png differ diff --git a/pictures/image-20230707154052952.png b/pictures/image-20230707154052952.png new file mode 100644 index 000000000..a316a4845 Binary files /dev/null and b/pictures/image-20230707154052952.png differ diff --git a/pictures/image-20230707160328001.png b/pictures/image-20230707160328001.png new file mode 100644 index 000000000..fb178b6f9 Binary files /dev/null and b/pictures/image-20230707160328001.png differ diff --git a/pictures/image-20230708092745456.png b/pictures/image-20230708092745456.png new file mode 100644 index 000000000..d958159ff Binary files /dev/null and b/pictures/image-20230708092745456.png differ diff --git a/pictures/image-20230708093600923.png b/pictures/image-20230708093600923.png new file mode 100644 index 000000000..4743c246a Binary files /dev/null and b/pictures/image-20230708093600923.png differ diff --git a/pictures/image-20230708095940126.png b/pictures/image-20230708095940126.png new file mode 100644 index 000000000..edf230913 Binary files /dev/null and b/pictures/image-20230708095940126.png differ diff --git a/pictures/image-20230708104321024.png b/pictures/image-20230708104321024.png new file mode 100644 index 000000000..1d8b2b569 Binary files /dev/null and b/pictures/image-20230708104321024.png differ diff --git a/pictures/image-20230708104848501.png b/pictures/image-20230708104848501.png new file mode 100644 index 000000000..2cc7d0608 Binary files /dev/null and b/pictures/image-20230708104848501.png differ diff --git a/pictures/image-20230708152102009.png b/pictures/image-20230708152102009.png new file mode 100644 index 000000000..299305cba Binary files /dev/null and b/pictures/image-20230708152102009.png differ diff --git a/pictures/image-20230709155555491.png b/pictures/image-20230709155555491.png new file mode 100644 index 000000000..2176d1f54 Binary files /dev/null and b/pictures/image-20230709155555491.png differ diff --git a/pictures/image-20230709160343565.png b/pictures/image-20230709160343565.png new file mode 100644 index 000000000..5ddb8d7db Binary files /dev/null and b/pictures/image-20230709160343565.png differ diff --git a/pictures/image-20230709160552461.png b/pictures/image-20230709160552461.png new file mode 100644 index 000000000..12f211bf0 Binary files /dev/null and b/pictures/image-20230709160552461.png differ diff --git a/pictures/image-20230709160932416.png b/pictures/image-20230709160932416.png new file mode 100644 index 000000000..6a27a38d8 Binary files /dev/null and b/pictures/image-20230709160932416.png differ diff --git a/pictures/image-20230709161151542.png b/pictures/image-20230709161151542.png new file mode 100644 index 000000000..0ea83c707 Binary files /dev/null and b/pictures/image-20230709161151542.png differ diff --git a/pictures/image-20230709161346717.png b/pictures/image-20230709161346717.png new file mode 100644 index 000000000..11ebeb4db Binary files /dev/null and b/pictures/image-20230709161346717.png differ diff --git a/pictures/image-20230709161600398.png b/pictures/image-20230709161600398.png new file mode 100644 index 000000000..204d6ad14 Binary files /dev/null and b/pictures/image-20230709161600398.png differ diff --git a/pictures/image-20230709161752103.png b/pictures/image-20230709161752103.png new file mode 100644 index 000000000..7e7b166e5 Binary files /dev/null and b/pictures/image-20230709161752103.png differ diff --git a/pictures/image-20230709162502383.png b/pictures/image-20230709162502383.png new file mode 100644 index 000000000..c2157d841 Binary files /dev/null and b/pictures/image-20230709162502383.png differ diff --git a/pictures/image-20230709163303477.png b/pictures/image-20230709163303477.png new file mode 100644 index 000000000..25438b190 Binary files /dev/null and b/pictures/image-20230709163303477.png differ diff --git a/pictures/image-20230709163457833.png b/pictures/image-20230709163457833.png new file mode 100644 index 000000000..6d191ec60 Binary files /dev/null and b/pictures/image-20230709163457833.png differ diff --git a/pictures/image-20230709163639856.png b/pictures/image-20230709163639856.png new file mode 100644 index 000000000..31312aa29 Binary files /dev/null and b/pictures/image-20230709163639856.png differ diff --git a/pictures/image-20230709164012746.png b/pictures/image-20230709164012746.png new file mode 100644 index 000000000..9023b84f1 Binary files /dev/null and b/pictures/image-20230709164012746.png differ diff --git a/pictures/image-20230709164208747.png b/pictures/image-20230709164208747.png new file mode 100644 index 000000000..25a7ab836 Binary files /dev/null and b/pictures/image-20230709164208747.png differ diff --git a/pictures/image-20230709170626946.png b/pictures/image-20230709170626946.png new file mode 100644 index 000000000..542bddd73 Binary files /dev/null and b/pictures/image-20230709170626946.png differ diff --git a/pictures/image-20230710111805705.png b/pictures/image-20230710111805705.png new file mode 100644 index 000000000..691f0f64f Binary files /dev/null and b/pictures/image-20230710111805705.png differ diff --git a/pictures/image-20230710143341531.png b/pictures/image-20230710143341531.png new file mode 100644 index 000000000..aee5ef9f5 Binary files /dev/null and b/pictures/image-20230710143341531.png differ diff --git a/pictures/image-20230710143906286.png b/pictures/image-20230710143906286.png new file mode 100644 index 000000000..c70b64720 Binary files /dev/null and b/pictures/image-20230710143906286.png differ diff --git a/pictures/image-20230710144052877.png b/pictures/image-20230710144052877.png new file mode 100644 index 000000000..20a28ac0e Binary files /dev/null and b/pictures/image-20230710144052877.png differ diff --git a/pictures/image-20230710145648233.png b/pictures/image-20230710145648233.png new file mode 100644 index 000000000..9af07d1bb Binary files /dev/null and b/pictures/image-20230710145648233.png differ diff --git a/pictures/image-20230710153635804.png b/pictures/image-20230710153635804.png new file mode 100644 index 000000000..e6ebb8ff9 Binary files /dev/null and b/pictures/image-20230710153635804.png differ diff --git a/pictures/image-20230710153651752.png b/pictures/image-20230710153651752.png new file mode 100644 index 000000000..04afa171d Binary files /dev/null and b/pictures/image-20230710153651752.png differ diff --git a/pictures/image-20230710153709464.png b/pictures/image-20230710153709464.png new file mode 100644 index 000000000..475bc0895 Binary files /dev/null and b/pictures/image-20230710153709464.png differ diff --git a/pictures/image-20230710163735955.png b/pictures/image-20230710163735955.png new file mode 100644 index 000000000..6c4923495 Binary files /dev/null and b/pictures/image-20230710163735955.png differ diff --git a/pictures/image-20230710165738655.png b/pictures/image-20230710165738655.png new file mode 100644 index 000000000..3f66a81a7 Binary files /dev/null and b/pictures/image-20230710165738655.png differ diff --git a/pictures/image-20230714163437984.png b/pictures/image-20230714163437984.png new file mode 100644 index 000000000..de5fdd1e1 Binary files /dev/null and b/pictures/image-20230714163437984.png differ diff --git a/pictures/image-20230714171221296.png b/pictures/image-20230714171221296.png new file mode 100644 index 000000000..0fda50a99 Binary files /dev/null and b/pictures/image-20230714171221296.png differ diff --git a/pictures/image-20230714175205635.png b/pictures/image-20230714175205635.png new file mode 100644 index 000000000..9c3210531 Binary files /dev/null and b/pictures/image-20230714175205635.png differ diff --git a/pictures/image-20230714181620263.png b/pictures/image-20230714181620263.png new file mode 100644 index 000000000..500e49ecc Binary files /dev/null and b/pictures/image-20230714181620263.png differ diff --git a/pictures/image-20230714182057982.png b/pictures/image-20230714182057982.png new file mode 100644 index 000000000..a3777e32c Binary files /dev/null and b/pictures/image-20230714182057982.png differ diff --git a/pictures/image-20230716100845389.png b/pictures/image-20230716100845389.png new file mode 100644 index 000000000..af12dbdc9 Binary files /dev/null and b/pictures/image-20230716100845389.png differ diff --git a/pictures/image-20230716100909465.png b/pictures/image-20230716100909465.png new file mode 100644 index 000000000..247a1ae0c Binary files /dev/null and b/pictures/image-20230716100909465.png differ diff --git a/pictures/image-20230716101939869.png b/pictures/image-20230716101939869.png new file mode 100644 index 000000000..7665aa260 Binary files /dev/null and b/pictures/image-20230716101939869.png differ diff --git a/pictures/image-20230716102510738.png b/pictures/image-20230716102510738.png new file mode 100644 index 000000000..ef9e38012 Binary files /dev/null and b/pictures/image-20230716102510738.png differ diff --git a/pictures/image-20230716112105848.png b/pictures/image-20230716112105848.png new file mode 100644 index 000000000..f87c69b86 Binary files /dev/null and b/pictures/image-20230716112105848.png differ diff --git a/pictures/image-20230716152821097.png b/pictures/image-20230716152821097.png new file mode 100644 index 000000000..fb8198e67 Binary files /dev/null and b/pictures/image-20230716152821097.png differ diff --git a/pictures/image-20230716160027391.png b/pictures/image-20230716160027391.png new file mode 100644 index 000000000..320e09699 Binary files /dev/null and b/pictures/image-20230716160027391.png differ diff --git a/pictures/image-20230716173453604.png b/pictures/image-20230716173453604.png new file mode 100644 index 000000000..76e33eeeb Binary files /dev/null and b/pictures/image-20230716173453604.png differ diff --git a/pictures/image-20230716173628024.png b/pictures/image-20230716173628024.png new file mode 100644 index 000000000..de6282b57 Binary files /dev/null and b/pictures/image-20230716173628024.png differ diff --git a/pictures/image-20230716173751202.png b/pictures/image-20230716173751202.png new file mode 100644 index 000000000..f6379afaa Binary files /dev/null and b/pictures/image-20230716173751202.png differ diff --git a/pictures/image-20230716181052580.png b/pictures/image-20230716181052580.png new file mode 100644 index 000000000..1d8d01fa7 Binary files /dev/null and b/pictures/image-20230716181052580.png differ diff --git a/pictures/image-20230717094656565.png b/pictures/image-20230717094656565.png new file mode 100644 index 000000000..551f19fd5 Binary files /dev/null and b/pictures/image-20230717094656565.png differ diff --git a/pictures/image-20230717095740594.png b/pictures/image-20230717095740594.png new file mode 100644 index 000000000..a3d3f29aa Binary files /dev/null and b/pictures/image-20230717095740594.png differ diff --git a/pictures/image-20230717123217404.png b/pictures/image-20230717123217404.png new file mode 100644 index 000000000..2665c4a0c Binary files /dev/null and b/pictures/image-20230717123217404.png differ diff --git a/pictures/image-20230717183636642.png b/pictures/image-20230717183636642.png new file mode 100644 index 000000000..61d334fef Binary files /dev/null and b/pictures/image-20230717183636642.png differ diff --git a/pictures/image-20230717200635208.png b/pictures/image-20230717200635208.png new file mode 100644 index 000000000..c20e617ee Binary files /dev/null and b/pictures/image-20230717200635208.png differ diff --git a/pictures/image-20230718102912884.png b/pictures/image-20230718102912884.png new file mode 100644 index 000000000..5d7183506 Binary files /dev/null and b/pictures/image-20230718102912884.png differ diff --git a/pictures/image-20230718102955735.png b/pictures/image-20230718102955735.png new file mode 100644 index 000000000..0ce2559d5 Binary files /dev/null and b/pictures/image-20230718102955735.png differ diff --git a/pictures/image-20230718103127971.png b/pictures/image-20230718103127971.png new file mode 100644 index 000000000..c2a61adcf Binary files /dev/null and b/pictures/image-20230718103127971.png differ diff --git a/pictures/image-20230718103316734.png b/pictures/image-20230718103316734.png new file mode 100644 index 000000000..7dad8309b Binary files /dev/null and b/pictures/image-20230718103316734.png differ diff --git a/pictures/image-20230718111914252.png b/pictures/image-20230718111914252.png new file mode 100644 index 000000000..12dd3ccae Binary files /dev/null and b/pictures/image-20230718111914252.png differ diff --git a/pictures/image-20230718112624247.png b/pictures/image-20230718112624247.png new file mode 100644 index 000000000..aa747cd56 Binary files /dev/null and b/pictures/image-20230718112624247.png differ diff --git a/pictures/image-20230718113250173.png b/pictures/image-20230718113250173.png new file mode 100644 index 000000000..fd4655672 Binary files /dev/null and b/pictures/image-20230718113250173.png differ diff --git a/pictures/image-20230718144613165.png b/pictures/image-20230718144613165.png new file mode 100644 index 000000000..993f6f1af Binary files /dev/null and b/pictures/image-20230718144613165.png differ diff --git a/pictures/image-20230718161455207.png b/pictures/image-20230718161455207.png new file mode 100644 index 000000000..0b8230251 Binary files /dev/null and b/pictures/image-20230718161455207.png differ diff --git a/pictures/image-20230719101438239.png b/pictures/image-20230719101438239.png new file mode 100644 index 000000000..20ba03d35 Binary files /dev/null and b/pictures/image-20230719101438239.png differ diff --git a/pictures/image-20230719101554905.png b/pictures/image-20230719101554905.png new file mode 100644 index 000000000..e9077c7e2 Binary files /dev/null and b/pictures/image-20230719101554905.png differ diff --git a/pictures/image-20230719153737087.png b/pictures/image-20230719153737087.png new file mode 100644 index 000000000..77d7e847b Binary files /dev/null and b/pictures/image-20230719153737087.png differ diff --git a/pictures/image-20230720085336595.png b/pictures/image-20230720085336595.png new file mode 100644 index 000000000..d7306dd8f Binary files /dev/null and b/pictures/image-20230720085336595.png differ diff --git a/pictures/image-20230723162613707.png b/pictures/image-20230723162613707.png new file mode 100644 index 000000000..27af0a1d0 Binary files /dev/null and b/pictures/image-20230723162613707.png differ diff --git a/pictures/image-20230724143913498.png b/pictures/image-20230724143913498.png new file mode 100644 index 000000000..8062a8dfe Binary files /dev/null and b/pictures/image-20230724143913498.png differ diff --git a/pictures/image-20230724143941076.png b/pictures/image-20230724143941076.png new file mode 100644 index 000000000..eb9bc9699 Binary files /dev/null and b/pictures/image-20230724143941076.png differ diff --git a/pictures/image-20230724144239262.png b/pictures/image-20230724144239262.png new file mode 100644 index 000000000..c8849cb64 Binary files /dev/null and b/pictures/image-20230724144239262.png differ diff --git a/pictures/image-20230724144452090.png b/pictures/image-20230724144452090.png new file mode 100644 index 000000000..1662fa082 Binary files /dev/null and b/pictures/image-20230724144452090.png differ diff --git a/pictures/image-20230724144924432.png b/pictures/image-20230724144924432.png new file mode 100644 index 000000000..5c9b704bf Binary files /dev/null and b/pictures/image-20230724144924432.png differ diff --git a/pictures/image-20230725125615906.png b/pictures/image-20230725125615906.png new file mode 100644 index 000000000..98b641c6c Binary files /dev/null and b/pictures/image-20230725125615906.png differ diff --git a/pictures/image-20230725130201249.png b/pictures/image-20230725130201249.png new file mode 100644 index 000000000..1934b7438 Binary files /dev/null and b/pictures/image-20230725130201249.png differ diff --git a/pictures/image-20230725160846882.png b/pictures/image-20230725160846882.png new file mode 100644 index 000000000..ed90c0733 Binary files /dev/null and b/pictures/image-20230725160846882.png differ diff --git a/pictures/image-20230725162201612.png b/pictures/image-20230725162201612.png new file mode 100644 index 000000000..79cb3ad75 Binary files /dev/null and b/pictures/image-20230725162201612.png differ diff --git a/pictures/image-20230725163026091.png b/pictures/image-20230725163026091.png new file mode 100644 index 000000000..3925a105f Binary files /dev/null and b/pictures/image-20230725163026091.png differ diff --git a/pictures/image-20230725163212347.png b/pictures/image-20230725163212347.png new file mode 100644 index 000000000..887a1aeef Binary files /dev/null and b/pictures/image-20230725163212347.png differ diff --git a/pictures/image-20230725164124574.png b/pictures/image-20230725164124574.png new file mode 100644 index 000000000..f94fb62a9 Binary files /dev/null and b/pictures/image-20230725164124574.png differ diff --git a/pictures/image-20230725165414051.png b/pictures/image-20230725165414051.png new file mode 100644 index 000000000..ba7e2e564 Binary files /dev/null and b/pictures/image-20230725165414051.png differ diff --git a/pictures/image-20230725170257030.png b/pictures/image-20230725170257030.png new file mode 100644 index 000000000..20a0d2e07 Binary files /dev/null and b/pictures/image-20230725170257030.png differ diff --git a/pictures/image-20230725170329032.png b/pictures/image-20230725170329032.png new file mode 100644 index 000000000..53eec1a78 Binary files /dev/null and b/pictures/image-20230725170329032.png differ diff --git a/pictures/image-20230725171130925.png b/pictures/image-20230725171130925.png new file mode 100644 index 000000000..e2c609d45 Binary files /dev/null and b/pictures/image-20230725171130925.png differ diff --git a/pictures/image-20230725171550618.png b/pictures/image-20230725171550618.png new file mode 100644 index 000000000..ce0bd619e Binary files /dev/null and b/pictures/image-20230725171550618.png differ diff --git a/pictures/image-20230725172719608.png b/pictures/image-20230725172719608.png new file mode 100644 index 000000000..b2086951c Binary files /dev/null and b/pictures/image-20230725172719608.png differ diff --git a/pictures/image-20230725172834666.png b/pictures/image-20230725172834666.png new file mode 100644 index 000000000..7a1a248d5 Binary files /dev/null and b/pictures/image-20230725172834666.png differ diff --git a/pictures/image-20230725174350129.png b/pictures/image-20230725174350129.png new file mode 100644 index 000000000..3bcb8acf5 Binary files /dev/null and b/pictures/image-20230725174350129.png differ diff --git a/pictures/image-20230725175010683.png b/pictures/image-20230725175010683.png new file mode 100644 index 000000000..c1ce93243 Binary files /dev/null and b/pictures/image-20230725175010683.png differ diff --git a/pictures/image-20230726154539201.png b/pictures/image-20230726154539201.png new file mode 100644 index 000000000..cd754369d Binary files /dev/null and b/pictures/image-20230726154539201.png differ diff --git a/pictures/image-20230802141422751.png b/pictures/image-20230802141422751.png new file mode 100644 index 000000000..2ac6a8d74 Binary files /dev/null and b/pictures/image-20230802141422751.png differ diff --git a/pictures/image-20230802153951542.png b/pictures/image-20230802153951542.png new file mode 100644 index 000000000..0c6ccbd32 Binary files /dev/null and b/pictures/image-20230802153951542.png differ diff --git a/pictures/image-20230802155222746.png b/pictures/image-20230802155222746.png new file mode 100644 index 000000000..7d20f4876 Binary files /dev/null and b/pictures/image-20230802155222746.png differ diff --git a/pictures/image-20230802160618158.png b/pictures/image-20230802160618158.png new file mode 100644 index 000000000..5b3b8f89e Binary files /dev/null and b/pictures/image-20230802160618158.png differ diff --git a/pictures/image-20230802161004735.png b/pictures/image-20230802161004735.png new file mode 100644 index 000000000..095794cb5 Binary files /dev/null and b/pictures/image-20230802161004735.png differ diff --git a/pictures/image-20230802162236987.png b/pictures/image-20230802162236987.png new file mode 100644 index 000000000..ead9cfcac Binary files /dev/null and b/pictures/image-20230802162236987.png differ diff --git a/pictures/image-20230802163429366.png b/pictures/image-20230802163429366.png new file mode 100644 index 000000000..5473d08c5 Binary files /dev/null and b/pictures/image-20230802163429366.png differ diff --git a/pictures/image-20230802163708198.png b/pictures/image-20230802163708198.png new file mode 100644 index 000000000..5d47e2dc1 Binary files /dev/null and b/pictures/image-20230802163708198.png differ diff --git a/pictures/image-20230802164424689.png b/pictures/image-20230802164424689.png new file mode 100644 index 000000000..f18e749a9 Binary files /dev/null and b/pictures/image-20230802164424689.png differ diff --git a/pictures/image-20230803103657995.png b/pictures/image-20230803103657995.png new file mode 100644 index 000000000..ecc68715a Binary files /dev/null and b/pictures/image-20230803103657995.png differ diff --git a/pictures/image-20230803104032505.png b/pictures/image-20230803104032505.png new file mode 100644 index 000000000..993080fee Binary files /dev/null and b/pictures/image-20230803104032505.png differ diff --git a/pictures/image-20230803104655578.png b/pictures/image-20230803104655578.png new file mode 100644 index 000000000..8c51a4b7b Binary files /dev/null and b/pictures/image-20230803104655578.png differ diff --git a/pictures/image-20230803104919434.png b/pictures/image-20230803104919434.png new file mode 100644 index 000000000..fa6b07bd8 Binary files /dev/null and b/pictures/image-20230803104919434.png differ diff --git a/pictures/image-20230803134143859.png b/pictures/image-20230803134143859.png new file mode 100644 index 000000000..3c5858db7 Binary files /dev/null and b/pictures/image-20230803134143859.png differ diff --git a/pictures/image-20230803165113057.png b/pictures/image-20230803165113057.png new file mode 100644 index 000000000..57f388d5a Binary files /dev/null and b/pictures/image-20230803165113057.png differ diff --git a/pictures/image-20230803180257123.png b/pictures/image-20230803180257123.png new file mode 100644 index 000000000..30beaf4ae Binary files /dev/null and b/pictures/image-20230803180257123.png differ diff --git a/pictures/image-20230803180600438.png b/pictures/image-20230803180600438.png new file mode 100644 index 000000000..e9d35d089 Binary files /dev/null and b/pictures/image-20230803180600438.png differ diff --git a/pictures/image-20230803180633611.png b/pictures/image-20230803180633611.png new file mode 100644 index 000000000..851ed06ff Binary files /dev/null and b/pictures/image-20230803180633611.png differ diff --git a/pictures/image-20230809133846464.png b/pictures/image-20230809133846464.png new file mode 100644 index 000000000..481f8b703 Binary files /dev/null and b/pictures/image-20230809133846464.png differ diff --git a/pictures/image-20230809150915450.png b/pictures/image-20230809150915450.png new file mode 100644 index 000000000..383e8d8eb Binary files /dev/null and b/pictures/image-20230809150915450.png differ diff --git a/pictures/image-20230809151427867.png b/pictures/image-20230809151427867.png new file mode 100644 index 000000000..3b9fd4921 Binary files /dev/null and b/pictures/image-20230809151427867.png differ diff --git a/pictures/image-20230809155753715.png b/pictures/image-20230809155753715.png new file mode 100644 index 000000000..f48290e3d Binary files /dev/null and b/pictures/image-20230809155753715.png differ diff --git a/pictures/image-20230809160406516.png b/pictures/image-20230809160406516.png new file mode 100644 index 000000000..96b994f38 Binary files /dev/null and b/pictures/image-20230809160406516.png differ diff --git a/pictures/image-20230809160846527.png b/pictures/image-20230809160846527.png new file mode 100644 index 000000000..0ba9dc089 Binary files /dev/null and b/pictures/image-20230809160846527.png differ diff --git a/pictures/image-20230809161533834.png b/pictures/image-20230809161533834.png new file mode 100644 index 000000000..13753ecb7 Binary files /dev/null and b/pictures/image-20230809161533834.png differ diff --git a/pictures/image-20230809164101374.png b/pictures/image-20230809164101374.png new file mode 100644 index 000000000..67b4b0768 Binary files /dev/null and b/pictures/image-20230809164101374.png differ diff --git a/pictures/image-20230809165149278.png b/pictures/image-20230809165149278.png new file mode 100644 index 000000000..ec03a03af Binary files /dev/null and b/pictures/image-20230809165149278.png differ diff --git a/pictures/image-20230809170653756.png b/pictures/image-20230809170653756.png new file mode 100644 index 000000000..6b5a4df42 Binary files /dev/null and b/pictures/image-20230809170653756.png differ diff --git a/pictures/image-20230809170823626.png b/pictures/image-20230809170823626.png new file mode 100644 index 000000000..16ceca9a5 Binary files /dev/null and b/pictures/image-20230809170823626.png differ diff --git a/pictures/image-20230809170929986.png b/pictures/image-20230809170929986.png new file mode 100644 index 000000000..e58d509a7 Binary files /dev/null and b/pictures/image-20230809170929986.png differ diff --git a/pictures/image-20230810153039562.png b/pictures/image-20230810153039562.png new file mode 100644 index 000000000..1719742ec Binary files /dev/null and b/pictures/image-20230810153039562.png differ diff --git a/pictures/image-20230810153700690.png b/pictures/image-20230810153700690.png new file mode 100644 index 000000000..f382eae64 Binary files /dev/null and b/pictures/image-20230810153700690.png differ diff --git a/pictures/image-20230811092618793.png b/pictures/image-20230811092618793.png new file mode 100644 index 000000000..76d54dcbb Binary files /dev/null and b/pictures/image-20230811092618793.png differ diff --git a/pictures/image-20230811095834803.png b/pictures/image-20230811095834803.png new file mode 100644 index 000000000..af051b814 Binary files /dev/null and b/pictures/image-20230811095834803.png differ diff --git a/pictures/image-20230811095858532.png b/pictures/image-20230811095858532.png new file mode 100644 index 000000000..b19e62867 Binary files /dev/null and b/pictures/image-20230811095858532.png differ diff --git a/pictures/image-20230811103629846.png b/pictures/image-20230811103629846.png new file mode 100644 index 000000000..590f28460 Binary files /dev/null and b/pictures/image-20230811103629846.png differ diff --git a/pictures/image-20230811103800217.png b/pictures/image-20230811103800217.png new file mode 100644 index 000000000..a3c4e40b0 Binary files /dev/null and b/pictures/image-20230811103800217.png differ diff --git a/pictures/image-20230811104150226.png b/pictures/image-20230811104150226.png new file mode 100644 index 000000000..7ed649241 Binary files /dev/null and b/pictures/image-20230811104150226.png differ diff --git a/pictures/image-20230811104320075.png b/pictures/image-20230811104320075.png new file mode 100644 index 000000000..35639e3e8 Binary files /dev/null and b/pictures/image-20230811104320075.png differ diff --git a/pictures/image-20230811104523506.png b/pictures/image-20230811104523506.png new file mode 100644 index 000000000..9ecf54013 Binary files /dev/null and b/pictures/image-20230811104523506.png differ diff --git a/pictures/image-20230811104741394.png b/pictures/image-20230811104741394.png new file mode 100644 index 000000000..f21df3ebb Binary files /dev/null and b/pictures/image-20230811104741394.png differ diff --git a/pictures/image-20230811105925843.png b/pictures/image-20230811105925843.png new file mode 100644 index 000000000..63733bbb6 Binary files /dev/null and b/pictures/image-20230811105925843.png differ diff --git a/pictures/image-20230811152915886.png b/pictures/image-20230811152915886.png new file mode 100644 index 000000000..fb4f4cadc Binary files /dev/null and b/pictures/image-20230811152915886.png differ diff --git a/pictures/image-20230811154426723.png b/pictures/image-20230811154426723.png new file mode 100644 index 000000000..83c8e91c6 Binary files /dev/null and b/pictures/image-20230811154426723.png differ diff --git a/pictures/image-20230811160530674.png b/pictures/image-20230811160530674.png new file mode 100644 index 000000000..1c1e3780b Binary files /dev/null and b/pictures/image-20230811160530674.png differ diff --git a/pictures/image-20230811165505210.png b/pictures/image-20230811165505210.png new file mode 100644 index 000000000..81fc735e6 Binary files /dev/null and b/pictures/image-20230811165505210.png differ diff --git a/pictures/image-20230811165808912.png b/pictures/image-20230811165808912.png new file mode 100644 index 000000000..62fb61cc3 Binary files /dev/null and b/pictures/image-20230811165808912.png differ diff --git a/pictures/image-20230812091950114.png b/pictures/image-20230812091950114.png new file mode 100644 index 000000000..8ccc76bb8 Binary files /dev/null and b/pictures/image-20230812091950114.png differ diff --git a/pictures/image-20230812092825385.png b/pictures/image-20230812092825385.png new file mode 100644 index 000000000..1889d5028 Binary files /dev/null and b/pictures/image-20230812092825385.png differ diff --git a/pictures/image-20230812094224699.png b/pictures/image-20230812094224699.png new file mode 100644 index 000000000..bd4dd87bb Binary files /dev/null and b/pictures/image-20230812094224699.png differ diff --git a/pictures/image-20230812094755583.png b/pictures/image-20230812094755583.png new file mode 100644 index 000000000..b52aaa1ea Binary files /dev/null and b/pictures/image-20230812094755583.png differ diff --git a/pictures/image-20230812095137195.png b/pictures/image-20230812095137195.png new file mode 100644 index 000000000..a18508a6a Binary files /dev/null and b/pictures/image-20230812095137195.png differ diff --git a/pictures/image-20230812100059378.png b/pictures/image-20230812100059378.png new file mode 100644 index 000000000..7cc362006 Binary files /dev/null and b/pictures/image-20230812100059378.png differ diff --git a/pictures/image-20230812172036450.png b/pictures/image-20230812172036450.png new file mode 100644 index 000000000..bcda5a86b Binary files /dev/null and b/pictures/image-20230812172036450.png differ diff --git a/pictures/image-20230812173024036.png b/pictures/image-20230812173024036.png new file mode 100644 index 000000000..3c0c512c2 Binary files /dev/null and b/pictures/image-20230812173024036.png differ diff --git a/pictures/image-20230812180918302.png b/pictures/image-20230812180918302.png new file mode 100644 index 000000000..3bf4937a9 Binary files /dev/null and b/pictures/image-20230812180918302.png differ diff --git a/pictures/image-20230813162510205.png b/pictures/image-20230813162510205.png new file mode 100644 index 000000000..e16ae6d7f Binary files /dev/null and b/pictures/image-20230813162510205.png differ diff --git a/pictures/image-20230813163451814.png b/pictures/image-20230813163451814.png new file mode 100644 index 000000000..15540c8b3 Binary files /dev/null and b/pictures/image-20230813163451814.png differ diff --git a/pictures/image-20230813165436808.png b/pictures/image-20230813165436808.png new file mode 100644 index 000000000..4969484c6 Binary files /dev/null and b/pictures/image-20230813165436808.png differ diff --git a/pictures/image-20230813170950932.png b/pictures/image-20230813170950932.png new file mode 100644 index 000000000..aa0dfe93d Binary files /dev/null and b/pictures/image-20230813170950932.png differ diff --git a/pictures/image-20230813171244848.png b/pictures/image-20230813171244848.png new file mode 100644 index 000000000..c02927a3f Binary files /dev/null and b/pictures/image-20230813171244848.png differ diff --git a/pictures/image-20230813172211110.png b/pictures/image-20230813172211110.png new file mode 100644 index 000000000..0fd990b65 Binary files /dev/null and b/pictures/image-20230813172211110.png differ diff --git a/pictures/image-20230813172355353.png b/pictures/image-20230813172355353.png new file mode 100644 index 000000000..51534d8ed Binary files /dev/null and b/pictures/image-20230813172355353.png differ diff --git a/pictures/image-20230813172605624.png b/pictures/image-20230813172605624.png new file mode 100644 index 000000000..e1b1ae758 Binary files /dev/null and b/pictures/image-20230813172605624.png differ diff --git a/pictures/image-20230813172720116.png b/pictures/image-20230813172720116.png new file mode 100644 index 000000000..c293a2341 Binary files /dev/null and b/pictures/image-20230813172720116.png differ diff --git a/pictures/image-20230814142355139.png b/pictures/image-20230814142355139.png new file mode 100644 index 000000000..4e2eaf098 Binary files /dev/null and b/pictures/image-20230814142355139.png differ diff --git a/pictures/image-20230814142700349.png b/pictures/image-20230814142700349.png new file mode 100644 index 000000000..22a281bf3 Binary files /dev/null and b/pictures/image-20230814142700349.png differ diff --git a/pictures/image-20230814143113564.png b/pictures/image-20230814143113564.png new file mode 100644 index 000000000..b773ea808 Binary files /dev/null and b/pictures/image-20230814143113564.png differ diff --git a/pictures/image-20230814143939252.png b/pictures/image-20230814143939252.png new file mode 100644 index 000000000..b02eb3d06 Binary files /dev/null and b/pictures/image-20230814143939252.png differ diff --git a/pictures/image-20230814144314358.png b/pictures/image-20230814144314358.png new file mode 100644 index 000000000..a910723ad Binary files /dev/null and b/pictures/image-20230814144314358.png differ diff --git a/pictures/image-20230814144806282.png b/pictures/image-20230814144806282.png new file mode 100644 index 000000000..5917dcdf8 Binary files /dev/null and b/pictures/image-20230814144806282.png differ diff --git a/pictures/image-20230814151319382.png b/pictures/image-20230814151319382.png new file mode 100644 index 000000000..1cfefd134 Binary files /dev/null and b/pictures/image-20230814151319382.png differ diff --git a/pictures/image-20230814152058708.png b/pictures/image-20230814152058708.png new file mode 100644 index 000000000..899bd6815 Binary files /dev/null and b/pictures/image-20230814152058708.png differ diff --git a/pictures/image-20230814152144765.png b/pictures/image-20230814152144765.png new file mode 100644 index 000000000..08cadbea6 Binary files /dev/null and b/pictures/image-20230814152144765.png differ diff --git a/pictures/image-20230814153335349.png b/pictures/image-20230814153335349.png new file mode 100644 index 000000000..ff3815f0b Binary files /dev/null and b/pictures/image-20230814153335349.png differ diff --git a/pictures/image-20230814154205134.png b/pictures/image-20230814154205134.png new file mode 100644 index 000000000..a5eb18b58 Binary files /dev/null and b/pictures/image-20230814154205134.png differ diff --git a/pictures/image-20230814154405531.png b/pictures/image-20230814154405531.png new file mode 100644 index 000000000..7202be5e2 Binary files /dev/null and b/pictures/image-20230814154405531.png differ diff --git a/pictures/image-20230814160312382.png b/pictures/image-20230814160312382.png new file mode 100644 index 000000000..3edc9825c Binary files /dev/null and b/pictures/image-20230814160312382.png differ diff --git a/pictures/image-20230814160547893.png b/pictures/image-20230814160547893.png new file mode 100644 index 000000000..914285997 Binary files /dev/null and b/pictures/image-20230814160547893.png differ diff --git a/pictures/image-20230814160652330.png b/pictures/image-20230814160652330.png new file mode 100644 index 000000000..568a0fb40 Binary files /dev/null and b/pictures/image-20230814160652330.png differ diff --git a/pictures/image-20230814160728715.png b/pictures/image-20230814160728715.png new file mode 100644 index 000000000..a13fffc81 Binary files /dev/null and b/pictures/image-20230814160728715.png differ diff --git a/pictures/image-20230814160805398.png b/pictures/image-20230814160805398.png new file mode 100644 index 000000000..28b226553 Binary files /dev/null and b/pictures/image-20230814160805398.png differ diff --git a/pictures/image-20230814160823771.png b/pictures/image-20230814160823771.png new file mode 100644 index 000000000..cbb080548 Binary files /dev/null and b/pictures/image-20230814160823771.png differ diff --git a/pictures/image-20230814161909312.png b/pictures/image-20230814161909312.png new file mode 100644 index 000000000..569da5574 Binary files /dev/null and b/pictures/image-20230814161909312.png differ diff --git a/pictures/image-20230814163019582.png b/pictures/image-20230814163019582.png new file mode 100644 index 000000000..9aeff16d5 Binary files /dev/null and b/pictures/image-20230814163019582.png differ diff --git a/pictures/image-20230815091741019.png b/pictures/image-20230815091741019.png new file mode 100644 index 000000000..8dd71556e Binary files /dev/null and b/pictures/image-20230815091741019.png differ diff --git a/pictures/image-20230815153318041.png b/pictures/image-20230815153318041.png new file mode 100644 index 000000000..1a67138ca Binary files /dev/null and b/pictures/image-20230815153318041.png differ diff --git a/pictures/image-20230815160034436.png b/pictures/image-20230815160034436.png new file mode 100644 index 000000000..764cca93f Binary files /dev/null and b/pictures/image-20230815160034436.png differ diff --git a/pictures/image-20230815162119814.png b/pictures/image-20230815162119814.png new file mode 100644 index 000000000..7ce9a9929 Binary files /dev/null and b/pictures/image-20230815162119814.png differ diff --git a/pictures/image-20230815171925527.png b/pictures/image-20230815171925527.png new file mode 100644 index 000000000..b888e8f40 Binary files /dev/null and b/pictures/image-20230815171925527.png differ diff --git a/pictures/image-20230815172326472.png b/pictures/image-20230815172326472.png new file mode 100644 index 000000000..0585b8bfc Binary files /dev/null and b/pictures/image-20230815172326472.png differ diff --git a/pictures/image-20230815184540157.png b/pictures/image-20230815184540157.png new file mode 100644 index 000000000..31ddb898f Binary files /dev/null and b/pictures/image-20230815184540157.png differ diff --git a/pictures/image-20230816091726035.png b/pictures/image-20230816091726035.png new file mode 100644 index 000000000..3fb3faea7 Binary files /dev/null and b/pictures/image-20230816091726035.png differ diff --git a/pictures/image-20230816095012271.png b/pictures/image-20230816095012271.png new file mode 100644 index 000000000..02e41d651 Binary files /dev/null and b/pictures/image-20230816095012271.png differ diff --git a/pictures/image-20230816142705510.png b/pictures/image-20230816142705510.png new file mode 100644 index 000000000..996773632 Binary files /dev/null and b/pictures/image-20230816142705510.png differ diff --git a/pictures/image-20230816143201242.png b/pictures/image-20230816143201242.png new file mode 100644 index 000000000..b92d642d4 Binary files /dev/null and b/pictures/image-20230816143201242.png differ diff --git a/pictures/image-20230816143544351.png b/pictures/image-20230816143544351.png new file mode 100644 index 000000000..90ba378d8 Binary files /dev/null and b/pictures/image-20230816143544351.png differ diff --git a/pictures/image-20230816145012534.png b/pictures/image-20230816145012534.png new file mode 100644 index 000000000..5a275e2e1 Binary files /dev/null and b/pictures/image-20230816145012534.png differ diff --git a/pictures/image-20230816145923876.png b/pictures/image-20230816145923876.png new file mode 100644 index 000000000..74630777d Binary files /dev/null and b/pictures/image-20230816145923876.png differ diff --git a/pictures/image-20230816150936881.png b/pictures/image-20230816150936881.png new file mode 100644 index 000000000..70de89dc3 Binary files /dev/null and b/pictures/image-20230816150936881.png differ diff --git a/pictures/image-20230816152750639.png b/pictures/image-20230816152750639.png new file mode 100644 index 000000000..0479dfece Binary files /dev/null and b/pictures/image-20230816152750639.png differ diff --git a/pictures/image-20230816170028648.png b/pictures/image-20230816170028648.png new file mode 100644 index 000000000..580e44254 Binary files /dev/null and b/pictures/image-20230816170028648.png differ diff --git a/pictures/image-20230816170550343.png b/pictures/image-20230816170550343.png new file mode 100644 index 000000000..74816bef4 Binary files /dev/null and b/pictures/image-20230816170550343.png differ diff --git a/pictures/image-20230816170704421.png b/pictures/image-20230816170704421.png new file mode 100644 index 000000000..d6c970f4b Binary files /dev/null and b/pictures/image-20230816170704421.png differ diff --git a/pictures/image-20230816170909497.png b/pictures/image-20230816170909497.png new file mode 100644 index 000000000..bda791411 Binary files /dev/null and b/pictures/image-20230816170909497.png differ diff --git a/pictures/image-20230816171119081.png b/pictures/image-20230816171119081.png new file mode 100644 index 000000000..edc44b895 Binary files /dev/null and b/pictures/image-20230816171119081.png differ diff --git a/pictures/image-20230816172633599.png b/pictures/image-20230816172633599.png new file mode 100644 index 000000000..d9ffa37f3 Binary files /dev/null and b/pictures/image-20230816172633599.png differ diff --git a/pictures/image-20230816174550953.png b/pictures/image-20230816174550953.png new file mode 100644 index 000000000..3c76b1e40 Binary files /dev/null and b/pictures/image-20230816174550953.png differ diff --git a/pictures/image-20230818145600002.png b/pictures/image-20230818145600002.png new file mode 100644 index 000000000..62428ffc6 Binary files /dev/null and b/pictures/image-20230818145600002.png differ diff --git a/pictures/image-20230818145721071.png b/pictures/image-20230818145721071.png new file mode 100644 index 000000000..49b25df9e Binary files /dev/null and b/pictures/image-20230818145721071.png differ diff --git a/pictures/image-20230818150154249.png b/pictures/image-20230818150154249.png new file mode 100644 index 000000000..1a9b8ef18 Binary files /dev/null and b/pictures/image-20230818150154249.png differ diff --git a/pictures/image-20230818150902795.png b/pictures/image-20230818150902795.png new file mode 100644 index 000000000..aa2deb858 Binary files /dev/null and b/pictures/image-20230818150902795.png differ diff --git a/pictures/image-20230818151929679.png b/pictures/image-20230818151929679.png new file mode 100644 index 000000000..5a5c6793b Binary files /dev/null and b/pictures/image-20230818151929679.png differ diff --git a/pictures/image-20230818154409682.png b/pictures/image-20230818154409682.png new file mode 100644 index 000000000..15eaf7f2c Binary files /dev/null and b/pictures/image-20230818154409682.png differ diff --git a/pictures/image-20230818154858988.png b/pictures/image-20230818154858988.png new file mode 100644 index 000000000..8ca1403c4 Binary files /dev/null and b/pictures/image-20230818154858988.png differ diff --git a/pictures/image-20230818155021308.png b/pictures/image-20230818155021308.png new file mode 100644 index 000000000..548d37f7a Binary files /dev/null and b/pictures/image-20230818155021308.png differ diff --git a/pictures/image-20230818155501495.png b/pictures/image-20230818155501495.png new file mode 100644 index 000000000..2bb99ddbd Binary files /dev/null and b/pictures/image-20230818155501495.png differ diff --git a/pictures/image-20230818165138917.png b/pictures/image-20230818165138917.png new file mode 100644 index 000000000..a49d18b71 Binary files /dev/null and b/pictures/image-20230818165138917.png differ diff --git a/pictures/image-20230818210507999.png b/pictures/image-20230818210507999.png new file mode 100644 index 000000000..e8bb2e8b1 Binary files /dev/null and b/pictures/image-20230818210507999.png differ diff --git a/pictures/image-20230818210638036.png b/pictures/image-20230818210638036.png new file mode 100644 index 000000000..dc54f5935 Binary files /dev/null and b/pictures/image-20230818210638036.png differ diff --git a/pictures/image-20230818211310728.png b/pictures/image-20230818211310728.png new file mode 100644 index 000000000..177c24096 Binary files /dev/null and b/pictures/image-20230818211310728.png differ diff --git a/pictures/image-20230819103638685.png b/pictures/image-20230819103638685.png new file mode 100644 index 000000000..7c2f6dacb Binary files /dev/null and b/pictures/image-20230819103638685.png differ diff --git a/pictures/image-20230819153507360.png b/pictures/image-20230819153507360.png new file mode 100644 index 000000000..7f9098786 Binary files /dev/null and b/pictures/image-20230819153507360.png differ diff --git a/pictures/image-20230819155229550.png b/pictures/image-20230819155229550.png new file mode 100644 index 000000000..3d55f9e04 Binary files /dev/null and b/pictures/image-20230819155229550.png differ diff --git a/pictures/image-20230819155333131.png b/pictures/image-20230819155333131.png new file mode 100644 index 000000000..8ba3e1972 Binary files /dev/null and b/pictures/image-20230819155333131.png differ diff --git a/pictures/image-20230819155853252.png b/pictures/image-20230819155853252.png new file mode 100644 index 000000000..7f3809424 Binary files /dev/null and b/pictures/image-20230819155853252.png differ diff --git a/pictures/image-20230819161148830.png b/pictures/image-20230819161148830.png new file mode 100644 index 000000000..ea7ca3d9b Binary files /dev/null and b/pictures/image-20230819161148830.png differ diff --git a/pictures/image-20230819161529210.png b/pictures/image-20230819161529210.png new file mode 100644 index 000000000..91baaec39 Binary files /dev/null and b/pictures/image-20230819161529210.png differ diff --git a/pictures/image-20230819161650118.png b/pictures/image-20230819161650118.png new file mode 100644 index 000000000..f3307314c Binary files /dev/null and b/pictures/image-20230819161650118.png differ diff --git a/pictures/image-20230819162116240.png b/pictures/image-20230819162116240.png new file mode 100644 index 000000000..0b5ac7b7a Binary files /dev/null and b/pictures/image-20230819162116240.png differ diff --git a/pictures/image-20230819162230133.png b/pictures/image-20230819162230133.png new file mode 100644 index 000000000..d737d05a4 Binary files /dev/null and b/pictures/image-20230819162230133.png differ diff --git a/pictures/image-20230819205317659.png b/pictures/image-20230819205317659.png new file mode 100644 index 000000000..1526207af Binary files /dev/null and b/pictures/image-20230819205317659.png differ diff --git a/pictures/image-20230821105529796.png b/pictures/image-20230821105529796.png new file mode 100644 index 000000000..8b671c1e4 Binary files /dev/null and b/pictures/image-20230821105529796.png differ diff --git a/pictures/image-20230821112246355.png b/pictures/image-20230821112246355.png new file mode 100644 index 000000000..e9baa05b8 Binary files /dev/null and b/pictures/image-20230821112246355.png differ diff --git a/pictures/image-20230821223922621.png b/pictures/image-20230821223922621.png new file mode 100644 index 000000000..9c42c221e Binary files /dev/null and b/pictures/image-20230821223922621.png differ diff --git a/pictures/image-20230821224404450.png b/pictures/image-20230821224404450.png new file mode 100644 index 000000000..60e5c5b91 Binary files /dev/null and b/pictures/image-20230821224404450.png differ diff --git a/pictures/image-20230826211541424.png b/pictures/image-20230826211541424.png new file mode 100644 index 000000000..c47b633bd Binary files /dev/null and b/pictures/image-20230826211541424.png differ diff --git a/pictures/image-20230826212400849.png b/pictures/image-20230826212400849.png new file mode 100644 index 000000000..ae2907d9f Binary files /dev/null and b/pictures/image-20230826212400849.png differ diff --git a/pictures/image-20230826213125490.png b/pictures/image-20230826213125490.png new file mode 100644 index 000000000..e2a1ef43b Binary files /dev/null and b/pictures/image-20230826213125490.png differ diff --git a/pictures/image-20230826214253187.png b/pictures/image-20230826214253187.png new file mode 100644 index 000000000..b678b8f0c Binary files /dev/null and b/pictures/image-20230826214253187.png differ diff --git a/pictures/image-20230826221229898.png b/pictures/image-20230826221229898.png new file mode 100644 index 000000000..f1bfa8143 Binary files /dev/null and b/pictures/image-20230826221229898.png differ diff --git a/pictures/image-20230826222050817.png b/pictures/image-20230826222050817.png new file mode 100644 index 000000000..ad91f153f Binary files /dev/null and b/pictures/image-20230826222050817.png differ diff --git a/pictures/image-20230826224503510.png b/pictures/image-20230826224503510.png new file mode 100644 index 000000000..bcb6b989d Binary files /dev/null and b/pictures/image-20230826224503510.png differ diff --git a/pictures/image-20230827112742347.png b/pictures/image-20230827112742347.png new file mode 100644 index 000000000..1778cff81 Binary files /dev/null and b/pictures/image-20230827112742347.png differ diff --git a/pictures/image-20230827114256749.png b/pictures/image-20230827114256749.png new file mode 100644 index 000000000..7b68207c1 Binary files /dev/null and b/pictures/image-20230827114256749.png differ diff --git a/pictures/image-20230827115900097.png b/pictures/image-20230827115900097.png new file mode 100644 index 000000000..745e6062c Binary files /dev/null and b/pictures/image-20230827115900097.png differ diff --git a/pictures/image-20230828095915653.png b/pictures/image-20230828095915653.png new file mode 100644 index 000000000..14a083cc1 Binary files /dev/null and b/pictures/image-20230828095915653.png differ diff --git a/pictures/image-20230828110908056.png b/pictures/image-20230828110908056.png new file mode 100644 index 000000000..e93ee6ceb Binary files /dev/null and b/pictures/image-20230828110908056.png differ diff --git a/pictures/image-20230828110928951.png b/pictures/image-20230828110928951.png new file mode 100644 index 000000000..a97c5d344 Binary files /dev/null and b/pictures/image-20230828110928951.png differ diff --git a/pictures/image-20230828113335033.png b/pictures/image-20230828113335033.png new file mode 100644 index 000000000..ddf5138c1 Binary files /dev/null and b/pictures/image-20230828113335033.png differ diff --git a/pictures/image-20230828142631189.png b/pictures/image-20230828142631189.png new file mode 100644 index 000000000..aeffc84d8 Binary files /dev/null and b/pictures/image-20230828142631189.png differ diff --git a/pictures/image-20230828143224633.png b/pictures/image-20230828143224633.png new file mode 100644 index 000000000..98a9fa2ed Binary files /dev/null and b/pictures/image-20230828143224633.png differ diff --git a/pictures/image-20230828150633322.png b/pictures/image-20230828150633322.png new file mode 100644 index 000000000..9b031299d Binary files /dev/null and b/pictures/image-20230828150633322.png differ diff --git a/pictures/image-20230828151416487.png b/pictures/image-20230828151416487.png new file mode 100644 index 000000000..c1d0713e0 Binary files /dev/null and b/pictures/image-20230828151416487.png differ diff --git a/pictures/image-20230828152006549.png b/pictures/image-20230828152006549.png new file mode 100644 index 000000000..8fe82a15c Binary files /dev/null and b/pictures/image-20230828152006549.png differ diff --git a/pictures/image-20230828153902598.png b/pictures/image-20230828153902598.png new file mode 100644 index 000000000..df445e6de Binary files /dev/null and b/pictures/image-20230828153902598.png differ diff --git a/pictures/image-20230828153914619.png b/pictures/image-20230828153914619.png new file mode 100644 index 000000000..17aa99ab6 Binary files /dev/null and b/pictures/image-20230828153914619.png differ diff --git a/pictures/image-20230828154818630.png b/pictures/image-20230828154818630.png new file mode 100644 index 000000000..bd79a80c9 Binary files /dev/null and b/pictures/image-20230828154818630.png differ diff --git a/pictures/image-20230828155537932.png b/pictures/image-20230828155537932.png new file mode 100644 index 000000000..0a45c9119 Binary files /dev/null and b/pictures/image-20230828155537932.png differ diff --git a/pictures/image-20230828160606081.png b/pictures/image-20230828160606081.png new file mode 100644 index 000000000..d86dd11bb Binary files /dev/null and b/pictures/image-20230828160606081.png differ diff --git a/pictures/image-20230828160731314.png b/pictures/image-20230828160731314.png new file mode 100644 index 000000000..544d96c87 Binary files /dev/null and b/pictures/image-20230828160731314.png differ diff --git a/pictures/image-20230828215513289.png b/pictures/image-20230828215513289.png new file mode 100644 index 000000000..36afc5622 Binary files /dev/null and b/pictures/image-20230828215513289.png differ diff --git a/pictures/image-20230828223201038.png b/pictures/image-20230828223201038.png new file mode 100644 index 000000000..3a1550f16 Binary files /dev/null and b/pictures/image-20230828223201038.png differ diff --git a/pictures/image-20230828233052417.png b/pictures/image-20230828233052417.png new file mode 100644 index 000000000..ba1b50a25 Binary files /dev/null and b/pictures/image-20230828233052417.png differ diff --git a/pictures/image-20230828233917645.png b/pictures/image-20230828233917645.png new file mode 100644 index 000000000..f452fc488 Binary files /dev/null and b/pictures/image-20230828233917645.png differ diff --git a/pictures/image-20230829160901904.png b/pictures/image-20230829160901904.png new file mode 100644 index 000000000..825eca38e Binary files /dev/null and b/pictures/image-20230829160901904.png differ diff --git a/pictures/image-20230829160938795.png b/pictures/image-20230829160938795.png new file mode 100644 index 000000000..39788b60b Binary files /dev/null and b/pictures/image-20230829160938795.png differ diff --git a/pictures/image-20230829223442421.png b/pictures/image-20230829223442421.png new file mode 100644 index 000000000..09094e1d0 Binary files /dev/null and b/pictures/image-20230829223442421.png differ diff --git a/pictures/image-20230829224339809.png b/pictures/image-20230829224339809.png new file mode 100644 index 000000000..1fb38cb87 Binary files /dev/null and b/pictures/image-20230829224339809.png differ diff --git a/pictures/image-20230829225017032.png b/pictures/image-20230829225017032.png new file mode 100644 index 000000000..30eb1228d Binary files /dev/null and b/pictures/image-20230829225017032.png differ diff --git a/pictures/image-20230829225740156.png b/pictures/image-20230829225740156.png new file mode 100644 index 000000000..caf7467c2 Binary files /dev/null and b/pictures/image-20230829225740156.png differ diff --git a/pictures/image-20230829230236500.png b/pictures/image-20230829230236500.png new file mode 100644 index 000000000..f6202bac3 Binary files /dev/null and b/pictures/image-20230829230236500.png differ diff --git a/pictures/image-20230829230843477.png b/pictures/image-20230829230843477.png new file mode 100644 index 000000000..91f94296a Binary files /dev/null and b/pictures/image-20230829230843477.png differ diff --git a/pictures/image-20230902145814017.png b/pictures/image-20230902145814017.png new file mode 100644 index 000000000..8ccb0b873 Binary files /dev/null and b/pictures/image-20230902145814017.png differ diff --git a/pictures/image-20230902154806019.png b/pictures/image-20230902154806019.png new file mode 100644 index 000000000..d5d741e4f Binary files /dev/null and b/pictures/image-20230902154806019.png differ diff --git a/pictures/image-20230902220022201.png b/pictures/image-20230902220022201.png new file mode 100644 index 000000000..10a17b0bc Binary files /dev/null and b/pictures/image-20230902220022201.png differ diff --git a/pictures/image-20230903125528872.png b/pictures/image-20230903125528872.png new file mode 100644 index 000000000..c66e762ac Binary files /dev/null and b/pictures/image-20230903125528872.png differ diff --git a/pictures/image-20230903151216766.png b/pictures/image-20230903151216766.png new file mode 100644 index 000000000..849c55a66 Binary files /dev/null and b/pictures/image-20230903151216766.png differ diff --git a/pictures/image-20230903185956428.png b/pictures/image-20230903185956428.png new file mode 100644 index 000000000..007fe2f98 Binary files /dev/null and b/pictures/image-20230903185956428.png differ diff --git a/pictures/image-20230903194641431.png b/pictures/image-20230903194641431.png new file mode 100644 index 000000000..ee53a5ace Binary files /dev/null and b/pictures/image-20230903194641431.png differ diff --git a/pictures/image-20230904151910210.png b/pictures/image-20230904151910210.png new file mode 100644 index 000000000..e135530d1 Binary files /dev/null and b/pictures/image-20230904151910210.png differ diff --git a/pictures/image-20230904161407239.png b/pictures/image-20230904161407239.png new file mode 100644 index 000000000..119189542 Binary files /dev/null and b/pictures/image-20230904161407239.png differ diff --git a/pictures/image-20230905083009199.png b/pictures/image-20230905083009199.png new file mode 100644 index 000000000..ed261b402 Binary files /dev/null and b/pictures/image-20230905083009199.png differ diff --git a/pictures/image-20230905204135018.png b/pictures/image-20230905204135018.png new file mode 100644 index 000000000..99eb76356 Binary files /dev/null and b/pictures/image-20230905204135018.png differ diff --git a/pictures/image-20230910120750378.png b/pictures/image-20230910120750378.png new file mode 100644 index 000000000..cd07096be Binary files /dev/null and b/pictures/image-20230910120750378.png differ diff --git a/pictures/image-20230910121038131.png b/pictures/image-20230910121038131.png new file mode 100644 index 000000000..e081fce7c Binary files /dev/null and b/pictures/image-20230910121038131.png differ diff --git a/pictures/image-20230911141826287.png b/pictures/image-20230911141826287.png new file mode 100644 index 000000000..c75b2adc0 Binary files /dev/null and b/pictures/image-20230911141826287.png differ diff --git a/pictures/image-20230911195638524.png b/pictures/image-20230911195638524.png new file mode 100644 index 000000000..d070b0886 Binary files /dev/null and b/pictures/image-20230911195638524.png differ diff --git a/pictures/image-20230912105041466.png b/pictures/image-20230912105041466.png new file mode 100644 index 000000000..c45686d1f Binary files /dev/null and b/pictures/image-20230912105041466.png differ diff --git a/pictures/image-20230912141232184.png b/pictures/image-20230912141232184.png new file mode 100644 index 000000000..a9ed7bde2 Binary files /dev/null and b/pictures/image-20230912141232184.png differ diff --git a/pictures/image-20230913123344514.png b/pictures/image-20230913123344514.png new file mode 100644 index 000000000..e10daad58 Binary files /dev/null and b/pictures/image-20230913123344514.png differ diff --git a/pictures/image-20230913123704246.png b/pictures/image-20230913123704246.png new file mode 100644 index 000000000..4f49244ce Binary files /dev/null and b/pictures/image-20230913123704246.png differ diff --git a/pictures/image-20230913123809940.png b/pictures/image-20230913123809940.png new file mode 100644 index 000000000..d8c7c8497 Binary files /dev/null and b/pictures/image-20230913123809940.png differ diff --git a/pictures/image-20230913124042655.png b/pictures/image-20230913124042655.png new file mode 100644 index 000000000..d558ee6ca Binary files /dev/null and b/pictures/image-20230913124042655.png differ diff --git a/pictures/image-20230913124616244.png b/pictures/image-20230913124616244.png new file mode 100644 index 000000000..72406ffed Binary files /dev/null and b/pictures/image-20230913124616244.png differ diff --git a/pictures/image-20230913124842536.png b/pictures/image-20230913124842536.png new file mode 100644 index 000000000..414feea7e Binary files /dev/null and b/pictures/image-20230913124842536.png differ diff --git a/pictures/image-20230913125129535.png b/pictures/image-20230913125129535.png new file mode 100644 index 000000000..2ed840ee5 Binary files /dev/null and b/pictures/image-20230913125129535.png differ diff --git a/pictures/image-20230913125324974.png b/pictures/image-20230913125324974.png new file mode 100644 index 000000000..6c9922311 Binary files /dev/null and b/pictures/image-20230913125324974.png differ diff --git a/pictures/image-20230913125539561.png b/pictures/image-20230913125539561.png new file mode 100644 index 000000000..361ed77d1 Binary files /dev/null and b/pictures/image-20230913125539561.png differ diff --git a/pictures/image-20230913132602702.png b/pictures/image-20230913132602702.png new file mode 100644 index 000000000..d6f6223bb Binary files /dev/null and b/pictures/image-20230913132602702.png differ diff --git a/pictures/image-20230913132742999.png b/pictures/image-20230913132742999.png new file mode 100644 index 000000000..41e89879a Binary files /dev/null and b/pictures/image-20230913132742999.png differ diff --git a/pictures/image-20230913132918535.png b/pictures/image-20230913132918535.png new file mode 100644 index 000000000..6c3323aa1 Binary files /dev/null and b/pictures/image-20230913132918535.png differ diff --git a/pictures/image-20230914154911197.png b/pictures/image-20230914154911197.png new file mode 100644 index 000000000..f6c7af503 Binary files /dev/null and b/pictures/image-20230914154911197.png differ diff --git a/pictures/image-20230916150211012.png b/pictures/image-20230916150211012.png new file mode 100644 index 000000000..7b50c5c59 Binary files /dev/null and b/pictures/image-20230916150211012.png differ diff --git a/pictures/image-20230916150430708.png b/pictures/image-20230916150430708.png new file mode 100644 index 000000000..86aab9800 Binary files /dev/null and b/pictures/image-20230916150430708.png differ diff --git a/pictures/image-20230916152706301.png b/pictures/image-20230916152706301.png new file mode 100644 index 000000000..96aaf675a Binary files /dev/null and b/pictures/image-20230916152706301.png differ diff --git a/pictures/image-20230916152828704.png b/pictures/image-20230916152828704.png new file mode 100644 index 000000000..b038d8272 Binary files /dev/null and b/pictures/image-20230916152828704.png differ diff --git a/pictures/image-20230916153448893.png b/pictures/image-20230916153448893.png new file mode 100644 index 000000000..24aa54c48 Binary files /dev/null and b/pictures/image-20230916153448893.png differ diff --git a/pictures/image-20230916153931010.png b/pictures/image-20230916153931010.png new file mode 100644 index 000000000..584cc6f75 Binary files /dev/null and b/pictures/image-20230916153931010.png differ diff --git a/pictures/image-20230916154915533.png b/pictures/image-20230916154915533.png new file mode 100644 index 000000000..5a863e0f6 Binary files /dev/null and b/pictures/image-20230916154915533.png differ diff --git a/pictures/image-20230916155113863.png b/pictures/image-20230916155113863.png new file mode 100644 index 000000000..319bae657 Binary files /dev/null and b/pictures/image-20230916155113863.png differ diff --git a/pictures/image-20230916155356755.png b/pictures/image-20230916155356755.png new file mode 100644 index 000000000..5dcff40f6 Binary files /dev/null and b/pictures/image-20230916155356755.png differ diff --git a/pictures/image-20230916155525548.png b/pictures/image-20230916155525548.png new file mode 100644 index 000000000..1a133cbf8 Binary files /dev/null and b/pictures/image-20230916155525548.png differ diff --git a/pictures/image-20230916155637133.png b/pictures/image-20230916155637133.png new file mode 100644 index 000000000..b18ce87a6 Binary files /dev/null and b/pictures/image-20230916155637133.png differ diff --git a/pictures/image-20230916161524219.png b/pictures/image-20230916161524219.png new file mode 100644 index 000000000..ee4bb4ca8 Binary files /dev/null and b/pictures/image-20230916161524219.png differ diff --git a/pictures/image-20230916161623546.png b/pictures/image-20230916161623546.png new file mode 100644 index 000000000..c13f563b7 Binary files /dev/null and b/pictures/image-20230916161623546.png differ diff --git a/pictures/image-20230916162305576.png b/pictures/image-20230916162305576.png new file mode 100644 index 000000000..eb2f1fac3 Binary files /dev/null and b/pictures/image-20230916162305576.png differ diff --git a/pictures/image-20230916163234103.png b/pictures/image-20230916163234103.png new file mode 100644 index 000000000..c974660c8 Binary files /dev/null and b/pictures/image-20230916163234103.png differ diff --git a/pictures/image-20230916164042303.png b/pictures/image-20230916164042303.png new file mode 100644 index 000000000..1ab5c3f47 Binary files /dev/null and b/pictures/image-20230916164042303.png differ diff --git a/pictures/image-20230916164314700.png b/pictures/image-20230916164314700.png new file mode 100644 index 000000000..0d87235c0 Binary files /dev/null and b/pictures/image-20230916164314700.png differ diff --git a/pictures/image-20230916164500444.png b/pictures/image-20230916164500444.png new file mode 100644 index 000000000..7f2969c27 Binary files /dev/null and b/pictures/image-20230916164500444.png differ diff --git a/pictures/image-20230916164903702.png b/pictures/image-20230916164903702.png new file mode 100644 index 000000000..2a4be88e4 Binary files /dev/null and b/pictures/image-20230916164903702.png differ diff --git a/pictures/image-20230916165007465.png b/pictures/image-20230916165007465.png new file mode 100644 index 000000000..b92936453 Binary files /dev/null and b/pictures/image-20230916165007465.png differ diff --git a/pictures/image-20230916165329434.png b/pictures/image-20230916165329434.png new file mode 100644 index 000000000..062868ec5 Binary files /dev/null and b/pictures/image-20230916165329434.png differ diff --git a/pictures/image-20230916165516779.png b/pictures/image-20230916165516779.png new file mode 100644 index 000000000..0e981ed92 Binary files /dev/null and b/pictures/image-20230916165516779.png differ diff --git a/pictures/image-20230916165647255.png b/pictures/image-20230916165647255.png new file mode 100644 index 000000000..659c07315 Binary files /dev/null and b/pictures/image-20230916165647255.png differ diff --git a/pictures/image-20230916170012664.png b/pictures/image-20230916170012664.png new file mode 100644 index 000000000..ca6a0f02f Binary files /dev/null and b/pictures/image-20230916170012664.png differ diff --git a/pictures/image-20230916170421435.png b/pictures/image-20230916170421435.png new file mode 100644 index 000000000..54838fc1a Binary files /dev/null and b/pictures/image-20230916170421435.png differ diff --git a/pictures/image-20230916170537693.png b/pictures/image-20230916170537693.png new file mode 100644 index 000000000..1ba192531 Binary files /dev/null and b/pictures/image-20230916170537693.png differ diff --git a/pictures/image-20230917091746406.png b/pictures/image-20230917091746406.png new file mode 100644 index 000000000..1e010c8ff Binary files /dev/null and b/pictures/image-20230917091746406.png differ diff --git a/pictures/image-20230917093020287.png b/pictures/image-20230917093020287.png new file mode 100644 index 000000000..1fbbeb3e5 Binary files /dev/null and b/pictures/image-20230917093020287.png differ diff --git a/pictures/image-20230917093634100.png b/pictures/image-20230917093634100.png new file mode 100644 index 000000000..81d997d6d Binary files /dev/null and b/pictures/image-20230917093634100.png differ diff --git a/pictures/image-20230917094314803.png b/pictures/image-20230917094314803.png new file mode 100644 index 000000000..75a73c845 Binary files /dev/null and b/pictures/image-20230917094314803.png differ diff --git a/pictures/image-20230917095232011.png b/pictures/image-20230917095232011.png new file mode 100644 index 000000000..799bc4abb Binary files /dev/null and b/pictures/image-20230917095232011.png differ diff --git a/pictures/image-20230917095320953.png b/pictures/image-20230917095320953.png new file mode 100644 index 000000000..72244854a Binary files /dev/null and b/pictures/image-20230917095320953.png differ diff --git a/pictures/image-20230917095911304.png b/pictures/image-20230917095911304.png new file mode 100644 index 000000000..602880c9f Binary files /dev/null and b/pictures/image-20230917095911304.png differ diff --git a/pictures/image-20230917101205097.png b/pictures/image-20230917101205097.png new file mode 100644 index 000000000..61c3bc4d2 Binary files /dev/null and b/pictures/image-20230917101205097.png differ diff --git a/pictures/image-20230917101337147.png b/pictures/image-20230917101337147.png new file mode 100644 index 000000000..4d42418fe Binary files /dev/null and b/pictures/image-20230917101337147.png differ diff --git a/pictures/image-20230917141334774.png b/pictures/image-20230917141334774.png new file mode 100644 index 000000000..7c3803eb4 Binary files /dev/null and b/pictures/image-20230917141334774.png differ diff --git a/pictures/image-20230917142410276.png b/pictures/image-20230917142410276.png new file mode 100644 index 000000000..dee97e719 Binary files /dev/null and b/pictures/image-20230917142410276.png differ diff --git a/pictures/image-20230917142707646.png b/pictures/image-20230917142707646.png new file mode 100644 index 000000000..ed3f48d4f Binary files /dev/null and b/pictures/image-20230917142707646.png differ diff --git a/pictures/image-20230917142948227.png b/pictures/image-20230917142948227.png new file mode 100644 index 000000000..10a63c278 Binary files /dev/null and b/pictures/image-20230917142948227.png differ diff --git a/pictures/image-20230917143143047.png b/pictures/image-20230917143143047.png new file mode 100644 index 000000000..3ef4570e9 Binary files /dev/null and b/pictures/image-20230917143143047.png differ diff --git a/pictures/image-20230917143421975.png b/pictures/image-20230917143421975.png new file mode 100644 index 000000000..0732082e7 Binary files /dev/null and b/pictures/image-20230917143421975.png differ diff --git a/pictures/image-20230917143842497.png b/pictures/image-20230917143842497.png new file mode 100644 index 000000000..895b8e11b Binary files /dev/null and b/pictures/image-20230917143842497.png differ diff --git a/pictures/image-20230917144459567.png b/pictures/image-20230917144459567.png new file mode 100644 index 000000000..0f2f215c3 Binary files /dev/null and b/pictures/image-20230917144459567.png differ diff --git a/pictures/image-20230917145046280.png b/pictures/image-20230917145046280.png new file mode 100644 index 000000000..ac7f03c04 Binary files /dev/null and b/pictures/image-20230917145046280.png differ diff --git a/pictures/image-20230917145643194.png b/pictures/image-20230917145643194.png new file mode 100644 index 000000000..f20d8a9a9 Binary files /dev/null and b/pictures/image-20230917145643194.png differ diff --git a/pictures/image-20230917153757773.png b/pictures/image-20230917153757773.png new file mode 100644 index 000000000..2367fcccb Binary files /dev/null and b/pictures/image-20230917153757773.png differ diff --git a/pictures/image-20230917154300147.png b/pictures/image-20230917154300147.png new file mode 100644 index 000000000..721a21057 Binary files /dev/null and b/pictures/image-20230917154300147.png differ diff --git a/pictures/image-20230917155017641.png b/pictures/image-20230917155017641.png new file mode 100644 index 000000000..209e52ecc Binary files /dev/null and b/pictures/image-20230917155017641.png differ diff --git a/pictures/image-20230917155223679.png b/pictures/image-20230917155223679.png new file mode 100644 index 000000000..0618024a8 Binary files /dev/null and b/pictures/image-20230917155223679.png differ diff --git a/pictures/image-20230917155748055.png b/pictures/image-20230917155748055.png new file mode 100644 index 000000000..906dfa9a3 Binary files /dev/null and b/pictures/image-20230917155748055.png differ diff --git a/pictures/image-20230917160616521.png b/pictures/image-20230917160616521.png new file mode 100644 index 000000000..b7b5ece63 Binary files /dev/null and b/pictures/image-20230917160616521.png differ diff --git a/pictures/image-20230917181618942.png b/pictures/image-20230917181618942.png new file mode 100644 index 000000000..b0b6f606b Binary files /dev/null and b/pictures/image-20230917181618942.png differ diff --git a/pictures/image-20230917181842030.png b/pictures/image-20230917181842030.png new file mode 100644 index 000000000..2e9338203 Binary files /dev/null and b/pictures/image-20230917181842030.png differ diff --git a/pictures/image-20230917205500523.png b/pictures/image-20230917205500523.png new file mode 100644 index 000000000..66c141aa8 Binary files /dev/null and b/pictures/image-20230917205500523.png differ diff --git a/pictures/image-20230917205636137.png b/pictures/image-20230917205636137.png new file mode 100644 index 000000000..65114fe46 Binary files /dev/null and b/pictures/image-20230917205636137.png differ diff --git a/pictures/image-20230917205833596.png b/pictures/image-20230917205833596.png new file mode 100644 index 000000000..64c578002 Binary files /dev/null and b/pictures/image-20230917205833596.png differ diff --git a/pictures/image-20230917210112389.png b/pictures/image-20230917210112389.png new file mode 100644 index 000000000..ea8181c16 Binary files /dev/null and b/pictures/image-20230917210112389.png differ diff --git a/pictures/image-20230917210303469.png b/pictures/image-20230917210303469.png new file mode 100644 index 000000000..19eb3681b Binary files /dev/null and b/pictures/image-20230917210303469.png differ diff --git a/pictures/image-20230917210616858.png b/pictures/image-20230917210616858.png new file mode 100644 index 000000000..ccb2f5925 Binary files /dev/null and b/pictures/image-20230917210616858.png differ diff --git a/pictures/image-20230917210730392.png b/pictures/image-20230917210730392.png new file mode 100644 index 000000000..6c94cdc49 Binary files /dev/null and b/pictures/image-20230917210730392.png differ diff --git a/pictures/image-20230917211254167.png b/pictures/image-20230917211254167.png new file mode 100644 index 000000000..e74e1ae32 Binary files /dev/null and b/pictures/image-20230917211254167.png differ diff --git a/pictures/image-20230917211543109.png b/pictures/image-20230917211543109.png new file mode 100644 index 000000000..aa4a57126 Binary files /dev/null and b/pictures/image-20230917211543109.png differ diff --git a/pictures/image-20230917212026101.png b/pictures/image-20230917212026101.png new file mode 100644 index 000000000..e46c686a0 Binary files /dev/null and b/pictures/image-20230917212026101.png differ diff --git a/pictures/image-20230917212530539.png b/pictures/image-20230917212530539.png new file mode 100644 index 000000000..dc074b6d3 Binary files /dev/null and b/pictures/image-20230917212530539.png differ diff --git a/pictures/image-20230917212709489.png b/pictures/image-20230917212709489.png new file mode 100644 index 000000000..6e05adeeb Binary files /dev/null and b/pictures/image-20230917212709489.png differ diff --git a/pictures/image-20230917213029734.png b/pictures/image-20230917213029734.png new file mode 100644 index 000000000..69ea233d4 Binary files /dev/null and b/pictures/image-20230917213029734.png differ diff --git a/pictures/image-20230917213316193.png b/pictures/image-20230917213316193.png new file mode 100644 index 000000000..6765856e1 Binary files /dev/null and b/pictures/image-20230917213316193.png differ diff --git a/pictures/image-20230917213859581.png b/pictures/image-20230917213859581.png new file mode 100644 index 000000000..e68e6cf98 Binary files /dev/null and b/pictures/image-20230917213859581.png differ diff --git a/pictures/image-20230917214424722.png b/pictures/image-20230917214424722.png new file mode 100644 index 000000000..9047b9300 Binary files /dev/null and b/pictures/image-20230917214424722.png differ diff --git a/pictures/image-20230919214601245.png b/pictures/image-20230919214601245.png new file mode 100644 index 000000000..51b126acc Binary files /dev/null and b/pictures/image-20230919214601245.png differ diff --git a/pictures/image-20230919221346055.png b/pictures/image-20230919221346055.png new file mode 100644 index 000000000..658c6c8b0 Binary files /dev/null and b/pictures/image-20230919221346055.png differ diff --git a/pictures/image-20230919221906997.png b/pictures/image-20230919221906997.png new file mode 100644 index 000000000..51c1d8fa0 Binary files /dev/null and b/pictures/image-20230919221906997.png differ diff --git a/pictures/image-20230919222258965.png b/pictures/image-20230919222258965.png new file mode 100644 index 000000000..7cc6ff74e Binary files /dev/null and b/pictures/image-20230919222258965.png differ diff --git a/pictures/image-20230919224644515.png b/pictures/image-20230919224644515.png new file mode 100644 index 000000000..8d1d35b54 Binary files /dev/null and b/pictures/image-20230919224644515.png differ diff --git a/pictures/image-20230919230906304.png b/pictures/image-20230919230906304.png new file mode 100644 index 000000000..0076750d2 Binary files /dev/null and b/pictures/image-20230919230906304.png differ diff --git a/pictures/image-20230919231127383.png b/pictures/image-20230919231127383.png new file mode 100644 index 000000000..a911fec0e Binary files /dev/null and b/pictures/image-20230919231127383.png differ diff --git a/pictures/image-20230920100712177.png b/pictures/image-20230920100712177.png new file mode 100644 index 000000000..48285331e Binary files /dev/null and b/pictures/image-20230920100712177.png differ diff --git a/pictures/image-20230920101220932.png b/pictures/image-20230920101220932.png new file mode 100644 index 000000000..f0177e8a9 Binary files /dev/null and b/pictures/image-20230920101220932.png differ diff --git a/pictures/image-20230920101719542.png b/pictures/image-20230920101719542.png new file mode 100644 index 000000000..6c109ae00 Binary files /dev/null and b/pictures/image-20230920101719542.png differ diff --git a/pictures/image-20230920112553412.png b/pictures/image-20230920112553412.png new file mode 100644 index 000000000..7266731e7 Binary files /dev/null and b/pictures/image-20230920112553412.png differ diff --git a/pictures/image-20230920112730660.png b/pictures/image-20230920112730660.png new file mode 100644 index 000000000..e51345803 Binary files /dev/null and b/pictures/image-20230920112730660.png differ diff --git a/pictures/image-20230920231224351.png b/pictures/image-20230920231224351.png new file mode 100644 index 000000000..38dc6f4ad Binary files /dev/null and b/pictures/image-20230920231224351.png differ diff --git a/pictures/image-20230921145126572.png b/pictures/image-20230921145126572.png new file mode 100644 index 000000000..e2080c32d Binary files /dev/null and b/pictures/image-20230921145126572.png differ diff --git a/pictures/image-20230922205223275.png b/pictures/image-20230922205223275.png new file mode 100644 index 000000000..e0ff545f5 Binary files /dev/null and b/pictures/image-20230922205223275.png differ diff --git a/pictures/image-20230922214526400.png b/pictures/image-20230922214526400.png new file mode 100644 index 000000000..a7852dbf4 Binary files /dev/null and b/pictures/image-20230922214526400.png differ diff --git a/pictures/insertionSort.gif b/pictures/insertionSort.gif new file mode 100644 index 000000000..bab6db57b Binary files /dev/null and b/pictures/insertionSort.gif differ diff --git a/pictures/mergeSort.gif b/pictures/mergeSort.gif new file mode 100644 index 000000000..3f0adb94e Binary files /dev/null and b/pictures/mergeSort.gif differ diff --git a/pictures/quickSort.gif b/pictures/quickSort.gif new file mode 100644 index 000000000..6a3faec92 Binary files /dev/null and b/pictures/quickSort.gif differ diff --git a/pictures/radixSort.gif b/pictures/radixSort.gif new file mode 100644 index 000000000..f5f8c801d Binary files /dev/null and b/pictures/radixSort.gif differ diff --git a/pictures/selectionSort.gif b/pictures/selectionSort.gif new file mode 100644 index 000000000..17a2cc7bc Binary files /dev/null and b/pictures/selectionSort.gif differ diff --git a/posts/11844.html b/posts/11844.html new file mode 100644 index 000000000..c849744f7 --- /dev/null +++ b/posts/11844.html @@ -0,0 +1,213 @@ +验证码服务 | The Blog + + + + + + + + + + + + +

验证码服务

1.使用邮件发送验证码

1.1 引入依赖

坑点:有时候遇到验证码发不出去的情况,要调整依赖的版本,更新为高版本的依赖

+
1
2
3
4
5
6
7
8
9
10
11
12
13
<!--javaMail-->
<dependencies>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.5.3</version>
</dependency>
</dependencies>
+ + + +

1.2 导入发送验证码的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//读取配置文件中的值
//静态属性只会加载一次 我们要在第一次加载的时候给静态属性赋上值
//还要其他的方法 比如使用@Value注解
static { //读取配置文件中的关于邮件的配置
try {
FileInputStream fileInputStream = new FileInputStream("src/main/resources/application.properties");
Properties properties = new Properties();
properties.load(fileInputStream);
user = properties.getProperty("guli.user");
password = properties.getProperty("guli.password");
} catch (IOException e) {
throw new RuntimeException(e);
}

}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

/**
* 发邮件工具类
*/
public class MailUtils {
private static String USER = "xxxxxxxx"; // 发件人称号,同邮箱地址
private static String PASSWORD = "xxxxxxxx"; // 授权码,开启SMTP时显示

/**
* @param to 收件人邮箱
* @param text 邮件正文
* @param title 标题
*/
/* 发送验证信息的邮件 */
public static boolean sendMail(String to, String text, String title) {
try {
final Properties props = new Properties();
props.put("mail.smtp.auth", "true");
//注意发送邮件的方法中,发送给谁的,发送给对应的app,※
//要改成对应的app。扣扣的改成qq的,网易的要改成网易的。※
//props.put("mail.smtp.host", "smtp.qq.com");
props.put("mail.smtp.host", "smtp.qq.com");

// 发件人的账号
props.put("mail.user", USER);
//发件人的密码
props.put("mail.password", PASSWORD);

// 构建授权信息,用于进行SMTP进行身份验证
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
// 用户名、密码
String userName = props.getProperty("mail.user");
String password = props.getProperty("mail.password");
return new PasswordAuthentication(userName, password);
}
};
// 使用环境属性和授权信息,创建邮件会话
Session mailSession = Session.getInstance(props, authenticator);
// 创建邮件消息
MimeMessage message = new MimeMessage(mailSession);
// 设置发件人
String username = props.getProperty("mail.user");
InternetAddress form = new InternetAddress(username);
message.setFrom(form);

// 设置收件人
InternetAddress toAddress = new InternetAddress(to);
message.setRecipient(Message.RecipientType.TO, toAddress);

// 设置邮件标题
message.setSubject(title);

// 设置邮件的内容体
message.setContent(text, "text/html;charset=UTF-8");
// 发送邮件
Transport.send(message);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}


}
+ +

1.3 导入随机生成验证码的工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.util.Random;

/**
* 随机生成验证码工具类
*/
public class ValidateCodeUtils {
/**
* 随机生成验证码
*
* @param length 长度为4位或者6位
* @return
*/
public static Integer generateValidateCode(int length) {
Integer code = null;
if (length == 4) {
code = new Random().nextInt(9999);//生成随机数,最大为9999
if (code < 1000) {
code = code + 1000;//保证随机数为4位数字
}
} else if (length == 6) {
code = new Random().nextInt(999999);//生成随机数,最大为999999
if (code < 100000) {
code = code + 100000;//保证随机数为6位数字
}
} else {
throw new RuntimeException("只能生成4位或6位数字验证码");
}
return code;
}

/**
* 随机生成指定长度字符串验证码
*
* @param length 长度
* @return
*/
public static String generateValidateCode4String(int length) {
Random rdm = new Random();
String hash1 = Integer.toHexString(rdm.nextInt());
String capstr = hash1.substring(0, length);
return capstr;
}
}
+ +

1.4 测试使用

1
2
3
1.设置发送邮件的账号和授权码信息
2.编写测试类测试邮件是否可以正常的发送
3.添加到业务代码中,实现验证码的发送功能
+ + + +

2.使用阿里云的短信服务

阿里云的短信服务不面向个人用户,无法使用,但是使用的基本逻辑和使用邮箱类似

+

3.Token字符串生成的工具类(JWT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.atguigu.commonutils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
* @author helen
* @since 2019/10/16
*/
public class JwtUtils {

/**
* 设置token的过期时间
*/
public static final long EXPIRE = 1000 * 60 * 60 * 24;
/**
* 密钥
*/
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";


/**
* 生成token字符串
*/
public static String getJwtToken(String id, String nickname){

String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("guli-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id)
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();

return JwtToken;
}

/**
* 判断token是否存在与有效
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 判断token是否存在与有效
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 根据token获取会员id
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}

+ +
\ No newline at end of file diff --git a/posts/12929.html b/posts/12929.html new file mode 100644 index 000000000..a96848feb --- /dev/null +++ b/posts/12929.html @@ -0,0 +1,224 @@ +SpringBoot整合MongoDB | The Blog + + + + + + + + + + + + +

SpringBoot整合MongoDB

1.介绍

​ MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

+

适用场景
1、网站数据:Mongo非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
2、缓存:由于性能很高,Mongo也适合作为信息基础设施的缓存层。在系统重启之后,由M ongo搭建的持久化缓存层可以避免下层的数据源过载。
3、大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵, 在此之前,很多时候程序员往往会选择传统的文件进行存储。
4、高伸缩性的场景:Mongo非常适合由数十或数百台服务器组成的数据库。Mongo的路线图中已经包含对Map Reduce弓摩的内置支持。
5、用于对象及 JSON数据的存储:Mongo的BSON数据格式非常适合文档化格式的存储 及查询。
不适用场合
1、高度事务性的系统:例如银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。
2、传统的商业智能应用:针对特定问题的BI数据库会对产生高度优化的查询方式。对于此类应用,数据仓库可能是更合适的选择。

+

中文文档:https://docs.mongoing.com/

+

2.入门

​ spring-data-mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodb,MongoRepository操作简单,MongoTemplate操作灵活,我们在项目中可以灵活适用这两种方式操作mongodb,MongoRepository的缺点是不够灵活,MongoTemplate正好可以弥补不足

+

2.1引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
+ +

2.2 配置文件中添加MongoDB的配置

1
spring.data.mongodb.uri=mongodb://localhost:27017/test
+ +

2.3 创建对应的实体类

1
2
3
4
5
6
7
8
9
10
@Data
@Document("User") //指定实体类对应的MongoDB集合的名称
public class User {
@Id //自动生成的主键ID
private String id;
private String name;
private Integer age;
private String email;
private String createDate;
}
+ +

2.4 常用的方法

1
2
3
4
5
6
7
//常用方法
mongoTemplate.findAll(User.class); //查询User文档的全部数据
mongoTemplate.findById(<id>, User.class); //查询User文档id为id的数据
mongoTemplate.find(query, User.class); //根据query内的查询条件查询
mongoTemplate.upsert(query, update, User.class);// 修改
mongoTemplate.remove(query, User.class);// 删除
mongoTemplate.insert(User);//新增
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Query对象
1、创建一个query对象(用来封装所有条件对象),再创建一个criteria对象(用来构建条件)

2、精准条件:criteria.and(“key”).is(“条件”)
模糊条件:criteria.and(“key”).regex(“条件”)

3、封装条件:query.addCriteria(criteria)

4、大于(创建新的criteria):Criteria gt = Criteria.where(“key”).gt(“条件”)
小于(创建新的criteria):Criteria lt = Criteria.where(“key”).lt(“条件”)

5、Query.addCriteria(new Criteria().andOperator(gt,lt));

6、一个query中只能有一个andOperator() 其参数也可以是Criteria数组

7、排序 :query.with(new Sort(Sort.Direction.ASC, "age"). and(new Sort(Sort.Direction.DESC, "date")))
+ +
1
2
3
4
5
6
7
8
9
10
11
主要注解:

@Document,文档是 MongoDB 中最基本的数据单元,由键值对组成,类似于 JSON 格式,可以存储不同字段,字段的值可以包括其他文档,数组和文档数组。

@Id(主键):用来将成员变量的值映射为文档的_id的值

@Indexed(索引): 索引是一种特殊的数据结构,存储在一个易于遍历读取的数据集合中,能够对数据库文档中的数据进行排序。索引能极大提高文档查询效率,如果没有设置索引,MongoDB 会遍历集合中的整个文档,选取符合查询条件的文档记录。这种查询效率是非常低的。

@Field(字段): 文档中的字段,类似于 MySql 中的列。

@Aggregation(聚合): 聚合主要用于数据处理,例如统计平均值、求和等。
+ +

示例代码

+

2.4.1 使用MongoTemplate的方式访问MongoDB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
@SpringBootTest
class DemomogoApplicationTests {

@Autowired
private MongoTemplate mongoTemplate;

//添加
@Test
public void createUser() {
User user = new User();
user.setAge(20);
user.setName("test");
user.setEmail("4932200@qq.com");
User user1 = mongoTemplate.insert(user);
System.out.println(user1);
}

//查询所有
@Test
public void findUser() {
List<User> userList = mongoTemplate.findAll(User.class);
System.out.println(userList);
}

//根据id查询
@Test
public void getById() {
User user =
mongoTemplate.findById("5ffbfa2ac290f356edf9b5aa", User.class);
System.out.println(user);
}

//条件查询
@Test
public void findUserList() {
Query query = new Query(Criteria
.where("name").is("test")
.and("age").is(20));
List<User> userList = mongoTemplate.find(query, User.class);
System.out.println(userList);
}

//模糊查询
@Test
public void findUsersLikeName() {
String name = "est";
String regex = String.format("%s%s%s", "^.*", name, ".*$");
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Query query = new Query(Criteria.where("name").regex(pattern));
List<User> userList = mongoTemplate.find(query, User.class);
System.out.println(userList);
}

//分页查询
@Test
public void findUsersPage() {
String name = "est";
int pageNo = 1;
int pageSize = 10;

//分页查询的条件
Query query = new Query();
String regex = String.format("%s%s%s", "^.*", name, ".*$");
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
query.addCriteria(Criteria.where("name").regex(pattern));
//总记录数
int totalCount = (int) mongoTemplate.count(query, User.class);
//分页查询
List<User> userList = mongoTemplate.find(query.skip((pageNo - 1) * pageSize).limit(pageSize), User.class);

//将分页的总记录数和分页数据封装在一个map集合中
Map<String, Object> pageMap = new HashMap<>();
pageMap.put("list", userList);
pageMap.put("totalCount",totalCount);
System.out.println(pageMap);
}

//修改
@Test
public void updateUser() {
User user = mongoTemplate.findById("5ffbfa2ac290f356edf9b5aa", User.class);
user.setName("test_1");
user.setAge(25);
user.setEmail("493220990@qq.com");
Query query = new Query(Criteria.where("_id").is(user.getId()));
Update update = new Update();
update.set("name", user.getName());
update.set("age", user.getAge());
update.set("email", user.getEmail());
UpdateResult result = mongoTemplate.upsert(query, update, User.class);
//影响的行数
long count = result.getModifiedCount();
System.out.println(count);
}

//删除操作
@Test
public void delete() {
Query query =
new Query(Criteria.where("_id").is("5ffbfa2ac290f356edf9b5aa"));
DeleteResult result = mongoTemplate.remove(query, User.class);
long count = result.getDeletedCount();
System.out.println(count);
}
}
+ +

2.4.2 使用MongoRepository的方式访问MongoDB

a.创建UserRepository类
1
2
3
4
5
6
7
8
9
10
11
12
package com.atguigu.mongodb.repository;

import com.atguigu.mongodb.entity.User;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserRepository extends MongoRepository<User, String> {

}
+ +
b.测试使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@SpringBootTest
class DemomogoApplicationTests1 {

@Autowired
private UserRepository userRepository;

//添加
@Test
public void createUser() {
User user = new User();
user.setAge(20);
user.setName("张三");
user.setEmail("3332200@qq.com");
User user1 = userRepository.save(user);
}

//查询所有
@Test
public void findUser() {
List<User> userList = userRepository.findAll();
System.out.println(userList);
}

//id查询
@Test
public void getById() {
User user = userRepository.findById("5ffbfe8197f24a07007bd6ce").get();
System.out.println(user);
}

//条件查询
@Test
public void findUserList() {
//查询条件 名字是张三 年龄是20
User user = new User();
user.setName("张三");
user.setAge(20);
Example<User> userExample = Example.of(user);
List<User> userList = userRepository.findAll(userExample);
System.out.println(userList);
}

//模糊查询
@Test
public void findUsersLikeName() {
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
User user = new User();
user.setName("三");
Example<User> userExample = Example.of(user, matcher);
List<User> userList = userRepository.findAll(userExample);
System.out.println(userList);
}

//分页查询
@Test
public void findUsersPage() {
//根据年龄降序
Sort sort = Sort.by(Sort.Direction.DESC, "age");
//0为第一页
Pageable pageable = PageRequest.of(0, 10, sort);
//创建匹配器,即如何使用查询条件
ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
.withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
User user = new User();
user.setName("三");//查询名字中包含 '三' 的数据
Example<User> userExample = Example.of(user, matcher);
//创建实例
Example<User> example = Example.of(user, matcher);
Page<User> pages = userRepository.findAll(example, pageable);
System.out.println(pages);
}

//修改
@Test
public void updateUser() {
User user = userRepository.findById("5ffbfe8197f24a07007bd6ce").get();
user.setName("张三_1");
user.setAge(25);
user.setEmail("883220990@qq.com");
User save = userRepository.save(user);//有id值就是修改 m
System.out.println(save);
}

//删除
@Test
public void delete() {
userRepository.deleteById("5ffbfe8197f24a07007bd6ce");
}
}
+ + + + + + + +
\ No newline at end of file diff --git a/posts/13579.html b/posts/13579.html new file mode 100644 index 000000000..77ac5f2a9 --- /dev/null +++ b/posts/13579.html @@ -0,0 +1,269 @@ +力扣(LeetCode)算法刷题 | The Blog + + + + + + + + + + + + +

力扣(LeetCode)算法刷题

刷题网站: 力扣 视频: AcWing

+

一.数组

A.简单

1.两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。

+

示例 1:

+
1
2
3
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
+ +

示例 2:

+
1
2
输入:nums = [3,2,4], target = 6
输出:[1,2]
+ +

示例 3:

+
1
2
输入:nums = [3,3], target = 6
输出:[0,1]
+ +

提示:

+
    +
  • 2 <= nums.length <= 104
  • +
  • -109 <= nums[i] <= 109
  • +
  • -109 <= target <= 109
  • +
  • 只会存在一个有效答案
  • +
+

代码实现

+

自己的代码 暴力枚举

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < nums.length; j++) {
if (nums[i] + nums[j] == target && i != j) {
result[0] = i;
result[1] = j;
}
}
}
return result;
}
}
+ +

2.删除有序数组中的重复项

给你一个 升序排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

+

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

+
    +
  • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • +
  • 返回 k
  • +
+

判题标准:

+

系统会用下面的代码来测试你的题解:

+
1
2
3
4
5
6
7
8
9
int[] nums = [...]; // 输入数组
int[] expectedNums = [...]; // 长度正确的期望答案

int k = removeDuplicates(nums); // 调用

assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
assert nums[i] == expectedNums[i];
}
+ +

如果所有断言都通过,那么您的题解将被 通过

+

示例 1:

+
1
2
3
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
+ +

示例 2:

+
1
2
3
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
+ +

提示:

+
    +
  • 1 <= nums.length <= 3 * 104
  • +
  • -104 <= nums[i] <= 104
  • +
  • nums 已按 升序 排列
  • +
+

代码实现

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.leetcode.array;

import java.util.Arrays;

/**
* @author GongChangjiang
* @version 1.0
* @Date 2023/5/16
* @Description 删除有序数组中的重复项
*/
public class RemoveDuplicates {
public static void main(String[] args) {
RemoveDuplicates removeDuplicates = new RemoveDuplicates();
int[] arr = {0,0,1,1,1,2,2,3,3,4};
System.out.println(removeDuplicates.removeDuplicates(arr));

}

/**
* 数组去重
*/
public int removeDuplicates(int[] nums) {
int n = nums.length;//获取数组长度
if (n == 0) { //数组的长度是零就直接的退出
return 0;
}
int fast = 1, slow = 1; //定义两个快慢指针
while (fast < n) {
//如果后面一个数和前面的那个数相同 慢指针不动 快指针动
//然后下一次判断的时候 慢指针就别快指针慢了一次 再将慢指针指向快指针
//相同的数就后移了
if (nums[fast] != nums[fast - 1]) {
nums[slow] = nums[fast];
++slow;
}
++fast;
}
return slow;
}
}
+ +

3.移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

+

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

+

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

+

说明:

+

为什么返回数值是整数,但输出的答案是数组呢?

+

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

+

你可以想象内部操作如下:

+
1
2
3
4
5
6
7
8
// nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
int len = removeElement(nums, val);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
+ +

示例 1:

+
1
2
3
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
+ +

示例 2:

+
1
2
3
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
+ +

提示:

+
    +
  • 0 <= nums.length <= 100
  • +
  • 0 <= nums[i] <= 50
  • +
  • 0 <= val <= 100
  • +
+

代码实现

+
1

+ + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/13813.html b/posts/13813.html new file mode 100644 index 000000000..3983f7a4b --- /dev/null +++ b/posts/13813.html @@ -0,0 +1,290 @@ +SpringBoot整合Redis | The Blog + + + + + + + + + + + + +

SpringBoot整合Redis

1.Redis的介绍

​ Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。

+

中文文档:https://www.redis.net.cn/

+

2.入门

2.1 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
+ + + +

2.2 添加配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.atguigu.yygh.common.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.lang.reflect.Method;
import java.time.Duration;

/**
* @author GongChangjiang
* @version 1.0
* @Date 2023/4/14
* @Description
*/
@Configuration
@EnableCaching
public class RedisConfig {

/**
* 自定义key规则
*/
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}

/**
* 设置RedisTemplate规则
*
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);

//序列号key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

redisTemplate.afterPropertiesSet();
return redisTemplate;
}

/**
* 设置CacheManager缓存规则
* 缓存的时间
* 乱码的处理
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);

// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();

RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}

}
+ +

说明:
@EnableCaching:标记注解 @EnableCaching,开启缓存,并配置Redis缓存管理器。@EnableCaching 注释触发后置处理器, 检查每一个Spring bean 的 public 方法是否存在缓存注解。如果找到这样的一个注释, 自动创建一个代理拦截方法调用和处理相应的缓存行为。

+

2.3 配置文件中加入redis的配置

application.properties

+
1
2
3
4
5
6
7
8
9
10
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000

spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
+ +

2.4 使用Spring Cache + Redis执行缓存操作

2.1.2 @Cacheable

​ 根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。

+ + + + + + + + + + + + + + + + + + + +
属性/方法名解释
value缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames与 value 差不多,二选一即可
key可选属性,可以使用 SpEL 标签自定义缓存的key
+

2.1.2 @CachePut

​ 使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。

+ + + + + + + + + + + + + + + + + + + +
属性/方法名解释
value缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames与 value 差不多,二选一即可
key可选属性,可以使用 SpEL 标签自定义缓存的key
+

2.1.3 @CacheEvict

​ 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
属性/方法名解释
value缓存名,必填,它指定了你的缓存存放在哪块命名空间
cacheNames与 value 差不多,二选一即可
key可选属性,可以使用 SpEL 标签自定义缓存的key
allEntries是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
beforeInvocation是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存
+

3.示例

下面的第一个方法是查询数据位list集合

+

​ @Cacheable(value = “dict”,keyGenerator = “keyGenerator”) 对方法的结果进行缓存

+

​ value属性表示key的前缀

+

​ keyGenerator表示key的生成规则,生成规则在配置文件中配置,这里我们使用的是方法的全类名作为key的后缀

+

第二个方法是添加数据 添加数据会造成数据库中数据的变化 我们要清除缓存

+

​ @CacheEvict(value = “dict”,allEntries = true) 清空指定的缓存

+

​ value属性表示清空以dict为前缀的所有缓存

+

​ allEntries 属性表示是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
@Cacheable(value = "dict",keyGenerator = "keyGenerator")
public List<Dict> findByParentId(Long parentId) {
LambdaQueryWrapper<Dict> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Dict::getParentId, parentId);
List<Dict> dictList = dictMapper.selectList(queryWrapper);
for (Dict dict : dictList) {
dict.setHasChildren(this.isHasChildren(dict.getId()));
}
return dictList;
}

@CacheEvict(value = "dict",allEntries = true)
public void importDictData(MultipartFile file) {
try {
EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictListener(dictMapper)).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}



+ +

key的生成规则如下图:

+

image-20230414152035347

+
\ No newline at end of file diff --git a/posts/1416.html b/posts/1416.html new file mode 100644 index 000000000..4632262de --- /dev/null +++ b/posts/1416.html @@ -0,0 +1,280 @@ +压力测试与性能监控 | The Blog + + + + + + + + + + + + +

压力测试与性能监控

一.压力测试

性能指标

+
    +
  • 响应时间(Response Time: RT)响应时间指用户从客户端发起一个请求开始,到客户端接收到从服务器端返回的响应结束,整个过程所耗费的时间。

    +
  • +
  • HPS(Hits Per Second) :每秒点击次数,单位是次/秒。

    +
  • +
  • TPS(Transaction per Second):系统每秒处理交易数,单位是笔/秒。

    +
  • +
  • QPS(Query per Second):系统每秒处理查询次数,单位是次/秒。对于互联网业务中,如果某些业务有且仅有一个请求连接,那么 TPS=QPS=HPS,一般情况下用 TPS 来衡量整个业务流程,用 QPS 来衡量接口查询次数,用 HPS 来表示对服务器单击请求。

    +
  • +
  • 无论 TPS、QPS、HPS,此指标是衡量系统处理能力非常重要的指标,越大越好,根据经验,一般情况下:

    +

    金融行业:1000TPS~50000TPS,不包括互联网化的活动

    +

    保险行业:100TPS~100000TPS,不包括互联网化的活动

    +

    制造行业:10TPS~5000TPS

    +

    互联网电子商务:10000TPS~1000000TPS

    +

    互联网中型网站:1000TPS~50000TPS

    +

    互联网小型网站:500TPS~10000TPS

    +
  • +
  • 最大响应时间(Max Response Time) 指用户发出请求或者指令到系统做出反应(响应)的最大时间。

    +
  • +
  • 最少响应时间(Mininum ResponseTime) 指用户发出请求或者指令到系统做出反应(响应)的最少时间。

    +
  • +
  • 90%响应时间(90% Response Time) 是指所有用户的响应时间进行排序,第 90%的响应时间。

    +
  • +
  • 从外部看,性能测试主要关注如下三个指标

    +

    吞吐量:每秒钟系统能够处理的请求数、任务数。

    +

    响应时间:服务处理一个请求或一个任务的耗时。

    +

    错误率:一批请求中结果出错的请求所占比例。

    +
  • +
+

1.JMeter的使用

1.1 安装

1.下载安装包

+

安装网址: https://jmeter.apache.org/download_jmeter.cgi

+

image-20230709161151542

+

2.解压并双击运行

+

image-20230709161346717

+

image-20230709161600398

+

1.2 设置

设置为中文页面

+

点击Options->Language->选择简体中文

+

image-20230709161752103

+

1.3 压测示例

1.新建线程组

+

右键点击测试计划->添加->线程->线程组

+

image-20230709162502383

+

线程组参数详解:

+
    +
  • 线程数:虚拟用户数。一个虚拟用户占用一个进程或线程。设置多少虚拟用户数在这里也就是设置多少个线程数。

    +
  • +
  • 线程数:虚拟用户数。一个虚拟用户占用一个进程或线程。设置多少虚拟用户数在这里也就是设置多少个线程数。

    +
  • +
  • Ramp-Up Period(in seconds)准备时长:设置的虚拟用户数需要多长时间全部启动。如果线程数为 10,准备时长为 2,那么需要 2 秒钟启动10个线程,也就是每秒钟启动 5 个线程。

    +
  • +
  • 循环次数:每个线程发送请求的次数。如果线程数为 10,循环次数为 100,那么每个线程发送 100 次请求。总请求数为 10*100=1000 。如勾选了“永远”,那么所有线程会一直发送请求,一到选择停止运行脚本。

    +
  • +
  • Delay Thread creation until needed:直到需要时延迟线程的创建。

    +
  • +
  • 调度器:设置线程组启动的开始时间和结束时间(配置调度器时,需要勾选循环次数为永远)

    +
  • +
  • 持续时间(秒):测试持续时间,会覆盖结束时间

    +
  • +
  • 启动延迟(秒):测试延迟启动时间,会覆盖启动时间

    +
  • +
  • 启动时间:测试启动时间,启动延迟会覆盖它。当启动时间已过,手动只需测试时当前时间也会覆盖它。

    +
  • +
  • 结束时间:测试结束时间,持续时间会覆盖它。

    +
  • +
+

2.添加HTTP请求

+

image-20230709163303477

+

image-20230709163457833

+

3.添加监听器

+

image-20230709163639856

+

4.启动压测和查看分析结果

+

image-20230709164012746

+

image-20230709164208747

+

1.4 JMeter Address Already in use 错误解决

windows 本身提供的端口访问机制的问题。
Windows 提供给 TCP/IP 链接的端口为 1024-5000,并且要四分钟来循环回收他们。就导致
我们在短时间内跑大量的请求时将端口占满了。
1.cmd 中,用 regedit 命令打开注册表

+

2.在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 下,
右击 parameters,添加一个新的 DWORD,名字为 MaxUserPort,然后双击 MaxUserPort,输入数值数据为 65534,基数选择十进制(如果是分布式运行的话,控制机器和负载机器都需要这样操作)

+

3.修改配置完毕之后记得重启机器才会生效
官网解释:https://support.microsoft.com/zh-cn/help/196271/when-you-try-to-connect-from-tcp-ports-greater-than-5000-you-receive-t
TCPTimedWaitDelay:30

+

二.性能监控

Jdk 的两个小工具 jconsole、jvisualvm(升级版的 jconsole);通过命令行启动,可监控本地和远程应用。

+

1.jconsole

image-20230709160343565

+

2.jvisualvm

jvisualvm相比jconsole功能更全,一般使用jvisualvm

+

image-20230709160552461

+

Visual GC插件的安装

+

点击工具->插件->在可用插件中选择Visual GC安装

+

安装成功之后显示如下

+

image-20230709160932416

+

image-20230709170626946

+
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/14438.html b/posts/14438.html new file mode 100644 index 000000000..8487eff2d --- /dev/null +++ b/posts/14438.html @@ -0,0 +1,212 @@ +常见的问题及解决方法 | The Blog + + + + + + + + + + + + +

常见的问题及解决方法

1.前后端时间格式的问题

配置文件中设置时间的格式和时区

+

image-20230506130238222

+

2.MyBatis分页插件统计总记录数total失效的问题

检查一下分页插件的配置 选择以下正确的分页插件配置

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.atguigu.common.config.mp;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* mp的分页插件
*/
@Configuration
@MapperScan("com.atguigu.auth.mapper")
public class MybatisPlusConfig {

/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}

@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.atguigu.config;

import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author GongChangjiang
* @version 1.0
* @Date 2023/3/4
* @Description mybatis-plus的配置文件
*/
@Configuration
public class MpConfig {

/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}

+ +

3.网关中的路由问题

网关中的路由匹配问题,模糊的路由放在精确的路由前面,容易是精确的路由配置失效

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
cloud:
gateway:
routes:
#这个是精确的路由,下面的路由在前,特定的请求就不会转发到这个请求中来
- id: product_route
uri: lb://gulimall-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/(?<segment>.*),/$\{segment}

#这个是模糊的路由,如果放在上面的那个路由配置的前面,会导致上面的那个路由配置失效
- id: admin_route
uri: lb://renren-fast
predicates:
- Path=/api/**
filters:
- RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
+ +

4.Docker中部署mysql后SpringBoot连接时提示表不存在(修改表名忽略大小写)解决方案

使用Docer启动容器的时候设置忽略大小写,表不存在是因为表名大小写的原因。

+

方案一:启动的时候加上–lower_case_table_names= 1

+
1
docker run -p 3306:3306 --name mysql -v /usr/local/docker/mysql/conf:/etc/mysql -v /usr/local/docker/mysql/logs:/var/log/mysql -v /usr/local/docker/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names= 1
+ +

方案二: 修改mysql挂载在宿主机的配置文件

+
1
2
# 在配置文件中添加如下的配置
lower_case_table_names=1
+ +
\ No newline at end of file diff --git a/posts/1530.html b/posts/1530.html new file mode 100644 index 000000000..b38ea0298 --- /dev/null +++ b/posts/1530.html @@ -0,0 +1,207 @@ +信息系统分析与设计项目运行环境的搭建 | The Blog + + + + + + + + + + + + +

信息系统分析与设计项目运行环境的搭建

​ 安装时请仔细的看一遍需要安装的环境,避免因为错装版本导致项目无法运行,安装过程中遇到链接失效可以点击头像下方的图标联系我获取最新的链接

+

注:项目是基于java的项目,这些才需要安装,C或者C++,Python,Go,PHP等语言编写的项目,请移步

+

1.需要安装的环境(最小安装环境,不涉及微服务的项目,微服务项目环境安装私聊我)

1.1:写代码的工具: Intellij IDEA

1.2:Java运行环境:java8 (也叫JDK1.8,高版本的不推荐使用,可能出现项目运行错误的情况)

1.3:数据库:MySQL 5.7(必须安装,数据的持久化需要用到)

1.4:构建工具:Maven 3.6.3(用于安装依赖,IDEA中自带的有Maven构建工具,但是由于用的是国外的镜像,安装依赖可能失败,这根据具体的项目决定),不想安装可以先用用自带的maven,不行的话再安装。

2.具体安装教程

2.1:Intellij IDEA :安装社区版也就是免费版的(项目运行完全够用),收费版需要自助开源(自行百度搜索破解教程)

安装教程:官网安装: https://www.jetbrains.com/idea/download/#section=windows

+

image-20230311104034350

+

2.2Java运行环境JDk的安装:这个必须安装,java程序运行的基础,推荐JDK1.8,不推荐高版本。必须安装成功。

安装教程:1.下载安装 2.配置环境变量 3.测试安装是否成功(任意位置打开cmd窗口输入java -version都可以执行)

+

保姆级教程: https://blog.csdn.net/weixin_52161454/article/details/122467301

+

2.3:Mysql 5.7:最好是安装5.7版本的,8.0版本的mysql驱动与5.7版本不一样(5.7版本安装不了可以安装8.0版本的),需要更改驱动和链接url (这里安装mysql数据库就行了,Oracle数据库个人项目不常用)

注意:安装最好一次成功,不然安装失败再安装会有很多的不便

+

以下保姆级教程二选一:

+

安装教程一: https://blog.csdn.net/m0_49284219/article/details/121972531

+

安装教程二: https://blog.csdn.net/qq_55660421/article/details/123023804

+

2.4:Maven3.6.3:可以安装最新版的maven ,这个是构建工具,用来安装项目运行所需要的依赖,安装成功后将配置文件中的安装源换成阿里源,安装依赖更快更稳定

保姆级别教程: https://blog.csdn.net/tirster/article/details/123418269

+

注:

​ 安装的过程中出现错误的话可以通过百度解决,你遇到的错误大多数人也会遇到,要学会自己寻找解决这些问题的办法。以上安装的是本次课程设计的基本环境依赖(够用了),如果项目中包含缓存优化(redis),前后端分离开发,分布式,还需安装redis数据库,nginx,VsCode,Dubbo,Zookeeper等软件和环境。安装过程中遇到实在是无法解决的问题,可以联系我,在线解答。下期可能会出一期怎么在github上寻找优质的项目,敬请期待。

+
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
最近更新
\ No newline at end of file diff --git a/posts/17259.html b/posts/17259.html new file mode 100644 index 000000000..056a661e6 --- /dev/null +++ b/posts/17259.html @@ -0,0 +1,233 @@ +MySQL5.7安装教程 | The Blog + + + + + + + + + + + + +

MySQL5.7安装教程

1.下载的地址

下载之后直接解压使用(下载之前看电脑上有没有mysql的服务,如果有先删除),解压的文件路径最好不要有中文

+

查看的方式

+
1
Win + R   输入    services.msc  回车打开   找是否有一个名为mysqld的服务
+ +

删除的指令

+
1
sc delete mysql
+ +

Mysql5.7地址:https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.19-winx64.zip

+

2.配置环境变量

添加环境变量 : 电脑-属性-高级系统设置-环境变量,在Path 环境变量增加mysql的安装目录\bin目录

+

3.在mysql的安装目录下创建my.ini文件

image-20230312152323079

+

my.ini

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[client]
port=3306
default-character-set=utf8
[mysqld]
# 设置为自己MYSQL的安装目录
basedir=C:\mysql\mysql-5.7.19-winx64\
# 设置为MYSQL的数据目录,这个目录系统帮我们创建
datadir=C:\mysql\mysql-5.7.19-winx64\data\
port=3306
character_set_server=utf8
#跳过安全检查,注释后需要输入正确的用户名和密码才能登录
#第一登录不要注释掉,第一次进入没有设置密码,就可以不用输入密码了
#skip-grant-tables

+ +

4.下载服务和初始化数据库

1.下载mysql的服务

+

​ 使用管理员身份打开 cmd , 并切换到mysql-5.7.19-winx64\bin 目录下, 执行 mysqld -install

+
1
2
cd  **/**/**/mysql-5.7.19-winx64\bin
mysqld -install
+ +

2.初始化数据

+
1
mysqld --initialize-insecure --user=mysql
+ +

5.启动和停止mysql的服务

1
2
3
4
#开启mysql的服务
net start mysql
#停止mysql的服务
net stop mysql
+ +

6.修改账户和密码

1
2
3
4
5
6
7
#9. 进入mysql 管理终端: 初次登录没有密码,直接按两下回车键进入
mysql -u root -p
进入之后执行:
use mysql;
update user set authentication_string=password('123456') where user='root' and Host='localhost';
flush privileges; #刷新权限
quit #退出
+ +

注释掉my.ini中的跳过权限检查 #skip-grant-tables

+

7.重启mysql的服务

1
2
3
net stop mysql
net start mysql
mysql -u root -p #输入刚才设置的密码进入mysql服务
+ +

安装失败,删除服务,重新安装

+

8.可视化工具

推荐SQLyog Navicat

+

网盘地址

+

PDF版本的教程(来源于韩顺平教育,转载注明出处)

+ +
+ +
+ + + +
\ No newline at end of file diff --git a/posts/1727.html b/posts/1727.html new file mode 100644 index 000000000..11df02e75 --- /dev/null +++ b/posts/1727.html @@ -0,0 +1,225 @@ +代码注释模板 | The Blog + + + + + + + + + + + + +

代码注释模板

VScode插件推荐:koroFileHeader (用于生成文件头部注释和函数注释的插件)

+

完整的模板大全:佛祖保佑永无BUG、神兽护体、注释图案 · OBKoro1/koro1FileHeader Wiki (github.com)

+

image-20230912105041466

+

佛祖保佑一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

////////////////////////////////////////////////////////////////////
// _ooOoo_ //
// o8888888o //
// 88" . "88 //
// (| ^_^ |) //
// O\ = /O //
// ____/`---'\____ //
// .' \\| |// `. //
// / \\||| : |||// \ //
// / _||||| -:- |||||- \ //
// | | \\\ - /// | | //
// | \_| ''\---/'' | | //
// \ .-\__ `-` ___/-. / //
// ___`. .' /--.--\ `. . ___ //
// ."" '< `.___\_<|>_/___.' >'"". //
// | | : `- \`.;`\ _ /`;.`/ - ` : | | //
// \ \ `-. \_ __\ /__ _/ .-` / / //
// ========`-.____`-.___\_____/___.-`____.-'======== //
// `=---=' //
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
// 佛祖保佑 永无BUG 永不修改 //
////////////////////////////////////////////////////////////////////
+ +

佛祖保佑一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* _ooOoo_
* o8888888o
* 88" . "88
* (| -_- |)
* O\ = /O
* ____/`---'\____
* .' \\| |// `.
* / \\||| : |||// \
* / _||||| -:- |||||- \
* | | \\\ - /// | |
* | \_| ''\---/'' | |
* \ .-\__ `-` ___/-. /
* ___`. .' /--.--\ `. . __
* ."" '< `.___\_<|>_/___.' >'"".
* | | : `- \`.;`\ _ /`;.`/ - ` : | |
* \ \ `-. \_ __\ /__ _/ .-` / /
* ======`-.____`-.___\_____/___.-`____.-'======
* `=---='
* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* 佛祖保佑 永无BUG
* 佛曰:
* 写字楼里写字间,写字间里程序员;
* 程序人员写程序,又拿程序换酒钱。
* 酒醒只在网上坐,酒醉还来网下眠;
* 酒醉酒醒日复日,网上网下年复年。
* 但愿老死电脑间,不愿鞠躬老板前;
* 奔驰宝马贵者趣,公交自行程序员。
* 别人笑我忒疯癫,我笑自己命太贱;
* 不见满街漂亮妹,哪个归得程序员?
*/
+ +

草泥马一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
*
*   ┏┓   ┏┓+ +
*  ┏┛┻━━━┛┻┓ + +
*  ┃       ┃  
*  ┃   ━   ┃ ++ + + +
* ████━████ ┃+
*  ┃       ┃ +
*  ┃   ┻   ┃
*  ┃       ┃ + +
*  ┗━┓   ┏━┛
*    ┃   ┃           
*    ┃   ┃ + + + +
*    ┃   ┃
*    ┃   ┃ + 神兽保佑
*    ┃   ┃ 代码无bug  
*    ┃   ┃  +         
*    ┃    ┗━━━┓ + +
*    ┃        ┣┓
*    ┃        ┏┛
*    ┗┓┓┏━┳┓┏┛ + + + +
*     ┃┫┫ ┃┫┫
*     ┗┻┛ ┗┻┛+ + + +
*
*/
+ +

草泥马二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
*
* ┏┓   ┏┓
* ┏┛┻━━━┛┻┓
* ┃       ┃
* ┃   ━   ┃
* ┃ >   < ┃
* ┃       ┃
* ┃... ⌒ ... ┃
* ┃       ┃
* ┗━┓   ┏━┛
* ┃   ┃ 
* ┃   ┃
* ┃   ┃
* ┃   ┃ 神兽保佑
* ┃   ┃ 代码无bug  
* ┃   ┃
* ┃   ┗━━━┓
* ┃       ┣┓
* ┃       ┏┛
* ┗┓┓┏━┳┓┏┛
* ┃┫┫ ┃┫┫
* ┗┻┛ ┗┻┛
*/
+ +

全键盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* ┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐
* │Esc│ │ F1│ F2│ F3│ F4│ │ F5│ F6│ F7│ F8│ │ F9│F10│F11│F12│ │P/S│S L│P/B│ ┌┐ ┌┐ ┌┐
* └───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘ └┘ └┘ └┘
* ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ ┌───┬───┬───┐ ┌───┬───┬───┬───┐
* │~ `│! 1│@ 2│# 3│$ 4│% 5│^ 6│& 7│* 8│( 9│) 0│_ -│+ =│ BacSp │ │Ins│Hom│PUp│ │N L│ / │ * │ - │
* ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ ├───┼───┼───┤ ├───┼───┼───┼───┤
* │ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{ [│} ]│ | \ │ │Del│End│PDn│ │ 7 │ 8 │ 9 │ │
* ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ └───┴───┴───┘ ├───┼───┼───┤ + │
* │ Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter │ │ 4 │ 5 │ 6 │ │
* ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ ┌───┐ ├───┼───┼───┼───┤
* │ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│ Shift │ │ ↑ │ │ 1 │ 2 │ 3 │ │
* ├─────┬──┴─┬─┴──┬┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ ┌───┼───┼───┐ ├───┴───┼───┤ E││
* │ Ctrl│ │Alt │ Space │ Alt│ │ │Ctrl│ │ ← │ ↓ │ → │ │ 0 │ . │←─┘│
* └─────┴────┴────┴───────────────────────┴────┴────┴────┴────┘ └───┴───┴───┘ └───────┴───┴───┘
*/
+ +

小键盘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* ┌─────────────────────────────────────────────────────────────┐
* │┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐│
* ││Esc│!1 │@2 │#3 │$4 │%5 │^6 │&7 │*8 │(9 │)0 │_- │+= │|\ │`~ ││
* │├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴───┤│
* ││ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{[ │}] │ BS ││
* │├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤│
* ││ Ctrl │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter ││
* │├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────┬───┤│
* ││ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│Shift │Fn ││
* │└─────┬──┴┬──┴──┬┴───┴───┴───┴───┴───┴──┬┴───┴┬──┴┬─────┴───┘│
* │ │Fn │ Alt │ Space │ Alt │Win│ HHKB │
* │ └───┴─────┴───────────────────────┴─────┴───┘ │
* └─────────────────────────────────────────────────────────────┘
*/
+ +

耶稣

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/*
* |~~~~~~~|
* | |
* | |
* | |
* | |
* | |
* |~.\\\_\~~~~~~~~~~~~~~xx~~~ ~~~~~~~~~~~~~~~~~~~~~/_//;~|
* | \ o \_ ,XXXXX), _..-~ o / |
* | ~~\ ~-. XXXXX`)))), _.--~~ .-~~~ |
* ~~~~~~~`\ ~\~~~XXX' _/ ';)) |~~~~~~..-~ _.-~ ~~~~~~~
* `\ ~~--`_\~\, ;;;\)__.---.~~~ _.-~
* ~-. `:;;/;; \ _..-~~
* ~-._ `'' /-~-~
* `\ / /
* | , | |
* | ' / |
* \/; |
* ;; |
* `; . |
* |~~~-----.....|
* | \ \
* | /\~~--...__ |
* (| `\ __-\|
* || \_ /~ |
* |) \~-' |
* | | \ '
* | | \ :
* \ | | |
* | ) ( )
* \ /; /\ |
* | |/ |
* | | |
* \ .' ||
* | | | |
* ( | | |
* | \ \ |
* || o `.)|
* |`\\) |
* | |
* | |
*/
+ +

美女

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
* .::::.
* .::::::::.
* :::::::::::
* ..:::::::::::'
* '::::::::::::'
* .::::::::::
* '::::::::::::::..
* ..::::::::::::.
* ``::::::::::::::::
* ::::``:::::::::' .:::.
* ::::' ':::::' .::::::::.
* .::::' :::: .:::::::'::::.
* .:::' ::::: .:::::::::' ':::::.
* .::' :::::.:::::::::' ':::::.
* .::' ::::::::::::::' ``::::.
* ...::: ::::::::::::' ``::.
* ````':. ':::::::::' ::::..
* '.:::::' ':'````..
*/
+ +

程序员之歌

1
2
3
4
5
6
7
8
9
10
11
12
/*
* 江城子 . 程序员之歌
*
* 十年生死两茫茫,写程序,到天亮。
* 千行代码,Bug何处藏。
* 纵使上线又怎样,朝令改,夕断肠。
*
* 领导每天新想法,天天改,日日忙。
* 相顾无言,惟有泪千行。
* 每晚灯火阑珊处,夜难寐,加班狂。
*
*/
+ +

龙图腾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* ......................................&&.........................
* ....................................&&&..........................
* .................................&&&&............................
* ...............................&&&&..............................
* .............................&&&&&&..............................
* ...........................&&&&&&....&&&..&&&&&&&&&&&&&&&........
* ..................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..............
* ................&...&&&&&&&&&&&&&&&&&&&&&&&&&&&&.................
* .......................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&.........
* ...................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...............
* ..................&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&............
* ...............&&&&&@ &&&&&&&&&&..&&&&&&&&&&&&&&&&&&&...........
* ..............&&&&&&&&&&&&&&&.&&....&&&&&&&&&&&&&..&&&&&.........
* ..........&&&&&&&&&&&&&&&&&&...&.....&&&&&&&&&&&&&...&&&&........
* ........&&&&&&&&&&&&&&&&&&&.........&&&&&&&&&&&&&&&....&&&.......
* .......&&&&&&&&.....................&&&&&&&&&&&&&&&&.....&&......
* ........&&&&&.....................&&&&&&&&&&&&&&&&&&.............
* ..........&...................&&&&&&&&&&&&&&&&&&&&&&&............
* ................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&............
* ..................&&&&&&&&&&&&&&&&&&&&&&&&&&&&..&&&&&............
* ..............&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&....&&&&&............
* ...........&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&......&&&&............
* .........&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&.........&&&&............
* .......&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...........&&&&............
* ......&&&&&&&&&&&&&&&&&&&...&&&&&&...............&&&.............
* .....&&&&&&&&&&&&&&&&............................&&..............
* ....&&&&&&&&&&&&&&&.................&&...........................
* ...&&&&&&&&&&&&&&&.....................&&&&......................
* ...&&&&&&&&&&.&&&........................&&&&&...................
* ..&&&&&&&&&&&..&&..........................&&&&&&&...............
* ..&&&&&&&&&&&&...&............&&&.....&&&&...&&&&&&&.............
* ..&&&&&&&&&&&&&.................&&&.....&&&&&&&&&&&&&&...........
* ..&&&&&&&&&&&&&&&&..............&&&&&&&&&&&&&&&&&&&&&&&&.........
* ..&&.&&&&&&&&&&&&&&&&&.........&&&&&&&&&&&&&&&&&&&&&&&&&&&.......
* ...&&..&&&&&&&&&&&&.........&&&&&&&&&&&&&&&&...&&&&&&&&&&&&......
* ....&..&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...........&&&&&&&&.....
* .......&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..............&&&&&&&....
* .......&&&&&.&&&&&&&&&&&&&&&&&&..&&&&&&&&...&..........&&&&&&....
* ........&&&.....&&&&&&&&&&&&&.....&&&&&&&&&&...........&..&&&&...
* .......&&&........&&&.&&&&&&&&&.....&&&&&.................&&&&...
* .......&&&...............&&&&&&&.......&&&&&&&&............&&&...
* ........&&...................&&&&&&.........................&&&..
* .........&.....................&&&&........................&&....
* ...............................&&&.......................&&......
* ................................&&......................&&.......
* .................................&&..............................
* ..................................&..............................
*/
+ +

蝙蝠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* ___====-_ _-====___
* _--^^^#####// \\#####^^^--_
* _-^##########// ( ) \\##########^-_
* -############// |\^^/| \\############-
* _/############// (@::@) \############\_
* /#############(( \\// ))#############\
* -###############\\ (oo) //###############-
* -#################\\ / VV \ //#################-
* -###################\\/ \//###################-
* _#/|##########/\######( /\ )######/\##########|\#_
* |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
* ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
* ` ` ` ` / | | | | \ ' ' ' '
* ( | | | | )
* __\ | | | | /__
* (vvv(VVV)(VVV)vvv)
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 神兽保佑 永无BUG
*/
+ +

喷火龙

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* __----~~~~~~~~~~~------___
* . . ~~//====...... __--~ ~~
* -. \_|// |||\\ ~~~~~~::::... /~
* ___-==_ _-~o~ \/ ||| \\ _/~~-
* __---~~~.==~||\=_ -_--~/_-~|- |\\ \\ _/~
* _-~~ .=~ | \\-_ '-~7 /- / || \ /
* .~ .~ | \\ -_ / /- / || \ /
* / ____ / | \\ ~-_/ /|- _/ .|| \ /
* |~~ ~~|--~~~~--_ \ ~==-/ | \~--===~~ .\
* ' ~-| /| |-~\~~ __--~~
* |-~~-_/ | | ~\_ _-~ /\
* / \ \__ \/~ \__
* _--~ _/ | .-~~____--~-/ ~~==.
* ((->/~ '.|||' -_| ~~-/ , . _||
* -_ ~\ ~~---l__i__i__i--~~_/
* _-~-__ ~) \--______________--~~
* //.-~~~-~_--~- |-------~~~~~~~~
* //.-~~~--\
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* 神兽保佑 永无BUG
*/
+ + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/17772.html b/posts/17772.html new file mode 100644 index 000000000..01b1712a0 --- /dev/null +++ b/posts/17772.html @@ -0,0 +1,196 @@ +常用网站及网址信息 | The Blog + + + + + + + + + + + + +

常用网站及网址信息

avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/18459.html b/posts/18459.html new file mode 100644 index 000000000..c116d5200 --- /dev/null +++ b/posts/18459.html @@ -0,0 +1,202 @@ +Git命令速查 | The Blog + + + + + + + + + + + + +

Git命令速查

常用命令

+

image-20230520173824247

+
+

安装教程

+

官网下载:https://git-scm.com/downloads

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#以下的操作在下载安装完毕之后进行
#1.鼠标在桌面右键 选择Git Bash Here 打开控制台

#2.配置用户名和邮箱
git config --global user.name "用户名" #随意
git config --global user.email "邮箱" #自己的邮箱

#3.配置SSH免密连接
#生成密钥
ssh-keygen -t rsa -C "在码云上注册的邮箱地址" #连续三次回车
#查看密钥并复制公钥的内容
cat ~/.ssh/id_rsa.pub

#4.将密钥的复制到码云的SSH公钥中
#4.1添加公钥 公钥名随意 公钥内容就是上面复制的内容

#5.测试
ssh -T git@gitee.com
+ +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/19270.html b/posts/19270.html new file mode 100644 index 000000000..bde4a7f2d --- /dev/null +++ b/posts/19270.html @@ -0,0 +1,229 @@ +gitHub上的优质课设项目 | The Blog + + + + + + + + + + + + +

gitHub上的优质课设项目

1.学生成绩管理系统

基于SpringBoot开发

+

包含:学生信息管理 班级信息管理 教师信息管理 课程信息管理 选课信息管理 考勤信息管理 请假信息管理 成绩信息管理 系统管理

+

项目代码截图:

+

image-20230311113425453

+

数据库:

+

image-20230311113536220

+

页面展示:

+

image-20230311113653032

+

image-20230311113841997

+

2.物流管理系统

基于spring boot的中小型仓库物流管理系统(springboot+mybatis-plus+shiro+mysql+layui前端框架)

+

可以使用shiro进行权限管理

+

主要功能实现:

+

1.基础管理:商品管理、客户管理、供应商管理;

+

2.物流管理:进货管理、发货管理、交付管理;

+

3.系统管理:菜单管理、部门管理;

+

4.人事管理:权限管理、角色管理、用户管理;

+

5.其他:

+

后台代码部分截图:

+

image-20230311114451668

+

数据库:

+

image-20230311114537761

+

项目展示:

+

image-20230311114653791

+

image-20230311114849263

+

3.酒店管理系统

一个精简的基于SSM框架开发的酒店后台管理系统

+

支持报表的导出和导入:EXCEL报表的导出和导入,项目的亮点

+

后台部分代码截图:

+

image-20230311115057351

+

数据库:

+

image-20230311115223537

+

页面展示:

+

image-20230311120902633

+

image-20230311121529641

+

注:

需要更多的优质项目,收藏此网站的链接,后续会更新更多的课设项目,敬请期待!

+
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/19306.html b/posts/19306.html new file mode 100644 index 000000000..4da73acae --- /dev/null +++ b/posts/19306.html @@ -0,0 +1,362 @@ +Docker容器化技术 | The Blog + + + + + + + + + + + + +

Docker容器化技术

img

+

1.Docker概念

• Docker 是一个开源的应用容器引擎

+

• 诞生于 2013 年初,基于 Go 语言实现, dotCloud 公司出品(后改名为Docker Inc)

+

• Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上。

+

• 容器是完全使用沙箱机制,相互隔离

+

• 容器性能开销极低。

+

• Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版)

+

2.安装Docker

1
2
3
4
5
6
7
8
9
10
# 1、yum 包更新到最新 
yum update
# 2、安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
yum install -y yum-utils device-mapper-persistent-data lvm2
# 3、 设置yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 4、 安装docker,出现输入的界面都按 y
yum install -y docker-ce
# 5、 查看docker版本,验证是否验证成功
docker -v
+ +

3.配置Docker镜像加速器

提升下载速度

+

第一步:登录阿里云获取镜像加速地址

每个人的加速器地址不一样

+

image-20230421103035776

+

第二步:Linux命令行执行如下命令

直接将阿里云上提供的命令直接粘贴到命令行即可

+

image-20230421103610184

+

image-20230421103755984

+

第三步:查看是否配置成功

1
2
# 查看刚才配置的文件是否存在
cat /etc/docker/daemon.json
+ +

出现下图所示的内容表示配置成功

+

image-20230421104103726

+

4.Docker相关命令

4.1 Docker进程相关的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 启动docker服务:
systemctl start docker

# 停止docker服务:
systemctl stop docker

#重启docker服务:
systemctl restart docker

# 查看docker服务状态:
systemctl status docker

# 设置开机启动docker服务:
systemctl enable docker

+ + + +

4.2 Docker镜像相关的命令

查看镜像对应版本号的网站: https://hub.docker.com/

+

image-20230421110034731

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 查看镜像: 查看本地所有的镜像
docker images
docker images –q # 查看所用镜像的id


# 搜索镜像:从网络中查找需要的镜像
docker search 镜像名称


# 拉取镜像:从Docker仓库下载镜像到本地,镜像名称格式为 名称:版本号,如果版本号不指定则是最新的版本。
# 如果不知道镜像版本,可以去docker hub 搜索对应镜像查看。
docker pull 镜像名称 # 默认下载的是latest版本的镜像
docker pull 镜像名称:版本号 #下载指定版本的镜像


# 删除镜像: 删除本地镜像
docker rmi 镜像id # 删除指定本地镜像
docker rmi 镜像名称:版本号 #删除指定版本的镜像
docker rmi `docker images -q` # 删除所有本地镜像
+ + + +

4.3 容器相关的命令

1
2
3
4
5
6
7
8
9
# 查看容器
docker ps # 查看正在运行的容器
docker ps –a # 查看所有容器

# 创建并启动容器
docker run 参数

#删除所有的容器
docker rm $(docker ps -a -q)
+ +

参数说明:
• -i:保持容器运行。通常与 -t 同时使用。加入it这两个参数后,容器创建后自动进入容器中,退出容器后,容器自动关闭。
• -t:为容器重新分配一个伪输入终端,通常与 -i 同时使用。
• -d:以守护(后台)模式运行容器。创建一个容器在后台运行,需要使用docker exec 进入容器。退出后,容器不会关闭。
• -it 创建的容器一般称为交互式容器,-id 创建的容器一般称为守护式容器
• –name:为创建的容器命名。

+
1
2
3
4
5
6
7
8
9
10
11
12
# 进入容器
docker exec 参数 # 退出容器,容器不会关闭
# 停止容器
docker stop 容器名称
# 启动容器
docker start 容器名称
# 删除容器:如果容器是运行状态则删除失败,需要停止容器才能删除
docker rm 容器名称
# 查看容器信息
docker inspect 容器名称
#查看容器的日志信息
docker logs 容器名称
+ + + +

5.Docker 应用部署

Docker中常见的应用安装部署,详细内容请参考下面文章

+

Linux中开发环境的搭建 | The Blog (gitee.io)

+
+ +

开发环境的搭建 | The Blog (gitee.io)

+

5.1、部署MySQL

+
    +
  1. 搜索mysql镜像
  2. +
+
1
docker search mysql
+ +
    +
  1. 拉取mysql镜像
  2. +
+
1
docker pull mysql:5.6
+ +
    +
  1. 创建容器,设置端口映射、目录映射
  2. +
+
1
2
3
# 在/root目录下创建mysql目录用于存储mysql数据信息
mkdir ~/mysql
cd ~/mysql
+ +
1
2
3
4
5
6
7
8
docker run -id \
-p 3307:3306 \
--name=c_mysql \
-v $PWD/conf:/etc/mysql/conf.d \
-v $PWD/logs:/logs \
-v $PWD/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:5.6
+ +
    +
  • 参数说明:
      +
    • -p 3307:3306:将容器的 3306 端口映射到宿主机的 3307 端口。
    • +
    • -v $PWD/conf:/etc/mysql/conf.d:将主机当前目录下的 conf/my.cnf 挂载到容器的 /etc/mysql/my.cnf。配置目录
    • +
    • -v $PWD/logs:/logs:将主机当前目录下的 logs 目录挂载到容器的 /logs。日志目录
    • +
    • -v $PWD/data:/var/lib/mysql :将主机当前目录下的data目录挂载到容器的 /var/lib/mysql 。数据目录
    • +
    • -e MYSQL_ROOT_PASSWORD=123456:初始化 root 用户的密码。
    • +
    +
  • +
+
    +
  1. 进入容器,操作mysql
  2. +
+
1
docker exec –it c_mysql /bin/bash
+ +
    +
  1. 使用外部机器连接容器中的mysql
  2. +
+

1573636765632

+

5.2、部署Tomcat

+
    +
  1. 搜索tomcat镜像
  2. +
+
1
docker search tomcat
+ +
    +
  1. 拉取tomcat镜像
  2. +
+
1
docker pull tomcat
+ +
    +
  1. 创建容器,设置端口映射、目录映射
  2. +
+
1
2
3
# 在/root目录下创建tomcat目录用于存储tomcat数据信息
mkdir ~/tomcat
cd ~/tomcat
+ +
1
2
3
4
docker run -id --name=c_tomcat \
-p 8080:8080 \
-v $PWD:/usr/local/tomcat/webapps \
tomcat
+ +
    +
  • 参数说明:

    +
      +
    • -p 8080:8080:将容器的8080端口映射到主机的8080端口

      +

      -v $PWD:/usr/local/tomcat/webapps:将主机中当前目录挂载到容器的webapps

      +
    • +
    +
  • +
+
    +
  1. 使用外部机器访问tomcat
  2. +
+

1573649804623

+

5.3 部署Nginx

+
    +
  1. 搜索nginx镜像
  2. +
+
1
docker search nginx
+ +
    +
  1. 拉取nginx镜像
  2. +
+
1
docker pull nginx
+ +
    +
  1. 创建容器,设置端口映射、目录映射
  2. +
+
1
2
3
4
5
6
7
# 在/root目录下创建nginx目录用于存储nginx数据信息
mkdir ~/nginx
cd ~/nginx
mkdir conf
cd conf
# 在~/nginx/conf/下创建nginx.conf文件,粘贴下面内容
vim nginx.conf
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
user  nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;


events {
worker_connections 1024;
}


http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;

include /etc/nginx/conf.d/*.conf;
}


+ + + + +
1
2
3
4
5
6
docker run -id --name=c_nginx \
-p 80:80 \
-v $PWD/conf/nginx.conf:/etc/nginx/nginx.conf \
-v $PWD/logs:/var/log/nginx \
-v $PWD/html:/usr/share/nginx/html \
nginx
+ +
    +
  • 参数说明:
      +
    • -p 80:80:将容器的 80端口映射到宿主机的 80 端口。
    • +
    • -v $PWD/conf/nginx.conf:/etc/nginx/nginx.conf:将主机当前目录下的 /conf/nginx.conf 挂载到容器的 :/etc/nginx/nginx.conf。配置目录
    • +
    • -v $PWD/logs:/var/log/nginx:将主机当前目录下的 logs 目录挂载到容器的/var/log/nginx。日志目录
    • +
    +
  • +
+
    +
  1. 使用外部机器访问nginx
  2. +
+

1573652396669

+

5.4 部署Redis

+
    +
  1. 搜索redis镜像
  2. +
+
1
docker search redis
+ +
    +
  1. 拉取redis镜像
  2. +
+
1
docker pull redis:5.0
+ +
    +
  1. 创建容器,设置端口映射
  2. +
+
1
docker run -id --name=c_redis -p 6379:6379 redis:5.0
+ +
    +
  1. 使用外部机器连接redis
  2. +
+
1
./redis-cli.exe -h 192.168.149.135 -p 6379
+ + + +
\ No newline at end of file diff --git a/posts/20683.html b/posts/20683.html new file mode 100644 index 000000000..efe1f7708 --- /dev/null +++ b/posts/20683.html @@ -0,0 +1,244 @@ +Linux中开发环境的搭建 | The Blog + + + + + + + + + + + + + +

Linux中开发环境的搭建

一.Linux操作系统的安装与配置

软件:VMware 、Linux镜像文件
VMWare虚拟机安装Linux教程 | The Blog (gitee.io)

+

二.配置Java环境

2.1.删除自带的JDK

+
1
2
3
4
5
6
#查看系统是否有java环境
java -version
# 查找Linux中已有的java环境的相关文件
rpm -qa |grep java
# 执行命令 删除上面的java文件
rpm -e --nodeps +上面查找出来的文件
+ +

2.2.安装JDK

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#创建一个文件夹,将JDK8上传到这个文件夹中去
mkdir /opt/jdk
# 解压上传的压缩包 解压后得到一个名为jdk1.8.0_261的文件夹
tar -zvxf jdk-8u261-linux-x64.tar.gz
# 创建一个目录,作为java的安装目录
mkdir /usr/local/java
#将解压后的文件移动到这个安装目录中去
mv jdk1.8.0_261/ /usr/local/java/
#配置环境变量
#进入环境变量的配置文件中
vim /etc/profile
#在文件的最后添加如下的配置
export JAVA_HOME=/usr/local/java/jdk1.8.0_261
export PATH=$JAVA_HOME/bin:$PATH
#刷新配置文件,让新的环境变量生效
source /etc/profile
+ + + +

三.使用Docker安装常用软件

Docker容器化技术 | The Blog (gitee.io)

+

3.1 Docker的安装

3.1.1 安装Docker

+
1
2
3
4
5
6
7
8
9
10
# 1、yum 包更新到最新 
yum update
# 2、安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
yum install -y yum-utils device-mapper-persistent-data lvm2
# 3、 设置yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 4、 安装docker,出现输入的界面都按 y
yum install -y docker-ce
# 5、 查看docker版本,验证是否验证成功
docker -v
+ +

3.1.2 配置镜像加速服务

+
1
2
3
4
5
6
7
8
9
10
11
12
# 1.创建相关的目录
sudo mkdir -p /etc/docker
# 2,加入配置
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://cagfgua5.mirror.aliyuncs.com"]
}
EOF
# 3.重新加载,让配置文件生效
sudo systemctl daemon-reload
# 4.重新启动Docker服务
sudo systemctl restart docker
+ +

3.1.3 启动Docker服务

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 启动docker服务:
systemctl start docker

# 停止docker服务:
systemctl stop docker

#重启docker服务:
systemctl restart docker

# 查看docker服务状态:
systemctl status docker

# 设置开机启动docker服务:
systemctl enable docker
+ +

3.2 使用Docker安装Mysql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#下载mysql的镜像文件 这里以下载mysql5.7为例
docker pull mysql:5.7

#查看是否下载成功
docker images

#使用MySQL的镜像启动一个容器
docker run -p 3306:3306 --name mysql \
-v /mydata/mysql/log:/var/log/mysql \
-v /mydata/mysql/data:/var/lib/mysql \
-v /mydata/mysql/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=root \
-d mysql:5.7
#参数说明
# -p 3306:3306:将容器的 3306 端口映射到主机的 3306 端口
#-v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机
#-v /mydata/mysql/log:/var/log/mysql:将日志文件夹挂载到主机
#-v /mydata/mysql/data:/var/lib/mysql/:将配置文件夹挂载到主机
#-e MYSQL_ROOT_PASSWORD=root:初始化 root 用户的密码

#查看是否运行成功 这时使用Navicat就可以连接上了 连接不上 关闭防火墙
docker ps #查看正在运行的容器

#进入容器的内部
docker exec -it mysql /bin/bash

#查看容器内部的目录结构 我们可以看到容器的内部是一个小小的linux系统
ls / #注意中间有一个空格

#查看与mysql相关的目录
whereis mysql

#退出容器
exit;

#在容器外部查看mysql挂载在本地的三个文件
#容器内部文件的变化会同步在这三个文件中 conf data log
#外部修改 也会同步到内部
cd /mydata/mysql
ls

#创建一个mysql的配置文件设置字符编码 将下面的内容粘贴到配置文件中
#1.创建配置文件
vi /mydata/mysql/conf/my.cnf
#2.将以下的内容粘贴到配置文件配置文件内容
[client]
default-character-set=utf8
[mysql]
default-character-set=utf8
[mysqld]
init_connect='SET collation_connection = utf8_unicode_ci'
init_connect='SET NAMES utf8'
character-set-server=utf8
collation-server=utf8_unicode_ci
skip-character-set-client-handshake
skip-name-resolve
#3.重启MySQl的服务 让配置文件生效 这个配置文件也会同步到容器内部
docker restart mysql

#设置开机自启动
docker update mysql --restart=always
+ +

3.3 使用Docker安装Redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#下载redis的镜像文件 直接下载最新的
docker pull redis

#创建redis挂载在宿主机的配置文件和目录
mkdir -p /mydata/redis/conf #创建目录
touch /mydata/redis/conf/redis.conf #创建配置文件

#创建并启动容器 这是以配置文件的方式启动 \前面有一个空格
docker run -p 6379:6379 --name redis \
-v /mydata/redis/data:/data \
-v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
-d redis redis-server /etc/redis/redis.conf

#检查容器的运行情况
docker ps

#测试客户端的使用
docker exec -it redis redis-cli

#可以在redis客户端中使用如下命令
127.0.0.1:6379> set name tom #存值
OK
127.0.0.1:6379> get name #取值
"tom"

#redis中的数据不能持久化 关机重启之后数据会丢失
#通过以下的配置 实现redis的持久化操作 AOF的持久化
vi /mydata/redis/conf/redis.conf #修改之前创建的配置文件
#添加如下配置
appendonly yes

#重启redis 让配置生效
docker restart redis

#设置开机自启动
docker update redis --restart=always
+ +

3.4 使用Docker安装ElasticSearch

3.4.1 安装ElasticSearch

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 拉取ElasticSearch
docker pull elasticsearch:7.4.2

#ES的配置文件存放的位置
mkdir -p /mydata/elasticsearch/config

#ES相关的数据
mkdir -p /mydata/elasticsearch/data

#"http.host: 0.0.0.0"(可以被任何的机器访问)的配置写入elasticsearch.yml中(注意冒号后面的空格)
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

#设置文件的权限
# 递归更改权限,es需要访问
chmod -R 777 /mydata/elasticsearch/

#运行容器的命令 9200端口用于发送请求使用 9300端口用于集群中节点中的通信使用
#单节点运行
#初始占用64m,最大占用5m(不指定,ES会占用所有的内存)
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

# 设置开机启动elasticsearch
docker update elasticsearch --restart=always

#访问端口 测试是否安装成功
http://虚拟机的ip:9200/
+ +

安装成功之后:

+

image-20230628191936254

+

3.4.2 安装Kibana(可视化界面)

+
1
2
3
4
5
6
7
8
#拉取Kibana
docker pull kibana:7.4.2
# 这里-e是自己的elasticsearch服务地址(这里的地址一定要改为自己虚拟机的地址)
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.195.100:9200 -p 5601:5601 -d kibana:7.4.2
# 设置开机启动kibana
docker update kibana --restart=always
#访问对应的网址查看服务
http://虚拟机的ip:5601
+ +

安装成功之后

+

image-20230701104616032

+

3.4.3 安装IK分词器

+

安装地址: https://github.com/medcl/elasticsearch-analysis-ik

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#切换到es挂载在注解的plugins目录
cd /mydata/elasticsearch/plugins/
#安装wget
yum install wget
#下载ik分词器的压缩包(下载的速度不是很快(5分钟左右),建议本地下载好了之后,上传上去)
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
#进入容器内部查看一下文件是否同步到了容器的内部
docker exec -it 容器Id /bin/bash
#切换到plugins目录中去,这时目录中也存在elasticsearch-analysis-ik-7.4.2.zip这个压缩包
cd plugins/ #使用ls查看一下
#退出容器
exit
#在主机下解压elasticsearch-analysis-ik-7.4.2.zip这个压缩包
#zip的解压需要使用unzip解压命令 这里我们可以直接在本地解压一下 上传到plugins目录(解压的文件名为ik)
#安装unzip
yum install -y unzip
#查看ik分词器是否安装成功
docker exec -it 容器Id /bin/bash
#切换到bin目录
cd bin/
#执行下面的命令,有ik显示即为安装成功
elasticsearch-plugin list
#退出容器,重启一下
docker restart elasticsearch
+ +

3.5 使用Docker安装nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#创建nginx容器挂载的目录
mkdir /mydata/nginx/conf
#拉取nginx的镜像
docker pull nginx:1.10
#随便启动一个nginx实例,为了复制其配置
docker run -p 80:80 --name nginx -d nginx:1.10
#将容器内的配置文件拷贝到conf目录(注意这里有个点和空格)
#注意在nginx的目录下操作,(成功之后/mydata/nginx/conf目录下会有相应的配置文件和目录)
docker container cp nginx:/etc/nginx /mydata/nginx/conf
#停掉并删除之前的启动的nginx
docker stop nginx
docker rm nginx
#正式的启动容器
docker run -p 80:80 --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10
# 设置开机启动nginx
docker update nginx --restart=always
+ +

3.6 使用Docker安装RabbitMQ

1
2
3
4
5
6
7
8
9
10
11
12
13
#直接执行运行rabbitMQ容器的命令,没有镜像会自动下载
docker run -d --name rabbitmq \
-p 5671:5671 \
-p 5672:5672 \
-p 4369:4369 \
-p 25672:25672 \
-p 15671:15671 \
-p 15672:15672 \
rabbitmq:management
# 设置开机启动rabbitmq
docker update rabbitmq --restart=always
#测试访问
虚拟机的ip:15672
+ +

账号:guest

+

密码:guest

+

image-20230717123217404

+

3.7 使用docker安装nacos

1
2
3
4
5
#拉取镜像
docker pull nacos/nacos-server:1.2.0
#创建容器并运行
#MODE=standalone 单机版
docker run --env MODE=standalone --name nacos --restart=always -d -p 8848:8848 nacos/nacos-server:1.2.0
+ +

测试访问:http://虚拟机ip:8848/nacos

+

image-20230811105925843

+
\ No newline at end of file diff --git a/posts/21883.html b/posts/21883.html new file mode 100644 index 000000000..733093edf --- /dev/null +++ b/posts/21883.html @@ -0,0 +1,211 @@ +Linux设置静态IP | The Blog + + + + + + + + + + + + +

Linux设置静态IP

1.查看IP的状态

1
2
#查看是否为静态ip
ip addr
+ +

如图所示的是动态IP,我们要配置静态IP

+

image-20230511230406172

+

2.设置网络

2.1 进入配置文件对网络进行配置

1.修改配置文件中的配置

+
1
2
#进入配置文件的命令
vim /etc/sysconfig/network-scripts/ifcfg-ens33
+ +

image-20230511233324998

+

2.重启网络

+
1
2
3
4
#重启网络的配置
systemctl restart network
#查看设置的IP情况
ifconfig
+ +

image-20230511233838595

+

在主机上使用ping命令ping 刚才的设置的静态IP也可以ping通

+

image-20230511234154523

+

虚拟机ping外部也可以ping通

+

image-20230511235621228

+
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/22202.html b/posts/22202.html new file mode 100644 index 000000000..4b8dacf1e --- /dev/null +++ b/posts/22202.html @@ -0,0 +1,245 @@ +细节知识 | The Blog + + + + + + + + + + + + +

细节知识

Linux开启/关闭防火墙的命令

+
1
2
3
4
5
6
7
8
9
10
# 检查防火墙状态:
systemctl status firewalld
# 开启防火墙
systemctl start firewalld
# 关闭防火墙(有时间限制)
systemctl stop firewalld
# 设置开机禁用防火墙(先执行上面一条命令之后执行此命令,即可永久关闭)
systemctl disable firewalld.service
# 设置开机启用防火墙
systemctl enable firewalld.service
+ + + +

windows查看端口占用情况,杀死端口的命令

+
1
2
3
4
# 查询被占用的端口号的信息
netstat -ano | findstr "8080"
#根据端口号的PID杀死该端口的进程 其中 17156 是8080端口的PID值
taskkill /pid 17156 /f
+ + + +

Linux开启关闭键盘背光灯

+
1
2
3
4
5
#不是永久有效的
# 开启键盘背光灯
xset led named "Scroll Lock"
#关闭键盘背光灯
xset -led named "Scroll Lock"
+ + + +

关闭Mysql的服务

+
1
2
3
4
5
6
7
8
9
10
#查找是否安装了mysql的服务
rpm -qa | grep -i mysql
#查看mysql服务的状态
service mysqld status
#关闭mysql的服务
service mysqld stop
#重启的命令
service mysqld restart
#关闭开机自启动
systemctl disable mysqld
+ + + +

前端向后端传递对象数据

+

传递普通对象参数的写法

+

params: searchObj

+
1
2
3
4
5
6
7
getPageList(current,limit,searchObj){
return request({
url: `${api_name}/${current}/${limit}`,
method: 'get',
params: searchObj //使用这种方式进行传递
})
}
+ +
1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/{page}/{limit}")
public Result pageQueryRole(@PathVariable Long page,
@PathVariable Integer limit,
SysRoleQueryVo sysRoleQueryVo) { //普通的对象进行接收 没有添加注解

Page<SysRole> pageParam = new Page<>(page, limit);
LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(!StringUtils.isEmpty(sysRoleQueryVo.getRoleName()),SysRole::getRoleName, sysRoleQueryVo.getRoleName());
Page<SysRole> sysRolePage = sysRoleService.page(pageParam, queryWrapper);
return Result.ok(sysRolePage);
}
+ +

json格式的传递

+

data: searchObj

+
1
2
3
4
5
6
7
add(sysRole){
return request({
url: `${api_name}/save`,
method: 'post',
data: sysRole //使用这种方式进行传递
})
}
+ +
1
2
3
4
5
@PostMapping("/save")
public Result save(@RequestBody SysRole sysRole){ //JSON格式对象的传递 需要使用注解@RequestBody
boolean isSave = sysRoleService.save(sysRole);
return isSave? Result.ok() : Result.fail();
}
+ + + +

调整日志的级别

+
1
2
3
4
logging:
level:
# 注意这里包的路径,要根据实际情况t
com.atguigu.gulimall: debug
+ + + +

@JsonInclude注解

+
1
2
@JsonInclude(JsonInclude.Include.NON_EMPTY)//这里是当children为空的时候,向前端传递数据就不带这个
private List<CategoryEntity> children;
+ + + +

node相关的问题

+
1
2
3
4
5
6
7
8
#查看node的版本
node -v
#查看npm的版本
npm -v
#查看npm使用的镜像源(npm镜像源)
npm get registry
#设置淘宝镜像源
npm config set registry http://registry.npm.taobao.org
+ + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/22654.html b/posts/22654.html new file mode 100644 index 000000000..08a2dc64a --- /dev/null +++ b/posts/22654.html @@ -0,0 +1,403 @@ +ElasticSearch | The Blog + + + + + + + + + + + + +

ElasticSearch

官网: https://www.elastic.co/cn/elasticsearch/

+

简介

​ Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。作为 Elastic Stack 的核心,Elasticsearch 会集中存储您的数据,让您飞快完成搜索,微调相关性,进行强大的分析,并轻松缩放规模。

+

一.基本概念

1.Index(索引)

+

动词:相当于mysql的insert

+

名词:相当于mysql的database

+

2.Type(类型)

+

在index中可以定义一个或者多个类型

+

类似于mysql中的Table;每一种类型的数据放在一起

+

3.Document(文档)

+

保存在某个索引(index)下,某种类型(Type)的一个数据(Document),文档是JSON格式的,

+

Document就是像mysql中的某个Table里面的内容。

+

4.倒排索引

+

image-20230628180000365

+

二.Docker安装ElasticSearch

1.安装ElasticSearch(存储和检索数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 拉取ElasticSearch
docker pull elasticsearch:7.4.2

#ES的配置文件存放的位置
mkdir -p /mydata/elasticsearch/config

#ES相关的数据
mkdir -p /mydata/elasticsearch/data

#"http.host: 0.0.0.0"(可以被任何的机器访问)的配置写入elasticsearch.yml中(注意冒号后面的空格)
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

#设置文件的权限
# 递归更改权限,es需要访问
chmod -R 777 /mydata/elasticsearch/

#运行容器的命令 9200端口用于发送请求使用 9300端口用于集群中节点中的通信使用
#单节点运行
#初始占用64m,最大占用512m(不指定,ES会占用所有的内存)
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

# 设置开机启动elasticsearch
docker update elasticsearch --restart=always

#访问端口 测试是否安装成功
http://虚拟机的ip:9200/
+ +

image-20230628191936254

+

2.安装Kibana(可视化检索数据)

1
2
3
4
5
6
7
8
#拉取Kibana
docker pull kibana:7.4.2
# 这里-e是自己的elasticsearch服务地址(这里的地址一定要改为自己虚拟机的地址)
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.195.100:9200 -p 5601:5601 -d kibana:7.4.2
# 设置开机启动kibana
docker update kibana --restart=always
#访问对应的网址查看服务
http://虚拟机的ip:5601
+ +

image-20230701104616032

+

三.初步检索

1._Cat

GET/_cat/nodes 查看所有节点

+

GET/_cat/health 查看es的健康状态

+

GET/_cat/master 查看主节点

+

GET/_cat/indices 查看所有索引

+

2.索引文档(保存)

保存操作可以发送PUT或者POST请求

+

put需要在路径上指定id

+

post请求可以不指定id,自动生成一个唯一标识

+

例:PUT customer/external/1 在customer索引下的external类型下保存1号数据为

+
1
2
3
4
5
-> PUT customer/external/1
//请求体
{
"name":"John Doe"
}
+ +

image-20230701144040577

+

返回的数据解析

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//_开头的数据称为元数据
{
"_index": "customer",//数据存在的数据库
"_type": "external",//保存数据的类型
"_id": "1",//数据的id
"_version": 1,//数据的版本
"result": "created",//保存为"created",修改为"updated",发送多次为更新的操作
"_shards": { //分片
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
+ +

3.查询文档

1
-> GET customer/external/1
+ +

image-20230701145518348

+

返回数据解析

+
1
2
3
4
5
6
7
8
9
10
11
12
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 1,
"_seq_no": 0,//并发控制的字段,每次更新就会+1,用来做乐观锁
"_primary_term": 1,//同上,主分片重新分配,如重启,就会有变化
"found": true,//是否找到
"_source": {//数据真正的内容
"name": "John Doe"
}
}
+ +

4.更新文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST customer/external/1/_update  //对比原先的数据,和原先的一样_version和_seq_no不会发生改变
{
"doc":{
"name": "John Doe"
}
}
或者
POST customer/external/1 //和之前的数据一样的话也会执行更新操作
{
"name": "John Doe"
}
或者
PUT customer/external/1
{
"name": "John Doe"
}
+ +

​ 使用POST customer/external/1/_update 的方式进行更新会对比原先的数据,和原先的一样version和_seq_no不会发生改变,下面是我们使用同样的数据发送两次更新请求,第二次的result显示”noop”,没有进行操作

+

image-20230701151445190

+

5.删除文档|索引

1
2
DELETE customer/external/1
DELETE customer
+ +

删除文档

+

image-20230701152501989

+

删除索引

+

image-20230701152648756

+

6.bulk批量API

Kibana的使用

+

image-20230701153754438

+

批量保存数据

+
1
2
3
4
5
6
7
8
POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"a"}
{"index":{"_id":"2"}}
{"name":"b"}
新增两条数据
新增id为1的 对应 name为a
新增Id为2的 对应 name为b
+ +

image-20230701154217181

+

复杂批量操作

+
1
2
3
4
5
6
7
8
POST /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"my first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"my second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"my updated blog post"}}
+ +

image-20230701154839574

+

7.样本测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
POST /bank/account/_bulk
{"index":{"_id":"1"}}
{"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
{"index":{"_id":"6"}}
{"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"}
{"index":{"_id":"13"}}
{"account_number":13,"balance":32838,"firstname":"Nanette","lastname":"Bates","age":28,"gender":"F","address":"789 Madison Street","employer":"Quility","email":"nanettebates@quility.com","city":"Nogal","state":"VA"}
{"index":{"_id":"18"}}
{"account_number":18,"balance":4180,"firstname":"Dale","lastname":"Adams","age":33,"gender":"M","address":"467 Hutchinson Court","employer":"Boink","email":"daleadams@boink.com","city":"Orick","state":"MD"}
{"index":{"_id":"20"}}
{"account_number":20,"balance":16418,"firstname":"Elinor","lastname":"Ratliff","age":36,"gender":"M","address":"282 Kings Place","employer":"Scentric","email":"elinorratliff@scentric.com","city":"Ribera","state":"WA"}
{"index":{"_id":"25"}}
{"account_number":25,"balance":40540,"firstname":"Virginia","lastname":"Ayala","age":39,"gender":"F","address":"171 Putnam Avenue","employer":"Filodyne","email":"virginiaayala@filodyne.com","city":"Nicholson","state":"PA"}
{"index":{"_id":"32"}}
{"account_number":32,"balance":48086,"firstname":"Dillard","lastname":"Mcpherson","age":34,"gender":"F","address":"702 Quentin Street","employer":"Quailcom","email":"dillardmcpherson@quailcom.com","city":"Veguita","state":"IN"}
{"index":{"_id":"37"}}
{"account_number":37,"balance":18612,"firstname":"Mcgee","lastname":"Mooney","age":39,"gender":"M","address":"826 Fillmore Place","employer":"Reversus","email":"mcgeemooney@reversus.com","city":"Tooleville","state":"OK"}
{"index":{"_id":"44"}}
{"account_number":44,"balance":34487,"firstname":"Aurelia","lastname":"Harding","age":37,"gender":"M","address":"502 Baycliff Terrace","employer":"Orbalix","email":"aureliaharding@orbalix.com","city":"Yardville","state":"DE"}
{"index":{"_id":"49"}}
{"account_number":49,"balance":29104,"firstname":"Fulton","lastname":"Holt","age":23,"gender":"F","address":"451 Humboldt Street","employer":"Anocha","email":"fultonholt@anocha.com","city":"Sunriver","state":"RI"}
{"index":{"_id":"51"}}
{"account_number":51,"balance":14097,"firstname":"Burton","lastname":"Meyers","age":31,"gender":"F","address":"334 River Street","employer":"Bezal","email":"burtonmeyers@bezal.com","city":"Jacksonburg","state":"MO"}
{"index":{"_id":"56"}}
{"account_number":56,"balance":14992,"firstname":"Josie","lastname":"Nelson","age":32,"gender":"M","address":"857 Tabor Court","employer":"Emtrac","email":"josienelson@emtrac.com","city":"Sunnyside","state":"UT"}
{"index":{"_id":"63"}}
{"account_number":63,"balance":6077,"firstname":"Hughes","lastname":"Owens","age":30,"gender":"F","address":"510 Sedgwick Street","employer":"Valpreal","email":"hughesowens@valpreal.com","city":"Guilford","state":"KS"}
{"index":{"_id":"68"}}
{"account_number":68,"balance":44214,"firstname":"Hall","lastname":"Key","age":25,"gender":"F","address":"927 Bay Parkway","employer":"Eventex","email":"hallkey@eventex.com","city":"Shawmut","state":"CA"}
{"index":{"_id":"70"}}
{"account_number":70,"balance":38172,"firstname":"Deidre","lastname":"Thompson","age":33,"gender":"F","address":"685 School Lane","employer":"Netplode","email":"deidrethompson@netplode.com","city":"Chestnut","state":"GA"}
{"index":{"_id":"75"}}
{"account_number":75,"balance":40500,"firstname":"Sandoval","lastname":"Kramer","age":22,"gender":"F","address":"166 Irvington Place","employer":"Overfork","email":"sandovalkramer@overfork.com","city":"Limestone","state":"NH"}
{"index":{"_id":"82"}}
{"account_number":82,"balance":41412,"firstname":"Concetta","lastname":"Barnes","age":39,"gender":"F","address":"195 Bayview Place","employer":"Fitcore","email":"concettabarnes@fitcore.com","city":"Summerfield","state":"NC"}
{"index":{"_id":"87"}}
{"account_number":87,"balance":1133,"firstname":"Hewitt","lastname":"Kidd","age":22,"gender":"M","address":"446 Halleck Street","employer":"Isologics","email":"hewittkidd@isologics.com","city":"Coalmont","state":"ME"}
{"index":{"_id":"94"}}
{"account_number":94,"balance":41060,"firstname":"Brittany","lastname":"Cabrera","age":30,"gender":"F","address":"183 Kathleen Court","employer":"Mixers","email":"brittanycabrera@mixers.com","city":"Cornucopia","state":"AZ"}
{"index":{"_id":"99"}}
{"account_number":99,"balance":47159,"firstname":"Ratliff","lastname":"Heath","age":39,"gender":"F","address":"806 Rockwell Place","employer":"Zappix","email":"ratliffheath@zappix.com","city":"Shaft","state":"ND"}
{"index":{"_id":"102"}}
{"account_number":102,"balance":29712,"firstname":"Dena","lastname":"Olson","age":27,"gender":"F","address":"759 Newkirk Avenue","employer":"Hinway","email":"denaolson@hinway.com","city":"Choctaw","state":"NJ"}
{"index":{"_id":"107"}}
{"account_number":107,"balance":48844,"firstname":"Randi","lastname":"Rich","age":28,"gender":"M","address":"694 Jefferson Street","employer":"Netplax","email":"randirich@netplax.com","city":"Bellfountain","state":"SC"}
{"index":{"_id":"114"}}
{"account_number":114,"balance":43045,"firstname":"Josephine","lastname":"Joseph","age":31,"gender":"F","address":"451 Oriental Court","employer":"Turnabout","email":"josephinejoseph@turnabout.com","city":"Sedley","state":"AL"}
{"index":{"_id":"119"}}
{"account_number":119,"balance":49222,"firstname":"Laverne","lastname":"Johnson","age":28,"gender":"F","address":"302 Howard Place","employer":"Senmei","email":"lavernejohnson@senmei.com","city":"Herlong","state":"DC"}
{"index":{"_id":"121"}}
{"account_number":121,"balance":19594,"firstname":"Acevedo","lastname":"Dorsey","age":32,"gender":"M","address":"479 Nova Court","employer":"Netropic","email":"acevedodorsey@netropic.com","city":"Islandia","state":"CT"}
{"index":{"_id":"126"}}
{"account_number":126,"balance":3607,"firstname":"Effie","lastname":"Gates","age":39,"gender":"F","address":"620 National Drive","employer":"Digitalus","email":"effiegates@digitalus.com","city":"Blodgett","state":"MD"}
{"index":{"_id":"133"}}
{"account_number":133,"balance":26135,"firstname":"Deena","lastname":"Richmond","age":36,"gender":"F","address":"646 Underhill Avenue","employer":"Sunclipse","email":"deenarichmond@sunclipse.com","city":"Austinburg","state":"SC"}
{"index":{"_id":"138"}}
{"account_number":138,"balance":9006,"firstname":"Daniel","lastname":"Arnold","age":39,"gender":"F","address":"422 Malbone Street","employer":"Ecstasia","email":"danielarnold@ecstasia.com","city":"Gardiner","state":"MO"}
{"index":{"_id":"140"}}
{"account_number":140,"balance":26696,"firstname":"Cotton","lastname":"Christensen","age":32,"gender":"M","address":"878 Schermerhorn Street","employer":"Prowaste","email":"cottonchristensen@prowaste.com","city":"Mayfair","state":"LA"}
{"index":{"_id":"145"}}
{"account_number":145,"balance":47406,"firstname":"Rowena","lastname":"Wilkinson","age":32,"gender":"M","address":"891 Elton Street","employer":"Asimiline","email":"rowenawilkinson@asimiline.com","city":"Ripley","state":"NH"}
{"index":{"_id":"152"}}
{"account_number":152,"balance":8088,"firstname":"Wolfe","lastname":"Rocha","age":21,"gender":"M","address":"457 Guernsey Street","employer":"Hivedom","email":"wolferocha@hivedom.com","city":"Adelino","state":"MS"}
{"index":{"_id":"157"}}
{"account_number":157,"balance":39868,"firstname":"Claudia","lastname":"Terry","age":20,"gender":"F","address":"132 Gunnison Court","employer":"Lumbrex","email":"claudiaterry@lumbrex.com","city":"Castleton","state":"MD"}
{"index":{"_id":"164"}}
{"account_number":164,"balance":9101,"firstname":"Cummings","lastname":"Little","age":26,"gender":"F","address":"308 Schaefer Street","employer":"Comtrak","email":"cummingslittle@comtrak.com","city":"Chaparrito","state":"WI"}
{"index":{"_id":"169"}}
{"account_number":169,"balance":45953,"firstname":"Hollie","lastname":"Osborn","age":34,"gender":"M","address":"671 Seaview Court","employer":"Musaphics","email":"hollieosborn@musaphics.com","city":"Hanover","state":"GA"}
{"index":{"_id":"171"}}
{"account_number":171,"balance":7091,"firstname":"Nelda","lastname":"Hopper","age":39,"gender":"M","address":"742 Prospect Place","employer":"Equicom","email":"neldahopper@equicom.com","city":"Finderne","state":"SC"}
{"index":{"_id":"176"}}
{"account_number":176,"balance":18607,"firstname":"Kemp","lastname":"Walters","age":28,"gender":"F","address":"906 Howard Avenue","employer":"Eyewax","email":"kempwalters@eyewax.com","city":"Why","state":"KY"}
{"index":{"_id":"183"}}
{"account_number":183,"balance":14223,"firstname":"Hudson","lastname":"English","age":26,"gender":"F","address":"823 Herkimer Place","employer":"Xinware","email":"hudsonenglish@xinware.com","city":"Robbins","state":"ND"}
{"index":{"_id":"188"}}
{"account_number":188,"balance":41504,"firstname":"Tia","lastname":"Miranda","age":24,"gender":"F","address":"583 Ainslie Street","employer":"Jasper","email":"tiamiranda@jasper.com","city":"Summerset","state":"UT"}
{"index":{"_id":"190"}}
{"account_number":190,"balance":3150,"firstname":"Blake","lastname":"Davidson","age":30,"gender":"F","address":"636 Diamond Street","employer":"Quantasis","email":"blakedavidson@quantasis.com","city":"Crumpler","state":"KY"}
{"index":{"_id":"195"}}
{"account_number":195,"balance":5025,"firstname":"Kaye","lastname":"Gibson","age":31,"gender":"M","address":"955 Hopkins Street","employer":"Zork","email":"kayegibson@zork.com","city":"Ola","state":"WY"}
{"index":{"_id":"203"}}
{"account_number":203,"balance":21890,"firstname":"Eve","lastname":"Wyatt","age":33,"gender":"M","address":"435 Furman Street","employer":"Assitia","email":"evewyatt@assitia.com","city":"Jamestown","state":"MN"}
{"index":{"_id":"208"}}
{"account_number":208,"balance":40760,"firstname":"Garcia","lastname":"Hess","age":26,"gender":"F","address":"810 Nostrand Avenue","employer":"Quiltigen","email":"garciahess@quiltigen.com","city":"Brooktrails","state":"GA"}
{"index":{"_id":"210"}}
{"account_number":210,"balance":33946,"firstname":"Cherry","lastname":"Carey","age":24,"gender":"M","address":"539 Tiffany Place","employer":"Martgo","email":"cherrycarey@martgo.com","city":"Fairacres","state":"AK"}
{"index":{"_id":"215"}}
{"account_number":215,"balance":37427,"firstname":"Copeland","lastname":"Solomon","age":20,"gender":"M","address":"741 McDonald Avenue","employer":"Recognia","email":"copelandsolomon@recognia.com","city":"Edmund","state":"ME"}
{"index":{"_id":"222"}}
{"account_number":222,"balance":14764,"firstname":"Rachelle","lastname":"Rice","age":36,"gender":"M","address":"333 Narrows Avenue","employer":"Enaut","email":"rachellerice@enaut.com","city":"Wright","state":"AZ"}
{"index":{"_id":"227"}}
{"account_number":227,"balance":19780,"firstname":"Coleman","lastname":"Berg","age":22,"gender":"M","address":"776 Little Street","employer":"Exoteric","email":"colemanberg@exoteric.com","city":"Eagleville","state":"WV"}
{"index":{"_id":"234"}}
{"account_number":234,"balance":44207,"firstname":"Betty","lastname":"Hall","age":37,"gender":"F","address":"709 Garfield Place","employer":"Miraclis","email":"bettyhall@miraclis.com","city":"Bendon","state":"NY"}
{"index":{"_id":"239"}}
{"account_number":239,"balance":25719,"firstname":"Chang","lastname":"Boyer","age":36,"gender":"M","address":"895 Brigham Street","employer":"Qaboos","email":"changboyer@qaboos.com","city":"Belgreen","state":"NH"}
{"index":{"_id":"241"}}
{"account_number":241,"balance":25379,"firstname":"Schroeder","lastname":"Harrington","age":26,"gender":"M","address":"610 Tapscott Avenue","employer":"Otherway","email":"schroederharrington@otherway.com","city":"Ebro","state":"TX"}
{"index":{"_id":"246"}}
{"account_number":246,"balance":28405,"firstname":"Katheryn","lastname":"Foster","age":21,"gender":"F","address":"259 Kane Street","employer":"Quantalia","email":"katherynfoster@quantalia.com","city":"Bath","state":"TX"}
{"index":{"_id":"253"}}
{"account_number":253,"balance":20240,"firstname":"Melissa","lastname":"Gould","age":31,"gender":"M","address":"440 Fuller Place","employer":"Buzzopia","email":"melissagould@buzzopia.com","city":"Lumberton","state":"MD"}
{"index":{"_id":"258"}}
{"account_number":258,"balance":5712,"firstname":"Lindsey","lastname":"Hawkins","age":37,"gender":"M","address":"706 Frost Street","employer":"Enormo","email":"lindseyhawkins@enormo.com","city":"Gardners","state":"AK"}
{"index":{"_id":"260"}}
{"account_number":260,"balance":2726,"firstname":"Kari","lastname":"Skinner","age":30,"gender":"F","address":"735 Losee Terrace","employer":"Singavera","email":"kariskinner@singavera.com","city":"Rushford","state":"WV"}
{"index":{"_id":"265"}}
{"account_number":265,"balance":46910,"firstname":"Marion","lastname":"Schneider","age":26,"gender":"F","address":"574 Everett Avenue","employer":"Evidends","email":"marionschneider@evidends.com","city":"Maplewood","state":"WY"}
{"index":{"_id":"272"}}
{"account_number":272,"balance":19253,"firstname":"Lilly","lastname":"Morgan","age":25,"gender":"F","address":"689 Fleet Street","employer":"Biolive","email":"lillymorgan@biolive.com","city":"Sunbury","state":"OH"}
{"index":{"_id":"277"}}
{"account_number":277,"balance":29564,"firstname":"Romero","lastname":"Lott","age":31,"gender":"M","address":"456 Danforth Street","employer":"Plasto","email":"romerolott@plasto.com","city":"Vincent","state":"VT"}
{"index":{"_id":"284"}}
{"account_number":284,"balance":22806,"firstname":"Randolph","lastname":"Banks","age":29,"gender":"M","address":"875 Hamilton Avenue","employer":"Caxt","email":"randolphbanks@caxt.com","city":"Crawfordsville","state":"WA"}
{"index":{"_id":"289"}}
{"account_number":289,"balance":7798,"firstname":"Blair","lastname":"Church","age":29,"gender":"M","address":"370 Sutton Street","employer":"Cubix","email":"blairchurch@cubix.com","city":"Nile","state":"NH"}
{"index":{"_id":"291"}}
{"account_number":291,"balance":19955,"firstname":"Lynn","lastname":"Pollard","age":40,"gender":"F","address":"685 Pierrepont Street","employer":"Slambda","email":"lynnpollard@slambda.com","city":"Mappsville","state":"ID"}
{"index":{"_id":"296"}}
{"account_number":296,"balance":24606,"firstname":"Rosa","lastname":"Oliver","age":34,"gender":"M","address":"168 Woodbine Street","employer":"Idetica","email":"rosaoliver@idetica.com","city":"Robinson","state":"WY"}
{"index":{"_id":"304"}}
{"account_number":304,"balance":28647,"firstname":"Palmer","lastname":"Clark","age":35,"gender":"M","address":"866 Boulevard Court","employer":"Maximind","email":"palmerclark@maximind.com","city":"Avalon","state":"NH"}
{"index":{"_id":"309"}}
{"account_number":309,"balance":3830,"firstname":"Rosemarie","lastname":"Nieves","age":30,"gender":"M","address":"206 Alice Court","employer":"Zounds","email":"rosemarienieves@zounds.com","city":"Ferney","state":"AR"}
{"index":{"_id":"311"}}
{"account_number":311,"balance":13388,"firstname":"Vinson","lastname":"Ballard","age":23,"gender":"F","address":"960 Glendale Court","employer":"Gynk","email":"vinsonballard@gynk.com","city":"Fairforest","state":"WY"}
{"index":{"_id":"316"}}
{"account_number":316,"balance":8214,"firstname":"Anita","lastname":"Ewing","age":32,"gender":"M","address":"396 Lombardy Street","employer":"Panzent","email":"anitaewing@panzent.com","city":"Neahkahnie","state":"WY"}
{"index":{"_id":"323"}}
{"account_number":323,"balance":42230,"firstname":"Chelsea","lastname":"Gamble","age":34,"gender":"F","address":"356 Dare Court","employer":"Isosphere","email":"chelseagamble@isosphere.com","city":"Dundee","state":"MD"}
{"index":{"_id":"328"}}
{"account_number":328,"balance":12523,"firstname":"Good","lastname":"Campbell","age":27,"gender":"F","address":"438 Hicks Street","employer":"Gracker","email":"goodcampbell@gracker.com","city":"Marion","state":"CA"}
{"index":{"_id":"330"}}
{"account_number":330,"balance":41620,"firstname":"Yvette","lastname":"Browning","age":34,"gender":"F","address":"431 Beekman Place","employer":"Marketoid","email":"yvettebrowning@marketoid.com","city":"Talpa","state":"CO"}
{"index":{"_id":"335"}}
{"account_number":335,"balance":35433,"firstname":"Vera","lastname":"Hansen","age":24,"gender":"M","address":"252 Bushwick Avenue","employer":"Zanilla","email":"verahansen@zanilla.com","city":"Manila","state":"TN"}
{"index":{"_id":"342"}}
{"account_number":342,"balance":33670,"firstname":"Vivian","lastname":"Wells","age":36,"gender":"M","address":"570 Cobek Court","employer":"Nutralab","email":"vivianwells@nutralab.com","city":"Fontanelle","state":"OK"}
{"index":{"_id":"347"}}
{"account_number":347,"balance":36038,"firstname":"Gould","lastname":"Carson","age":24,"gender":"F","address":"784 Pulaski Street","employer":"Mobildata","email":"gouldcarson@mobildata.com","city":"Goochland","state":"MI"}
{"index":{"_id":"354"}}
{"account_number":354,"balance":21294,"firstname":"Kidd","lastname":"Mclean","age":22,"gender":"M","address":"691 Saratoga Avenue","employer":"Ronbert","email":"kiddmclean@ronbert.com","city":"Tioga","state":"ME"}
{"index":{"_id":"359"}}
{"account_number":359,"balance":29927,"firstname":"Vanessa","lastname":"Harvey","age":28,"gender":"F","address":"679 Rutledge Street","employer":"Zentime","email":"vanessaharvey@zentime.com","city":"Williston","state":"IL"}
{"index":{"_id":"361"}}
{"account_number":361,"balance":23659,"firstname":"Noreen","lastname":"Shelton","age":36,"gender":"M","address":"702 Tillary Street","employer":"Medmex","email":"noreenshelton@medmex.com","city":"Derwood","state":"NH"}
{"index":{"_id":"366"}}
{"account_number":366,"balance":42368,"firstname":"Lydia","lastname":"Cooke","age":31,"gender":"M","address":"470 Coleman Street","employer":"Comstar","email":"lydiacooke@comstar.com","city":"Datil","state":"TN"}
{"index":{"_id":"373"}}
{"account_number":373,"balance":9671,"firstname":"Simpson","lastname":"Carpenter","age":21,"gender":"M","address":"837 Horace Court","employer":"Snips","email":"simpsoncarpenter@snips.com","city":"Tolu","state":"MA"}
{"index":{"_id":"378"}}
{"account_number":378,"balance":27100,"firstname":"Watson","lastname":"Simpson","age":36,"gender":"F","address":"644 Thomas Street","employer":"Wrapture","email":"watsonsimpson@wrapture.com","city":"Keller","state":"TX"}
{"index":{"_id":"380"}}
{"account_number":380,"balance":35628,"firstname":"Fernandez","lastname":"Reid","age":33,"gender":"F","address":"154 Melba Court","employer":"Cosmosis","email":"fernandezreid@cosmosis.com","city":"Boyd","state":"NE"}
{"index":{"_id":"385"}}
{"account_number":385,"balance":11022,"firstname":"Rosalinda","lastname":"Valencia","age":22,"gender":"M","address":"933 Lloyd Street","employer":"Zoarere","email":"rosalindavalencia@zoarere.com","city":"Waverly","state":"GA"}
{"index":{"_id":"392"}}
{"account_number":392,"balance":31613,"firstname":"Dotson","lastname":"Dean","age":35,"gender":"M","address":"136 Ford Street","employer":"Petigems","email":"dotsondean@petigems.com","city":"Chical","state":"SD"}
{"index":{"_id":"397"}}
{"account_number":397,"balance":37418,"firstname":"Leonard","lastname":"Gray","age":36,"gender":"F","address":"840 Morgan Avenue","employer":"Recritube","email":"leonardgray@recritube.com","city":"Edenburg","state":"AL"}
{"index":{"_id":"400"}}
{"account_number":400,"balance":20685,"firstname":"Kane","lastname":"King","age":21,"gender":"F","address":"405 Cornelia Street","employer":"Tri@Tribalog","email":"kaneking@tri@tribalog.com","city":"Gulf","state":"VT"}
{"index":{"_id":"405"}}
{"account_number":405,"balance":5679,"firstname":"Strickland","lastname":"Fuller","age":26,"gender":"M","address":"990 Concord Street","employer":"Digique","email":"stricklandfuller@digique.com","city":"Southmont","state":"NV"}
{"index":{"_id":"412"}}
{"account_number":412,"balance":27436,"firstname":"Ilene","lastname":"Abbott","age":26,"gender":"M","address":"846 Vine Street","employer":"Typhonica","email":"ileneabbott@typhonica.com","city":"Cedarville","state":"VT"}
{"index":{"_id":"417"}}
{"account_number":417,"balance":1788,"firstname":"Wheeler","lastname":"Ayers","age":35,"gender":"F","address":"677 Hope Street","employer":"Fortean","email":"wheelerayers@fortean.com","city":"Ironton","state":"PA"}
{"index":{"_id":"424"}}
{"account_number":424,"balance":36818,"firstname":"Tracie","lastname":"Gregory","age":34,"gender":"M","address":"112 Hunterfly Place","employer":"Comstruct","email":"traciegregory@comstruct.com","city":"Onton","state":"TN"}
{"index":{"_id":"429"}}
{"account_number":429,"balance":46970,"firstname":"Cantu","lastname":"Lindsey","age":31,"gender":"M","address":"404 Willoughby Avenue","employer":"Inquala","email":"cantulindsey@inquala.com","city":"Cowiche","state":"IA"}
{"index":{"_id":"431"}}
{"account_number":431,"balance":13136,"firstname":"Laurie","lastname":"Shaw","age":26,"gender":"F","address":"263 Aviation Road","employer":"Zillanet","email":"laurieshaw@zillanet.com","city":"Harmon","state":"WV"}
{"index":{"_id":"436"}}
{"account_number":436,"balance":27585,"firstname":"Alexander","lastname":"Sargent","age":23,"gender":"M","address":"363 Albemarle Road","employer":"Fangold","email":"alexandersargent@fangold.com","city":"Calpine","state":"OR"}
{"index":{"_id":"443"}}
{"account_number":443,"balance":7588,"firstname":"Huff","lastname":"Thomas","age":23,"gender":"M","address":"538 Erskine Loop","employer":"Accufarm","email":"huffthomas@accufarm.com","city":"Corinne","state":"AL"}
{"index":{"_id":"448"}}
{"account_number":448,"balance":22776,"firstname":"Adriana","lastname":"Mcfadden","age":35,"gender":"F","address":"984 Woodside Avenue","employer":"Telequiet","email":"adrianamcfadden@telequiet.com","city":"Darrtown","state":"WI"}
{"index":{"_id":"450"}}
{"account_number":450,"balance":2643,"firstname":"Bradford","lastname":"Nielsen","age":25,"gender":"M","address":"487 Keen Court","employer":"Exovent","email":"bradfordnielsen@exovent.com","city":"Hamilton","state":"DE"}
{"index":{"_id":"455"}}
{"account_number":455,"balance":39556,"firstname":"Lynn","lastname":"Tran","age":36,"gender":"M","address":"741 Richmond Street","employer":"Optyk","email":"lynntran@optyk.com","city":"Clinton","state":"WV"}
{"index":{"_id":"462"}}
{"account_number":462,"balance":10871,"firstname":"Calderon","lastname":"Day","age":27,"gender":"M","address":"810 Milford Street","employer":"Cofine","email":"calderonday@cofine.com","city":"Kula","state":"OK"}
{"index":{"_id":"467"}}
{"account_number":467,"balance":6312,"firstname":"Angelica","lastname":"May","age":32,"gender":"F","address":"384 Karweg Place","employer":"Keeg","email":"angelicamay@keeg.com","city":"Tetherow","state":"IA"}
{"index":{"_id":"474"}}
{"account_number":474,"balance":35896,"firstname":"Obrien","lastname":"Walton","age":40,"gender":"F","address":"192 Ide Court","employer":"Suremax","email":"obrienwalton@suremax.com","city":"Crucible","state":"UT"}
{"index":{"_id":"479"}}
{"account_number":479,"balance":31865,"firstname":"Cameron","lastname":"Ross","age":40,"gender":"M","address":"904 Bouck Court","employer":"Telpod","email":"cameronross@telpod.com","city":"Nord","state":"MO"}
{"index":{"_id":"481"}}
{"account_number":481,"balance":20024,"firstname":"Lina","lastname":"Stanley","age":33,"gender":"M","address":"361 Hanover Place","employer":"Strozen","email":"linastanley@strozen.com","city":"Wyoming","state":"NC"}
{"index":{"_id":"486"}}
{"account_number":486,"balance":35902,"firstname":"Dixie","lastname":"Fuentes","age":22,"gender":"F","address":"991 Applegate Court","employer":"Portico","email":"dixiefuentes@portico.com","city":"Salix","state":"VA"}
{"index":{"_id":"493"}}
{"account_number":493,"balance":5871,"firstname":"Campbell","lastname":"Best","age":24,"gender":"M","address":"297 Friel Place","employer":"Fanfare","email":"campbellbest@fanfare.com","city":"Kidder","state":"GA"}
{"index":{"_id":"498"}}
{"account_number":498,"balance":10516,"firstname":"Stella","lastname":"Hinton","age":39,"gender":"F","address":"649 Columbia Place","employer":"Flyboyz","email":"stellahinton@flyboyz.com","city":"Crenshaw","state":"SC"}
{"index":{"_id":"501"}}
{"account_number":501,"balance":16572,"firstname":"Kelley","lastname":"Ochoa","age":36,"gender":"M","address":"451 Clifton Place","employer":"Bluplanet","email":"kelleyochoa@bluplanet.com","city":"Gouglersville","state":"CT"}
{"index":{"_id":"506"}}
{"account_number":506,"balance":43440,"firstname":"Davidson","lastname":"Salas","age":28,"gender":"M","address":"731 Cleveland Street","employer":"Sequitur","email":"davidsonsalas@sequitur.com","city":"Lloyd","state":"ME"}
{"index":{"_id":"513"}}
{"account_number":513,"balance":30040,"firstname":"Maryellen","lastname":"Rose","age":37,"gender":"F","address":"428 Durland Place","employer":"Waterbaby","email":"maryellenrose@waterbaby.com","city":"Kiskimere","state":"RI"}
{"index":{"_id":"518"}}
{"account_number":518,"balance":48954,"firstname":"Finch","lastname":"Curtis","age":29,"gender":"F","address":"137 Ryder Street","employer":"Viagrand","email":"finchcurtis@viagrand.com","city":"Riverton","state":"MO"}
{"index":{"_id":"520"}}
{"account_number":520,"balance":27987,"firstname":"Brandy","lastname":"Calhoun","age":32,"gender":"M","address":"818 Harden Street","employer":"Maxemia","email":"brandycalhoun@maxemia.com","city":"Sidman","state":"OR"}
{"index":{"_id":"525"}}
{"account_number":525,"balance":23545,"firstname":"Holly","lastname":"Miles","age":25,"gender":"M","address":"746 Ludlam Place","employer":"Xurban","email":"hollymiles@xurban.com","city":"Harold","state":"AR"}
{"index":{"_id":"532"}}
{"account_number":532,"balance":17207,"firstname":"Hardin","lastname":"Kirk","age":26,"gender":"M","address":"268 Canarsie Road","employer":"Exposa","email":"hardinkirk@exposa.com","city":"Stouchsburg","state":"IL"}
{"index":{"_id":"537"}}
{"account_number":537,"balance":31069,"firstname":"Morin","lastname":"Frost","age":29,"gender":"M","address":"910 Lake Street","employer":"Primordia","email":"morinfrost@primordia.com","city":"Rivera","state":"DE"}
{"index":{"_id":"544"}}
{"account_number":544,"balance":41735,"firstname":"Short","lastname":"Dennis","age":21,"gender":"F","address":"908 Glen Street","employer":"Minga","email":"shortdennis@minga.com","city":"Dale","state":"KY"}
{"index":{"_id":"549"}}
{"account_number":549,"balance":1932,"firstname":"Jacqueline","lastname":"Maxwell","age":40,"gender":"M","address":"444 Schenck Place","employer":"Fuelworks","email":"jacquelinemaxwell@fuelworks.com","city":"Oretta","state":"OR"}
{"index":{"_id":"551"}}
{"account_number":551,"balance":21732,"firstname":"Milagros","lastname":"Travis","age":27,"gender":"F","address":"380 Murdock Court","employer":"Sloganaut","email":"milagrostravis@sloganaut.com","city":"Homeland","state":"AR"}
{"index":{"_id":"556"}}
{"account_number":556,"balance":36420,"firstname":"Collier","lastname":"Odonnell","age":35,"gender":"M","address":"591 Nolans Lane","employer":"Sultraxin","email":"collierodonnell@sultraxin.com","city":"Fulford","state":"MD"}
{"index":{"_id":"563"}}
{"account_number":563,"balance":43403,"firstname":"Morgan","lastname":"Torres","age":30,"gender":"F","address":"672 Belvidere Street","employer":"Quonata","email":"morgantorres@quonata.com","city":"Hollymead","state":"KY"}
{"index":{"_id":"568"}}
{"account_number":568,"balance":36628,"firstname":"Lesa","lastname":"Maynard","age":29,"gender":"F","address":"295 Whitty Lane","employer":"Coash","email":"lesamaynard@coash.com","city":"Broadlands","state":"VT"}
{"index":{"_id":"570"}}
{"account_number":570,"balance":26751,"firstname":"Church","lastname":"Mercado","age":24,"gender":"F","address":"892 Wyckoff Street","employer":"Xymonk","email":"churchmercado@xymonk.com","city":"Gloucester","state":"KY"}
{"index":{"_id":"575"}}
{"account_number":575,"balance":12588,"firstname":"Buchanan","lastname":"Pope","age":39,"gender":"M","address":"581 Sumner Place","employer":"Stucco","email":"buchananpope@stucco.com","city":"Ellerslie","state":"MD"}
{"index":{"_id":"582"}}
{"account_number":582,"balance":33371,"firstname":"Manning","lastname":"Guthrie","age":24,"gender":"F","address":"271 Jodie Court","employer":"Xerex","email":"manningguthrie@xerex.com","city":"Breinigsville","state":"NM"}
{"index":{"_id":"587"}}
{"account_number":587,"balance":3468,"firstname":"Carly","lastname":"Johns","age":33,"gender":"M","address":"390 Noll Street","employer":"Gallaxia","email":"carlyjohns@gallaxia.com","city":"Emison","state":"DC"}
{"index":{"_id":"594"}}
{"account_number":594,"balance":28194,"firstname":"Golden","lastname":"Donovan","age":26,"gender":"M","address":"199 Jewel Street","employer":"Organica","email":"goldendonovan@organica.com","city":"Macdona","state":"RI"}
{"index":{"_id":"599"}}
{"account_number":599,"balance":11944,"firstname":"Joanna","lastname":"Jennings","age":36,"gender":"F","address":"318 Irving Street","employer":"Extremo","email":"joannajennings@extremo.com","city":"Bartley","state":"MI"}
{"index":{"_id":"602"}}
{"account_number":602,"balance":38699,"firstname":"Mcgowan","lastname":"Mcclain","age":33,"gender":"M","address":"361 Stoddard Place","employer":"Oatfarm","email":"mcgowanmcclain@oatfarm.com","city":"Kapowsin","state":"MI"}
{"index":{"_id":"607"}}
{"account_number":607,"balance":38350,"firstname":"White","lastname":"Small","age":38,"gender":"F","address":"736 Judge Street","employer":"Immunics","email":"whitesmall@immunics.com","city":"Fairfield","state":"HI"}
{"index":{"_id":"614"}}
{"account_number":614,"balance":13157,"firstname":"Salazar","lastname":"Howard","age":35,"gender":"F","address":"847 Imlay Street","employer":"Retrack","email":"salazarhoward@retrack.com","city":"Grill","state":"FL"}
{"index":{"_id":"619"}}
{"account_number":619,"balance":48755,"firstname":"Grimes","lastname":"Reynolds","age":36,"gender":"M","address":"378 Denton Place","employer":"Frenex","email":"grimesreynolds@frenex.com","city":"Murillo","state":"LA"}
{"index":{"_id":"621"}}
{"account_number":621,"balance":35480,"firstname":"Leslie","lastname":"Sloan","age":26,"gender":"F","address":"336 Kansas Place","employer":"Dancity","email":"lesliesloan@dancity.com","city":"Corriganville","state":"AR"}
{"index":{"_id":"626"}}
{"account_number":626,"balance":19498,"firstname":"Ava","lastname":"Richardson","age":31,"gender":"F","address":"666 Nautilus Avenue","employer":"Cinaster","email":"avarichardson@cinaster.com","city":"Sutton","state":"AL"}
{"index":{"_id":"633"}}
{"account_number":633,"balance":35874,"firstname":"Conner","lastname":"Ramos","age":34,"gender":"M","address":"575 Agate Court","employer":"Insource","email":"connerramos@insource.com","city":"Madaket","state":"OK"}
{"index":{"_id":"638"}}
{"account_number":638,"balance":2658,"firstname":"Bridget","lastname":"Gallegos","age":31,"gender":"M","address":"383 Wogan Terrace","employer":"Songlines","email":"bridgetgallegos@songlines.com","city":"Linganore","state":"WA"}
{"index":{"_id":"640"}}
{"account_number":640,"balance":35596,"firstname":"Candace","lastname":"Hancock","age":25,"gender":"M","address":"574 Riverdale Avenue","employer":"Animalia","email":"candacehancock@animalia.com","city":"Blandburg","state":"KY"}
{"index":{"_id":"645"}}
{"account_number":645,"balance":29362,"firstname":"Edwina","lastname":"Hutchinson","age":26,"gender":"F","address":"892 Pacific Street","employer":"Essensia","email":"edwinahutchinson@essensia.com","city":"Dowling","state":"NE"}
{"index":{"_id":"652"}}
{"account_number":652,"balance":17363,"firstname":"Bonner","lastname":"Garner","age":26,"gender":"M","address":"219 Grafton Street","employer":"Utarian","email":"bonnergarner@utarian.com","city":"Vandiver","state":"PA"}
{"index":{"_id":"657"}}
{"account_number":657,"balance":40475,"firstname":"Kathleen","lastname":"Wilder","age":34,"gender":"F","address":"286 Sutter Avenue","employer":"Solgan","email":"kathleenwilder@solgan.com","city":"Graniteville","state":"MI"}
{"index":{"_id":"664"}}
{"account_number":664,"balance":16163,"firstname":"Hart","lastname":"Mccormick","age":40,"gender":"M","address":"144 Guider Avenue","employer":"Dyno","email":"hartmccormick@dyno.com","city":"Carbonville","state":"ID"}
{"index":{"_id":"669"}}
{"account_number":669,"balance":16934,"firstname":"Jewel","lastname":"Estrada","age":28,"gender":"M","address":"896 Meeker Avenue","employer":"Zilla","email":"jewelestrada@zilla.com","city":"Goodville","state":"PA"}
{"index":{"_id":"671"}}
{"account_number":671,"balance":29029,"firstname":"Antoinette","lastname":"Cook","age":34,"gender":"M","address":"375 Cumberland Street","employer":"Harmoney","email":"antoinettecook@harmoney.com","city":"Bergoo","state":"VT"}
{"index":{"_id":"676"}}
{"account_number":676,"balance":23842,"firstname":"Lisa","lastname":"Dudley","age":34,"gender":"M","address":"506 Vanderveer Street","employer":"Tropoli","email":"lisadudley@tropoli.com","city":"Konterra","state":"NY"}
{"index":{"_id":"683"}}
{"account_number":683,"balance":4381,"firstname":"Matilda","lastname":"Berger","age":39,"gender":"M","address":"884 Noble Street","employer":"Fibrodyne","email":"matildaberger@fibrodyne.com","city":"Shepardsville","state":"TN"}
{"index":{"_id":"688"}}
{"account_number":688,"balance":17931,"firstname":"Freeman","lastname":"Zamora","age":22,"gender":"F","address":"114 Herzl Street","employer":"Elemantra","email":"freemanzamora@elemantra.com","city":"Libertytown","state":"NM"}
{"index":{"_id":"690"}}
{"account_number":690,"balance":18127,"firstname":"Russo","lastname":"Swanson","age":35,"gender":"F","address":"256 Roebling Street","employer":"Zaj","email":"russoswanson@zaj.com","city":"Hoagland","state":"MI"}
{"index":{"_id":"695"}}
{"account_number":695,"balance":36800,"firstname":"Gonzales","lastname":"Mcfarland","age":26,"gender":"F","address":"647 Louisa Street","employer":"Songbird","email":"gonzalesmcfarland@songbird.com","city":"Crisman","state":"ID"}
{"index":{"_id":"703"}}
{"account_number":703,"balance":27443,"firstname":"Dona","lastname":"Burton","age":29,"gender":"M","address":"489 Flatlands Avenue","employer":"Cytrex","email":"donaburton@cytrex.com","city":"Reno","state":"VA"}
{"index":{"_id":"708"}}
{"account_number":708,"balance":34002,"firstname":"May","lastname":"Ortiz","age":28,"gender":"F","address":"244 Chauncey Street","employer":"Syntac","email":"mayortiz@syntac.com","city":"Munjor","state":"ID"}
{"index":{"_id":"710"}}
{"account_number":710,"balance":33650,"firstname":"Shelton","lastname":"Stark","age":37,"gender":"M","address":"404 Ovington Avenue","employer":"Kraggle","email":"sheltonstark@kraggle.com","city":"Ogema","state":"TN"}
{"index":{"_id":"715"}}
{"account_number":715,"balance":23734,"firstname":"Tammi","lastname":"Hodge","age":24,"gender":"M","address":"865 Church Lane","employer":"Netur","email":"tammihodge@netur.com","city":"Lacomb","state":"KS"}
{"index":{"_id":"722"}}
{"account_number":722,"balance":27256,"firstname":"Roberts","lastname":"Beasley","age":34,"gender":"F","address":"305 Kings Hwy","employer":"Quintity","email":"robertsbeasley@quintity.com","city":"Hayden","state":"PA"}
{"index":{"_id":"727"}}
{"account_number":727,"balance":27263,"firstname":"Natasha","lastname":"Knapp","age":36,"gender":"M","address":"723 Hubbard Street","employer":"Exostream","email":"natashaknapp@exostream.com","city":"Trexlertown","state":"LA"}
{"index":{"_id":"734"}}
{"account_number":734,"balance":20325,"firstname":"Keri","lastname":"Kinney","age":23,"gender":"M","address":"490 Balfour Place","employer":"Retrotex","email":"kerikinney@retrotex.com","city":"Salunga","state":"PA"}
{"index":{"_id":"739"}}
{"account_number":739,"balance":39063,"firstname":"Gwen","lastname":"Hardy","age":33,"gender":"F","address":"733 Stuart Street","employer":"Exozent","email":"gwenhardy@exozent.com","city":"Drytown","state":"NY"}
{"index":{"_id":"741"}}
{"account_number":741,"balance":33074,"firstname":"Nielsen","lastname":"Good","age":22,"gender":"M","address":"404 Norfolk Street","employer":"Kiggle","email":"nielsengood@kiggle.com","city":"Cumberland","state":"WA"}
{"index":{"_id":"746"}}
{"account_number":746,"balance":15970,"firstname":"Marguerite","lastname":"Wall","age":28,"gender":"F","address":"364 Crosby Avenue","employer":"Aquoavo","email":"margueritewall@aquoavo.com","city":"Jeff","state":"MI"}
{"index":{"_id":"753"}}
{"account_number":753,"balance":33340,"firstname":"Katina","lastname":"Alford","age":21,"gender":"F","address":"690 Ross Street","employer":"Intrawear","email":"katinaalford@intrawear.com","city":"Grimsley","state":"OK"}
{"index":{"_id":"758"}}
{"account_number":758,"balance":15739,"firstname":"Berta","lastname":"Short","age":28,"gender":"M","address":"149 Surf Avenue","employer":"Ozean","email":"bertashort@ozean.com","city":"Odessa","state":"UT"}
{"index":{"_id":"760"}}
{"account_number":760,"balance":40996,"firstname":"Rhea","lastname":"Blair","age":37,"gender":"F","address":"440 Hubbard Place","employer":"Bicol","email":"rheablair@bicol.com","city":"Stockwell","state":"LA"}
{"index":{"_id":"765"}}
{"account_number":765,"balance":31278,"firstname":"Knowles","lastname":"Cunningham","age":23,"gender":"M","address":"753 Macdougal Street","employer":"Thredz","email":"knowlescunningham@thredz.com","city":"Thomasville","state":"WA"}
{"index":{"_id":"772"}}
{"account_number":772,"balance":37849,"firstname":"Eloise","lastname":"Sparks","age":21,"gender":"M","address":"608 Willow Street","employer":"Satiance","email":"eloisesparks@satiance.com","city":"Richford","state":"NY"}
{"index":{"_id":"777"}}
{"account_number":777,"balance":48294,"firstname":"Adkins","lastname":"Mejia","age":32,"gender":"M","address":"186 Oxford Walk","employer":"Datagen","email":"adkinsmejia@datagen.com","city":"Faywood","state":"OK"}
{"index":{"_id":"784"}}
{"account_number":784,"balance":25291,"firstname":"Mabel","lastname":"Thornton","age":21,"gender":"M","address":"124 Louisiana Avenue","employer":"Zolavo","email":"mabelthornton@zolavo.com","city":"Lynn","state":"AL"}
{"index":{"_id":"789"}}
{"account_number":789,"balance":8760,"firstname":"Cunningham","lastname":"Kerr","age":27,"gender":"F","address":"154 Sharon Street","employer":"Polarium","email":"cunninghamkerr@polarium.com","city":"Tuskahoma","state":"MS"}
{"index":{"_id":"791"}}
{"account_number":791,"balance":48249,"firstname":"Janine","lastname":"Huber","age":38,"gender":"F","address":"348 Porter Avenue","employer":"Viocular","email":"janinehuber@viocular.com","city":"Fivepointville","state":"MA"}
{"index":{"_id":"796"}}
{"account_number":796,"balance":23503,"firstname":"Mona","lastname":"Craft","age":35,"gender":"F","address":"511 Henry Street","employer":"Opticom","email":"monacraft@opticom.com","city":"Websterville","state":"IN"}
{"index":{"_id":"804"}}
{"account_number":804,"balance":23610,"firstname":"Rojas","lastname":"Oneal","age":27,"gender":"M","address":"669 Sandford Street","employer":"Glukgluk","email":"rojasoneal@glukgluk.com","city":"Wheaton","state":"ME"}
{"index":{"_id":"809"}}
{"account_number":809,"balance":47812,"firstname":"Christie","lastname":"Strickland","age":30,"gender":"M","address":"346 Bancroft Place","employer":"Anarco","email":"christiestrickland@anarco.com","city":"Baden","state":"NV"}
{"index":{"_id":"811"}}
{"account_number":811,"balance":26007,"firstname":"Walls","lastname":"Rogers","age":28,"gender":"F","address":"352 Freeman Street","employer":"Geekmosis","email":"wallsrogers@geekmosis.com","city":"Caroleen","state":"NV"}
{"index":{"_id":"816"}}
{"account_number":816,"balance":9567,"firstname":"Cornelia","lastname":"Lane","age":20,"gender":"F","address":"384 Bainbridge Street","employer":"Sulfax","email":"cornelialane@sulfax.com","city":"Elizaville","state":"MS"}
{"index":{"_id":"823"}}
{"account_number":823,"balance":48726,"firstname":"Celia","lastname":"Bernard","age":33,"gender":"F","address":"466 Amboy Street","employer":"Mitroc","email":"celiabernard@mitroc.com","city":"Skyland","state":"GA"}
{"index":{"_id":"828"}}
{"account_number":828,"balance":44890,"firstname":"Blanche","lastname":"Holmes","age":33,"gender":"F","address":"605 Stryker Court","employer":"Motovate","email":"blancheholmes@motovate.com","city":"Loomis","state":"KS"}
{"index":{"_id":"830"}}
{"account_number":830,"balance":45210,"firstname":"Louella","lastname":"Chan","age":23,"gender":"M","address":"511 Heath Place","employer":"Conferia","email":"louellachan@conferia.com","city":"Brookfield","state":"OK"}
{"index":{"_id":"835"}}
{"account_number":835,"balance":46558,"firstname":"Glover","lastname":"Rutledge","age":25,"gender":"F","address":"641 Royce Street","employer":"Ginkogene","email":"gloverrutledge@ginkogene.com","city":"Dixonville","state":"VA"}
{"index":{"_id":"842"}}
{"account_number":842,"balance":49587,"firstname":"Meagan","lastname":"Buckner","age":23,"gender":"F","address":"833 Bushwick Court","employer":"Biospan","email":"meaganbuckner@biospan.com","city":"Craig","state":"TX"}
{"index":{"_id":"847"}}
{"account_number":847,"balance":8652,"firstname":"Antonia","lastname":"Duncan","age":23,"gender":"M","address":"644 Stryker Street","employer":"Talae","email":"antoniaduncan@talae.com","city":"Dawn","state":"MO"}
{"index":{"_id":"854"}}
{"account_number":854,"balance":49795,"firstname":"Jimenez","lastname":"Barry","age":25,"gender":"F","address":"603 Cooper Street","employer":"Verton","email":"jimenezbarry@verton.com","city":"Moscow","state":"AL"}
{"index":{"_id":"859"}}
{"account_number":859,"balance":20734,"firstname":"Beulah","lastname":"Stuart","age":24,"gender":"F","address":"651 Albemarle Terrace","employer":"Hatology","email":"beulahstuart@hatology.com","city":"Waiohinu","state":"RI"}
{"index":{"_id":"861"}}
{"account_number":861,"balance":44173,"firstname":"Jaime","lastname":"Wilson","age":35,"gender":"M","address":"680 Richardson Street","employer":"Temorak","email":"jaimewilson@temorak.com","city":"Fidelis","state":"FL"}
{"index":{"_id":"866"}}
{"account_number":866,"balance":45565,"firstname":"Araceli","lastname":"Woodward","age":28,"gender":"M","address":"326 Meadow Street","employer":"Olympix","email":"araceliwoodward@olympix.com","city":"Dana","state":"KS"}
{"index":{"_id":"873"}}
{"account_number":873,"balance":43931,"firstname":"Tisha","lastname":"Cotton","age":39,"gender":"F","address":"432 Lincoln Road","employer":"Buzzmaker","email":"tishacotton@buzzmaker.com","city":"Bluetown","state":"GA"}
{"index":{"_id":"878"}}
{"account_number":878,"balance":49159,"firstname":"Battle","lastname":"Blackburn","age":40,"gender":"F","address":"234 Hendrix Street","employer":"Zilphur","email":"battleblackburn@zilphur.com","city":"Wanamie","state":"PA"}
{"index":{"_id":"880"}}
{"account_number":880,"balance":22575,"firstname":"Christian","lastname":"Myers","age":35,"gender":"M","address":"737 Crown Street","employer":"Combogen","email":"christianmyers@combogen.com","city":"Abrams","state":"OK"}
{"index":{"_id":"885"}}
{"account_number":885,"balance":31661,"firstname":"Valdez","lastname":"Roberson","age":40,"gender":"F","address":"227 Scholes Street","employer":"Delphide","email":"valdezroberson@delphide.com","city":"Chilton","state":"MT"}
{"index":{"_id":"892"}}
{"account_number":892,"balance":44974,"firstname":"Hill","lastname":"Hayes","age":29,"gender":"M","address":"721 Dooley Street","employer":"Fuelton","email":"hillhayes@fuelton.com","city":"Orason","state":"MT"}
{"index":{"_id":"897"}}
{"account_number":897,"balance":45973,"firstname":"Alyson","lastname":"Irwin","age":25,"gender":"M","address":"731 Poplar Street","employer":"Quizka","email":"alysonirwin@quizka.com","city":"Singer","state":"VA"}
{"index":{"_id":"900"}}
{"account_number":900,"balance":6124,"firstname":"Gonzalez","lastname":"Watson","age":23,"gender":"M","address":"624 Sullivan Street","employer":"Marvane","email":"gonzalezwatson@marvane.com","city":"Wikieup","state":"IL"}
{"index":{"_id":"905"}}
{"account_number":905,"balance":29438,"firstname":"Schultz","lastname":"Moreno","age":20,"gender":"F","address":"761 Cedar Street","employer":"Paragonia","email":"schultzmoreno@paragonia.com","city":"Glenshaw","state":"SC"}
{"index":{"_id":"912"}}
{"account_number":912,"balance":13675,"firstname":"Flora","lastname":"Alvarado","age":26,"gender":"M","address":"771 Vandervoort Avenue","employer":"Boilicon","email":"floraalvarado@boilicon.com","city":"Vivian","state":"ID"}
{"index":{"_id":"917"}}
{"account_number":917,"balance":47782,"firstname":"Parks","lastname":"Hurst","age":24,"gender":"M","address":"933 Cozine Avenue","employer":"Pyramis","email":"parkshurst@pyramis.com","city":"Lindcove","state":"GA"}
{"index":{"_id":"924"}}
{"account_number":924,"balance":3811,"firstname":"Hilary","lastname":"Leonard","age":24,"gender":"M","address":"235 Hegeman Avenue","employer":"Metroz","email":"hilaryleonard@metroz.com","city":"Roosevelt","state":"ME"}
{"index":{"_id":"929"}}
{"account_number":929,"balance":34708,"firstname":"Willie","lastname":"Hickman","age":35,"gender":"M","address":"430 Devoe Street","employer":"Apextri","email":"williehickman@apextri.com","city":"Clay","state":"MS"}
{"index":{"_id":"931"}}
{"account_number":931,"balance":8244,"firstname":"Ingrid","lastname":"Garcia","age":23,"gender":"F","address":"674 Indiana Place","employer":"Balooba","email":"ingridgarcia@balooba.com","city":"Interlochen","state":"AZ"}
{"index":{"_id":"936"}}
{"account_number":936,"balance":22430,"firstname":"Beth","lastname":"Frye","age":36,"gender":"M","address":"462 Thatford Avenue","employer":"Puria","email":"bethfrye@puria.com","city":"Hiseville","state":"LA"}
{"index":{"_id":"943"}}
{"account_number":943,"balance":24187,"firstname":"Wagner","lastname":"Griffin","age":23,"gender":"M","address":"489 Ellery Street","employer":"Gazak","email":"wagnergriffin@gazak.com","city":"Lorraine","state":"HI"}
{"index":{"_id":"948"}}
{"account_number":948,"balance":37074,"firstname":"Sargent","lastname":"Powers","age":40,"gender":"M","address":"532 Fiske Place","employer":"Accuprint","email":"sargentpowers@accuprint.com","city":"Umapine","state":"AK"}
{"index":{"_id":"950"}}
{"account_number":950,"balance":30916,"firstname":"Sherrie","lastname":"Patel","age":32,"gender":"F","address":"658 Langham Street","employer":"Futurize","email":"sherriepatel@futurize.com","city":"Garfield","state":"OR"}
{"index":{"_id":"955"}}
{"account_number":955,"balance":41621,"firstname":"Klein","lastname":"Kemp","age":33,"gender":"M","address":"370 Vanderbilt Avenue","employer":"Synkgen","email":"kleinkemp@synkgen.com","city":"Bonanza","state":"FL"}
{"index":{"_id":"962"}}
{"account_number":962,"balance":32096,"firstname":"Trujillo","lastname":"Wilcox","age":21,"gender":"F","address":"914 Duffield Street","employer":"Extragene","email":"trujillowilcox@extragene.com","city":"Golconda","state":"MA"}
{"index":{"_id":"967"}}
{"account_number":967,"balance":19161,"firstname":"Carrie","lastname":"Huffman","age":36,"gender":"F","address":"240 Sands Street","employer":"Injoy","email":"carriehuffman@injoy.com","city":"Leroy","state":"CA"}
{"index":{"_id":"974"}}
{"account_number":974,"balance":38082,"firstname":"Deborah","lastname":"Yang","age":26,"gender":"F","address":"463 Goodwin Place","employer":"Entogrok","email":"deborahyang@entogrok.com","city":"Herald","state":"KY"}
{"index":{"_id":"979"}}
{"account_number":979,"balance":43130,"firstname":"Vaughn","lastname":"Pittman","age":29,"gender":"M","address":"446 Tompkins Place","employer":"Phormula","email":"vaughnpittman@phormula.com","city":"Fingerville","state":"WI"}
{"index":{"_id":"981"}}
{"account_number":981,"balance":20278,"firstname":"Nolan","lastname":"Warner","age":29,"gender":"F","address":"753 Channel Avenue","employer":"Interodeo","email":"nolanwarner@interodeo.com","city":"Layhill","state":"MT"}
{"index":{"_id":"986"}}
{"account_number":986,"balance":35086,"firstname":"Norris","lastname":"Hubbard","age":31,"gender":"M","address":"600 Celeste Court","employer":"Printspan","email":"norrishubbard@printspan.com","city":"Cassel","state":"MI"}
{"index":{"_id":"993"}}
{"account_number":993,"balance":26487,"firstname":"Campos","lastname":"Olsen","age":37,"gender":"M","address":"873 Covert Street","employer":"Isbol","email":"camposolsen@isbol.com","city":"Glendale","state":"AK"}
{"index":{"_id":"998"}}
{"account_number":998,"balance":16869,"firstname":"Letha","lastname":"Baker","age":40,"gender":"F","address":"206 Llama Court","employer":"Dognosis","email":"lethabaker@dognosis.com","city":"Dunlo","state":"WV"}
{"index":{"_id":"2"}}
{"account_number":2,"balance":28838,"firstname":"Roberta","lastname":"Bender","age":22,"gender":"F","address":"560 Kingsway Place","employer":"Chillium","email":"robertabender@chillium.com","city":"Bennett","state":"LA"}
{"index":{"_id":"7"}}
{"account_number":7,"balance":39121,"firstname":"Levy","lastname":"Richard","age":22,"gender":"M","address":"820 Logan Street","employer":"Teraprene","email":"levyrichard@teraprene.com","city":"Shrewsbury","state":"MO"}
{"index":{"_id":"14"}}
{"account_number":14,"balance":20480,"firstname":"Erma","lastname":"Kane","age":39,"gender":"F","address":"661 Vista Place","employer":"Stockpost","email":"ermakane@stockpost.com","city":"Chamizal","state":"NY"}
{"index":{"_id":"19"}}
{"account_number":19,"balance":27894,"firstname":"Schwartz","lastname":"Buchanan","age":28,"gender":"F","address":"449 Mersereau Court","employer":"Sybixtex","email":"schwartzbuchanan@sybixtex.com","city":"Greenwich","state":"KS"}
{"index":{"_id":"21"}}
{"account_number":21,"balance":7004,"firstname":"Estella","lastname":"Paul","age":38,"gender":"M","address":"859 Portal Street","employer":"Zillatide","email":"estellapaul@zillatide.com","city":"Churchill","state":"WV"}
{"index":{"_id":"26"}}
{"account_number":26,"balance":14127,"firstname":"Lorraine","lastname":"Mccullough","age":39,"gender":"F","address":"157 Dupont Street","employer":"Zosis","email":"lorrainemccullough@zosis.com","city":"Dennard","state":"NH"}
{"index":{"_id":"33"}}
{"account_number":33,"balance":35439,"firstname":"Savannah","lastname":"Kirby","age":30,"gender":"F","address":"372 Malta Street","employer":"Musanpoly","email":"savannahkirby@musanpoly.com","city":"Muse","state":"AK"}
{"index":{"_id":"38"}}
{"account_number":38,"balance":10511,"firstname":"Erna","lastname":"Fields","age":32,"gender":"M","address":"357 Maple Street","employer":"Eweville","email":"ernafields@eweville.com","city":"Twilight","state":"MS"}
{"index":{"_id":"40"}}
{"account_number":40,"balance":33882,"firstname":"Pace","lastname":"Molina","age":40,"gender":"M","address":"263 Ovington Court","employer":"Cytrak","email":"pacemolina@cytrak.com","city":"Silkworth","state":"OR"}
{"index":{"_id":"45"}}
{"account_number":45,"balance":44478,"firstname":"Geneva","lastname":"Morin","age":21,"gender":"F","address":"357 Herkimer Street","employer":"Ezent","email":"genevamorin@ezent.com","city":"Blanco","state":"AZ"}
{"index":{"_id":"52"}}
{"account_number":52,"balance":46425,"firstname":"Kayla","lastname":"Bradshaw","age":31,"gender":"M","address":"449 Barlow Drive","employer":"Magnemo","email":"kaylabradshaw@magnemo.com","city":"Wawona","state":"AZ"}
{"index":{"_id":"57"}}
{"account_number":57,"balance":8705,"firstname":"Powell","lastname":"Herring","age":21,"gender":"M","address":"263 Merit Court","employer":"Digiprint","email":"powellherring@digiprint.com","city":"Coral","state":"MT"}
{"index":{"_id":"64"}}
{"account_number":64,"balance":44036,"firstname":"Miles","lastname":"Battle","age":35,"gender":"F","address":"988 Homecrest Avenue","employer":"Koffee","email":"milesbattle@koffee.com","city":"Motley","state":"ID"}
{"index":{"_id":"69"}}
{"account_number":69,"balance":14253,"firstname":"Desiree","lastname":"Harrison","age":24,"gender":"M","address":"694 Garland Court","employer":"Barkarama","email":"desireeharrison@barkarama.com","city":"Hackneyville","state":"GA"}
{"index":{"_id":"71"}}
{"account_number":71,"balance":38201,"firstname":"Sharpe","lastname":"Hoffman","age":39,"gender":"F","address":"450 Conklin Avenue","employer":"Centree","email":"sharpehoffman@centree.com","city":"Urbana","state":"WY"}
{"index":{"_id":"76"}}
{"account_number":76,"balance":38345,"firstname":"Claudette","lastname":"Beard","age":24,"gender":"F","address":"748 Dorset Street","employer":"Repetwire","email":"claudettebeard@repetwire.com","city":"Caln","state":"TX"}
{"index":{"_id":"83"}}
{"account_number":83,"balance":35928,"firstname":"Mayo","lastname":"Cleveland","age":28,"gender":"M","address":"720 Brooklyn Road","employer":"Indexia","email":"mayocleveland@indexia.com","city":"Roberts","state":"ND"}
{"index":{"_id":"88"}}
{"account_number":88,"balance":26418,"firstname":"Adela","lastname":"Tyler","age":21,"gender":"F","address":"737 Clove Road","employer":"Surelogic","email":"adelatyler@surelogic.com","city":"Boling","state":"SD"}
{"index":{"_id":"90"}}
{"account_number":90,"balance":25332,"firstname":"Herman","lastname":"Snyder","age":22,"gender":"F","address":"737 College Place","employer":"Lunchpod","email":"hermansnyder@lunchpod.com","city":"Flintville","state":"IA"}
{"index":{"_id":"95"}}
{"account_number":95,"balance":1650,"firstname":"Dominguez","lastname":"Le","age":20,"gender":"M","address":"539 Grace Court","employer":"Portica","email":"dominguezle@portica.com","city":"Wollochet","state":"KS"}
{"index":{"_id":"103"}}
{"account_number":103,"balance":11253,"firstname":"Calhoun","lastname":"Bruce","age":33,"gender":"F","address":"731 Clarkson Avenue","employer":"Automon","email":"calhounbruce@automon.com","city":"Marienthal","state":"IL"}
{"index":{"_id":"108"}}
{"account_number":108,"balance":19015,"firstname":"Christensen","lastname":"Weaver","age":21,"gender":"M","address":"398 Dearborn Court","employer":"Quilk","email":"christensenweaver@quilk.com","city":"Belvoir","state":"TX"}
{"index":{"_id":"110"}}
{"account_number":110,"balance":4850,"firstname":"Daphne","lastname":"Byrd","age":23,"gender":"F","address":"239 Conover Street","employer":"Freakin","email":"daphnebyrd@freakin.com","city":"Taft","state":"MN"}
{"index":{"_id":"115"}}
{"account_number":115,"balance":18750,"firstname":"Nikki","lastname":"Doyle","age":31,"gender":"F","address":"537 Clara Street","employer":"Fossiel","email":"nikkidoyle@fossiel.com","city":"Caron","state":"MS"}
{"index":{"_id":"122"}}
{"account_number":122,"balance":17128,"firstname":"Aurora","lastname":"Fry","age":31,"gender":"F","address":"227 Knapp Street","employer":"Makingway","email":"aurorafry@makingway.com","city":"Maybell","state":"NE"}
{"index":{"_id":"127"}}
{"account_number":127,"balance":48734,"firstname":"Diann","lastname":"Mclaughlin","age":33,"gender":"F","address":"340 Clermont Avenue","employer":"Enomen","email":"diannmclaughlin@enomen.com","city":"Rutherford","state":"ND"}
{"index":{"_id":"134"}}
{"account_number":134,"balance":33829,"firstname":"Madelyn","lastname":"Norris","age":30,"gender":"F","address":"176 Noel Avenue","employer":"Endicil","email":"madelynnorris@endicil.com","city":"Walker","state":"NE"}
{"index":{"_id":"139"}}
{"account_number":139,"balance":18444,"firstname":"Rios","lastname":"Todd","age":35,"gender":"F","address":"281 Georgia Avenue","employer":"Uberlux","email":"riostodd@uberlux.com","city":"Hannasville","state":"PA"}
{"index":{"_id":"141"}}
{"account_number":141,"balance":20790,"firstname":"Liliana","lastname":"Caldwell","age":29,"gender":"M","address":"414 Huron Street","employer":"Rubadub","email":"lilianacaldwell@rubadub.com","city":"Hiwasse","state":"OK"}
{"index":{"_id":"146"}}
{"account_number":146,"balance":39078,"firstname":"Lang","lastname":"Kaufman","age":32,"gender":"F","address":"626 Beverley Road","employer":"Rodeomad","email":"langkaufman@rodeomad.com","city":"Mahtowa","state":"RI"}
{"index":{"_id":"153"}}
{"account_number":153,"balance":32074,"firstname":"Bird","lastname":"Cochran","age":31,"gender":"F","address":"691 Bokee Court","employer":"Supremia","email":"birdcochran@supremia.com","city":"Barrelville","state":"NE"}
{"index":{"_id":"158"}}
{"account_number":158,"balance":9380,"firstname":"Natalie","lastname":"Mcdowell","age":27,"gender":"M","address":"953 Roder Avenue","employer":"Myopium","email":"nataliemcdowell@myopium.com","city":"Savage","state":"ND"}
{"index":{"_id":"160"}}
{"account_number":160,"balance":48974,"firstname":"Hull","lastname":"Cherry","age":23,"gender":"F","address":"275 Beaumont Street","employer":"Noralex","email":"hullcherry@noralex.com","city":"Whipholt","state":"WA"}
{"index":{"_id":"165"}}
{"account_number":165,"balance":18956,"firstname":"Sims","lastname":"Mckay","age":40,"gender":"F","address":"205 Jackson Street","employer":"Comtour","email":"simsmckay@comtour.com","city":"Tilden","state":"DC"}
{"index":{"_id":"172"}}
{"account_number":172,"balance":18356,"firstname":"Marie","lastname":"Whitehead","age":20,"gender":"M","address":"704 Monaco Place","employer":"Sultrax","email":"mariewhitehead@sultrax.com","city":"Dragoon","state":"IL"}
{"index":{"_id":"177"}}
{"account_number":177,"balance":48972,"firstname":"Harris","lastname":"Gross","age":40,"gender":"F","address":"468 Suydam Street","employer":"Kidstock","email":"harrisgross@kidstock.com","city":"Yettem","state":"KY"}
{"index":{"_id":"184"}}
{"account_number":184,"balance":9157,"firstname":"Cathy","lastname":"Morrison","age":27,"gender":"M","address":"882 Pine Street","employer":"Zytrek","email":"cathymorrison@zytrek.com","city":"Fedora","state":"FL"}
{"index":{"_id":"189"}}
{"account_number":189,"balance":20167,"firstname":"Ada","lastname":"Cortez","age":38,"gender":"F","address":"700 Forest Place","employer":"Micronaut","email":"adacortez@micronaut.com","city":"Eagletown","state":"TX"}
{"index":{"_id":"191"}}
{"account_number":191,"balance":26172,"firstname":"Barr","lastname":"Sharpe","age":28,"gender":"M","address":"428 Auburn Place","employer":"Ziggles","email":"barrsharpe@ziggles.com","city":"Springdale","state":"KS"}
{"index":{"_id":"196"}}
{"account_number":196,"balance":29931,"firstname":"Caldwell","lastname":"Daniel","age":28,"gender":"F","address":"405 Oliver Street","employer":"Furnigeer","email":"caldwelldaniel@furnigeer.com","city":"Zortman","state":"NE"}
{"index":{"_id":"204"}}
{"account_number":204,"balance":27714,"firstname":"Mavis","lastname":"Deleon","age":39,"gender":"F","address":"400 Waldane Court","employer":"Lotron","email":"mavisdeleon@lotron.com","city":"Stollings","state":"LA"}
{"index":{"_id":"209"}}
{"account_number":209,"balance":31052,"firstname":"Myers","lastname":"Noel","age":30,"gender":"F","address":"691 Alton Place","employer":"Greeker","email":"myersnoel@greeker.com","city":"Hinsdale","state":"KY"}
{"index":{"_id":"211"}}
{"account_number":211,"balance":21539,"firstname":"Graciela","lastname":"Vaughan","age":22,"gender":"M","address":"558 Montauk Court","employer":"Fishland","email":"gracielavaughan@fishland.com","city":"Madrid","state":"PA"}
{"index":{"_id":"216"}}
{"account_number":216,"balance":11422,"firstname":"Price","lastname":"Haley","age":35,"gender":"M","address":"233 Portland Avenue","employer":"Zeam","email":"pricehaley@zeam.com","city":"Titanic","state":"UT"}
{"index":{"_id":"223"}}
{"account_number":223,"balance":9528,"firstname":"Newton","lastname":"Fletcher","age":26,"gender":"F","address":"654 Dewitt Avenue","employer":"Assistia","email":"newtonfletcher@assistia.com","city":"Nipinnawasee","state":"AK"}
{"index":{"_id":"228"}}
{"account_number":228,"balance":10543,"firstname":"Rosella","lastname":"Albert","age":20,"gender":"M","address":"185 Gotham Avenue","employer":"Isoplex","email":"rosellaalbert@isoplex.com","city":"Finzel","state":"NY"}
{"index":{"_id":"230"}}
{"account_number":230,"balance":10829,"firstname":"Chris","lastname":"Raymond","age":28,"gender":"F","address":"464 Remsen Street","employer":"Cogentry","email":"chrisraymond@cogentry.com","city":"Bowmansville","state":"SD"}
{"index":{"_id":"235"}}
{"account_number":235,"balance":17729,"firstname":"Mcpherson","lastname":"Mueller","age":31,"gender":"M","address":"541 Strong Place","employer":"Tingles","email":"mcphersonmueller@tingles.com","city":"Brantleyville","state":"AR"}
{"index":{"_id":"242"}}
{"account_number":242,"balance":42318,"firstname":"Berger","lastname":"Roach","age":21,"gender":"M","address":"125 Wakeman Place","employer":"Ovium","email":"bergerroach@ovium.com","city":"Hessville","state":"WI"}
{"index":{"_id":"247"}}
{"account_number":247,"balance":45123,"firstname":"Mccormick","lastname":"Moon","age":37,"gender":"M","address":"582 Brighton Avenue","employer":"Norsup","email":"mccormickmoon@norsup.com","city":"Forestburg","state":"DE"}
{"index":{"_id":"254"}}
{"account_number":254,"balance":35104,"firstname":"Yang","lastname":"Dodson","age":21,"gender":"M","address":"531 Lott Street","employer":"Mondicil","email":"yangdodson@mondicil.com","city":"Enoree","state":"UT"}
{"index":{"_id":"259"}}
{"account_number":259,"balance":41877,"firstname":"Eleanor","lastname":"Gonzalez","age":30,"gender":"M","address":"800 Sumpter Street","employer":"Futuris","email":"eleanorgonzalez@futuris.com","city":"Jenkinsville","state":"ID"}
{"index":{"_id":"261"}}
{"account_number":261,"balance":39998,"firstname":"Millicent","lastname":"Pickett","age":34,"gender":"F","address":"722 Montieth Street","employer":"Gushkool","email":"millicentpickett@gushkool.com","city":"Norwood","state":"MS"}
{"index":{"_id":"266"}}
{"account_number":266,"balance":2777,"firstname":"Monique","lastname":"Conner","age":35,"gender":"F","address":"489 Metrotech Courtr","employer":"Flotonic","email":"moniqueconner@flotonic.com","city":"Retsof","state":"MD"}
{"index":{"_id":"273"}}
{"account_number":273,"balance":11181,"firstname":"Murphy","lastname":"Chandler","age":20,"gender":"F","address":"569 Bradford Street","employer":"Zilch","email":"murphychandler@zilch.com","city":"Vicksburg","state":"FL"}
{"index":{"_id":"278"}}
{"account_number":278,"balance":22530,"firstname":"Tamra","lastname":"Navarro","age":27,"gender":"F","address":"175 Woodruff Avenue","employer":"Norsul","email":"tamranavarro@norsul.com","city":"Glasgow","state":"VT"}
{"index":{"_id":"280"}}
{"account_number":280,"balance":3380,"firstname":"Vilma","lastname":"Shields","age":26,"gender":"F","address":"133 Berriman Street","employer":"Applidec","email":"vilmashields@applidec.com","city":"Adamstown","state":"ME"}
{"index":{"_id":"285"}}
{"account_number":285,"balance":47369,"firstname":"Hilda","lastname":"Phillips","age":28,"gender":"F","address":"618 Nixon Court","employer":"Comcur","email":"hildaphillips@comcur.com","city":"Siglerville","state":"NC"}
{"index":{"_id":"292"}}
{"account_number":292,"balance":26679,"firstname":"Morrow","lastname":"Greene","age":20,"gender":"F","address":"691 Nassau Street","employer":"Columella","email":"morrowgreene@columella.com","city":"Sanborn","state":"FL"}
{"index":{"_id":"297"}}
{"account_number":297,"balance":20508,"firstname":"Tucker","lastname":"Patrick","age":35,"gender":"F","address":"978 Whitwell Place","employer":"Valreda","email":"tuckerpatrick@valreda.com","city":"Deseret","state":"CO"}
{"index":{"_id":"300"}}
{"account_number":300,"balance":25654,"firstname":"Lane","lastname":"Tate","age":26,"gender":"F","address":"632 Kay Court","employer":"Genesynk","email":"lanetate@genesynk.com","city":"Lowell","state":"MO"}
{"index":{"_id":"305"}}
{"account_number":305,"balance":11655,"firstname":"Augusta","lastname":"Winters","age":29,"gender":"F","address":"377 Paerdegat Avenue","employer":"Vendblend","email":"augustawinters@vendblend.com","city":"Gwynn","state":"MA"}
{"index":{"_id":"312"}}
{"account_number":312,"balance":8511,"firstname":"Burgess","lastname":"Gentry","age":25,"gender":"F","address":"382 Bergen Court","employer":"Orbixtar","email":"burgessgentry@orbixtar.com","city":"Conestoga","state":"WI"}
{"index":{"_id":"317"}}
{"account_number":317,"balance":31968,"firstname":"Ruiz","lastname":"Morris","age":31,"gender":"F","address":"972 Dean Street","employer":"Apex","email":"ruizmorris@apex.com","city":"Jacksonwald","state":"WV"}
{"index":{"_id":"324"}}
{"account_number":324,"balance":44976,"firstname":"Gladys","lastname":"Erickson","age":22,"gender":"M","address":"250 Battery Avenue","employer":"Eternis","email":"gladyserickson@eternis.com","city":"Marne","state":"IA"}
{"index":{"_id":"329"}}
{"account_number":329,"balance":31138,"firstname":"Nellie","lastname":"Mercer","age":25,"gender":"M","address":"967 Ebony Court","employer":"Scenty","email":"nelliemercer@scenty.com","city":"Jardine","state":"AK"}
{"index":{"_id":"331"}}
{"account_number":331,"balance":46004,"firstname":"Gibson","lastname":"Potts","age":34,"gender":"F","address":"994 Dahill Road","employer":"Zensus","email":"gibsonpotts@zensus.com","city":"Frizzleburg","state":"CO"}
{"index":{"_id":"336"}}
{"account_number":336,"balance":40891,"firstname":"Dudley","lastname":"Avery","age":25,"gender":"M","address":"405 Powers Street","employer":"Genmom","email":"dudleyavery@genmom.com","city":"Clarksburg","state":"CO"}
{"index":{"_id":"343"}}
{"account_number":343,"balance":37684,"firstname":"Robbie","lastname":"Logan","age":29,"gender":"M","address":"488 Linden Boulevard","employer":"Hydrocom","email":"robbielogan@hydrocom.com","city":"Stockdale","state":"TN"}
{"index":{"_id":"348"}}
{"account_number":348,"balance":1360,"firstname":"Karina","lastname":"Russell","age":37,"gender":"M","address":"797 Moffat Street","employer":"Limozen","email":"karinarussell@limozen.com","city":"Riegelwood","state":"RI"}
{"index":{"_id":"350"}}
{"account_number":350,"balance":4267,"firstname":"Wyatt","lastname":"Wise","age":22,"gender":"F","address":"896 Bleecker Street","employer":"Rockyard","email":"wyattwise@rockyard.com","city":"Joes","state":"MS"}
{"index":{"_id":"355"}}
{"account_number":355,"balance":40961,"firstname":"Gregory","lastname":"Delacruz","age":38,"gender":"M","address":"876 Cortelyou Road","employer":"Oulu","email":"gregorydelacruz@oulu.com","city":"Waterloo","state":"WV"}
{"index":{"_id":"362"}}
{"account_number":362,"balance":14938,"firstname":"Jimmie","lastname":"Dejesus","age":26,"gender":"M","address":"351 Navy Walk","employer":"Ecolight","email":"jimmiedejesus@ecolight.com","city":"Berlin","state":"ME"}
{"index":{"_id":"367"}}
{"account_number":367,"balance":40458,"firstname":"Elaine","lastname":"Workman","age":20,"gender":"M","address":"188 Ridge Boulevard","employer":"Colaire","email":"elaineworkman@colaire.com","city":"Herbster","state":"AK"}
{"index":{"_id":"374"}}
{"account_number":374,"balance":19521,"firstname":"Blanchard","lastname":"Stein","age":30,"gender":"M","address":"313 Bartlett Street","employer":"Cujo","email":"blanchardstein@cujo.com","city":"Cascades","state":"OR"}
{"index":{"_id":"379"}}
{"account_number":379,"balance":12962,"firstname":"Ruthie","lastname":"Lamb","age":21,"gender":"M","address":"796 Rockaway Avenue","employer":"Incubus","email":"ruthielamb@incubus.com","city":"Hickory","state":"TX"}
{"index":{"_id":"381"}}
{"account_number":381,"balance":40978,"firstname":"Sophie","lastname":"Mays","age":31,"gender":"M","address":"261 Varanda Place","employer":"Uneeq","email":"sophiemays@uneeq.com","city":"Cressey","state":"AR"}
{"index":{"_id":"386"}}
{"account_number":386,"balance":42588,"firstname":"Wallace","lastname":"Barr","age":39,"gender":"F","address":"246 Beverly Road","employer":"Concility","email":"wallacebarr@concility.com","city":"Durham","state":"IN"}
{"index":{"_id":"393"}}
{"account_number":393,"balance":43936,"firstname":"William","lastname":"Kelly","age":24,"gender":"M","address":"178 Lawrence Avenue","employer":"Techtrix","email":"williamkelly@techtrix.com","city":"Orin","state":"PA"}
{"index":{"_id":"398"}}
{"account_number":398,"balance":8543,"firstname":"Leticia","lastname":"Duran","age":35,"gender":"F","address":"305 Senator Street","employer":"Xleen","email":"leticiaduran@xleen.com","city":"Cavalero","state":"PA"}
{"index":{"_id":"401"}}
{"account_number":401,"balance":29408,"firstname":"Contreras","lastname":"Randolph","age":38,"gender":"M","address":"104 Lewis Avenue","employer":"Inrt","email":"contrerasrandolph@inrt.com","city":"Chesapeake","state":"CT"}
{"index":{"_id":"406"}}
{"account_number":406,"balance":28127,"firstname":"Mccarthy","lastname":"Dunlap","age":28,"gender":"F","address":"684 Seacoast Terrace","employer":"Canopoly","email":"mccarthydunlap@canopoly.com","city":"Elliott","state":"NC"}
{"index":{"_id":"413"}}
{"account_number":413,"balance":15631,"firstname":"Pugh","lastname":"Hamilton","age":39,"gender":"F","address":"124 Euclid Avenue","employer":"Techade","email":"pughhamilton@techade.com","city":"Beaulieu","state":"CA"}
{"index":{"_id":"418"}}
{"account_number":418,"balance":10207,"firstname":"Reed","lastname":"Goff","age":32,"gender":"M","address":"959 Everit Street","employer":"Zillan","email":"reedgoff@zillan.com","city":"Hiko","state":"WV"}
{"index":{"_id":"420"}}
{"account_number":420,"balance":44699,"firstname":"Brandie","lastname":"Hayden","age":22,"gender":"M","address":"291 Ash Street","employer":"Digifad","email":"brandiehayden@digifad.com","city":"Spelter","state":"NM"}
{"index":{"_id":"425"}}
{"account_number":425,"balance":41308,"firstname":"Queen","lastname":"Leach","age":30,"gender":"M","address":"105 Fair Street","employer":"Magneato","email":"queenleach@magneato.com","city":"Barronett","state":"NH"}
{"index":{"_id":"432"}}
{"account_number":432,"balance":28969,"firstname":"Preston","lastname":"Ferguson","age":40,"gender":"F","address":"239 Greenwood Avenue","employer":"Bitendrex","email":"prestonferguson@bitendrex.com","city":"Idledale","state":"ND"}
{"index":{"_id":"437"}}
{"account_number":437,"balance":41225,"firstname":"Rosales","lastname":"Marquez","age":29,"gender":"M","address":"873 Ryerson Street","employer":"Ronelon","email":"rosalesmarquez@ronelon.com","city":"Allendale","state":"CA"}
{"index":{"_id":"444"}}
{"account_number":444,"balance":44219,"firstname":"Dolly","lastname":"Finch","age":24,"gender":"F","address":"974 Interborough Parkway","employer":"Zytrac","email":"dollyfinch@zytrac.com","city":"Vowinckel","state":"WY"}
{"index":{"_id":"449"}}
{"account_number":449,"balance":41950,"firstname":"Barnett","lastname":"Cantrell","age":39,"gender":"F","address":"945 Bedell Lane","employer":"Zentility","email":"barnettcantrell@zentility.com","city":"Swartzville","state":"ND"}
{"index":{"_id":"451"}}
{"account_number":451,"balance":31950,"firstname":"Mason","lastname":"Mcleod","age":31,"gender":"F","address":"438 Havemeyer Street","employer":"Omatom","email":"masonmcleod@omatom.com","city":"Ryderwood","state":"NE"}
{"index":{"_id":"456"}}
{"account_number":456,"balance":21419,"firstname":"Solis","lastname":"Kline","age":33,"gender":"M","address":"818 Ashford Street","employer":"Vetron","email":"soliskline@vetron.com","city":"Ruffin","state":"NY"}
{"index":{"_id":"463"}}
{"account_number":463,"balance":36672,"firstname":"Heidi","lastname":"Acosta","age":20,"gender":"F","address":"692 Kenmore Terrace","employer":"Elpro","email":"heidiacosta@elpro.com","city":"Ezel","state":"SD"}
{"index":{"_id":"468"}}
{"account_number":468,"balance":18400,"firstname":"Foreman","lastname":"Fowler","age":40,"gender":"M","address":"443 Jackson Court","employer":"Zillactic","email":"foremanfowler@zillactic.com","city":"Wakarusa","state":"WA"}
{"index":{"_id":"470"}}
{"account_number":470,"balance":20455,"firstname":"Schneider","lastname":"Hull","age":35,"gender":"M","address":"724 Apollo Street","employer":"Exospeed","email":"schneiderhull@exospeed.com","city":"Watchtower","state":"ID"}
{"index":{"_id":"475"}}
{"account_number":475,"balance":24427,"firstname":"Morales","lastname":"Jacobs","age":22,"gender":"F","address":"225 Desmond Court","employer":"Oronoko","email":"moralesjacobs@oronoko.com","city":"Clayville","state":"CT"}
{"index":{"_id":"482"}}
{"account_number":482,"balance":14834,"firstname":"Janie","lastname":"Bass","age":39,"gender":"M","address":"781 Grattan Street","employer":"Manglo","email":"janiebass@manglo.com","city":"Kenwood","state":"IA"}
{"index":{"_id":"487"}}
{"account_number":487,"balance":30718,"firstname":"Sawyer","lastname":"Vincent","age":26,"gender":"F","address":"238 Lancaster Avenue","employer":"Brainquil","email":"sawyervincent@brainquil.com","city":"Galesville","state":"MS"}
{"index":{"_id":"494"}}
{"account_number":494,"balance":3592,"firstname":"Holden","lastname":"Bowen","age":30,"gender":"M","address":"374 Elmwood Avenue","employer":"Endipine","email":"holdenbowen@endipine.com","city":"Rosine","state":"ID"}
{"index":{"_id":"499"}}
{"account_number":499,"balance":26060,"firstname":"Lara","lastname":"Perkins","age":26,"gender":"M","address":"703 Monroe Street","employer":"Paprikut","email":"laraperkins@paprikut.com","city":"Barstow","state":"NY"}
{"index":{"_id":"502"}}
{"account_number":502,"balance":31898,"firstname":"Woodard","lastname":"Bailey","age":31,"gender":"F","address":"585 Albee Square","employer":"Imperium","email":"woodardbailey@imperium.com","city":"Matheny","state":"MT"}
{"index":{"_id":"507"}}
{"account_number":507,"balance":27675,"firstname":"Blankenship","lastname":"Ramirez","age":31,"gender":"M","address":"630 Graham Avenue","employer":"Bytrex","email":"blankenshipramirez@bytrex.com","city":"Bancroft","state":"CT"}
{"index":{"_id":"514"}}
{"account_number":514,"balance":30125,"firstname":"Solomon","lastname":"Bush","age":34,"gender":"M","address":"409 Harkness Avenue","employer":"Snacktion","email":"solomonbush@snacktion.com","city":"Grayhawk","state":"TX"}
{"index":{"_id":"519"}}
{"account_number":519,"balance":3282,"firstname":"Lorna","lastname":"Franco","age":31,"gender":"F","address":"722 Schenck Court","employer":"Zentia","email":"lornafranco@zentia.com","city":"National","state":"FL"}
{"index":{"_id":"521"}}
{"account_number":521,"balance":16348,"firstname":"Josefa","lastname":"Buckley","age":34,"gender":"F","address":"848 Taylor Street","employer":"Mazuda","email":"josefabuckley@mazuda.com","city":"Saranap","state":"NM"}
{"index":{"_id":"526"}}
{"account_number":526,"balance":35375,"firstname":"Sweeney","lastname":"Fulton","age":33,"gender":"F","address":"550 Martense Street","employer":"Cormoran","email":"sweeneyfulton@cormoran.com","city":"Chalfant","state":"IA"}
{"index":{"_id":"533"}}
{"account_number":533,"balance":13761,"firstname":"Margarita","lastname":"Diaz","age":23,"gender":"M","address":"295 Tapscott Street","employer":"Zilodyne","email":"margaritadiaz@zilodyne.com","city":"Hondah","state":"ID"}
{"index":{"_id":"538"}}
{"account_number":538,"balance":16416,"firstname":"Koch","lastname":"Barker","age":21,"gender":"M","address":"919 Gerry Street","employer":"Xplor","email":"kochbarker@xplor.com","city":"Dixie","state":"WY"}
{"index":{"_id":"540"}}
{"account_number":540,"balance":40235,"firstname":"Tammy","lastname":"Wiggins","age":32,"gender":"F","address":"186 Schenectady Avenue","employer":"Speedbolt","email":"tammywiggins@speedbolt.com","city":"Salvo","state":"LA"}
{"index":{"_id":"545"}}
{"account_number":545,"balance":27011,"firstname":"Lena","lastname":"Lucas","age":20,"gender":"M","address":"110 Lamont Court","employer":"Kindaloo","email":"lenalucas@kindaloo.com","city":"Harleigh","state":"KY"}
{"index":{"_id":"552"}}
{"account_number":552,"balance":14727,"firstname":"Kate","lastname":"Estes","age":39,"gender":"M","address":"785 Willmohr Street","employer":"Rodeocean","email":"kateestes@rodeocean.com","city":"Elfrida","state":"HI"}
{"index":{"_id":"557"}}
{"account_number":557,"balance":3119,"firstname":"Landry","lastname":"Buck","age":20,"gender":"M","address":"558 Schweikerts Walk","employer":"Protodyne","email":"landrybuck@protodyne.com","city":"Edneyville","state":"AL"}
{"index":{"_id":"564"}}
{"account_number":564,"balance":43631,"firstname":"Owens","lastname":"Bowers","age":22,"gender":"M","address":"842 Congress Street","employer":"Nspire","email":"owensbowers@nspire.com","city":"Machias","state":"VA"}
{"index":{"_id":"569"}}
{"account_number":569,"balance":40019,"firstname":"Sherri","lastname":"Rowe","age":39,"gender":"F","address":"591 Arlington Place","employer":"Netility","email":"sherrirowe@netility.com","city":"Bridgetown","state":"SC"}
{"index":{"_id":"571"}}
{"account_number":571,"balance":3014,"firstname":"Ayers","lastname":"Duffy","age":28,"gender":"F","address":"721 Wortman Avenue","employer":"Aquasseur","email":"ayersduffy@aquasseur.com","city":"Tilleda","state":"MS"}
{"index":{"_id":"576"}}
{"account_number":576,"balance":29682,"firstname":"Helena","lastname":"Robertson","age":33,"gender":"F","address":"774 Devon Avenue","employer":"Vicon","email":"helenarobertson@vicon.com","city":"Dyckesville","state":"NV"}
{"index":{"_id":"583"}}
{"account_number":583,"balance":26558,"firstname":"Castro","lastname":"West","age":34,"gender":"F","address":"814 Williams Avenue","employer":"Cipromox","email":"castrowest@cipromox.com","city":"Nescatunga","state":"IL"}
{"index":{"_id":"588"}}
{"account_number":588,"balance":43531,"firstname":"Martina","lastname":"Collins","age":31,"gender":"M","address":"301 Anna Court","employer":"Geekwagon","email":"martinacollins@geekwagon.com","city":"Oneida","state":"VA"}
{"index":{"_id":"590"}}
{"account_number":590,"balance":4652,"firstname":"Ladonna","lastname":"Tucker","age":31,"gender":"F","address":"162 Kane Place","employer":"Infotrips","email":"ladonnatucker@infotrips.com","city":"Utting","state":"IA"}
{"index":{"_id":"595"}}
{"account_number":595,"balance":12478,"firstname":"Mccall","lastname":"Britt","age":36,"gender":"F","address":"823 Hill Street","employer":"Cablam","email":"mccallbritt@cablam.com","city":"Vernon","state":"CA"}
{"index":{"_id":"603"}}
{"account_number":603,"balance":28145,"firstname":"Janette","lastname":"Guzman","age":31,"gender":"F","address":"976 Kingston Avenue","employer":"Splinx","email":"janetteguzman@splinx.com","city":"Boomer","state":"NC"}
{"index":{"_id":"608"}}
{"account_number":608,"balance":47091,"firstname":"Carey","lastname":"Whitley","age":32,"gender":"F","address":"976 Lawrence Street","employer":"Poshome","email":"careywhitley@poshome.com","city":"Weogufka","state":"NE"}
{"index":{"_id":"610"}}
{"account_number":610,"balance":40571,"firstname":"Foster","lastname":"Weber","age":24,"gender":"F","address":"323 Rochester Avenue","employer":"Firewax","email":"fosterweber@firewax.com","city":"Winston","state":"NY"}
{"index":{"_id":"615"}}
{"account_number":615,"balance":28726,"firstname":"Delgado","lastname":"Curry","age":28,"gender":"F","address":"706 Butler Street","employer":"Zoxy","email":"delgadocurry@zoxy.com","city":"Gracey","state":"SD"}
{"index":{"_id":"622"}}
{"account_number":622,"balance":9661,"firstname":"Paulette","lastname":"Hartman","age":38,"gender":"M","address":"375 Emerald Street","employer":"Locazone","email":"paulettehartman@locazone.com","city":"Canterwood","state":"OH"}
{"index":{"_id":"627"}}
{"account_number":627,"balance":47546,"firstname":"Crawford","lastname":"Sears","age":37,"gender":"F","address":"686 Eastern Parkway","employer":"Updat","email":"crawfordsears@updat.com","city":"Bison","state":"VT"}
{"index":{"_id":"634"}}
{"account_number":634,"balance":29805,"firstname":"Deloris","lastname":"Levy","age":38,"gender":"M","address":"838 Foster Avenue","employer":"Homelux","email":"delorislevy@homelux.com","city":"Kempton","state":"PA"}
{"index":{"_id":"639"}}
{"account_number":639,"balance":28875,"firstname":"Caitlin","lastname":"Clements","age":32,"gender":"F","address":"627 Aster Court","employer":"Bunga","email":"caitlinclements@bunga.com","city":"Cetronia","state":"SC"}
{"index":{"_id":"641"}}
{"account_number":641,"balance":18345,"firstname":"Sheppard","lastname":"Everett","age":39,"gender":"F","address":"791 Norwood Avenue","employer":"Roboid","email":"sheppardeverett@roboid.com","city":"Selma","state":"AK"}
{"index":{"_id":"646"}}
{"account_number":646,"balance":15559,"firstname":"Lavonne","lastname":"Reyes","age":31,"gender":"F","address":"983 Newport Street","employer":"Parcoe","email":"lavonnereyes@parcoe.com","city":"Monument","state":"LA"}
{"index":{"_id":"653"}}
{"account_number":653,"balance":7606,"firstname":"Marcia","lastname":"Bennett","age":33,"gender":"F","address":"455 Bragg Street","employer":"Opticall","email":"marciabennett@opticall.com","city":"Magnolia","state":"NC"}
{"index":{"_id":"658"}}
{"account_number":658,"balance":10210,"firstname":"Bass","lastname":"Mcconnell","age":32,"gender":"F","address":"274 Ocean Avenue","employer":"Combot","email":"bassmcconnell@combot.com","city":"Beyerville","state":"OH"}
{"index":{"_id":"660"}}
{"account_number":660,"balance":46427,"firstname":"Moon","lastname":"Wood","age":33,"gender":"F","address":"916 Amersfort Place","employer":"Olucore","email":"moonwood@olucore.com","city":"Como","state":"VA"}
{"index":{"_id":"665"}}
{"account_number":665,"balance":15215,"firstname":"Britney","lastname":"Young","age":36,"gender":"M","address":"766 Sackman Street","employer":"Geoforma","email":"britneyyoung@geoforma.com","city":"Tuttle","state":"WI"}
{"index":{"_id":"672"}}
{"account_number":672,"balance":12621,"firstname":"Camille","lastname":"Munoz","age":36,"gender":"F","address":"959 Lewis Place","employer":"Vantage","email":"camillemunoz@vantage.com","city":"Whitmer","state":"IN"}
{"index":{"_id":"677"}}
{"account_number":677,"balance":8491,"firstname":"Snider","lastname":"Benton","age":26,"gender":"M","address":"827 Evans Street","employer":"Medicroix","email":"sniderbenton@medicroix.com","city":"Kaka","state":"UT"}
{"index":{"_id":"684"}}
{"account_number":684,"balance":46091,"firstname":"Warren","lastname":"Snow","age":25,"gender":"M","address":"756 Oakland Place","employer":"Bizmatic","email":"warrensnow@bizmatic.com","city":"Hatteras","state":"NE"}
{"index":{"_id":"689"}}
{"account_number":689,"balance":14985,"firstname":"Ines","lastname":"Chaney","age":28,"gender":"M","address":"137 Dikeman Street","employer":"Zidant","email":"ineschaney@zidant.com","city":"Nettie","state":"DC"}
{"index":{"_id":"691"}}
{"account_number":691,"balance":10792,"firstname":"Mclean","lastname":"Colon","age":22,"gender":"M","address":"876 Classon Avenue","employer":"Elentrix","email":"mcleancolon@elentrix.com","city":"Unionville","state":"OK"}
{"index":{"_id":"696"}}
{"account_number":696,"balance":17568,"firstname":"Crane","lastname":"Matthews","age":32,"gender":"F","address":"721 Gerritsen Avenue","employer":"Intradisk","email":"cranematthews@intradisk.com","city":"Brewster","state":"WV"}
{"index":{"_id":"704"}}
{"account_number":704,"balance":45347,"firstname":"Peters","lastname":"Kent","age":22,"gender":"F","address":"871 Independence Avenue","employer":"Extragen","email":"peterskent@extragen.com","city":"Morriston","state":"CA"}
{"index":{"_id":"709"}}
{"account_number":709,"balance":11015,"firstname":"Abbott","lastname":"Odom","age":29,"gender":"M","address":"893 Union Street","employer":"Jimbies","email":"abbottodom@jimbies.com","city":"Leeper","state":"NJ"}
{"index":{"_id":"711"}}
{"account_number":711,"balance":26939,"firstname":"Villarreal","lastname":"Horton","age":35,"gender":"F","address":"861 Creamer Street","employer":"Lexicondo","email":"villarrealhorton@lexicondo.com","city":"Lydia","state":"MS"}
{"index":{"_id":"716"}}
{"account_number":716,"balance":19789,"firstname":"Paul","lastname":"Mason","age":34,"gender":"F","address":"618 Nichols Avenue","employer":"Slax","email":"paulmason@slax.com","city":"Snowville","state":"OK"}
{"index":{"_id":"723"}}
{"account_number":723,"balance":16421,"firstname":"Nixon","lastname":"Moran","age":27,"gender":"M","address":"569 Campus Place","employer":"Cuizine","email":"nixonmoran@cuizine.com","city":"Buxton","state":"DC"}
{"index":{"_id":"728"}}
{"account_number":728,"balance":44818,"firstname":"Conley","lastname":"Preston","age":28,"gender":"M","address":"450 Coventry Road","employer":"Obones","email":"conleypreston@obones.com","city":"Alden","state":"CO"}
{"index":{"_id":"730"}}
{"account_number":730,"balance":41299,"firstname":"Moore","lastname":"Lee","age":30,"gender":"M","address":"797 Turner Place","employer":"Orbean","email":"moorelee@orbean.com","city":"Highland","state":"DE"}
{"index":{"_id":"735"}}
{"account_number":735,"balance":3984,"firstname":"Loraine","lastname":"Willis","age":32,"gender":"F","address":"928 Grove Street","employer":"Gadtron","email":"lorainewillis@gadtron.com","city":"Lowgap","state":"NY"}
{"index":{"_id":"742"}}
{"account_number":742,"balance":24765,"firstname":"Merle","lastname":"Wooten","age":26,"gender":"M","address":"317 Pooles Lane","employer":"Tropolis","email":"merlewooten@tropolis.com","city":"Bentley","state":"ND"}
{"index":{"_id":"747"}}
{"account_number":747,"balance":16617,"firstname":"Diaz","lastname":"Austin","age":38,"gender":"M","address":"676 Harway Avenue","employer":"Irack","email":"diazaustin@irack.com","city":"Cliff","state":"HI"}
{"index":{"_id":"754"}}
{"account_number":754,"balance":10779,"firstname":"Jones","lastname":"Vega","age":25,"gender":"F","address":"795 India Street","employer":"Gluid","email":"jonesvega@gluid.com","city":"Tyhee","state":"FL"}
{"index":{"_id":"759"}}
{"account_number":759,"balance":38007,"firstname":"Rose","lastname":"Carlson","age":27,"gender":"M","address":"987 Navy Street","employer":"Aquasure","email":"rosecarlson@aquasure.com","city":"Carlton","state":"CT"}
{"index":{"_id":"761"}}
{"account_number":761,"balance":7663,"firstname":"Rae","lastname":"Juarez","age":34,"gender":"F","address":"560 Gilmore Court","employer":"Entropix","email":"raejuarez@entropix.com","city":"Northchase","state":"ID"}
{"index":{"_id":"766"}}
{"account_number":766,"balance":21957,"firstname":"Thomas","lastname":"Gillespie","age":38,"gender":"M","address":"993 Williams Place","employer":"Octocore","email":"thomasgillespie@octocore.com","city":"Defiance","state":"MS"}
{"index":{"_id":"773"}}
{"account_number":773,"balance":31126,"firstname":"Liza","lastname":"Coffey","age":36,"gender":"F","address":"540 Bulwer Place","employer":"Assurity","email":"lizacoffey@assurity.com","city":"Gilgo","state":"WV"}
{"index":{"_id":"778"}}
{"account_number":778,"balance":46007,"firstname":"Underwood","lastname":"Wheeler","age":28,"gender":"M","address":"477 Provost Street","employer":"Decratex","email":"underwoodwheeler@decratex.com","city":"Sardis","state":"ID"}
{"index":{"_id":"780"}}
{"account_number":780,"balance":4682,"firstname":"Maryanne","lastname":"Hendricks","age":26,"gender":"F","address":"709 Wolcott Street","employer":"Sarasonic","email":"maryannehendricks@sarasonic.com","city":"Santel","state":"NH"}
{"index":{"_id":"785"}}
{"account_number":785,"balance":25078,"firstname":"Fields","lastname":"Lester","age":29,"gender":"M","address":"808 Chestnut Avenue","employer":"Visualix","email":"fieldslester@visualix.com","city":"Rowe","state":"PA"}
{"index":{"_id":"792"}}
{"account_number":792,"balance":13109,"firstname":"Becky","lastname":"Jimenez","age":40,"gender":"F","address":"539 Front Street","employer":"Isologia","email":"beckyjimenez@isologia.com","city":"Summertown","state":"MI"}
{"index":{"_id":"797"}}
{"account_number":797,"balance":6854,"firstname":"Lindsay","lastname":"Mills","age":26,"gender":"F","address":"919 Quay Street","employer":"Zoinage","email":"lindsaymills@zoinage.com","city":"Elliston","state":"VA"}
{"index":{"_id":"800"}}
{"account_number":800,"balance":26217,"firstname":"Candy","lastname":"Oconnor","age":28,"gender":"M","address":"200 Newel Street","employer":"Radiantix","email":"candyoconnor@radiantix.com","city":"Sandston","state":"OH"}
{"index":{"_id":"805"}}
{"account_number":805,"balance":18426,"firstname":"Jackson","lastname":"Sampson","age":27,"gender":"F","address":"722 Kenmore Court","employer":"Daido","email":"jacksonsampson@daido.com","city":"Bellamy","state":"ME"}
{"index":{"_id":"812"}}
{"account_number":812,"balance":42593,"firstname":"Graves","lastname":"Newman","age":32,"gender":"F","address":"916 Joralemon Street","employer":"Ecrater","email":"gravesnewman@ecrater.com","city":"Crown","state":"PA"}
{"index":{"_id":"817"}}
{"account_number":817,"balance":36582,"firstname":"Padilla","lastname":"Bauer","age":36,"gender":"F","address":"310 Cadman Plaza","employer":"Exoblue","email":"padillabauer@exoblue.com","city":"Ahwahnee","state":"MN"}
{"index":{"_id":"824"}}
{"account_number":824,"balance":6053,"firstname":"Dyer","lastname":"Henson","age":33,"gender":"M","address":"650 Seaview Avenue","employer":"Nitracyr","email":"dyerhenson@nitracyr.com","city":"Gibsonia","state":"KS"}
{"index":{"_id":"829"}}
{"account_number":829,"balance":20263,"firstname":"Althea","lastname":"Bell","age":37,"gender":"M","address":"319 Cook Street","employer":"Hyplex","email":"altheabell@hyplex.com","city":"Wadsworth","state":"DC"}
{"index":{"_id":"831"}}
{"account_number":831,"balance":25375,"firstname":"Wendy","lastname":"Savage","age":37,"gender":"M","address":"421 Veranda Place","employer":"Neurocell","email":"wendysavage@neurocell.com","city":"Fresno","state":"MS"}
{"index":{"_id":"836"}}
{"account_number":836,"balance":20797,"firstname":"Lloyd","lastname":"Lindsay","age":25,"gender":"F","address":"953 Dinsmore Place","employer":"Suretech","email":"lloydlindsay@suretech.com","city":"Conway","state":"VA"}
{"index":{"_id":"843"}}
{"account_number":843,"balance":15555,"firstname":"Patricia","lastname":"Barton","age":34,"gender":"F","address":"406 Seabring Street","employer":"Providco","email":"patriciabarton@providco.com","city":"Avoca","state":"RI"}
{"index":{"_id":"848"}}
{"account_number":848,"balance":15443,"firstname":"Carmella","lastname":"Cash","age":38,"gender":"M","address":"988 Exeter Street","employer":"Bristo","email":"carmellacash@bristo.com","city":"Northridge","state":"ID"}
{"index":{"_id":"850"}}
{"account_number":850,"balance":6531,"firstname":"Carlene","lastname":"Gaines","age":37,"gender":"F","address":"753 Monroe Place","employer":"Naxdis","email":"carlenegaines@naxdis.com","city":"Genoa","state":"OR"}
{"index":{"_id":"855"}}
{"account_number":855,"balance":40170,"firstname":"Mia","lastname":"Stevens","age":31,"gender":"F","address":"326 Driggs Avenue","employer":"Aeora","email":"miastevens@aeora.com","city":"Delwood","state":"IL"}
{"index":{"_id":"862"}}
{"account_number":862,"balance":38792,"firstname":"Clayton","lastname":"Golden","age":38,"gender":"F","address":"620 Regent Place","employer":"Accusage","email":"claytongolden@accusage.com","city":"Ona","state":"NC"}
{"index":{"_id":"867"}}
{"account_number":867,"balance":45453,"firstname":"Blanca","lastname":"Ellison","age":23,"gender":"F","address":"593 McKibben Street","employer":"Koogle","email":"blancaellison@koogle.com","city":"Frystown","state":"WY"}
{"index":{"_id":"874"}}
{"account_number":874,"balance":23079,"firstname":"Lynette","lastname":"Higgins","age":22,"gender":"M","address":"377 McKinley Avenue","employer":"Menbrain","email":"lynettehiggins@menbrain.com","city":"Manitou","state":"TX"}
{"index":{"_id":"879"}}
{"account_number":879,"balance":48332,"firstname":"Sabrina","lastname":"Lancaster","age":31,"gender":"F","address":"382 Oak Street","employer":"Webiotic","email":"sabrinalancaster@webiotic.com","city":"Lindisfarne","state":"AZ"}
{"index":{"_id":"881"}}
{"account_number":881,"balance":26684,"firstname":"Barnes","lastname":"Ware","age":38,"gender":"F","address":"666 Hooper Street","employer":"Norali","email":"barnesware@norali.com","city":"Cazadero","state":"GA"}
{"index":{"_id":"886"}}
{"account_number":886,"balance":14867,"firstname":"Willa","lastname":"Leblanc","age":38,"gender":"F","address":"773 Bergen Street","employer":"Nurali","email":"willaleblanc@nurali.com","city":"Hilltop","state":"NC"}
{"index":{"_id":"893"}}
{"account_number":893,"balance":42584,"firstname":"Moses","lastname":"Campos","age":38,"gender":"F","address":"991 Bevy Court","employer":"Trollery","email":"mosescampos@trollery.com","city":"Freetown","state":"AK"}
{"index":{"_id":"898"}}
{"account_number":898,"balance":12019,"firstname":"Lori","lastname":"Stevenson","age":29,"gender":"M","address":"910 Coles Street","employer":"Honotron","email":"loristevenson@honotron.com","city":"Shindler","state":"VT"}
{"index":{"_id":"901"}}
{"account_number":901,"balance":35038,"firstname":"Irma","lastname":"Dotson","age":23,"gender":"F","address":"245 Mayfair Drive","employer":"Bleeko","email":"irmadotson@bleeko.com","city":"Lodoga","state":"UT"}
{"index":{"_id":"906"}}
{"account_number":906,"balance":24073,"firstname":"Vicki","lastname":"Suarez","age":36,"gender":"M","address":"829 Roosevelt Place","employer":"Utara","email":"vickisuarez@utara.com","city":"Albrightsville","state":"AR"}
{"index":{"_id":"913"}}
{"account_number":913,"balance":47657,"firstname":"Margery","lastname":"Monroe","age":25,"gender":"M","address":"941 Fanchon Place","employer":"Exerta","email":"margerymonroe@exerta.com","city":"Bannock","state":"MD"}
{"index":{"_id":"918"}}
{"account_number":918,"balance":36776,"firstname":"Dianna","lastname":"Hernandez","age":25,"gender":"M","address":"499 Moultrie Street","employer":"Isologica","email":"diannahernandez@isologica.com","city":"Falconaire","state":"ID"}
{"index":{"_id":"920"}}
{"account_number":920,"balance":41513,"firstname":"Jerri","lastname":"Mitchell","age":26,"gender":"M","address":"831 Kent Street","employer":"Tasmania","email":"jerrimitchell@tasmania.com","city":"Cotopaxi","state":"IA"}
{"index":{"_id":"925"}}
{"account_number":925,"balance":18295,"firstname":"Rosario","lastname":"Jackson","age":24,"gender":"M","address":"178 Leonora Court","employer":"Progenex","email":"rosariojackson@progenex.com","city":"Rivereno","state":"DE"}
{"index":{"_id":"932"}}
{"account_number":932,"balance":3111,"firstname":"Summer","lastname":"Porter","age":33,"gender":"F","address":"949 Grand Avenue","employer":"Multiflex","email":"summerporter@multiflex.com","city":"Spokane","state":"OK"}
{"index":{"_id":"937"}}
{"account_number":937,"balance":43491,"firstname":"Selma","lastname":"Anderson","age":24,"gender":"M","address":"205 Reed Street","employer":"Dadabase","email":"selmaanderson@dadabase.com","city":"Malo","state":"AL"}
{"index":{"_id":"944"}}
{"account_number":944,"balance":46478,"firstname":"Donaldson","lastname":"Woodard","age":38,"gender":"F","address":"498 Laurel Avenue","employer":"Zogak","email":"donaldsonwoodard@zogak.com","city":"Hasty","state":"ID"}
{"index":{"_id":"949"}}
{"account_number":949,"balance":48703,"firstname":"Latasha","lastname":"Mullins","age":29,"gender":"F","address":"272 Lefferts Place","employer":"Zenolux","email":"latashamullins@zenolux.com","city":"Kieler","state":"MN"}
{"index":{"_id":"951"}}
{"account_number":951,"balance":36337,"firstname":"Tran","lastname":"Burris","age":25,"gender":"F","address":"561 Rutland Road","employer":"Geoform","email":"tranburris@geoform.com","city":"Longbranch","state":"IL"}
{"index":{"_id":"956"}}
{"account_number":956,"balance":19477,"firstname":"Randall","lastname":"Lynch","age":22,"gender":"F","address":"490 Madison Place","employer":"Cosmetex","email":"randalllynch@cosmetex.com","city":"Wells","state":"SD"}
{"index":{"_id":"963"}}
{"account_number":963,"balance":30461,"firstname":"Griffin","lastname":"Sheppard","age":20,"gender":"M","address":"682 Linden Street","employer":"Zanymax","email":"griffinsheppard@zanymax.com","city":"Fannett","state":"NM"}
{"index":{"_id":"968"}}
{"account_number":968,"balance":32371,"firstname":"Luella","lastname":"Burch","age":39,"gender":"M","address":"684 Arkansas Drive","employer":"Krag","email":"luellaburch@krag.com","city":"Brambleton","state":"SD"}
{"index":{"_id":"970"}}
{"account_number":970,"balance":19648,"firstname":"Forbes","lastname":"Wallace","age":28,"gender":"M","address":"990 Mill Road","employer":"Pheast","email":"forbeswallace@pheast.com","city":"Lopezo","state":"AK"}
{"index":{"_id":"975"}}
{"account_number":975,"balance":5239,"firstname":"Delores","lastname":"Booker","age":27,"gender":"F","address":"328 Conselyea Street","employer":"Centice","email":"deloresbooker@centice.com","city":"Williams","state":"HI"}
{"index":{"_id":"982"}}
{"account_number":982,"balance":16511,"firstname":"Buck","lastname":"Robinson","age":24,"gender":"M","address":"301 Melrose Street","employer":"Calcu","email":"buckrobinson@calcu.com","city":"Welch","state":"PA"}
{"index":{"_id":"987"}}
{"account_number":987,"balance":4072,"firstname":"Brock","lastname":"Sandoval","age":20,"gender":"F","address":"977 Gem Street","employer":"Fiberox","email":"brocksandoval@fiberox.com","city":"Celeryville","state":"NY"}
{"index":{"_id":"994"}}
{"account_number":994,"balance":33298,"firstname":"Madge","lastname":"Holcomb","age":31,"gender":"M","address":"612 Hawthorne Street","employer":"Escenta","email":"madgeholcomb@escenta.com","city":"Alafaya","state":"OR"}
{"index":{"_id":"999"}}
{"account_number":999,"balance":6087,"firstname":"Dorothy","lastname":"Barron","age":22,"gender":"F","address":"499 Laurel Avenue","employer":"Xurban","email":"dorothybarron@xurban.com","city":"Belvoir","state":"CA"}
{"index":{"_id":"4"}}
{"account_number":4,"balance":27658,"firstname":"Rodriquez","lastname":"Flores","age":31,"gender":"F","address":"986 Wyckoff Avenue","employer":"Tourmania","email":"rodriquezflores@tourmania.com","city":"Eastvale","state":"HI"}
{"index":{"_id":"9"}}
{"account_number":9,"balance":24776,"firstname":"Opal","lastname":"Meadows","age":39,"gender":"M","address":"963 Neptune Avenue","employer":"Cedward","email":"opalmeadows@cedward.com","city":"Olney","state":"OH"}
{"index":{"_id":"11"}}
{"account_number":11,"balance":20203,"firstname":"Jenkins","lastname":"Haney","age":20,"gender":"M","address":"740 Ferry Place","employer":"Qimonk","email":"jenkinshaney@qimonk.com","city":"Steinhatchee","state":"GA"}
{"index":{"_id":"16"}}
{"account_number":16,"balance":35883,"firstname":"Adrian","lastname":"Pitts","age":34,"gender":"F","address":"963 Fay Court","employer":"Combogene","email":"adrianpitts@combogene.com","city":"Remington","state":"SD"}
{"index":{"_id":"23"}}
{"account_number":23,"balance":42374,"firstname":"Kirsten","lastname":"Fox","age":20,"gender":"M","address":"330 Dumont Avenue","employer":"Codax","email":"kirstenfox@codax.com","city":"Walton","state":"AK"}
{"index":{"_id":"28"}}
{"account_number":28,"balance":42112,"firstname":"Vega","lastname":"Flynn","age":20,"gender":"M","address":"647 Hyman Court","employer":"Accupharm","email":"vegaflynn@accupharm.com","city":"Masthope","state":"OH"}
{"index":{"_id":"30"}}
{"account_number":30,"balance":19087,"firstname":"Lamb","lastname":"Townsend","age":26,"gender":"M","address":"169 Lyme Avenue","employer":"Geeknet","email":"lambtownsend@geeknet.com","city":"Epworth","state":"AL"}
{"index":{"_id":"35"}}
{"account_number":35,"balance":42039,"firstname":"Darla","lastname":"Bridges","age":27,"gender":"F","address":"315 Central Avenue","employer":"Xeronk","email":"darlabridges@xeronk.com","city":"Woodlake","state":"RI"}
{"index":{"_id":"42"}}
{"account_number":42,"balance":21137,"firstname":"Harding","lastname":"Hobbs","age":26,"gender":"F","address":"474 Ridgewood Place","employer":"Xth","email":"hardinghobbs@xth.com","city":"Heil","state":"ND"}
{"index":{"_id":"47"}}
{"account_number":47,"balance":33044,"firstname":"Georgia","lastname":"Wilkerson","age":23,"gender":"M","address":"369 Herbert Street","employer":"Endipin","email":"georgiawilkerson@endipin.com","city":"Dellview","state":"WI"}
{"index":{"_id":"54"}}
{"account_number":54,"balance":23406,"firstname":"Angel","lastname":"Mann","age":22,"gender":"F","address":"229 Ferris Street","employer":"Amtas","email":"angelmann@amtas.com","city":"Calverton","state":"WA"}
{"index":{"_id":"59"}}
{"account_number":59,"balance":37728,"firstname":"Malone","lastname":"Justice","age":37,"gender":"F","address":"721 Russell Street","employer":"Emoltra","email":"malonejustice@emoltra.com","city":"Trucksville","state":"HI"}
{"index":{"_id":"61"}}
{"account_number":61,"balance":6856,"firstname":"Shawn","lastname":"Baird","age":20,"gender":"M","address":"605 Monument Walk","employer":"Moltonic","email":"shawnbaird@moltonic.com","city":"Darlington","state":"MN"}
{"index":{"_id":"66"}}
{"account_number":66,"balance":25939,"firstname":"Franks","lastname":"Salinas","age":28,"gender":"M","address":"437 Hamilton Walk","employer":"Cowtown","email":"frankssalinas@cowtown.com","city":"Chase","state":"VT"}
{"index":{"_id":"73"}}
{"account_number":73,"balance":33457,"firstname":"Irene","lastname":"Stephenson","age":32,"gender":"M","address":"684 Miller Avenue","employer":"Hawkster","email":"irenestephenson@hawkster.com","city":"Levant","state":"AR"}
{"index":{"_id":"78"}}
{"account_number":78,"balance":48656,"firstname":"Elvira","lastname":"Patterson","age":23,"gender":"F","address":"834 Amber Street","employer":"Assistix","email":"elvirapatterson@assistix.com","city":"Dunbar","state":"TN"}
{"index":{"_id":"80"}}
{"account_number":80,"balance":13445,"firstname":"Lacey","lastname":"Blanchard","age":30,"gender":"F","address":"823 Himrod Street","employer":"Comdom","email":"laceyblanchard@comdom.com","city":"Matthews","state":"MO"}
{"index":{"_id":"85"}}
{"account_number":85,"balance":48735,"firstname":"Wilcox","lastname":"Sellers","age":20,"gender":"M","address":"212 Irving Avenue","employer":"Confrenzy","email":"wilcoxsellers@confrenzy.com","city":"Kipp","state":"MT"}
{"index":{"_id":"92"}}
{"account_number":92,"balance":26753,"firstname":"Gay","lastname":"Brewer","age":34,"gender":"M","address":"369 Ditmars Street","employer":"Savvy","email":"gaybrewer@savvy.com","city":"Moquino","state":"HI"}
{"index":{"_id":"97"}}
{"account_number":97,"balance":49671,"firstname":"Karen","lastname":"Trujillo","age":40,"gender":"F","address":"512 Cumberland Walk","employer":"Tsunamia","email":"karentrujillo@tsunamia.com","city":"Fredericktown","state":"MO"}
{"index":{"_id":"100"}}
{"account_number":100,"balance":29869,"firstname":"Madden","lastname":"Woods","age":32,"gender":"F","address":"696 Ryder Avenue","employer":"Slumberia","email":"maddenwoods@slumberia.com","city":"Deercroft","state":"ME"}
{"index":{"_id":"105"}}
{"account_number":105,"balance":29654,"firstname":"Castillo","lastname":"Dickerson","age":33,"gender":"F","address":"673 Oxford Street","employer":"Tellifly","email":"castillodickerson@tellifly.com","city":"Succasunna","state":"NY"}
{"index":{"_id":"112"}}
{"account_number":112,"balance":38395,"firstname":"Frederick","lastname":"Case","age":30,"gender":"F","address":"580 Lexington Avenue","employer":"Talkalot","email":"frederickcase@talkalot.com","city":"Orovada","state":"MA"}
{"index":{"_id":"117"}}
{"account_number":117,"balance":48831,"firstname":"Robin","lastname":"Hays","age":38,"gender":"F","address":"347 Hornell Loop","employer":"Pasturia","email":"robinhays@pasturia.com","city":"Sims","state":"WY"}
{"index":{"_id":"124"}}
{"account_number":124,"balance":16425,"firstname":"Fern","lastname":"Lambert","age":20,"gender":"M","address":"511 Jay Street","employer":"Furnitech","email":"fernlambert@furnitech.com","city":"Cloverdale","state":"FL"}
{"index":{"_id":"129"}}
{"account_number":129,"balance":42409,"firstname":"Alexandria","lastname":"Sanford","age":33,"gender":"F","address":"934 Ridgecrest Terrace","employer":"Kyagoro","email":"alexandriasanford@kyagoro.com","city":"Concho","state":"UT"}
{"index":{"_id":"131"}}
{"account_number":131,"balance":28030,"firstname":"Dollie","lastname":"Koch","age":22,"gender":"F","address":"287 Manhattan Avenue","employer":"Skinserve","email":"dolliekoch@skinserve.com","city":"Shasta","state":"PA"}
{"index":{"_id":"136"}}
{"account_number":136,"balance":45801,"firstname":"Winnie","lastname":"Holland","age":38,"gender":"M","address":"198 Mill Lane","employer":"Neteria","email":"winnieholland@neteria.com","city":"Urie","state":"IL"}
{"index":{"_id":"143"}}
{"account_number":143,"balance":43093,"firstname":"Cohen","lastname":"Noble","age":39,"gender":"M","address":"454 Nelson Street","employer":"Buzzworks","email":"cohennoble@buzzworks.com","city":"Norvelt","state":"CO"}
{"index":{"_id":"148"}}
{"account_number":148,"balance":3662,"firstname":"Annmarie","lastname":"Snider","age":34,"gender":"F","address":"857 Lafayette Walk","employer":"Edecine","email":"annmariesnider@edecine.com","city":"Hollins","state":"OH"}
{"index":{"_id":"150"}}
{"account_number":150,"balance":15306,"firstname":"Ortega","lastname":"Dalton","age":20,"gender":"M","address":"237 Mermaid Avenue","employer":"Rameon","email":"ortegadalton@rameon.com","city":"Maxville","state":"NH"}
{"index":{"_id":"155"}}
{"account_number":155,"balance":27878,"firstname":"Atkinson","lastname":"Hudson","age":39,"gender":"F","address":"434 Colin Place","employer":"Qualitern","email":"atkinsonhudson@qualitern.com","city":"Hoehne","state":"OH"}
{"index":{"_id":"162"}}
{"account_number":162,"balance":6302,"firstname":"Griffith","lastname":"Calderon","age":35,"gender":"M","address":"871 Vandervoort Place","employer":"Quotezart","email":"griffithcalderon@quotezart.com","city":"Barclay","state":"FL"}
{"index":{"_id":"167"}}
{"account_number":167,"balance":42051,"firstname":"Hampton","lastname":"Ryan","age":20,"gender":"M","address":"618 Fleet Place","employer":"Zipak","email":"hamptonryan@zipak.com","city":"Irwin","state":"KS"}
{"index":{"_id":"174"}}
{"account_number":174,"balance":1464,"firstname":"Gamble","lastname":"Pierce","age":23,"gender":"F","address":"650 Eagle Street","employer":"Matrixity","email":"gamblepierce@matrixity.com","city":"Abiquiu","state":"OR"}
{"index":{"_id":"179"}}
{"account_number":179,"balance":13265,"firstname":"Elise","lastname":"Drake","age":25,"gender":"M","address":"305 Christopher Avenue","employer":"Turnling","email":"elisedrake@turnling.com","city":"Loretto","state":"LA"}
{"index":{"_id":"181"}}
{"account_number":181,"balance":27983,"firstname":"Bennett","lastname":"Hampton","age":22,"gender":"F","address":"435 Billings Place","employer":"Voipa","email":"bennetthampton@voipa.com","city":"Rodman","state":"WY"}
{"index":{"_id":"186"}}
{"account_number":186,"balance":18373,"firstname":"Kline","lastname":"Joyce","age":32,"gender":"M","address":"285 Falmouth Street","employer":"Tetratrex","email":"klinejoyce@tetratrex.com","city":"Klondike","state":"SD"}
{"index":{"_id":"193"}}
{"account_number":193,"balance":13412,"firstname":"Patty","lastname":"Petty","age":34,"gender":"F","address":"251 Vermont Street","employer":"Kinetica","email":"pattypetty@kinetica.com","city":"Grantville","state":"MS"}
{"index":{"_id":"198"}}
{"account_number":198,"balance":19686,"firstname":"Rachael","lastname":"Sharp","age":38,"gender":"F","address":"443 Vernon Avenue","employer":"Powernet","email":"rachaelsharp@powernet.com","city":"Canoochee","state":"UT"}
{"index":{"_id":"201"}}
{"account_number":201,"balance":14586,"firstname":"Ronda","lastname":"Perry","age":25,"gender":"F","address":"856 Downing Street","employer":"Artiq","email":"rondaperry@artiq.com","city":"Colton","state":"WV"}
{"index":{"_id":"206"}}
{"account_number":206,"balance":47423,"firstname":"Kelli","lastname":"Francis","age":20,"gender":"M","address":"671 George Street","employer":"Exoswitch","email":"kellifrancis@exoswitch.com","city":"Babb","state":"NJ"}
{"index":{"_id":"213"}}
{"account_number":213,"balance":34172,"firstname":"Bauer","lastname":"Summers","age":27,"gender":"M","address":"257 Boynton Place","employer":"Voratak","email":"bauersummers@voratak.com","city":"Oceola","state":"NC"}
{"index":{"_id":"218"}}
{"account_number":218,"balance":26702,"firstname":"Garrison","lastname":"Bryan","age":24,"gender":"F","address":"478 Greenpoint Avenue","employer":"Uniworld","email":"garrisonbryan@uniworld.com","city":"Comptche","state":"WI"}
{"index":{"_id":"220"}}
{"account_number":220,"balance":3086,"firstname":"Tania","lastname":"Middleton","age":22,"gender":"F","address":"541 Gunther Place","employer":"Zerology","email":"taniamiddleton@zerology.com","city":"Linwood","state":"IN"}
{"index":{"_id":"225"}}
{"account_number":225,"balance":21949,"firstname":"Maryann","lastname":"Murphy","age":24,"gender":"F","address":"894 Bridgewater Street","employer":"Cinesanct","email":"maryannmurphy@cinesanct.com","city":"Cartwright","state":"RI"}
{"index":{"_id":"232"}}
{"account_number":232,"balance":11984,"firstname":"Carr","lastname":"Jensen","age":34,"gender":"F","address":"995 Micieli Place","employer":"Biohab","email":"carrjensen@biohab.com","city":"Waikele","state":"OH"}
{"index":{"_id":"237"}}
{"account_number":237,"balance":5603,"firstname":"Kirby","lastname":"Watkins","age":27,"gender":"F","address":"348 Blake Court","employer":"Sonique","email":"kirbywatkins@sonique.com","city":"Freelandville","state":"PA"}
{"index":{"_id":"244"}}
{"account_number":244,"balance":8048,"firstname":"Judith","lastname":"Riggs","age":27,"gender":"F","address":"590 Kosciusko Street","employer":"Arctiq","email":"judithriggs@arctiq.com","city":"Gorham","state":"DC"}
{"index":{"_id":"249"}}
{"account_number":249,"balance":16822,"firstname":"Mckinney","lastname":"Gallagher","age":38,"gender":"F","address":"939 Seigel Court","employer":"Premiant","email":"mckinneygallagher@premiant.com","city":"Catharine","state":"NH"}
{"index":{"_id":"251"}}
{"account_number":251,"balance":13475,"firstname":"Marks","lastname":"Graves","age":39,"gender":"F","address":"427 Lawn Court","employer":"Dentrex","email":"marksgraves@dentrex.com","city":"Waukeenah","state":"IL"}
{"index":{"_id":"256"}}
{"account_number":256,"balance":48318,"firstname":"Simon","lastname":"Hogan","age":31,"gender":"M","address":"789 Suydam Place","employer":"Dancerity","email":"simonhogan@dancerity.com","city":"Dargan","state":"GA"}
{"index":{"_id":"263"}}
{"account_number":263,"balance":12837,"firstname":"Thornton","lastname":"Meyer","age":29,"gender":"M","address":"575 Elliott Place","employer":"Peticular","email":"thorntonmeyer@peticular.com","city":"Dotsero","state":"NH"}
{"index":{"_id":"268"}}
{"account_number":268,"balance":20925,"firstname":"Avis","lastname":"Blackwell","age":36,"gender":"M","address":"569 Jerome Avenue","employer":"Magnina","email":"avisblackwell@magnina.com","city":"Bethany","state":"MD"}
{"index":{"_id":"270"}}
{"account_number":270,"balance":43951,"firstname":"Moody","lastname":"Harmon","age":39,"gender":"F","address":"233 Vanderbilt Street","employer":"Otherside","email":"moodyharmon@otherside.com","city":"Elwood","state":"MT"}
{"index":{"_id":"275"}}
{"account_number":275,"balance":2384,"firstname":"Reynolds","lastname":"Barnett","age":31,"gender":"M","address":"394 Stockton Street","employer":"Austex","email":"reynoldsbarnett@austex.com","city":"Grandview","state":"MS"}
{"index":{"_id":"282"}}
{"account_number":282,"balance":38540,"firstname":"Gay","lastname":"Schultz","age":25,"gender":"F","address":"805 Claver Place","employer":"Handshake","email":"gayschultz@handshake.com","city":"Tampico","state":"MA"}
{"index":{"_id":"287"}}
{"account_number":287,"balance":10845,"firstname":"Valerie","lastname":"Lang","age":35,"gender":"F","address":"423 Midwood Street","employer":"Quarx","email":"valerielang@quarx.com","city":"Cannondale","state":"VT"}
{"index":{"_id":"294"}}
{"account_number":294,"balance":29582,"firstname":"Pitts","lastname":"Haynes","age":26,"gender":"M","address":"901 Broome Street","employer":"Aquazure","email":"pittshaynes@aquazure.com","city":"Turah","state":"SD"}
{"index":{"_id":"299"}}
{"account_number":299,"balance":40825,"firstname":"Angela","lastname":"Talley","age":36,"gender":"F","address":"822 Bills Place","employer":"Remold","email":"angelatalley@remold.com","city":"Bethpage","state":"DC"}
{"index":{"_id":"302"}}
{"account_number":302,"balance":11298,"firstname":"Isabella","lastname":"Hewitt","age":40,"gender":"M","address":"455 Bedford Avenue","employer":"Cincyr","email":"isabellahewitt@cincyr.com","city":"Blanford","state":"IN"}
{"index":{"_id":"307"}}
{"account_number":307,"balance":43355,"firstname":"Enid","lastname":"Ashley","age":23,"gender":"M","address":"412 Emerson Place","employer":"Avenetro","email":"enidashley@avenetro.com","city":"Catherine","state":"WI"}
{"index":{"_id":"314"}}
{"account_number":314,"balance":5848,"firstname":"Norton","lastname":"Norton","age":35,"gender":"M","address":"252 Ditmas Avenue","employer":"Talkola","email":"nortonnorton@talkola.com","city":"Veyo","state":"SC"}
{"index":{"_id":"319"}}
{"account_number":319,"balance":15430,"firstname":"Ferrell","lastname":"Mckinney","age":36,"gender":"M","address":"874 Cranberry Street","employer":"Portaline","email":"ferrellmckinney@portaline.com","city":"Rose","state":"WV"}
{"index":{"_id":"321"}}
{"account_number":321,"balance":43370,"firstname":"Marta","lastname":"Larsen","age":35,"gender":"M","address":"617 Williams Court","employer":"Manufact","email":"martalarsen@manufact.com","city":"Sisquoc","state":"MA"}
{"index":{"_id":"326"}}
{"account_number":326,"balance":9692,"firstname":"Pearl","lastname":"Reese","age":30,"gender":"F","address":"451 Colonial Court","employer":"Accruex","email":"pearlreese@accruex.com","city":"Westmoreland","state":"MD"}
{"index":{"_id":"333"}}
{"account_number":333,"balance":22778,"firstname":"Trudy","lastname":"Sweet","age":27,"gender":"F","address":"881 Kiely Place","employer":"Acumentor","email":"trudysweet@acumentor.com","city":"Kent","state":"IA"}
{"index":{"_id":"338"}}
{"account_number":338,"balance":6969,"firstname":"Pierce","lastname":"Lawrence","age":35,"gender":"M","address":"318 Gallatin Place","employer":"Lunchpad","email":"piercelawrence@lunchpad.com","city":"Iola","state":"MD"}
{"index":{"_id":"340"}}
{"account_number":340,"balance":42072,"firstname":"Juarez","lastname":"Gutierrez","age":40,"gender":"F","address":"802 Seba Avenue","employer":"Billmed","email":"juarezgutierrez@billmed.com","city":"Malott","state":"OH"}
{"index":{"_id":"345"}}
{"account_number":345,"balance":9812,"firstname":"Parker","lastname":"Hines","age":38,"gender":"M","address":"715 Mill Avenue","employer":"Baluba","email":"parkerhines@baluba.com","city":"Blackgum","state":"KY"}
{"index":{"_id":"352"}}
{"account_number":352,"balance":20290,"firstname":"Kendra","lastname":"Mcintosh","age":31,"gender":"F","address":"963 Wolf Place","employer":"Orboid","email":"kendramcintosh@orboid.com","city":"Bladensburg","state":"AK"}
{"index":{"_id":"357"}}
{"account_number":357,"balance":15102,"firstname":"Adele","lastname":"Carroll","age":39,"gender":"F","address":"381 Arion Place","employer":"Aquafire","email":"adelecarroll@aquafire.com","city":"Springville","state":"RI"}
{"index":{"_id":"364"}}
{"account_number":364,"balance":35247,"firstname":"Felicia","lastname":"Merrill","age":40,"gender":"F","address":"229 Branton Street","employer":"Prosely","email":"feliciamerrill@prosely.com","city":"Dola","state":"MA"}
{"index":{"_id":"369"}}
{"account_number":369,"balance":17047,"firstname":"Mcfadden","lastname":"Guy","age":28,"gender":"F","address":"445 Lott Avenue","employer":"Kangle","email":"mcfaddenguy@kangle.com","city":"Greenbackville","state":"DE"}
{"index":{"_id":"371"}}
{"account_number":371,"balance":19751,"firstname":"Barker","lastname":"Allen","age":32,"gender":"F","address":"295 Wallabout Street","employer":"Nexgene","email":"barkerallen@nexgene.com","city":"Nanafalia","state":"NE"}
{"index":{"_id":"376"}}
{"account_number":376,"balance":44407,"firstname":"Mcmillan","lastname":"Dunn","age":21,"gender":"F","address":"771 Dorchester Road","employer":"Eargo","email":"mcmillandunn@eargo.com","city":"Yogaville","state":"RI"}
{"index":{"_id":"383"}}
{"account_number":383,"balance":48889,"firstname":"Knox","lastname":"Larson","age":28,"gender":"F","address":"962 Bartlett Place","employer":"Bostonic","email":"knoxlarson@bostonic.com","city":"Smeltertown","state":"TX"}
{"index":{"_id":"388"}}
{"account_number":388,"balance":9606,"firstname":"Julianne","lastname":"Nicholson","age":26,"gender":"F","address":"338 Crescent Street","employer":"Viasia","email":"juliannenicholson@viasia.com","city":"Alleghenyville","state":"MO"}
{"index":{"_id":"390"}}
{"account_number":390,"balance":7464,"firstname":"Ramona","lastname":"Roy","age":32,"gender":"M","address":"135 Banner Avenue","employer":"Deminimum","email":"ramonaroy@deminimum.com","city":"Dodge","state":"ID"}
{"index":{"_id":"395"}}
{"account_number":395,"balance":18679,"firstname":"Juliet","lastname":"Whitaker","age":31,"gender":"M","address":"128 Remsen Avenue","employer":"Toyletry","email":"julietwhitaker@toyletry.com","city":"Yonah","state":"LA"}
{"index":{"_id":"403"}}
{"account_number":403,"balance":18833,"firstname":"Williamson","lastname":"Horn","age":32,"gender":"M","address":"223 Strickland Avenue","employer":"Nimon","email":"williamsonhorn@nimon.com","city":"Bawcomville","state":"NJ"}
{"index":{"_id":"408"}}
{"account_number":408,"balance":34666,"firstname":"Lidia","lastname":"Guerrero","age":30,"gender":"M","address":"254 Stratford Road","employer":"Snowpoke","email":"lidiaguerrero@snowpoke.com","city":"Fairlee","state":"LA"}
{"index":{"_id":"410"}}
{"account_number":410,"balance":31200,"firstname":"Fox","lastname":"Cardenas","age":39,"gender":"M","address":"987 Monitor Street","employer":"Corpulse","email":"foxcardenas@corpulse.com","city":"Southview","state":"NE"}
{"index":{"_id":"415"}}
{"account_number":415,"balance":19449,"firstname":"Martinez","lastname":"Benson","age":36,"gender":"M","address":"172 Berkeley Place","employer":"Enersol","email":"martinezbenson@enersol.com","city":"Chumuckla","state":"AL"}
{"index":{"_id":"422"}}
{"account_number":422,"balance":40162,"firstname":"Brigitte","lastname":"Scott","age":26,"gender":"M","address":"662 Vermont Court","employer":"Waretel","email":"brigittescott@waretel.com","city":"Elrama","state":"VA"}
{"index":{"_id":"427"}}
{"account_number":427,"balance":1463,"firstname":"Rebekah","lastname":"Garrison","age":36,"gender":"F","address":"837 Hampton Avenue","employer":"Niquent","email":"rebekahgarrison@niquent.com","city":"Zarephath","state":"NY"}
{"index":{"_id":"434"}}
{"account_number":434,"balance":11329,"firstname":"Christa","lastname":"Huff","age":25,"gender":"M","address":"454 Oriental Boulevard","employer":"Earthpure","email":"christahuff@earthpure.com","city":"Stevens","state":"DC"}
{"index":{"_id":"439"}}
{"account_number":439,"balance":22752,"firstname":"Lula","lastname":"Williams","age":35,"gender":"M","address":"630 Furman Avenue","employer":"Vinch","email":"lulawilliams@vinch.com","city":"Newcastle","state":"ME"}
{"index":{"_id":"441"}}
{"account_number":441,"balance":47947,"firstname":"Dickson","lastname":"Mcgee","age":29,"gender":"M","address":"478 Knight Court","employer":"Gogol","email":"dicksonmcgee@gogol.com","city":"Laurelton","state":"AR"}
{"index":{"_id":"446"}}
{"account_number":446,"balance":23071,"firstname":"Lolita","lastname":"Fleming","age":32,"gender":"F","address":"918 Bridge Street","employer":"Vidto","email":"lolitafleming@vidto.com","city":"Brownlee","state":"HI"}
{"index":{"_id":"453"}}
{"account_number":453,"balance":21520,"firstname":"Hood","lastname":"Powell","age":24,"gender":"F","address":"479 Brevoort Place","employer":"Vortexaco","email":"hoodpowell@vortexaco.com","city":"Alderpoint","state":"CT"}
{"index":{"_id":"458"}}
{"account_number":458,"balance":8865,"firstname":"Aida","lastname":"Wolf","age":21,"gender":"F","address":"403 Thames Street","employer":"Isis","email":"aidawolf@isis.com","city":"Bordelonville","state":"ME"}
{"index":{"_id":"460"}}
{"account_number":460,"balance":37734,"firstname":"Aguirre","lastname":"White","age":21,"gender":"F","address":"190 Crooke Avenue","employer":"Unq","email":"aguirrewhite@unq.com","city":"Albany","state":"NJ"}
{"index":{"_id":"465"}}
{"account_number":465,"balance":10681,"firstname":"Pearlie","lastname":"Holman","age":29,"gender":"M","address":"916 Evergreen Avenue","employer":"Hometown","email":"pearlieholman@hometown.com","city":"Needmore","state":"UT"}
{"index":{"_id":"472"}}
{"account_number":472,"balance":25571,"firstname":"Lee","lastname":"Long","age":32,"gender":"F","address":"288 Mill Street","employer":"Comverges","email":"leelong@comverges.com","city":"Movico","state":"MT"}
{"index":{"_id":"477"}}
{"account_number":477,"balance":25892,"firstname":"Holcomb","lastname":"Cobb","age":40,"gender":"M","address":"369 Marconi Place","employer":"Steeltab","email":"holcombcobb@steeltab.com","city":"Byrnedale","state":"CA"}
{"index":{"_id":"484"}}
{"account_number":484,"balance":3274,"firstname":"Staci","lastname":"Melendez","age":35,"gender":"F","address":"751 Otsego Street","employer":"Namebox","email":"stacimelendez@namebox.com","city":"Harborton","state":"NV"}
{"index":{"_id":"489"}}
{"account_number":489,"balance":7879,"firstname":"Garrett","lastname":"Langley","age":36,"gender":"M","address":"331 Bowne Street","employer":"Zillidium","email":"garrettlangley@zillidium.com","city":"Riviera","state":"LA"}
{"index":{"_id":"491"}}
{"account_number":491,"balance":42942,"firstname":"Teresa","lastname":"Owen","age":24,"gender":"F","address":"713 Canton Court","employer":"Plasmos","email":"teresaowen@plasmos.com","city":"Bartonsville","state":"NH"}
{"index":{"_id":"496"}}
{"account_number":496,"balance":14869,"firstname":"Alison","lastname":"Conrad","age":35,"gender":"F","address":"347 Varet Street","employer":"Perkle","email":"alisonconrad@perkle.com","city":"Cliffside","state":"OH"}
{"index":{"_id":"504"}}
{"account_number":504,"balance":49205,"firstname":"Shanna","lastname":"Chambers","age":23,"gender":"M","address":"220 Beard Street","employer":"Corporana","email":"shannachambers@corporana.com","city":"Cashtown","state":"AZ"}
{"index":{"_id":"509"}}
{"account_number":509,"balance":34754,"firstname":"Durham","lastname":"Pacheco","age":40,"gender":"M","address":"129 Plymouth Street","employer":"Datacator","email":"durhampacheco@datacator.com","city":"Loveland","state":"NC"}
{"index":{"_id":"511"}}
{"account_number":511,"balance":40908,"firstname":"Elba","lastname":"Grant","age":24,"gender":"F","address":"157 Bijou Avenue","employer":"Dognost","email":"elbagrant@dognost.com","city":"Coyote","state":"MT"}
{"index":{"_id":"516"}}
{"account_number":516,"balance":44940,"firstname":"Roy","lastname":"Smith","age":37,"gender":"M","address":"770 Cherry Street","employer":"Parleynet","email":"roysmith@parleynet.com","city":"Carrsville","state":"RI"}
{"index":{"_id":"523"}}
{"account_number":523,"balance":28729,"firstname":"Amalia","lastname":"Benjamin","age":40,"gender":"F","address":"173 Bushwick Place","employer":"Sentia","email":"amaliabenjamin@sentia.com","city":"Jacumba","state":"OK"}
{"index":{"_id":"528"}}
{"account_number":528,"balance":4071,"firstname":"Thompson","lastname":"Hoover","age":27,"gender":"F","address":"580 Garden Street","employer":"Portalis","email":"thompsonhoover@portalis.com","city":"Knowlton","state":"AL"}
{"index":{"_id":"530"}}
{"account_number":530,"balance":8840,"firstname":"Kathrine","lastname":"Evans","age":37,"gender":"M","address":"422 Division Place","employer":"Spherix","email":"kathrineevans@spherix.com","city":"Biddle","state":"CO"}
{"index":{"_id":"535"}}
{"account_number":535,"balance":8715,"firstname":"Fry","lastname":"George","age":34,"gender":"M","address":"722 Green Street","employer":"Ewaves","email":"frygeorge@ewaves.com","city":"Kenmar","state":"DE"}
{"index":{"_id":"542"}}
{"account_number":542,"balance":23285,"firstname":"Michelle","lastname":"Mayo","age":35,"gender":"M","address":"657 Caton Place","employer":"Biflex","email":"michellemayo@biflex.com","city":"Beaverdale","state":"WY"}
{"index":{"_id":"547"}}
{"account_number":547,"balance":12870,"firstname":"Eaton","lastname":"Rios","age":32,"gender":"M","address":"744 Withers Street","employer":"Podunk","email":"eatonrios@podunk.com","city":"Chelsea","state":"IA"}
{"index":{"_id":"554"}}
{"account_number":554,"balance":33163,"firstname":"Townsend","lastname":"Atkins","age":39,"gender":"M","address":"566 Ira Court","employer":"Acruex","email":"townsendatkins@acruex.com","city":"Valle","state":"IA"}
{"index":{"_id":"559"}}
{"account_number":559,"balance":11450,"firstname":"Tonia","lastname":"Schmidt","age":38,"gender":"F","address":"508 Sheffield Avenue","employer":"Extro","email":"toniaschmidt@extro.com","city":"Newry","state":"CT"}
{"index":{"_id":"561"}}
{"account_number":561,"balance":12370,"firstname":"Sellers","lastname":"Davis","age":30,"gender":"M","address":"860 Madoc Avenue","employer":"Isodrive","email":"sellersdavis@isodrive.com","city":"Trail","state":"KS"}
{"index":{"_id":"566"}}
{"account_number":566,"balance":6183,"firstname":"Cox","lastname":"Roman","age":37,"gender":"M","address":"349 Winthrop Street","employer":"Medcom","email":"coxroman@medcom.com","city":"Rosewood","state":"WY"}
{"index":{"_id":"573"}}
{"account_number":573,"balance":32171,"firstname":"Callie","lastname":"Castaneda","age":36,"gender":"M","address":"799 Scott Avenue","employer":"Earthwax","email":"calliecastaneda@earthwax.com","city":"Marshall","state":"NH"}
{"index":{"_id":"578"}}
{"account_number":578,"balance":34259,"firstname":"Holmes","lastname":"Mcknight","age":37,"gender":"M","address":"969 Metropolitan Avenue","employer":"Cubicide","email":"holmesmcknight@cubicide.com","city":"Aguila","state":"PA"}
{"index":{"_id":"580"}}
{"account_number":580,"balance":13716,"firstname":"Mcmahon","lastname":"York","age":34,"gender":"M","address":"475 Beacon Court","employer":"Zillar","email":"mcmahonyork@zillar.com","city":"Farmington","state":"MO"}
{"index":{"_id":"585"}}
{"account_number":585,"balance":26745,"firstname":"Nieves","lastname":"Nolan","age":32,"gender":"M","address":"115 Seagate Terrace","employer":"Jumpstack","email":"nievesnolan@jumpstack.com","city":"Eastmont","state":"UT"}
{"index":{"_id":"592"}}
{"account_number":592,"balance":32968,"firstname":"Head","lastname":"Webster","age":36,"gender":"F","address":"987 Lefferts Avenue","employer":"Empirica","email":"headwebster@empirica.com","city":"Rockingham","state":"TN"}
{"index":{"_id":"597"}}
{"account_number":597,"balance":11246,"firstname":"Penny","lastname":"Knowles","age":33,"gender":"M","address":"139 Forbell Street","employer":"Ersum","email":"pennyknowles@ersum.com","city":"Vallonia","state":"IA"}
{"index":{"_id":"600"}}
{"account_number":600,"balance":10336,"firstname":"Simmons","lastname":"Byers","age":37,"gender":"M","address":"250 Dictum Court","employer":"Qualitex","email":"simmonsbyers@qualitex.com","city":"Wanship","state":"OH"}
{"index":{"_id":"605"}}
{"account_number":605,"balance":38427,"firstname":"Mcclain","lastname":"Manning","age":24,"gender":"M","address":"832 Leonard Street","employer":"Qiao","email":"mcclainmanning@qiao.com","city":"Calvary","state":"TX"}
{"index":{"_id":"612"}}
{"account_number":612,"balance":11868,"firstname":"Dunn","lastname":"Cameron","age":32,"gender":"F","address":"156 Lorimer Street","employer":"Isonus","email":"dunncameron@isonus.com","city":"Virgie","state":"ND"}
{"index":{"_id":"617"}}
{"account_number":617,"balance":35445,"firstname":"Kitty","lastname":"Cooley","age":22,"gender":"M","address":"788 Seagate Avenue","employer":"Ultrimax","email":"kittycooley@ultrimax.com","city":"Clarktown","state":"MD"}
{"index":{"_id":"624"}}
{"account_number":624,"balance":27538,"firstname":"Roxanne","lastname":"Franklin","age":39,"gender":"F","address":"299 Woodrow Court","employer":"Silodyne","email":"roxannefranklin@silodyne.com","city":"Roulette","state":"VA"}
{"index":{"_id":"629"}}
{"account_number":629,"balance":32987,"firstname":"Mcclure","lastname":"Rodgers","age":26,"gender":"M","address":"806 Pierrepont Place","employer":"Elita","email":"mcclurerodgers@elita.com","city":"Brownsville","state":"MI"}
{"index":{"_id":"631"}}
{"account_number":631,"balance":21657,"firstname":"Corrine","lastname":"Barber","age":32,"gender":"F","address":"447 Hunts Lane","employer":"Quarmony","email":"corrinebarber@quarmony.com","city":"Wyano","state":"IL"}
{"index":{"_id":"636"}}
{"account_number":636,"balance":8036,"firstname":"Agnes","lastname":"Hooper","age":25,"gender":"M","address":"865 Hanson Place","employer":"Digial","email":"agneshooper@digial.com","city":"Sperryville","state":"OK"}
{"index":{"_id":"643"}}
{"account_number":643,"balance":8057,"firstname":"Hendricks","lastname":"Stokes","age":23,"gender":"F","address":"142 Barbey Street","employer":"Remotion","email":"hendricksstokes@remotion.com","city":"Lewis","state":"MA"}
{"index":{"_id":"648"}}
{"account_number":648,"balance":11506,"firstname":"Terry","lastname":"Montgomery","age":21,"gender":"F","address":"115 Franklin Avenue","employer":"Enervate","email":"terrymontgomery@enervate.com","city":"Bascom","state":"MA"}
{"index":{"_id":"650"}}
{"account_number":650,"balance":18091,"firstname":"Benton","lastname":"Knight","age":28,"gender":"F","address":"850 Aitken Place","employer":"Pholio","email":"bentonknight@pholio.com","city":"Cobbtown","state":"AL"}
{"index":{"_id":"655"}}
{"account_number":655,"balance":22912,"firstname":"Eula","lastname":"Taylor","age":30,"gender":"M","address":"520 Orient Avenue","employer":"Miracula","email":"eulataylor@miracula.com","city":"Wacissa","state":"IN"}
{"index":{"_id":"662"}}
{"account_number":662,"balance":10138,"firstname":"Daisy","lastname":"Burnett","age":33,"gender":"M","address":"114 Norman Avenue","employer":"Liquicom","email":"daisyburnett@liquicom.com","city":"Grahamtown","state":"MD"}
{"index":{"_id":"667"}}
{"account_number":667,"balance":22559,"firstname":"Juliana","lastname":"Chase","age":32,"gender":"M","address":"496 Coleridge Street","employer":"Comtract","email":"julianachase@comtract.com","city":"Wilsonia","state":"NJ"}
{"index":{"_id":"674"}}
{"account_number":674,"balance":36038,"firstname":"Watts","lastname":"Shannon","age":22,"gender":"F","address":"600 Story Street","employer":"Joviold","email":"wattsshannon@joviold.com","city":"Fairhaven","state":"ID"}
{"index":{"_id":"679"}}
{"account_number":679,"balance":20149,"firstname":"Henrietta","lastname":"Bonner","age":33,"gender":"M","address":"461 Bond Street","employer":"Geekol","email":"henriettabonner@geekol.com","city":"Richville","state":"WA"}
{"index":{"_id":"681"}}
{"account_number":681,"balance":34244,"firstname":"Velazquez","lastname":"Wolfe","age":33,"gender":"M","address":"773 Eckford Street","employer":"Zisis","email":"velazquezwolfe@zisis.com","city":"Smock","state":"ME"}
{"index":{"_id":"686"}}
{"account_number":686,"balance":10116,"firstname":"Decker","lastname":"Mcclure","age":30,"gender":"F","address":"236 Commerce Street","employer":"Everest","email":"deckermcclure@everest.com","city":"Gibbsville","state":"TN"}
{"index":{"_id":"693"}}
{"account_number":693,"balance":31233,"firstname":"Tabatha","lastname":"Zimmerman","age":30,"gender":"F","address":"284 Emmons Avenue","employer":"Pushcart","email":"tabathazimmerman@pushcart.com","city":"Esmont","state":"NC"}
{"index":{"_id":"698"}}
{"account_number":698,"balance":14965,"firstname":"Baker","lastname":"Armstrong","age":36,"gender":"F","address":"796 Tehama Street","employer":"Nurplex","email":"bakerarmstrong@nurplex.com","city":"Starks","state":"UT"}
{"index":{"_id":"701"}}
{"account_number":701,"balance":23772,"firstname":"Gardner","lastname":"Griffith","age":27,"gender":"M","address":"187 Moore Place","employer":"Vertide","email":"gardnergriffith@vertide.com","city":"Coventry","state":"NV"}
{"index":{"_id":"706"}}
{"account_number":706,"balance":5282,"firstname":"Eliza","lastname":"Potter","age":39,"gender":"M","address":"945 Dunham Place","employer":"Playce","email":"elizapotter@playce.com","city":"Woodruff","state":"AK"}
{"index":{"_id":"713"}}
{"account_number":713,"balance":20054,"firstname":"Iris","lastname":"Mcguire","age":21,"gender":"F","address":"508 Benson Avenue","employer":"Duflex","email":"irismcguire@duflex.com","city":"Hillsboro","state":"MO"}
{"index":{"_id":"718"}}
{"account_number":718,"balance":13876,"firstname":"Hickman","lastname":"Dillard","age":22,"gender":"F","address":"132 Etna Street","employer":"Genmy","email":"hickmandillard@genmy.com","city":"Curtice","state":"NV"}
{"index":{"_id":"720"}}
{"account_number":720,"balance":31356,"firstname":"Ruth","lastname":"Vance","age":32,"gender":"F","address":"229 Adams Street","employer":"Zilidium","email":"ruthvance@zilidium.com","city":"Allison","state":"IA"}
{"index":{"_id":"725"}}
{"account_number":725,"balance":14677,"firstname":"Reeves","lastname":"Tillman","age":26,"gender":"M","address":"674 Ivan Court","employer":"Cemention","email":"reevestillman@cemention.com","city":"Navarre","state":"MA"}
{"index":{"_id":"732"}}
{"account_number":732,"balance":38445,"firstname":"Delia","lastname":"Cruz","age":37,"gender":"F","address":"870 Cheever Place","employer":"Multron","email":"deliacruz@multron.com","city":"Cresaptown","state":"NH"}
{"index":{"_id":"737"}}
{"account_number":737,"balance":40431,"firstname":"Sampson","lastname":"Yates","age":23,"gender":"F","address":"214 Cox Place","employer":"Signidyne","email":"sampsonyates@signidyne.com","city":"Brazos","state":"GA"}
{"index":{"_id":"744"}}
{"account_number":744,"balance":8690,"firstname":"Bernard","lastname":"Martinez","age":21,"gender":"M","address":"148 Dunne Place","employer":"Dragbot","email":"bernardmartinez@dragbot.com","city":"Moraida","state":"MN"}
{"index":{"_id":"749"}}
{"account_number":749,"balance":1249,"firstname":"Rush","lastname":"Boyle","age":36,"gender":"M","address":"310 Argyle Road","employer":"Sportan","email":"rushboyle@sportan.com","city":"Brady","state":"WA"}
{"index":{"_id":"751"}}
{"account_number":751,"balance":49252,"firstname":"Patrick","lastname":"Osborne","age":23,"gender":"M","address":"915 Prospect Avenue","employer":"Gynko","email":"patrickosborne@gynko.com","city":"Takilma","state":"MO"}
{"index":{"_id":"756"}}
{"account_number":756,"balance":40006,"firstname":"Jasmine","lastname":"Howell","age":32,"gender":"M","address":"605 Elliott Walk","employer":"Ecratic","email":"jasminehowell@ecratic.com","city":"Harrodsburg","state":"OH"}
{"index":{"_id":"763"}}
{"account_number":763,"balance":12091,"firstname":"Liz","lastname":"Bentley","age":22,"gender":"F","address":"933 Debevoise Avenue","employer":"Nipaz","email":"lizbentley@nipaz.com","city":"Glenville","state":"NJ"}
{"index":{"_id":"768"}}
{"account_number":768,"balance":2213,"firstname":"Sondra","lastname":"Soto","age":21,"gender":"M","address":"625 Colonial Road","employer":"Navir","email":"sondrasoto@navir.com","city":"Benson","state":"VA"}
{"index":{"_id":"770"}}
{"account_number":770,"balance":39505,"firstname":"Joann","lastname":"Crane","age":26,"gender":"M","address":"798 Farragut Place","employer":"Lingoage","email":"joanncrane@lingoage.com","city":"Kirk","state":"MA"}
{"index":{"_id":"775"}}
{"account_number":775,"balance":27943,"firstname":"Wilson","lastname":"Merritt","age":33,"gender":"F","address":"288 Thornton Street","employer":"Geeky","email":"wilsonmerritt@geeky.com","city":"Holtville","state":"HI"}
{"index":{"_id":"782"}}
{"account_number":782,"balance":3960,"firstname":"Maldonado","lastname":"Craig","age":36,"gender":"F","address":"345 Myrtle Avenue","employer":"Zilencio","email":"maldonadocraig@zilencio.com","city":"Yukon","state":"ID"}
{"index":{"_id":"787"}}
{"account_number":787,"balance":11876,"firstname":"Harper","lastname":"Wynn","age":21,"gender":"F","address":"139 Oceanic Avenue","employer":"Interfind","email":"harperwynn@interfind.com","city":"Gerber","state":"ND"}
{"index":{"_id":"794"}}
{"account_number":794,"balance":16491,"firstname":"Walker","lastname":"Charles","age":32,"gender":"M","address":"215 Kenilworth Place","employer":"Orbin","email":"walkercharles@orbin.com","city":"Rivers","state":"WI"}
{"index":{"_id":"799"}}
{"account_number":799,"balance":2889,"firstname":"Myra","lastname":"Guerra","age":28,"gender":"F","address":"625 Dahlgreen Place","employer":"Digigene","email":"myraguerra@digigene.com","city":"Draper","state":"CA"}
{"index":{"_id":"802"}}
{"account_number":802,"balance":19630,"firstname":"Gracie","lastname":"Foreman","age":40,"gender":"F","address":"219 Kent Avenue","employer":"Supportal","email":"gracieforeman@supportal.com","city":"Westboro","state":"NH"}
{"index":{"_id":"807"}}
{"account_number":807,"balance":29206,"firstname":"Hatfield","lastname":"Lowe","age":23,"gender":"M","address":"499 Adler Place","employer":"Lovepad","email":"hatfieldlowe@lovepad.com","city":"Wiscon","state":"DC"}
{"index":{"_id":"814"}}
{"account_number":814,"balance":9838,"firstname":"Morse","lastname":"Mcbride","age":26,"gender":"F","address":"776 Calyer Street","employer":"Inear","email":"morsemcbride@inear.com","city":"Kingstowne","state":"ND"}
{"index":{"_id":"819"}}
{"account_number":819,"balance":3971,"firstname":"Karyn","lastname":"Medina","age":24,"gender":"F","address":"417 Utica Avenue","employer":"Qnekt","email":"karynmedina@qnekt.com","city":"Kerby","state":"WY"}
{"index":{"_id":"821"}}
{"account_number":821,"balance":33271,"firstname":"Trisha","lastname":"Blankenship","age":22,"gender":"M","address":"329 Jamaica Avenue","employer":"Chorizon","email":"trishablankenship@chorizon.com","city":"Sexton","state":"VT"}
{"index":{"_id":"826"}}
{"account_number":826,"balance":11548,"firstname":"Summers","lastname":"Vinson","age":22,"gender":"F","address":"742 Irwin Street","employer":"Globoil","email":"summersvinson@globoil.com","city":"Callaghan","state":"WY"}
{"index":{"_id":"833"}}
{"account_number":833,"balance":46154,"firstname":"Woodward","lastname":"Hood","age":22,"gender":"M","address":"398 Atkins Avenue","employer":"Zedalis","email":"woodwardhood@zedalis.com","city":"Stonybrook","state":"NE"}
{"index":{"_id":"838"}}
{"account_number":838,"balance":24629,"firstname":"Latonya","lastname":"Blake","age":37,"gender":"F","address":"531 Milton Street","employer":"Rugstars","email":"latonyablake@rugstars.com","city":"Tedrow","state":"WA"}
{"index":{"_id":"840"}}
{"account_number":840,"balance":39615,"firstname":"Boone","lastname":"Gomez","age":38,"gender":"M","address":"256 Hampton Place","employer":"Geekular","email":"boonegomez@geekular.com","city":"Westerville","state":"HI"}
{"index":{"_id":"845"}}
{"account_number":845,"balance":35422,"firstname":"Tracy","lastname":"Vaughn","age":39,"gender":"M","address":"645 Rockaway Parkway","employer":"Andryx","email":"tracyvaughn@andryx.com","city":"Wilmington","state":"ME"}
{"index":{"_id":"852"}}
{"account_number":852,"balance":6041,"firstname":"Allen","lastname":"Hammond","age":26,"gender":"M","address":"793 Essex Street","employer":"Tersanki","email":"allenhammond@tersanki.com","city":"Osmond","state":"NC"}
{"index":{"_id":"857"}}
{"account_number":857,"balance":39678,"firstname":"Alyce","lastname":"Douglas","age":23,"gender":"M","address":"326 Robert Street","employer":"Earbang","email":"alycedouglas@earbang.com","city":"Thornport","state":"GA"}
{"index":{"_id":"864"}}
{"account_number":864,"balance":21804,"firstname":"Duffy","lastname":"Anthony","age":23,"gender":"M","address":"582 Cooke Court","employer":"Schoolio","email":"duffyanthony@schoolio.com","city":"Brenton","state":"CO"}
{"index":{"_id":"869"}}
{"account_number":869,"balance":43544,"firstname":"Corinne","lastname":"Robbins","age":25,"gender":"F","address":"732 Quentin Road","employer":"Orbaxter","email":"corinnerobbins@orbaxter.com","city":"Roy","state":"TN"}
{"index":{"_id":"871"}}
{"account_number":871,"balance":35854,"firstname":"Norma","lastname":"Burt","age":32,"gender":"M","address":"934 Cyrus Avenue","employer":"Magnafone","email":"normaburt@magnafone.com","city":"Eden","state":"TN"}
{"index":{"_id":"876"}}
{"account_number":876,"balance":48568,"firstname":"Brady","lastname":"Glover","age":21,"gender":"F","address":"565 Oceanview Avenue","employer":"Comvex","email":"bradyglover@comvex.com","city":"Noblestown","state":"ID"}
{"index":{"_id":"883"}}
{"account_number":883,"balance":33679,"firstname":"Austin","lastname":"Jefferson","age":34,"gender":"M","address":"846 Lincoln Avenue","employer":"Polarax","email":"austinjefferson@polarax.com","city":"Savannah","state":"CT"}
{"index":{"_id":"888"}}
{"account_number":888,"balance":22277,"firstname":"Myrna","lastname":"Herman","age":39,"gender":"F","address":"649 Harwood Place","employer":"Enthaze","email":"myrnaherman@enthaze.com","city":"Idamay","state":"AR"}
{"index":{"_id":"890"}}
{"account_number":890,"balance":31198,"firstname":"Alvarado","lastname":"Pate","age":25,"gender":"M","address":"269 Ashland Place","employer":"Ovolo","email":"alvaradopate@ovolo.com","city":"Volta","state":"MI"}
{"index":{"_id":"895"}}
{"account_number":895,"balance":7327,"firstname":"Lara","lastname":"Mcdaniel","age":36,"gender":"M","address":"854 Willow Place","employer":"Acusage","email":"laramcdaniel@acusage.com","city":"Imperial","state":"NC"}
{"index":{"_id":"903"}}
{"account_number":903,"balance":10238,"firstname":"Wade","lastname":"Page","age":35,"gender":"F","address":"685 Waldorf Court","employer":"Eplosion","email":"wadepage@eplosion.com","city":"Welda","state":"AL"}
{"index":{"_id":"908"}}
{"account_number":908,"balance":45975,"firstname":"Mosley","lastname":"Holloway","age":31,"gender":"M","address":"929 Eldert Lane","employer":"Anivet","email":"mosleyholloway@anivet.com","city":"Biehle","state":"MS"}
{"index":{"_id":"910"}}
{"account_number":910,"balance":36831,"firstname":"Esmeralda","lastname":"James","age":23,"gender":"F","address":"535 High Street","employer":"Terrasys","email":"esmeraldajames@terrasys.com","city":"Dubois","state":"IN"}
{"index":{"_id":"915"}}
{"account_number":915,"balance":19816,"firstname":"Farrell","lastname":"French","age":35,"gender":"F","address":"126 McKibbin Street","employer":"Techmania","email":"farrellfrench@techmania.com","city":"Wescosville","state":"AL"}
{"index":{"_id":"922"}}
{"account_number":922,"balance":39347,"firstname":"Irwin","lastname":"Pugh","age":32,"gender":"M","address":"463 Shale Street","employer":"Idego","email":"irwinpugh@idego.com","city":"Ivanhoe","state":"ID"}
{"index":{"_id":"927"}}
{"account_number":927,"balance":19976,"firstname":"Jeanette","lastname":"Acevedo","age":26,"gender":"M","address":"694 Polhemus Place","employer":"Halap","email":"jeanetteacevedo@halap.com","city":"Harrison","state":"MO"}
{"index":{"_id":"934"}}
{"account_number":934,"balance":43987,"firstname":"Freida","lastname":"Daniels","age":34,"gender":"M","address":"448 Cove Lane","employer":"Vurbo","email":"freidadaniels@vurbo.com","city":"Snelling","state":"NJ"}
{"index":{"_id":"939"}}
{"account_number":939,"balance":31228,"firstname":"Hodges","lastname":"Massey","age":37,"gender":"F","address":"431 Dahl Court","employer":"Kegular","email":"hodgesmassey@kegular.com","city":"Katonah","state":"MD"}
{"index":{"_id":"941"}}
{"account_number":941,"balance":38796,"firstname":"Kim","lastname":"Moss","age":28,"gender":"F","address":"105 Onderdonk Avenue","employer":"Digirang","email":"kimmoss@digirang.com","city":"Centerville","state":"TX"}
{"index":{"_id":"946"}}
{"account_number":946,"balance":42794,"firstname":"Ina","lastname":"Obrien","age":36,"gender":"M","address":"339 Rewe Street","employer":"Eclipsent","email":"inaobrien@eclipsent.com","city":"Soham","state":"RI"}
{"index":{"_id":"953"}}
{"account_number":953,"balance":1110,"firstname":"Baxter","lastname":"Black","age":27,"gender":"M","address":"720 Stillwell Avenue","employer":"Uplinx","email":"baxterblack@uplinx.com","city":"Drummond","state":"MN"}
{"index":{"_id":"958"}}
{"account_number":958,"balance":32849,"firstname":"Brown","lastname":"Wilkins","age":40,"gender":"M","address":"686 Delmonico Place","employer":"Medesign","email":"brownwilkins@medesign.com","city":"Shelby","state":"WY"}
{"index":{"_id":"960"}}
{"account_number":960,"balance":2905,"firstname":"Curry","lastname":"Vargas","age":40,"gender":"M","address":"242 Blake Avenue","employer":"Pearlesex","email":"curryvargas@pearlesex.com","city":"Henrietta","state":"NH"}
{"index":{"_id":"965"}}
{"account_number":965,"balance":21882,"firstname":"Patrica","lastname":"Melton","age":28,"gender":"M","address":"141 Rodney Street","employer":"Flexigen","email":"patricamelton@flexigen.com","city":"Klagetoh","state":"MD"}
{"index":{"_id":"972"}}
{"account_number":972,"balance":24719,"firstname":"Leona","lastname":"Christian","age":26,"gender":"F","address":"900 Woodpoint Road","employer":"Extrawear","email":"leonachristian@extrawear.com","city":"Roderfield","state":"MA"}
{"index":{"_id":"977"}}
{"account_number":977,"balance":6744,"firstname":"Rodgers","lastname":"Mccray","age":21,"gender":"F","address":"612 Duryea Place","employer":"Papricut","email":"rodgersmccray@papricut.com","city":"Marenisco","state":"MD"}
{"index":{"_id":"984"}}
{"account_number":984,"balance":1904,"firstname":"Viola","lastname":"Crawford","age":35,"gender":"F","address":"354 Linwood Street","employer":"Ginkle","email":"violacrawford@ginkle.com","city":"Witmer","state":"AR"}
{"index":{"_id":"989"}}
{"account_number":989,"balance":48622,"firstname":"Franklin","lastname":"Frank","age":38,"gender":"M","address":"270 Carlton Avenue","employer":"Shopabout","email":"franklinfrank@shopabout.com","city":"Guthrie","state":"NC"}
{"index":{"_id":"991"}}
{"account_number":991,"balance":4239,"firstname":"Connie","lastname":"Berry","age":28,"gender":"F","address":"647 Gardner Avenue","employer":"Flumbo","email":"connieberry@flumbo.com","city":"Frierson","state":"MO"}
{"index":{"_id":"996"}}
{"account_number":996,"balance":17541,"firstname":"Andrews","lastname":"Herrera","age":30,"gender":"F","address":"570 Vandam Street","employer":"Klugger","email":"andrewsherrera@klugger.com","city":"Whitehaven","state":"MN"}
{"index":{"_id":"0"}}
{"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
{"index":{"_id":"5"}}
{"account_number":5,"balance":29342,"firstname":"Leola","lastname":"Stewart","age":30,"gender":"F","address":"311 Elm Place","employer":"Diginetic","email":"leolastewart@diginetic.com","city":"Fairview","state":"NJ"}
{"index":{"_id":"12"}}
{"account_number":12,"balance":37055,"firstname":"Stafford","lastname":"Brock","age":20,"gender":"F","address":"296 Wythe Avenue","employer":"Uncorp","email":"staffordbrock@uncorp.com","city":"Bend","state":"AL"}
{"index":{"_id":"17"}}
{"account_number":17,"balance":7831,"firstname":"Bessie","lastname":"Orr","age":31,"gender":"F","address":"239 Hinsdale Street","employer":"Skyplex","email":"bessieorr@skyplex.com","city":"Graball","state":"FL"}
{"index":{"_id":"24"}}
{"account_number":24,"balance":44182,"firstname":"Wood","lastname":"Dale","age":39,"gender":"M","address":"582 Gelston Avenue","employer":"Besto","email":"wooddale@besto.com","city":"Juntura","state":"MI"}
{"index":{"_id":"29"}}
{"account_number":29,"balance":27323,"firstname":"Leah","lastname":"Santiago","age":33,"gender":"M","address":"193 Schenck Avenue","employer":"Isologix","email":"leahsantiago@isologix.com","city":"Gerton","state":"ND"}
{"index":{"_id":"31"}}
{"account_number":31,"balance":30443,"firstname":"Kristen","lastname":"Santana","age":22,"gender":"F","address":"130 Middagh Street","employer":"Dogspa","email":"kristensantana@dogspa.com","city":"Vale","state":"MA"}
{"index":{"_id":"36"}}
{"account_number":36,"balance":15902,"firstname":"Alexandra","lastname":"Nguyen","age":39,"gender":"F","address":"389 Elizabeth Place","employer":"Bittor","email":"alexandranguyen@bittor.com","city":"Hemlock","state":"KY"}
{"index":{"_id":"43"}}
{"account_number":43,"balance":33474,"firstname":"Ryan","lastname":"Howe","age":25,"gender":"M","address":"660 Huntington Street","employer":"Microluxe","email":"ryanhowe@microluxe.com","city":"Clara","state":"CT"}
{"index":{"_id":"48"}}
{"account_number":48,"balance":40608,"firstname":"Peck","lastname":"Downs","age":39,"gender":"F","address":"594 Dwight Street","employer":"Ramjob","email":"peckdowns@ramjob.com","city":"Coloma","state":"WA"}
{"index":{"_id":"50"}}
{"account_number":50,"balance":43695,"firstname":"Sheena","lastname":"Kirkland","age":33,"gender":"M","address":"598 Bank Street","employer":"Zerbina","email":"sheenakirkland@zerbina.com","city":"Walland","state":"IN"}
{"index":{"_id":"55"}}
{"account_number":55,"balance":22020,"firstname":"Shelia","lastname":"Puckett","age":33,"gender":"M","address":"265 Royce Place","employer":"Izzby","email":"sheliapuckett@izzby.com","city":"Slovan","state":"HI"}
{"index":{"_id":"62"}}
{"account_number":62,"balance":43065,"firstname":"Lester","lastname":"Stanton","age":37,"gender":"M","address":"969 Doughty Street","employer":"Geekko","email":"lesterstanton@geekko.com","city":"Itmann","state":"DC"}
{"index":{"_id":"67"}}
{"account_number":67,"balance":39430,"firstname":"Isabelle","lastname":"Spence","age":39,"gender":"M","address":"718 Troy Avenue","employer":"Geeketron","email":"isabellespence@geeketron.com","city":"Camptown","state":"WA"}
{"index":{"_id":"74"}}
{"account_number":74,"balance":47167,"firstname":"Lauri","lastname":"Saunders","age":38,"gender":"F","address":"768 Lynch Street","employer":"Securia","email":"laurisaunders@securia.com","city":"Caroline","state":"TN"}
{"index":{"_id":"79"}}
{"account_number":79,"balance":28185,"firstname":"Booker","lastname":"Lowery","age":29,"gender":"M","address":"817 Campus Road","employer":"Sensate","email":"bookerlowery@sensate.com","city":"Carlos","state":"MT"}
{"index":{"_id":"81"}}
{"account_number":81,"balance":46568,"firstname":"Dennis","lastname":"Gilbert","age":40,"gender":"M","address":"619 Minna Street","employer":"Melbacor","email":"dennisgilbert@melbacor.com","city":"Kersey","state":"ND"}
{"index":{"_id":"86"}}
{"account_number":86,"balance":15428,"firstname":"Walton","lastname":"Butler","age":36,"gender":"M","address":"999 Schenck Street","employer":"Unisure","email":"waltonbutler@unisure.com","city":"Bentonville","state":"IL"}
{"index":{"_id":"93"}}
{"account_number":93,"balance":17728,"firstname":"Jeri","lastname":"Booth","age":31,"gender":"M","address":"322 Roosevelt Court","employer":"Geekology","email":"jeribooth@geekology.com","city":"Leming","state":"ND"}
{"index":{"_id":"98"}}
{"account_number":98,"balance":15085,"firstname":"Cora","lastname":"Barrett","age":24,"gender":"F","address":"555 Neptune Court","employer":"Kiosk","email":"corabarrett@kiosk.com","city":"Independence","state":"MN"}
{"index":{"_id":"101"}}
{"account_number":101,"balance":43400,"firstname":"Cecelia","lastname":"Grimes","age":31,"gender":"M","address":"972 Lincoln Place","employer":"Ecosys","email":"ceceliagrimes@ecosys.com","city":"Manchester","state":"AR"}
{"index":{"_id":"106"}}
{"account_number":106,"balance":8212,"firstname":"Josefina","lastname":"Wagner","age":36,"gender":"M","address":"418 Estate Road","employer":"Kyaguru","email":"josefinawagner@kyaguru.com","city":"Darbydale","state":"FL"}
{"index":{"_id":"113"}}
{"account_number":113,"balance":41652,"firstname":"Burt","lastname":"Moses","age":27,"gender":"M","address":"633 Berry Street","employer":"Uni","email":"burtmoses@uni.com","city":"Russellville","state":"CT"}
{"index":{"_id":"118"}}
{"account_number":118,"balance":2223,"firstname":"Ballard","lastname":"Vasquez","age":33,"gender":"F","address":"101 Bush Street","employer":"Intergeek","email":"ballardvasquez@intergeek.com","city":"Century","state":"MN"}
{"index":{"_id":"120"}}
{"account_number":120,"balance":38565,"firstname":"Browning","lastname":"Rodriquez","age":33,"gender":"M","address":"910 Moore Street","employer":"Opportech","email":"browningrodriquez@opportech.com","city":"Cutter","state":"ND"}
{"index":{"_id":"125"}}
{"account_number":125,"balance":5396,"firstname":"Tanisha","lastname":"Dixon","age":30,"gender":"M","address":"482 Hancock Street","employer":"Junipoor","email":"tanishadixon@junipoor.com","city":"Wauhillau","state":"IA"}
{"index":{"_id":"132"}}
{"account_number":132,"balance":37707,"firstname":"Horton","lastname":"Romero","age":35,"gender":"M","address":"427 Rutherford Place","employer":"Affluex","email":"hortonromero@affluex.com","city":"Hall","state":"AK"}
{"index":{"_id":"137"}}
{"account_number":137,"balance":3596,"firstname":"Frost","lastname":"Freeman","age":29,"gender":"F","address":"191 Dennett Place","employer":"Beadzza","email":"frostfreeman@beadzza.com","city":"Sabillasville","state":"HI"}
{"index":{"_id":"144"}}
{"account_number":144,"balance":43257,"firstname":"Evans","lastname":"Dyer","age":30,"gender":"F","address":"912 Post Court","employer":"Magmina","email":"evansdyer@magmina.com","city":"Gordon","state":"HI"}
{"index":{"_id":"149"}}
{"account_number":149,"balance":22994,"firstname":"Megan","lastname":"Gonzales","age":21,"gender":"M","address":"836 Tampa Court","employer":"Andershun","email":"megangonzales@andershun.com","city":"Rockhill","state":"AL"}
{"index":{"_id":"151"}}
{"account_number":151,"balance":34473,"firstname":"Kent","lastname":"Joyner","age":20,"gender":"F","address":"799 Truxton Street","employer":"Kozgene","email":"kentjoyner@kozgene.com","city":"Allamuchy","state":"DC"}
{"index":{"_id":"156"}}
{"account_number":156,"balance":40185,"firstname":"Sloan","lastname":"Pennington","age":24,"gender":"F","address":"573 Opal Court","employer":"Hopeli","email":"sloanpennington@hopeli.com","city":"Evergreen","state":"CT"}
{"index":{"_id":"163"}}
{"account_number":163,"balance":43075,"firstname":"Wilda","lastname":"Norman","age":33,"gender":"F","address":"173 Beadel Street","employer":"Kog","email":"wildanorman@kog.com","city":"Bodega","state":"ME"}
{"index":{"_id":"168"}}
{"account_number":168,"balance":49568,"firstname":"Carissa","lastname":"Simon","age":20,"gender":"M","address":"975 Flatbush Avenue","employer":"Zillacom","email":"carissasimon@zillacom.com","city":"Neibert","state":"IL"}
{"index":{"_id":"170"}}
{"account_number":170,"balance":6025,"firstname":"Mann","lastname":"Madden","age":36,"gender":"F","address":"161 Radde Place","employer":"Farmex","email":"mannmadden@farmex.com","city":"Thermal","state":"LA"}
{"index":{"_id":"175"}}
{"account_number":175,"balance":16213,"firstname":"Montoya","lastname":"Donaldson","age":28,"gender":"F","address":"481 Morton Street","employer":"Envire","email":"montoyadonaldson@envire.com","city":"Delco","state":"MA"}
{"index":{"_id":"182"}}
{"account_number":182,"balance":7803,"firstname":"Manuela","lastname":"Dillon","age":21,"gender":"M","address":"742 Garnet Street","employer":"Moreganic","email":"manueladillon@moreganic.com","city":"Ilchester","state":"TX"}
{"index":{"_id":"187"}}
{"account_number":187,"balance":26581,"firstname":"Autumn","lastname":"Hodges","age":35,"gender":"M","address":"757 Granite Street","employer":"Ezentia","email":"autumnhodges@ezentia.com","city":"Martinsville","state":"KY"}
{"index":{"_id":"194"}}
{"account_number":194,"balance":16311,"firstname":"Beck","lastname":"Rosario","age":39,"gender":"M","address":"721 Cambridge Place","employer":"Zoid","email":"beckrosario@zoid.com","city":"Efland","state":"ID"}
{"index":{"_id":"199"}}
{"account_number":199,"balance":18086,"firstname":"Branch","lastname":"Love","age":26,"gender":"M","address":"458 Commercial Street","employer":"Frolix","email":"branchlove@frolix.com","city":"Caspar","state":"NC"}
{"index":{"_id":"202"}}
{"account_number":202,"balance":26466,"firstname":"Medina","lastname":"Brown","age":31,"gender":"F","address":"519 Sunnyside Court","employer":"Bleendot","email":"medinabrown@bleendot.com","city":"Winfred","state":"MI"}
{"index":{"_id":"207"}}
{"account_number":207,"balance":45535,"firstname":"Evelyn","lastname":"Lara","age":35,"gender":"F","address":"636 Chestnut Street","employer":"Ultrasure","email":"evelynlara@ultrasure.com","city":"Logan","state":"MI"}
{"index":{"_id":"214"}}
{"account_number":214,"balance":24418,"firstname":"Luann","lastname":"Faulkner","age":37,"gender":"F","address":"697 Hazel Court","employer":"Zolar","email":"luannfaulkner@zolar.com","city":"Ticonderoga","state":"TX"}
{"index":{"_id":"219"}}
{"account_number":219,"balance":17127,"firstname":"Edwards","lastname":"Hurley","age":25,"gender":"M","address":"834 Stockholm Street","employer":"Austech","email":"edwardshurley@austech.com","city":"Bayview","state":"NV"}
{"index":{"_id":"221"}}
{"account_number":221,"balance":15803,"firstname":"Benjamin","lastname":"Barrera","age":34,"gender":"M","address":"568 Main Street","employer":"Zaphire","email":"benjaminbarrera@zaphire.com","city":"Germanton","state":"WY"}
{"index":{"_id":"226"}}
{"account_number":226,"balance":37720,"firstname":"Wilkins","lastname":"Brady","age":40,"gender":"F","address":"486 Baltic Street","employer":"Dogtown","email":"wilkinsbrady@dogtown.com","city":"Condon","state":"MT"}
{"index":{"_id":"233"}}
{"account_number":233,"balance":23020,"firstname":"Washington","lastname":"Walsh","age":27,"gender":"M","address":"366 Church Avenue","employer":"Candecor","email":"washingtonwalsh@candecor.com","city":"Westphalia","state":"MA"}
{"index":{"_id":"238"}}
{"account_number":238,"balance":21287,"firstname":"Constance","lastname":"Wong","age":28,"gender":"M","address":"496 Brown Street","employer":"Grainspot","email":"constancewong@grainspot.com","city":"Cecilia","state":"IN"}
{"index":{"_id":"240"}}
{"account_number":240,"balance":49741,"firstname":"Oconnor","lastname":"Clay","age":35,"gender":"F","address":"659 Highland Boulevard","employer":"Franscene","email":"oconnorclay@franscene.com","city":"Kilbourne","state":"NH"}
{"index":{"_id":"245"}}
{"account_number":245,"balance":22026,"firstname":"Fran","lastname":"Bolton","age":28,"gender":"F","address":"147 Jerome Street","employer":"Solaren","email":"franbolton@solaren.com","city":"Nash","state":"RI"}
{"index":{"_id":"252"}}
{"account_number":252,"balance":18831,"firstname":"Elvia","lastname":"Poole","age":22,"gender":"F","address":"836 Delevan Street","employer":"Velity","email":"elviapoole@velity.com","city":"Groveville","state":"MI"}
{"index":{"_id":"257"}}
{"account_number":257,"balance":5318,"firstname":"Olive","lastname":"Oneil","age":35,"gender":"F","address":"457 Decatur Street","employer":"Helixo","email":"oliveoneil@helixo.com","city":"Chicopee","state":"MI"}
{"index":{"_id":"264"}}
{"account_number":264,"balance":22084,"firstname":"Samantha","lastname":"Ferrell","age":35,"gender":"F","address":"488 Fulton Street","employer":"Flum","email":"samanthaferrell@flum.com","city":"Brandywine","state":"MT"}
{"index":{"_id":"269"}}
{"account_number":269,"balance":43317,"firstname":"Crosby","lastname":"Figueroa","age":34,"gender":"M","address":"910 Aurelia Court","employer":"Pyramia","email":"crosbyfigueroa@pyramia.com","city":"Leyner","state":"OH"}
{"index":{"_id":"271"}}
{"account_number":271,"balance":11864,"firstname":"Holt","lastname":"Walter","age":30,"gender":"F","address":"645 Poplar Avenue","employer":"Grupoli","email":"holtwalter@grupoli.com","city":"Mansfield","state":"OR"}
{"index":{"_id":"276"}}
{"account_number":276,"balance":11606,"firstname":"Pittman","lastname":"Mathis","age":23,"gender":"F","address":"567 Charles Place","employer":"Zuvy","email":"pittmanmathis@zuvy.com","city":"Roeville","state":"KY"}
{"index":{"_id":"283"}}
{"account_number":283,"balance":24070,"firstname":"Fuentes","lastname":"Foley","age":30,"gender":"M","address":"729 Walker Court","employer":"Knowlysis","email":"fuentesfoley@knowlysis.com","city":"Tryon","state":"TN"}
{"index":{"_id":"288"}}
{"account_number":288,"balance":27243,"firstname":"Wong","lastname":"Stone","age":39,"gender":"F","address":"440 Willoughby Street","employer":"Zentix","email":"wongstone@zentix.com","city":"Wheatfields","state":"DC"}
{"index":{"_id":"290"}}
{"account_number":290,"balance":26103,"firstname":"Neva","lastname":"Burgess","age":37,"gender":"F","address":"985 Wyona Street","employer":"Slofast","email":"nevaburgess@slofast.com","city":"Cawood","state":"DC"}
{"index":{"_id":"295"}}
{"account_number":295,"balance":37358,"firstname":"Howe","lastname":"Nash","age":20,"gender":"M","address":"833 Union Avenue","employer":"Aquacine","email":"howenash@aquacine.com","city":"Indio","state":"MN"}
{"index":{"_id":"303"}}
{"account_number":303,"balance":21976,"firstname":"Huffman","lastname":"Green","age":24,"gender":"F","address":"455 Colby Court","employer":"Comtest","email":"huffmangreen@comtest.com","city":"Weeksville","state":"UT"}
{"index":{"_id":"308"}}
{"account_number":308,"balance":33989,"firstname":"Glass","lastname":"Schroeder","age":25,"gender":"F","address":"670 Veterans Avenue","employer":"Realmo","email":"glassschroeder@realmo.com","city":"Gratton","state":"NY"}
{"index":{"_id":"310"}}
{"account_number":310,"balance":23049,"firstname":"Shannon","lastname":"Morton","age":39,"gender":"F","address":"412 Pleasant Place","employer":"Ovation","email":"shannonmorton@ovation.com","city":"Edgar","state":"AZ"}
{"index":{"_id":"315"}}
{"account_number":315,"balance":1314,"firstname":"Clare","lastname":"Morrow","age":33,"gender":"F","address":"728 Madeline Court","employer":"Gaptec","email":"claremorrow@gaptec.com","city":"Mapletown","state":"PA"}
{"index":{"_id":"322"}}
{"account_number":322,"balance":6303,"firstname":"Gilliam","lastname":"Horne","age":27,"gender":"M","address":"414 Florence Avenue","employer":"Shepard","email":"gilliamhorne@shepard.com","city":"Winesburg","state":"WY"}
{"index":{"_id":"327"}}
{"account_number":327,"balance":29294,"firstname":"Nell","lastname":"Contreras","age":27,"gender":"M","address":"694 Gold Street","employer":"Momentia","email":"nellcontreras@momentia.com","city":"Cumminsville","state":"AL"}
{"index":{"_id":"334"}}
{"account_number":334,"balance":9178,"firstname":"Cross","lastname":"Floyd","age":21,"gender":"F","address":"815 Herkimer Court","employer":"Maroptic","email":"crossfloyd@maroptic.com","city":"Kraemer","state":"AK"}
{"index":{"_id":"339"}}
{"account_number":339,"balance":3992,"firstname":"Franco","lastname":"Welch","age":38,"gender":"F","address":"776 Brightwater Court","employer":"Earthplex","email":"francowelch@earthplex.com","city":"Naomi","state":"ME"}
{"index":{"_id":"341"}}
{"account_number":341,"balance":44367,"firstname":"Alberta","lastname":"Bradford","age":30,"gender":"F","address":"670 Grant Avenue","employer":"Bugsall","email":"albertabradford@bugsall.com","city":"Romeville","state":"MT"}
{"index":{"_id":"346"}}
{"account_number":346,"balance":26594,"firstname":"Shelby","lastname":"Sanchez","age":36,"gender":"F","address":"257 Fillmore Avenue","employer":"Geekus","email":"shelbysanchez@geekus.com","city":"Seymour","state":"CO"}
{"index":{"_id":"353"}}
{"account_number":353,"balance":45182,"firstname":"Rivera","lastname":"Sherman","age":37,"gender":"M","address":"603 Garden Place","employer":"Bovis","email":"riverasherman@bovis.com","city":"Otranto","state":"CA"}
{"index":{"_id":"358"}}
{"account_number":358,"balance":44043,"firstname":"Hale","lastname":"Baldwin","age":40,"gender":"F","address":"845 Menahan Street","employer":"Kidgrease","email":"halebaldwin@kidgrease.com","city":"Day","state":"AK"}
{"index":{"_id":"360"}}
{"account_number":360,"balance":26651,"firstname":"Ward","lastname":"Hicks","age":34,"gender":"F","address":"592 Brighton Court","employer":"Biotica","email":"wardhicks@biotica.com","city":"Kanauga","state":"VT"}
{"index":{"_id":"365"}}
{"account_number":365,"balance":3176,"firstname":"Sanders","lastname":"Holder","age":31,"gender":"F","address":"453 Cypress Court","employer":"Geekola","email":"sandersholder@geekola.com","city":"Staples","state":"TN"}
{"index":{"_id":"372"}}
{"account_number":372,"balance":28566,"firstname":"Alba","lastname":"Forbes","age":24,"gender":"M","address":"814 Meserole Avenue","employer":"Isostream","email":"albaforbes@isostream.com","city":"Clarence","state":"OR"}
{"index":{"_id":"377"}}
{"account_number":377,"balance":5374,"firstname":"Margo","lastname":"Gay","age":34,"gender":"F","address":"613 Chase Court","employer":"Rotodyne","email":"margogay@rotodyne.com","city":"Waumandee","state":"KS"}
{"index":{"_id":"384"}}
{"account_number":384,"balance":48758,"firstname":"Sallie","lastname":"Houston","age":31,"gender":"F","address":"836 Polar Street","employer":"Squish","email":"salliehouston@squish.com","city":"Morningside","state":"NC"}
{"index":{"_id":"389"}}
{"account_number":389,"balance":8839,"firstname":"York","lastname":"Cummings","age":27,"gender":"M","address":"778 Centre Street","employer":"Insurity","email":"yorkcummings@insurity.com","city":"Freeburn","state":"RI"}
{"index":{"_id":"391"}}
{"account_number":391,"balance":14733,"firstname":"Holman","lastname":"Jordan","age":30,"gender":"M","address":"391 Forrest Street","employer":"Maineland","email":"holmanjordan@maineland.com","city":"Cade","state":"CT"}
{"index":{"_id":"396"}}
{"account_number":396,"balance":14613,"firstname":"Marsha","lastname":"Elliott","age":38,"gender":"F","address":"297 Liberty Avenue","employer":"Orbiflex","email":"marshaelliott@orbiflex.com","city":"Windsor","state":"TX"}
{"index":{"_id":"404"}}
{"account_number":404,"balance":34978,"firstname":"Massey","lastname":"Becker","age":26,"gender":"F","address":"930 Pitkin Avenue","employer":"Genekom","email":"masseybecker@genekom.com","city":"Blairstown","state":"OR"}
{"index":{"_id":"409"}}
{"account_number":409,"balance":36960,"firstname":"Maura","lastname":"Glenn","age":31,"gender":"M","address":"183 Poly Place","employer":"Viagreat","email":"mauraglenn@viagreat.com","city":"Foscoe","state":"DE"}
{"index":{"_id":"411"}}
{"account_number":411,"balance":1172,"firstname":"Guzman","lastname":"Whitfield","age":22,"gender":"M","address":"181 Perry Terrace","employer":"Springbee","email":"guzmanwhitfield@springbee.com","city":"Balm","state":"IN"}
{"index":{"_id":"416"}}
{"account_number":416,"balance":27169,"firstname":"Hunt","lastname":"Schwartz","age":28,"gender":"F","address":"461 Havens Place","employer":"Danja","email":"huntschwartz@danja.com","city":"Grenelefe","state":"NV"}
{"index":{"_id":"423"}}
{"account_number":423,"balance":38852,"firstname":"Hines","lastname":"Underwood","age":21,"gender":"F","address":"284 Louise Terrace","employer":"Namegen","email":"hinesunderwood@namegen.com","city":"Downsville","state":"CO"}
{"index":{"_id":"428"}}
{"account_number":428,"balance":13925,"firstname":"Stephens","lastname":"Cain","age":20,"gender":"F","address":"189 Summit Street","employer":"Rocklogic","email":"stephenscain@rocklogic.com","city":"Bourg","state":"HI"}
{"index":{"_id":"430"}}
{"account_number":430,"balance":15251,"firstname":"Alejandra","lastname":"Chavez","age":34,"gender":"M","address":"651 Butler Place","employer":"Gology","email":"alejandrachavez@gology.com","city":"Allensworth","state":"VT"}
{"index":{"_id":"435"}}
{"account_number":435,"balance":14654,"firstname":"Sue","lastname":"Lopez","age":22,"gender":"F","address":"632 Stone Avenue","employer":"Emergent","email":"suelopez@emergent.com","city":"Waterford","state":"TN"}
{"index":{"_id":"442"}}
{"account_number":442,"balance":36211,"firstname":"Lawanda","lastname":"Leon","age":27,"gender":"F","address":"126 Canal Avenue","employer":"Xixan","email":"lawandaleon@xixan.com","city":"Berwind","state":"TN"}
{"index":{"_id":"447"}}
{"account_number":447,"balance":11402,"firstname":"Lucia","lastname":"Livingston","age":35,"gender":"M","address":"773 Lake Avenue","employer":"Soprano","email":"lucialivingston@soprano.com","city":"Edgewater","state":"TN"}
{"index":{"_id":"454"}}
{"account_number":454,"balance":31687,"firstname":"Alicia","lastname":"Rollins","age":22,"gender":"F","address":"483 Verona Place","employer":"Boilcat","email":"aliciarollins@boilcat.com","city":"Lutsen","state":"MD"}
{"index":{"_id":"459"}}
{"account_number":459,"balance":18869,"firstname":"Pamela","lastname":"Henry","age":20,"gender":"F","address":"361 Locust Avenue","employer":"Imageflow","email":"pamelahenry@imageflow.com","city":"Greenfields","state":"OH"}
{"index":{"_id":"461"}}
{"account_number":461,"balance":38807,"firstname":"Mcbride","lastname":"Padilla","age":34,"gender":"F","address":"550 Borinquen Pl","employer":"Zepitope","email":"mcbridepadilla@zepitope.com","city":"Emory","state":"AZ"}
{"index":{"_id":"466"}}
{"account_number":466,"balance":25109,"firstname":"Marcie","lastname":"Mcmillan","age":30,"gender":"F","address":"947 Gain Court","employer":"Entroflex","email":"marciemcmillan@entroflex.com","city":"Ronco","state":"ND"}
{"index":{"_id":"473"}}
{"account_number":473,"balance":5391,"firstname":"Susan","lastname":"Luna","age":25,"gender":"F","address":"521 Bogart Street","employer":"Zaya","email":"susanluna@zaya.com","city":"Grazierville","state":"MI"}
{"index":{"_id":"478"}}
{"account_number":478,"balance":28044,"firstname":"Dana","lastname":"Decker","age":35,"gender":"M","address":"627 Dobbin Street","employer":"Acrodance","email":"danadecker@acrodance.com","city":"Sharon","state":"MN"}
{"index":{"_id":"480"}}
{"account_number":480,"balance":40807,"firstname":"Anastasia","lastname":"Parker","age":24,"gender":"M","address":"650 Folsom Place","employer":"Zilladyne","email":"anastasiaparker@zilladyne.com","city":"Oberlin","state":"WY"}
{"index":{"_id":"485"}}
{"account_number":485,"balance":44235,"firstname":"Albert","lastname":"Roberts","age":40,"gender":"M","address":"385 Harman Street","employer":"Stralum","email":"albertroberts@stralum.com","city":"Watrous","state":"NM"}
{"index":{"_id":"492"}}
{"account_number":492,"balance":31055,"firstname":"Burnett","lastname":"Briggs","age":35,"gender":"M","address":"987 Cass Place","employer":"Pharmex","email":"burnettbriggs@pharmex.com","city":"Cornfields","state":"TX"}
{"index":{"_id":"497"}}
{"account_number":497,"balance":13493,"firstname":"Doyle","lastname":"Jenkins","age":30,"gender":"M","address":"205 Nevins Street","employer":"Unia","email":"doylejenkins@unia.com","city":"Nicut","state":"DC"}
{"index":{"_id":"500"}}
{"account_number":500,"balance":39143,"firstname":"Pope","lastname":"Keith","age":28,"gender":"F","address":"537 Fane Court","employer":"Zboo","email":"popekeith@zboo.com","city":"Courtland","state":"AL"}
{"index":{"_id":"505"}}
{"account_number":505,"balance":45493,"firstname":"Shelley","lastname":"Webb","age":29,"gender":"M","address":"873 Crawford Avenue","employer":"Quadeebo","email":"shelleywebb@quadeebo.com","city":"Topanga","state":"IL"}
{"index":{"_id":"512"}}
{"account_number":512,"balance":47432,"firstname":"Alisha","lastname":"Morales","age":29,"gender":"M","address":"623 Batchelder Street","employer":"Terragen","email":"alishamorales@terragen.com","city":"Gilmore","state":"VA"}
{"index":{"_id":"517"}}
{"account_number":517,"balance":3022,"firstname":"Allyson","lastname":"Walls","age":38,"gender":"F","address":"334 Coffey Street","employer":"Gorganic","email":"allysonwalls@gorganic.com","city":"Dahlen","state":"GA"}
{"index":{"_id":"524"}}
{"account_number":524,"balance":49334,"firstname":"Salas","lastname":"Farley","age":30,"gender":"F","address":"499 Trucklemans Lane","employer":"Xumonk","email":"salasfarley@xumonk.com","city":"Noxen","state":"AL"}
{"index":{"_id":"529"}}
{"account_number":529,"balance":21788,"firstname":"Deann","lastname":"Fisher","age":23,"gender":"F","address":"511 Buffalo Avenue","employer":"Twiist","email":"deannfisher@twiist.com","city":"Templeton","state":"WA"}
{"index":{"_id":"531"}}
{"account_number":531,"balance":39770,"firstname":"Janet","lastname":"Pena","age":38,"gender":"M","address":"645 Livonia Avenue","employer":"Corecom","email":"janetpena@corecom.com","city":"Garberville","state":"OK"}
{"index":{"_id":"536"}}
{"account_number":536,"balance":6255,"firstname":"Emma","lastname":"Adkins","age":33,"gender":"F","address":"971 Calder Place","employer":"Ontagene","email":"emmaadkins@ontagene.com","city":"Ruckersville","state":"GA"}
{"index":{"_id":"543"}}
{"account_number":543,"balance":48022,"firstname":"Marina","lastname":"Rasmussen","age":31,"gender":"M","address":"446 Love Lane","employer":"Crustatia","email":"marinarasmussen@crustatia.com","city":"Statenville","state":"MD"}
{"index":{"_id":"548"}}
{"account_number":548,"balance":36930,"firstname":"Sandra","lastname":"Andrews","age":37,"gender":"M","address":"973 Prospect Street","employer":"Datagene","email":"sandraandrews@datagene.com","city":"Inkerman","state":"MO"}
{"index":{"_id":"550"}}
{"account_number":550,"balance":32238,"firstname":"Walsh","lastname":"Goodwin","age":22,"gender":"M","address":"953 Canda Avenue","employer":"Proflex","email":"walshgoodwin@proflex.com","city":"Ypsilanti","state":"MT"}
{"index":{"_id":"555"}}
{"account_number":555,"balance":10750,"firstname":"Fannie","lastname":"Slater","age":31,"gender":"M","address":"457 Tech Place","employer":"Kineticut","email":"fannieslater@kineticut.com","city":"Basye","state":"MO"}
{"index":{"_id":"562"}}
{"account_number":562,"balance":10737,"firstname":"Sarah","lastname":"Strong","age":39,"gender":"F","address":"177 Pioneer Street","employer":"Megall","email":"sarahstrong@megall.com","city":"Ladera","state":"WY"}
{"index":{"_id":"567"}}
{"account_number":567,"balance":6507,"firstname":"Diana","lastname":"Dominguez","age":40,"gender":"M","address":"419 Albany Avenue","employer":"Ohmnet","email":"dianadominguez@ohmnet.com","city":"Wildwood","state":"TX"}
{"index":{"_id":"574"}}
{"account_number":574,"balance":32954,"firstname":"Andrea","lastname":"Mosley","age":24,"gender":"M","address":"368 Throop Avenue","employer":"Musix","email":"andreamosley@musix.com","city":"Blende","state":"DC"}
{"index":{"_id":"579"}}
{"account_number":579,"balance":12044,"firstname":"Banks","lastname":"Sawyer","age":36,"gender":"M","address":"652 Doone Court","employer":"Rooforia","email":"bankssawyer@rooforia.com","city":"Foxworth","state":"ND"}
{"index":{"_id":"581"}}
{"account_number":581,"balance":16525,"firstname":"Fuller","lastname":"Mcintyre","age":32,"gender":"M","address":"169 Bergen Place","employer":"Applideck","email":"fullermcintyre@applideck.com","city":"Kenvil","state":"NY"}
{"index":{"_id":"586"}}
{"account_number":586,"balance":13644,"firstname":"Love","lastname":"Velasquez","age":26,"gender":"F","address":"290 Girard Street","employer":"Zomboid","email":"lovevelasquez@zomboid.com","city":"Villarreal","state":"SD"}
{"index":{"_id":"593"}}
{"account_number":593,"balance":41230,"firstname":"Muriel","lastname":"Vazquez","age":37,"gender":"M","address":"395 Montgomery Street","employer":"Sustenza","email":"murielvazquez@sustenza.com","city":"Strykersville","state":"OK"}
{"index":{"_id":"598"}}
{"account_number":598,"balance":33251,"firstname":"Morgan","lastname":"Coleman","age":33,"gender":"M","address":"324 McClancy Place","employer":"Aclima","email":"morgancoleman@aclima.com","city":"Bowden","state":"WA"}
{"index":{"_id":"601"}}
{"account_number":601,"balance":20796,"firstname":"Vickie","lastname":"Valentine","age":34,"gender":"F","address":"432 Bassett Avenue","employer":"Comvene","email":"vickievalentine@comvene.com","city":"Teasdale","state":"UT"}
{"index":{"_id":"606"}}
{"account_number":606,"balance":28770,"firstname":"Michael","lastname":"Bray","age":31,"gender":"M","address":"935 Lake Place","employer":"Telepark","email":"michaelbray@telepark.com","city":"Lemoyne","state":"CT"}
{"index":{"_id":"613"}}
{"account_number":613,"balance":39340,"firstname":"Eddie","lastname":"Mccarty","age":34,"gender":"F","address":"971 Richards Street","employer":"Bisba","email":"eddiemccarty@bisba.com","city":"Fruitdale","state":"NY"}
{"index":{"_id":"618"}}
{"account_number":618,"balance":8976,"firstname":"Cheri","lastname":"Ford","age":30,"gender":"F","address":"803 Ridgewood Avenue","employer":"Zorromop","email":"cheriford@zorromop.com","city":"Gambrills","state":"VT"}
{"index":{"_id":"620"}}
{"account_number":620,"balance":7224,"firstname":"Coleen","lastname":"Bartlett","age":38,"gender":"M","address":"761 Carroll Street","employer":"Idealis","email":"coleenbartlett@idealis.com","city":"Mathews","state":"DE"}
{"index":{"_id":"625"}}
{"account_number":625,"balance":46010,"firstname":"Cynthia","lastname":"Johnston","age":23,"gender":"M","address":"142 Box Street","employer":"Zentry","email":"cynthiajohnston@zentry.com","city":"Makena","state":"MA"}
{"index":{"_id":"632"}}
{"account_number":632,"balance":40470,"firstname":"Kay","lastname":"Warren","age":20,"gender":"F","address":"422 Alabama Avenue","employer":"Realysis","email":"kaywarren@realysis.com","city":"Homestead","state":"HI"}
{"index":{"_id":"637"}}
{"account_number":637,"balance":3169,"firstname":"Kathy","lastname":"Carter","age":27,"gender":"F","address":"410 Jamison Lane","employer":"Limage","email":"kathycarter@limage.com","city":"Ernstville","state":"WA"}
{"index":{"_id":"644"}}
{"account_number":644,"balance":44021,"firstname":"Etta","lastname":"Miller","age":21,"gender":"F","address":"376 Lawton Street","employer":"Bluegrain","email":"ettamiller@bluegrain.com","city":"Baker","state":"MD"}
{"index":{"_id":"649"}}
{"account_number":649,"balance":20275,"firstname":"Jeanine","lastname":"Malone","age":26,"gender":"F","address":"114 Dodworth Street","employer":"Nixelt","email":"jeaninemalone@nixelt.com","city":"Keyport","state":"AK"}
{"index":{"_id":"651"}}
{"account_number":651,"balance":18360,"firstname":"Young","lastname":"Reeves","age":34,"gender":"M","address":"581 Plaza Street","employer":"Krog","email":"youngreeves@krog.com","city":"Sussex","state":"WY"}
{"index":{"_id":"656"}}
{"account_number":656,"balance":21632,"firstname":"Olson","lastname":"Hunt","age":36,"gender":"M","address":"342 Jaffray Street","employer":"Volax","email":"olsonhunt@volax.com","city":"Bangor","state":"WA"}
{"index":{"_id":"663"}}
{"account_number":663,"balance":2456,"firstname":"Rollins","lastname":"Richards","age":37,"gender":"M","address":"129 Sullivan Place","employer":"Geostele","email":"rollinsrichards@geostele.com","city":"Morgandale","state":"FL"}
{"index":{"_id":"668"}}
{"account_number":668,"balance":45069,"firstname":"Potter","lastname":"Michael","age":27,"gender":"M","address":"803 Glenmore Avenue","employer":"Ontality","email":"pottermichael@ontality.com","city":"Newkirk","state":"KS"}
{"index":{"_id":"670"}}
{"account_number":670,"balance":10178,"firstname":"Ollie","lastname":"Riley","age":22,"gender":"M","address":"252 Jackson Place","employer":"Adornica","email":"ollieriley@adornica.com","city":"Brethren","state":"WI"}
{"index":{"_id":"675"}}
{"account_number":675,"balance":36102,"firstname":"Fisher","lastname":"Shepard","age":27,"gender":"F","address":"859 Varick Street","employer":"Qot","email":"fishershepard@qot.com","city":"Diaperville","state":"MD"}
{"index":{"_id":"682"}}
{"account_number":682,"balance":14168,"firstname":"Anne","lastname":"Hale","age":22,"gender":"F","address":"708 Anthony Street","employer":"Cytrek","email":"annehale@cytrek.com","city":"Beechmont","state":"WV"}
{"index":{"_id":"687"}}
{"account_number":687,"balance":48630,"firstname":"Caroline","lastname":"Cox","age":31,"gender":"M","address":"626 Hillel Place","employer":"Opticon","email":"carolinecox@opticon.com","city":"Loma","state":"ND"}
{"index":{"_id":"694"}}
{"account_number":694,"balance":33125,"firstname":"Craig","lastname":"Palmer","age":31,"gender":"F","address":"273 Montrose Avenue","employer":"Comvey","email":"craigpalmer@comvey.com","city":"Cleary","state":"OK"}
{"index":{"_id":"699"}}
{"account_number":699,"balance":4156,"firstname":"Gallagher","lastname":"Marshall","age":37,"gender":"F","address":"648 Clifford Place","employer":"Exiand","email":"gallaghermarshall@exiand.com","city":"Belfair","state":"KY"}
{"index":{"_id":"702"}}
{"account_number":702,"balance":46490,"firstname":"Meadows","lastname":"Delgado","age":26,"gender":"M","address":"612 Jardine Place","employer":"Daisu","email":"meadowsdelgado@daisu.com","city":"Venice","state":"AR"}
{"index":{"_id":"707"}}
{"account_number":707,"balance":30325,"firstname":"Sonya","lastname":"Trevino","age":30,"gender":"F","address":"181 Irving Place","employer":"Atgen","email":"sonyatrevino@atgen.com","city":"Enetai","state":"TN"}
{"index":{"_id":"714"}}
{"account_number":714,"balance":16602,"firstname":"Socorro","lastname":"Murray","age":34,"gender":"F","address":"810 Manhattan Court","employer":"Isoswitch","email":"socorromurray@isoswitch.com","city":"Jugtown","state":"AZ"}
{"index":{"_id":"719"}}
{"account_number":719,"balance":33107,"firstname":"Leanna","lastname":"Reed","age":25,"gender":"F","address":"528 Krier Place","employer":"Rodeology","email":"leannareed@rodeology.com","city":"Carrizo","state":"WI"}
{"index":{"_id":"721"}}
{"account_number":721,"balance":32958,"firstname":"Mara","lastname":"Dickson","age":26,"gender":"M","address":"810 Harrison Avenue","employer":"Comtours","email":"maradickson@comtours.com","city":"Thynedale","state":"DE"}
{"index":{"_id":"726"}}
{"account_number":726,"balance":44737,"firstname":"Rosemary","lastname":"Salazar","age":21,"gender":"M","address":"290 Croton Loop","employer":"Rockabye","email":"rosemarysalazar@rockabye.com","city":"Helen","state":"IA"}
{"index":{"_id":"733"}}
{"account_number":733,"balance":15722,"firstname":"Lakeisha","lastname":"Mccarthy","age":37,"gender":"M","address":"782 Turnbull Avenue","employer":"Exosis","email":"lakeishamccarthy@exosis.com","city":"Caberfae","state":"NM"}
{"index":{"_id":"738"}}
{"account_number":738,"balance":44936,"firstname":"Rosalind","lastname":"Hunter","age":32,"gender":"M","address":"644 Eaton Court","employer":"Zolarity","email":"rosalindhunter@zolarity.com","city":"Cataract","state":"SD"}
{"index":{"_id":"740"}}
{"account_number":740,"balance":6143,"firstname":"Chambers","lastname":"Hahn","age":22,"gender":"M","address":"937 Windsor Place","employer":"Medalert","email":"chambershahn@medalert.com","city":"Dorneyville","state":"DC"}
{"index":{"_id":"745"}}
{"account_number":745,"balance":4572,"firstname":"Jacobs","lastname":"Sweeney","age":32,"gender":"M","address":"189 Lott Place","employer":"Comtent","email":"jacobssweeney@comtent.com","city":"Advance","state":"NJ"}
{"index":{"_id":"752"}}
{"account_number":752,"balance":14039,"firstname":"Jerry","lastname":"Rush","age":31,"gender":"M","address":"632 Dank Court","employer":"Ebidco","email":"jerryrush@ebidco.com","city":"Geyserville","state":"AR"}
{"index":{"_id":"757"}}
{"account_number":757,"balance":34628,"firstname":"Mccullough","lastname":"Moore","age":30,"gender":"F","address":"304 Hastings Street","employer":"Nikuda","email":"mcculloughmoore@nikuda.com","city":"Charco","state":"DC"}
{"index":{"_id":"764"}}
{"account_number":764,"balance":3728,"firstname":"Noemi","lastname":"Gill","age":30,"gender":"M","address":"427 Chester Street","employer":"Avit","email":"noemigill@avit.com","city":"Chesterfield","state":"AL"}
{"index":{"_id":"769"}}
{"account_number":769,"balance":15362,"firstname":"Francis","lastname":"Beck","age":28,"gender":"M","address":"454 Livingston Street","employer":"Furnafix","email":"francisbeck@furnafix.com","city":"Dunnavant","state":"HI"}
{"index":{"_id":"771"}}
{"account_number":771,"balance":32784,"firstname":"Jocelyn","lastname":"Boone","age":23,"gender":"M","address":"513 Division Avenue","employer":"Collaire","email":"jocelynboone@collaire.com","city":"Lisco","state":"VT"}
{"index":{"_id":"776"}}
{"account_number":776,"balance":29177,"firstname":"Duke","lastname":"Atkinson","age":24,"gender":"M","address":"520 Doscher Street","employer":"Tripsch","email":"dukeatkinson@tripsch.com","city":"Lafferty","state":"NC"}
{"index":{"_id":"783"}}
{"account_number":783,"balance":11911,"firstname":"Faith","lastname":"Cooper","age":25,"gender":"F","address":"539 Rapelye Street","employer":"Insuron","email":"faithcooper@insuron.com","city":"Jennings","state":"MN"}
{"index":{"_id":"788"}}
{"account_number":788,"balance":12473,"firstname":"Marianne","lastname":"Aguilar","age":39,"gender":"F","address":"213 Holly Street","employer":"Marqet","email":"marianneaguilar@marqet.com","city":"Alfarata","state":"HI"}
{"index":{"_id":"790"}}
{"account_number":790,"balance":29912,"firstname":"Ellis","lastname":"Sullivan","age":39,"gender":"F","address":"877 Coyle Street","employer":"Enersave","email":"ellissullivan@enersave.com","city":"Canby","state":"MS"}
{"index":{"_id":"795"}}
{"account_number":795,"balance":31450,"firstname":"Bruce","lastname":"Avila","age":34,"gender":"M","address":"865 Newkirk Placez","employer":"Plasmosis","email":"bruceavila@plasmosis.com","city":"Ada","state":"ID"}
{"index":{"_id":"803"}}
{"account_number":803,"balance":49567,"firstname":"Marissa","lastname":"Spears","age":25,"gender":"M","address":"963 Highland Avenue","employer":"Centregy","email":"marissaspears@centregy.com","city":"Bloomington","state":"MS"}
{"index":{"_id":"808"}}
{"account_number":808,"balance":11251,"firstname":"Nola","lastname":"Quinn","age":20,"gender":"M","address":"863 Wythe Place","employer":"Iplax","email":"nolaquinn@iplax.com","city":"Cuylerville","state":"NH"}
{"index":{"_id":"810"}}
{"account_number":810,"balance":10563,"firstname":"Alyssa","lastname":"Ortega","age":40,"gender":"M","address":"977 Clymer Street","employer":"Eventage","email":"alyssaortega@eventage.com","city":"Convent","state":"SC"}
{"index":{"_id":"815"}}
{"account_number":815,"balance":19336,"firstname":"Guthrie","lastname":"Morse","age":30,"gender":"M","address":"685 Vandalia Avenue","employer":"Gronk","email":"guthriemorse@gronk.com","city":"Fowlerville","state":"OR"}
{"index":{"_id":"822"}}
{"account_number":822,"balance":13024,"firstname":"Hicks","lastname":"Farrell","age":25,"gender":"M","address":"468 Middleton Street","employer":"Zolarex","email":"hicksfarrell@zolarex.com","city":"Columbus","state":"OR"}
{"index":{"_id":"827"}}
{"account_number":827,"balance":37536,"firstname":"Naomi","lastname":"Ball","age":29,"gender":"F","address":"319 Stewart Street","employer":"Isotronic","email":"naomiball@isotronic.com","city":"Trona","state":"NM"}
{"index":{"_id":"834"}}
{"account_number":834,"balance":38049,"firstname":"Sybil","lastname":"Carrillo","age":25,"gender":"M","address":"359 Baughman Place","employer":"Phuel","email":"sybilcarrillo@phuel.com","city":"Kohatk","state":"CT"}
{"index":{"_id":"839"}}
{"account_number":839,"balance":38292,"firstname":"Langley","lastname":"Neal","age":39,"gender":"F","address":"565 Newton Street","employer":"Liquidoc","email":"langleyneal@liquidoc.com","city":"Osage","state":"AL"}
{"index":{"_id":"841"}}
{"account_number":841,"balance":28291,"firstname":"Dalton","lastname":"Waters","age":21,"gender":"M","address":"859 Grand Street","employer":"Malathion","email":"daltonwaters@malathion.com","city":"Tonopah","state":"AZ"}
{"index":{"_id":"846"}}
{"account_number":846,"balance":35099,"firstname":"Maureen","lastname":"Glass","age":22,"gender":"M","address":"140 Amherst Street","employer":"Stelaecor","email":"maureenglass@stelaecor.com","city":"Cucumber","state":"IL"}
{"index":{"_id":"853"}}
{"account_number":853,"balance":38353,"firstname":"Travis","lastname":"Parks","age":40,"gender":"M","address":"930 Bay Avenue","employer":"Pyramax","email":"travisparks@pyramax.com","city":"Gadsden","state":"ND"}
{"index":{"_id":"858"}}
{"account_number":858,"balance":23194,"firstname":"Small","lastname":"Hatfield","age":36,"gender":"M","address":"593 Tennis Court","employer":"Letpro","email":"smallhatfield@letpro.com","city":"Haena","state":"KS"}
{"index":{"_id":"860"}}
{"account_number":860,"balance":23613,"firstname":"Clark","lastname":"Boyd","age":37,"gender":"M","address":"501 Rock Street","employer":"Deepends","email":"clarkboyd@deepends.com","city":"Whitewater","state":"MA"}
{"index":{"_id":"865"}}
{"account_number":865,"balance":10574,"firstname":"Cook","lastname":"Kelley","age":28,"gender":"F","address":"865 Lincoln Terrace","employer":"Quizmo","email":"cookkelley@quizmo.com","city":"Kansas","state":"KY"}
{"index":{"_id":"872"}}
{"account_number":872,"balance":26314,"firstname":"Jane","lastname":"Greer","age":36,"gender":"F","address":"717 Hewes Street","employer":"Newcube","email":"janegreer@newcube.com","city":"Delshire","state":"DE"}
{"index":{"_id":"877"}}
{"account_number":877,"balance":42879,"firstname":"Tracey","lastname":"Ruiz","age":34,"gender":"F","address":"141 Tompkins Avenue","employer":"Waab","email":"traceyruiz@waab.com","city":"Zeba","state":"NM"}
{"index":{"_id":"884"}}
{"account_number":884,"balance":29316,"firstname":"Reva","lastname":"Rosa","age":40,"gender":"M","address":"784 Greene Avenue","employer":"Urbanshee","email":"revarosa@urbanshee.com","city":"Bakersville","state":"MS"}
{"index":{"_id":"889"}}
{"account_number":889,"balance":26464,"firstname":"Fischer","lastname":"Klein","age":38,"gender":"F","address":"948 Juliana Place","employer":"Comtext","email":"fischerklein@comtext.com","city":"Jackpot","state":"PA"}
{"index":{"_id":"891"}}
{"account_number":891,"balance":34829,"firstname":"Jacobson","lastname":"Clemons","age":24,"gender":"F","address":"507 Wilson Street","employer":"Quilm","email":"jacobsonclemons@quilm.com","city":"Muir","state":"TX"}
{"index":{"_id":"896"}}
{"account_number":896,"balance":31947,"firstname":"Buckley","lastname":"Peterson","age":26,"gender":"M","address":"217 Beayer Place","employer":"Earwax","email":"buckleypeterson@earwax.com","city":"Franklin","state":"DE"}
{"index":{"_id":"904"}}
{"account_number":904,"balance":27707,"firstname":"Mendez","lastname":"Mcneil","age":26,"gender":"M","address":"431 Halsey Street","employer":"Macronaut","email":"mendezmcneil@macronaut.com","city":"Troy","state":"OK"}
{"index":{"_id":"909"}}
{"account_number":909,"balance":18421,"firstname":"Stark","lastname":"Lewis","age":36,"gender":"M","address":"409 Tilden Avenue","employer":"Frosnex","email":"starklewis@frosnex.com","city":"Axis","state":"CA"}
{"index":{"_id":"911"}}
{"account_number":911,"balance":42655,"firstname":"Annie","lastname":"Lyons","age":21,"gender":"M","address":"518 Woods Place","employer":"Enerforce","email":"annielyons@enerforce.com","city":"Stagecoach","state":"MA"}
{"index":{"_id":"916"}}
{"account_number":916,"balance":47887,"firstname":"Jarvis","lastname":"Alexander","age":40,"gender":"M","address":"406 Bergen Avenue","employer":"Equitax","email":"jarvisalexander@equitax.com","city":"Haring","state":"KY"}
{"index":{"_id":"923"}}
{"account_number":923,"balance":48466,"firstname":"Mueller","lastname":"Mckee","age":26,"gender":"M","address":"298 Ruby Street","employer":"Luxuria","email":"muellermckee@luxuria.com","city":"Coleville","state":"TN"}
{"index":{"_id":"928"}}
{"account_number":928,"balance":19611,"firstname":"Hester","lastname":"Copeland","age":22,"gender":"F","address":"425 Cropsey Avenue","employer":"Dymi","email":"hestercopeland@dymi.com","city":"Wolcott","state":"NE"}
{"index":{"_id":"930"}}
{"account_number":930,"balance":47257,"firstname":"Kinney","lastname":"Lawson","age":39,"gender":"M","address":"501 Raleigh Place","employer":"Neptide","email":"kinneylawson@neptide.com","city":"Deltaville","state":"MD"}
{"index":{"_id":"935"}}
{"account_number":935,"balance":4959,"firstname":"Flowers","lastname":"Robles","age":30,"gender":"M","address":"201 Hull Street","employer":"Xelegyl","email":"flowersrobles@xelegyl.com","city":"Rehrersburg","state":"AL"}
{"index":{"_id":"942"}}
{"account_number":942,"balance":21299,"firstname":"Hamilton","lastname":"Clayton","age":26,"gender":"M","address":"413 Debevoise Street","employer":"Architax","email":"hamiltonclayton@architax.com","city":"Terlingua","state":"NM"}
{"index":{"_id":"947"}}
{"account_number":947,"balance":22039,"firstname":"Virgie","lastname":"Garza","age":30,"gender":"M","address":"903 Matthews Court","employer":"Plasmox","email":"virgiegarza@plasmox.com","city":"Somerset","state":"WY"}
{"index":{"_id":"954"}}
{"account_number":954,"balance":49404,"firstname":"Jenna","lastname":"Martin","age":22,"gender":"M","address":"688 Hart Street","employer":"Zinca","email":"jennamartin@zinca.com","city":"Oasis","state":"MD"}
{"index":{"_id":"959"}}
{"account_number":959,"balance":34743,"firstname":"Shaffer","lastname":"Cervantes","age":40,"gender":"M","address":"931 Varick Avenue","employer":"Oceanica","email":"shaffercervantes@oceanica.com","city":"Bowie","state":"AL"}
{"index":{"_id":"961"}}
{"account_number":961,"balance":43219,"firstname":"Betsy","lastname":"Hyde","age":27,"gender":"F","address":"183 Junius Street","employer":"Tubalum","email":"betsyhyde@tubalum.com","city":"Driftwood","state":"TX"}
{"index":{"_id":"966"}}
{"account_number":966,"balance":20619,"firstname":"Susanne","lastname":"Rodriguez","age":35,"gender":"F","address":"255 Knickerbocker Avenue","employer":"Comtrek","email":"susannerodriguez@comtrek.com","city":"Trinway","state":"TX"}
{"index":{"_id":"973"}}
{"account_number":973,"balance":45756,"firstname":"Rice","lastname":"Farmer","age":31,"gender":"M","address":"476 Nassau Avenue","employer":"Photobin","email":"ricefarmer@photobin.com","city":"Suitland","state":"ME"}
{"index":{"_id":"978"}}
{"account_number":978,"balance":21459,"firstname":"Melanie","lastname":"Rojas","age":33,"gender":"M","address":"991 Java Street","employer":"Kage","email":"melanierojas@kage.com","city":"Greenock","state":"VT"}
{"index":{"_id":"980"}}
{"account_number":980,"balance":42436,"firstname":"Cash","lastname":"Collier","age":33,"gender":"F","address":"999 Sapphire Street","employer":"Ceprene","email":"cashcollier@ceprene.com","city":"Glidden","state":"AK"}
{"index":{"_id":"985"}}
{"account_number":985,"balance":20083,"firstname":"Martin","lastname":"Gardner","age":28,"gender":"F","address":"644 Fairview Place","employer":"Golistic","email":"martingardner@golistic.com","city":"Connerton","state":"NJ"}
{"index":{"_id":"992"}}
{"account_number":992,"balance":11413,"firstname":"Kristie","lastname":"Kennedy","age":33,"gender":"F","address":"750 Hudson Avenue","employer":"Ludak","email":"kristiekennedy@ludak.com","city":"Warsaw","state":"WY"}
{"index":{"_id":"997"}}
{"account_number":997,"balance":25311,"firstname":"Combs","lastname":"Frederick","age":20,"gender":"M","address":"586 Lloyd Court","employer":"Pathways","email":"combsfrederick@pathways.com","city":"Williamson","state":"CA"}
{"index":{"_id":"3"}}
{"account_number":3,"balance":44947,"firstname":"Levine","lastname":"Burks","age":26,"gender":"F","address":"328 Wilson Avenue","employer":"Amtap","email":"levineburks@amtap.com","city":"Cochranville","state":"HI"}
{"index":{"_id":"8"}}
{"account_number":8,"balance":48868,"firstname":"Jan","lastname":"Burns","age":35,"gender":"M","address":"699 Visitation Place","employer":"Glasstep","email":"janburns@glasstep.com","city":"Wakulla","state":"AZ"}
{"index":{"_id":"10"}}
{"account_number":10,"balance":46170,"firstname":"Dominique","lastname":"Park","age":37,"gender":"F","address":"100 Gatling Place","employer":"Conjurica","email":"dominiquepark@conjurica.com","city":"Omar","state":"NJ"}
{"index":{"_id":"15"}}
{"account_number":15,"balance":43456,"firstname":"Bobbie","lastname":"Sexton","age":21,"gender":"M","address":"232 Sedgwick Place","employer":"Zytrex","email":"bobbiesexton@zytrex.com","city":"Hendersonville","state":"CA"}
{"index":{"_id":"22"}}
{"account_number":22,"balance":40283,"firstname":"Barrera","lastname":"Terrell","age":23,"gender":"F","address":"292 Orange Street","employer":"Steelfab","email":"barreraterrell@steelfab.com","city":"Bynum","state":"ME"}
{"index":{"_id":"27"}}
{"account_number":27,"balance":6176,"firstname":"Meyers","lastname":"Williamson","age":26,"gender":"F","address":"675 Henderson Walk","employer":"Plexia","email":"meyerswilliamson@plexia.com","city":"Richmond","state":"AZ"}
{"index":{"_id":"34"}}
{"account_number":34,"balance":35379,"firstname":"Ellison","lastname":"Kim","age":30,"gender":"F","address":"986 Revere Place","employer":"Signity","email":"ellisonkim@signity.com","city":"Sehili","state":"IL"}
{"index":{"_id":"39"}}
{"account_number":39,"balance":38688,"firstname":"Bowers","lastname":"Mendez","age":22,"gender":"F","address":"665 Bennet Court","employer":"Farmage","email":"bowersmendez@farmage.com","city":"Duryea","state":"PA"}
{"index":{"_id":"41"}}
{"account_number":41,"balance":36060,"firstname":"Hancock","lastname":"Holden","age":20,"gender":"M","address":"625 Gaylord Drive","employer":"Poochies","email":"hancockholden@poochies.com","city":"Alamo","state":"KS"}
{"index":{"_id":"46"}}
{"account_number":46,"balance":12351,"firstname":"Karla","lastname":"Bowman","age":23,"gender":"M","address":"554 Chapel Street","employer":"Undertap","email":"karlabowman@undertap.com","city":"Sylvanite","state":"DC"}
{"index":{"_id":"53"}}
{"account_number":53,"balance":28101,"firstname":"Kathryn","lastname":"Payne","age":29,"gender":"F","address":"467 Louis Place","employer":"Katakana","email":"kathrynpayne@katakana.com","city":"Harviell","state":"SD"}
{"index":{"_id":"58"}}
{"account_number":58,"balance":31697,"firstname":"Marva","lastname":"Cannon","age":40,"gender":"M","address":"993 Highland Place","employer":"Comcubine","email":"marvacannon@comcubine.com","city":"Orviston","state":"MO"}
{"index":{"_id":"60"}}
{"account_number":60,"balance":45955,"firstname":"Maude","lastname":"Casey","age":31,"gender":"F","address":"566 Strauss Street","employer":"Quilch","email":"maudecasey@quilch.com","city":"Enlow","state":"GA"}
{"index":{"_id":"65"}}
{"account_number":65,"balance":23282,"firstname":"Leonor","lastname":"Pruitt","age":24,"gender":"M","address":"974 Terrace Place","employer":"Velos","email":"leonorpruitt@velos.com","city":"Devon","state":"WI"}
{"index":{"_id":"72"}}
{"account_number":72,"balance":9732,"firstname":"Barlow","lastname":"Rhodes","age":25,"gender":"F","address":"891 Clinton Avenue","employer":"Zialactic","email":"barlowrhodes@zialactic.com","city":"Echo","state":"TN"}
{"index":{"_id":"77"}}
{"account_number":77,"balance":5724,"firstname":"Byrd","lastname":"Conley","age":24,"gender":"F","address":"698 Belmont Avenue","employer":"Zidox","email":"byrdconley@zidox.com","city":"Rockbridge","state":"SC"}
{"index":{"_id":"84"}}
{"account_number":84,"balance":3001,"firstname":"Hutchinson","lastname":"Newton","age":34,"gender":"F","address":"553 Locust Street","employer":"Zaggles","email":"hutchinsonnewton@zaggles.com","city":"Snyderville","state":"DC"}
{"index":{"_id":"89"}}
{"account_number":89,"balance":13263,"firstname":"Mcdowell","lastname":"Bradley","age":28,"gender":"M","address":"960 Howard Alley","employer":"Grok","email":"mcdowellbradley@grok.com","city":"Toftrees","state":"TX"}
{"index":{"_id":"91"}}
{"account_number":91,"balance":29799,"firstname":"Vonda","lastname":"Galloway","age":20,"gender":"M","address":"988 Voorhies Avenue","employer":"Illumity","email":"vondagalloway@illumity.com","city":"Holcombe","state":"HI"}
{"index":{"_id":"96"}}
{"account_number":96,"balance":15933,"firstname":"Shirley","lastname":"Edwards","age":38,"gender":"M","address":"817 Caton Avenue","employer":"Equitox","email":"shirleyedwards@equitox.com","city":"Nelson","state":"MA"}
{"index":{"_id":"104"}}
{"account_number":104,"balance":32619,"firstname":"Casey","lastname":"Roth","age":29,"gender":"M","address":"963 Railroad Avenue","employer":"Hotcakes","email":"caseyroth@hotcakes.com","city":"Davenport","state":"OH"}
{"index":{"_id":"109"}}
{"account_number":109,"balance":25812,"firstname":"Gretchen","lastname":"Dawson","age":31,"gender":"M","address":"610 Bethel Loop","employer":"Tetak","email":"gretchendawson@tetak.com","city":"Hailesboro","state":"CO"}
{"index":{"_id":"111"}}
{"account_number":111,"balance":1481,"firstname":"Traci","lastname":"Allison","age":35,"gender":"M","address":"922 Bryant Street","employer":"Enjola","email":"traciallison@enjola.com","city":"Robinette","state":"OR"}
{"index":{"_id":"116"}}
{"account_number":116,"balance":21335,"firstname":"Hobbs","lastname":"Wright","age":24,"gender":"M","address":"965 Temple Court","employer":"Netbook","email":"hobbswright@netbook.com","city":"Strong","state":"CA"}
{"index":{"_id":"123"}}
{"account_number":123,"balance":3079,"firstname":"Cleo","lastname":"Beach","age":27,"gender":"F","address":"653 Haring Street","employer":"Proxsoft","email":"cleobeach@proxsoft.com","city":"Greensburg","state":"ME"}
{"index":{"_id":"128"}}
{"account_number":128,"balance":3556,"firstname":"Mack","lastname":"Bullock","age":34,"gender":"F","address":"462 Ingraham Street","employer":"Terascape","email":"mackbullock@terascape.com","city":"Eureka","state":"PA"}
{"index":{"_id":"130"}}
{"account_number":130,"balance":24171,"firstname":"Roxie","lastname":"Cantu","age":33,"gender":"M","address":"841 Catherine Street","employer":"Skybold","email":"roxiecantu@skybold.com","city":"Deputy","state":"NE"}
{"index":{"_id":"135"}}
{"account_number":135,"balance":24885,"firstname":"Stevenson","lastname":"Crosby","age":40,"gender":"F","address":"473 Boardwalk ","employer":"Accel","email":"stevensoncrosby@accel.com","city":"Norris","state":"OK"}
{"index":{"_id":"142"}}
{"account_number":142,"balance":4544,"firstname":"Vang","lastname":"Hughes","age":27,"gender":"M","address":"357 Landis Court","employer":"Bolax","email":"vanghughes@bolax.com","city":"Emerald","state":"WY"}
{"index":{"_id":"147"}}
{"account_number":147,"balance":35921,"firstname":"Charmaine","lastname":"Whitney","age":28,"gender":"F","address":"484 Seton Place","employer":"Comveyer","email":"charmainewhitney@comveyer.com","city":"Dexter","state":"DC"}
{"index":{"_id":"154"}}
{"account_number":154,"balance":40945,"firstname":"Burns","lastname":"Solis","age":31,"gender":"M","address":"274 Lorraine Street","employer":"Rodemco","email":"burnssolis@rodemco.com","city":"Ballico","state":"WI"}
{"index":{"_id":"159"}}
{"account_number":159,"balance":1696,"firstname":"Alvarez","lastname":"Mack","age":22,"gender":"F","address":"897 Manor Court","employer":"Snorus","email":"alvarezmack@snorus.com","city":"Rosedale","state":"CA"}
{"index":{"_id":"161"}}
{"account_number":161,"balance":4659,"firstname":"Doreen","lastname":"Randall","age":37,"gender":"F","address":"178 Court Street","employer":"Calcula","email":"doreenrandall@calcula.com","city":"Belmont","state":"TX"}
{"index":{"_id":"166"}}
{"account_number":166,"balance":33847,"firstname":"Rutledge","lastname":"Rivas","age":23,"gender":"M","address":"352 Verona Street","employer":"Virxo","email":"rutledgerivas@virxo.com","city":"Brandermill","state":"NE"}
{"index":{"_id":"173"}}
{"account_number":173,"balance":5989,"firstname":"Whitley","lastname":"Blevins","age":32,"gender":"M","address":"127 Brooklyn Avenue","employer":"Pawnagra","email":"whitleyblevins@pawnagra.com","city":"Rodanthe","state":"ND"}
{"index":{"_id":"178"}}
{"account_number":178,"balance":36735,"firstname":"Clements","lastname":"Finley","age":39,"gender":"F","address":"270 Story Court","employer":"Imaginart","email":"clementsfinley@imaginart.com","city":"Lookingglass","state":"MN"}
{"index":{"_id":"180"}}
{"account_number":180,"balance":34236,"firstname":"Ursula","lastname":"Goodman","age":32,"gender":"F","address":"414 Clinton Street","employer":"Earthmark","email":"ursulagoodman@earthmark.com","city":"Rote","state":"AR"}
{"index":{"_id":"185"}}
{"account_number":185,"balance":43532,"firstname":"Laurel","lastname":"Cline","age":40,"gender":"M","address":"788 Fenimore Street","employer":"Prismatic","email":"laurelcline@prismatic.com","city":"Frank","state":"UT"}
{"index":{"_id":"192"}}
{"account_number":192,"balance":23508,"firstname":"Ramsey","lastname":"Carr","age":31,"gender":"F","address":"209 Williamsburg Street","employer":"Strezzo","email":"ramseycarr@strezzo.com","city":"Grapeview","state":"NM"}
{"index":{"_id":"197"}}
{"account_number":197,"balance":17246,"firstname":"Sweet","lastname":"Sanders","age":33,"gender":"F","address":"712 Homecrest Court","employer":"Isosure","email":"sweetsanders@isosure.com","city":"Sheatown","state":"VT"}
{"index":{"_id":"200"}}
{"account_number":200,"balance":26210,"firstname":"Teri","lastname":"Hester","age":39,"gender":"M","address":"653 Abbey Court","employer":"Electonic","email":"terihester@electonic.com","city":"Martell","state":"MD"}
{"index":{"_id":"205"}}
{"account_number":205,"balance":45493,"firstname":"Johnson","lastname":"Chang","age":28,"gender":"F","address":"331 John Street","employer":"Gleamink","email":"johnsonchang@gleamink.com","city":"Sultana","state":"KS"}
{"index":{"_id":"212"}}
{"account_number":212,"balance":10299,"firstname":"Marisol","lastname":"Fischer","age":39,"gender":"M","address":"362 Prince Street","employer":"Autograte","email":"marisolfischer@autograte.com","city":"Oley","state":"SC"}
{"index":{"_id":"217"}}
{"account_number":217,"balance":33730,"firstname":"Sally","lastname":"Mccoy","age":38,"gender":"F","address":"854 Corbin Place","employer":"Omnigog","email":"sallymccoy@omnigog.com","city":"Escondida","state":"FL"}
{"index":{"_id":"224"}}
{"account_number":224,"balance":42708,"firstname":"Billie","lastname":"Nixon","age":28,"gender":"F","address":"241 Kaufman Place","employer":"Xanide","email":"billienixon@xanide.com","city":"Chapin","state":"NY"}
{"index":{"_id":"229"}}
{"account_number":229,"balance":2740,"firstname":"Jana","lastname":"Hensley","age":30,"gender":"M","address":"176 Erasmus Street","employer":"Isotrack","email":"janahensley@isotrack.com","city":"Caledonia","state":"ME"}
{"index":{"_id":"231"}}
{"account_number":231,"balance":46180,"firstname":"Essie","lastname":"Clarke","age":34,"gender":"F","address":"308 Harbor Lane","employer":"Pharmacon","email":"essieclarke@pharmacon.com","city":"Fillmore","state":"MS"}
{"index":{"_id":"236"}}
{"account_number":236,"balance":41200,"firstname":"Suzanne","lastname":"Bird","age":39,"gender":"F","address":"219 Luquer Street","employer":"Imant","email":"suzannebird@imant.com","city":"Bainbridge","state":"NY"}
{"index":{"_id":"243"}}
{"account_number":243,"balance":29902,"firstname":"Evangelina","lastname":"Perez","age":20,"gender":"M","address":"787 Joval Court","employer":"Keengen","email":"evangelinaperez@keengen.com","city":"Mulberry","state":"SD"}
{"index":{"_id":"248"}}
{"account_number":248,"balance":49989,"firstname":"West","lastname":"England","age":36,"gender":"M","address":"717 Hendrickson Place","employer":"Obliq","email":"westengland@obliq.com","city":"Maury","state":"WA"}
{"index":{"_id":"250"}}
{"account_number":250,"balance":27893,"firstname":"Earlene","lastname":"Ellis","age":39,"gender":"F","address":"512 Bay Street","employer":"Codact","email":"earleneellis@codact.com","city":"Sunwest","state":"GA"}
{"index":{"_id":"255"}}
{"account_number":255,"balance":49339,"firstname":"Iva","lastname":"Rivers","age":38,"gender":"M","address":"470 Rost Place","employer":"Mantrix","email":"ivarivers@mantrix.com","city":"Disautel","state":"MD"}
{"index":{"_id":"262"}}
{"account_number":262,"balance":30289,"firstname":"Tameka","lastname":"Levine","age":36,"gender":"F","address":"815 Atlantic Avenue","employer":"Acium","email":"tamekalevine@acium.com","city":"Winchester","state":"SD"}
{"index":{"_id":"267"}}
{"account_number":267,"balance":42753,"firstname":"Weeks","lastname":"Castillo","age":21,"gender":"F","address":"526 Holt Court","employer":"Talendula","email":"weekscastillo@talendula.com","city":"Washington","state":"NV"}
{"index":{"_id":"274"}}
{"account_number":274,"balance":12104,"firstname":"Frieda","lastname":"House","age":33,"gender":"F","address":"171 Banker Street","employer":"Quonk","email":"friedahouse@quonk.com","city":"Aberdeen","state":"NJ"}
{"index":{"_id":"279"}}
{"account_number":279,"balance":15904,"firstname":"Chapman","lastname":"Hart","age":32,"gender":"F","address":"902 Bliss Terrace","employer":"Kongene","email":"chapmanhart@kongene.com","city":"Bradenville","state":"NJ"}
{"index":{"_id":"281"}}
{"account_number":281,"balance":39830,"firstname":"Bean","lastname":"Aguirre","age":20,"gender":"F","address":"133 Pilling Street","employer":"Amril","email":"beanaguirre@amril.com","city":"Waterview","state":"TX"}
{"index":{"_id":"286"}}
{"account_number":286,"balance":39063,"firstname":"Rosetta","lastname":"Turner","age":35,"gender":"M","address":"169 Jefferson Avenue","employer":"Spacewax","email":"rosettaturner@spacewax.com","city":"Stewart","state":"MO"}
{"index":{"_id":"293"}}
{"account_number":293,"balance":29867,"firstname":"Cruz","lastname":"Carver","age":28,"gender":"F","address":"465 Boerum Place","employer":"Vitricomp","email":"cruzcarver@vitricomp.com","city":"Crayne","state":"CO"}
{"index":{"_id":"298"}}
{"account_number":298,"balance":34334,"firstname":"Bullock","lastname":"Marsh","age":20,"gender":"M","address":"589 Virginia Place","employer":"Renovize","email":"bullockmarsh@renovize.com","city":"Coinjock","state":"UT"}
{"index":{"_id":"301"}}
{"account_number":301,"balance":16782,"firstname":"Minerva","lastname":"Graham","age":35,"gender":"M","address":"532 Harrison Place","employer":"Sureplex","email":"minervagraham@sureplex.com","city":"Belleview","state":"GA"}
{"index":{"_id":"306"}}
{"account_number":306,"balance":2171,"firstname":"Hensley","lastname":"Hardin","age":40,"gender":"M","address":"196 Maujer Street","employer":"Neocent","email":"hensleyhardin@neocent.com","city":"Reinerton","state":"HI"}
{"index":{"_id":"313"}}
{"account_number":313,"balance":34108,"firstname":"Alston","lastname":"Henderson","age":36,"gender":"F","address":"132 Prescott Place","employer":"Prosure","email":"alstonhenderson@prosure.com","city":"Worton","state":"IA"}
{"index":{"_id":"318"}}
{"account_number":318,"balance":8512,"firstname":"Nichole","lastname":"Pearson","age":34,"gender":"F","address":"656 Lacon Court","employer":"Yurture","email":"nicholepearson@yurture.com","city":"Juarez","state":"MO"}
{"index":{"_id":"320"}}
{"account_number":320,"balance":34521,"firstname":"Patti","lastname":"Brennan","age":37,"gender":"F","address":"870 Degraw Street","employer":"Cognicode","email":"pattibrennan@cognicode.com","city":"Torboy","state":"FL"}
{"index":{"_id":"325"}}
{"account_number":325,"balance":1956,"firstname":"Magdalena","lastname":"Simmons","age":25,"gender":"F","address":"681 Townsend Street","employer":"Geekosis","email":"magdalenasimmons@geekosis.com","city":"Sterling","state":"CA"}
{"index":{"_id":"332"}}
{"account_number":332,"balance":37770,"firstname":"Shepherd","lastname":"Davenport","age":28,"gender":"F","address":"586 Montague Terrace","employer":"Ecraze","email":"shepherddavenport@ecraze.com","city":"Accoville","state":"NM"}
{"index":{"_id":"337"}}
{"account_number":337,"balance":43432,"firstname":"Monroe","lastname":"Stafford","age":37,"gender":"F","address":"183 Seigel Street","employer":"Centuria","email":"monroestafford@centuria.com","city":"Camino","state":"DE"}
{"index":{"_id":"344"}}
{"account_number":344,"balance":42654,"firstname":"Sasha","lastname":"Baxter","age":35,"gender":"F","address":"700 Bedford Place","employer":"Callflex","email":"sashabaxter@callflex.com","city":"Campo","state":"MI"}
{"index":{"_id":"349"}}
{"account_number":349,"balance":24180,"firstname":"Allison","lastname":"Fitzpatrick","age":22,"gender":"F","address":"913 Arlington Avenue","employer":"Veraq","email":"allisonfitzpatrick@veraq.com","city":"Marbury","state":"TX"}
{"index":{"_id":"351"}}
{"account_number":351,"balance":47089,"firstname":"Hendrix","lastname":"Stephens","age":29,"gender":"M","address":"181 Beaver Street","employer":"Recrisys","email":"hendrixstephens@recrisys.com","city":"Denio","state":"OR"}
{"index":{"_id":"356"}}
{"account_number":356,"balance":34540,"firstname":"Lourdes","lastname":"Valdez","age":20,"gender":"F","address":"700 Anchorage Place","employer":"Interloo","email":"lourdesvaldez@interloo.com","city":"Goldfield","state":"OK"}
{"index":{"_id":"363"}}
{"account_number":363,"balance":34007,"firstname":"Peggy","lastname":"Bright","age":21,"gender":"M","address":"613 Engert Avenue","employer":"Inventure","email":"peggybright@inventure.com","city":"Chautauqua","state":"ME"}
{"index":{"_id":"368"}}
{"account_number":368,"balance":23535,"firstname":"Hooper","lastname":"Tyson","age":39,"gender":"M","address":"892 Taaffe Place","employer":"Zaggle","email":"hoopertyson@zaggle.com","city":"Nutrioso","state":"ME"}
{"index":{"_id":"370"}}
{"account_number":370,"balance":28499,"firstname":"Oneill","lastname":"Carney","age":25,"gender":"F","address":"773 Adelphi Street","employer":"Bedder","email":"oneillcarney@bedder.com","city":"Yorklyn","state":"FL"}
{"index":{"_id":"375"}}
{"account_number":375,"balance":23860,"firstname":"Phoebe","lastname":"Patton","age":25,"gender":"M","address":"564 Hale Avenue","employer":"Xoggle","email":"phoebepatton@xoggle.com","city":"Brule","state":"NM"}
{"index":{"_id":"382"}}
{"account_number":382,"balance":42061,"firstname":"Finley","lastname":"Singleton","age":37,"gender":"F","address":"407 Clay Street","employer":"Quarex","email":"finleysingleton@quarex.com","city":"Bedias","state":"LA"}
{"index":{"_id":"387"}}
{"account_number":387,"balance":35916,"firstname":"April","lastname":"Hill","age":29,"gender":"M","address":"818 Bayard Street","employer":"Kengen","email":"aprilhill@kengen.com","city":"Chloride","state":"NC"}
{"index":{"_id":"394"}}
{"account_number":394,"balance":6121,"firstname":"Lorrie","lastname":"Nunez","age":38,"gender":"M","address":"221 Ralph Avenue","employer":"Bullzone","email":"lorrienunez@bullzone.com","city":"Longoria","state":"ID"}
{"index":{"_id":"399"}}
{"account_number":399,"balance":32587,"firstname":"Carmela","lastname":"Franks","age":23,"gender":"M","address":"617 Dewey Place","employer":"Zensure","email":"carmelafranks@zensure.com","city":"Sanders","state":"DC"}
{"index":{"_id":"402"}}
{"account_number":402,"balance":1282,"firstname":"Pacheco","lastname":"Rosales","age":32,"gender":"M","address":"538 Pershing Loop","employer":"Circum","email":"pachecorosales@circum.com","city":"Elbert","state":"ID"}
{"index":{"_id":"407"}}
{"account_number":407,"balance":36417,"firstname":"Gilda","lastname":"Jacobson","age":29,"gender":"F","address":"883 Loring Avenue","employer":"Comveyor","email":"gildajacobson@comveyor.com","city":"Topaz","state":"NH"}
{"index":{"_id":"414"}}
{"account_number":414,"balance":17506,"firstname":"Conway","lastname":"Daugherty","age":37,"gender":"F","address":"643 Kermit Place","employer":"Lyria","email":"conwaydaugherty@lyria.com","city":"Vaughn","state":"NV"}
{"index":{"_id":"419"}}
{"account_number":419,"balance":34847,"firstname":"Helen","lastname":"Montoya","age":29,"gender":"F","address":"736 Kingsland Avenue","employer":"Hairport","email":"helenmontoya@hairport.com","city":"Edinburg","state":"NE"}
{"index":{"_id":"421"}}
{"account_number":421,"balance":46868,"firstname":"Tamika","lastname":"Mccall","age":27,"gender":"F","address":"764 Bragg Court","employer":"Eventix","email":"tamikamccall@eventix.com","city":"Tivoli","state":"RI"}
{"index":{"_id":"426"}}
{"account_number":426,"balance":4499,"firstname":"Julie","lastname":"Parsons","age":31,"gender":"M","address":"768 Keap Street","employer":"Goko","email":"julieparsons@goko.com","city":"Coldiron","state":"VA"}
{"index":{"_id":"433"}}
{"account_number":433,"balance":19266,"firstname":"Wilkinson","lastname":"Flowers","age":39,"gender":"M","address":"154 Douglass Street","employer":"Xsports","email":"wilkinsonflowers@xsports.com","city":"Coultervillle","state":"MN"}
{"index":{"_id":"438"}}
{"account_number":438,"balance":16367,"firstname":"Walter","lastname":"Velez","age":27,"gender":"F","address":"931 Farragut Road","employer":"Virva","email":"waltervelez@virva.com","city":"Tyro","state":"WV"}
{"index":{"_id":"440"}}
{"account_number":440,"balance":41590,"firstname":"Ray","lastname":"Wiley","age":31,"gender":"F","address":"102 Barwell Terrace","employer":"Polaria","email":"raywiley@polaria.com","city":"Hardyville","state":"IA"}
{"index":{"_id":"445"}}
{"account_number":445,"balance":41178,"firstname":"Rodriguez","lastname":"Macias","age":34,"gender":"M","address":"164 Boerum Street","employer":"Xylar","email":"rodriguezmacias@xylar.com","city":"Riner","state":"AL"}
{"index":{"_id":"452"}}
{"account_number":452,"balance":3589,"firstname":"Blackwell","lastname":"Delaney","age":39,"gender":"F","address":"443 Sackett Street","employer":"Imkan","email":"blackwelldelaney@imkan.com","city":"Gasquet","state":"DC"}
{"index":{"_id":"457"}}
{"account_number":457,"balance":14057,"firstname":"Bush","lastname":"Gordon","age":34,"gender":"M","address":"975 Dakota Place","employer":"Softmicro","email":"bushgordon@softmicro.com","city":"Chemung","state":"PA"}
{"index":{"_id":"464"}}
{"account_number":464,"balance":20504,"firstname":"Cobb","lastname":"Humphrey","age":21,"gender":"M","address":"823 Sunnyside Avenue","employer":"Apexia","email":"cobbhumphrey@apexia.com","city":"Wintersburg","state":"NY"}
{"index":{"_id":"469"}}
{"account_number":469,"balance":26509,"firstname":"Marci","lastname":"Shepherd","age":26,"gender":"M","address":"565 Hall Street","employer":"Shadease","email":"marcishepherd@shadease.com","city":"Springhill","state":"IL"}
{"index":{"_id":"471"}}
{"account_number":471,"balance":7629,"firstname":"Juana","lastname":"Silva","age":36,"gender":"M","address":"249 Amity Street","employer":"Artworlds","email":"juanasilva@artworlds.com","city":"Norfolk","state":"TX"}
{"index":{"_id":"476"}}
{"account_number":476,"balance":33386,"firstname":"Silva","lastname":"Marks","age":31,"gender":"F","address":"183 Eldert Street","employer":"Medifax","email":"silvamarks@medifax.com","city":"Hachita","state":"RI"}
{"index":{"_id":"483"}}
{"account_number":483,"balance":6344,"firstname":"Kelley","lastname":"Harper","age":29,"gender":"M","address":"758 Preston Court","employer":"Xyqag","email":"kelleyharper@xyqag.com","city":"Healy","state":"IA"}
{"index":{"_id":"488"}}
{"account_number":488,"balance":6289,"firstname":"Wilma","lastname":"Hopkins","age":38,"gender":"M","address":"428 Lee Avenue","employer":"Entality","email":"wilmahopkins@entality.com","city":"Englevale","state":"WI"}
{"index":{"_id":"490"}}
{"account_number":490,"balance":1447,"firstname":"Strong","lastname":"Hendrix","age":26,"gender":"F","address":"134 Beach Place","employer":"Duoflex","email":"stronghendrix@duoflex.com","city":"Allentown","state":"ND"}
{"index":{"_id":"495"}}
{"account_number":495,"balance":13478,"firstname":"Abigail","lastname":"Nichols","age":40,"gender":"F","address":"887 President Street","employer":"Enquility","email":"abigailnichols@enquility.com","city":"Bagtown","state":"NM"}
{"index":{"_id":"503"}}
{"account_number":503,"balance":42649,"firstname":"Leta","lastname":"Stout","age":39,"gender":"F","address":"518 Bowery Street","employer":"Pivitol","email":"letastout@pivitol.com","city":"Boonville","state":"ND"}
{"index":{"_id":"508"}}
{"account_number":508,"balance":41300,"firstname":"Lawrence","lastname":"Mathews","age":27,"gender":"F","address":"987 Rose Street","employer":"Deviltoe","email":"lawrencemathews@deviltoe.com","city":"Woodburn","state":"FL"}
{"index":{"_id":"510"}}
{"account_number":510,"balance":48504,"firstname":"Petty","lastname":"Sykes","age":28,"gender":"M","address":"566 Village Road","employer":"Nebulean","email":"pettysykes@nebulean.com","city":"Wedgewood","state":"MO"}
{"index":{"_id":"515"}}
{"account_number":515,"balance":18531,"firstname":"Lott","lastname":"Keller","age":27,"gender":"M","address":"827 Miami Court","employer":"Translink","email":"lottkeller@translink.com","city":"Gila","state":"TX"}
{"index":{"_id":"522"}}
{"account_number":522,"balance":19879,"firstname":"Faulkner","lastname":"Garrett","age":29,"gender":"F","address":"396 Grove Place","employer":"Pigzart","email":"faulknergarrett@pigzart.com","city":"Felt","state":"AR"}
{"index":{"_id":"527"}}
{"account_number":527,"balance":2028,"firstname":"Carver","lastname":"Peters","age":35,"gender":"M","address":"816 Victor Road","employer":"Housedown","email":"carverpeters@housedown.com","city":"Nadine","state":"MD"}
{"index":{"_id":"534"}}
{"account_number":534,"balance":20470,"firstname":"Cristina","lastname":"Russo","age":25,"gender":"F","address":"500 Highlawn Avenue","employer":"Cyclonica","email":"cristinarusso@cyclonica.com","city":"Gorst","state":"KS"}
{"index":{"_id":"539"}}
{"account_number":539,"balance":24560,"firstname":"Tami","lastname":"Maddox","age":23,"gender":"F","address":"741 Pineapple Street","employer":"Accidency","email":"tamimaddox@accidency.com","city":"Kennedyville","state":"OH"}
{"index":{"_id":"541"}}
{"account_number":541,"balance":42915,"firstname":"Logan","lastname":"Burke","age":32,"gender":"M","address":"904 Clarendon Road","employer":"Overplex","email":"loganburke@overplex.com","city":"Johnsonburg","state":"OH"}
{"index":{"_id":"546"}}
{"account_number":546,"balance":43242,"firstname":"Bernice","lastname":"Sims","age":33,"gender":"M","address":"382 Columbia Street","employer":"Verbus","email":"bernicesims@verbus.com","city":"Sena","state":"KY"}
{"index":{"_id":"553"}}
{"account_number":553,"balance":28390,"firstname":"Aimee","lastname":"Cohen","age":28,"gender":"M","address":"396 Lafayette Avenue","employer":"Eplode","email":"aimeecohen@eplode.com","city":"Thatcher","state":"NJ"}
{"index":{"_id":"558"}}
{"account_number":558,"balance":8922,"firstname":"Horne","lastname":"Valenzuela","age":20,"gender":"F","address":"979 Kensington Street","employer":"Isoternia","email":"hornevalenzuela@isoternia.com","city":"Greenbush","state":"NC"}
{"index":{"_id":"560"}}
{"account_number":560,"balance":24514,"firstname":"Felecia","lastname":"Oneill","age":26,"gender":"M","address":"995 Autumn Avenue","employer":"Mediot","email":"feleciaoneill@mediot.com","city":"Joppa","state":"IN"}
{"index":{"_id":"565"}}
{"account_number":565,"balance":15197,"firstname":"Taylor","lastname":"Ingram","age":37,"gender":"F","address":"113 Will Place","employer":"Lyrichord","email":"tayloringram@lyrichord.com","city":"Collins","state":"ME"}
{"index":{"_id":"572"}}
{"account_number":572,"balance":49355,"firstname":"Therese","lastname":"Espinoza","age":20,"gender":"M","address":"994 Chester Court","employer":"Gonkle","email":"thereseespinoza@gonkle.com","city":"Hayes","state":"UT"}
{"index":{"_id":"577"}}
{"account_number":577,"balance":21398,"firstname":"Gilbert","lastname":"Serrano","age":38,"gender":"F","address":"294 Troutman Street","employer":"Senmao","email":"gilbertserrano@senmao.com","city":"Greer","state":"MT"}
{"index":{"_id":"584"}}
{"account_number":584,"balance":5346,"firstname":"Pearson","lastname":"Bryant","age":40,"gender":"F","address":"971 Heyward Street","employer":"Anacho","email":"pearsonbryant@anacho.com","city":"Bluffview","state":"MN"}
{"index":{"_id":"589"}}
{"account_number":589,"balance":33260,"firstname":"Ericka","lastname":"Cote","age":39,"gender":"F","address":"425 Bath Avenue","employer":"Venoflex","email":"erickacote@venoflex.com","city":"Blue","state":"CT"}
{"index":{"_id":"591"}}
{"account_number":591,"balance":48997,"firstname":"Rivers","lastname":"Macdonald","age":34,"gender":"F","address":"919 Johnson Street","employer":"Ziore","email":"riversmacdonald@ziore.com","city":"Townsend","state":"IL"}
{"index":{"_id":"596"}}
{"account_number":596,"balance":4063,"firstname":"Letitia","lastname":"Walker","age":26,"gender":"F","address":"963 Vanderveer Place","employer":"Zizzle","email":"letitiawalker@zizzle.com","city":"Rossmore","state":"ID"}
{"index":{"_id":"604"}}
{"account_number":604,"balance":10675,"firstname":"Isabel","lastname":"Gilliam","age":23,"gender":"M","address":"854 Broadway ","employer":"Zenthall","email":"isabelgilliam@zenthall.com","city":"Ventress","state":"WI"}
{"index":{"_id":"609"}}
{"account_number":609,"balance":28586,"firstname":"Montgomery","lastname":"Washington","age":30,"gender":"M","address":"169 Schroeders Avenue","employer":"Kongle","email":"montgomerywashington@kongle.com","city":"Croom","state":"AZ"}
{"index":{"_id":"611"}}
{"account_number":611,"balance":17528,"firstname":"Katherine","lastname":"Prince","age":33,"gender":"F","address":"705 Elm Avenue","employer":"Zillacon","email":"katherineprince@zillacon.com","city":"Rew","state":"MI"}
{"index":{"_id":"616"}}
{"account_number":616,"balance":25276,"firstname":"Jessie","lastname":"Mayer","age":35,"gender":"F","address":"683 Chester Avenue","employer":"Emtrak","email":"jessiemayer@emtrak.com","city":"Marysville","state":"HI"}
{"index":{"_id":"623"}}
{"account_number":623,"balance":20514,"firstname":"Rose","lastname":"Combs","age":32,"gender":"F","address":"312 Grimes Road","employer":"Aquamate","email":"rosecombs@aquamate.com","city":"Fostoria","state":"OH"}
{"index":{"_id":"628"}}
{"account_number":628,"balance":42736,"firstname":"Buckner","lastname":"Chen","age":37,"gender":"M","address":"863 Rugby Road","employer":"Jamnation","email":"bucknerchen@jamnation.com","city":"Camas","state":"TX"}
{"index":{"_id":"630"}}
{"account_number":630,"balance":46060,"firstname":"Leanne","lastname":"Jones","age":31,"gender":"M","address":"451 Bayview Avenue","employer":"Wazzu","email":"leannejones@wazzu.com","city":"Kylertown","state":"OK"}
{"index":{"_id":"635"}}
{"account_number":635,"balance":44705,"firstname":"Norman","lastname":"Gilmore","age":33,"gender":"M","address":"330 Gates Avenue","employer":"Comfirm","email":"normangilmore@comfirm.com","city":"Riceville","state":"TN"}
{"index":{"_id":"642"}}
{"account_number":642,"balance":32852,"firstname":"Reyna","lastname":"Harris","age":35,"gender":"M","address":"305 Powell Street","employer":"Bedlam","email":"reynaharris@bedlam.com","city":"Florence","state":"KS"}
{"index":{"_id":"647"}}
{"account_number":647,"balance":10147,"firstname":"Annabelle","lastname":"Velazquez","age":30,"gender":"M","address":"299 Kensington Walk","employer":"Sealoud","email":"annabellevelazquez@sealoud.com","city":"Soudan","state":"ME"}
{"index":{"_id":"654"}}
{"account_number":654,"balance":38695,"firstname":"Armstrong","lastname":"Frazier","age":25,"gender":"M","address":"899 Seeley Street","employer":"Zensor","email":"armstrongfrazier@zensor.com","city":"Cherokee","state":"UT"}
{"index":{"_id":"659"}}
{"account_number":659,"balance":29648,"firstname":"Dorsey","lastname":"Sosa","age":40,"gender":"M","address":"270 Aberdeen Street","employer":"Daycore","email":"dorseysosa@daycore.com","city":"Chamberino","state":"SC"}
{"index":{"_id":"661"}}
{"account_number":661,"balance":3679,"firstname":"Joanne","lastname":"Spencer","age":39,"gender":"F","address":"910 Montauk Avenue","employer":"Visalia","email":"joannespencer@visalia.com","city":"Valmy","state":"NH"}
{"index":{"_id":"666"}}
{"account_number":666,"balance":13880,"firstname":"Mcguire","lastname":"Lloyd","age":40,"gender":"F","address":"658 Just Court","employer":"Centrexin","email":"mcguirelloyd@centrexin.com","city":"Warren","state":"MT"}
{"index":{"_id":"673"}}
{"account_number":673,"balance":11303,"firstname":"Mcdaniel","lastname":"Harrell","age":33,"gender":"M","address":"565 Montgomery Place","employer":"Eyeris","email":"mcdanielharrell@eyeris.com","city":"Garnet","state":"NV"}
{"index":{"_id":"678"}}
{"account_number":678,"balance":43663,"firstname":"Ruby","lastname":"Shaffer","age":28,"gender":"M","address":"350 Clark Street","employer":"Comtrail","email":"rubyshaffer@comtrail.com","city":"Aurora","state":"MA"}
{"index":{"_id":"680"}}
{"account_number":680,"balance":31561,"firstname":"Melton","lastname":"Camacho","age":32,"gender":"F","address":"771 Montana Place","employer":"Insuresys","email":"meltoncamacho@insuresys.com","city":"Sparkill","state":"IN"}
{"index":{"_id":"685"}}
{"account_number":685,"balance":22249,"firstname":"Yesenia","lastname":"Rowland","age":24,"gender":"F","address":"193 Dekalb Avenue","employer":"Coriander","email":"yeseniarowland@coriander.com","city":"Lupton","state":"NC"}
{"index":{"_id":"692"}}
{"account_number":692,"balance":10435,"firstname":"Haney","lastname":"Barlow","age":21,"gender":"F","address":"267 Lenox Road","employer":"Egypto","email":"haneybarlow@egypto.com","city":"Detroit","state":"IN"}
{"index":{"_id":"697"}}
{"account_number":697,"balance":48745,"firstname":"Mallory","lastname":"Emerson","age":24,"gender":"F","address":"318 Dunne Court","employer":"Exoplode","email":"malloryemerson@exoplode.com","city":"Montura","state":"LA"}
{"index":{"_id":"700"}}
{"account_number":700,"balance":19164,"firstname":"Patel","lastname":"Durham","age":21,"gender":"F","address":"440 King Street","employer":"Icology","email":"pateldurham@icology.com","city":"Mammoth","state":"IL"}
{"index":{"_id":"705"}}
{"account_number":705,"balance":28415,"firstname":"Krystal","lastname":"Cross","age":22,"gender":"M","address":"604 Drew Street","employer":"Tubesys","email":"krystalcross@tubesys.com","city":"Dalton","state":"MO"}
{"index":{"_id":"712"}}
{"account_number":712,"balance":12459,"firstname":"Butler","lastname":"Alston","age":37,"gender":"M","address":"486 Hemlock Street","employer":"Quordate","email":"butleralston@quordate.com","city":"Verdi","state":"MS"}
{"index":{"_id":"717"}}
{"account_number":717,"balance":29270,"firstname":"Erickson","lastname":"Mcdonald","age":31,"gender":"M","address":"873 Franklin Street","employer":"Exotechno","email":"ericksonmcdonald@exotechno.com","city":"Jessie","state":"MS"}
{"index":{"_id":"724"}}
{"account_number":724,"balance":12548,"firstname":"Hopper","lastname":"Peck","age":31,"gender":"M","address":"849 Hendrickson Street","employer":"Uxmox","email":"hopperpeck@uxmox.com","city":"Faxon","state":"UT"}
{"index":{"_id":"729"}}
{"account_number":729,"balance":41812,"firstname":"Katy","lastname":"Rivera","age":36,"gender":"F","address":"791 Olive Street","employer":"Blurrybus","email":"katyrivera@blurrybus.com","city":"Innsbrook","state":"MI"}
{"index":{"_id":"731"}}
{"account_number":731,"balance":4994,"firstname":"Lorene","lastname":"Weiss","age":35,"gender":"M","address":"990 Ocean Court","employer":"Comvoy","email":"loreneweiss@comvoy.com","city":"Lavalette","state":"WI"}
{"index":{"_id":"736"}}
{"account_number":736,"balance":28677,"firstname":"Rogers","lastname":"Mcmahon","age":21,"gender":"F","address":"423 Cameron Court","employer":"Brainclip","email":"rogersmcmahon@brainclip.com","city":"Saddlebrooke","state":"FL"}
{"index":{"_id":"743"}}
{"account_number":743,"balance":14077,"firstname":"Susana","lastname":"Moody","age":23,"gender":"M","address":"842 Fountain Avenue","employer":"Bitrex","email":"susanamoody@bitrex.com","city":"Temperanceville","state":"TN"}
{"index":{"_id":"748"}}
{"account_number":748,"balance":38060,"firstname":"Ford","lastname":"Branch","age":25,"gender":"M","address":"926 Cypress Avenue","employer":"Buzzness","email":"fordbranch@buzzness.com","city":"Beason","state":"DC"}
{"index":{"_id":"750"}}
{"account_number":750,"balance":40481,"firstname":"Cherie","lastname":"Brooks","age":20,"gender":"F","address":"601 Woodhull Street","employer":"Kaggle","email":"cheriebrooks@kaggle.com","city":"Groton","state":"MA"}
{"index":{"_id":"755"}}
{"account_number":755,"balance":43878,"firstname":"Bartlett","lastname":"Conway","age":22,"gender":"M","address":"453 Times Placez","employer":"Konnect","email":"bartlettconway@konnect.com","city":"Belva","state":"VT"}
{"index":{"_id":"762"}}
{"account_number":762,"balance":10291,"firstname":"Amanda","lastname":"Head","age":20,"gender":"F","address":"990 Ocean Parkway","employer":"Zentury","email":"amandahead@zentury.com","city":"Hegins","state":"AR"}
{"index":{"_id":"767"}}
{"account_number":767,"balance":26220,"firstname":"Anthony","lastname":"Sutton","age":27,"gender":"F","address":"179 Fayette Street","employer":"Xiix","email":"anthonysutton@xiix.com","city":"Iberia","state":"TN"}
{"index":{"_id":"774"}}
{"account_number":774,"balance":35287,"firstname":"Lynnette","lastname":"Alvarez","age":38,"gender":"F","address":"991 Brightwater Avenue","employer":"Gink","email":"lynnettealvarez@gink.com","city":"Leola","state":"NC"}
{"index":{"_id":"779"}}
{"account_number":779,"balance":40983,"firstname":"Maggie","lastname":"Pace","age":32,"gender":"F","address":"104 Harbor Court","employer":"Bulljuice","email":"maggiepace@bulljuice.com","city":"Floris","state":"MA"}
{"index":{"_id":"781"}}
{"account_number":781,"balance":29961,"firstname":"Sanford","lastname":"Mullen","age":26,"gender":"F","address":"879 Dover Street","employer":"Zanity","email":"sanfordmullen@zanity.com","city":"Martinez","state":"TX"}
{"index":{"_id":"786"}}
{"account_number":786,"balance":3024,"firstname":"Rene","lastname":"Vang","age":33,"gender":"M","address":"506 Randolph Street","employer":"Isopop","email":"renevang@isopop.com","city":"Vienna","state":"NJ"}
{"index":{"_id":"793"}}
{"account_number":793,"balance":16911,"firstname":"Alford","lastname":"Compton","age":36,"gender":"M","address":"186 Veronica Place","employer":"Zyple","email":"alfordcompton@zyple.com","city":"Sugartown","state":"AK"}
{"index":{"_id":"798"}}
{"account_number":798,"balance":3165,"firstname":"Catherine","lastname":"Ward","age":30,"gender":"F","address":"325 Burnett Street","employer":"Dreamia","email":"catherineward@dreamia.com","city":"Glenbrook","state":"SD"}
{"index":{"_id":"801"}}
{"account_number":801,"balance":14954,"firstname":"Molly","lastname":"Maldonado","age":37,"gender":"M","address":"518 Maple Avenue","employer":"Straloy","email":"mollymaldonado@straloy.com","city":"Hebron","state":"WI"}
{"index":{"_id":"806"}}
{"account_number":806,"balance":36492,"firstname":"Carson","lastname":"Riddle","age":31,"gender":"M","address":"984 Lois Avenue","employer":"Terrago","email":"carsonriddle@terrago.com","city":"Leland","state":"MN"}
{"index":{"_id":"813"}}
{"account_number":813,"balance":30833,"firstname":"Ebony","lastname":"Bishop","age":20,"gender":"M","address":"487 Ridge Court","employer":"Optique","email":"ebonybishop@optique.com","city":"Fairmount","state":"WA"}
{"index":{"_id":"818"}}
{"account_number":818,"balance":24433,"firstname":"Espinoza","lastname":"Petersen","age":26,"gender":"M","address":"641 Glenwood Road","employer":"Futurity","email":"espinozapetersen@futurity.com","city":"Floriston","state":"MD"}
{"index":{"_id":"820"}}
{"account_number":820,"balance":1011,"firstname":"Shepard","lastname":"Ramsey","age":24,"gender":"F","address":"806 Village Court","employer":"Mantro","email":"shepardramsey@mantro.com","city":"Tibbie","state":"NV"}
{"index":{"_id":"825"}}
{"account_number":825,"balance":49000,"firstname":"Terra","lastname":"Witt","age":21,"gender":"F","address":"590 Conway Street","employer":"Insectus","email":"terrawitt@insectus.com","city":"Forbestown","state":"AR"}
{"index":{"_id":"832"}}
{"account_number":832,"balance":8582,"firstname":"Laura","lastname":"Gibbs","age":39,"gender":"F","address":"511 Osborn Street","employer":"Corepan","email":"lauragibbs@corepan.com","city":"Worcester","state":"KS"}
{"index":{"_id":"837"}}
{"account_number":837,"balance":14485,"firstname":"Amy","lastname":"Villarreal","age":35,"gender":"M","address":"381 Stillwell Place","employer":"Fleetmix","email":"amyvillarreal@fleetmix.com","city":"Sanford","state":"IA"}
{"index":{"_id":"844"}}
{"account_number":844,"balance":26840,"firstname":"Jill","lastname":"David","age":31,"gender":"M","address":"346 Legion Street","employer":"Zytrax","email":"jilldavid@zytrax.com","city":"Saticoy","state":"SC"}
{"index":{"_id":"849"}}
{"account_number":849,"balance":16200,"firstname":"Barry","lastname":"Chapman","age":26,"gender":"M","address":"931 Dekoven Court","employer":"Darwinium","email":"barrychapman@darwinium.com","city":"Whitestone","state":"WY"}
{"index":{"_id":"851"}}
{"account_number":851,"balance":22026,"firstname":"Henderson","lastname":"Price","age":33,"gender":"F","address":"530 Hausman Street","employer":"Plutorque","email":"hendersonprice@plutorque.com","city":"Brutus","state":"RI"}
{"index":{"_id":"856"}}
{"account_number":856,"balance":27583,"firstname":"Alissa","lastname":"Knox","age":25,"gender":"M","address":"258 Empire Boulevard","employer":"Geologix","email":"alissaknox@geologix.com","city":"Hartsville/Hartley","state":"MN"}
{"index":{"_id":"863"}}
{"account_number":863,"balance":23165,"firstname":"Melendez","lastname":"Fernandez","age":40,"gender":"M","address":"661 Johnson Avenue","employer":"Vixo","email":"melendezfernandez@vixo.com","city":"Farmers","state":"IL"}
{"index":{"_id":"868"}}
{"account_number":868,"balance":27624,"firstname":"Polly","lastname":"Barron","age":22,"gender":"M","address":"129 Frank Court","employer":"Geofarm","email":"pollybarron@geofarm.com","city":"Loyalhanna","state":"ND"}
{"index":{"_id":"870"}}
{"account_number":870,"balance":43882,"firstname":"Goff","lastname":"Phelps","age":21,"gender":"M","address":"164 Montague Street","employer":"Digigen","email":"goffphelps@digigen.com","city":"Weedville","state":"IL"}
{"index":{"_id":"875"}}
{"account_number":875,"balance":19655,"firstname":"Mercer","lastname":"Pratt","age":24,"gender":"M","address":"608 Perry Place","employer":"Twiggery","email":"mercerpratt@twiggery.com","city":"Eggertsville","state":"MO"}
{"index":{"_id":"882"}}
{"account_number":882,"balance":10895,"firstname":"Mari","lastname":"Landry","age":39,"gender":"M","address":"963 Gerald Court","employer":"Kenegy","email":"marilandry@kenegy.com","city":"Lithium","state":"NC"}
{"index":{"_id":"887"}}
{"account_number":887,"balance":31772,"firstname":"Eunice","lastname":"Watts","age":36,"gender":"F","address":"707 Stuyvesant Avenue","employer":"Memora","email":"eunicewatts@memora.com","city":"Westwood","state":"TN"}
{"index":{"_id":"894"}}
{"account_number":894,"balance":1031,"firstname":"Tyler","lastname":"Fitzgerald","age":32,"gender":"M","address":"787 Meserole Street","employer":"Jetsilk","email":"tylerfitzgerald@jetsilk.com","city":"Woodlands","state":"WV"}
{"index":{"_id":"899"}}
{"account_number":899,"balance":32953,"firstname":"Carney","lastname":"Callahan","age":23,"gender":"M","address":"724 Kimball Street","employer":"Mangelica","email":"carneycallahan@mangelica.com","city":"Tecolotito","state":"MT"}
{"index":{"_id":"902"}}
{"account_number":902,"balance":13345,"firstname":"Hallie","lastname":"Jarvis","age":23,"gender":"F","address":"237 Duryea Court","employer":"Anixang","email":"halliejarvis@anixang.com","city":"Boykin","state":"IN"}
{"index":{"_id":"907"}}
{"account_number":907,"balance":12961,"firstname":"Ingram","lastname":"William","age":36,"gender":"M","address":"826 Overbaugh Place","employer":"Genmex","email":"ingramwilliam@genmex.com","city":"Kimmell","state":"AK"}
{"index":{"_id":"914"}}
{"account_number":914,"balance":7120,"firstname":"Esther","lastname":"Bean","age":32,"gender":"F","address":"583 Macon Street","employer":"Applica","email":"estherbean@applica.com","city":"Homeworth","state":"MN"}
{"index":{"_id":"919"}}
{"account_number":919,"balance":39655,"firstname":"Shauna","lastname":"Hanson","age":27,"gender":"M","address":"557 Hart Place","employer":"Exospace","email":"shaunahanson@exospace.com","city":"Outlook","state":"LA"}
{"index":{"_id":"921"}}
{"account_number":921,"balance":49119,"firstname":"Barbara","lastname":"Wade","age":29,"gender":"M","address":"687 Hoyts Lane","employer":"Roughies","email":"barbarawade@roughies.com","city":"Sattley","state":"CO"}
{"index":{"_id":"926"}}
{"account_number":926,"balance":49433,"firstname":"Welch","lastname":"Mcgowan","age":21,"gender":"M","address":"833 Quincy Street","employer":"Atomica","email":"welchmcgowan@atomica.com","city":"Hampstead","state":"VT"}
{"index":{"_id":"933"}}
{"account_number":933,"balance":18071,"firstname":"Tabitha","lastname":"Cole","age":21,"gender":"F","address":"916 Rogers Avenue","employer":"Eclipto","email":"tabithacole@eclipto.com","city":"Lawrence","state":"TX"}
{"index":{"_id":"938"}}
{"account_number":938,"balance":9597,"firstname":"Sharron","lastname":"Santos","age":40,"gender":"F","address":"215 Matthews Place","employer":"Zenco","email":"sharronsantos@zenco.com","city":"Wattsville","state":"VT"}
{"index":{"_id":"940"}}
{"account_number":940,"balance":23285,"firstname":"Melinda","lastname":"Mendoza","age":38,"gender":"M","address":"806 Kossuth Place","employer":"Kneedles","email":"melindamendoza@kneedles.com","city":"Coaldale","state":"OK"}
{"index":{"_id":"945"}}
{"account_number":945,"balance":23085,"firstname":"Hansen","lastname":"Hebert","age":33,"gender":"F","address":"287 Conduit Boulevard","employer":"Capscreen","email":"hansenhebert@capscreen.com","city":"Taycheedah","state":"AK"}
{"index":{"_id":"952"}}
{"account_number":952,"balance":21430,"firstname":"Angelique","lastname":"Weeks","age":33,"gender":"M","address":"659 Reeve Place","employer":"Exodoc","email":"angeliqueweeks@exodoc.com","city":"Turpin","state":"MD"}
{"index":{"_id":"957"}}
{"account_number":957,"balance":11373,"firstname":"Michael","lastname":"Giles","age":31,"gender":"M","address":"668 Court Square","employer":"Yogasm","email":"michaelgiles@yogasm.com","city":"Rosburg","state":"WV"}
{"index":{"_id":"964"}}
{"account_number":964,"balance":26154,"firstname":"Elena","lastname":"Waller","age":34,"gender":"F","address":"618 Crystal Street","employer":"Insurety","email":"elenawaller@insurety.com","city":"Gallina","state":"NY"}
{"index":{"_id":"969"}}
{"account_number":969,"balance":22214,"firstname":"Briggs","lastname":"Lynn","age":30,"gender":"M","address":"952 Lester Court","employer":"Quinex","email":"briggslynn@quinex.com","city":"Roland","state":"ID"}
{"index":{"_id":"971"}}
{"account_number":971,"balance":22772,"firstname":"Gabrielle","lastname":"Reilly","age":32,"gender":"F","address":"964 Tudor Terrace","employer":"Blanet","email":"gabriellereilly@blanet.com","city":"Falmouth","state":"AL"}
{"index":{"_id":"976"}}
{"account_number":976,"balance":31707,"firstname":"Mullen","lastname":"Tanner","age":26,"gender":"M","address":"711 Whitney Avenue","employer":"Pulze","email":"mullentanner@pulze.com","city":"Mooresburg","state":"MA"}
{"index":{"_id":"983"}}
{"account_number":983,"balance":47205,"firstname":"Mattie","lastname":"Eaton","age":24,"gender":"F","address":"418 Allen Avenue","employer":"Trasola","email":"mattieeaton@trasola.com","city":"Dupuyer","state":"NJ"}
{"index":{"_id":"988"}}
{"account_number":988,"balance":17803,"firstname":"Lucy","lastname":"Castro","age":34,"gender":"F","address":"425 Fleet Walk","employer":"Geekfarm","email":"lucycastro@geekfarm.com","city":"Mulino","state":"VA"}
{"index":{"_id":"990"}}
{"account_number":990,"balance":44456,"firstname":"Kelly","lastname":"Steele","age":35,"gender":"M","address":"809 Hoyt Street","employer":"Eschoir","email":"kellysteele@eschoir.com","city":"Stewartville","state":"ID"}
{"index":{"_id":"995"}}
{"account_number":995,"balance":21153,"firstname":"Phelps","lastname":"Parrish","age":25,"gender":"M","address":"666 Miller Place","employer":"Pearlessa","email":"phelpsparrish@pearlessa.com","city":"Brecon","state":"ME"}
+ +

image-20230701155812781

+

四.进阶检索

1.SearchAPI

ES支持两种基本的方式检索:

+
    +
  • 一个是通过使用REST request URI 发送搜索参数(uri+检索参数)
  • +
  • 另一个是通过使用 REST request body来发送它们(uri+请求体)
  • +
+

1.1 检索信息

一切检索从_search开始

+

1.1.1 使用uri+检索参数的方式进行检索

1
2
3
4
5
6
7
8
9
10
11
GET bank/_search //检索bank下所有信息,包含type和docs
GET bank/_search?q=*&sort=account_number:asc //请求参数方式检索
//q=*表示查询所有
//sort=account_number:asc 按照account_number进行升序排序
//相应的结果解析
{
"took":29,//执行搜索的时间
"time_out":false, //搜索是否超时
"_shards":{.....},//有多少个分片被搜索了,以及统计成功/失败的搜索分片
"hits":{.......}//搜索的结果
}
+ +

image-20230701162247192

+

完整的响应结果

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
{
"took" : 29,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1000,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "bank",
"_type" : "account",
"_id" : "0",
"_score" : null,
"_source" : {
"account_number" : 0,
"balance" : 16623,
"firstname" : "Bradshaw",
"lastname" : "Mckenzie",
"age" : 29,
"gender" : "F",
"address" : "244 Columbus Place",
"employer" : "Euron",
"email" : "bradshawmckenzie@euron.com",
"city" : "Hobucken",
"state" : "CO"
},
"sort" : [
0
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "1",
"_score" : null,
"_source" : {
"account_number" : 1,
"balance" : 39225,
"firstname" : "Amber",
"lastname" : "Duke",
"age" : 32,
"gender" : "M",
"address" : "880 Holmes Lane",
"employer" : "Pyrami",
"email" : "amberduke@pyrami.com",
"city" : "Brogan",
"state" : "IL"
},
"sort" : [
1
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "2",
"_score" : null,
"_source" : {
"account_number" : 2,
"balance" : 28838,
"firstname" : "Roberta",
"lastname" : "Bender",
"age" : 22,
"gender" : "F",
"address" : "560 Kingsway Place",
"employer" : "Chillium",
"email" : "robertabender@chillium.com",
"city" : "Bennett",
"state" : "LA"
},
"sort" : [
2
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "3",
"_score" : null,
"_source" : {
"account_number" : 3,
"balance" : 44947,
"firstname" : "Levine",
"lastname" : "Burks",
"age" : 26,
"gender" : "F",
"address" : "328 Wilson Avenue",
"employer" : "Amtap",
"email" : "levineburks@amtap.com",
"city" : "Cochranville",
"state" : "HI"
},
"sort" : [
3
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "4",
"_score" : null,
"_source" : {
"account_number" : 4,
"balance" : 27658,
"firstname" : "Rodriquez",
"lastname" : "Flores",
"age" : 31,
"gender" : "F",
"address" : "986 Wyckoff Avenue",
"employer" : "Tourmania",
"email" : "rodriquezflores@tourmania.com",
"city" : "Eastvale",
"state" : "HI"
},
"sort" : [
4
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "5",
"_score" : null,
"_source" : {
"account_number" : 5,
"balance" : 29342,
"firstname" : "Leola",
"lastname" : "Stewart",
"age" : 30,
"gender" : "F",
"address" : "311 Elm Place",
"employer" : "Diginetic",
"email" : "leolastewart@diginetic.com",
"city" : "Fairview",
"state" : "NJ"
},
"sort" : [
5
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "6",
"_score" : null,
"_source" : {
"account_number" : 6,
"balance" : 5686,
"firstname" : "Hattie",
"lastname" : "Bond",
"age" : 36,
"gender" : "M",
"address" : "671 Bristol Street",
"employer" : "Netagy",
"email" : "hattiebond@netagy.com",
"city" : "Dante",
"state" : "TN"
},
"sort" : [
6
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "7",
"_score" : null,
"_source" : {
"account_number" : 7,
"balance" : 39121,
"firstname" : "Levy",
"lastname" : "Richard",
"age" : 22,
"gender" : "M",
"address" : "820 Logan Street",
"employer" : "Teraprene",
"email" : "levyrichard@teraprene.com",
"city" : "Shrewsbury",
"state" : "MO"
},
"sort" : [
7
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "8",
"_score" : null,
"_source" : {
"account_number" : 8,
"balance" : 48868,
"firstname" : "Jan",
"lastname" : "Burns",
"age" : 35,
"gender" : "M",
"address" : "699 Visitation Place",
"employer" : "Glasstep",
"email" : "janburns@glasstep.com",
"city" : "Wakulla",
"state" : "AZ"
},
"sort" : [
8
]
},
{
"_index" : "bank",
"_type" : "account",
"_id" : "9",
"_score" : null,
"_source" : {
"account_number" : 9,
"balance" : 24776,
"firstname" : "Opal",
"lastname" : "Meadows",
"age" : 39,
"gender" : "M",
"address" : "963 Neptune Avenue",
"employer" : "Cedward",
"email" : "opalmeadows@cedward.com",
"city" : "Olney",
"state" : "OH"
},
"sort" : [
9
]
}
]
}
}
+ +

1.1.2 使用uri+请求体的方式进行检索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
GET /bank/_search 
{
"query": {
"match_all": {} //查询所有(匹配条件)
},
"sort": [//排序规则
{
"account_number": "asc"//根据account_number升序
},
{
"balance":"desc" //根据balance降序
}
]
}
+ +

image-20230701163210869

+

2.Query DSL

2.1 基本的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GET /bank/_search
{
"query": { //查询操作
"match_all": {} //查询的条件
},
"sort": [ //排序的条件(数组,可以指定多个排序规则)
{
"balance": { //排序的字段
"order": "desc" //排序的规则(升序或者降序)
}
}
],
"from": 0,//分页字段,从第几页开始
"size": 5,//每页的记录数
"_source": ["balance","firstname"] //返回指定的字段
}
+ +

2.2 match查询[匹配查询]

根据某个字段的值进行匹配查询

+
1
2
3
4
5
6
7
8
9
//查询account_number是20的数据
GET /bank/_search
{
"query": {
"match": { //查询account_number是20的数据
"account_number": "20"
}
}
}
+ +

模糊匹配

+

对检索的条件进行分词匹配,按照评分的进行排序

+
1
2
3
4
5
6
7
8
9
//查询地址中包含mill或者lane或者mill lane的数据
GET /bank/_search
{
"query": {
"match": {
"address": "Mill lane" //查询地址中包含Mill或者lane中一个或者多个的数据,然后按照命中的评分排序
}
}
}
+ +

2.3 match_phrase[短语匹配]

匹配的短语不分割进行匹配,一整个进行匹配

+
1
2
3
4
5
6
7
8
9
//查询地址中包含Mill lane短语的数据
GET /bank/_search
{
"query": {
"match_phrase": {
"address": "Mill lane" //查询包含整个Mill lane的数据
}
}
}
+ +

2.4 multi_match[多字段匹配]

1
2
3
4
5
6
7
8
9
10
//查询state和address中包含mill的数据
GET /bank/_search
{
"query": {
"multi_match": { //查询在state或者在address字段中包含mill的数据
"query": "mill",//多字段匹配在查询的时候也会分词进行匹配
"fields": ["state","address"]
}
}
}
+ +

2.5 bool[复合查询]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//查询性别是M和地址中包含mill并且年龄不是28,lastname最好是Hines的数据
GET /bank/_search
{
"query": {
"bool": { //多条件复合查询
"must": [ //必须符合的条件
{"match": {
"gender": "M"
}},
{"match": {
"address": "mill"
}}
],
"must_not": [//必须不符合的条件
{"match": {
"age": "38"
}}
],
"should": [
{"match": {
"lastname": "Hines" //最好lastname是Hines,满足这个条件的得分会高一些
}}
]
}
}
}
+ +

2.6 Filter[结果过滤]

使用Filter进行检索相比上面的检索,不会影响过滤结果的得分(上面的检索都可以通过Filter实现)

+

举例:未使用filter的查询:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//查询年龄在18-20之间的所有数据
GET /bank/_search
{
"query": {
"bool": {
"must": [
{"range": {
"age": {
"gte": 18,
"lte": 30
}
}}
]
}
}
}
+ +

有得分的变化

+

image-20230702111451660

+

使用了Filter的查询:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET /bank/_search
{
"query": {
"bool": {
"filter": {
"range": {
"age": {
"gte": 18,
"lte": 30
}
}
}
}
}
}
+ +

没有得分的变化

+

image-20230702111554667

+

Filter可以和上面的检索条件一起使用

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
GET /bank/_search
{
"query": {

"bool": {

"must": [
{"match": {
"gender": "M"
}},
{"match": {
"address": "mill"
}}
],

"must_not": [
{"match": {
"age": "18"
}}
],

"should": [
{"match": {
"lastname": "Wallace"
}}
],

"filter": [
{"range": {
"age": {
"gte": 18,
"lte": 30
}
}}
]
}
}
}
+ +

2.7 term

和match一样,匹配某个属性的值。全文检索字段用match,其他非text字段匹配用term(精确性字段推荐使用term,例如年龄,数量等)

+
1
2
3
4
5
6
7
8
GET /bank/_search
{
"query": {
"term": {//精确的使用term,全文检索字段用match
"age": "28"
}
}
}
+ +

2.8 aggregations[执行聚合]

聚合提供了从数据中分组和提取数据的能力.类似于MySql中的Group by函数

+

搜索address中包含mill的所有人的年龄分布以及平均年龄

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//搜索address中包含mill的所有人的年龄分布以及平均年龄
GET /bank/_search
{
"query": {
"match": {
"address": "mill"
}
},
"aggs": {
"ageAgg": {
"terms": {//查看年龄的分布情况
"field": "age",
"size": 10
}
},
"ageAvg":{//年龄平均值
"avg": {
"field": "age"
}
},
"balanceAvg":{//工资的平均值
"avg": {
"field": "balance"
}
}
}
}
+ +

image-20230703101958163

+

按照年龄聚合,并且获取这些年龄段的这些人的平均薪资[子聚合]

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//按照年龄聚合,并且获取这些年龄段的这些人的平均薪资
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {//年龄段的聚合
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"ageAvg": {//年龄段的聚合里面嵌套一个求年龄平均值的聚合
"avg": {
"field": "balance"
}
}
}
}
}
}
+ +

image-20230703102805047

+

查询所有年龄分布,并且这些年龄段中男的平均工资和女的平均工资以及这个年龄段的总体平均薪资

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
GET /bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"avgAgg": {
"terms": {//根据年龄进行分段
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {//根据性别进行分段
"field": "gender.keyword"
},
"aggs": {
"balanceAvg": {
"avg": {//求出每个年龄段对应的不同性别的平均工资
"field": "balance"
}
}
}
},
"ageBalanceAvg": {//计算每个年龄段对应的平均工资
"avg": {
"field": "balance"
}
}
}
}
}
}
+ +

image-20230703105247964

+

3.Mapping

指定ES中数据的数据类型

+

3.1 创建映射

在创建索引的时候我们可以指定属性名的数据类型

+
1
2
3
4
5
6
7
8
9
10
11
##创建一个新的索引,指定索引下字段的映射关系
PUT /my_index
{
"mappings": {
"properties": {
"age":{"type": "integer"},
"email":{"type": "keyword"},
"name":{"type": "text"}
}
}
}
+ +

image-20230703112342368

+

3.2 添加新的映射

1
2
3
4
5
6
7
8
9
10
##添加一个映射
PUT /my_index/_mapping
{
"properties": {
"employee-id": {
"type": "keyword",
"index": false //表示这个字段不参与检索
}
}
}
+ +

image-20230703113121234

+

3.3 更新映射

对于已经存在的映射字段,我们不能更新,更新必须创建新的索引进行数据迁移

+

3.4 数据迁移

1.创建一个新的索引并指定映射

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
PUT /new_bank
{
"mappings": {
"properties": {
"account_number": {
"type": "long"
},
"address": {
"type": "text"
},
"age": {
"type": "integer"
},
"balance": {
"type": "long"
},
"city": {
"type": "keyword"
},
"email": {
"type": "keyword"
},
"employer": {
"type": "keyword"
},
"firstname": {
"type": "text"
},
"gender": {
"type": "keyword"
},
"lastname": {
"type": "keyword"
},
"state": {
"type": "keyword"
}
}
}
}
+ +

2.数据迁移

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//新版本的写法(6.0以后)
POST _reindex
{
"source":{
"index":"twitter" //老的索引
},
"dest":{
"index":"new_twitter" //新的索引
}
}
//旧版本需要指定数据类型
#数据迁移
POST _reindex
{
"source": {
"index": "bank",
"type": "account"
},
"dest": {
"index": "new_bank"
}
}
+ +

image-20230703115140810

+

4.分词

4.1 安装分词器

安装地址: https://github.com/medcl/elasticsearch-analysis-ik

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#切换到es挂载在注解的plugins目录
cd /mydata/elasticsearch/plugins/
#安装wget
yum install wget
#下载ik分词器的压缩包(下载的速度不是很快(5分钟左右),建议本地下载好了之后,上传上去)
wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
#进入容器内部查看一下文件是否同步到了容器的内部
docker exec -it 容器Id /bin/bash
#切换到plugins目录中去,这时目录中也存在elasticsearch-analysis-ik-7.4.2.zip这个压缩包
cd plugins/ #使用ls查看一下
#退出容器
exit
#在主机下解压elasticsearch-analysis-ik-7.4.2.zip这个压缩包
#zip的解压需要使用unzip解压命令 这里我们直接在本地解压一下 上传到plugins目录(解压的文件名为ik)
#安装unzip
yum install -y unzip
#查看ik分词器是否安装成功
docker exec -it 容器Id /bin/bash
#切换到bin目录
cd bin/
#执行下面的命令,有ik显示即为安装成功
elasticsearch-plugin list
#退出容器,重启一下
docker restart elasticsearch
+ +

测试使用

+

image-20230703152110823

+

4.2 自定义词库

1.安装nginx,用作设置ik的远程分词库

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#创建nginx容器挂载的目录
mkdir /mydata/nginx/conf
#拉取nginx的镜像
docker pull nginx:1.10
#随便启动一个nginx实例,为了复制其配置
docker run -p 80:80 --name nginx -d nginx:1.10
#将容器内的配置文件拷贝到conf目录(注意这里有个点和空格)
#注意在nginx的目录下操作,(成功之后/mydata/nginx/conf目录下会有相应的配置文件和目录)
docker container cp nginx:/etc/nginx /mydata/nginx/conf
#停掉并删除之前的启动的nginx
docker stop nginx
docker rm nginx
#正式的启动容器
docker run -p 80:80 --name nginx \
-v /mydata/nginx/html:/usr/share/nginx/html \
-v /mydata/nginx/logs:/var/log/nginx \
-v /mydata/nginx/conf:/etc/nginx \
-d nginx:1.10
#设置开机自启
docker update nginx --restart=always
+ +

2.配置自定义的词库

+
1
2
3
4
5
6
7
#在nginx外部挂载的html目录下创建相应的目录
mkdir es
#创建分词需要使用的文本文件
vim fenci.txt
#在这个文本文件中加入我们需要添加的新词
#例如:奥里给 老铁之类的
#在浏览器上可以通过http://192.168.195.100/es/fenci.txt访问到里面的内容
+ +

3.配置es远程词库的地址

+
1
2
3
4
#切换到ik分词器配置文件所在的目录
cd elasticsearch/plugins/ik/config/
#修改配置文件,设置远程分词库的地址
vim IKAnalyzer.cfg.xml
+ +

image-20230703170719714

+
1
2
#重启es
docker restart elasticsearch
+ +

4.测试一下(这时我们使用网络中新颖的词就可以识别成一个词语)

+

image-20230703171420471

+

五.Elasticsearch-Rest-Client

1.环境配置

1.导入依赖

+
1
2
3
4
5
6
7
8
9
10
<!--指定版本-->
<properties>
<elasticsearch.version>7.4.2</elasticsearch.version>
</properties>
<!--ES的依赖-->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.4.2</version>
</dependency>
+ +

2.编写配置文件

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.nio.protocol.HttpAsyncResponseConsumer;
import org.elasticsearch.client.HttpAsyncResponseConsumerFactory;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @author Jason Gong
* @version 1.0
* @Date 2023/7/3
* @Description ES的配置文件
*/
@Configuration
public class ElasticSearchConfig {


//通用设置项
public static final RequestOptions COMMON_OPTIONS;
static {
RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();

COMMON_OPTIONS = builder.build();
}


@Bean
public RestHighLevelClient esRestClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.195.100", 9200, "http")
)
);
return client;
}
}
+ +

3.测试使用

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.atguigu.gulimall.search;

import org.elasticsearch.client.RestHighLevelClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class GulimallSearchApplicationTests {

@Autowired
private RestHighLevelClient client;
@Test
void contextLoads() {
System.out.println(client);
}

}
+ +

2.代码操作ES

2.1 创建索引

1
2
3
4
5
6
7
8
9
/**
* 创建一个索引
*/
@Test
void testCreateIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("test_create");
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
log.info("创建一个索引:{}",createIndexResponse);
}
+ +

2.2 判断索引是否存在

1
2
3
4
5
6
7
8
9
/**
* 判断索引是否存在
*/
@Test
void testExistIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("users");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
log.info("索引是否存在:{}",exists);
}
+ +

2.3 删除索引

1
2
3
4
5
6
7
8
9
/**
* 删除索引
*/
@Test
void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("test_create");
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
+ +

2.4 保存文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 /**
* 测试向es中存储数据
* 保存和修改的操作
*/
@Test
void indexData() throws IOException {
//在users索引下存储数据
IndexRequest indexRequest = new IndexRequest("users");
//存储的数据的id
indexRequest.id("1");
//第一种方式
//indexRequest.source("userName","zhangSan","age",18,"gender","男");
//第二种方式
User user = new User("tom","男",22);
String jsonString = JSON.toJSONString(user);
indexRequest.source(jsonString, XContentType.JSON);
//执行真正的保存操作
//下面方法的第二个参数也可以使用这个参数GulimallElasticSearchConfig.COMMON_OPTIONS
IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
log.info("保存之后响应的数据:{}",index);
}
+ +

2.5 判断文档是否存在

1
2
3
4
5
6
7
8
9
10
11
/**
* 判断文档是否存在
*/
@Test
void testIsExists() throws IOException {
GetRequest getRequest = new GetRequest("users", "1");
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
System.out.println(exists);
}
+ +

2.6 获取文档信息

1
2
3
4
5
6
7
8
9
10
/**
* 获取文档的信息
*/
@Test
void testGetDocument() throws IOException {
GetRequest getRequest = new GetRequest("users", "1");
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString());
System.out.println(getResponse);
}
+ +

2.7 修改文档信息

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 更新文档的信息
*/
@Test
void testUpdateRequest() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("users", "1");
updateRequest.timeout("1s");
User user = new User("修改之后的用户名", "男",23);
updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());
}
+ +

2.8 删除文档记录

1
2
3
4
5
6
7
8
9
10
/**
* 删除文档记录
*/
@Test
void testDeleteRequest() throws IOException {
DeleteRequest request = new DeleteRequest("users", "1");
request.timeout("1s");
DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
+ +

2.9 批量添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 批量添加文档
*/
@Test
void testBulkRequest() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
ArrayList<User> userList = new ArrayList<>();
userList.add(new User("A", "男",23));
userList.add(new User("B", "男",32));
userList.add(new User("C", "女",34));
userList.add(new User("D", "女",56));
userList.add(new User("E", "男",34));
userList.add(new User("F", "女",65));
// 批处理请求
for (int i = 0; i < userList.size(); i++) {
// 批量更新和批量删除,就在这里修改对应的请求就可以了
bulkRequest.add(new IndexRequest("users").id("" + (i + 1))
.source(JSON.toJSONString(userList.get(i)), XContentType.JSON));
}
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println("是否批量添加成功:"+!bulkResponse.hasFailures());
}
+ +

2.10 条件查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 检索的方法
*/
@Test
void testSearchRequest() throws IOException {
//1.创建一个检索请求
SearchRequest searchRequest = new SearchRequest();
//设置检索的索引
searchRequest.indices("bank");
//指定DSL,检索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));//查询地址中包含mill的数据
sourceBuilder.aggregation(AggregationBuilders.terms("ageAgg").field("age").size(10));//聚合名ageAgg,根据age字段聚合,只取10条
sourceBuilder.aggregation(AggregationBuilders.avg("balanceAvg").field("balance"));//求平均工资
System.out.println("检索的条件:" + sourceBuilder);
searchRequest.source(sourceBuilder);

//2.执行检索
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

//3.分析检索的结果
System.out.println("查询到的数据:" + searchResponse.toString());
//Map map = JSON.parseObject(searchResponse.toString(), Map.class);//可以转换成一个map
SearchHits hits = searchResponse.getHits();//拿到命中的记录
for (SearchHit hit : hits) {
String index = hit.getIndex();//获取索引信息
String type = hit.getType();//类型信息
String id = hit.getId();//id信息
float score = hit.getScore();//得分信息
TimeValue took = searchResponse.getTook();
String sourceAsString = hit.getSourceAsString();//直接转化成对应的java对象
//转换成Account对象
Account account = JSON.parseObject(sourceAsString, Account.class);
System.out.println("Account:" + account);
}

//获取聚合分析的信息
//获取年龄的分段信息
Aggregations aggregations = searchResponse.getAggregations();
Terms ageAgg = aggregations.get("ageAgg");
for (Terms.Bucket bucket : ageAgg.getBuckets()) {

System.out.println("年龄:" + bucket.getKeyAsString() + "有" + bucket.getDocCount() + "人");
}
//获取薪资的平均值
Avg balanceAvg = aggregations.get("balanceAvg");
System.out.println("平均薪资:" + balanceAvg.getValue());
}
+ +

示例的完整代码

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
package com.atguigu.gulimall.search;

import com.alibaba.fastjson.JSON;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import lombok.Data;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.metrics.Avg;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Slf4j
@SpringBootTest
class GulimallSearchApplicationTests {

@Autowired
private RestHighLevelClient client;

@Test
void contextLoads() {
System.out.println(client);
}

/**
* 测试向es中存储数据
* 保存和修改的操作
*/
@Test
void indexData() throws IOException {
//在users索引下存储数据
IndexRequest indexRequest = new IndexRequest("users");
//存储的数据的id
indexRequest.id("1");
//第一种方式
//indexRequest.source("userName","zhangSan","age",18,"gender","男");
//第二种方式
User user = new User("tom", "男", 22);
String jsonString = JSON.toJSONString(user);
indexRequest.source(jsonString, XContentType.JSON);
//执行真正的保存操作
//下面方法的第二个参数也可以使用这个参数GulimallElasticSearchConfig.COMMON_OPTIONS
IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
log.info("保存之后响应的数据:{}", index);
}

/**
* 判断索引是否存在
*/
@Test
void testExistIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("users");
boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
log.info("索引是否存在:{}", exists);
}

/**
* 创建一个索引
*/
@Test
void testCreateIndex() throws IOException {
CreateIndexRequest request = new CreateIndexRequest("test_create");
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
log.info("创建一个索引:{}", createIndexResponse);
}


/**
* 删除索引
*/
@Test
void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("users");
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}


/**
* 判断文档是否存在
*/
@Test
void testIsExists() throws IOException {
GetRequest getRequest = new GetRequest("users", "1");
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
System.out.println(exists);
}

/**
* 获取文档的信息
*/
@Test
void testGetDocument() throws IOException {
GetRequest getRequest = new GetRequest("users", "1");
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString());
System.out.println(getResponse);
}


/**
* 更新文档的信息
*/
@Test
void testUpdateRequest() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("users", "1");
updateRequest.timeout("1s");
User user = new User("修改之后的用户名", "男", 23);
updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());
}

/**
* 删除文档记录
*/
@Test
void testDeleteRequest() throws IOException {
DeleteRequest request = new DeleteRequest("users", "1");
request.timeout("1s");
DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}

/**
* 批量添加文档
*/
@Test
void testBulkRequest() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
ArrayList<User> userList = new ArrayList<>();
userList.add(new User("A", "男", 23));
userList.add(new User("B", "男", 32));
userList.add(new User("C", "女", 34));
userList.add(new User("D", "女", 56));
userList.add(new User("E", "男", 34));
userList.add(new User("F", "女", 65));
// 批处理请求
for (int i = 0; i < userList.size(); i++) {
// 批量更新和批量删除,就在这里修改对应的请求就可以了
bulkRequest.add(new IndexRequest("users").id("" + (i + 1))
.source(JSON.toJSONString(userList.get(i)), XContentType.JSON));
}
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println("是否批量添加成功:" + !bulkResponse.hasFailures());
}


/**
* 检索的方法
*/
@Test
void testSearchRequest() throws IOException {
//1.创建一个检索请求
SearchRequest searchRequest = new SearchRequest();
//设置检索的索引
searchRequest.indices("bank");
//指定DSL,检索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));//查询地址中包含mill的数据
sourceBuilder.aggregation(AggregationBuilders.terms("ageAgg").field("age").size(10));//聚合名ageAgg,根据age字段聚合,只取10条
sourceBuilder.aggregation(AggregationBuilders.avg("balanceAvg").field("balance"));//求平均工资
System.out.println("检索的条件:" + sourceBuilder);
searchRequest.source(sourceBuilder);

//2.执行检索
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

//3.分析检索的结果
System.out.println("查询到的数据:" + searchResponse.toString());
//Map map = JSON.parseObject(searchResponse.toString(), Map.class);//可以转换成一个map
SearchHits hits = searchResponse.getHits();//拿到命中的记录
for (SearchHit hit : hits) {
String index = hit.getIndex();//获取索引信息
String type = hit.getType();//类型信息
String id = hit.getId();//id信息
float score = hit.getScore();//得分信息
TimeValue took = searchResponse.getTook();
String sourceAsString = hit.getSourceAsString();//直接转化成对应的java对象
//转换成Account对象
Account account = JSON.parseObject(sourceAsString, Account.class);
System.out.println("Account:" + account);
}

//获取聚合分析的信息
//获取年龄的分段信息
Aggregations aggregations = searchResponse.getAggregations();
Terms ageAgg = aggregations.get("ageAgg");
for (Terms.Bucket bucket : ageAgg.getBuckets()) {

System.out.println("年龄:" + bucket.getKeyAsString() + "有" + bucket.getDocCount() + "人");
}
//获取薪资的平均值
Avg balanceAvg = aggregations.get("balanceAvg");
System.out.println("平均薪资:" + balanceAvg.getValue());
}

@ToString
@Data
static class Account {
private int account_number;
private int balance;
private String firstname;
private String lastname;
private int age;
private String gender;
private String address;
private String employer;
private String email;
private String city;
private String state;
}


@Data
static class User {
private String userName;
private String gender;
private Integer age;

public User(String userName, String gender, Integer age) {
this.userName = userName;
this.gender = gender;
this.age = age;
}
}

}
+ + + + + + + + + + + + + + + + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/24183.html b/posts/24183.html new file mode 100644 index 000000000..ae3ab4008 --- /dev/null +++ b/posts/24183.html @@ -0,0 +1,209 @@ +任务进度 | The Blog + + + + + + + + + + + + +

任务进度

+ +
+
+ + + + + +
+
+
+
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/24606.html b/posts/24606.html new file mode 100644 index 000000000..9142f2067 --- /dev/null +++ b/posts/24606.html @@ -0,0 +1,200 @@ +MybatisX插件的使用 | The Blog + + + + + + + + + + + + +

MybatisX插件的使用

代码生成器 根据数据库表生成Mapper接口,Mapper配置文件,service

+

注解 mybatisPlus提供的注解

+

公共字段的自动填充 逻辑删除 乐观锁 雪花算法生成主键

+

image-20230321181437910

+

image-20230321181452909

+
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/24637.html b/posts/24637.html new file mode 100644 index 000000000..924569e9b --- /dev/null +++ b/posts/24637.html @@ -0,0 +1,220 @@ +Map集合的遍历 | The Blog + + + + + + + + + + + + +

Map集合的遍历

1.方法一

lambda表达式遍历

+
1
2
3
4
5
Map<String, String> map = new HashMap<String, String>();
map.forEach((key,value)->{
System.out.println(key);
System.out.println(value);
});
+ + + +

1.方法二

在 for 循环中使用 entries 实现 Map 的遍历(最常见和最常用的)

+
1
2
3
4
5
6
Map<String, String> map = new HashMap<String, String>();
for (Map.Entry<String, String> entry : map.entrySet()) {
String mapKey = entry.getKey();
String mapValue = entry.getValue();
System.out.println(mapKey + ":" + mapValue);
}
+ + + +

2.方法三

使用 for-each 循环遍历 key 或者 values,一般适用于只需要 Map 中的 key 或者 value 时使用,性能上比 entrySet 好

+
1
2
3
4
5
6
7
8
9
Map<String, String> map = new HashMap<String, String>();
// 打印键集合
for (String key : map.keySet()) {
System.out.println(key);
}
// 打印值集合
for (String value : map.values()) {
System.out.println(value);
}
+ + + +

3.方法四

使用迭代器(Iterator)遍历

+
1
2
3
4
5
6
7
8
Map<String, String> map = new HashMap<String, String>();
Iterator<Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Entry<String, String> entry = entries.next();
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + ":" + value);
}
+ + + +

4.方法五

通过键找值遍历,效率比较低,从键取值是耗时的操作

+
1
2
3
4
for(String key : map.keySet()){
String value = map.get(key);
System.out.println(key+":"+value);
}
+ + + +

…….

avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/25154.html b/posts/25154.html new file mode 100644 index 000000000..d6f52e8b4 --- /dev/null +++ b/posts/25154.html @@ -0,0 +1,223 @@ +Windows10的Linux子系统WSL的安装和使用 | The Blog + + + + + + + + + + + + +

Windows10的Linux子系统WSL的安装和使用

一. WSL简介

image-20230919221346055

+

image-20230919221906997

+

二.安装教程

1.开启WSL相关功能

+

image-20230919222258965

+

2.在微软应用商店下载并安装Ubuntu

+

image-20230919224644515

+

3.下载完成后打开Ubuntu,设置用户名和密码就可以使用了,下次打开直接搜索Ubuntu这个应用,打开即可

+

用户名要小写,下面我们可以看到如果使用大写的用户名,会一直提示我们使用小写的用户名

+

image-20230920100712177

+

4.使用Windows Terminal操作Ubuntu

+

下载Windows Terminal

+

image-20230920101220932

+

使用Windows Terminal打开Ubuntu界面

+

image-20230920101719542

+

三.FinalShell连接本机Ubuntu

1.先卸载重装一遍ssh服务,这里不是很确定是不是自带ssh服务有没有问题 ,这里使用root

+
1
2
3
4
sudo -s #切换到管理员用户
apt-get remove openssh-server
apt-get install openssh-server
apt-get update #执行上面的指令出现404错误的时候可以执行一下这一条指令,然后再执行上面的命令
+ +

2.编辑sshd_config文件

+
1
2
3
4
5
vim /etc/ssh/sshd_config

Port 2222 #设置ssh的端口号, 由于22在windows中有别的用处, 尽量不修改系统的端口号
PermitRootLogin yes # 可以root远程登录
PasswordAuthentication yes # 密码验证登录
+ +

3.重启服务

+
1
sudo service ssh --full-restart
+ +

4.使用finall连接

+

注: 用户名填写设置的用户名,端口号是2222,如果是其它的主机连接本机的ubuntu,修改主机地址即可

+

image-20230920112553412

+

5.连接成功之后

+

image-20230920112730660

+
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/26768.html b/posts/26768.html new file mode 100644 index 000000000..154cd949b --- /dev/null +++ b/posts/26768.html @@ -0,0 +1,209 @@ +Maven配置文件settings.xml | The Blog + + + + + + + + + + + + +

Maven配置文件settings.xml

更换IDEA中Maven配置文件实现不下载配置maven的阿里云镜像

+

设置本地仓库磁盘存储位置

1.在C盘的根目录下创建如下的文件夹C:\maven\maven-repository

+

2.任选下面的两个配置文件中的一个放在C:\maven目录下 配置文件的名字为 settings.xml

+

3.在IDEA中配置本地仓库的位置和配置文件的位置

+

配置文件

apache-maven-3.6.3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
<?xml version="1.0" encoding="UTF-8"?>

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
-->

<!--
| This is the configuration file for Maven. It can be specified at two levels:
|
| 1. User Level. This settings.xml file provides configuration for a single user,
| and is normally provided in ${user.home}/.m2/settings.xml.
|
| NOTE: This location can be overridden with the CLI option:
|
| -s /path/to/user/settings.xml
|
| 2. Global Level. This settings.xml file provides configuration for all Maven
| users on a machine (assuming they're all using the same Maven
| installation). It's normally provided in
| ${maven.conf}/settings.xml.
|
| NOTE: This location can be overridden with the CLI option:
|
| -gs /path/to/global/settings.xml
|
| The sections in this sample file are intended to give you a running start at
| getting the most out of your Maven installation. Where appropriate, the default
| values (values used when the setting is not specified) are provided.
|
|-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<localRepository>C:\maven\maven-repository</localRepository>
<!-- interactiveMode
| This will determine whether maven prompts you when it needs input. If set to false,
| maven will use a sensible default value, perhaps based on some other setting, for
| the parameter in question.
|
| Default: true
<interactiveMode>true</interactiveMode>
-->

<!-- offline
| Determines whether maven should attempt to connect to the network when executing a build.
| This will have an effect on artifact downloads, artifact deployment, and others.
|
| Default: false
<offline>false</offline>
-->

<!-- pluginGroups
| This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
| when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
| "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
|-->
<pluginGroups>
<!-- pluginGroup
| Specifies a further group identifier to use for plugin lookup.
<pluginGroup>com.your.plugins</pluginGroup>
-->
</pluginGroups>

<!-- proxies
| This is a list of proxies which can be used on this machine to connect to the network.
| Unless otherwise specified (by system property or command-line switch), the first proxy
| specification in this list marked as active will be used.
|-->
<proxies>
<!-- proxy
| Specification for one proxy, to be used in connecting to the network.
|
<proxy>
<id>optional</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
</proxy>
-->
</proxies>

<!-- servers
| This is a list of authentication profiles, keyed by the server-id used within the system.
| Authentication profiles can be used whenever maven must make a connection to a remote server.
|-->
<servers>
<!-- server
| Specifies the authentication information to use when connecting to a particular server, identified by
| a unique name within the system (referred to by the 'id' attribute below).
|
| NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
| used together.
|
<server>
<id>deploymentRepo</id>
<username>repouser</username>
<password>repopwd</password>
</server>
-->

<!-- Another sample, using keys to authenticate.
<server>
<id>siteServer</id>
<privateKey>/path/to/private/key</privateKey>
<passphrase>optional; leave empty if not used.</passphrase>
</server>
-->
</servers>

<!-- mirrors
| This is a list of mirrors to be used in downloading artifacts from remote repositories.
|
| It works like this: a POM may declare a repository to use in resolving certain artifacts.
| However, this repository may have problems with heavy traffic at times, so people have mirrored
| it to several places.
|
| That repository definition will have a unique id, so we can create a mirror reference for that
| repository, to be used as an alternate download site. The mirror site will be the preferred
| server for that repository.
|-->
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
<!-- 阿里云仓库,个人配置 -->
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
</mirrors>

<!-- profiles
| This is a list of profiles which can be activated in a variety of ways, and which can modify
| the build process. Profiles provided in the settings.xml are intended to provide local machine-
| specific paths and repository locations which allow the build to work in the local environment.
|
| For example, if you have an integration testing plugin - like cactus - that needs to know where
| your Tomcat instance is installed, you can provide a variable here such that the variable is
| dereferenced during the build process to configure the cactus plugin.
|
| As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
| section of this document (settings.xml) - will be discussed later. Another way essentially
| relies on the detection of a system property, either matching a particular value for the property,
| or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
| value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
| Finally, the list of active profiles can be specified directly from the command line.
|
| NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
| repositories, plugin repositories, and free-form properties to be used as configuration
| variables for plugins in the POM.
|
|-->
<profiles>
<!-- profile
| Specifies a set of introductions to the build process, to be activated using one or more of the
| mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
| or the command line, profiles have to have an ID that is unique.
|
| An encouraged best practice for profile identification is to use a consistent naming convention
| for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
| This will make it more intuitive to understand what the set of introduced profiles is attempting
| to accomplish, particularly when you only have a list of profile id's for debug.
|
| This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
<profile>
<id>jdk-1.4</id>

<activation>
<jdk>1.4</jdk>
</activation>

<repositories>
<repository>
<id>jdk14</id>
<name>Repository for JDK 1.4 builds</name>
<url>http://www.myhost.com/maven/jdk14</url>
<layout>default</layout>
<snapshotPolicy>always</snapshotPolicy>
</repository>
</repositories>
</profile>
-->

<!--
| Here is another profile, activated by the system property 'target-env' with a value of 'dev',
| which provides a specific path to the Tomcat instance. To use this, your plugin configuration
| might hypothetically look like:
|
| ...
| <plugin>
| <groupId>org.myco.myplugins</groupId>
| <artifactId>myplugin</artifactId>
|
| <configuration>
| <tomcatLocation>${tomcatPath}</tomcatLocation>
| </configuration>
| </plugin>
| ...
|
| NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
| anything, you could just leave off the <value/> inside the activation-property.
|
<profile>
<id>env-dev</id>

<activation>
<property>
<name>target-env</name>
<value>dev</value>
</property>
</activation>

<properties>
<tomcatPath>/path/to/tomcat/instance</tomcatPath>
</properties>
</profile>
-->
</profiles>

<!-- activeProfiles
| List of profiles that are active for all builds.
|
<activeProfiles>
<activeProfile>alwaysActiveProfile</activeProfile>
<activeProfile>anotherAlwaysActiveProfile</activeProfile>
</activeProfiles>
-->
</settings>
+ +

apache-maven-3.8.6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
<?xml version="1.0" encoding="UTF-8"?>

<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you 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.
-->

<!--
| This is the configuration file for Maven. It can be specified at two levels:
|
| 1. User Level. This settings.xml file provides configuration for a single user,
| and is normally provided in ${user.home}/.m2/settings.xml.
|
| NOTE: This location can be overridden with the CLI option:
|
| -s /path/to/user/settings.xml
|
| 2. Global Level. This settings.xml file provides configuration for all Maven
| users on a machine (assuming they're all using the same Maven
| installation). It's normally provided in
| ${maven.conf}/settings.xml.
|
| NOTE: This location can be overridden with the CLI option:
|
| -gs /path/to/global/settings.xml
|
| The sections in this sample file are intended to give you a running start at
| getting the most out of your Maven installation. Where appropriate, the default
| values (values used when the setting is not specified) are provided.
|
|-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<localRepository>C:\maven\maven-repository</localRepository>

<!-- interactiveMode
| This will determine whether maven prompts you when it needs input. If set to false,
| maven will use a sensible default value, perhaps based on some other setting, for
| the parameter in question.
|
| Default: true
<interactiveMode>true</interactiveMode>
-->

<!-- offline
| Determines whether maven should attempt to connect to the network when executing a build.
| This will have an effect on artifact downloads, artifact deployment, and others.
|
| Default: false
<offline>false</offline>
-->

<!-- pluginGroups
| This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
| when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
| "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
|-->
<pluginGroups>
<!-- pluginGroup
| Specifies a further group identifier to use for plugin lookup.
<pluginGroup>com.your.plugins</pluginGroup>
-->
</pluginGroups>

<!-- proxies
| This is a list of proxies which can be used on this machine to connect to the network.
| Unless otherwise specified (by system property or command-line switch), the first proxy
| specification in this list marked as active will be used.
|-->
<proxies>
<!-- proxy
| Specification for one proxy, to be used in connecting to the network.
|
<proxy>
<id>optional</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
</proxy>
-->
</proxies>

<!-- servers
| This is a list of authentication profiles, keyed by the server-id used within the system.
| Authentication profiles can be used whenever maven must make a connection to a remote server.
|-->
<servers>
<!-- server
| Specifies the authentication information to use when connecting to a particular server, identified by
| a unique name within the system (referred to by the 'id' attribute below).
|
| NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
| used together.
|
<server>
<id>deploymentRepo</id>
<username>repouser</username>
<password>repopwd</password>
</server>
-->

<!-- Another sample, using keys to authenticate.
<server>
<id>siteServer</id>
<privateKey>/path/to/private/key</privateKey>
<passphrase>optional; leave empty if not used.</passphrase>
</server>
-->
</servers>

<!-- mirrors
| This is a list of mirrors to be used in downloading artifacts from remote repositories.
|
| It works like this: a POM may declare a repository to use in resolving certain artifacts.
| However, this repository may have problems with heavy traffic at times, so people have mirrored
| it to several places.
|
| That repository definition will have a unique id, so we can create a mirror reference for that
| repository, to be used as an alternate download site. The mirror site will be the preferred
| server for that repository.
|-->
<mirrors>
<!-- mirror
| Specifies a repository mirror site to use instead of a given repository. The repository that
| this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
| for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
|
<mirror>
<id>mirrorId</id>
<mirrorOf>repositoryId</mirrorOf>
<name>Human Readable Name for this Mirror.</name>
<url>http://my.repository.com/repo/path</url>
</mirror>
-->
<!--<mirror>
<id>maven-default-http-blocker</id>
<mirrorOf>external:http:*</mirrorOf>
<name>Pseudo repository to mirror external repositories initially using HTTP.</name>
<url>http://0.0.0.0/</url>
<blocked>true</blocked>
</mirror>-->
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>central</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>

<!-- profiles
| This is a list of profiles which can be activated in a variety of ways, and which can modify
| the build process. Profiles provided in the settings.xml are intended to provide local machine-
| specific paths and repository locations which allow the build to work in the local environment.
|
| For example, if you have an integration testing plugin - like cactus - that needs to know where
| your Tomcat instance is installed, you can provide a variable here such that the variable is
| dereferenced during the build process to configure the cactus plugin.
|
| As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
| section of this document (settings.xml) - will be discussed later. Another way essentially
| relies on the detection of a system property, either matching a particular value for the property,
| or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
| value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
| Finally, the list of active profiles can be specified directly from the command line.
|
| NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
| repositories, plugin repositories, and free-form properties to be used as configuration
| variables for plugins in the POM.
|
|-->
<profiles>
<!-- profile
| Specifies a set of introductions to the build process, to be activated using one or more of the
| mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
| or the command line, profiles have to have an ID that is unique.
|
| An encouraged best practice for profile identification is to use a consistent naming convention
| for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
| This will make it more intuitive to understand what the set of introduced profiles is attempting
| to accomplish, particularly when you only have a list of profile id's for debug.
|
| This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
<profile>
<id>jdk-1.4</id>

<activation>
<jdk>1.4</jdk>
</activation>

<repositories>
<repository>
<id>jdk14</id>
<name>Repository for JDK 1.4 builds</name>
<url>http://www.myhost.com/maven/jdk14</url>
<layout>default</layout>
<snapshotPolicy>always</snapshotPolicy>
</repository>
</repositories>
</profile>
-->

<!--
| Here is another profile, activated by the system property 'target-env' with a value of 'dev',
| which provides a specific path to the Tomcat instance. To use this, your plugin configuration
| might hypothetically look like:
|
| ...
| <plugin>
| <groupId>org.myco.myplugins</groupId>
| <artifactId>myplugin</artifactId>
|
| <configuration>
| <tomcatLocation>${tomcatPath}</tomcatLocation>
| </configuration>
| </plugin>
| ...
|
| NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
| anything, you could just leave off the <value/> inside the activation-property.
|
<profile>
<id>env-dev</id>

<activation>
<property>
<name>target-env</name>
<value>dev</value>
</property>
</activation>

<properties>
<tomcatPath>/path/to/tomcat/instance</tomcatPath>
</properties>
</profile>
-->
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>

<!-- activeProfiles
| List of profiles that are active for all builds.
|
<activeProfiles>
<activeProfile>alwaysActiveProfile</activeProfile>
<activeProfile>anotherAlwaysActiveProfile</activeProfile>
</activeProfiles>
-->
</settings>
+ +

Linux中使用,去掉了注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?xml version="1.0" encoding="UTF-8"?>

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

<localRepository>/root/.m2/repository</localRepository>
<pluginGroups>

</pluginGroups>

<proxies>

</proxies>

<servers>

</servers>

<mirrors>

<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
</mirror>
</mirrors>

<profiles>

</profiles>

</settings>
+ + + + + +
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/27166.html b/posts/27166.html new file mode 100644 index 000000000..270773127 --- /dev/null +++ b/posts/27166.html @@ -0,0 +1,258 @@ +数据校验 | The Blog + + + + + + + + + + + + +

数据校验

​ 在前端接收数据和前端向后端传递数据的时候,都需要进行数据校验,避免传入错误的信息,比如在需要传入一个非空的值时,传入了一个空字符串,需要传入邮箱号码的时候,传入的非邮箱格式的数据。同时在写接口时经常要写效验请求参数逻辑,这时候我们会常用做法是写大量的 if 与 if else 类似这样的代码,大量if-else代码看起来比较混乱,降低了代码的可读性。

+

一.JSR303数据校验

1.引入依赖

1
2
3
4
5
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.7.Final</version>
</dependency>
+ +

2.给实体类添加校验注解,并定义自己的message提示

常用的检验注解

+image-20230612170704286 + +

示例

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.atguigu.gulimall.product.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;

import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

/**
* 品牌
*
* @author JasonGong
* @email JasonGong@gmail.com
* @date 2023-05-19 00:23:36
*/
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;

/**
* 品牌id
*/
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交") //非空的校验
private String name;
/**
* 品牌logo地址
*/
@NotEmpty//既不能为空,也要符合URL的格式
@URL(message = "logo必须是一个合法的URL地址")//URL的校验
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")//正则表达式的校验,在java中使用正则的时候不需要/符号
private String firstLetter;
/**
* 排序
*/
@NotNull //Integer类型不为空,使用@NotNull不能使用@NotEmpty
@Min(value = 0, message = "排序必须大于等于0")//最小值是0
private Integer sort;

}
+ +

3.开启校验功能@Valid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 保存
* @Valid注解开启校验
* @param result 检验之后响应的结果信息
* 给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result/*紧跟在需要校验的字段后面*/){
if(result.hasErrors()){//判断有没有校验的错误
Map<String,String> map = new HashMap<>();
//获取检验的错误结果, result.getFieldErrors():校验出错的字段的集合
for (FieldError fieldError : result.getFieldErrors()) {
String defaultMessage = fieldError.getDefaultMessage();//获取到错误的提示
String field = fieldError.getField();//获取错误的属性(字段)的名字
//封装在一个集合中方便后期统一通过R返回结果
map.put(field,defaultMessage);
}
return R.error(400,"提交的数据不合法").put("data",map);
}else {
//校验合格的话,就做进行业务的处理
brandService.save(brand);
}
return R.ok();
}
+ +

测试查看返回的数据的格式,这里我们输入的都是不合法的数据格式,返回的结果如下

+
1
2
3
4
5
6
7
8
9
10
{
"msg": "提交的数据不合法",
"code": 400,
"data": {
"name": "品牌名必须提交",//字段:错误信息
"logo": "logo必须是一个合法的URL地址",
"sort": "排序必须大于等于0",
"firstLetter": "检索首字母必须是一个字母"
}
}
+ +

参数没有错误之后返回的数据

+
1
2
3
4
{
"msg": "success",
"code": 0
}
+ +

4.上面的代码过于冗余,我们可以直接使用统一异常处理处理数据校验的异常

在统一异常处理类上加上数据校验异常的异常处理

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.atguigu.gulimall.product.exception;

import com.atguigu.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
* @author Jason Gong
* @version 1.0
* @Date 2023/6/12
* @Description 异常处理类
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {

@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
Map<String,String> map = new HashMap<>();
//获取检验的错误结果
BindingResult result = e.getBindingResult();
for (FieldError fieldError : result.getFieldErrors()) {
//获取到错误的提示
String defaultMessage = fieldError.getDefaultMessage();
//获取错误的属性的名字
String field = fieldError.getField();
map.put(field,defaultMessage);
}
return R.error(400,"数据校验出现问题").put("data",map);
}
}
+ +

这个时候上面的代码就可以简化为下面的格式,数据校验出现问题之后就直接在统一异常处理中处理了

+
1
2
3
4
5
6
7
8
/**
* 保存
*/
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand ){
brandService.save(brand);
return R.ok();
}
+ + + +

5.分组校验功能

 例如:当我们在添加一个品牌的时候,我们不需要传入这个品牌的id信息,需要这个品牌的品牌名信息,但是在修改这个品牌的时候,我们需要这个品牌的id信息和品牌名的信息,这时我们就需要使用分组校验了
+
+

5.1 定义空接口,作为分组校验的组

添加操作的组

+
1
2
3
4
5
package com.atguigu.common.valid;

public interface AddGroup {
}

+ +

修改操作的组

+
1
2
3
4
5
package com.atguigu.common.valid;

public interface UpdateGroup {
}

+ +

5.2 在实体类上添加上分组的信息

注意:使用了分组校验之后,其余的字段也要加上分组信息,否则没有加上分组信息的会失效

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.atguigu.gulimall.product.entity;

import com.atguigu.common.valid.AddGroup;
import com.atguigu.common.valid.UpdateGroup;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.io.Serializable;

import lombok.Data;
import org.hibernate.validator.constraints.URL;

import javax.validation.constraints.*;

@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;

/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class})//在修改的时候进行校验
@Null(message = "新增不能指定id", groups = {AddGroup.class})//在添加的时候进行校验
@TableId
private Long brandId;


/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})//这个就是在添加和修改的组中都需要进行校验
private String name;
}

+ +

5.3 在控制层添加上相应的注解信息,指定当前是操作属于的分组

注意:这里如果产生数据校验的出现问题的异常,会由统一异常处理进行处理

+

保存的控制器方法上添加上添加分组信息

+
1
2
3
4
5
6
7
8
9
/**
* 保存
* @Validated({AddGroup.class}) 指定使用添加分组的校验
*/
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand ){
brandService.save(brand);
return R.ok();
}
+ +

修改的分组上添加上修改的分组信息

+
1
2
3
4
5
6
7
8
9
/**
* 修改
*/
@RequestMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateById(brand);

return R.ok();
}
+ +

测试:我们在新增的时候加上品牌的id,这时就会产生错误

+

image-20230614105450858

+

修改的时候不带品牌的id信息

+

image-20230614111024292

+

6.自定义校验

这里我们以编写一个输入的值只能是指定值的注解为例

+
1
2
3
4
5
6
 /**
* 显示状态[0-不显示;1-显示]
*/
//注意:我们使用了分组校验,所以这里也要加上分组groups
@ListValue(vals = {0, 1}, groups = {AddGroup.class, UpdateGroup.class})//自定义的注解,输入的时候只能是0或者1
private Integer showStatus;
+ +

6.1 编写一个自定义的校验注解

编写自定义注解

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.atguigu.common.valid;

import javax.validation.Constraint;
import javax.validation.Payload;

import java.lang.annotation.Documented;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* @author Jason Gong
* @version 1.0
* @Date 2023/6/15
* @Description 自定义的校验注解
*/
@Documented
@Constraint(validatedBy = {})//校验注解使用的校验器
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})//注解可以标注的位置
@Retention(RUNTIME)
public @interface ListValue {

String message() default "{com.atguigu.common.valid.ListValue.message}";//在配置文件中指明

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

int[] vals() default {};//使用注解的时候会有vals属性 在属性里面指定值
}
+ +

编写注解中默认提示消息的配置文件

+

ValidationMessages.properties

+
1
com.atguigu.common.valid.ListValue.message=必须提交指定的值
+ +

6.2 编写一个自定义的校验器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.atguigu.common.valid;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

/**
* @author Jason Gong
* @version 1.0
* @Date 2023/6/15
* @Description 自定义的校验器
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set = new HashSet<>();//创建一个set集合 用于存储指定输入的值0和1
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();//@ListValue(vals = {0, 1})
for (int val : vals) {
set.add(val);
}
}

//判断是否校验成功
//value:需要校验的值
@Override
public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
return set.contains(integer);//判断真实树的值在不在包含0和1的这个set集合中
}
}
+ +

6.3 关联自定义的校验器和校验注解

在自定义的注解上面关联上上面自定义的校验规则

+

image-20230615151144311

+

测试自定义的注解

+

image-20230615152331597

+
\ No newline at end of file diff --git a/posts/28118.html b/posts/28118.html new file mode 100644 index 000000000..c928685e4 --- /dev/null +++ b/posts/28118.html @@ -0,0 +1,225 @@ +JWT-token生成工具 | The Blog + + + + + + + + + + + + +

JWT-token生成工具

1.介绍

​ JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。JWT最重要的作用就是对 token信息的防伪作用。

+

​ JWT的原理:

+

​ 一个JWT由三个部分组成:公共部分、私有部分、签名部分。最后由这三者组合进行base64编码得到JWT。

+

image-20230420102523288

+

image-20230420103234232

+

1、 公共部分

+

​ 主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。

+

​ Key=ATGUIGU

+

2、 私有部分

+

​ 用户自定义的内容,根据实际需要真正要封装的信息。

+

​ userInfo{用户的Id,用户的昵称nickName}

+

3、 签名部分

+

​ SaltiP: 当前服务器的Ip地址!{linux 中配置代理服务器的ip}

+

​ 主要用户对JWT生成字符串的时候,进行加密{盐值}

+

最终组成 key+salt+userInfo -> token!

+

base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以把base64编码解成明文,所以不要在JWT中放入涉及私密的信息。

+

2.使用

1.导入依赖

1
2
3
4
5
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
+ +

2.引入工具类

以下工具类根据情况任选一种即可

+

一种是在请求头中获取token,然后再获取用户的信息,另一种直接传入token,然后获取用户信息

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.atguigu.yygh.common.helper;

import io.jsonwebtoken.*;
import org.springframework.util.StringUtils;

import java.util.Date;

/**
* @author GongChangjiang
* @version 1.0
* @Date 2023/4/20
* @Description
*/
public class JwtHelper {
private static long tokenExpiration = 24*60*60*1000; //token的过期时间
private static String tokenSignKey = "123456"; //token 签名的密钥

/**
* 根据参数生成token
* @param userId 用户的id
* @param userName 用户名
* @return token字符串
*/
public static String createToken(Long userId, String userName) {
String token = Jwts.builder()
.setSubject("YYGH-USER") //分类,自定义
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))//设置过期时间
.claim("userId", userId)//设置主体信息
.claim("userName", userName)
.signWith(SignatureAlgorithm.HS512, tokenSignKey)//签名hash
.compressWith(CompressionCodecs.GZIP)
.compact();
return token;
}

/**
* 根据token字符串得到用户的id
*/
public static Long getUserId(String token) {
if(StringUtils.isEmpty(token)) return null;
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
Integer userId = (Integer)claims.get("userId");
return userId.longValue();
}

/**
* 根据token字符串得到用户名
*/
public static String getUserName(String token) {
if(StringUtils.isEmpty(token)) return "";
Jws<Claims> claimsJws
= Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
Claims claims = claimsJws.getBody();
return (String)claims.get("userName");
}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package com.atguigu.commonutils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
* @author helen
* @since 2019/10/16
*/
public class JwtUtils {

/**
* 设置token的过期时间
*/
public static final long EXPIRE = 1000 * 60 * 60 * 24;
/**
* 密钥
*/
public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";


/**
* 生成token字符串
*/
public static String getJwtToken(String id, String nickname){

String JwtToken = Jwts.builder()
.setHeaderParam("typ", "JWT")
.setHeaderParam("alg", "HS256")
.setSubject("guli-user")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.claim("id", id)
.claim("nickname", nickname)
.signWith(SignatureAlgorithm.HS256, APP_SECRET)
.compact();

return JwtToken;
}

/**
* 判断token是否存在与有效
*/
public static boolean checkToken(String jwtToken) {
if(StringUtils.isEmpty(jwtToken)) return false;
try {
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 判断token是否存在与有效
*/
public static boolean checkToken(HttpServletRequest request) {
try {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return false;
Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 根据token获取会员id
*/
public static String getMemberIdByJwtToken(HttpServletRequest request) {
String jwtToken = request.getHeader("token");
if(StringUtils.isEmpty(jwtToken)) return "";
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
Claims claims = claimsJws.getBody();
return (String)claims.get("id");
}
}
+ +

3.在登录中使用Jwt工具

1.登录成功之后返回token字符串

+

image-20230420110340570

+

2.前端处理的思路

+

获取token字符串 -> 将token字符串和用户信息存放在cokkie中 -> 页面在cokkie中获取用户信息显示在页面上

+

前端登录与否的校验、后端网关登录与否的校验…….

+

之后补充完整的代码示例 这里没有遇到好的登录示例 ……….

+
avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
\ No newline at end of file diff --git a/posts/28687.html b/posts/28687.html new file mode 100644 index 000000000..ab5db6dac --- /dev/null +++ b/posts/28687.html @@ -0,0 +1,296 @@ +常用类及其方法 | The Blog + + + + + + + + + + + + +

常用类及其方法

1.String类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.hspedu.string_;

/**
* @author 韩顺平
* @version 1.0
*/
public class StringMethod01 {
public static void main(String[] args) {
//1. equals 前面已经讲过了. 比较内容是否相同,区分大小写
String str1 = "hello";
String str2 = "Hello";
System.out.println(str1.equals(str2));//

// 2.equalsIgnoreCase 忽略大小写的判断内容是否相等
String username = "johN";
if ("john".equalsIgnoreCase(username)) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
}
// 3.length 获取字符的个数,字符串的长度
System.out.println("韩顺平".length());

// 4.indexOf 获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到,返回-1
String s1 = "wer@terwe@g";
int index = s1.indexOf('@');
System.out.println(index);// 3
System.out.println("weIndex=" + s1.indexOf("we"));//0

// 5.lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1
s1 = "wer@terwe@g@";
index = s1.lastIndexOf('@');
System.out.println(index);//11
System.out.println("ter的位置=" + s1.lastIndexOf("ter"));//4

// 6.substring 截取指定范围的子串
String name = "hello,张三";
//下面name.substring(6) 从索引6开始截取后面所有的内容
System.out.println(name.substring(6));//截取后面的字符
//name.substring(0,5)表示从索引0开始截取,截取到索引 5-1=4位置
System.out.println(name.substring(2,5));//llo

}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.hspedu.string_;

/**
* @author 韩顺平
* @version 1.0
*/
public class StringMethod02 {
public static void main(String[] args) {
// 1.toUpperCase转换成大写
String s = "heLLo";
System.out.println(s.toUpperCase());//HELLO

// 2.toLowerCase
System.out.println(s.toLowerCase());//hello

// 3.concat拼接字符串
String s1 = "宝玉";
s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
System.out.println(s1);//宝玉林黛玉薛宝钗together

// 4.replace 替换字符串中的字符
s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
//在s1中,将 所有的 林黛玉 替换成薛宝钗
// 老韩解读: s1.replace() 方法执行后,返回的结果才是替换过的.
// 注意对 s1没有任何影响
String s11 = s1.replace("宝玉", "jack");
System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉
System.out.println(s11);//jack and 林黛玉 林黛玉 林黛玉

// 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等
String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
//老韩解读:
// 1. 以 , 为标准对 poem 进行分割 , 返回一个数组
// 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \
String[] split = poem.split(",");
poem = "E:\\aaa\\bbb";
split = poem.split("\\\\");
System.out.println("==分割后内容===");
for (int i = 0; i < split.length; i++) {
System.out.println(split[i]);
}

// 6.toCharArray 转换成字符数组
s = "happy";
char[] chs = s.toCharArray();
for (int i = 0; i < chs.length; i++) {
System.out.println(chs[i]);
}

// 7.compareTo 比较两个字符串的大小,如果前者大,
// 则返回正数,后者大,则返回负数,如果相等,返回0
// 老韩解读
// (1) 如果长度相同,并且每个字符也相同,就返回 0
// (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小
// 就返回 if (c1 != c2) {
// return c1 - c2;
// }
// (3) 如果前面的部分都相同,就返回 str1.len - str2.len
String a = "jcck";// len = 3
String b = "jack";// len = 4
System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2的值

// 8.format 格式字符串
/* 占位符有:
* %s 字符串 %c 字符 %d 整型 %.2f 浮点型
*
*/
String name = "john";
int age = 10;
double score = 56.857;
char gender = '男';
//将所有的信息都拼接在一个字符串.
String info =
"我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我!";

System.out.println(info);


//老韩解读
//1. %s , %d , %.2f %c 称为占位符
//2. 这些占位符由后面变量来替换
//3. %s 表示后面由 字符串来替换
//4. %d 是整数来替换
//5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
//6. %c 使用char 类型来替换
String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";

String info2 = String.format(formatStr, name, age, score, gender);

System.out.println("info2=" + info2);
}
}
+ +

2.StringBufferd类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.hspedu.stringbuffer_;

/**
* @author 韩顺平
* @version 1.0
*/
public class StringBufferMethod {
public static void main(String[] args) {

StringBuffer s = new StringBuffer("hello");
//增
s.append(',');// "hello,"
s.append("张三丰");//"hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏100true10.5"
System.out.println(s);//"hello,张三丰赵敏100true10.5"


//删
/*
* 删除索引为>=start && <end 处的字符
* 解读: 删除 11~14的字符 [11, 14)
*/
s.delete(11, 14);
System.out.println(s);//"hello,张三丰赵敏true10.5"

//改
//老韩解读,使用 周芷若 替换 索引9-11的字符 [9,11)
s.replace(9, 11, "周芷若");
System.out.println(s);//"hello,张三丰周芷若true10.5"
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);//6

//插
//老韩解读,在索引为9的位置插入 "赵敏",原来索引为9的内容自动后移
s.insert(9, "赵敏");
System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
//长度
System.out.println(s.length());//22
System.out.println(s);

}
}
+ +

3.Math类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.hspedu.math_;

/**
* @author 韩顺平
* @version 1.0
*/
public class MathMethod {
public static void main(String[] args) {
//看看Math常用的方法(静态方法)
//1.abs 绝对值
int abs = Math.abs(-9);
System.out.println(abs);//9

//2.pow 求幂
double pow = Math.pow(2, 4);//2的4次方
System.out.println(pow);//16

//3.ceil 向上取整,返回>=该参数的最小整数(转成double);
double ceil = Math.ceil(3.9);
System.out.println(ceil);//4.0

//4.floor 向下取整,返回<=该参数的最大整数(转成double)
double floor = Math.floor(4.001);
System.out.println(floor);//4.0

//5.round 四舍五入 Math.floor(该参数+0.5)
long round = Math.round(5.51);
System.out.println(round);//6

//6.sqrt 求开方
double sqrt = Math.sqrt(9.0);
System.out.println(sqrt);//3.0

//7.random 求随机数
// random 返回的是 0 <= x < 1 之间的一个随机小数
// 思考:请写出获取 a-b之间的一个随机整数,a,b均为整数 ,比如 a = 2, b=7
// 即返回一个数 x 2 <= x <= 7
// 老韩解读 Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a
// (1) (int)(a) <= x <= (int)(a + Math.random() * (b-a +1) )
// (2) 使用具体的数给小伙伴介绍 a = 2 b = 7
// (int)(a + Math.random() * (b-a +1) ) = (int)( 2 + Math.random()*6)
// Math.random()*6 返回的是 0 <= x < 6 小数
// 2 + Math.random()*6 返回的就是 2<= x < 8 小数
// (int)(2 + Math.random()*6) = 2 <= x <= 7
// (3) 公式就是 (int)(a + Math.random() * (b-a +1) )
for(int i = 0; i < 100; i++) {
System.out.println((int)(2 + Math.random() * (7 - 2 + 1)));
}

//8.max , min 返回最大值和最小值
int min = Math.min(1, 9);
int max = Math.max(45, 90);
System.out.println("min=" + min);
System.out.println("max=" + max);

}
}
+ +

4.Arrays类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.hspedu.arrays_;

import java.util.Arrays;
import java.util.Comparator;

/**
* @author 韩顺平
* @version 1.0
*/
public class ArraysMethod01 {
public static void main(String[] args) {

Integer[] integers = {1, 20, 90};
//遍历数组
// for(int i = 0; i < integers.length; i++) {
// System.out.println(integers[i]);
// }
//直接使用Arrays.toString方法,显示数组
// System.out.println(Arrays.toString(integers));//

//演示 sort方法的使用

Integer arr[] = {1, -1, 7, 0, 89};
//进行排序
//老韩解读
//1. 可以直接使用冒泡排序 , 也可以直接使用Arrays提供的sort方法排序
//2. 因为数组是引用类型,所以通过sort排序后,会直接影响到 实参 arr
//3. sort重载的,也可以通过传入一个接口 Comparator 实现定制排序
//4. 调用 定制排序 时,传入两个参数 (1) 排序的数组 arr
// (2) 实现了Comparator接口的匿名内部类 , 要求实现 compare方法
//5. 先演示效果,再解释
//6. 这里体现了接口编程的方式 , 看看源码,就明白
// 源码分析
//(1) Arrays.sort(arr, new Comparator()
//(2) 最终到 TimSort类的 private static <T> void binarySort(T[] a, int lo, int hi, int start,
// Comparator<? super T> c)()
//(3) 执行到 binarySort方法的代码, 会根据动态绑定机制 c.compare()执行我们传入的
// 匿名内部类的 compare ()
// while (left < right) {
// int mid = (left + right) >>> 1;
// if (c.compare(pivot, a[mid]) < 0)
// right = mid;
// else
// left = mid + 1;
// }
//(4) new Comparator() {
// @Override
// public int compare(Object o1, Object o2) {
// Integer i1 = (Integer) o1;
// Integer i2 = (Integer) o2;
// return i2 - i1;
// }
// }
//(5) public int compare(Object o1, Object o2) 返回的值>0 还是 <0
// 会影响整个排序结果, 这就充分体现了 接口编程+动态绑定+匿名内部类的综合使用
// 将来的底层框架和源码的使用方式,会非常常见
//Arrays.sort(arr); // 默认排序方法
//定制排序
Arrays.sort(arr, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Integer i1 = (Integer) o1;
Integer i2 = (Integer) o2;
return i2 - i1;
}
});
System.out.println("===排序后===");
System.out.println(Arrays.toString(arr));//



}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.hspedu.arrays_;

import java.util.Arrays;
import java.util.List;

/**
* @author 韩顺平
* @version 1.0
*/
public class ArraysMethod02 {
public static void main(String[] args) {
Integer[] arr = {1, 2, 90, 123, 567};
// binarySearch 通过二分搜索法进行查找,要求必须排好
// 老韩解读
//1. 使用 binarySearch 二叉查找
//2. 要求该数组是有序的. 如果该数组是无序的,不能使用binarySearch
//3. 如果数组中不存在该元素,就返回 return -(low + 1); // key not found.
int index = Arrays.binarySearch(arr, 567);
System.out.println("index=" + index);

//copyOf 数组元素的复制
// 老韩解读
//1. 从 arr 数组中,拷贝 arr.length个元素到 newArr数组中
//2. 如果拷贝的长度 > arr.length 就在新数组的后面 增加 null
//3. 如果拷贝长度 < 0 就抛出异常NegativeArraySizeException
//4. 该方法的底层使用的是 System.arraycopy()
Integer[] newArr = Arrays.copyOf(arr, arr.length);
System.out.println("==拷贝执行完毕后==");
System.out.println(Arrays.toString(newArr));

//ill 数组元素的填充
Integer[] num = new Integer[]{9,3,2};
//老韩解读
//1. 使用 99 去填充 num数组,可以理解成是替换原理的元素
Arrays.fill(num, 99);
System.out.println("==num数组填充后==");
System.out.println(Arrays.toString(num));

//equals 比较两个数组元素内容是否完全一致
Integer[] arr2 = {1, 2, 90, 123};
//老韩解读
//1. 如果arr 和 arr2 数组的元素一样,则方法true;
//2. 如果不是完全一样,就返回 false
boolean equals = Arrays.equals(arr, arr2);
System.out.println("equals=" + equals);

//asList 将一组值,转换成list
//老韩解读
//1. asList方法,会将 (2,3,4,5,6,1)数据转成一个List集合
//2. 返回的 asList 编译类型 List(接口)
//3. asList 运行类型 java.util.Arrays#ArrayList, 是Arrays类的
// 静态内部类 private static class ArrayList<E> extends AbstractList<E>
// implements RandomAccess, java.io.Serializable
List asList = Arrays.asList(2,3,4,5,6,1);
System.out.println("asList=" + asList);
System.out.println("asList的运行类型" + asList.getClass());



}
}
+ +

5.System类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.hspedu.system_;

import java.util.Arrays;

/**
* @author 韩顺平
* @version 1.0
*/
public class System_ {
public static void main(String[] args) {

//exit 退出当前程序

// System.out.println("ok1");
// //老韩解读
// //1. exit(0) 表示程序退出
// //2. 0 表示一个状态 , 正常的状态
// System.exit(0);//
// System.out.println("ok2");

//arraycopy :复制数组元素,比较适合底层调用,
// 一般使用Arrays.copyOf完成复制数组

int[] src={1,2,3};
int[] dest = new int[3];// dest 当前是 {0,0,0}

//老韩解读
//1. 主要是搞清楚这五个参数的含义
//2.
// 源数组
// * @param src the source array.
// srcPos: 从源数组的哪个索引位置开始拷贝
// * @param srcPos starting position in the source array.
// dest : 目标数组,即把源数组的数据拷贝到哪个数组
// * @param dest the destination array.
// destPos: 把源数组的数据拷贝到 目标数组的哪个索引
// * @param destPos starting position in the destination data.
// length: 从源数组拷贝多少个数据到目标数组
// * @param length the number of array elements to be copied.
System.arraycopy(src, 0, dest, 0, src.length);
// int[] src={1,2,3};
System.out.println("dest=" + Arrays.toString(dest));//[1, 2, 3]

//currentTimeMillens:返回当前时间距离1970-1-1 的毫秒数
// 老韩解读:
System.out.println(System.currentTimeMillis());


}
}
+ +

6.BigDecimal类和BigInteger类使用(大数处理)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.hspedu.bignum;

import java.math.BigDecimal;

/**
* @author 韩顺平
* @version 1.0
*/
public class BigDecimal_ {
public static void main(String[] args) {
//当我们需要保存一个精度很高的数时,double 不够用
//可以是 BigDecimal
// double d = 1999.11111111111999999999999977788d;
// System.out.println(d);
BigDecimal bigDecimal = new BigDecimal("1999.11");
BigDecimal bigDecimal2 = new BigDecimal("3");
System.out.println(bigDecimal);

//老韩解读
//1. 如果对 BigDecimal进行运算,比如加减乘除,需要使用对应的方法
//2. 创建一个需要操作的 BigDecimal 然后调用相应的方法即可
System.out.println(bigDecimal.add(bigDecimal2));
System.out.println(bigDecimal.subtract(bigDecimal2));
System.out.println(bigDecimal.multiply(bigDecimal2));
//System.out.println(bigDecimal.divide(bigDecimal2));//可能抛出异常ArithmeticException
//在调用divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING
//如果有无限循环小数,就会保留 分子 的精度
System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING));
}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.hspedu.bignum;

import java.math.BigInteger;

/**
* @author 韩顺平
* @version 1.0
*/
public class BigInteger_ {
public static void main(String[] args) {

//当我们编程中,需要处理很大的整数,long 不够用
//可以使用BigInteger的类来搞定
// long l = 23788888899999999999999999999l;
// System.out.println("l=" + l);

BigInteger bigInteger = new BigInteger("23788888899999999999999999999");
BigInteger bigInteger2 = new BigInteger("10099999999999999999999999999999999999999999999999999999999999999999999999999999999");
System.out.println(bigInteger);
//老韩解读
//1. 在对 BigInteger 进行加减乘除的时候,需要使用对应的方法,不能直接进行 + - * /
//2. 可以创建一个 要操作的 BigInteger 然后进行相应操作
BigInteger add = bigInteger.add(bigInteger2);
System.out.println(add);//
BigInteger subtract = bigInteger.subtract(bigInteger2);
System.out.println(subtract);//减
BigInteger multiply = bigInteger.multiply(bigInteger2);
System.out.println(multiply);//乘
BigInteger divide = bigInteger.divide(bigInteger2);
System.out.println(divide);//除


}
}
+ +

7.时间类、日期类的常用方法(Date、Calendar,第三代日期类)

第一代日期类:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.hspedu.date_;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;


/**
* @author 韩顺平
* @version 1.0
*/
public class Date01 {
public static void main(String[] args) throws ParseException {

//老韩解读
//1. 获取当前系统时间
//2. 这里的Date 类是在java.util包
//3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
Date d1 = new Date(); //获取当前系统时间
System.out.println("当前日期=" + d1);
Date d2 = new Date(9234567); //通过指定毫秒数得到时间
System.out.println("d2=" + d2); //获取某个时间对应的毫秒数
//

//老韩解读
//1. 创建 SimpleDateFormat对象,可以指定相应的格式
//2. 这里的格式使用的字母是规定好,不能乱写

SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);

//老韩解读
//1. 可以把一个格式化的String 转成对应的 Date
//2. 得到Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
//3. 在把String -> Date , 使用的 sdf 格式需要和你给的String的格式一样,否则会抛出转换异常
String s = "1996年01月01日 10:20:30 星期一";
Date parse = sdf.parse(s);
System.out.println("parse=" + sdf.format(parse));

}
}
+ +

第二代日期类

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.hspedu.date_;

import java.util.Calendar;

/**
* @author 韩顺平
* @version 1.0
*/
public class Calendar_ {
public static void main(String[] args) {
//老韩解读
//1. Calendar是一个抽象类, 并且构造器是private,不能实例化对象
//2. 可以通过 getInstance() 来获取实例
//3. 提供大量的方法和字段提供给程序员
//4. Calendar没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
//5. 如果我们需要按照 24小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
System.out.println("c=" + c);
//2.获取日历对象的某个日历字段
System.out.println("年:" + c.get(Calendar.YEAR));
// 这里为什么要 + 1, 因为Calendar 返回月时候,是按照 0 开始编号
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));
//Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) +
" " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );

}
}
+ +

第三代日期类(开发中尽量使用第三代日期类)

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.hspedu.date_;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;

/**
* @author 韩顺平
* @version 1.0
*/
public class LocalDate_ {
public static void main(String[] args) {
//第三代日期
//老韩解读
//1. 使用now() 返回表示当前日期时间的 对象
LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
System.out.println(ldt);

//2. 使用DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);

System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());
System.out.println("月=" + ldt.getMonthValue());
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());

LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒


//提供 plus 和 minus方法可以对当前时间进行加或者减
//看看890天后,是什么时候 把 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890天后=" + dateTimeFormatter.format(localDateTime));

//看看在 3456分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456分钟前 日期=" + dateTimeFormatter.format(localDateTime2));

}
}

+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.hspedu.date_;

import java.time.Instant;
import java.util.Date;

/**
* @author 韩顺平
* @version 1.0
*/
public class Instant_ {
public static void main(String[] args) {

//1.通过 静态方法 now() 获取表示当前时间戳的对象
Instant now = Instant.now();//获取时间戳对象,时间更加精准
System.out.println(now);
//2. 通过 from 可以把 Instant转成 Date
Date date = Date.from(now);
//3. 通过 date的toInstant() 可以把 date 转成Instant对象
Instant instant = date.toInstant();

}
}
+ +

8.实现List接口实现类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.hspedu.list_;

import java.util.ArrayList;
import java.util.List;

/**
* @author 韩顺平
* @version 1.0
*/
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
list.add("贾宝玉");
// void add(int index, Object ele):在index位置插入ele元素
//在index = 1的位置插入一个对象
list.add(1, "韩顺平");
System.out.println("list=" + list);
// boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List list2 = new ArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1, list2);
System.out.println("list=" + list);
// Object get(int index):获取指定index位置的元素
//说过
// int indexOf(Object obj):返回obj在集合中首次出现的位置
System.out.println(list.indexOf("tom"));//2
// int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
list.add("韩顺平");
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("韩顺平"));
// Object remove(int index):移除指定index位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
// Object set(int index, Object ele):设置指定index位置的元素为ele , 相当于是替换.
list.set(1, "玛丽");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist);

}
}
+ +

9.Map接口的常用方法以及遍历方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package com.hspedu.map_;

import java.util.HashMap;
import java.util.Map;

/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class MapMethod {
public static void main(String[] args) {
//演示map接口常用方法

Map map = new HashMap();
map.put("邓超", new Book("", 100));//OK
map.put("邓超", "孙俪");//替换-> 一会分析源码
map.put("王宝强", "马蓉");//OK
map.put("宋喆", "马蓉");//OK
map.put("刘令博", null);//OK
map.put(null, "刘亦菲");//OK
map.put("鹿晗", "关晓彤");//OK
map.put("hsp", "hsp的老婆");

System.out.println("map=" + map);

// remove:根据键删除映射关系
map.remove(null);
System.out.println("map=" + map);
// get:根据键获取值
Object val = map.get("鹿晗");
System.out.println("val=" + val);
// size:获取元素个数
System.out.println("k-v=" + map.size());
// isEmpty:判断个数是否为0
System.out.println(map.isEmpty());//F
// clear:清除k-v
//map.clear();
System.out.println("map=" + map);
// containsKey:查找键是否存在
System.out.println("结果=" + map.containsKey("hsp"));//T


}
}

class Book {
private String name;
private int num;

public Book(String name, int num) {
this.name = name;
this.num = num;
}
}
+ +

遍历方式

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.hspedu.map_;

import java.util.*;

/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class MapFor {
public static void main(String[] args) {

Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");

//第一组: 先取出 所有的Key , 通过Key 取出对应的Value

//着重记住第一种方式,其他的方式开发中用到了,可以查
Set keyset = map.keySet();
//(1) 增强for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}


//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}

//第二组: 把所有的values取出
Collection values = map.values();
//这里可以使用所有的Collections使用的遍历方法
//(1) 增强for
System.out.println("---取出所有的value 增强for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);

}

//第三组: 通过EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强for
System.out.println("----使用EntrySet 的 for增强(第3种)----");
for (Object entry : entrySet) {
//将entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用EntrySet 的 迭代器(第4种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}


}
}
+ +

9.Collections工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
package com.hspedu.collections_;

import java.util.*;

/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class Collections_ {
public static void main(String[] args) {

//创建ArrayList 集合,用于测试.
List list = new ArrayList();
list.add("tom");
list.add("smith");
list.add("king");
list.add("milan");
list.add("tom");


// reverse(List):反转 List 中元素的顺序
Collections.reverse(list);
System.out.println("list=" + list);
// shuffle(List):对 List 集合元素进行随机排序
// for (int i = 0; i < 5; i++) {
// Collections.shuffle(list);
// System.out.println("list=" + list);
// }

// sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
Collections.sort(list);
System.out.println("自然排序后");
System.out.println("list=" + list);
// sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
//我们希望按照 字符串的长度大小排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//可以加入校验代码.
return ((String) o2).length() - ((String) o1).length();
}
});
System.out.println("字符串长度大小排序=" + list);
// swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

//比如
Collections.swap(list, 0, 1);
System.out.println("交换后的情况");
System.out.println("list=" + list);

//Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
System.out.println("自然顺序最大元素=" + Collections.max(list));
//Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
//比如,我们要返回长度最大的元素
Object maxObject = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length() - ((String)o2).length();
}
});
System.out.println("长度最大的元素=" + maxObject);


//Object min(Collection)
//Object min(Collection,Comparator)
//上面的两个方法,参考max即可

//int frequency(Collection,Object):返回指定集合中指定元素的出现次数
System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));

//void copy(List dest,List src):将src中的内容复制到dest中

ArrayList dest = new ArrayList();
//为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样
for(int i = 0; i < list.size(); i++) {
dest.add("");
}
//拷贝
Collections.copy(dest, list);
System.out.println("dest=" + dest);//把后一个集合的数据拷贝到前一个集合

//boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
//如果list中,有tom 就替换成 汤姆
Collections.replaceAll(list, "tom", "汤姆");
System.out.println("list替换后=" + list);


}
}
+ +

10.创建文件的三种方式以及文件的相关操作(相关方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.hspedu.file;

import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;

/**
* @author GongChangjiang
* @version 1.0
* 我亦无他 惟手熟尔
* 创建文件的方式
*/
public class FileCreate {
public static void main(String[] args) {

}
@Test
//第一种方式:new File(fileName);
public void create01(){
String filePath = "c:\\news1.txt";//也可以写成"c:/news1.txt"
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}



//第二种方式:new File(File parent,String child)//根据父目录文件+子路径构建
@Test
public void create02(){
File parentFile = new File("c:\\");//也可以写成"c:/"
String fileName = "news2.txt";
File file = new File(parentFile, fileName);
try {
file.createNewFile();
System.out.println(fileName+" 文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}



//第三种方式:new File(String parent,String child)//根据父目录+子路径构建
@Test
public void create03(){
String parentPath = "c:\\";//也可以写成"c:/"
String fileName = "news3.txt";
File file = new File(parentPath, fileName);
try {
file.createNewFile();
System.out.println(fileName+"文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}



}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.hspedu.file;

import org.junit.jupiter.api.Test;

import java.io.File;

/**
* @author GongChangjiang
* @version 1.0
* 我亦无他 惟手熟尔
*/
public class FileInformation {
public static void main(String[] args) {

}
//获取文件信息
@Test
public void info(){
//先创建文件的对象,这里并没有创建文件,有点像是读取文件
File file = new File("c:\\news1.txt");

//调用相应的方法,得到对应的信息
//得到文件名
System.out.println("文件名="+file.getName());
//文件的绝对路径
System.out.println("文件的绝对路径="+file.getAbsolutePath());
//文件的父级目录
System.out.println("文件父级目录="+file.getParent());
//文件的大小,返回的是字节(一个英文字母对应的是一个字节,一个汉字对应的是三个字节)
System.out.println("文件的大小(字节)="+file.length());
//文件是否存在,返回的是true /false
System.out.println("文件是否存在="+file.exists());
//是不是一个文件
System.out.println("是不是一个文件="+file.isFile());
//是不是一个目录
System.out.println("是不是一个目录="+file.isDirectory());

}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.hspedu.file;

import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;

/**
* @author 韩顺平
* @version 1.0
*/
public class Directory_ {
public static void main(String[] args) {

//
}

//判断 d:\\news1.txt 是否存在,如果存在就删除
@Test
public void m1() {

String filePath = "e:\\news1.txt";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println(filePath + "删除成功");
} else {
System.out.println(filePath + "删除失败");
}
} else {
System.out.println("该文件不存在...");
}

}

//判断 D:\\demo02 是否存在,存在就删除,否则提示不存在
//这里我们需要体会到,在java编程中,目录也被当做文件
@Test
public void m2() {

String filePath = "D:\\demo02";
File file = new File(filePath);
if (file.exists()) {
if (file.delete()) {
System.out.println(filePath + "删除成功");
} else {
System.out.println(filePath + "删除失败");
}
} else {
System.out.println("该目录不存在...");
}

}

//判断 D:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
@Test
public void m3() {

String directoryPath = "D:\\demo\\a\\b\\c";
File file = new File(directoryPath);
if (file.exists()) {
System.out.println(directoryPath + "存在..");
} else {
if (file.mkdirs()) { //创建一级目录使用mkdir() ,创建多级目录使用mkdirs()
System.out.println(directoryPath + "创建成功..");
} else {
System.out.println(directoryPath + "创建失败...");
}
}



}
}
+ +

11.FileInputStream 字节输入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.hspedu.inputstream_;

import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
* @author 韩顺平
* @version 1.0
* 演示FileInputStream的使用(字节输入流 文件--> 程序)
*/
public class FileInputStream_ {
public static void main(String[] args) {

}

/**
* 演示读取文件...
* 单个字节的读取,效率比较低
* -> 使用 read(byte[] b)
*/
@Test
public void readFile01() {
String filePath = "e:\\hello.txt";
int readData = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
//如果返回-1 , 表示读取完毕
while ((readData = fileInputStream.read()) != -1) {//返回的是一个int类型的字节值
System.out.print((char)readData);//转成char显示
}

} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}

/**
* 使用 read(byte[] b) 读取文件,提高效率
*/
@Test
public void readFile02() {
String filePath = "e:\\hello.txt";
//字节数组
byte[] buf = new byte[8]; //一次读取8个字节.
int readLen = 0;
FileInputStream fileInputStream = null;
try {
//创建 FileInputStream 对象,用于读取 文件
fileInputStream = new FileInputStream(filePath);
//从该输入流读取最多b.length字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
//如果返回-1 , 表示读取完毕
//如果读取正常, 返回实际读取的字节数
while ((readLen = fileInputStream.read(buf)) != -1) {//返回的是读取的字节数
System.out.print(new String(buf, 0, readLen));//显示
}

} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭文件流,释放资源.
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}

}
}
+ +

12.FileOutputStream 字节输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.hspedu.outputstream_;

import org.junit.jupiter.api.Test;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
* @author 韩顺平
* @version 1.0
*/
public class FileOutputStream01 {
public static void main(String[] args) {

}

/**
* 演示使用FileOutputStream 将数据写到文件中,
* 如果该文件不存在,则创建该文件
*/
@Test
public void writeFile() {

//创建 FileOutputStream对象
String filePath = "e:\\a.txt";
FileOutputStream fileOutputStream = null;
try {
//得到 FileOutputStream对象 对象
//老师说明
//1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
//2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面
fileOutputStream = new FileOutputStream(filePath, true);
//写入一个字节
//fileOutputStream.write('H');//
//写入字符串
String str = "hsp,world!";
//str.getBytes() 可以把 字符串-> 字节数组
//fileOutputStream.write(str.getBytes());
/*
write(byte[] b, int off, int len) 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流
从off开始,写len个字节写入到文件中
*/
fileOutputStream.write(str.getBytes(), 0, 3);

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
+ +

13.文件拷贝(FileInputStream和FileOutputStream)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.hspedu.outputstream_;

import com.hspedu.inputstream_.FileInputStream_;

import java.io.*;

/**
* @author 韩顺平
* @version 1.0
*/
public class FileCopy {
public static void main(String[] args) {
//完成 文件拷贝,将 e:\\Koala.jpg 拷贝 c:\\
//思路分析
//1. 创建文件的输入流 , 将文件读入到程序
//2. 创建文件的输出流, 将读取到的文件数据,写入到指定的文件.
String srcFilePath = "e:\\Koala.jpg";
String destFilePath = "e:\\Koala3.jpg";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;

try {

fileInputStream = new FileInputStream(srcFilePath);
fileOutputStream = new FileOutputStream(destFilePath);
//定义一个字节数组,提高读取效果
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = fileInputStream.read(buf)) != -1) {
//读取到后,就写入到文件 通过 fileOutputStream
//即,是一边读,一边写
fileOutputStream.write(buf, 0, readLen);//一定要使用这个方法

}
System.out.println("拷贝ok~");


} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关闭输入流和输出流,释放资源
if (fileInputStream != null) {
fileInputStream.close();
}
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}


}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.hspedu.writer_;

import java.io.*;

/**
* @author GongChangjiang
* @version 1.0
* 我亦无他 惟手熟尔
* 拷贝文件
*/
public class BufferedCopy_ {
public static void main(String[] args) throws Exception {
//源文件
String srcFilePath = "c:\\story.txt";
//目标文件
String destFilePath = "c:\\dest.txt";
BufferedReader bufferedReader = new BufferedReader(new FileReader(srcFilePath));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(destFilePath));
String line;
while ((line = bufferedReader.readLine()) != null){
//取到的每一行加入到目标文件中
bufferedWriter.write(line);
//换行符
bufferedWriter.newLine();
}
//关闭流
bufferedReader.close();
bufferedWriter.close();
}
}
+ +

14.FileReader 字符输入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.hspedu.reader_;

import org.junit.jupiter.api.Test;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

/**
* @author 韩顺平
* @version 1.0
*/
public class FileReader_ {
public static void main(String[] args) {


}

/**
* 单个字符读取文件
*/
@Test
public void readFile01() {
String filePath = "e:\\story.txt";
FileReader fileReader = null;
int data = 0;
//1. 创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read, 单个字符读取
while ((data = fileReader.read()) != -1) {
System.out.print((char) data);
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* 字符数组读取文件
*/
@Test
public void readFile02() {
System.out.println("~~~readFile02 ~~~");
String filePath = "e:\\story.txt";
FileReader fileReader = null;

int readLen = 0;
char[] buf = new char[8];
//1. 创建FileReader对象
try {
fileReader = new FileReader(filePath);
//循环读取 使用read(buf), 返回的是实际读取到的字符数
//如果返回-1, 说明到文件结束
while ((readLen = fileReader.read(buf)) != -1) {
System.out.print(new String(buf, 0, readLen));
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

}
+ +

15.FileWriter 字符输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.hspedu.writer_;

import java.io.FileWriter;
import java.io.IOException;

/**
* @author GongChangjiang
* @version 1.0
* 我亦无他 惟手熟尔
*/
public class FileWriter_ {
public static void main(String[] args) {
String filePath = "c:\\note.txt";
FileWriter fileWriter = null;
char[] chars = {'a','b','c'};
try {
fileWriter = new FileWriter(filePath);
fileWriter.write('H');//写入单个字符
fileWriter.write(chars);//写入字符数组
fileWriter.write("韩顺平教育".toCharArray(),0,3);//写入数组的指定部分
fileWriter.write(" 你好北京~");//写入整个字符串
fileWriter.write("上海你好",0,2);//写入字符串的指定部分
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
//下面的两种方式二选一,必须选一个,否则文件无法写入
fileWriter.flush();
fileWriter.close();//等价于 flush() + 关闭
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
+ +

16.BufferedReader 字符处理流(包装流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.hspedu.reader_;

import java.io.BufferedReader;
import java.io.FileReader;

/**
* @author 韩顺平
* @version 1.0
* 演示bufferedReader 使用
*/
public class BufferedReader_ {
public static void main(String[] args) throws Exception {

String filePath = "e:\\a.java";
//创建bufferedReader
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
//读取
String line; //按行读取, 效率高
//说明
//1. bufferedReader.readLine() 是按行读取文件
//2. 当返回null 时,表示文件读取完毕
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}

//关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭 节点流
//FileReader。
/*
public void close() throws IOException {
synchronized (lock) {
if (in == null)
return;
try {
in.close();//in 就是我们传入的 new FileReader(filePath), 关闭了.
} finally {
in = null;
cb = null;
}
}
}

*/
bufferedReader.close();

}
}
+ +

17.BufferedWriter 字符处理流(包装流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.hspedu.writer_;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

/**
* @author GongChangjiang
* @version 1.0
* 我亦无他 惟手熟尔
*/
public class BufferWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "c:\\note.txt";
//new FileWriter(filePath,true);表示以追加的方式加入
//new FileWriter(filePath);表示以覆盖的方式加入
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath,true));
bufferedWriter.write("你好,韩顺平!");
bufferedWriter.newLine();//插入一个和系统相关的换行
bufferedWriter.write("你好,韩顺平!");
bufferedWriter.newLine();
bufferedWriter.write("你好,韩顺平!");
//关闭的是外层流(BufferedWriter),在底层也关闭了FileWriter节点流
bufferedWriter.close();
}
}
+ +

18.文件拷贝(BufferedInputStream和BufferedOutputStream)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.hspedu.outputstream_;

import java.io.*;

/**
* @author 韩顺平
* @version 1.0
* 演示使用BufferedOutputStream 和 BufferedInputStream使用
* 使用他们,可以完成二进制文件拷贝.
* 思考:字节流可以操作二进制文件,可以操作文本文件吗?当然可以
*/
public class BufferedCopy02 {
public static void main(String[] args) {

// String srcFilePath = "e:\\Koala.jpg";
// String destFilePath = "e:\\hsp.jpg";
// String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";
// String destFilePath = "e:\\hsp.avi";
String srcFilePath = "e:\\a.java";
String destFilePath = "e:\\a3.java";

//创建BufferedOutputStream对象BufferedInputStream对象
BufferedInputStream bis = null;
BufferedOutputStream bos = null;

try {
//因为 FileInputStream 是 InputStream 子类
bis = new BufferedInputStream(new FileInputStream(srcFilePath));
bos = new BufferedOutputStream(new FileOutputStream(destFilePath));

//循环的读取文件,并写入到 destFilePath
byte[] buff = new byte[1024];
int readLen = 0;
//当返回 -1 时,就表示文件读取完毕
while ((readLen = bis.read(buff)) != -1) {
bos.write(buff, 0, readLen);
}

System.out.println("文件拷贝完毕~~~");

} catch (IOException e) {
e.printStackTrace();
} finally {

//关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
try {
if(bis != null) {
bis.close();
}
if(bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}

}


}
}
+ +

19. ObjectOutputStream 序列化操作(对象字节输出流 )保存数据类型和数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.hspedu.outputstream_;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
* @author 韩顺平
* @version 1.0
* 演示ObjectOutputStream的使用, 完成数据的序列化
*/
public class ObjectOutStream_ {
public static void main(String[] args) throws Exception {
//序列化后,保存的文件格式,不是纯文本,而是按照他的格式来保存
String filePath = "e:\\data.dat";

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));

//序列化数据到 e:\data.dat
oos.writeInt(100);// int -> Integer (实现了 Serializable)
oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
oos.writeChar('a');// char -> Character (实现了 Serializable)
oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
oos.writeUTF("韩顺平教育");//String (实现了 Serializable)
//保存一个dog对象
//Dog也要实现序列化接口
oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
oos.close();
System.out.println("数据保存完毕(序列化形式)");


}
}
+ +

20.ObjectInputStream 反序列化操作(对象字节输入流 )恢复数据类型和数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.hspedu.inputstream_;
import com.hspedu.outputstream_.Dog;
import java.io.*;
/**
* @author 韩顺平
* @version 1.0
*/
public class ObjectInputStream_ {
public static void main(String[] args) throws IOException, ClassNotFoundException {

//指定反序列化的文件
String filePath = "e:\\data.dat";

ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));

//读取
//老师解读
//1. 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
//2. 否则会出现异常

System.out.println(ois.readInt());
System.out.println(ois.readBoolean());

System.out.println(ois.readChar());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());


//dog 的编译类型是 Object , dog 的运行类型是 Dog
Object dog = ois.readObject();
System.out.println("运行类型=" + dog.getClass());
System.out.println("dog信息=" + dog);//底层 Object -> Dog

//这里是特别重要的细节:

//1. 如果我们希望调用Dog的方法, 需要向下转型
Dog dog2 = (Dog)dog;
System.out.println(dog2.getName()); //旺财..

//关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流
ois.close();


}
}
+ +

21.InputStreamReader (转换流 字节流转化成字符流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.hspedu.transformation;

import java.io.*;

/**
* @author 韩顺平
* @version 1.0
* 演示使用 InputStreamReader 转换流解决中文乱码问题
* 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
*/
public class InputStreamReader_ {
public static void main(String[] args) throws IOException {

String filePath = "e:\\a.txt";
//解读
//1. 把 FileInputStream 转成 InputStreamReader
//2. 指定编码 gbk
//InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
//3. 把 InputStreamReader 传入 BufferedReader
//BufferedReader br = new BufferedReader(isr);

//将2 和 3 合在一起
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gbk"));

//4. 读取
String s = br.readLine();
System.out.println("读取内容=" + s);
//5. 关闭外层流
br.close();

}


}
+ +

22.OutputStreamWriter(转换流 字节流转成成字符流)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.hspedu.transformation;

import java.io.*;

/**
* @author 韩顺平
* @version 1.0
* 演示 OutputStreamWriter 使用
* 把FileOutputStream 字节流,转成字符流 OutputStreamWriter
* 指定处理的编码 gbk/utf-8/utf8
*/
public class OutputStreamWriter_ {
public static void main(String[] args) throws IOException {
String filePath = "e:\\hsp.txt";
String charSet = "utf-8";
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
osw.write("hi, 韩顺平教育");
osw.close();
System.out.println("按照 " + charSet + " 保存文件成功~");


}
}
+ +

23.PrintStream(字节打印流)只有输出流,没有输入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.hspedu.printstream;

import java.io.IOException;
import java.io.PrintStream;

/**
* @author 韩顺平
* @version 1.0
* 演示PrintStream (字节打印流/输出流)
*/
public class PrintStream_ {
public static void main(String[] args) throws IOException {

PrintStream out = System.out;
//在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器
/*
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}

*/
out.print("john, hello");
//因为print底层使用的是write , 所以我们可以直接调用write进行打印/输出
out.write("韩顺平,你好".getBytes());
out.close();

//我们可以去修改打印流输出的位置/设备
//1. 输出修改成到 "e:\\f1.txt"
//2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt
//3. public static void setOut(PrintStream out) {
// checkIO();
// setOut0(out); // native 方法,修改了out
// }
System.setOut(new PrintStream("e:\\f1.txt"));
System.out.println("hello, 韩顺平教育~");


}
}
+ +

24.PrintWriter (字符打印流)只有输出流,没有输入流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.hspedu.transformation;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

/**
* @author 韩顺平
* @version 1.0
* 演示 PrintWriter 使用方式
*/
public class PrintWriter_ {
public static void main(String[] args) throws IOException {

//PrintWriter printWriter = new PrintWriter(System.out);//默认显示在显示器上
PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
printWriter.print("hi, 北京你好~~~~");
//关闭或者刷新才会写入
printWriter.close();//flush + 关闭流, 才会将数据写入到文件..

}
}
+ +

25.Properties读取修改配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.hspedu.properties_;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

/**
* @author 韩顺平
* @version 1.0
*/
public class Properties02 {
public static void main(String[] args) throws IOException {
//使用Properties 类来读取mysql.properties 文件

//1. 创建Properties 对象
Properties properties = new Properties();
//2. 加载指定配置文件
properties.load(new FileReader("src\\mysql.properties"));
//3. 把k-v显示控制台
properties.list(System.out);
//4. 根据key 获取对应的值
String user = properties.getProperty("user");
String pwd = properties.getProperty("pwd");
System.out.println("用户名=" + user);
System.out.println("密码是=" + pwd);



}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.hspedu.properties_;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;

/**
* @author 韩顺平
* @version 1.0
*/
public class Properties03 {
public static void main(String[] args) throws IOException {
//使用Properties 类来创建 配置文件, 修改配置文件内容

Properties properties = new Properties();
//创建
//1.如果该文件没有key 就是创建
//2.如果该文件有key ,就是修改
/*
Properties 父类是 Hashtable , 底层就是Hashtable 核心方法
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;//如果key 存在,就替换
return old;
}
}

addEntry(hash, key, value, index);//如果是新k, 就addEntry
return null;
}

*/
properties.setProperty("charset", "utf8");
properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode码值
properties.setProperty("pwd", "888888");

//将k-v 存储文件中即可
properties.store(new FileOutputStream("src\\mysql2.properties"), null);
System.out.println("保存配置文件成功~");

}
}
+ +

26.InetAddress类的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.hspedu.api;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
* @author 韩顺平
* @version 1.0
* 演示InetAddress 类的使用
*/
public class API_ {
public static void main(String[] args) throws UnknownHostException {

//1. 获取本机的InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);//DESKTOP-S4MP84S/192.168.12.1

//2. 根据指定主机名 获取 InetAddress对象
InetAddress host1 = InetAddress.getByName("DESKTOP-S4MP84S");
System.out.println("host1=" + host1);//DESKTOP-S4MP84S/192.168.12.1

//3. 根据域名返回 InetAddress对象, 比如 www.baidu.com 对应
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println("host2=" + host2);//www.baidu.com / 110.242.68.4

//4. 通过 InetAddress 对象,获取对应的地址
String hostAddress = host2.getHostAddress();//IP 110.242.68.4
System.out.println("host2 对应的ip = " + hostAddress);//110.242.68.4

//5. 通过 InetAddress 对象,获取对应的主机名/或者的域名
String hostName = host2.getHostName();
System.out.println("host2对应的主机名/域名=" + hostName); // www.baidu.com

}
}
+ +

27.网络编程相关题目的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
题目
(1)编写客户端程序和服务器端程序
(2)客户端可以输入一个 音乐 的文件名,比如 高山流水,服务端 收到音乐后,可以给客户端 返回这个 音乐文件
如果服务器没有这文件,返回一个默认的音乐即可。
(3)客户端收到文件后,保存在本地 c:\\
(4)该程序可以使用StreamUtils.java
*/



package com.hspedu.homework;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

/**
* @author GongChangjiang
* @version 1.0
* 我亦无他 惟手熟尔
* 客户端
*/
public class Homework03Client {
public static void main(String[] args) throws Exception {
//通过对方的IP地址和端口向对方发送数据
Socket socket = new Socket(InetAddress.getLocalHost(), 9898);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
Scanner scanner = new Scanner(System.in);
System.out.println("请输入一个歌曲名:");
String songName = scanner.next();
bufferedWriter.write(songName);
bufferedWriter.flush();
socket.shutdownOutput();
//接收相应的歌曲
String destFilePath = "src\\歌曲.mp3";
InputStream inputStream = socket.getInputStream();
byte[] bytes = StreamUtils.streamToByteArray(inputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFilePath));
bufferedOutputStream.write(bytes);
bufferedOutputStream.flush();


//关闭对应的流和socket
System.out.println("客户端退出");
socket.close();
bufferedWriter.close();
bufferedOutputStream.close();


}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.hspedu.homework;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author GongChangjiang
* @version 1.0
* 我亦无他 惟手熟尔
* 服务端
*/
public class Homework03Server {
public static void main(String[] args) throws Exception {
System.out.println("服务器端正在9898端口监听......");
ServerSocket serverSocket = new ServerSocket(9898);
Socket socket = serverSocket.accept();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s = bufferedReader.readLine();
System.out.println(s);
//根据输入的歌曲名做出相应的操作

if("高山流水".equals(s)){
String filePath = "c:\\AlYun\\高山流水.mp3";
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(bytes);
socket.shutdownOutput();
}else {
String filePath = "c:\\AlYun\\无名.mp3";
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(bytes);
socket.shutdownOutput();
}

//关闭相应的流和socket
System.out.println("服务端退出");
serverSocket.close();
socket.close();
bufferedReader.close();

}
}
+ +
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.hspedu.homework;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
* 此类用于演示关于流的读写方法
*
*/
public class StreamUtils {
/**
* 功能:将输入流转换成byte[], 即可以把文件的内容读入到byte[]
* @param is
* @return
* @throws Exception
*/
public static byte[] streamToByteArray(InputStream is) throws Exception{
ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
byte[] b = new byte[1024];//字节数组
int len;
while((len=is.read(b))!=-1){//循环读取
bos.write(b, 0, len);//把读取到的数据,写入bos
}
byte[] array = bos.toByteArray();//然后将bos 转成字节数组
bos.close();
return array;
}
/**
* 功能:将InputStream转换成String
* @param is
* @return
* @throws Exception
*/

public static String streamToString(InputStream is) throws Exception{
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder= new StringBuilder();
String line;
while((line=reader.readLine())!=null){
builder.append(line+"\r\n");
}
return builder.toString();

}

}
+ +

28.Class类对象的常用方法举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.hspedu.reflection.class_;

import com.hspedu.Car;

import java.lang.reflect.Field;

/**
* @author 韩顺平
* @version 1.0
* 演示Class类的常用方法
*/
public class Class02 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {

String classAllPath = "com.hspedu.Car";
//1 . 获取到Car类 对应的 Class对象
//<?> 表示不确定的Java类型
Class<?> cls = Class.forName(classAllPath);
//2. 输出cls
System.out.println(cls); //显示cls对象, 是哪个类的Class对象 com.hspedu.Car
System.out.println(cls.getClass());//输出cls运行类型 java.lang.Class
//3. 得到包名
System.out.println(cls.getPackage().getName());//包名
//4. 得到全类名
System.out.println(cls.getName());
//5. 通过cls创建对象实例
Car car = (Car) cls.newInstance();
System.out.println(car);//car.toString()
//6. 通过反射获取属性 brand
Field brand = cls.getField("brand");
System.out.println(brand.get(car));//宝马
//7. 通过反射给属性赋值
brand.set(car, "奔驰");
System.out.println(brand.get(car));//奔驰
//8 我希望大家可以得到所有的属性(字段)
System.out.println("=======所有的字段属性====");
Field[] fields = cls.getFields();
for (Field f : fields) {
System.out.println(f.getName());//名称
}


}
}
//用于举例的辅助类
public class Car {
public String brand = "宝马";//品牌
public int price = 500000;
public String color = "白色";

@Override
public String toString() {
return "Car{" +
"brand='" + brand + '\'' +
", price=" + price +
", color='" + color + '\'' +
'}';
}
}

+ +

29.获取Class对象的六种方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.hspedu.reflection.class_;

import com.hspedu.Car;

/**
* @author 韩顺平
* @version 1.0
* 演示得到Class对象的各种方式(6)
*/
public class GetClass_ {
public static void main(String[] args) throws ClassNotFoundException {

//1. Class.forName
String classAllPath = "com.hspedu.Car"; //通过读取配置文件获取
Class<?> cls1 = Class.forName(classAllPath);
System.out.println(cls1);

//2. 类名.class , 应用场景: 用于参数传递
Class cls2 = Car.class;
System.out.println(cls2);

//3. 对象.getClass(), 应用场景,有对象实例
Car car = new Car();
Class cls3 = car.getClass();
System.out.println(cls3);

//4. 通过类加载器【4种】来获取到类的Class对象
//(1)先得到类加载器 car
ClassLoader classLoader = car.getClass().getClassLoader();
//(2)通过类加载器得到Class对象
Class cls4 = classLoader.loadClass(classAllPath);
System.out.println(cls4);

//cls1 , cls2 , cls3 , cls4 其实是同一个对象
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());

//5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class类对象
Class<Integer> integerClass = int.class;
Class<Character> characterClass = char.class;
Class<Boolean> booleanClass = boolean.class;
System.out.println(integerClass);//int

//6. 基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
Class<Integer> type1 = Integer.TYPE;
Class<Character> type2 = Character.TYPE; //其它包装类BOOLEAN, DOUBLE, LONG,BYTE等待
System.out.println(type1);

System.out.println(integerClass.hashCode());//?
System.out.println(type1.hashCode());//?




}
}
+ +

30.通过Class对象获取类的结构信息的相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
package com.hspedu.reflection;

import org.junit.jupiter.api.Test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* @author 韩顺平
* @version 1.0
* 演示如何通过反射获取类的结构信息
*/
public class ReflectionUtils {
public static void main(String[] args) {

}
//第二组方法API
@Test
public void api_02() throws ClassNotFoundException, NoSuchMethodException {
//得到Class对象
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
//getDeclaredFields:获取本类中所有属性
//规定 说明: 默认修饰符 是0 , public 是1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName()
+ " 该属性的修饰符值=" + declaredField.getModifiers()
+ " 该属性的类型=" + declaredField.getType());
}

//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName()
+ " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
+ " 该方法返回类型" + declaredMethod.getReturnType());

//输出当前这个方法的形参数组情况
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型=" + parameterType);
}
}

//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("====================");
System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名

Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该构造器的形参类型=" + parameterType);
}



}

}

//第一组方法API
@Test
public void api_01() throws ClassNotFoundException, NoSuchMethodException {

//得到Class对象
Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
//getName:获取全类名
System.out.println(personCls.getName());//com.hspedu.reflection.Person
//getSimpleName:获取简单类名
System.out.println(personCls.getSimpleName());//Person
//getFields:获取所有public修饰的属性,包含本类以及父类的
Field[] fields = personCls.getFields();
for (Field field : fields) {//增强for
System.out.println("本类以及父类的属性=" + field.getName());
}
//getDeclaredFields:获取本类中所有属性
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName());
}
//getMethods:获取所有public修饰的方法,包含本类以及父类的
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println("本类以及父类的方法=" + method.getName());
}
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName());
}
//getConstructors: 获取所有public修饰的构造器,包含本类
Constructor<?>[] constructors = personCls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("本类的构造器=" + constructor.getName());
}
//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名
}
//getPackage:以Package形式返回 包信息
System.out.println(personCls.getPackage());//com.hspedu.reflection
//getSuperClass:以Class形式返回父类信息
Class<?> superclass = personCls.getSuperclass();
System.out.println("父类的class对象=" + superclass);//
//getInterfaces:以Class[]形式返回接口信息
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口信息=" + anInterface);
}
//getAnnotations:以Annotation[] 形式返回注解信息
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息=" + annotation);//注解
}


}
}

//用于举例的类、接口和方法
class A {
public String hobby;

public void hi() {

}

public A() {
}

public A(String name) {
}
}

interface IA {
}

interface IB {

}

@Deprecated
class Person extends A implements IA, IB {
//属性
public String name;
protected static int age; // 4 + 8 = 12
String job;
private double sal;

//构造器
public Person() {
}

public Person(String name) {
}

//私有的
private Person(String name, int age) {

}

//方法
public void m1(String name, int age, double sal) {

}

protected String m2() {
return null;
}

void m3() {

}

private void m4() {

}
}
+ +

31.反射暴破创建实例/操作属性/操作方法

暴破创建实例

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.hspedu.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
* @author 韩顺平
* @version 1.0
* 演示通过反射机制创建实例
*/
public class ReflecCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

//1. 先获取到User类的Class对象
Class<?> userClass = Class.forName("com.hspedu.reflection.User");
//2. 通过public的无参构造器创建实例
Object o = userClass.newInstance();
System.out.println(o);
//3. 通过public的有参构造器创建实例
/*
constructor 对象就是
public User(String name) {//public的有参构造器
this.name = name;
}
*/
//3.1 先得到对应构造器
Constructor<?> constructor = userClass.getConstructor(String.class);
//3.2 创建实例,并传入实参
Object hsp = constructor.newInstance("hsp");
System.out.println("hsp=" + hsp);
//4. 通过非public的有参构造器创建实例
//4.1 得到private的构造器对象
Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
//4.2 创建实例
//暴破【暴力破解】 , 使用反射可以访问private构造器/方法/属性, 反射面前,都是纸老虎
constructor1.setAccessible(true);
Object user2 = constructor1.newInstance(100, "张三丰");
System.out.println("user2=" + user2);
}
}

class User { //User类
private int age = 10;
private String name = "韩顺平教育";

public User() {//无参 public
}

public User(String name) {//public的有参构造器
this.name = name;
}

private User(int age, String name) {//private 有参构造器
this.age = age;
this.name = name;
}

public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}
+ +

暴破操作属性

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.hspedu.reflection;

import java.lang.reflect.Field;

/**
* @author 韩顺平
* @version 1.0
* 演示反射操作属性
*/
public class ReflecAccessProperty {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {

//1. 得到Student类对应的 Class对象
Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");
//2. 创建对象
Object o = stuClass.newInstance();//o 的运行类型就是Student
System.out.println(o.getClass());//Student
//3. 使用反射得到age 属性对象
Field age = stuClass.getField("age");
age.set(o, 88);//通过反射来操作属性
System.out.println(o);//
System.out.println(age.get(o));//返回age属性的值

//4. 使用反射操作name 属性
Field name = stuClass.getDeclaredField("name");
//对name 进行暴破, 可以操作private 属性
name.setAccessible(true);
//name.set(o, "老韩");
name.set(null, "老韩~");//因为name是static属性,因此 o 也可以写出null
System.out.println(o);
System.out.println(name.get(o)); //获取属性值
System.out.println(name.get(null));//获取属性值, 要求name是static

}
}

class Student {//类
public int age;
private static String name;

public Student() {//构造器
}

public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
+ +

暴破操作方法

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.hspedu.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* @author 韩顺平
* @version 1.0
* 演示通过反射调用方法
*/
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {

//1. 得到Boss类对应的Class对象
Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");
//2. 创建对象
Object o = bossCls.newInstance();
//3. 调用public的hi方法
//Method hi = bossCls.getMethod("hi", String.class);//OK
//3.1 得到hi方法对象
Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
//3.2 调用
hi.invoke(o, "韩顺平教育~");

//4. 调用private static 方法
//4.1 得到 say 方法对象
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
//4.2 因为say方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样
say.setAccessible(true);
System.out.println(say.invoke(o, 100, "张三", '男'));
//4.3 因为say方法是static的,还可以这样调用 ,可以传入null
System.out.println(say.invoke(null, 200, "李四", '女'));

//5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
Object reVal = say.invoke(null, 300, "王五", '男');
System.out.println("reVal 的运行类型=" + reVal.getClass());//String


//在演示一个返回的案例
Method m1 = bossCls.getDeclaredMethod("m1");
Object reVal2 = m1.invoke(o);
System.out.println("reVal2的运行类型=" + reVal2.getClass());//Monster


}
}

class Monster {}
class Boss {//类
public int age;
private static String name;

public Boss() {//构造器
}

public Monster m1() {
return new Monster();
}

private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}

public void hi(String s) {//普通public方法
System.out.println("hi " + s);
}
}
+ +

avatar
Jason
Debug the world!
公告
本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
目录
  1. 1. 1.String类的常用方法
  2. 2. 2.StringBufferd类的常用方法
  3. 3. 3.Math类的常用方法
  4. 4. 4.Arrays类的常用方法
  5. 5. 5.System类的常用方法
  6. 6. 6.BigDecimal类和BigInteger类使用(大数处理)
  7. 7. 7.时间类、日期类的常用方法(Date、Calendar,第三代日期类)
  8. 8. 8.实现List接口实现类的常用方法
  9. 9. 9.Map接口的常用方法以及遍历方式
  10. 10. 9.Collections工具类
  11. 11. 10.创建文件的三种方式以及文件的相关操作(相关方法)
  12. 12. 11.FileInputStream 字节输入流
  13. 13. 12.FileOutputStream 字节输出流
  14. 14. 13.文件拷贝(FileInputStream和FileOutputStream)
  15. 15. 14.FileReader 字符输入流
  16. 16. 15.FileWriter 字符输出流
  17. 17. 16.BufferedReader 字符处理流(包装流)
  18. 18. 17.BufferedWriter 字符处理流(包装流)
  19. 19. 18.文件拷贝(BufferedInputStream和BufferedOutputStream)
  20. 20. 19. ObjectOutputStream 序列化操作(对象字节输出流 )保存数据类型和数据
  21. 21. 20.ObjectInputStream 反序列化操作(对象字节输入流 )恢复数据类型和数据
  22. 22. 21.InputStreamReader (转换流 字节流转化成字符流)
  23. 23. 22.OutputStreamWriter(转换流 字节流转成成字符流)
  24. 24. 23.PrintStream(字节打印流)只有输出流,没有输入流
  25. 25. 24.PrintWriter (字符打印流)只有输出流,没有输入流
  26. 26. 25.Properties读取修改配置文件
  27. 27. 26.InetAddress类的常用方法
  28. 28. 27.网络编程相关题目的源码
  29. 29. 28.Class类对象的常用方法举例
  30. 30. 29.获取Class对象的六种方式
  31. 31. 30.通过Class对象获取类的结构信息的相关方法
  32. 32. 31.反射暴破创建实例/操作属性/操作方法
  • 最近更新
    \ No newline at end of file diff --git a/posts/29250.html b/posts/29250.html new file mode 100644 index 000000000..d78a39d35 --- /dev/null +++ b/posts/29250.html @@ -0,0 +1,288 @@ +简历模板 | The Blog + + + + + + + + + + + + +

    简历模板

    黑马程序员新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题) https://www.bilibili.com/video/BV1yT411H7YK/?share_source=copy_web&vd_source=aee5e475191b69e6c781059ab6662584

    +

    Java批注简历标准

    image-20230508235133469

    +

    面试参考话术

    + +
    + +
    + + + + +

    模板

    全部的模板地址: https://gitee.com/JasonsGong/resume

    +

    模板一 本科一年经验

    + +
    + +
    + + + + +

    模板二 Java开发_AAA_N年

    + +
    + +
    + + + + +

    模板三 灰蓝色色时尚简历模板

    + +
    + +
    + + + + +

    模板四 灰色大气简约简历模板

    + +
    + +
    + + + + +

    模板五 简约大气橙色简历模板

    + +
    + +
    + + + + +

    模板六 经典风格简历模板

    + +
    + +
    + + + + +

    模板七 时尚线条简历模板

    + +
    + +
    + + + + +

    模板八 科技版简历模板

    + +
    + +
    + + + + +

    模板九 JAVA开发_李传播_5年

    + +
    + +
    + + + + +
    \ No newline at end of file diff --git a/posts/29367.html b/posts/29367.html new file mode 100644 index 000000000..a0d4aa576 --- /dev/null +++ b/posts/29367.html @@ -0,0 +1,386 @@ +FreeMarker模板引擎 | The Blog + + + + + + + + + + + + +

    FreeMarker模板引擎

    一.FreeMarker介绍

    ​ FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

    +

    ​ 模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

    +

    image-20230816145012534

    +

    技术选型对比

    + + + + + + + + + + + + + + + + + + + + + + + +
    技术说明
    JspJsp 为 Servlet 专用,不能单独进行使用
    VelocityVelocity从2010年更新完 2.0 版本后,7年没有更新。Spring Boot 官方在 1.4 版本后对此也不在支持
    thmeleaf新技术,功能较为强大,但是执行的效率比较低
    freemarker性能好,强大的模板语言、轻量
    +

    二.快速入门

    1.环境搭建

    1.1 创建一个demo工程

    此处为了方便建的是子模块,测试使用可以创建一个单独的工程

    +

    image-20230816145923876

    +

    1.2 引入依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!-- lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>

    <!-- apache 对 java io 的封装工具库 -->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
    </dependency>
    </dependencies>
    + +

    1.3 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    server:
    port: 8881 #服务端口
    spring:
    application:
    name: freemarker-demo #指定服务名
    freemarker:
    cache: false #关闭模板缓存,方便测试
    settings:
    template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl #指定Freemarker模板文件的后缀名,通常都是以ftl作为扩展名,也可以为html、xml、jsp等!
    + +

    1.4 创建模板

    在resources下创建templates,此目录为freemarker的默认模板存放目录

    +

    image-20230816150936881

    +

    在templates下创建模板文件 01-basic.ftl ,模板中的插值表达式最终会被freemarker替换成具体的数据

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>
    <b>普通文本 String 展示:</b><br><br>
    Hello ${name} <br>
    <hr>
    <b>对象Student中的数据展示:</b><br/>
    姓名:${stu.name}<br/>
    年龄:${stu.age}
    <hr>
    </body>
    </html>
    + +

    1.5 创建启动类和模型类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.heima.freemarker;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/16
    * @Description
    */
    @SpringBootApplication
    public class FreeMarkerDemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(FreeMarkerDemoApplication.class,args);
    }
    }
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.heima.freemarker.entity;

    import lombok.Data;

    import java.util.Date;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/16
    * @Description
    */
    @Data
    public class Student {
    private String name;//姓名
    private int age;//年龄
    private Date birthday;//生日
    private Float money;//钱包
    }
    + +

    1.6 创建Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package com.heima.freemarker.controller;

    import com.heima.freemarker.entity.Student;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/16
    * @Description
    */
    @Controller
    public class HelloController {

    @GetMapping("/basic")
    public String test(Model model) {
    //1.纯文本形式的参数
    model.addAttribute("name", "freemarker");

    //2.实体类相关的参数
    Student student = new Student();
    student.setName("小明");
    student.setAge(18);
    model.addAttribute("stu", student);

    return "01-basic";
    }
    }
    + +

    1.7 测试访问

    访问 http://localhost:8881/basic

    +

    image-20230816152750639

    +

    三.FreeMarker语法基础

    1.基础语法种类

    1、注释,即<#– –>,介于其之间的内容会被freemarker忽略

    +
    1
    <#--我是一个freemarker注释-->
    + +

    2、插值(Interpolation):即 ${..} 部分,freemarker会用真实的值代替**${..}**

    +
    1
    Hello ${name}
    + +

    3、FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑。

    +
    1
    <# >FTL指令</#> 
    + +

    4、文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内容。

    +
    1
    2
    <#--freemarker中的普通文本-->
    我是一个普通的文本
    + +

    2.集合指令

    image-20230816170028648

    +

    image-20230816170704421

    +

    1、数据模型:

    +

    在HelloController中新增如下方法:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    @GetMapping("/list")
    public String list(Model model){

    //------------------------------------
    Student stu1 = new Student();
    stu1.setName("小强");
    stu1.setAge(18);
    stu1.setMoney(1000.86f);
    stu1.setBirthday(new Date());

    //小红对象模型数据
    Student stu2 = new Student();
    stu2.setName("小红");
    stu2.setMoney(200.1f);
    stu2.setAge(19);

    //将两个对象模型数据存放到List集合中
    List<Student> stus = new ArrayList<>();
    stus.add(stu1);
    stus.add(stu2);

    //向model中存放List集合数据
    model.addAttribute("stus",stus);

    //------------------------------------

    //创建Map数据
    HashMap<String,Student> stuMap = new HashMap<>();
    stuMap.put("stu1",stu1);
    stuMap.put("stu2",stu2);
    // 3.1 向model中存放Map数据
    model.addAttribute("stuMap", stuMap);

    return "02-list";
    }
    + +

    2、模板:

    +

    在templates中新增02-list.ftl文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>

    <#-- list 数据的展示 -->
    <b>展示list中的stu数据:</b>
    <br>
    <br>
    <table>
    <tr>
    <td>序号</td>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    </table>
    <hr>

    <#-- Map 数据的展示 -->
    <b>map数据的展示:</b>
    <br/><br/>
    <a href="###">方式一:通过map['keyname'].property</a><br/>
    输出stu1的学生信息:<br/>
    姓名:<br/>
    年龄:<br/>
    <br/>
    <a href="###">方式二:通过map.keyname.property</a><br/>
    输出stu2的学生信息:<br/>
    姓名:<br/>
    年龄:<br/>

    <br/>
    <a href="###">遍历map中两个学生信息:</a><br/>
    <table>
    <tr>
    <td>序号</td>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    </table>
    <hr>

    </body>
    </html>
    + +

    实例代码:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>

    <#-- list 数据的展示 -->
    <b>展示list中的stu数据:</b>
    <br>
    <br>
    <table>
    <tr>
    <td>序号</td>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    <#list stus as stu>
    <tr>
    <td>${stu_index+1}</td>
    <td>${stu.name}</td>
    <td>${stu.age}</td>
    <td>${stu.money}</td>
    </tr>
    </#list>

    </table>
    <hr>

    <#-- Map 数据的展示 -->
    <b>map数据的展示:</b>
    <br/><br/>
    <a href="###">方式一:通过map['keyname'].property</a><br/>
    输出stu1的学生信息:<br/>
    姓名:${stuMap['stu1'].name}<br/>
    年龄:${stuMap['stu1'].age}<br/>
    <br/>
    <a href="###">方式二:通过map.keyname.property</a><br/>
    输出stu2的学生信息:<br/>
    姓名:${stuMap.stu2.name}<br/>
    年龄:${stuMap.stu2.age}<br/>

    <br/>
    <a href="###">遍历map中两个学生信息:</a><br/>
    <table>
    <tr>
    <td>序号</td>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    <#list stuMap?keys as key >
    <tr>
    <td>${key_index}</td>
    <td>${stuMap[key].name}</td>
    <td>${stuMap[key].age}</td>
    <td>${stuMap[key].money}</td>
    </tr>
    </#list>
    </table>
    <hr>

    </body>
    </html>
    + +

    上面代码解释:

    +

    ${k_index}:index:得到循环的下标,使用方法是在stu后边加”_index”,它的值是从0开始,${stu_index+1}下标从1开始

    +

    展示效果:

    +

    image-20230816170550343

    +

    3.if指令

    image-20230816170909497

    +

    if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否则跳过内容不再输出。

    +
      +
    • 指令格式
    • +
    +
    1
    <#if ></if>
    + +

    1、数据模型:

    +

    使用list指令中测试数据模型,判断名称为小红的数据字体显示为红色。

    +

    2、模板:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <table>
    <tr>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    <#list stus as stu>
    <tr>
    <td >${stu.name}</td>
    <td>${stu.age}</td>
    <td >${stu.mondy}</td>
    </tr>
    </#list>

    </table>
    + +

    实例代码:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <table>
    <tr>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    <#list stus as stu >
    <#if stu.name='小红'>
    <tr style="color: red">
    <td>${stu_index}</td>
    <td>${stu.name}</td>
    <td>${stu.age}</td>
    <td>${stu.money}</td>
    </tr>
    <#else >
    <tr>
    <td>${stu_index}</td>
    <td>${stu.name}</td>
    <td>${stu.age}</td>
    <td>${stu.money}</td>
    </tr>
    </#if>
    </#list>
    </table>
    + +

    3、输出:

    +

    image-20230816171119081

    +

    4.运算符

    1、算数运算符

    +

    FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:

    +
      +
    • 加法: +
    • +
    • 减法: -
    • +
    • 乘法: *
    • +
    • 除法: /
    • +
    • 求模 (求余): %
    • +
    +

    模板代码

    +
    1
    2
    3
    4
    5
    6
    7
    <b>算数运算符</b>
    <br/><br/>
    100+5 运算: ${100 + 5 }<br/>
    100 - 5 * 5运算:${100 - 5 * 5}<br/>
    5 / 2运算:${5 / 2}<br/>
    12 % 10运算:${12 % 10}<br/>
    <hr>
    + +

    除了 + 运算以外,其他的运算只能和 number 数字类型的计算。

    +

    2、比较运算符

    +
      +
    • =或者==:判断两个值是否相等.
    • +
    • !=:判断两个值是否不等.
    • +
    • >或者gt:判断左边值是否大于右边值
    • +
    • >=或者gte:判断左边值是否大于等于右边值
    • +
    • <或者lt:判断左边值是否小于右边值
    • +
    • <=或者lte:判断左边值是否小于等于右边值
    • +
    +

    = 和 == 模板代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>

    <b>比较运算符</b>
    <br/>
    <br/>

    <dl>
    <dt> =/== 和 != 比较:</dt>
    <dd>
    <#if "xiaoming" == "xiaoming">
    字符串的比较 "xiaoming" == "xiaoming"
    </#if>
    </dd>
    <dd>
    <#if 10 != 100>
    数值的比较 10 != 100
    </#if>
    </dd>
    </dl>



    <dl>
    <dt>其他比较</dt>
    <dd>
    <#if 10 gt 5 >
    形式一:使用特殊字符比较数值 10 gt 5
    </#if>
    </dd>
    <dd>
    <#-- 日期的比较需要通过?date将属性转为data类型才能进行比较 -->
    <#if (date1?date >= date2?date)>
    形式二:使用括号形式比较时间 date1?date >= date2?date
    </#if>
    </dd>
    </dl>

    <br/>
    <hr>
    </body>
    </html>
    + +

    Controller 的 数据模型代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @GetMapping("operation")
    public String testOperation(Model model) {
    //构建 Date 数据
    Date now = new Date();
    model.addAttribute("date1", now);
    model.addAttribute("date2", now);

    return "03-operation";
    }
    + +

    比较运算符注意

    +
      +
    • =!=可以用于字符串、数值和日期来比较是否相等
    • +
    • =!=两边必须是相同类型的值,否则会产生错误
    • +
    • 字符串 "x""x ""X"比较是不等的.因为FreeMarker是精确比较
    • +
    • 其它的运行符可以作用于数字和日期,但不能作用于字符串
    • +
    • 使用gt等字母运算符代替>会有更好的效果,因为 FreeMarker会把>解释成FTL标签的结束字符
    • +
    • 可以使用括号来避免这种情况,如:<#if (x>y)>
    • +
    +

    3、逻辑运算符

    +
      +
    • 逻辑与:&&
    • +
    • 逻辑或:||
    • +
    • 逻辑非:!
    • +
    +

    逻辑运算符只能作用于布尔值,否则将产生错误 。

    +

    模板代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <b>逻辑运算符</b>
    <br/>
    <br/>
    <#if (10 lt 12 )&&( 10 gt 5 ) >
    (10 lt 12 )&&( 10 gt 5 ) 显示为 true
    </#if>
    <br/>
    <br/>
    <#if !false>
    false 取反为true
    </#if>
    <hr>
    + +

    5.空值处理

    1、判断某变量是否存在使用 “??”

    +

    用法为:variable??,如果该变量存在,返回true,否则返回false

    +

    例:为防止stus为空报错可以加上判断如下:

    +
    1
    2
    3
    4
    5
    <#if stus??>
    <#list stus as stu>
    ......
    </#list>
    </#if>
    + +

    2、缺失变量默认值使用 “!”

    +
      +
    • 使用!要以指定一个默认值,当变量为空时显示默认值

      +

      例: ${name!’’}表示如果name为空显示空字符串。

      +
    • +
    • 如果是嵌套对象则建议使用()括起来

      +

      例: ${(stu.bestFriend.name)!’’}表示,如果stu或bestFriend或name为空默认显示空字符串。

      +
    • +
    +

    6.内建函数

    内建函数语法格式: 变量+?+函数名称

    +

    1、某个集合的大小

    +

    ${集合名?size}

    +

    2、日期格式化

    +

    显示年月日: ${today?date}

    +

    显示时分秒:${today?time}

    +

    显示日期+时间:${today?datetime}

    +

    自定义格式化: ${today?string("yyyy年MM月")}

    +

    3、内建函数c

    +

    model.addAttribute(“point”, 102920122);

    +

    point是数字型,使用${point}会显示这个数字的值,每三位使用逗号分隔。

    +

    如果不想显示为每三位分隔的数字,可以使用c函数将数字型转成字符串输出

    +

    ${point?c}

    +

    4、将json字符串转成对象

    +

    一个例子:

    +

    其中用到了 assign标签,assign的作用是定义一个变量。

    +
    1
    2
    3
    <#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
    <#assign data=text?eval />
    开户行:${data.bank} 账号:${data.account}
    + +

    模板代码:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>inner Function</title>
    </head>
    <body>

    <b>获得集合大小</b><br>

    集合大小:
    <hr>


    <b>获得日期</b><br>

    显示年月日: <br>

    显示时分秒:<br>

    显示日期+时间:<br>

    自定义格式化: <br>

    <hr>

    <b>内建函数C</b><br>
    没有C函数显示的数值: <br>

    有C函数显示的数值:

    <hr>

    <b>声明变量assign</b><br>


    <hr>
    </body>
    </html>
    + +

    内建函数模板页面:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>inner Function</title>
    </head>
    <body>

    <b>获得集合大小</b><br>

    集合大小:${stus?size}
    <hr>


    <b>获得日期</b><br>

    显示年月日: ${today?date} <br>

    显示时分秒:${today?time}<br>

    显示日期+时间:${today?datetime}<br>

    自定义格式化: ${today?string("yyyy年MM月")}<br>

    <hr>

    <b>内建函数C</b><br>
    没有C函数显示的数值:${point} <br>

    有C函数显示的数值:${point?c}

    <hr>

    <b>声明变量assign</b><br>
    <#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
    <#assign data=text?eval />
    开户行:${data.bank} 账号:${data.account}

    <hr>
    </body>
    </html>
    + +

    内建函数Controller数据模型:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @GetMapping("innerFunc")
    public String testInnerFunc(Model model) {
    //1.1 小强对象模型数据
    Student stu1 = new Student();
    stu1.setName("小强");
    stu1.setAge(18);
    stu1.setMoney(1000.86f);
    stu1.setBirthday(new Date());
    //1.2 小红对象模型数据
    Student stu2 = new Student();
    stu2.setName("小红");
    stu2.setMoney(200.1f);
    stu2.setAge(19);
    //1.3 将两个对象模型数据存放到List集合中
    List<Student> stus = new ArrayList<>();
    stus.add(stu1);
    stus.add(stu2);
    model.addAttribute("stus", stus);
    // 2.1 添加日期
    Date date = new Date();
    model.addAttribute("today", date);
    // 3.1 添加数值
    model.addAttribute("point", 102920122);
    return "04-innerFunc";
    }
    + +

    四.静态化测试

    1.思路分析

    image-20230816172633599

    +

    2.实现步骤

    根据模板文件生成html文件

    +

    ①:修改application.yml文件,添加以下模板存放位置的配置信息,完整配置如下:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    server:
    port: 8881 #服务端口
    spring:
    application:
    name: freemarker-demo #指定服务名
    freemarker:
    cache: false #关闭模板缓存,方便测试
    settings:
    template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl #指定Freemarker模板文件的后缀名
    template-loader-path: classpath:/templates #模板存放位置
    + +

    ②:在test下创建测试类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    package com.heima.freemarker.test;

    import com.heima.freemarker.FreeMarkerDemoApplication;
    import com.heima.freemarker.entity.Student;
    import freemarker.template.Configuration;
    import freemarker.template.Template;
    import freemarker.template.TemplateException;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;

    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/16
    * @Description
    */
    @SpringBootTest(classes = FreeMarkerDemoApplication.class)
    @RunWith(SpringRunner.class)
    public class FreeMarkerTest {
    @Autowired
    //freemarker.template.Configuration
    private Configuration configuration;

    @Test
    public void test() throws IOException, TemplateException {
    Template template = configuration.getTemplate("01-basic.ftl");
    /**
    * 第一个参数:数据模型
    * 第二个参数:输出流
    */
    template.process(getData(),new FileWriter("C:\\Gong\\data\\test.html"));

    }

    /**
    * 构建数据模型
    */
    private Map<String,Object> getData(){
    HashMap<String, Object> map = new HashMap<>();
    //1.纯文本形式的参数
    map.put("name", "freemarker");
    //2.实体类相关的参数
    Student student = new Student();
    student.setName("小明");
    student.setAge(18);
    map.put("stu", student);
    return map;
    }
    }
    + +

    ③ :运行结果

    +

    image-20230816174550953

    +
    \ No newline at end of file diff --git a/posts/29985.html b/posts/29985.html new file mode 100644 index 000000000..e160bb2ad --- /dev/null +++ b/posts/29985.html @@ -0,0 +1,646 @@ +面试题集锦 | The Blog + + + + + + + + + + + + +

    面试题集锦

    【Java工程师面试宝典】学习说明_互联网校招面试真题面经汇总_牛客网 (nowcoder.com)

    +

    一.Java基础面试题

    1.谈谈你对面向对象的理解

    ​ 对比面向过程,是两种不同处理问题的角度,面向过程更注重事情的每一步骤及顺序,面向对象更注重事情有哪些参与者(对象),以及各自需要做什么。面向过程比较直接高效,面向对象易于复用、扩展和维护。

    +

    面向对象的三大基本特征:封装、继承、多态(父类应用指向子类对象)

    +

    2.JDK、JRE、JVM之间的区别

    JDK java 开发工具

    +

    JRE(Java Runtime Environment Java 运行环境)

    +

    JVM java虚拟机

    +

    JDK = JRE + 开发工具集(例如 Javac,java 编译工具等)

    +

    JRE = JVM + Java SE 标准类库(java 核心类库)

    +

    3.==和equals⽅法的区别

    ==:如果是基本数据类型,⽐较是值,如果是引⽤类型,⽐较的是引⽤地址
    equals:具体看各个类重写equals⽅法之后的⽐较逻辑,⽐如String类,虽然是引⽤类型,但是String类中重写了equals⽅法,⽅法内部⽐较的是字符串中的各个字符是否全部相等。

    +

    4.String,StringBuffer,StringBuilder的区别

    1.String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象,StringBuffer和StringBuilder是可变的,修改是在原对象上操作的

    +

    2.StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更⾼

    +

    性能: StringBuilder > StringBuffer > String

    +

    5.重载和重写的区别

    重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同、方法返回值和访问修饰符可以不同,发生在编译时。

    +

    重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类,抛出异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类访问修饰符为私有,子类不能重写该方法。

    +

    6.接口和抽象类的区别

    1.抽象类可以有实现的方法和抽象的方法

    +

    2.抽象类的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型(默认)的

    +

    3.抽象类只能继承一个,接口可以实现多个

    +

    4.关键字不同,接口的关键字是interface,抽象类的关键字是abstract

    +

    7.List和set的区别

    List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用iterator取出所有的元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素。

    +

    Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用iterator接口取得所有元素,再逐一遍历各个元素。

    +

    8.hashCode()与equals()之间的关系

    hashCode()的作用是获取哈希码,也称散列码,哈希码的作用是确定该对象在哈希表中的索引位置。

    +
      +
    • 如果两个对象相等,那么它们的hashCode()值一定相同

      +
    • +
    • 如果两个对象hashCode()相等,它们并不一定相等

      +
    • +
    +

    9.ArrayList和LinkedList的区别

    ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问)。扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,将老数组的数据拷贝到新数组,然后插入需要加入到数组中的数据。

    +

    LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询。遍历LinkedList必须使用iterator不能使用for循环,因为for循环体内通过get(i)取得某一元素时都需要对list重新遍历,性能消耗大。

    +

    10.HashMap和HashTable的区别?底层实现是什么?

    区别:

    +

    1.HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全

    +

    2.HashMap允许key和value为null,而HashTable不允许

    +

    底层实现: 数组 + 链表

    +

    11.ConcurrentHashMap

    ConcurrentHashMap和HashTable都是线程安全的,但ConcurrentHashMap相比HashTable性能更高

    +

    12.如何实现一个IOC容器

    1.配置文件配置包扫描路径

    +

    2.递归包扫描获取.class文件

    +

    3.反射、确定需要交给IOC管理的类

    +

    4.对需要注入的类进行依赖注入

    +

    13.什么是字节码?采用字节码的好处是什么?

    什么是字节码?

    +

    Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转化为特定系统的机器码执行,在java中,这种供虚拟机理解的代码叫做字节码(即扩展名为.class的文件)。

    +

    采用字节码的好处是什么?

    +

    Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无需重新编译便可在多种计算机上运行。

    +

    14.Java类加载器有哪些

    JDK有三个类加载器:

    +
      +
    1. bootstrap ClassLoader是ExClassLoader 的父类加载器,默认负责加载%JAVA_HOME%bin下的jar包和class文件
    2. +
    3. ExClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/bin/ext文件夹下的jar包和class文件
    4. +
    5. AppClassLoader是自定义加载器的父类,负责加载classpath下的类文件,系统类加载器,线程上下文加载器
    6. +
    +

    自定义加载器的方法: 继承ClassLoader实现自定义加载器

    +

    15.双亲委派模型

    双亲委派模型的执行流程是这样的:

    +

    1、当加载一个类时,会先从应用程序类加载器的缓存里查找相应的类,如果能找到就返回对象,如果找不到就执行下面流程;

    +

    2、在扩展加载器缓存中查找相应的类,如果能找到就返回对象,如果找不到就继续下面流程;

    +

    3、在启动类加载器中查询相应的类,如果找到就返回对象,如果找不到就继续下面流程;

    +

    4、在扩展加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就继续下面流程;

    +

    5、在应用程序类加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就返回 ClassNotFound 异常。

    +

    加载流程如下图所示:

    +

    image-20230904161407239

    +

    一般“双亲”指的是“父亲”和“母亲”,而在这里“双亲”指的是类加载类先向上找,再向下找的流程就叫做双亲委派模型。

    +

    双亲委派模型的好处

    +
      +
    1. 主要是为了安全性,避免用户自己编写的类动他替换java的一些核心类,比如String
    2. +
    3. 同时避免了类的重复加载,因为jvm中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类
    4. +
    +

    16.Java中的异常体系

      +
    1. Java中所有异常都来自顶级父类Throwable
    2. +
    3. Throwable下面有两个子类Exception和Error
    4. +
    5. Error是程序无法处理的错误,一旦出现这个错误,程序将被迫停止运行;Exception不会导致程序停止
    6. +
    7. Exception有分为两个部分RunTimeException运行时异常和CheckedException检查异常
    8. +
    9. RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。
    10. +
    +

    17.GC如何判断对象可以被回收

      +
    • 引用计数法:每一个对象有一个引用计数属性,新增一个引用计数加1,引用释放减1,计数为0时可以回收。(java中没有使用这个方法,原因:可能会出现A引用了B,B又引用了A,这时就算他们都不再使用了,但是因为他们互相引用,计数器=1永远无法被回收)
    • +
    • 可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就可以判断是可回收对象。
    • +
    +

    18.线程的生命周期,线程有哪些状态

    线程通常有五种状态:创建、就绪、运行、阻塞、死亡状态

    +

    阻塞又分为三种情况:等待阻塞、同步阻塞、其他阻塞

    +
      +
    1. 新建状态:新创建了一个线程对象
    2. +
    3. 就绪状态:线程对象创建后,其他线程调用了该对象的start()方法。该线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
    4. +
    5. 运行状态:就绪状态的线程获取了CPU,执行了程序代码
    6. +
    7. 阻塞状态:阻塞状态是线程因为某种原因放弃了CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
    8. +
    9. 死亡状态:线程执行完了或者因为异常退出了run()方法,该线程结束生命周期
    10. +
    +

    19.sleep(),wait(),join(),yield()的区别

    sleep(),wait()的区别

    +
      +
    1. sleep()是Thread类的静态本地方法,wait()是Object类的本地方法
    2. +
    3. sleep()不会释放锁(把锁带着进入冻结状态),wait会释放锁
    4. +
    5. sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字
    6. +
    7. sleep不需要被唤醒(休眠之后退出阻塞),但是wait需要(不指定时间需要被别人中断)
    8. +
    9. sleep一般用于当前线程休眠,或者轮循暂停操作,wait则用于多线程之间的通信
    10. +
    11. sleep会让出cpu执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行
    12. +
    +

    yield()执行后线程直接进入就绪状态,马上释放了cpu,但是依然保留了cpu的执行资格,所有有可能cpu下次进行线程调度还会让这个线程取到执行权

    +

    join()执行后线程进入阻塞状态,例如在线程B中调用了A的join(),那线程B会进入到阻塞队列,直到线程A结束或者中断线程

    +

    20.说说你对线程安全的理解

    线程安全讲的不是线程安全,应该是内存安全堆是共享内存,可以被所有线程访问

    +

    线程安全的定义:当多个线程访问一个对象的时候,如果不用进行额外的同步控制或者其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个线程数是安全的。

    +

    产生线程安全问题的原因: 在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内所有线程都可以访问到该区域,这就是造成问题的潜在原因。

    +

    21.说说你对守护线程的理解

    守护线程:为非守护线程(用户线程)提供服务的线程,任何一个守护线程都是整个jvm中所有非守护线程的守护线程。

    +

    守护线程的作用:

    +

    举例:GC垃圾回收机制,就是一个经典的守护线程,当我们的程序不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就没事可做,所以当垃圾回收线程是jvm上仅剩的线程时,垃圾回收线程会自动离开,它始终再低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

    +

    应用场景:

    +
      +
    1. 为其他的线程提供服务支持情况
    2. +
    3. 或者在任何情况下,程序结束时,这个线程必须正常的且立刻关闭,就可以作为守护线程来使用
    4. +
    +

    22.ThreadLocal的原理和使用场景

    同一个线程中通过ThreadLocal存进去的数据,在任何位置取出来是一致的

    +

    原理

    +

    每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及对应的值。

    +

    当执行set()方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

    +

    get方法的执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

    +

    由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立,互不影响,因此不会存在线程安全性问题,从而无需使用同步机制来保证多条线程访问容器的互斥性。

    +

    使用场景

    +
      +
    1. 在进行对象的跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束
    2. +
    3. 线程间数据隔离
    4. +
    5. 进行事务操作,用于存储事务信息
    6. +
    7. 数据库连接,Session会话管理
    8. +
    +

    23.ThreadLocal内存泄漏原因,怎么避免

    内存泄漏:不会使用的对象或者变量占用的内存不能被回收,就是内存泄漏(内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏的危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光,导致OOM。)

    +

    强引用:使用最普遍的引用(通过new),一个对象具有强应用,不会被垃圾回收器回收,即使是内存不足。我们想要取消强引用,可以显示的将引用赋值为null,jvm在合适的时间就会回收该对象。

    +

    ThreadLocal内存泄漏的根源

    +

    由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除对应key就会导致内存泄漏

    +

    怎么避免

    +
      +
    1. 每次使用ThreadLocal都调用它的remove()方法清除数据
    2. +
    3. 将ThreadLocal变量定义成private static,这样就一直村子ThreadLocal的强应用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。
    4. +
    +

    24.产生内存泄漏的原因有哪些

      +
    1. 资源未关闭或释放导致内存泄露(io资源,数据库的连接)
    2. +
    3. 使用 ThreadLocal 造成内存泄露
    4. +
    5. 静态集合类引起内存泄漏,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。生命周期长的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
    6. +
    7. 重写了 finalize() 的类,如果 finalize() 方法重写的不合理或 finalizer 队列无法跟上 Java 垃圾回收器的速度,那么迟早,应用程序会出现 OutOfMemoryError 异常
    8. +
    +

    25.并发、并行、串行的区别

    串行在时间上不可能发生重叠,前一个任务没有搞定,下一个任务只能等

    +

    并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行

    +

    并发允许两个任务彼此干扰,同一时间点,只有一个任务运行,交替执行

    +

    26.并发的三大特性

      +
    • 原子性:是指在在一个操作中,CPU不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
    • +
    • 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他的线程能够立即看到修改的值。
    • +
    • 有序性:虚拟机再进行代码编译的时,对于那些改变顺序之后不会对最终的结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将它们重排序。
    • +
    +

    27.为什么用线程池?解释线程池参数?

    为什么

    +
      +
    1. 降低资源的消耗;提高线程的利用率,降低创建和销毁现成的消耗
    2. +
    3. 提高响应速度,任务来了,直接有线程可用执行,不是创建线程之后再执行
    4. +
    5. 提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控
    6. +
    +

    线程池参数

    +
      +
    • corePoolSize代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建之后不会消除,而是一种常驻线程。
    • +
    • maxnumPoolSize代表最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足要求时,此时就会创建新的线程,但是线程池总数不会超过最大线程数
    • +
    • keepAliveTime空闲线程存活时间,当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收; unit:keepAliveTime的时间单位
    • +
    • workQueue工作队列,存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在工作队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。工作队列实现了BlockingQueue接口。
    • +
    • threadFactory线程工厂,创建线程的工厂,可以设定线程名、线程编号等。
    • +
    • handler拒绝策略,当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需实现RejectedExecutionHandler接口。
    • +
    +

    28.简述线程池的处理流程

    image-20230905204135018

    +

    29.线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程?

    线程池中阻塞队列的作用?

    +

    1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。

    +

    2、阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。

    +

    3、阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占着cpu资源。

    +

    为什么是先添加队列而不是先创建最大线程?

    +

    在创建新线程的时候。是要获取全局锁的,这个时候其它就得阻塞,影响了整体效率。

    +

    就好比一个企业里面有10个(core)正式工的名额,最多招10个正式工,要是任务超过正式工人数,(task > core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10个人,但是任务会稍微的积压一下,即先放到队列去(代价低),10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就得招外包帮忙了(注意是临时工),要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。

    +

    30.线程池中线程复用原理

    线程池将线程和任务解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制。

    +

    在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行都会调用Thread.start()来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来。

    +

    31.Spring是什么

    轻量级的开源的j2EE框架。他是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)可以起一个连接作用,比如说把struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁

    +

    Spring是一个轻量级的控制反转(ioc)和面向切面(aop)的容器框架

    +
      +
    • 从大小与开销两方面而言Spring都是轻量级的
    • +
    • 通过控制反转(ioc)的技术达到松耦合的目的
    • +
    • 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚的开发
    • +
    • 包含并管理应用对象(bean)的配置和生命周期,这个意义上是一个容器
    • +
    • 将简单的组件配置、组合成复杂的应用,这个意义上是一个框架
    • +
    +

    32.谈谈你对AOP的理解

    系统是由许多不同的组件所组成的,每一个组件各负责一块特定的功能。除了实现自身的核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为他们会跨域系统的多个组件。

    +

    当我们需要将分散的对象引入公共行为的时候,OOP则显得无能为力,也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能

    +

    AOP:将程序中的交叉业务逻辑(比如安全、日志、事务),封装成一个切面。然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或者某些功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外做一些事情,在某个方法执行之后额外做一些事情。

    +

    33.谈谈你对IOC的理解

    容器概念、控制反转、依赖注入三方面理解

    +

    容器概念

    +

    ioc容器:实际上就是个map(key、value),里面存的就是各种对象(在xml里配置bean节点、@Repository、@Service、@Controller),在项目启动的时候会读取配置文件路面的bean节点。

    +

    控制反转

    +

    在没有引入ioc容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

    +

    引入ioc容器之后,对象A和对象B之间失去了直接联系。当对象A运行到需要对象B的时候,ioc容器会主动创建一个对象B注入到对象A需要的地方。

    +

    通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变成了被动行为,控制权颠倒过来了,这就是控制反转这个名称的由来。

    +

    依赖注入

    +

    “获得依赖对象的过程被反转了”。控制器反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

    +

    34.BeanFactory和ApplicationContext有什么区别?

    ApplicationContext是BeanFactory的子接口

    +

    ApplicationContext提供了更完整的功能

    +

    1、继承了MessageSource,因此支持国际化

    +

    2、统一资源文件的访问方式

    +

    3、提供在监听器中注册bean的事件

    +

    4、同时加载多个配置文件

    +

    5、载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层

    +

    35.深拷贝和浅拷贝

    深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实列对象的引用

    +
      +
    • 浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是一个对象
    • +
    • 深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的类执行指向的不是同一个对象
    • +
    +

    举例:A对象中有一个user属性,A1拷贝A对象,两个user指向同一个对象的话,就是浅拷贝,反之是深拷贝

    +

    36.TCP和UDP有什么区别?TCP为什么是三次握手,而不是两次?

    TCP (Transfer Control Protocol)是一种面向连接的、可靠的、传输层通信协议

    +
      +
    • 面向连接的,点对点的通信,高可靠,效率比较低、占用系统的资源比较多(类似于打电话)
    • +
    +

    UDP (User Datagram Protocal) 是一种无连接、不可靠的、传输层通信协议

    +
      +
    • 不需要连接、发送方不管接收方有没有准备好,直接发消息;可以进行广播发送的;传输不可靠,有可能会丢失消息;效率比较高;协议比较简单,占用的系统资源少(类似于广播)
    • +
    +

    TCP为什么是三次握手,而不是两次?

    +

    如果是两次握手,可能会造成连接资源浪费的问题

    +

    37.JVM中有哪些垃圾回收算法?

    MarkSweep标记清除算法

    +

    Copying拷贝算法

    +

    MarkCompack标记压缩算法

    +

    二.框架篇相关面试题

    1.Spring Bean的生命周期

    1.创建前准备阶段

    +

    这个阶段主要是在开始Bean加载之前,从Spring上下文和相关配置中解析并查找Bean有关的配置内容,比如init-method-容器在初始化bean时调用的方法、destory-method,容器在销毁Bean时调用的方法。以及,BeanFactoryPostProcessor这类的bean加载过程中的前置和后置处理。这些类或者配置其实是Spring提供给开发者,用来实现Bean加载过程中的扩展机制,在很多和Spring集成的中间件经常使用,比如Dubbo。

    +

    2.创建实例阶段

    +

    这个阶段主要是通过反射来创建Bean的实例对象,并且扫描和解析Bean声明的一些属性。

    +

    3.依赖注入阶段

    +

    在这个阶段,会检测被实例化的Bean是否存在其他依赖,如果存在其他依赖,就需要对这些被依赖Bean进行注入。比如通过@Autowired、@Setter等依赖注入的配置。在这个阶段还会触发一些扩展的调用,比如常见的扩展类:BeanPostProcessors(用来实现Bean初始化前后的回调)、InitializingBean类(这个类有一个afterPropertiesSet()方法,给属性赋值)、还有BeanFactoryAware等等。

    +

    4.容器缓存阶段

    +

    容器缓存阶段主要是把Bean保存到IoC容器中缓存起来,到了这个阶段,Bean就可以被开发者使用了。这个阶段涉及到的操作,常见的有init-method这个属性配置的方法,会在这个阶段调用。比如BeanPostProcessors方法中的后置处理器方法postProcessAfterInitialization,也是在这个阶段触发的。

    +

    5.销毁实例阶段

    +

    这个阶段,是完成Spring应用上下文关闭时,将销毁Spring上下文中所有的Bean。如果Bean实现了DisposableBean接口,或者配置了destory-method属 性,将会在这个阶段被调用。

    +

    2.Spring框架中的单例Bean是线程安全的吗?

    不是线程安全的!

    +

    Sping中Bean默认是单例模式的,框架中并没有对Bean进行多线程的封装处理。

    +

    线程安全这个问题,要从单例与原型Bean分别进行说明。

    +

    「原型Bean」对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。

    +

    「单例Bean」对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。

    +

    如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行「查询」以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。

    +

    3.Spring Bean作用域

    Spring 的 bean 作用域(scope)类型有5种:

    +

    1、singleton:单例,默认作用域。

    +

    2、prototype:原型,每次创建一个新对象。

    +

    3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。

    +

    4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。

    +

    5、global-session:全局会话,所有会话共享一个实例。

    +

    4.Spring框架中用到了哪些设计模式?

      +
    1. 简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。Spring中的BeanFactory就是简单工厂模式的体现,根据传入的一个唯一的标识来获得Bean对象,但是否在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
    2. +
    3. 工厂方法:实现了FactoryBean接口的bean是一类叫做factory的bean.其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getObject()方法的返回值。
    4. +
    5. 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。spring对单例的实现:spring中的单例模式完成了后半句话,即提供了全局访问点BeanFactory,但没有从构造器级别去控制单例,这时因为spring管理的是任意的java对象。
    6. +
    7. 适配器模式:spring中定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行响应的方法。这样扩展controller时,只需要增加一个适配类就完成了springMvc的扩展了。
    8. +
    9. 装饰器模式:动态地给一个对象增加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。spring中用到的装饰器模式在类名上有两种表现:一是类名中含有wrapper,另一种时类名中含有Decorator.
    10. +
    11. 动态代理:切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的。 织入:把切面应用到目标对象并创建新代理对象的过程。
    12. +
    13. 观察者模式:Spring的事件驱动模型使用的是观察者模式,spring中observer模式常用的地方是listener的实现。
    14. +
    15. 策略模式:spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,spring框架本身大量使用了Resource接口来访问底层资源。
    16. +
    +

    5.spring中事务实现方和原理式以及隔离级别?

    实现方式

    +

    在使用Spring框架时,可以有两种使用事务的方式,一种是编程式,一种是申明式的,@Transactional注解就是申明式的。

    +

    首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功失败。

    +

    原理

    +

    在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法存在@Transactional注解,那么代理逻辑会先把事务的自动提价设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。

    +

    隔离级别

    +

    spring的事务隔离级别就是数据库的隔离级别外加一个默认级别

    +
      +
    1. read uncommitted(未提交读)
    2. +
    3. read committed(提交读、不可重复读)
    4. +
    5. repeatable read (可重复读)
    6. +
    7. serializable(可串行化)
    8. +
    +

    注:数据库设置的隔离级别会被spring的配置覆盖

    +

    6.Spring事务传播机制

    多个事务方法相互调用的时,事务是如何在这些方法间传播

    +

    方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。

    +

    REQUIRED(Spring默认的事务传播类型)如果当前没有事务,则自己创建一个事务,如果当前存在事务,则加入这个事务

    +

    SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

    +

    MANDATORY:当前存在事务,则加入当前事务,如果当前的事务不存在,则抛出异常

    +

    REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务

    +

    NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务

    +

    NEVER:不使用事务,如果当前事务存在,则抛出异常

    +

    NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)

    +

    7.Spring事务什么时候会失效?

    Spring事务原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了,常见情况有如下几种

    +
      +
    1. 发生自调用,类里面使用this调用本类的方法,此时这个this对象不是代理类,而是UserService对象本身
    2. +
    3. 方法不是public的,非要在非public上使用事务,可以开启Aspectj代理模式
    4. +
    5. 数据库不支持事务,例如使用的MyISAM存储引擎
    6. +
    7. 没有被spring管理
    8. +
    9. 异常被吃掉,事务不会回滚(或者抛出的异常没有定义,默认为RuntimeException)
    10. +
    +

    8.什么是bean的自动装配,有哪些方式?

    autowire属性有五种装配的方式

    +
      +
    • no – 缺省情况下,自动配置是通过“ref”属性手动设定
    • +
    +
    1
    <!--手动装配:以value或ref的方式明确指定属性值都是手动装配。 需要通过‘ref’属性来连接bean-->
    + +
      +
    • byName-根据bean的属性名称进行自动装配
    • +
    +
    1
    2
    3
    <!--Cutomer的属性名称是person,Spring会将bean id为person的bean通过setter方法进行自动装配-->
    < bean id=“cutomer” class=“com.xxx.xxx.Cutomer” autowire=“byName”/>
    < bean id=“person” class=“com.xxx.xxx.Person”/>
    + +
      +
    • byType-根据bean的类型进行自动装配
    • +
    +
    1
    2
    3
    <!--Cutomer的属性person的类型为Person,Spirng会将Person类型通过setter方法进行自动装配-->
    < bean> id=“cutomer” class=“com.xxx.xxx.Cutomer” autowire=“byType”/>
    < bean> id=“person” class=“com.xxx.xxx.Person”/>
    + +
      +
    • constructor-类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型形
      同,则进行自动装配,否则导致异常
    • +
    +
    1
    2
    3
    <!--Cutomer构造函数的参数person的类型为Person,Spirng会将Person类型通过构造方法进行自动装配-->
    < bean> id=“cutomer” class=“com.xxx.xxx.Cutomer” autowire=“construtor”/>
    < bean> id=“person” class=“com.xxx.xxx.Person”/>
    + +
      +
    • autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式 进行自动装配
    • +
    +
    1
    <!--如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配-->
    + +

    9.Spring Boot、SpringMVC和Spring有什么区别?

    Spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题,更方便将不同方法中的共同处理抽取成切面,自动注入给方法执行,比如日志、异常等

    +

    SpringMvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接受请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端

    +

    SpringBoot是Spring提供的一个快速开发工具包,让程序员更方便、更快速的开发spring + springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制),redis、mongodb、es可以开箱即用

    +

    10.SpringMVC的工作流程

      +
    1. 用户发送请求到前端控制器DispatcherServlet
    2. +
    3. DispatcherServlet收到请求调用HandlerMapping处理器映射器
    4. +
    5. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器及处理器拦截器(如果有则生成)一并返回给DispatcherServlet
    6. +
    7. DispatcherServlet调用HandlerAdapter处理器适配器
    8. +
    9. HandlerAdapter调用具体的处理器(controller,也叫后端控制器)
    10. +
    11. Controller执行完成返回ModelAndView
    12. +
    13. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet
    14. +
    15. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
    16. +
    17. ViewReslover解析后返回具体view
    18. +
    19. DispatcherServlet根据view进行渲染视图(即将模型数据填充到视图中)
    20. +
    21. DispatcherServlet响应给用户
    22. +
    +

    image-20230911141826287

    +

    11.SpringMvc中的九大组件

    Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人

    +
      +
    1. HandlerMapping是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。

      +
    2. +
    3. HandlerAdapter,从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

      +
    4. +
    5. HandlerExceptionResolver其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。

      +
    6. +
    7. ViewResolverViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

      +
    8. +
    9. RequestToViewNameTranslatorViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。

      +
    10. +
    11. LocaleResolver解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。

      +
    12. +
    13. ThemeResolver用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。

      +
    14. +
    15. MultipartResolver用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。

      +
    16. +
    17. FlashMapManager用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

      +
    18. +
    +

    12.SpringBoot自动配置原理(简答的阐述见下面15条)

    自动装配,简单来说就是自动把第三方组件的Bean装载到Spring IOC器里面,不需要开发人员再去写Bean的装配配置。

    +

    在Spring Boot应用里面,只需要在启动类加上@SpringBootApplication注解就可以实现自动装配。@SpringBootApplication是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration。

    +

    image-20230911195638524

    +

    13.MyBatis的优缺点

    优点:

    +
      +
    1. 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
    2. +
    3. 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
    4. +
    5. 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
    6. +
    7. 能够与Spring很好的集成
    8. +
    9. 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
    10. +
    +

    缺点:

    +

    1.
    SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
    2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

    +

    14.MyBatis中#{}和${}的区别是什么?

      +
    1. 功能不同:${} 是直接替换,而 #{} 是预处理
    2. +
    3. 使用场景不同:普通参数使用 #{},如果传递的是 SQL 命令或 SQL 关键字,需要使用 ${},但在使用前一定要做好安全验证
    4. +
    5. 安全性不同:使用 ${} 存在安全问题(SQL注入),而 #{} 则不存在安全问题
    6. +
    +

    15.Spring Boot自动装配的过程

    image-20230921145126572

    +

    整个自动装配的过程是:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。

    +

    二.Mysql相关面试题

    1.存储引擎InnoDB 与MyISAM 的区别

      +
    1. 数据存储的方式不同,MyISAM中的数据和索引是分开存储的,而InnoDB是把索引和数据存储在同一个文件里面
    2. +
    3. 对于事务的支持不同,MyISAM不支持事务,而InnoDB支持ACID特性的事务处理
    4. +
    5. 对于锁的支持不同,MyISAM只支持表锁,而InnoDB可以根据不同的情况,支持行锁,表锁,间隙锁,临键锁
    6. +
    7. MyISAM不支持外键,InnoDB支持外键因此基于这些特性,我们在实际应用中,可以根据不同的场景来选择合适的存储引擎
    8. +
    9. 比如如果需要支持事务,那必须要选择InnoDB。如果大部分的表操作都是查询,可以选择MyISAM
    10. +
    +

    2.索引的基本原理

    原理:把无序的数据变成有序的查询

    +
      +
    1. 把创建了索引的列的内容进行排序
    2. +
    3. 对排序结果生成倒排表
    4. +
    5. 在倒排表内容上拼上数据地址链
    6. +
    7. 在查询的时候,先拿到倒排表的内容,再取出数据地址链,从而拿到数据
    8. +
    +

    3.索引失效的场景

      +
    1. 索引在使用的时候没有遵循最左匹配法则.
    2. +
    3. 模糊查询,如果%号在前面也会导致索引失效。
    4. +
    5. 在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。
    6. +
    7. 如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效
    8. +
    9. 查询的时候发生了类型转换,在查询的时候做了运算的操作和模糊查询也会导致索引失效
    10. +
    +

    4.事务的隔离级别有哪些?MySQL 的默认隔离级别是什么?

    读未提交(Read Uncommitted)可能读到其他事务未提交的数据,也叫做脏读
    读已提交(Read Committed)两次读取结果不一致,叫做不可重复读
    可重复读(Repeatable Read)是mysql默认的隔离级别,每次读取的结果都一样,当时可能产生幻读
    串行化(Serializable)一般是不会使用的,他给每一行读取的数据加锁,会导致大量超时和锁竞争的问题
    Mysql 默认的事务隔离级别是可重复读(Repeatable Read)

    +

    5.事务的基本特性

    事务的基本特性ACID分别是:

    +
      +
    1. A 原子性:一个事务的操作要么全部成功,要么全部失败
    2. +
    3. C 一致性:数据库从一个一致性的状态转换到另一个一致性的状态
    4. +
    5. I 隔离性:一个事务的修改在最终提交前,对其他事务是不可见的
    6. +
    7. D 持久性 一旦事务提交,所做的修改就会永远的保存到数据库中
    8. +
    +

    6.怎么处理慢查询

    SQL查询慢的原因

    +
      +
    1. 查询没有命中索引
    2. +
    3. 查询了不需要的数据列
    4. +
    5. 数据量太大
    6. +
    +

    根据上面的原因给出优化的措施

    +
      +
    1. 分析语句的执行计划,然后获得其使用索引的情况,然后修改语句或者索引,使得语句可以尽可能的命中索引
    2. +
    3. 分析语句,是否查询了不需要的数据列
    4. +
    5. 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表
    6. +
    +

    7.ACID靠什么保证的

    A 原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql

    +

    C 一致性由其他的三大特性保证 程序代码要保证业务上的一致性

    +

    I 隔离性由MVCC来保证

    +

    D 持久性由内存 + redo log 来保证。Mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redo log恢复

    +

    三.Redis相关面试题

    1.Redis过期键的删除策略

    redis是key-value数据库,我们可以设置redis中缓存的key的过期时间。redis的过期策略就是指当redis中缓存的key过期了,redis该如何处理。

    +
      +
    • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则删除。该策略可以最大化地节省CU资源,但是对内存非常的不友好。极端地情况可能出现大量地过期key没有再次被访问,从而不被清除,占用大量内存。
    • +
    • **定期过期:**每隔一定地时间,会扫描一定数量地数据库地expires字典中一定数量地key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过定时扫描的时间间隔和每次扫描的限定功耗,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
    • +
    +

    在Redis中同时使用了这两种策略

    +

    2.Redis的线程模型,单线程为什么快

    IO多路复用机制监听多个Socket

    +

    单线程快的原因:

    +
      +
    1. 纯内存操作
    2. +
    3. 核心是基于非阻塞的IO多路复用机制
    4. +
    5. 单线程反而避免了多线程的频繁上下文切换带来的性能问题
    6. +
    +

    3.缓存雪崩、缓存穿透、缓存击穿

    缓存雪崩 缓存在同一时间大面积失效,所以,后面的请求都会落在数据库上,造成数据库短时间内承受大量请求而崩掉。

    +

    解决方案:

    +
      +
    1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
    2. +
    3. 给每一个缓存数据增加响应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存
    4. +
    5. 缓存预热,启动系统之前先把热点数据放在缓存中去
    6. +
    7. 互斥锁
    8. +
    +

    缓存穿透 缓存和数据库中都没有数据,导致所有的请求都落在数据库上,造成数据库短时间内承受大量请求

    +

    解决方案:

    +
      +
    1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截掉
    2. +
    3. 从缓存取不到数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短一些,如30秒(设置太长会导致正常的情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
    4. +
    5. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
    6. +
    +

    缓存击穿 缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没有读到数据,又同时去数据库取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

    +

    解决方案:

    +
      +
    1. 设置热点数据永不过期
    2. +
    3. 加互斥锁
    4. +
    +

    4.Redis的数据结构

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(无序集合)及zset(有序集合)

    +

    image-20230914154911197

    +

    PDF

    + + +
    + +
    + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    目录
    1. 1. 一.Java基础面试题
      1. 1.1. 1.谈谈你对面向对象的理解
      2. 1.2. 2.JDK、JRE、JVM之间的区别
      3. 1.3. 3.==和equals⽅法的区别
      4. 1.4. 4.String,StringBuffer,StringBuilder的区别
      5. 1.5. 5.重载和重写的区别
      6. 1.6. 6.接口和抽象类的区别
      7. 1.7. 7.List和set的区别
      8. 1.8. 8.hashCode()与equals()之间的关系
      9. 1.9. 9.ArrayList和LinkedList的区别
      10. 1.10. 10.HashMap和HashTable的区别?底层实现是什么?
      11. 1.11. 11.ConcurrentHashMap
      12. 1.12. 12.如何实现一个IOC容器
      13. 1.13. 13.什么是字节码?采用字节码的好处是什么?
      14. 1.14. 14.Java类加载器有哪些
      15. 1.15. 15.双亲委派模型
      16. 1.16. 16.Java中的异常体系
      17. 1.17. 17.GC如何判断对象可以被回收
      18. 1.18. 18.线程的生命周期,线程有哪些状态
      19. 1.19. 19.sleep(),wait(),join(),yield()的区别
      20. 1.20. 20.说说你对线程安全的理解
      21. 1.21. 21.说说你对守护线程的理解
      22. 1.22. 22.ThreadLocal的原理和使用场景
      23. 1.23. 23.ThreadLocal内存泄漏原因,怎么避免
      24. 1.24. 24.产生内存泄漏的原因有哪些
      25. 1.25. 25.并发、并行、串行的区别
      26. 1.26. 26.并发的三大特性
      27. 1.27. 27.为什么用线程池?解释线程池参数?
      28. 1.28. 28.简述线程池的处理流程
      29. 1.29. 29.线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程?
      30. 1.30. 30.线程池中线程复用原理
      31. 1.31. 31.Spring是什么
      32. 1.32. 32.谈谈你对AOP的理解
      33. 1.33. 33.谈谈你对IOC的理解
      34. 1.34. 34.BeanFactory和ApplicationContext有什么区别?
      35. 1.35. 35.深拷贝和浅拷贝
      36. 1.36. 36.TCP和UDP有什么区别?TCP为什么是三次握手,而不是两次?
      37. 1.37. 37.JVM中有哪些垃圾回收算法?
    2. 2. 二.框架篇相关面试题
      1. 2.1. 1.Spring Bean的生命周期
      2. 2.2. 2.Spring框架中的单例Bean是线程安全的吗?
      3. 2.3. 3.Spring Bean作用域
      4. 2.4. 4.Spring框架中用到了哪些设计模式?
      5. 2.5. 5.spring中事务实现方和原理式以及隔离级别?
      6. 2.6. 6.Spring事务传播机制
      7. 2.7. 7.Spring事务什么时候会失效?
      8. 2.8. 8.什么是bean的自动装配,有哪些方式?
      9. 2.9. 9.Spring Boot、SpringMVC和Spring有什么区别?
      10. 2.10. 10.SpringMVC的工作流程
      11. 2.11. 11.SpringMvc中的九大组件
      12. 2.12. 12.SpringBoot自动配置原理(简答的阐述见下面15条)
      13. 2.13. 13.MyBatis的优缺点
      14. 2.14. 14.MyBatis中#{}和${}的区别是什么?
      15. 2.15. 15.Spring Boot自动装配的过程
    3. 3. 二.Mysql相关面试题
      1. 3.1. 1.存储引擎InnoDB 与MyISAM 的区别
      2. 3.2. 2.索引的基本原理
      3. 3.3. 3.索引失效的场景
      4. 3.4. 4.事务的隔离级别有哪些?MySQL 的默认隔离级别是什么?
      5. 3.5. 5.事务的基本特性
      6. 3.6. 6.怎么处理慢查询
      7. 3.7. 7.ACID靠什么保证的
    4. 4. 三.Redis相关面试题
      1. 4.1. 1.Redis过期键的删除策略
      2. 4.2. 2.Redis的线程模型,单线程为什么快
      3. 4.3. 3.缓存雪崩、缓存穿透、缓存击穿
      4. 4.4. 4.Redis的数据结构
    最近更新
    \ No newline at end of file diff --git a/posts/30127.html b/posts/30127.html new file mode 100644 index 000000000..e7da35ca1 --- /dev/null +++ b/posts/30127.html @@ -0,0 +1,244 @@ +使用Robot类编写自动化脚本 | The Blog + + + + + + + + + + + + +

    使用Robot类编写自动化脚本

    一.概述

    ​ Java中Robot类位于java.awt.Robot,该类用于为测试自动化,自运行演示程序和其他需要控制鼠标和键盘的应用程序生成本机系统输入事件,Robot类的主要目的是便于Java平台实现自动测试

    +

    二.基本API

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    方法名使用说明
    delay(n)延迟电脑操作n毫秒,类似于Thread.sleep()
    keyPress()模拟手动按下电脑键盘上的某个键
    keyRelease()模拟手动松开电脑键盘上的某个键(与keyPress()对应,按下一个键必须松开这个键)
    mouseMove(x,y)将鼠标移动到指定的x,y位置
    mousePress()按下鼠标上的某个键
    mouseRelease()松开鼠标上的某个键
    getPixelColor(x,y)获取指定坐标处的像素颜色
    mouseWheel(int wheelAmt)鼠标滚动(参数小于0,表示向上滚动;参数大于0,表示向下滚动)
    +

    三.基本使用

    1.模拟按键

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import java.awt.*;
    import java.awt.event.KeyEvent;
    import java.util.Random;

    public class Test {
    public static void main(String[] args) throws AWTException {
    Robot robot = new Robot();
    //延时函数,延时5秒
    robot.delay(5000);
    //模拟按一下K键
    robot.keyPress(KeyEvent.VK_K);
    //产生一个随机的时间(0.2 ~ 0.4秒之间)
    Random random = new Random();
    double randomValue = random.nextDouble() * 0.2 + 0.2;
    robot.delay((int)randomValue * 1000);
    //释放k键
    robot.keyRelease(KeyEvent.VK_K);
    }
    }
    + +

    2.模拟鼠标

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import java.awt.*;
    import java.awt.event.InputEvent;

    public class Test {
    public static void main(String[] args) throws AWTException {
    Robot robot = new Robot();
    //鼠标的移动
    robot.mouseMove(800, 200);
    //按下左键
    robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
    //弹起左键
    robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    //鼠标滚轮(大于1 向上滚动)
    robot.mouseWheel(1);
    }
    }
    + +

    3.获取颜色

    1
    2
    3
    4
    5
    6
    7
    8
    import java.awt.*;

    public class Test {
    public static void main(String[] args) throws AWTException {
    Robot robot = new Robot();
    Color color = robot.getPixelColor(520, 70);
    }
    }
    + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/31385.html b/posts/31385.html new file mode 100644 index 000000000..7ccd5d5b5 --- /dev/null +++ b/posts/31385.html @@ -0,0 +1,205 @@ +统一异常处理 | The Blog + + + + + + + + + + + + +

    统一异常处理

    我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要统一异常处理。

    +

    image-20230506153955544

    +

    1.全局异常处理

    异常返回的结果也为统一的返回结果的对象

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    package com.atguigu.common.exception;

    import com.atguigu.common.result.Result;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/6
    * @Description 全局的异常处理器
    */
    @ControllerAdvice
    public class GlobalExceptionHandler {

    /**
    * 全局的异常处理
    */
    @ExceptionHandler(Exception.class) //标记什么异常使用该方法
    @ResponseBody //返回的是json数据
    public Result error(Exception e){
    e.printStackTrace();
    return Result.fail().message("全局异常处理器捕获到该异常");
    }


    /**
    * 特定的异常处理(先找特定的异常 再找全局的异常)
    */
    @ExceptionHandler(ArithmeticException.class) //标记什么异常使用该方法
    @ResponseBody //返回的是json数据
    public Result error(ArithmeticException e){
    e.printStackTrace();
    return Result.fail().message("数学运算异常");
    }

    /**
    * 自定义异常处理
    * GuiguException为自定义的异常
    */
    @ExceptionHandler(GuiguException.class) //标记什么异常使用该方法
    @ResponseBody //返回的是json数据
    public Result error(GuiguException e){
    e.printStackTrace();
    //获取抛出异常的时候填入的code和message的值并返回
    return Result.fail().code(e.getCode()).message(e.getMessage());
    }
    }
    + +

    2.自定义异常

    创建异常类 继承RuntimeException

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    package com.atguigu.common.exception;

    import com.atguigu.common.result.ResultCodeEnum;
    import lombok.Data;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/6
    * @Description 自定义的异常
    */
    @Data
    public class GuiguException extends RuntimeException {
    /**
    * 异常的状态码
    */
    private Integer code;

    /**
    * 描述信息
    */
    private String message;

    /**
    * 通过状态码和错误消息创建异常对象
    */
    public GuiguException(Integer code, String message) {
    super(message);
    this.code = code;
    this.message = message;
    }

    /**
    * 接收枚举类型对象
    *
    * @param resultCodeEnum 枚举类型的结果信息
    */
    public GuiguException(ResultCodeEnum resultCodeEnum) {
    super(resultCodeEnum.getMessage());
    this.code = resultCodeEnum.getCode();
    this.message = resultCodeEnum.getMessage();
    }

    @Override
    public String toString() {
    return "GuliException{" +
    "code=" + code +
    ", message=" + this.getMessage() +
    '}';
    }


    }
    + +

    在需要抛出自定义异常的地方抛出异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 查询所有的方法
    */
    @ApiOperation("查询所有角色的接口")
    @GetMapping("/findAll")
    public Result<List<SysRole>> findAll() {
    List<SysRole> sysRoles = sysRoleService.list();
    try {
    //这里我们使用除零模拟异常
    int i = 1/0;
    } catch (Exception e) {
    throw new GuiguException(2001, "抛出了自定义的异常");
    }
    return Result.ok(sysRoles);
    }
    + +

    全局异常处理器捕获异常并返回异常信息

    image-20230506154605339

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/32246.html b/posts/32246.html new file mode 100644 index 000000000..c0f33f8de --- /dev/null +++ b/posts/32246.html @@ -0,0 +1,216 @@ +SpringBoot中整合Swagger2 | The Blog + + + + + + + + + + + + +

    SpringBoot中整合Swagger2

    1.Swagger2的介绍

    什么是swagger2

    +

    编写和维护接口文档是每个程序员的职责,根据Swagger2可以快速帮助我们编写最新的API接口文档,再也不用担心开会前仍忙于整理各种资料了,间接提升了团队开发的沟通效率。

    +

    常用注解

    +

    swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息等等

    +

    @Api:修饰整个类,描述Controller的作用

    +

    @ApiOperation:描述一个类的一个方法,或者说一个接口

    +

    @ApiParam:单个参数描述

    +

    @ApiModel:用对象来接收参数

    +

    @ApiModelProperty:用对象接收参数时,描述对象的一个字段

    +

    @ApiImplicitParam:一个请求参数

    +

    @ApiImplicitParams:多个请求参数

    +

    2.SpringBoot中使用Swagger

    2.1导入相关的依赖

    注意:Swagger2和SpringBoot存在版本兼容的问题,选择的时候要根据SpringBoot的版本进行选择

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
    </dependency>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
    </dependency>
    + +

    2.2创建配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Configuration
    @EnableSwagger2
    public class Swagger2 {
    @Bean
    public Docket adminApiConfig(){

    return new Docket(DocumentationType.SWAGGER_2)
    .groupName("adminApi")
    .apiInfo(apiInfo())
    .select()
    //只显示admin路径下的页面
    .paths(Predicates.and(PathSelectors.regex("/admin/.*")))// .paths(PathSelectors.any())表示所有
    .build();

    }

    private ApiInfo apiInfo(){
    return new ApiInfoBuilder()
    .title("后台管理系统-API文档")
    .description("本文档描述了后台管理系统微服务接口定义")
    .version("1.0")
    .contact(new Contact("GongChangjiang", "https://gong-changjiang.gitee.io/", "2602183349@qq.com"))
    .build();
    }

    }
    + +

    2.3配置包扫描的规则

    1
    2
    //在项目的启动类加上该注解,swagger就会扫描如下包下的controller方法,生成接口文档
    @ComponentScan(basePackages = "com.xxxx")
    + +

    2.4使用接口文档

    1
    2
    3
    //浏览器访问该网址
    //这里的端口号是要生成接口文档端口的端口号
    http://localhost:8080/swagger-ui.html
    + +

    image-20230412141010237

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/32679.html b/posts/32679.html new file mode 100644 index 000000000..54325c048 --- /dev/null +++ b/posts/32679.html @@ -0,0 +1,208 @@ +SSM常用配置 | The Blog + + + + + + + + + + + + +

    SSM常用配置

    1.pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atguigu.ssm</groupId>
    <artifactId>ssm</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <!--管理版本信息,将版本信息提取出来,各个依赖的版本通过${spring.version}访问-->
    <properties>
    <spring.version>5.3.1</spring.version>
    </properties>

    <dependencies>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <!--springmvc-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <!-- Mybatis核心 -->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
    </dependency>

    <!--mybatis和spring的整合包-->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
    </dependency>

    <!-- 连接池 -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.9</version>
    </dependency>

    <!-- junit测试 -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.3</version>
    </dependency>

    <!-- log4j日志 -->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>

    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
    </dependency>

    <!-- 日志 -->
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
    </dependency>

    <!-- ServletAPI -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency>

    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
    </dependency>

    <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
    </dependency>

    <!-- Spring5和Thymeleaf整合包 -->
    <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.12.RELEASE</version>
    </dependency>

    </dependencies>

    </project>
    + +

    2.spring.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--扫描组件(除控制层)-->
    <context:component-scan base-package="com.atguigu.ssm">
    <!--通过控制层注解的全类名,排除控制层的扫描-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--引入数据库的配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

    <!--配置SqlSessionFactoryBean,可以直接在IOC容器中获取SqlSessionFactory-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--获取mybatis的核心配置文件-->
    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    <!--设置数据源,取代配置文件中的数据源的环境配置-->
    <property name="dataSource" ref="dataSource"></property>
    <!--设置类型别名所对应的包-->
    <property name="typeAliasesPackage" value="com.atguigu.ssm.pojo"></property>
    <!--设置mapper映射文件的路径,在这里我们不需要设置,只有在mapper接口和映射文件的路径不一致时设置-->
    <!-- <property name="mapperLocations" value="classpath:"></property>-->
    </bean>

    <!--
    配置mapper接口扫描,可以将指定包下的所有mapper接口,
    通过SqlSession创建代理实现类对象,
    并将这些对象交给ioc容器管理
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.atguigu.ssm.mapper"></property>
    </bean>

    </beans>
    + +

    3.springmvc.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--组件扫描-->
    <context:component-scan base-package="com.atguigu.ssm.controller"></context:component-scan>

    <!--配置视图解析器-->
    <bean id="viewResolver"
    class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine">
    <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
    <property name="templateResolver">
    <bean
    class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    <!-- 视图前缀 -->
    <property name="prefix" value="/WEB-INF/templates/"/>
    <!-- 视图后缀 -->
    <property name="suffix" value=".html"/>
    <property name="templateMode" value="HTML5"/>
    <property name="characterEncoding" value="UTF-8" />
    </bean>
    </property>
    </bean>
    </property>
    </bean>

    <!-- 配置访问首页的视图控制 -->
    <mvc:view-controller path="/" view-name="index"></mvc:view-controller>

    <!-- 配置默认的servlet处理静态资源 -->
    <mvc:default-servlet-handler />

    <!-- 开启MVC的注解驱动 -->
    <mvc:annotation-driven />

    <!--拦截器-->
    <!--
    SpringMVC中的拦截器用于拦截控制器方法的执行
    SpringMVC中的拦截器需要实现HandlerInterceptor
    SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:
    <bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
    <ref bean="firstInterceptor"></ref>
    以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截
    <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/testRequestEntity"/>
    <ref bean="firstInterceptor"></ref>
    </mvc:interceptor>
    以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
    mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
    -->

    <!--异常处理器-->
    <!--
    <bean
    class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
    <props>
    properties的键表示处理器方法执行过程中出现的异常
    properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
    <prop key="java.lang.ArithmeticException">error</prop>
    </props>
    </property>
    exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
    <property name="exceptionAttribute" value="ex"></property>
    </bean>
    -->

    <!--文件上传解析器,是根据id获取目标对象的,id必须是multipartResolver-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
    </beans>
    + +

    4.mybatis-config.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>


    <!--解决数据库中的字段取名为emp_id,而在java中取名为empId,两者无法映射的问题-->
    <!--下划线映射为驼峰-->
    <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--设置分页插件-->
    <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

    </configuration>
    + +

    5.jdbc.properties

    1
    2
    3
    4
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/ssm
    jdbc.username=root
    jdbc.password=hsp
    + +

    6.log4j.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
    <param name="Encoding" value="UTF-8" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
    %m (%F:%L) \n" />
    </layout>
    </appender>
    <logger name="java.sql">
    <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
    <level value="info" />
    </logger>
    <root>
    <level value="debug" />
    <appender-ref ref="STDOUT" />
    </root>
    </log4j:configuration>
    + +

    7.log4j.properties

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    log4j.rootLogger=debug, stdout, R

    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

    # Pattern to output the caller's file name and line number.
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

    log4j.appender.R=org.apache.log4j.RollingFileAppender
    log4j.appender.R.File=example.log

    log4j.appender.R.MaxFileSize=100KB
    # Keep one backup file
    log4j.appender.R.MaxBackupIndex=5

    log4j.appender.R.layout=org.apache.log4j.PatternLayout
    log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/32696.html b/posts/32696.html new file mode 100644 index 000000000..f49abe60e --- /dev/null +++ b/posts/32696.html @@ -0,0 +1,209 @@ +Blog | The Blog + + + + + + + + + + + + +

    Blog

    + +
    +
    + + + + + +
    +
    +
    +
    \ No newline at end of file diff --git a/posts/35630.html b/posts/35630.html new file mode 100644 index 000000000..70711f301 --- /dev/null +++ b/posts/35630.html @@ -0,0 +1,222 @@ +接口测试工具 | The Blog + + + + + + + + + + + + +

    接口测试工具

    一 Postman的使用

    官网网址: https://www.postman.com/

    +

    Postman的首页

    +

    image-20230813162510205

    +

    1 使用教程

    1.1 发送Post请求

    以一个普通的登录操作来演示Postman的使用教程

    +

    发送Post请求的: 1.修改请求的方式 2.设置请求体的数据类型

    +

    image-20230813163451814

    +

    请求数据的格式

    +
    1
    2
    3
    4
    {
    "phone":"13511223456",
    "password":"admin"
    }
    + +

    响应数据的格式

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    {
    "host": null,
    "code": 200,
    "errorMessage": "操作成功",
    "data": {
    "user": {
    "id": 4,
    "salt": "",
    "name": "admin",
    "password": "",
    "phone": "13511223456",
    "image": null,
    "sex": true,
    "certification": null,
    "identityAuthentication": null,
    "status": true,
    "flag": 1,
    "createdTime": "2020-03-30T08:36:32.000+00:00"
    },
    "token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQrDMAzA_uJzc3DnBKe_cRKPpVAIOIWNsb_PPfQmIfSFfXbYgCqlEkmDVI6BHlJCrrWF9IzKiMKNERboMmHDlDFjTMgL2Fn8to9NPa5u5vrSfoibnM1NxnDW97jPvF5n90a_P2LDCJyAAAAA.Rk6wC_YpLmxrqylQxEgz19uKHkrBRV6_JmIqbHrSQENkURtavrzhLzr0D8mQf-wtKivaGGviDGHmdckw8IWi7g"
    }
    }
    + +

    二 Swagger的使用

    SpringBoot整合Swagger:https://jasonsgong.gitee.io/posts/32246.html

    +

    以一个普通的登录操作来演示Swagger的使用教程

    +

    image-20230813170950932

    +

    image-20230813171244848

    +

    三 knife4j的使用

    SpringBoot整合Knife4j教程: https://jasonsgong.gitee.io/posts/855.html

    +

    gitee地址:https://gitee.com/xiaoym/knife4j

    +

    官方文档:https://doc.xiaominfo.com/

    +

    核心功能 :文档说明 在线调试 个性化配置 离线文档 接口排序

    +

    image-20230813172211110

    +

    以一个普通的登录操作来演示knife4j的使用教程

    +

    image-20230813172355353

    +

    下载离线文档

    +

    image-20230813172605624

    +

    下载成功之后打开的效果

    +

    image-20230813172720116

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/36397.html b/posts/36397.html new file mode 100644 index 000000000..4ef92d8f7 --- /dev/null +++ b/posts/36397.html @@ -0,0 +1,344 @@ +对象存储服务MinIO | The Blog + + + + + + + + + + + + +

    对象存储服务MinIO

    一.MinIo基本介绍

    1.简介

    ​ MinIO基于Apache License v2.0开源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档。由于采用Golang实现,服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令可以运行起来。MinIO兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

    +

    S3 ( Simple Storage Service简单存储服务)

    +

    基本概念

    +
      +
    • bucket – 类比于文件系统的目录
    • +
    • Object – 类比文件系统的文件
    • +
    • Keys – 类比文件名
    • +
    +

    官网文档:http://docs.minio.org.cn/docs/

    +

    2.特点

      +
    • 数据保护

      +

      Minio使用Minio Erasure Code(纠删码)来防止硬件故障。即便损坏一半以上的driver,但是仍然可以从中恢复。

      +
    • +
    • 高性能

      +

      作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率

      +
    • +
    • 可扩容

      +

      不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心

      +
    • +
    • SDK支持

      +

      基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持

      +
    • +
    • 有操作页面

      +

      面向用户友好的简单操作界面,非常方便的管理Bucket及里面的文件资源

      +
    • +
    • 功能简单

      +

      这一设计原则让MinIO不容易出错、更快启动

      +
    • +
    • 丰富的API

      +

      支持文件资源的分享连接及分享链接的过期策略、存储桶操作、文件列表访问及文件上传下载的基本功能等。

      +
    • +
    • 文件变化主动通知

      +

      存储桶(Bucket)如果发生改变,比如上传对象和删除对象,可以使用存储桶事件通知机制进行监控,并通过以下方式发布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。

      +
    • +
    +

    3.对象存储方式比较

    + + + + + + + + + + + + + + + + + + + + + + +
    存储方式优点缺点
    服务器磁盘开发便捷,成本低扩展困难
    分布式文件系统 (如MinIo)容易实现扩容复杂度高
    第三方存储 (如阿里OSS)开发简单,功能强大,免维护收费
    +

    4.分布式文件系统比较

    + + + + + + + + + + + + + + + + + +
    存储方式优点缺点
    FastDFS1,主备服务,高可用 2,支持主从文件,支持自定义扩展名 3,支持动态扩容1,没有完备官方文档,近几年没有更新 2,环境搭建较为麻烦
    MinIO1,性能高,准硬件条件下它能达到55GB/s的读、35GB/s的写速率 2,部署自带管理界面 3,MinIO.Inc运营的开源项目,社区活跃度高 4,提供了所有主流开发语言的SDK1,不支持动态增加节点
    +

    二.MinIo安装教程

    使用Docker的方式安装MInIo,Docker使用教程 https://jasonsgong.gitee.io/posts/19306.html

    +
    1
    2
    3
    4
    5
    6
    #拉取minio的镜像
    docker pull minio/minio
    #创建并运行容器
    #设置用户名-e "MINIO_ACCESS_KEY=minio"
    #设置密码 -e "MINIO_SECRET_KEY=minio123"
    docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data
    + +

    访问: http://192.168.200.130:9000

    +

    image-20230818145600002

    +

    Access Key为minio Secret_key 为minio123 进入系统后可以看到主界面

    +

    image-20230818145721071

    +

    三.快速入门

    基本概念

    +
      +
    • bucket – 类比于文件系统的目录
    • +
    • Object – 类比文件系统的文件
    • +
    • Keys – 类比文件名
    • +
    +

    1.创建一个bucket

    点击右下角的“+”号 ,创建一个桶

    +

    image-20230818150154249

    +

    创建成功后

    +

    image-20230818150902795

    +

    2.环境搭建

    2.1 创建一个Demo工程

    image-20230818151929679

    +

    2.2 导入依赖和创建启动类

    pom.xml

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <dependencies>

    <dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.1.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    </dependencies>
    + +

    启动类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.heima.minio;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class MinIOApplication {

    public static void main(String[] args) {
    SpringApplication.run(MinIOApplication.class,args);
    }
    }
    + +

    3.测试案例

    3.1 上传文件进行静态访问

    目标:把test.html文件上传到minio中,并且可以在浏览器中访问

    +

    test.html测试文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>
    <b>普通文本 String 展示:</b><br><br>
    Hello freemarker <br>
    <hr>
    <b>对象Student中的数据展示:</b><br/>
    姓名:小明<br/>
    年龄:18
    <hr>
    </body>
    </html>
    + +

    文件上传代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    package com.heima.minio;


    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/18
    * @Description
    */
    @SpringBootTest(classes = MinIOApplication.class)
    @RunWith(SpringRunner.class)
    @Slf4j
    public class MinIOTest {

    /**
    * 把list.html文件上传到minio中,并且可以在浏览器中访问
    */
    @Test
    public void testMinIO(){
    try {
    //读取一个文件
    FileInputStream inputStream = new FileInputStream("C:\\Gong\\data\\test.html");
    //1.获取MinIO的连接信息,创建一个minio的客户端
    MinioClient minioClient = MinioClient.builder()
    .credentials("minio", "minio123")//minio的账号密码
    .endpoint("http://192.168.200.130:9000")//minio的地址
    .build();
    //2.上传
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object("test.html")//文件的名称
    .contentType("text/html")//文件的类型
    .bucket("leadnews")//桶的名称,与之前的minio管理界面创建的bucket名称一致即可
    .stream(inputStream,inputStream.available(),-1)
    .build();
    //3.访问的路径 minio的访问路径 + 桶的名称 + 文件的名称
    log.info("http://192.168.200.130:9000/leadnews/test.html");
    minioClient.putObject(putObjectArgs);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }
    + +

    执行成功之后可以在MinIO中找到该文件

    +

    image-20230818154409682

    +

    设置浏览器输入文件在minio中的地址可以直接访问文件的内容

    +

    设置bucket的访问权限

    +

    image-20230818154858988

    +

    设置完毕后访问文件的地址可以直接访问到

    +

    image-20230818155021308

    +

    4.封装MinIO为Starter-以黑马头条项目为例

    黑马头条项目gitee地址: https://gitee.com/JasonsGong/heima-leadnews

    +

    在开发的过程中,有很多模块需要使用到文件服务,我们直接把文件服务封装成一个Starter,方便其余的模块调用

    +

    image-20230818155501495

    +

    4.1 创建模块heima-file-starter

    导入依赖

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.1.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    </dependencies>
    + +

    4.2 配置类

    MinIOConfigProperties

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.heima.file.config;


    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    import java.io.Serializable;

    @Data
    @ConfigurationProperties(prefix = "minio")
    public class MinIOConfigProperties implements Serializable {

    private String accessKey;
    private String secretKey;
    private String bucket;
    private String endpoint;
    private String readPath;
    }
    + +

    MinIOConfig

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package com.heima.file.config;

    import com.heima.file.service.FileStorageService;
    import io.minio.MinioClient;
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;


    @Data
    @Configuration
    @EnableConfigurationProperties({MinIOConfigProperties.class})
    //当引入FileStorageService接口时
    @ConditionalOnClass(FileStorageService.class)
    public class MinIOConfig {

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    @Bean
    public MinioClient buildMinioClient(){
    return MinioClient
    .builder()
    .credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
    .endpoint(minIOConfigProperties.getEndpoint())
    .build();
    }
    }
    + +

    4.3 封装操作minIO类

    FileStorageService

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.heima.file.service;

    import java.io.InputStream;

    /**
    * @author itheima
    */
    public interface FileStorageService {


    /**
    * 上传图片文件
    * @param prefix 文件前缀
    * @param filename 文件名
    * @param inputStream 文件流
    * @return 文件全路径
    */
    public String uploadImgFile(String prefix, String filename,InputStream inputStream);

    /**
    * 上传html文件
    * @param prefix 文件前缀
    * @param filename 文件名
    * @param inputStream 文件流
    * @return 文件全路径
    */
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);

    /**
    * 删除文件
    * @param pathUrl 文件全路径
    */
    public void delete(String pathUrl);

    /**
    * 下载文件
    * @param pathUrl 文件全路径
    * @return
    *
    */
    public byte[] downLoadFile(String pathUrl);

    }
    + +

    MinIOFileStorageService

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    package com.heima.file.service.impl;


    import com.heima.file.config.MinIOConfig;
    import com.heima.file.config.MinIOConfigProperties;
    import com.heima.file.service.FileStorageService;
    import io.minio.GetObjectArgs;
    import io.minio.MinioClient;
    import io.minio.PutObjectArgs;
    import io.minio.RemoveObjectArgs;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Import;
    import org.springframework.util.StringUtils;

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.text.SimpleDateFormat;
    import java.util.Date;

    @Slf4j
    @EnableConfigurationProperties(MinIOConfigProperties.class)
    @Import(MinIOConfig.class)
    public class MinIOFileStorageService implements FileStorageService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    private final static String separator = "/";

    /**
    * @param dirPath
    * @param filename yyyy/mm/dd/file.jpg
    * @return
    */
    public String builderFilePath(String dirPath,String filename) {
    StringBuilder stringBuilder = new StringBuilder(50);
    if(!StringUtils.isEmpty(dirPath)){
    stringBuilder.append(dirPath).append(separator);
    }
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
    String todayStr = sdf.format(new Date());
    stringBuilder.append(todayStr).append(separator);
    stringBuilder.append(filename);
    return stringBuilder.toString();
    }

    /**
    * 上传图片文件
    * @param prefix 文件前缀
    * @param filename 文件名
    * @param inputStream 文件流
    * @return 文件全路径
    */
    @Override
    public String uploadImgFile(String prefix, String filename,InputStream inputStream) {
    String filePath = builderFilePath(prefix, filename);
    try {
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object(filePath)
    .contentType("image/jpg")
    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
    .build();
    minioClient.putObject(putObjectArgs);
    StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
    urlPath.append(separator+minIOConfigProperties.getBucket());
    urlPath.append(separator);
    urlPath.append(filePath);
    return urlPath.toString();
    }catch (Exception ex){
    log.error("minio put file error.",ex);
    throw new RuntimeException("上传文件失败");
    }
    }

    /**
    * 上传html文件
    * @param prefix 文件前缀
    * @param filename 文件名
    * @param inputStream 文件流
    * @return 文件全路径
    */
    @Override
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {
    String filePath = builderFilePath(prefix, filename);
    try {
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object(filePath)
    .contentType("text/html")
    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
    .build();
    minioClient.putObject(putObjectArgs);
    StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
    urlPath.append(separator+minIOConfigProperties.getBucket());
    urlPath.append(separator);
    urlPath.append(filePath);
    return urlPath.toString();
    }catch (Exception ex){
    log.error("minio put file error.",ex);
    ex.printStackTrace();
    throw new RuntimeException("上传文件失败");
    }
    }

    /**
    * 删除文件
    * @param pathUrl 文件全路径
    */
    @Override
    public void delete(String pathUrl) {
    String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
    int index = key.indexOf(separator);
    String bucket = key.substring(0,index);
    String filePath = key.substring(index+1);
    // 删除Objects
    RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
    try {
    minioClient.removeObject(removeObjectArgs);
    } catch (Exception e) {
    log.error("minio remove file error. pathUrl:{}",pathUrl);
    e.printStackTrace();
    }
    }


    /**
    * 下载文件
    * @param pathUrl 文件全路径
    * @return 文件流
    *
    */
    @Override
    public byte[] downLoadFile(String pathUrl) {
    String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
    int index = key.indexOf(separator);
    String bucket = key.substring(0,index);
    String filePath = key.substring(index+1);
    InputStream inputStream = null;
    try {
    inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
    } catch (Exception e) {
    log.error("minio down file error. pathUrl:{}",pathUrl);
    e.printStackTrace();
    }

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    byte[] buff = new byte[100];
    int rc = 0;
    while (true) {
    try {
    if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
    } catch (IOException e) {
    e.printStackTrace();
    }
    byteArrayOutputStream.write(buff, 0, rc);
    }
    return byteArrayOutputStream.toByteArray();
    }
    }
    + +

    4.4 对外加入自动配置

    在resources中新建META-INF/spring.factories

    +
    1
    2
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.heima.file.service.impl.MinIOFileStorageService
    + +

    4.5 其他微服务使用

    第一,导入heima-file-starter的依赖

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-file-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    + +

    第二,在微服务中添加minio所需要的配置

    +
    1
    2
    3
    4
    5
    6
    minio:
    accessKey: minio
    secretKey: minio123
    bucket: leadnews
    endpoint: http://192.168.200.130:9000
    readPath: http://192.168.200.130:9000
    + +

    第三,在对应使用的业务类中注入FileStorageService,样例如下:

    +
    1
    2
    @Autowired
    private FileStorageService fileStorageService;
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.heima.minio;


    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/18
    * @Description
    */
    @SpringBootTest(classes = MinIOApplication.class)
    @RunWith(SpringRunner.class)
    @Slf4j
    public class MinIOTest {

    @Autowired
    private FileStorageService fileStorageService;

    @Test
    public void testMinIOStarter(){
    //读取一个文件
    try {
    FileInputStream inputStream = new FileInputStream("C:\\Gong\\data\\test.html");
    String s = fileStorageService.uploadHtmlFile("", "test,html", inputStream);
    log.info(s);
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    }
    }
    }
    + +

    image-20230818165138917

    +
    \ No newline at end of file diff --git a/posts/3661.html b/posts/3661.html new file mode 100644 index 000000000..9de8d851b --- /dev/null +++ b/posts/3661.html @@ -0,0 +1,214 @@ +技术书籍-Linux指令大全 | The Blog + + + + + + + + + + + + +

    技术书籍-Linux指令大全

    Linux命令行与shell脚本编程大全 第三版 ,布鲁姆 ,P606

    PDF

    + +
    + +
    + + + + + + +

    精通Linux 第二版

    PDF

    + +
    + +
    + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/38823.html b/posts/38823.html new file mode 100644 index 000000000..7caa41991 --- /dev/null +++ b/posts/38823.html @@ -0,0 +1,223 @@ +SpringBoot整合EasyExcel | The Blog + + + + + + + + + + + + +

    SpringBoot整合EasyExcel

    1.EasyExcel介绍

    ​ Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

    +

    官网地址:https://easyexcel.opensource.alibaba.com/

    +

    2.入门

    2.1 引入依赖

    1
    2
    3
    4
    5
    6
    <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.1</version>
    </dependency>
    + +

    2.2 创建与Excel表格对应的实体类,并在属性上加上注解

    不用自己设置excel表中的表头信息,easyexcel会帮我们设置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package test;

    import com.alibaba.excel.annotation.ExcelProperty;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description 与Excel表格对应的实体类
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UserData {

    /**
    * 注解中value 设置的是表头的信息
    * index 设置的是这个属性在excel表格中的索引位置 列索引
    */

    @ExcelProperty(value = "用户编号", index = 0)
    private int uid;

    @ExcelProperty(value = "用户名称", index = 1)
    private String username;


    }
    + +

    2.3 读写操作

    1.写操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package test;

    import com.alibaba.excel.EasyExcel;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description 测试对excel的操作
    */
    public class TestEasyExcel {
    public static void main(String[] args) {
    //设置excel文件的路径和名称
    String filename = "C:\\AlYun\\user.xlsx";

    //构建一个数据的list集合,存放要保存在excel表中的数据
    List<UserData> userDataList = new ArrayList<>();
    userDataList.add(new UserData(1,"张三"));
    userDataList.add(new UserData(2,"李四"));
    userDataList.add(new UserData(3,"王五"));
    userDataList.add(new UserData(4,"赵六"));

    //调用方法实现写的操作
    EasyExcel.write(filename,UserData.class)
    .sheet("用户信息")
    .doWrite(userDataList);

    }
    }

    + +

    执行之后,空的excel表中会添加如下的数据

    +

    image-20230414131020923

    +

    2.读操作

    1 .创建一个读操作的监听器

    继承AnalysisEventListener T表示实体类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package test;

    import com.alibaba.excel.context.AnalysisContext;
    import com.alibaba.excel.event.AnalysisEventListener;

    import java.util.Map;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description 读操作的监听器
    */
    public class ExcelLister extends AnalysisEventListener<UserData> {

    /**
    * 一行一行的读取 从第二行开始读取
    */
    @Override
    public void invoke(UserData data, AnalysisContext context) {
    //打印输出读取到的数据
    System.out.println(data);
    }

    /**
    * 读取之后执行
    */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

    }


    /**
    * 非必须实现的方法
    * 此方法可以读取表头的信息
    */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
    //打印读取到的表头信息
    System.out.println("表头信息:"+headMap);
    }
    }
    + +
    5.编写方法测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package test;

    import com.alibaba.excel.EasyExcel;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description
    */
    public class TestReadExcel {
    public static void main(String[] args) {
    //设置excel文件的路径和名称
    String filename = "C:\\AlYun\\user.xlsx";
    //调用方法实现读取操作
    //new ExcelLister()自己创建的监听器
    EasyExcel.read(filename,UserData.class,new ExcelLister())
    .sheet()
    .doRead();
    }
    }
    + +

    3.监听器中实现对数据库的操作

    解决listener中无法操作数据库的问题

    +

    3.1 通过构造器注入的方式

    在进行读操作创建Listener的时候,注入mapper

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package com.atguigu.yygh.cmn.listener;

    import com.alibaba.excel.context.AnalysisContext;
    import com.alibaba.excel.event.AnalysisEventListener;
    import com.atguigu.yygh.cmn.mapper.DictMapper;
    import com.atguigu.yygh.model.cmn.Dict;
    import com.atguigu.yygh.vo.cmn.DictEeVo;
    import org.springframework.beans.BeanUtils;


    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description
    */
    public class DictListener extends AnalysisEventListener<DictEeVo> {

    private DictMapper dictMapper;

    public DictListener(DictMapper dictMapper) {
    this.dictMapper = dictMapper;
    }

    @Override
    public void invoke(DictEeVo data, AnalysisContext context) {
    //调用mapper 添加数据
    Dict dict = new Dict();
    BeanUtils.copyProperties(data,dict);
    dictMapper.insert(dict);

    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

    }
    }

    + +

    3.2 使用注解加属性注入的方式

    1
    2
    3
    @Component
    +
    @Autowired
    + + + + + + + +
    \ No newline at end of file diff --git a/posts/39654.html b/posts/39654.html new file mode 100644 index 000000000..d35ed0dd8 --- /dev/null +++ b/posts/39654.html @@ -0,0 +1,307 @@ +MySql进阶教程 | The Blog + + + + + + + + + + + + +

    MySql进阶教程

    全部的PDF笔记: https://jasonsgong.gitee.io/posts/50465.html

    +

    原视频地址: 黑马程序员 MySQL数据库入门到精通,从mysql安装到mysql高级、mysql优化全囊括

    +

    一.基础回顾

    1.DDL、DML、DQL、DCL

    数据定义语言(DDL)

    +

    DDL全称是Data Definition Language,即数据定义语言,定义语言就是定义关系模式、删除关系、修改关系模式以及创建数据库中的各种对象,比如表、聚簇、索引、视图、函数、存储过程和触发器等等。数据定义语言是由SQL语言集中负责数据结构定义与数据库对象定义的语言,并且由CREATE、ALTER、DROP和TRUNCATE四个语法组成。

    +

    数据操纵语言(DML)

    +

    数据操纵语言全程是Data Manipulation Language,主要是进行插入元组、删除元组、修改元组的操作。主要有insert、update、delete语法组成。

    +

    数据查询语言(DQL)

    +

    数据查询语言全称是Data Query Language,所以是用来进行数据库中数据的查询的,即最常用的select语句。

    +

    数据控制语言(DCL)

    +

    数据控制语言:Data Control Language。用来授权或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果,能够对数据库进行监视。比如常见的授权、取消授权、回滚、提交等等操作。

    +

    二.MySql进阶

    1.存储引擎

    1.1 MySql的体系结构

    image-20230916152706301

    +

    image-20230916152828704

    +

    1.2 存储引擎简介

    简介

    +

    ​ 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是 基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果 没有指定将自动选择默认的存储引擎。

    +

    查询一张数据库表的建表语句

    +
    1
    2
    -- 查询建表语句
    show create table book_info;
    + +

    image-20230916153448893

    +

    建表的时候指定存储引擎的语句

    +

    image-20230916153931010

    +

    1.3 存储引擎特点

    image-20230916154915533

    +

    image-20230916155113863

    +

    image-20230916155356755

    +

    image-20230916155525548

    +

    存储引擎的区别

    +

    image-20230916155637133

    +

    1.4 存储引擎的选择

    ​ 在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据 实际情况选择多种存储引擎进行组合。

    +
      +
    • InnoDB: 是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要 求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操 作,那么InnoDB存储引擎是比较合适的选择。
    • +
    • MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完 整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。(例如评论的数据、日志相关的数据、电商中足迹相关的数据)
    • +
    • MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是 对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
    • +
    +

    一句话:项目中绝大多数的时候使用的都是InnoDB

    +

    2.索引

    2.1 索引概述

    2.1.1 介绍

    ​ 索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

    +

    2.1.2 演示

    注:下面的二叉树并不是一个真实的索引结构,而是一个举例

    +

    image-20230916150211012

    +

    2.1.3 优缺点

    image-20230916150430708

    +

    2.2 索引结构

    2.2.1 索引的数据结构有哪些

    默认的索引结构是B+树索引结构

    +

    image-20230916161524219

    +

    不同类型的存储引擎对不同的索引结构的支持情况

    +

    image-20230916161623546

    +

    2.2.2 B+树的索引数据结构

    二叉树和红黑树的不足

    +

    image-20230916162305576

    +

    B树

    +

    image-20230916163234103

    +

    B+树

    +

    image-20230916164042303

    +

    image-20230916164314700

    +

    MySql中的B+树结构

    +

    image-20230916164500444

    +

    2.2.3 hash的索引数据结构

    image-20230916164903702

    +

    image-20230916165007465

    +

    2.3.4 思考题

    image-20230916165329434

    2.3 索引分类

    image-20230916165516779

    +

    image-20230916165647255

    +

    image-20230916170012664

    +

    当我们要查询name值是Arm的数据的时候,会先通过二级索引查询,然后通过聚集索引查询这一行的信息(回表查询)

    +

    image-20230916170421435

    +

    思考题

    +

    image-20230916170537693

    +

    第一条的执行效率高,第二条要回表查询(先查询name所在行对应的主键id,然后通过主键id查询这一行的数据)

    +

    2.4 索引语法

    创建|查看|删除语法

    +

    image-20230917091746406

    +

    举例

    +

    image-20230917093020287

    +

    2.5 SQL性能分析

    2.5.1 SQL执行频率

    image-20230917093634100

    +
    1
    2
    -- 查看系统的状态信息
    show global status like 'Com_______';
    + +

    2.5.2 慢查询日志

    image-20230917094314803

    +
    1
    2
    -- 查询慢查询日志是否开启
    show variables like 'slow_query_log';
    + +

    2.5.3 profile详情

    image-20230917095232011

    +
    1
    2
    3
    4
    5
    6
    7
    8
    -- 是否支持profile详情功能
    select @@have_profiling;

    -- 查看是否开启该功能
    select @@profiling;

    -- 开启profile
    set profiling = 1;
    + +

    image-20230917095320953

    +

    2.5.4 explain执行计划

    image-20230917095911304

    +

    image-20230917101205097

    +

    image-20230917101337147

    +

    2.6 索引使用

    2.6.1 验证索引效率

    image-20230917141334774

    +

    现在有一个有着10000000数据的表(tb_sku),我们根据sn这一字段查询某一条具体的数据,没有建立索引之前的查询耗费的时间是20多秒,建立索引耗费大约90秒,为sn字段建立索引之后,查询耗费时间大概是零点几秒。

    +

    2.6.2 最左前缀法则

    image-20230917142410276

    +

    image-20230917142707646

    +

    2.6.3 索引失效的情况

    image-20230917142948227

    +

    image-20230917143143047

    +

    image-20230917143421975

    +

    image-20230917143842497

    +

    image-20230917144459567

    +

    2.6.4 SQL提示

    image-20230917145046280

    +

    2.6.5 覆盖索引

    image-20230917145643194

    +

    2.6.6 前缀索引

    image-20230917153757773

    +

    image-20230917154300147

    +

    2.6.7 单例索引与联合索引的选择问题

    image-20230917155017641

    +

    image-20230917155223679

    +

    2.7 索引设计原则

    image-20230917155748055

    +

    3.SQL优化

    3.1 插入数据的优化

    image-20230917181618942

    +

    image-20230917181842030

    +

    100万的数据如果使用insert插入的话需要10分钟左右,如果使用文件的方式导入,只需要10多秒。

    +

    3.2 主键优化

    image-20230917205500523

    +

    image-20230917205636137

    +

    image-20230917205833596

    +

    image-20230917210112389

    +

    image-20230917210303469

    +

    3.3 order by优化

    image-20230917210730392

    +

    image-20230917211254167

    +

    image-20230917210616858

    +

    image-20230917211543109

    +

    3.4 group by优化

    image-20230917212026101

    +

    3.5 limit优化

    image-20230917212530539

    +

    3.6 count优化

    image-20230917212709489

    +

    image-20230917213029734

    +

    image-20230917213316193

    +

    3.7 update优化

    image-20230917213859581

    +

    3.8 总结

    image-20230917214424722

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/40445.html b/posts/40445.html new file mode 100644 index 000000000..3aefc6546 --- /dev/null +++ b/posts/40445.html @@ -0,0 +1,508 @@ +数据结构与算法 | The Blog + + + + + + + + + + + + +

    数据结构与算法

    代码仓库的地址:https://gitee.com/JasonsGong/DataStructures

    +

    一.经典算法问题

    字符串匹配 KMP算法

    +

    汉诺塔问题 分治算法

    +

    八皇后问题 回溯算法

    +

    马踏棋盘问题 图的深度优化遍历算法(DFS)和 贪心算法优化

    +

    二.数据结构与算法的概述

    2.1 数据结构与算法的关系

    (1)数据(data)结构(structure)是一门研究组织数据方式的学科,有了编程语言也就有了数据结构,学好了数据结构可以编写出更加漂亮,更加有效率的代码。

    +

    (2)程序=数据结构+算法

    +

    (3)数据结构是算法的基础

    +

    2.2解决实际的问题

    五子棋程序 稀疏数组(压缩存档) 二维数组->转化成稀疏数组->存档 读档反之

    +

    约瑟夫问题(丢手帕问题) 单向环形列表

    +

    修路问题 求最小生成树 + 普利姆算法

    +

    最短路径问题 图+弗洛伊德算法

    +

    2.3 数据结构

    线性结构:

    +

    ​ 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系

    +

    ​ 线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的

    +

    ​ 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息

    +

    ​ 线性结构常见的有:数组、队列、链表和栈

    +

    非线性结构:

    +

    ​ 二维数组,多维数组,广义表,树结构,图结构

    +

    三.稀疏数组和队列

    3.1稀疏数组

    3.1.1 基本的介绍

    ​ 当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。

    +

    稀疏数组的处理方法是:

    +

    ​ 1)记录数组一共有几行几列,有多少个不同的值

    +

    ​ 2)把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模

    +

    3.1.2使用的场景

    ​ 五子棋程序的存档和退出功能的实现

    +

    3.1.3图解

    第一行记录的是原始的数组有几行几列有几个非零的值 例如下面的数组是一个6行7列有8个非零值的数组

    +

    image-20230313124552408

    +

    3.1.5实现的思路

    image-20230318100138197

    +

    3.1.5代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    package com.atguigu.sparsearray;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/3/18
    * @Description 稀疏数组的代码实现 稀疏数组和二维数组之间的互转
    */
    public class SparseArray {

    public static void main(String[] args) {
    //创建一个原始的二维数组
    //0:表示没有棋子 1:表示黑子 2:表示白子
    int[][] chessArr1 = new int[11][11];
    chessArr1[1][2] = 1;
    chessArr1[2][3] = 2;
    chessArr1[4][5] = 1;
    //遍历输出
    System.out.println("原始的二维数组");
    for (int[] row : chessArr1) {
    for (int num : row) {
    System.out.print(num + " ");
    }
    System.out.println();
    }


    //将二维数组转化成稀疏数组
    //获取原始二维数组中非零元素的个数
    int sum = 0;
    for (int[] row : chessArr1) {
    for (int num : row) {
    if (num != 0) {
    sum++;
    }
    }
    }
    System.out.println("非零元素的个数:" + sum);
    //根据非零的元素的个数创建对应的稀疏数组
    int[][] sparseArr2 = new int[sum + 1][3];
    //给稀疏数组赋值
    //第一行的赋值操作
    sparseArr2[0][0] = 11;
    sparseArr2[0][1] = 11;
    sparseArr2[0][2] = sum;
    //其余行的赋值操作
    int row = 1; //充当计数器的作用
    for (int i = 0; i < chessArr1.length; i++) {
    for (int j = 0; j < chessArr1[i].length; j++) {
    if (chessArr1[i][j] != 0) {
    sparseArr2[row][0] = i;
    sparseArr2[row][1] = j;
    sparseArr2[row][2] = chessArr1[i][j];
    row++;
    }
    }
    }


    //遍历稀疏数组
    System.out.println("得到的稀疏数组如下");
    for (int[] ints : sparseArr2) {
    for (int anInt : ints) {
    System.out.print(anInt + " ");
    }
    System.out.println();
    }


    //将稀疏数组转化成二维数组
    //获取原先的二维数组的大小
    //读取第一行 获取原始二维数组的行列值
    int[][] sparseArr3 = new int[sparseArr2[0][0]][sparseArr2[0][1]];
    //遍历稀疏数组第二行之后的值 赋值给原始的数组 从第二行开始算
    for (int i = 1; i < sparseArr2.length; i++) {
    sparseArr3[sparseArr2[i][0]][sparseArr2[i][1]] = sparseArr2[i][2];
    }

    //遍历还原之后的二维数组
    System.out.println("稀疏数组还原成二维数组");
    for (int[] ints : sparseArr3) {
    for (int anInt : ints) {
    System.out.print(anInt+" ");
    }
    System.out.println();
    }


    }
    }

    + + + +

    3.2队列

    3.2.1基本的介绍

    先进先出

    +

    队列是一个有序列表,可以通过数组或者链表实现。

    +

    遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入队列的数据要后取出。

    +

    3.2.2代码实现

    使用数组模拟队列

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    package com.atguigu.queue;

    import java.util.Scanner;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/3/18
    * @Description 使用数组模拟队列
    */
    public class ArrayQueueDemo {
    public static void main(String[] args) {
    //测试一把
    //创建一个队列
    ArrayQueue queue = new ArrayQueue(3);
    char key = ' '; //接收用户输入
    Scanner scanner = new Scanner(System.in);//
    boolean loop = true;
    //输出一个菜单
    while (loop) {
    System.out.println("s(show): 显示队列");
    System.out.println("e(exit): 退出程序");
    System.out.println("a(add): 添加数据到队列");
    System.out.println("g(get): 从队列取出数据");
    System.out.println("h(head): 查看队列头的数据");
    key = scanner.next().charAt(0);//接收一个字符
    switch (key) {
    case 's':
    queue.showQueue();
    break;
    case 'a':
    System.out.println("输出一个数");
    int value = scanner.nextInt();
    queue.addQueue(value);
    break;
    case 'g': //取出数据
    try {
    int res = queue.getQueue();
    System.out.printf("取出的数据是%d\n", res);
    } catch (Exception e) {
    // TODO: handle exception
    System.out.println(e.getMessage());
    }
    break;
    case 'h': //查看队列头的数据
    try {
    int res = queue.headQueue();
    System.out.printf("队列头的数据是%d\n", res);
    } catch (Exception e) {
    // TODO: handle exception
    System.out.println(e.getMessage());
    }
    break;
    case 'e': //退出
    scanner.close();
    loop = false;
    break;
    default:
    break;
    }
    }

    System.out.println("程序退出~~");
    }

    }

    // 使用数组模拟队列-编写一个ArrayQueue类
    class ArrayQueue {
    private int maxSize; // 表示数组的最大容量
    private int front; // 队列头
    private int rear; // 队列尾
    private int[] arr; // 该数据用于存放数据, 模拟队列

    // 创建队列的构造器
    public ArrayQueue(int arrMaxSize) {
    maxSize = arrMaxSize;
    arr = new int[maxSize];
    front = -1; // 指向队列头部,分析出front是指向队列头的前一个位置.
    rear = -1; // 指向队列尾,指向队列尾的数据(即就是队列最后一个数据)
    }

    // 判断队列是否满
    public boolean isFull() {
    return rear == maxSize - 1;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
    return rear == front;
    }

    // 添加数据到队列
    public void addQueue(int n) {
    // 判断队列是否满
    if (isFull()) {
    System.out.println("队列满,不能加入数据~");
    return;
    }
    rear++; // 让rear 后移
    arr[rear] = n;
    }

    // 获取队列的数据, 出队列
    public int getQueue() {
    // 判断队列是否空
    if (isEmpty()) {
    // 通过抛出异常
    throw new RuntimeException("队列空,不能取数据");
    }
    front++; // front后移
    return arr[front];

    }

    // 显示队列的所有数据
    public void showQueue() {
    // 遍历
    if (isEmpty()) {
    System.out.println("队列空的,没有数据~~");
    return;
    }
    for (int i = 0; i < arr.length; i++) {
    System.out.printf("arr[%d]=%d\n", i, arr[i]);
    }
    }

    // 显示队列的头数据, 注意不是取出数据
    public int headQueue() {
    // 判断
    if (isEmpty()) {
    throw new RuntimeException("队列空的,没有数据~~");
    }
    return arr[front + 1];
    }
    }
    + + + +

    3.2.3 数组模拟环形队列

    环形队列的思路分析

    +
      +
    • 环形队列满的条件:(rear + 1) % maxSize == front
    • +
    • 环形队列空的条件:rear == front
    • +
    • 环形队列中有效数据的个数: (rear + maxSize - front) % maxSize
    • +
    +

    image-20230428124659873

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    package com.atguigu.queue;

    import java.util.Scanner;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/28
    * @Description 模拟环形队列
    * <p>
    * 环形队列满的条件:(rear + 1) % maxSize == front
    * 环形队列空的条件:rear == front
    * 环形队列中有效数据的个数: (rear + maxSize - front) % maxSize
    */

    public class CircleArrayQueueDemo {
    public static void main(String[] args) {
    //创建一个队列
    System.out.println("测试环形队列");
    CircleArray queue = new CircleArray(3);
    char key = ' '; //接收用户输入
    Scanner scanner = new Scanner(System.in);//
    boolean loop = true;
    //输出一个菜单
    while (loop) {
    System.out.println("s(show): 显示队列");
    System.out.println("e(exit): 退出程序");
    System.out.println("a(add): 添加数据到队列");
    System.out.println("g(get): 从队列取出数据");
    System.out.println("h(head): 查看队列头的数据");
    key = scanner.next().charAt(0);//接收一个字符
    switch (key) {
    case 's':
    queue.showQueue();
    break;
    case 'a'://添加数据
    System.out.println("输出一个数");
    int value = scanner.nextInt();
    queue.addQueue(value);
    break;
    case 'g': //取出数据
    try {
    int res = queue.getQueue();
    System.out.printf("取出的数据是%d\n", res);
    } catch (Exception e) {
    // TODO: handle exception
    System.out.println(e.getMessage());
    }
    break;
    case 'h': //查看队列头的数据
    try {
    int res = queue.headQueue();
    System.out.printf("队列头的数据是%d\n", res);
    } catch (Exception e) {
    // TODO: handle exception
    System.out.println(e.getMessage());
    }
    break;
    case 'e': //退出
    scanner.close();
    loop = false;
    break;
    default:
    break;
    }
    }

    System.out.println("程序退出~~");
    }
    }

    class CircleArray {
    private int maxSize; // 表示数组的最大容量 因为预留了一个空间 实际上maxSize=3 只能存储两个元素
    private int front; // 队列头 初始值是0
    //rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定
    private int rear; // 队列尾
    private int[] arr; // 该数据用于存放数据, 模拟队列

    /**
    * 构造方法
    *
    * @param arrMaxSize 环形队列的最大容量
    */
    public CircleArray(int arrMaxSize) {
    maxSize = arrMaxSize;
    arr = new int[maxSize];
    }

    /**
    * 判断队列是否为满
    */
    public boolean isFull() {
    //因为是环形队列 如果尾部队列的后一个指向队列的头部 那么我们认为队列是满的
    return (rear + 1) % maxSize == front; //这里是一个理解点
    }

    /**
    * 判断队列是否为空
    */
    public boolean isEmpty() {
    return rear == front;
    }


    /**
    * 添加数据
    */
    public void addQueue(int n) {
    //先判断队列是否为满 满了就不添加
    if (this.isFull()) {
    System.out.println("队列为满,不能添加数据!");
    return;
    }
    //添加数据
    arr[rear] = n;
    //将人rear向后移
    rear = (rear + 1) % maxSize; //这里是一个理解点
    }


    /**
    * 取出数据
    */
    public int getQueue() {
    //先判断队列是否为空 空了就无法取出数据
    if (this.isEmpty()) {
    System.out.println("队列为空,无法取出数据!");
    throw new RuntimeException("队列空,不能取数据");
    }
    //不为空 取出数据
    int result = arr[front];
    front = (front + 1) % maxSize; //这里是一个理解点
    return result;
    }


    /**
    * 显示对垒数据的方法
    */
    public void showQueue() {
    //判断队列是否为空 如果队列为空就不遍历
    if (this.isEmpty()) {
    System.out.println("队列为空!");
    return;
    }
    //遍历的方式 从front开始遍历 遍历多少个元素
    int count = this.size();
    for (int i = front; i < front + count; i++) {//主要作用是遍历多少次
    System.out.println("arr[" + i % maxSize + "]=" + arr[i % maxSize]);
    }
    }

    /**
    * 求出当前数列中有效数据的个数
    */
    public int size() {
    return (rear + maxSize - front) % maxSize; //这是一个理解点
    }

    /**
    * 显示队列的头元素
    */
    public int headQueue() {
    // 判断
    if (isEmpty()) {
    throw new RuntimeException("队列空的,没有数据~~");
    }
    return arr[front];
    }

    }
    + + + + + +

    四.链表

    4.1 链表(Linked List)介绍

    在内存中不是连续存储的

    +

    链表的逻辑结构

    +

    image-20230428135032883

    +

    4.2单链表的应用实例(CRUD)

    使用带head头的单向链表实现 –水浒英雄排行榜管理 完成对英雄人物的增删改查操作

    +

    4.2.1向单链表中添加数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    package com.atguigu.linkedlist;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/28
    * @Description 单链表的实现
    * 使用带head头的单向链表实现 –水浒英雄排行榜管理
    * 完成对英雄人物的增删改查操作
    */
    public class SingleLinkedListDemo {
    public static void main(String[] args) {
    SingleLinkedList singleLinkedList = new SingleLinkedList();
    singleLinkedList.add(new HeroNode(1, "宋江", "及时雨"));
    singleLinkedList.add(new HeroNode(2, "卢俊义", "玉麒麟"));
    singleLinkedList.add(new HeroNode(3, "吴用", "智多星"));
    singleLinkedList.add(new HeroNode(4, "林冲", "豹子头"));
    //显示链表
    singleLinkedList.list();
    }
    }

    //定义SingleLinkedList来管理我们的英雄任务
    class SingleLinkedList {
    //初始化一个头节点 不存储具体的数据
    private HeroNode head = new HeroNode(0, "", "");

    /**
    * 添加节点到单向链表
    */
    public void add(HeroNode heroNode) {
    //找到当前链表的最后一个节点
    HeroNode temp = head;
    while (temp.next != null) {
    temp = temp.next;
    }
    temp.next = heroNode;
    }

    /**
    * 显示链表
    */
    public void list() {
    //先判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空!");
    return;
    }
    HeroNode temp = head;
    while (temp.next != null) {
    System.out.println(temp.next);
    temp = temp.next;
    }
    }

    }

    //定义一个heroNode,每个heroNode对象都是一个节点
    class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点

    //构造器
    public HeroNode(int no, String name, String nickname) {
    this.no = no;
    this.name = name;
    this.nickname = nickname;
    }

    @Override
    public String toString() {
    return "HeroNode{" +
    "no=" + no +
    ", name='" + name + '\'' +
    ", nickname='" + nickname + '\'' +
    '}';
    }
    }
    + +

    4.2.2修改单链表中节点的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /**
    * 修改节点的信息
    * 根据新的节点的编号进行修改
    */
    public void update(HeroNode newHeroNode) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法修改该节点!");
    return;
    }
    //找到需要修改的节点
    //首先找到头节点的位置
    HeroNode temp = head.next;
    boolean idFind = false; //是否找到节点的信息
    while (true) {
    if (temp == null) {
    break;//已经到了链表的结尾,就退出循环
    }
    if (temp.no == newHeroNode.no) {//找到修改的节点
    temp.name = newHeroNode.name;
    temp.nickname = newHeroNode.nickname;
    idFind = true; //找到了节点并且修改了节点的信息
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(idFind ? "修改成功" : "没有找到该节点的信息");
    }
    + +

    4.2.3删除单链表中节点的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /**
    * 删除链表中的节点
    * 根据节点的编号删除节点的信息
    */
    public void delete(int no) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法删除!");
    return;
    }
    //找到要删除的节点的信息
    HeroNode temp = head;
    boolean isDelete = false;//是否删除
    while (true) {
    if (temp.next == null) {
    break;
    }
    //这里的temp指的是要删除节点的上一个节点 temp.next.next指的是要删除节点的下一个节点
    if (temp.next.no == no) {
    //将要删除的节点的上一个节点指向要删除节点的下一个节点 实现删除的操作
    //这样要删除的节点没有引用指向,会被垃圾回收机制回收
    temp.next = temp.next.next;
    isDelete = true;
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(isDelete ? "删除成功" : "没有找到该节点的信息");
    }
    + +

    4.2.4完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    package com.atguigu.linkedlist;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/28
    * @Description 单链表的实现
    * 使用带head头的单向链表实现 –水浒英雄排行榜管理
    * 完成对英雄人物的增删改查操作
    */
    public class SingleLinkedListDemo {
    public static void main(String[] args) {
    SingleLinkedList singleLinkedList = new SingleLinkedList();
    singleLinkedList.add(new HeroNode(1, "宋江", "及时雨"));
    singleLinkedList.add(new HeroNode(2, "卢俊义", "玉麒麟"));
    singleLinkedList.add(new HeroNode(3, "吴用", "智多星"));
    singleLinkedList.add(new HeroNode(4, "林冲", "豹子头"));
    //显示链表
    singleLinkedList.list();
    //测试修改节点的功能
    singleLinkedList.update(new HeroNode(2, "卢俊义", "玉麒麟增强版"));
    //显示链表的信息
    singleLinkedList.list();
    //测试删除节点
    singleLinkedList.delete(2);
    //显示链表的信息
    singleLinkedList.list();
    }
    }

    //定义SingleLinkedList来管理我们的英雄任务
    class SingleLinkedList {
    //初始化一个头节点 不存储具体的数据
    private HeroNode head = new HeroNode(0, "", "");


    /**
    * 删除链表中的节点
    * 根据节点的编号删除节点的信息
    */
    public void delete(int no) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法删除!");
    return;
    }
    //找到要删除的节点的信息
    HeroNode temp = head;
    boolean isDelete = false;//是否删除
    while (true) {
    if (temp.next == null) {
    break;
    }
    if (temp.next.no == no) {//这里的temp指的是要删除节点的上一个节点 temp.next.next指的是要删除节点的下一个节点
    //将当前节点的上一个节点指向当前节点的下一个节点
    temp.next = temp.next.next; //将要删除的节点的上一个节点指向要删除节点的下一个节点 这样要删除的节点没有引用指向 会被垃圾回收机制回收
    isDelete = true;
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(isDelete ? "删除成功" : "没有找到该节点的信息");
    }


    /**
    * 修改节点的信息
    * 根据新的节点的编号进行修改
    */
    public void update(HeroNode newHeroNode) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法修改该节点!");
    return;
    }
    //找到需要修改的节点
    //首先找到头节点的位置
    HeroNode temp = head.next;
    boolean idFind = false; //是否找到节点的信息
    while (true) {
    if (temp == null) {
    break;//已经到了链表的结尾,就退出循环
    }
    if (temp.no == newHeroNode.no) {//找到修改的节点
    temp.name = newHeroNode.name;
    temp.nickname = newHeroNode.nickname;
    idFind = true; //找到了节点并且修改了节点的信息
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(idFind ? "修改成功" : "没有找到该节点的信息");
    }


    /**
    * 添加节点到单向链表
    */
    public void add(HeroNode heroNode) {
    //找到当前链表的最后一个节点
    HeroNode temp = head;
    while (temp.next != null) {
    temp = temp.next;
    }
    temp.next = heroNode;
    }


    /**
    * 显示链表
    */
    public void list() {
    //先判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空!");
    return;
    }
    HeroNode temp = head;
    while (temp.next != null) {
    System.out.println(temp.next);
    temp = temp.next;
    }
    }

    }

    //定义一个heroNode,每个heroNode对象都是一个节点
    class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点

    //构造器
    public HeroNode(int no, String name, String nickname) {
    this.no = no;
    this.name = name;
    this.nickname = nickname;
    }

    @Override
    public String toString() {
    return "HeroNode{" +
    "no=" + no +
    ", name='" + name + '\'' +
    ", nickname='" + nickname + '\'' +
    '}';
    }
    }
    + +

    4.2.5 单链表的面试题(新浪,百度,腾讯)

    1.求单链表中有效节点的个数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
    * 获取有效节点的个数(如果是带头节点的链表,需求不统计头节点)
    * 注意:这里是有头节点的情况下统计节点的个数 (这里我们去除了头节点)
    * @param heroNode 头节点
    * @return 有效节点的个数
    */
    public int getLength(HeroNode heroNode){
    //先判断节点是否为空
    if(heroNode.next == null){
    return 0;
    }
    //遍历节点进行统计
    int length = 0;//有效节点的个数
    HeroNode temp = heroNode.next;//这里没有统计头节点
    while (temp != null){//遍历 统计个数
    length++;
    temp = temp.next;
    }
    return length;
    }
    + +
    2.查找单链表中的倒数第K个节点(新浪面试题)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /**
    * 查找单链表中的倒数第K个节点
    *
    * @param index 倒数第几
    * @param heroNode 头节点
    * @return 倒数第K个节点的信息
    */
    public HeroNode getHeroNodeByLastIndex(int index, HeroNode heroNode) {
    //先判断节点是否为空
    if (heroNode.next == null) {
    return null;
    }
    //首先获取有效节点的个数
    int length = this.getLength(heroNode);//使用求节点有效个数的方法
    //求倒数第k个节点就是在求正数第(length - index + 1)节点
    index = length - index; //求出正向的索引位置
    if (index < 0 || index > length) {
    System.out.println("所求的在链表中不存在");
    return null;
    }
    int count = 0;
    HeroNode temp = heroNode.next;
    while (true) {
    if (count == index) {
    return temp;
    }
    count++;
    temp = temp.next;
    }
    }
    + +
    3.单链表的反转(腾讯面试题)

    image-20230506105453269

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * 单链表的反转
    * @param heroNode 需要反转单链表的头节点
    */
    public void reverseHeroNode(HeroNode heroNode) {
    //先判断当前的链表是否为空 或者当前的链表只有一个节点
    if(head.next == null || head.next.next == null){
    System.out.println("当前链表为空或者只有一个节点,无需反转");
    return;
    }
    //定义一个辅助的变量 帮助我们遍历原来的链表
    HeroNode cur = head.next;
    HeroNode next = null;//指向当前节点的下一个节点 我们要在挪动当前节点之前帮当前指针 下移
    HeroNode reverseHeroNode = new HeroNode(0, "", "");//新链表的头节点
    //遍历原先的列表 确定每一个链表的指向关系
    while (cur != null){
    next = cur.next;//保存当前节点的下一个节点
    //reverseHeroNode.next表示头节点的下一个节点 我们让cur的下一个节点(cur.next)指向头节点的下一个节点 //(reverseHeroNode.next) 就把当前的节点(cur)穿插进去了
    //下一句将头节点和当前节点建立关系reverseHeroNode.next = cur 整个节点就连接起来了
    cur.next = reverseHeroNode.next;//将cur的下一个节点指向新的链表的最前端 相当于在头节点和头节点的下一个节点 //之间穿插了一个节点
    reverseHeroNode.next = cur;//将cur连接到新的链表上
    cur = next; //cur像后移动
    }
    //将head.next指向reverseHeroNode.next 实现单链表的反转
    head.next = reverseHeroNode.next;
    }
    + +
    4.从尾到头打印单链表(百度面试题)

    不改变链表本身的结构(不是通过链表的反转之后再打印的)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /**
    * (通过栈的方式实现单链表的逆序打印)
    * 栈的特点是先入后出 正好可以满足逆序打印
    * 单链表的逆序打印
    */
    public void reversePrint(HeroNode heroNode) {
    //先判断当前的链表是否为空 或者当前的链表只有一个节点
    if (head.next == null) {
    System.out.println("当前链表为空,无法逆序打印!");
    return;
    }
    //创建一个栈,将各个节点压入栈中
    //先创建一个栈
    Stack<HeroNode> stack = new Stack<>();
    HeroNode cur = heroNode.next;
    //循环将每一个节点添加进栈中
    while (cur != null) {
    stack.push(cur);//将节点压入栈中
    cur = cur.next;//cur后移 压入下一个节点
    }
    //将栈中节点打印
    while (stack.size() > 0) {
    System.out.println(stack.pop());
    }
    }
    + +
    5.合并两个有序的单链表,合并之后的链表依然有序

    以下代码有一些bug 后期修改

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    /**
    * 合并两个有序的单链表,合并之后的链表依然有序
    */
    public static void mergeHeroNode(HeroNode oneHead, HeroNode twoHead ,HeroNode head) {
    //先判断两个链表是否为空
    if (oneHead.next == null || twoHead.next == null) {
    System.out.println("其中有一个(两个)链表为空,无法合并");
    return;
    }
    //合并
    HeroNode oneCur = oneHead.next;
    HeroNode twoCur = twoHead.next;
    HeroNode oneNext = null;
    HeroNode twoNext = null;
    HeroNode finalHeroHead = new HeroNode(0, "", "");
    while (oneCur != null && twoCur != null) {
    oneNext = oneCur.next;
    twoNext = twoCur.next;
    if(oneCur.no <= twoCur.no) {
    oneCur.next = finalHeroHead.next;
    finalHeroHead.next = oneCur;
    oneCur = oneNext;
    }else {
    twoCur.next = finalHeroHead.next;
    finalHeroHead.next = twoCur;
    twoCur = twoNext;
    }
    }
    head.next = finalHeroHead.next;
    }
    + +
    全部代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    package com.atguigu.linkedlist;

    import java.util.Stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/28
    * @Description 单链表的实现
    * 使用带head头的单向链表实现 –水浒英雄排行榜管理
    * 完成对英雄人物的增删改查操作
    */
    public class SingleLinkedListDemo {
    public static void main(String[] args) {
    SingleLinkedList singleLinkedList = new SingleLinkedList();
    // singleLinkedList.add(new HeroNode(1, "宋江", "及时雨"));
    // singleLinkedList.add(new HeroNode(2, "卢俊义", "玉麒麟"));
    // singleLinkedList.add(new HeroNode(3, "吴用", "智多星"));
    // singleLinkedList.add(new HeroNode(4, "林冲", "豹子头"));
    // //显示链表
    // singleLinkedList.list();
    // //测试修改节点的功能
    // singleLinkedList.update(new HeroNode(2, "卢俊义", "玉麒麟增强版"));
    // //显示链表的信息
    // singleLinkedList.list();
    // //测试删除节点
    // singleLinkedList.delete(2);
    // //显示链表的信息
    // singleLinkedList.list();
    // //统计有效节点的个数
    // System.out.println("有效节点的个数" + singleLinkedList.getLength(singleLinkedList.getHead()));
    // //查找单链表中的倒数第K个节点
    // int index = 2;
    // System.out.println("倒数第" + index + "个节点的位置:" + singleLinkedList.getHeroNodeByLastIndex(index, singleLinkedList.getHead()));
    // //测试单链表的反转
    // singleLinkedList.reverseHeroNode(singleLinkedList.getHead());
    // singleLinkedList.list();
    // //测试通过栈的方式逆序打印
    // System.out.println("测试通过栈的方式逆序打印头节点");
    // singleLinkedList.reversePrint(singleLinkedList.getHead());

    //测试合并两个有序的链表
    SingleLinkedList singleLinkedList1 = new SingleLinkedList();
    singleLinkedList1.add(new HeroNode(1, "宋江", "及时雨"));
    singleLinkedList1.add(new HeroNode(3, "卢俊义", "玉麒麟"));
    singleLinkedList1.add(new HeroNode(5, "吴用", "智多星"));
    singleLinkedList1.add(new HeroNode(7, "林冲", "豹子头"));
    SingleLinkedList singleLinkedList2 = new SingleLinkedList();
    singleLinkedList2.add(new HeroNode(2, "宋江", "及时雨"));
    singleLinkedList2.add(new HeroNode(4, "卢俊义", "玉麒麟"));
    singleLinkedList2.add(new HeroNode(6, "吴用", "智多星"));
    singleLinkedList2.add(new HeroNode(8, "林冲", "豹子头"));
    mergeHeroNode(singleLinkedList1.getHead(),singleLinkedList2.getHead(),singleLinkedList.getHead());
    singleLinkedList.list();
    }
    /**
    * 合并两个有序的单链表,合并之后的链表依然有序
    */
    public static void mergeHeroNode(HeroNode oneHead, HeroNode twoHead ,HeroNode head) {
    //先判断两个链表是否为空
    if (oneHead.next == null || twoHead.next == null) {
    System.out.println("其中有一个(两个)链表为空,无法合并");
    return;
    }
    //合并
    HeroNode oneCur = oneHead.next;
    HeroNode twoCur = twoHead.next;
    HeroNode oneNext = null;
    HeroNode twoNext = null;
    HeroNode finalHeroHead = new HeroNode(0, "", "");
    while (oneCur != null && twoCur != null) {
    oneNext = oneCur.next;
    twoNext = twoCur.next;
    if(oneCur.no <= twoCur.no) {
    oneCur.next = finalHeroHead.next;
    finalHeroHead.next = oneCur;
    oneCur = oneNext;
    }else {
    twoCur.next = finalHeroHead.next;
    finalHeroHead.next = twoCur;
    twoCur = twoNext;
    }
    }
    head.next = finalHeroHead.next;
    }
    }

    //定义SingleLinkedList来管理我们的英雄任务
    class SingleLinkedList {
    //初始化一个头节点 不存储具体的数据
    private HeroNode head = new HeroNode(0, "", "");

    public HeroNode getHead() {
    return head;
    }





    /**
    * (通过栈的方式实现单链表的逆序打印)
    * 栈的特点是先入后出 正好可以满足逆序打印
    * 单链表的逆序打印
    */
    public void reversePrint(HeroNode heroNode) {
    //先判断当前的链表是否为空 或者当前的链表只有一个节点
    if (heroNode.next == null) {
    System.out.println("当前链表为空,无法逆序打印!");
    return;
    }
    //创建一个栈,将各个节点压入栈中
    //先创建一个栈
    Stack<HeroNode> stack = new Stack<>();
    HeroNode cur = heroNode.next;
    //循环将每一个节点添加进栈中
    while (cur != null) {
    stack.push(cur);//将节点压入栈中
    cur = cur.next;//cur后移 压入下一个节点
    }
    //将栈中节点打印
    while (stack.size() > 0) {
    System.out.println(stack.pop());
    }
    }


    /**
    * 单链表的反转
    *
    * @param heroNode 需要反转单链表的头节点
    */
    public void reverseHeroNode(HeroNode heroNode) {
    //先判断当前的链表是否为空 或者当前的链表只有一个节点
    if (heroNode.next == null || heroNode.next.next == null) {
    System.out.println("当前链表为空或者只有一个节点,无需反转");
    return;
    }
    //定义一个辅助的变量 帮助我们遍历原来的链表
    HeroNode cur = heroNode.next;
    HeroNode next = null;//指向当前节点的下一个节点 我们要在挪动当前节点之前帮当前指针 下移
    HeroNode reverseHeroNode = new HeroNode(0, "", "");//新链表的头节点
    //遍历原先的列表 确定每一个链表的指向关系
    while (cur != null) {
    next = cur.next;//保存当前节点的下一个节点
    //reverseHeroNode.next表示头节点的下一个节点 我们让cur的下一个节点(cur.next)指向头节点的下一个节点(reverseHeroNode.next) 就把当前的节点(cur)穿插进去了
    //下一句将头节点和当前节点建立关系reverseHeroNode.next = cur 整个节点就连接起来了
    cur.next = reverseHeroNode.next;//将cur的下一个节点指向新的链表的最前端 相当于在头节点和头节点的下一个节点之间穿插了一个节点
    reverseHeroNode.next = cur;//将cur连接到新的链表上
    cur = next; //cur像后移动
    }
    //将head.next指向reverseHeroNode.next 实现单链表的反转
    heroNode.next = reverseHeroNode.next;
    }


    /**
    * 查找单链表中的倒数第K个节点
    *
    * @param index 倒数第几
    * @param heroNode 头节点
    * @return 倒数第K个节点的信息
    */
    public HeroNode getHeroNodeByLastIndex(int index, HeroNode heroNode) {
    //先判断节点是否为空
    if (heroNode.next == null) {
    return null;
    }
    //首先获取有效节点的个数
    int length = this.getLength(heroNode);//使用求节点有效个数的方法
    //求倒数第k个节点就是在求正数第(length - index + 1)节点
    index = length - index; //求出正向的索引位置
    if (index < 0 || index > length) {
    System.out.println("所求的在链表中不存在");
    return null;
    }
    int count = 0;
    HeroNode temp = heroNode.next;
    while (true) {
    if (count == index) {
    return temp;
    }
    count++;
    temp = temp.next;
    }
    }

    /**
    * 获取有效节点的个数(如果是带头节点的链表,需求不统计头节点)
    *
    * @param heroNode 头节点
    * @return 有效节点的个数
    */
    public int getLength(HeroNode heroNode) {
    //先判断节点是否为空
    if (heroNode.next == null) {
    return 0;
    }
    //遍历节点进行统计
    int length = 0;//有效节点的个数
    HeroNode temp = heroNode.next;//这里没有统计头节点
    while (temp != null) {//遍历 统计个数
    length++;
    temp = temp.next;
    }
    return length;
    }


    /**
    * 删除链表中的节点
    * 根据节点的编号删除节点的信息
    */
    public void delete(int no) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法删除!");
    return;
    }
    //找到要删除的节点的信息
    HeroNode temp = head;
    boolean isDelete = false;//是否删除
    while (true) {
    if (temp.next == null) {
    break;
    }
    if (temp.next.no == no) {//这里的temp指的是要删除节点的上一个节点 temp.next.next指的是要删除节点的下一个节点
    //将当前节点的上一个节点指向当前节点的下一个节点
    temp.next = temp.next.next; //将要删除的节点的上一个节点指向要删除节点的下一个节点 这样要删除的节点没有引用指向 会被垃圾回收机制回收
    isDelete = true;
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(isDelete ? "删除成功" : "没有找到该节点的信息");
    }


    /**
    * 修改节点的信息
    * 根据新的节点的编号进行修改
    */
    public void update(HeroNode newHeroNode) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法修改该节点!");
    return;
    }
    //找到需要修改的节点
    //首先找到头节点的位置
    HeroNode temp = head.next;
    boolean idFind = false; //是否找到节点的信息
    while (true) {
    if (temp == null) {
    break;//已经到了链表的结尾,就退出循环
    }
    if (temp.no == newHeroNode.no) {//找到修改的节点
    temp.name = newHeroNode.name;
    temp.nickname = newHeroNode.nickname;
    idFind = true; //找到了节点并且修改了节点的信息
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(idFind ? "修改成功" : "没有找到该节点的信息");
    }


    /**
    * 添加节点到单向链表
    */
    public void add(HeroNode heroNode) {
    //找到当前链表的最后一个节点
    HeroNode temp = head;
    while (temp.next != null) {
    temp = temp.next;
    }
    temp.next = heroNode;
    }


    /**
    * 显示链表
    */
    public void list() {
    //先判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空!");
    return;
    }
    HeroNode temp = head;
    while (temp.next != null) {
    System.out.println(temp.next);
    temp = temp.next;
    }
    }


    }

    //定义一个heroNode,每个heroNode对象都是一个节点
    class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点

    //构造器
    public HeroNode(int no, String name, String nickname) {
    this.no = no;
    this.name = name;
    this.nickname = nickname;
    }

    @Override
    public String toString() {
    return "HeroNode{" +
    "no=" + no +
    ", name='" + name + '\'' +
    ", nickname='" + nickname + '\'' +
    '}';
    }
    }
    + + + +

    4.3 双向链表

    双向的链表的CRUD操作于单向链表的CRUD操作类似

    +

    image-20230507100730848

    +

    完整的增删改查代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    package com.atguigu.linkedlist;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/7
    * @Description 双向链表的使用
    */
    public class DoubleLinkedListDemo {
    public static void main(String[] args) {
    DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
    //测试添加链表的功能
    doubleLinkedList.add(new HeroNode2(1, "宋江", "及时雨"));
    doubleLinkedList.add(new HeroNode2(2, "卢俊义", "玉麒麟"));
    doubleLinkedList.add(new HeroNode2(3, "吴用", "智多星"));
    doubleLinkedList.add(new HeroNode2(4, "林冲", "豹子头"));
    //测试显示链表的功能
    doubleLinkedList.list();
    //测试删除双向链表中的节点
    doubleLinkedList.delete(2);
    doubleLinkedList.list();
    //测试修改双向链表中的节点
    doubleLinkedList.update(new HeroNode2(2, "卢俊义修改版", "玉麒麟"));
    doubleLinkedList.list();
    System.out.println("--------------");
    doubleLinkedList.orderAdd(new HeroNode2(3, "小卢", "玉麒麟"));
    doubleLinkedList.list();
    }
    }

    class DoubleLinkedList {
    //初始化一个头节点 不存储具体的数据
    private HeroNode2 head = new HeroNode2(0, "", "");

    //返回头节点的信息
    public HeroNode2 getHead() {
    return head;
    }


    /**
    * 双向链表的顺序添加
    * 应该有问题 不是参考答案
    */
    public void orderAdd(HeroNode2 heroNode) {
    //找到当前链表的最后一个节点
    HeroNode2 temp = head;
    boolean isSuccess = false;
    while (temp.next != null) {
    if (temp.next.no >= heroNode.no) {
    heroNode.next = temp.next;
    temp.next.pre = heroNode;
    temp.next = heroNode;
    heroNode.pre = temp;
    isSuccess = true;
    break;
    }
    temp = temp.next;
    }
    if (!isSuccess) {//没有添加成功 说明当前链表的序号超过了链表里面已有节点序号的最高值 直接添加到最后
    this.add(heroNode);
    }

    }


    /**
    * 删除双向链表中的一个节点
    */
    public void delete(int no) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法删除!");
    return;
    }
    //找到要删除的节点的信息
    HeroNode2 temp = head.next;
    boolean isDelete = false;//是否删除
    while (true) {
    if (temp == null) {
    break;
    }
    if (temp.no == no) {
    temp.pre.next = temp.next;
    if (temp.next != null) {
    temp.next.pre = temp.pre;//这句话有个条件 temp不能是最后一个节点
    }
    isDelete = true;
    break;
    }
    temp = temp.next;
    }
    System.out.println(isDelete ? "删除成功" : "没有找到该节点的信息");
    }


    /**
    * 修改双向链表的信息
    */
    public void update(HeroNode2 newHeroNode) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法修改该节点!");
    return;
    }
    //找到需要修改的节点
    //首先找到头节点的位置
    HeroNode2 temp = head.next;
    boolean idFind = false; //是否找到节点的信息
    while (true) {
    if (temp == null) {
    break;//已经到了链表的结尾,就退出循环
    }
    if (temp.no == newHeroNode.no) {//找到修改的节点
    temp.name = newHeroNode.name;
    temp.nickname = newHeroNode.nickname;
    idFind = true; //找到了节点并且修改了节点的信息
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(idFind ? "修改成功" : "没有找到该节点的信息");
    }


    /**
    * 向双向链表中添加节点信息
    */
    public void add(HeroNode2 heroNode) {
    //找到当前链表的最后一个节点
    HeroNode2 temp = head;
    while (temp.next != null) {
    temp = temp.next;
    }
    temp.next = heroNode;
    heroNode.pre = temp;
    }


    /**
    * 显示链表
    */
    public void list() {
    //先判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空!");
    return;
    }
    HeroNode2 temp = head;
    while (temp.next != null) {
    System.out.println(temp.next);
    temp = temp.next;
    }
    }


    }

    //定义一个heroNode,每个heroNode对象都是一个节点
    class HeroNode2 {
    public int no;
    public String name;
    public String nickname;
    public HeroNode2 next; //指向下一个节点
    public HeroNode2 pre;//指向上一个节点

    //构造器
    public HeroNode2(int no, String name, String nickname) {
    this.no = no;
    this.name = name;
    this.nickname = nickname;
    }

    @Override
    public String toString() {
    return "HeroNode{" +
    "no=" + no +
    ", name='" + name + '\'' +
    ", nickname='" + nickname + '\'' +
    '}';
    }
    }
    + +

    4.4 单向环形链表的应用场景(Josephu问题)

    ​ Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

    +

    ​ 解决的方案:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。

    +

    image-20230508104207815

    +

    image-20230508121934697

    +

    完整的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    package com.atguigu.linkedlist;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/8
    * @Description 约瑟夫问题
    */
    public class Josephu {
    public static void main(String[] args) {
    //测试构建环形链表
    CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
    circleSingleLinkedList.addBoy(5);
    //测试遍历环形单向链表
    circleSingleLinkedList.showBoy();
    //测试生成小孩出圈的序列
    circleSingleLinkedList.countBoy(1,2,5);
    }

    }


    //创建一个环形的单向链表
    class CircleSingleLinkedList {
    //创建一个first节点,当前没有编号
    private Boy first = null;

    /**
    * 根据用户的输入产生一个出队编号的序列
    *
    * @param startNo 表示从第几个小孩开始数
    * @param countNum 表示数几下
    * @param nums 传入几个小孩
    */
    public void countBoy(int startNo, int countNum, int nums) {
    //先对输入的数据进行校验
    if (first == null || startNo < 0 || startNo > nums) {
    System.out.println("参数输入有误,请重新输入!");
    return;
    }
    //创建一个辅助指针
    Boy helper = first;
    //将helper指向环形链表的最后一个节点
    while (true) {
    if (helper.getNext() == first) {
    break;
    }
    helper = helper.getNext();
    }
    //将first和helper移动到指定的位置 考虑从第几个小孩开始数
    //小孩报数前,先让 first 和 helper 移动 k - 1次
    for (int i = 0; i < startNo - 1; i++) {
    first = first.getNext();
    helper = helper.getNext();
    }
    //让first和helper移动 m-1次,然后出圈
    //循环操作 直到圈中只有一个小孩节点
    while (true){
    if(helper == first){//说明圈中只有一个节点
    break;
    }
    //让first和helper移动 countNum - 1
    for (int i = 0; i < countNum - 1; i++) {
    first = first.getNext();
    helper = helper.getNext();
    }
    //这时first指向的节点就是要出圈的小孩
    System.out.printf("小孩%d出圈\n",first.getNo());
    //将first指向的小孩出圈
    first = first.getNext();
    helper.setNext(first);
    }
    System.out.printf("最后留在圈中小孩编号是:%d \n",helper.getNo());
    }


    /**
    * 遍历当前的环形链表
    */
    public void showBoy() {
    //先判断链表是不是为空
    if (first == null) {
    System.out.println("环形链表为空!");
    return;
    }
    //创建一个辅助指针
    Boy curBoy = first;
    while (true) {
    System.out.printf("小孩的编号:%d \n", curBoy.getNo());
    if (curBoy.getNext() == first) {//说明已经循环一圈了 就不在循环 退出了
    break;
    }
    //向后移动
    curBoy = curBoy.getNext();
    }
    }


    /**
    * 添加小孩节点 构建成一个环形的链表
    *
    * @param nums 传入几个小孩
    */
    public void addBoy(int nums) {
    //将传入的数据进行一个校验
    if (nums < 1) {//传入小孩的的数量不能小于一
    System.out.println("输入的值不正确!");
    return;
    }
    Boy curBoy = null;//辅助指针,帮助构建环形链表
    //使用循环创建我们的环形链表
    for (int i = 1; i <= nums; i++) { //要加入几个小孩 就循环几次
    //根据编号创建小孩节点
    Boy boy = new Boy(i);
    //将小孩加入到环形链表中
    if (i == 1) {//说明是第一个小孩
    first = boy;
    first.setNext(first);//添加的第一个节点 先自己指向自己 构建成一个环
    curBoy = first; //first节点我们不能动 使用辅助节点
    } else {
    //这是在循环里面 每次循环之后 小孩节点都不一样
    curBoy.setNext(boy);
    boy.setNext(first); //形成回路
    curBoy = boy;//curBoy每次指向的都是最后一个小孩节点
    }
    }
    }

    }

    //创建一个boy类 表示一个节点
    class Boy {
    private int no; //编号
    private Boy next;//指向下一个节点 默认是null

    public Boy(int no) {
    this.no = no;
    }

    public int getNo() {
    return no;
    }

    public void setNo(int no) {
    this.no = no;
    }

    public Boy getNext() {
    return next;
    }

    public void setNext(Boy next) {
    this.next = next;
    }
    }
    + + + + + +

    五.栈

    1.介绍

    1)栈的英文为(stack)

    +

    2)栈是一个先入后出(FILO-First In Last Out)的有序列表。

    +

    3)栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。

    +

    4)根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

    +

    2.应用场景

    1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。

    +

    2)处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。

    +

    3)表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。

    +

    4)二叉树的遍历。

    +

    5)图形的深度优先(depth一first)搜索法。

    +

    3.图解

    image-20230510101713914

    +

    image-20230510101722707

    +

    4.使用数组模拟栈

    image-20230510101916230

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    package com.atguigu.stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/10
    * @Description 使用数组模拟栈
    */
    public class ArrayStackDemo {
    public static void main(String[] args) {
    ArrayStack stack = new ArrayStack(5);
    //入栈
    stack.push(1);
    stack.push(2);
    stack.push(3);
    //出栈
    System.out.println("出栈的数是:"+stack.pop());
    //显示栈中所有的数据
    stack.list();
    }
    }

    //定义一个ArrayStack表示栈
    class ArrayStack {
    private int maxSize;//栈的大小
    private int[] stack;//数组 数组模拟栈 数据放在数组中
    private int top = -1; //top表示栈顶 初始化为1

    //构造器
    public ArrayStack(int maxSize) {
    this.maxSize = maxSize;
    //完成数组的初始化
    stack = new int[maxSize];
    }

    /**
    * 判断是否栈满
    */
    public boolean isFull() {
    return top == maxSize - 1;
    }

    /**
    * 判断栈是否为空
    */
    public boolean isEmpty() {
    return top == -1;
    }


    /**
    * 入栈的操作
    */
    public void push(int value) {
    //先判断栈是否为满
    if (isFull()) {
    System.out.println("栈满,无法添加数据!");
    return;
    }
    top++;
    stack[top] = value;
    }

    /**
    * 出栈的操作
    * 将栈顶的数据返回
    */
    public int pop() {
    //先判断栈是否为空
    if (isEmpty()) {
    throw new RuntimeException("栈空,无法取出数据!");
    }
    //取出数据
    int value = stack[top];
    top--;
    return value;
    }

    /**
    * 显示栈的情况
    */
    public void list() {
    //先判断栈是否为空
    if (isEmpty()) {
    System.out.println("栈空,无法取出数据!");
    return;
    }
    for (int i = top; i >= 0; i--) {
    System.out.printf("stack[%d] = %d \n", i, stack[i]);
    }
    }

    }
    + +

    5.栈实现综合计算器

    中缀表达式

    +

    image-20230510105251076

    +

    image-20230510110403055

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    package com.atguigu.stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/10
    * @Description 综合计算器
    */
    public class Calculator {
    public static void main(String[] args) {
    /**
    * 1. 通过一个 index 值(索引),来遍历我们的表达式
    * 2. 如果我们发现是一个数字, 就直接入数栈
    * 3. 如果发现扫描到的是一个符号, 就分如下情况
    * 3.1 如果发现当前的符号栈为 空,就直接入栈
    * 3.2 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符, 就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈, 如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
    * 4. 当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
    * 5. 最后在数栈只有一个数字,就是表达式的结果
    */
    String expression = "30+2*6-2";
    //创建两个栈 一个是数栈 一个是符号栈
    ArrayStack2 numStack = new ArrayStack2(10);
    ArrayStack2 operStack = new ArrayStack2(10);
    //定义需要的相关变量
    int index = 0;//用于扫描
    int num1 = 0;
    int num2 = 0;
    int oper = 0;
    int result = 0;
    char ch = ' '; //将每次扫描得到的char保存在ch中
    String keepNum = "";//用于拼接多位数的
    while (true) {
    //得到expression的每一个字符
    ch = expression.substring(index, index + 1).charAt(0);
    //判断ch是什么 做相应的处理
    if (operStack.isOper(ch)) {
    //先符号栈判断是不是为空
    if (!operStack.isEmpty()) { //判断符号栈是否为空
    //不为空的情况
    if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
    //如果当前的操作符的优先级小于或者等于栈中的操作符, 就需要从数栈中pop出两个数,
    // 在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈
    num1 = numStack.pop();
    num2 = numStack.pop();
    oper = operStack.pop();
    result = numStack.cal(num1, num2, oper);
    //将运算的结果入数栈
    numStack.push(result);
    //将当前的操作符入符号栈
    operStack.push(ch);
    } else {
    //如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
    operStack.push(ch);
    }
    } else {
    //如果为空 直接入栈
    operStack.push(ch);
    }
    } else {
    //如果是数字 就直接入数栈
    //要考虑是多位数的情况
    keepNum += ch;
    //如果ch是表达式的最后一位 就直接入栈
    if (index == expression.length() - 1) {
    numStack.push(Integer.parseInt(keepNum));
    } else {
    //判断下一个字符是不是数字 如果是数字 就继续扫描 如果是运算符 则入栈
    if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {//下一位是操作符
    //直接将当前为添加到数栈
    numStack.push(Integer.parseInt(keepNum));
    //清空keepNum
    keepNum = "";
    }
    }
    }
    //让index + 1 ,并判断是否扫描到表达式的最后
    index++;
    if (index >= expression.length()) {
    break;
    }
    }
    //当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
    while (true) {
    //如果符号栈为空 则计算结束 数栈中只有 一个数字
    if (operStack.isEmpty()) {
    break;
    }
    num1 = numStack.pop();
    num2 = numStack.pop();
    oper = operStack.pop();
    result = numStack.cal(num1, num2, oper);
    numStack.push(result);
    }
    System.out.printf("表达式:%s = %d", expression, numStack.pop());
    }
    }

    //定义一个ArrayStack表示栈
    class ArrayStack2 {
    private int maxSize;//栈的大小
    private int[] stack;//数组 数组模拟栈 数据放在数组中
    private int top = -1; //top表示栈顶 初始化为1

    //构造器
    public ArrayStack2(int maxSize) {
    this.maxSize = maxSize;
    //完成数组的初始化
    stack = new int[maxSize];
    }

    /**
    * 判断是否栈满
    */
    public boolean isFull() {
    return top == maxSize - 1;
    }

    /**
    * 判断栈是否为空
    */
    public boolean isEmpty() {
    return top == -1;
    }


    /**
    * 入栈的操作
    */
    public void push(int value) {
    //先判断栈是否为满
    if (isFull()) {
    System.out.println("栈满,无法添加数据!");
    return;
    }
    top++;
    stack[top] = value;
    }

    /**
    * 出栈的操作
    * 将栈顶的数据返回
    */
    public int pop() {
    //先判断栈是否为空
    if (isEmpty()) {
    throw new RuntimeException("栈空,无法取出数据!");
    }
    //取出数据
    int value = stack[top];
    top--;
    return value;
    }

    /**
    * 显示栈的情况
    */
    public void list() {
    //先判断栈是否为空
    if (isEmpty()) {
    System.out.println("栈空,无法取出数据!");
    return;
    }
    for (int i = top; i >= 0; i--) {
    System.out.printf("stack[%d] = %d \n", i, stack[i]);
    }
    }


    /**
    * 返回运算符的优先级
    * 优先级由数字表示
    * 数字大 优先级高
    */
    public int priority(int oper) {
    if (oper == '*' || oper == '/') {
    return 1;
    } else if (oper == '+' || oper == '-') {
    return 0;
    } else {
    return -1;
    }
    }


    /**
    * 判断是不是一个运算符
    */
    public boolean isOper(char val) {
    return val == '+' || val == '-' || val == '*' || val == '/';
    }

    /**
    * 计算方法
    */
    public int cal(int num1, int num2, int oper) {
    int result = 0;
    switch (oper) {
    case '+':
    result = num1 + num2;
    break;
    case '-':
    result = num2 - num1; //注意顺序
    break;
    case '*':
    result = num1 * num2;
    break;
    case '/':
    result = num2 / num1;
    }
    return result;
    }

    /**
    * 查看栈顶数据的方法
    * 返回栈顶的值 不是pop出来
    */
    public int peek() {
    return stack[top];
    }


    }
    + +

    6.逆波兰计算器的设计与实现

    逆波兰表达式(后缀表达式)

    +

    1)输入一个逆波兰表达式(后缀表达式),使用栈(Stack),计算其结果

    +

    2)支持小括号和多位数整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持对整数的计算。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    package com.atguigu.stack;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/11
    * @Description 逆波兰计算器的设计与实现
    */
    public class PolandNotation {
    public static void main(String[] args) {
    //先定义一个逆波兰表达式
    //(3+4)×5-6 的逆波兰表达式是 3 4 + 5 × 6 -
    //为了方便 逆波兰表达式的数字和符号有空格隔开
    String suffixExpression = "3 4 + 5 * 6 -";//中间有空格隔开
    //1.先将suffixExpression放入到List集合中
    //2.将List集合传递给一个方法 配合栈 完成计算
    List<String> rpnList = getListString(suffixExpression);
    System.out.println("计算的结果是:"+calculate(rpnList));


    }

    //将一个逆波兰表达式依次将数据和运算符放入到List集合中
    public static List<String> getListString(String suffixExpression) {
    //将suffixExpression按照空格分割
    String[] strings = suffixExpression.split(" ");
    List<String> list = new ArrayList<>();
    for (String string : strings) {
    list.add(string);
    }
    return list;
    }

    /**
    * 从左至右扫描,将3和4压入堆栈;
    * 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
    * 将5入栈;
    * 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
    * 将6入栈;
    * 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
    */
    public static int calculate(List<String> list) {
    //创建一个栈
    Stack<String> stack = new Stack<>();
    //遍历list
    for (String item : list) {
    //使用正则表达式取出数字
    if (item.matches("\\d+")) { //匹配的是多位数
    //入栈
    stack.push(item);
    } else {
    //弹出两个数 并运算 再入栈
    int num2 = Integer.parseInt(stack.pop());
    int num1 = Integer.parseInt(stack.pop());
    //运算
    int res = 0;
    if (item.equals("+")) {
    res = num1 + num2;
    } else if (item.equals("-")) {
    res = num1 - num2;
    } else if (item.equals("*")) {
    res = num1 * num2;
    } else if (item.equals("/")) {
    res = num1 / num2;
    }else {
    throw new RuntimeException("运算符有误!");
    }
    //把结果入栈
    stack.push(res + "");
    }
    }
    //最后留在stack中的数就是运算结果
    return Integer.parseInt(stack.pop());
    }
    }
    + +

    将中缀表达式转化为后缀表达式(逆波兰表达式)

    +

    解题的步骤:

    +

    1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;

    +

    2)从左至右扫描中缀表达式;

    +

    3)遇到操作数时,将其压s2;

    +

    4)遇到运算符时,比较其与s1栈顶运算符的优先级:

    +

    ​ (1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;

    +

    ​ (2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;

    +

    ​ (3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较

    +

    5)遇到括号时:
    (1) 如果是左括号“(”,则直接压入s1
    (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

    +

    6)重复步骤2至5,直到表达式的最右边

    +

    7)将s1中剩余的运算符依次弹出并压入s2

    +

    8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

    +

    image-20230511094011546

    +

    先将中缀表达式转化成list集合

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * 将中缀表达式转化成对应的list
    * @param expression 中缀表达式
    * @return 中缀表达式转化成的一个list集合
    */
    public static List<String> toInfixExpressionList(String expression) {
    //定义一个集合,存储中缀表达式对应的list集合
    List<String> list = new ArrayList<>();
    int i = 0;//这个是一个指针 用于遍历中缀表达式的字符串
    String str;//做多位数的拼接工作 ,因为我们要考虑多位数的情况
    char c;//每遍历一个字符就放入到c中
    do {
    if ((c = expression.charAt(i)) < 48 || (c = expression.charAt(i)) > 57) { //如果c是一个非数字的字符需要加入到ls中
    list.add(c + ""); //直接将这个字符添加到list集合中
    i++;
    } else {//如果是一个数 要考虑多位数的问题
    str = "";//先将str置空
    while (i < expression.length() && (c = expression.charAt(i)) >= 48 && (c = expression.charAt(i)) <= 57) {
    str += c;
    i++;
    }
    list.add(str);
    }
    } while (i < expression.length());
    return list;
    }
    + +

    将中缀表达式的集合转化成逆波兰表达式(后缀表达式)的集合

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    /**
    * 将中缀表达式对应的List转化成后缀表达式对应的list
    * tips:
    * 1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;
    * 2)从左至右扫描中缀表达式;
    * 3)遇到操作数时,将其压s2;
    * 4)遇到运算符时,比较其与s1栈顶运算符的优先级:
    * (1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    * (2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    * (3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较
    * 5)遇到括号时:
    * (1) 如果是左括号“(”,则直接压入s1
    * (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    * 6)重复步骤2至5,直到表达式的最右边
    * 7)将s1中剩余的运算符依次弹出并压入s2
    * 8)依次弹出s2中的元素并输出,**结果的逆序即为中缀表达式对应的后缀表达式**
    *
    * @param ls 中缀表达式对应的list
    * @return 后缀表达式对应的list
    */
    public static List<String> parseSuffixExpressionList(List<String> ls) {
    //定义栈
    Stack<String> s1 = new Stack<>();//符号栈 s1栈
    //说明:因为思路分析中使用的s2的栈在整个的运算的过程中没有进行pop的操作 我们直接使用list集合代替s2栈 (同时也为了方便后面逆序的输出)
    ArrayList<String> s2 = new ArrayList<>();//用于存储中间结果的list
    //遍历ls
    for (String item : ls) {
    //如果是一个数 就加入到s2栈
    if (item.matches("\\d+")) {//正则匹配
    s2.add(item);
    } else if (item.equals("(")) { //如果是s1的话就直接入符号栈
    s1.push(item);
    } else if (item.equals(")")) {
    //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    while (!s1.peek().equals("(")) {
    s2.add(s1.pop());
    }
    s1.pop();//将(弹出s1栈,这里的操作就是消除括号
    } else {//加减乘除的操作
    //当item的优先级小于栈顶的优先级,
    // 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
    while (s1.size() != 0 && getVal(s1.peek()) >= getVal(item)) {
    s2.add(s1.pop());
    }
    //还需要将item压入栈中
    s1.push(item);
    }
    }
    //将s1中剩余的运算符依次弹出并加入s2
    while (s1.size() != 0) {
    s2.add(s1.pop());
    }
    return s2;
    }
    + +

    通过逆波兰表达式(后缀表达式)的集合得到最终的计算结果

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    /**
    * 通过一个后缀表达式的list集合得到表达式最终的计算结果
    * @param list 后缀表达式的list集合
    * @return 根据后缀表达式的集合计算出的表达式的结果信息
    */
    public static int calculate(List<String> list) {
    //创建一个栈
    Stack<String> stack = new Stack<>();
    //遍历list
    for (String item : list) {
    //使用正则表达式取出数字
    if (item.matches("\\d+")) { //匹配的是多位数
    //入栈
    stack.push(item);
    } else {
    //弹出两个数 并运算 再入栈
    int num2 = Integer.parseInt(stack.pop());
    int num1 = Integer.parseInt(stack.pop());
    //运算
    int res = 0;
    if (item.equals("+")) {
    res = num1 + num2;
    } else if (item.equals("-")) {
    res = num1 - num2;
    } else if (item.equals("*")) {
    res = num1 * num2;
    } else if (item.equals("/")) {
    res = num1 / num2;
    } else {
    throw new RuntimeException("运算符有误!");
    }
    //把结果入栈
    stack.push(res + "");
    }
    }
    //最后留在stack中的数就是运算结果
    return Integer.parseInt(stack.pop());
    }
    + +

    完整的代码

    +

    (不支持小数)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    package com.atguigu.stack;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/11
    * @Description 逆波兰计算器的设计与实现
    */
    public class PolandNotation {
    public static void main(String[] args) {
    //先定义一个逆波兰表达式
    //(3+4)×5-6 的逆波兰表达式是 3 4 + 5 × 6 -
    //为了方便 逆波兰表达式的数字和符号有空格隔开
    String suffixExpression = "3 4 + 5 * 6 -";//中间有空格隔开
    //1.先将suffixExpression放入到List集合中
    //2.将List集合传递给一个方法 配合栈 完成计算
    List<String> rpnList = getListString(suffixExpression);
    System.out.println("计算的结果是:" + calculate(rpnList));

    //测试将中缀表达式转化成逆波兰表达式
    //完成一个将中缀表达式 转换为一个后缀表达式
    //1. 1+((2+3)×4)-5(中缀表达式) -> 1 2 3 + 4 × + 5 –(后缀表达式)
    //2. 将中缀表达式转化成List集合
    String expression = "1+((2+3)*4)-5";
    List<String> list = toInfixExpressionList(expression);
    System.out.println("中缀表达式对应的List:" + list);
    //3.将得到的中缀表达式对应的List 转换成一个逆波兰表达式(后缀表达式)对应的List
    List<String> stringList = parseSuffixExpressionList(list);
    System.out.println("中缀表达式对应的List:" + stringList);
    System.out.println("计算的结果:" + calculate(stringList));


    }

    /**
    * 输入一个运算符 返回对应的优先级
    */
    public static int getVal(String operation) {
    int res = 0;
    switch (operation) {
    case "+":
    case "-":
    res = 1;
    break;
    case "/":
    case "*":
    res = 2;
    break;
    default:
    break;
    }
    return res;
    }

    /**
    * 将中缀表达式对应的List转化成后缀表达式对应的list
    * tips:
    * 1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;
    * 2)从左至右扫描中缀表达式;
    * 3)遇到操作数时,将其压s2;
    * 4)遇到运算符时,比较其与s1栈顶运算符的优先级:
    * (1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    * (2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    * (3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较
    * 5)遇到括号时:
    * (1) 如果是左括号“(”,则直接压入s1
    * (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    * 6)重复步骤2至5,直到表达式的最右边
    * 7)将s1中剩余的运算符依次弹出并压入s2
    * 8)依次弹出s2中的元素并输出,**结果的逆序即为中缀表达式对应的后缀表达式**
    *
    * @param ls 中缀表达式对应的list
    * @return 后缀表达式对应的list
    */
    public static List<String> parseSuffixExpressionList(List<String> ls) {
    //定义栈
    Stack<String> s1 = new Stack<>();//符号栈 s1栈
    //说明:因为思路分析中使用的s2的栈在整个的运算的过程中没有进行pop的操作 我们直接使用list集合代替s2栈 (同时也为了方便后面逆序的输出)
    ArrayList<String> s2 = new ArrayList<>();//用于存储中间结果的list
    //遍历ls
    for (String item : ls) {
    //如果是一个数 就加入到s2栈
    if (item.matches("\\d+")) {//正则匹配
    s2.add(item);
    } else if (item.equals("(")) { //如果是s1的话就直接入符号栈
    s1.push(item);
    } else if (item.equals(")")) {
    //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    while (!s1.peek().equals("(")) {
    s2.add(s1.pop());
    }
    s1.pop();//将(弹出s1栈,这里的操作就是消除括号
    } else {//加减乘除的操作
    //当item的优先级小于栈顶的优先级,
    // 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
    while (s1.size() != 0 && getVal(s1.peek()) >= getVal(item)) {
    s2.add(s1.pop());
    }
    //还需要将item压入栈中
    s1.push(item);
    }
    }
    //将s1中剩余的运算符依次弹出并加入s2
    while (s1.size() != 0) {
    s2.add(s1.pop());
    }
    return s2;
    }

    /**
    * 将中缀表达式转化成对应的list
    *
    * @param expression 中缀表达式
    * @return 中缀表达式转化成的一个list集合
    */
    public static List<String> toInfixExpressionList(String expression) {
    //定义一个集合,存储中缀表达式对应的list集合
    List<String> list = new ArrayList<>();
    int i = 0;//这个是一个指针 用于遍历中缀表达式的字符串
    String str;//做多位数的拼接工作 ,因为我们要考虑多位数的情况
    char c;//每遍历一个字符就放入到c中
    do {
    if ((c = expression.charAt(i)) < 48 || (c = expression.charAt(i)) > 57) { //如果c是一个非数字的字符需要加入到ls中
    list.add(c + ""); //直接将这个字符添加到list集合中
    i++;
    } else {//如果是一个数 要考虑多位数的问题
    str = "";//先将str置空
    //这个只要遍历的有一个字符不是数字 就会退出while循环
    while (i < expression.length() && (c = expression.charAt(i)) >= 48 && (c = expression.charAt(i)) <= 57) {
    str += c;
    i++;
    }
    list.add(str);
    }
    } while (i < expression.length());
    return list;
    }


    //将一个逆波兰表达式依次将数据和运算符放入到List集合中
    public static List<String> getListString(String suffixExpression) {
    //将suffixExpression按照空格分割
    String[] strings = suffixExpression.split(" ");
    List<String> list = new ArrayList<>();
    for (String string : strings) {
    list.add(string);
    }
    return list;
    }

    /**
    * 通过一个后缀表达式的list集合得到表达式最终的计算结果
    *
    * @param list 后缀表达式的list集合
    * @return 根据后缀表达式的集合计算出的表达式的结果信息
    */
    public static int calculate(List<String> list) {
    //创建一个栈
    Stack<String> stack = new Stack<>();
    //遍历list
    for (String item : list) {
    //使用正则表达式取出数字
    if (item.matches("\\d+")) { //匹配的是多位数
    //入栈
    stack.push(item);
    } else {
    //弹出两个数 并运算 再入栈
    int num2 = Integer.parseInt(stack.pop());
    int num1 = Integer.parseInt(stack.pop());
    //运算
    int res = 0;
    if (item.equals("+")) {
    res = num1 + num2;
    } else if (item.equals("-")) {
    res = num1 - num2;
    } else if (item.equals("*")) {
    res = num1 * num2;
    } else if (item.equals("/")) {
    res = num1 / num2;
    } else {
    throw new RuntimeException("运算符有误!");
    }
    //把结果入栈
    stack.push(res + "");
    }
    }
    //最后留在stack中的数就是运算结果
    return Integer.parseInt(stack.pop());
    }
    }
    + +

    完整的逆波兰计算器,含小数点的计算

    +

    (老师的代码)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    package com.atguigu.stack;


    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Stack;
    import java.util.regex.Pattern;

    public class ReversePolishMultiCalc {

    /**
    * 匹配 + - * / ( ) 运算符
    */
    static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";

    static final String LEFT = "(";
    static final String RIGHT = ")";
    static final String ADD = "+";
    static final String MINUS= "-";
    static final String TIMES = "*";
    static final String DIVISION = "/";

    /**
    * 加減 + -
    */
    static final int LEVEL_01 = 1;
    /**
    * 乘除 * /
    */
    static final int LEVEL_02 = 2;

    /**
    * 括号
    */
    static final int LEVEL_HIGH = Integer.MAX_VALUE;


    static Stack<String> stack = new Stack<>();
    static List<String> data = Collections.synchronizedList(new ArrayList<String>());

    /**
    * 去除所有空白符
    * @param s
    * @return
    */
    public static String replaceAllBlank(String s ){
    // \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]
    return s.replaceAll("\\s+","");
    }

    /**
    * 判断是不是数字 int double long float
    * @param s
    * @return
    */
    public static boolean isNumber(String s){
    Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
    return pattern.matcher(s).matches();
    }

    /**
    * 判断是不是运算符
    * @param s
    * @return
    */
    public static boolean isSymbol(String s){
    return s.matches(SYMBOL);
    }

    /**
    * 匹配运算等级
    * @param s
    * @return
    */
    public static int calcLevel(String s){
    if("+".equals(s) || "-".equals(s)){
    return LEVEL_01;
    } else if("*".equals(s) || "/".equals(s)){
    return LEVEL_02;
    }
    return LEVEL_HIGH;
    }

    /**
    * 匹配
    * @param s
    * @throws Exception
    */
    public static List<String> doMatch (String s) throws Exception{
    if(s == null || "".equals(s.trim())) throw new RuntimeException("data is empty");
    if(!isNumber(s.charAt(0)+"")) throw new RuntimeException("data illeagle,start not with a number");

    s = replaceAllBlank(s);

    String each;
    int start = 0;

    for (int i = 0; i < s.length(); i++) {
    if(isSymbol(s.charAt(i)+"")){
    each = s.charAt(i)+"";
    //栈为空,(操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及是 ) 不能直接入栈
    if(stack.isEmpty() || LEFT.equals(each)
    || ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)){
    stack.push(each);
    }else if( !stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())){
    //栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈
    while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek()) ){
    if(calcLevel(stack.peek()) == LEVEL_HIGH){
    break;
    }
    data.add(stack.pop());
    }
    stack.push(each);
    }else if(RIGHT.equals(each)){
    // ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈
    while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())){
    if(LEVEL_HIGH == calcLevel(stack.peek())){
    stack.pop();
    break;
    }
    data.add(stack.pop());
    }
    }
    start = i ; //前一个运算符的位置
    }else if( i == s.length()-1 || isSymbol(s.charAt(i+1)+"") ){
    each = start == 0 ? s.substring(start,i+1) : s.substring(start+1,i+1);
    if(isNumber(each)) {
    data.add(each);
    continue;
    }
    throw new RuntimeException("data not match number");
    }
    }
    //如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个stack 添加到队列
    Collections.reverse(stack);
    data.addAll(new ArrayList<>(stack));

    System.out.println(data);
    return data;
    }

    /**
    * 算出结果
    * @param list
    * @return
    */
    public static Double doCalc(List<String> list){
    Double d = 0d;
    if(list == null || list.isEmpty()){
    return null;
    }
    if (list.size() == 1){
    System.out.println(list);
    d = Double.valueOf(list.get(0));
    return d;
    }
    ArrayList<String> list1 = new ArrayList<>();
    for (int i = 0; i < list.size(); i++) {
    list1.add(list.get(i));
    if(isSymbol(list.get(i))){
    Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
    list1.remove(i);
    list1.remove(i-1);
    list1.set(i-2,d1+"");
    list1.addAll(list.subList(i+1,list.size()));
    break;
    }
    }
    doCalc(list1);
    return d;
    }

    /**
    * 运算
    * @param s1
    * @param s2
    * @param symbol
    * @return
    */
    public static Double doTheMath(String s1,String s2,String symbol){
    Double result ;
    switch (symbol){
    case ADD : result = Double.valueOf(s1) + Double.valueOf(s2); break;
    case MINUS : result = Double.valueOf(s1) - Double.valueOf(s2); break;
    case TIMES : result = Double.valueOf(s1) * Double.valueOf(s2); break;
    case DIVISION : result = Double.valueOf(s1) / Double.valueOf(s2); break;
    default : result = null;
    }
    return result;

    }

    public static void main(String[] args) {
    //String math = "9+(3-1)*3+10/2";
    String math = "12.8 + (2 - 3.55)*4+10/5.0";
    try {
    doCalc(doMatch(math));
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }
    + +

    六.递归

    1.简单介绍

    递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得简洁

    +

    image-20230511123527496

    +

    2.入门案例

    image-20230511123245408

    +

    3.递归解决的问题

    1)各种数学问题如: 8皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google编程大赛)

    +

    2)各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.

    +

    3)将用栈解决的问题–>第归代码比较简洁

    +

    4.递归遵循的规则

    1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)

    +

    2)方法的局部变量是独立的,不会相互影响, 比如n变量

    +

    3)如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.

    +

    4)递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死龟了:)

    +

    5)当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

    +

    5.递归的实际应用

    5.1递归解决迷宫问题

    image-20230512092144712

    +

    代码实现

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    package com.atguigu.recursion;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/12
    * @Description 递归解决迷宫问题
    */
    public class MiGong {
    public static void main(String[] args) {
    //创建一个二维数组模拟迷宫
    //地图
    int[][] map = new int[8][7];
    //使用1表示墙的位置
    //把四周变成墙
    //将上下变成墙
    for (int i = 0; i < 7; i++) {
    map[0][i] = 1;
    map[7][i] = 1;
    }
    //将左右变成墙
    for (int i = 0; i < 8; i++) {
    map[i][0] = 1;
    map[i][6] = 1;
    }
    //设置自定义的墙的位置
    map[3][1] = 1;
    map[3][2] = 1;

    //输出地图
    showMap(map);
    //使用递归回溯给小球找路
    getWay(map, 1, 1);
    System.out.println("标识过的路");
    showMap(map);


    }

    /**
    * 遍历输出地图的信息
    *
    * @param map 地图组成的二维数组
    */
    public static void showMap(int[][] map) {
    for (int i = 0; i < map.length; i++) {
    for (int j = 0; j < map[i].length; j++) {
    System.out.print(map[i][j] + " ");
    }
    System.out.println();
    }
    }


    /**
    * 1.表示墙 2.表示通路 可以走 3.表示该位置已经走过 但是走不通
    * 在就走迷宫的时候我们要确定一个策略 比如下->右->上->左 如果该点走不通 再回溯
    * 使用递归的方法给小球找路
    *
    * @param map 传进来的地图信息
    * @param i 从哪个位置开始找
    * @param j 从哪个位置开始找
    * @return 是否找到路
    */
    public static boolean getWay(int[][] map, int i, int j) {
    if (map[6][5] == 2) { //说明通路已经找到了 就直接退出
    return true;
    } else {
    if (map[i][j] == 0) {//如果当前这个点还没有走过
    //按照策略走 下->右->上->左
    map[i][j] = 2;//先假定这个点可以走通
    if (getWay(map, i + 1, j)) { //向下走
    return true;
    } else if (getWay(map, i, j + 1)) {//向右走
    return true;
    } else if (getWay(map, i - 1, j)) {//向上走
    return true;
    } else if (getWay(map, i, j - 1)) {//向左走
    return true;
    } else {//四个方向都走不通的话说明不是一个通路 设置成false
    map[i][j] = 3;
    return false;
    }
    } else {//不为0 可以是1,2,3
    //可以理解为只走没有走过的
    return false;
    }

    }
    }


    }
    + + + +

    5.2 递归解决八皇后问题

    使用回溯算法解决 类似于穷举法 后期使用别的算法优化

    +

    问题介绍

    +

    image-20230512112050729

    +

    思路分析

    +

    image-20230512112456731

    +

    代码实现

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    package com.atguigu.recursion;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/13
    * @Description 八皇后问题的解题思路及代码实现
    */
    public class Queue8 {
    public static void main(String[] args) {
    Queue8 queue8 = new Queue8();
    queue8.check(0);
    }

    //定义一个max表示共有多少个皇后
    int max = 8;
    //定义一个一维数组 记录皇后在列上的位置 arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3}
    int[] array = new int[max];
    //记录摆法的次数
    int count = 0;

    /**
    * 编写一个方法 放置n个皇后
    * 会自己回溯
    */
    public void check(int n) {
    if (n == max) { //说明已经放到了第九个皇后了
    print(); //打印
    return;
    }
    //依次放入皇后 判断是否冲突
    for (int i = 0; i < max; i++) {
    //先把当前的这个皇后n,放到该行的第i列
    array[n] = i;
    //判断当前放置第n个皇后到i列时,与前面放置的皇后是否冲突
    if (judge(n)) { //不冲突
    //接着放n+1个皇后
    check(n + 1);
    }
    //如果冲突 就继续执行 array[n] = i 就皇后在本行后移一个位置
    //因为在循环里面 i++会自增 n会后移
    }
    }

    /**
    * 当我们放置了n个皇后之后 就去检测
    * 该皇后是否和前面已经摆放的皇后冲突
    *
    * @param n 表示第n个皇后
    */
    public boolean judge(int n) {
    for (int i = 0; i < n; i++) {
    // array[8] = {0 , 4, 7, 5, 2, 6, 1, 3} 再次注意这里数组记录的是每个皇后在列上的值
    //array[i] == array[i] 判断是不是在同一列
    //Math.abs(n - i) == Math.abs(array[n] - array[i]) 判断是不是在同一斜线上
    //自己的理解:(n -i) 相当于在行上的距离 array[n] - array[i]相当于在列上的距离 行距离等于列距离 说明二者在同一斜线上
    if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {//与前面放置的位置是否冲突
    return false;
    }
    }
    return true;
    }

    /**
    * 定义一个数组 打印皇后拜访的位置
    */
    public void print() {
    System.out.println("第" + (++count) + "中摆法");
    int[][] arr = new int[max][max];
    for (int i = 0; i < array.length; i++) {
    arr[i][array[i]] = 1;
    }
    for (int[] ints : arr) {
    for (int i : ints) {
    System.out.print(i + " ");
    }
    System.out.println();
    }
    }
    }
    + + + +

    七.排序算法

    十大经典的排序算法:菜鸟教程排序算法

    +

    1.介绍

    image-20230513115319799

    +

    2.时间复杂度

    度量时间复杂度的两种方法

    +

    事后统计法的不足:

    +
      +
    • 需要实际的运行程序 比较耗时
    • +
    • 受计算机硬件和软件的影响
    • +
    +

    image-20230513115606521

    +

    时间频度

    +

    ​ 基本的介绍:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)

    +

    时间复杂度介绍

    +

    image-20230515093520587

    +

    常见的时间复杂度

    +

    image-20230515093753893

    +

    3.空间复杂度

    image-20230515095247319

    +

    排序算法的时间和空间复杂度

    +

    image-20230516083738747

    +

    4.冒泡排序

    冒泡排序的时间复杂度:o(n^2)

    +

    同一台电脑 8万个数据 十几秒左右

    +

    4.1 基本介绍

    image-20230515100345339

    +

    4.2 图解

    image-20230515100754827

    +

    4.3 代码实现

    优化之前的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/15
    * @Description 冒泡排序
    */
    public class BubbleSort {
    public static void main(String[] args) {
    int arr[] = {3, 9, -1, 10, -2};
    BubbleSort.sort(arr);
    }

    public static void sort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
    int temp = 0; //临时变量
    for (int j = 0; j < arr.length - 1 - i; j++) {
    //如果前面的数字大于后面的数字就交换
    if (arr[j] > arr[j + 1]) {
    temp = arr[j];
    arr[j] = arr[j + 1];
    arr[j + 1] = temp;
    }
    }
    System.out.println("第" + (i + 1) + "次排序:" + Arrays.toString(arr));
    }
    System.out.println("排序之后的数组:" + Arrays.toString(arr));
    }
    }
    + +

    优化之后的代码(如果排序的过程中代码有序就不在排序)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/15
    * @Description 冒泡排序
    */
    public class BubbleSort {
    public static void main(String[] args) {
    int arr[] = {3, 9, -1, 10, -2};
    BubbleSort.sort(arr);
    }

    /**
    * 冒泡排序
    * @param arr 需要排序的数组
    */
    public static void sort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
    int temp = 0; //临时变量
    boolean flag = false; //表示是否进行过排序
    for (int j = 0; j < arr.length - 1 - i; j++) {
    //如果前面的数字大于后面的数字就交换
    if (arr[j] > arr[j + 1]) {
    flag = true;
    temp = arr[j];
    arr[j] = arr[j + 1];
    arr[j + 1] = temp;
    }
    }
    System.out.println("第" + (i + 1) + "次排序:" + Arrays.toString(arr));
    if(!flag){ //说明一次都没有交换 说明已经有序
    break;
    }
    }
    System.out.println("排序之后的数组:" + Arrays.toString(arr));
    }
    }

    + +

    image-20230516091218716

    +

    5.选择排序

    5.1 基本介绍

    同一台电脑 8万个数据 两秒左右

    +

    选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的

    +

    image-20230515105359552

    +

    5.2图解

    img

    +

    image-20230516083148055

    +

    5.3代码实现

    两种方法 一个是自己的 一个是老师的

    +

    使用老师的代码 老师的代码验证过

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/16
    * @Description 选择排序
    */
    public class SelectSort {
    public static void main(String[] args) {
    int[] arr = {101,34,119,1};
    int[] arr1 = {3, 9, -1, 10, -2};
    int[] arr2 = {3, 9, -1, 10, -2};
    System.out.println("自己的代码");
    SelectSort.selectSort(arr2);
    System.out.println("老师的代码");
    SelectSort.selectSortByTeacher(arr1);
    }

    /**
    * 选择排序
    * 自己写的 中间的过程和老师的不一样 不确定是不是选择排序
    */
    public static void selectSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
    for (int j = i + 1; j < arr.length; j++) {//从第一个数字开始 依次找到最小值
    int temp = 0;
    if(arr[i] > arr[j]) {
    temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
    }
    }
    System.out.println("第次"+(i+1)+"排序的结果:"+Arrays.toString(arr));
    }
    System.out.println("最终的结果:"+Arrays.toString(arr));
    }

    /**
    * 老师的代码
    */
    public static void selectSortByTeacher(int[] arr){

    for (int i = 0; i < arr.length - 1; i++) {
    int minIndex = i;
    int min = arr[i];
    for (int j = i + 1; j < arr.length; j++) {//从第一个数字开始 依次找到最小值
    if(min > arr[j]){
    min = arr[j];
    minIndex = j;
    }
    }
    //交换
    arr[minIndex] = arr[i];
    arr[i] = min;
    System.out.println("第次"+(i+1)+"排序的结果:"+Arrays.toString(arr));
    }
    System.out.println("最终的结果:"+Arrays.toString(arr));
    }
    }
    + +

    image-20230516093143656

    +

    6.插入排序

    6.1 基本介绍

    同一台电脑 8万个数据 五秒左右

    +

    插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。

    +

    image-20230516100256880

    +

    6.2 图解

    img + +

    6.3 代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/16
    * @Description 插入排序
    */
    public class InsertSort {
    public static void main(String[] args) {
    int[] arr = {101, 34, 119, 1,-1,89};
    System.out.println("插入前的数组:"+Arrays.toString(arr));
    InsertSort.insertSort(arr);
    }

    /**
    * 插入排序
    */
    public static void insertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
    //定义待插入的数
    int insertVal = arr[i];
    //待插入的数的前一个数的下标
    int insertIndex = i - 1;
    while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
    arr[insertIndex + 1] = arr[insertIndex];
    insertIndex--;
    }
    //退出循环之后 代表找到了要插入数据的位置
    //插入数据
    arr[insertIndex + 1] = insertVal;
    System.out.println("第" + i + "次插入的结果:" + Arrays.toString(arr));
    }
    System.out.println("最终的结果:" + Arrays.toString(arr));
    }
    }
    + +

    image-20230516105517026

    +

    7.希尔排序

    7.1基本介绍

    同一台电脑 8万个数据 十七秒左右(交换法)

    +

    同一台电脑 8万个数据 一秒左右(移动法)

    +

    image-20230517091741355

    +

    image-20230517091841366

    +

    7.2.图解

    img

    +image-20230517102649272 + +

    7.3 代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/17
    * @Description 希尔排序
    */
    public class ShellSort {
    public static void main(String[] args) {
    int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
    //shellSort(arr);
    shellSort2(arr);
    }

    /**
    * 希尔排序 使用的是交换法 这个很慢 比直接的插入排序还慢
    */
    public static void shellSort(int[] arr) {
    int temp = 0;
    int count = 0;
    //用于分组 10个数据 第一次分5组 第二次分2组 第三次分1组 每组分别插入排序
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
    for (int i = gap; i < arr.length; i++) {
    for (int j = i - gap; j >= 0; j -= gap) {
    //如果当前的那个元素大于加上步长后的那个元素,就交换
    if (arr[j] > arr[j + gap]) {
    temp = arr[j];
    arr[j] = arr[j + gap];
    arr[j + gap] = temp;
    }
    }
    }
    System.out.println("排序第" + (++count) + "轮的结果:" + Arrays.toString(arr));
    }
    System.out.println("最终的结果:" + Arrays.toString(arr));
    }

    /**
    * 使用移动法的希尔排序 这个更快
    */
    public static void shellSort2(int[] arr) {
    //用于分组 10个数据 第一次分5组 第二次分2组 第三次分1组 每组分别插入排序
    int count = 0;
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
    for (int i = gap; i < arr.length; i++) {
    int j = i;
    int temp = arr[j];
    if (arr[j] < arr[j - gap]) {
    while (j - gap >= 0 && temp < arr[j - gap]) {
    arr[j] = arr[j - gap];
    j -= gap;
    }
    arr[j] = temp; }
    }
    System.out.println("排序第" + (++count) + "轮的结果:" + Arrays.toString(arr));
    }
    System.out.println("最终的结果:" + Arrays.toString(arr));
    }
    }
    + +

    8.快速排序

    8.1 基本介绍

    同一台电脑 8万个数据 不到一秒 800万个数据两秒钟

    +

    快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

    +

    8.2 图解

    img + +image-20230517103139900 + +

    8.3 代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/17
    * @Description 快速排序
    */
    public class QuickSort {
    public static void main(String[] args) {
    int[] arr = {-9, 78, 0, 23, -567, 70};
    quickSort(arr, 0, arr.length - 1);
    System.out.println(Arrays.toString(arr));
    }

    /**
    * @param arr 需要排序的数组
    * @param left 左下标
    * @param right 右下标
    */
    public static void quickSort(int[] arr, int left, int right) {
    int l = left;
    int r = right;
    //pivot 中轴
    int pivot = arr[(left + right) / 2];
    int temp = 0;
    //while循环的目的是让比pivot小的放到左边 大的放在右边
    while (l < r) {
    //在pivot的左边一直找 找到大于等于pivot的值 才退出
    while (arr[l] < pivot) {
    l += 1;
    }
    //在pivot的左边一直找 找到大于等于pivot的值 才退出
    while (arr[r] > pivot) {
    r -= 1;
    }
    //如果l >= r成立 说明pivot的左右两边的值 已经是按照左边全部是
    //小于等于pivot值 右边全部是大于等于pivot值
    if (l >= r) {
    break;
    }
    //交换
    temp = arr[l];
    arr[l] = arr[r];
    arr[r] = temp;
    //如果交换完之后发现arr[l] = pivot值
    if (arr[l] == pivot) {
    r--;
    }
    //如果交换完之后发现arr[r] = pivot值
    if (arr[r] == pivot) {
    l++;
    }
    }

    //如果l == r 必须l++ r--
    if (l == r) {
    l += 1;
    r -= 1;
    }
    //向左递归
    if (left < r) {//左边的数全部有序
    quickSort(arr, left, r);
    }
    //向右递归
    if (right > l) {
    quickSort(arr, l, right);
    }
    }
    }
    + + + +

    9. 归并排序

    9.1基本介绍

    同一台电脑 8万个数据 大约一秒钟 800万个数据三秒

    +

    image-20230518111233009

    +

    9.2 图解

    img + +

    image-20230518111454775

    +

    image-20230518111536026

    +

    9.3代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/18
    * @Description 归并排序
    */
    public class MergeSort {
    public static void main(String[] args) {
    int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
    int[] temp = new int[arr.length];
    mergeSort(arr,0, arr.length -1,temp);
    System.out.println("归并排序之后的数组:"+ Arrays.toString(arr));
    }

    /**
    * 分治的过程
    */
    public static void mergeSort(int[] arr, int left, int right, int[] temp){
    if(left < right){
    int mid = (left + right) / 2;
    //向左递归分解
    mergeSort(arr,left,mid,temp);
    //向右递归分解
    mergeSort(arr,mid+1,right,temp);
    //合并
    merge(arr,left,mid,right,temp);
    }
    }

    /**
    * 合并的方法 治
    *
    * @param arr 需要排序的数组
    * @param left 右边有序序列的初始索引
    * @param mid 中间索引
    * @param right 右边索引
    * @param temp 中转的数组
    */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
    int i = left;//左边有序序列的初始索引
    int j = mid + 1;//右边有序序列的初始化索引
    int t = 0;//指向temp数组的当前索引

    //1.
    //先把左右两边(有序)的数据填充到temp数组
    //直到左右两边的数据有一边处理完毕为止
    while (i <= mid && j <= right) {//相当于左右两边的数组都有一个指针
    if (arr[i] <= arr[j]) {
    temp[t] = arr[i];
    t++;
    i++;
    } else {//反之将右边有序序列的当前元素 填充到temp数组
    temp[t] = arr[j];
    t++;
    j++;
    }
    }
    //2.
    //把有剩余数据的一边的数据依次全部填充到temp
    while (i <= mid) {//说明左边的有序序列还有剩余的元素
    temp[t] = arr[i];
    t++;
    i++;
    }
    while (j <= right) {
    temp[t] = arr[j];
    t++;
    j++;
    }

    //3.
    //将temp数组的元素拷贝到arr
    //并不是每次都拷贝
    t = 0;
    int tempLeft = left;
    while (tempLeft <= right) {
    arr[tempLeft] = temp[t];
    t++;
    tempLeft++;
    }
    }
    }
    + +

    10.基数排序

    同一台电脑8万个数据 一秒左右 8千万个数据会报内存不足

    +

    10.1 基本介绍

    image-20230525195008102

    +

    image-20230526094945578

    +

    image-20230526113739081

    +

    10.2 图解

    img

    +

    10.3 代码实现

    推导代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/26
    * @Description 基数排序
    */
    public class RadixSort {
    public static void main(String[] args) {
    //定义一个待排序的数组
    int arr[] = {53, 3, 542, 748, 14, 214};
    RadixSort.radixSort(arr);
    }

    //基数排序
    public static void radixSort(int[] arr) {
    //1.第一轮(针对每个元素的个位进行排序处理)
    //定义一个二维数组,表示十个桶,每个桶就是一个一维数组
    //为了防止数据的溢出,这里每个桶的大小我们设置的大一些 大小定为arr.length
    int[][] bucket = new int[10][arr.length];
    //为了记录每个桶中实际存放了多少个数据,我们定义一个一维数组来记录每个桶中依次放入数据的个数
    //bucketElementCounts[0]记录的就是第0个桶的数据的个数
    int[] bucketElementCounts = new int[10];
    for (int j = 0; j < arr.length; j++) {
    //取出每个元素的个位数的值
    int digitOfElement = arr[j] % 10;
    //放入对应的桶中
    bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
    //[bucketElementCounts[digitOfElement]]++的意思是在digitOfElement对应的桶中数据个数加一
    bucketElementCounts[digitOfElement]++;
    }
    //按照这个桶中顺序取出数据
    int index = 0;
    for (int k = 0; k < bucketElementCounts.length; k++) {
    //如果桶中有数据,我们才放入到原数组
    if (bucketElementCounts[k] != 0) {
    //循环该桶即第K个桶,放入
    for (int l = 0; l < bucketElementCounts[k]; l++) {
    arr[index++] = bucket[k][l];
    }
    }
    bucketElementCounts[k] = 0;
    }
    System.out.println("第一轮:" + Arrays.toString(arr));

    //2.第2轮
    for (int j = 0; j < arr.length; j++) {
    //取出每个元素的十位数的值
    int digitOfElement = arr[j] / 10 % 10;
    //放入对应的桶中
    bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
    //[bucketElementCounts[digitOfElement]]++的意思是在digitOfElement对应的桶中数据个数加一
    bucketElementCounts[digitOfElement]++;
    }
    //按照这个桶中顺序取出数据
    index = 0;
    for (int k = 0; k < bucketElementCounts.length; k++) {
    //如果桶中有数据,我们才放入到原数组
    if (bucketElementCounts[k] != 0) {
    //循环该桶即第K个桶,放入
    for (int l = 0; l < bucketElementCounts[k]; l++) {
    arr[index++] = bucket[k][l];
    }
    }
    bucketElementCounts[k] = 0;
    }
    System.out.println("第2轮:" + Arrays.toString(arr));

    //3.第3轮
    for (int j = 0; j < arr.length; j++) {
    //取出每个元素的百位数的值
    int digitOfElement = arr[j] / 100 % 10;
    //放入对应的桶中
    bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
    //[bucketElementCounts[digitOfElement]]++的意思是在digitOfElement对应的桶中数据个数加一
    bucketElementCounts[digitOfElement]++;
    }
    //按照这个桶中顺序取出数据
    index = 0;
    for (int k = 0; k < bucketElementCounts.length; k++) {
    //如果桶中有数据,我们才放入到原数组
    if (bucketElementCounts[k] != 0) {
    //循环该桶即第K个桶,放入
    for (int l = 0; l < bucketElementCounts[k]; l++) {
    arr[index++] = bucket[k][l];
    }
    }
    }
    System.out.println("第3轮:" + Arrays.toString(arr));

    }
    }
    + +

    最终代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/26
    * @Description 基数排序
    */
    public class RadixSort {
    public static void main(String[] args) {
    //定义一个待排序的数组
    int arr[] = {53, 3, 542, 748, 14, 214};
    RadixSort.radixSort(arr);
    }

    //基数排序
    public static void radixSort(int[] arr) {
    //得到数组中最大数的位数
    int max = arr[0];
    for (int i = 0; i < arr.length; i++) {
    if (arr[i] > max) {
    max = arr[i];
    }
    }
    //最大数的位数
    int maxLength = (max + "").length();
    //定义一个二维数组,表示十个桶,每个桶就是一个一维数组
    int[][] bucket = new int[10][arr.length];
    //为了记录每个桶中实际存放了多少个数据,我们定义一个一维数组来记录每个桶中依次放入数据的个数
    int[] bucketElementCounts = new int[10];
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {//循环的是每一轮,第一次是个位。第二次是百位
    for (int j = 0; j < arr.length; j++) {
    //取出每个元素的对应位数的值
    int digitOfElement = arr[j] / n % 10;
    //放入对应的桶中
    bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
    //[bucketElementCounts[digitOfElement]]++的意思是在digitOfElement对应的桶中数据个数加一
    bucketElementCounts[digitOfElement]++;
    }
    //按照这个桶中顺序取出数据
    int index = 0;
    for (int k = 0; k < bucketElementCounts.length; k++) {
    //如果桶中有数据,我们才放入到原数组
    if (bucketElementCounts[k] != 0) {
    //循环该桶即第K个桶,放入
    for (int l = 0; l < bucketElementCounts[k]; l++) {
    arr[index++] = bucket[k][l];
    }
    }
    bucketElementCounts[k] = 0;
    }
    System.out.println("第" + (i + 1) + "轮:" + Arrays.toString(arr));
    }
    }
    }
    + +

    11.常用排序算法总结和对比

    image-20230526114127402

    +

    八.查找算法

    1.简单介绍

    image-20230526115027518 + +

    2.线性查找算法

    2.1代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package com.atguigu.search;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/29
    * @Description 线性查找
    */
    public class SeqSearch {
    public static void main(String[] args) {
    int[] arr = {1,9,11,-1,34,89};//没有顺序的数组
    System.out.println("目标值的下标:"+seqSearch(arr, 11));
    }

    /**
    * 找到一个满足条件的值就返回
    * 线性查找是逐一比对,发现有相同的值时就返回这个值的下标
    * @param arr 需要在这里面找目标值的数组
    * @param value 目标值
    * @return 目标值的下标
    */
    public static int seqSearch(int[] arr,int value){
    for (int i = 0; i < arr.length; i++) {
    if(arr[i] == value){
    return i;
    }
    }
    return -1;
    }
    }

    + +

    3.二分查找算法

    3.1 图解

    image-20230529104112317 + +

    3.2 代码实现

    递归的方式解决

    +

    基本的写法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package com.atguigu.search;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/29
    * @Description 二分查找
    */
    public class BinarySearch {
    public static void main(String[] args) {
    int[] arr = {1, 8, 10, 89, 1000, 1234};
    System.out.println("目标值的下标:" + binarySearch(arr, 0, arr.length - 1, 3));
    }

    /**
    * 二分查找
    *
    * @param arr 需要在这里面查找目标值的数组
    * @param left 左边的索引
    * @param right 右边的索引
    * @param findVal 要查找的值
    * @return 目标值在数组中的下标,找到就返回下标,没有就返回-1
    */
    public static int binarySearch(int[] arr, int left, int right, int findVal) {
    //结束递归的条件
    if (left > right) {
    return -1;
    }
    int mid = (left + right) / 2;
    int midVal = arr[mid];
    if (findVal > midVal) {//向右递归
    return binarySearch(arr, mid + 1, right, findVal);
    } else if (findVal < midVal) {//向左递归
    return binarySearch(arr, left, mid - 1, findVal);
    } else {
    return mid;
    }
    }

    }
    + +

    完整的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    package com.atguigu.search;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/29
    * @Description 二分查找
    */
    public class BinarySearch {
    public static void main(String[] args) {
    int[] arr = {1, 8, 10, 89, 1000, 1234};
    System.out.println("目标值的下标:" + binarySearch(arr, 0, arr.length - 1, 3));
    int[] arr2 = {1, 8, 10, 89, 89, 89, 1000, 1234};
    System.out.println("目标值在数组中下标的集合:" + binarySearch2(arr2, 0, arr2.length - 1, 89));
    }

    /**
    * 二分查找
    *
    * @param arr 需要在这里面查找目标值的数组
    * @param left 左边的索引
    * @param right 右边的索引
    * @param findVal 要查找的值
    * @return 目标值在数组中的下标,找到就返回下标,没有就返回-1
    */
    public static int binarySearch(int[] arr, int left, int right, int findVal) {
    //结束递归的条件
    if (left > right) {
    return -1;
    }
    int mid = (left + right) / 2;
    int midVal = arr[mid];
    if (findVal > midVal) {//向右递归
    return binarySearch(arr, mid + 1, right, findVal);
    } else if (findVal < midVal) {//向左递归
    return binarySearch(arr, left, mid - 1, findVal);
    } else {
    return mid;
    }
    }

    /**
    * 二分查找查找出所有与目标值相同的数组的下标
    *
    * @param arr 需要在这里面查找目标值的数组
    * @param left 左边的索引
    * @param right 右边的索引
    * @param findVal 要查找的的值
    * @return 目标值在数组中的下标的集合
    */
    public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {
    //结束递归的条件
    if (left > right) {
    return new ArrayList<>();
    }
    int mid = (left + right) / 2;
    int midVal = arr[mid];
    if (findVal > midVal) {//向右递归
    return binarySearch2(arr, mid + 1, right, findVal);
    } else if (findVal < midVal) {//向左递归
    return binarySearch2(arr, left, mid - 1, findVal);
    } else {
    List<Integer> resList = new ArrayList<>();
    int temp = mid - 1;
    while (true) { //向左边探测相同值的元素
    if (temp < 0 || arr[temp] != findVal) {
    break;
    }
    //否则就把temp放入到集合中
    resList.add(temp);
    temp--;//左移
    }
    resList.add(mid);
    //向右边探测
    temp = mid + 1;
    while (true) { //向右边探测相同值的元素
    if (temp > arr.length - 1 || arr[temp] != findVal) {
    break;
    }
    //否则就把temp放入到集合中
    resList.add(temp);
    temp++;//右移
    }
    return resList;
    }
    }

    }
    + +

    4.插值查找算法

    4.1 图解

    +image-20230530101803392 + +

    4.2 代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    package com.atguigu.search;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/30
    * @Description 插值查找算法
    */
    public class InsertValueSearch {
    public static void main(String[] args) {
    //创建一个数组 用于模拟需要查找数据的数组
    int[] arr = new int[100];
    for (int i = 0; i < 100; i++) {
    arr[i] = i + 1;
    }
    System.out.println(Arrays.toString(arr));
    System.out.println("查找的数的下标为:" + insertValueSearch(arr, 0, arr.length - 1, 30));
    }

    /**
    * 插值查找算法
    *
    * @param arr 目标数组(有序的)等差序列的最好,1-100有序的数组,找一个数只用找一次就行了
    * @param left 左边索引
    * @param right 右边索引
    * @param findVal 需要查找的值
    * @return 查找的值在数组中的索引
    */
    public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
    System.out.println("方法被调用了");
    //退出的条件和防止数组越界
    if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
    return -1;
    }
    //求出mid,这个是插值查找算法的灵魂,自适应的查找
    int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
    int midVal = arr[mid];
    if (findVal > midVal) { //应向右递归
    return insertValueSearch(arr, mid + 1, right, findVal);
    } else if (findVal < midVal) {//向左边递归
    return insertValueSearch(arr, left, mid - 1, findVal);
    } else {
    return mid;
    }
    }
    }
    + +

    4.3 注意事项

    image-20230530105652529

    +

    5.斐波那契(黄金分割法)查找算法

    5.1 基本介绍

    image-20230530110145048 + +

    5.2 原理介绍

    image-20230530110511769

    +
    +

    PDF笔记

    + +
    + +
    + + + + + + + + + + + + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    目录
    1. 1. 一.经典算法问题
    2. 2. 二.数据结构与算法的概述
      1. 2.1. 2.1 数据结构与算法的关系
      2. 2.2. 2.2解决实际的问题
      3. 2.3. 2.3 数据结构
    3. 3. 三.稀疏数组和队列
      1. 3.1. 3.1稀疏数组
        1. 3.1.1. 3.1.1 基本的介绍
        2. 3.1.2. 3.1.2使用的场景
        3. 3.1.3. 3.1.3图解
        4. 3.1.4. 3.1.5实现的思路
        5. 3.1.5. 3.1.5代码实现
      2. 3.2. 3.2队列
        1. 3.2.1. 3.2.1基本的介绍
        2. 3.2.2. 3.2.2代码实现
        3. 3.2.3. 3.2.3 数组模拟环形队列
    4. 4. 四.链表
      1. 4.1. 4.1 链表(Linked List)介绍
      2. 4.2.
      3. 4.3. 4.2单链表的应用实例(CRUD)
        1. 4.3.1. 4.2.1向单链表中添加数据
        2. 4.3.2. 4.2.2修改单链表中节点的方法
        3. 4.3.3. 4.2.3删除单链表中节点的方法
        4. 4.3.4. 4.2.4完整代码
        5. 4.3.5. 4.2.5 单链表的面试题(新浪,百度,腾讯)
          1. 4.3.5.1. 1.求单链表中有效节点的个数
          2. 4.3.5.2. 2.查找单链表中的倒数第K个节点(新浪面试题)
          3. 4.3.5.3. 3.单链表的反转(腾讯面试题)
          4. 4.3.5.4. 4.从尾到头打印单链表(百度面试题)
          5. 4.3.5.5. 5.合并两个有序的单链表,合并之后的链表依然有序
          6. 4.3.5.6. 全部代码
      4. 4.4. 4.3 双向链表
      5. 4.5. 4.4 单向环形链表的应用场景(Josephu问题)
    5. 5. 五.栈
      1. 5.1. 1.介绍
      2. 5.2. 2.应用场景
      3. 5.3. 3.图解
      4. 5.4. 4.使用数组模拟栈
      5. 5.5. 5.栈实现综合计算器
      6. 5.6. 6.逆波兰计算器的设计与实现
    6. 6. 六.递归
      1. 6.1. 1.简单介绍
      2. 6.2. 2.入门案例
      3. 6.3. 3.递归解决的问题
      4. 6.4. 4.递归遵循的规则
      5. 6.5. 5.递归的实际应用
        1. 6.5.1. 5.1递归解决迷宫问题
        2. 6.5.2. 5.2 递归解决八皇后问题
    7. 7. 七.排序算法
      1. 7.1. 1.介绍
      2. 7.2. 2.时间复杂度
      3. 7.3. 3.空间复杂度
      4. 7.4. 4.冒泡排序
        1. 7.4.1. 4.1 基本介绍
        2. 7.4.2. 4.2 图解
        3. 7.4.3. 4.3 代码实现
      5. 7.5. 5.选择排序
        1. 7.5.1. 5.1 基本介绍
        2. 7.5.2. 5.2图解
        3. 7.5.3. 5.3代码实现
      6. 7.6. 6.插入排序
        1. 7.6.1. 6.1 基本介绍
        2. 7.6.2. 6.2 图解
        3. 7.6.3. 6.3 代码实现
      7. 7.7. 7.希尔排序
        1. 7.7.1. 7.1基本介绍
        2. 7.7.2. 7.2.图解
        3. 7.7.3. 7.3 代码实现
      8. 7.8. 8.快速排序
        1. 7.8.1. 8.1 基本介绍
        2. 7.8.2. 8.2 图解
        3. 7.8.3. 8.3 代码实现
      9. 7.9. 9. 归并排序
        1. 7.9.1. 9.1基本介绍
        2. 7.9.2. 9.2 图解
        3. 7.9.3. 9.3代码实现
      10. 7.10. 10.基数排序
        1. 7.10.1. 10.1 基本介绍
        2. 7.10.2. 10.2 图解
        3. 7.10.3. 10.3 代码实现
      11. 7.11. 11.常用排序算法总结和对比
    8. 8. 八.查找算法
      1. 8.1. 1.简单介绍
      2. 8.2. 2.线性查找算法
        1. 8.2.1. 2.1代码实现
      3. 8.3. 3.二分查找算法
        1. 8.3.1. 3.1 图解
        2. 8.3.2. 3.2 代码实现
      4. 8.4. 4.插值查找算法
        1. 8.4.1. 4.1 图解
        2. 8.4.2. 4.2 代码实现
        3. 8.4.3. 4.3 注意事项
      5. 8.5. 5.斐波那契(黄金分割法)查找算法
        1. 8.5.1. 5.1 基本介绍
        2. 8.5.2. 5.2 原理介绍
    9. 9. PDF笔记
    最近更新
    \ No newline at end of file diff --git a/posts/432.html b/posts/432.html new file mode 100644 index 000000000..085ad2d49 --- /dev/null +++ b/posts/432.html @@ -0,0 +1,345 @@ +前端基础知识 | The Blog + + + + + + + + + + + + +

    前端基础知识

    一.VSCode的使用

    常用插件

    image-20230521233651126

    +

    二. ES6

    菜鸟ES6教程教程

    +

    1.简介

    ​ ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。

    +

    2.基础语法

    img + +

    2.1 声明变量

      +
    • let 声明的变量有严格的作用域 var没有严格的作用域
    • +
    • let变量只能声明一次
    • +
    • let不存在变量提升 var存在变量提升(先使用后声明)
    • +
    • const声明的是常量,不能重新赋值
    • +
    • 总计:使用let声明变量
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>声明变量</title>
    </head>

    <body>

    <script>
    //1.有严格的作用域
    //var 声明的变量往往会越域
    //let 声明的变量有严格的作用域
    {
    var a = 1;
    let b = 1;
    }
    console.log(a) //不会报错
    console.log(b) //ReferenceError: b is not defined,有严格的作用域,所以会报错



    //2.let的变量只能声明一次
    var m = 1
    var m = 2
    let n = 3
    // let n = 4
    console.log(m) //不会报错
    console.log(n) //Identifier 'n' has already been declared 不能声明两次



    //3.let不存在变量提升 var存在变量提升
    console,log(x)
    var x = 10; //这里不会报错,可以先使用后定义


    //4.const声明的是常量,不能重新赋值

    //总计:使用let声明变量

    </script>
    </body>
    </html>
    + +

    2.2 解构表达式和模板字符串

      +
    • 数组的解构
    • +
    • 对象的解构
    • +
    • 字符串API的扩展
    • +
    • 模板字符串
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>解构表达式</title>
    </head>

    <body>
    <script>
    //解构表达式
    //1.原始的数组的赋值方式
    let arr = [1, 2, 3]
    // let a = arr[0]
    // let b = arr[1]
    // let c = arr[2]

    //2.使用解构表达式给数组赋值(数组解构)
    let [a, b, c] = arr;
    console.log(a, b, c);

    //3.使用解构表达式给对象赋值(对象解构)
    const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
    }
    //原始的赋值方式
    // const name = person.name
    // const age = person.age
    // const language = person.language

    //使用解构赋值(对象解构)
    //const { name, age, language } = person
    //更换属性名
    const { name: abc, age, language } = person
    //将属性名name更换为abc
    console.log(abc, age, language)


    //4.字符串扩展
    //几个新的字符串API
    let str = "hello.vue"
    //是不是以什么开头
    console.log(str.startsWith("hello"));//true
    //是不是以什么结束
    console.log(str.endsWith(".vue"));//true
    //是不是包含什么
    console.log(str.includes("e"));//true
    console.log(str.includes("hello"));//true


    //5.模板字符串
    //${}插值
    let name = "Tom"
    let result = `你好${name}`
    console.log(result)
    //可以直接调用字符串的返回值组成一个新的字符串
    function fun(){
    return "这是一个函数"
    }
    result = `你好${name},我想说:${fun()}`
    console.log(result)

    </script>
    </body>
    </html>
    + +

    2.3 函数优化

      +
    • 默认初始值
    • +
    • 不定参数
    • +
    • 箭头函数
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>函数优化</title>
    </title>
    </head>

    <body>
    <script>
    //1.函数优化
    //在ES6以前,我们无法给一个函数参数设置默认值,只能采用变通的写法
    function add(a, b) {
    //判断b是否为空 如果为空就给默认值1
    b = b || 1
    return a + b
    }
    //传一个参数
    console.log(add(10)) //结果是11

    //优化后 直接在参数上写上默认的值 如果没有传就会自动使用默认值
    function add2(a, b = 1) {
    return a + b
    }
    console.log(add2(20)) //结果是21


    //2.不定参数 类似于java里面的可变形参
    function fun(...values) {
    console.log(values.length)
    }
    fun(1, 2)
    fun(1, 2, 3)
    fun(1, 2, 3, 4)


    //3.箭头函数
    //以前的声明方法
    var print = function (obj) {
    console.log(obj)
    }
    var print1 = obj => console.log(obj)
    print1("hello") //输出hello

    //求两数之和
    //以前的方法
    var sum = function (a, b) {
    return a + b
    }
    //使用箭头函数
    var sum2 = (a, b) => a + b //方法体中只有一行代码 不用写return
    console.log(sum2(11, 3))

    var sum3 = (a, b) => {//方法体中有多行代码 写法如下
    c = a + b;
    return a + c
    }
    console.log(sum3(11, 3))

    //复杂一点的箭头函数
    const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
    }
    //原始的方法
    function hello(person) {
    console.log("hello " + person.name)
    }

    //单纯的箭头函数
    var hello2 = (obj) => console.log("hello," + obj.name)
    hello2(person)

    //箭头函数加解构表达式
    var hello3 = ({name}) => console.log("hello," + name)
    hello3(person)
    </script>
    </body>

    </html>
    + +

    2.4 对象优化

      +
    • 对象新增的API
    • +
    • 声明对象的简写方式
    • +
    • 对象函数属性的简写
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>对象优化</title>
    </head>

    <body>
    <script>
    //1.对象相关的API
    //定义一个person对象
    const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
    }
    //测试对象新增的API
    //返回对象所有的属性信息
    console.log(Object.keys(person)) //['name', 'age', 'language']
    //返回对象中vaules
    console.log(Object.values(person))// ['jack', 21, Array(3)]
    //返回key-vaues
    console.log(Object.entries(person))// [Array(2), Array(2), Array(2)]


    //将source1和source2的属性合并到target中
    const target = { a: 1 }
    const source1 = { b: 2 }
    const source2 = { c: 3 }
    //将source1和source2中的属性合并在一起
    Object.assign(target, source1, source2)
    console.log(target) //{a: 1, b: 2, c: 3}


    //2.声明对象的简写方式
    //声明两个变量
    const age = 23;
    const name = "张三";
    //原始的方式然后声明一个对象,属性值为这两个变量的值
    const person1 = { age: age, name: name }
    console.log(person1)

    //ES6的方式
    const person2 = { age, name }// 属性名和属性值代表的变量的名字一样可以省写
    console.log(person2)

    //3.对象函数属性的简写
    let person3 = {
    name: "jack",//对象的属性
    //以前的语法格式
    eat: function (food) {//对象的方法
    console.log(this.name + "在吃" + food)
    },

    //使用箭头有函数简写 箭头函数中this.name无法使用
    eat2: food => console.log(person3.name + "在吃" + food),

    eat3(food) {
    console.log(this.name + "在吃" + food)
    }
    }
    person3.eat("香蕉")
    person3.eat2("苹果")
    person3.eat3("梨子")

    //4.对象的扩展运算符
    //拷贝对象(深拷贝)
    //将person01的值拷贝给someone
    let person01 = { name: "Amy", age: 15 }
    let someone = { ...person01 }
    console.log(someone)

    //合并对象
    let age1 = { age: 15 }
    let name1 = { name: "Amy" }
    let person03 = { ...age1, ...name1 }//两个对象属性的合并一个对象中,如果有相同的值,后一个覆盖前面的那一个属性的值
    console.log(person03)//{age: 15, name: 'Amy'}

    </script>
    </body>

    </html>
    + +

    2.5 数组的map、reduce方法

      +
    • map方法
    • +
    • reduce方法
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>map和reduce</title>
    </head>

    <body>
    <script>
    //数组中新增了map和reduce方法
    //map():接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回
    let arr = ['1', '20', '-5', '3']
    //需求:将原数组中的每一个值乘以2并返回
    //第一种方法
    arr1 = arr.map((item) => {//item为数组中的每一个元素
    return item * 2
    })
    console.log(arr1)
    //第二种方法
    arr2 = arr.map(item => item * 2)
    console.log(arr2)

    //reduce()方法:为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素
    arr3 = [2, 40, -10, 6]
    //arr.reduce(callback,[initialValue]) //可以传一个回调函数callback,也可以传一个初始值initialValue
    //传入的是回调函数 可以传入的值有
    /**
    * previousValue (上一次调用回调返回的值,或者是提供的初始值initialValue)
    * currentValue (数组中当前被处理的元素)
    * index (当前元素在数组中的索引)
    * array (调用reduce的数组)
    * */
    let result = arr3.reduce((a, b) => {//这个方法相当于求数组中每一个元素的和
    console.log("上一次处理后的值:" + a)
    console.log("当前处理的值:" + b)
    return a + b;
    })
    console.log(result)
    /**结果
    * 上一次处理后的值:2
    当前处理的值:40
    上一次处理后的值:42
    当前处理的值:-10
    上一次处理后的值:32
    当前处理的值:6
    38
    **/
    </script>
    </body>

    </html>
    + +

    2.6 promise异步编排

    解决回调地域的问题

    +

    ​ Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let promise = new Promise(function(resolve, reject) {
    console.log('Promise');
    resolve();
    });

    promise.then(function() {
    console.log('resolved.');
    });

    console.log('Hi!');

    // Promise
    // Hi!
    // resolved
    + +

    原有的基础上进一步的封装,链式调用

    +

    3.模块化

    3.1 什么是模块化

    ​ 模块化就是把代码进行拆分,分辨重复使用。类似java中的导包:要使用一个包,必须先导包。而JS中没有包的概念,换来的是模块。

    +

    模块功能主要由两个命令构成: export和import

    +
      +
    • export命令用于规定模块的对外接口
    • +
    • import命令用于导入其他模块提供的功能
    • +
    +

    3.2 export

    export不仅可以导出对象,一切js对象都可以导出。比如:基本类型的变量、函数、数组、对象

    +

    比如我们定义了一个js文件hello.js,里面有一个对象

    +
    1
    2
    3
    4
    5
    const util = {
    dsum(a, b) {
    return a + b;
    }
    }
    + +

    我们可以使用export将这个对象导出

    +
    1
    2
    3
    4
    5
    6
    7
    8
    const util = {
    dsum(a, b) {
    return a + b;
    }
    }

    //导出对象
    export { util }
    + +

    也可以简写成

    +
    1
    2
    3
    4
    5
    export const util = {
    dsum(a, b) {
    return a + b;
    }
    }
    + +

    当要导出多个值时,还可以简写。比如我有一个文件: user.js

    +
    1
    2
    3
    4
    var name  = "jack"
    var age = 21

    export{name, age}
    + +

    3.3 import

    比如我们之前定义了两个JS文件

    +

    hello.js

    +
    1
    2
    3
    4
    5
    6
    7
    8
    const util = {
    dsum(a, b) {
    return a + b;
    }
    }

    //导出对象
    export { util }
    + +

    user.js

    +
    1
    2
    3
    4
    5
    6
    7
    var name = "jack"
    var age = 21
    function add(a, b) {
    return a + b
    }

    export { name, age, add }
    + +

    我们在main.js文件中导入上面的两个文件,并使用上面的两个文件的方法

    +
    1
    2
    3
    4
    5
    6
    import util from "./hello"  //这里只能是util 不能是其他的名字
    import {name,age,add} from './user' //可以不用全部导入 只用导入需要的即可

    util.sum(1,2)
    console.log(name);
    add(1,2)
    + +

    3.4 注意

    当我们这样定义时,导入的时候可以自定义导入时使用的变量名

    +
    1
    2
    3
    4
    5
    export default {  //导出的时候这样写
    dsum(a, b) {
    return a + b;
    }
    }
    + +
    1
    import xxx from "./hello"  //这里使用可以随意的取名
    + +

    三.Vue

    1.MVVM思想

      +
    • M:即 Model,模型,包括数据和一些基本操作
    • +
    • V:即View视图,页面渲染结果
    • +
    • VM:即 View-Model,模型与视图间的双向操作(无需开发人员干涉)
    • +
    +

    ​ 在MVVM之前,开发人员从后端获取需要的数据模型,然后要通过DOM操作 Model渲染到View中。而后当用户操作视图,我们还需要通过DOM获取View中的数据,然后同步到Model中。而MVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用再关心Model和View之间是如何互相影响的:

    +

    2.vue的简介

    ​ Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

    +

    Vue2官方文档

    +

    3.语法基础

    3.1 快速入门

    1.创建一个文件夹,用VSCode打开

    +

    2.下载vue的依赖

    +
    1
    2
    3
    4
    # 初始化项目
    npm init -y
    # 安装vue的依赖,这里vue2和vue3版本语法有些差别,使用的时候要注意
    npm install vue@2
    + +

    3.创建一个index.html文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue入门</title>
    </head>

    <body>
    <div id="app">
    <h1>{{name}},是个傻子</h1>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    //这里最新版的vue上面无法获取到name的值
    let vm = new Vue({
    el: "#app",
    data: {
    name:"张三"
    }
    });
    </script>
    </body>

    </html>
    + +

    3.2 基本的语法和插件的安装

    3.2.1双向绑定v-model

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue入门</title>
    </head>

    <body>
    <div id="app">
    <!-- 双向绑定 -->
    <input type="text" v-model="num">
    <h1>{{name}}今年{{num}}岁了</h1>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    //声明式渲染
    let vm = new Vue({
    el: "#app",
    data: {
    name:"张三",
    num: 1
    }
    });
    </script>
    </body>
    </html>
    + +

    3.2.2事件处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue入门</title>
    </head>

    <body>
    <div id="app">
    <!-- 双向绑定 -->
    <input type="text" v-model="num">
    <!-- 点击事件绑定 -->
    <button v-on:click="num++">点赞</button>
    <!-- 取消点赞 -->
    <button v-on:click="cancle()">取消点赞</button></button>
    <h1>有{{num}}为{{name}}点赞!</h1>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    //声明式渲染
    let vm = new Vue({
    el: "#app", //绑定元素
    data: {//封装数据
    name:"张三",
    num: 1
    },
    methods:{//封装方法
    cancle(){
    this.num--;
    }
    }
    });
    //v-xx 相当于指令
    //总结
    //1.创建vue的实例,关联页面的模板,将自己的数据(data)渲染到关联的模板
    //2.使用指令简化对dom的操作 例如 v-XX
    </script>
    </body>

    </html>
    + +

    3.2.3VUE代码提示插件和浏览器插件

    浏览器插件地址 直接解压,打开浏览器插件的开发者模式,选择加载已解压的文件,选择chrome目录即可

    +

    image-20230525192244158

    +image-20230525191246045 + +

    3.2.4常用指令

    v-html 和 v-text
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>常用指令</title>
    </head>
    <body>
    <div id="app">
    <!-- 1.显示类的指令 -->
    <!-- 插值表达式会发生插值闪烁的问题,先显示{{msg}}后显示内容 -->
    <!-- 打印的是<h1>Hello</h1> -->
    {{msg}} <br>
    {{1+1}} <br>
    {{hello()}} <br>
    <span v-text="msg"></span>
    <!-- 直接显示h1的大标题 -->
    <span v-html="msg"></span>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el:"#app",
    data:{
    msg:"<h1>Hello</h1>"
    },
    methods: {
    hello(){
    return "hello";
    }
    },
    })
    </script>
    </body>
    </html>
    + +
    v-bind单向绑定
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>单向绑定</title>
    </head>
    <body>
    <div id = "app">
    <!-- 单向绑定 -->
    <a v-bind:href="link">百度</a>
    <!-- class style也可以通过标签进行单向绑定 -->
    <span v-bind:style="{color:color}">你好</span>
    <span :style="{color:color}">使用缩写形式的单向绑定</span>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    link:"http://www.baidu.com",
    color: 'green'
    }
    })
    </script>
    </body>
    </html>
    + +
    v-model 双向绑定
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>双向绑定</title>
    </head>
    <body>
    <div id = "app">
    精通的语言:<br>
    <input type="checkbox" v-model="language" value="java">JAVA <br>
    <input type="checkbox" v-model="language" value="php">PHP <br>
    <input type="checkbox" v-model="language" value="python">Python <br>
    选中了: {{language}}
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    language:[]
    }
    })
    </script>
    </body>
    </html>
    + +

    image-20230526164858205

    +
    v-on 事件绑定
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件绑定</title>
    </head>

    <body>
    <div id="app">
    <!-- 绑定一个单机事件 -->
    <button v-on:click="num++">v-on:click的方式点赞</button><br>
    <!-- 简写的方式实现点赞 -->
    <button @click="num++">@click的方式点赞</button><br>
    <!-- 点击事件指定一个回调函数 -->
    <button v-on:click="cancle()">取消点赞</button><br>
    点赞的数量:{{num}}


    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    num: 0
    },
    methods: {
    cancle(){
    this.num--;
    }
    },
    })

    </script>
    </body>
    </html>
    + +image-20230526170222378 + +
    v-for 循环显示
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>循环遍历</title>
    </head>

    <body>
    <div id="app">
    <ul>
    <!-- 1.遍历数组 -->
    <!-- 可以使用v-for="user in users"遍历用户信息 -->
    <!-- 获取下标的信息加上一个index -->
    <li v-for="(user,index) in users" :key="">
    序号:{{index+1}} <br>
    姓名:{{user.name}} <br>
    性别:{{user.gender}} <br>
    年龄:{{user.age}} <br>
    <!-- 2.遍历对象,获取每一个属性信息 -->
    对象信息:
    <span v-for="(v,k,i) in user">属性索引={{i}}|属性名={{k}}|属性值={{v}}|</span><br>
    </li>
    <!-- 遍历的时候加上 :key 提高渲染的效率 -->
    <!-- <li v-for="(user,index) in users" :key=""> -->
    </ul>

    <!-- 遍历数组 -->
    <ul>
    <li v-for="(item,index) in nums" ::key="index">
    {{item}}
    </li>
    </ul>

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    users: [
    { name: "mary", gender: '女', age: 21 },
    { name: "tom", gender: '男', age: 35 },
    { name: "mike", gender: '男', age: 21 },
    { name: "amy", gender: '女', age: 64 }
    ],
    nums:[1,2,3,4,5,6,7,8,9,10,11]
    }
    })

    </script>
    </body>

    </html>
    + +

    image-20230526172228532

    +
    v-if 和 v-show
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>判断</title>
    </head>
    <body>
    <!-- v-if 条件判断,当结果为true时,所有的元素才会被渲染 -->
    <!-- v-show 当得到的结果为true时,所有的元素才会被显示 -->
    <div id="app">
    <button v-on:click="show = !show">点击显示与否</button></button>
    <!-- 1.使用v-if显示 -->
    <h1 v-if="show">if=看到我...</h1>
    <!-- 2.使用v-show显示 -->
    <h1 v-show="show">show=显示...</h1></h1>
    <!-- 多重判断 -->
    <!-- v-if、v-else-if、v-else -->
    <!-- 循环的时候也可以加上判断 -->

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    show:true
    }
    })

    </script>
    </body>
    </html>
    + +

    3.2.5 计算属性和侦听器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算属性和侦听器</title>
    </head>

    <body>
    <div id="app">
    <!-- 计算属性 -->
    <ul>
    <li>西游记;价格:{{xyjPrice}},数量:<input type="number" v-model="xyjNum"></li>
    <li>水浒传;价格:{{shzPrice}},数量:<input type="number" v-model="shzNum"></li>
    <li>总价:{{totalPrice}}</li>
    {{msg}}
    </ul>
    <!-- 侦听器,监听属性值的变化 -->
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    xyjPrice: 99.98, //西游记价格
    shzPrice: 99.00, //水浒传价格
    xyjNum: 0, //西游记的数量
    shzNum: 0, //水浒传的数量
    msg: "" //提示的消息
    },
    computed: {//计算属性
    totalPrice() {//计算总价
    return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum
    }
    },
    watch: {//监听器
    //newVal:新值
    //oldVal:旧值
    //下面的这个方法是监听当购买的数量超出了3,就提示超库存了,并把输入框的值设置为最大的3
    xyjNum: function (newVal, oldVal) {//监听西游记数量的变化
    console.log("newVal:" + newVal + " | " + "oldVal:" + oldVal)
    if (newVal >= 3) {
    this.msg = "库存超出了限制"
    this.xyjNum = 3
    } else {
    this.msg = ""
    }
    }
    }

    })

    </script>

    </body>
    </html>
    + +

    3.2.6 过滤器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>过滤器</title>
    </head>
    <div id="app">
    <!-- 过滤器常用来处理文本格式化的操作,过滤器可以用在两个地方:插值表达式和v-bind表达式 -->
    <ul>
    <li v-for="user in userList">
    <!-- 显示性别的时候不用代号显示,使用男女显示 -->
    <!-- 方式一:通过三元运算符的形式显示 -->
    <!-- 姓名:{{user.name}} | 性别:{{user.gender== 1?"男":"女"}} -->
    <!-- 方式二:使用过滤器的方式 其中 | 是管道符 genderFilter是过滤器 -->
    姓名:{{user.name}} | 性别:{{user.gender | genderFilter}}
    </li>
    </ul>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>

    Vue.filter("gFilter", function (val) {//全局过滤器
    if (val == 1) {
    return "男··"
    } else {
    return "女··"
    }
    })


    new Vue({
    el: "#app",
    data: {
    userList: [
    { id: 1, name: "jacky", gender: 1 },
    { id: 2, name: "peter", gender: 0 },
    ]
    },
    filters: {//局部的过滤器
    genderFilter(val) {
    if (val == 1) {
    return "男"
    } else {
    return "女"
    }
    }
    }
    })

    </script>

    <body>

    </body>

    </html>
    + +

    3.3 组件化

    ​ 在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。但是每个页面都都独自开发,这无疑增加了开发的成本。所以我们会把页面的不同部分拆分成独立的组件,然后在不同的页面就可以共享这些组件,避免重复开发。

    +image-20230527215736955 + +

    3.3.1 全局组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>组件化</title>
    </head>

    <body>
    <div id="app">
    <button @click="count++">我被点击了{{count}}次</button>

    <!-- 组件的使用 -->
    <counter></counter>

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    //1.全局声明一个组件
    Vue.component("counter", {//counter是组件的名称,可以随便起名,见名知意就行
    template: `<button @click="count++">我被点击了{{count}}次</button>`,//模板
    data() { //模板中的属性信息,这里的data必须是一个函数
    return {
    count: 1
    }
    }
    });
    new Vue({
    el: "#app",
    data: {
    count: 0
    }
    })

    </script>
    </body>

    </html>
    + +image-20230527220905922 + +

    3.3.2 局部组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>组件化</title>
    </head>

    <body>
    <div id="app">
    <button @click="count++">我被点击了{{count}}次</button>

    <!-- 局部组件的使用 -->
    <button-counter></button-counter>

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>

    //声明一个局部的组件
    const buttonCounter = {
    template: `<button @click="count++">我被点击了{{count}}次</button>`,
    data() {
    return {
    count: 1
    }
    }
    }
    new Vue({
    el: "#app",
    data: {
    count: 0
    },
    //声明一个局部的组件
    components:{
    'button-counter':buttonCounter
    }
    })

    </script>
    </body>

    </html>
    + +

    3.3 生命周期和钩子函数

    生命周期图

    +

    image-20230527221756442

    +

    3.4 Vue模块化开发

    1.全局安装webpack

    +
    1
    npm install webpack -g
    + +

    2.全局安装vue脚手架

    +
    1
    npm install -g @vue/cli-init
    + +

    3.初始化vue的项目

    +

    创建一个文件夹,打开cmd,切换到这个文件夹,执行以下的命令

    +
    1
    2
    # vue脚手架使用webpacke模板初始化一个项目
    vue init webpack 项目名称
    + +

    4.启动vue的项目

    +

    项目中的package.json中有scripts,代表我们能运行的命令

    +
    1
    2
    3
    4
    5
    6
    # 启动项目
    npm start
    #或者
    npm run dev
    # 将项目打包
    npm run build
    + +

    4.Eelment-UI

    官方文档:https://element.eleme.cn/#/zh-CN/component/quickstart

    +
    \ No newline at end of file diff --git a/posts/45572.html b/posts/45572.html new file mode 100644 index 000000000..9b7c9aa7a --- /dev/null +++ b/posts/45572.html @@ -0,0 +1,338 @@ +微信登录 | The Blog + + + + + + + + + + + + +

    微信登录

    1、OAuth2

    微信登录使用了OAuth2解决方案

    +

    1.1 OAuth2解决什么问题

    1.1.1 开放系统间授权

    照片拥有者想要在云冲印服务上打印照片,云冲印服务需要访问云存储服务上的资源

    +

    IMG_256

    +

    1.1.2图例

    资源拥有者:照片拥有者

    +

    客户应用:云冲印

    +

    受保护的资源:照片

    +

    IMG_256

    +

    1.1.3方式一:用户名密码复制

    IMG_258

    +

    用户将自己的”云存储”服务的用户名和密码,告诉”云冲印”,后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

    +

    (1)”云冲印”为了后续的服务,会保存用户的密码,这样很不安全。

    +

    (2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。

    +

    (3)”云冲印”拥有了获取用户储存在Google所有资料的权力,用户没法限制”云冲印”获得授权的范围和有效期。

    +

    (4)用户只有修改密码,才能收回赋予”云冲印”的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

    +

    (5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

    +

    总结:

    +

    将受保护的资源中的用户名和密码存储在客户应用的服务器上,使用时直接使用这个用户名和密码登录

    +

    适用于同一公司内部的多个系统,不适用于不受信的第三方应用

    +

    1.1.4方式二:通用开发者key

    适用于合作商或者授信的不同业务部门之间

    +

    IMG_259

    +

    1.1.5方式三:颁发令牌

    接近OAuth2方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议,因此就有了OAuth2协议

    +

    IMG_256

    +

    令牌类比仆从钥匙

    +

    IMG_256

    +

    1.2 OAuth2最简向导

    1.2.1 OAuth主要角色

    IMG_256

    +

    1.2.2最简向导

    川崎高彦:OAuth2领域专家,开发了一个OAuth2 sass服务,OAuth2 as Service,并且做成了一个公司

    +

    在融资的过程中为了向投资人解释OAuth2是什么,于是写了一篇文章,《OAuth2最简向导》

    +

    1.3 OAuth2的应用

    1.3.1 微服务安全

    现代微服务中系统微服务化以及应用的形态和设备类型增多,不能用传统的登录方式

    +

    核心的技术不是用户名和密码,而是token,由AuthServer颁发token,用户使用token进行登录

    +

    IMG_256

    +

    1.3.2 社交登录

    +

    2、微信登录介绍

    2.1 前期准备

    1、注册

    +

    微信开放平台:https://open.weixin.qq.com

    +

    2、邮箱激活

    +

    3、完善开发者资料

    +

    4、开发者资质认证

    +

    准备营业执照,1-2个工作日审批、300元

    +

    5、创建网站应用

    +

    提交审核,7个工作日审批

    +

    6、内网穿透

    +

    ngrok的使用

    +

    2.2 授权流程

    参考文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN

    +

    获取access_token时序图

    +

    IMG_256

    +

    第一步:请求CODE(生成授权URL)

    +

    第二步:通过code获取access_token(开发回调URL)

    +

    3、使用教程

    由于微信登录需要企业用户才能注册,这里我们使用的是尚硅谷的密钥

    +

    当前项目的启动端口设置为 8160 才可以获取到登录的二维码

    +

    3.1 配置文件中添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    # 微信开放平台appid
    wx.open.app_id=wxed9954c01bb89b47
    # 微信开放平台appsecret
    wx.open.app_secret=a7482517235173ddb4083788de60b90e
    #回调的地址
    wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback
    #前端项目的地址
    yygh.baseUrl=http://localhost:3000
    + +

    3.2 编写配置类读取配置文件中的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;

    @Component
    public class ConstantPropertiesUtil implements InitializingBean {

    @Value("${wx.open.app_id}")
    private String appId;

    @Value("${wx.open.app_secret}")
    private String appSecret;

    @Value("${wx.open.redirect_url}")
    private String redirectUrl;

    @Value("${yygh.baseUrl}")
    private String yyghBaseUrl;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;
    public static String YYGH_BASE_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
    WX_OPEN_APP_ID = appId;
    WX_OPEN_APP_SECRET = appSecret;
    WX_OPEN_REDIRECT_URL = redirectUrl;
    YYGH_BASE_URL = yyghBaseUrl;
    }
    }
    + +

    3.3 生成二维码和扫描二维码之后的回调操作

    3.3.1 生成二维码

    生成内嵌的二维码

    +

    返回给前端的相关参数

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数是否必须说明
    self_redirecttrue:手机点击确认登录后可以在 iframe 内跳转到 redirect_uri,false:手机点击确认登录后可以在 top window 跳转到 redirect_uri。默认为 false。
    id第三方页面显示二维码的容器id
    appid应用唯一标识,在微信开放平台提交应用审核通过后获得
    scope应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
    redirect_uri重定向地址,需要进行UrlEncode
    state用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
    style提供”black”、”white”可选,默认为黑色文字描述。详见文档底部FAQ
    href自定义样式链接,第三方可根据实际需求覆盖默认样式。详见文档底部FAQ
    +

    生成扫描二维码的后端部分

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    import com.atguigu.yygh.common.result.Result;
    import com.atguigu.yygh.user.utils.ConstantPropertiesUtil;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;

    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.HashMap;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/20
    * @Description 微信登录的控制器方法
    */
    @Controller
    @RequestMapping("/api/ucenter/wx")
    @CrossOrigin
    public class WxController {


    /**
    * 生成微信扫描二维码
    * 返回给前端一些生成二维码的参数
    */
    @GetMapping("/getLoginParameters")
    @ResponseBody
    public Result getLoginParameters() {
    try {
    HashMap<String, Object> map = new HashMap<>();
    map.put("appid", ConstantPropertiesUtil.WX_OPEN_APP_ID);
    map.put("scope", "snsapi_login");
    map.put("redirect_uri", URLEncoder.encode(ConstantPropertiesUtil.WX_OPEN_REDIRECT_URL, "utf-8"));
    map.put("state", System.currentTimeMillis() + "");//原样返回,可以没有
    return Result.ok(map);
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    return Result.fail();
    }
    }
    }
    + +

    生成扫描二维码的前端部分

    +

    JS部分

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import request from '@/utils/request';

    export default {
    //获取生成二维码需要的参数信息
    getLoginParam() {
    return request({
    url: `/api/ucenter/wx/getLoginParam`,
    method: 'get'
    })
    }
    }
    + +

    引入api

    +
    1
    import weixinApi from "@/api/weixin";
    + +

    在mounted()方法中初始化微信js

    +
    1
    2
    3
    4
    5
    6
    7
    mounted() {
    //初始化微信js
    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
    document.body.appendChild(script)
    },
    + +

    在页面中微信登录按钮绑定的是weixinLogin()点击事件,所以我们要在这个方法中初始化对象

    +

    向后端发起请求获取生成二维码需要的参数信息

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    weixinLogin() {
    weixinApi.getLoginParam().then((response) => {
    var obj = new WxLogin({
    self_redirect: true,
    id: "weixinLogin", // 需要显示的容器id <div id="weixinLogin"></div>
    appid: response.data.appid, // appid
    scope: response.data.scope, // 网页默认即可
    redirect_uri: response.data.redirectUri, // 授权成功后回调的url
    state: response.data.state, // 可设置为简单的随机数加session用来校验
    style: "black", //。二维码的样式, 提供"black"、"white"可选
    href: "", // 外部css文件url,需要https
    });
    });
    },
    + +

    点击微信登录之后,页面上的效果

    +

    image-20230421203717902

    +

    3.3.2 处理用户扫描成功之后的回调

    实现的思路:

    +

    用户扫描二维码 -> 点击确认 -> 会执行回调请求(请求配置文件中这个地址wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback) -> 获取回调请求中携带的参数,再根据参数获取扫描用户的信息,执行相关的操作

    +

    扫描成功之后发起如下的请求:

    +

    image-20230421205258647

    +

    image-20230421210454720

    +

    引入httpclient(不依赖浏览器发起请求)依赖

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.1</version>
    </dependency>
    + +

    导入HttpClientUtils工具类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    import org.apache.commons.io.IOUtils;
    import org.apache.commons.lang.StringUtils;
    import org.apache.http.Consts;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    import org.apache.http.NameValuePair;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.client.config.RequestConfig.Builder;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.conn.ConnectTimeoutException;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.conn.ssl.SSLContextBuilder;
    import org.apache.http.conn.ssl.TrustStrategy;
    import org.apache.http.conn.ssl.X509HostnameVerifier;
    import org.apache.http.entity.ContentType;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
    import org.apache.http.message.BasicNameValuePair;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLException;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.SSLSocket;
    import java.io.IOException;
    import java.net.SocketTimeoutException;
    import java.security.GeneralSecurityException;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/21
    * @Description
    */
    public class HttpClientUtils {

    public static final int connTimeout=10000;
    public static final int readTimeout=10000;
    public static final String charset="UTF-8";
    private static HttpClient client = null;

    static {
    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    cm.setMaxTotal(128);
    cm.setDefaultMaxPerRoute(128);
    client = HttpClients.custom().setConnectionManager(cm).build();
    }

    public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {
    return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {
    return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String get(String url) throws Exception {
    return get(url, charset, null, null);
    }

    public static String get(String url, String charset) throws Exception {
    return get(url, charset, connTimeout, readTimeout);
    }

    /**
    * 发送一个 Post 请求, 使用指定的字符集编码.
    *
    * @param url
    * @param body RequestBody
    * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
    * @param charset 编码
    * @param connTimeout 建立链接超时时间,毫秒.
    * @param readTimeout 响应超时时间,毫秒.
    * @return ResponseBody, 使用指定的字符集编码.
    * @throws ConnectTimeoutException 建立链接超时异常
    * @throws SocketTimeoutException 响应超时
    * @throws Exception
    */
    public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
    throws ConnectTimeoutException, SocketTimeoutException, Exception {
    HttpClient client = null;
    HttpPost post = new HttpPost(url);
    String result = "";
    try {
    if (StringUtils.isNotBlank(body)) {
    HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
    post.setEntity(entity);
    }
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    post.setConfig(customReqConf.build());

    HttpResponse res;
    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(post);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(post);
    }
    result = IOUtils.toString(res.getEntity().getContent(), charset);
    } finally {
    post.releaseConnection();
    if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    return result;
    }


    /**
    * 提交form表单
    *
    * @param url
    * @param params
    * @param connTimeout
    * @param readTimeout
    * @return
    * @throws ConnectTimeoutException
    * @throws SocketTimeoutException
    * @throws Exception
    */
    public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {

    HttpClient client = null;
    HttpPost post = new HttpPost(url);
    try {
    if (params != null && !params.isEmpty()) {
    List<NameValuePair> formParams = new ArrayList<NameValuePair>();
    Set<Entry<String, String>> entrySet = params.entrySet();
    for (Entry<String, String> entry : entrySet) {
    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
    }
    UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
    post.setEntity(entity);
    }

    if (headers != null && !headers.isEmpty()) {
    for (Entry<String, String> entry : headers.entrySet()) {
    post.addHeader(entry.getKey(), entry.getValue());
    }
    }
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    post.setConfig(customReqConf.build());
    HttpResponse res = null;
    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(post);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(post);
    }
    return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
    } finally {
    post.releaseConnection();
    if (url.startsWith("https") && client != null
    && client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    }

    /**
    * 发送一个 GET 请求
    */
    public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
    throws ConnectTimeoutException,SocketTimeoutException, Exception {

    HttpClient client = null;
    HttpGet get = new HttpGet(url);
    String result = "";
    try {
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    get.setConfig(customReqConf.build());

    HttpResponse res = null;

    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(get);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(get);
    }

    result = IOUtils.toString(res.getEntity().getContent(), charset);
    } finally {
    get.releaseConnection();
    if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    return result;
    }

    /**
    * 从 response 里获取 charset
    */
    @SuppressWarnings("unused")
    private static String getCharsetFromResponse(HttpResponse ressponse) {
    // Content-Type:text/html; charset=GBK
    if (ressponse.getEntity() != null && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
    String contentType = ressponse.getEntity().getContentType().getValue();
    if (contentType.contains("charset=")) {
    return contentType.substring(contentType.indexOf("charset=") + 8);
    }
    }
    return null;
    }

    /**
    * 创建 SSL连接
    * @return
    * @throws GeneralSecurityException
    */
    private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
    try {
    SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
    public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
    return true;
    }
    }).build();

    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

    @Override
    public boolean verify(String arg0, SSLSession arg1) {
    return true;
    }

    @Override
    public void verify(String host, SSLSocket ssl)
    throws IOException {
    }

    @Override
    public void verify(String host, X509Certificate cert)
    throws SSLException {
    }

    @Override
    public void verify(String host, String[] cns,
    String[] subjectAlts) throws SSLException {
    }
    });
    return HttpClients.custom().setSSLSocketFactory(sslsf).build();

    } catch (GeneralSecurityException e) {
    throw e;
    }
    }
    }


    + +

    Controller

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    package com.atguigu.yygh.user.controller;

    import com.alibaba.fastjson.JSONObject;
    import com.atguigu.yygh.common.result.Result;
    import com.atguigu.yygh.user.utils.ConstantPropertiesUtil;
    import com.atguigu.yygh.user.utils.HttpClientUtils;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;

    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.HashMap;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/20
    * @Description 微信登录的控制器方法
    */
    @Controller
    @RequestMapping("/api/ucenter/wx")
    @CrossOrigin
    public class WxController {


    /**
    * 生成微信扫描二维码
    * 返回给前端一些生成二维码的参数
    */
    @GetMapping("/getLoginParam")
    @ResponseBody
    public Result getLoginParameters() {
    try {
    HashMap<String, Object> map = new HashMap<>();
    map.put("appid", ConstantPropertiesUtil.WX_OPEN_APP_ID);
    map.put("scope", "snsapi_login");
    map.put("redirect_uri", URLEncoder.encode(ConstantPropertiesUtil.WX_OPEN_REDIRECT_URL, "utf-8"));
    map.put("state", System.currentTimeMillis() + "");//原样返回,可以没有
    return Result.ok(map);
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    return Result.fail();
    }
    }

    /**
    * 扫描之后回调的请求中携带了相关的参数,帮助我们获取用户的信息
    * 登录成功之后的回调方法
    * @param code 临时票据
    * @param state 生成微信二维码之前put进去的参数 原样返回
    * @return
    */
    @GetMapping("/callback")
    public String callback(String code ,String state){
    //通过code、appid和app_secret去请求固定地址换取access_token
    StringBuffer baseAccessTokenUrl = new StringBuffer()
    .append("https://api.weixin.qq.com/sns/oauth2/access_token") //请求的固定地址
    .append("?appid=%s") //%s占位符
    .append("&secret=%s")
    .append("&code=%s")
    .append("&grant_type=authorization_code");

    //拼接完整之后的地址
    String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
    ConstantPropertiesUtil.WX_OPEN_APP_ID,
    ConstantPropertiesUtil.WX_OPEN_APP_SECRET,
    code);

    //使用httpclient去请求这个地址换取access_token
    try {
    String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);//json格式的字符串
    //从这个字符串中获取openid和 access_token的值
    JSONObject jsonObject = JSONObject.parseObject(accessTokenInfo);
    String accessToken = jsonObject.getString("access_token");
    String openid = jsonObject.getString("openid");
    //根据access_token和openid获取微信用户的基本信息
    String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
    "?access_token=%s" +
    "&openid=%s";
    String userInfoUrl = String.format(baseUserInfoUrl, accessToken, openid);
    //发起请求获取用户信息
    String userInfo = HttpClientUtils.get(userInfoUrl);//json格式的字符串
    JSONObject parseObject = JSONObject.parseObject(userInfo);
    //获取具体的用户信息
    String nickname = parseObject.getString("nickname");//用户昵称
    String sex = parseObject.getString("sex");//性别
    String language = parseObject.getString("language");//语言
    String city = parseObject.getString("city");//所在城市
    String province = parseObject.getString("province");//省份
    String country = parseObject.getString("country");//注册国家
    String headimgurl = parseObject.getString("headimgurl");//头像的url地址
    System.out.println("昵称:"+nickname+",性别:"+sex+",语言:"+language+",城市:"+city+",省份:"+province+",注册国家:"+country+",头像地址:"+headimgurl);
    //TODO 将扫码人的信息添加到数据库中
    } catch (Exception e) {
    e.printStackTrace();
    }
    return "redirect:地址信息&参数信息(用户信息或者token信息等)";
    }
    }
    + + + + + + + + + +
    \ No newline at end of file diff --git a/posts/45726.html b/posts/45726.html new file mode 100644 index 000000000..715ba0924 --- /dev/null +++ b/posts/45726.html @@ -0,0 +1,1231 @@ +项目实战-谷粒商城 | The Blog + + + + + + + + + + + + +

    项目实战-谷粒商城

    一.项目简介

    1.项目背景

    电商模式 谷粒商城使用的B2C模式,销售自营的商品给客户

    +

    市面上5种常见的电商模式B2B、B2C、C2B、C2C、O2O

    +

    1、B2B模式:
    business to business,是指商家与商家建立的商业关系。如阿里巴巴。

    +

    2、B2C模式:
    business to consumer,商对客模式。即通常说的商业零售,供应商直接把商品卖给用户。如:苏宁易购、京东、天猫、小米商城。

    +

    3、C2B模式:
    customer to business,即消费者对企业。先有消费者需求而后有企业生产。例如众筹类的商城。

    +

    4、C2C模式:
    customer to consumer,客户之间直接把东西放上网上去卖。如:淘宝、闲鱼。

    +

    5、O2O模式:
    online to offline,线上线下。线上快速支付,线下优质服务。如:饿了么、美团、京东到家。

    +

    2.项目架构图

    项目的架构图

    +

    +

    微服务划分图

    +

    image-20230518015831413

    +

    3.项目技术和特色

      +
    • 前后端分离开发,开发基于VUE的后台管理系统
    • +
    • SpringCloud全新解决方案
    • +
    • 应用监控、限流、网关、熔断降级等分布式方案 全方位涉及
    • +
    • 包含分布式事务、分布式锁等技术难点
    • +
    • 分析高并发场景的编码方式、线程池、异步编排等使用
    • +
    • 压力测试与性能优化
    • +
    • 各种集群技术的区别已经使用
    • +
    • CI/CD的使用
    • +
    +

    4.项目的前置要求

      +
    • 熟悉SpringBoot以及常见的整合方案
    • +
    • 了解SpringCloud
    • +
    • 熟悉Maven git
    • +
    • 熟悉linux、redis、docker基本操作
    • +
    • 了解html、css、js、vue
    • +
    • 熟练使用idea开发项目
    • +
    +

    二.分布式基础概念

    1.微服务

    微服务架构风格,就像是一个单独的应用程序开发为一套小服务,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言编写,以及不同的数据存储技术,并保持最低限度的集中式管理。

    +

    简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个应用独立部署运行。

    +

    2.集群|分布式|节点

    集群是一个物理形态 ,分布式是个工作方式

    +

    分布式: 将不同的业务分布在不同的地方

    +

    集群: 集群指的是将几台服务器集中在一起,实现同一业务

    +

    分布式中的每一个节点,都可以做集群。而集群不一定是分布式的。

    +

    节点: 集群中的一个服务器

    +

    3.远程调用

    在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要相互调用,我们称之为远程调用。

    +

    SpringCloud中使用HTTP+JSON的方式完成远程调用

    +

    image-20230518010847966

    +

    4.负载均衡

    image-20230518011030343 + +
    分布式系统中,A服务需要调用B服务,B服务在多台机器上都存在,A调用任意一个服务均可完成功能。为了使每一个服务器都不要太忙或者太闲,我们可以使用负载均衡的调用每一个服务器,提升网站的健壮性。
    +
    +

    常见的负载均衡算法:

    +

    轮询: 为第一个请求选择健康池中的第一台后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。

    +

    最小连接: 有限选择连接数量少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。

    +

    随机法:通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。

    +

    源地址哈希法:源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。

    +

    加权轮询法:不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。

    +

    加权随机法:与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

    +

    5.服务注册|发现|注册中心

    image-20230518012211042

    +

    6.配置中心

    每一个服务最终会有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的配置。
    +
    +

    image-20230518012538349

    +

    7.服务熔断和服务降级

    在微服务的架构中,微服务之间通过网络进行通信,存在相互依赖,当其中的一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。
    +
    +

    服务熔断: 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启短路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据。

    +

    服务降级: 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心的业务降级运行。降级:某些业务不处理,或者简单处理[抛异常、返回NULL、调用mock数据、调用Fallback处理逻辑]。

    +

    image-20230518013254404

    +

    image-20230518013624395

    +

    8.API网关

    在微服务的架构中,API Gateway作为整体架构的重要组件,他抽象了微服务中都需要的公共功能,同时提供了客户端的负载均衡,服务自动熔断,灰度发布、统一认证、限流流控、日志统计等丰富功能,帮助我们解决很多API管理难题。
    +
    +image-20230518014732955 + +

    三.环境搭建

    1.安装虚拟机

    需要使用虚拟机安装相关的软件和部署集群

    +

    VMWare虚拟机安装Linux教程 | The Blog (gitee.io)

    +

    将Linxu的IP地址固定下来

    +

    Linux设置静态IP | The Blog (gitee.io)

    +

    2.Docker安装与配置

    包含docker安装、开启开机自启动、配置镜像加速服务

    +

    Docker容器化技术 | The Blog (gitee.io)image-20230518141429712

    +

    image-20230518145456236

    +

    3.Docker安装mysql

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    #下载mysql的镜像文件 这里以下载mysql5.7为例
    docker pull mysql:5.7

    #查看是否下载成功
    docker images

    #使用MySQL的镜像启动一个容器
    docker run -p 3306:3306 --name mysql \
    -v /mydata/mysql/log:/var/log/mysql \
    -v /mydata/mysql/data:/var/lib/mysql \
    -v /mydata/mysql/conf:/etc/mysql \
    -e MYSQL_ROOT_PASSWORD=root \
    -d mysql:5.7
    #参数说明
    # -p 3306:3306:将容器的 3306 端口映射到主机的 3306 端口
    #-v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机
    #-v /mydata/mysql/log:/var/log/mysql:将日志文件夹挂载到主机
    #-v /mydata/mysql/data:/var/lib/mysql/:将配置文件夹挂载到主机
    #-e MYSQL_ROOT_PASSWORD=root:初始化 root 用户的密码

    #查看是否运行成功 这时使用Navicat就可以连接上了 连接不上 关闭防火墙
    docker ps #查看正在运行的容器

    #进入容器的内部
    docker exec -it mysql /bin/bash

    #查看容器内部的目录结构 我们可以看到容器的内部是一个小小的linux系统
    ls / #注意中间有一个空格

    #查看与mysql相关的目录
    whereis mysql

    #退出容器
    exit;

    #在容器外部查看mysql挂载在本地的三个文件
    #容器内部文件的变化会同步在这三个文件中 conf data log
    #外部修改 也会同步到内部
    cd /mydata/mysql
    ls

    #创建一个mysql的配置文件设置字符编码 将下面的内容粘贴到配置文件中
    #1.创建配置文件
    vi /mydata/mysql/conf/my.cnf
    #2.将以下的内容粘贴到配置文件配置文件内容
    [client]
    default-character-set=utf8
    [mysql]
    default-character-set=utf8
    [mysqld]
    init_connect='SET collation_connection = utf8_unicode_ci'
    init_connect='SET NAMES utf8'
    character-set-server=utf8
    collation-server=utf8_unicode_ci
    skip-character-set-client-handshake
    skip-name-resolve
    #3.重启MySQl的服务 让配置文件生效 这个配置文件也会同步到容器内部
    docker restart mysql

    #设置开机自启动
    docker update mysql --restart=always
    + +

    4.Docker安装redis

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #下载redis的镜像文件 直接下载最新的
    docker pull redis

    #创建redis挂载在宿主机的配置文件和目录
    mkdir -p /mydata/redis/conf #创建目录
    touch /mydata/redis/conf/redis.conf #创建配置文件

    #创建并启动容器 这是以配置文件的方式启动 \前面有一个空格
    docker run -p 6379:6379 --name redis \
    -v /mydata/redis/data:/data \
    -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
    -d redis redis-server /etc/redis/redis.conf

    #检查容器的运行情况
    docker ps

    #测试客户端的使用
    docker exec -it redis redis-cli

    #可以在redis客户端中使用如下命令
    127.0.0.1:6379> set name tom #存值
    OK
    127.0.0.1:6379> get name #取值
    "tom"

    #redis中的数据不能持久化 关机重启之后数据会丢失
    #通过以下的配置 实现redis的持久化操作 AOF的持久化
    vi /mydata/redis/conf/redis.conf #修改之前创建的配置文件
    #添加如下配置
    appendonly yes

    #重启redis 让配置生效
    docker restart redis

    #设置开机自启动
    docker update redis --restart=always
    + +

    5.统一开发环境

    其中Java使用的是java8及以上

    +

    Maven 配置阿里云的镜像 profiles

    +

    开发环境的搭建 | The Blog (gitee.io)

    +

    IDEA中修改使用自己安装的Maven

    +

    IDEA插件:Lombok|MybatisX

    +

    VSCode插件:

    +

    image-20230518160444927

    +

    image-20230518155308167

    +

    6.配置Git

    官网下载:https://git-scm.com/downloads

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #以下的操作在下载安装完毕之后进行
    #1.鼠标在桌面右键 选择Git Bash Here 打开控制台

    #2.配置用户名和邮箱
    git config --global user.name "用户名" #随意
    git config --global user.email "邮箱" #自己的邮箱

    #3.配置SSH免密连接
    #生成密钥
    ssh-keygen -t rsa -C "在码云上注册的邮箱地址" #连续三次回车
    #查看密钥并复制公钥的内容
    cat ~/.ssh/id_rsa.pub

    #4.将密钥的复制到码云的SSH公钥中
    #4.1添加公钥 公钥名随意 公钥内容就是上面复制的内容

    #5.测试
    ssh -T git@gitee.com
    + +

    常用命令

    +image-20230518161145808 + +

    7.从Gitee上初始化一个项目

    我们是先从码云上初始化一个项目 然后拉取到本地

    +

    创建一个仓库

    +

    image-20230518163703370

    +

    复制刚才创建仓库的地址

    +

    image-20230518163838162

    +

    将工程导入到IDEA中

    +

    image-20230518164005690

    +

    导入成功

    +

    image-20230518164217773

    +

    8.创建微服务模块

    image-20230518171355588 + +

    9.数据库设计

    image-20230518171546236

    +image-20230518173203605 + +

    10.后台管理系统的搭建

    10.1.后端的搭建

    后台管理系统使用的是人人开源提供的一个脚手架:

    +

    仓库的地址信息:

    +

    仓库主页:https://gitee.com/renrenio

    +

    后端renren-fast:https://gitee.com/renrenio/renren-fast.git

    +

    前端renren-fast-vue:https://gitee.com/renrenio/renren-fast-vue.git

    +

    将其作为一个模块集成到gulimall中

    +

    image-20230518221222845

    +

    同时创建好这个脚手架需要的数据库

    +

    image-20230518221433314

    +

    修改配置文件中数据库账号密码ip的设置,启动项目看有没有报错

    +

    10.2 前端的搭建

    安装node.js 和 npm包管理工具 (node.js自带npm包管理工具)

    +
    1
    2
    3
    4
    5
    6
    7
    #检查是否安装成功
    #出现版本号就是安装成功了
    node -v
    npm -v

    #npm配置淘宝的镜像 下载依赖的速度更快
    npm config set registry https://registry.npmmirror.com
    + +

    将项目导入到vscode中,并安装依赖

    +
    1
    2
    #安装依赖的命令
    npm install
    + +

    image-20230518223021928

    +

    下载完成之后,运行前端的项目

    +
    1
    2
    #运行命令
    npm run dev
    + +

    运行成功过之后访问 http://localhost:8001

    +

    成功的页面显示如下

    +

    image-20230518223525151

    +

    **使用账号:admin密码:admin 登录 **

    +

    可以登录成功并显示以下的页面说明前后端的联调通过

    +

    image-20230518223921179

    +

    11.快速开发-逆向工程的搭建

    1.代码生成器快速使用案例

    人人开源代码生成器的地址:https://gitee.com/renrenio/renren-generator.git

    +

    集成代码生成器

    +

    image-20230518230016421

    +

    修改代码生成器的配置文件

    +

    1.修改yml配置文件中数据库的连接信息

    +

    2.修改properties中与代码生成相关的配置信息

    +

    配置举例 根据不同的模块 不同的数据库 配置不同

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    #代码生成器配置信息

    mainPath=com.atguigu
    #包名
    package=com.atguigu.gulimall
    moduleName=product
    #作者
    author=JasonGong
    #Email
    email=JasonGong@gmail.com
    #表前缀(类名不会包含表前缀)
    tablePrefix=pms_


    #以下的配置信息一般不动
    #类型转换配置信息
    tinyint=Integer
    smallint=Integer
    mediumint=Integer
    int=Integer
    integer=Integer
    bigint=Long
    float=Float
    double=Double
    decimal=BigDecimal
    bit=Boolean

    char=String
    varchar=String
    tinytext=String
    text=String
    mediumtext=String
    longtext=String


    date=Date
    datetime=Date
    timestamp=Date

    NUMBER=Integer
    INT=Integer
    INTEGER=Integer
    BINARY_INTEGER=Integer
    LONG=String
    FLOAT=Float
    BINARY_FLOAT=Float
    DOUBLE=Double
    BINARY_DOUBLE=Double
    DECIMAL=BigDecimal
    CHAR=String
    VARCHAR=String
    VARCHAR2=String
    NVARCHAR=String
    NVARCHAR2=String
    CLOB=String
    BLOB=String
    DATE=Date
    DATETIME=Date
    TIMESTAMP=Date
    TIMESTAMP(6)=Date

    int8=Long
    int4=Integer
    int2=Integer
    numeric=BigDecimal

    nvarchar=String
    + +

    3.运行项目

    +

    通过http://localhost:81 访问

    +

    image-20230518234030568

    +

    4.使用生成代码的功能生成代码

    +

    image-20230518234311272

    +

    解压之后的文件如下

    +

    image-20230518234941961

    +

    压缩包中的文件如下所示

    +

    image-20230518234922087

    +

    由于项目中没有使用shiro安全框架,我们注释掉安全框架的注解生成模板

    +

    image-20230519002146272

    +

    配置每个模块的配置文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 8080
    spring:
    datasource:
    url: jdbc:mysql://192.168.111.100:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    mybatis-plus:
    mapper-locations: classpath*:/mapper/**/*.xml #定义mapper.xml文件的位置
    global-config:
    db-config:
    id-type: auto #主键的生成规则
    + +

    测试生成的代码

    +

    image-20230519005131468

    +

    2.代码生成器的使用步骤

    1.修改代码生成器的配置文件generator.properties

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    #代码生成器配置信息

    mainPath=com.atguigu
    #包名
    package=com.atguigu.gulimall
    moduleName=ware #1.修改模块名
    #作者
    author=JasonGong
    #Email
    email=JasonGong@gmail.com
    #表前缀(类名不会包含表前缀)
    tablePrefix=wms_ #2.数据库表的前缀


    #以下的配置信息一般不动
    #类型转换配置信息
    tinyint=Integer
    smallint=Integer
    mediumint=Integer
    int=Integer
    integer=Integer
    bigint=Long
    float=Float
    double=Double
    decimal=BigDecimal
    bit=Boolean

    char=String
    varchar=String
    tinytext=String
    text=String
    mediumtext=String
    longtext=String


    date=Date
    datetime=Date
    timestamp=Date

    NUMBER=Integer
    INT=Integer
    INTEGER=Integer
    BINARY_INTEGER=Integer
    LONG=String
    FLOAT=Float
    BINARY_FLOAT=Float
    DOUBLE=Double
    BINARY_DOUBLE=Double
    DECIMAL=BigDecimal
    CHAR=String
    VARCHAR=String
    VARCHAR2=String
    NVARCHAR=String
    NVARCHAR2=String
    CLOB=String
    BLOB=String
    DATE=Date
    DATETIME=Date
    TIMESTAMP=Date
    TIMESTAMP(6)=Date

    int8=Long
    int4=Integer
    int2=Integer
    numeric=BigDecimal

    nvarchar=String
    + +

    2修改要生成增删改查代码的数据库的连接配置application.yml

    +

    只用配置数据库的连接信息即可

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    server:
    port: 81

    # mysql
    spring:
    main:
    allow-circular-references: true
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #MySQL配置
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.111.100:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    #oracle配置
    # driverClassName: oracle.jdbc.OracleDriver
    # url: jdbc:oracle:thin:@47.100.206.162:1521:xe
    # username: renren
    # password: 123456
    #SQLServer配置
    # driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
    # url: jdbc:sqlserver://192.168.10.10:1433;DatabaseName=renren_fast
    # username: sa
    # password: 123456
    #PostgreSQL配置
    # driverClassName: org.postgresql.Driver
    # url: jdbc:postgresql://192.168.10.10:5432/renren_fast
    # username: postgres
    # password: 123456



    jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
    web:
    resources:
    static-locations: classpath:/static/,classpath:/views/

    #mongodb:
    # host: localhost
    # port: 27017
    # auth: false #是否使用密码验证
    # username: tincery
    # password: renren
    # source: 123456
    # database: test

    mybatis-plus:
    mapperLocations: classpath:mapper/**/*.xml


    pagehelper:
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql


    #指定数据库,可选值有【mysql、oracle、sqlserver、postgresql、mongodb】
    renren:
    database: mysql
    + +

    3.启动服务生成代码,将生成的代码解压并粘贴到对应的模块中去

    +

    将main这个目录替换原来模块中的main目录即可,sql文件暂时不用管

    +

    4.创建模块的配置文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server:
    port: 8080
    spring:
    datasource:
    url: jdbc:mysql://192.168.111.100:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    mybatis-plus:
    mapper-locations: classpath*:/mapper/**/*.xml #定义mapper.xml文件的位置
    global-config:
    db-config:
    id-type: auto #主键的生成规则
    + +

    5.启动测试生成的代码

    +
    在启动的时候,我们要处理好代码依赖的包,这些包都存在与生成代码的模块中,我们在生成代码的模块中复制过来即可,然后手动的引入相应的包,处理好报错信息。
    +
    +

    3.后台搭建完成之后的项目树

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
    560
    561
    562
    563
    564
    565
    566
    567
    568
    569
    570
    571
    572
    573
    574
    575
    576
    577
    578
    579
    580
    581
    582
    583
    584
    585
    586
    587
    588
    589
    590
    591
    592
    593
    594
    595
    596
    597
    598
    599
    600
    601
    602
    603
    604
    605
    606
    607
    608
    609
    610
    611
    612
    613
    614
    615
    616
    617
    618
    619
    620
    621
    622
    623
    624
    625
    626
    627
    628
    629
    630
    631
    632
    633
    634
    635
    636
    637
    638
    639
    640
    641
    642
    643
    644
    645
    646
    647
    648
    649
    650
    651
    652
    653
    654
    655
    656
    657
    658
    659
    660
    661
    662
    663
    664
    665
    666
    667
    668
    669
    670
    671
    672
    673
    674
    675
    676
    677
    678
    679
    680
    681
    682
    683
    684
    685
    686
    687
    688
    689
    690
    691
    692
    693
    694
    695
    696
    697
    698
    699
    700
    701
    702
    703
    704
    705
    706
    707
    708
    709
    710
    711
    712
    713
    714
    715
    716
    717
    718
    719
    720
    721
    722
    723
    724
    725
    726
    727
    728
    729
    730
    731
    732
    733
    734
    735
    736
    737
    738
    739
    740
    741
    742
    743
    744
    745
    746
    747
    748
    749
    750
    751
    752
    753
    754
    755
    756
    757
    758
    759
    760
    761
    762
    763
    764
    765
    766
    767
    768
    769
    770
    771
    772
    773
    774
    775
    776
    777
    778
    779
    780
    781
    782
    783
    784
    785
    786
    787
    788
    789
    790
    791
    792
    793
    794
    795
    796
    797
    798
    799
    800
    801
    802
    803
    804
    805
    806
    807
    808
    809
    810
    811
    812
    813
    814
    815
    816
    817
    818
    819
    820
    821
    822
    823
    824
    825
    826
    827
    828
    829
    830
    831
    832
    833
    834
    835
    836
    837
    838
    839
    840
    841
    842
    843
    844
    845
    846
    847
    848
    849
    850
    851
    852
    853
    854
    855
    856
    857
    858
    859
    860
    861
    862
    863
    864
    865
    866
    867
    868
    869
    870
    871
    872
    873
    874
    875
    876
    877
    878
    879
    880
    881
    882
    883
    884
    885
    886
    887
    888
    889
    890
    891
    892
    893
    894
    895
    896
    897
    898
    899
    900
    901
    902
    903
    904
    905
    906
    907
    908
    909
    910
    911
    912
    913
    914
    915
    916
    917
    918
    919
    920
    921
    922
    923
    924
    925
    926
    927
    928
    929
    930
    931
    932
    933
    934
    935
    936
    937
    938
    939
    940
    941
    942
    943
    944
    945
    946
    947
    948
    949
    950
    951
    952
    953
    954
    955
    956
    957
    958
    959
    960
    961
    962
    963
    964
    965
    966
    967
    968
    969
    970
    971
    972
    973
    974
    975
    976
    977
    978
    979
    980
    981
    982
    983
    984
    985
    986
    987
    988
    989
    990
    991
    992
    993
    994
    995
    996
    997
    998
    999
    1000
    1001
    1002
    1003
    1004
    1005
    1006
    1007
    1008
    1009
    1010
    1011
    1012
    1013
    1014
    1015
    1016
    1017
    1018
    1019
    1020
    1021
    1022
    1023
    1024
    1025
    1026
    1027
    1028
    1029
    1030
    1031
    1032
    1033
    1034
    1035
    1036
    1037
    1038
    1039
    1040
    1041
    1042
    1043
    1044
    1045
    1046
    1047
    1048
    1049
    1050
    1051
    1052
    1053
    1054
    1055
    1056
    1057
    1058
    1059
    1060
    1061
    1062
    1063
    1064
    1065
    1066
    1067
    1068
    1069
    1070
    1071
    1072
    1073
    1074
    1075
    1076
    1077
    1078
    1079
    1080
    1081
    1082
    1083
    1084
    1085
    1086
    1087
    1088
    1089
    1090
    1091
    1092
    1093
    1094
    1095
    1096
    1097
    1098
    1099
    1100
    1101
    1102
    1103
    1104
    1105
    1106
    1107
    1108
    1109
    1110
    1111
    1112
    1113
    1114
    1115
    1116
    1117
    1118
    1119
    1120
    1121
    1122
    1123
    1124
    1125
    1126
    1127
    1128
    1129
    1130
    1131
    1132
    1133
    1134
    1135
    1136
    1137
    1138
    1139
    1140
    1141
    1142
    1143
    1144
    1145
    1146
    1147
    1148
    1149
    1150
    1151
    1152
    1153
    1154
    1155
    1156
    1157
    1158
    1159
    1160
    1161
    1162
    1163
    1164
    1165
    1166
    1167
    1168
    1169
    1170
    1171
    1172
    1173
    1174
    1175
    1176
    1177
    1178
    1179
    1180
    1181
    1182
    1183
    1184
    1185
    1186
    1187
    1188
    1189
    1190
    1191
    1192
    1193
    1194
    1195
    1196
    1197
    1198
    1199
    1200
    1201
    1202
    1203
    1204
    1205
    1206
    1207
    1208
    1209
    1210
    1211
    1212
    1213
    1214
    1215
    1216
    1217
    1218
    1219
    1220
    1221
    1222
    1223
    1224
    1225
    1226
    1227
    1228
    1229
    1230
    1231
    1232
    1233
    1234
    1235
    1236
    1237
    1238
    1239
    1240
    1241
    1242
    1243
    1244
    1245
    1246
    1247
    1248
    1249
    1250
    1251
    1252
    1253
    1254
    1255
    1256
    1257
    1258
    1259
    1260
    1261
    1262
    1263
    1264
    1265
    1266
    1267
    1268
    1269
    1270
    1271
    1272
    1273
    1274
    1275
    1276
    1277
    1278
    1279
    1280
    1281
    1282
    1283
    1284
    1285
    1286
    1287
    1288
    1289
    1290
    1291
    1292
    1293
    1294
    1295
    1296
    1297
    1298
    1299
    1300
    1301
    1302
    1303
    1304
    1305
    1306
    1307
    1308
    1309
    1310
    1311
    1312
    1313
    1314
    1315
    1316
    1317
    1318
    1319
    1320
    1321
    1322
    1323
    1324
    1325
    1326
    1327
    1328
    1329
    1330
    1331
    1332
    1333
    1334
    1335
    1336
    1337
    1338
    1339
    1340
    1341
    1342
    1343
    1344
    1345
    1346
    1347
    1348
    1349
    1350
    1351
    1352
    1353
    1354
    1355
    1356
    1357
    1358
    1359
    1360
    1361
    1362
    1363
    1364
    1365
    1366
    1367
    1368
    1369
    1370
    1371
    1372
    1373
    1374
    1375
    1376
    1377
    1378
    1379
    1380
    1381
    1382
    1383
    1384
    1385
    1386
    1387
    1388
    1389
    1390
    1391
    1392
    1393
    1394
    1395
    1396
    1397
    1398
    1399
    1400
    1401
    1402
    1403
    1404
    1405
    1406
    1407
    1408
    1409
    1410
    1411
    1412
    1413
    1414
    1415
    1416
    1417
    1418
    1419
    1420
    1421
    1422
    1423
    1424
    1425
    1426
    1427
    1428
    1429
    1430
    1431
    1432
    1433
    1434
    1435
    1436
    1437
    1438
    1439
    1440
    1441
    1442
    1443
    1444
    1445
    1446
    1447
    1448
    1449
    1450
    1451
    1452
    1453
    1454
    1455
    1456
    1457
    1458
    1459
    1460
    1461
    1462
    1463
    1464
    1465
    1466
    1467
    1468
    1469
    1470
    1471
    1472
    1473
    1474
    1475
    1476
    1477
    1478
    1479
    1480
    1481
    1482
    1483
    1484
    1485
    1486
    1487
    1488
    1489
    1490
    1491
    1492
    1493
    1494
    1495
    1496
    1497
    1498
    1499
    1500
    1501
    1502
    1503
    1504
    1505
    1506
    1507
    1508
    1509
    1510
    1511
    1512
    1513
    1514
    1515
    1516
    1517
    1518
    1519
    1520
    1521
    1522
    1523
    1524
    1525
    1526
    1527
    1528
    1529
    1530
    1531
    1532
    1533
    1534
    1535
    1536
    1537
    1538
    1539
    1540
    1541
    1542
    1543
    1544
    1545
    1546
    1547
    1548
    1549
    1550
    1551
    1552
    1553
    1554
    1555
    1556
    1557
    1558
    1559
    1560
    1561
    1562
    1563
    1564
    1565
    1566
    1567
    1568
    1569
    1570
    1571
    1572
    1573
    1574
    1575
    1576
    1577
    1578
    1579
    1580
    1581
    1582
    1583
    1584
    1585
    1586
    1587
    1588
    1589
    1590
    1591
    1592
    1593
    1594
    1595
    1596
    1597
    1598
    1599
    1600
    1601
    1602
    1603
    1604
    1605
    1606
    1607
    1608
    1609
    1610
    1611
    1612
    1613
    1614
    1615
    1616
    1617
    1618
    1619
    1620
    1621
    1622
    1623
    1624
    1625
    1626
    1627
    1628
    1629
    1630
    1631
    1632
    1633
    1634
    1635
    1636
    1637
    1638
    1639
    1640
    1641
    1642
    1643
    1644
    1645
    1646
    1647
    1648
    1649
    1650
    1651
    1652
    1653
    1654
    1655
    1656
    1657
    1658
    1659
    1660
    1661
    1662
    1663
    1664
    1665
    1666
    1667
    1668
    1669
    1670
    1671
    1672
    1673
    1674
    1675
    1676
    1677
    1678
    1679
    1680
    1681
    1682
    1683
    1684
    1685
    1686
    1687
    1688
    1689
    1690
    1691
    1692
    1693
    1694
    1695
    1696
    1697
    1698
    1699
    1700
    1701
    1702
    1703
    1704
    1705
    1706
    1707
    1708
    1709
    1710
    1711
    1712
    1713
    1714
    1715
    1716
    1717
    1718
    1719
    1720
    1721
    1722
    1723
    1724
    1725
    1726
    1727
    1728
    1729
    1730
    1731
    1732
    1733
    1734
    1735
    1736
    1737
    1738
    1739
    1740
    1741
    1742
    1743
    1744
    1745
    1746
    1747
    1748
    1749
    1750
    1751
    1752
    1753
    1754
    1755
    1756
    1757
    1758
    1759
    1760
    1761
    1762
    1763
    1764
    1765
    1766
    1767
    1768
    1769
    1770
    1771
    1772
    1773
    1774
    1775
    1776
    1777
    1778
    1779
    1780
    1781
    1782
    1783
    1784
    1785
    1786
    1787
    1788
    1789
    1790
    1791
    1792
    1793
    1794
    1795
    1796
    1797
    1798
    1799
    1800
    1801
    1802
    1803
    1804
    1805
    1806
    1807
    1808
    1809
    1810
    1811
    1812
    1813
    1814
    1815
    1816
    1817
    1818
    1819
    1820
    1821
    1822
    1823
    1824
    1825
    1826
    1827
    1828
    1829
    1830
    1831
    1832
    1833
    1834
    1835
    1836
    1837
    1838
    1839
    1840
    1841
    1842
    1843
    1844
    1845
    1846
    1847
    1848
    1849
    1850
    1851
    1852
    1853
    1854
    1855
    1856
    1857
    1858
    1859
    1860
    1861
    1862
    1863
    1864
    1865
    1866
    1867
    1868
    1869
    1870
    1871
    1872
    1873
    1874
    1875
    1876
    1877
    1878
    1879
    1880
    1881
    1882
    1883
    1884
    1885
    1886
    1887
    1888
    1889
    1890
    1891
    1892
    1893
    1894
    1895
    1896
    1897
    1898
    1899
    1900
    1901
    1902
    1903
    1904
    1905
    1906
    1907
    1908
    1909
    1910
    1911
    1912
    1913
    1914
    1915
    1916
    1917
    1918
    1919
    1920
    1921
    1922
    1923
    1924
    1925
    1926
    1927
    1928
    1929
    1930
    1931
    1932
    1933
    1934
    1935
    1936
    1937
    1938
    1939
    1940
    1941
    1942
    1943
    1944
    1945
    1946
    1947
    1948
    1949
    1950
    1951
    1952
    1953
    1954
    1955
    1956
    1957
    1958
    1959
    1960
    1961
    1962
    1963
    1964
    1965
    1966
    1967
    1968
    1969
    1970
    1971
    1972
    1973
    1974
    1975
    1976
    1977
    1978
    1979
    1980
    1981
    1982
    1983
    1984
    1985
    1986
    1987
    1988
    1989
    1990
    1991
    1992
    1993
    1994
    1995
    1996
    1997
    1998
    1999
    2000
    2001
    2002
    2003
    2004
    2005
    2006
    2007
    2008
    2009
    2010
    2011
    2012
    2013
    2014
    2015
    2016
    2017
    2018
    2019
    2020
    2021
    2022
    2023
    2024
    2025
    2026
    2027
    2028
    2029
    2030
    2031
    2032
    2033
    2034
    2035
    2036
    2037
    2038
    2039
    2040
    2041
    2042
    2043
    2044
    2045
    2046
    2047
    2048
    2049
    2050
    2051
    2052
    2053
    2054
    2055
    2056
    2057
    2058
    2059
    2060
    2061
    2062
    2063
    2064
    2065
    2066
    2067
    2068
    2069
    2070
    2071
    2072
    2073
    2074
    2075
    2076
    2077
    2078
    2079
    2080
    2081
    2082
    2083
    2084
    2085
    2086
    2087
    2088
    2089
    2090
    2091
    2092
    2093
    2094
    2095
    2096
    2097
    2098
    2099
    2100
    2101
    2102
    2103
    2104
    2105
    2106
    2107
    2108
    2109
    2110
    2111
    2112
    2113
    2114
    2115
    2116
    2117
    2118
    2119
    2120
    2121
    2122
    2123
    2124
    2125
    2126
    2127
    2128
    2129
    2130
    2131
    2132
    2133
    2134
    2135
    2136
    2137
    2138
    2139
    2140
    2141
    2142
    2143
    2144
    2145
    2146
    2147
    2148
    2149
    2150
    2151
    2152
    2153
    2154
    2155
    2156
    2157
    2158
    2159
    2160
    2161
    2162
    2163
    2164
    2165
    2166
    2167
    2168
    2169
    2170
    2171
    2172
    2173
    2174
    2175
    2176
    2177
    2178
    2179
    2180
    2181
    2182
    2183
    2184
    2185
    2186
    2187
    2188
    2189
    2190
    2191
    2192
    2193
    2194
    2195
    2196
    2197
    2198
    2199
    2200
    2201
    2202
    2203
    2204
    2205
    2206
    2207
    2208
    2209
    2210
    2211
    2212
    2213
    2214
    2215
    2216
    2217
    2218
    2219
    2220
    2221
    2222
    2223
    2224
    2225
    2226
    2227
    2228
    2229
    2230
    2231
    2232
    2233
    2234
    2235
    2236
    2237
    2238
    2239
    2240
    2241
    2242
    2243
    2244
    2245
    2246
    2247
    2248
    2249
    2250
    2251
    2252
    2253
    2254
    2255
    2256
    2257
    2258
    2259
    2260
    2261
    2262
    2263
    2264
    2265
    2266
    2267
    2268
    2269
    2270
    2271
    2272
    2273
    2274
    2275
    2276
    2277
    2278
    2279
    2280
    2281
    2282
    2283
    2284
    2285
    2286
    2287
    2288
    2289
    2290
    2291
    2292
    2293
    2294
    2295
    2296
    2297
    2298
    2299
    2300
    2301
    2302
    2303
    2304
    2305
    2306
    2307
    2308
    2309
    2310
    2311
    2312
    2313
    2314
    2315
    2316
    2317
    2318
    2319
    2320
    2321
    2322
    2323
    2324
    2325
    2326
    2327
    2328
    2329
    2330
    2331
    2332
    2333
    2334
    2335
    2336
    2337
    2338
    2339
    2340
    2341
    2342
    2343
    2344
    2345
    2346
    2347
    2348
    2349
    2350
    2351
    2352
    2353
    2354
    2355
    2356
    2357
    2358
    2359
    2360
    2361
    2362
    2363
    2364
    2365
    2366
    2367
    2368
    2369
    2370
    2371
    2372
    2373
    2374
    2375
    2376
    2377
    2378
    2379
    2380
    2381
    2382
    2383
    2384
    2385
    2386
    C:.
    │ .gitignore
    │ LICENSE
    │ pom.xml

    ├─.idea
    │ │ .gitignore
    │ │ compiler.xml
    │ │ encodings.xml
    │ │ jarRepositories.xml
    │ │ misc.xml
    │ │ uiDesigner.xml
    │ │ vcs.xml
    │ │ workspace.xml
    │ │
    │ └─codeStyles
    │ codeStyleConfig.xml
    │ Project.xml

    ├─gulimall-common
    │ │ pom.xml
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─common
    │ │ │ │ ├─exception
    │ │ │ │ │ RRException.java
    │ │ │ │ │
    │ │ │ │ ├─utils
    │ │ │ │ │ Constant.java
    │ │ │ │ │ PageUtils.java
    │ │ │ │ │ Query.java
    │ │ │ │ │ R.java
    │ │ │ │ │
    │ │ │ │ └─xss
    │ │ │ │ HTMLFilter.java
    │ │ │ │ SQLFilter.java
    │ │ │ │
    │ │ │ └─resources
    │ │ └─test
    │ │ └─java
    │ └─target
    │ ├─classes
    │ │ └─com
    │ │ └─atguigu
    │ │ └─common
    │ │ ├─exception
    │ │ │ RRException.class
    │ │ │
    │ │ ├─utils
    │ │ │ Constant$MenuType.class
    │ │ │ Constant$ScheduleStatus.class
    │ │ │ Constant.class
    │ │ │ PageUtils.class
    │ │ │ Query.class
    │ │ │ R.class
    │ │ │
    │ │ └─xss
    │ │ HTMLFilter.class
    │ │ SQLFilter.class
    │ │
    │ └─generated-sources
    │ └─annotations
    ├─gulimall-coupon
    │ │ .gitignore
    │ │ gulimall-coupon.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─coupon
    │ │ │ │ │ GulimallCouponApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ CouponController.java
    │ │ │ │ │ CouponHistoryController.java
    │ │ │ │ │ CouponSpuCategoryRelationController.java
    │ │ │ │ │ CouponSpuRelationController.java
    │ │ │ │ │ HomeAdvController.java
    │ │ │ │ │ HomeSubjectController.java
    │ │ │ │ │ HomeSubjectSpuController.java
    │ │ │ │ │ MemberPriceController.java
    │ │ │ │ │ SeckillPromotionController.java
    │ │ │ │ │ SeckillSessionController.java
    │ │ │ │ │ SeckillSkuNoticeController.java
    │ │ │ │ │ SeckillSkuRelationController.java
    │ │ │ │ │ SkuFullReductionController.java
    │ │ │ │ │ SkuLadderController.java
    │ │ │ │ │ SpuBoundsController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ CouponDao.java
    │ │ │ │ │ CouponHistoryDao.java
    │ │ │ │ │ CouponSpuCategoryRelationDao.java
    │ │ │ │ │ CouponSpuRelationDao.java
    │ │ │ │ │ HomeAdvDao.java
    │ │ │ │ │ HomeSubjectDao.java
    │ │ │ │ │ HomeSubjectSpuDao.java
    │ │ │ │ │ MemberPriceDao.java
    │ │ │ │ │ SeckillPromotionDao.java
    │ │ │ │ │ SeckillSessionDao.java
    │ │ │ │ │ SeckillSkuNoticeDao.java
    │ │ │ │ │ SeckillSkuRelationDao.java
    │ │ │ │ │ SkuFullReductionDao.java
    │ │ │ │ │ SkuLadderDao.java
    │ │ │ │ │ SpuBoundsDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ CouponEntity.java
    │ │ │ │ │ CouponHistoryEntity.java
    │ │ │ │ │ CouponSpuCategoryRelationEntity.java
    │ │ │ │ │ CouponSpuRelationEntity.java
    │ │ │ │ │ HomeAdvEntity.java
    │ │ │ │ │ HomeSubjectEntity.java
    │ │ │ │ │ HomeSubjectSpuEntity.java
    │ │ │ │ │ MemberPriceEntity.java
    │ │ │ │ │ SeckillPromotionEntity.java
    │ │ │ │ │ SeckillSessionEntity.java
    │ │ │ │ │ SeckillSkuNoticeEntity.java
    │ │ │ │ │ SeckillSkuRelationEntity.java
    │ │ │ │ │ SkuFullReductionEntity.java
    │ │ │ │ │ SkuLadderEntity.java
    │ │ │ │ │ SpuBoundsEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ CouponHistoryService.java
    │ │ │ │ │ CouponService.java
    │ │ │ │ │ CouponSpuCategoryRelationService.java
    │ │ │ │ │ CouponSpuRelationService.java
    │ │ │ │ │ HomeAdvService.java
    │ │ │ │ │ HomeSubjectService.java
    │ │ │ │ │ HomeSubjectSpuService.java
    │ │ │ │ │ MemberPriceService.java
    │ │ │ │ │ SeckillPromotionService.java
    │ │ │ │ │ SeckillSessionService.java
    │ │ │ │ │ SeckillSkuNoticeService.java
    │ │ │ │ │ SeckillSkuRelationService.java
    │ │ │ │ │ SkuFullReductionService.java
    │ │ │ │ │ SkuLadderService.java
    │ │ │ │ │ SpuBoundsService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ CouponHistoryServiceImpl.java
    │ │ │ │ CouponServiceImpl.java
    │ │ │ │ CouponSpuCategoryRelationServiceImpl.java
    │ │ │ │ CouponSpuRelationServiceImpl.java
    │ │ │ │ HomeAdvServiceImpl.java
    │ │ │ │ HomeSubjectServiceImpl.java
    │ │ │ │ HomeSubjectSpuServiceImpl.java
    │ │ │ │ MemberPriceServiceImpl.java
    │ │ │ │ SeckillPromotionServiceImpl.java
    │ │ │ │ SeckillSessionServiceImpl.java
    │ │ │ │ SeckillSkuNoticeServiceImpl.java
    │ │ │ │ SeckillSkuRelationServiceImpl.java
    │ │ │ │ SkuFullReductionServiceImpl.java
    │ │ │ │ SkuLadderServiceImpl.java
    │ │ │ │ SpuBoundsServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.properties
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─coupon
    │ │ │ │ CouponDao.xml
    │ │ │ │ CouponHistoryDao.xml
    │ │ │ │ CouponSpuCategoryRelationDao.xml
    │ │ │ │ CouponSpuRelationDao.xml
    │ │ │ │ HomeAdvDao.xml
    │ │ │ │ HomeSubjectDao.xml
    │ │ │ │ HomeSubjectSpuDao.xml
    │ │ │ │ MemberPriceDao.xml
    │ │ │ │ SeckillPromotionDao.xml
    │ │ │ │ SeckillSessionDao.xml
    │ │ │ │ SeckillSkuNoticeDao.xml
    │ │ │ │ SeckillSkuRelationDao.xml
    │ │ │ │ SkuFullReductionDao.xml
    │ │ │ │ SkuLadderDao.xml
    │ │ │ │ SpuBoundsDao.xml
    │ │ │ │
    │ │ │ ├─src
    │ │ │ │ └─views
    │ │ │ │ └─modules
    │ │ │ │ └─coupon
    │ │ │ │ coupon-add-or-update.vue
    │ │ │ │ coupon.vue
    │ │ │ │ couponhistory-add-or-update.vue
    │ │ │ │ couponhistory.vue
    │ │ │ │ couponspucategoryrelation-add-or-update.vue
    │ │ │ │ couponspucategoryrelation.vue
    │ │ │ │ couponspurelation-add-or-update.vue
    │ │ │ │ couponspurelation.vue
    │ │ │ │ homeadv-add-or-update.vue
    │ │ │ │ homeadv.vue
    │ │ │ │ homesubject-add-or-update.vue
    │ │ │ │ homesubject.vue
    │ │ │ │ homesubjectspu-add-or-update.vue
    │ │ │ │ homesubjectspu.vue
    │ │ │ │ memberprice-add-or-update.vue
    │ │ │ │ memberprice.vue
    │ │ │ │ seckillpromotion-add-or-update.vue
    │ │ │ │ seckillpromotion.vue
    │ │ │ │ seckillsession-add-or-update.vue
    │ │ │ │ seckillsession.vue
    │ │ │ │ seckillskunotice-add-or-update.vue
    │ │ │ │ seckillskunotice.vue
    │ │ │ │ seckillskurelation-add-or-update.vue
    │ │ │ │ seckillskurelation.vue
    │ │ │ │ skufullreduction-add-or-update.vue
    │ │ │ │ skufullreduction.vue
    │ │ │ │ skuladder-add-or-update.vue
    │ │ │ │ skuladder.vue
    │ │ │ │ spubounds-add-or-update.vue
    │ │ │ │ spubounds.vue
    │ │ │ │
    │ │ │ ├─static
    │ │ │ └─templates
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─coupon
    │ │ GulimallCouponApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.properties
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ └─coupon
    │ │ │ │ GulimallCouponApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ CouponController.class
    │ │ │ │ CouponHistoryController.class
    │ │ │ │ CouponSpuCategoryRelationController.class
    │ │ │ │ CouponSpuRelationController.class
    │ │ │ │ HomeAdvController.class
    │ │ │ │ HomeSubjectController.class
    │ │ │ │ HomeSubjectSpuController.class
    │ │ │ │ MemberPriceController.class
    │ │ │ │ SeckillPromotionController.class
    │ │ │ │ SeckillSessionController.class
    │ │ │ │ SeckillSkuNoticeController.class
    │ │ │ │ SeckillSkuRelationController.class
    │ │ │ │ SkuFullReductionController.class
    │ │ │ │ SkuLadderController.class
    │ │ │ │ SpuBoundsController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ CouponDao.class
    │ │ │ │ CouponHistoryDao.class
    │ │ │ │ CouponSpuCategoryRelationDao.class
    │ │ │ │ CouponSpuRelationDao.class
    │ │ │ │ HomeAdvDao.class
    │ │ │ │ HomeSubjectDao.class
    │ │ │ │ HomeSubjectSpuDao.class
    │ │ │ │ MemberPriceDao.class
    │ │ │ │ SeckillPromotionDao.class
    │ │ │ │ SeckillSessionDao.class
    │ │ │ │ SeckillSkuNoticeDao.class
    │ │ │ │ SeckillSkuRelationDao.class
    │ │ │ │ SkuFullReductionDao.class
    │ │ │ │ SkuLadderDao.class
    │ │ │ │ SpuBoundsDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ CouponEntity.class
    │ │ │ │ CouponHistoryEntity.class
    │ │ │ │ CouponSpuCategoryRelationEntity.class
    │ │ │ │ CouponSpuRelationEntity.class
    │ │ │ │ HomeAdvEntity.class
    │ │ │ │ HomeSubjectEntity.class
    │ │ │ │ HomeSubjectSpuEntity.class
    │ │ │ │ MemberPriceEntity.class
    │ │ │ │ SeckillPromotionEntity.class
    │ │ │ │ SeckillSessionEntity.class
    │ │ │ │ SeckillSkuNoticeEntity.class
    │ │ │ │ SeckillSkuRelationEntity.class
    │ │ │ │ SkuFullReductionEntity.class
    │ │ │ │ SkuLadderEntity.class
    │ │ │ │ SpuBoundsEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ CouponHistoryService.class
    │ │ │ │ CouponService.class
    │ │ │ │ CouponSpuCategoryRelationService.class
    │ │ │ │ CouponSpuRelationService.class
    │ │ │ │ HomeAdvService.class
    │ │ │ │ HomeSubjectService.class
    │ │ │ │ HomeSubjectSpuService.class
    │ │ │ │ MemberPriceService.class
    │ │ │ │ SeckillPromotionService.class
    │ │ │ │ SeckillSessionService.class
    │ │ │ │ SeckillSkuNoticeService.class
    │ │ │ │ SeckillSkuRelationService.class
    │ │ │ │ SkuFullReductionService.class
    │ │ │ │ SkuLadderService.class
    │ │ │ │ SpuBoundsService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ CouponHistoryServiceImpl.class
    │ │ │ CouponServiceImpl.class
    │ │ │ CouponSpuCategoryRelationServiceImpl.class
    │ │ │ CouponSpuRelationServiceImpl.class
    │ │ │ HomeAdvServiceImpl.class
    │ │ │ HomeSubjectServiceImpl.class
    │ │ │ HomeSubjectSpuServiceImpl.class
    │ │ │ MemberPriceServiceImpl.class
    │ │ │ SeckillPromotionServiceImpl.class
    │ │ │ SeckillSessionServiceImpl.class
    │ │ │ SeckillSkuNoticeServiceImpl.class
    │ │ │ SeckillSkuRelationServiceImpl.class
    │ │ │ SkuFullReductionServiceImpl.class
    │ │ │ SkuLadderServiceImpl.class
    │ │ │ SpuBoundsServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─coupon
    │ │ │ CouponDao.xml
    │ │ │ CouponHistoryDao.xml
    │ │ │ CouponSpuCategoryRelationDao.xml
    │ │ │ CouponSpuRelationDao.xml
    │ │ │ HomeAdvDao.xml
    │ │ │ HomeSubjectDao.xml
    │ │ │ HomeSubjectSpuDao.xml
    │ │ │ MemberPriceDao.xml
    │ │ │ SeckillPromotionDao.xml
    │ │ │ SeckillSessionDao.xml
    │ │ │ SeckillSkuNoticeDao.xml
    │ │ │ SeckillSkuRelationDao.xml
    │ │ │ SkuFullReductionDao.xml
    │ │ │ SkuLadderDao.xml
    │ │ │ SpuBoundsDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─coupon
    │ │ coupon-add-or-update.vue
    │ │ coupon.vue
    │ │ couponhistory-add-or-update.vue
    │ │ couponhistory.vue
    │ │ couponspucategoryrelation-add-or-update.vue
    │ │ couponspucategoryrelation.vue
    │ │ couponspurelation-add-or-update.vue
    │ │ couponspurelation.vue
    │ │ homeadv-add-or-update.vue
    │ │ homeadv.vue
    │ │ homesubject-add-or-update.vue
    │ │ homesubject.vue
    │ │ homesubjectspu-add-or-update.vue
    │ │ homesubjectspu.vue
    │ │ memberprice-add-or-update.vue
    │ │ memberprice.vue
    │ │ seckillpromotion-add-or-update.vue
    │ │ seckillpromotion.vue
    │ │ seckillsession-add-or-update.vue
    │ │ seckillsession.vue
    │ │ seckillskunotice-add-or-update.vue
    │ │ seckillskunotice.vue
    │ │ seckillskurelation-add-or-update.vue
    │ │ seckillskurelation.vue
    │ │ skufullreduction-add-or-update.vue
    │ │ skufullreduction.vue
    │ │ skuladder-add-or-update.vue
    │ │ skuladder.vue
    │ │ spubounds-add-or-update.vue
    │ │ spubounds.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ └─maven-status
    │ └─maven-compiler-plugin
    │ └─compile
    │ └─default-compile
    │ createdFiles.lst
    │ inputFiles.lst

    ├─gulimall-member
    │ │ .gitignore
    │ │ gulimall-member.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─member
    │ │ │ │ │ GulimallMemberApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ GrowthChangeHistoryController.java
    │ │ │ │ │ IntegrationChangeHistoryController.java
    │ │ │ │ │ MemberCollectSpuController.java
    │ │ │ │ │ MemberCollectSubjectController.java
    │ │ │ │ │ MemberController.java
    │ │ │ │ │ MemberLevelController.java
    │ │ │ │ │ MemberLoginLogController.java
    │ │ │ │ │ MemberReceiveAddressController.java
    │ │ │ │ │ MemberStatisticsInfoController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ GrowthChangeHistoryDao.java
    │ │ │ │ │ IntegrationChangeHistoryDao.java
    │ │ │ │ │ MemberCollectSpuDao.java
    │ │ │ │ │ MemberCollectSubjectDao.java
    │ │ │ │ │ MemberDao.java
    │ │ │ │ │ MemberLevelDao.java
    │ │ │ │ │ MemberLoginLogDao.java
    │ │ │ │ │ MemberReceiveAddressDao.java
    │ │ │ │ │ MemberStatisticsInfoDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ GrowthChangeHistoryEntity.java
    │ │ │ │ │ IntegrationChangeHistoryEntity.java
    │ │ │ │ │ MemberCollectSpuEntity.java
    │ │ │ │ │ MemberCollectSubjectEntity.java
    │ │ │ │ │ MemberEntity.java
    │ │ │ │ │ MemberLevelEntity.java
    │ │ │ │ │ MemberLoginLogEntity.java
    │ │ │ │ │ MemberReceiveAddressEntity.java
    │ │ │ │ │ MemberStatisticsInfoEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ GrowthChangeHistoryService.java
    │ │ │ │ │ IntegrationChangeHistoryService.java
    │ │ │ │ │ MemberCollectSpuService.java
    │ │ │ │ │ MemberCollectSubjectService.java
    │ │ │ │ │ MemberLevelService.java
    │ │ │ │ │ MemberLoginLogService.java
    │ │ │ │ │ MemberReceiveAddressService.java
    │ │ │ │ │ MemberService.java
    │ │ │ │ │ MemberStatisticsInfoService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ GrowthChangeHistoryServiceImpl.java
    │ │ │ │ IntegrationChangeHistoryServiceImpl.java
    │ │ │ │ MemberCollectSpuServiceImpl.java
    │ │ │ │ MemberCollectSubjectServiceImpl.java
    │ │ │ │ MemberLevelServiceImpl.java
    │ │ │ │ MemberLoginLogServiceImpl.java
    │ │ │ │ MemberReceiveAddressServiceImpl.java
    │ │ │ │ MemberServiceImpl.java
    │ │ │ │ MemberStatisticsInfoServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.properties
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─member
    │ │ │ │ GrowthChangeHistoryDao.xml
    │ │ │ │ IntegrationChangeHistoryDao.xml
    │ │ │ │ MemberCollectSpuDao.xml
    │ │ │ │ MemberCollectSubjectDao.xml
    │ │ │ │ MemberDao.xml
    │ │ │ │ MemberLevelDao.xml
    │ │ │ │ MemberLoginLogDao.xml
    │ │ │ │ MemberReceiveAddressDao.xml
    │ │ │ │ MemberStatisticsInfoDao.xml
    │ │ │ │
    │ │ │ ├─src
    │ │ │ │ └─views
    │ │ │ │ └─modules
    │ │ │ │ └─member
    │ │ │ │ growthchangehistory-add-or-update.vue
    │ │ │ │ growthchangehistory.vue
    │ │ │ │ integrationchangehistory-add-or-update.vue
    │ │ │ │ integrationchangehistory.vue
    │ │ │ │ member-add-or-update.vue
    │ │ │ │ member.vue
    │ │ │ │ membercollectspu-add-or-update.vue
    │ │ │ │ membercollectspu.vue
    │ │ │ │ membercollectsubject-add-or-update.vue
    │ │ │ │ membercollectsubject.vue
    │ │ │ │ memberlevel-add-or-update.vue
    │ │ │ │ memberlevel.vue
    │ │ │ │ memberloginlog-add-or-update.vue
    │ │ │ │ memberloginlog.vue
    │ │ │ │ memberreceiveaddress-add-or-update.vue
    │ │ │ │ memberreceiveaddress.vue
    │ │ │ │ memberstatisticsinfo-add-or-update.vue
    │ │ │ │ memberstatisticsinfo.vue
    │ │ │ │
    │ │ │ ├─static
    │ │ │ └─templates
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─member
    │ │ GulimallMemberApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.properties
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ └─member
    │ │ │ │ GulimallMemberApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ GrowthChangeHistoryController.class
    │ │ │ │ IntegrationChangeHistoryController.class
    │ │ │ │ MemberCollectSpuController.class
    │ │ │ │ MemberCollectSubjectController.class
    │ │ │ │ MemberController.class
    │ │ │ │ MemberLevelController.class
    │ │ │ │ MemberLoginLogController.class
    │ │ │ │ MemberReceiveAddressController.class
    │ │ │ │ MemberStatisticsInfoController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ GrowthChangeHistoryDao.class
    │ │ │ │ IntegrationChangeHistoryDao.class
    │ │ │ │ MemberCollectSpuDao.class
    │ │ │ │ MemberCollectSubjectDao.class
    │ │ │ │ MemberDao.class
    │ │ │ │ MemberLevelDao.class
    │ │ │ │ MemberLoginLogDao.class
    │ │ │ │ MemberReceiveAddressDao.class
    │ │ │ │ MemberStatisticsInfoDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ GrowthChangeHistoryEntity.class
    │ │ │ │ IntegrationChangeHistoryEntity.class
    │ │ │ │ MemberCollectSpuEntity.class
    │ │ │ │ MemberCollectSubjectEntity.class
    │ │ │ │ MemberEntity.class
    │ │ │ │ MemberLevelEntity.class
    │ │ │ │ MemberLoginLogEntity.class
    │ │ │ │ MemberReceiveAddressEntity.class
    │ │ │ │ MemberStatisticsInfoEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ GrowthChangeHistoryService.class
    │ │ │ │ IntegrationChangeHistoryService.class
    │ │ │ │ MemberCollectSpuService.class
    │ │ │ │ MemberCollectSubjectService.class
    │ │ │ │ MemberLevelService.class
    │ │ │ │ MemberLoginLogService.class
    │ │ │ │ MemberReceiveAddressService.class
    │ │ │ │ MemberService.class
    │ │ │ │ MemberStatisticsInfoService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ GrowthChangeHistoryServiceImpl.class
    │ │ │ IntegrationChangeHistoryServiceImpl.class
    │ │ │ MemberCollectSpuServiceImpl.class
    │ │ │ MemberCollectSubjectServiceImpl.class
    │ │ │ MemberLevelServiceImpl.class
    │ │ │ MemberLoginLogServiceImpl.class
    │ │ │ MemberReceiveAddressServiceImpl.class
    │ │ │ MemberServiceImpl.class
    │ │ │ MemberStatisticsInfoServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─member
    │ │ │ GrowthChangeHistoryDao.xml
    │ │ │ IntegrationChangeHistoryDao.xml
    │ │ │ MemberCollectSpuDao.xml
    │ │ │ MemberCollectSubjectDao.xml
    │ │ │ MemberDao.xml
    │ │ │ MemberLevelDao.xml
    │ │ │ MemberLoginLogDao.xml
    │ │ │ MemberReceiveAddressDao.xml
    │ │ │ MemberStatisticsInfoDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─member
    │ │ growthchangehistory-add-or-update.vue
    │ │ growthchangehistory.vue
    │ │ integrationchangehistory-add-or-update.vue
    │ │ integrationchangehistory.vue
    │ │ member-add-or-update.vue
    │ │ member.vue
    │ │ membercollectspu-add-or-update.vue
    │ │ membercollectspu.vue
    │ │ membercollectsubject-add-or-update.vue
    │ │ membercollectsubject.vue
    │ │ memberlevel-add-or-update.vue
    │ │ memberlevel.vue
    │ │ memberloginlog-add-or-update.vue
    │ │ memberloginlog.vue
    │ │ memberreceiveaddress-add-or-update.vue
    │ │ memberreceiveaddress.vue
    │ │ memberstatisticsinfo-add-or-update.vue
    │ │ memberstatisticsinfo.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ └─maven-status
    │ └─maven-compiler-plugin
    │ └─compile
    │ └─default-compile
    │ createdFiles.lst
    │ inputFiles.lst

    ├─gulimall-order
    │ │ .gitignore
    │ │ gulimall-order.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─order
    │ │ │ │ │ GulimallOrderApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ OrderController.java
    │ │ │ │ │ OrderItemController.java
    │ │ │ │ │ OrderOperateHistoryController.java
    │ │ │ │ │ OrderReturnApplyController.java
    │ │ │ │ │ OrderReturnReasonController.java
    │ │ │ │ │ OrderSettingController.java
    │ │ │ │ │ PaymentInfoController.java
    │ │ │ │ │ RefundInfoController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ OrderDao.java
    │ │ │ │ │ OrderItemDao.java
    │ │ │ │ │ OrderOperateHistoryDao.java
    │ │ │ │ │ OrderReturnApplyDao.java
    │ │ │ │ │ OrderReturnReasonDao.java
    │ │ │ │ │ OrderSettingDao.java
    │ │ │ │ │ PaymentInfoDao.java
    │ │ │ │ │ RefundInfoDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ OrderEntity.java
    │ │ │ │ │ OrderItemEntity.java
    │ │ │ │ │ OrderOperateHistoryEntity.java
    │ │ │ │ │ OrderReturnApplyEntity.java
    │ │ │ │ │ OrderReturnReasonEntity.java
    │ │ │ │ │ OrderSettingEntity.java
    │ │ │ │ │ PaymentInfoEntity.java
    │ │ │ │ │ RefundInfoEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ OrderItemService.java
    │ │ │ │ │ OrderOperateHistoryService.java
    │ │ │ │ │ OrderReturnApplyService.java
    │ │ │ │ │ OrderReturnReasonService.java
    │ │ │ │ │ OrderService.java
    │ │ │ │ │ OrderSettingService.java
    │ │ │ │ │ PaymentInfoService.java
    │ │ │ │ │ RefundInfoService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ OrderItemServiceImpl.java
    │ │ │ │ OrderOperateHistoryServiceImpl.java
    │ │ │ │ OrderReturnApplyServiceImpl.java
    │ │ │ │ OrderReturnReasonServiceImpl.java
    │ │ │ │ OrderServiceImpl.java
    │ │ │ │ OrderSettingServiceImpl.java
    │ │ │ │ PaymentInfoServiceImpl.java
    │ │ │ │ RefundInfoServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.properties
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─order
    │ │ │ │ OrderDao.xml
    │ │ │ │ OrderItemDao.xml
    │ │ │ │ OrderOperateHistoryDao.xml
    │ │ │ │ OrderReturnApplyDao.xml
    │ │ │ │ OrderReturnReasonDao.xml
    │ │ │ │ OrderSettingDao.xml
    │ │ │ │ PaymentInfoDao.xml
    │ │ │ │ RefundInfoDao.xml
    │ │ │ │
    │ │ │ ├─src
    │ │ │ │ └─views
    │ │ │ │ └─modules
    │ │ │ │ └─order
    │ │ │ │ order-add-or-update.vue
    │ │ │ │ order.vue
    │ │ │ │ orderitem-add-or-update.vue
    │ │ │ │ orderitem.vue
    │ │ │ │ orderoperatehistory-add-or-update.vue
    │ │ │ │ orderoperatehistory.vue
    │ │ │ │ orderreturnapply-add-or-update.vue
    │ │ │ │ orderreturnapply.vue
    │ │ │ │ orderreturnreason-add-or-update.vue
    │ │ │ │ orderreturnreason.vue
    │ │ │ │ ordersetting-add-or-update.vue
    │ │ │ │ ordersetting.vue
    │ │ │ │ paymentinfo-add-or-update.vue
    │ │ │ │ paymentinfo.vue
    │ │ │ │ refundinfo-add-or-update.vue
    │ │ │ │ refundinfo.vue
    │ │ │ │
    │ │ │ ├─static
    │ │ │ └─templates
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─order
    │ │ GulimallOrderApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.properties
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ ├─gulimallorder
    │ │ │ │ GulimallOrderApplication.class
    │ │ │ │
    │ │ │ └─order
    │ │ │ │ GulimallOrderApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ OrderController.class
    │ │ │ │ OrderItemController.class
    │ │ │ │ OrderOperateHistoryController.class
    │ │ │ │ OrderReturnApplyController.class
    │ │ │ │ OrderReturnReasonController.class
    │ │ │ │ OrderSettingController.class
    │ │ │ │ PaymentInfoController.class
    │ │ │ │ RefundInfoController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ OrderDao.class
    │ │ │ │ OrderItemDao.class
    │ │ │ │ OrderOperateHistoryDao.class
    │ │ │ │ OrderReturnApplyDao.class
    │ │ │ │ OrderReturnReasonDao.class
    │ │ │ │ OrderSettingDao.class
    │ │ │ │ PaymentInfoDao.class
    │ │ │ │ RefundInfoDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ OrderEntity.class
    │ │ │ │ OrderItemEntity.class
    │ │ │ │ OrderOperateHistoryEntity.class
    │ │ │ │ OrderReturnApplyEntity.class
    │ │ │ │ OrderReturnReasonEntity.class
    │ │ │ │ OrderSettingEntity.class
    │ │ │ │ PaymentInfoEntity.class
    │ │ │ │ RefundInfoEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ OrderItemService.class
    │ │ │ │ OrderOperateHistoryService.class
    │ │ │ │ OrderReturnApplyService.class
    │ │ │ │ OrderReturnReasonService.class
    │ │ │ │ OrderService.class
    │ │ │ │ OrderSettingService.class
    │ │ │ │ PaymentInfoService.class
    │ │ │ │ RefundInfoService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ OrderItemServiceImpl.class
    │ │ │ OrderOperateHistoryServiceImpl.class
    │ │ │ OrderReturnApplyServiceImpl.class
    │ │ │ OrderReturnReasonServiceImpl.class
    │ │ │ OrderServiceImpl.class
    │ │ │ OrderSettingServiceImpl.class
    │ │ │ PaymentInfoServiceImpl.class
    │ │ │ RefundInfoServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─order
    │ │ │ OrderDao.xml
    │ │ │ OrderItemDao.xml
    │ │ │ OrderOperateHistoryDao.xml
    │ │ │ OrderReturnApplyDao.xml
    │ │ │ OrderReturnReasonDao.xml
    │ │ │ OrderSettingDao.xml
    │ │ │ PaymentInfoDao.xml
    │ │ │ RefundInfoDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─order
    │ │ order-add-or-update.vue
    │ │ order.vue
    │ │ orderitem-add-or-update.vue
    │ │ orderitem.vue
    │ │ orderoperatehistory-add-or-update.vue
    │ │ orderoperatehistory.vue
    │ │ orderreturnapply-add-or-update.vue
    │ │ orderreturnapply.vue
    │ │ orderreturnreason-add-or-update.vue
    │ │ orderreturnreason.vue
    │ │ ordersetting-add-or-update.vue
    │ │ ordersetting.vue
    │ │ paymentinfo-add-or-update.vue
    │ │ paymentinfo.vue
    │ │ refundinfo-add-or-update.vue
    │ │ refundinfo.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ └─maven-status
    │ └─maven-compiler-plugin
    │ └─compile
    │ └─default-compile
    │ createdFiles.lst
    │ inputFiles.lst

    ├─gulimall-product
    │ │ .gitignore
    │ │ gulimall-product.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─product
    │ │ │ │ │ GulimallProductApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ AttrAttrgroupRelationController.java
    │ │ │ │ │ AttrController.java
    │ │ │ │ │ AttrGroupController.java
    │ │ │ │ │ BrandController.java
    │ │ │ │ │ CategoryBrandRelationController.java
    │ │ │ │ │ CategoryController.java
    │ │ │ │ │ CommentReplayController.java
    │ │ │ │ │ ProductAttrValueController.java
    │ │ │ │ │ SkuImagesController.java
    │ │ │ │ │ SkuInfoController.java
    │ │ │ │ │ SkuSaleAttrValueController.java
    │ │ │ │ │ SpuCommentController.java
    │ │ │ │ │ SpuImagesController.java
    │ │ │ │ │ SpuInfoController.java
    │ │ │ │ │ SpuInfoDescController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ AttrAttrgroupRelationDao.java
    │ │ │ │ │ AttrDao.java
    │ │ │ │ │ AttrGroupDao.java
    │ │ │ │ │ BrandDao.java
    │ │ │ │ │ CategoryBrandRelationDao.java
    │ │ │ │ │ CategoryDao.java
    │ │ │ │ │ CommentReplayDao.java
    │ │ │ │ │ ProductAttrValueDao.java
    │ │ │ │ │ SkuImagesDao.java
    │ │ │ │ │ SkuInfoDao.java
    │ │ │ │ │ SkuSaleAttrValueDao.java
    │ │ │ │ │ SpuCommentDao.java
    │ │ │ │ │ SpuImagesDao.java
    │ │ │ │ │ SpuInfoDao.java
    │ │ │ │ │ SpuInfoDescDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ AttrAttrgroupRelationEntity.java
    │ │ │ │ │ AttrEntity.java
    │ │ │ │ │ AttrGroupEntity.java
    │ │ │ │ │ BrandEntity.java
    │ │ │ │ │ CategoryBrandRelationEntity.java
    │ │ │ │ │ CategoryEntity.java
    │ │ │ │ │ CommentReplayEntity.java
    │ │ │ │ │ ProductAttrValueEntity.java
    │ │ │ │ │ SkuImagesEntity.java
    │ │ │ │ │ SkuInfoEntity.java
    │ │ │ │ │ SkuSaleAttrValueEntity.java
    │ │ │ │ │ SpuCommentEntity.java
    │ │ │ │ │ SpuImagesEntity.java
    │ │ │ │ │ SpuInfoDescEntity.java
    │ │ │ │ │ SpuInfoEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ AttrAttrgroupRelationService.java
    │ │ │ │ │ AttrGroupService.java
    │ │ │ │ │ AttrService.java
    │ │ │ │ │ BrandService.java
    │ │ │ │ │ CategoryBrandRelationService.java
    │ │ │ │ │ CategoryService.java
    │ │ │ │ │ CommentReplayService.java
    │ │ │ │ │ ProductAttrValueService.java
    │ │ │ │ │ SkuImagesService.java
    │ │ │ │ │ SkuInfoService.java
    │ │ │ │ │ SkuSaleAttrValueService.java
    │ │ │ │ │ SpuCommentService.java
    │ │ │ │ │ SpuImagesService.java
    │ │ │ │ │ SpuInfoDescService.java
    │ │ │ │ │ SpuInfoService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ AttrAttrgroupRelationServiceImpl.java
    │ │ │ │ AttrGroupServiceImpl.java
    │ │ │ │ AttrServiceImpl.java
    │ │ │ │ BrandServiceImpl.java
    │ │ │ │ CategoryBrandRelationServiceImpl.java
    │ │ │ │ CategoryServiceImpl.java
    │ │ │ │ CommentReplayServiceImpl.java
    │ │ │ │ ProductAttrValueServiceImpl.java
    │ │ │ │ SkuImagesServiceImpl.java
    │ │ │ │ SkuInfoServiceImpl.java
    │ │ │ │ SkuSaleAttrValueServiceImpl.java
    │ │ │ │ SpuCommentServiceImpl.java
    │ │ │ │ SpuImagesServiceImpl.java
    │ │ │ │ SpuInfoDescServiceImpl.java
    │ │ │ │ SpuInfoServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.properties
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─product
    │ │ │ │ AttrAttrgroupRelationDao.xml
    │ │ │ │ AttrDao.xml
    │ │ │ │ AttrGroupDao.xml
    │ │ │ │ BrandDao.xml
    │ │ │ │ CategoryBrandRelationDao.xml
    │ │ │ │ CategoryDao.xml
    │ │ │ │ CommentReplayDao.xml
    │ │ │ │ ProductAttrValueDao.xml
    │ │ │ │ SkuImagesDao.xml
    │ │ │ │ SkuInfoDao.xml
    │ │ │ │ SkuSaleAttrValueDao.xml
    │ │ │ │ SpuCommentDao.xml
    │ │ │ │ SpuImagesDao.xml
    │ │ │ │ SpuInfoDao.xml
    │ │ │ │ SpuInfoDescDao.xml
    │ │ │ │
    │ │ │ ├─src
    │ │ │ │ └─views
    │ │ │ │ └─modules
    │ │ │ │ └─product
    │ │ │ │ attr-add-or-update.vue
    │ │ │ │ attr.vue
    │ │ │ │ attrattrgrouprelation-add-or-update.vue
    │ │ │ │ attrattrgrouprelation.vue
    │ │ │ │ attrgroup-add-or-update.vue
    │ │ │ │ attrgroup.vue
    │ │ │ │ brand-add-or-update.vue
    │ │ │ │ brand.vue
    │ │ │ │ category-add-or-update.vue
    │ │ │ │ category.vue
    │ │ │ │ categorybrandrelation-add-or-update.vue
    │ │ │ │ categorybrandrelation.vue
    │ │ │ │ commentreplay-add-or-update.vue
    │ │ │ │ commentreplay.vue
    │ │ │ │ productattrvalue-add-or-update.vue
    │ │ │ │ productattrvalue.vue
    │ │ │ │ skuimages-add-or-update.vue
    │ │ │ │ skuimages.vue
    │ │ │ │ skuinfo-add-or-update.vue
    │ │ │ │ skuinfo.vue
    │ │ │ │ skusaleattrvalue-add-or-update.vue
    │ │ │ │ skusaleattrvalue.vue
    │ │ │ │ spucomment-add-or-update.vue
    │ │ │ │ spucomment.vue
    │ │ │ │ spuimages-add-or-update.vue
    │ │ │ │ spuimages.vue
    │ │ │ │ spuinfo-add-or-update.vue
    │ │ │ │ spuinfo.vue
    │ │ │ │ spuinfodesc-add-or-update.vue
    │ │ │ │ spuinfodesc.vue
    │ │ │ │
    │ │ │ ├─static
    │ │ │ └─templates
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─product
    │ │ GulimallProductApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.properties
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ └─product
    │ │ │ │ GulimallProductApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ AttrAttrgroupRelationController.class
    │ │ │ │ AttrController.class
    │ │ │ │ AttrGroupController.class
    │ │ │ │ BrandController.class
    │ │ │ │ CategoryBrandRelationController.class
    │ │ │ │ CategoryController.class
    │ │ │ │ CommentReplayController.class
    │ │ │ │ ProductAttrValueController.class
    │ │ │ │ SkuImagesController.class
    │ │ │ │ SkuInfoController.class
    │ │ │ │ SkuSaleAttrValueController.class
    │ │ │ │ SpuCommentController.class
    │ │ │ │ SpuImagesController.class
    │ │ │ │ SpuInfoController.class
    │ │ │ │ SpuInfoDescController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ AttrAttrgroupRelationDao.class
    │ │ │ │ AttrDao.class
    │ │ │ │ AttrGroupDao.class
    │ │ │ │ BrandDao.class
    │ │ │ │ CategoryBrandRelationDao.class
    │ │ │ │ CategoryDao.class
    │ │ │ │ CommentReplayDao.class
    │ │ │ │ ProductAttrValueDao.class
    │ │ │ │ SkuImagesDao.class
    │ │ │ │ SkuInfoDao.class
    │ │ │ │ SkuSaleAttrValueDao.class
    │ │ │ │ SpuCommentDao.class
    │ │ │ │ SpuImagesDao.class
    │ │ │ │ SpuInfoDao.class
    │ │ │ │ SpuInfoDescDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ AttrAttrgroupRelationEntity.class
    │ │ │ │ AttrEntity.class
    │ │ │ │ AttrGroupEntity.class
    │ │ │ │ BrandEntity.class
    │ │ │ │ CategoryBrandRelationEntity.class
    │ │ │ │ CategoryEntity.class
    │ │ │ │ CommentReplayEntity.class
    │ │ │ │ ProductAttrValueEntity.class
    │ │ │ │ SkuImagesEntity.class
    │ │ │ │ SkuInfoEntity.class
    │ │ │ │ SkuSaleAttrValueEntity.class
    │ │ │ │ SpuCommentEntity.class
    │ │ │ │ SpuImagesEntity.class
    │ │ │ │ SpuInfoDescEntity.class
    │ │ │ │ SpuInfoEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ AttrAttrgroupRelationService.class
    │ │ │ │ AttrGroupService.class
    │ │ │ │ AttrService.class
    │ │ │ │ BrandService.class
    │ │ │ │ CategoryBrandRelationService.class
    │ │ │ │ CategoryService.class
    │ │ │ │ CommentReplayService.class
    │ │ │ │ ProductAttrValueService.class
    │ │ │ │ SkuImagesService.class
    │ │ │ │ SkuInfoService.class
    │ │ │ │ SkuSaleAttrValueService.class
    │ │ │ │ SpuCommentService.class
    │ │ │ │ SpuImagesService.class
    │ │ │ │ SpuInfoDescService.class
    │ │ │ │ SpuInfoService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ AttrAttrgroupRelationServiceImpl.class
    │ │ │ AttrGroupServiceImpl.class
    │ │ │ AttrServiceImpl.class
    │ │ │ BrandServiceImpl.class
    │ │ │ CategoryBrandRelationServiceImpl.class
    │ │ │ CategoryServiceImpl.class
    │ │ │ CommentReplayServiceImpl.class
    │ │ │ ProductAttrValueServiceImpl.class
    │ │ │ SkuImagesServiceImpl.class
    │ │ │ SkuInfoServiceImpl.class
    │ │ │ SkuSaleAttrValueServiceImpl.class
    │ │ │ SpuCommentServiceImpl.class
    │ │ │ SpuImagesServiceImpl.class
    │ │ │ SpuInfoDescServiceImpl.class
    │ │ │ SpuInfoServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─product
    │ │ │ AttrAttrgroupRelationDao.xml
    │ │ │ AttrDao.xml
    │ │ │ AttrGroupDao.xml
    │ │ │ BrandDao.xml
    │ │ │ CategoryBrandRelationDao.xml
    │ │ │ CategoryDao.xml
    │ │ │ CommentReplayDao.xml
    │ │ │ ProductAttrValueDao.xml
    │ │ │ SkuImagesDao.xml
    │ │ │ SkuInfoDao.xml
    │ │ │ SkuSaleAttrValueDao.xml
    │ │ │ SpuCommentDao.xml
    │ │ │ SpuImagesDao.xml
    │ │ │ SpuInfoDao.xml
    │ │ │ SpuInfoDescDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─product
    │ │ attr-add-or-update.vue
    │ │ attr.vue
    │ │ attrattrgrouprelation-add-or-update.vue
    │ │ attrattrgrouprelation.vue
    │ │ attrgroup-add-or-update.vue
    │ │ attrgroup.vue
    │ │ brand-add-or-update.vue
    │ │ brand.vue
    │ │ category-add-or-update.vue
    │ │ category.vue
    │ │ categorybrandrelation-add-or-update.vue
    │ │ categorybrandrelation.vue
    │ │ commentreplay-add-or-update.vue
    │ │ commentreplay.vue
    │ │ productattrvalue-add-or-update.vue
    │ │ productattrvalue.vue
    │ │ skuimages-add-or-update.vue
    │ │ skuimages.vue
    │ │ skuinfo-add-or-update.vue
    │ │ skuinfo.vue
    │ │ skusaleattrvalue-add-or-update.vue
    │ │ skusaleattrvalue.vue
    │ │ spucomment-add-or-update.vue
    │ │ spucomment.vue
    │ │ spuimages-add-or-update.vue
    │ │ spuimages.vue
    │ │ spuinfo-add-or-update.vue
    │ │ spuinfo.vue
    │ │ spuinfodesc-add-or-update.vue
    │ │ spuinfodesc.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ ├─generated-test-sources
    │ │ └─test-annotations
    │ ├─maven-status
    │ │ └─maven-compiler-plugin
    │ │ └─compile
    │ │ └─default-compile
    │ │ createdFiles.lst
    │ │ inputFiles.lst
    │ │
    │ └─test-classes
    │ └─com
    │ └─atguigu
    │ └─gulimall
    │ └─product
    │ GulimallProductApplicationTests.class

    ├─gulimall-ware
    │ │ .gitignore
    │ │ gulimall-ware.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─ware
    │ │ │ │ │ GulimallWareApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ WareInfoController.java
    │ │ │ │ │ WareOrderTaskController.java
    │ │ │ │ │ WareOrderTaskDetailController.java
    │ │ │ │ │ WareSkuController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ WareInfoDao.java
    │ │ │ │ │ WareOrderTaskDao.java
    │ │ │ │ │ WareOrderTaskDetailDao.java
    │ │ │ │ │ WareSkuDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ WareInfoEntity.java
    │ │ │ │ │ WareOrderTaskDetailEntity.java
    │ │ │ │ │ WareOrderTaskEntity.java
    │ │ │ │ │ WareSkuEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ WareInfoService.java
    │ │ │ │ │ WareOrderTaskDetailService.java
    │ │ │ │ │ WareOrderTaskService.java
    │ │ │ │ │ WareSkuService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ WareInfoServiceImpl.java
    │ │ │ │ WareOrderTaskDetailServiceImpl.java
    │ │ │ │ WareOrderTaskServiceImpl.java
    │ │ │ │ WareSkuServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─ware
    │ │ │ │ WareInfoDao.xml
    │ │ │ │ WareOrderTaskDao.xml
    │ │ │ │ WareOrderTaskDetailDao.xml
    │ │ │ │ WareSkuDao.xml
    │ │ │ │
    │ │ │ └─src
    │ │ │ └─views
    │ │ │ └─modules
    │ │ │ └─ware
    │ │ │ wareinfo-add-or-update.vue
    │ │ │ wareinfo.vue
    │ │ │ wareordertask-add-or-update.vue
    │ │ │ wareordertask.vue
    │ │ │ wareordertaskdetail-add-or-update.vue
    │ │ │ wareordertaskdetail.vue
    │ │ │ waresku-add-or-update.vue
    │ │ │ waresku.vue
    │ │ │
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─gulimallware
    │ │ GulimallWareApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ ├─gulimallware
    │ │ │ │ GulimallWareApplication.class
    │ │ │ │
    │ │ │ └─ware
    │ │ │ │ GulimallWareApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ WareInfoController.class
    │ │ │ │ WareOrderTaskController.class
    │ │ │ │ WareOrderTaskDetailController.class
    │ │ │ │ WareSkuController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ WareInfoDao.class
    │ │ │ │ WareOrderTaskDao.class
    │ │ │ │ WareOrderTaskDetailDao.class
    │ │ │ │ WareSkuDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ WareInfoEntity.class
    │ │ │ │ WareOrderTaskDetailEntity.class
    │ │ │ │ WareOrderTaskEntity.class
    │ │ │ │ WareSkuEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ WareInfoService.class
    │ │ │ │ WareOrderTaskDetailService.class
    │ │ │ │ WareOrderTaskService.class
    │ │ │ │ WareSkuService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ WareInfoServiceImpl.class
    │ │ │ WareOrderTaskDetailServiceImpl.class
    │ │ │ WareOrderTaskServiceImpl.class
    │ │ │ WareSkuServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─ware
    │ │ │ WareInfoDao.xml
    │ │ │ WareOrderTaskDao.xml
    │ │ │ WareOrderTaskDetailDao.xml
    │ │ │ WareSkuDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─ware
    │ │ wareinfo-add-or-update.vue
    │ │ wareinfo.vue
    │ │ wareordertask-add-or-update.vue
    │ │ wareordertask.vue
    │ │ wareordertaskdetail-add-or-update.vue
    │ │ wareordertaskdetail.vue
    │ │ waresku-add-or-update.vue
    │ │ waresku.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ ├─generated-test-sources
    │ │ └─test-annotations
    │ ├─maven-status
    │ │ └─maven-compiler-plugin
    │ │ └─compile
    │ │ └─default-compile
    │ │ createdFiles.lst
    │ │ inputFiles.lst
    │ │
    │ └─test-classes
    │ └─com
    │ └─atguigu
    │ └─gulimall
    │ └─gulimallware
    │ GulimallWareApplicationTests.class

    ├─renren-fast
    │ │ .gitignore
    │ │ docker-compose.yml
    │ │ Dockerfile
    │ │ LICENSE
    │ │ pom.xml
    │ │ README.md
    │ │ renren-fast.iml
    │ │
    │ ├─db
    │ │ mysql.sql
    │ │ oracle.sql
    │ │ postgresql.sql
    │ │ sqlserver.sql
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─io
    │ │ │ │ └─renren
    │ │ │ │ │ RenrenApplication.java
    │ │ │ │ │
    │ │ │ │ ├─common
    │ │ │ │ │ ├─annotation
    │ │ │ │ │ │ SysLog.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─aspect
    │ │ │ │ │ │ RedisAspect.java
    │ │ │ │ │ │ SysLogAspect.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─exception
    │ │ │ │ │ │ RRException.java
    │ │ │ │ │ │ RRExceptionHandler.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─utils
    │ │ │ │ │ │ ConfigConstant.java
    │ │ │ │ │ │ Constant.java
    │ │ │ │ │ │ DateUtils.java
    │ │ │ │ │ │ HttpContextUtils.java
    │ │ │ │ │ │ IPUtils.java
    │ │ │ │ │ │ MapUtils.java
    │ │ │ │ │ │ PageUtils.java
    │ │ │ │ │ │ Query.java
    │ │ │ │ │ │ R.java
    │ │ │ │ │ │ RedisKeys.java
    │ │ │ │ │ │ RedisUtils.java
    │ │ │ │ │ │ ShiroUtils.java
    │ │ │ │ │ │ SpringContextUtils.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─validator
    │ │ │ │ │ │ │ Assert.java
    │ │ │ │ │ │ │ ValidatorUtils.java
    │ │ │ │ │ │ │
    │ │ │ │ │ │ └─group
    │ │ │ │ │ │ AddGroup.java
    │ │ │ │ │ │ AliyunGroup.java
    │ │ │ │ │ │ Group.java
    │ │ │ │ │ │ QcloudGroup.java
    │ │ │ │ │ │ QiniuGroup.java
    │ │ │ │ │ │ UpdateGroup.java
    │ │ │ │ │ │
    │ │ │ │ │ └─xss
    │ │ │ │ │ HTMLFilter.java
    │ │ │ │ │ SQLFilter.java
    │ │ │ │ │ XssFilter.java
    │ │ │ │ │ XssHttpServletRequestWrapper.java
    │ │ │ │ │
    │ │ │ │ ├─config
    │ │ │ │ │ CorsConfig.java
    │ │ │ │ │ FilterConfig.java
    │ │ │ │ │ KaptchaConfig.java
    │ │ │ │ │ MybatisPlusConfig.java
    │ │ │ │ │ RedisConfig.java
    │ │ │ │ │ ShiroConfig.java
    │ │ │ │ │ SwaggerConfig.java
    │ │ │ │ │
    │ │ │ │ ├─datasource
    │ │ │ │ │ ├─annotation
    │ │ │ │ │ │ DataSource.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─aspect
    │ │ │ │ │ │ DataSourceAspect.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─config
    │ │ │ │ │ │ DynamicContextHolder.java
    │ │ │ │ │ │ DynamicDataSource.java
    │ │ │ │ │ │ DynamicDataSourceConfig.java
    │ │ │ │ │ │ DynamicDataSourceFactory.java
    │ │ │ │ │ │
    │ │ │ │ │ └─properties
    │ │ │ │ │ DataSourceProperties.java
    │ │ │ │ │ DynamicDataSourceProperties.java
    │ │ │ │ │
    │ │ │ │ └─modules
    │ │ │ │ ├─app
    │ │ │ │ │ ├─annotation
    │ │ │ │ │ │ Login.java
    │ │ │ │ │ │ LoginUser.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─config
    │ │ │ │ │ │ WebMvcConfig.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─controller
    │ │ │ │ │ │ AppLoginController.java
    │ │ │ │ │ │ AppRegisterController.java
    │ │ │ │ │ │ AppTestController.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─dao
    │ │ │ │ │ │ UserDao.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─entity
    │ │ │ │ │ │ UserEntity.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─form
    │ │ │ │ │ │ LoginForm.java
    │ │ │ │ │ │ RegisterForm.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─interceptor
    │ │ │ │ │ │ AuthorizationInterceptor.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─resolver
    │ │ │ │ │ │ LoginUserHandlerMethodArgumentResolver.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─service
    │ │ │ │ │ │ │ UserService.java
    │ │ │ │ │ │ │
    │ │ │ │ │ │ └─impl
    │ │ │ │ │ │ UserServiceImpl.java
    │ │ │ │ │ │
    │ │ │ │ │ └─utils
    │ │ │ │ │ JwtUtils.java
    │ │ │ │ │
    │ │ │ │ ├─job
    │ │ │ │ │ ├─config
    │ │ │ │ │ │ ScheduleConfig.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─controller
    │ │ │ │ │ │ ScheduleJobController.java
    │ │ │ │ │ │ ScheduleJobLogController.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─dao
    │ │ │ │ │ │ ScheduleJobDao.java
    │ │ │ │ │ │ ScheduleJobLogDao.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─entity
    │ │ │ │ │ │ ScheduleJobEntity.java
    │ │ │ │ │ │ ScheduleJobLogEntity.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─service
    │ │ │ │ │ │ │ ScheduleJobLogService.java
    │ │ │ │ │ │ │ ScheduleJobService.java
    │ │ │ │ │ │ │
    │ │ │ │ │ │ └─impl
    │ │ │ │ │ │ ScheduleJobLogServiceImpl.java
    │ │ │ │ │ │ ScheduleJobServiceImpl.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─task
    │ │ │ │ │ │ ITask.java
    │ │ │ │ │ │ TestTask.java
    │ │ │ │ │ │
    │ │ │ │ │ └─utils
    │ │ │ │ │ ScheduleJob.java
    │ │ │ │ │ ScheduleUtils.java
    │ │ │ │ │
    │ │ │ │ ├─oss
    │ │ │ │ │ ├─cloud
    │ │ │ │ │ │ AliyunCloudStorageService.java
    │ │ │ │ │ │ CloudStorageConfig.java
    │ │ │ │ │ │ CloudStorageService.java
    │ │ │ │ │ │ OSSFactory.java
    │ │ │ │ │ │ QcloudCloudStorageService.java
    │ │ │ │ │ │ QiniuCloudStorageService.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─controller
    │ │ │ │ │ │ SysOssController.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─dao
    │ │ │ │ │ │ SysOssDao.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─entity
    │ │ │ │ │ │ SysOssEntity.java
    │ │ │ │ │ │
    │ │ │ │ │ └─service
    │ │ │ │ │ │ SysOssService.java
    │ │ │ │ │ │
    │ │ │ │ │ └─impl
    │ │ │ │ │ SysOssServiceImpl.java
    │ │ │ │ │
    │ │ │ │ └─sys
    │ │ │ │ ├─controller
    │ │ │ │ │ AbstractController.java
    │ │ │ │ │ SysConfigController.java
    │ │ │ │ │ SysLogController.java
    │ │ │ │ │ SysLoginController.java
    │ │ │ │ │ SysMenuController.java
    │ │ │ │ │ SysRoleController.java
    │ │ │ │ │ SysUserController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ SysCaptchaDao.java
    │ │ │ │ │ SysConfigDao.java
    │ │ │ │ │ SysLogDao.java
    │ │ │ │ │ SysMenuDao.java
    │ │ │ │ │ SysRoleDao.java
    │ │ │ │ │ SysRoleMenuDao.java
    │ │ │ │ │ SysUserDao.java
    │ │ │ │ │ SysUserRoleDao.java
    │ │ │ │ │ SysUserTokenDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ SysCaptchaEntity.java
    │ │ │ │ │ SysConfigEntity.java
    │ │ │ │ │ SysLogEntity.java
    │ │ │ │ │ SysMenuEntity.java
    │ │ │ │ │ SysRoleEntity.java
    │ │ │ │ │ SysRoleMenuEntity.java
    │ │ │ │ │ SysUserEntity.java
    │ │ │ │ │ SysUserRoleEntity.java
    │ │ │ │ │ SysUserTokenEntity.java
    │ │ │ │ │
    │ │ │ │ ├─form
    │ │ │ │ │ PasswordForm.java
    │ │ │ │ │ SysLoginForm.java
    │ │ │ │ │
    │ │ │ │ ├─oauth2
    │ │ │ │ │ OAuth2Filter.java
    │ │ │ │ │ OAuth2Realm.java
    │ │ │ │ │ OAuth2Token.java
    │ │ │ │ │ TokenGenerator.java
    │ │ │ │ │
    │ │ │ │ ├─redis
    │ │ │ │ │ SysConfigRedis.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ ShiroService.java
    │ │ │ │ │ SysCaptchaService.java
    │ │ │ │ │ SysConfigService.java
    │ │ │ │ │ SysLogService.java
    │ │ │ │ │ SysMenuService.java
    │ │ │ │ │ SysRoleMenuService.java
    │ │ │ │ │ SysRoleService.java
    │ │ │ │ │ SysUserRoleService.java
    │ │ │ │ │ SysUserService.java
    │ │ │ │ │ SysUserTokenService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ ShiroServiceImpl.java
    │ │ │ │ SysCaptchaServiceImpl.java
    │ │ │ │ SysConfigServiceImpl.java
    │ │ │ │ SysLogServiceImpl.java
    │ │ │ │ SysMenuServiceImpl.java
    │ │ │ │ SysRoleMenuServiceImpl.java
    │ │ │ │ SysRoleServiceImpl.java
    │ │ │ │ SysUserRoleServiceImpl.java
    │ │ │ │ SysUserServiceImpl.java
    │ │ │ │ SysUserTokenServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application-dev.yml
    │ │ │ │ application-prod.yml
    │ │ │ │ application-test.yml
    │ │ │ │ application.yml
    │ │ │ │ banner.txt
    │ │ │ │ logback-spring.xml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ ├─app
    │ │ │ │ │ UserDao.xml
    │ │ │ │ │
    │ │ │ │ ├─job
    │ │ │ │ │ ScheduleJobDao.xml
    │ │ │ │ │ ScheduleJobLogDao.xml
    │ │ │ │ │
    │ │ │ │ ├─oss
    │ │ │ │ │ SysOssDao.xml
    │ │ │ │ │
    │ │ │ │ └─sys
    │ │ │ │ SysConfigDao.xml
    │ │ │ │ SysLogDao.xml
    │ │ │ │ SysMenuDao.xml
    │ │ │ │ SysRoleDao.xml
    │ │ │ │ SysRoleMenuDao.xml
    │ │ │ │ SysUserDao.xml
    │ │ │ │ SysUserRoleDao.xml
    │ │ │ │ SysUserTokenDao.xml
    │ │ │ │
    │ │ │ └─static
    │ │ │ │ favicon.ico
    │ │ │ │
    │ │ │ └─swagger
    │ │ │ │ favicon-16x16.png
    │ │ │ │ favicon-32x32.png
    │ │ │ │ index.html
    │ │ │ │ index.yaml
    │ │ │ │ o2c.html
    │ │ │ │ oauth2-redirect.html
    │ │ │ │ swagger-ui-bundle.js
    │ │ │ │ swagger-ui-bundle.js.map
    │ │ │ │ swagger-ui-standalone-preset.js
    │ │ │ │ swagger-ui-standalone-preset.js.map
    │ │ │ │ swagger-ui.css
    │ │ │ │ swagger-ui.css.map
    │ │ │ │ swagger-ui.js
    │ │ │ │ swagger-ui.js.map
    │ │ │ │ swagger-ui.min.js
    │ │ │ │
    │ │ │ ├─css
    │ │ │ │ print.css
    │ │ │ │ reset.css
    │ │ │ │ screen.css
    │ │ │ │ style.css
    │ │ │ │ typography.css
    │ │ │ │
    │ │ │ ├─fonts
    │ │ │ │ DroidSans-Bold.ttf
    │ │ │ │ DroidSans.ttf
    │ │ │ │
    │ │ │ ├─images
    │ │ │ │ collapse.gif
    │ │ │ │ expand.gif
    │ │ │ │ explorer_icons.png
    │ │ │ │ favicon-16x16.png
    │ │ │ │ favicon-32x32.png
    │ │ │ │ favicon.ico
    │ │ │ │ logo_small.png
    │ │ │ │ pet_store_api.png
    │ │ │ │ throbber.gif
    │ │ │ │ wordnik_api.png
    │ │ │ │
    │ │ │ ├─lang
    │ │ │ │ en.js
    │ │ │ │ translator.js
    │ │ │ │ zh-cn.js
    │ │ │ │
    │ │ │ └─lib
    │ │ │ backbone-min.js
    │ │ │ es5-shim.js
    │ │ │ handlebars-4.0.5.js
    │ │ │ highlight.9.1.0.pack.js
    │ │ │ highlight.9.1.0.pack_extended.js
    │ │ │ jquery-1.8.0.min.js
    │ │ │ jquery.ba-bbq.min.js
    │ │ │ jquery.slideto.min.js
    │ │ │ jquery.wiggle.min.js
    │ │ │ js-yaml.min.js
    │ │ │ jsoneditor.min.js
    │ │ │ lodash.min.js
    │ │ │ marked.js
    │ │ │ object-assign-pollyfill.js
    │ │ │ sanitize-html.min.js
    │ │ │ swagger-oauth.js
    │ │ │
    │ │ └─test
    │ │ └─java
    │ │ └─io
    │ │ └─renren
    │ │ │ DynamicDataSourceTest.java
    │ │ │ JwtTest.java
    │ │ │ RedisTest.java
    │ │ │
    │ │ └─service
    │ │ DynamicDataSourceTestService.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application-dev.yml
    │ │ │ application-prod.yml
    │ │ │ application-test.yml
    │ │ │ application.yml
    │ │ │ banner.txt
    │ │ │ logback-spring.xml
    │ │ │
    │ │ ├─io
    │ │ │ └─renren
    │ │ │ │ RenrenApplication.class
    │ │ │ │
    │ │ │ ├─common
    │ │ │ │ ├─annotation
    │ │ │ │ │ SysLog.class
    │ │ │ │ │
    │ │ │ │ ├─aspect
    │ │ │ │ │ RedisAspect.class
    │ │ │ │ │ SysLogAspect.class
    │ │ │ │ │
    │ │ │ │ ├─exception
    │ │ │ │ │ RRException.class
    │ │ │ │ │ RRExceptionHandler.class
    │ │ │ │ │
    │ │ │ │ ├─utils
    │ │ │ │ │ ConfigConstant.class
    │ │ │ │ │ Constant$CloudService.class
    │ │ │ │ │ Constant$MenuType.class
    │ │ │ │ │ Constant$ScheduleStatus.class
    │ │ │ │ │ Constant.class
    │ │ │ │ │ DateUtils.class
    │ │ │ │ │ HttpContextUtils.class
    │ │ │ │ │ IPUtils.class
    │ │ │ │ │ MapUtils.class
    │ │ │ │ │ PageUtils.class
    │ │ │ │ │ Query.class
    │ │ │ │ │ R.class
    │ │ │ │ │ RedisKeys.class
    │ │ │ │ │ RedisUtils.class
    │ │ │ │ │ ShiroUtils.class
    │ │ │ │ │ SpringContextUtils.class
    │ │ │ │ │
    │ │ │ │ ├─validator
    │ │ │ │ │ │ Assert.class
    │ │ │ │ │ │ ValidatorUtils.class
    │ │ │ │ │ │
    │ │ │ │ │ └─group
    │ │ │ │ │ AddGroup.class
    │ │ │ │ │ AliyunGroup.class
    │ │ │ │ │ Group.class
    │ │ │ │ │ QcloudGroup.class
    │ │ │ │ │ QiniuGroup.class
    │ │ │ │ │ UpdateGroup.class
    │ │ │ │ │
    │ │ │ │ └─xss
    │ │ │ │ HTMLFilter.class
    │ │ │ │ SQLFilter.class
    │ │ │ │ XssFilter.class
    │ │ │ │ XssHttpServletRequestWrapper$1.class
    │ │ │ │ XssHttpServletRequestWrapper.class
    │ │ │ │
    │ │ │ ├─config
    │ │ │ │ CorsConfig.class
    │ │ │ │ FilterConfig.class
    │ │ │ │ KaptchaConfig.class
    │ │ │ │ MybatisPlusConfig.class
    │ │ │ │ RedisConfig.class
    │ │ │ │ ShiroConfig.class
    │ │ │ │ SwaggerConfig.class
    │ │ │ │
    │ │ │ ├─datasource
    │ │ │ │ ├─annotation
    │ │ │ │ │ DataSource.class
    │ │ │ │ │
    │ │ │ │ ├─aspect
    │ │ │ │ │ DataSourceAspect.class
    │ │ │ │ │
    │ │ │ │ ├─config
    │ │ │ │ │ DynamicContextHolder$1.class
    │ │ │ │ │ DynamicContextHolder.class
    │ │ │ │ │ DynamicDataSource.class
    │ │ │ │ │ DynamicDataSourceConfig.class
    │ │ │ │ │ DynamicDataSourceFactory.class
    │ │ │ │ │
    │ │ │ │ └─properties
    │ │ │ │ DataSourceProperties.class
    │ │ │ │ DynamicDataSourceProperties.class
    │ │ │ │
    │ │ │ └─modules
    │ │ │ ├─app
    │ │ │ │ ├─annotation
    │ │ │ │ │ Login.class
    │ │ │ │ │ LoginUser.class
    │ │ │ │ │
    │ │ │ │ ├─config
    │ │ │ │ │ WebMvcConfig.class
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ AppLoginController.class
    │ │ │ │ │ AppRegisterController.class
    │ │ │ │ │ AppTestController.class
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ UserDao.class
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ UserEntity.class
    │ │ │ │ │
    │ │ │ │ ├─form
    │ │ │ │ │ LoginForm.class
    │ │ │ │ │ RegisterForm.class
    │ │ │ │ │
    │ │ │ │ ├─interceptor
    │ │ │ │ │ AuthorizationInterceptor.class
    │ │ │ │ │
    │ │ │ │ ├─resolver
    │ │ │ │ │ LoginUserHandlerMethodArgumentResolver.class
    │ │ │ │ │
    │ │ │ │ ├─service
    │ │ │ │ │ │ UserService.class
    │ │ │ │ │ │
    │ │ │ │ │ └─impl
    │ │ │ │ │ UserServiceImpl.class
    │ │ │ │ │
    │ │ │ │ └─utils
    │ │ │ │ JwtUtils.class
    │ │ │ │
    │ │ │ ├─job
    │ │ │ │ ├─config
    │ │ │ │ │ ScheduleConfig.class
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ ScheduleJobController.class
    │ │ │ │ │ ScheduleJobLogController.class
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ ScheduleJobDao.class
    │ │ │ │ │ ScheduleJobLogDao.class
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ ScheduleJobEntity.class
    │ │ │ │ │ ScheduleJobLogEntity.class
    │ │ │ │ │
    │ │ │ │ ├─service
    │ │ │ │ │ │ ScheduleJobLogService.class
    │ │ │ │ │ │ ScheduleJobService.class
    │ │ │ │ │ │
    │ │ │ │ │ └─impl
    │ │ │ │ │ ScheduleJobLogServiceImpl.class
    │ │ │ │ │ ScheduleJobServiceImpl.class
    │ │ │ │ │
    │ │ │ │ ├─task
    │ │ │ │ │ ITask.class
    │ │ │ │ │ TestTask.class
    │ │ │ │ │
    │ │ │ │ └─utils
    │ │ │ │ ScheduleJob.class
    │ │ │ │ ScheduleUtils.class
    │ │ │ │
    │ │ │ ├─oss
    │ │ │ │ ├─cloud
    │ │ │ │ │ AliyunCloudStorageService.class
    │ │ │ │ │ CloudStorageConfig.class
    │ │ │ │ │ CloudStorageService.class
    │ │ │ │ │ OSSFactory.class
    │ │ │ │ │ QcloudCloudStorageService.class
    │ │ │ │ │ QiniuCloudStorageService.class
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ SysOssController.class
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ SysOssDao.class
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ SysOssEntity.class
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ SysOssService.class
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ SysOssServiceImpl.class
    │ │ │ │
    │ │ │ └─sys
    │ │ │ ├─controller
    │ │ │ │ AbstractController.class
    │ │ │ │ SysConfigController.class
    │ │ │ │ SysLogController.class
    │ │ │ │ SysLoginController.class
    │ │ │ │ SysMenuController.class
    │ │ │ │ SysRoleController.class
    │ │ │ │ SysUserController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ SysCaptchaDao.class
    │ │ │ │ SysConfigDao.class
    │ │ │ │ SysLogDao.class
    │ │ │ │ SysMenuDao.class
    │ │ │ │ SysRoleDao.class
    │ │ │ │ SysRoleMenuDao.class
    │ │ │ │ SysUserDao.class
    │ │ │ │ SysUserRoleDao.class
    │ │ │ │ SysUserTokenDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ SysCaptchaEntity.class
    │ │ │ │ SysConfigEntity.class
    │ │ │ │ SysLogEntity.class
    │ │ │ │ SysMenuEntity.class
    │ │ │ │ SysRoleEntity.class
    │ │ │ │ SysRoleMenuEntity.class
    │ │ │ │ SysUserEntity.class
    │ │ │ │ SysUserRoleEntity.class
    │ │ │ │ SysUserTokenEntity.class
    │ │ │ │
    │ │ │ ├─form
    │ │ │ │ PasswordForm.class
    │ │ │ │ SysLoginForm.class
    │ │ │ │
    │ │ │ ├─oauth2
    │ │ │ │ OAuth2Filter.class
    │ │ │ │ OAuth2Realm.class
    │ │ │ │ OAuth2Token.class
    │ │ │ │ TokenGenerator.class
    │ │ │ │
    │ │ │ ├─redis
    │ │ │ │ SysConfigRedis.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ ShiroService.class
    │ │ │ │ SysCaptchaService.class
    │ │ │ │ SysConfigService.class
    │ │ │ │ SysLogService.class
    │ │ │ │ SysMenuService.class
    │ │ │ │ SysRoleMenuService.class
    │ │ │ │ SysRoleService.class
    │ │ │ │ SysUserRoleService.class
    │ │ │ │ SysUserService.class
    │ │ │ │ SysUserTokenService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ ShiroServiceImpl.class
    │ │ │ SysCaptchaServiceImpl.class
    │ │ │ SysConfigServiceImpl.class
    │ │ │ SysLogServiceImpl.class
    │ │ │ SysMenuServiceImpl.class
    │ │ │ SysRoleMenuServiceImpl.class
    │ │ │ SysRoleServiceImpl.class
    │ │ │ SysUserRoleServiceImpl.class
    │ │ │ SysUserServiceImpl.class
    │ │ │ SysUserTokenServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ ├─app
    │ │ │ │ UserDao.xml
    │ │ │ │
    │ │ │ ├─job
    │ │ │ │ ScheduleJobDao.xml
    │ │ │ │ ScheduleJobLogDao.xml
    │ │ │ │
    │ │ │ ├─oss
    │ │ │ │ SysOssDao.xml
    │ │ │ │
    │ │ │ └─sys
    │ │ │ SysConfigDao.xml
    │ │ │ SysLogDao.xml
    │ │ │ SysMenuDao.xml
    │ │ │ SysRoleDao.xml
    │ │ │ SysRoleMenuDao.xml
    │ │ │ SysUserDao.xml
    │ │ │ SysUserRoleDao.xml
    │ │ │ SysUserTokenDao.xml
    │ │ │
    │ │ ├─META-INF
    │ │ │ spring-configuration-metadata.json
    │ │ │
    │ │ └─static
    │ │ │ favicon.ico
    │ │ │
    │ │ └─swagger
    │ │ │ favicon-16x16.png
    │ │ │ favicon-32x32.png
    │ │ │ index.html
    │ │ │ index.yaml
    │ │ │ o2c.html
    │ │ │ oauth2-redirect.html
    │ │ │ swagger-ui-bundle.js
    │ │ │ swagger-ui-bundle.js.map
    │ │ │ swagger-ui-standalone-preset.js
    │ │ │ swagger-ui-standalone-preset.js.map
    │ │ │ swagger-ui.css
    │ │ │ swagger-ui.css.map
    │ │ │ swagger-ui.js
    │ │ │ swagger-ui.js.map
    │ │ │ swagger-ui.min.js
    │ │ │
    │ │ ├─css
    │ │ │ print.css
    │ │ │ reset.css
    │ │ │ screen.css
    │ │ │ style.css
    │ │ │ typography.css
    │ │ │
    │ │ ├─fonts
    │ │ │ DroidSans-Bold.ttf
    │ │ │ DroidSans.ttf
    │ │ │
    │ │ ├─images
    │ │ │ collapse.gif
    │ │ │ expand.gif
    │ │ │ explorer_icons.png
    │ │ │ favicon-16x16.png
    │ │ │ favicon-32x32.png
    │ │ │ favicon.ico
    │ │ │ logo_small.png
    │ │ │ pet_store_api.png
    │ │ │ throbber.gif
    │ │ │ wordnik_api.png
    │ │ │
    │ │ ├─lang
    │ │ │ en.js
    │ │ │ translator.js
    │ │ │ zh-cn.js
    │ │ │
    │ │ └─lib
    │ │ backbone-min.js
    │ │ es5-shim.js
    │ │ handlebars-4.0.5.js
    │ │ highlight.9.1.0.pack.js
    │ │ highlight.9.1.0.pack_extended.js
    │ │ jquery-1.8.0.min.js
    │ │ jquery.ba-bbq.min.js
    │ │ jquery.slideto.min.js
    │ │ jquery.wiggle.min.js
    │ │ js-yaml.min.js
    │ │ jsoneditor.min.js
    │ │ lodash.min.js
    │ │ marked.js
    │ │ object-assign-pollyfill.js
    │ │ sanitize-html.min.js
    │ │ swagger-oauth.js
    │ │
    │ └─generated-sources
    │ └─annotations
    └─renren-generator
    │ .gitignore
    │ LICENSE
    │ pom.xml
    │ README.md
    │ renren-generator.iml

    ├─src
    │ ├─main
    │ │ ├─java
    │ │ │ └─io
    │ │ │ └─renren
    │ │ │ │ RenrenApplicationGenerator.java
    │ │ │ │
    │ │ │ ├─adaptor
    │ │ │ │ MongoTableInfoAdaptor.java
    │ │ │ │
    │ │ │ ├─config
    │ │ │ │ DbConfig.java
    │ │ │ │ MongoCondition.java
    │ │ │ │ MongoConfig.java
    │ │ │ │ MongoManager.java
    │ │ │ │ MongoNullCondition.java
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ SysGeneratorController.java
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ GeneratorDao.java
    │ │ │ │ MongoDBGeneratorDao.java
    │ │ │ │ MySQLGeneratorDao.java
    │ │ │ │ OracleGeneratorDao.java
    │ │ │ │ PostgreSQLGeneratorDao.java
    │ │ │ │ SQLServerGeneratorDao.java
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ │ ColumnEntity.java
    │ │ │ │ │ TableEntity.java
    │ │ │ │ │
    │ │ │ │ └─mongo
    │ │ │ │ MongoDefinition.java
    │ │ │ │ MongoGeneratorEntity.java
    │ │ │ │ Type.java
    │ │ │ │
    │ │ │ ├─factory
    │ │ │ │ MongoDBCollectionFactory.java
    │ │ │ │
    │ │ │ ├─service
    │ │ │ │ SysGeneratorService.java
    │ │ │ │
    │ │ │ └─utils
    │ │ │ Constant.java
    │ │ │ DateUtils.java
    │ │ │ GenUtils.java
    │ │ │ MongoScanner.java
    │ │ │ PageUtils.java
    │ │ │ Query.java
    │ │ │ R.java
    │ │ │ RRException.java
    │ │ │ RRExceptionHandler.java
    │ │ │
    │ │ └─resources
    │ │ │ application.yml
    │ │ │ generator.properties
    │ │ │
    │ │ ├─mapper
    │ │ │ MySQLGeneratorDao.xml
    │ │ │ OracleGeneratorDao.xml
    │ │ │ PostgreSQLGeneratorDao.xml
    │ │ │ SQLServerGeneratorDao.xml
    │ │ │
    │ │ ├─static
    │ │ │ │ favicon.ico
    │ │ │ │
    │ │ │ ├─css
    │ │ │ │ AdminLTE.min.css
    │ │ │ │ all-skins.min.css
    │ │ │ │ bootstrap.min.css
    │ │ │ │ font-awesome.min.css
    │ │ │ │ main.css
    │ │ │ │
    │ │ │ ├─fonts
    │ │ │ │ fontawesome-webfont.eot
    │ │ │ │ fontawesome-webfont.svg
    │ │ │ │ fontawesome-webfont.ttf
    │ │ │ │ fontawesome-webfont.woff
    │ │ │ │ fontawesome-webfont.woff2
    │ │ │ │ FontAwesome.otf
    │ │ │ │ glyphicons-halflings-regular.eot
    │ │ │ │ glyphicons-halflings-regular.svg
    │ │ │ │ glyphicons-halflings-regular.ttf
    │ │ │ │ glyphicons-halflings-regular.woff
    │ │ │ │ glyphicons-halflings-regular.woff2
    │ │ │ │
    │ │ │ ├─js
    │ │ │ │ common.js
    │ │ │ │ generator.js
    │ │ │ │ index.js
    │ │ │ │
    │ │ │ ├─libs
    │ │ │ │ app.js
    │ │ │ │ app.min.js
    │ │ │ │ bootstrap.min.js
    │ │ │ │ fastclick.min.js
    │ │ │ │ jquery.min.js
    │ │ │ │ jquery.slimscroll.min.js
    │ │ │ │ router.js
    │ │ │ │ vue.min.js
    │ │ │ │
    │ │ │ └─plugins
    │ │ │ ├─jqgrid
    │ │ │ │ grid.locale-cn.js
    │ │ │ │ jquery.jqGrid.min.js
    │ │ │ │ ui.jqgrid-bootstrap-ui.css
    │ │ │ │ ui.jqgrid-bootstrap.css
    │ │ │ │ ui.jqgrid.css
    │ │ │ │
    │ │ │ └─layer
    │ │ │ │ layer.js
    │ │ │ │
    │ │ │ ├─mobile
    │ │ │ │ │ layer.js
    │ │ │ │ │
    │ │ │ │ └─need
    │ │ │ │ layer.css
    │ │ │ │
    │ │ │ └─skin
    │ │ │ ├─default
    │ │ │ │ icon-ext.png
    │ │ │ │ icon.png
    │ │ │ │ layer.css
    │ │ │ │ loading-0.gif
    │ │ │ │ loading-1.gif
    │ │ │ │ loading-2.gif
    │ │ │ │
    │ │ │ └─moon
    │ │ │ default.png
    │ │ │ style.css
    │ │ │
    │ │ ├─template
    │ │ │ add-or-update.vue.vm
    │ │ │ Controller.java.vm
    │ │ │ Dao.java.vm
    │ │ │ Dao.xml.vm
    │ │ │ Entity.java.vm
    │ │ │ index.vue.vm
    │ │ │ menu.sql.vm
    │ │ │ MongoChildrenEntity.java.vm
    │ │ │ MongoEntity.java.vm
    │ │ │ Service.java.vm
    │ │ │ ServiceImpl.java.vm
    │ │ │
    │ │ └─views
    │ │ generator.html
    │ │ index.html
    │ │ main.html
    │ │
    │ └─test
    │ └─java
    │ └─io
    │ └─renren
    │ RenrenApplicationTests.java

    └─target
    ├─classes
    │ │ application.yml
    │ │ generator.properties
    │ │
    │ ├─io
    │ │ └─renren
    │ │ │ RenrenApplicationGenerator.class
    │ │ │
    │ │ ├─adaptor
    │ │ │ MongoTableInfoAdaptor.class
    │ │ │
    │ │ ├─config
    │ │ │ DbConfig.class
    │ │ │ MongoCondition.class
    │ │ │ MongoConfig.class
    │ │ │ MongoManager.class
    │ │ │ MongoNullCondition.class
    │ │ │
    │ │ ├─controller
    │ │ │ SysGeneratorController.class
    │ │ │
    │ │ ├─dao
    │ │ │ GeneratorDao.class
    │ │ │ MongoDBGeneratorDao.class
    │ │ │ MySQLGeneratorDao.class
    │ │ │ OracleGeneratorDao.class
    │ │ │ PostgreSQLGeneratorDao.class
    │ │ │ SQLServerGeneratorDao.class
    │ │ │
    │ │ ├─entity
    │ │ │ │ ColumnEntity.class
    │ │ │ │ TableEntity.class
    │ │ │ │
    │ │ │ └─mongo
    │ │ │ MongoDefinition.class
    │ │ │ MongoGeneratorEntity.class
    │ │ │ Type.class
    │ │ │
    │ │ ├─factory
    │ │ │ MongoDBCollectionFactory.class
    │ │ │
    │ │ ├─service
    │ │ │ SysGeneratorService.class
    │ │ │
    │ │ └─utils
    │ │ Constant.class
    │ │ DateUtils.class
    │ │ GenUtils.class
    │ │ MongoScanner$ForkJoinGetProcessName.class
    │ │ MongoScanner$ForkJoinProcessType.class
    │ │ MongoScanner.class
    │ │ PageUtils.class
    │ │ Query.class
    │ │ R.class
    │ │ RRException.class
    │ │ RRExceptionHandler.class
    │ │
    │ ├─mapper
    │ │ MySQLGeneratorDao.xml
    │ │ OracleGeneratorDao.xml
    │ │ PostgreSQLGeneratorDao.xml
    │ │ SQLServerGeneratorDao.xml
    │ │
    │ ├─static
    │ │ │ favicon.ico
    │ │ │
    │ │ ├─css
    │ │ │ AdminLTE.min.css
    │ │ │ all-skins.min.css
    │ │ │ bootstrap.min.css
    │ │ │ font-awesome.min.css
    │ │ │ main.css
    │ │ │
    │ │ ├─fonts
    │ │ │ fontawesome-webfont.eot
    │ │ │ fontawesome-webfont.svg
    │ │ │ fontawesome-webfont.ttf
    │ │ │ fontawesome-webfont.woff
    │ │ │ fontawesome-webfont.woff2
    │ │ │ FontAwesome.otf
    │ │ │ glyphicons-halflings-regular.eot
    │ │ │ glyphicons-halflings-regular.svg
    │ │ │ glyphicons-halflings-regular.ttf
    │ │ │ glyphicons-halflings-regular.woff
    │ │ │ glyphicons-halflings-regular.woff2
    │ │ │
    │ │ ├─js
    │ │ │ common.js
    │ │ │ generator.js
    │ │ │ index.js
    │ │ │
    │ │ ├─libs
    │ │ │ app.js
    │ │ │ app.min.js
    │ │ │ bootstrap.min.js
    │ │ │ fastclick.min.js
    │ │ │ jquery.min.js
    │ │ │ jquery.slimscroll.min.js
    │ │ │ router.js
    │ │ │ vue.min.js
    │ │ │
    │ │ └─plugins
    │ │ ├─jqgrid
    │ │ │ grid.locale-cn.js
    │ │ │ jquery.jqGrid.min.js
    │ │ │ ui.jqgrid-bootstrap-ui.css
    │ │ │ ui.jqgrid-bootstrap.css
    │ │ │ ui.jqgrid.css
    │ │ │
    │ │ └─layer
    │ │ │ layer.js
    │ │ │
    │ │ ├─mobile
    │ │ │ │ layer.js
    │ │ │ │
    │ │ │ └─need
    │ │ │ layer.css
    │ │ │
    │ │ └─skin
    │ │ ├─default
    │ │ │ icon-ext.png
    │ │ │ icon.png
    │ │ │ layer.css
    │ │ │ loading-0.gif
    │ │ │ loading-1.gif
    │ │ │ loading-2.gif
    │ │ │
    │ │ └─moon
    │ │ default.png
    │ │ style.css
    │ │
    │ ├─template
    │ │ add-or-update.vue.vm
    │ │ Controller.java.vm
    │ │ Dao.java.vm
    │ │ Dao.xml.vm
    │ │ Entity.java.vm
    │ │ index.vue.vm
    │ │ menu.sql.vm
    │ │ MongoChildrenEntity.java.vm
    │ │ MongoEntity.java.vm
    │ │ Service.java.vm
    │ │ ServiceImpl.java.vm
    │ │
    │ └─views
    │ generator.html
    │ index.html
    │ main.html

    └─generated-sources
    └─annotations

    + +

    四.项目前置知识

    1.SpringCloud Alibaba

    1.1简介

    SpringCloud Alibaba致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务必须的组件,方便开发者通过SpringCloud编程模型轻松使用这些组件来开发分布式的应用服务。
    +
    +依托SpringCloud Alibaba,你只需要添加一些注解和少量的配置,就可以将SpringCloud应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
    +
    +

    github的地址:https://github.com/alibaba/spring-cloud-alibaba

    +

    1.2为什么使用SpringCloud Alibaba?

    SpringCloud的不足:

    +
      +
    • 部分组件停止维护,给开发带来不便
    • +
    • 环境搭建复杂,没有完善的可视化界面,需要大量的二次开发和定制
    • +
    • 配置复杂,难以上手,部分配置差别难以区分和合理应用
    • +
    +

    SpringCloud Alibaba的优势:

    +

    阿里使用过的组件经历了考验,性能强悍,设计合理,开源成套的搭配和可视化的界面给开发带来极大的便利,搭建简单,学习成本低。

    +

    1.3 SpringCloud Alibaba最终的技术搭配方案

    SpringCloud Alibaba - Nacos :注册中心(服务发现/注册)

    +

    SpringCloud Alibaba - Nacos :配置中心(动态的配置管理)

    +

    SpringCloud - Ribbon: 负载均衡

    +

    SpringCloud - Feign: 声明式HTTP客户端(远程调用服务/服务调用)

    +

    SpringCloud Alibaba - Sentinel:服务容错(限流、降级、熔断)

    +

    SpringCloud - Gateway: API网关(webflux编程模式)

    +

    SpringCloud - Sleuth:调用链监控

    +

    SpringCloud Alibaba - Seata:原Fescar,即分布式事务解决方案

    +image-20230519161707857 + +

    1.4 项目中使用SpringCloud Alibaba

    在common模块中添加如下的依赖

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.0.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
    + +

    1.5 SpringCloud Alibaba的组件

    1.Nacos注册中心

    image-20230519162859782 + +

    简介

    +

    Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

    +

    Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

    +

    Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施

    +

    Nacos 支持如下核心特性

    +

    1)服务发现: 支持 DNS 与 RPC 服务发现,也提供原生 SDK 、OpenAPI 等多种服务注册方式和 DNS、HTTP 与 API 等多种服务发现方式。
    2)服务健康监测: Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。
    3)动态配置服务: Nacos 提供配置统一管理功能,能够帮助我们将配置以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
    4)动态 DNS 服务: Nacos 支持动态 DNS 服务权重路由,能够让我们很容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单 DNS 解析服务。
    5)服务及其元数据管理: Nacos 支持从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

    +

    使用nacos

    +

    使用之前需要下载nacos并启动nacos的服务

    +

    image-20230519164012950

    +

    在common工程中添加依赖

    +
    1
    2
    3
    4
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    + +

    配置中心中配置Nacos的地址

    +
    1
    2
    3
    4
    5
    6
    spring:
    application:
    name: gulimall-coupon #指定服务的名字,指定了名字才能注册到注册中心中
    cloud:
    nacos:
    server-addr: 127.0.0.1:8848 #指定注册中心的地址信息
    + +

    在每个启动类上使用注解开启服务的注册与发现功能

    +
    1
    2
    //开启服务的注册与发现功能
    @EnableDiscoveryClient
    + +

    查看注册服务列表

    +
    1
    通过访问: http://localhost:8848/nacos
    + +

    image-20230519165606784

    +

    2.Feign声明式远程调用

    简介

    +
    Feign是一个声明式的HTTP客户端,它的目的是让远程调用变得更简单。Feign提供了HTTP请求的模板,通过**编写简单的接口和插入注解**,就可以定义好HTTP请求的参数、格式、地址等信息。
    +
    +Feign整合了Ribbon(负载均衡)和Hystrix(服务熔断),可以让我们不再需要显式的使用这两个组件。
    +
    +

    使用

    +

    引入依赖,每个模块都需要引入这个依赖

    +
    1
    2
    3
    4
     <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    + +

    编写代码测试远程调用,这里我们以用户模块调用优惠卷模块来获取用户的拥有的优惠卷为例来测试远程调用。

    +

    会员模块提供获取用户名下优惠卷的方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
      /**
    * 获取用户明名下的优惠卷
    */
    @RequestMapping("member/list")
    public R membercoupons(){
    CouponEntity couponEntity = new CouponEntity();
    couponEntity.setCouponName("满一百减五十");
    return R.ok().put("coupons",Arrays.asList(couponEntity));
    }
    + +

    用户模块远程调用优惠卷模块

    +

    远程调用别的服务端步骤

    +
      +
    1. 引入open-feign的依赖

      +
    2. +
    3. 调用端编写一个接口,告诉SpringCloud这个接口需要调用远程服务,

      +

      声明接口的每一个方法都是调用哪个远程服务的那个请求

      +
    4. +
    5. 开启远程调用的功能,在调用端的启动类上添加**@EnableFeignClients**注解

      +
    6. +
    +

    在用户模块创建一个Feign包(维护的时候见名知义),声明调用的接口,在启动类上添加注解

    +
    1
    2
    3
    //注解
    //basePackages声明的远程调用的接口所在的包
    @EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.atguigu.gulimall.member.feign;

    import com.atguigu.common.utils.R;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/19
    * @Description 远程调用优惠卷服务
    */
    @FeignClient("gulimall-coupon")//告诉springCloud这是一个远程客户端,并指定要调用服务的服务名
    public interface CouponFeignService {


    /**
    * 这里需要写全路径
    * 远程调用优惠卷模块中获取优惠卷信息的方法
    */
    @RequestMapping("/coupon/coupon/member/list")
    public R membercoupons();
    }
    + +

    测试远程调用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Autowired
    private CouponFeignService couponFeignService;

    /**
    * 测试远程调用优惠卷模块的服务
    */
    @RequestMapping("/coupons")
    public R test(){
    MemberEntity memberEntity = new MemberEntity();
    memberEntity.setNickname("张三");
    R membercoupons = couponFeignService.membercoupons();
    return R.ok().put("member", memberEntity).put("coupons", membercoupons.get("coupons"));
    }
    + +

    测试的时候出现的小问题

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <!--No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalanc
    出现这个问题是没有loadbalanc,但是nacos中ribbon会造成loadbalanc包失效,在common的pom中的依赖改成如下的依赖-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <exclusions>
    <exclusion>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
    <version>2.2.2.RELEASE</version>
    </dependency>
    + +

    处理完上面的报错之后,浏览器访问 http://localhost:8000/member/member/coupons ,显示如下的内容,说明远程调用成功

    +

    image-20230519210917423

    +

    3.Nacos配置中心

    简介

    +

    简介见上面的nacos注册中心,这里的Nacos配置中心和上面的nacos注册中心是同一个东西

    +

    使用

    +

    1.在common中引入nacos配置中心的依赖,和上面的注册中心的依赖不一样

    +
    1
    2
    3
    4
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    + +

    2.给需要配置中心管理的模块下创建配置文件bootstrap.properties,这个文件会优先于application.properties读取

    +
    1
    2
    3
    4
    5
    #服务名
    spring.application.name=gulimall-coupon
    #Nacos配置中心的地址的地址
    #注意看这里多了一个config,和模块中nacos的地址还是有区别的
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    + +

    3.测试

    +

    3.1.在application.properties配置文件中添加如下的配置

    +
    1
    2
    coupon.user.name=zhansan
    coupon.user.age=18
    + +

    3.2 编写接口并访问测试

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    /**
    * 获取配置文件中的配置信息
    */
    @Value("${user.name}")
    private String name;
    @Value("${user.age}")
    private Integer age;

    /**
    * 测试优先获取哪个配置文件的值
    */
    @RequestMapping("/test")
    public R test(){
    return R.ok().put("name",name).put("age",age);
    }
    + +

    测试的结果如下:

    +image-20230519213945320 + +

    3.3 需求:应用不重启,实时的修改配置文件中的值

    +

    3.3.1 在Nacos配置中心中创建一个名为 应用名.properties(例如:gulimall-coupon.properties) 的配置文件

    +

    进入nacos管理的可视化页面,点击配置列表,点击 + ,新建一个配置 ,同时将配置文件中name值改为lihua

    +

    image-20230519215420623

    +

    发布之后,就可以在配置列表中看到这个配置了

    +

    image-20230519215232782

    +

    重启项目测试,配置中心的配置是否生效

    +

    没有生效的话,添加这个依赖,springBoot2.4以上的版本需要

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    <version>4.0.1</version>
    </dependency>
    + +

    生效的话,这时刷新页面,可以看到name已经由zhangsan变成lihua了

    +image-20230519220357933 + +

    3.3.2 想让配置文件实时生效的话,这时我们还要添加以下的注解,然后重启应用,让注解生效

    +

    注意: @RefreshScope 这里的注解添加在Controller上,不是添加在启动类上

    +
    1
    2
    //给需要配置中心统一配置的模块的Controller上上添加@RefreshScope的注解
    @RefreshScope
    + +

    最终测试

    +

    这时我们在配置中心中修改配置并发布,这些配置就可以实时的生效了

    +

    我们在配置中心修改name和age的值,并发布

    +

    image-20230519220922701

    +

    这时刷新请求接口的页面,我们可以看到页面中的数据发生了实时的变化

    +image-20230519223403139 + +

    总结

    +

    实现nacos配置中心统一配置的流程

    +
      +
    1. 引入依赖

      +
      1
      2
      3
      4
      <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
      </dependency>
    2. +
    3. 创建一个bootstrap.properties的配置文件,添加如下的内容:

      +
      1
      2
      3
      4
      5
      #服务名
      spring.application.name=gulimall-coupon
      #Nacos配置中心的地址
      #注意看这里多了一个config,和模块中nacos的地址还是有区别的
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    4. +
    5. 需要给配置中心中新建一个数据集(Data Id)为 应用名.properties(例如:gulimall-coupon.properties)的配置。默认规则: 应用名.properties

      +
    6. +
    7. 给 应用名.properties 添加任何配置 这些配置都可以在配置中心配置生效

      +
    8. +
    9. 在Controller上添加**@RefreshScope**注解就可以实时的动态刷新配置,配置中心更新了配置并发布了之后,配置实时的生效。

      +
    10. +
    +

    注意

    +

    如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置

    +

    细节

    +

    命名空间: 配置隔离

    +

    默认:public(保留空间)

    +

    1.可以分别设置开发、测试、生产环境的命名空间,不同的环境下使用不同的命名空间中的配置

    +

    通过命令空间实现环境隔离

    +

    bootstrap.properties设置使用哪个命名空间,可以在配置文件中添加如下的配置

    +

    image-20230519225637848

    +
    1
    2
    3
    4
    # 切换名称空间
    # 可以选择对应的命名空间 # 写上对应环境的命名空间ID
    # 使用唯一的命名空间ID
    spring.cloud.nacos.config.namespace=8a84e478-92d0-4581-a316-b0274fc76eb8
    + +

    2.可以为每一个微服务可以创建自己的命名空间,让每个服务的配置放在每个服务的命名空间下

    +

    配置集:所有的配置的集合

    +

    配置集ID: 类似文件名

    +
    Data Id: 就是配置集ID
    +
    +

    配置分组:

    +

    默认所有的配置集都属于 : DEFAULT_GROUP

    +

    例如在生产环境的命名空间下,淘宝的配置分组有以下几个: 双十一,688,平时

    +
    1
    2
    #指定配置分组
    spring.cloud.nacos.config.group=1111
    + +

    Tips:同一个命名空间下可能会有不同的配置分组

    +

    每个微服务创建自己的命令空间,使用配置分组区分环境 dev、test、prod

    +

    从配置中心中读取多个配置集(配置文件):

    +

    可以在bootstrap.properties添加以下的配置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #第一个配置文件
    #配置文件名
    spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
    #配置文件的对应的配置分组
    spring.cloud.nacos.config.ext-config[0].group=dev
    #是否动态的刷新
    spring.cloud.nacos.config.ext-config[0].refresh=true

    #第二个配置文件
    spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
    spring.cloud.nacos.config.ext-config[1].group=dev
    spring.cloud.nacos.config.ext-config[1].refresh=true

    #第三个配置文件
    spring.cloud.nacos.config.ext-config[2].data-id=others.yml
    spring.cloud.nacos.config.ext-config[2].group=dev
    spring.cloud.nacos.config.ext-config[2].refresh=true
    + +

    总结:

    +

    1.微服务的任何配置信息,任何配置文件都可以放在配置中心中

    +

    2.加载配置中心的哪些配置文件,只需要在bootstrap.properties说明加载哪些配置文件即可

    +

    3.如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置

    +

    4.Gateway网关

    image-20230519233741029 + +

    简介

    +
    网关最为流量的入口,常用的功能包括**路由转发**、**权限校验**、**限流控制**等。SpringCloud gateway是springcloud官方提供的第二代网关框架,取代了Zull网关。
    +
    +

    网关的功能

    +
      +
    • 针对所有请求进行登陆统一鉴权(登录态)限流缓存日志(用户打点)
    • +
    • 可以根据不同的请求路径pattern,来进行请求的鉴权、转发、和拒绝。
    • +
    • 协议转化。针对后段多种不同的协议,在网关层统一处理后以HTTP对外提供服务。
    • +
    • 提供统一的错误码
    • +
    • 请求转发,并且可以基于网关实现内网与外网的隔离
    • +
    +

    使用

    +
      +
    1. 创建一个springboot initializr的工程 ,勾选上Gateway的依赖

      +
    2. +
    3. 在网关模块的pom.xml文件中添加上对common工程的依赖

      +
    4. +
    5. 在网关的启动类上添加上**@EnableDiscoveryClient**的注解,把网关注册到注册中心

      +
    6. +
    7. 配置nacos的地址和服务名信息

      +
      1
      2
      3
      4
      #配置网关的地址
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
      #服务名
      spring.application.name=gulimall-gateway
    8. +
    9. 创建bootstrap.properties配置文件,将网关模块的配置通过交给nacos配置中心管理

      +

      image-20230520205015056

      +
      1
      2
      3
      4
      5
      spring.application.name=gulimall-gateway
      #配置配置中心的地址
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
      #名称空间
      spring.cloud.nacos.config.namespace=4a8e80ba-3d3c-40ef-897f-d400797c4676
    10. +
    11. 启动测试

      +

      启动之后出现的问题及解决方案:

      +

      a.单元测试@Test注解爆红,是我们更换了springboot的版本的原因,删除爆红的地方,重新导包即可

      +

      b.数据库相关的报错,是因为网关中没有使用数据库,就没有配置数据库的配置,我们通过@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})解决。

      +
    12. +
    13. 转发测试

      +

      测试案例:我们在浏览器的地址栏输入localhost:88?url=baidu,就给我们转发到百度,输入localhost:88?url=qq就给我们转发到腾讯qq

      +

      实现:创建一个yml的配置文件,在配置文件中添加如下的配置

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      spring:
      cloud:
      gateway:
      routes:
      # 路由的唯一id
      - id: baidu_route
      uri: https://www.baidu.com
      # 断言,什么时候去上面那个url
      predicates:
      # 下面的这句话的意思是如果请求中存在请求参数url=baidu,我们就去 https://www.baidu.com这个url
      - Query=url,baidu
      - id: qq_route
      uri: https://www.qq.com
      predicates:
      # 下面的这句话的意思是如果请求中存在请求参数url=qq,我们就去 https://www.qq.com这个url
      - Query=url,qq
      + +image-20230520212001874
    14. +
    +

    2.前端开发的基础知识

    前端基础知识 | The Blog (gitee.io)

    +

    前后端技术类比

    +

    image-20230520212351174

    +

    五.基础篇程序设计

    提示:所有的API路径一定要和老师的一样,不然在后面会吃亏!!!

    +

    1.商品服务

    1.1 三级分类

    1.1.1 查询-递归树形结构数据获取

    controller

    +
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 查询所有的分类以及子分类,以树形结构组装起来
    */
    @RequestMapping("/list/tree")
    public R list(){
    List<CategoryEntity> entities = categoryService.listWithTree();
    return R.ok().put("data", entities);
    }
    + +

    service

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    package com.atguigu.gulimall.product.service.impl;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.atguigu.common.utils.PageUtils;
    import com.atguigu.common.utils.Query;

    import com.atguigu.gulimall.product.dao.CategoryDao;
    import com.atguigu.gulimall.product.entity.CategoryEntity;
    import com.atguigu.gulimall.product.service.CategoryService;


    @Service("categoryService")
    public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryDao categoryDao;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
    IPage<CategoryEntity> page = this.page(
    new Query<CategoryEntity>().getPage(params),
    new QueryWrapper<CategoryEntity>()
    );

    return new PageUtils(page);
    }

    /**
    * 查询所有分类,以树形结构显示
    */
    @Override
    public List<CategoryEntity> listWithTree() {
    //查出所有的分类
    List<CategoryEntity> entities = categoryDao.selectList(null);
    //组装成父子的树形结构
    //找到所有的一级分类
    List<CategoryEntity> level1List = entities.stream().filter(categoryEntity ->
    //父级分类的id等于0的就是一级分类
    categoryEntity.getParentCid() == 0
    ).map((menu) -> {
    //为当前分类的子分类赋值
    menu.setChildren(getChildren(menu, entities));
    return menu;
    }).sorted((menu1, menu2) -> {
    //排序
    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());

    return level1List;
    }

    /**
    * 递归查询所有菜单的子菜单
    *
    * @param root 当前分类
    * @param all 所有的分类数据
    * @return 子菜单的集合
    */
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
    List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
    return categoryEntity.getParentCid().equals(root.getCatId());
    }).map(categoryEntity -> {
    //1.递归找子菜单
    categoryEntity.setChildren(getChildren(categoryEntity, all));
    return categoryEntity;
    }).sorted((menu1, menu2) -> {
    //2.菜单的排序
    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());
    return children;
    }

    }
    + +

    1.1.2 配置路由和网关和路径重写

    前端访问的地址

    +

    image-20230529170214769

    +

    前端代码中的配置

    +

    image-20230529170149102

    +

    修改访问的基准路径,统一访问网关,再由网关做请求的转发,转发到指定的服务中去(renren-fast-vue\static\config\index.js文件)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 开发环境
    */
    ;(function () {
    window.SITE_CONFIG = {};

    // api接口请求地址
    window.SITE_CONFIG['baseUrl'] = 'http://localhost:88';

    // cdn地址 = 域名 + 版本号
    window.SITE_CONFIG['domain'] = './'; // 域名
    window.SITE_CONFIG['version'] = ''; // 版本号(年月日时分)
    window.SITE_CONFIG['cdnUrl'] = window.SITE_CONFIG.domain + window.SITE_CONFIG.version;
    })();

    + +

    将renren-fast注册到注册中心中去

    +

    修改pom.xml中的依赖

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- 修改springBoot的版本 -->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
    <relativePath/>
    </parent>
    <!-- 依赖common文件,获取nacos相关的注解 -->
    <dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    + +

    在启动类上添加@EnableDiscoveryClient注解,在配置文件中添加上nacos地址的配置

    +
    1
    2
    3
    4
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    + +

    修改网关中的断言,让前台的请求转发到正确的路径上

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    spring:
    cloud:
    gateway:
    routes:
    # 路由的唯一id
    - id: admin_route
    uri: lb://renren-fast
    predicates:
    - Path=/api/**
    # 路径重写
    filters:
    - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
    # 前端项目中的基准路径 http://localhost:88/api
    # http://localhost:88/api/captcha.jpg 路径重写为 http://localhost:8080/renren-fast/captcha.jpg
    + +

    解决登录的时候的跨域问题

    +

    浏览器中的跨域的报错信息

    +

    image-20230530111856491

    +

    跨域问题的介绍

    +

    image-20230530112115261

    +

    跨域流程

    +

    文档介绍

    +

    image-20230530112630678

    +

    跨域的解决方案

    +
      +
    1. 使用nginx部署为同一域
    2. +
    3. 配置当前请求允许跨域 (在网关中通过过滤器给请求添加响应头)
    4. +
    +

    跨域的配置

    +

    添加跨域的配置类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package com.eatguigu.gulimall.gateway.config;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.reactive.CorsWebFilter;
    import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/30
    * @Description 跨域配置类
    */
    @Configuration
    public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter(){
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    //1.配置跨域
    //允许所有的请求头跨域
    corsConfiguration.addAllowedHeader("*");
    //允许所有的请求方式跨域
    corsConfiguration.addAllowedMethod("*");
    //任意请求来源都可以跨域
    //corsConfiguration.addAllowedOrigin("*"); 老版本的springBoot使用这个,新版本的使用下面这个
    corsConfiguration.addAllowedOriginPattern("*");
    //允许携带cookie跨域
    corsConfiguration.setAllowCredentials(true);
    source.registerCorsConfiguration("/**",corsConfiguration);
    return new CorsWebFilter(source);
    }
    }

    + +

    注掉renren-fast工程中的跨域配置,避免产生两次跨域的问题

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    /**
    * Copyright (c) 2016-2019 人人开源 All rights reserved.
    *
    * https://www.renren.io
    *
    * 版权所有,侵权必究!
    */

    package io.renren.config;

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    @Configuration
    public class CorsConfig implements WebMvcConfigurer {

    // @Override
    // public void addCorsMappings(CorsRegistry registry) {
    // registry.addMapping("/**")
    // .allowedOrigins("*")
    // .allowCredentials(true)
    // .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
    // .maxAge(3600);
    // }
    }
    + +

    再次测试,成功的进入后台管理系统

    +

    1.2.3 查询-页面中树形显示

    新增一个商品系统的目录,并在该目录下创建一个分类维护的二级分类

    +

    1.添加一个一级分类

    +

    image-20230529161308671

    +

    image-20230529161317694

    +

    2.在商品系统的一级分类下添加一个分类维护的二级分类

    +

    image-20230529161648257

    +

    3.路径的问题

    +

    image-20230529162235482

    +

    4.路径与文件的对应关系

    +

    image-20230529162946584

    +

    5.发送请求的示例

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    <template>
    <div>
    <!-- 树形控件 -->
    <el-tree
    :data="data"
    :props="defaultProps"
    @node-click="handleNodeClick"
    ></el-tree>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    data: [],
    defaultProps: {
    children: "children",
    label: "label",
    },
    };
    },
    //钩子函数
    created() {
    this.getMenus();
    },

    //方法
    methods: {
    handleNodeClick(data) {
    console.log(data);
    },
    //获取所有的菜单
    getMenus() {
    //正式的发送请求
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    console.log(data);
    });
    },
    },
    };
    </script>
    <style>
    </style>
    + +

    6.树形显示商品的分类信息

    +

    前端修改网关商品模块的路由(注意:匹配精确的路由放在模糊的路由的上面)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    spring:
    cloud:
    gateway:
    routes:
    #这个路由精确一些,放在上面,避免被下面模糊的路由吞掉
    - id: product_route
    uri: lb://gulimall-product
    predicates:
    - Path=/api/product/**
    filters:
    - RewritePath=/api/(?<segment>.*),/$\{segment}

    # 路由的唯一id
    - id: admin_route
    uri: lb://renren-fast
    predicates:
    - Path=/api/**
    # 路径重写
    filters:
    - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}


    # 前端项目中的基准路径 http://localhost:88/api
    # http://localhost:88/api/captcha.jpg 路径重写为 http://localhost:8080/renren-fast/captcha.jpg
    + +

    前端发送请求,正确地获取数据

    +

    image-20230530172846989

    +

    在页面上显示数据

    +

    Element的配置

    +

    image-20230530173658950

    +

    前端代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <template>
    <div>
    <!-- 树形控件 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    @node-click="handleNodeClick"
    ></el-tree>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {//这里的配置看官方的文档
    children: "children",//父节点上的子节点
    label: "name",//每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    handleNodeClick(data) {
    console.log(data);
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    显示的效果

    +

    image-20230530173801824

    +

    1.2.4 删除-删除商品分类

    前端页面修改

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    <template>
    <!-- 树形控件-->
    <!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
    <!-- show-checkbox表示开启选择框,批量选择-->
    <!-- node-key表示在整个树中唯一的表示 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <= 2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >
    添加
    </el-button>
    <!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
    <el-button
    v-if="data.children.length <= 0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >
    删除
    </el-button>
    </span>
    </span></el-tree
    >
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {
    //这里的配置看官方的文档
    children: "children", //父节点上的子节点
    label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    expandedKey:[] //需要展开的数组,用于删除之后树形控件仍然展开
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    append(data) {
    console.log("append: ", data);
    },
    //删除节点的方法
    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才删除的节点)
    this.expandedKey = [node.parent.data.catId]
    });
    })
    .catch(() => {
    this.$message({
    type: "info",
    message: "已取消删除",
    });
    });
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    配置逻辑删除

    +

    配置全局的逻辑删除规则

    +
    1
    2
    3
    4
    5
    mybatis-plus:
    global-config:
    db-config:
    logic-delete-value: 1 # 逻辑已删除值(默认为 1)
    logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    + +

    在showStatus上添加上逻辑删除的注解

    +
    1
    @TableLogic(value = "1",delval = "0")
    + +

    后端代码的实现

    +

    controlller

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 删除
    */
    @RequestMapping("/delete")
    //@RequiresPermissions("product:category:delete")
    public R delete(@RequestBody Long[] catIds) {
    //检查当前删除的菜单,是否被别的地方引用
    //categoryService.removeByIds(Arrays.asList(catIds));
    categoryService.removeMenuByIds(Arrays.asList(catIds));
    return R.ok();
    }
    + +

    service 这里后面需要优化

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 删除分类的方法
    * @param list 分类id的集合
    */
    @Override
    public void removeMenuByIds(List<Long> list) {
    //TODO 检查当前删除的菜单是否被其他的地方引用 后面优化
    //逻辑删除
    categoryDao.deleteBatchIds(list);
    }
    + +

    1.2.5 添加-添加商品分类

    前端代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    <template>
    <div>
    <!-- 树形控件-->
    <!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
    <!-- show-checkbox表示开启选择框,批量选择-->
    <!-- node-key表示在整个树中唯一的表示 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <= 2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >
    添加
    </el-button>
    <!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
    <el-button
    v-if="data.children.length <= 0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >
    删除
    </el-button>
    </span>
    </span></el-tree
    >
    <!-- 添加分类的时候弹出的对话框 -->
    <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
    <el-form :model="category">
    <el-form-item label="分类名称">
    <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCategory">确 定</el-button>
    </span>
    </el-dialog>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {
    //这里的配置看官方的文档
    children: "children", //父节点上的子节点
    label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    expandedKey: [], //需要展开的数组,用于删除之后树形控件仍然展开
    dialogVisible: false, //添加菜单的对话框的开启或者关闭
    //表单绑定的对象
    category: {
    name: "", //分类的名称
    parentCid: 0, //父类的id
    catLevel: 0, //分类的级别
    showStatus: 1, //是否显示该分类
    sort: 0, //排序
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    //添加分类(弹出的对话框确定按钮执行的方法)
    addCategory() {
    console.log("提交的数据:", this.category);
    //将添加的数据返回给后端
    this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单添加成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus()
    //设置需要默认展开的菜单(依然展开刚才添加的节点)
    this.expandedKey = [this.category.parentCid];
    });
    //关闭对话框
    this.dialogVisible = false;
    },
    //添加分类的方法
    append(data) {
    //清空一下表单的数据
    this.category.name = "";
    //打开添加的对话框
    this.dialogVisible = true;
    //为分类的对象赋值
    this.category.parentCid = data.catId;
    this.category.catLevel = data.catLevel * 1 + 1; //防止catLevel是一个字符串
    },
    //删除节点的方法
    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才删除的节点)
    this.expandedKey = [node.parent.data.catId];
    });
    })
    .catch(() => {
    this.$message({
    type: "info",
    message: "已取消删除",
    });
    });
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    后端代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     /**
    * 保存
    */
    @RequestMapping("/save")
    //@RequiresPermissions("product:category:save")
    public R save(@RequestBody CategoryEntity category) {
    categoryService.save(category);

    return R.ok();
    }
    + +

    实现的效果

    +

    image-20230605163004850

    +

    1.2.6 修改-修改商品的分类

    前端的代码(太复杂了,没有写完)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    <template>
    <div>
    <!-- 树形控件-->
    <!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
    <!-- show-checkbox表示开启选择框,批量选择-->
    <!-- node-key表示在整个树中唯一的表示 -->
    <!-- draggable 实现节点自由拖拽的效果 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    draggable
    :allow-drop="allowDrop"
    @node-drop="handleDrop"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <= 2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >
    添加
    </el-button>
    <el-button type="text" size="mini" @click="() => edit(data)">
    修改
    </el-button>
    <!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
    <el-button
    v-if="data.children.length <= 0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >
    删除
    </el-button>
    </span>
    </span></el-tree
    >
    <!-- 添加分类的时候弹出的对话框 -->
    <el-dialog
    :title="title"
    :visible.sync="dialogVisible"
    width="30%"
    :close-on-click-modal="false"
    >
    <el-form :model="category">
    <el-form-item label="分类名称">
    <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="图标">
    <el-input v-model="category.icon" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="计量单位">
    <el-input
    v-model="category.productUnit"
    autocomplete="off"
    ></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="submitDate">确 定</el-button>
    </span>
    </el-dialog>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    updateNodes: [], //经过修改之后的节点的信息
    maxLevel: 0, //最大的层级
    title: "", //弹出框的提示信息
    dialogType: "", //弹出的对话框的类型 edit/add
    menus: [],
    defaultProps: {
    //这里的配置看官方的文档
    children: "children", //父节点上的子节点
    label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    expandedKey: [], //需要展开的数组,用于删除之后树形控件仍然展开
    dialogVisible: false, //添加菜单的对话框的开启或者关闭
    //表单绑定的对象
    category: {
    name: "", //分类的名称
    parentCid: 0, //父类的id
    catLevel: 0, //分类的级别
    showStatus: 1, //是否显示该分类
    sort: 0, //排序
    catId: null, //分类的id信息
    icon: "", //商品分类的图标
    productUnit: "", //计量单位
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    //拖拽成功之后调用这个函数进行回调的处理
    handleDrop(draggingNode, dropNode, dropType, ev) {
    console.log("handleDrop: ", draggingNode, dropNode, dropType);
    //当前的节点的最新父节点id
    let pCid = 0; //当前节点的组最新父节点id
    let siblings = null; //当前的节点的兄弟节点
    //判断的节点的进入的方式
    if (dropType == "before" || dropType == "after") {
    pCid =
    dropNode.parent.data.catId == undefined
    ? 0
    : dropNode.parent.data.catId;
    siblings = dropNode.parent.childNodes;
    } else {
    pCid = dropNode.data.catId;
    siblings = dropNode.childNodes;
    }
    //当前的节点的最新的顺序
    for (let i = 0; i < siblings.length; i++) {
    if (siblings[i].data.catId == draggingNode.data.catId) {
    //如果当前遍历的是正在拖拽的节点
    let catLevel = draggingNode.catLevel;
    if (siblings[i].level != draggingNode.level) {
    //当前的节点的层级发生的变化
    catLevel = siblings[i].level;
    //修改子节点的层级
    this.updateChildNodeLevel(siblings[i]);
    }
    this.updateNodes.push({
    catId: siblings[i].data.catId,
    sort: i,
    parentCid: pCid,
    catLevel: catLevel,
    });
    } else {
    this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
    }
    }
    //当前的拖拽节点的最新的层级
    console.log("updateNodes:", this.updateNodes);
    },
    //递归修改子节点的层级
    updateChildNodeLevel(node) {
    if ((node.childNodes, length > 0)) {
    for (let i = 0; i < node.childNodes.length; i++) {
    var cNode = node.childNodes[i].data;
    this.updateNodes.push({
    catId: cNode.catId,
    catLevel: node.childNodes[i].level,
    });
    //递归调用
    this.updateChildNodeLevel(node.childNodes[i]);
    }
    }
    },
    //拖拽之后节点的位置的判断 是否的可以放在该位置上面
    allowDrop(draggingNode, dropNode, type) {
    //判断被拖动的当前节点以及所在的父节点的总层数不能大于三
    //被拖动的当前节点的总层数
    console.log("allowDrop:", draggingNode, dropNode, type);
    //获取当前节点的总层数
    this.countNodeLevel(draggingNode.data);
    let deep = this.maxLevel - draggingNode.data.catLevel + 1;
    console.log("深度:", deep);
    if ((type = "inner")) {
    return deep + dropNode.level <= 3;
    } else {
    return deep + dropNode.parent.level <= 3;
    }
    },
    //统计当前的节点的总层数
    countNodeLevel(node) {
    //找到所有子节点 求出最大深度
    if (node.children != null && node.children.length > 0) {
    for (let i = 0; i < node.children.length; i++) {
    if (node.children[i].catLevel > this.maxLevel) {
    this.maxLevel = node.children[i].catLevel;
    }
    //递归一下
    this.countNodeLevel(node.children[i]);
    }
    }
    },
    //弹出修改的对话框,并做数据的回显
    edit(data) {
    //修改的title
    this.title = "修改分类";
    //设置弹出的对话框为修改的类型
    this.dialogType = "edit";
    //发送请求回显数据
    this.$http({
    url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
    method: "get",
    }).then(({ data }) => {
    //请求成功 回显数据
    console.log("回显的数据:", data);
    this.category.name = data.data.name;
    this.category.catId = data.data.catId;
    this.category.icon = data.data.icon;
    this.category.productUnit = data.data.productUnit;
    this.category.parentCid = data.data.parentCid;
    });
    //弹出对话框
    this.dialogVisible = true;
    },
    //保存或者添加数据
    submitDate() {
    if (this.dialogType == "add") {
    //调用添加的方法
    this.addCategory();
    }
    if (this.dialogType == "edit") {
    //修改的方法
    this.editCategory();
    }
    },
    //修改商品的分类信息
    editCategory() {
    //只发送我们要修改的数据
    var { catId, name, icon, productUnit } = this.category;
    this.$http({
    url: this.$http.adornUrl("/product/category/update"),
    method: "post",
    data: this.$http.adornData({ catId, name, icon, productUnit }, false),
    }).then(({ data }) => {
    this.$message({
    message: "分类修改成功",
    type: "success",
    });
    //关闭弹出框
    this.dialogVisible = false;
    //刷新一下表单
    this.getMenus();
    //设置默认展开的菜单
    this.expandedKey = [this.category.parentCid];
    });
    },
    //添加分类(弹出的对话框确定按钮执行的方法)
    addCategory() {
    console.log("提交的数据:", this.category);
    //将添加的数据返回给后端
    this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false),
    }).then(({ data }) => {
    this.$message({
    message: "分类添加成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才添加的节点)
    this.expandedKey = [this.category.parentCid];
    });
    //关闭对话框
    this.dialogVisible = false;
    },
    //添加分类的方法
    append(data) {
    //添加的title
    this.title = "添加分类";
    //设置当前提交的对话框为添加的对话框
    this.dialogType = "add";
    //清空一下表单的数据
    this.category.name = "";
    //打开添加的对话框
    this.dialogVisible = true;
    //为分类的对象赋值
    this.category.parentCid = data.catId;
    this.category.catLevel = data.catLevel * 1 + 1; //防止catLevel是一个字符串
    this.catId = null;
    this.category.icon = "";
    this.category.productUnit = "";
    this.category.sort = 0;
    this.category.showStatus = 1;
    },
    //删除节点的方法
    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才删除的节点)
    this.expandedKey = [node.parent.data.catId];
    });
    })
    .catch(() => {
    this.$message({
    type: "info",
    message: "已取消删除",
    });
    });
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    老师的前端的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    <template>
    <div>
    <el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽"></el-switch>
    <el-button v-if="draggable" @click="batchSave">批量保存</el-button>
    <el-button type="danger" @click="batchDelete">批量删除</el-button>
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    :draggable="draggable"
    :allow-drop="allowDrop"
    @node-drop="handleDrop"
    ref="menuTree"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <=2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >Append</el-button>
    <el-button type="text" size="mini" @click="edit(data)">edit</el-button>
    <el-button
    v-if="node.childNodes.length==0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >Delete</el-button>
    </span>
    </span>
    </el-tree>

    <el-dialog
    :title="title"
    :visible.sync="dialogVisible"
    width="30%"
    :close-on-click-modal="false"
    >
    <el-form :model="category">
    <el-form-item label="分类名称">
    <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="图标">
    <el-input v-model="category.icon" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="计量单位">
    <el-input v-model="category.productUnit" autocomplete="off"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="submitData">确 定</el-button>
    </span>
    </el-dialog>
    </div>
    </template>

    <script>
    //这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
    //例如:import 《组件名称》 from '《组件路径》';

    export default {
    //import引入的组件需要注入到对象中才能使用
    components: {},
    props: {},
    data() {
    return {
    pCid: [],
    draggable: false,
    updateNodes: [],
    maxLevel: 0,
    title: "",
    dialogType: "", //edit,add
    category: {
    name: "",
    parentCid: 0,
    catLevel: 0,
    showStatus: 1,
    sort: 0,
    productUnit: "",
    icon: "",
    catId: null
    },
    dialogVisible: false,
    menus: [],
    expandedKey: [],
    defaultProps: {
    children: "children",
    label: "name"
    }
    };
    },

    //计算属性 类似于data概念
    computed: {},
    //监控data中的数据变化
    watch: {},
    //方法集合
    methods: {
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get"
    }).then(({ data }) => {
    console.log("成功获取到菜单数据...", data.data);
    this.menus = data.data;
    });
    },
    batchDelete() {
    let catIds = [];
    let checkedNodes = this.$refs.menuTree.getCheckedNodes();
    console.log("被选中的元素", checkedNodes);
    for (let i = 0; i < checkedNodes.length; i++) {
    catIds.push(checkedNodes[i].catId);
    }
    this.$confirm(`是否批量删除【${catIds}】菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning"
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(catIds, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单批量删除成功",
    type: "success"
    });
    this.getMenus();
    });
    })
    .catch(() => {});
    },
    batchSave() {
    this.$http({
    url: this.$http.adornUrl("/product/category/update/sort"),
    method: "post",
    data: this.$http.adornData(this.updateNodes, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单顺序等修改成功",
    type: "success"
    });
    //刷新出新的菜单
    this.getMenus();
    //设置需要默认展开的菜单
    this.expandedKey = this.pCid;
    this.updateNodes = [];
    this.maxLevel = 0;
    // this.pCid = 0;
    });
    },
    handleDrop(draggingNode, dropNode, dropType, ev) {
    console.log("handleDrop: ", draggingNode, dropNode, dropType);
    //1、当前节点最新的父节点id
    let pCid = 0;
    let siblings = null;
    if (dropType == "before" || dropType == "after") {
    pCid =
    dropNode.parent.data.catId == undefined
    ? 0
    : dropNode.parent.data.catId;
    siblings = dropNode.parent.childNodes;
    } else {
    pCid = dropNode.data.catId;
    siblings = dropNode.childNodes;
    }
    this.pCid.push(pCid);

    //2、当前拖拽节点的最新顺序,
    for (let i = 0; i < siblings.length; i++) {
    if (siblings[i].data.catId == draggingNode.data.catId) {
    //如果遍历的是当前正在拖拽的节点
    let catLevel = draggingNode.level;
    if (siblings[i].level != draggingNode.level) {
    //当前节点的层级发生变化
    catLevel = siblings[i].level;
    //修改他子节点的层级
    this.updateChildNodeLevel(siblings[i]);
    }
    this.updateNodes.push({
    catId: siblings[i].data.catId,
    sort: i,
    parentCid: pCid,
    catLevel: catLevel
    });
    } else {
    this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
    }
    }

    //3、当前拖拽节点的最新层级
    console.log("updateNodes", this.updateNodes);
    },
    updateChildNodeLevel(node) {
    if (node.childNodes.length > 0) {
    for (let i = 0; i < node.childNodes.length; i++) {
    var cNode = node.childNodes[i].data;
    this.updateNodes.push({
    catId: cNode.catId,
    catLevel: node.childNodes[i].level
    });
    this.updateChildNodeLevel(node.childNodes[i]);
    }
    }
    },
    allowDrop(draggingNode, dropNode, type) {
    //1、被拖动的当前节点以及所在的父节点总层数不能大于3

    //1)、被拖动的当前节点总层数
    console.log("allowDrop:", draggingNode, dropNode, type);
    //
    this.countNodeLevel(draggingNode);
    //当前正在拖动的节点+父节点所在的深度不大于3即可
    let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
    console.log("深度:", deep);

    // this.maxLevel
    if (type == "inner") {
    // console.log(
    // `this.maxLevel:${this.maxLevel};draggingNode.data.catLevel:${draggingNode.data.catLevel};dropNode.level:${dropNode.level}`
    // );
    return deep + dropNode.level <= 3;
    } else {
    return deep + dropNode.parent.level <= 3;
    }
    },
    countNodeLevel(node) {
    //找到所有子节点,求出最大深度
    if (node.childNodes != null && node.childNodes.length > 0) {
    for (let i = 0; i < node.childNodes.length; i++) {
    if (node.childNodes[i].level > this.maxLevel) {
    this.maxLevel = node.childNodes[i].level;
    }
    this.countNodeLevel(node.childNodes[i]);
    }
    }
    },
    edit(data) {
    console.log("要修改的数据", data);
    this.dialogType = "edit";
    this.title = "修改分类";
    this.dialogVisible = true;

    //发送请求获取当前节点最新的数据
    this.$http({
    url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
    method: "get"
    }).then(({ data }) => {
    //请求成功
    console.log("要回显的数据", data);
    this.category.name = data.data.name;
    this.category.catId = data.data.catId;
    this.category.icon = data.data.icon;
    this.category.productUnit = data.data.productUnit;
    this.category.parentCid = data.data.parentCid;
    this.category.catLevel = data.data.catLevel;
    this.category.sort = data.data.sort;
    this.category.showStatus = data.data.showStatus;
    /**
    * parentCid: 0,
    catLevel: 0,
    showStatus: 1,
    sort: 0,
    */
    });
    },
    append(data) {
    console.log("append", data);
    this.dialogType = "add";
    this.title = "添加分类";
    this.dialogVisible = true;
    this.category.parentCid = data.catId;
    this.category.catLevel = data.catLevel * 1 + 1;
    this.category.catId = null;
    this.category.name = "";
    this.category.icon = "";
    this.category.productUnit = "";
    this.category.sort = 0;
    this.category.showStatus = 1;
    },

    submitData() {
    if (this.dialogType == "add") {
    this.addCategory();
    }
    if (this.dialogType == "edit") {
    this.editCategory();
    }
    },
    //修改三级分类数据
    editCategory() {
    var { catId, name, icon, productUnit } = this.category;
    this.$http({
    url: this.$http.adornUrl("/product/category/update"),
    method: "post",
    data: this.$http.adornData({ catId, name, icon, productUnit }, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单修改成功",
    type: "success"
    });
    //关闭对话框
    this.dialogVisible = false;
    //刷新出新的菜单
    this.getMenus();
    //设置需要默认展开的菜单
    this.expandedKey = [this.category.parentCid];
    });
    },
    //添加三级分类
    addCategory() {
    console.log("提交的三级分类数据", this.category);
    this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单保存成功",
    type: "success"
    });
    //关闭对话框
    this.dialogVisible = false;
    //刷新出新的菜单
    this.getMenus();
    //设置需要默认展开的菜单
    this.expandedKey = [this.category.parentCid];
    });
    },

    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否删除【${data.name}】菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning"
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success"
    });
    //刷新出新的菜单
    this.getMenus();
    //设置需要默认展开的菜单
    this.expandedKey = [node.parent.data.catId];
    });
    })
    .catch(() => {});

    console.log("remove", node, data);
    }
    },
    //生命周期 - 创建完成(可以访问当前this实例)
    created() {
    this.getMenus();
    },
    //生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {},
    beforeCreate() {}, //生命周期 - 创建之前
    beforeMount() {}, //生命周期 - 挂载之前
    beforeUpdate() {}, //生命周期 - 更新之前
    updated() {}, //生命周期 - 更新之后
    beforeDestroy() {}, //生命周期 - 销毁之前
    destroyed() {}, //生命周期 - 销毁完成
    activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
    };
    </script>
    <style scoped>
    </style>
    + +

    后端的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 批量修改商品的分类信息
    */
    @RequestMapping("/update/sort")
    public R updateSort(@RequestBody CategoryEntity[] categoryEntities) {
    categoryService.updateBatchById(Arrays.asList(categoryEntities));
    return R.ok();
    }
    + +

    1.2 品牌管理

    1.2.1 品牌的增删改查功能

    这里前端的代码是和先前生成的后端的代码一起生成的

    +

    image-20230611111440999

    +

    商品品牌的列表显示

    +

    优化之后的前端的代码 (表头的优化 需要按钮显示的使用按钮进行显示)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    <template>
    <div class="mod-config">
    <el-form
    :inline="true"
    :model="dataForm"
    @keyup.enter.native="getDataList()"
    >
    <el-form-item>
    <el-input
    v-model="dataForm.key"
    placeholder="参数名"
    clearable
    ></el-input>
    </el-form-item>
    <el-form-item>
    <el-button @click="getDataList()">查询</el-button>
    <el-button
    v-if="isAuth('product:brand:save')"
    type="primary"
    @click="addOrUpdateHandle()"
    >新增</el-button
    >
    <el-button
    v-if="isAuth('product:brand:delete')"
    type="danger"
    @click="deleteHandle()"
    :disabled="dataListSelections.length <= 0"
    >批量删除</el-button
    >
    </el-form-item>
    </el-form>
    <el-table
    :data="dataList"
    border
    v-loading="dataListLoading"
    @selection-change="selectionChangeHandle"
    style="width: 100%"
    >
    <el-table-column
    type="selection"
    header-align="center"
    align="center"
    width="50"
    >
    </el-table-column>
    <el-table-column
    prop="brandId"
    header-align="center"
    align="center"
    label="品牌id"
    >
    </el-table-column>
    <el-table-column
    prop="name"
    header-align="center"
    align="center"
    label="品牌名"
    >
    </el-table-column>
    <el-table-column
    prop="logo"
    header-align="center"
    align="center"
    label="品牌logo地址"
    >
    </el-table-column>
    <el-table-column
    prop="descript"
    header-align="center"
    align="center"
    label="介绍"
    >
    </el-table-column>
    <el-table-column
    prop="showStatus"
    header-align="center"
    align="center"
    label="显示状态"
    >
    <template slot-scope="scope">
    <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    :active-value="1"
    :inactive-value="0"
    @change="updateBrandStatus(scope.row)"
    >
    </el-switch>
    </template>
    </el-table-column>
    <el-table-column
    prop="firstLetter"
    header-align="center"
    align="center"
    label="检索首字母"
    >
    </el-table-column>
    <el-table-column
    prop="sort"
    header-align="center"
    align="center"
    label="排序"
    >
    </el-table-column>
    <el-table-column
    fixed="right"
    header-align="center"
    align="center"
    width="150"
    label="操作"
    >
    <template slot-scope="scope">
    <el-button
    type="text"
    size="small"
    @click="addOrUpdateHandle(scope.row.brandId)"
    >修改</el-button
    >
    <el-button
    type="text"
    size="small"
    @click="deleteHandle(scope.row.brandId)"
    >删除</el-button
    >
    </template>
    </el-table-column>
    </el-table>
    <el-pagination
    @size-change="sizeChangeHandle"
    @current-change="currentChangeHandle"
    :current-page="pageIndex"
    :page-sizes="[10, 20, 50, 100]"
    :page-size="pageSize"
    :total="totalPage"
    layout="total, sizes, prev, pager, next, jumper"
    >
    </el-pagination>
    <!-- 弹窗, 新增 / 修改 -->
    <add-or-update
    v-if="addOrUpdateVisible"
    ref="addOrUpdate"
    @refreshDataList="getDataList"
    ></add-or-update>
    </div>
    </template>

    <script>
    import AddOrUpdate from "./brand-add-or-update";
    export default {
    data() {
    return {
    dataForm: {
    key: "",
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false,
    };
    },
    components: {
    AddOrUpdate,
    },
    activated() {
    this.getDataList();
    },
    methods: {
    //修改品牌的显示的状态的
    updateBrandStatus(data) {
    console.log("修改的状态信息:", data);
    let { brandId, showStatus } = data;
    //发送修改状态的请求
    this.$http({
    url: this.$http.adornUrl(`/product/brand/update`),
    method: "post",
    data: this.$http.adornData(
    { brandId: brandId, showStatus: showStatus },
    false
    ),
    }).then(({ data }) => {
    this.$message({
    message: "状态更新成功",
    type: "success",
    });
    });
    },
    // 获取数据列表
    getDataList() {
    this.dataListLoading = true;
    this.$http({
    url: this.$http.adornUrl("/product/brand/list"),
    method: "get",
    params: this.$http.adornParams({
    page: this.pageIndex,
    limit: this.pageSize,
    key: this.dataForm.key,
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataList = data.page.list;
    this.totalPage = data.page.totalCount;
    } else {
    this.dataList = [];
    this.totalPage = 0;
    }
    this.dataListLoading = false;
    });
    },
    // 每页数
    sizeChangeHandle(val) {
    this.pageSize = val;
    this.pageIndex = 1;
    this.getDataList();
    },
    // 当前页
    currentChangeHandle(val) {
    this.pageIndex = val;
    this.getDataList();
    },
    // 多选
    selectionChangeHandle(val) {
    this.dataListSelections = val;
    },
    // 新增 / 修改
    addOrUpdateHandle(id) {
    this.addOrUpdateVisible = true;
    this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id);
    });
    },
    // 删除
    deleteHandle(id) {
    var ids = id
    ? [id]
    : this.dataListSelections.map((item) => {
    return item.brandId;
    });
    this.$confirm(
    `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
    "提示",
    {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    }
    ).then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/brand/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.getDataList();
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    });
    },
    },
    };
    </script>
    + +

    后端代码(修改品牌的显示状态)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     /**
    * 修改
    */
    @RequestMapping("/update")
    //@RequiresPermissions("product:brand:update")
    public R update(@RequestBody BrandEntity brand){
    brandService.updateById(brand);

    return R.ok();
    }
    + +

    品牌添加功能

    +

    阿里云对象存储OSS | The Blog (gitee.io)

    +

    image-20230611153418426

    +

    前端的代码

    +

    品牌管理的列表页面

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    <template>
    <div class="mod-config">
    <el-form
    :inline="true"
    :model="dataForm"
    @keyup.enter.native="getDataList()"
    >
    <el-form-item>
    <el-input
    v-model="dataForm.key"
    placeholder="参数名"
    clearable
    ></el-input>
    </el-form-item>
    <el-form-item>
    <el-button @click="getDataList()">查询</el-button>
    <el-button
    v-if="isAuth('product:brand:save')"
    type="primary"
    @click="addOrUpdateHandle()"
    >新增</el-button
    >
    <el-button
    v-if="isAuth('product:brand:delete')"
    type="danger"
    @click="deleteHandle()"
    :disabled="dataListSelections.length <= 0"
    >批量删除</el-button
    >
    </el-form-item>
    </el-form>
    <el-table
    :data="dataList"
    border
    v-loading="dataListLoading"
    @selection-change="selectionChangeHandle"
    style="width: 100%"
    >
    <el-table-column
    type="selection"
    header-align="center"
    align="center"
    width="50"
    >
    </el-table-column>
    <el-table-column
    prop="brandId"
    header-align="center"
    align="center"
    label="品牌id"
    >
    </el-table-column>
    <el-table-column
    prop="name"
    header-align="center"
    align="center"
    label="品牌名"
    >
    </el-table-column>
    <el-table-column
    prop="logo"
    header-align="center"
    align="center"
    label="品牌logo地址"
    >
    <template slot-scope="scope">
    <img :src="scope.row.logo" style="width: 100px; height: 80px">
    </template>
    </el-table-column>
    <el-table-column
    prop="descript"
    header-align="center"
    align="center"
    label="介绍"
    >
    </el-table-column>
    <el-table-column
    prop="showStatus"
    header-align="center"
    align="center"
    label="显示状态"
    >
    <template slot-scope="scope">
    <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    :active-value="1"
    :inactive-value="0"
    @change="updateBrandStatus(scope.row)"
    >
    </el-switch>
    </template>
    </el-table-column>
    <el-table-column
    prop="firstLetter"
    header-align="center"
    align="center"
    label="检索首字母"
    >
    </el-table-column>
    <el-table-column
    prop="sort"
    header-align="center"
    align="center"
    label="排序"
    >
    </el-table-column>
    <el-table-column
    fixed="right"
    header-align="center"
    align="center"
    width="150"
    label="操作"
    >
    <template slot-scope="scope">
    <el-button
    type="text"
    size="small"
    @click="addOrUpdateHandle(scope.row.brandId)"
    >修改</el-button
    >
    <el-button
    type="text"
    size="small"
    @click="deleteHandle(scope.row.brandId)"
    >删除</el-button
    >
    </template>
    </el-table-column>
    </el-table>
    <el-pagination
    @size-change="sizeChangeHandle"
    @current-change="currentChangeHandle"
    :current-page="pageIndex"
    :page-sizes="[10, 20, 50, 100]"
    :page-size="pageSize"
    :total="totalPage"
    layout="total, sizes, prev, pager, next, jumper"
    >
    </el-pagination>
    <!-- 弹窗, 新增 / 修改 -->
    <add-or-update
    v-if="addOrUpdateVisible"
    ref="addOrUpdate"
    @refreshDataList="getDataList"
    ></add-or-update>
    </div>
    </template>

    <script>
    import AddOrUpdate from "./brand-add-or-update";
    export default {
    data() {
    return {
    dataForm: {
    key: "",
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false,
    };
    },
    components: {
    AddOrUpdate,
    },
    activated() {
    this.getDataList();
    },
    methods: {
    //修改品牌的显示的状态的
    updateBrandStatus(data) {
    console.log("修改的状态信息:", data);
    let { brandId, showStatus } = data;
    //发送修改状态的请求
    this.$http({
    url: this.$http.adornUrl(`/product/brand/update`),
    method: "post",
    data: this.$http.adornData(
    { brandId: brandId, showStatus: showStatus },
    false
    ),
    }).then(({ data }) => {
    this.$message({
    message: "状态更新成功",
    type: "success",
    });
    });
    },
    // 获取数据列表
    getDataList() {
    this.dataListLoading = true;
    this.$http({
    url: this.$http.adornUrl("/product/brand/list"),
    method: "get",
    params: this.$http.adornParams({
    page: this.pageIndex,
    limit: this.pageSize,
    key: this.dataForm.key,
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataList = data.page.list;
    this.totalPage = data.page.totalCount;
    } else {
    this.dataList = [];
    this.totalPage = 0;
    }
    this.dataListLoading = false;
    });
    },
    // 每页数
    sizeChangeHandle(val) {
    this.pageSize = val;
    this.pageIndex = 1;
    this.getDataList();
    },
    // 当前页
    currentChangeHandle(val) {
    this.pageIndex = val;
    this.getDataList();
    },
    // 多选
    selectionChangeHandle(val) {
    this.dataListSelections = val;
    },
    // 新增 / 修改
    addOrUpdateHandle(id) {
    this.addOrUpdateVisible = true;
    this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id);
    });
    },
    // 删除
    deleteHandle(id) {
    var ids = id
    ? [id]
    : this.dataListSelections.map((item) => {
    return item.brandId;
    });
    this.$confirm(
    `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
    "提示",
    {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    }
    ).then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/brand/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.getDataList();
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    });
    },
    },
    };
    </script>

    + +

    品牌管理的添加和修改组件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    <template>
    <el-dialog
    :title="!dataForm.brandId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible"
    >
    <el-form
    :model="dataForm"
    :rules="dataRule"
    ref="dataForm"
    @keyup.enter.native="dataFormSubmit()"
    label-width="140px"
    >
    <el-form-item label="品牌名" prop="name">
    <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
    </el-form-item>
    <el-form-item label="品牌logo地址" prop="logo">
    <!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
    <!-- 自定义的组件 -->
    <SingleUpload v-model="dataForm.logo"></SingleUpload>
    </el-form-item>
    <el-form-item label="介绍" prop="descript">
    <el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
    </el-form-item>
    <el-form-item label="显示状态" prop="showStatus">
    <el-switch
    v-model="dataForm.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    :active-value="1"
    :inactive-value="0"
    >
    </el-switch>
    </el-form-item>
    <el-form-item label="检索首字母" prop="firstLetter">
    <el-input
    v-model="dataForm.firstLetter"
    placeholder="检索首字母"
    ></el-input>
    </el-form-item>
    <el-form-item label="排序" prop="sort">
    <el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="visible = false">取消</el-button>
    <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
    </el-dialog>
    </template>

    <script>
    import SingleUpload from "@/components/upload/singleUpload";
    export default {
    components: { SingleUpload }, //声明一下组件
    data() {
    return {
    visible: false,
    dataForm: {
    brandId: 0,
    name: "",
    logo: "",
    descript: "",
    showStatus: 1,
    firstLetter: "",
    sort: 0,
    },
    dataRule: {
    name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
    logo: [
    { required: true, message: "品牌logo地址不能为空", trigger: "blur" },
    ],
    descript: [
    { required: true, message: "介绍不能为空", trigger: "blur" },
    ],
    showStatus: [
    {
    required: true,
    message: "显示状态[0-不显示;1-显示]不能为空",
    trigger: "blur",
    },
    ],
    firstLetter: [
    {
    validator: (rule, value, callback) => {
    if (value == "") {
    //检测是不是空串
    callback(new Error("检索首字母不能为空"));
    } else if (!/^[a-zA-Z]$/.test(value)) {
    //检测输入的是不是字母
    callback(new Error("首字母必须是a-z或者A-Z"));
    } else {
    //成功
    callback();
    }
    },
    trigger: "blur",
    },
    ],
    sort: [
    {
    validator: (rule, value, callback) => {
    if (value === "") {
    //检测是不是空串
    callback(new Error("排序不能为空"));
    } else if (!Number.isInteger(value) || value < 0) {
    //检测输入的是不是字母
    callback(new Error("排序必须为整数并且大于0"));
    } else {
    //成功
    callback();
    }
    },
    trigger: "blur",
    },
    ],
    },
    };
    },
    methods: {
    init(id) {
    this.dataForm.brandId = id || 0;
    this.visible = true;
    this.$nextTick(() => {
    this.$refs["dataForm"].resetFields();
    if (this.dataForm.brandId) {
    this.$http({
    url: this.$http.adornUrl(
    `/product/brand/info/${this.dataForm.brandId}`
    ),
    method: "get",
    params: this.$http.adornParams(),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataForm.name = data.brand.name;
    this.dataForm.logo = data.brand.logo;
    this.dataForm.descript = data.brand.descript;
    this.dataForm.showStatus = data.brand.showStatus;
    this.dataForm.firstLetter = data.brand.firstLetter;
    this.dataForm.sort = data.brand.sort;
    }
    });
    }
    });
    },
    // 表单提交
    dataFormSubmit() {
    this.$refs["dataForm"].validate((valid) => {
    if (valid) {
    this.$http({
    url: this.$http.adornUrl(
    `/product/brand/${!this.dataForm.brandId ? "save" : "update"}`
    ),
    method: "post",
    data: this.$http.adornData({
    brandId: this.dataForm.brandId || undefined,
    name: this.dataForm.name,
    logo: this.dataForm.logo,
    descript: this.dataForm.descript,
    showStatus: this.dataForm.showStatus,
    firstLetter: this.dataForm.firstLetter,
    sort: this.dataForm.sort,
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.visible = false;
    this.$emit("refreshDataList");
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    }
    });
    },
    },
    };
    </script>
    + +

    后端的代码

    +

    图片上传相关的后台接口

    +

    配置文件
    apllication.yml

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    spring:
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    alicloud:
    access-key: XXXXXXXXXXXXXXXXXXXX
    secret-key: XXXXXXXXXXXXXXXXXXXX
    oss:
    endpoint: oss-cn-beijing.aliyuncs.com
    bucket: gulimall-0611 #因为是公用的配置,这个配置是自己配置的

    application:
    name: gulimall-third-party

    server:
    port: 30000
    + +

    Controller

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    package com.atguigu.gulimall.thirdparty.controller;

    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.MatchMode;
    import com.aliyun.oss.model.PolicyConditions;
    import com.atguigu.common.utils.R;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.LinkedHashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/11
    * @Description 阿里云对象存储的控制器方法
    */

    @RestController
    public class OSSController {

    @Autowired
    OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;
    @Value("${spring.cloud.alicloud.secret-key}")
    private String accessKey;

    @RequestMapping("/oss/policy")
    public R policy() {
    // 填写Host地址,格式为https://bucketname.endpoint。
    String host = "https://"+bucket+"."+endpoint;
    // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
    //String callbackUrl = "https://192.168.0.0:8888";
    // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
    String format = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
    String dir = format+"/";

    // 创建ossClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
    Map<String, String> respMap = null;
    try {
    long expireTime = 30;
    long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
    Date expiration = new Date(expireEndTime);
    PolicyConditions policyConds = new PolicyConditions();
    policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
    policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

    String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
    byte[] binaryData = postPolicy.getBytes("utf-8");
    String encodedPolicy = BinaryUtil.toBase64String(binaryData);
    String postSignature = ossClient.calculatePostSignature(postPolicy);

    respMap = new LinkedHashMap<String, String>();
    respMap.put("accessId", accessId);
    respMap.put("policy", encodedPolicy);
    respMap.put("signature", postSignature);
    respMap.put("dir", dir);
    respMap.put("host", host);
    respMap.put("expire", String.valueOf(expireEndTime / 1000));
    // respMap.put("expire", formatISO8601Date(expiration));
    } catch (Exception e) {
    System.out.println(e.getMessage());
    }
    return R.ok().put("data",respMap);
    }
    }

    + +

    配置网关,通过网关访问到该接口

    +
    1
    2
    3
    4
    5
    6
    - id: third_party_route
    uri: lb://gulimall-third-party
    predicates:
    - Path=/api/thirdparty/**
    filters:
    - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
    + +

    1.2.2 JSR303-数据校验

    数据校验 | The Blog (gitee.io)

    +

    1.依赖文件

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.7.Final</version>
    </dependency>
    + +

    2.在实体类上添加上校验的规则

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    package com.atguigu.gulimall.product.entity;

    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;

    import java.io.Serializable;

    import lombok.Data;
    import org.hibernate.validator.constraints.URL;

    import javax.validation.constraints.*;

    /**
    * 品牌
    *
    * @author JasonGong
    * @email JasonGong@gmail.com
    * @date 2023-05-19 00:23:36
    */
    @Data
    @TableName("pms_brand")
    public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
    * 品牌id
    */
    @TableId
    private Long brandId;
    /**
    * 品牌名
    */
    @NotBlank(message = "品牌名必须提交") //非空的校验
    private String name;
    /**
    * 品牌logo地址
    */
    @NotEmpty
    @URL(message = "logo必须是一个合法的URL地址")//URL的校验
    private String logo;
    /**
    * 介绍
    */
    private String descript;
    /**
    * 显示状态[0-不显示;1-显示]
    */
    private Integer showStatus;
    /**
    * 检索首字母
    */
    @NotEmpty
    @Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")//正则表达式的校验
    private String firstLetter;
    /**
    * 排序
    */
    @NotNull
    @Min(value = 0, message = "排序必须大于等于0")//最小值是0
    private Integer sort;

    }
    + +

    3.接口上开启检验,并自定义校验出错时的返回值

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /**
    * 保存
    *"@Valid" 开启校验
    * @param result 检验之后响应的结果信息
    */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
    if(result.hasErrors()){
    Map<String,String> map = new HashMap<>();
    //获取检验的错误结果
    for (FieldError fieldError : result.getFieldErrors()) {
    //获取到错误的提示
    String defaultMessage = fieldError.getDefaultMessage();
    //获取错误的属性的名字
    String field = fieldError.getField();
    map.put(field,defaultMessage);
    }
    return R.error(400,"提交的数据不合法").put("data",map);
    }else {
    brandService.save(brand);
    }
    return R.ok();
    }
    + +

    返回的json格式示例

    +

    image-20230612165021780

    +

    1.2.3 统一异常处理

    统一异常处理 | The Blog (gitee.io)

    +

    统一异常返回状态码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.atguigu.common.exception;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/12
    * @Description 返回的状态码信息
    */
    public enum BizCodeEnum {
    UNKNOWN_EXCEPTION(10000,"系统未知异常"),
    VALID_EXCEPTION(10001,"参数格式校验");

    private Integer code;
    private String msg;

    BizCodeEnum(Integer code, String msg) {
    this.code = code;
    this.msg = msg;
    }

    public Integer getCode() {
    return code;
    }

    public String getMsg() {
    return msg;
    }
    }
    + +

    创建异常处理类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    package com.atguigu.gulimall.product.exception;

    import com.atguigu.common.exception.BizCodeEnum;
    import com.atguigu.common.utils.R;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/12
    * @Description 异常处理类
    */
    @Slf4j
    @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
    public class GulimallExceptionControllerAdvice {

    /**
    * 统一处理数据校验的异常
    * @param e 数据校验的异常
    */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
    log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
    Map<String,String> map = new HashMap<>();
    //获取检验的错误结果
    BindingResult result = e.getBindingResult();
    for (FieldError fieldError : result.getFieldErrors()) {
    //获取到错误的提示
    String defaultMessage = fieldError.getDefaultMessage();
    //获取错误的属性的名字
    String field = fieldError.getField();
    map.put(field,defaultMessage);
    }
    return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",map);
    }

    /**
    * 捕获任意的异常
    */
    @ExceptionHandler(value = Throwable.class)
    public R handleException(){
    return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(), BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
    }
    }
    + +

    1.2.4 关联分类功能

    功能截图

    +

    image-20230618232437618

    +

    后端代码的实现

    +

    查询品牌和分类的关联关系

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 查询商品的关联分类数据
    * @param brandId 品牌的id
    * @return 关联的分类数据
    */
    @GetMapping("/catelog/list")
    public R catelogList(@RequestParam("brandId") Long brandId){
    LambdaQueryWrapper<CategoryBrandRelationEntity> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(CategoryBrandRelationEntity::getBrandId,brandId);
    List<CategoryBrandRelationEntity> categoryBrandRelationEntityList = categoryBrandRelationService.list(queryWrapper);
    return R.ok().put("data", categoryBrandRelationEntityList);
    }
    + +

    新增品牌和商品的关联关系

    +

    controller

    +
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 保存商品的关联的分类数据信息
    */
    @PostMapping("/save")
    public R saveCategoryBrandRelation(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
    categoryBrandRelationService.saveDetail(categoryBrandRelation);
    return R.ok();
    }
    + +

    service

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * 保存商品和分类的关联数据
    *
    * @param categoryBrandRelation 商品和分类的关联数据表
    */
    @Override
    public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
    //获取品牌的id和关联分类的id
    Long brandId = categoryBrandRelation.getBrandId();
    Long catelogId = categoryBrandRelation.getCatelogId();
    //根据id查询对应的品牌名和分类名
    BrandEntity brand = brandDao.selectById(brandId);
    CategoryEntity category = categoryDao.selectById(catelogId);
    //设置值并保存
    categoryBrandRelation.setBrandName(brand.getName());
    categoryBrandRelation.setCatelogName(category.getName());
    this.save(categoryBrandRelation);
    }
    + +

    注意:这里品牌和关联分类之间的关系表中使用了冗余的字段,所以在修改品牌的时候,关系表中的字段也要修改一下

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Override
    public void updateDetail(BrandEntity brand) {
    //保证冗余字段的数据的一致性
    //更新品牌表中的数据
    this.updateById(brand);
    if (!StringUtils.isEmpty(brand.getName())) {
    //同步更新关联表中的数据
    categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
    //TODO 更新其他的关联
    }
    }
    + +

    关联的分类也是冗余的字段 也要同步修改

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 级联更新所有的关联的数据
    * @param category 商品分类的实体
    */
    @Override
    public void updateCascade(CategoryEntity category) {
    //更新分类表中的数据
    this.updateById(category);
    //更新分类与品牌名关联表中分类的名称
    categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }
    + +

    功能实现之后的截图

    +

    image-20230619111515367

    +

    1.3 平台属性

    创建属性管理的菜单(通过sql导入所有的菜单,导入所有以后需要使用的菜单)

    +

    资料中的sys_menus.sql文件保存的是所有的菜单信息

    +

    image-20230616152846339

    +

    谷粒商城的接口的在线文档:https://easydoc.net/s/78237135

    +

    image-20230616153304953

    +

    Object的划分

    +
      +
    1. PO 持久对象

      +

      PO就是对应数据库中某个表的一条记录,多个记录可以用PO的集合。PO中应该不包含对数据库的操作。

      +
    2. +
    3. DO 领域对象

      +

      就是从现实世界中抽象出来的有形或者无形的业务实体。

      +
    4. +
    5. TO 数据传输对象

      +

      不同的应用程序之间传输的对象

      +
    6. +
    7. DTO 数据传输对象

      +

      泛指展示层与服务层之间的数据传输对象

      +
    8. +
    9. VO 值对象

      +

      视图对象 接收页面传入过来的数据,封装对象;将业务处理完成的对象,封装成页面需要的数据

      +
    10. +
    11. BO 业务对象

      +
    12. +
    13. POJO 简单无规则的java对象

      +
    14. +
    15. DAO 数据访问对象

      +
    16. +
    +

    1.3.1 属性分组功能

    实现一个分类 联动的显示相应的分组信息

    +

    抽取商品分类的组件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    <template>
    <el-tree
    :data="menus"
    :props="defaultProps"
    node-key="catId"
    ref="menuTree"
    ></el-tree>
    </template>
    <script>
    export default {
    components: {},
    data() {
    return {
    menus: [],
    expandedKey: [],
    defaultProps: {
    children: "children",
    label: "name",
    },
    };
    },
    created() {
    this.getMenus();
    },
    methods: {
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    console.log("商品分类数据:", data.data);
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    属性分组页面

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    <template>
    <el-row :gutter="20">
    <el-col :span="6"> <category></category></el-col>
    <el-col :span="18">
    <div class="mod-config">
    <el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
    <el-form-item>
    <el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
    </el-form-item>
    <el-form-item>
    <el-button @click="getDataList()">查询</el-button>
    <el-button v-if="isAuth('product:attrgroup:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
    <el-button v-if="isAuth('product:attrgroup:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
    </el-form-item>
    </el-form>
    <el-table
    :data="dataList"
    border
    v-loading="dataListLoading"
    @selection-change="selectionChangeHandle"
    style="width: 100%;">
    <el-table-column
    type="selection"
    header-align="center"
    align="center"
    width="50">
    </el-table-column>
    <el-table-column
    prop="attrGroupId"
    header-align="center"
    align="center"
    label="分组id">
    </el-table-column>
    <el-table-column
    prop="attrGroupName"
    header-align="center"
    align="center"
    label="组名">
    </el-table-column>
    <el-table-column
    prop="sort"
    header-align="center"
    align="center"
    label="排序">
    </el-table-column>
    <el-table-column
    prop="descript"
    header-align="center"
    align="center"
    label="描述">
    </el-table-column>
    <el-table-column
    prop="icon"
    header-align="center"
    align="center"
    label="组图标">
    </el-table-column>
    <el-table-column
    prop="catelogId"
    header-align="center"
    align="center"
    label="所属分类id">
    </el-table-column>
    <el-table-column
    fixed="right"
    header-align="center"
    align="center"
    width="150"
    label="操作">
    <template slot-scope="scope">
    <el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.attrGroupId)">修改</el-button>
    <el-button type="text" size="small" @click="deleteHandle(scope.row.attrGroupId)">删除</el-button>
    </template>
    </el-table-column>
    </el-table>
    <el-pagination
    @size-change="sizeChangeHandle"
    @current-change="currentChangeHandle"
    :current-page="pageIndex"
    :page-sizes="[10, 20, 50, 100]"
    :page-size="pageSize"
    :total="totalPage"
    layout="total, sizes, prev, pager, next, jumper">
    </el-pagination>
    <!-- 弹窗, 新增 / 修改 -->
    <add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
    </div></el-col>
    </el-row>
    </template>
    <script>
    import Category from "../common/category.vue";
    import AddOrUpdate from './attrgroup-add-or-update'
    export default {
    data () {
    return {
    dataForm: {
    key: ''
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false
    }
    },
    components: {
    AddOrUpdate,Category
    },
    activated () {
    this.getDataList()
    },
    methods: {
    // 获取数据列表
    getDataList () {
    this.dataListLoading = true
    this.$http({
    url: this.$http.adornUrl('/product/attrgroup/list'),
    method: 'get',
    params: this.$http.adornParams({
    'page': this.pageIndex,
    'limit': this.pageSize,
    'key': this.dataForm.key
    })
    }).then(({data}) => {
    if (data && data.code === 0) {
    this.dataList = data.page.list
    this.totalPage = data.page.totalCount
    } else {
    this.dataList = []
    this.totalPage = 0
    }
    this.dataListLoading = false
    })
    },
    // 每页数
    sizeChangeHandle (val) {
    this.pageSize = val
    this.pageIndex = 1
    this.getDataList()
    },
    // 当前页
    currentChangeHandle (val) {
    this.pageIndex = val
    this.getDataList()
    },
    // 多选
    selectionChangeHandle (val) {
    this.dataListSelections = val
    },
    // 新增 / 修改
    addOrUpdateHandle (id) {
    this.addOrUpdateVisible = true
    this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id)
    })
    },
    // 删除
    deleteHandle (id) {
    var ids = id ? [id] : this.dataListSelections.map(item => {
    return item.attrGroupId
    })
    this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
    }).then(() => {
    this.$http({
    url: this.$http.adornUrl('/product/attrgroup/delete'),
    method: 'post',
    data: this.$http.adornData(ids, false)
    }).then(({data}) => {
    if (data && data.code === 0) {
    this.$message({
    message: '操作成功',
    type: 'success',
    duration: 1500,
    onClose: () => {
    this.getDataList()
    }
    })
    } else {
    this.$message.error(data.msg)
    }
    })
    })
    }
    }
    }
    </script>
    <style>
    </style>
    + +

    页面效果

    +

    image-20230617173759895

    +

    父子组件中数据的传递

    +

    子组件给父组件传递的数据,事件传递,子组件给父组件发送一个事件,事件传递

    +

    子组件调整

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    <template>
    <el-tree
    :data="menus"
    :props="defaultProps"
    node-key="catId"
    ref="menuTree"
    @node-click="nodeclick"
    ></el-tree>
    </template>
    <script>
    export default {
    components: {},
    data() {
    return {
    menus: [],
    expandedKey: [],
    defaultProps: {
    children: "children",
    label: "name",
    },
    };
    },
    created() {
    this.getMenus();
    },
    methods: {
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    console.log("商品分类数据:", data.data);
    this.menus = data.data;
    });
    },
    nodeclick(data,node,component) {
    console.log("子组件被点击,传递的数据有:data:",data,"node:",node,"component:",component);
    //子组件向父组件传递数据
    this.$emit("tree-node-click",data,node,component);
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    父组件接受相应的数据

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    <template>
    <el-row :gutter="20">
    <el-col :span="6">
    <category @tree-node-click="treeNodeClick"></category
    ></el-col>
    <el-col :span="18">
    <div class="mod-config">
    <el-form
    :inline="true"
    :model="dataForm"
    @keyup.enter.native="getDataList()"
    >
    <el-form-item>
    <el-input
    v-model="dataForm.key"
    placeholder="参数名"
    clearable
    ></el-input>
    </el-form-item>
    <el-form-item>
    <el-button @click="getDataList()">查询</el-button>
    <el-button
    v-if="isAuth('product:attrgroup:save')"
    type="primary"
    @click="addOrUpdateHandle()"
    >新增</el-button
    >
    <el-button
    v-if="isAuth('product:attrgroup:delete')"
    type="danger"
    @click="deleteHandle()"
    :disabled="dataListSelections.length <= 0"
    >批量删除</el-button
    >
    </el-form-item>
    </el-form>
    <el-table
    :data="dataList"
    border
    v-loading="dataListLoading"
    @selection-change="selectionChangeHandle"
    style="width: 100%"
    >
    <el-table-column
    type="selection"
    header-align="center"
    align="center"
    width="50"
    >
    </el-table-column>
    <el-table-column
    prop="attrGroupId"
    header-align="center"
    align="center"
    label="分组id"
    >
    </el-table-column>
    <el-table-column
    prop="attrGroupName"
    header-align="center"
    align="center"
    label="组名"
    >
    </el-table-column>
    <el-table-column
    prop="sort"
    header-align="center"
    align="center"
    label="排序"
    >
    </el-table-column>
    <el-table-column
    prop="descript"
    header-align="center"
    align="center"
    label="描述"
    >
    </el-table-column>
    <el-table-column
    prop="icon"
    header-align="center"
    align="center"
    label="组图标"
    >
    </el-table-column>
    <el-table-column
    prop="catelogId"
    header-align="center"
    align="center"
    label="所属分类id"
    >
    </el-table-column>
    <el-table-column
    fixed="right"
    header-align="center"
    align="center"
    width="150"
    label="操作"
    >
    <template slot-scope="scope">
    <el-button
    type="text"
    size="small"
    @click="addOrUpdateHandle(scope.row.attrGroupId)"
    >修改</el-button
    >
    <el-button
    type="text"
    size="small"
    @click="deleteHandle(scope.row.attrGroupId)"
    >删除</el-button
    >
    </template>
    </el-table-column>
    </el-table>
    <el-pagination
    @size-change="sizeChangeHandle"
    @current-change="currentChangeHandle"
    :current-page="pageIndex"
    :page-sizes="[10, 20, 50, 100]"
    :page-size="pageSize"
    :total="totalPage"
    layout="total, sizes, prev, pager, next, jumper"
    >
    </el-pagination>
    <!-- 弹窗, 新增 / 修改 -->
    <add-or-update
    v-if="addOrUpdateVisible"
    ref="addOrUpdate"
    @refreshDataList="getDataList"
    ></add-or-update></div
    ></el-col>
    </el-row>
    </template>
    <script>
    import Category from "../common/category.vue";
    import AddOrUpdate from "./attrgroup-add-or-update";
    export default {
    data() {
    return {
    catId: 0,
    dataForm: {
    key: "",
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false,
    };
    },
    components: {
    AddOrUpdate,
    Category,
    },
    activated() {
    this.getDataList();
    },
    methods: {
    treeNodeClick(data, node, component) {
    //获取子组件传递过来的值
    console.log(
    "获取子组件传递过来的值:data:",
    data,
    "node",
    node,
    "component:",
    component
    );
    //查询三级分类信息 当点击三级分类id的时候 才查询相关的信息
    if(node.level == 3){
    this.catId = data.catId;
    this.getDataList();
    }
    },
    // 获取数据列表
    getDataList() {
    this.dataListLoading = true;
    this.$http({
    url: this.$http.adornUrl(`/product/attrgroup/list/${this.catId}`),
    method: "get",
    params: this.$http.adornParams({
    page: this.pageIndex,
    limit: this.pageSize,
    key: this.dataForm.key,
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataList = data.page.list;
    this.totalPage = data.page.totalCount;
    } else {
    this.dataList = [];
    this.totalPage = 0;
    }
    this.dataListLoading = false;
    });
    },
    // 每页数
    sizeChangeHandle(val) {
    this.pageSize = val;
    this.pageIndex = 1;
    this.getDataList();
    },
    // 当前页
    currentChangeHandle(val) {
    this.pageIndex = val;
    this.getDataList();
    },
    // 多选
    selectionChangeHandle(val) {
    this.dataListSelections = val;
    },
    // 新增 / 修改
    addOrUpdateHandle(id) {
    this.addOrUpdateVisible = true;
    this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id);
    });
    },
    // 删除
    deleteHandle(id) {
    var ids = id
    ? [id]
    : this.dataListSelections.map((item) => {
    return item.attrGroupId;
    });
    this.$confirm(
    `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
    "提示",
    {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    }
    ).then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/attrgroup/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.getDataList();
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    后端代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @Override
    public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
    //首先先判断catelogId是不是0 如果是0的话就查询所有的属性分组信息
    if (catelogId == 0) {
    IPage<AttrGroupEntity> page = this.page(
    new Query<AttrGroupEntity>().getPage(params),
    new QueryWrapper<AttrGroupEntity>()
    );
    return new PageUtils(page);
    } else {
    //查询的时候传递过来的关键词
    String key = (String) params.get("key");
    //SQL语句:select * from pms_attr_group where catelog_id = ? and (attr_group_id = key or attr_group_name = %key%)
    LambdaQueryWrapper<AttrGroupEntity> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(AttrGroupEntity::getCatelogId, catelogId);
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.and((obj) -> {
    obj.eq(AttrGroupEntity::getAttrGroupId, key).or().like(AttrGroupEntity::getAttrGroupName, key);
    });
    }
    IPage<AttrGroupEntity> page = this.page(
    new Query<AttrGroupEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    }
    + +

    属性分组的添加功能

    +

    前端代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    <template>
    <el-dialog
    :title="!dataForm.attrGroupId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible"
    @closed="dialogClose"
    >
    <el-form
    :model="dataForm"
    :rules="dataRule"
    ref="dataForm"
    @keyup.enter.native="dataFormSubmit()"
    label-width="100px"
    >
    <el-form-item label="组名" prop="attrGroupName">
    <el-input
    v-model="dataForm.attrGroupName"
    placeholder="组名"
    ></el-input>
    </el-form-item>
    <el-form-item label="排序" prop="sort">
    <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
    </el-form-item>
    <el-form-item label="描述" prop="descript">
    <el-input v-model="dataForm.descript" placeholder="描述"></el-input>
    </el-form-item>
    <el-form-item label="组图标" prop="icon">
    <el-input v-model="dataForm.icon" placeholder="组图标"></el-input>
    </el-form-item>
    <el-form-item label="所属分类id" prop="catelogId">
    <!-- <el-input v-model="dataForm.catelogId" placeholder="所属分类id"></el-input> -->
    <el-cascader
    v-model="dataForm.catelogPath"
    :options="categorys"
    :props="props"
    placeholder="试试搜索:手机"
    filterable
    ></el-cascader>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="visible = false">取消</el-button>
    <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
    </el-dialog>
    </template>

    <script>
    import Category from "../common/category.vue";

    export default {
    data() {
    return {
    props: {
    value: "catId",
    label: "name",
    children: "children",
    },
    categorys: [], //所有的三级分类信息
    visible: false,
    dataForm: {
    attrGroupId: 0,
    attrGroupName: "",
    sort: "",
    descript: "",
    icon: "",
    catelogPath: [],
    catelogId: 0,
    },
    dataRule: {
    attrGroupName: [
    { required: true, message: "组名不能为空", trigger: "blur" },
    ],
    sort: [{ required: true, message: "排序不能为空", trigger: "blur" }],
    descript: [
    { required: true, message: "描述不能为空", trigger: "blur" },
    ],
    icon: [{ required: true, message: "组图标不能为空", trigger: "blur" }],
    catelogId: [
    { required: true, message: "所属分类id不能为空", trigger: "blur" },
    ],
    },
    };
    },
    created() {
    this.getCategorys();
    },
    methods: {
    dialogClose() {
    this.dataForm.catelogPath = [];
    },
    getCategorys() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.categorys = data.data;
    });
    },
    init(id) {
    this.dataForm.attrGroupId = id || 0;
    this.visible = true;
    this.$nextTick(() => {
    this.$refs["dataForm"].resetFields();
    if (this.dataForm.attrGroupId) {
    this.$http({
    url: this.$http.adornUrl(
    `/product/attrgroup/info/${this.dataForm.attrGroupId}`
    ),
    method: "get",
    params: this.$http.adornParams(),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataForm.attrGroupName = data.attrGroup.attrGroupName;
    this.dataForm.sort = data.attrGroup.sort;
    this.dataForm.descript = data.attrGroup.descript;
    this.dataForm.icon = data.attrGroup.icon;
    this.dataForm.catelogId = data.attrGroup.catelogId;
    //查出catelog的完整路径
    this.dataForm.catelogPath = data.attrGroup.catelogPath;
    }
    });
    }
    });
    },
    // 表单提交
    dataFormSubmit() {
    this.$refs["dataForm"].validate((valid) => {
    if (valid) {
    this.$http({
    url: this.$http.adornUrl(
    `/product/attrgroup/${
    !this.dataForm.attrGroupId ? "save" : "update"
    }`
    ),
    method: "post",
    data: this.$http.adornData({
    attrGroupId: this.dataForm.attrGroupId || undefined,
    attrGroupName: this.dataForm.attrGroupName,
    sort: this.dataForm.sort,
    descript: this.dataForm.descript,
    icon: this.dataForm.icon,
    catelogId:
    this.dataForm.catelogPath[this.dataForm.catelogPath.length - 1],
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.visible = false;
    this.$emit("refreshDataList");
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    }
    });
    },
    },
    };
    </script>
    + +

    后端代码

    +

    在商品分类的children上加上@JsonInclude注解,解决后面的级联选择多出一个空白的选择的问题

    +
    1
    2
    3
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @TableField(exist = false)//这个注解要加,因为这个属性不是数据库中的字段
    private List<CategoryEntity> children;//这里的属性名不是瞎起的,与ElementUI中的显示相对应,这里改了,前端也要改
    + +

    修改的时候,级联选择部分数据的回显

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 找到catelog的完整路径
    * @param catelogId 路径的最后一个catelog的id
    * @return 完整的路径
    */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
    List<Long> paths = new ArrayList<>();
    List<Long> parentPath = findParentPath(catelogId, paths);
    Collections.reverse(parentPath);
    return (Long[]) parentPath.toArray(new Long[parentPath.size()]);
    }
    /**
    * 递归找出所有的路径
    */
    private List<Long> findParentPath(Long catelogId,List<Long> paths){
    paths.add(catelogId);
    //根据id查询相关的信息
    CategoryEntity category = this.getById(catelogId);
    if(category.getParentCid()!=0){
    findParentPath(category.getParentCid(),paths);
    }
    return paths;
    }
    + +

    属性关联商品的规格参数和基本属性信息

    +

    功能描述

    +

    image-20230625090118059

    +

    查询商品下关联的属性分组信息

    +

    查询商品关联属性分组的后端关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * 根据分组的id查询关联的所有属性
    *
    * @param attrgroupId 分组的id
    * @return 关联的所有属性组成集合
    */
    @Override
    public List<AttrEntity> getRelationAttr(Long attrgroupId) {
    //根据中间表查询出属性分组的id信息
    List<AttrAttrgroupRelationEntity> relationEntityList = relationDao.selectList(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrGroupId, attrgroupId));
    List<Long> attrIds = new ArrayList<>();
    for (AttrAttrgroupRelationEntity relationEntity : relationEntityList) {
    attrIds.add(relationEntity.getAttrId());
    }
    //根据属性分组的id查询属性分组的详细信息
    return (List<AttrEntity>) this.listByIds(attrIds);
    }
    + +

    移除关联属性分组的功能后端的关键代码

    +

    service层的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /**
    * 批量删除商品关联的属性分组信息
    *
    * @param vos 属性id和属性分组id组成的集合
    */
    @Override
    public void deleteRelation(AttrGroupRelationVo[] vos) {
    List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {
    AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
    BeanUtils.copyProperties(item, relationEntity);
    return relationEntity;
    }).collect(Collectors.toList());
    relationDao.deleteBatchRelation(entities);
    }
    + +

    dao层的代码

    +

    使用动态的sql批量的删除属性和分组的关联关系

    +
    1
    2
    3
    4
    5
    6
    7
    <!--void deleteBatchRelation(List<AttrAttrgroupRelationEntity> entities);-->
    <delete id="deleteBatchRelation">
    delete from `pms_attr_attrgroup_relation` where
    <foreach collection="entities" item="item" separator=" OR ">
    (attr_id = #{item.attrId}) and attr_group_id = #{item.attrGroupId}
    </foreach>
    </delete>
    + +

    查询属性分组没有关联的属性

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * 获取当前分组没有关联的所有属性
    *
    * @param params 分页相关的参数
    * @param attrgroupId 属性分组的id
    * @return 分页相关的数据
    */
    @Override
    public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
    //当前的分组只能关联自己所属的分类里面的所有属性 例如手机的分类里面只能关联手机的所有属性
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
    Long catelogId = attrGroupEntity.getCatelogId();
    //当前分组只能关联别的分组没有引用的属性
    List<AttrGroupEntity> groups = attrGroupDao.selectList(
    new LambdaQueryWrapper<AttrGroupEntity>().eq(AttrGroupEntity::getCatelogId, catelogId));
    List<Long> collect = groups.stream().map((item) -> {
    return item.getAttrGroupId();
    }).collect(Collectors.toList());
    List<AttrAttrgroupRelationEntity> groupId = relationDao.selectList(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().in(AttrAttrgroupRelationEntity::getAttrGroupId, collect));
    List<Long> attrIds = groupId.stream().map((item) -> {
    return item.getAttrId();
    }).collect(Collectors.toList());
    LambdaQueryWrapper<AttrEntity> queryWrapper =
    new LambdaQueryWrapper<AttrEntity>().eq(AttrEntity::getCatelogId, catelogId).eq(AttrEntity::getAttrType,ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
    if (attrIds != null && attrIds.size() > 0) {
    queryWrapper.notIn(AttrEntity::getAttrId, attrIds);
    }
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.eq(AttrEntity::getAttrId, key).or().like(AttrEntity::getAttrName, key);
    }
    IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), queryWrapper);
    return new PageUtils(page);
    }
    + +

    给属性关联相关的规格参数和销售属性

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Override
    public void saveBatch(List<AttrGroupRelationVo> vos) {
    List<AttrAttrgroupRelationEntity> entities = vos.stream().map((item) -> {
    AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
    BeanUtils.copyProperties(item, relationEntity);
    return relationEntity;
    }).collect(Collectors.toList());
    this.saveBatch(entities);
    }
    + +

    1.3.2 规格参数功能

    功能截图

    +

    image-20230619111338153

    +

    新增功能的后端关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 保存规格参数信息
    * @param attr 页面中提交的规格参数相关的信息组成的实体
    */
    @Transactional
    @Override
    public void saveAttr(AttrVo attr) {
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attr,attrEntity);
    //保存基本的数据
    this.save(attrEntity);
    //保存关联关系
    AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
    relationEntity.setAttrGroupId(attr.getAttrGroupId());
    relationEntity.setAttrId(attrEntity.getAttrId());
    relationDao.insert(relationEntity);
    }
    + +

    查询列表后端的关键代码

    +

    这里使用的stream流处理数据,没有使用多表联合查询来查询数据

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    @Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
    LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<>();
    if (catelogId != 0) {//等于0就是查询所有的分类信息下的规格参数信息
    queryWrapper.eq(AttrEntity::getCatelogId, catelogId);
    }
    //获取检索的条件
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.like(AttrEntity::getAttrName, key).or().eq(AttrEntity::getAttrId, key);
    }
    IPage<AttrEntity> page = this.page(
    new Query<AttrEntity>().getPage(params),
    queryWrapper
    );
    PageUtils pageUtils = new PageUtils(page);
    List<AttrEntity> records = page.getRecords();
    List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
    AttrRespVo attrRespVo = new AttrRespVo();
    BeanUtils.copyProperties(attrEntity, attrRespVo);
    //设置三级商品分类名和分组名
    //设置所属的商品的分类名
    CategoryEntity categoryEntity = categoryDao.selectById(attrRespVo.getCatelogId());
    if (categoryEntity != null) {
    attrRespVo.setCatelogName(categoryEntity.getName());
    }
    //设置分组名(需要从中间表查询分组的id,再查询分组名)
    //先从中间表查询到分组的id
    AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
    //从分组的表中查询分组名
    if (relationEntity != null) {
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
    }
    return attrRespVo;
    }).collect(Collectors.toList());
    pageUtils.setList(respVos);
    return pageUtils;
    }
    + +

    规格参数修改功能后端的关键代码

    +

    修改前的数据回显

    +

    查询属性的详情信息

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    /**
    * 查询属性的详细信息
    *
    * @param attrId 属性的id
    * @return 属性的详细信息封装的集合
    */
    @Override
    public AttrRespVo getAttrInfo(Long attrId) {
    AttrRespVo attrRespVo = new AttrRespVo();
    //查询属性的基本信息
    AttrEntity attr = this.getById(attrId);
    BeanUtils.copyProperties(attr, attrRespVo);
    //查询分组id和分类的详细路径
    //分组信息的设置
    AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
    if (relationEntity != null) {
    attrRespVo.setAttrGroupId(relationEntity.getAttrGroupId());
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
    if (attrGroupEntity != null) {
    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
    }
    }
    //设置分类信息
    Long catelogId = attr.getCatelogId();
    //查询分类的完整路径
    Long[] catelogPath = categoryService.findCatelogPath(catelogId);
    if (catelogPath != null) {
    attrRespVo.setCatelogPath(catelogPath);
    }
    //设置分类名
    CategoryEntity category = categoryDao.selectById(catelogId);
    if (category != null) {
    attrRespVo.setCatelogName(category.getName());
    }
    return attrRespVo;
    }
    + +

    修改的时候页面回显的效果

    +

    image-20230624192906124

    +

    修改功能后端的关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 修改属性的方法
    *
    * @param attr 页面传输过来的商品属性的实体
    */
    @Transactional
    @Override
    public void updateAttr(AttrVo attr) {
    //修改基本的数据
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attr, attrEntity);
    this.updateById(attrEntity);
    //修改关联表的数据
    AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
    BeanUtils.copyProperties(attr, relationEntity);
    LambdaQueryWrapper<AttrAttrgroupRelationEntity> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId, relationEntity.getAttrId());
    Integer count = relationDao.selectCount(queryWrapper);
    if (count > 0) {//判断是修改还是添加的操作
    relationDao.update(relationEntity, queryWrapper);
    } else {
    relationDao.insert(relationEntity);
    }
    }
    + +

    1.3.3 销售属性功能

    分类属性列表显示

    +

    controller层的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * /product/attr/sale/list/{catelogId}
    * 路径 /product/attr/base/list/{catelogId}
    * 规格参数的列表显示
    *
    * @param params 条件查询的参数
    * @param catelogId 商品分类的id
    * @return 规格参数的分页数据
    */
    @GetMapping("/{attrType}/list/{catelogId}")
    public R baseAttrList(@RequestParam Map<String, Object> params, @PathVariable Long catelogId, @PathVariable("attrType") String type) {
    log.warn("规格参数分页的查询条件:{},商品分类的id:{}", params, catelogId);
    PageUtils page = attrService.queryBaseAttrPage(params, catelogId,type);
    return R.ok().put("page", page);
    }
    + +

    service层的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    @Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String type) {
    LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<>();
    //路径中属性的attrType是base就是基本属性,否则就代表的是销售属性
    queryWrapper.eq(AttrEntity::getAttrType, "base".equalsIgnoreCase(type) ? 1 : 0);
    if (catelogId != 0) {//等于0就是查询所有的分类信息下的规格参数信息
    queryWrapper.eq(AttrEntity::getCatelogId, catelogId);
    }
    //获取检索的条件
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.like(AttrEntity::getAttrName, key).or().eq(AttrEntity::getAttrId, key);
    }
    IPage<AttrEntity> page = this.page(
    new Query<AttrEntity>().getPage(params),
    queryWrapper
    );
    PageUtils pageUtils = new PageUtils(page);
    List<AttrEntity> records = page.getRecords();
    List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
    AttrRespVo attrRespVo = new AttrRespVo();
    BeanUtils.copyProperties(attrEntity, attrRespVo);
    //设置三级商品分类名和分组名
    //设置所属的商品的分类名
    CategoryEntity categoryEntity = categoryDao.selectById(attrRespVo.getCatelogId());
    if (categoryEntity != null) {
    attrRespVo.setCatelogName(categoryEntity.getName());
    }
    //设置分组名(需要从中间表查询分组的id,再查询分组名)
    //先从中间表查询到分组的id
    AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
    //从分组的表中查询分组名
    if (relationEntity != null) {
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
    if (attrGroupEntity != null) {
    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
    }
    }
    return attrRespVo;
    }).collect(Collectors.toList());
    pageUtils.setList(respVos);
    return pageUtils;
    }
    + +

    完整的功能展示

    +

    image-20230625090037616

    +

    1.4 商品维护

    1.4.1 SPU管理

    条件查询功能(Spu检索)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    /**
    * Spu检索功能
    *
    * @param params 分页相关的数据
    * @return 分页对象
    */
    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
    LambdaQueryWrapper<SpuInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
    //关键字检索
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    //这里使用了or,为了和下面的区分开来,要使用and,将下面这个条件用括号括起来
    //SELECT COUNT(1) FROM pms_spu_info WHERE (((spu_name LIKE ? OR id = ?)) AND catalog_id = ? AND brand_id = ? AND publish_status = ?)
    queryWrapper.and((wrapper) -> {
    wrapper.like(SpuInfoEntity::getSpuName, key).or().eq(SpuInfoEntity::getId, key);
    });
    }
    //分类
    String catelogId = (String) params.get("catelogId");
    if (!StringUtils.isEmpty(catelogId)) {
    queryWrapper.eq(SpuInfoEntity::getCatalogId, catelogId);
    }
    //品牌
    String brandId = (String) params.get("brandId");
    if (!StringUtils.isEmpty(brandId)) {
    queryWrapper.eq(SpuInfoEntity::getBrandId, brandId);
    }
    //状态
    String status = (String) params.get("status");
    if (!StringUtils.isEmpty(status)) {
    queryWrapper.eq(SpuInfoEntity::getPublishStatus, status);
    }

    IPage<SpuInfoEntity> page = this.page(
    new Query<SpuInfoEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的功能

    +

    image-20230627102918192

    +

    前端显示时间格式的数据有问题

    +

    配置文件中的配置

    +
    1
    2
    3
    jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    + +

    商品规格的回显

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * 查询商品的规格属性
    * /product/attr/base/listforspu/{spuId}
    */
    @GetMapping("/base/listforspu/{spuId}")
    public R baseAttrList(@PathVariable("spuId") Long spuId){
    List<ProductAttrValueEntity> entities = productAttrValueService.baseAttrListForSpu(spuId);
    return R.ok().put("data",entities);
    }
    + +

    修改商品的规格

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /**
    * 修改商品的规格参数
    * @param spuId 商品的id
    * @param productAttrValueEntities 提交的修改之后的商品规格参数组成的集合
    */
    @Transactional
    @Override
    public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> productAttrValueEntities) {
    //1.删除这个spuId之前对应的所有属性
    this.baseMapper.delete(new LambdaQueryWrapper<ProductAttrValueEntity>().eq(ProductAttrValueEntity::getSpuId,spuId));
    //2.添加修改之后的商品的属性信息
    List<ProductAttrValueEntity> collect = productAttrValueEntities.stream().map(item -> {
    item.setSpuId(spuId);
    return item;
    }).collect(Collectors.toList());
    this.saveBatch(collect);
    }
    + +

    实现的功能

    +

    image-20230628165931139

    +

    中间遇到的问题,点击规格,页面显示400的问题

    +

    image-20230628171558364

    +

    解决方案

    +

    第一步:在gulimall-admin数据库中执行以下的sql语句

    +
    1
    INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num) VALUES (76, 37, '规格维护', 'product/attrupdate', '', 2, 'log', 0);
    + +

    第二步:将前端src/router/index.js替换成如下的内容

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    /**
    * 全站路由配置
    *
    * 建议:
    * 1. 代码中路由统一使用name属性跳转(不使用path属性)
    */
    import Vue from 'vue'
    import Router from 'vue-router'
    import http from '@/utils/httpRequest'
    import { isURL } from '@/utils/validate'
    import { clearLoginInfo } from '@/utils'

    Vue.use(Router)

    // 开发环境不使用懒加载, 因为懒加载页面太多的话会造成webpack热更新太慢, 所以只有生产环境使用懒加载
    const _import = require('./import-' + process.env.NODE_ENV)

    // 全局路由(无需嵌套上左右整体布局)
    const globalRoutes = [
    { path: '/404', component: _import('common/404'), name: '404', meta: { title: '404未找到' } },
    { path: '/login', component: _import('common/login'), name: 'login', meta: { title: '登录' } }
    ]

    // 主入口路由(需嵌套上左右整体布局)
    const mainRoutes = {
    path: '/',
    component: _import('main'),
    name: 'main',
    redirect: { name: 'home' },
    meta: { title: '主入口整体布局' },
    children: [
    // 通过meta对象设置路由展示方式
    // 1. isTab: 是否通过tab展示内容, true: 是, false: 否
    // 2. iframeUrl: 是否通过iframe嵌套展示内容, '以http[s]://开头': 是, '': 否
    // 提示: 如需要通过iframe嵌套展示内容, 但不通过tab打开, 请自行创建组件使用iframe处理!
    { path: '/home', component: _import('common/home'), name: 'home', meta: { title: '首页' } },
    { path: '/theme', component: _import('common/theme'), name: 'theme', meta: { title: '主题' } },
    { path: '/demo-echarts', component: _import('demo/echarts'), name: 'demo-echarts', meta: { title: 'demo-echarts', isTab: true } },
    { path: '/demo-ueditor', component: _import('demo/ueditor'), name: 'demo-ueditor', meta: { title: 'demo-ueditor', isTab: true } },
    { path: '/product-attrupdate', component: _import('modules/product/attrupdate'), name: 'attr-update', meta: { title: '规格维护', isTab: true } }
    ],
    beforeEnter(to, from, next) {
    let token = Vue.cookie.get('token')
    if (!token || !/\S/.test(token)) {
    clearLoginInfo()
    next({ name: 'login' })
    }
    next()
    }
    }

    const router = new Router({
    mode: 'hash',
    scrollBehavior: () => ({ y: 0 }),
    isAddDynamicMenuRoutes: false, // 是否已经添加动态(菜单)路由
    routes: globalRoutes.concat(mainRoutes)
    })

    router.beforeEach((to, from, next) => {
    // 添加动态(菜单)路由
    // 1. 已经添加 or 全局路由, 直接访问
    // 2. 获取菜单列表, 添加并保存本地存储
    if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {
    next()
    } else {
    http({
    url: http.adornUrl('/sys/menu/nav'),
    method: 'get',
    params: http.adornParams()
    }).then(({ data }) => {
    if (data && data.code === 0) {
    fnAddDynamicMenuRoutes(data.menuList)
    router.options.isAddDynamicMenuRoutes = true
    sessionStorage.setItem('menuList', JSON.stringify(data.menuList || '[]'))
    sessionStorage.setItem('permissions', JSON.stringify(data.permissions || '[]'))
    next({ ...to, replace: true })
    } else {
    sessionStorage.setItem('menuList', '[]')
    sessionStorage.setItem('permissions', '[]')
    next()
    }
    }).catch((e) => {
    console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue')
    router.push({ name: 'login' })
    })
    }
    })

    /**
    * 判断当前路由类型, global: 全局路由, main: 主入口路由
    * @param {*} route 当前路由
    */
    function fnCurrentRouteType(route, globalRoutes = []) {
    var temp = []
    for (var i = 0; i < globalRoutes.length; i++) {
    if (route.path === globalRoutes[i].path) {
    return 'global'
    } else if (globalRoutes[i].children && globalRoutes[i].children.length >= 1) {
    temp = temp.concat(globalRoutes[i].children)
    }
    }
    return temp.length >= 1 ? fnCurrentRouteType(route, temp) : 'main'
    }

    /**
    * 添加动态(菜单)路由
    * @param {*} menuList 菜单列表
    * @param {*} routes 递归创建的动态(菜单)路由
    */
    function fnAddDynamicMenuRoutes(menuList = [], routes = []) {
    var temp = []
    for (var i = 0; i < menuList.length; i++) {
    if (menuList[i].list && menuList[i].list.length >= 1) {
    temp = temp.concat(menuList[i].list)
    } else if (menuList[i].url && /\S/.test(menuList[i].url)) {
    menuList[i].url = menuList[i].url.replace(/^\//, '')
    var route = {
    path: menuList[i].url.replace('/', '-'),
    component: null,
    name: menuList[i].url.replace('/', '-'),
    meta: {
    menuId: menuList[i].menuId,
    title: menuList[i].name,
    isDynamic: true,
    isTab: true,
    iframeUrl: ''
    }
    }
    // url以http[s]://开头, 通过iframe展示
    if (isURL(menuList[i].url)) {
    route['path'] = `i-${menuList[i].menuId}`
    route['name'] = `i-${menuList[i].menuId}`
    route['meta']['iframeUrl'] = menuList[i].url
    } else {
    try {
    route['component'] = _import(`modules/${menuList[i].url}`) || null
    } catch (e) { }
    }
    routes.push(route)
    }
    }
    if (temp.length >= 1) {
    fnAddDynamicMenuRoutes(temp, routes)
    } else {
    mainRoutes.name = 'main-dynamic'
    mainRoutes.children = routes
    router.addRoutes([
    mainRoutes,
    { path: '*', redirect: { name: '404' } }
    ])
    sessionStorage.setItem('dynamicMenuRoutes', JSON.stringify(mainRoutes.children || '[]'))
    console.log('\n')
    console.log('%c!<-------------------- 动态(菜单)路由 s -------------------->', 'color:blue')
    console.log(mainRoutes.children)
    console.log('%c!<-------------------- 动态(菜单)路由 e -------------------->', 'color:blue')
    }
    }

    export default router
    + +

    1.4.2 发布商品

    根据商品的分类联动的查询商品的品牌

    +

    image-20230626101113941

    +

    后端关键部分的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 查询商品分类关联的品牌信息
    *
    * @return 某一商品分类下关联的品牌信息组成的集合
    */
    @Override
    public List<BrandEntity> getBrandsByCatId(Long catId) {
    List<CategoryBrandRelationEntity> categoryBrandRelationEntityList = relationDao.selectList(
    new LambdaQueryWrapper<CategoryBrandRelationEntity>().eq(CategoryBrandRelationEntity::getCatelogId, catId));
    List<BrandEntity> brandEntityList = categoryBrandRelationEntityList.stream().map(item -> {
    Long brandId = item.getBrandId();
    return brandService.getById(brandId);
    }).collect(Collectors.toList());
    return brandEntityList;
    }
    + +

    根据JSON数据生成实体类

    +

    生成工具: https://www.bejson.com/

    +

    image-20230626142854091

    +

    前端提交的发布商品的详细信息

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    {
    "spuName": "华为Mate30 Pro",
    "spuDescription": "华为手机",
    "catalogId": 225,
    "brandId": 1,
    "weight": 0.198,
    "publishStatus": 0,
    "decript": ["https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7b9f237d-9d37-4087-83bd-13d0ea76eda4_73366cc235d68202.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5c4fbe5-0685-4a12-8623-a4972c623d6c_528211b97272d88a.jpg"],
    "images": ["https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7627fc94-1ac7-4406-8e1c-3866a002918b_0d40c24b264aa511.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/788f45d7-d399-4482-88db-4dd939a51f4a_2b1837c6c50add30.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/b18dd3f9-3a38-4e9a-86a0-8c24db552a09_3c24f9cd69534030.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/d19426e9-c518-4fc8-85dd-7fae758a4000_23d9fbb256ea5d4a.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7200dec5-af25-4264-87dc-19985c4cbe23_335b2c690e43a8f8.jpg"],
    "bounds": {
    "buyBounds": 500,
    "growBounds": 500
    },
    "baseAttrs": [{
    "attrId": 3,
    "attrValues": "2019",
    "showDesc": 1
    }, {
    "attrId": 4,
    "attrValues": "mate30pro",
    "showDesc": 1
    }, {
    "attrId": 10,
    "attrValues": "45W",
    "showDesc": 1
    }, {
    "attrId": 11,
    "attrValues": "LCD;OLED",
    "showDesc": 1
    }, {
    "attrId": 8,
    "attrValues": "16GB",
    "showDesc": 1
    }],
    "skus": [{
    "attr": [{
    "attrId": 7,
    "attrName": "手机颜色",
    "attrValue": "星河银"
    }, {
    "attrId": 9,
    "attrName": "商品产地",
    "attrValue": "中国"
    }],
    "skuName": "华为Mate30 Pro 星河银 中国",
    "price": "5799",
    "skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
    "skuSubtitle": "星河银的副标题",
    "images": [{
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7627fc94-1ac7-4406-8e1c-3866a002918b_0d40c24b264aa511.jpg",
    "defaultImg": 1
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/788f45d7-d399-4482-88db-4dd939a51f4a_2b1837c6c50add30.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/b18dd3f9-3a38-4e9a-86a0-8c24db552a09_3c24f9cd69534030.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }],
    "descar": ["星河银", "中国"],
    "fullCount": 3,
    "discount": 0.98,
    "countStatus": 0,
    "fullPrice": 10000,
    "reducePrice": 50,
    "priceStatus": 0,
    "memberPrice": [{
    "id": 2,
    "name": "铜牌会员",
    "price": 5788
    }, {
    "id": 3,
    "name": "银牌会员",
    "price": 5699
    }]
    }, {
    "attr": [{
    "attrId": 7,
    "attrName": "手机颜色",
    "attrValue": "亮黑色"
    }, {
    "attrId": 9,
    "attrName": "商品产地",
    "attrValue": "中国"
    }],
    "skuName": "华为Mate30 Pro 亮黑色 中国",
    "price": "6299",
    "skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
    "skuSubtitle": "亮黑色的副标题",
    "images": [{
    "imgUrl": "",
    "defaultImg": 1
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7200dec5-af25-4264-87dc-19985c4cbe23_335b2c690e43a8f8.jpg",
    "defaultImg": 0
    }],
    "descar": ["亮黑色", "中国"],
    "fullCount": 0,
    "discount": 0,
    "countStatus": 0,
    "fullPrice": 0,
    "reducePrice": 0,
    "priceStatus": 0,
    "memberPrice": [{
    "id": 2,
    "name": "铜牌会员",
    "price": 0
    }, {
    "id": 3,
    "name": "银牌会员",
    "price": 0
    }]
    }, {
    "attr": [{
    "attrId": 7,
    "attrName": "手机颜色",
    "attrValue": "翡冷翠"
    }, {
    "attrId": 9,
    "attrName": "商品产地",
    "attrValue": "中国"
    }],
    "skuName": "华为Mate30 Pro 翡冷翠 中国",
    "price": "6299",
    "skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
    "skuSubtitle": "翡冷翠的副标题",
    "images": [{
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/d19426e9-c518-4fc8-85dd-7fae758a4000_23d9fbb256ea5d4a.jpg",
    "defaultImg": 1
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }],
    "descar": ["翡冷翠", "中国"],
    "fullCount": 0,
    "discount": 0,
    "countStatus": 0,
    "fullPrice": 0,
    "reducePrice": 0,
    "priceStatus": 0,
    "memberPrice": [{
    "id": 2,
    "name": "铜牌会员",
    "price": 0
    }, {
    "id": 3,
    "name": "银牌会员",
    "price": 0
    }]
    }, {
    "attr": [{
    "attrId": 7,
    "attrName": "手机颜色",
    "attrValue": "罗兰紫"
    }, {
    "attrId": 9,
    "attrName": "商品产地",
    "attrValue": "中国"
    }],
    "skuName": "华为Mate30 Pro 罗兰紫 中国",
    "price": "5799",
    "skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
    "skuSubtitle": "罗兰紫的副标题",
    "images": [{
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
    "defaultImg": 1
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }],
    "descar": ["罗兰紫", "中国"],
    "fullCount": 0,
    "discount": 0,
    "countStatus": 0,
    "fullPrice": 0,
    "reducePrice": 0,
    "priceStatus": 0,
    "memberPrice": [{
    "id": 2,
    "name": "铜牌会员",
    "price": 0
    }, {
    "id": 3,
    "name": "银牌会员",
    "price": 0
    }]
    }]
    }
    + +

    下载生成的实体类代码

    +

    image-20230626143051042

    +

    生成的实体类(将生成的实体类复制到项目中指定的位置)

    +

    image-20230626143228035

    +

    发布商品信息(核心业务)

    +

    后端关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    /**
    * 发布商品的功能
    *
    * @param vo 需要保存的商品信息封装的数据
    */
    @Transactional
    @Override
    public void saveSpuInfo(SpuSaveVo vo) {
    //保存spu的基本信息 pms_spu_info
    SpuInfoEntity infoEntity = new SpuInfoEntity();
    BeanUtils.copyProperties(vo, infoEntity);
    infoEntity.setCreateTime(new Date());
    infoEntity.setUpdateTime(new Date());
    this.saveBaseSpuInfo(infoEntity);
    //保存spu的描述图片 pms_spu_info_desc
    List<String> decript = vo.getDecript();
    SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
    descEntity.setSpuId(infoEntity.getId());
    descEntity.setDecript(String.join(",", decript));
    spuInfoDescService.saveSpuInfoDecript(descEntity);
    //保存spu的图片集 pms_spu_images
    List<String> images = vo.getImages();
    spuImagesService.saveImages(infoEntity.getId(), images);
    //保存spu的规格参数 pms_product_attr_value
    List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
    List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
    ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
    valueEntity.setAttrId(attr.getAttrId());
    AttrEntity attrEntity = attrService.getById(attr.getAttrId());
    valueEntity.setAttrName(attrEntity.getAttrName());
    valueEntity.setAttrValue(attr.getAttrValues());
    valueEntity.setQuickShow(attr.getShowDesc());
    valueEntity.setSpuId(infoEntity.getId());
    return valueEntity;
    }).collect(Collectors.toList());
    productAttrValueService.saveProductAttr(collect);
    //保存spu的积分信息
    Bounds bounds = vo.getBounds();
    SpuBoundTo spuBoundTo = new SpuBoundTo();
    spuBoundTo.setSpuId(infoEntity.getId());
    BeanUtils.copyProperties(bounds, spuBoundTo);
    R r = couponFeignService.saveSpuBounds(spuBoundTo);
    if (r.getCode() != 0) {
    log.error("远程保存spu的积分信息失败");
    }
    //保存当前spu对应的sku信息
    List<Skus> skus = vo.getSkus();
    if (skus != null && skus.size() != 0) {
    skus.forEach(item -> {
    String defaultImg = "";
    for (Images img : item.getImages()) {
    if (img.getDefaultImg() == 1) {
    defaultImg = img.getImgUrl();
    }
    }
    SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
    BeanUtils.copyProperties(item, skuInfoEntity);
    skuInfoEntity.setBrandId(infoEntity.getBrandId());
    skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
    skuInfoEntity.setSaleCount(0L);
    skuInfoEntity.setSpuId(infoEntity.getId());
    skuInfoEntity.setSkuDefaultImg(defaultImg);
    // 保存sku的基本信息pms_sku_info
    skuInfoService.saveSkuInfo(skuInfoEntity);
    Long skuId = skuInfoEntity.getSkuId();
    List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
    skuImagesEntity.setSkuId(skuId);
    skuImagesEntity.setImgUrl(img.getImgUrl());
    skuImagesEntity.setDefaultImg(img.getDefaultImg());
    return skuImagesEntity;
    }).filter(entity -> {
    //返回true就添加上去
    return !StringUtils.isEmpty(entity.getImgUrl());
    }).collect(Collectors.toList());
    // 保存sku的图片信息pms_sku_images
    skuImagesService.saveBatch(imagesEntities);
    // sku的销售属性信息pms_sku_sale_attr_value
    List<Attr> attrs = item.getAttr();
    List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attrs.stream().map(attr -> {
    SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
    BeanUtils.copyProperties(attr, skuSaleAttrValueEntity);
    skuSaleAttrValueEntity.setSkuId(skuId);
    return skuSaleAttrValueEntity;
    }).collect(Collectors.toList());
    skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
    // sku的优惠信息、满减等信息gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
    SkuReductionTo skuReductionTo = new SkuReductionTo();
    BeanUtils.copyProperties(item, skuReductionTo);
    List<MemberPrice> memberPrices = item.getMemberPrice().stream().map(memberPrice -> {
    MemberPrice memberPriceTo = new MemberPrice();
    BeanUtils.copyProperties(memberPrice, memberPriceTo);
    return memberPriceTo;
    }).collect(Collectors.toList());
    skuReductionTo.setMemberPrice(memberPrices);
    skuReductionTo.setSkuId(skuId);
    if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1) {
    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
    if (r1.getCode() != 0) {
    log.error("远程调用sku的优惠信息、满减信息失败");
    }
    }
    });
    }
    }
    + +

    最终的实现效果

    +

    image-20230626184303186

    +

    image-20230626182832804

    +

    1.4.3 商品管理

    Sku检索

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    /**
    * 检索条件
    * {
    * page: 1,//当前页码
    * limit: 10,//每页记录数
    * sidx: 'id',//排序字段
    * order: 'asc/desc',//排序方式
    * key: '华为',//检索关键字
    * catelogId: 0,
    * brandId: 0,
    * min: 0,
    * max: 0
    * }
    */
    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
    LambdaQueryWrapper<SkuInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
    //Preparing: SELECT sku_id,sku_name,sku_title,catalog_id,sale_count,price,brand_id,sku_default_img,sku_subtitle,sku_desc,spu_id
    // FROM pms_sku_info
    // WHERE (( (sku_id = ? OR sku_name LIKE ?) ) AND catalog_id = ? AND brand_id = ? AND price >= ? AND price <= ?) LIMIT ?,?
    //检索关键字
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.and((wrapper) -> {
    wrapper.eq(SkuInfoEntity::getSkuId, key).or().like(SkuInfoEntity::getSkuName, key);
    });
    }
    //分类
    String catelogId = (String) params.get("catelogId");
    if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) {
    queryWrapper.eq(SkuInfoEntity::getCatalogId, catelogId);
    }
    //品牌
    String brandId = (String) params.get("brandId");
    if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {
    queryWrapper.eq(SkuInfoEntity::getBrandId, brandId);
    }
    //价格区间
    String min = (String) params.get("min");
    if (!StringUtils.isEmpty(min)) {
    queryWrapper.ge(SkuInfoEntity::getPrice, min);
    }
    String max = (String) params.get("max");
    if (!StringUtils.isEmpty(max)) {
    try {
    BigDecimal bigDecimal = new BigDecimal(max);
    if(bigDecimal.compareTo(new BigDecimal("0"))==1){//说明大于0
    queryWrapper.le(SkuInfoEntity::getPrice, max);
    }
    } catch (Exception e) {
    log.error("com/atguigu/gulimall/product/service/impl/SkuInfoServiceImpl");
    e.printStackTrace();
    throw new RuntimeException(e);
    }
    }
    IPage<SkuInfoEntity> page = this.page(
    new Query<SkuInfoEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的效果

    +

    image-20230627112218894

    +

    2.库存系统

    2.1 仓库维护

    仓库的分页加条件查询

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @Override
    public PageUtils queryPage(Map<String, Object> params) {

    //分页查询的条件
    LambdaQueryWrapper<WareInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.like(WareInfoEntity::getName, key).or()
    .eq(WareInfoEntity::getId, key)
    .or().like(WareInfoEntity::getAddress, key)
    .or().like(WareInfoEntity::getAreacode, key);
    }
    IPage<WareInfoEntity> page = this.page(
    new Query<WareInfoEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的效果

    +

    image-20230627153518692

    +

    2.2 库存工作单

    2.3 商品库存

    商品库存的条件查询

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    /**
    * {
    * page: 1,//当前页码
    * limit: 10,//每页记录数
    * sidx: 'id',//排序字段
    * order: 'asc/desc',//排序方式
    * wareId: 123,//仓库id
    * skuId: 123//商品id
    * }
    */
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
    LambdaQueryWrapper<WareSkuEntity> queryWrapper = new LambdaQueryWrapper<>();
    String wareId = (String) params.get("wareId");
    if (!StringUtils.isEmpty(wareId)) {
    queryWrapper.eq(WareSkuEntity::getWareId, wareId);
    }
    String skuId = (String) params.get("skuId");
    if (!StringUtils.isEmpty(skuId)) {
    queryWrapper.eq(WareSkuEntity::getSkuId, skuId);
    }
    IPage<WareSkuEntity> page = this.page(
    new Query<WareSkuEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的效果

    +

    image-20230627160605717

    +

    2.4 采购单维护

    2.4.1 采购需求

    采购需求的分页加条件查询

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    /**
    * status: 0,//状态
    * wareId: 1,//仓库id
    */
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
    LambdaQueryWrapper<PurchaseDetailEntity> queryWrapper = new LambdaQueryWrapper<>();
    String status = (String) params.get("status");
    if (!StringUtils.isEmpty(status)) {
    queryWrapper.eq(PurchaseDetailEntity::getStatus, status);
    }
    String wareId = (String) params.get("wareId");
    if (!StringUtils.isEmpty(wareId)) {
    queryWrapper.eq(PurchaseDetailEntity::getWareId, wareId);
    }
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.and((wrapper->{
    wrapper.eq(PurchaseDetailEntity::getPurchaseId, key).or().like(PurchaseDetailEntity::getSkuId,key);
    }));
    }
    IPage<PurchaseDetailEntity> page = this.page(
    new Query<PurchaseDetailEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的效果

    +

    image-20230627161955351

    +

    合并采购单

    +

    业务需求介绍

    +

    image-20230627162707377

    +

    获取没有领取的采购单,将采购需求合并到这个采购单

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /**
    * 获取没有领取的采购单
    */
    @Override
    public PageUtils queryPageUnreceivePurchase(Map<String, Object> params) {
    IPage<PurchaseEntity> page = this.page(
    new Query<PurchaseEntity>().getPage(params),
    new LambdaQueryWrapper<PurchaseEntity>().eq(PurchaseEntity::getStatus,0).or().eq(PurchaseEntity::getStatus,1)
    );

    return new PageUtils(page);
    }
    + +

    合并采购需求

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    /**
    * 合并采购需求
    *
    * @param mergeVo 前端传递的需要合并的采购需求
    */
    @Transactional
    @Override
    public void mergePurchase(MergeVo mergeVo) {
    //判断采购单是不是存在
    Long purchaseId = mergeVo.getPurchaseId();
    if (purchaseId == null) {//说明没有选择需要合并的采购单,需要先创建一个采购单
    PurchaseEntity purchaseEntity = new PurchaseEntity();
    purchaseEntity.setStatus(WareConstant.PurchaseStatesEnum.CREATED.getCode());
    purchaseEntity.setCreateTime(new Date());
    purchaseEntity.setUpdateTime(new Date());
    this.save(purchaseEntity);
    purchaseId = purchaseEntity.getId();
    }
    List<Long> items = mergeVo.getItems();
    Long finalPurchaseId = purchaseId;
    List<PurchaseDetailEntity> detailEntityList = items.stream().map(item -> {
    PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
    detailEntity.setId(item);
    detailEntity.setPurchaseId(finalPurchaseId);
    detailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.ASSIGNED.getCode());
    return detailEntity;
    }).collect(Collectors.toList());
    detailService.updateBatchById(detailEntityList);
    //更新一下时间
    PurchaseEntity purchaseEntity = new PurchaseEntity();
    purchaseEntity.setId(purchaseId);
    purchaseEntity.setUpdateTime(new Date());
    this.updateById(purchaseEntity);
    }

    + +

    2.4.2 采购单

    领取采购单

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /**
    * @param ids 采购单id
    */
    @Override
    public void received(List<Long> ids) {
    //确认当前的采购单是新建或者分配状态
    List<PurchaseEntity> purchaseEntities = ids.stream().map(id -> {
    PurchaseEntity byId = this.getById(id);
    return byId;
    }).filter(item -> {
    if (item.getStatus() == WareConstant.PurchaseStatesEnum.CREATED.getCode() ||
    item.getStatus() == WareConstant.PurchaseDeatilStatesEnum.ASSIGNED.getCode()) {
    return true;
    }
    return false;
    }).map(item->{
    //改变采购单状态
    item.setStatus(WareConstant.PurchaseStatesEnum.RECEIVE.getCode());
    item.setUpdateTime(new Date());
    return item;
    }).collect(Collectors.toList());
    this.updateBatchById(purchaseEntities);
    //改变采购项状态
    purchaseEntities.forEach(item->{
    List<PurchaseDetailEntity> entities = detailService.listDetailByPurchaseId(item.getId());
    List<PurchaseDetailEntity> detailEntityList = entities.stream().map(entity -> {
    PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
    purchaseDetailEntity.setId(entity.getId());
    purchaseDetailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.BUYING.getCode());
    return purchaseDetailEntity;
    }).collect(Collectors.toList());
    detailService.updateBatchById(detailEntityList);
    });
    }
    + +

    完成采购的功能

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @Transactional
    @Override
    public void done(PurchaseDoneVo doneVo) {
    Long id = doneVo.getId();
    //改变采购项的状态
    Boolean flag = true;
    List<PurchaseItemDoneVo> items = doneVo.getItems();
    List<PurchaseDetailEntity> purchaseItemDoneVos = new ArrayList<>();
    for (PurchaseItemDoneVo item : items) {
    PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
    if (item.getStatus() == WareConstant.PurchaseDeatilStatesEnum.HASERROR.getCode()) {
    flag = false;
    detailEntity.setStatus(item.getStatus());
    } else {
    detailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.FINISH.getCode());
    //将成功采购的进行入库
    PurchaseDetailEntity purchaseDetailEntity = detailService.getById(item.getItemId());
    wareSkuService.addStock(purchaseDetailEntity.getSkuId(),purchaseDetailEntity.getWareId(),purchaseDetailEntity.getSkuNum());
    }
    detailEntity.setId(item.getItemId());
    purchaseItemDoneVos.add(detailEntity);
    }
    detailService.updateBatchById(purchaseItemDoneVos);
    //改变采购单的状态
    PurchaseEntity purchaseEntity = new PurchaseEntity();
    purchaseEntity.setId(id);
    purchaseEntity.setStatus(flag ? WareConstant.PurchaseStatesEnum.FINISH.getCode() : WareConstant.PurchaseStatesEnum.HASERROR.getCode());
    purchaseEntity.setUpdateTime(new Date());
    this.updateById(purchaseEntity);
    }
    + +

    修改库存的功能

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    @Transactional
    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
    //判断是添加的操作还是修改的操作
    List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new LambdaQueryWrapper<WareSkuEntity>().eq(WareSkuEntity::getSkuId, skuId).eq(WareSkuEntity::getWareId, wareId));
    if (wareSkuEntities == null || wareSkuEntities.size() == 0) {
    WareSkuEntity wareSkuEntity = new WareSkuEntity();
    wareSkuEntity.setSkuId(skuId);
    wareSkuEntity.setWareId(wareId);
    wareSkuEntity.setStock(skuNum);
    wareSkuEntity.setStockLocked(0);
    //远程查询sku的名字
    try {
    R r = productFeignService.info(skuId);
    if(r.getCode() == 0){
    Map<String,Object> skuInfo = (Map<String, Object>) r.get("skuInfo");
    wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
    }
    } catch (Exception e) {
    log.warn("远程获取商品的Sku信息失败!");
    }
    wareSkuDao.insert(wareSkuEntity);
    } else {
    wareSkuDao.addStock(skuId, wareId, skuNum);
    }
    }
    + +

    sql语句

    +
    1
    2
    3
    4
    5
    6
    7
     <!--void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);-->
    <insert id="addStock">
    update `wms_ware_sku`
    set stock = stock + #{skuNum}
    where sku_id = #{skuId}
    and ware_id = #{wareId}
    </insert>
    + +

    3.基础篇总结

    image-20230628170218776 + +

    个人总结:

    +
      +
    • 细节很重要,编写代码的时候要注意细节问题(比如网关的配置,模糊路径和精确路径先后配置的问题)
    • +
    • 要有良好的编码习惯,注意代码解耦和代码复用
    • +
    • 业务逻辑很重要,编码之前要搞懂了业务逻辑之后再开始写代码
    • +
    • 学习技术的时候要融会贯通,学习编程的思想
    • +
    • 学技术一定要学会看开发文档(技术说明书),可以先从开发文档提供的demo开始
    • +
    +

    六.高级篇程序设计

    1.ElasticSearch

    详细的教程

    +

    ElasticSearch | The Blog (gitee.io)

    +

    2.商城业务

    2.1 商品上架

    1.商品Mapping

    +

    创建一个product索引和指定映射关系

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    PUT product
    {
    "mappings": {
    "properties": {
    "skuId": {
    "type": "long"
    },
    "spuId": {
    "type": "keyword"
    },
    "skuTitle": {
    "type": "text",
    "analyzer": "ik_smart"
    },
    "skuPrice": {
    "type": "keyword"
    },
    "skuImg": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "saleCount": {
    "type": "long"
    },
    "hasStock": {
    "type": "boolean"
    },
    "hotScore": {
    "type": "long"
    },
    "brandId": {
    "type": "long"
    },
    "catalogId": {
    "type": "long"
    },
    "brandName": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "brandImg": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "catalogName": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "attrs": {
    "type": "nested",
    "properties": {
    "attrId": {
    "type": "long"
    },
    "attrName": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "attrValue": {
    "type": "keyword"
    }
    }
    }
    }
    }
    }
    + +

    2.上架功能后端代码

    +

    所需的实体类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package com.atguigu.common.to.es;

    import lombok.Data;

    import java.math.BigDecimal;
    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/7/5
    * @Description 对应ES中保存的文档的实体类
    */
    @Data
    public class SkuEsModel {
    private Long skuId;

    private Long spuId;

    private String skuTitle;

    private BigDecimal skuPrice;

    private String skuImg;

    private Long saleCount;

    private Boolean hasStock;

    private Long hotScore;

    private Long brandId;

    private Long catalogId;

    private String brandName;

    private String brandImg;

    private String catalogName;

    private List<Attrs> attrs;

    @Data
    public static class Attrs {

    private Long attrId;

    private String attrName;

    private String attrValue;

    }

    }
    + +

    商品上架的后端关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    /**
    * 商品上架的功能
    */
    @Override
    public void up(Long spuId) {
    //查询当前spuId对应的所有sku信息
    List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);
    List<Long> skuIds = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
    //规格属性信息的封装(可以被检索的规格属性信息)
    List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListForSpu(spuId);
    List<Long> attrIds = baseAttrs.stream().map(attr -> {
    return attr.getAttrId();
    }).collect(Collectors.toList());
    //获取可以检索的属性id的集合
    List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);
    //将其转换成set集合
    Set<Long> idSet = new HashSet<>(searchAttrIds);
    //最终需要封装的attrs
    List<SkuEsModel.Attrs> attrs = baseAttrs.stream().filter(item -> {
    return idSet.contains(item.getAttrId());
    }).map(item -> {
    SkuEsModel.Attrs skuAttr = new SkuEsModel.Attrs();
    BeanUtils.copyProperties(item, skuAttr);
    return skuAttr;
    }).collect(Collectors.toList());
    //库存
    Map<Long, Boolean> stockMap = null;
    try {
    R r = wareFeignService.getSkusHasStock(skuIds);
    List<SkuHasStockVo> skuHasStockVos = r.getData(new TypeReference<List<SkuHasStockVo>>(){});
    stockMap = skuHasStockVos.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
    } catch (Exception e) {
    log.error("库存服务查询异常:原因{}",e);
    }
    //封装sku的信息
    Map<Long, Boolean> finalStockMap = stockMap;
    List<SkuEsModel> upProducts = skus.stream().map(sku -> {
    //组装需要的数据
    SkuEsModel skuEsModel = new SkuEsModel();
    BeanUtils.copyProperties(sku, skuEsModel);
    //价格 图片
    skuEsModel.setSkuPrice(sku.getPrice());
    skuEsModel.setSkuImg(sku.getSkuDefaultImg());
    //设置库存信息
    if(finalStockMap == null){
    skuEsModel.setHasStock(true);
    }else {
    skuEsModel.setHasStock(finalStockMap.get(sku.getSkuId()));
    }
    //热度评分信息
    skuEsModel.setHotScore(0L);
    //品牌名 分类名 品牌价格
    BrandEntity brand = brandService.getById(sku.getBrandId());
    skuEsModel.setBrandName(brand.getName());
    skuEsModel.setBrandImg(brand.getLogo());
    CategoryEntity category = categoryService.getById(sku.getCatalogId());
    skuEsModel.setCatalogName(category.getName());
    //设置attrs
    skuEsModel.setAttrs(attrs);
    return skuEsModel;
    }).collect(Collectors.toList());
    R r = searchFeignService.productStatusUp(upProducts);
    if(r.getCode() == 0){
    //远程调用成功
    spuInfoDao.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
    }else {
    //远程调用失败
    //TODO 重复调用的问题
    /**
    * feign的调用流程
    * 1.构造请求的数据,将对象转化成json
    * 2.发送请求进行执行(执行成功之后会解码响应数据)
    * 3.执行请求会有重试机制
    */
    }
    }
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 过滤出属性id中可以被检索的属性的id
    * @param attrIds 原属性id的集合
    * @return 可以被检索的属性id的集合
    */
    @Override
    public List<Long> selectSearchAttrs(List<Long> attrIds) {
    //select attr_id from `pms_attr` where attr_id in (?) and search_type=1
    return attrDao.selectSearchAttrs(attrIds);
    }
    + +
    1
    2
    3
    4
    5
    6
    7
    <select id="selectSearchAttrs" resultType="java.lang.Long">
    select `attr_id`from `pms_attr` where `attr_id` in
    <foreach collection="attrIds" item="attr" separator="," open="(" close=")">
    #{attr}
    </foreach>
    and `search_type`=1
    </select>
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /**
    * 查询库存的方法
    */
    @Override
    public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
    List<SkuHasStockVo> skuHasStockVos = skuIds.stream().map(skuId -> {
    SkuHasStockVo skuHasStockVo = new SkuHasStockVo();
    Long count = wareSkuDao.getSkuStock(skuId);
    skuHasStockVo.setSkuId(skuId);
    if(count != null){
    skuHasStockVo.setHasStock(count > 0);
    }else {
    skuHasStockVo.setHasStock(false);
    }
    return skuHasStockVo;
    }).collect(Collectors.toList());
    return skuHasStockVos;
    }
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /**
    * 存储商品数据
    */
    @Override
    public Boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
    //将数据保存到ES中
    //在ES中建立好索引
    BulkRequest bulkRequest = new BulkRequest();
    for (SkuEsModel skuEsModel : skuEsModels) {
    IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
    indexRequest.id(skuEsModel.getSkuId().toString());
    indexRequest.source(JSON.toJSONString(skuEsModel), XContentType.JSON);
    bulkRequest.add(indexRequest);
    }
    BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
    boolean hasFailure = bulk.hasFailures();
    List<String> ids = Arrays.stream(bulk.getItems()).map(item -> {
    return item.getId();
    }).collect(Collectors.toList());
    log.info("上架商品的id:{}",ids);
    return !hasFailure;
    }
    + +

    2.2 首页

    项目的页面视图部署在对应的微服务项目的静态资源中

    +

    image-20230706112521327

    +

    2.2.1整合thymeleaf

    添加thymeleaf的依赖

    +
    1
    2
    3
    4
    5
    <!-- thymeleaf-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    + +

    将资料中的静态资源放在微服务工程的对应位置,并在配置文件中加入对应的配置

    +

    image-20230706114304695

    +

    首页页面

    +

    image-20230706145050684

    +

    在index.html上加上thymeleaf的名称空间

    +
    1
    <html xmlns:th="http://www.thymeleaf.org">
    + +

    修改页面的时候需要频繁的重启项目,使用热更新来解决该问题

    +

    导入devtools的依赖

    +
    1
    2
    3
    4
    5
    6
    <!--devtools-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    + +

    重启项目之后会显示如下的效果

    +

    image-20230706154112026

    +

    这时我们在每次修改页面之后使用Ctrl+Shift+F9键可以热更新

    +

    image-20230706161401528

    +

    2.2.2 首页面三级分类显示

    首页面的跳转以及一级分类的显示

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 首页面的跳转
    */
    @GetMapping({"/","index.html"})
    public String indexPage(Model model){
    //查询所有的一级分类
    List<CategoryEntity> categorys = categoryService.getLevelOneCategorys();
    model.addAttribute("categorys",categorys);
    return "index";
    }
    + +

    用于用户端首页面三级分类封装的数据

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    package com.atguigu.gulimall.product.vo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/7
    * @Description 用户端首页面三级分类封装的数据
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Catelog2Vo {

    /**
    * 一级父分类的id
    */
    private String catalog1Id;

    /**
    * 三级子分类
    */
    private List<Category3Vo> catalog3List;

    private String id;

    private String name;


    /**
    * 三级分类vo
    * 静态内部类
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Category3Vo {

    /**
    * 父分类、二级分类id
    */
    private String catalog2Id;

    private String id;

    private String name;
    }
    }
    + +

    前端相关的修改

    +
    1
    2
    3
     <li th:each=" category:${categorys}">
    <a href="#" class="header_main_left_a" ctg-data="3" th:attr="ctg-data=${category.catId}"><b th:text="${category.name}"></b></a>
    </li>
    + +

    image-20230707151505310

    +

    controller层代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 首页面的商品分分类信息
    */
    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map<String, List<Catelog2Vo>> getCatalogJson(){
    return categoryService.getCatalogJson();
    }
    + +

    service层代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
    //查出所有的一级分类
    List<CategoryEntity> levelOneCategorys = getLevelOneCategorys();
    //封装数据
    Map<String, List<Catelog2Vo>> catelog2VoMap = levelOneCategorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
    //查询以及分类下的二级分类
    List<CategoryEntity> categoryEntities = categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, v.getCatId()));
    List<Catelog2Vo> catelog2Vos = null;
    if (categoryEntities != null) {
    catelog2Vos = categoryEntities.stream().map(item -> {
    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
    //当前二级分类的三级分类
    List<CategoryEntity> levelThreeCatelog = categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, item.getCatId()));
    if(levelThreeCatelog != null){
    //封装成指定的数据
    List<Catelog2Vo.Category3Vo> category3VoList = levelThreeCatelog.stream().map(threeCatelog -> {
    Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo();
    category3Vo.setCatalog2Id(item.getCatId().toString());
    category3Vo.setName(threeCatelog.getName());
    category3Vo.setId(threeCatelog.getCatId().toString());
    return category3Vo;
    }).collect(Collectors.toList());
    catelog2Vo.setCatalog3List(category3VoList);
    }
    return catelog2Vo;
    }).collect(Collectors.toList());
    }
    return catelog2Vos;
    }));
    return catelog2VoMap;
    }
    + +

    页面实现的效果

    +

    image-20230707151543593

    +

    2.3 搭建域名访问环境

    1.安装SwitchHosts软件

    +

    可以快捷的编辑操作hosts文件

    +

    打开的时候以管理员的身份打开

    +

    image-20230707153628394

    +

    image-20230707154052952

    +

    在添加的方案中添加如下的内容,点击右下角的保存按钮保存(保存失败的更改hosts文件的权限,将只读勾选掉)

    +
    1
    192.168.195.100 gulimall.com
    + +

    保存成功之后可以直接访问域名,解析到虚拟机的ip

    +

    Tips:有科学上网的测试的时候要先关闭科学上网的软件

    +

    安装了ES的访问gulimall.com:9200可以看到相应的内容

    +

    image-20230707160328001

    +

    2.正式搭建项目的域名访问环境

    +

    image-20230708092745456

    +

    nginx的配置文件

    +

    image-20230708093600923

    +

    配置nginx

    +
    1
    2
    3
    4
    5
    6
    7
    8
    #切换到nginx挂载在本机的配置文件所在的目录
    cd /mydata/nginx/conf
    #切换到nginx的分配置文件下复制一份 定义gulimall的配置,主配置文件中有include /etc/nginx/conf.d/*.conf;这么一段配置,主配置文件
    #会包含conf.d目录下的所有配置
    cd /mydata/nginx/conf/conf.d
    cp default.conf gulimall.conf 复制一份
    #在gulimall.conf配置文件中配置我们的配置
    #配置如下配置
    + +

    niginx的配置文件如下

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    server {
    listen 80;
    server_name gulimall.com;

    location / {
    #服务的IP 192.168.0.112
    #服务的端口: 10000
    proxy_pass http://192.168.0.112:10000;
    }
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    }
    + +

    这时我们在访问gulimall.com的时候就可以访问一下的页面了

    +

    image-20230708095940126

    +

    3.让nginx代理到网关

    +

    在nginx.conf配置文件中加上如下的配置

    +
    1
    2
    3
    4
    5
    6
    #配置上游服务器
    #gulimall是上游服务器的组名
    upstream gulimall{
    #网关的地址
    server 192.168.0.112:88;
    }
    + +

    修改gulimall.conf中的配置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    server {
    listen 80;
    server_name gulimall.com;

    location / {
    proxy_pass http://gulimall;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    }
    + +

    在网关的配置文件中加上一下的配置

    +
    1
    2
    3
    4
    5
    # nginx代理相关的断言
    - id: gulimall_host_route
    uri: lb://gulimall-product
    predicates:
    - Host=**.gulimall.com,gulimall.com
    + +

    这时我们重启网关的时候发现无法访问到相应的页面 是由于nginx访问网关的时候丢了host

    +

    这时我们需要加上如下的配置

    +
    1
    proxy_set_header Host $host;
    + +

    image-20230708104321024

    +image-20230708104848501 + +

    2.4 性能压测

    压力测试与性能监控 | The Blog (gitee.io)

    +

    JMeter压力测试

    +

    image-20230708152102009

    +

    性能监控

    +
    1
    2
    jconsole
    jvisualvm
    + +

    image-20230709155555491

    +

    2.4.1优化中间件对性能的影响

    网关的优化、数据库索引的优化、thymeleaf的优化、静态资源的优化、nginx动静分离

    +

    image-20230710111805705

    +

    2.5 缓存使用

    2.5.1 本地缓存与分布式缓存

    为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落盘工作。
    哪些数据适合放入缓存?
    1.即时性、数据一致性要求不高的
    2.访问量大且更新频率不高的数据(读多,写少)

    +

    本地缓存

    +

    image-20230710143906286

    +

    分布式缓存

    +

    image-20230710143341531

    +

    2.5.2 整合redis

    1.安装Redis

    +

    image-20230710144052877

    +

    2.项目中使用Redis

    +

    SpringBoot整合Redis | The Blog (gitee.io)

    +

    2.1 引入依赖

    +
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    + +

    2.2 添加配置

    +
    1
    2
    3
    4
    spring:
    redis:
    host: 192.168.195.100
    port: 6379
    + +

    2.3 测试使用

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testRedis(){
    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
    ops.set("hello","word_"+ UUID.randomUUID().toString());
    String hello = ops.get("hello");
    System.out.println(hello);
    }
    + +

    image-20230710145648233

    +

    lettuce堆外内存溢出bug

    +

    产生原因:

    +

    1)、springboot2.0以后默认使用 lettuce作为操作redis的客户端,它使用netty进行网络通信

    +

    2)、lettuce的bug导致netty堆外内存溢出。netty如果没有指定堆外内存,默认使用Xms的值,可以使用-Dio.netty.maxDirectMemory进行设置

    +

    解决方案:由于是lettuce的bug造成,不要直接使用-Dio.netty.maxDirectMemory去调大虚拟机堆外内存,治标不治本。

    +
      +
    • 1)、升级lettuce客户端
    • +
    • 2)、切换使用jedis
    • +
    +

    修改依赖文件,使用jedis作为客户端工具

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
    <exclusion>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    </dependency>
    + +

    2.5.3 高并发下缓存失效问题

    缓存穿透

    +image-20230710153635804 + +

    缓存雪崩

    +image-20230710153651752 + +

    缓存击穿

    +image-20230710153709464 + +

    分布式下如何加锁?

    +image-20230710163735955 + +

    锁-时序问题

    +image-20230710165738655 + +

    分布式锁演进-基本原理

    +

    image-20230714163437984

    +

    分布式锁的最终形态

    +

    image-20230714171221296

    +

    2.5.4 分布式锁-Redisson

    github地址: https://github.com/redisson/redisson

    +

    1.引入依赖

    +
    1
    2
    3
    4
    5
    6
    <!--redisson-->
    <dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
    </dependency>
    + +

    2.配置redisson

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    package com.atguigu.gulimall.product.config;

    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    import java.io.IOException;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/14
    * @Description Redisson的配置
    */
    @Configuration
    public class MyRedissonConfig {

    /**
    * 所有对redisson的操作通过RedissonClient
    */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
    //创建配置
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.195.100:6379");
    //根据配置config创建RedissonClient实例
    return Redisson.create(config);
    }
    }
    + +

    3.测试使用

    +
    1
    2
    3
    4
    5
    6
    7
    @Autowired
    private RedissonClient redissonClient;

    @Test
    void testRedisson(){
    System.out.println(redissonClient);
    }
    + +

    image-20230714175205635

    +

    4.简单的使用分布式锁

    +

    上一个线程加锁之后没有解锁的话,后面的线程会一直等待前面的线程释放锁,自己再加锁。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @ResponseBody
    @GetMapping("/hello")
    public String hello() {
    //打印时间需要
    SimpleDateFormat sdf = new SimpleDateFormat();// 格式化时间
    sdf.applyPattern("yyyy-MM-dd HH:mm:ss");// a为am/pm的标记
    Date date = new Date();// 获取当前时间

    //获取同一把锁,只要锁的名字一样,就是同一把锁
    RLock lock = redissonClient.getLock("my-lock");
    //加锁
    lock.lock();//阻塞式等待,锁没有释放的情况下会一直等待下去
    //1.锁的自动续期,如果业务耗费的时间超长,运行时会自动给锁续上新的30s(默认时续期30s),不必担心业务时间长,锁自动过期被删掉
    //2.加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动s
    System.out.println("现在时间:" + sdf.format(date));
    try {
    //执行业务
    System.out.println("加锁成功,执行业务,线程Id:"+Thread.currentThread().getId());
    Thread.sleep(10000);//等待10秒
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    } finally {
    //解锁
    System.out.println("释放锁,线程Id:"+Thread.currentThread().getId());
    lock.unlock();
    }
    return "hello";
    }
    + +

    image-20230714181620263

    +

    my-lock对应的值会跟随线程的变化而变化

    +

    image-20230714182057982

    +

    读写锁

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    /**
    * 写锁没有释放的情况下,读锁必须等待写锁的释放,才能进行读的操作
    * 读写锁的测试
    * 写锁
    */
    @ResponseBody
    @GetMapping("/write")
    public String writeValue(){
    RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
    String s = "";
    RLock rLock = lock.writeLock();//写锁
    try {
    rLock.lock();
    s = UUID.randomUUID().toString();
    Thread.sleep(30000);
    //写的操作
    redisTemplate.opsForValue().set("writeValue",s);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally {
    rLock.unlock();
    }
    return s;
    }

    /**
    * 读写锁的测试
    * 读锁
    */
    @GetMapping("/read")
    @ResponseBody
    public String readValue(){
    RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
    String s = "";
    RLock rLock = lock.readLock();//读锁
    try {
    rLock.lock();
    //读的操作
    s = redisTemplate.opsForValue().get("writeValue");
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    rLock.unlock();
    }
    return s;
    }
    + +

    2.5.5 分布式缓存一致性问题

    双写模式

    +

    image-20230716100845389

    +

    失效模式

    +

    image-20230716100909465

    +

    缓存数据一致性-解决方案

    +

    image-20230716101939869

    +

    缓存数据一致性-解决-Canal

    +

    image-20230716102510738

    +

    2.5.6 缓存-SpringCache

    1、简介

    +
      +
    • Spring 从 3.1 开始定义了 org.springframework.cache.Cache和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发;

      +
    • +
    • Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
      Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache ,ConcurrentMapCache 等;

      +
    • +
    • 每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

      +
    • +
    • 使用 Spring 缓存抽象时我们需要关注以下两点;

      +

      1、确定方法需要被缓存以及他们的缓存策略

      +

      2、从缓存中读取之前缓存存储的数据

      +
    • +
    +

    2.以前的笔记教程

    +

    SpringBoot整合Redis | The Blog (gitee.io)

    +

    3.项目中整合SpringCache简化开发

    +

    3.1.引入依赖

    +
    1
    2
    3
    4
    5
    <!--我们使redis作为缓存的使用场景,在前面我们还要引入redis的依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    + +

    3.2.编写配置文件

    +
    1
    2
    #使用缓存的类型
    spring.cache.type=redis
    + +

    3.3.测试使用缓存

    +

    常用的缓存注解

    +

    @Cacheable 触发将数据保存到缓存的操作

    +

    @CachePut 不影响方法执行更新缓存

    +

    @CacheEvict 触发将数据从缓存中删除的操作

    +

    @Caching 组合多个缓存操作

    +

    @CacheConfig 在类级别上共享缓存的相关配置

    +

    1.在启动类上添加缓存的注解

    +
    1
    @EnableCaching //开启缓存功能
    + +

    2.测试使用

    +
    1
    2
    3
    4
    5
    6
    //每一个需要缓存的数据需要我们指定放到那个名字的缓存[缓存的分区(按照业务类型分)]
    @Cacheable({"category"}) //代表当前方法的结果需要缓存,如果缓存中有,方法就不需要调用了,如果缓存中没有,就会调用方法,将方法的结果放入到缓存
    @Override
    public List<CategoryEntity> getLevelOneCategorys() {
    return categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, 0));
    }
    + +

    image-20230716112105848

    +

    自定义数据的存储

    +

    设置自定义的key值

    +
    1
    @Cacheable(value = {"category"},key = "'levelOneCategorys'")  //自定义key值
    + +

    tips: #root.method.name是SpEL表达式

    +
    1
    @Cacheable(value = {"category"},key = "#root.method.name")   //使用方法名作为key值
    + +

    设置缓存数据的存活时间

    +
    1
    2
    #设置一个小时的存活时间
    spring.cache.redis.time-to-live=3600000
    + +

    image-20230716152821097

    +

    将数据保存为json的格式

    +

    添加配置类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.atguigu.gulimall.product.config;

    import org.springframework.boot.autoconfigure.cache.CacheProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/16
    * @Description 缓存的序列化的配置
    */
    @EnableConfigurationProperties(CacheProperties.class)
    @Configuration
    public class MyCacheConfig {
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
    config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
    config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    //让配置文件中所有的配置生效(下面的不配置的话,缓存的时间会失效)
    CacheProperties.Redis redisProperties = cacheProperties.getRedis();
    if (redisProperties.getTimeToLive() != null) {
    config = config.entryTtl(redisProperties.getTimeToLive());
    }
    if (redisProperties.getKeyPrefix() != null) {
    config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
    }
    if (!redisProperties.isCacheNullValues()) {
    config = config.disableCachingNullValues();
    }
    if (!redisProperties.isUseKeyPrefix()) {
    config = config.disableKeyPrefix();
    }
    return config;
    }
    }
    + +

    效果

    +

    image-20230716160027391

    +

    其它的常用配置

    +
    1
    2
    3
    4
    5
    6
    #缓存的前缀,如果指定了前缀就使用指定的前缀,没有指定前缀就使用缓存的名字作为前缀
    spring.cache.redis.key-prefix=CACHE_
    #是否使用缓存的前缀
    spring.cache.redis.use-key-prefix=true
    #是否缓存空值(缓存穿透的问题的解决)
    spring.cache.redis.cache-null-values=true
    + +

    2.6 检索服务

    2.6.1 整合页面

    image-20230716173751202

    +

    2.6.2 配置域名访问

    image-20230716173453604

    +

    Nginx的配置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    server {
    listen 80;
    server_name *.gulimall.com;

    location / {
    proxy_set_header Host $host;
    proxy_pass http://gulimall;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }

    }
    + +

    网关中配置断言

    +
    1
    2
    3
    4
    5
    #搜索
    - id: gulimall_search_route
    uri: lb://gulimall-search
    predicates:
    - Host=search.gulimall.com
    + +

    image-20230716173628024

    +

    2.6.3 首页和搜索页相互跳转

    首页无法跳转到搜索页的原因,gumall改为gulimall即可

    +

    image-20230716181052580

    +

    搜索框跳转到搜索页,修改页面中的代码

    +

    image-20230717094656565

    +

    2.6.4 商品的检索

    image-20230717095740594

    +

    检索条件的VO

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    package com.atguigu.gulimall.search.vo;

    import lombok.Data;

    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/17
    * @Description 检索条件的vo
    */
    @Data
    public class SearchParam {
    /**
    * 页面传递过来的全文匹配关键字
    */
    private String keyword;

    /**
    * 品牌id,可以多选
    */
    private List<Long> brandId;

    /**
    * 三级分类id
    */
    private Long catalog3Id;

    /**
    * 排序条件:sort=price/salecount/hotscore_desc/asc
    */
    private String sort;

    /**
    * 是否显示有货
    */
    private Integer hasStock;

    /**
    * 价格区间查询
    */
    private String skuPrice;

    /**
    * 按照属性进行筛选
    */
    private List<String> attrs;

    /**
    * 页码
    */
    private Integer pageNum = 1;

    /**
    * 原生的所有查询条件
    */
    private String _queryString;
    }
    + +

    检索的结果

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    package com.atguigu.gulimall.search.vo;

    import com.atguigu.common.to.es.SkuEsModel;
    import lombok.Data;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/17
    * @Description 根据检索条件返回给页面的商品信息
    */
    @Data
    public class SearchResult {


    /**
    * 查询到的所有商品信息
    */
    private List<SkuEsModel> product;


    /**
    * 当前页码
    */
    private Integer pageNum;

    /**
    * 总记录数
    */
    private Long total;

    /**
    * 总页码
    */
    private Integer totalPages;

    private List<Integer> pageNavs;

    /**
    * 当前查询到的结果,所有涉及到的品牌
    */
    private List<BrandVo> brands;

    /**
    * 当前查询到的结果,所有涉及到的所有属性
    */
    private List<AttrVo> attrs;

    /**
    * 当前查询到的结果,所有涉及到的所有分类
    */
    private List<CatalogVo> catalogs;

    private List<NavVo> navs;

    @Data
    public static class NavVo {
    private String navName;
    private String navValue;
    private String link;
    }


    @Data
    public static class BrandVo {

    private Long brandId;

    private String brandName;

    private String brandImg;
    }


    @Data
    public static class AttrVo {

    private Long attrId;

    private String attrName;

    private List<String> attrValue;
    }


    @Data
    public static class CatalogVo {

    private Long catalogId;

    private String catalogName;
    }
    }
    + +

    未完待续(学习到177)……

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    目录
    1. 1. 一.项目简介
      1. 1.1. 1.项目背景
      2. 1.2. 2.项目架构图
      3. 1.3. 3.项目技术和特色
      4. 1.4. 4.项目的前置要求
    2. 2. 二.分布式基础概念
      1. 2.1. 1.微服务
      2. 2.2. 2.集群|分布式|节点
      3. 2.3. 3.远程调用
      4. 2.4. 4.负载均衡
      5. 2.5. 5.服务注册|发现|注册中心
      6. 2.6. 6.配置中心
      7. 2.7. 7.服务熔断和服务降级
      8. 2.8. 8.API网关
    3. 3. 三.环境搭建
      1. 3.1. 1.安装虚拟机
      2. 3.2. 2.Docker安装与配置
      3. 3.3. 3.Docker安装mysql
      4. 3.4. 4.Docker安装redis
      5. 3.5. 5.统一开发环境
      6. 3.6. 6.配置Git
      7. 3.7. 7.从Gitee上初始化一个项目
      8. 3.8. 8.创建微服务模块
      9. 3.9. 9.数据库设计
      10. 3.10. 10.后台管理系统的搭建
        1. 3.10.1. 10.1.后端的搭建
        2. 3.10.2. 10.2 前端的搭建
      11. 3.11. 11.快速开发-逆向工程的搭建
        1. 3.11.1. 1.代码生成器快速使用案例
        2. 3.11.2. 2.代码生成器的使用步骤
        3. 3.11.3. 3.后台搭建完成之后的项目树
    4. 4. 四.项目前置知识
      1. 4.1. 1.SpringCloud Alibaba
        1. 4.1.1. 1.1简介
        2. 4.1.2. 1.2为什么使用SpringCloud Alibaba?
        3. 4.1.3. 1.3 SpringCloud Alibaba最终的技术搭配方案
        4. 4.1.4. 1.4 项目中使用SpringCloud Alibaba
        5. 4.1.5. 1.5 SpringCloud Alibaba的组件
          1. 4.1.5.1. 1.Nacos注册中心
          2. 4.1.5.2. 2.Feign声明式远程调用
          3. 4.1.5.3. 3.Nacos配置中心
          4. 4.1.5.4. 4.Gateway网关
      2. 4.2. 2.前端开发的基础知识
    5. 5. 五.基础篇程序设计
      1. 5.1. 1.商品服务
        1. 5.1.1. 1.1 三级分类
          1. 5.1.1.1. 1.1.1 查询-递归树形结构数据获取
          2. 5.1.1.2. 1.1.2 配置路由和网关和路径重写
          3. 5.1.1.3. 1.2.3 查询-页面中树形显示
          4. 5.1.1.4. 1.2.4 删除-删除商品分类
          5. 5.1.1.5. 1.2.5 添加-添加商品分类
          6. 5.1.1.6. 1.2.6 修改-修改商品的分类
        2. 5.1.2. 1.2 品牌管理
          1. 5.1.2.1. 1.2.1 品牌的增删改查功能
          2. 5.1.2.2. 1.2.2 JSR303-数据校验
          3. 5.1.2.3. 1.2.3 统一异常处理
          4. 5.1.2.4. 1.2.4 关联分类功能
        3. 5.1.3. 1.3 平台属性
          1. 5.1.3.1. 1.3.1 属性分组功能
          2. 5.1.3.2. 1.3.2 规格参数功能
          3. 5.1.3.3. 1.3.3 销售属性功能
        4. 5.1.4. 1.4 商品维护
          1. 5.1.4.1. 1.4.1 SPU管理
          2. 5.1.4.2. 1.4.2 发布商品
          3. 5.1.4.3. 1.4.3 商品管理
      2. 5.2. 2.库存系统
        1. 5.2.1. 2.1 仓库维护
        2. 5.2.2. 2.2 库存工作单
        3. 5.2.3. 2.3 商品库存
        4. 5.2.4. 2.4 采购单维护
          1. 5.2.4.1. 2.4.1 采购需求
          2. 5.2.4.2. 2.4.2 采购单
      3. 5.3. 3.基础篇总结
    6. 6. 六.高级篇程序设计
      1. 6.1. 1.ElasticSearch
      2. 6.2. 2.商城业务
        1. 6.2.1. 2.1 商品上架
        2. 6.2.2. 2.2 首页
          1. 6.2.2.1. 2.2.1整合thymeleaf
          2. 6.2.2.2. 2.2.2 首页面三级分类显示
        3. 6.2.3. 2.3 搭建域名访问环境
        4. 6.2.4. 2.4 性能压测
          1. 6.2.4.1. 2.4.1优化中间件对性能的影响
        5. 6.2.5. 2.5 缓存使用
          1. 6.2.5.1. 2.5.1 本地缓存与分布式缓存
          2. 6.2.5.2. 2.5.2 整合redis
          3. 6.2.5.3. 2.5.3 高并发下缓存失效问题
          4. 6.2.5.4. 2.5.4 分布式锁-Redisson
          5. 6.2.5.5. 2.5.5 分布式缓存一致性问题
          6. 6.2.5.6. 2.5.6 缓存-SpringCache
        6. 6.2.6. 2.6 检索服务
          1. 6.2.6.1. 2.6.1 整合页面
          2. 6.2.6.2. 2.6.2 配置域名访问
          3. 6.2.6.3. 2.6.3 首页和搜索页相互跳转
          4. 6.2.6.4. 2.6.4 商品的检索
    最近更新
    \ No newline at end of file diff --git a/posts/46306.html b/posts/46306.html new file mode 100644 index 000000000..5d75294d5 --- /dev/null +++ b/posts/46306.html @@ -0,0 +1,203 @@ +技术书籍-java8实战 | The Blog + + + + + + + + + + + + +

    技术书籍-java8实战

    java 8实战

    + +
    + +
    + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/46317.html b/posts/46317.html new file mode 100644 index 000000000..a1203d0da --- /dev/null +++ b/posts/46317.html @@ -0,0 +1,846 @@ +面试专题 | The Blog + + + + + + + + + + + + +

    面试专题

    50W字的面试文档

    来源于:咕泡教育 个人使用 不外传

    +

    在线预览链接:https://www.aliyundrive.com/s/F2wn9fxYhFs

    +

    image-20230513134435330

    +

    黑马程序员Java面试视频教程

    黑马程序员新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题) https://www.bilibili.com/video/BV1yT411H7YK/?share_source=copy_web&vd_source=aee5e475191b69e6c781059ab6662584

    +

    1.Redis篇

    +

    面试官:什么是缓存穿透 ? 怎么解决 ?

    +

    候选人

    +

    嗯~~,我想一下

    +

    缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。

    +

    解决方案的话,我们通常都会用布隆过滤器来解决它

    +

    面试官:好的,你能介绍一下布隆过滤器吗?

    +

    候选人

    +

    嗯,是这样~

    +

    布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是redisson实现的布隆过滤器。

    +

    它的底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一个key的存在。查找的过程也是一样的。

    +

    当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%,其实这个误判是必然存在的,要不就得增加数组的长度,其实已经算是很划分了,5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。

    +

    面试官:什么是缓存击穿 ? 怎么解决 ?

    +

    候选人

    +

    嗯!!

    +

    缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。

    +

    解决方案有两种方式:

    +

    第一可以使用互斥锁:当缓存失效时,不立即去load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db的操作并回设缓存,否则重试get缓存的方法

    +

    第二种方案可以设置当前key逻辑过期,大概是思路如下:

    +

    ①:在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间

    +

    ②:当查询的时候,从redis取出数据后判断时间是否过期

    +

    ③:如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新

    +

    当然两种方案各有利弊:===============

    +

    如果选择数据的强一致性,建议使用分布式锁的方案,性能上可能没那么高,锁需要等,也有可能产生死锁的问题

    +

    如果选择key的逻辑删除,则优先考虑的高可用性,性能比较高,但是数据同步这块做不到强一致。

    +

    面试官:什么是缓存雪崩 ? 怎么解决 ?

    +

    候选人

    +

    嗯!!

    +

    缓存雪崩意思是设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB 瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多key,击穿是某一个key缓存。

    +

    解决方案主要是可以将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

    +

    面试官:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

    +

    候选人:嗯!就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,需要让数据库与redis高度保持一致,因为要求时效性比较高,我们当时采用的读写锁保证的强一致性。

    +

    我们采用的是redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。

    +

    面试官:那这个排他锁是如何保证读写、读读互斥的呢?

    +

    候选人:其实排他锁底层使用也是setnx,保证了同时只能有一个线程操作锁住的方法

    +

    面试官:你听说过延时双删吗?为什么不用它呢?

    +

    候选人:延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一致性,所以没有采用它。

    +

    面试官:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

    +

    候选人:嗯!就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,数据同步可以有一定的延时(符合大部分业务)

    +

    我们当时采用的阿里的canal组件实现数据同步:不需要更改业务代码,部署一个canal服务。canal服务把自己伪装成mysql的一个从节点,当mysql数据更新以后,canal会读取binlog数据,然后在通过canal的客户端获取到数据,更新缓存即可。

    +

    面试官:redis做为缓存,数据的持久化是怎么做的?

    +

    候选人:在Redis中提供了两种数据持久化的方式:1、RDB 2、AOF

    +

    面试官:这两种持久化方式有什么区别呢?

    +

    候选人:RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。

    +

    AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据

    +

    面试官:这两种方式,哪种恢复的比较快呢?

    +

    候选人:RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我们通常在项目中也会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略,我们当时设置的就是每秒批量写入一次命令

    +

    面试官:Redis的数据过期策略有哪些 ?

    +

    候选人

    +

    嗯~,在redis中提供了两种数据过期删除策略

    +

    第一种是惰性删除,在设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

    +

    第二种是 定期删除,就是说每隔一段时间,我们就对一些key进行检查,删除里面过期的key

    +

    定期清理的两种模式:

    +
      +
    • SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的 hz 选项来调整这个次数
    • +
    • FAST模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms
    • +
    +

    Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。

    +

    面试官:Redis的数据淘汰策略有哪些 ?

    +

    候选人

    +

    嗯,这个在redis中提供了很多种,默认是noeviction,不删除任何数据,内部不足直接报错

    +

    是可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFU

    +

    LRU的意思就是最少最近使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。

    +

    LFU的意思是最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高

    +

    我们在项目设置的allkeys-lru,挑选最近最少使用的数据淘汰,把一些经常访问的key留在redis中

    +

    面试官:数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?

    +

    候选人

    +

    嗯,我想一下~~

    +

    可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据

    +

    面试官:Redis的内存用完了会发生什么?

    +

    候选人

    +

    嗯~,这个要看redis的数据淘汰策略是什么,如果是默认的配置,redis内存用完以后则直接报错。我们当时设置的 allkeys-lru 策略。把最近最常访问的数据留在缓存中。

    +

    面试官:Redis分布式锁如何实现 ?

    +

    候选人:嗯,在redis中提供了一个命令setnx(SET if not exists)

    +

    由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key的时候是其他客户端是不能设置这个key的

    +

    面试官:好的,那你如何控制Redis实现分布式锁有效时长呢?

    +

    候选人:嗯,的确,redis的setnx指令不好控制这个问题,我们当时采用的redis的一个框架redisson实现的。

    +

    在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后需要使用释放锁就可以了

    +

    还有一个好处就是,在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会自旋不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。

    +

    面试官:好的,redisson实现的分布式锁是可重入的吗?

    +

    候选人:嗯,是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数

    +

    面试官:redisson实现的分布式锁能解决主从一致性的问题吗

    +

    候选人:这个是不能的,比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。

    +

    我们可以利用redisson提供的红锁来解决这个问题,它的主要作用是,不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,并且要求在大多数redis节点上都成功创建锁,红锁中要求是redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。

    +

    但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变的很低了,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁

    +

    面试官:好的,如果业务非要保证数据的强一致性,这个该怎么解决呢?

    +

    候选人:嗯~,redis本身就是支持高可用的,做到强一致性,就非常影响性能,所以,如果有强一致性要求高的业务,建议使用zookeeper实现的分布式锁,它是可以保证强一致性的。

    +

    面试官:Redis集群有哪些方案, 知道嘛 ?

    +

    候选人:嗯~~,在Redis中提供的集群方案总共有三种:主从复制、哨兵模式、Redis分片集群

    +

    面试官:那你来介绍一下主从同步

    +

    候选人:嗯,是这样的,单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中

    +

    面试官:能说一下,主从同步数据的流程

    +

    候选人:嗯~~,好!主从同步分为了两个阶段,一个是全量同步,一个是增量同步

    +

    全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:

    +

    第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。

    +

    第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致。

    +

    第三:在同时主节点会执行bgsave,生成rdb文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致

    +

    当然,如果在rdb生成执行期间,依然有请求到了主节点,而主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时候,都是依赖于这个日志文件,这个就是全量同步

    +

    增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步

    +

    面试官:怎么保证Redis的高并发高可用

    +

    候选人:首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用

    +

    面试官:你们使用redis是单点还是集群,哪种集群

    +

    候选人:嗯!,我们当时使用的是主从(1主1从)加哨兵。一般单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务

    +

    面试官:redis集群脑裂,该怎么解决呢?

    +

    候选人:嗯! 这个在项目很少见,不过脑裂的问题是这样的,我们现在用的是redis的哨兵模式集群的

    +

    有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redis master节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将old master降为salve,这时再从新master同步数据,这会导致old master中的大量数据丢失。

    +

    关于解决的话,我记得在redis的配置中可以设置:第一可以设置最少的salve节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失

    +

    面试官:redis的分片集群有什么作用

    +

    候选人:分片集群主要解决的是,海量数据存储的问题,集群中有多个master,每个master保存不同数据,并且还可以给每个master设置多个slave节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点

    +

    面试官:Redis分片集群中数据是怎么存储和读取的?

    +

    候选人

    +

    嗯~,在redis集群中是这样的

    +

    Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围, key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,通过槽找到对应的节点进行存储。

    +

    取值的逻辑是一样的

    +

    面试官:Redis是单线程的,但是为什么还那么快?

    +

    候选人

    +

    嗯,这个有几个原因吧~~~

    +

    1、完全基于内存的,C语言编写

    +

    2、采用单线程,避免不必要的上下文切换可竞争条件

    +

    3、使用多路I/O复用模型,非阻塞IO

    +

    例如:bgsave 和 bgrewriteaof 都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞

    +

    面试官:能解释一下I/O多路复用模型?

    +

    候选人:嗯~~,I/O多路复用是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。

    +

    其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;

    +

    在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程

    +
    +

    pdf

    + + +
    + +
    + + + + + + +

    2.数据库篇

    +

    面试官:MySQL中,如何定位慢查询?

    +

    候选人:

    +

    嗯~,我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了2秒以上,因为我们当时的系统部署了运维的监控系统Skywalking ,在展示的报表中可以看到是哪一个接口比较慢,并且可以分析这个接口哪部分比较慢,这里可以看到SQL的具体的执行时间,所以可以定位是哪个sql出了问题

    +

    如果,项目中没有这种运维的监控系统,其实在MySQL中也提供了慢日志查询的功能,可以在MySQL的系统配置文件中开启这个慢日志的功能,并且也可以设置SQL执行超过多少时间来记录到一个日志文件中,我记得上一个项目配置的是2秒,只要SQL执行的时间超过了2秒就会记录到日志文件中,我们就可以在日志文件找到执行比较慢的SQL了。

    +

    面试官:那这个SQL语句执行很慢, 如何分析呢?

    +

    候选人:如果一条sql执行很慢的话,我们通常会使用mysql自动的执行计划explain来去查看这条sql的执行情况,比如在这里面可以通过key和key_len检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况,第二个,可以通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描,第三个可以通过extra建议来判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复

    +

    面试官:了解过索引吗?(什么是索引)

    +

    候选人:嗯,索引在项目中还是比较常见的,它是帮助MySQL高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的IO成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了CPU的消耗

    +

    面试官:索引的底层数据结构了解过嘛 ?

    +

    候选人:MySQL的默认的存储引擎InnoDB采用的B+树的数据结构来存储索引,选择B+树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是B+树便于扫库和区间查询,叶子节点是一个双向链表

    +

    面试官:B树和B+树的区别是什么呢?

    +

    候选人:第一:在B树中,非叶子节点和叶子节点都会存放数据,而B+树的所有的数据都会出现在叶子节点,在查询的时候,B+树查找效率更加稳定

    +

    第二:在进行范围查询的时候,B+树效率更高,因为B+树都在叶子节点存储,并且叶子节点是一个双向链表

    +

    面试官:什么是聚簇索引什么是非聚簇索引 ?

    +

    候选人:

    +

    好的~,聚簇索引主要是指数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个,一般情况下主键在作为聚簇索引的

    +

    非聚簇索引值的是数据与索引分开存储,B+树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚簇索引

    +

    面试官:知道什么是回表查询嘛 ?

    +

    候选人:嗯,其实跟刚才介绍的聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表

    +

    备注:如果面试官直接问回表,则需要先介绍聚簇索引和非聚簇索引】

    +

    面试官:知道什么叫覆盖索引嘛 ?

    +

    候选人:嗯~,清楚的

    +

    覆盖索引是指select查询语句使用了索引,在返回的列,必须在索引中全部能够找到,如果我们使用id查询,它会直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。

    +

    如果按照二级索引查询数据的时候,返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *,尽量在返回的列中都包含添加索引的字段

    +

    面试官:MYSQL超大分页怎么处理 ?

    +

    候选人:嗯,超大分页一般都是在数据量比较大时,我们使用了limit分页查询,并且需要对数据进行排序,这个时候效率就很低,我们可以采用覆盖索引和子查询来解决

    +

    先分页查询数据的id字段,确定了id之后,再用子查询来过滤,只查询这个id列表中的数据就可以了

    +

    因为查询id的时候,走的覆盖索引,所以效率可以提升很多

    +

    面试官:索引创建原则有哪些?

    +

    候选人:嗯,这个情况有很多,不过都有一个大前提,就是表中的数据要超过10万以上,我们才会创建索引,并且添加索引的字段是查询比较频繁的字段,一般也是像作为查询条件,排序字段或分组的字段这些。

    +

    还有就是,我们通常创建索引的时候都是使用复合索引来创建,一条sql的返回值,尽量使用覆盖索引,如果字段的区分度不高的话,我们也会把它放在组合索引后面的字段。

    +

    如果某一个字段的内容较长,我们会考虑使用前缀索引来使用,当然并不是所有的字段都要添加索引,这个索引的数量也要控制,因为添加索引也会导致新增改的速度变慢。

    +

    面试官:什么情况下索引会失效 ?

    +

    候选人:嗯,这个情况比较多,我说一些自己的经验,以前遇到过的

    +

    比如,索引在使用的时候没有遵循最左匹配法则,第二个是,模糊查询,如果%号在前面也会导致索引失效。如果在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。

    +

    我们之前还遇到过一个就是,如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效

    +

    查询的时候发生了类型转换,在查询的时候做了运算的操作和模糊查询也会导致索引失效

    +

    所以,通常情况下,想要判断出这条sql是否有索引失效的情况,可以使用explain执行计划来分析

    +

    面试官:sql的优化的经验

    +

    候选人:嗯,这个在项目还是挺常见的,当然如果直说sql优化的话,我们会从这几方面考虑,比如

    +

    建表的时候、使用索引、sql语句的编写、主从复制,读写分离,还有一个是如果量比较大的话,可以考虑分库分表

    +

    直接经验:

    +

    ①SELECT语句务必指明字段名称(避免直接使用select * )

    +

    ②SQL语句要避免造成索引失效的写法

    +

    ③尽量用union all代替union union会多一次过滤,效率低

    +

    ④避免在where子句中对字段进行表达式操作

    +

    ⑤Join优化 能用innerjoin 就不用left join right join,如必须使用 一定要以小表为驱动,内连接会对两个表进行优化,优先把小表放到外边,把大表放到里边。left join 或 right join,不会重新调整顺序

    +

    面试官:创建表的时候,你们是如何优化的呢?

    +

    候选人:这个我们主要参考的阿里出的那个开发手册《嵩山版》,就比如,在定义字段的时候需要结合字段的内容来选择合适的类型,如果是数值的话,像tinyint、int 、bigint这些类型,要根据实际情况选择。如果是字符串类型,也是结合存储的内容来选择char和varchar或者text类型

    +

    面试官:那在使用索引的时候,是如何优化呢?

    +

    候选人:【参考索引创建原则 进行描述】

    +

    1). 针对于数据量较大,且查询比较频繁的表建立索引。

    +

    2). 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。

    +

    3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。

    +

    4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。

    +

    5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。

    +

    6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。

    +

    7). 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。

    +

    面试官:你平时对sql语句做了哪些优化呢?

    +

    候选人:嗯,这个也有很多,比如SELECT语句务必指明字段名称,不要直接使用select * ,还有就是要注意SQL语句避免造成索引失效的写法;如果是聚合查询,尽量用union all代替union ,union会多一次过滤,效率比较低;如果是表关联的话,尽量使用innerjoin ,不要使用用left join right join,如必须使用 一定要以小表为驱动

    +

    面试官:事务的特性是什么?可以详细说一下吗?

    +

    候选人:嗯,这个比较清楚,ACID,分别指的是:原子性、一致性、隔离性、持久性;我举个例子:

    +

    A向B转账500,转账成功,A扣除500元,B增加500元,原子操作体现在要么都成功,要么都失败

    +

    在转账的过程中,数据要一致,A扣除了500,B必须增加500

    +

    在转账的过程中,隔离性体现在A像B转账,不能受其他事务干扰

    +

    在转账的过程中,持久性体现在事务提交后,要把数据持久化(可以说是落盘操作)

    +

    ACID介绍:

    +

    原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。

    +

    一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。

    +

    隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。

    +

    持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

    +

    面试官:并发事务带来哪些问题?

    +

    候选人

    +

    我们在项目开发中,多个事务并发进行是经常发生的,并发也是必然的,有可能导致一些问题

    +

    第一是脏读, 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

    +

    第二是不可重复读:比如在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

    +

    第三是幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

    +

    image-20230511172226172

    +

    面试官:怎么解决这些问题呢?MySQL的默认隔离级别是?

    +

    候选人:解决方案是对事务进行隔离

    +

    MySQL支持四种隔离级别,分别有:

    +

    第一个是,未提交读(read uncommitted)它解决不了刚才提出的所有问题,一般项目中也不用这个。第二个是读已提交(read committed)它能解决脏读的问题的,但是解决不了不可重复读和幻读。第三个是可重复读(repeatable read)它能解决脏读和不可重复读,但是解决不了幻读,这个也是mysql默认的隔离级别。第四个是串行化(serializable)它可以解决刚才提出来的所有问题,但是由于让是事务串行执行的,性能比较低。所以,我们一般使用的都是mysql默认的隔离级别:可重复读

    +

    image-20230511172259204

    +

    面试官:undo log和redo log的区别

    +

    候选人:好的,其中redo log日志记录的是数据页的物理变化,服务宕机可用来同步数据,而undo log 不同,它主要记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据,比如我们删除一条数据的时候,就会在undo log日志文件中新增一条delete语句,如果发生回滚就执行逆操作;

    +

    redo log保证了事务的持久性,undo log保证了事务的原子性和一致性

    +

    面试官:事务中的隔离性是如何保证的呢?(你解释一下MVCC)

    +

    候选人:事务的隔离性是由锁和mvcc实现的。

    +

    其中mvcc的意思是多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段,第二个是undo log日志,第三个是readView读视图

    +

    隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id(事务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer(回滚指针),指向上一个版本的事务版本记录地址

    +

    undo log主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表

    +

    readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。如果是rc隔离级别,每一次执行快照读时生成ReadView,如果是rr隔离级别仅在事务中第一次执行快照读时生成ReadView,后续复用

    +

    面试官:MySQL主从同步原理

    +

    候选人:MySQL主从复制的核心就是二进制日志(DDL(数据定义语言)语句和 DML(数据操纵语言)语句),它的步骤是这样的:

    +

    第一:主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。

    +

    第二:从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。

    +

    第三:从库重做中继日志中的事件,将改变反映它自己的数据

    +

    面试官:你们项目用过MySQL的分库分表吗?

    +

    候选人

    +

    嗯,因为我们都是微服务开发,每个微服务对应了一个数据库,是根据业务进行拆分的,这个其实就是垂直拆分。

    +

    面试官:那你之前使用过水平分库吗?

    +

    候选人

    +

    嗯,这个是使用过的,我们当时的业务是(xxx),一开始,我们也是单库,后来这个业务逐渐发展,业务量上来的很迅速,其中(xx)表已经存放了超过1000万的数据,我们做了很多优化也不好使,性能依然很慢,所以当时就使用了水平分库。

    +

    我们一开始先做了3台服务器对应了3个数据库,由于库多了,需要分片,我们当时采用的mycat来作为数据库的中间件。数据都是按照id(自增)取模的方式来存取的。

    +

    当然一开始的时候,那些旧数据,我们做了一些清洗的工作,我们也是按照id取模规则分别存储到了各个数据库中,好处就是可以让各个数据库分摊存储和读取的压力,解决了我们当时性能的问题

    +
    +

    pdf

    + + +
    + +
    + + + + + + +

    3.框架篇

    +

    面试官:Spring框架中的单例bean是线程安全的吗?

    +

    候选人

    +

    嗯!

    +

    不是线程安全的,是这样的

    +

    当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。

    +

    Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。

    +

    比如:我们通常在项目中使用的Spring bean都是不可可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。

    +

    如果你的bean有多种状态的话(比如 View Model对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由“singleton”变更为“prototype”。

    +

    面试官:什么是AOP

    +

    候选人

    +

    aop是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等

    +

    面试官:你们项目中有没有使用到AOP

    +

    候选人

    +

    我们当时在后台管理系统中,就是使用aop来记录了系统的操作日志

    +

    主要思路是这样的,使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库

    +

    面试官:Spring中的事务是如何实现的

    +

    候选人

    +

    spring实现的事务本质就是aop完成,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

    +

    面试官:Spring中事务失效的场景有哪些

    +

    候选人

    +

    嗯!这个在项目中之前遇到过,我想想啊

    +

    第一个,如果方法上异常捕获处理,自己处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了跑出去就行了

    +

    第二个,如果方法抛出检查异常,如果报错也会导致事务失效,最后在spring事务的注解上,就是@Transactional上配置rollbackFor属性为Exception,这样别管是什么异常,都会回滚事务

    +

    第三,我之前还遇到过一个,如果方法上不是public修饰的,也会导致事务失效

    +

    嗯,就能想起来那么多

    +

    面试官:Spring的bean的生命周期

    +

    候选人

    +

    嗯!,这个步骤还是挺多的,我之前看过一些源码,它大概流程是这样的

    +

    首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息,这里面就封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等等这些信息

    +

    在创建bean的时候,第一步是调用构造函数实例化bean

    +

    第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowire都是这一步完成

    +

    第三步是处理Aware接口,如果某一个bean实现了Aware接口就会重写方法执行

    +

    第四步是bean的后置处理器BeanPostProcessor,这个是前置处理器

    +

    第五步是初始化方法,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct

    +

    第六步是执行了bean的后置处理器BeanPostProcessor,主要是对bean进行增强,有可能在这里产生代理对象

    +

    最后一步是销毁bean

    +

    面试官:Spring中的循环引用

    +

    候选人

    +

    嗯,好的,我来解释一下

    +

    循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A

    +

    循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖

    +

    ①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象

    +

    ②二级缓存:缓存早期的bean对象(生命周期还没走完)

    +

    ③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

    +

    面试官:那具体解决流程清楚吗?

    +

    候选人

    +

    第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories

    +

    第二,A在初始化的时候需要B对象,这个走B的创建的逻辑

    +

    第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories

    +

    第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键

    +

    第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects

    +

    第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects

    +

    第七,二级缓存中的临时对象A清除

    +

    面试官:构造方法出现了循环依赖怎么解决?

    +

    候选人

    +

    由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建

    +

    面试官:SpringMVC的执行流程知道嘛

    +

    候选人

    +

    嗯,这个知道的,它分了好多步骤

    +

    1、用户发送出请求到前端控制器DispatcherServlet,这是一个调度中心

    +

    2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。

    +

    3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。

    +

    4、DispatcherServlet调用HandlerAdapter(处理器适配器)。

    +

    5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。

    +

    6、Controller执行完成返回ModelAndView对象。

    +

    7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。

    +

    8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。

    +

    9、ViewReslover解析后返回具体View(视图)。

    +

    10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

    +

    11、DispatcherServlet响应用户。

    +

    当然现在的开发,基本都是前后端分离的开发的,并没有视图这些,一般都是handler中使用Response直接结果返回

    +

    面试官:Springboot自动配置原理

    +

    候选人

    +

    嗯,好的,它是这样的。

    +

    在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:

    +
      +
    • @SpringBootConfiguration
    • +
    • @EnableAutoConfiguration
    • +
    • @ComponentScan
    • +
    +

    其中@EnableAutoConfiguration是实现自动化配置的核心注解。

    +

    该注解通过@Import注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。

    +

    在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。

    +

    一般条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。

    +

    面试官:Spring 的常见注解有哪些?

    +

    候选人

    +

    嗯,这个就很多了

    +

    第一类是:声明bean,有@Component、@Service、@Repository、@Controller

    +

    第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse

    +

    第三类是:设置作用域 @Scope

    +

    第四类是:spring配置相关的,比如@Configuration,@ComponentScan 和 @Bean

    +

    第五类是:跟aop相关做增强的注解 @Aspect,@Before,@After,@Around,@Pointcut

    +

    面试官:SpringMVC常见的注解有哪些?

    +

    候选人

    +

    嗯,这个也很多的

    +

    有@RequestMapping:用于映射请求路径;

    +

    @RequestBody:注解实现接收http请求的json数据,将json转换为java对象;

    +

    @RequestParam:指定请求参数的名称;

    +

    @PathViriable:从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数;@ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户端。@RequestHeader:获取指定的请求头数据,还有像@PostMapping、@GetMapping这些。

    +

    面试官:Springboot常见注解有哪些?

    +

    候选人

    +

    嗯~~

    +

    Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :

    +
      +
    • @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能;
    • +
    • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
    • +
    • @ComponentScan:Spring组件扫描
    • +
    +

    面试官:MyBatis执行流程

    +

    候选人

    +

    好,这个知道的,不过步骤也很多

    +

    ①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件

    +

    ②构造会话工厂SqlSessionFactory,一个项目只需要一个,单例的,一般由spring进行管理

    +

    ③会话工厂创建SqlSession对象,这里面就含了执行SQL语句的所有方法

    +

    ④操作数据库的接口,Executor执行器,同时负责查询缓存的维护

    +

    ⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息

    +

    ⑥输入参数映射

    +

    ⑦输出结果映射

    +

    面试官:Mybatis是否支持延迟加载?

    +

    候选人

    +

    是支持的~

    +

    延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。

    +

    Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载

    +

    在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false,默认是关闭的

    +

    面试官:延迟加载的底层原理知道吗?

    +

    候选人

    +

    嗯,我想想啊

    +

    延迟加载在底层主要使用的CGLIB动态代理完成的

    +

    第一是,使用CGLIB创建目标对象的代理对象,这里的目标对象就是开启了延迟加载的mapper

    +

    第二个是当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,再执行sql查询

    +

    第三个是获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

    +

    面试官:Mybatis的一级、二级缓存用过吗?

    +

    候选人

    +

    嗯~~,用过的~

    +

    mybatis的一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存

    +

    关于二级缓存需要单独开启

    +

    二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。

    +

    如果想要开启二级缓存需要在全局配置文件和映射文件中开启配置才行。

    +

    面试官:Mybatis的二级缓存什么时候会清理缓存中的数据

    +

    候选人

    +

    嗯!!

    +

    当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。

    +
    +

    pdf

    + + +
    + +
    + + + + +

    4.微服务篇

    +

    面试官:Spring Cloud 5大组件有哪些?

    +

    候选人:

    +

    早期我们一般认为的Spring Cloud五大组件是

    +
      +
    • Eureka : 注册中心
    • +
    • Ribbon : 负载均衡
    • +
    • Feign : 远程调用
    • +
    • Hystrix : 服务熔断
    • +
    • Zuul/Gateway : 网关
    • +
    +

    随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件

    +
      +
    • 注册中心/配置中心 Nacos
    • +
    • 负载均衡 Ribbon
    • +
    • 服务调用 Feign
    • +
    • 服务保护 sentinel
    • +
    • 服务网关 Gateway
    • +
    +

    面试官:服务注册和发现是什么意思?Spring Cloud 如何实现服务注册发现?

    +

    候选人:

    +

    我理解的是主要三块大功能,分别是服务注册 、服务发现、服务状态监控

    +

    我们当时项目采用的eureka作为注册中心,这个也是spring cloud体系中的一个核心组件

    +

    服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等等

    +

    服务发现:消费者向eureka拉取服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用

    +

    服务监控:服务提供者会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没接收到心跳,从eureka中剔除

    +

    面试官:我看你之前也用过nacos、你能说下nacos与eureka的区别?

    +

    候选人:

    +

    我们当时xx项目就是采用的nacos作为注册中心,选择nacos还要一个重要原因就是它支持配置中心,不过nacos作为注册中心,也比eureka要方便好用一些,主要相同不同点在于几点:

    +
      +
    • 共同点
    • +
    +

    Nacos与eureka都支持服务注册和服务拉取,都支持服务提供者心跳方式做健康检测

    +
      +
    • Nacos与Eureka的区别
    • +
    +

    ①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

    +

    ②临时实例心跳不正常会被剔除,非临时实例则不会被剔除

    +

    ③Nacos支持服务列表变更的消息推送模式,服务列表更新更及时

    +

    ④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

    +

    面试官:你们项目负载均衡如何实现的 ?

    +

    候选人:

    +

    是这样~~

    +

    在服务调用过程中的负载均衡一般使用SpringCloud的Ribbon 组件实现 , Feign的底层已经自动集成了Ribbon , 使用起来非常简单

    +

    当发起远程调用时,ribbon先从注册中心拉取服务地址列表,然后按照一定的路由策略选择一个发起远程调用,一般的调用策略是轮询

    +

    面试官:Ribbon负载均衡策略有哪些 ?

    +

    候选人:

    +

    我想想啊,有很多种,我记得几个:

    +
      +
    • RoundRobinRule:简单轮询服务列表来选择服务器
    • +
    • WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
    • +
    • RandomRule:随机选择一个可用的服务器
    • +
    • ZoneAvoidanceRule:区域敏感策略,以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询(默认)
    • +
    +

    面试官:如果想自定义负载均衡策略如何实现 ?

    +

    候选人:

    +

    提供了两种方式:

    +

    1,创建类实现IRule接口,可以指定负载均衡策略,这个是全局的,对所有的远程调用都起作用

    +

    2,在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略,只是对配置的这个服务生效远程调用

    +

    面试官:什么是服务雪崩,怎么解决这个问题?

    +

    候选人:

    +

    服务雪崩是指一个服务失败,导致整条链路的服务都失败的情形,一般我们在项目解决的话就是两种方案,第一个是服务降级,第二个是服务熔断,如果流量太大的话,可以考虑限流

    +

    服务降级:服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际开发中与feign接口整合,编写降级逻辑

    +

    服务熔断:默认关闭,需要手动打开,如果检测到 10 秒内请求的失败率超过 50%,就触发熔断机制。之后每隔 5 秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求

    +

    面试官:你们的微服务是怎么监控的?

    +

    候选人:

    +

    我们项目中采用的skywalking进行监控的

    +

    1,skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢,我们可以针对性的分析和优化。

    +

    2,我们还在skywalking设置了告警规则,特别是在项目上线以后,如果报错,我们分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目的bug情况,第一时间修复

    +

    面试官:你们项目中有没有做过限流 ? 怎么做的 ?

    +

    候选人:

    +

    我当时做的xx项目,采用就是微服务的架构,因为xx因为,应该会有突发流量,最大QPS可以达到2000,但是服务支撑不住,我们项目都通过压测最多可以支撑1200QPS。因为我们平时的QPS也就不到100,为了解决这些突发流量,所以采用了限流。

    +

    【版本1】

    +

    我们当时采用的nginx限流操作,nginx使用的漏桶算法来实现过滤,让请求以固定的速率处理请求,可以应对突发流量,我们控制的速率是按照ip进行限流,限制的流量是每秒20

    +

    【版本2】

    +

    我们当时采用的是spring cloud gateway中支持局部过滤器RequestRateLimiter来做限流,使用的是令牌桶算法,可以根据ip或路径进行限流,可以设置每秒填充平均速率,和令牌桶总容量

    +

    面试官:限流常见的算法有哪些呢?

    +

    候选人:

    +

    比较常见的限流算法有漏桶算法和令牌桶算法

    +

    漏桶算法是把请求存入到桶中,以固定速率从桶中流出,可以让我们的服务做到绝对的平均,起到很好的限流效果

    +

    令牌桶算法在桶中存储的是令牌,按照一定的速率生成令牌,每个请求都要先申请令牌,申请到令牌以后才能正常请求,也可以起到很好的限流作用

    +

    它们的区别是,漏桶和令牌桶都可以处理突发流量,其中漏桶可以做到绝对的平滑,令牌桶有可能会产生突发大量请求的情况,一般nginx限流采用的漏桶,spring cloud gateway中可以支持令牌桶算法

    +

    面试官:什么是CAP理论?

    +

    候选人

    +

    CAP主要是在分布式项目下的一个理论。包含了三项,一致性、可用性、分区容错性

    +
      +
    • 一致性(Consistency)是指更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致(强一致性),不能存在中间状态。
    • +
    • 可用性(Availability) 是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
    • +
    • 分区容错性(Partition tolerance) 是指分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
    • +
    +

    面试官:为什么分布式系统中无法同时保证一致性和可用性?

    +

    候选人

    +

    嗯,是这样的~~

    +

    首先一个前提,对于分布式系统而言,分区容错性是一个最基本的要求,因此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之间进行取舍。

    +

    如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。

    +

    如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据的话,这就违背了一致性的要求。

    +

    面试官:什么是BASE理论?

    +

    候选人

    +

    嗯,这个也是CAP分布式系统设计理论

    +

    BASE是CAP理论中AP方案的延伸,核心思想是即使无法做到强一致性(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。它的思想包含三方面:

    +

    1、Basically Available(基本可用):基本可用是指分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用。

    +

    2、Soft state(软状态):即是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

    +

    3、Eventually consistent(最终一致性):强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

    +

    面试官:你们采用哪种分布式事务解决方案?

    +

    候选人:

    +

    我们当时是xx项目,主要使用到的seata的at模式解决的分布式事务

    +

    seata的AT模型分为两个阶段:

    +

    1、阶段一RM的工作:① 注册分支事务 ② 记录undo-log(数据快照)③ 执行业务sql并提交 ④报告事务状态

    +

    2、阶段二提交时RM的工作:删除undo-log即可

    +

    3、阶段二回滚时RM的工作:根据undo-log恢复数据到更新前

    +

    at模式牺牲了一致性,保证了可用性,不过,它保证的是最终一致性

    +

    面试官:分布式服务的接口幂等性如何设计?

    +

    候选人:

    +

    嗯,我们当时有一个xx项目的下单操作,采用的token+redis实现的,流程是这样的

    +

    第一次请求,也就是用户打开了商品详情页面,我们会发起一个请求,在后台生成一个唯一token存入redis,key就是用户的id,value就是这个token,同时把这个token返回前端

    +

    第二次请求,当用户点击了下单操作会后,会携带之前的token,后台先到redis进行验证,如果存在token,可以执行业务,同时删除token;如果不存在,则直接返回,不处理业务,就保证了同一个token只处理一次业务,就保证了幂等性

    +

    面试官:xxl-job路由策略有哪些?

    +

    候选人:

    +

    xxl-job提供了很多的路由策略,我们平时用的较多就是:轮询、故障转移、分片广播…

    +

    面试官:xxl-job任务执行失败怎么解决?

    +

    候选人:

    +

    有这么几个操作

    +

    第一:路由策略选择故障转移,优先使用健康的实例来执行任务

    +

    第二,如果还有失败的,我们在创建任务时,可以设置重试次数

    +

    第三,如果还有失败的,就可以查看日志或者配置邮件告警来通知相关负责人解决

    +

    面试官:如果有大数据量的任务同时都需要执行,怎么解决?

    +

    候选人:

    +

    我们会让部署多个实例,共同去执行这些批量的任务,其中任务的路由策略是分片广播

    +

    在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行就可以了

    +
    +

    pdf

    + + +
    + +
    + + + + +

    5.消息中间件篇

    +

    面试官:RabbitMQ-如何保证消息不丢失

    +

    候选人

    +

    嗯!我们当时MYSQL和Redis的数据双写一致性就是采用RabbitMQ实现同步的,这里面就要求了消息的高可用性,我们要保证消息的不丢失。主要从三个层面考虑

    +

    第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据

    +

    第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化

    +

    第三个是开启消费者确认机制为auto,由spring确认消息处理成功后完成ack,当然也需要设置一定的重试次数,我们当时设置了3次,如果重试3次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理

    +

    面试官:RabbitMQ消息的重复消费问题如何解决的

    +

    候选人

    +

    嗯,这个我们还真遇到过,是这样的,我们当时消费者是设置了自动确认机制,当服务还没来得及给MQ确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了

    +

    因为我们当时处理的支付(订单|业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了

    +

    面试官:那你还知道其他的解决方案吗?

    +

    候选人

    +

    嗯,我想想~

    +

    其实这个就是典型的幂等的问题,比如,redis分布式锁、数据库的锁都是可以的

    +

    面试官:RabbitMQ中死信交换机 ? (RabbitMQ延迟队列有了解过嘛)

    +

    候选人

    +

    嗯!了解过!

    +

    我们当时的xx项目有一个xx业务,需要用到延迟队列,其中就是使用RabbitMQ来实现的。

    +

    延迟队列就是用到了死信交换机和TTL(消息存活时间)实现的。

    +

    如果消息超时未消费就会变成死信,在RabbitMQ中如果消息成为死信,队列可以绑定一个死信交换机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定TTL的时间,这样就实现了延迟队列的功能了。

    +

    我记得RabbitMQ还有一种方式可以实现延迟队列,在RabbitMQ中安装一个死信插件,这样更方便一些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超时时间就行了,相对于死信交换机+TTL要省略了一些步骤

    +

    面试官:如果有100万消息堆积在MQ , 如何解决 ?

    +

    候选人

    +

    我在实际的开发中,没遇到过这种情况,不过,如果发生了堆积的问题,解决方案也所有很多的

    +

    第一:提高消费者的消费能力 ,可以使用多线程消费任务

    +

    第二:增加更多消费者,提高消费速度

    +

    使用工作队列模式, 设置多个消费者消费消费同一个队列中的消息

    +

    第三:扩大队列容积,提高堆积上限

    +

    可以使用RabbitMQ惰性队列,惰性队列的好处主要是

    +

    ①接收到消息后直接存入磁盘而非内存

    +

    ②消费者要消费消息时才会从磁盘中读取并加载到内存

    +

    ③支持数百万条的消息存储

    +

    面试官:RabbitMQ的高可用机制有了解过嘛

    +

    候选人

    +

    嗯,熟悉的~

    +

    我们当时项目在生产环境下,使用的集群,当时搭建是镜像模式集群,使用了3台机器。

    +

    镜像队列结构是一主多从,所有操作都是主节点完成,然后同步给镜像节点,如果主节点宕机后,镜像节点会替代成新的主节点,不过在主从同步完成前,主节点就已经宕机,可能出现数据丢失

    +

    面试官:那出现丢数据怎么解决呢?

    +

    候选人

    +

    我们可以采用仲裁队列,与镜像队列一样,都是主从模式,支持主从数据同步,主从同步基于Raft协议,强一致。

    +

    并且使用起来也非常简单,不需要额外的配置,在声明队列的时候只要指定这个是仲裁队列即可

    +

    面试官:Kafka是如何保证消息不丢失

    +

    候选人

    +

    嗯,这个保证机制很多,在发送消息到消费者接收消息,在每个阶段都有可能会丢失消息,所以我们解决的话也是从多个方面考虑

    +

    第一个是生产者发送消息的时候,可以使用异步回调发送,如果消息发送失败,我们可以通过回调获取失败后的消息信息,可以考虑重试或记录日志,后边再做补偿都是可以的。同时在生产者这边还可以设置消息重试,有的时候是由于网络抖动的原因导致发送不成功,就可以使用重试机制来解决

    +

    第二个在broker中消息有可能会丢失,我们可以通过kafka的复制机制来确保消息不丢失,在生产者发送消息的时候,可以设置一个acks,就是确认机制。我们可以设置参数为all,这样的话,当生产者发送消息到了分区之后,不仅仅只在leader分区保存确认,在follwer分区也会保存确认,只有当所有的副本都保存确认以后才算是成功发送了消息,所以,这样设置就很大程度了保证了消息不会在broker丢失

    +

    第三个有可能是在消费者端丢失消息,kafka消费消息都是按照offset进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了

    +

    面试官:Kafka中消息的重复消费问题如何解决的

    +

    候选人

    +

    kafka消费消息都是按照offset进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了

    +

    为了消息的幂等,我们也可以设置唯一主键来进行区分,或者是加锁,数据库的锁,或者是redis分布式锁,都能解决幂等的问题

    +

    面试官:Kafka是如何保证消费的顺序性

    +

    候选人

    +

    kafka默认存储和消费消息,是不能保证顺序性的,因为一个topic数据可能存储在不同的分区中,每个分区都有一个按照顺序的存储的偏移量,如果消费者关联了多个分区不能保证顺序性

    +

    如果有这样的需求的话,我们是可以解决的,把消息都存储同一个分区下就行了,有两种方式都可以进行设置,第一个是发送消息时指定分区号,第二个是发送消息时按照相同的业务设置相同的key,因为默认情况下分区也是通过key的hashcode值来选择分区的,hash值如果一样的话,分区肯定也是一样的

    +

    面试官:Kafka的高可用机制有了解过嘛

    +

    候选人

    +

    嗯,主要是有两个层面,第一个是集群,第二个是提供了复制机制

    +

    kafka集群指的是由多个broker实例组成,即使某一台宕机,也不耽误其他broker继续对外提供服务

    +

    复制机制是可以保证kafka的高可用的,一个topic有多个分区,每个分区有多个副本,有一个leader,其余的是follower,副本存储在不同的broker中;所有的分区副本的内容是都是相同的,如果leader发生故障时,会自动将其中一个follower提升为leader,保证了系统的容错性、高可用性

    +

    面试官:解释一下复制机制中的ISR

    +

    候选人

    +

    ISR的意思是in-sync replica,就是需要同步复制保存的follower

    +

    其中分区副本有很多的follower,分为了两类,一个是ISR,与leader副本同步保存数据,另外一个普通的副本,是异步同步数据,当leader挂掉之后,会优先从ISR副本列表中选取一个作为leader,因为ISR是同步保存数据,数据更加的完整一些,所以优先选择ISR副本列表

    +

    面试官:Kafka数据清理机制了解过嘛

    +

    候选人

    +

    嗯,了解过~~

    +

    Kafka中topic的数据存储在分区上,分区如果文件过大会分段存储segment

    +

    每个分段都在磁盘上以索引(xxxx.index)和日志文件(xxxx.log)的形式存储,这样分段的好处是,第一能够减少单个文件内容的大小,查找数据方便,第二方便kafka进行日志清理。

    +

    在kafka中提供了两个日志的清理策略:

    +

    第一,根据消息的保留时间,当消息保存的时间超过了指定的时间,就会触发清理,默认是168小时( 7天)

    +

    第二是根据topic存储的数据大小,当topic所占的日志文件大小大于一定的阈值,则开始删除最久的消息。这个默认是关闭的

    +

    这两个策略都可以通过kafka的broker中的配置文件进行设置

    +

    面试官:Kafka中实现高性能的设计有了解过嘛

    +

    候选人

    +

    Kafka 高性能,是多方面协同的结果,包括宏观架构、分布式存储、ISR 数据同步、以及高效的利用磁盘、操作系统特性等。主要体现有这么几点:

    +

    消息分区:不受单台服务器的限制,可以不受限的处理更多的数据

    +

    顺序读写:磁盘顺序读写,提升读写效率

    +

    页缓存:把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问

    +

    零拷贝:减少上下文切换及数据拷贝

    +

    消息压缩:减少磁盘IO和网络IO

    +

    分批发送:将消息打包批量发送,减少网络开销

    +
    +

    pdf

    + + +
    + +
    + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/47003.html b/posts/47003.html new file mode 100644 index 000000000..7a43c6735 --- /dev/null +++ b/posts/47003.html @@ -0,0 +1,198 @@ +常用正则表达式大全 | The Blog + + + + + + + + + + + + +

    常用正则表达式大全

    一、校验数字的表达式
    1 数字:^[0-9]$
    2 n位的数字:^\d{n}$
    3 至少n位的数字:^\d{n,}$
    4 m-n位的数字:^\d{m,n}$
    5 零和非零开头的数字:^(0|[1-9][0-9])$
    6 非零开头的最多带两位小数的数字:^([1-9][0-9])+(.[0-9]{1,2})?$
    7 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?$
    8 正数、负数、和小数:^(-|+)?\d+(.\d+)?$
    9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
    10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
    11 非零的正整数:^[1-9]\d$ 或 ^([1-9][0-9]){1,3}$ 或 ^+?[1-9][0-9]$
    12 非零的负整数:^-[1-9][]0-9”$ 或 ^-[1-9]\d$
    13 非负整数:^\d+$ 或 ^[1-9]\d|0$
    14 非正整数:^-[1-9]\d|0$ 或 ^((-\d+)|(0+))$
    15 非负浮点数:^\d+(.\d+)?$ 或 ^[1-9]\d.\d|0.\d[1-9]\d|0?.0+|0$
    16 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d.\d|0.\d[1-9]\d))|0?.0+|0$
    17 正浮点数:^[1-9]\d.\d|0.\d[1-9]\d$ 或 ^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$
    18 负浮点数:^-([1-9]\d.\d|0.\d[1-9]\d)$ 或 ^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$
    19 浮点数:^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d.\d|0.\d[1-9]\d|0?.0+|0)$

    +

    二、校验字符的表达式
    1 汉字:^[\u4e00-\u9fa5]{0,}$
    2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
    3 长度为3-20的所有字符:^.{3,20}$
    4 由26个英文字母组成的字符串:^[A-Za-z]+$
    5 由26个大写英文字母组成的字符串:^[A-Z]+$
    6 由26个小写英文字母组成的字符串:^[a-z]+$
    7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
    8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
    9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
    10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
    11 可以输入含有^%&’,;=?$"等字符:[^%&’,;=?$\x22]+
    12 禁止输入含有的字符:[^\x22]+

    +

    三、特殊需求表达式
    1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)$
    2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
    3 InternetURL:[a-zA-z]+://[^\s] 或 ^https://([\w-]+.)+[\w-]+(/[\w-./?%&=])?$
    4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
    5 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$
    6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
    7 身份证号:
    15或18位身份证:^\d{15}|\d{18}$
    15位身份证:^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$
    18位身份证:^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$
    8 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
    9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
    10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
    11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$
    12 日期格式:^\d{4}-\d{1,2}-\d{1,2}
    13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
    14 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
    15 钱的输入格式:
    16 1.有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]$
    17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9])$
    18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9])$
    19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
    20 5.必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$
    21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
    22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})(.[0-9]{1,2})?$
    23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3}))(.[0-9]{1,2})?$
    24 备注:这就是最终结果了,别忘了”+”可以用””替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
    25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$
    26 中文字符的正则表达式:[\u4e00-\u9fa5]
    27 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
    28 空白行的正则表达式:\n\s\r (可以用来删除空白行)
    29 HTML标记的正则表达式:<(\S?)[^>]>.?|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
    30 首尾空白字符的正则表达式:^\s|\s$或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
    31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
    32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
    33 IP地址:\d+.\d+.\d+.\d+ (提取IP地址时有用)

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/47407.html b/posts/47407.html new file mode 100644 index 000000000..9e5b0a80b --- /dev/null +++ b/posts/47407.html @@ -0,0 +1,248 @@ +VMWare虚拟机安装Linux教程 | The Blog + + + + + + + + + + + + +

    VMWare虚拟机安装Linux教程

    1.下载CentOs镜像文件

    这里下载的是7版本的 如果有其他的需要 自行安装其他的版本

    +

    下载完成之后 记录好安装的位置

    +

    阿里镜像下载地址:https://mirrors.aliyun.com/centos/7/isos/x86_64/?spm=a2c6h.25603864.0.0.4eab4511uQRsgc

    +

    image-20230510224239950

    +

    2.下载VMWare 虚拟机

    这里下载的是17版本的VMWare ,可以免费试用30天,需要许可证的可自行百度搜索

    +

    官网下载地址: https://download3.vmware.com/software/WKST-1701-WIN/VMware-workstation-full-17.0.1-21139696.exe

    +

    安装步骤:

    +

    image-20230510222210848

    +

    3.配置虚拟机 (以CentOs为例)

    CentOs的不同版本在VmWare上面的配置略有不同

    +

    CentOs 7.6版本的配置教程

    + +
    + +
    + + + + +

    CentOs 8.1版本的配置教程

    + +
    + +
    + + + + + + +

    连接Linux 的远程工具

    其实本地安装Linux大可不必安装远程控制工具 直接进入Linux里面控制即可

    +

    image-20230510225825430

    +

    XShell6的安装和配置

    1.安装

    下载地址: https://wwvc.lanzouy.com/ipF2w0v6np8j

    +

    下载解压之后双击.exe文件 点击下一步安装 直到安装完成

    +

    2.配置

    1.获取Linux的ip地址信息 并记录ip的信息

    +
    1
    2
    3
    # 命令 
    # 注意这里没有写错 就是ifconfig 不是ipconfig,ipconfig是windows系统的查看ip的命令
    ifconfig
    + +

    image-20230510230114550

    +

    2.打开xShell工具

    +

    2.1创建会话

    +

    image-20230510230758405

    +

    2.2 选择会话并连接

    +

    image-20230510230937843

    +

    image-20230510231329208

    +

    image-20230510231432103

    +

    2.3 用户名和密码正确之后就连接成功

    +

    连接成功如下图

    +

    image-20230510231607314

    +

    完整的配置过程

    +

    image-20230510232029231

    +

    FinalShell的下载地址

    下载地址: http://www.hostbuf.com/downloads/finalshell_install.exe

    +

    Tips:

    +

    ​ 这里我们没有设置Linux的静态IP,电脑每次网络变化之后,IP地址会发生变化,这时我们需要获取最新的IP地址,重新执行以上的连接步骤。如果想要IP地址固定,我们可以设置静态IP,设置静态的IP的方法如下:

    +

    Linux设置静态IP | The Blog (gitee.io)

    +
    \ No newline at end of file diff --git a/posts/48020.html b/posts/48020.html new file mode 100644 index 000000000..6ec509e52 --- /dev/null +++ b/posts/48020.html @@ -0,0 +1,203 @@ +Java程序设计基础 | The Blog + + + + + + + + + + + + +

    Java程序设计基础

    韩顺平零基础学java完整版笔记

    + +
    + +
    + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/50465.html b/posts/50465.html new file mode 100644 index 000000000..7c5c90758 --- /dev/null +++ b/posts/50465.html @@ -0,0 +1,226 @@ +MySql基础进阶运维篇PDF笔记 | The Blog + + + + + + + + + + + + +

    MySql基础进阶运维篇PDF笔记

    1.PDF笔记

    1.1MySql基础篇PDF笔记

    + +
    + +
    + + + + +

    1.2MySql进阶篇PDF笔记

    + +
    + +
    + + + + +

    1.3MySql运维篇PDF笔记

    + +
    + +
    + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/50908.html b/posts/50908.html new file mode 100644 index 000000000..8f7bdd732 --- /dev/null +++ b/posts/50908.html @@ -0,0 +1,247 @@ +ElementUI使用示例 | The Blog + + + + + + + + + + + + +

    ElementUI使用示例

    ElementUI官网

    +

    一.树形显示-树形控件

    1.树形显示

    示例-以谷粒商城项目的商品分类为例

    +

    数据中商品分类的数据表,所有的分类数据在同一张表中

    +image-20230530174911409 + +

    商品分类的实体类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    package com.atguigu.gulimall.product.entity;

    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;

    import java.io.Serializable;
    import java.util.Date;
    import java.util.List;

    import lombok.Data;

    /**
    * 商品三级分类
    *
    * @author JasonGong
    * @email JasonGong@gmail.com
    * @date 2023-05-19 00:23:36
    */
    @Data
    @TableName("pms_category")
    public class CategoryEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
    * 分类id
    */
    @TableId
    private Long catId;
    /**
    * 分类名称
    */
    private String name;
    /**
    * 父分类id
    */
    private Long parentCid;
    /**
    * 层级
    */
    private Integer catLevel;
    /**
    * 是否显示[0-不显示,1显示]
    */
    private Integer showStatus;
    /**
    * 排序
    */
    private Integer sort;
    /**
    * 图标地址
    */
    private String icon;
    /**
    * 计量单位
    */
    private String productUnit;
    /**
    * 商品数量
    */
    private Integer productCount;

    @TableField(exist = false)//这个注解要加,因为这个属性不是数据库中的字段
    private List<CategoryEntity> children;//这里的属性名不是瞎起的,与ElementUI中的显示相对应,这里改了,前端也要改

    }
    + +

    封装数据,供前端显示

    +

    controller层

    +
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 查询所有的分类以及子分类,以树形结构组装起来
    */
    @RequestMapping("/list/tree")
    public R list(){
    List<CategoryEntity> entities = categoryService.listWithTree();
    return R.ok().put("data", entities);
    }
    + +

    service层,这里主要有一个单独的递归方法,使用的时候会改就行

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    /**
    * 查询所有分类,以树形结构显示
    */
    @Override
    public List<CategoryEntity> listWithTree() {
    //查出所有的分类
    List<CategoryEntity> entities = categoryDao.selectList(null);
    //组装成父子的树形结构
    //找到所有的一级分类
    List<CategoryEntity> level1List = entities.stream().filter(categoryEntity ->
    //父级分类的id等于0的就是一级分类
    categoryEntity.getParentCid() == 0
    ).map((menu) -> {
    //为当前分类的子分类赋值
    menu.setChildren(getChildren(menu, entities));
    return menu;
    }).sorted((menu1, menu2) -> {
    //排序
    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());

    return level1List;
    }

    /**
    * 递归查询所有菜单的子菜单
    *
    * @param root 当前分类
    * @param all 所有的分类数据
    * @return 子菜单的集合
    */
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
    List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
    return categoryEntity.getParentCid().equals(root.getCatId());
    }).map(categoryEntity -> {
    //1.递归找子菜单
    categoryEntity.setChildren(getChildren(categoryEntity, all));
    return categoryEntity;
    }).sorted((menu1, menu2) -> {
    //2.菜单的排序
    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());
    return children;
    }
    + +

    返回数据的格式

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    {
    "msg": "success",
    "code": 0,
    "data": [
    {
    "catId": 1,
    "name": "图书、音像、电子书刊",
    "parentCid": 0,
    "catLevel": 1,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": [
    {
    "catId": 22,
    "name": "电子书刊",
    "parentCid": 1,
    "catLevel": 2,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": [
    {
    "catId": 165,
    "name": "电子书",
    "parentCid": 22,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 166,
    "name": "网络原创",
    "parentCid": 22,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 167,
    "name": "数字杂志",
    "parentCid": 22,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 168,
    "name": "多媒体图书",
    "parentCid": 22,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    }
    ]
    },
    {
    "catId": 23,
    "name": "音像",
    "parentCid": 1,
    "catLevel": 2,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": [
    {
    "catId": 169,
    "name": "音乐",
    "parentCid": 23,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 170,
    "name": "影视",
    "parentCid": 23,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 171,
    "name": "教育音像",
    "parentCid": 23,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    }
    ]
    },
    略.............................
    + +

    前端代码

    +

    模板

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <template>
    <div>
    <!-- 树形控件 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    @node-click="handleNodeClick"
    ></el-tree>
    </div>
    </template>

    <script>
    export default {
    data() {
    return {
    data: [],//发送请求获取的数据(比如 return R.ok().put("data", entities);里面的data)
    defaultProps: {
    children: 'children',//实体类中的属性(比如:private List<CategoryEntity> children;)
    label: 'label'//需要显示的属性的属性名(比如商品分类名,属性名是name,这里就填name)
    }
    };
    },
    methods: {
    handleNodeClick(data) {
    console.log(data);
    }
    }
    };
    </script>
    + +

    完整代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <template>
    <div>
    <!-- 树形控件 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    @node-click="handleNodeClick"
    ></el-tree>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {//这里的配置看官方的文档
    children: "children",//父节点上的子节点
    label: "name",//每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    handleNodeClick(data) {
    console.log(data);
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    显示效果

    +image-20230530181102832 + +

    2.树形控件的删除、添加

    页面的显示效果

    +

    image-20230605145343384

    +

    使用的组件 在树形控件的基础上添加相关的代码

    +image-20230605145433594 + +

    添加的时候弹出的对话框使用的组件

    +image-20230605153935967 + +image-20230605154629290 + +

    前端代码的实现

    +

    Tips:删除成功之后依然展开删除的节点

    +

    删除和添加的前端代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    <template>
    <div>
    <!-- 树形控件-->
    <!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
    <!-- show-checkbox表示开启选择框,批量选择-->
    <!-- node-key表示在整个树中唯一的表示 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <= 2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >
    添加
    </el-button>
    <!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
    <el-button
    v-if="data.children.length <= 0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >
    删除
    </el-button>
    </span>
    </span></el-tree
    >
    <!-- 添加分类的时候弹出的对话框 -->
    <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
    <el-form :model="category">
    <el-form-item label="分类名称">
    <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCategory">确 定</el-button>
    </span>
    </el-dialog>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {
    //这里的配置看官方的文档
    children: "children", //父节点上的子节点
    label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    expandedKey: [], //需要展开的数组,用于删除之后树形控件仍然展开
    dialogVisible: false, //添加菜单的对话框的开启或者关闭
    //表单绑定的对象
    category: {
    name: "", //分类的名称
    parentCid: 0, //父类的id
    catLevel: 0, //分类的级别
    showStatus: 1, //是否显示该分类
    sort: 0, //排序
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    //添加分类(弹出的对话框确定按钮执行的方法)
    addCategory() {
    console.log("提交的数据:", this.category);
    //将添加的数据返回给后端
    this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单添加成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus()
    //设置需要默认展开的菜单(依然展开刚才添加的节点)
    this.expandedKey = [this.category.parentCid];
    });
    //关闭对话框
    this.dialogVisible = false;
    },
    //添加分类的方法
    append(data) {
    //清空一下表单的数据
    this.category.name = "";
    //打开添加的对话框
    this.dialogVisible = true;
    //为分类的对象赋值
    this.category.parentCid = data.catId;
    this.category.catLevel = data.catLevel * 1 + 1; //防止catLevel是一个字符串
    },
    //删除节点的方法
    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才删除的节点)
    this.expandedKey = [node.parent.data.catId];
    });
    })
    .catch(() => {
    this.$message({
    type: "info",
    message: "已取消删除",
    });
    });
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    后端代码

    +

    删除的后端代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 删除
    */
    @RequestMapping("/delete")
    //@RequiresPermissions("product:category:delete")
    public R delete(@RequestBody Long[] catIds) {
    //检查当前删除的菜单,是否被别的地方引用
    //categoryService.removeByIds(Arrays.asList(catIds));
    categoryService.removeMenuByIds(Arrays.asList(catIds));
    return R.ok();
    }
    + +

    添加的后端代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 保存
    */
    @RequestMapping("/save")
    //@RequiresPermissions("product:category:save")
    public R save(@RequestBody CategoryEntity category) {
    categoryService.save(category);

    return R.ok();
    }
    + +

    实现的效果

    +

    image-20230605163004850

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/51007.html b/posts/51007.html new file mode 100644 index 000000000..e755a9535 --- /dev/null +++ b/posts/51007.html @@ -0,0 +1,213 @@ +常用的DOS命令 | The Blog + + + + + + + + + + + + +

    常用的DOS命令

    1.一些基础的DOS命令

    1
    c:\javacode>dir	  //查看C盘javacode目录下有那些文件
    + +
    1
    c:\javacode>cd /D d:   //从C盘切换到D盘
    + +
    1
    c:\javacode>help cd	  //解释cd是怎么使用的
    + +
    1
    c:\javacode>cd ..     //切换到上一级目录
    + +
    1
    c:\javacode>cd \      //切换到根目录
    + +
    1
    C:\tree c:\javacode   //查看C盘下的子目录javacode下面的所有子目录,形成一个目录树
    + +
    1
    2
    3
    4
    5
    6
    7
    >cls    //清空所有内容,清屏
    >exit //退出控制台
    md[创建目录],rd[删除目录],copy[拷贝文件],del[删除文件],echo[输入内容到文件],type,move[剪切]
    >echo ok >pic.txt //创建pic.txt并把ok输入到pic.txt中
    >echo nul >pic.txt //创建一个空文件pic.txt
    >copy ok.txt d:\ok.txt //把ok.txt从当前路径拷贝到D盘下面,可以更改第二个ok.txt的文件名
    >move ok.txt d:\ok.txt //把ok.txt剪切到D盘下面
    + +

    2.网络编程相关的DOS命令

    1
    2
    3
    4
    5
    6
    7
    8
    //获取IP相关的信息
    >ipconfig
    //端口监听和网络监听情况
    >netstat -an
    >netstat -an | more//分页显示
    >netstat -anb
    //ping命令,查看网络是否连通
    >ping IP地址或者域名
    + +

    3.MySql的相关DOS命令

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    //开启MySQL和关闭MySQL服务
    >net start mysql //开
    >new stop mysql //关


    //登录mysql的命令
    >mysql -u 用户名 -p密码 //密码可以先不输入,直接回车输入,或者直接写在p的后面,中间不要空格



    //连接mysql服务(mysql数据库)的指令
    >mysql -h 主机ip -P端口 -u 用户名 -p密码
    //tips:
    //p和密码中间不要有空格
    //-p后面没有密码的话,会让回车的时候输入密码
    //如果没有写-h主机,默认就是本机
    //如果没有写-P端口,默认就是3306
    //在实际的工作中端口一般会修改,使用3306这一端口,容易被黑客发现并攻击





    //备份数据库的命令
    //备份整个数据库
    >mysqldump -u root -p -B 数据库 数据库 > c:\\test\\bak.sql
    //备份数据库的某个表
    >mysqldump -u root -p 数据库 表1 表2 表n > c:\\test\\bak.sql
    //这里就是把数据库hsp_db02 和数据库hsp_db03备份在C盘下面test目录中一个叫bak.sql的文件中,这里备份的是sql语句




    //根据备份的文件,恢复数据库
    //第一种方式
    >source c:\\test\\bak.sql //根据C盘下面的c:\\test\\bak.sql的文件恢复数据库
    //第二种方式
    //直接把这个.sql的文件再执行一遍,就会恢复两个数据库了

    + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/53088.html b/posts/53088.html new file mode 100644 index 000000000..838e31748 --- /dev/null +++ b/posts/53088.html @@ -0,0 +1,209 @@ +个人简历 | The Blog + + + + + + + + + + + + +

    个人简历

    + +
    +
    + + + + + +
    +
    +
    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/53306.html b/posts/53306.html new file mode 100644 index 000000000..98c8ad8e1 --- /dev/null +++ b/posts/53306.html @@ -0,0 +1,216 @@ +Mybatis-Plus的使用教程 | The Blog + + + + + + + + + + + + +

    Mybatis-Plus的使用教程

    官方文档:MyBatis-Plus

    +

    常用插件

    1.公共字段填充

    对于每个实体类共有的属性字段,例如创建时间、修改时间、创建人、修改人,我们可以使用公共字段填充,来统一填充这些字段,这样我们在创建这些实体类的对象的时候就不需要set这些属性,实现丝滑开发

    +
    1
    2
    3
    4
    @TableField(fill = FieldFill.INSERT) //插入的时候填充
    private String gmtCreate;
    @TableField(fill = FieldFill.INSERT_UPDATE)//插入和修改的时候填充
    private String gmtModified;
    + +

    第一种配置的方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package com.itheima.config;

    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;

    import java.time.LocalDateTime;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/25
    * @Description 公共字段填充的配置
    */
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
    * 插入时的字段填充
    * @param metaObject 元对象
    */
    @Override
    public void insertFill(MetaObject metaObject) {
    //创建时间的字段填充
    metaObject.setValue("gmtCreate", LocalDateTime.now());
    //修改时间的字段填充
    metaObject.setValue("gmtModified", LocalDateTime.now());
    }

    /**
    * 修改时的字段填充
    * @param metaObject 元对象
    */
    @Override
    public void updateFill(MetaObject metaObject) {
    //修改时间的字段填充
    metaObject.setValue("gmtModified", LocalDateTime.now());
    }
    }
    + +

    第二种配置的方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
    // 起始版本 3.3.0(推荐使用)
    this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    // 或者
    this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
    // 或者
    // 也可以使用(3.3.0 该方法有bug)
    this.fillStrategy(metaObject, "createTime", LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
    // 起始版本 3.3.0(推荐)
    this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    // 或者
    this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
    // 或者
    // 也可以使用(3.3.0 该方法有bug)
    this.fillStrategy(metaObject, "updateTime", LocalDateTime.now());
    }
    }
    + +

    2.逻辑删除

    1.配置全局的配置文件(可以省略)

    +
    1
    2
    3
    4
    5
    mybatis-plus:
    global-config:
    db-config:
    logic-delete-value: 1 # 逻辑已删除值(默认为 1)
    logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    + +

    2.配置逻辑删除的组件(3.1.1版本之后不需要这一步,可以省略)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import com.baomidou.mybatisplus.core.injector.ISqlInjector;
    import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    public class MyBatisPlusConfiguration {

    @Bean
    public ISqlInjector sqlInjector() {
    return new LogicSqlInjector();
    }
    }
    + +

    3.在实体类上逻辑删除的字段上加上逻辑删除的注解

    +
    1
    2
    3
    4
    //value:表示不删除对应的值
    //delval:表示删除对应的值
    //正常的情况下1代表删除,0代表未删除,这里我们没有使用这个规则,下面设置的是自己的规则
    @TableLogic(value = "1",delval = "0")
    + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/54835.html b/posts/54835.html new file mode 100644 index 000000000..ef76e4f41 --- /dev/null +++ b/posts/54835.html @@ -0,0 +1,238 @@ +Thymeleaf教程 | The Blog + + + + + + + + + + + + +

    Thymeleaf教程

    Thymeleaf 是一个服务器端 Java 模板引擎,能够处理 HTML、XML、CSS、JAVASCRIPT 等模板文件。Thymeleaf 模板可以直接当作静态原型来使用,它主要目标是为开发者的开发工作流程带来优雅的自然模板,也是 Java 服务器端 HTML5 开发的理想选择。

    +

    image-20230706152329777

    +

    教程: https://www.thymeleaf.org

    +

    项目集成Thymeleaf

    1.导入依赖

    +
    1
    2
    3
    4
    5
    <!-- thymeleaf-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    + +

    2.添加配置

    +
    1
    spring.thymeleaf.cache=false #关闭缓存(后面使用devtools进行热更新也要关闭缓存)
    + +

    3.在HTML文件中引入名称空间

    +
    1
    <html xmlns:th="http://www.thymeleaf.org">
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <title>标题</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
    <p th:text="#{home.welcome}">Welcome to our grocery store!</p>
    </body>
    </html>
    + +

    Thymeleaf

    devtools热部署

    Tips:在使用Thymeleaf的时候修改页面我们要频繁的重启项目,非常浪费时间,这时我们可以导入devtools进行热部署

    +
    1
    2
    3
    4
    5
    6
    <!--devtools-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    + +

    导入之后重启项目,会显示如下的标识

    +

    image-20230706154112026

    +

    每次修改页面之后,我们只需要在当前文件下重新build一下(Ctrl+Shift+F9),就可以实现热更新

    +

    image-20230706161401528

    +

    使用文档

    英文文档: https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html

    +

    博客文章: https://fanlychie.github.io/post/thymeleaf.html

    +

    中文PDF文档

    + + +
    + +
    + + + + + + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/56742.html b/posts/56742.html new file mode 100644 index 000000000..1a60eb85e --- /dev/null +++ b/posts/56742.html @@ -0,0 +1,215 @@ +SSM框架基础知识及整合 | The Blog + + + + + + + + + + + + +

    SSM框架基础知识及整合

    PDF版本的笔记

    +

    Mybatis

    + + +
    + +
    + + + + +

    Spring SpringMVC MyBatis

    + + +
    + +
    + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/5727.html b/posts/5727.html new file mode 100644 index 000000000..615753593 --- /dev/null +++ b/posts/5727.html @@ -0,0 +1,223 @@ +免费域名注册教程 | The Blog + + + + + + + + + + + + +

    免费域名注册教程

    一.域名注册

    免费域名注册网址:https://nic.eu.org

    +

    image-20230913123344514

    +

    1.注册账号

    第一步:点击: here

    +

    image-20230913123704246

    +

    第二步: 点击Register

    +

    image-20230913123809940

    +

    第三步:填写相关的信息

    +

    地址信息填写英国的地址,可以使用在线地址生成器生成:https://www.meiguodizhi.com/uk-address

    +

    image-20230913124042655

    +

    填写地址相关的信息,其余的信息根据自己的情况填写(fax可不填)

    +

    image-20230913124616244

    +

    显示下面的提示就是注册成功了

    +

    image-20230913124842536

    +

    这时在系统会给刚才注册的邮箱发送一个激活链接,点击激活

    +

    image-20230913125129535

    +

    打开链接之后点击的按钮Validate验证,验证之后显示如下页面表示成功

    +

    image-20230913125324974

    +

    2.登录

    登录的账号就在刚才发送的邮件里面,密码就是注册时设置的密码

    +

    image-20230913125539561

    +

    3.申请域名

    输入你要注册的域名,域名的格式是 xxxx.eu.org

    +

    image-20230913132602702

    +

    域名验证,在输入框中填写下面的信息

    +
    1
    2
    grannbo.ns.cloudflare.com
    sid.ns.cloudflare.com
    + +

    image-20230913132742999

    +

    点击Submit按钮之后显示如下的内容表示这个免费的域名是可以申请到的,然后等待人工审核通过

    +

    image-20230913132918535

    +

    审核通过之后,你注册时使用的邮箱就会收到信息

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/60684.html b/posts/60684.html new file mode 100644 index 000000000..bfdf498d5 --- /dev/null +++ b/posts/60684.html @@ -0,0 +1,1541 @@ +SpringBoot入门教程 | The Blog + + + + + + + + + + + + +

    SpringBoot入门教程

    尚硅谷雷丰阳的SpringBoot零基础入门教程

    +

    本视频笔记地址:https://yuque.com/atguigu/springboot

    +

    本视频源码地址:https://gitee.com/leifengyang/springboot2

    +

    Spring官网: https://spring.io/

    +

    SpringBoot2的环境要求

    +

    image-20230620092919922

    +

    一.SpringBoot2核心技术-基础入门

    1.Spring能做什么

    这里的Spring指的是整个Spring生态

    +

    image-20230620093420150

    +

    微服务 响应式编程 分布式 WEB开发 无服务开发 事件驱动 批处理

    +

    2.SpringBoot

    Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

    +

    能快速创建出生产级别的Spring应用

    +

    SpringBoot是整合Spring技术栈的一站式框架

    +

    SpringBoot是简化Spring技术栈的快速开发脚手架

    +

    2.1 SpringBoot的优点

    ● Create stand-alone Spring applications
    ○ 创建独立Spring应用
    ● Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
    ○ 内嵌web服务器
    ● Provide opinionated ‘starter’ dependencies to simplify your build configuration
    ○ 自动starter依赖,简化构建配置
    ● Automatically configure Spring and 3rd party libraries whenever possible
    ○ 自动配置Spring以及第三方功能
    ● Provide production-ready features such as metrics, health checks, and externalized configuration
    ○ 提供生产级别的监控、健康检查及外部化配置
    ● Absolutely no code generation and no requirement for XML configuration
    ○ 无代码生成、无需编写XML

    +

    2.2、SpringBoot缺点

      +
    • 人称版本帝,迭代快,需要时刻关注变化(说明社区活跃,也是个优点)
    • +
    • 封装太深,内部原理复杂,不容易精通
    • +
    +

    2.3 官方文档

    进入官网 找到SpringBoot 点击Reference Doc进入官方文档

    +

    image-20230620101533445

    +

    3.SpringBoot入门

    1.系统的要求

      +
    • Java 8 & 兼容java14 .
    • +
    • Maven 3.3+
    • +
    • idea 2019.1.2
    • +
    +

    **Maven的配置教程: **

    +

    Maven配置文件settings.xml | The Blog (gitee.io)

    +

    开发环境的搭建 | The Blog (gitee.io)

    +

    2.HelloWorld

    需求:浏览发送/hello请求,响应 Hello,Spring Boot 2

    +

    1创建maven工程

    +

    image-20230725160846882

    +

    2.引入依赖

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
    </parent>
    <!-- WEB场景的启动器 -->
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    </dependencies>
    + +

    3.创建主程序

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.atguigu.boot;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/25
    * @Description
    */
    @SpringBootApplication //标注这是一个SpringBoot应用
    public class MainApplication {
    public static void main(String[] args) {
    SpringApplication.run(MainApplication.class,args);
    }
    }
    + +

    4.编写Controller

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.atguigu.boot.controller;

    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/25
    * @Description
    */
    @RestController
    public class HelloController {

    @RequestMapping
    public String handle01(){
    return "Hello,Spring Boot2";
    }
    }
    + +image-20230725162201612 + +

    3.简化配置

    官网地址:https://docs.spring.io/spring-boot/docs/2.7.14/reference/html/application-properties.html#appendix.application-properties

    +
    1
    server.port=8888
    + +

    4.简化部署

    通过在pom.xml文件中插入这个插件,可以直接将项目打成jar包简化部署

    +
    1
    2
    3
    4
    5
    6
    7
    8
     <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>
    + +

    image-20230725163026091

    +

    可以直接使用cmd切换到target目录下使用java -jar 命令运行jar包

    +

    image-20230725163212347

    +

    注意点

    +

    去掉cmd的快速编辑模式(不去掉启动的时候点击屏幕会停止)

    +

    image-20230725164124574

    +

    4.了解自动装配原理

    4.1 依赖管理

    1.SpringBoot的自动版本仲裁机制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- 项目的继承的父项目-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
    </parent>
    <!-- 父项目继承的父项目-->
    <!--几乎声明了开发中常用依赖的版本号-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.4.RELEASE</version>
    </parent>
    + +

    image-20230725165414051

    +

    自定义mysql版本驱动

    +

    在父依赖中mysql是8版本的

    +

    image-20230725170329032

    +

    可以在pom.xml中通过自定义properties修改版本

    +
    1
    2
    3
    4
    <properties>
    <!--重写mysql的版本号-->
    <mysql.version>5.1.43</mysql.version>
    </properties>
    + +

    image-20230725170257030

    +

    2.Starters场景启动器

    官网: https://docs.spring.io/spring-boot/docs/2.7.14/reference/html/using.html#using.build-systems.starters

    +

    spring-boot-starter-*起头的starter为官方提供的starter,*-spring-boot-starter为第三方提供的starter

    +

    常见的Starter

    +

    image-20230725171130925

    +

    以引入spring-boot-starter-web为例,引入该starter它会帮我们引入以下的其它依赖

    +

    image-20230725171550618

    +

    4.2 自动配置

      +
    • 自动配置Tomcat

      +
        +
      1. 引入依赖

        +

        image-20230725172719608

        +
      2. +
      3. 配置Tomcat

        +
      4. +
      +
    • +
    • 自动配置SpringMvc

      +
        +
      1. 引入springMvc全套组件

        +
      2. +
      3. 自动配置好springMvc的常用组件(字符编码,文件上传解析器,dispatcherServlet等)

        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        @SpringBootApplication
        public class MainApplication {
        public static void main(String[] args) {
        //返回ioc容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        //获取容器里面组件的名字
        String[] beanDefinitionNames = run.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
        //打印容器内的组件
        System.out.println(beanDefinitionName);
        }
        }
        }
        + +

        image-20230725172834666

        +
      4. +
      +
    • +
    • 默认的包结构

      +

      主程序所在的包以及其的子包都可以被扫描到,无需配置包扫描

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      com
      +- example
      +- myapplication
      +- MyApplication.java #主程序
      |
      +- customer
      | +- Customer.java
      | +- CustomerController.java
      | +- CustomerService.java
      | +- CustomerRepository.java
      |
      +- order
      +- Order.java
      +- OrderController.java
      +- OrderService.java
      +- OrderRepository.java
      + +

      如果需要扫描的文件在主程序的上级目录,我们也想扫描到它,我们需要扩大一下包的扫描范围

      +
      1
      2
      3
      @SpringBootApplication(scanBasePackages = "com.atguigu")
      或者
      @ComponentScan("com.atguigu")
      +
    • +
    • 各种配置拥有默认值

      +

      image-20230725174350129

      +
    • +
    • 按需加载所有的自动配置项

      +

      spingboot的所有配置存在于下面的这个依赖中

      +
      1
      2
      3
      4
      5
      6
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
      </dependency>
      + +

      image-20230725175010683

      +
    • +
    +

    4.3 容器功能

    1.组件添加

    1.1 @Configuration
      +
    • 基本使用

      +
    • +
    • Full模式与Lite模式

      +
    • +
      • +
      • 示例
      • +
      • 最佳实战
      • +
      +
    • +
      • +
        • +
        • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
        • +
        • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
        • +
        +
      • +
      +
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**
    * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
    * 2、配置类本身也是组件
    * 3、proxyBeanMethods:代理bean的方法
    * Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
    * Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
    * 组件依赖必须使用Full模式默认。其他默认是否Lite模式
    */
    @Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
    public class MyConfig {

    /**
    * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
    * @return
    */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
    User zhangsan = new User("zhangsan", 18);
    //user组件依赖了Pet组件
    zhangsan.setPet(tomcatPet());
    return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
    return new Pet("tomcat");
    }
    }
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan("com.atguigu.boot")
    public class MainApplication {

    public static void main(String[] args) {
    //1、返回我们IOC容器
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

    //2、查看容器里面的组件
    String[] names = run.getBeanDefinitionNames();
    for (String name : names) {
    System.out.println(name);
    }

    //3、从容器中获取组件

    Pet tom01 = run.getBean("tom", Pet.class);

    Pet tom02 = run.getBean("tom", Pet.class);

    System.out.println("组件:"+(tom01 == tom02));


    //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
    MyConfig bean = run.getBean(MyConfig.class);
    System.out.println(bean);

    //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
    //保持组件单实例
    User user = bean.user01();
    User user1 = bean.user01();
    System.out.println(user == user1);


    User user01 = run.getBean("user01", User.class);
    Pet tom = run.getBean("tom", Pet.class);

    System.out.println("用户的宠物:"+(user01.getPet() == tom));
    }
    }
    + +
    1.2 @Bean @Component @Controller @Service @Repository
    1
    2
    3
    4
    5
    //默认是单实例的
    @Bean//给容器中添加组件,以方法名作为组件的id(组件名),返回类型就是组件类型,返回的值,就是组件在容器中的实例,@Bean("user") 是自定义组件名为user
    public User user01(){
    return new User("Tom",100);
    }
    + +
    1.3 @ComponentScan @Import
    1
    2
    3
    4
    5
    6
    7
    /*
    * @Import({User.class, DBHelper.class}) 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
    */
    @Import({User.class, DBHelper.class})
    @Configuration(proxyBeanMethods = false)
    public class MyConfig {
    }
    + +
    1.4 @Conditional 条件装配:满足Conditional指定的条件,则进行组件注入

    image-20230802141422751

    +
    1
    2
    3
    //放在类上面,条件成立,这个类下面的所有配置生效,放在方法上面,条件成立,这个方法下的配置才会成立
    @ConditionalOnBean(name = "tom") //存在一个id为tom的组件的时候才会生效
    @ConditionalOnMissingBean(name = "tom")
    + +

    2.原生配置文件的引入

    2.1、@ImportResource

    beans.xml*)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.atguigu.boot.bean.User">
    <property name="name" value="zhangsan"></property>
    <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
    <property name="name" value="tomcat"></property>
    </bean>
    </beans>
    + +

    在java类中红使用@ImportResource(“classpath:beans.xml”)导入上面的配置文件

    +
    1
    2
    @ImportResource("classpath:beans.xml")
    public class MyConfig {}
    + +

    3.配置绑定

    3.1 @ConfigurationProperties
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /**
    * 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
    */
    @Component //或者在配置类上添加@EnableConfigurationProperties(Car.class)注解,不使用@Component
    @ConfigurationProperties(prefix = "mycar")
    public class Car {

    private String brand;
    private Integer price;

    public String getBrand() {
    return brand;
    }

    public void setBrand(String brand) {
    this.brand = brand;
    }

    public Integer getPrice() {
    return price;
    }

    public void setPrice(Integer price) {
    this.price = price;
    }

    @Override
    public String toString() {
    return "Car{" +
    "brand='" + brand + '\'' +
    ", price=" + price +
    '}';
    }
    }
    + +

    配置文件中的值

    +
    1
    2
    mycar.brand=BYD
    mycar.price=120000
    + +

    编写测试的controller

    +
    1
    2
    3
    4
    5
    6
    @Autowired
    private Car car;
    @RequestMapping("/car")
    public Car ggetCar(){
    return car;
    }
    + +image-20230802153951542 + +

    4.4 自动配置原理入门

    1.引导加载自动配置类

    @SpringBootApplication

    +

    image-20230802155222746

    +
    1
    2
    3
    4
    5
    @SpringBootConfiguration //代表当前是一个配置类
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })//指定扫描那些spring注解
    public @interface SpringBootApplication{}
    + +

    @EnableAutoConfiguration

    +
    1
    2
    3
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {}
    + +

    @AutoConfigurationPackage

    +
    1
    2
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {}
    + +

    Registrar.class

    +

    image-20230802160618158

    +

    利用Registrar给容器中导入com.atguigu.boot(主程序所在的包)包下的一系列组件

    +

    image-20230802161004735

    +

    @Import(AutoConfigurationImportSelector.class)

    +
    1
    2
    3
    4
    5
    6
    1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
    2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
    3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
    4、从META-INF/spring.factories位置来加载一个文件。
    默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
    spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
    + +

    AutoConfigurationImportSelector.java

    +
    1
    2
    3
    4
    5
    6
    7
    8
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    + +

    getAutoConfigurationEntry()方法

    +

    利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
    }
    + +

    **getCandidateConfigurations()**获取所有候选的配置

    +

    调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类

    +

    image-20230802162236987

    +

    loadSpringFactories()

    +

    利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
    return result;
    } else {
    try {
    Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
    MultiValueMap<String, String> result = new LinkedMultiValueMap();

    while(urls.hasMoreElements()) {
    URL url = (URL)urls.nextElement();
    UrlResource resource = new UrlResource(url);
    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    Iterator var6 = properties.entrySet().iterator();

    while(var6.hasNext()) {
    Map.Entry<?, ?> entry = (Map.Entry)var6.next();
    String factoryTypeName = ((String)entry.getKey()).trim();
    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
    int var10 = var9.length;

    for(int var11 = 0; var11 < var10; ++var11) {
    String factoryImplementationName = var9[var11];
    result.add(factoryTypeName, factoryImplementationName.trim());
    }
    }
    }

    cache.put(classLoader, result);
    return result;
    } catch (IOException var13) {
    throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
    }
    }
    }
    + +

    从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件,spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

    +

    image-20230802163429366

    +

    在spring.factories这个配置文件中写死了springBoot一启动就要给容器加载的所有配置类,一共127个

    +

    image-20230802163708198

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer

    # Auto Configuration Import Listeners
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
    org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
    org.springframework.boot.autoconfigure.condition.OnClassCondition,\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
    org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
    org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
    org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
    org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
    org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
    org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
    org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
    org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
    org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
    org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
    org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
    org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
    org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
    org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
    org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
    org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
    org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
    org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
    org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
    org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
    org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
    org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
    org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
    org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
    org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
    org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
    org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
    org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
    org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
    org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
    org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
    org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
    org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
    org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
    org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
    org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
    org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
    org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
    org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

    # Failure analyzers
    org.springframework.boot.diagnostics.FailureAnalyzer=\
    org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\
    org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
    org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

    # Template availability providers
    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
    + +

    2.按需开启自动配置项

    ​ 虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration,按照条件装配规则(@Conditional),最终会按需配置,比如下面的配置是否生效还要通过@Conditional的条件判断。

    +

    简单的一句话:启动的时候加载所有,使用的时候按照条件(@Conditional)进行装配

    +

    image-20230802164424689

    +

    3.修改默认配置

    ​ 相当于当我们在容器中注入了类型为MultipartResolver但是id不为multipartResolver的组件的时候,会帮我们规范一下命名,命名为multipartResolver。

    +
    1
    2
    3
    4
    5
    6
    7
    @Bean
    @ConditionalOnBean(MultipartResolver.class) //存在MultipartResolver类型的组件
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)//不存在以multipartResolver为id的组件
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
    // Detect if the user has created a MultipartResolver but named it incorrectly
    return resolver;
    }
    + +

    SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Bean
    @ConditionalOnMissingBean //没有的话就将这个配置注入到容器中
    public CharacterEncodingFilter characterEncodingFilter() {
    CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
    filter.setEncoding(this.properties.getCharset().name());
    filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
    filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
    return filter;
    }
    + +

    总结:

    +
      +
    • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

      +
    • +
    • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

      +
    • +
    • 生效的配置类就会给容器中装配很多组件

      +
    • +
    • 只要容器中有这些组件,相当于这些功能就有了

      +
    • +
    • 定制化配置

      +
    • +
      • +
      • 用户直接自己@Bean替换底层的组件
      • +
      • 用户去看这个组件是获取的配置文件什么值就去修改。
      • +
      +
    • +
    +

    xxxxxAutoConfiguration —> 组件 —> xxxxProperties取值 —-> application.properties

    +

    3.4.最佳实践

    +

    4.5 简化开发

    1.Lombok

    引入依赖(使用前需要安装插件)

    +
    1
    2
    3
    4
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
    + +

    注解

    +
    1
    2
    3
    4
    5
    @NoArgsConstructor  //无参构造器
    @AllArgsConstructor //有参构造器
    @Data //getter和setter方法
    @ToString
    @EqualsAndHashCode
    + +

    2.简化日志开发

    1
    @Slf4j
    + +

    3.dev-tools

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    + +

    项目或者页面修改以后:Ctrl+F9;

    +

    4.Spring Initailizr(项目初始化向导)

    image-20230803103657995

    +

    image-20230803104032505

    +

    image-20230803104655578

    +

    二.SpringBoot2核心技术-核心功能

    image-20230803104919434

    +

    一.配置文件

    1、文件类型

    1.1、properties

    同以前的properties用法

    +

    1.2、yaml

    1.2.1、简介

    YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。

    +

    非常适合用来做以数据为中心的配置文件

    +
    1.2.2、基本语法
      +
    • key: value;kv之间有空格
    • +
    • 大小写敏感
    • +
    • 使用缩进表示层级关系
    • +
    • 缩进不允许使用tab,只允许空格
    • +
    • 缩进的空格数不重要,只要相同层级的元素左对齐即可
    • +
    • ‘#’表示注释
    • +
    • 字符串无需加引号,如果要加,’’与””表示字符串内容 会被 转义/不转义
    • +
    +
    1.2.3、数据类型
      +
    • 字面量:单个的、不可再分的值。date、boolean、string、number、null
    • +
    +
    1
    k: v
    + +
      +
    • 对象:键值对的集合。map、hash、set、object
    • +
    +
    1
    2
    3
    4
    5
    6
    行内写法:  k: {k1:v1,k2:v2,k3:v3}
    #或
    k:
    k1: v1
    k2: v2
    k3: v3
    + +
      +
    • 数组:一组按次序排列的值。array、list、queue
    • +
    +
    1
    2
    3
    4
    5
    6
    行内写法:  k: [v1,v2,v3]
    #或者
    k:
    - v1
    - v2
    - v3
    + +
    1.2.4、示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @ConfigurationProperties(prefix="person")//和以person开头的配置文件绑定
    @Data
    public class Person {

    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] interests;
    private List<String> animal;
    private Map<String, Object> score;
    private Set<Double> salarys;
    private Map<String, List<Pet>> allPets;
    }

    @Data
    public class Pet {
    private String name;
    private Double weight;
    }
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    # yaml表示以上对象
    person:
    # 单引号会将 \n作为字符串输出 双引号会将\n 作为换行输出
    # 双引号不会转义,单引号会转义
    userName: zhangsan
    boss: false
    birth: 2019/12/12 20:12:33
    age: 18
    pet:
    name: tomcat
    weight: 23.4
    interests: [篮球,游泳]
    animal:
    - jerry
    - mario
    score:
    english:
    first: 30
    second: 40
    third: 50
    math: [131,140,148]
    chinese: {first: 128,second: 136}
    salarys: [3999,4999.98,5999.99]
    allPets:
    sick:
    - {name: tom}
    - {name: jerry,weight: 47}
    health: [{name: mario,weight: 47}]
    + +

    2、配置提示

    自定义的类和配置文件绑定一般没有提示

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>

    <!--打包的时候去除这个配置提示器-->
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    <excludes>
    <exclude>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    </exclude>
    </excludes>
    </configuration>
    </plugin>
    </plugins>
    </build>
    + +

    二.Web开发

    image-20230803134143859

    +

    1.简单的功能分析

    1.1 静态资源访问

    1.静态资源目录

    只要静态资源放在类路径下: /static (or /public or /resources or /META-INF/resources

    +

    访问 : 当前项目根路径/ + 静态资源名

    +

    原理: 静态映射/**

    +

    请求进来,先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器,静态资源也找不到则响应404页面

    +

    改变默认的静态资源路径

    +
    1
    2
    3
    spring:
    resources:
    static-locations: [classpath:/haha/] #静态资源的目录
    + +
    2.静态资源访问前缀

    默认无前缀

    +

    默认的访问路径: localhost:8080/xxx.png

    +

    设置访问前缀之后: localhost:8080/res/xxx.png (文件在目录中的位置没有改变,只是访问的时候加上了res这一层路径)

    +
    1
    2
    3
    spring:
    mvc:
    static-path-pattern: /res/**
    + +

    当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

    +
    3.webjar

    自动映射 /webjars/**

    +

    https://www.webjars.org/

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
    </dependency>
    + +

    访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

    +

    1.2 欢迎页支持

      +
    • 静态资源路径下 index.html

      +
    • +
      • +
      • 可以配置静态资源路径
      • +
      • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
      • +
      +
    • +
    +
    1
    2
    3
    4
    5
    6
    spring:
    # mvc:
    # static-path-pattern: /res/** 这个会导致welcome page功能失效

    resources:
    static-locations: [classpath:/haha/]
    + +
      +
    • controller能处理/index
    • +
    +

    1.3 自定义 Favicon

    favicon.ico 放在静态资源目录下即可(名称为favicon.ico)

    +
    1
    2
    3
    spring:
    # mvc:
    # static-path-pattern: /res/** 这个会导致 Favicon 功能失效
    + +

    1.4 静态资源配置原理

    1.SpringMvc的自动配置类WebMvcAutoConfiguration

    +

    image-20230803165113057

    +
    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {}
    + +

    2.SpringMvc给容器中配置的组件

    +

    image-20230803180257123

    +
    1
    2
    3
    4
    5
    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
    + +

    3.绑定的配置

    +

    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })

    +

    WebMvcProperties.class

    +

    image-20230803180600438

    +

    ResourceProperties.class

    +

    image-20230803180633611

    +

    2.请求参数处理与数据响应

    2.1、请求映射

    1、rest使用与原理
      +
    • @xxxMapping;

      +
    • +
    • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作

      +
    • +
      • +
      • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
      • +
      • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
      • +
      • 核心Filter;HiddenHttpMethodFilter
      • +
      +
    • +
      • +
        • +
        • 用法: 表单method=post,隐藏域 _method=put
        • +
        • SpringBoot中手动开启
        • +
        +
        1
        2
        3
        4
        5
        6
        #开启使用rest风格的注解
        spring:
        mvc:
        hiddenmethod:
        filter:
        enabled: true
        + +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        <!--发送put或者delete请求-->
        <form action="/user" method="post">
        <input name="_method" type="hidden" value="delete"/>
        <input name="_m" type="hidden" value="delete"/>
        <input value="REST-DELETE 提交" type="submit"/>
        </form>
        <form action="/user" method="post">
        <input name="_method" type="hidden" value="PUT"/>
        <input value="REST-PUT 提交" type="submit"/>
        </form>
      • +
      +
    • +
      • +
      • 扩展:如何把_method 这个名字换成我们自己喜欢的。
      • +
      +
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
    return "GET-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
    return "POST-张三";
    }


    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
    return "PUT-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
    return "DELETE-张三";
    }



    //自定义filter
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m");
    return methodFilter;
    }
    + +

    Rest原理(表单提交要使用REST的时候)

    +
      +
    • 表单提交会带上_method=PUT

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <!--发送put或者delete请求-->
      <form action="/user" method="post">
      <input name="_method" type="hidden" value="delete"/>
      <input name="_m" type="hidden" value="delete"/>
      <input value="REST-DELETE 提交" type="submit"/>
      </form>
      <form action="/user" method="post">
      <input name="_method" type="hidden" value="PUT"/>
      <input value="REST-PUT 提交" type="submit"/>
      </form>
      +
    • +
    • 请求过来被HiddenHttpMethodFilter拦截

      +

      WebMvcAutoConfiguration

      +
      1
      2
      3
      4
      5
      6
      @Bean
      @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
      @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
      public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
      return new OrderedHiddenHttpMethodFilter();
      }
      +
    • +
      • +
      • 请求是否正常,并且是POST
      • +
      +
    • +
      • +
        • +
        • 获取到_method的值。

          +
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
          throws ServletException, IOException {

          HttpServletRequest requestToUse = request;

          if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
          String paramValue = request.getParameter(this.methodParam);
          if (StringUtils.hasLength(paramValue)) {
          String method = paramValue.toUpperCase(Locale.ENGLISH);
          if (ALLOWED_METHODS.contains(method)) {
          requestToUse = new HttpMethodRequestWrapper(request, method);
          }
          }
          }

          filterChain.doFilter(requestToUse, response);
          }
          +
        • +
        • 兼容以下请求;PUT.DELETE.PATCH

          +
          1
          2
          3
          private static final List<String> ALLOWED_METHODS =
          Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
          HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
          +
        • +
        • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值

          +
        • +
        • 过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用requesWrapper的

          +
        • +
        +
      • +
      +
    • +
    +

    Tips:

    +

    Rest使用客户端工具:如PostMan直接发送put、delete等方式请求,无需Filter,不需要转换

    +

    扩展点:

    +

    如何将<input name="_method" type="hidden" value="delete"/>中的name值_method改为自定义的值?

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Configuration(proxyBeanMethods = false) //组件中没有依赖关系,设置成false直接放
    public class WebConfig{
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m"); //将name="_method"改成name="_m"
    return methodFilter;
    }
    }
    + +
    2、请求映射原理

    DispatcherServlet 继承 FrameworkServlet

    +

    FrameworkServlet 继承 HttpServletBean

    +

    FrameworkServlet 重写了doGet和doPost方法

    +

    image-20230809133846464

    +

    DispatcherServlet的doDispatch方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
    processedRequest = checkMultipart(request);
    multipartRequestParsed = (processedRequest != request);

    // Determine handler for the current request.
    //找到请求使用那个hander(contrller)处理
    /**
    * getHandler()方法
    */
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);
    return;
    }

    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // Process last-modified header, if supported by the handler.
    String method = request.getMethod();
    boolean isGet = "GET".equals(method);
    if (isGet || "HEAD".equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    return;
    }
    }

    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
    }

    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    if (asyncManager.isConcurrentHandlingStarted()) {
    return;
    }

    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
    dispatchException = ex;
    }
    catch (Throwable err) {
    // As of 4.3, we're processing Errors thrown from handler methods as well,
    // making them available for @ExceptionHandler methods and other scenarios.
    dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
    new NestedServletException("Handler processing failed", err));
    }
    finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
    // Instead of postHandle and afterCompletion
    if (mappedHandler != null) {
    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    }
    }
    else {
    // Clean up any resources used by a multipart request.
    if (multipartRequestParsed) {
    cleanupMultipart(processedRequest);
    }
    }
    }
    }
    + +

    getHandler()方法 遍历查找可以处理请求的方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
    for (HandlerMapping mapping : this.handlerMappings) {
    HandlerExecutionChain handler = mapping.getHandler(request);
    if (handler != null) {
    return handler;
    }
    }
    }
    return null;
    }
    + +

    **HandlerMapping ** 处理器映射

    +

    默认有5个HandlerMapping

    +

    image-20230809150915450

    +

    RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则

    +

    image-20230809151427867

    +

    所有的请求映射都在HandlerMapping中

    +
      +
    • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 访问 /能访问到index.html;

      +
    • +
    • SpringBoot自动配置了默认的 RequestMappingHandlerMapping

      +
    • +
    • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息

      +
    • +
      • +
      • 如果有就找到这个请求对应的handler
      • +
      • 如果没有就是下一个 HandlerMapping
      • +
      +
    • +
    • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping 自定义 HandlerMapping

      +
    • +
    +

    2.2、普通参数与基本注解

    1.注解
    1
    2
    3
    4
    5
    6
    7
    @PathVariable  //获取路径中的参数信息(restful风格)
    @RequestHeader //获取请求头的信息
    @RequestAttribute //获取请求域中的数据
    @RequestParam //获取路径中拼接的请求参数(/info?age=12&name=lisi)
    @MatrixVariable //矩阵变量 /cars/sell;low=34;brand=byd,audi,yd
    @CookieValue //获取cookie的值
    @RequestBody //获取请求体的值(Post请求)
    + +

    @PathVariable

    +

    接受路径中的参数也可以使用map集合接受,但是map集合必须是Map<String, String>的形式

    +

    image-20230809155753715

    +

    @RequestHeader

    +

    获取指定的请求头或者所有的请求头信息

    +

    image-20230809160406516

    +

    image-20230809160846527

    +

    @RequestParam

    +

    可以使用map接受所有的请求参数

    +

    image-20230809161533834

    +

    @CookieValue

    +

    获取cookie的值,可以直接获取字符串的值也可以封装成cookie的对象

    +

    image-20230809164101374

    +

    @RequestBody

    +

    这里用一个字符串接收表单数据和平时使用的不一样,开发中使用一个对象接受表单的数据

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * ·@RequestBody注解
    */
    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
    Map<String,Object> map = new HashMap<>();
    map.put("content",content);
    return map;
    }
    + +

    image-20230809165149278

    +

    @RequestAttribute

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.atguigu.boot.controller;

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestAttribute;
    import org.springframework.web.bind.annotation.ResponseBody;

    import javax.servlet.http.HttpServletRequest;
    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/11
    * @Description
    */
    @Controller
    public class RequestController {


    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){

    request.setAttribute("msg","获取请求域中的数据成功");
    request.setAttribute("code",200);
    return "forward:/success"; //转发到下面的/success请求
    }

    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute(value = "msg",required = false) String msg,
    @RequestAttribute(value = "code",required = false)Integer code,//通过注解的方式获取上面请求域的值
    HttpServletRequest request){//通过原生的request获取
    //Object msg1 = request.getAttribute("msg");
    Map<String,Object> map = new HashMap<>();
    map.put("msg",msg);
    map.put("code",code);
    return map;
    }
    }
    + +

    image-20230811092618793

    +

    @MatrixVariable

    +

    开启SpringBoot的矩阵变量功能

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    UrlPathHelper urlPathHelper = new UrlPathHelper();
    // 不移除 后面的内容 矩阵变量功能就可以生效
    urlPathHelper.setRemoveSemicolonContent(false);
    configurer.setUrlPathHelper(urlPathHelper);
    }
    };
    }
    + +

    测试

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    // 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    // removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
    @MatrixVariable("brand") List<String> brand,
    @PathVariable("path") String path){
    Map<String,Object> map = new HashMap<>();

    map.put("low",low);
    map.put("brand",brand);
    map.put("path",path);
    return map;
    }

    // /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
    Map<String,Object> map = new HashMap<>();
    map.put("bossAge",bossAge);
    map.put("empAge",empAge);
    return map;
    }
    + +

    image-20230811095858532

    +

    image-20230811095834803

    +
    2.Servlet API

    WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

    +

    ServletRequestMethodArgumentResolver 以上的部分参数

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
    ServletRequest.class.isAssignableFrom(paramType) ||
    MultipartRequest.class.isAssignableFrom(paramType) ||
    HttpSession.class.isAssignableFrom(paramType) ||
    (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
    Principal.class.isAssignableFrom(paramType) ||
    InputStream.class.isAssignableFrom(paramType) ||
    Reader.class.isAssignableFrom(paramType) ||
    HttpMethod.class == paramType ||
    Locale.class == paramType ||
    TimeZone.class == paramType ||
    ZoneId.class == paramType);
    }
    + +
    3.复杂参数:

    Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

    +

    向请求域中共享数据

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * ra数据通过 map model HttpServletRequest HttpServletResponse 等方式共享到请求域中,在success页面中取出来
    */
    @GetMapping("/params")
    public String testParam(Map<String,Object> map,
    Model model,
    HttpServletRequest request,
    HttpServletResponse response){
    map.put("hello","world666");
    model.addAttribute("world","hello666");
    request.setAttribute("message","HelloWorld");

    Cookie cookie = new Cookie("c1","v1");
    response.addCookie(cookie);
    return "forward:/success";
    }
    + +
    4.自定义对象参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 姓名: <input name="userName"/> <br/>
    * 年龄: <input name="age"/> <br/>
    * 生日: <input name="birth"/> <br/>
    * 宠物姓名:<input name="pet.name"/><br/>
    * 宠物年龄:<input name="pet.age"/>
    */
    @Data
    public class Person {

    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;

    }

    @Data
    public class Pet {

    private String name;
    private String age;

    }
    + +

    页面测试代码

    +

    index.html

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1>atguigu,欢迎您</h1>
    测试REST风格;
    <form action="/user" method="get">
    <input value="REST-GET 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
    <input value="REST-POST 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
    <input name="_method" type="hidden" value="delete"/>
    <input name="_m" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT 提交" type="submit"/>
    </form>
    <hr/>
    测试基本注解:
    <ul>
    <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
    <li>@RequestParam(获取请求参数)</li>
    <li>@CookieValue(获取cookie值)</li>
    <li>@RequestBody(获取请求体[POST])</li>

    <li>@RequestAttribute(获取request域属性)</li>
    <li>@MatrixVariable(矩阵变量)</li>
    </ul>

    /cars/{path}?xxx=xxx&aaa=ccc queryString 查询字符串。@RequestParam;<br/>
    /cars/sell;low=34;brand=byd,audi,yd ;矩阵变量 <br/>
    页面开发,cookie禁用了,session里面的内容怎么使用;
    session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带。
    url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.

    /boss/1/2

    /boss/1;age=20/2;age=20

    <a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a>
    <a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a>
    <a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>
    <br/>
    <form action="/save" method="post">
    测试@RequestBody获取数据 <br/>
    用户名:<input name="userName"/> <br>
    邮箱:<input name="email"/>
    <input type="submit" value="提交"/>
    </form>
    <ol>
    <li>矩阵变量需要在SpringBoot中手动开启</li>
    <li>根据RFC3986的规范,矩阵变量应当绑定在路径变量中!</li>
    <li>若是有多个矩阵变量,应当使用英文符号;进行分隔。</li>
    <li>若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可。</li>
    <li>如:/cars/sell;low=34;brand=byd,audi,yd</li>
    </ol>
    <hr/>
    测试原生API:
    <a href="/testapi">测试原生API</a>
    <hr/>
    测试复杂类型:<hr/>
    测试封装POJO;
    <form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="2019/12/10"/> <br/>
    <!-- 宠物姓名:<input name="pet.name" value="阿猫"/><br/>-->
    <!-- 宠物年龄:<input name="pet.age" value="5"/>-->
    宠物: <input name="pet" value="小猫"/>
    <input type="submit" value="保存"/>
    </form>
    <br>
    </body>
    </html>
    + + + +

    2.3 参数处理原理

    1.页面发送请求

    +
    1
    http://localhost:8080/car/3/owner/lisi  
    + +

    2.进入DispatcherServlet的doDispatch( )方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
    processedRequest = checkMultipart(request);
    multipartRequestParsed = (processedRequest != request);

    // Determine handler for the current request.
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);
    return;
    }

    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // Process last-modified header, if supported by the handler.
    String method = request.getMethod();
    boolean isGet = "GET".equals(method);
    if (isGet || "HEAD".equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    return;
    }
    }

    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
    }

    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    if (asyncManager.isConcurrentHandlingStarted()) {
    return;
    }

    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
    dispatchException = ex;
    }
    catch (Throwable err) {
    // As of 4.3, we're processing Errors thrown from handler methods as well,
    // making them available for @ExceptionHandler methods and other scenarios.
    dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
    new NestedServletException("Handler processing failed", err));
    }
    finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
    // Instead of postHandle and afterCompletion
    if (mappedHandler != null) {
    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    }
    }
    else {
    // Clean up any resources used by a multipart request.
    if (multipartRequestParsed) {
    cleanupMultipart(processedRequest);
    }
    }
    }
    }
    + +

    3.封装一个方法获取对应的处理器适配器

    +
    1
    2
    //方法的调用
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    + +

    4.遍历所有的处理器适配器(一共有四种,如下图)找到支持的适配器

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
    for (HandlerAdapter adapter : this.handlerAdapters) {
    if (adapter.supports(handler)) {
    return adapter;
    }
    }
    }
    throw new ServletException("No adapter for handler [" + handler +
    "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
    + +

    image-20230812091950114

    +

    上图中四个处理器适配器中前两个是常用的处理器适配器: 0 - 支持方法上标注@RequestMapping 1 - 支持函数式编程的

    +

    5.查看当前的处理器适配器是否支持的方法

    +
    1
    2
    3
    4
    5
    6
    @Override
    public final boolean supports(Object handler) {
    //条件一 handler instanceof HandlerMethod
    //条件二 supportsInternal((HandlerMethod) handler)
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }
    + +

    遍历查找之后返回的适配器是RequestMappingHandlerAdapter

    +

    image-20230812092825385

    +

    6.回到DispatcherServlet执行下面的方法

    +
    1
    2
    3
    //根据上面获取到的处理器适配器真正的执行处理器方法
    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    + +

    7.内部的处理过程

    +

    handle( )方法

    +
    1
    2
    3
    4
    5
    6
    @Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    return handleInternal(request, response, (HandlerMethod) handler);
    }
    + +

    进入当前处理器适配器的handleInternal( )方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);
    if (session != null) {
    Object mutex = WebUtils.getSessionMutex(session);
    synchronized (mutex) {
    mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    }
    else {
    // No HttpSession available -> no mutex necessary
    mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    }
    else {
    // No synchronization on session demanded at all...
    mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    }
    else {
    prepareResponse(response);
    }
    }

    return mav;
    }
    + +

    执行invokeHandlerMethod( )方法

    +
    1
    mav = invokeHandlerMethod(request, response, handlerMethod);
    + +

    进入invokeHandlerMethod()方法

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    invocableMethod.setDataBinderFactory(binderFactory);
    invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.setTaskExecutor(this.taskExecutor);
    asyncManager.setAsyncWebRequest(asyncWebRequest);
    asyncManager.registerCallableInterceptors(this.callableInterceptors);
    asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

    if (asyncManager.hasConcurrentResult()) {
    Object result = asyncManager.getConcurrentResult();
    mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
    asyncManager.clearConcurrentResult();
    LogFormatUtils.traceDebug(logger, traceOn -> {
    String formatted = LogFormatUtils.formatValue(result, !traceOn);
    return "Resume with async result [" + formatted + "]";
    });
    invocableMethod = invocableMethod.wrapConcurrentResult(result);
    }

    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    if (asyncManager.isConcurrentHandlingStarted()) {
    return null;
    }

    return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
    webRequest.requestCompleted();
    }
    }
    + +

    注意这里的26个参数解析器,参数解析器的作用是确定将要执行的目标方法的每一个参数的值是什么

    +

    这里的每一个参数解析器都对应了我们标注在参数上的注解

    +

    SpringMVC目标方法能写多少种参数类型取决于参数解析器

    +

    image-20230812094224699

    +

    参数解析器的接口设计

    +

    image-20230812094755583

    +
      +
    • 当前解析器是否支持解析这种参数
    • +
    • 支持就调用 resolveArgument
    • +
    +

    返回值处理器

    +

    定义了controller返回值的类型

    +image-20230812095137195 + +

    RequestMappingHandlerAdapter的invokeAndHandle( )方法真正的执行请求

    +
    1
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    //上面一行代码执行完毕之后会直接跳到controller方法,然后执行下面的代码
    setResponseStatus(webRequest);

    if (returnValue == null) {
    if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
    disableContentCachingIfNecessary(webRequest);
    mavContainer.setRequestHandled(true);
    return;
    }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
    mavContainer.setRequestHandled(true);
    return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
    this.returnValueHandlers.handleReturnValue(
    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
    if (logger.isTraceEnabled()) {
    logger.trace(formatErrorForReturnValue(returnValue), ex);
    }
    throw ex;
    }
    }
    + +

    执行的过程

    +
    1
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    //这个args就记录了方法上所有的参数
    if (logger.isTraceEnabled()) {
    logger.trace("Arguments: " + Arrays.toString(args));
    }
    return doInvoke(args);
    }
    + +

    image-20230812100059378

    +

    获取方法参数值的过程

    +
    1
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {


    //获取所有参数的参数声明 参数类型 标注的注解
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
    return EMPTY_ARGS;
    }

    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
    MethodParameter parameter = parameters[i];
    parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
    args[i] = findProvidedArgument(parameter, providedArgs);
    if (args[i] != null) {
    continue;
    }
    if (!this.resolvers.supportsParameter(parameter)) {
    throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
    }
    try {
    args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
    }
    catch (Exception ex) {
    // Leave stack trace for later, exception may actually be resolved and handled...
    if (logger.isDebugEnabled()) {
    String exMsg = ex.getMessage();
    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
    logger.debug(formatArgumentError(parameter, exMsg));
    }
    }
    throw ex;
    }
    }
    return args;
    }
    + +

    遍历判断获取支持解析该参数的参数解析器

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
    for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
    if (resolver.supportsParameter(parameter)) {
    result = resolver;
    this.argumentResolverCache.put(parameter, result);
    break;
    }
    }
    }
    return result;
    }
    + +

    解析参数的值通过调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法

    +

    2.4 数据响应与内容协商

    返回值的处理逻辑

    +
    2.4.1 相关依赖的引入

    创建springBoot项目的时候导入了spring-boot-starter-web依赖会自动的帮我们导入相关的json依赖,便于json数据的前后端传递

    +
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    + +

    自动的帮我们导入相关的json的starter

    +
    1
    2
    3
    4
    5
    6
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.3.4.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    + +

    在spring-boot-starter-json中引入了jackson相关的依赖

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.2</version>
    <scope>compile</scope>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
    <version>2.11.2</version>
    <scope>compile</scope>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.11.2</version>
    <scope>compile</scope>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
    <version>2.11.2</version>
    <scope>compile</scope>
    </dependency>
    + +

    返回值解析器的原理

    +
      +
    • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType

      +
    • +
    • 2、返回值处理器调用 handleReturnValue 进行处理

      +
    • +
    • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。

      +
    • +
      • +
        1. +
        2. 利用 MessageConverters 进行处理 将数据写为json
        3. +
        +
      • +
      +
    • +
      • +
        • +
        • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
        • +
        • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
        • +
        • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        • +
        +
      • +
      +
    • +
      • +
        • +
          • +
          • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
          • +
          • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
          • +
          +
        • +
        +
      • +
      +
    • +
    +
    2.4.2 内容协商原理

    引入jackson-dataformat-xml,测试返回是xml格式的数据

    +
    1
    2
    3
    4
    <dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>
    + +

    开启基于请求参数的内容协商功能

    +

    发请求: http://localhost:8080/test/person?format=json //指定格式是json

    +

    http://localhost:8080/test/person?format=xml //指定格式是xml

    +
    1
    2
    3
    spring:
    contentnegotiation:
    favor-parameter: true #开启请求参数内容协商模式
    + +

    1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

    +

    2、最终进行内容协商返回给客户端json即可

    +

    3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

    +

    4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。

    +

    5、客户端需要【application/xml】。服务端能力【10种、json、xml】

    +

    6、进行内容协商的最佳匹配媒体类型

    +

    7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。

    +
    2.4.3 自定义MessageConverter

    1、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

    +

    2、Processor 处理方法返回值。通过 MessageConverter 处理

    +

    3、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

    +

    4、内容协商找到最终的 messageConverter;

    +

    步骤:
    1、添加自定义的MessageConverter进系统底层

    +

    2、系统底层就会统计出所有MessageConverter能操作哪些类型

    +

    3、客户端内容协商 [guigu—>guigu]

    +

    编写自定义的的converter

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    package com.atguigu.boot.converter;

    import com.atguigu.boot.bean.Person;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.List;

    /**
    * 自定义的Converter
    */
    public class GuiguMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
    return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
    return clazz.isAssignableFrom(Person.class);
    }

    /**
    * 服务器要统计所有MessageConverter都能写出哪些内容类型
    *
    * application/x-guigu
    * @return
    */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
    return MediaType.parseMediaTypes("application/x-guigu");
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    //自定义协议数据的写出
    String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();
    //写出去
    OutputStream body = outputMessage.getBody();
    body.write(data.getBytes());
    }
    }
    + +

    在配置文件中加入自定义的converter

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.atguigu.boot.config;


    import com.atguigu.boot.bean.Person;
    import com.atguigu.boot.bean.Pet;
    import com.atguigu.boot.converter.GuiguMessageConverter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.format.FormatterRegistry;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import org.springframework.util.StringUtils;
    import org.springframework.web.accept.HeaderContentNegotiationStrategy;
    import org.springframework.web.accept.ParameterContentNegotiationStrategy;
    import org.springframework.web.filter.HiddenHttpMethodFilter;
    import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
    import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import org.springframework.web.util.UrlPathHelper;

    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.*;

    @Configuration(proxyBeanMethods = false)
    public class WebConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {
    //加入自定义的converter
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new GuiguMessageConverter());
    }
    };
    }
    }
    + +

    在浏览器url上拼接参数的形式实现内容协商

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    package com.atguigu.boot.config;

    import com.atguigu.boot.bean.Person;
    import com.atguigu.boot.bean.Pet;
    import com.atguigu.boot.converter.GuiguMessageConverter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.format.FormatterRegistry;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import org.springframework.util.StringUtils;
    import org.springframework.web.accept.HeaderContentNegotiationStrategy;
    import org.springframework.web.accept.ParameterContentNegotiationStrategy;
    import org.springframework.web.filter.HiddenHttpMethodFilter;
    import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
    import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import org.springframework.web.util.UrlPathHelper;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.*;

    @Configuration(proxyBeanMethods = false)
    public class WebConfig{

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {

    /**
    * 自定义内容协商策略
    * @param configurer
    */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    Map<String, MediaType> mediaTypes = new HashMap<>();
    mediaTypes.put("json",MediaType.APPLICATION_JSON);
    mediaTypes.put("xml",MediaType.APPLICATION_XML);
    //在浏览器url上拼接参数的形式实现内容协商
    //http://localhost:8080/test/person?format=gg
    mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
    //指定支持解析哪些参数对应的哪些媒体类型
    ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
    //parameterStrategy.setParameterName("ff");

    HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();

    configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
    }

    //加入自定义的converter
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new GuiguMessageConverter());
    }
    };
    }
    }
    + +

    3.视图解析与模板引擎

    视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染

    +

    3.1 视图解析

    image-20230815091741019

    +

    3.2 模板引擎-Thymeleaf

    详细的使用教程: https://jasonsgong.gitee.io/posts/54835.html

    +
    3.2.1 Thymeleaf的使用

    1.引入依赖

    +
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    + +

    2.编写测试controller

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Controller
    public class ViewTestController {
    @GetMapping("/view")
    public String hello(Model model){
    //向请求域中共享数据
    model.addAttribute("keys","Thymeleaf");
    //转发到success页面中
    return "success";
    }
    }
    + +

    3.页面上渲染

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1 th:text="${keys}">Hello</h1>
    </body>
    </html>
    + +

    image-20230815153318041

    +

    3.3 后台管理系统注意点

    templates目录的访问规则

    +

    templates目录下的所有页面资源只能通过请求访问到

    +

    表单重复提交的问题

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package com.atguigu.admin.controller;


    @Slf4j
    @Controller
    public class IndexController {

    /**
    * 来登录页
    * @return
    */
    @GetMapping(value = {"/","/login"})
    public String loginPage(){

    return "login";
    }


    @PostMapping("/login")
    public String main(String username, String password){
    //登录成功重定向到main.html; 重定向防止表单重复提交
    //这里如果直接写 return "main"的话,页面在mian.html的情况下,url路径还是localhost:8080/login,如果再次刷新页面会重复提交表单
    //使用下面这个写法,执行"/login"这个请求之后,路径的url会变成localhost:8080/main.html
    return "redirect:/main.html";
    }

    /**
    * 去main页面
    * @return
    */
    @GetMapping("/main.html")
    public String mainPage(){
    return "main";
    }
    }
    + +

    4.拦截器

    1.HandlerInterceptor接口

    自定义拦截器需要实现的接口,以及需要实现接口中的方法

    +

    image-20230821105529796

    +

    2.拦截器实现登录检查操作

    登录检查的业务逻辑

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    /**
    * 登录检查
    * 1、配置好拦截器要拦截哪些请求
    * 2、把这些配置放在容器中
    */
    @Slf4j
    public class LoginInterceptor implements HandlerInterceptor {

    /**
    * 目标方法执行之前
    * @param request
    * @param response
    * @param handler
    * @return
    * @throws Exception
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    String requestURI = request.getRequestURI();
    log.info("preHandle拦截的请求路径是{}",requestURI);

    //登录检查逻辑
    HttpSession session = request.getSession();

    Object loginUser = session.getAttribute("loginUser");

    if(loginUser != null){
    //放行
    return true;
    }

    //拦截住 未登录 跳转到登录页
    request.setAttribute("msg","请先登录");
    request.getRequestDispatcher("/").forward(request,response);
    return false;
    }

    /**
    * 目标方法执行完成以后
    * @param request
    * @param response
    * @param handler
    * @param modelAndView
    * @throws Exception
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    log.info("postHandle执行{}",modelAndView);
    }

    /**
    * 页面渲染以后
    * @param request
    * @param response
    * @param handler
    * @param ex
    * @throws Exception
    */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    log.info("afterCompletion执行异常{}",ex);
    }
    }
    + +

    拦截器的配置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * 1、编写一个拦截器实现HandlerInterceptor接口
    * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
    * 3、指定拦截规则[如果是拦截所有,静态资源也会被拦截]
    */
    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())
    .addPathPatterns("/**") //所有请求都被拦截包括静态资源
    .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
    }
    + +

    3.拦截器原理

    1
    2
    3
    4
    5
    6
    /**
    * preHandle() :该方法在控制器方法之前执行,如果返回false,说明拦截器不放行该请求,自己处理或者结束该请求
    * 如果返回true,说明拦截器放该请求,由其它匹配的拦截器或者控制器继续处理
    * postHandle():该方法在控制器方法调用之后,且解析视图之前执行,可以通过此方法对请求域中的模型和视图做出进一步的修改
    * afterCompletion(): 该方法会在整个请求完成,即视图渲染结束之后执行,多用于资源的清理工作
    */
    + +

    1、根据当前请求,找到HandlerExecutionChain[可以处理请求的handler以及handler的所有 拦截器]

    +

    2、先来顺序执行 所有拦截器的 preHandle方法

    +
      +
    • 1、如果当前拦截器prehandler返回为true,则执行下一个拦截器的preHandle
    • +
    • 2、如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion方法
    • +
    +

    3、如果任何一个拦截器返回false,直接跳出不执行目标方法

    +

    4、所有拦截器都返回true,执行目标方法

    +

    5、倒序执行所有拦截器的postHandle方法

    +

    6、前面的步骤有任何异常都会直接倒序触发 afterCompletion

    +

    7、页面成功渲染完成以后,也会倒序触发 afterCompletion

    +

    image-20230821112246355

    +

    5.文件上传

    1.页面表单

    1
    2
    3
    4
    <form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
    </form>
    + +

    2.文件上传代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**
    * MultipartFile 自动封装上传过来的文件
    */
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
    @RequestParam("username") String username,
    @RequestPart("headerImg") MultipartFile headerImg,//单个文件
    @RequestPart("photos") MultipartFile[] photos) throws IOException {//多个文件

    log.info("上传的信息:email={},username={},headerImg={},photos={}",
    email, username, headerImg.getSize(), photos.length);

    if (!headerImg.isEmpty()) {
    //保存到文件服务器,OSS服务器
    String originalFilename = headerImg.getOriginalFilename();
    headerImg.transferTo(new File("H:\\cache\\" + originalFilename));
    }

    if (photos.length > 0) {
    for (MultipartFile photo : photos) {
    if (!photo.isEmpty()) {
    String originalFilename = photo.getOriginalFilename();
    photo.transferTo(new File("H:\\cache\\" + originalFilename));
    }
    }
    }
    return "main";
    }
    + +

    3.MultipartAutoConfiguration

    1
    2
    3
    4
    5
    6
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
    @ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @EnableConfigurationProperties(MultipartProperties.class)
    public class MultipartAutoConfiguration {}
    + +

    6.异常处理

    开发中异常处理方法: https://jasonsgong.gitee.io/posts/31385.html

    +

    1.默认规则

      +
    • 默认情况下,Spring Boot提供/error处理所有错误的映射
    • +
    • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
    • +
    +

    2.自定义错误页面

    templates/error/下的4xx,5xx页面会被自动解析

    +

    7.Web原生组件注入(Servlet、Filter、Listener)

    7.1 使用Servlet Api

    1.原生的Servlet的使用

    1.编写自定义的servlet继承HttpServlet

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.atguigu.boot.servlet;

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/26
    * @Description
    */
    @WebServlet(urlPatterns = "/my")
    public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("6666");
    }
    }
    + +

    2.在启动类上添加自定义servlet的包扫描注解

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.atguigu.boot;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;

    @ServletComponentScan("com.atguigu.boot") //指定原生的servlet的位置
    @SpringBootApplication
    public class Boot05Web01Application {

    public static void main(String[] args) {
    SpringApplication.run(Boot05Web01Application.class, args);
    }

    }
    + +

    3.启动程序测试

    +

    image-20230826211541424

    +
    2.原生的Filter的使用

    1.编写自定义的Filter实现Filter接口

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package com.atguigu.boot.servlet;

    import lombok.extern.slf4j.Slf4j;

    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import java.io.IOException;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/26
    * @Description
    */
    @Slf4j
    @WebFilter(urlPatterns={"/css/*","/images/*","/my"}) //设置拦截的路径
    public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    log.info("MyFilter初始化完成");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    log.info("MyFilter工作");
    chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
    log.info("MyFilter销毁");
    }
    }
    + +

    2.启动程序测试

    +

    image-20230826212400849

    +
    3.原生的Listener的使用

    1.编写自定义的MyServletContextListener实现ServletContextListener

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.atguigu.boot.servlet;

    import lombok.extern.slf4j.Slf4j;

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/26
    * @Description
    */
    @Slf4j
    @WebListener
    public class MyServletContextListener implements ServletContextListener {


    @Override
    public void contextInitialized(ServletContextEvent sce) {
    log.info("MyServletContextListener监听到项目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    log.info("MyServletContextListener监听到项目销毁");
    }
    }
    + +

    2.启动程序测试

    +

    image-20230826213125490

    +

    7.2 使用RegistrationBean

    不使用@WebServlet、@WebFilter、@WebListener注解的方式(注释掉自定义servlet、Filter、Listener上的注解)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.atguigu.boot.servlet;

    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
    import org.springframework.boot.web.servlet.ServletRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    import java.util.Arrays;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/26
    * @Description
    */
    @Configuration
    public class MyRegisterConfig {
    @Bean
    public ServletRegistrationBean myServlet(){
    MyServlet myServlet = new MyServlet();

    return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


    @Bean
    public FilterRegistrationBean myFilter(){

    MyFilter myFilter = new MyFilter();
    //return new FilterRegistrationBean(myFilter,myServlet());
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
    return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
    MyServletContextListener mySwervletContextListener = new MyServletContextListener();
    return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
    }
    + +

    重新启动项目我们可以看到依然生效

    +

    image-20230826214253187

    +

    8.嵌入式Servlet容器

    8.1 切换嵌入式Servlet容器

      +
    • 默认支持的webServer

      +
    • +
      • +
      • Tomcat, Jetty, or Undertow
      • +
      • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
      • +
      +
    • +
    • 切换服务器

      +

      1.排除tomcat的依赖

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
      <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
      </exclusions>
      </dependency>
      + +

      2.引入需要的服务器场景

      +
      1
      2
      3
      4
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
      </dependency>
      + +

      3.重启项目,可以看到服务器由tomcat变成了undertow

      +

      image-20230826221229898

      +
    • +
    • 原理

      +
    • +
      • +
      • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
      • +
      • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
      • +
      • ServletWebServerApplicationContext 启动的时候寻找 **ServletWebServerFactory**``(Servlet 的web服务器工厂---> Servlet 的web服务器)
      • +
      • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
      • +
      • 底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration
      • +
      • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
      • +
      • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
      • +
      • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
      • +
      • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)
      • +
      +
    • +
    +

    8.2 定制Servlet容器

    修改配置文件中以server.xxx打头的属性信息

    +

    image-20230826222050817

    +

    三.数据访问

    1.SQL

    1.1 JDBC的使用

    1.导入JDBC的场景

    +
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    + +

    2.导入自己使用的数据库的驱动(Mysql或者Oracle)版本号不写也可以,官方做了版本仲裁(但是要与实际安装的版本对应)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    想要修改版本
    1、直接依赖引入具体版本(maven的就近依赖原则)
    2、重新声明版本(maven的属性的就近优先原则)
    <properties>
    <java.version>1.8</java.version>
    <mysql.version>5.1.45</mysql.version>
    </properties>
    或者
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.45</version>
    </dependency>
    + +

    3.添加配置

    +
    1
    2
    3
    4
    5
    6
    7
    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    # type: com.zaxxer.hikari.HikariDataSource #配置数据源
    + +

    4.测试

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.atguigu.boot;

    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;

    @Slf4j
    @SpringBootTest
    class Boot05Web01ApplicationTests {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void contextLoads() {
    Long count = jdbcTemplate.queryForObject("select count(*) from `role` ", Long.class);
    log.info("记录数:{}",count);
    }
    }
    + +

    1.2 分析自动配置

    自动配置的类

    +

    image-20230826224503510

    +
      +
    • DataSourceAutoConfiguration : 数据源的自动配置

      +
    • +
      • +
      • 修改数据源相关的配置:spring.datasource
      • +
      • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
      • +
      • 底层配置好的连接池是:HikariDataSource
      • +
      +
    • +
    • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置

      +
    • +
    • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud

      +
    • +
      • +
      • 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
      • +
      +
    • +
    • JndiDataSourceAutoConfiguration: jndi的自动配置

      +
    • +
    • XADataSourceAutoConfiguration: 分布式事务相关的

      +
    • +
    +

    四.单元测试

    1.JUnit5的变化

    Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

    +

    作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成

    +

    JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

    +

    JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入

    +

    JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行

    +

    JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎

    +

    注意:

    +

    SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)

    +

    以前的版本

    +

    兼容以前的版本(junit4)需要额外导入的依赖(SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖)

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
    <exclusion>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-core</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    + +

    标注的注解

    +

    @SpringBootTest + @RunWith(SpringRunner.class)+@Test(4版本的@Test)

    +

    现在的版本

    +

    需要导入的依赖

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    + +

    标注的注解

    +

    @SpringBootTest + @Test

    +

    两个不同版本的 @Test 注解

    +

    image-20230827112742347

    +

    2.JUnit5常用注解

    JUnit5的注解与JUnit4的注解有所变化

    +

    https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

    +
      +
    • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
    • +
    • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
    • +
    • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
    • +
    • @DisplayName :为测试类或者测试方法设置展示名称
    • +
    +

    image-20230827114256749

    +
      +
    • @BeforeEach :表示在每个单元测试之前执行

      +
    • +
    • @AfterEach :表示在每个单元测试之后执行

      +
    • +
    • @BeforeAll :表示在所有单元测试之前执行

      +
    • +
    • @AfterAll :表示在所有单元测试之后执行

      +
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    package com.atguigu.boot;

    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/27
    * @Description 测试junit5的相关注解
    */
    @Slf4j
    @DisplayName("Junit5Test测试类")
    @SpringBootTest
    public class Junit5Test {
    /**
    * @DisplayName 加在方法或者类上,为测试类或者测试方法设置展示名称
    */
    @Disabled
    @DisplayName("测试@DisplayName注解")
    @Test
    void testDisplayName(){
    log.info("@DisplayName注解的测试");
    }

    /**
    * 在执行的单元测试方法之前运行
    */
    @BeforeEach
    void testBeforeEach(){
    log.info("执行了testBeforeEach()方法");
    }

    /**
    * 在执行的单元测试方法之后运行
    */
    @AfterEach
    void testAfterEach() {
    log.info("执行了testAfterEach()方法");
    }

    /**
    * 在所有的单元测试执行之后执行
    * 注意是静态方法
    */
    @BeforeAll
    static void testBeforeAll() {
    log.info("执行了testBeforeAll()方法");
    }

    /**
    * 在所有的单元测试执行之前执行
    * 注意是静态方法
    */
    @AfterAll
    static void testAfterAll() {
    log.info("执行了testAfterAll()方法");
    }
    }
    + +
      +
    • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
    • +
    • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
    • +
    +

    image-20230827115900097

    +
      +
    • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 规定方法超时时间 超出时间测试出异常
    */
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
    Thread.sleep(600);
    }
    + +
      +
    • @ExtendWith :为测试类或测试方法提供扩展类引用
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 重复测试
    */
    @RepeatedTest(5)
    @Test
    void testRepeatedTest() {
    log.info("重复测试中");
    }
    + +

    image-20230828095915653

    +

    全部的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    package com.atguigu.boot;

    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;

    import java.util.concurrent.TimeUnit;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/27
    * @Description 测试junit5的相关注解
    */
    @Slf4j
    @DisplayName("Junit5Test测试类")
    @SpringBootTest
    public class Junit5Test {
    /**
    * @DisplayName 加在方法或者类上,为测试类或者测试方法设置展示名称
    */
    @Disabled
    @DisplayName("测试@DisplayName注解")
    @Test
    void testDisplayName(){
    log.info("@DisplayName注解的测试");
    }

    /**
    * 在执行的单元测试方法之前运行
    */
    @BeforeEach
    void testBeforeEach(){
    log.info("执行了testBeforeEach()方法");
    }

    /**
    * 在执行的单元测试方法之后运行
    */
    @AfterEach
    void testAfterEach() {
    log.info("执行了testAfterEach()方法");
    }

    /**
    * 在所有的单元测试执行之后执行
    * 注意是静态方法
    */
    @BeforeAll
    static void testBeforeAll() {
    log.info("执行了testBeforeAll()方法");
    }

    /**
    * 在所有的单元测试执行之前执行
    * 注意是静态方法
    */
    @AfterAll
    static void testAfterAll() {
    log.info("执行了testAfterAll()方法");
    }
    /**
    * 规定方法超时时间 超出时间测试出异常
    */
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
    Thread.sleep(600);
    }

    /**
    * 重复测试
    */
    @RepeatedTest(5)
    @Test
    void testRepeatedTest() {
    log.info("重复测试中");
    }


    }
    + +

    3.断言

    ​ 断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。

    +

    3.1 简单断言

    用来对单个值进行简单的验证

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    方法说明
    assertEquals判断两个对象或两个原始类型是否相等
    assertNotEquals判断两个对象或两个原始类型是否不相等
    assertSame判断两个对象引用是否指向同一个对象
    assertNotSame判断两个对象引用是否指向不同的对象
    assertTrue判断给定的布尔值是否为 true
    assertFalse判断给定的布尔值是否为 false
    assertNull判断给定的对象引用是否为 null
    assertNotNull判断给定的对象引用是否不为 null
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    @DisplayName("简单断言")
    public void simple() {
    assertEquals(3, 1 + 2, "simple math");
    assertNotEquals(3, 1 + 1);

    assertNotSame(new Object(), new Object());
    Object obj = new Object();
    assertSame(obj, obj);

    assertFalse(1 > 2);
    assertTrue(1 < 2);

    assertNull(null);
    assertNotNull(new Object());
    }
    + +

    3.2 数组断言

    通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等

    +
    1
    2
    3
    4
    5
    @Test
    @DisplayName("数组断言")
    public void array() {
    assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
    }
    + +

    3.3 组合断言

    assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

    +
    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    @DisplayName("组合断言")
    public void all() {
    assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
    );
    }
    + +

    3.4 异常断言

    在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    @DisplayName("异常测试")
    public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
    //扔出断言异常
    ArithmeticException.class, () -> System.out.println(1 % 0));

    }
    + +

    3.5 超时断言

    Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

    +
    1
    2
    3
    4
    5
    6
    @Test
    @DisplayName("超时测试")
    public void timeoutTest() {
    //如果测试方法时间超过1s将会异常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
    }
    + +

    3.6 快速失败

    通过 fail 方法直接使得测试失败

    +
    1
    2
    3
    4
    5
    @Test
    @DisplayName("快速失败")
    public void shouldFail() {
    fail("This should fail");
    }
    + +

    全部代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    package com.atguigu.admin;

    import org.junit.jupiter.api.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;

    import java.util.concurrent.TimeUnit;

    import static org.junit.jupiter.api.Assertions.*;



    /*@RunWith*/

    /**
    * @BootstrapWith(SpringBootTestContextBootstrapper.class)
    * @ExtendWith(SpringExtension.class)
    */
    //@SpringBootTest
    @DisplayName("junit5功能测试类")
    public class Junit5Test {

    /**
    * 断言:前面断言失败,后面的代码都不会执行
    */
    @DisplayName("简单断言")
    @Test
    void testSimpleAssertions() {
    int cal = cal(3, 2);
    //相等
    assertEquals(6, cal, "业务逻辑计算失败");
    Object obj1 = new Object();
    Object obj2 = new Object();
    assertSame(obj1, obj2, "两个对象不一样");

    }

    @Test
    @DisplayName("数组断言")
    void array() {
    assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}, "数组内容不相等");
    }

    @Test
    @DisplayName("组合断言")
    void all() {
    /**
    * 所有断言全部需要成功
    */
    assertAll("test",
    () -> assertTrue(true && true, "结果不为true"),
    () -> assertEquals(1, 2, "结果不是1"));

    System.out.println("=====");
    }

    @DisplayName("异常断言")
    @Test
    void testException() {

    //断定业务逻辑一定出现异常
    assertThrows(ArithmeticException.class, () -> {
    int i = 10 / 2;
    }, "业务逻辑居然正常运行?");
    }

    @DisplayName("快速失败")
    @Test
    void testFail(){
    if(1 == 2){
    fail("测试失败");
    }

    }

    int cal(int i, int j) {
    return i + j;
    }
    }
    + +

    4.前置条件

    ​ JUnit 5 中的前置条件(assumptions[假设])类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @DisplayName("前置条件")
    public class AssumptionsTest {
    private final String environment = "DEV";

    @Test
    @DisplayName("simple")
    public void simpleAssume() {
    assumeTrue(Objects.equals(this.environment, "DEV"));
    assumeFalse(() -> Objects.equals(this.environment, "PROD"));
    }

    @Test
    @DisplayName("assume then do")
    public void assumeThenDo() {
    assumingThat(
    Objects.equals(this.environment, "DEV"),
    () -> System.out.println("In DEV")
    );
    }
    }
    + +

    assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止

    +

    assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象

    +

    只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止

    +

    5.嵌套测试

    ​ JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    @DisplayName("A stack")
    class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
    new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

    @BeforeEach
    void createNewStack() {
    stack = new Stack<>();
    }

    @Test
    @DisplayName("is empty")
    void isEmpty() {
    assertTrue(stack.isEmpty());
    }

    @Test
    @DisplayName("throws EmptyStackException when popped")
    void throwsExceptionWhenPopped() {
    assertThrows(EmptyStackException.class, stack::pop);
    }

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
    assertThrows(EmptyStackException.class, stack::peek);
    }

    @Nested//b
    @DisplayName("after pushing an element")
    class AfterPushing {

    String anElement = "an element";

    @BeforeEach
    void pushAnElement() {
    stack.push(anElement);
    }

    @Test
    @DisplayName("it is no longer empty")
    void isNotEmpty() {
    assertFalse(stack.isEmpty());
    }

    @Test
    @DisplayName("returns the element when popped and is empty")
    void returnElementWhenPopped() {
    assertEquals(anElement, stack.pop());
    assertTrue(stack.isEmpty());
    }

    @Test
    @DisplayName("returns the element when peeked but remains not empty")
    void returnElementWhenPeeked() {
    assertEquals(anElement, stack.peek());
    assertFalse(stack.isEmpty());
    }
    }
    }
    }
    + +

    6.参数化测试

    参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利

    +

    利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码

    +

    @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

    +

    @NullSource: 表示为参数化测试提供一个null的入参

    +

    @EnumSource: 表示为参数化测试提供一个枚举入参

    +

    @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

    +

    @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

    +

    当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @ParameterizedTest
    @ValueSource(strings = {"one", "two", "three"})
    @DisplayName("参数化测试")
    public void parameterizedTest(String string) {
    System.out.println(string);
    Assertions.assertTrue(StringUtils.isNotBlank(string));
    }


    @ParameterizedTest
    @MethodSource("method") //指定方法名
    @DisplayName("方法来源参数")
    public void testWithExplicitLocalMethodSource(String name) {//方法的参数来源于一个方法
    System.out.println(name);
    Assertions.assertNotNull(name);
    }

    static Stream<String> method() {
    return Stream.of("apple", "banana");
    }
    + +

    7.迁移指南

    在进行迁移(Junit4迁移到Junit5)的时候需要注意如下的变化:

    +
      +
    • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
    • +
    • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
    • +
    • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
    • +
    • 把@Ignore 替换成@Disabled。
    • +
    • 把@Category 替换成@Tag。
    • +
    • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。
    • +
    +

    五.指标监控

    1.SpringBoot Actuator

    ​ 未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能

    +

    1.1 如何使用

    1.引入依赖

    +
    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    + +

    2.测试访问 http://localhost:8080/actuator/ endpoint节点

    +

    image-20230828110908056

    +

    image-20230828110928951

    +

    3.设置以web的方式访问所有的端点

    +
    1
    2
    3
    4
    5
    6
    management:
    endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
    exposure:
    include: '*' #以web方式暴露所有的端点
    + +

    设置之后就可以通过web(访问 http://localhost:8080/actuator/beans)的方式访问beans端点了

    +

    image-20230828113335033

    +

    支持的暴露方式

    +
      +
    • HTTP(web):默认只暴露health和info Endpoint,只能访问 http://localhost:8080/actuator/health和 http://localhost:8080/actuator/info
    • +
    • JMX(如Jconsole):默认暴露所有Endpoint,类似于cmd下打开jconsole,可以访问下面所有的endPoints
    • +
    • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
    • +
    +

    是通过web还是jmx方式访问的端点一览表

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IDJMXWeb
    auditeventsYesNo
    beansYesNo
    cachesYesNo
    conditionsYesNo
    configpropsYesNo
    envYesNo
    flywayYesNo
    healthYesYes
    heapdumpN/ANo
    httptraceYesNo
    infoYesYes
    integrationgraphYesNo
    jolokiaN/ANo
    logfileN/ANo
    loggersYesNo
    liquibaseYesNo
    metricsYesNo
    mappingsYesNo
    prometheusN/ANo
    scheduledtasksYesNo
    sessionsYesNo
    shutdownYesNo
    startupYesNo
    threaddumpYesNo
    +

    1.2 常用的EndPoints

    http://localhost:8080/actuator/endPoint

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ID描述
    auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
    beans显示应用程序中所有Spring Bean的完整列表。
    caches暴露可用的缓存。
    conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因。
    configprops显示所有@ConfigurationProperties
    env暴露Spring的属性ConfigurableEnvironment
    flyway显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
    health显示应用程序运行状况信息。
    httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
    info显示应用程序信息。
    integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
    loggers显示和修改应用程序中日志的配置。
    liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
    metrics显示当前应用程序的“指标”信息。
    mappings显示所有@RequestMapping路径列表。
    scheduledtasks显示应用程序中的计划任务。
    sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
    shutdown使应用程序正常关闭。默认禁用。
    startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
    threaddump执行线程转储。
    +

    如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

    + + + + + + + + + + + + + + + + + + + + + + + +
    ID描述
    heapdump返回hprof堆转储文件。
    jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
    logfile返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
    prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus
    +

    最常用的Endpoint

    +
      +
    • Health:监控状况
    • +
    • Metrics:运行时指标
    • +
    • Loggers:日志记录
    • +
    +

    1.3 Health Endpoint

    健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

    +

    重要的几点:

    +
      +
    • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
    • +
    • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
    • +
    • 可以很容易的添加自定义的健康检查机制
    • +
    +

    设置显示health端点的详细信息

    +
    1
    2
    3
    4
    management:
    endpoint:
    health:
    show-details: always
    + +

    image-20230828142631189

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    {
    "status": "UP",
    "components": {
    "db": {
    "status": "UP",
    "details": {
    "database": "MySQL",
    "validationQuery": "isValid()"
    }
    },
    "diskSpace": {
    "status": "UP",
    "details": {
    "total": 510769754112,
    "free": 342642921472,
    "threshold": 10485760,
    "exists": true
    }
    },
    "ping": {
    "status": "UP"
    }
    }
    }
    + +

    1.4 Metrics Endpoint

    提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

    +
      +
    • 通过Metrics对接多种监控系统
    • +
    • 简化核心Metrics开发
    • +
    • 添加自定义Metrics或者扩展已有Metrics
    • +
    +

    查看完整的监控指标信息 http://localhost:8080/actuator/metrics

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    {
    "names": [
    "hikaricp.connections",
    "hikaricp.connections.acquire",
    "hikaricp.connections.active",
    "hikaricp.connections.creation",
    "hikaricp.connections.idle",
    "hikaricp.connections.max",
    "hikaricp.connections.min",
    "hikaricp.connections.pending",
    "hikaricp.connections.timeout",
    "hikaricp.connections.usage",
    "http.server.requests",
    "jdbc.connections.max",
    "jdbc.connections.min",
    "jvm.buffer.count",
    "jvm.buffer.memory.used",
    "jvm.buffer.total.capacity",
    "jvm.classes.loaded",
    "jvm.classes.unloaded",
    "jvm.gc.live.data.size",
    "jvm.gc.max.data.size",
    "jvm.gc.memory.allocated",
    "jvm.gc.memory.promoted",
    "jvm.gc.pause",
    "jvm.memory.committed",
    "jvm.memory.max",
    "jvm.memory.used",
    "jvm.threads.daemon",
    "jvm.threads.live",
    "jvm.threads.peak",
    "jvm.threads.states",
    "logback.events",
    "process.cpu.usage",
    "process.start.time",
    "process.uptime",
    "system.cpu.count",
    "system.cpu.usage"
    ]
    }
    + +

    image-20230828143224633

    +

    查看某一个监控指标的详细信息(例如查看hikaricp.connections的详细信息) http://localhost:8080/actuator/metrics/hikaricp.connections

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "name": "hikaricp.connections",
    "description": "Total connections",
    "baseUnit": null,
    "measurements": [
    {
    "statistic": "VALUE",
    "value": 10
    }
    ],
    "availableTags": [
    {
    "tag": "pool",
    "values": [
    "HikariPool-1"
    ]
    }
    ]
    }
    + +

    1.5 管理Endpoint

    开启与禁用Endpoints

    +
      +
    • 默认所有的Endpoint除过shutdown都是开启的。
    • +
    • 需要开启或者禁用某个Endpoint 配置模式为 management.endpoint.endpointName.enabled = true
    • +
    +
    1
    2
    3
    4
    management:
    endpoint:
    beans: #可以写health、metrics等端点
    enabled: true
    + +
      +
    • 或者禁用所有的Endpoint然后手动开启指定的Endpoint
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    management:
    endpoints:
    enabled-by-default: false #禁用所有的端点(jconsole中也无法使用)
    endpoint:
    beans:
    enabled: true #逐一开启端点
    health:
    enabled: true
    + +

    1.6 定制Endpoint

    1.6.1 定制health endpoint

    1.编写MyComHealthIndicator类继承AbstractHealthIndicator

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package com.atguigu.boot.acutuator.health;

    import org.springframework.boot.actuate.health.AbstractHealthIndicator;
    import org.springframework.boot.actuate.health.Health;
    import org.springframework.boot.actuate.health.Status;
    import org.springframework.stereotype.Component;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * 自定义端点
    * 类的后缀必须为HealthIndicator
    */
    @Component
    public class MyComHealthIndicator extends AbstractHealthIndicator {

    /**
    * 真实的检查方法 检查健康的方法
    */
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
    Map<String,Object> map = new HashMap<>();
    // 检查完成
    if(1 == 1){ //这里我们可以编写一个业务逻辑,例如数据库是否连接成功的业务逻辑
    // builder.up(); //健康
    builder.status(Status.UP);
    map.put("count",1);
    map.put("ms",100);
    }else {
    //builder.down();
    builder.status(Status.OUT_OF_SERVICE);
    map.put("err","连接超时");
    map.put("ms",3000);
    }
    builder.withDetail("code",100)
    .withDetails(map);

    }
    }
    + +

    myCom = MyComHealthIndicator - HealthIndicator 所以类的名字一定要以HealthIndicator为后缀

    +

    image-20230828150633322

    +
    1.6.2 定制 info endpoint

    方式一: 编写配置文件

    +
    1
    2
    3
    4
    5
    info:
    appName: boot-admin #应用名
    version: 2.0.1 #版本
    mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
    mavenProjectVersion: @project.version@
    + +

    image-20230828151416487

    +

    方式二: 编写InfoContributor

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.atguigu.boot.acutuator.info;

    import org.springframework.boot.actuate.info.Info;
    import org.springframework.boot.actuate.info.InfoContributor;
    import org.springframework.stereotype.Component;

    import java.util.Collections;


    @Component
    public class AppInfoInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {

    builder.withDetail("msg","你好")
    .withDetail("hello","atguigu")
    .withDetails(Collections.singletonMap("world","666600"));
    }
    }
    + +

    如果我们没有删除上面配置文件的配置,这时我们访问 http://localhost:8080/actuator/info 的时候会将配置文件和InfoContributor中的信息共同返回

    +

    image-20230828152006549

    +
    1.6.3 定制Metrics信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class MyService{

    Counter counter;

    //构造器注入MeterRegistry
    public MyService(MeterRegistry meterRegistry){
    //访问 http://localhost:8080/actuator/metrics 的时候会多一个监控项myservice.method.running.counter
    counter = meterRegistry.counter("myservice.method.running.counter");
    }

    public void hello() {
    //调用hello方法的时候counter加1
    counter.increment();
    }
    }

    //也可以使用下面的方式
    @Bean
    MeterBinder queueSize(Queue queue) {
    return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
    }
    + +

    1.7 新增Endpoint

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.atguigu.boot.acutuator.endpoint;


    import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
    import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
    import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
    import org.springframework.stereotype.Component;

    import java.util.Collections;
    import java.util.Map;


    @Component
    @Endpoint(id = "myservice")
    public class MyServiceEndPoint {


    @ReadOperation
    public Map getDockerInfo(){
    //端点的读操作 http://localhost:8080/actuator/myservice
    return Collections.singletonMap("dockerInfo","docker started.....");
    }

    @WriteOperation
    public void stopDocker(){
    System.out.println("docker stopped.....");
    }
    }
    + +

    image-20230828153914619

    +

    image-20230828153902598

    +

    1.8 可视化

    github地址: https://github.com/codecentric/spring-boot-admin

    +

    项目文档: https://docs.spring-boot-admin.com/current/getting-started.html

    +

    1.创建一个新的springBoot项目用作服务端(场景只需要选择web场景即可)

    +

    image-20230828154818630

    +

    2.引入依赖

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.3.1</version>
    </dependency>
    + +

    3.在启动类上添加注解

    +
    1
    @EnableAdminServer
    + +

    4.修改以下端口号以防和业务的端口冲突

    +
    1
    server.port=8888
    + +

    5.访问http://localhost:8888

    +

    image-20230828155537932

    +

    6.客户端上引入依赖

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.3.1</version>
    </dependency>
    + +

    7.客户端的配置文件添加以下的配置

    +
    1
    2
    spring.boot.admin.client.url=http://localhost:8888 #服务端的地址
    management.endpoints.web.exposure.include=*
    + +

    8.重启一下客户端,然后访问服务端的localhost:8080

    +

    image-20230828160606081

    +

    点击应用墙,点击需要查看信息的应用就可以看到应用的详细信息

    +

    image-20230828160731314

    +

    六.原理解析

    1.Profile功能

    为了方便多环境适配,springboot简化了profile功能

    +
      +
    • 默认配置文件 application.yaml;任何时候都会加载

      +
    • +
    • 指定环境配置文件 application-{env}.yaml

      +
    • +
    • 激活指定环境

      +
    • +
      • +
      • 配置文件激活,在默认配置文件 application.yaml 中添加spring.profiles.active=xxxx
      • +
      • 命令行激活:java -jar xxx.jar –spring.profiles.active=prod
      • +
      +
    • +
      • +
        • +
        • 修改配置文件的任意值,命令行优先
        • +
        +
      • +
      +
    • +
    • 默认配置与环境配置同时生效

      +
    • +
    • 同名配置项,profile配置优先

      +
    • +
    +

    @Profile注解

    +
    1
    2
    3
    //@Profile("prod")
    //可以标注在类和方法上
    //标注在类和方法上表示在prod环境下启用
    + +

    外部配置源

    +

    常用:Java属性文件、YAML文件、环境变量、命令行参数;

    +

    配置文件查找位置

    +

    下面的优先级覆盖上面的优先级

    +

    (1) classpath 根路径

    +

    (2) classpath 根路径下config目录

    +

    (3) jar包当前目录

    +

    (4) jar包当前目录的config目录

    +

    (5) /config子目录的直接子目录

    +

    配置文件加载顺序:

    +
      +
    1.  当前jar包内部的application.properties和application.yml
    2. +
    3.  当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
    4. +
    5.  引用的外部jar包的application.properties和application.yml
    6. +
    7.  引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
    8. +
    +

    总结: 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    目录
    1. 1. 一.SpringBoot2核心技术-基础入门
      1. 1.1. 1.Spring能做什么
      2. 1.2. 2.SpringBoot
        1. 1.2.1. 2.1 SpringBoot的优点
        2. 1.2.2. 2.2、SpringBoot缺点
        3. 1.2.3. 2.3 官方文档
      3. 1.3. 3.SpringBoot入门
        1. 1.3.1. 1.系统的要求
        2. 1.3.2. 2.HelloWorld
        3. 1.3.3. 3.简化配置
        4. 1.3.4. 4.简化部署
      4. 1.4. 4.了解自动装配原理
        1. 1.4.1. 4.1 依赖管理
          1. 1.4.1.1. 1.SpringBoot的自动版本仲裁机制
          2. 1.4.1.2. 2.Starters场景启动器
        2. 1.4.2. 4.2 自动配置
        3. 1.4.3. 4.3 容器功能
          1. 1.4.3.1. 1.组件添加
            1. 1.4.3.1.1. 1.1 @Configuration
            2. 1.4.3.1.2. 1.2 @Bean @Component @Controller @Service @Repository
            3. 1.4.3.1.3. 1.3 @ComponentScan @Import
            4. 1.4.3.1.4. 1.4 @Conditional 条件装配:满足Conditional指定的条件,则进行组件注入
          2. 1.4.3.2. 2.原生配置文件的引入
            1. 1.4.3.2.1. 2.1、@ImportResource
          3. 1.4.3.3. 3.配置绑定
            1. 1.4.3.3.1. 3.1 @ConfigurationProperties
        4. 1.4.4. 4.4 自动配置原理入门
          1. 1.4.4.1. 1.引导加载自动配置类
          2. 1.4.4.2. 2.按需开启自动配置项
          3. 1.4.4.3. 3.修改默认配置
          4. 1.4.4.4. 3.4.最佳实践
        5. 1.4.5. 4.5 简化开发
          1. 1.4.5.1. 1.Lombok
          2. 1.4.5.2. 2.简化日志开发
        6. 1.4.6. 3.dev-tools
        7. 1.4.7. 4.Spring Initailizr(项目初始化向导)
    2. 2.
    3. 3. 二.SpringBoot2核心技术-核心功能
      1. 3.1. 一.配置文件
        1. 3.1.1. 1、文件类型
          1. 3.1.1.1. 1.1、properties
          2. 3.1.1.2. 1.2、yaml
            1. 3.1.1.2.1. 1.2.1、简介
            2. 3.1.1.2.2. 1.2.2、基本语法
            3. 3.1.1.2.3. 1.2.3、数据类型
            4. 3.1.1.2.4. 1.2.4、示例
        2. 3.1.2. 2、配置提示
      2. 3.2. 二.Web开发
        1. 3.2.1. 1.简单的功能分析
          1. 3.2.1.1. 1.1 静态资源访问
            1. 3.2.1.1.1. 1.静态资源目录
            2. 3.2.1.1.2. 2.静态资源访问前缀
            3. 3.2.1.1.3. 3.webjar
          2. 3.2.1.2. 1.2 欢迎页支持
          3. 3.2.1.3. 1.3 自定义 Favicon
          4. 3.2.1.4. 1.4 静态资源配置原理
        2. 3.2.2. 2.请求参数处理与数据响应
          1. 3.2.2.1. 2.1、请求映射
            1. 3.2.2.1.1. 1、rest使用与原理
            2. 3.2.2.1.2. 2、请求映射原理
          2. 3.2.2.2. 2.2、普通参数与基本注解
            1. 3.2.2.2.1. 1.注解
            2. 3.2.2.2.2. 2.Servlet API
            3. 3.2.2.2.3. 3.复杂参数:
            4. 3.2.2.2.4. 4.自定义对象参数
          3. 3.2.2.3. 2.3 参数处理原理
          4. 3.2.2.4. 2.4 数据响应与内容协商
            1. 3.2.2.4.1. 2.4.1 相关依赖的引入
            2. 3.2.2.4.2. 2.4.2 内容协商原理
            3. 3.2.2.4.3. 2.4.3 自定义MessageConverter
        3. 3.2.3. 3.视图解析与模板引擎
          1. 3.2.3.1. 3.1 视图解析
          2. 3.2.3.2. 3.2 模板引擎-Thymeleaf
            1. 3.2.3.2.1. 3.2.1 Thymeleaf的使用
          3. 3.2.3.3. 3.3 后台管理系统注意点
        4. 3.2.4. 4.拦截器
          1. 3.2.4.1. 1.HandlerInterceptor接口
          2. 3.2.4.2. 2.拦截器实现登录检查操作
          3. 3.2.4.3. 3.拦截器原理
        5. 3.2.5. 5.文件上传
          1. 3.2.5.1. 1.页面表单
          2. 3.2.5.2. 2.文件上传代码
          3. 3.2.5.3. 3.MultipartAutoConfiguration
        6. 3.2.6. 6.异常处理
          1. 3.2.6.1. 1.默认规则
          2. 3.2.6.2. 2.自定义错误页面
        7. 3.2.7. 7.Web原生组件注入(Servlet、Filter、Listener)
          1. 3.2.7.1. 7.1 使用Servlet Api
            1. 3.2.7.1.1. 1.原生的Servlet的使用
            2. 3.2.7.1.2. 2.原生的Filter的使用
            3. 3.2.7.1.3. 3.原生的Listener的使用
          2. 3.2.7.2. 7.2 使用RegistrationBean
        8. 3.2.8. 8.嵌入式Servlet容器
          1. 3.2.8.1. 8.1 切换嵌入式Servlet容器
          2. 3.2.8.2. 8.2 定制Servlet容器
      3. 3.3. 三.数据访问
        1. 3.3.1. 1.SQL
          1. 3.3.1.1. 1.1 JDBC的使用
          2. 3.3.1.2. 1.2 分析自动配置
      4. 3.4. 四.单元测试
        1. 3.4.1. 1.JUnit5的变化
        2. 3.4.2. 2.JUnit5常用注解
        3. 3.4.3. 3.断言
          1. 3.4.3.1. 3.1 简单断言
          2. 3.4.3.2. 3.2 数组断言
          3. 3.4.3.3. 3.3 组合断言
          4. 3.4.3.4. 3.4 异常断言
          5. 3.4.3.5. 3.5 超时断言
          6. 3.4.3.6. 3.6 快速失败
        4. 3.4.4. 4.前置条件
        5. 3.4.5. 5.嵌套测试
        6. 3.4.6. 6.参数化测试
        7. 3.4.7. 7.迁移指南
      5. 3.5. 五.指标监控
        1. 3.5.1. 1.SpringBoot Actuator
          1. 3.5.1.1. 1.1 如何使用
          2. 3.5.1.2. 1.2 常用的EndPoints
          3. 3.5.1.3. 1.3 Health Endpoint
          4. 3.5.1.4. 1.4 Metrics Endpoint
          5. 3.5.1.5. 1.5 管理Endpoint
          6. 3.5.1.6. 1.6 定制Endpoint
            1. 3.5.1.6.1. 1.6.1 定制health endpoint
            2. 3.5.1.6.2. 1.6.2 定制 info endpoint
            3. 3.5.1.6.3. 1.6.3 定制Metrics信息
          7. 3.5.1.7. 1.7 新增Endpoint
          8. 3.5.1.8. 1.8 可视化
      6. 3.6. 六.原理解析
        1. 3.6.1. 1.Profile功能
    最近更新
    \ No newline at end of file diff --git a/posts/60685.html b/posts/60685.html new file mode 100644 index 000000000..be4f7598e --- /dev/null +++ b/posts/60685.html @@ -0,0 +1,214 @@ +Java爬虫 | The Blog + + + + + + + + + + + + +

    Java爬虫

    视频教程1: 狂神说Java Jsoup爬虫入门实战 https://www.bilibili.com/video/BV1La4y1x7Wm?vd_source=aee5e475191b69e6c781059ab6662584

    +

    视频教程2:https://www.bilibili.com/video/BV1RU4y147eZ?vd_source=aee5e475191b69e6c781059ab6662584

    +

    具体的看视频 急速入门

    +

    入门实战教程

    1.引入依赖

    1
    2
    3
    4
    5
    6
    <!-- 爬取视频或者音频需要使用别的依赖 -->
    <dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.14.3</version>
    </dependency>
    + +

    2.编写测试代码

    下面我们以爬取京东上的商品的图片和价格为例

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    package com.jason;


    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;

    import java.io.IOException;
    import java.net.URL;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/9
    * @Description
    */
    public class HtmlParseUtil {
    public static void main(String[] args) throws IOException {
    HtmlParseUtil.parseJd("联想电脑");
    }

    /**
    * 爬取京东上的商品数据
    *
    * @param keywords 商品的品牌或者关键字 例如 联想电脑、华为手机、java书籍
    */
    public static void parseJd(String keywords) throws IOException {
    //获取请求
    String url = "https://search.jd.com/Search?keyword=" + keywords;//页面上的地址信息
    //解析网页
    //第一个参数是url地址
    //第二个参数是响应时间,超过30秒就报错
    //返回的document就是浏览器的Document对象
    Document document = Jsoup.parse(new URL(url), 30000);
    //所有在js中可以使用的方法,这里都可以使用
    Element element = document.getElementById("J_goodsList");//通过id获取元素
    //获取所有的li元素
    Elements elements = element.getElementsByTag("li");
    //遍历li元素的集合 获取每一个li元素
    for (Element el : elements) {
    //这种图片特别多的图片 会有一个懒加载
    String attr = el.getElementsByTag("img").eq(0).attr("data-lazy-img");//通过标签获取元素
    String title = el.getElementsByClass("p-name").eq(0).text();//通过class获取元素
    String price = el.getElementsByClass("p-price").eq(0).text();
    System.out.println("图片地址:https:" + attr);
    System.out.println("商品名称:" + title);
    System.out.println("商品价格:" + price);
    }
    }
    }
    + +

    3.运行测试

    image-20230509161845558

    +

    image-20230509161907139

    +

    示例

    爬取房天下上面的房价信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    package com.jason;

    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;

    import java.io.IOException;
    import java.net.URL;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/9
    * @Description
    */
    public class ParseHouse {
    public static void main(String[] args) throws IOException {
    //爬起房天下上面的房源数据
    int i = 91;
    while (true){//换页读取 页面有分页条的情况
    String url = "https://newhouse.fang.com/house/s/b"+i;
    System.out.println("正在爬取第"+i+"页的数据.....");
    ParseHouse.selectHouses(url);
    i++;
    }

    }

    public static void selectHouses(String url) throws IOException {
    Document document = Jsoup.parse(new URL(url), 30000);
    Element element = document.getElementById("newhouse_loupan_list");
    Elements elements = element.getElementsByTag("li");
    for (Element el : elements) {
    String images = el.getElementsByTag("img").eq(1).attr("src");
    String alt = el.getElementsByTag("img").eq(1).attr("alt");
    String price = el.getElementsByClass("nhouse_price").text();
    System.out.println("图片:https:"+images);
    System.out.println("名称:"+alt);
    System.out.println("房价:"+price);
    }
    }
    }
    + +

    image-20230509180334969

    +

    image-20230509180435467

    +

    爬取豆瓣电影排行榜的信息

    image-20230509180547905

    +

    爬取全国各地房价的信息到excel表格中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    package com.jason;

    import com.alibaba.excel.EasyExcel;
    import com.jason.entity.HorseInfo;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;

    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/9
    * @Description 爬取全国各地房价的信息到excel表格中
    */
    public class HousePriseList {
    public static void main(String[] args) throws IOException {
    //爬取全国房价排行榜
    String fileName = "C:\\AlYun\\work\\housePriceList.xlsx";
    //通过下面的中间表,我们可以实现向excel中追加数据 不是每次删除之前的数据再写入 或者追加的时候有多个表头信息
    String temp = "C:\\AlYun\\work\\temp.xlsx"; //中间表 会自动被删除
    int i = 1;
    while (true) {
    try {
    String url = "https://fangjia.gotohui.com/top/" + i + ".html";
    List<HorseInfo> horseInfos = HousePriseList.selectHorse(url);
    File file = new File(fileName);
    File tempFile = new File(temp);
    if (file.exists()) { //判断excel表中是否写入过数据 写入过数据就追加数据
    // 第二次按照原有格式,不需要表头,追加写入
    EasyExcel.write(file, HorseInfo.class).needHead(false).
    withTemplate(file).file(tempFile).sheet().doWrite(horseInfos);
    } else {
    // 第一次写入需要表头
    EasyExcel.write(file, HorseInfo.class).sheet().doWrite(horseInfos);
    }
    if (tempFile.exists()) {
    file.delete();
    tempFile.renameTo(file);
    }
    i++;
    Thread.sleep(2000);
    } catch (Exception e) {
    e.printStackTrace();
    break;
    }
    }
    }

    public static List<HorseInfo> selectHorse(String url) throws IOException {
    Document document = Jsoup.parse(new URL(url), 30000);
    Elements elements = document.getElementsByClass("ntable table-striped table-hover");
    ArrayList<HorseInfo> horseInfos = new ArrayList<>();
    for (Element element : elements) {
    Elements tr = element.getElementsByTag("tr");
    for (Element el : tr) {
    Elements td = el.getElementsByTag("td");
    System.out.println("-----------------------------------------");
    System.out.println("编号:" + td.eq(0).text());
    System.out.println("城市:" + td.eq(1).text());
    System.out.println("单价(元/㎡):" + td.eq(2).text());
    System.out.println("同比(去年):" + td.eq(3).text());
    System.out.println("环比(上月):" + td.eq(4).text());
    System.out.println("收入比:" + td.eq(5).text());
    if (!td.eq(0).text().equals("")) {
    horseInfos.add(new HorseInfo(
    td.eq(0).text(),
    td.eq(1).text(),
    td.eq(2).text(),
    td.eq(3).text(),
    td.eq(4).text(),
    td.eq(5).text()
    ));
    }

    }
    }
    return horseInfos;
    }
    }
    + +

    image-20230509233922789

    +

    image-20230509234019249

    +
    \ No newline at end of file diff --git a/posts/60780.html b/posts/60780.html new file mode 100644 index 000000000..653912ffb --- /dev/null +++ b/posts/60780.html @@ -0,0 +1,200 @@ +xShell自定义配色方案 | The Blog + + + + + + + + + + + + +

    xShell自定义配色方案

    自定义配色方案

    +

    20230321180350

    +

    20230321180405

    +

    字体大小的设置

    +

    20230321180420

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/62439.html b/posts/62439.html new file mode 100644 index 000000000..a269ac2df --- /dev/null +++ b/posts/62439.html @@ -0,0 +1,271 @@ +Java生成二维码 | The Blog + + + + + + + + + + + + +

    Java生成二维码

    SpringBoot + zxing 生成二维码

    +

    SpringBoot + qrcode生成二维码

    +

    原视频地址: Java生成二维码教程 | 两小时学会Java生成二维码

    +

    源码地址: https://gitee.com/JasonsGong/two-dimensional-code

    +
    + +

    一.谷歌zxing开源库生成二维码

    1.创建一个sprinBoot项目

    image-20230828215513289

    +

    2.引入相关的依赖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.1.0</version>
    </dependency>

    <dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.1.0</version>
    </dependency>

    <dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    </dependencies>
    + +

    3.在templates目录下新建index.html文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>

    </body>
    </html>
    + +

    4.编写controller跳转到该页面,完成后重启项目,访问localhost:8080 测试

    1
    2
    3
    4
    5
    6
    7
    8
    @Controller
    public class CodeController {

    @GetMapping("/")
    public String index(){
    return "index";
    }
    }
    + +

    5.打开index.html,编写前端页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>二维码</title>
    </head>
    <body>
    <h1>谷歌zxing开源库生成黑白二维码</h1>
    <hr>
    请输入文本内容:<input type="text" id="url"><button onclick="generateQRCode()">生成二维码</button><br>
    <img id="qrCodeImg">
    <script>
    function generateQRCode(){
    //获取url
    let url = document.getElementById("url").value
    //设置img标签的src属性
    let qrCodeImg = document.getElementById("qrCodeImg")
    qrCodeImg.src = "/generate?url="+ url
    }
    </script>
    </body>
    </html>
    + +

    由于后端部分没有编写,这时我们访问项目的时候会返回404

    +

    image-20230828223201038

    +

    6.后端代码的编写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    package com.mine.code.controller;

    import com.google.zxing.BarcodeFormat;
    import com.google.zxing.EncodeHintType;
    import com.google.zxing.MultiFormatWriter;
    import com.google.zxing.WriterException;
    import com.google.zxing.aztec.encoder.AztecCode;
    import com.google.zxing.common.BitMatrix;
    import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;

    import javax.imageio.ImageIO;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/28
    * @Description
    */
    @Slf4j
    @Controller
    public class CodeController {

    @GetMapping("/")
    public String index() {
    return "index";
    }


    @GetMapping("/generate")
    public String generate(@RequestParam("url") String url, HttpServletResponse response) {
    log.info("文本内容:{}", url);
    try {
    //创建一个map集合,存储二维码的相关属性
    Map map = new HashMap<>();
    //EncodeHintType 编码的提示类型
    //设置二维码的误差校正级别 可选值有 L(7%) M(15%) Q(25%) H(30%)
    //选择L级别的容错率,相当于允许二维码在整体的颜色区域中,最多有7%的坏像素点;择H级别的容错率,相当于允许二维码在整体的颜色区域中,最多有30%的坏像素点
    map.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    //设置二维码的字符集
    map.put(EncodeHintType.CHARACTER_SET, "utf-8");
    //设置二维码四周的留白 1表示1像素
    map.put(EncodeHintType.MARGIN, 1);
    //创建zxing的核心对象MultiFormatWriter (多格式写入器)
    //通过MultiFormatWriter对象来生成二维码
    MultiFormatWriter writer = new MultiFormatWriter();
    //参数一:内容
    //参数二:二维码格式
    //BarcodeFormat(码格式) QR_CODE :常见的二维码格式之一,广泛应用于商品包装、扫码支付
    //AZTEC_CODE:高密度,可靠性很高 容错率更低 储存个人信息、证件信息、账户密码
    //PDF417 可以存储大量的信息 数据密度高 应用于航空机票、配送标签、法律文件
    //DATA_MATRIX: 小巧的二维码格式 编码格式类似于QR_CODE 但是优于QR_CODE 适合嵌入简单的产品标签 医疗图像 检测数据
    //参数三四:二维码的宽度和高度
    //参数五:二维码参数
    //位矩阵对象 (位矩阵对象对象的内部实际上是一个二位数组,二维数组中每一个元素是boolean类型 true代表黑色 false代表白色)
    BitMatrix bitMatrix = writer.encode(url, BarcodeFormat.QR_CODE, 300, 300, map);
    //获取矩阵的宽度和高度
    int width = bitMatrix.getWidth();
    int height = bitMatrix.getHeight();
    //生成二维码图片
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
    //遍历位矩阵对象
    for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
    //设置每一块的颜色值
    //0xFF000000表示黑色 0xFFFFFFFF表示白色
    image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
    }
    }
    //将图片响应到客户端
    ServletOutputStream out = response.getOutputStream();
    ImageIO.write(image,"png",out);
    out.flush();
    out.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    7.测试生成二维码

    image-20230828233052417

    +

    完整的项目结构

    +

    image-20230828233917645

    +

    8.生成一个带Logo的黑白二维码

    1.编写qrcode.html页面和跳转到该页面的controller

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>生成带logo的黑白二维码</title>
    </head>
    <body>
    <form action="/generateWithLogo" method="post" enctype="multipart/form-data">
    请输入文本内容:<input type="text" name="url"><br>
    请选择logo图片:<input type="file" name="logo"><br>
    <input type="submit" value="生成二维码">
    </form>
    </body>
    </html>
    + +
    1
    2
    3
    4
    5
    6
    7
    /**
    * 跳转到生成带logo的黑白二维码
    */
    @GetMapping("/logo")
    public String toLogo() {
    return "qrcode";
    }
    + +

    2.编写后端生成二维码逻辑

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    /**
    * 生成带logo的黑白二维码
    */
    @PostMapping("/generateWithLogo")
    public String generateWithLogo(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request) {
    try {
    Map map = new HashMap<>();
    map.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    map.put(EncodeHintType.CHARACTER_SET, "utf-8");
    map.put(EncodeHintType.MARGIN, 1);
    MultiFormatWriter writer = new MultiFormatWriter();
    BitMatrix bitMatrix = writer.encode(url, BarcodeFormat.QR_CODE, 300, 300, map);
    int width = bitMatrix.getWidth();
    int height = bitMatrix.getHeight();
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
    for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
    image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
    }
    }
    //给二维码添加logo
    //1.获取logo
    Part logoPart = request.getPart("logo");
    InputStream inputStream = logoPart.getInputStream();
    BufferedImage logoImage = ImageIO.read(inputStream);
    //2.对获取的logo图片进行缩放
    int logoWidth = logoImage.getWidth(null);
    int logoHeight = logoImage.getHeight(null);
    if (logoWidth > 60){
    logoWidth = 60;
    }
    if (logoHeight > 60){
    logoHeight = 60;
    }
    //使用平滑缩放算法对原始的logo图像进行缩放到一个全新的图像
    Image scaledLogo = logoImage.getScaledInstance(logoWidth, logoHeight, Image.SCALE_SMOOTH);
    //3.将缩放的图片画在黑白的二维码上
    //获取一个画笔
    Graphics2D graphics2D = image.createGraphics();
    //计算从哪里开始画 300指的是二维码的宽度和高度
    int x = (300 - logoWidth) /2;
    int y = (300 - logoHeight) /2;
    //画上去
    graphics2D.drawImage(scaledLogo,x,y,null);
    //实现logo的圆角效果
    Shape shape = new RoundRectangle2D.Float(x, y, logoWidth, logoHeight, 10, 10);
    //使用一个宽度为4像素的基本笔触
    graphics2D.setStroke(new BasicStroke(4f));
    //给logo画圆角矩形
    graphics2D.draw(shape);
    //释放画笔
    graphics2D.dispose();
    //将二维码响应到浏览器
    ImageIO.write(image, "png", response.getOutputStream());
    //关闭流
    inputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    + +

    访问 http://localhost:8080/logo 测试

    +

    实现的效果

    +

    image-20230829160901904

    +

    image-20230829160938795

    +

    二.github开源项目qrcode生成二维码

    1.引入依赖文件

    这里就不在重新创建工程了,直接在上面工程的基础上操作

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.liuyueyi.media</groupId>
    <artifactId>qrcode-plugin</artifactId>
    <version>2.5.2</version>
    </dependency>
    + +

    2.生成黑白二维码

    1.创建github-qrcode.html文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>qrcode生成黑白二维码</title>
    </head>
    <body>
    <h1>使用github上的开源项目qrcode生成二维码</h1>
    <hr>
    <form action="/generateWithQrCode" method="post" enctype="multipart/form-data">
    请输入文本内容:<input type="text" name="url"><br>
    请选择logo图片:<input type="file" name="logo"><br>
    <input type="submit" value="生成二维码">
    </form>
    </body>
    </html>
    + +

    2.编写controller处理请求

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.image.BufferedImage;
    import java.io.IOException;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成二维码
    BufferedImage image = QrCodeGenWrapper.of(url).asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    3.访问测试

    +

    image-20230829223442421

    +

    3.生成带有logo的黑白二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成二维码
    //生成黑白的二维码
    //BufferedImage image = QrCodeGenWrapper.of(url).asBufferedImage();
    //生成带logo的黑白二维码
    InputStream inputStream = request.getPart("logo").getInputStream();
    BufferedImage image = QrCodeGenWrapper.of(url)
    .setLogo(inputStream)//logo图片的输入流
    .setLogoRate(7)//设置logo图片和二维码之间的比例,7表示logo的宽度等于二维码的1/7
    .setLogoStyle(QrCodeOptions.LogoStyle.ROUND)//设置logo图片的样式,将logo的边框形状设置成圆形
    .asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    //关闭流
    inputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    image-20230829224339809

    +

    4.生成彩色的二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成彩色的二维码
    BufferedImage image = QrCodeGenWrapper.of(url).setDrawPreColor(Color.GREEN).asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    image-20230829225017032

    +

    5.生成带有背景图的二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成带有背景图的二维码
    InputStream inputStream = request.getPart("logo").getInputStream();
    BufferedImage image = QrCodeGenWrapper.of(url)
    .setBgImg(inputStream)
    .setBgOpacity(0.5F)//设置透明度
    .asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    //关闭流
    inputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    image-20230829225740156

    +

    6.生成特殊形状的二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成特殊形状的二维码
    BufferedImage image = QrCodeGenWrapper.of(url)
    .setDrawEnableScale(true)//启用二维码绘制时的缩放功能
    .setDrawStyle(QrCodeOptions.DrawStyle.DIAMOND)//绘制钻石形状的二维码
    .asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    image-20230829230236500

    +

    7.生成图片填充二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode() {
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request) {
    try {
    //生成图片填充二维码
    InputStream inputStream = request.getPart("logo").getInputStream();
    BufferedImage image = QrCodeGenWrapper.of(url)
    .setErrorCorrection(ErrorCorrectionLevel.H)//设置二维码的纠正级别
    .setDrawStyle(QrCodeOptions.DrawStyle.IMAGE)//绘制二维码的时候采用图片填充
    .addImg(1, 1, inputStream)//添加图片
    .asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image, "png", response.getOutputStream());
    //关闭流
    inputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    二维码中的每一个像素点都是上传的图片,生成的速度很慢

    +

    image-20230829230843477

    +
    \ No newline at end of file diff --git a/posts/6319.html b/posts/6319.html new file mode 100644 index 000000000..2f3e83f6d --- /dev/null +++ b/posts/6319.html @@ -0,0 +1,268 @@ +阿里云对象存储OSS | The Blog + + + + + + + + + + + + +

    阿里云对象存储OSS

    1.官网介绍

    ​ 阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,可提供99.9999999999%(12个9)的数据持久性,99.995%的数据可用性。多种存储类型供选择,全面优化存储成本。OSS具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。您可以使用阿里云提供的API、SDK接口或者OSS迁移工具轻松地将海量数据移入或移出阿里云OSS。数据存储到阿里云OSS以后,您可以选择标准存储(Standard)作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低、存储期限更长的低频访问存储(Infrequent Access)、归档存储(Archive)、冷归档存储(Cold Archive)作为不经常访问数据的存储方式。

    +

    2.使用

    2.1 使用的前置准备

    (1)申请阿里云账号
    (2)实名认证
    (3)开通“对象存储OSS”服务
    (4)进入管理控制台

    +

    2.2 创建Bucket

    点击创建Bucket

    +

    image-20230422210404204

    +

    填写相关的信息

    +

    image-20230422211448747

    +

    2.3获取AccessKey保存备用

    AccessKey拥有对阿里云提供服务(对象存储、短信服务、视频点播服务等等)的控制权,要妥善保管

    +

    image-20230422211940485

    +

    2.4 查看开发文档

    刚开始不会使用的话,查看开发文档是入门的最好方式

    +

    image-20230422212508029

    +

    2.5 入门案例

    创建一个SpringBoot工程

    +

    1.引入依赖

    可以引入一个日期工具类joda-time 方便后面生成图片存储的路径

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
    </dependency>
    + +

    2.修改配置文件

    入门案例中以下值会直接在代码中写死,先不从配置文件中获取

    +
    1
    2
    3
    4
    5
    6
    7
    8
    # 地域节点 bucket概览中查看
    aliyun.oss.endpoint=oss-cn-beijing.aliyuncs.com
    # accessKey
    aliyun.oss.accessKeyId=LTAI4G4SV6WtST7UYH77XXXX
    # secret
    aliyun.oss.secret=X9KHNYgztNr9MI5Zp8JffXXXXXXXX
    # bucket名
    aliyun.oss.bucket=yygh-atguigu
    + +

    3.创建读取配置文件的配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Component
    public class ConstantOssPropertiesUtils implements InitializingBean {

    @Value("${aliyun.oss.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;

    @Value("${aliyun.oss.secret}")
    private String secret;

    @Value("${aliyun.oss.bucket}")
    private String bucket;

    public static String EDNPOINT;
    public static String ACCESS_KEY_ID;
    public static String SECRECT;
    public static String BUCKET;

    @Override
    public void afterPropertiesSet() throws Exception {
    EDNPOINT=endpoint;
    ACCESS_KEY_ID=accessKeyId;
    SECRECT=secret;
    BUCKET=bucket;
    }
    }
    + +

    4.测试案例

    以下测试案例由官方开发文档提供

    +

    1.通过代码创建Bucket

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    // 创建存储空间。
    ossClient.createBucket(bucketName);

    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    2.上传文件

    +

    以下代码用于通过流式上传的方式将文件上传到OSS。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import java.io.ByteArrayInputStream;

    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";
    // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
    String objectName = "exampledir/exampleobject.txt";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    String content = "Hello OSS";
    ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    3.下载文件

    +

    以下代码用于通过流式下载方式从OSS下载文件。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.aliyun.oss.model.OSSObject;
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;

    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";
    // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
    String objectName = "exampledir/exampleobject.txt";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    // 调用ossClient.getObject返回一个OSSObject实例,该实例包含文件内容及文件元信息。
    OSSObject ossObject = ossClient.getObject(bucketName, objectName);
    // 调用ossObject.getObjectContent获取文件输入流,可读取此输入流获取其内容。
    InputStream content = ossObject.getObjectContent();
    if (content != null) {
    BufferedReader reader = new BufferedReader(new InputStreamReader(content));
    while (true) {
    String line = reader.readLine();
    if (line == null) break;
    System.out.println("\n" + line);
    }
    // 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
    content.close();
    }
    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    4.列举文件

    +

    以下代码用于列举examplebucket存储空间下的文件。默认列举100个文件。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.aliyun.oss.model.OSSObjectSummary;
    import com.aliyun.oss.model.ObjectListing;

    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    // ossClient.listObjects返回ObjectListing实例,包含此次listObject请求的返回结果。
    ObjectListing objectListing = ossClient.listObjects(bucketName);
    // objectListing.getObjectSummaries获取所有文件的描述信息。
    for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
    System.out.println(" - " + objectSummary.getKey() + " " +
    "(size = " + objectSummary.getSize() + ")");
    }
    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    5.删除文件

    +

    以下代码用于删除指定文件。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;

    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";
    // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
    String objectName = "exampledir/exampleobject.txt";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    // 删除文件。
    ossClient.deleteObject(bucketName, objectName);
    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    5.项目中使用OSS对象存储

    1.文件的上传操作

    controller层

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    import com.atguigu.yygh.common.result.Result;
    import com.atguigu.yygh.oss.service.OSSService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/22
    * @Description 对象存储的控制器方法
    */
    @RestController
    @RequestMapping("/api/oss/file")
    public class OSSController {

    @Autowired
    private OSSService ossService;

    /**
    * 上传文件到阿里云
    */
    @PostMapping("/fileUpload")
    public Result fileUpload(MultipartFile file){
    //上传文件,返回文件的url地址
    String url = ossService.upload(file);
    return Result.ok(url);
    }
    }
    + +

    service层

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.atguigu.yygh.oss.service.OSSService;
    import com.atguigu.yygh.oss.utils.ConstantOssPropertiesUtils;
    import org.joda.time.DateTime;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.UUID;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/22
    * @Description OSS文件相关操作
    */
    @Service
    public class OSSServiceImpl implements OSSService {
    /**
    * 上传文件
    *
    * @param file 文件
    * @return 文件的地址
    */
    @Override
    public String upload(MultipartFile file) {
    // Endpoint,地域节点信息
    String endpoint = ConstantOssPropertiesUtils.EDNPOINT;
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = ConstantOssPropertiesUtils.ACCESS_KEY_ID;
    String accessKeySecret = ConstantOssPropertiesUtils.SECRECT;
    // Bucket名称
    String bucketName = ConstantOssPropertiesUtils.BUCKET;
    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    //生成文件名 生成文件名的策略是日期/文件名.jpg 文件名由UUID随机生成
    //获取原始的文件名
    String originalFilename = file.getOriginalFilename();
    //分割出扩展名
    String suffixFilename = originalFilename.substring(originalFilename.lastIndexOf("."));
    //得到随机的文件名
    String prefixFilename = UUID.randomUUID().toString();
    //得到文件名
    String filename = prefixFilename + suffixFilename;
    //将文件按照日期进行分类 日期/文件名.jpg
    //获取当前的日期 使用时间作为目录
    String filePath = new DateTime().toString("yyyy/MM/dd");
    //得到最终的文件名
    filename = filePath + "/" + filename;
    try {
    //获取上传文件的流
    InputStream inputStream = file.getInputStream();
    //上传文件
    ossClient.putObject(bucketName, filename, inputStream);
    //拼接文件的url地址并返回
    //https://edu-guli-0306.oss-cn-beijing.aliyuncs.com/81ea0d8d-0e52-4749-be58-6292410deec6.jpg
    return "https://" + bucketName + "." + endpoint + "/" + filename;
    } catch (IOException e) {
    e.printStackTrace();
    return null;
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    3.Aliyun Spring Boot OSS

    [使用文档](aliyun-spring-boot/README-zh.md at master · alibaba/aliyun-spring-boot · GitHub)

    +

    相比与上面的Demo,这个Demo更加的简便,编写的代码比较少

    +

    项目说明

    +

    ​ 如果您的应用是 Spring Cloud 应用,且需要使用阿里云的 OSS 服务进行云端的文件存储,例如电商业务中常见的商品图片存储,那么您可以使用 OSS starter 完成 Spring Cloud 应用的对象存储。

    +

    阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。更多 OSS 相关的信息,请参考 OSS官网

    +

    1.引入依赖

    1
    2
    3
    4
    5
    6
    7
    <!-- 阿里云的对象存储的依赖-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
    <version>2.1.0.RELEASE</version>
    <type>pom</type>
    </dependency>
    + +

    2.在配置文件中增加配置

    1
    2
    3
    spring.cloud.alicloud.access-key=************
    spring.cloud.alicloud.secret-key=************
    spring.cloud.alicloud.oss.endpoint=************
    + +

    3.测试使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Autowired
    OSSClient ossClient;
    /**
    * 测试使用阿里云上传视频
    */
    @Test
    void testUpload() throws FileNotFoundException {
    String bucketName = "gulimall-0611";
    String objectName = "test1.jpg";
    InputStream inputStream = new FileInputStream("C:\\Gong\\data\\test.jpg");
    // 创建PutObjectRequest对象。
    ossClient.putObject(bucketName,objectName,inputStream);
    ossClient.shutdown();
    }
    + +

    4.在项目中使用OSS进行文件的上传和下载的操作

    服务端签名直传 使用这种方式可以减轻本地服务器的压力

    +

    参考文档:https://help.aliyun.com/document_detail/91868.htm?spm=a2c4g.31927.0.0.44193d70tJDLJb#concept-ahk-rfz-2fb

    +image-20230611214209712 + +

    4.1 获取文件上传相关的签名信息

    获取签名信息的接口

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    package com.atguigu.gulimall.thirdparty.controller;

    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.MatchMode;
    import com.aliyun.oss.model.PolicyConditions;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.LinkedHashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/11
    * @Description 阿里云对象存储的控制器方法
    */

    @RestController
    public class OSSController {

    @Autowired
    OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;
    @Value("${spring.cloud.alicloud.secret-key}")
    private String accessKey;

    @RequestMapping("/oss/policy")
    public Map<String, String> policy() {
    // 填写Host地址,格式为https://bucketname.endpoint。
    String host = "https://"+bucket+"."+endpoint;
    // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
    //String callbackUrl = "https://192.168.0.0:8888";
    // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
    String format = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
    String dir = format+"/";

    // 创建ossClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
    Map<String, String> respMap = null;
    try {
    long expireTime = 30;
    long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
    Date expiration = new Date(expireEndTime);
    PolicyConditions policyConds = new PolicyConditions();
    policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
    policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

    String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
    byte[] binaryData = postPolicy.getBytes("utf-8");
    String encodedPolicy = BinaryUtil.toBase64String(binaryData);
    String postSignature = ossClient.calculatePostSignature(postPolicy);

    respMap = new LinkedHashMap<String, String>();
    respMap.put("accessId", accessId);
    respMap.put("policy", encodedPolicy);
    respMap.put("signature", postSignature);
    respMap.put("dir", dir);
    respMap.put("host", host);
    respMap.put("expire", String.valueOf(expireEndTime / 1000));
    // respMap.put("expire", formatISO8601Date(expiration));
    } catch (Exception e) {
    System.out.println(e.getMessage());
    }
    return respMap;
    }
    }
    + +

    访问接口之后返回的数据格式

    +
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "accessId": "LTAI5tDDwk18w2S7w1KF24oT",//access-key
    "policy": "eyJleHBpcmF0aW9uIjoiMjAyMy0wNi0xMVQxNDoxNzozMi43OTdaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIzLzA2LzExLyJdXX0=",//策略,加密之后的结果
    "signature": "n67QffiYHceO2Hf7g8MDXgWsfpw=",//签名
    "dir": "2023/06/11/",//上传到的文件的地址
    "host": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com",//上传到的主机的
    "expire": "1686493052"//过期时间
    }
    + +

    前端相关的代码

    +

    前端相关的代码可以借鉴一下谷粒商城的前端上传文件的组件,里面封装了多文件上传和单文件上传的功能

    +

    image-20230612111805518

    +

    出现跨域的问题

    +

    image-20230612114314757

    +

    解决方法

    +

    image-20230612114238391

    +
    \ No newline at end of file diff --git a/posts/63333.html b/posts/63333.html new file mode 100644 index 000000000..c6ea45645 --- /dev/null +++ b/posts/63333.html @@ -0,0 +1,238 @@ +开发环境的搭建 | The Blog + + + + + + + + + + + + +

    开发环境的搭建

    1.后端环境搭建

    1.1 JDK环境的配置

    常规的安装,无需教程

    +

    IDEA中设置默认的JDK

    +

    image-20230604120610791

    +

    选中需要设置为默认JDK的JDK

    +

    image-20230604120654078

    +

    1.2 Maven的安装与配置

    环境变量的配置

    +

    MAVEN_HOME和Path 配置文件的配置

    +

    设置默认maven,每次创建新的工程的时候就会使用这个maven

    +

    image-20230604120307280

    +

    然后在弹出的页面中设置,这样每次创建新的工程就会使用当前设置的maven

    +

    image-20230604120436139

    +

    1.3 IDEA的配置

    展开目录树 取消勾选下面的选项

    +

    image-20230604151530534

    +

    快捷键的配置 IDEA中的全局查找的快捷键会和windows的中文的繁简字体切换的快捷键冲突,直接关掉系统的快捷键

    +

    image-20230604121126549

    +

    页面中字体的配置

    +image-20230603202245269 + +

    代码字体的配置

    +image-20230603154531129 + +

    设置代码粗体

    +image-20230603154622726 + +

    打开的多个文件多行显示,不在同一行显示

    +

    image-20230611211438017

    +

    浅色主题的配置

    +

    image-20230815171925527

    +

    修改注释的颜色

    +

    修改多行注释的颜色: 修改Doc comment -> Text的颜色

    +

    image-20230815172326472

    +

    1.4 mysql的安装

    具体的安装教程在《MySql5.7的安装教程的博客中》

    +

    1.5 Nacos的安装配置

    github下载之后,直接解压运行

    +

    下载的地址:https://github.com/alibaba/nacos/releases

    +

    踩坑:单节点部署的话,启动的时候需要配置一下,不然启动的时候会闪退

    +

    image-20230603193632659

    +

    2.前端环境的搭建

    2.1 Nodejs的安装与配置

    下载的网址:https://nodejs.org/zh-cn/download/releases

    +

    选择.msi的下载即可

    +

    不用配置环境变量,一直下一步,直到安装成功过,node自带npm包管理工具

    +
    1
    2
    3
    # 检验命令
    node -v
    npm -v
    + +

    3.前后端公共环境的搭建

    1.Git环境的搭建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #以下的操作在下载安装完毕之后进行
    #1.鼠标在桌面右键 选择Git Bash Here 打开控制台

    #2.配置用户名和邮箱
    git config --global user.name "用户名" #随意
    git config --global user.email "邮箱" #自己的邮箱

    #3.配置SSH免密连接
    #生成密钥
    ssh-keygen -t rsa -C "在码云上注册的邮箱地址" #连续三次回车
    #查看密钥并复制公钥的内容
    cat ~/.ssh/id_rsa.pub

    #4.将密钥的复制到码云的SSH公钥中
    #4.1添加公钥 公钥名随意 公钥内容就是上面复制的内容

    #5.测试
    ssh -T git@gitee.com
    + +
    \ No newline at end of file diff --git a/posts/63587.html b/posts/63587.html new file mode 100644 index 000000000..28a74494c --- /dev/null +++ b/posts/63587.html @@ -0,0 +1,209 @@ +Java学习路线 | The Blog + + + + + + + + + + + + +

    Java学习路线

    + +
    +
    + + + + + +
    +
    +
    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/63724.html b/posts/63724.html new file mode 100644 index 000000000..1a092af5d --- /dev/null +++ b/posts/63724.html @@ -0,0 +1,250 @@ +IDEA常用快捷键 | The Blog + + + + + + + + + + + + +

    IDEA常用快捷键

    1.常用快捷键

      +
    1. 在写一个main主函数的时候可以直接在键盘上敲main ,然后根据提示补全全部(模板快捷键)

      +
    2. +
    3. 在写System.out.println();输出函数代码的时候可以直接在键盘上面敲sout,然后根据提示补全(模板快捷键)

      +
    4. +
    5. 在写for循环的时候,我们可以直接在键盘上面打出 fori 然后根据提示补全代码(模板快捷键)

      +
    6. +
    7. 删除当前行 Ctrl+D (并非默认Ctrl + D,默认为Ctrl + Y),需要我们自己设置

      +
    8. +
    9. 复制当前行,快速向下复制一行 Ctrl + Alt + 向下箭头(并非默认,需要我们自己设置)

      +
    10. +
    11. 代码补全 Alt + /

      +
    12. +
    13. 添加注释和取消注释 Ctrl + /

      +
    14. +
    15. 自动导入import java.util.Scanner;我们在键盘上敲Scanner in = new Scanner(System,in);的时候按快捷键Alt + Enter 可以自动导入import java.util.Scanner;

      +
    16. +
    17. 代码格式化(格式调整为正常格式,,让代码变整洁,并非删除代码) Ctrl + Alt +L

      +
    18. +
    19. 快速运行程序(并非默认,需要我们自己设置)Alt + R,如果要使用这个快捷键,我们要切换到当前的主类,否则运行的还是之前运行的程序

      +
    20. +
    21. 构造器快捷键 Alt + insert ,按住Ctrl可以选择任意个参数的构造器,也可以点击Select None选择无参构造器; 我们在写一个私有类的getXxx()和setXxx()方法的时候,我们可以使用这个快捷键,光标移动到”Getter and Setter“的位置并选择。

      +
    22. +
    23. 查看一个类的层级关系 Ctrl + H

      +
    24. +
    25. 将光标放在一个方法上,使用快捷键 Ctrl + B 可以快速定位到这个方法的代码

      +
    26. +
    27. 我们在键盘上面敲Scanner in = new Scanner(System.in);的时候,我们先敲new Scanner(System.in),再在后面加上new Scanner(System.in).var,按Enter,就会自动补全前面的代码;定义一个Person类对象的时候我们也可以使用这种方法,如new Person().var,按Enter,就会自动补全前面的代码,变成 Person person = new Person();

      +
    28. +
    29. 重写某个方法的时候我们可以直接打出这个方法名,然后根据提示,移动光标选择。

      +
    30. +
    31. Alt+insert移动光标选择toString,可以重写toString方法,并输出对象的属性。

      +
    32. +
    33. 断点调试的快捷键:F7(跳入),F8(跳过),shift+F8(跳出),F9(resume,执行到下一个断点)

      +

      F7:跳入方法内

      +

      F8:逐行执行代码

      +

      shift+F8:跳出方法

      +
    34. +
    35. 一个普通类实现接口,就必须将该接口的所有方法都实现,我们在实现这些接口的时候可以使用快捷键Alt+Enter

      +
    36. +
    37. Ctrl+Alt+t 可以快捷的使用try-catch

      +
    38. +
    39. 遍历一个数组,生成while循环,可以快捷生成,快捷键是 itit + 回车 ,增强的for循环的迭代器的快捷输入 是大写的 I + enter

      +
    40. +
    41. Ctrl + J 可以显示所有的快捷模板

      +
    42. +
    43. Ctrl + R 查找替换

      +
    44. +
    45. Shift+Enter 快速向下换一行

      +
    46. +
    47. Shift+Home 快速选中一行

      +
    48. +
    49. Ctrl + F 全局查找

      +
    50. +
    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/64205.html b/posts/64205.html new file mode 100644 index 000000000..554487059 --- /dev/null +++ b/posts/64205.html @@ -0,0 +1,201 @@ +SpringBoot整合Logback日志 | The Blog + + + + + + + + + + + + +

    SpringBoot整合Logback日志

    1.创建一个SpringBoot的工程

    2.在resources目录下创建logback-spring.xml的配置文件

    创建的时候要修改日志输出的路径

    +

    日志的级别根据需要自己修改

    +

    级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="C:/AlYun/log" />

    <!-- 彩色日志 -->
    <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <property name="CONSOLE_LOG_PATTERN"
    value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>


    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
    <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <level>INFO</level>
    </filter>
    <encoder>
    <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
    <!-- 设置字符集 -->
    <charset>UTF-8</charset>
    </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 正在记录的日志文件的路径及文件名 -->
    <file>${log.path}/log_info.log</file>
    <!--日志文件输出格式-->
    <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    <charset>UTF-8</charset>
    </encoder>
    <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 每天日志归档路径以及格式 -->
    <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <!--日志文件保留天数-->
    <maxHistory>15</maxHistory>
    </rollingPolicy>
    <!-- 此日志文件只记录info级别的 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>INFO</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 正在记录的日志文件的路径及文件名 -->
    <file>${log.path}/log_warn.log</file>
    <!--日志文件输出格式-->
    <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    <charset>UTF-8</charset> <!-- 此处设置字符集 -->
    </encoder>
    <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <!--日志文件保留天数-->
    <maxHistory>15</maxHistory>
    </rollingPolicy>
    <!-- 此日志文件只记录warn级别的 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>warn</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 正在记录的日志文件的路径及文件名 -->
    <file>${log.path}/log_error.log</file>
    <!--日志文件输出格式-->
    <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    <charset>UTF-8</charset> <!-- 此处设置字符集 -->
    </encoder>
    <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <!--日志文件保留天数-->
    <maxHistory>15</maxHistory>
    </rollingPolicy>
    <!-- 此日志文件只记录ERROR级别的 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>ERROR</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>
    </appender>

    <!--
    <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
    <logger>仅有一个name属性,
    一个可选的level和一个可选的addtivity属性。
    name:用来指定受此logger约束的某一个包或者具体的某一个类。
    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
    如果未设置此属性,那么当前logger将会继承上级的级别。
    -->
    <!--
    使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
    第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
    第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
    -->
    <!--开发环境:打印控制台-->
    <springProfile name="dev">
    <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
    <logger name="com.guli" level="INFO" />

    <!--
    root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
    可以包含零个或多个appender元素。
    -->
    <root level="INFO">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="INFO_FILE" />
    <appender-ref ref="WARN_FILE" />
    <appender-ref ref="ERROR_FILE" />
    </root>
    </springProfile>


    <!--生产环境:输出到文件-->
    <springProfile name="pro">

    <root level="INFO">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="DEBUG_FILE" />
    <appender-ref ref="INFO_FILE" />
    <appender-ref ref="ERROR_FILE" />
    <appender-ref ref="WARN_FILE" />
    </root>
    </springProfile>

    </configuration>
    + +

    3.日志的样式如下图所示

    image-20230412171350769

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/64695.html b/posts/64695.html new file mode 100644 index 000000000..a1ec74646 --- /dev/null +++ b/posts/64695.html @@ -0,0 +1,779 @@ +项目实战-黑马头条 | The Blog + + + + + + + + + + + + +

    项目实战-黑马头条

    本项目基于黑马程序员黑马头条项目,编写此文档的目的是锻炼文档编写能力,完整正确的项目文档请关注黑马程序员!

    +

    项目文档或有欠缺,不对项目文档的完整性负责!

    +
    + +

    一.项目介绍

    1.项目概述

    ​ 随着智能手机的普及,人们更加习惯于通过手机来看新闻。由于生活节奏的加快,很多人只能利用碎片时间来获取信息,因此,对于移动资讯客户端的需求也越来越高。黑马头条项目正是在这样背景下开发出来。黑马头条项目采用当下火热的微服务+大数据技术架构实现。本项目主要着手于获取最新最热新闻资讯,通过大数据分析用户喜好精确推送咨询新闻。

    +

    image-20230810153039562

    +

    2.业务说明

    功能架构图

    +

    image-20230810153700690

    +

    3.技术栈

      +
    • Spring-Cloud-Gateway : 微服务之前架设的网关服务,实现服务注册中的API请求路由,以及控制流速控制和熔断处理都是常用的架构手段,而这些功能Gateway天然支持
    • +
    • 运用Spring Boot快速开发框架,构建项目工程;并结合Spring Cloud全家桶技术,实现后端个人中心、自媒体、管理中心等微服务。
    • +
    • 运用Spring Cloud Alibaba Nacos作为项目中的注册中心和配置中心
    • +
    • 运用mybatis-plus作为持久层提升开发效率
    • +
    • 运用Kafka完成内部系统消息通知;与客户端系统消息通知;以及实时数据计算
    • +
    • 运用Redis缓存技术,实现热数据的计算,提升系统性能指标
    • +
    • 使用Mysql存储用户数据,以保证上层数据查询的高性能
    • +
    • 使用Mongo存储用户热数据,以保证用户热数据高扩展和高性能指标
    • +
    • 使用FastDFS作为静态资源存储器,在其上实现热静态资源缓存、淘汰等功能
    • +
    • 运用Hbase技术,存储系统中的冷数据,保证系统数据的可靠性
    • +
    • 运用ES搜索技术,对冷数据、文章数据建立索引,以保证冷数据、文章查询性能
    • +
    • 运用AI技术,来完成系统自动化功能,以提升效率及节省成本。比如实名认证自动化
    • +
    • PMD&P3C : 静态代码扫描工具,在项目中扫描项目代码,检查异常点、优化点、代码规范等,为开发团队提供规范统一,提升项目代码质量
    • +
    +

    技术栈

    +

    image-20230809170653756

    +

    解决方案

    +

    image-20230809170929986

    +

    二.环境搭建

    1.Linxu环境的搭建

    1.1 虚拟机的安装

    1.解压分享的虚拟机镜像文件

    +

    image-20230811103629846

    +

    2.使用VmWare打开.vmx文件
    image-20230811103800217

    +

    3.配置虚拟机的网络环境

    +

    image-20230811104150226

    +

    image-20230811104320075

    +

    4.开启虚拟机

    +

    image-20230811104523506

    +

    5.使用FinalShell连接此虚拟机

    +

    用户名: root 密码:root IP地址: 192.168.200.130

    +

    image-20230811104741394

    +

    1.2 Linux软件安装

    Linux中开发环境的搭建 | The Blog (gitee.io)

    +

    2.开发环境的配置

    2.1 项目依赖的环境

      +
    • JDK1.8

      +
    • +
    • Intellij Idea

      +
    • +
    • maven-3.6.1

      +
    • +
    • Git

      +
    • +
    +

    2.2 后端工程的搭建

    image-20230811160530674

    +

    解压heima-leadnews.zip文件并用IDEA工具打开

    +

    image-20230811152915886

    +

    编码格式的设置

    +

    image-20230811154426723

    +

    三.app端功能开发

    1.app登录

    登录相关的表结构

    + + + + + + + + + + + + + + + + + + + + + + + +
    表名称说明
    ap_userAPP用户信息表
    ap_user_fanAPP用户粉丝信息表
    ap_user_followAPP用户关注信息表
    ap_user_realnameAPP实名认证信息表
    +

    ap_user表对应的实体类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    package com.heima.model.user.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * <p>
    * APP用户信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("ap_user")
    public class ApUser implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
    * 主键
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 密码、通信等加密盐
    */
    @TableField("salt")
    private String salt;

    /**
    * 用户名
    */
    @TableField("name")
    private String name;

    /**
    * 密码,md5加密
    */
    @TableField("password")
    private String password;

    /**
    * 手机号
    */
    @TableField("phone")
    private String phone;

    /**
    * 头像
    */
    @TableField("image")
    private String image;

    /**
    * 0 男
    1 女
    2 未知
    */
    @TableField("sex")
    private Boolean sex;

    /**
    * 0 未
    1 是
    */
    @TableField("is_certification")
    private Boolean certification;

    /**
    * 是否身份认证
    */
    @TableField("is_identity_authentication")
    private Boolean identityAuthentication;

    /**
    * 0正常
    1锁定
    */
    @TableField("status")
    private Boolean status;

    /**
    * 0 普通用户
    1 自媒体人
    2 大V
    */
    @TableField("flag")
    private Short flag;

    /**
    * 注册时间
    */
    @TableField("created_time")
    private Date createdTime;

    }
    + +

    1.1 用户登录逻辑

    注册加盐的过程

    +

    ​ 用户在登录的时候会生成一个随机的字符串(salt),这个随机的字符串会加到密码后面然后连同密码加密存储到数据库。

    +

    image-20230811165505210

    +

    登录加盐的过程

    +

    ​ 先根据账号查询是否存在该用户,如果存在的话,根据用户输入的密码和数据库中的salt进行md5加密,并和数据库中的密码比对,一致的话,比对通过,不一样的话不通过。

    +

    image-20230811165808912

    +

    1.2 用户模块搭建

    heima-leadnews-service父工程依赖文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
    <artifactId>heima-leadnews</artifactId>
    <groupId>com.heima</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <packaging>pom</packaging>
    <modules>
    <module>heima-leadnews-user</module>
    </modules>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>heima-leadnews-service</artifactId>

    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
    <!-- 数据模型子模块 -->
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-leadnews-model</artifactId>
    </dependency>

    <!-- 公共子模块 -->
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-leadnews-common</artifactId>
    </dependency>

    <!-- 远程调用子模块 -->
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-leadnews-feign-api</artifactId>
    </dependency>

    <!-- Spring Boot Web starter -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot Test测试 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>

    <!-- Nacos注册中心 -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- Nacos配置中心 -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <!-- Feign远程调用客户端 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    </dependencies>

    </project>
    + +
      +
    1. 创建子模块并创建出对应的目录结构

      +

      在heima-leadnews-service父工程下创建工程heima-leadnews-user

      +

      image-20230812172036450

      +
    2. +
    3. 编写用户模块的配置文件

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      server:
      port: 51801
      spring:
      application:
      name: leadnews-user
      cloud:
      nacos:
      discovery:
      server-addr: 192.168.200.130:8848
      config:
      server-addr: 192.168.200.130:8848
      file-extension: yml
      +
    4. +
    5. 在配置中心中添加数据库等相关的配置

      +

      image-20230812173024036

      +
    6. +
    7. 在resources目录下添加日志的配置文件

      +

      logback.xml

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      <?xml version="1.0" encoding="UTF-8"?>

      <configuration>
      <!--定义日志文件的存储地址,使用绝对路径-->
      <property name="LOG_HOME" value="e:/logs"/>

      <!-- Console 输出设置 -->
      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
      <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      <charset>utf8</charset>
      </encoder>
      </appender>

      <!-- 按照每天生成日志文件 -->
      <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!--日志文件输出的文件名-->
      <fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
      </rollingPolicy>
      <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      </encoder>
      </appender>

      <!-- 异步输出 -->
      <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
      <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
      <discardingThreshold>0</discardingThreshold>
      <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
      <queueSize>512</queueSize>
      <!-- 添加附加的appender,最多只能添加一个 -->
      <appender-ref ref="FILE"/>
      </appender>


      <logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
      <appender-ref ref="CONSOLE"/>
      </logger>
      <logger name="org.springframework.boot" level="debug"/>
      <root level="info">
      <!--<appender-ref ref="ASYNC"/>-->
      <appender-ref ref="FILE"/>
      <appender-ref ref="CONSOLE"/>
      </root>
      </configuration>
    8. +
    +

    遇到的问题:

    +
      +
    1. 问题一:引入@EnableDiscoveryClient注解的时候爆红

      +

      解决方案:在heima-leadnews-service父工程下加入如下的注解

      +
      1
      2
      3
      4
      5
      <!-- Feign远程调用客户端 -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
    2. +
    +

    1.3 登录功能实现

    1.3.1 接口定义

    + + + + + + + + + + + + + + + + + + +
    接口路径/api/v1/login/login_auth
    请求方式POST
    参数LoginDto
    响应结果ResponseResult
    +

    LoginDto

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @Data
    public class LoginDto {

    /**
    * 手机号
    */
    @ApiModelProperty(value = "手机号",required = true)
    private String phone;

    /**
    * 密码
    */
    @ApiModelProperty(value = "密码",required = true)
    private String password;
    }
    + +

    统一返回结果类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    package com.heima.model.common.dtos;

    import com.alibaba.fastjson.JSON;
    import com.heima.model.common.enums.AppHttpCodeEnum;

    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    /**
    * 通用的结果返回类
    * @param <T>
    */
    public class ResponseResult<T> implements Serializable {

    private String host;

    private Integer code;

    private String errorMessage;

    private T data;

    public ResponseResult() {
    this.code = 200;
    }

    public ResponseResult(Integer code, T data) {
    this.code = code;
    this.data = data;
    }

    public ResponseResult(Integer code, String msg, T data) {
    this.code = code;
    this.errorMessage = msg;
    this.data = data;
    }

    public ResponseResult(Integer code, String msg) {
    this.code = code;
    this.errorMessage = msg;
    }

    public static ResponseResult errorResult(int code, String msg) {
    ResponseResult result = new ResponseResult();
    return result.error(code, msg);
    }

    public static ResponseResult okResult(int code, String msg) {
    ResponseResult result = new ResponseResult();
    return result.ok(code, null, msg);
    }

    public static ResponseResult okResult(Object data) {
    ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
    if(data!=null) {
    result.setData(data);
    }
    return result;
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums){
    return setAppHttpCodeEnum(enums,enums.getErrorMessage());
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){
    return setAppHttpCodeEnum(enums,errorMessage);
    }

    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
    return okResult(enums.getCode(),enums.getErrorMessage());
    }

    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
    return okResult(enums.getCode(),errorMessage);
    }

    public ResponseResult<?> error(Integer code, String msg) {
    this.code = code;
    this.errorMessage = msg;
    return this;
    }

    public ResponseResult<?> ok(Integer code, T data) {
    this.code = code;
    this.data = data;
    return this;
    }

    public ResponseResult<?> ok(Integer code, T data, String msg) {
    this.code = code;
    this.data = data;
    this.errorMessage = msg;
    return this;
    }

    public ResponseResult<?> ok(T data) {
    this.data = data;
    return this;
    }

    public Integer getCode() {
    return code;
    }

    public void setCode(Integer code) {
    this.code = code;
    }

    public String getErrorMessage() {
    return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
    this.errorMessage = errorMessage;
    }

    public T getData() {
    return data;
    }

    public void setData(T data) {
    this.data = data;
    }

    public String getHost() {
    return host;
    }

    public void setHost(String host) {
    this.host = host;
    }


    public static void main(String[] args) {
    //前置
    /*AppHttpCodeEnum success = AppHttpCodeEnum.SUCCESS;
    System.out.println(success.getCode());
    System.out.println(success.getErrorMessage());*/

    //查询一个对象
    /*Map map = new HashMap();
    map.put("name","zhangsan");
    map.put("age",18);
    ResponseResult result = ResponseResult.okResult(map);
    System.out.println(JSON.toJSONString(result));*/


    //新增,修改,删除 在项目中统一返回成功即可
    /* ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SUCCESS);
    System.out.println(JSON.toJSONString(result));*/


    //根据不用的业务返回不同的提示信息 比如:当前操作需要登录、参数错误
    /*ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
    System.out.println(JSON.toJSONString(result));*/

    //查询分页信息
    PageResponseResult responseResult = new PageResponseResult(1,5,50);
    List list = new ArrayList();
    list.add("itcast");
    list.add("itheima");
    responseResult.setData(list);
    System.out.println(JSON.toJSONString(responseResult));

    }

    }
    + +

    1.3.2 登录思路分析

    image-20230812180918302

    +

    1.3.3 登录关键代码实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    package com.heima.user.service.impl;

    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.heima.model.common.dtos.ResponseResult;
    import com.heima.model.common.enums.AppHttpCodeEnum;
    import com.heima.model.user.dtos.LoginDto;
    import com.heima.model.user.pojos.ApUser;
    import com.heima.user.mapper.ApUserMapper;
    import com.heima.user.service.ApUserService;
    import com.heima.utils.common.AppJwtUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.util.DigestUtils;

    import java.util.HashMap;
    import java.util.Map;


    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/12
    * @Description
    */
    @Service
    @Transactional
    @Slf4j
    public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
    @Override
    public ResponseResult login(LoginDto loginDto) {
    //1 正常的登录 用户名和密码
    if(StringUtils.isNotBlank(loginDto.getPhone()) && StringUtils.isNotBlank(loginDto.getPassword())) {
    //1.1 根据手机号查询用户的信息
    ApUser dbUser = getOne(new LambdaQueryWrapper<ApUser>().eq(ApUser::getPhone, loginDto.getPhone()));
    if(dbUser == null){
    return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");
    }
    String salt = dbUser.getSalt();
    String password = loginDto.getPassword();
    String pwd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
    //1.2 比对密码
    if(!pwd.equals(dbUser.getPassword())){
    return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
    }
    //1.3 生成token
    String token = AppJwtUtil.getToken(dbUser.getId().longValue());
    Map<String,Object> map = new HashMap<>();
    map.put("token",token);
    dbUser.setSalt("");
    dbUser.setPassword("");
    map.put("user",dbUser);
    return ResponseResult.okResult(map);
    }else {
    //2.游客登录
    String token = AppJwtUtil.getToken(0L);
    Map<String,Object> map = new HashMap<>();
    map.put("token",token);
    return ResponseResult.okResult(map);
    }
    }
    }
    + +

    1.3.4 使用接口工具测试

    接口测试工具使用教程:https://jasonsgong.gitee.io/posts/35630.html

    +

    image-20230813165436808

    +

    2. app端网关搭建

    网关的概述

    +

    image-20230814142355139

    +

    项目中搭建的网关

    +

    image-20230814142700349

    +

    2.1 搭建过程

    1.在heima-leadnews-gateway导入以下依赖

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    </dependency>
    </dependencies>
    + +

    2.创建网关的模块

    +

    image-20230814143113564

    +

    3.创建启动类和bootstrap.yml配置文件

    +
    1
    2
    3
    4
    5
    6
    7
    @EnableDiscoveryClient
    @SpringBootApplication
    public class AppGatewayApplication {
    public static void main(String[] args) {
    SpringApplication.run(AppGatewayApplication.class,args);
    }
    }
    + +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server:
    port: 51601
    spring:
    application:
    name: leadnews-app-gateway
    cloud:
    nacos:
    discovery:
    server-addr: 192.168.200.130:8848
    config:
    server-addr: 192.168.200.130:8848
    file-extension: yml
    + +

    4.在nacos中创建app端网关的配置

    +

    image-20230814143939252

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    spring:
    cloud:
    gateway:
    globalcors:
    add-to-simple-url-handler-mapping: true
    corsConfigurations:
    '[/**]':
    allowedHeaders: "*"
    allowedOrigins: "*"
    allowedMethods:
    - GET
    - POST
    - DELETE
    - PUT
    - OPTION
    routes:
    # 平台管理
    - id: user
    uri: lb://leadnews-user
    predicates:
    - Path=/user/**
    filters:
    - StripPrefix= 1
    + +

    5.使用Postman测试网关

    +

    http://localhost:51601/user/api/v1/login/login_auth

    +

    image-20230814144314358

    +

    2.2 全局过滤器实现jwt校验

    网关的过滤流程

    +

    image-20230814144806282

    +

    思路分析:

    +
      +
    1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
    2. +
    3. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
    4. +
    5. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
    6. +
    7. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
    8. +
    +

    JWT认证的过滤器

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/14
    * @Description 权限认证的过滤器
    */
    @Component
    @Slf4j
    public class AuthorizeFilter implements Ordered, GlobalFilter {


    /**
    * 过滤的设置
    */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //1.获取request和response对象
    ServerHttpRequest request = exchange.getRequest();
    ServerHttpResponse response = exchange.getResponse();

    //2.判断是否登录
    //通过请求的路径的url判断
    if (request.getURI().getPath().contains("/login")) {
    //放行
    return chain.filter(exchange);
    }
    //3.获取token
    String token = request.getHeaders().getFirst("token");

    //4.判断token是否存在
    if (StringUtils.isBlank(token)) {
    //设置401的状态码
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }

    //5.判断token是否有效
    try {
    //获取token中的数据
    Claims claims = AppJwtUtil.getClaimsBody(token);
    //判断token是否过期 -1:有效,0:有效,1:过期,2:过期
    int res = AppJwtUtil.verifyToken(claims);
    if (res == 1 || res == 2) {
    //设置401的状态码
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }
    } catch (Exception e) {
    e.printStackTrace();
    //设置401的状态码
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }
    //6.放行
    return chain.filter(exchange);
    }

    /**
    * 优先级设置 值越小 优先值越高
    */
    @Override
    public int getOrder() {
    return 0;
    }
    }
    + +

    3.app前端项目集成

    image-20230814151319382

    +

    通过nginx来进行配置,功能如下

    +
      +
    • 通过nginx的反向代理功能访问后台的网关资源
    • +
    • 通过nginx的静态服务器功能访问前端静态页面
    • +
    +

    3.1 Nginx集成前端项目步骤

    ①:解压资料文件夹中的压缩包nginx-1.18.0.zip

    +

    cmd切换到nginx所有的目录输入nginx启动nginx

    +

    image-20230814152058708

    +

    image-20230814152144765

    +

    ②:解压资料文件夹中的前端项目app-web.zip

    +

    解压到一个没有中文的文件夹中,后面nginx配置中会指向这个目录

    +

    ③:配置nginx.conf文件

    +

    在nginx安装的conf目录下新建一个文件夹leadnews.conf,在当前文件夹中新建heima-leadnews-app.conf文件

    +

    heima-leadnews-app.conf配置如下:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    upstream  heima-app-gateway{
    #APP端网关所在的端口
    server localhost:51601;
    }

    server {
    listen 8801;
    location / {
    root C:/Gong/data/app-web/;
    index index.html;
    }

    location ~/app/(.*) {
    proxy_pass http://heima-app-gateway/$1;
    proxy_set_header HOST $host; # 不改变源请求头的值
    proxy_pass_request_body on; #开启获取请求体
    proxy_pass_request_headers on; #开启获取请求头
    proxy_set_header X-Real-IP $remote_addr; # 记录真实发出请求的客户端IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #记录代理信息
    }
    }
    + +

    nginx.conf 把里面注释的内容和静态资源配置相关删除,引入heima-leadnews-app.conf文件加载

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #user  nobody;
    worker_processes 1;

    events {
    worker_connections 1024;
    }
    http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;
    # 引入自定义配置文件
    include leadnews.conf/*.conf;
    }
    + +

    ④ :启动nginx

    +

    ​ 在nginx安装包中使用命令提示符打开,输入命令nginx启动项目

    +

    ​ 可查看进程,检查nginx是否启动

    +

    ​ 重新加载配置文件:nginx -s reload

    +

    ⑤:打开前端项目进行测试 – > http://localhost:8801

    +

    ​ 用谷歌浏览器打开,调试移动端模式进行访问

    +

    image-20230814153335349

    +

    4.app端文章列表功能

    开发前app的首页面

    +

    image-20230814154205134

    +

    文章的布局展示

    +

    image-20230814154405531

    +

    4.1 数据库表的创建

    文章的相关的数据库

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    表名称说明
    ap_article文章信息表,存储已发布的文章
    ap_article_configAPP已发布文章配置表
    ap_article_contentAPP已发布文章内容表
    ap_authorAPP文章作者信息表
    ap_collectionAPP收藏信息表
    +

    导入资料中的sql文件创建相关的数据库表

    +

    image-20230814160312382

    +

    关键的数据库表

    +

    文章基本信息表

    +

    image-20230814160547893

    +

    APP已发布文章配置表

    +

    image-20230814160652330

    +

    APP已发布文章内容表

    +

    image-20230814160728715

    +

    APP文章作者信息表

    +

    image-20230814160805398

    +

    APP收藏信息表

    +

    image-20230814160823771

    +

    垂直分表

    +

    将文章相关的表分成文章配置表和文章内容表和文章信息表

    +

    image-20230814161909312

    +

    垂直分表:将一个表的字段分散到多个表中,每个表存储其中一部分字段

    +

    优势:

    +
      +
    1. 减少IO争抢,减少锁表的几率,查看文章概述与文章详情互不影响

      +
    2. +
    3. 充分发挥高频数据的操作效率,对文章概述数据操作的高效率不会被操作文章详情数据的低效率所拖累

      +
    4. +
    +

    拆分规则:

    +

    1.把不常用的字段单独放在一张表

    +

    2.把text,blob等大字段拆分出来单独放在一张表

    +

    3.经常组合查询的字段单独放在一张表中

    +

    4.2 文章模块搭建

    导入资料中的模块

    +

    image-20230815184540157

    +

    在nacos中添加配置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring:
    datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
    # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
    mybatis-plus:
    mapper-locations: classpath*:mapper/*.xml
    # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
    type-aliases-package: com.heima.model.article.pojos
    + +

    image-20230815162119814

    +

    踩坑: 在导入项目的时候提示Caused by: java.io.FileNotFoundException: class path resource [com/heima/apis/article/IArticleClient.class] cannot be opened because it does not exist ,先删除这个项目,手动创建这个项目,然后复制资料里面文件到这个项目即可,直接复制整个项目可能会报这个错!

    +

    4.3 首页文章的列表显示

    首页上拉和下拉的实现思路

    +

    image-20230814163019582

    +

    Sql语句实现

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #按照发布时间倒序查询十条文章
    select * from ap_article aa order by aa.publish_time desc limit 10
    #频道筛选
    select * from ap_article aa where aa.channel_id = 1 order by aa.publish_time desc limit 10
    #加载首页
    select * from ap_article aa where aa.channel_id = 1 and aa.publish_time < '2063-09-08 10:20:12' order by aa.publish_time desc limit 10
    #加载更多
    select * from ap_article aa where aa.channel_id = 1 and aa.publish_time < '2020-09-07 22:30:09' order by aa.publish_time desc limit 10
    #加载最新
    select * from ap_article aa where aa.channel_id = 1 and aa.publish_time > '2020-09-07 22:30:09' order by aa.publish_time desc limit 10
    + +

    image-20230815160034436

    +

    4.2.1 接口定义

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    加载首页加载更多加载最新
    接口路径/api/v1/article/load/api/v1/article/loadmore/api/v1/article/loadnew
    请求方式POSTPOSTPOST
    参数ArticleHomeDtoArticleHomeDtoArticleHomeDto
    响应结果ResponseResultResponseResultResponseResult
    +

    ArticleHomeDto

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.heima.model.article.dtos;
    import lombok.Data;
    import java.util.Date;
    @Data
    public class ArticleHomeDto {

    // 最大时间
    Date maxBehotTime;
    // 最小时间
    Date minBehotTime;
    // 分页size
    Integer size;
    // 频道ID
    String tag;
    }
    + +

    4.2.2 实现思路

    ①:导入heima-leadnews-article微服务,资料在当天的文件夹中

    +

    需要在nacos中添加对应的配置

    +

    ②:定义接口

    +

    接口路径、请求方式、入参、出参

    +

    ③:编写mapper文件

    +

    文章表与文章配置表多表查询

    +

    ④:编写业务层代码

    +

    ⑤:编写控制器代码

    +

    ⑥:swagger测试或前后端联调测试

    +

    4.2.3 功能的关键代码实现

    mapper层的代码

    +

    image-20230816142705510

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.heima.article.mapper.ApArticleMapper">
    <resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
    <id column="id" property="id"/>
    <result column="title" property="title"/>
    <result column="author_id" property="authorId"/>
    <result column="author_name" property="authorName"/>
    <result column="channel_id" property="channelId"/>
    <result column="channel_name" property="channelName"/>
    <result column="layout" property="layout"/>
    <result column="flag" property="flag"/>
    <result column="images" property="images"/>
    <result column="labels" property="labels"/>
    <result column="likes" property="likes"/>
    <result column="collection" property="collection"/>
    <result column="comment" property="comment"/>
    <result column="views" property="views"/>
    <result column="province_id" property="provinceId"/>
    <result column="city_id" property="cityId"/>
    <result column="county_id" property="countyId"/>
    <result column="created_time" property="createdTime"/>
    <result column="publish_time" property="publishTime"/>
    <result column="sync_status" property="syncStatus"/>
    <result column="static_url" property="staticUrl"/>
    </resultMap>
    <select id="loadArticleList" resultMap="resultMap">
    SELECT
    aa.*
    FROM
    `ap_article` aa
    LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
    <where>
    and aac.is_delete != 1
    and aac.is_down != 1
    <!-- loadmore -->
    <if test="type != null and type == 1">
    and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}
    </if>
    <if test="type != null and type == 2">
    and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}
    </if>
    <if test="dto.tag != '__all__'">
    and aa.channel_id = #{dto.tag}
    </if>
    </where>
    order by aa.publish_time desc
    limit #{dto.size}
    </select>
    </mapper>
    + +

    service层的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    package com.heima.article.service.Impl;

    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.heima.article.mapper.ApArticleMapper;
    import com.heima.article.service.ApArticleService;
    import com.heima.common.constants.ArticleConstants;
    import com.heima.model.article.dtos.ArticleHomeDto;
    import com.heima.model.article.pojos.ApArticle;
    import com.heima.model.common.dtos.ResponseResult;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.Date;
    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/15
    * @Description
    */
    @Service
    @Transactional
    @Slf4j
    public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
    @Autowired
    private ApArticleMapper apArticleMapper;

    private static final Integer MAX_PAGE_SIZE = 50;

    @Override
    public ResponseResult load(ArticleHomeDto dto, Short type) {
    //1.参数校验
    //分页条数的校验
    Integer size = dto.getSize();
    if (size == null || size == 0) {
    size = 0;
    }
    //最大分页条数的限制
    size = Math.min(size, 50);

    //type参数的校验
    if (!type.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && !type.equals(ArticleConstants.LOADTYPE_LOAD_NEW)) {
    type = 1;
    }

    //频道参数校验
    if (StringUtils.isBlank(dto.getTag())) {
    dto.setTag(ArticleConstants.DEFAULT_TAG);
    }

    //时间检验
    if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
    if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());

    //2.查询
    List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, type);
    //3.数据返回
    return ResponseResult.okResult(apArticles);
    }
    }
    + +

    controller层的代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    package com.heima.article.controller.v1;

    import com.heima.article.service.ApArticleService;
    import com.heima.common.constants.ArticleConstants;
    import com.heima.model.article.dtos.ArticleHomeDto;
    import com.heima.model.common.dtos.ResponseResult;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/15
    * @Description
    */
    @RestController
    @RequestMapping("/api/v1/article")
    public class ArticleHomeController {

    @Autowired
    private ApArticleService apArticleService;

    /**
    * 加载首页
    */
    @PostMapping("/load")
    public ResponseResult<Object> load(@RequestBody ArticleHomeDto articleHomeDto){
    return apArticleService.load(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_MORE);
    }

    /**
    * 加载更多
    */
    @PostMapping("/loadmore")
    public ResponseResult<Object> loadmore(@RequestBody ArticleHomeDto articleHomeDto){
    return apArticleService.load(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_MORE);
    }


    /**
    * 加载更新
    */
    @PostMapping("/loadnew")
    public ResponseResult<Object> loadnew(@RequestBody ArticleHomeDto articleHomeDto){
    return apArticleService.load(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_NEW);
    }
    }
    + +

    网关中增加文章模块的路由

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    spring:
    cloud:
    gateway:
    globalcors:
    add-to-simple-url-handler-mapping: true
    corsConfigurations:
    '[/**]':
    allowedHeaders: "*"
    allowedOrigins: "*"
    allowedMethods:
    - GET
    - POST
    - DELETE
    - PUT
    - OPTION
    routes:
    # 平台管理
    - id: user
    uri: lb://leadnews-user
    predicates:
    - Path=/user/**
    filters:
    - StripPrefix= 1
    # 文章管理
    - id: article
    uri: lb://leadnews-article
    predicates:
    - Path=/article/**
    filters:
    - StripPrefix= 1
    + +

    5. app端文章详情功能

    5.1 需求分析

    image-20230816143201242

    +

    5.2 实现方案-静态模板展示

    image-20230816143544351

    +

    静态模板展示关键技术-Freemarker

    +

    Freemarker教程: https://jasonsgong.gitee.io/posts/29367.html

    +

    5.3 对象存储服务MinIO

    MinIO 教程: https://jasonsgong.gitee.io/posts/36397.html

    +

    5.4 实现思路以及代码实现

    image-20230818210507999

    +

    image-20230818210638036

    +

    代码实现

    +

    1.在文章模块的pom.xml文件中加入以下的依赖

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-file-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    </dependencies>
    + +

    2.在nacos中有关文章模块的配置中添加以下的内容

    +
    1
    2
    3
    4
    5
    6
    minio:
    accessKey: minio
    secretKey: minio123
    bucket: leadnews
    endpoint: http://192.168.200.130:9000
    readPath: http://192.168.200.130:9000
    + +

    image-20230818211310728

    +

    3.将资料中的article.ftl文件拷贝到文章模块的templates目录下,article.ftl文件内容如下

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    <!DOCTYPE html>
    <html>

    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport"
    content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
    <title>黑马头条</title>
    <!-- 引入样式文件 -->
    <link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/vant@2.12.20/lib/index.css">
    <!-- 页面样式 -->
    <link rel="stylesheet" href="../../../plugins/css/index.css">
    </head>

    <body>
    <div id="app">
    <div class="article">
    <van-row>
    <van-col span="24" class="article-title" v-html="title"></van-col>
    </van-row>

    <van-row type="flex" align="center" class="article-header">
    <van-col span="3">
    <van-image round class="article-avatar" src="https://p3.pstatp.com/thumb/1480/7186611868"></van-image>
    </van-col>
    <van-col span="16">
    <div v-html="authorName"></div>
    <div>{{ publishTime | timestampToDateTime }}</div>
    </van-col>
    <van-col span="5">
    <van-button round :icon="relation.isfollow ? '' : 'plus'" type="info" class="article-focus"
    :text="relation.isfollow ? '取消关注' : '关注'" :loading="followLoading" @click="handleClickArticleFollow">
    </van-button>
    </van-col>
    </van-row>

    <van-row class="article-content">
    <#if content??>
    <#list content as item>
    <#if item.type='text'>
    <van-col span="24" class="article-text">${item.value}</van-col>
    <#else>
    <van-col span="24" class="article-image">
    <van-image width="100%" src="${item.value}"></van-image>
    </van-col>
    </#if>
    </#list>
    </#if>
    </van-row>

    <van-row type="flex" justify="center" class="article-action">
    <van-col>
    <van-button round :icon="relation.islike ? 'good-job' : 'good-job-o'" class="article-like"
    :loading="likeLoading" :text="relation.islike ? '取消赞' : '点赞'" @click="handleClickArticleLike"></van-button>
    <van-button round :icon="relation.isunlike ? 'delete' : 'delete-o'" class="article-unlike"
    :loading="unlikeLoading" @click="handleClickArticleUnlike">不喜欢</van-button>
    </van-col>
    </van-row>

    <!-- 文章评论列表 -->
    <van-list v-model="commentsLoading" :finished="commentsFinished" finished-text="没有更多了"
    @load="onLoadArticleComments">
    <van-row id="#comment-view" type="flex" class="article-comment" v-for="(item, index) in comments" :key="index">
    <van-col span="3">
    <van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
    </van-col>
    <van-col span="21">
    <van-row type="flex" align="center" justify="space-between">
    <van-col class="comment-author" v-html="item.authorName"></van-col>
    <van-col>
    <van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
    @click="handleClickCommentLike(item)">{{ item.likes || '' }}
    </van-button>
    </van-col>
    </van-row>

    <van-row>
    <van-col class="comment-content" v-html="item.content"></van-col>
    </van-row>
    <van-row type="flex" align="center">
    <van-col span="10" class="comment-time">
    {{ item.createdTime | timestampToDateTime }}
    </van-col>
    <van-col span="3">
    <van-button round size="normal" v-html="item.reply" @click="showCommentRepliesPopup(item.id)">回复 {{
    item.reply || '' }}
    </van-button>
    </van-col>
    </van-row>
    </van-col>
    </van-row>
    </van-list>
    </div>
    <!-- 文章底部栏 -->
    <van-row type="flex" justify="space-around" align="center" class="article-bottom-bar">
    <van-col span="13">
    <van-field v-model="commentValue" placeholder="写评论">
    <template #button>
    <van-button icon="back-top" @click="handleSaveComment"></van-button>
    </template>
    </van-field>
    </van-col>
    <van-col span="3">
    <van-button icon="comment-o" @click="handleScrollIntoCommentView"></van-button>
    </van-col>
    <van-col span="3">
    <van-button :icon="relation.iscollection ? 'star' : 'star-o'" :loading="collectionLoading"
    @click="handleClickArticleCollection"></van-button>
    </van-col>
    <van-col span="3">
    <van-button icon="share-o"></van-button>
    </van-col>
    </van-row>

    <!-- 评论Popup 弹出层 -->
    <van-popup v-model="showPopup" closeable position="bottom"
    :style="{ width: '750px', height: '60%', left: '50%', 'margin-left': '-375px' }">
    <!-- 评论回复列表 -->
    <van-list v-model="commentRepliesLoading" :finished="commentRepliesFinished" finished-text="没有更多了"
    @load="onLoadCommentReplies">
    <van-row id="#comment-reply-view" type="flex" class="article-comment-reply"
    v-for="(item, index) in commentReplies" :key="index">
    <van-col span="3">
    <van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
    </van-col>
    <van-col span="21">
    <van-row type="flex" align="center" justify="space-between">
    <van-col class="comment-author" v-html="item.authorName"></van-col>
    <van-col>
    <van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
    @click="handleClickCommentReplyLike(item)">{{ item.likes || '' }}
    </van-button>
    </van-col>
    </van-row>

    <van-row>
    <van-col class="comment-content" v-html="item.content"></van-col>
    </van-row>
    <van-row type="flex" align="center">
    <!-- TODO: js计算时间差 -->
    <van-col span="10" class="comment-time">
    {{ item.createdTime | timestampToDateTime }}
    </van-col>
    </van-row>
    </van-col>
    </van-row>
    </van-list>
    <!-- 评论回复底部栏 -->
    <van-row type="flex" justify="space-around" align="center" class="comment-reply-bottom-bar">
    <van-col span="13">
    <van-field v-model="commentReplyValue" placeholder="写评论">
    <template #button>
    <van-button icon="back-top" @click="handleSaveCommentReply"></van-button>
    </template>
    </van-field>
    </van-col>
    <van-col span="3">
    <van-button icon="comment-o"></van-button>
    </van-col>
    <van-col span="3">
    <van-button icon="star-o"></van-button>
    </van-col>
    <van-col span="3">
    <van-button icon="share-o"></van-button>
    </van-col>
    </van-row>
    </van-popup>
    </div>

    <!-- 引入 Vue 和 Vant 的 JS 文件 -->
    <script src=" https://fastly.jsdelivr.net/npm/vue/dist/vue.min.js">
    </script>
    <script src="https://fastly.jsdelivr.net/npm/vant@2.12.20/lib/vant.min.js"></script>
    <!-- 引入 Axios 的 JS 文件 -->
    <#--<script src="https://unpkg.com/axios/dist/axios.min.js"></script>-->
    <script src="../../../plugins/js/axios.min.js"></script>
    <!-- 页面逻辑 -->
    <script src="../../../plugins/js/index.js"></script>
    </body>

    </html>
    + +

    4.手动上传资料中index.js和index.css两个文件到MinIO中

    +

    上传index.js

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Test
    public void testMinIO(){
    try {
    //读取一个文件
    FileInputStream inputStream = new FileInputStream("C:\\Gong\\java\\黑马头条\\day02-app端文章查看,静态化freemarker,分布式文件系统minIO\\资料\\模板文件\\plugins\\js\\index.js");
    //1.获取MinIO的连接信息,创建一个minio的客户端
    MinioClient minioClient = MinioClient.builder()
    .credentials("minio", "minio123")//minio的账号密码
    .endpoint("http://192.168.200.130:9000")//minio的地址
    .build();
    //2.上传
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object("plugins/js/index.js")//文件的名称
    .contentType("text/js")//文件的类型
    .bucket("leadnews")//桶的名称,与之前的minio管理界面创建的bucket名称一致即可
    .stream(inputStream,inputStream.available(),-1)
    .build();
    minioClient.putObject(putObjectArgs);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    + +

    上传index.css

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    @Test
    public void testMinIO(){
    try {
    //读取一个文件
    FileInputStream inputStream = new FileInputStream("C:\\Gong\\java\\黑马头条\\day02-app端文章查看,静态化freemarker,分布式文件系统minIO\\资料\\模板文件\\plugins\\css\\index.css");
    //1.获取MinIO的连接信息,创建一个minio的客户端
    MinioClient minioClient = MinioClient.builder()
    .credentials("minio", "minio123")//minio的账号密码
    .endpoint("http://192.168.200.130:9000")//minio的地址
    .build();
    //2.上传
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object("plugins/css/index.css")//文件的名称
    .contentType("text/css")//文件的类型
    .bucket("leadnews")//桶的名称,与之前的minio管理界面创建的bucket名称一致即可
    .stream(inputStream,inputStream.available(),-1)
    .build();
    minioClient.putObject(putObjectArgs);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    + +

    5.测试根据文章的内容生成html文件上传到minio中

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    package com.heima.article.test;

    import com.alibaba.fastjson.JSONArray;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.heima.article.ArticleApplication;
    import com.heima.article.mapper.ApArticleContentMapper;
    import com.heima.article.service.ApArticleService;
    import com.heima.file.service.FileStorageService;
    import com.heima.model.article.pojos.ApArticle;
    import com.heima.model.article.pojos.ApArticleContent;
    import freemarker.template.Configuration;
    import freemarker.template.Template;
    import freemarker.template.TemplateException;
    import org.apache.commons.lang.StringUtils;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;

    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.StringWriter;
    import java.util.HashMap;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/19
    * @Description
    */
    @SpringBootTest(classes = ArticleApplication.class)
    @RunWith(SpringRunner.class)
    public class ArticleFreeMarkerTest {

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private Configuration configuration;
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private ApArticleService apArticleService;

    @Test
    public void createStaticUrlTest() throws IOException, TemplateException {
    //已知文章的id
    //1.获取文章的内容
    ApArticleContent apArticleContent = apArticleContentMapper.selectOne(new LambdaQueryWrapper<ApArticleContent>().eq(ApArticleContent::getArticleId, "1383827995813531650L"));
    if (apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())) {
    //2.文章内容通过freemarker生成html文件 详细教程见:https://jasonsgong.gitee.io/posts/29367.html
    Template template = configuration.getTemplate("article.ftl");
    //构建数据模型
    HashMap<String, Object> map = new HashMap<>();
    map.put("content", JSONArray.parseArray(apArticleContent.getContent()));
    //输出流
    StringWriter out = new StringWriter();
    //合成html文件
    template.process(map, out);
    //3.把html文件上传到minio中
    //构建一个输入流
    InputStream in = new ByteArrayInputStream(out.toString().getBytes());
    //上传到minio并返回访问的路径
    String path = fileStorageService.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", in);
    System.out.println("文件在minio中的路径:"+path);
    //4.修改ap_article表,保存static_url字段
    ApArticle apArticle = new ApArticle();
    apArticle.setId(apArticleContent.getArticleId());
    apArticle.setStaticUrl(path);
    boolean isSuccess = apArticleService.updateById(apArticle);
    System.out.println(isSuccess ? "文件上传成功" : "文件上传失败");
    }

    }
    }
    + +

    6.实现效果

    +

    image-20230819153507360

    +

    四.自媒体端功能开发

    1.后端环境搭建

    需要搭建的模块

    +

    image-20230819155333131

    +

    搭建步骤

    +

    image-20230819155229550

    +

    1.创建自媒体模块的数据库

    +

    image-20230819155853252

    +

    2.导入相应的工程文件

    +

    image-20230819161650118

    +

    3.配置自媒体模块和网关模块在nacos中的配置

    +

    自媒体模块的配置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    spring:
    datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_wemedia?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
    # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
    mybatis-plus:
    mapper-locations: classpath*:mapper/*.xml
    # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
    type-aliases-package: com.heima.model.media.pojos
    + +

    image-20230819161148830

    +

    网关模块的配置

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    spring:
    cloud:
    gateway:
    globalcors:
    cors-configurations:
    '[/**]': # 匹配所有请求
    allowedOrigins: "*" #跨域处理 允许所有的域
    allowedMethods: # 支持的方法
    - GET
    - POST
    - PUT
    - DELETE
    routes:
    # 平台管理
    - id: wemedia
    uri: lb://leadnews-wemedia
    predicates:
    - Path=/wemedia/**
    filters:
    - StripPrefix= 1
    + +

    image-20230819161529210

    +

    2.前端环境搭建

    搭建思路

    +

    image-20230819162116240

    +

    搭建步骤

    +

    image-20230819162230133

    +

    heima-leadnews-wemedia配置文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    upstream  heima-wemedia-gateway{
    #APP端网关所在的端口
    server localhost:51602;
    }

    server {
    listen 8802;
    location / {
    root C:/Gong/data/wemedia-web/;
    index index.html;
    }

    location ~/wemedia/MEDIA/(.*) {
    proxy_pass http://heima-wemedia-gateway/$1;
    proxy_set_header HOST $host; # 不改变源请求头的值
    proxy_pass_request_body on; #开启获取请求体
    proxy_pass_request_headers on; #开启获取请求头
    proxy_set_header X-Real-IP $remote_addr; # 记录真实发出请求的客户端IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #记录代理信息
    }
    }
    + +

    重启nginx访问

    +

    image-20230902145814017

    +

    3.自媒体素材管理功能

    3.1 素材管理-图片上传

    图片素材相关的实体类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    package com.heima.model.wemedia.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * <p>
    * 自媒体图文素材信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("wm_material")
    public class WmMaterial implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
    * 主键
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 自媒体用户ID
    */
    @TableField("user_id")
    private Integer userId;

    /**
    * 图片地址
    */
    @TableField("url")
    private String url;

    /**
    * 素材类型
    0 图片
    1 视频
    */
    @TableField("type")
    private Short type;

    /**
    * 是否收藏
    */
    @TableField("is_collection")
    private Short isCollection;

    /**
    * 创建时间
    */
    @TableField("created_time")
    private Date createdTime;

    }
    + +

    3.1.1 解决图片素材实体类中获取图片userId的问题

    实现思路

    +

    image-20230902154806019

    +

    1.token解析为用户存入header

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    package com.heima.wemedia.gateway.filter;


    import com.heima.wemedia.gateway.util.AppJwtUtil;
    import io.jsonwebtoken.Claims;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;

    @Component
    @Slf4j
    public class AuthorizeFilter implements Ordered, GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //1.获取request和response对象
    ServerHttpRequest request = exchange.getRequest();
    ServerHttpResponse response = exchange.getResponse();

    //2.判断是否是登录
    if(request.getURI().getPath().contains("/login")){
    //放行
    return chain.filter(exchange);
    }

    //3.获取token
    String token = request.getHeaders().getFirst("token");

    //4.判断token是否存在
    if(StringUtils.isBlank(token)){
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }

    //5.判断token是否有效
    try {
    Claims claimsBody = AppJwtUtil.getClaimsBody(token);
    //是否是过期
    int result = AppJwtUtil.verifyToken(claimsBody);
    if(result == 1 || result == 2){
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }
    //获取用户信息
    Object userId = claimsBody.get("id");
    //存入header中
    ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
    httpHeaders.add("userId", userId + "");
    }).build();
    //重置请求
    exchange.mutate().request(serverHttpRequest);

    } catch (Exception e) {
    e.printStackTrace();
    }

    //6.放行
    return chain.filter(exchange);
    }

    /**
    * 优先级设置 值越小 优先级越高
    * @return
    */
    @Override
    public int getOrder() {
    return 0;
    }
    }
    + +

    2.创建拦截器

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package com.heima.wemedia.interceptor;

    import com.heima.model.wemedia.pojos.WmUser;
    import com.heima.utils.thread.WmThreadLocalUtil;
    import io.swagger.models.auth.In;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Enumeration;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/9/2
    * @Description
    */
    public class WmTokenInterceptor implements HandlerInterceptor {

    /**
    * 获取header中的信息 存入当前的线程中
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //获取header中的userId信息
    String userId = request.getHeader("userId");
    //判断userId是否为空
    if(userId != null){
    //将userId存入当前的线程中,我们可以在任何位置获取
    WmUser wmUser = new WmUser();
    wmUser.setId(Integer.valueOf(userId));
    WmThreadLocalUtil.setUser(wmUser);
    }
    return true;
    }


    /**
    * 清理线程中的数据
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    WmThreadLocalUtil.clear();
    }
    }
    + +

    ThreadLocal工具类,实现在线程中存储、获取、清理用户信息

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    package com.heima.utils.thread;

    import com.heima.model.wemedia.pojos.WmUser;

    public class WmThreadLocalUtil {

    private final static ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();

    /**
    * 添加用户
    * @param wmUser
    */
    public static void setUser(WmUser wmUser){
    WM_USER_THREAD_LOCAL.set(wmUser);
    }

    /**
    * 获取用户
    */
    public static WmUser getUser(){
    return WM_USER_THREAD_LOCAL.get();
    }

    /**
    * 清理用户
    */
    public static void clear(){
    WM_USER_THREAD_LOCAL.remove();
    }
    }
    + +

    3.让拦截器生效

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.heima.wemedia.config;

    import com.heima.wemedia.interceptor.WmTokenInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/9/2
    * @Description
    */
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
    }
    }
    + +

    3.1.2 图片上传接口的定义

    + + + + + + + + + + + + + + + + + + + + + + +
    说明
    接口路径/api/v1/material/upload_picture
    请求方式POST
    参数MultipartFile
    响应结果ResponseResult
    +

    3.1.3 代码实现

    1.在pom.xml中引入自定义minio的starter依赖

    +
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-file-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    + +

    2.在项目中添加minio的配置(在nacos中配置)

    +
    1
    2
    3
    4
    5
    6
    minio:
    accessKey: minio
    secretKey: minio123
    bucket: leadnews
    endpoint: http://192.168.200.130:9000
    readPath: http://192.168.200.130:9000
    + +

    3.上传图片的关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    package com.heima.wemedia.service.impl;

    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.heima.file.service.FileStorageService;
    import com.heima.model.common.dtos.ResponseResult;
    import com.heima.model.common.enums.AppHttpCodeEnum;
    import com.heima.model.wemedia.pojos.WmMaterial;
    import com.heima.utils.thread.WmThreadLocalUtil;
    import com.heima.wemedia.mapper.WmMaterialMapper;
    import com.heima.wemedia.service.WmMaterialService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.IOException;
    import java.util.Date;
    import java.util.UUID;


    @Slf4j
    @Service
    @Transactional
    public class WmMaterialServiceImpl extends ServiceImpl<WmMaterialMapper, WmMaterial> implements WmMaterialService {

    @Autowired
    private FileStorageService fileStorageService; //使用minio

    @Override
    public ResponseResult uploadPicture(MultipartFile multipartFile) {
    //1.检查参数
    if(multipartFile == null || multipartFile.getSize() == 0){
    return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    }
    //2.上传图片到minio中
    String fileName = UUID.randomUUID().toString().replace("-", "");
    String originalFilename = multipartFile.getOriginalFilename();
    String postfix = originalFilename.substring(originalFilename.lastIndexOf("."));
    String fileId = null;
    try {
    fileId = fileStorageService.uploadImgFile("", fileName + postfix, multipartFile.getInputStream());
    log.info("上传图片到Minio中,fileId:{}",fileId);
    } catch (IOException e) {
    log.info("WmMaterialServiceImpl-上传图片失败");
    e.printStackTrace();
    }
    //3.将图片的信息保存在数据库中
    WmMaterial wmMaterial = new WmMaterial();
    wmMaterial.setUserId(WmThreadLocalUtil.getUser().getId());
    wmMaterial.setUrl(fileId);
    wmMaterial.setType((short)0);
    wmMaterial.setIsCollection((short)0);
    wmMaterial.setCreatedTime(new Date());
    save(wmMaterial);
    //4.返回参数
    return ResponseResult.okResult(wmMaterial);
    }
    }
    + +

    4.测试上传功能

    +

    image-20230902220022201

    +

    3.2 素材管理-图片列表

    接口定义

    + + + + + + + + + + + + + + + + + + + +
    接口路径/api//v1/material/list
    请求方式POST
    参数WmMaterialDto
    响应结果ResponseResult
    +

    请求参数的DTO

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Data
    public class WmMaterialDto extends PageRequestDto {

    /**
    * 1 收藏
    * 0 未收藏
    */
    private Short isCollection;
    }
    + +

    关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 素材图片的列表显示
    */
    @Override
    public ResponseResult findList(WmMaterialDto wmMaterialDto) {
    //1.检查参数
    wmMaterialDto.checkParam();
    //2.分页查询
    IPage page = new Page(wmMaterialDto.getPage(),wmMaterialDto.getSize());
    LambdaQueryWrapper<WmMaterial> queryWrapper = new LambdaQueryWrapper<>();
    //是否收藏
    if(wmMaterialDto.getIsCollection() != null && wmMaterialDto.getIsCollection() == 1){
    queryWrapper.eq(WmMaterial::getIsCollection,wmMaterialDto.getIsCollection());
    }
    //按照用户查询
    queryWrapper.eq(WmMaterial::getUserId,WmThreadLocalUtil.getUser().getId());
    //按照时间查询
    queryWrapper.orderByDesc(WmMaterial::getCreatedTime);
    page = page(page,queryWrapper);
    //3.结果返回
    ResponseResult responseResult = new PageResponseResult(wmMaterialDto.getPage(), wmMaterialDto.getSize(), (int) page.getTotal());
    responseResult.setData(page.getRecords());
    return responseResult;
    }
    + +

    MP的配置

    +
    1
    2
    3
    4
    5
    6
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
    }
    + +

    实现效果

    +

    image-20230903125528872

    +

    4.自媒体文章管理功能

    4.1 频道列表查询

    频道对应的实体类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    package com.heima.model.wemedia.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * <p>
    * 频道信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("wm_channel")
    public class WmChannel implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 频道名称
    */
    @TableField("name")
    private String name;

    /**
    * 频道描述
    */
    @TableField("description")
    private String description;

    /**
    * 是否默认频道
    * 1:默认 true
    * 0:非默认 false
    */
    @TableField("is_default")
    private Boolean isDefault;

    /**
    * 是否启用
    * 1:启用 true
    * 0:禁用 false
    */
    @TableField("status")
    private Boolean status;

    /**
    * 默认排序
    */
    @TableField("ord")
    private Integer ord;

    /**
    * 创建时间
    */
    @TableField("created_time")
    private Date createdTime;
    }
    + +

    接口定义

    + + + + + + + + + + + + + + + + + + + + + + + +
    说明
    接口路径/api/v1/channel/channels
    请求方式GET
    参数
    响应结果ResponseResult
    +

    关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    /**
    * 查询所有的频道信息
    */
    @GetMapping("/channels")
    public ResponseResult findAllChannels(){
    List<WmChannel> channels = wmChannelService.list();
    return ResponseResult.okResult(channels);
    }
    + +

    实现效果

    +

    image-20230903151216766

    +

    4.2 文章列表加载

    文章表对应的实体类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    package com.heima.model.wemedia.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;
    import org.apache.ibatis.type.Alias;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * <p>
    * 自媒体图文内容信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("wm_news")
    public class WmNews implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
    * 主键
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 自媒体用户ID
    */
    @TableField("user_id")
    private Integer userId;

    /**
    * 标题
    */
    @TableField("title")
    private String title;

    /**
    * 图文内容
    */
    @TableField("content")
    private String content;

    /**
    * 文章布局
    0 无图文章
    1 单图文章
    3 多图文章
    */
    @TableField("type")
    private Short type;

    /**
    * 图文频道ID
    */
    @TableField("channel_id")
    private Integer channelId;

    @TableField("labels")
    private String labels;

    /**
    * 创建时间
    */
    @TableField("created_time")
    private Date createdTime;

    /**
    * 提交时间
    */
    @TableField("submited_time")
    private Date submitedTime;

    /**
    * 当前状态
    0 草稿
    1 提交(待审核)
    2 审核失败
    3 人工审核
    4 人工审核通过
    8 审核通过(待发布)
    9 已发布
    */
    @TableField("status")
    private Short status;

    /**
    * 定时发布时间,不定时则为空
    */
    @TableField("publish_time")
    private Date publishTime;

    /**
    * 拒绝理由
    */
    @TableField("reason")
    private String reason;

    /**
    * 发布库文章ID
    */
    @TableField("article_id")
    private Long articleId;

    /**
    * //图片用逗号分隔
    */
    @TableField("images")
    private String images;

    @TableField("enable")
    private Short enable;

    //状态枚举类
    @Alias("WmNewsStatus")
    public enum Status{
    NORMAL((short)0),SUBMIT((short)1),FAIL((short)2),ADMIN_AUTH((short)3),ADMIN_SUCCESS((short)4),SUCCESS((short)8),PUBLISHED((short)9);
    short code;
    Status(short code){
    this.code = code;
    }
    public short getCode(){
    return this.code;
    }
    }

    }
    + +

    接口定义

    + + + + + + + + + + + + + + + + + + + +
    接口路径/api//v1/news/list
    请求方式POST
    参数WmNewsPageReqDto
    响应结果ResponseResult
    +

    关键代码

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    @Override
    public ResponseResult findNewsList(WmNewsPageReqDto wmNewsPageReqDto) {
    //1.检查参数
    wmNewsPageReqDto.checkParam();
    //2.根据条件查询
    IPage page = new Page(wmNewsPageReqDto.getPage(), wmNewsPageReqDto.getSize());
    LambdaQueryWrapper<WmNews> queryWrapper = new LambdaQueryWrapper<>();
    //状态
    if(wmNewsPageReqDto.getStatus() != null){
    queryWrapper.eq(WmNews::getStatus,wmNewsPageReqDto.getStatus());
    }
    //开始时间 结束时间
    if(wmNewsPageReqDto.getBeginPubDate() != null && wmNewsPageReqDto.getEndPubDate()!=null){
    queryWrapper.between(WmNews::getPublishTime,wmNewsPageReqDto.getBeginPubDate(),wmNewsPageReqDto.getEndPubDate());
    }
    //所属频道id
    if(wmNewsPageReqDto.getChannelId() != null){
    queryWrapper.eq(WmNews::getChannelId,wmNewsPageReqDto.getChannelId());
    }
    //关键字
    if(wmNewsPageReqDto.getKeyword() != null){
    queryWrapper.like(WmNews::getTitle,wmNewsPageReqDto.getKeyword());
    }
    //查询当前登录人的文章
    queryWrapper.eq(WmNews::getUserId, WmThreadLocalUtil.getUser().getId());
    //按照发布时间倒序查询
    queryWrapper.orderByDesc(WmNews::getPublishTime);
    page = page(page,queryWrapper);
    //3.结果返回
    ResponseResult responseResult = new PageResponseResult(wmNewsPageReqDto.getPage(), wmNewsPageReqDto.getSize(), (int) page.getTotal());
    responseResult.setData(page.getRecords());
    return responseResult;
    }
    + +

    实现效果

    +

    image-20230903185956428

    +

    4.3 发布文章功能(核心功能)

    文章和素材对应关系表

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    package com.heima.model.wemedia.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.io.Serializable;

    /**
    * <p>
    * 自媒体图文引用素材信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("wm_news_material")
    public class WmNewsMaterial implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
    * 主键
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 素材ID
    */
    @TableField("material_id")
    private Integer materialId;

    /**
    * 图文ID
    */
    @TableField("news_id")
    private Integer newsId;

    /**
    * 引用类型
    0 内容引用
    1 主图引用
    */
    @TableField("type")
    private Short type;

    /**
    * 引用排序
    */
    @TableField("ord")
    private Short ord;

    }
    + +

    实现流程

    +

    image-20230904151910210

    +

    接口定义

    + + + + + + + + + + + + + + + + + + + + + + + +
    说明
    接口路径/api/v1/news/submit
    请求方式POST
    参数WmNewsDto
    响应结果ResponseResult
    +

    WmNewsDto

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    package com.heima.model.wemedia.dtos;

    import lombok.Data;

    import java.util.Date;
    import java.util.List;

    @Data
    public class WmNewsDto {

    private Integer id;
    /**
    * 标题
    */
    private String title;
    /**
    * 频道id
    */
    private Integer channelId;
    /**
    * 标签
    */
    private String labels;
    /**
    * 发布时间
    */
    private Date publishTime;
    /**
    * 文章内容
    */
    private String content;
    /**
    * 文章封面类型 0 无图 1 单图 3 多图 -1 自动
    */
    private Short type;
    /**
    * 提交时间
    */
    private Date submitedTime;
    /**
    * 状态 提交为1 草稿为0
    */
    private Short status;

    /**
    * 封面图片列表 多张图以逗号隔开
    */
    private List<String> images;
    }
    + +

    前端接受数据格式

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    {
    "title":"黑马头条项目背景",
    "type":"1",//这个 0 是无图 1 是单图 3 是多图 -1 是自动
    "labels":"黑马头条",
    "publishTime":"2020-03-14T11:35:49.000Z",
    "channelId":1,
    "images":[
    "http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
    ],
    "status":1,
    "content":"[
    {
    "type":"text",
    "value":"随着智能手机的普及,人们更加习惯于通过手机来看新闻。由于生活节奏的加快,很多人只能利用碎片时间来获取信息,因此,对于移动资讯客户端的需求也越来越高。黑马头条项目正是在这样背景下开发出来。黑马头条项目采用当下火热的微服务+大数据技术架构实现。本项目主要着手于获取最新最热新闻资讯,通过大数据分析用户喜好精确推送咨询新闻"
    },
    {
    "type":"image",
    "value":"http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
    }
    ]"
    }
    + +

    保存文章和素材对应关系mapper接口

    +

    mapper接口

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Mapper
    public interface WmNewsMaterialMapper extends BaseMapper<WmNewsMaterial> {


    /**
    * 批量保存文章和素材之间的关系
    * @param materialIds 素材的id
    * @param newsId 文章的id
    * @param type 类型
    */
    void saveRelations(@Param("materialIds") List<Integer> materialIds,@Param("newsId") Integer newsId, @Param("type")Short type);
    }
    + +

    xml文件

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.heima.wemedia.mapper.WmNewsMaterialMapper">

    <insert id="saveRelations">
    insert into wm_news_material (material_id,news_id,type,ord)
    values
    <foreach collection="materialIds" index="ord" item="mid" separator=",">
    (#{mid},#{newsId},#{type},#{ord})
    </foreach>
    </insert>

    </mapper>
    + +

    使用到的常量类

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.heima.common.constants;

    public class WemediaConstants {

    public static final Short COLLECT_MATERIAL = 1;//收藏

    public static final Short CANCEL_COLLECT_MATERIAL = 0;//取消收藏

    public static final String WM_NEWS_TYPE_IMAGE = "image";

    public static final Short WM_NEWS_NONE_IMAGE = 0;
    public static final Short WM_NEWS_SINGLE_IMAGE = 1;
    public static final Short WM_NEWS_MANY_IMAGE = 3;
    public static final Short WM_NEWS_TYPE_AUTO = -1;

    public static final Short WM_CONTENT_REFERENCE = 0;
    public static final Short WM_COVER_REFERENCE = 1;
    }
    + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    目录
    1. 1. 一.项目介绍
      1. 1.1. 1.项目概述
      2. 1.2. 2.业务说明
      3. 1.3. 3.技术栈
    2. 2. 二.环境搭建
      1. 2.1. 1.Linxu环境的搭建
        1. 2.1.1. 1.1 虚拟机的安装
        2. 2.1.2. 1.2 Linux软件安装
      2. 2.2. 2.开发环境的配置
        1. 2.2.1. 2.1 项目依赖的环境
        2. 2.2.2. 2.2 后端工程的搭建
    3. 3. 三.app端功能开发
      1. 3.1. 1.app登录
        1. 3.1.1. 1.1 用户登录逻辑
        2. 3.1.2. 1.2 用户模块搭建
        3. 3.1.3. 1.3 登录功能实现
          1. 3.1.3.1. 1.3.1 接口定义
          2. 3.1.3.2. 1.3.2 登录思路分析
          3. 3.1.3.3. 1.3.3 登录关键代码实现
          4. 3.1.3.4. 1.3.4 使用接口工具测试
      2. 3.2. 2. app端网关搭建
        1. 3.2.1. 2.1 搭建过程
        2. 3.2.2. 2.2 全局过滤器实现jwt校验
      3. 3.3. 3.app前端项目集成
        1. 3.3.1. 3.1 Nginx集成前端项目步骤
      4. 3.4. 4.app端文章列表功能
        1. 3.4.1. 4.1 数据库表的创建
        2. 3.4.2. 4.2 文章模块搭建
        3. 3.4.3. 4.3 首页文章的列表显示
          1. 3.4.3.1. 4.2.1 接口定义
          2. 3.4.3.2. 4.2.2 实现思路
          3. 3.4.3.3. 4.2.3 功能的关键代码实现
      5. 3.5. 5. app端文章详情功能
        1. 3.5.1. 5.1 需求分析
        2. 3.5.2. 5.2 实现方案-静态模板展示
        3. 3.5.3. 5.3 对象存储服务MinIO
        4. 3.5.4. 5.4 实现思路以及代码实现
    4. 4. 四.自媒体端功能开发
      1. 4.1. 1.后端环境搭建
      2. 4.2. 2.前端环境搭建
      3. 4.3. 3.自媒体素材管理功能
        1. 4.3.1. 3.1 素材管理-图片上传
          1. 4.3.1.1. 3.1.1 解决图片素材实体类中获取图片userId的问题
          2. 4.3.1.2. 3.1.2 图片上传接口的定义
          3. 4.3.1.3. 3.1.3 代码实现
        2. 4.3.2. 3.2 素材管理-图片列表
      4. 4.4. 4.自媒体文章管理功能
        1. 4.4.1. 4.1 频道列表查询
        2. 4.4.2. 4.2 文章列表加载
        3. 4.4.3. 4.3 发布文章功能(核心功能)
    最近更新
    \ No newline at end of file diff --git a/posts/6932.html b/posts/6932.html new file mode 100644 index 000000000..6d61b4ea6 --- /dev/null +++ b/posts/6932.html @@ -0,0 +1,205 @@ +内网穿透 | The Blog + + + + + + + + + + + + +

    内网穿透

    第一步 官网下载

    客户端下载 –> windows 64位 –>解压后得到.exe文件

    +

    image-20230409221503708

    +

    第二步 官网注册并登录进入后台

    官网注册登录 –> 购买免费隧道 –>获取authtoken

    +

    image-20230409221827414

    +

    image-20230409221954792

    +

    第三步 双击下载的.exe文件进入命令行页面输入命令

    1
    natapp -authtoken=刚刚你申请的authtoken
    + +

    image-20230409222457135

    +

    image-20230409222628425

    +

    第四步 根据生成的域名访问服务

    image-20230409222923480

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/73.html b/posts/73.html new file mode 100644 index 000000000..04b2c9110 --- /dev/null +++ b/posts/73.html @@ -0,0 +1,204 @@ +SpringBoot中使用定时任务 | The Blog + + + + + + + + + + + + +

    SpringBoot中使用定时任务

    1.在启动类上添加@EnableScheduling注解

    1
    @EnableScheduling
    + +

    2.创建定时任务类,设置cron表达式

    定时任务可以单独建立一个包 package com.atguigu.schedule

    +

    加上@Component注解,交给spring管理,启动这个模块,定时任务就开启了

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.atguigu.schedule;

    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/3/29
    * @Description 定时任务
    */
    @Component
    public class ScheduledTask {

    //每隔五秒执行这个方法
    //"0/5 * * * * ?" 是corn表达式
    @Scheduled(cron = "0/5 * * * * ?")
    public void tesSchedule(){
    System.out.println("定时任务执行了....");
    }
    }

    + +

    image-20230329184540977

    +

    3.Corn表达式(七子[七域]表达式)生成工具

    网址: https://cron.qqe2.com/

    +

    image-20230329184618098

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/7353.html b/posts/7353.html new file mode 100644 index 000000000..f82cff586 --- /dev/null +++ b/posts/7353.html @@ -0,0 +1,203 @@ +谷粒学苑课程分类列表显示前后端代码 | The Blog + + + + + + + + + + + + +

    谷粒学苑课程分类列表显示前后端代码

    ​ 谷粒学苑的前端课程分类显示使用的是树形列表显示,如图所示:

    +

    image-20230310102929346

    +

    ​ Swagger测试时显示的数据格式

    +

    image-20230310103351729

    +

    ​ 前端代码代码具体的实现(部分代码):

    +

    image-20230310103126938

    +

    ​ 后端代码具体的实现(业务层):

    +

    image-20230310103430906

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/855.html b/posts/855.html new file mode 100644 index 000000000..13bdc7576 --- /dev/null +++ b/posts/855.html @@ -0,0 +1,209 @@ +SpringBoot整合Knife4j | The Blog + + + + + + + + + + + + +

    SpringBoot整合Knife4j

    1.介绍

    一句话介绍Knife4j: Swagger的增强版,界面更好看,功能更加的丰富

    +

    文档地址:https://doc.xiaominfo.com/

    +

    image-20230505222154471

    +

    image-20230505223651097

    +

    2.使用教程

    2.1 引入依赖

    1
    2
    3
    4
    5
    6
    <!--引入Knife4j的官方start包,该指南选择Spring Boot版本<3.0,开发者需要注意-->
    <dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
    <version>4.0.0</version>
    </dependency>
    + +

    2.2 编写配置类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    package com.atguigu.common.config.knife4j;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.ParameterBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.schema.ModelRef;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.service.Parameter;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/5
    * @Description knife4j配置信息
    */

    @Configuration
    @EnableSwagger2WebMvc
    public class Knife4jConfig {

    @Bean
    public Docket adminApiConfig(){
    List<Parameter> pars = new ArrayList<>();
    ParameterBuilder tokenPar = new ParameterBuilder();
    tokenPar.name("token")
    .description("用户token")
    .defaultValue("")
    .modelRef(new ModelRef("string"))
    .parameterType("header")
    .required(false)
    .build();
    pars.add(tokenPar.build());
    //添加head参数end

    Docket adminApi = new Docket(DocumentationType.SWAGGER_2)
    .groupName("adminApi")
    .apiInfo(adminApiInfo())
    .select()
    //只显示admin路径下的页面
    .apis(RequestHandlerSelectors.basePackage("com.atguigu"))
    .paths(PathSelectors.regex("/admin/.*"))
    .build()
    .globalOperationParameters(pars);
    return adminApi;
    }

    private ApiInfo adminApiInfo(){

    return new ApiInfoBuilder()
    .title("后台管理系统-API文档")
    .description("本文档描述了后台管理系统微服务接口定义")
    .version("1.0")
    .contact(new Contact("JasonGong", "https://gong-changjiang.gitee.io/", "2602183349@qq.com"))
    .build();
    }
    }
    + +

    2.3 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    package com.atguigu.auth.controller;

    import com.atguigu.auth.service.SysRoleService;
    import com.atguigu.common.result.Result;
    import com.atguigu.model.system.SysRole;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/5
    * @Description 角色的控制器方法
    */
    @Api(tags = "角色管理的接口")
    @RestController
    @RequestMapping("/admin/system/sysRole")
    @CrossOrigin
    public class SysRoleController {

    @Autowired
    private SysRoleService sysRoleService;


    /**
    * 查询所有的方法
    */
    @ApiOperation("查询所有角色的接口")
    @GetMapping("/findAll")
    public Result<List<SysRole>> findAll(){
    List<SysRole> sysRoles = sysRoleService.list();
    return Result.ok(sysRoles);
    }

    }
    + +

    2.4接口文档的访问地址

    1
    2
    # 端口号根据项目的端口号进行改变
    http://localhost:8080/doc.html
    + +

    image-20230505223433369

    +

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/posts/8957.html b/posts/8957.html new file mode 100644 index 000000000..0f444d001 --- /dev/null +++ b/posts/8957.html @@ -0,0 +1,568 @@ +Linux从入门到进阶 | The Blog + + + + + + + + + + + + +

    Linux从入门到进阶

    本篇博客转载于传智播客黑马程序员,只作在线笔记使用,详细的课程资料请关注黑马程序员!

    +
    + +

    Linux相关的书籍: 技术书籍-Linux指令大全 | The Blog (gitee.io)

    +

    一.初识Linux

    ​ Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    +

    image-20230919214601245

    +

    视频链接: https://www.bilibili.com/video/BV1n84y1i7td/?share_source=copy_web&vd_source=aee5e475191b69e6c781059ab6662584

    +

    二.Linux的安装和配置教程

    1.通过虚拟机安装Linux

    Linux安装教程: VMWare虚拟机安装Linux教程 | The Blog (gitee.io)

    +

    Linux设置静态IP:Linux设置静态IP | The Blog (gitee.io)

    +

    2.通过WSL获取Linux环境

    安装教程: Windows10的Linux子系统WSL的安装和使用 | The Blog (gitee.io)

    +

    三.Linux的目录结构

    image-20230919230906304

    +

    image-20230919231127383

    +

    四.LInux基础命令

    ls命令

    功能:列出文件夹信息

    +

    语法:ls [-l -h -a] [参数]

    +
      +
    • 参数:被查看的文件夹,不提供参数,表示查看当前工作目录
    • +
    • -l,以列表形式查看
    • +
    • -h,配合-l,以更加人性化的方式显示文件大小
    • +
    • -a,显示隐藏文件
    • +
    +

    隐藏文件、文件夹

    在Linux中以 .开头的,均是隐藏的。

    +

    默认不显示出来,需要 -a选项才可查看到。

    +

    pwd命令

    功能:展示当前工作目录

    +

    语法:pwd

    +

    cd命令

    功能:切换工作目录

    +

    语法:cd [目标目录]

    +

    参数:目标目录,要切换去的地方,不提供默认切换到 当前登录用户HOME目录

    +

    HOME目录

    每一个用户在Linux系统中都有自己的专属工作目录,称之为HOME目录。

    +
      +
    • 普通用户的HOME目录,默认在:/home/用户名
    • +
    • root用户的HOME目录,在:/root
    • +
    +

    FinalShell登陆终端后,默认的工作目录就是用户的HOME目录

    +

    相对路径、绝对路径

      +
    • 相对路径,==非==/开头的称之为相对路径

      +

      相对路径表示以 当前目录作为起点,去描述路径,如 test/a.txt,表示当前工作目录内的test文件夹内的a.txt文件

      +
    • +
    • 绝对路径,==以==/开头的称之为绝对路径

      +

      绝对路径从 开始描述路径

      +
    • +
    +

    特殊路径符

      +
    • .,表示当前,比如./a.txt,表示当前文件夹内的 a.txt文件
    • +
    • ..,表示上级目录,比如 ../表示上级目录,../../表示上级的上级目录
    • +
    • ~,表示用户的HOME目录,比如 cd ~,即可切回用户HOME目录
    • +
    +

    mkdir命令

    功能:创建文件夹

    +

    语法:mkdir [-p] 参数

    +
      +
    • 参数:被创建文件夹的路径
    • +
    • 选项:-p,可选,表示创建前置路径
    • +
    +

    touch命令

    功能:创建文件

    +

    语法:touch 参数

    +
      +
    • 参数:被创建的文件路径
    • +
    +

    cat命令

    功能:查看文件内容

    +

    语法:cat 参数

    +
      +
    • 参数:被查看的文件路径
    • +
    +

    more命令

    功能:查看文件,可以支持翻页查看

    +

    语法:more 参数

    +
      +
    • 参数:被查看的文件路径
    • +
    • 在查看过程中:
        +
      • 空格键翻页
      • +
      • q退出查看
      • +
      +
    • +
    +

    cp命令

    功能:复制文件、文件夹

    +

    语法:cp [-r] 参数1 参数2

    +
      +
    • 参数1,被复制的
    • +
    • 参数2,要复制去的地方
    • +
    • 选项:-r,可选,复制文件夹使用
    • +
    +

    示例:

    +
      +
    • cp a.txt b.txt,复制当前目录下a.txt为b.txt
    • +
    • cp a.txt test/,复制当前目录a.txt到test文件夹内
    • +
    • cp -r test test2,复制文件夹test到当前文件夹内为test2存在
    • +
    +

    mv命令

    功能:移动文件、文件夹

    +

    语法:mv 参数1 参数2

    +
      +
    • 参数1:被移动的
    • +
    • 参数2:要移动去的地方,参数2如果不存在,则会进行改名
    • +
    +

    rm命令

    功能:删除文件、文件夹

    +

    语法:rm [-r -f] 参数...参数

    +
      +
    • 参数:支持多个,每一个表示被删除的,空格进行分隔
    • +
    • 选项:-r,删除文件夹使用
    • +
    • 选项:-f,强制删除,不会给出确认提示,一般root用户会用到
    • +
    +
    +

    rm命令很危险,一定要注意,特别是切换到root用户的时候。

    +
    +

    image-20230920231224351

    +

    which命令

    功能:查看命令的程序本体文件路径

    +

    语法:which 参数

    +
      +
    • 参数:被查看的命令
    • +
    +

    find命令

    功能:搜索文件

    +

    语法1按文件名搜索:find 路径 -name 参数

    +
      +
    • 路径,搜索的起始路径
    • +
    • 参数,搜索的关键字,支持通配符*, 比如:*test表示搜索任意以test结尾的文件
    • +
    +

    grep命令

    功能:过滤关键字

    +

    语法:grep [-n] 关键字 文件路径

    +
      +
    • 选项-n,可选,表示在结果中显示匹配的行的行号。
    • +
    • 参数,关键字,必填,表示过滤的关键字,带有空格或其它特殊符号,建议使用””将关键字包围起来
    • +
    • 参数,文件路径,必填,表示要过滤内容的文件路径,可作为内容输入端口
    • +
    +
    +

    参数文件路径,可以作为管道符的输入

    +
    +

    wc命令

    功能:统计

    +

    语法:wc [-c -m -l -w] 文件路径

    +
      +
    • 选项,-c,统计bytes数量
    • +
    • 选项,-m,统计字符数量
    • +
    • 选项,-l,统计行数
    • +
    • 选项,-w,统计单词数量
    • +
    • 参数,文件路径,被统计的文件,可作为内容输入端口
    • +
    +
    +

    参数文件路径,可作为管道符的输入

    +
    +

    管道符|

    写法:|

    +

    功能:将符号左边的结果,作为符号右边的输入

    +

    示例:

    +

    cat a.txt | grep itheima,将cat a.txt的结果,作为grep命令的输入,用来过滤 itheima关键字

    +

    可以支持嵌套:

    +

    cat a.txt | grep itheima | grep itcast

    +

    echo命令

    功能:输出内容

    +

    语法:echo 参数

    +
      +
    • 参数:被输出的内容
    • +
    +

    `反引号

    功能:被两个反引号包围的内容,会作为命令执行

    +

    示例:

    +
      +
    • echo `pwd`,会输出当前工作目录
    • +
    +

    tail命令

    功能:查看文件尾部内容

    +

    语法:tail [-f] 参数

    +
      +
    • 参数:被查看的文件
    • +
    • 选项:-f,持续跟踪文件修改
    • +
    +

    head命令

    功能:查看文件头部内容

    +

    语法:head [-n] 参数

    +
      +
    • 参数:被查看的文件
    • +
    • 选项:-n,查看的行数
    • +
    +

    重定向符

    功能:将符号左边的结果,输出到右边指定的文件中去

    +
      +
    • >,表示覆盖输出
    • +
    • >>,表示追加输出
    • +
    +

    vi编辑器

    命令模式快捷键

    image-20221027215841573

    +

    image-20221027215846581

    +

    image-20221027215849668

    +

    底线命令快捷键

    image-20221027215858967

    +

    命令的选项

    我们学习的一系列Linux命令,它们所拥有的选项都是非常多的。

    +

    比如,简单的ls命令就有:-a -A -b -c -C -d -D -f -F -g -G -h -H -i -I -k -l -L -m -n -N -o -p -q -Q -r-R -s -S -t -T -u -U -v -w -x -X -1等选项,可以发现选项是极其多的。

    +

    课程中, 并不会将全部的选项都进行讲解,否则,一个ls命令就可能讲解2小时之久。

    +

    课程中,会对常见的选项进行讲解, 足够满足绝大多数的学习、工作场景。

    +

    查看命令的帮助

    可以通过:命令 --help查看命令的帮助手册

    +

    image-20221027220005610

    +

    查看命令的详细手册

    可以通过:man 命令查看某命令的详细手册

    +

    image-20221027220009949

    +

    五.Linux常用操作

    软件安装

      +
    • CentOS系统使用:
        +
      • yum [install remove search] [-y] 软件名称
          +
        • install 安装
        • +
        • remove 卸载
        • +
        • search 搜索
        • +
        • -y,自动确认
        • +
        +
      • +
      +
    • +
    • Ubuntu系统使用
        +
      • apt [install remove search] [-y] 软件名称
          +
        • install 安装
        • +
        • remove 卸载
        • +
        • search 搜索
        • +
        • -y,自动确认
        • +
        +
      • +
      +
    • +
    +
    +

    yum 和 apt 均需要root权限

    +
    +

    systemctl

    功能:控制系统服务的启动关闭等

    +

    语法:systemctl start | stop | restart | disable | enable | status 服务名

    +
      +
    • start,启动
    • +
    • stop,停止
    • +
    • status,查看状态
    • +
    • disable,关闭开机自启
    • +
    • enable,开启开机自启
    • +
    • restart,重启
    • +
    +

    软链接

    功能:创建文件、文件夹软链接(快捷方式)

    +

    语法:ln -s 参数1 参数2

    +
      +
    • 参数1:被链接的
    • +
    • 参数2:要链接去的地方(快捷方式的名称和存放位置)
    • +
    +

    日期

    语法:date [-d] [+格式化字符串]

    +
      +
    • -d 按照给定的字符串显示日期,一般用于日期计算

      +
    • +
    • 格式化字符串:通过特定的字符串标记,来控制显示的日期格式

      +
        +
      • %Y 年%y 年份后两位数字 (00..99)
      • +
      • %m 月份 (01..12)
      • +
      • %d 日 (01..31)
      • +
      • %H 小时 (00..23)
      • +
      • %M 分钟 (00..59)
      • +
      • %S 秒 (00..60)
      • +
      • %s 自 1970-01-01 00:00:00 UTC 到现在的秒数
      • +
      +
    • +
    +

    示例:

    +
      +
    • 按照2022-01-01的格式显示日期

      +

      image-20221027220514640

      +
    • +
    • 按照2022-01-01 10:00:00的格式显示日期

      +

      image-20221027220525625

      +
    • +
    • -d选项日期计算

      +

      image-20221027220429831

      +
        +
      • 支持的时间标记为:

        +

        image-20221027220449312

        +
      • +
      +
    • +
    +

    时区

    修改时区为中国时区

    +

    image-20221027220554654

    +

    ntp

    功能:同步时间

    +

    安装:yum install -y ntp

    +

    启动管理:systemctl start | stop | restart | status | disable | enable ntpd

    +

    手动校准时间:ntpdate -u ntp.aliyun.com

    +

    ip地址

    格式:a.b.c.d

    +
      +
    • abcd为0~255的数字
    • +
    +

    特殊IP:

    +
      +
    • 127.0.0.1,表示本机
    • +
    • 0.0.0.0
        +
      • 可以表示本机
      • +
      • 也可以表示任意IP(看使用场景)
      • +
      +
    • +
    +

    查看ip:ifconfig

    +

    主机名

    功能:Linux系统的名称

    +

    查看:hostname

    +

    设置:hostnamectl set-hostname 主机名

    +

    配置VMware固定IP

      +
    1. 修改VMware网络,参阅PPT,图太多

      +
    2. +
    3. 设置Linux内部固定IP

      +

      修改文件:/etc/sysconfig/network-scripts/ifcfg-ens33

      +

      示例文件内容:

      +
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      TYPE="Ethernet"
      PROXY_METHOD="none"
      BROWSER_ONLY="no"
      BOOTPROTO="static" # 改为static,固定IP
      DEFROUTE="yes"
      IPV4_FAILURE_FATAL="no"
      IPV6INIT="yes"
      IPV6_AUTOCONF="yes"
      IPV6_DEFROUTE="yes"
      IPV6_FAILURE_FATAL="no"
      IPV6_ADDR_GEN_MODE="stable-privacy"
      NAME="ens33"
      UUID="1b0011cb-0d2e-4eaa-8a11-af7d50ebc876"
      DEVICE="ens33"
      ONBOOT="yes"
      IPADDR="192.168.88.131" # IP地址,自己设置,要匹配网络范围
      NETMASK="255.255.255.0" # 子网掩码,固定写法255.255.255.0
      GATEWAY="192.168.88.2" # 网关,要和VMware中配置的一致
      DNS1="192.168.88.2" # DNS1服务器,和网关一致即可
    4. +
    +

    ps命令

    功能:查看进程信息

    +

    语法:ps -ef,查看全部进程信息,可以搭配grep做过滤:ps -ef | grep xxx

    +

    kill命令

    image-20221027221303037

    +

    nmap命令

    image-20221027221241123

    +

    netstat命令

    功能:查看端口占用

    +

    用法:netstat -anp | grep xxx

    +

    ping命令

    测试网络是否联通

    +

    语法:ping [-c num] 参数

    +

    image-20221027221129782

    +

    wget命令

    image-20221027221148964

    +

    curl命令

    image-20221027221201079

    +

    image-20221027221210518

    +

    top命令

    功能:查看主机运行状态

    +

    语法:top,查看基础信息

    +

    可用选项:

    +

    image-20221027221340729

    +

    交互式模式中,可用快捷键:

    +

    image-20221027221354137

    +

    df命令

    查看磁盘占用

    +

    image-20221027221413787

    +

    iostat命令

    查看CPU、磁盘的相关信息

    +

    image-20221027221439990

    +

    image-20221027221514237

    +

    sar命令

    查看网络统计

    +

    image-20221027221545822

    +

    环境变量

      +
    • 临时设置:export 变量名=变量值
    • +
    • 永久设置:
        +
      • 针对用户,设置用户HOME目录内:.bashrc文件
      • +
      • 针对全局,设置 /etc/profile
      • +
      +
    • +
    +

    PATH变量

    记录了执行程序的搜索路径

    +

    可以将自定义路径加入PATH内,实现自定义命令在任意地方均可执行的效果

    +

    $符号

    可以取出指定的环境变量的值

    +

    语法:$变量名

    +

    示例:

    +

    echo $PATH,输出PATH环境变量的值

    +

    echo ${PATH}ABC,输出PATH环境变量的值以及ABC

    +

    如果变量名和其它内容混淆在一起,可以使用${}

    +

    压缩解压

    压缩

    tar -zcvf 压缩包 被压缩1...被压缩2...被压缩N

    +
      +
    • -z表示使用gzip,可以不写
    • +
    +

    zip [-r] 参数1 参数2 参数N

    +

    image-20221027221906247

    +

    解压

    tar -zxvf 被解压的文件 -C 要解压去的地方

    +
      +
    • -z表示使用gzip,可以省略
    • +
    • -C,可以省略,指定要解压去的地方,不写解压到当前目录
    • +
    +

    unzip [-d] 参数

    +

    image-20221027221939899

    +

    su命令

    切换用户

    +

    语法:su [-] [用户]

    +

    image-20221027222021619

    +

    sudo命令

    image-20221027222035337

    +

    比如:

    +
    1
    itheima ALL=(ALL)       NOPASSWD: ALL
    + +

    在visudo内配置如上内容,可以让itheima用户,无需密码直接使用 sudo

    +

    chmod命令

    修改文件、文件夹权限

    +

    语法:chmod [-R] 权限 参数

    +
      +
    • 权限,要设置的权限,比如755,表示:rwxr-xr-x

      +

      image-20221027222157276

      +
    • +
    • 参数,被修改的文件、文件夹

      +
    • +
    • 选项-R,设置文件夹和其内部全部内容一样生效

      +
    • +
    +

    chown命令

    修改文件、文件夹所属用户、组

    +

    语法:chown [-R] [用户][:][用户组] 文件或文件夹

    +

    image-20221027222326192

    +

    用户组管理

    image-20221027222354498

    +

    用户管理

    image-20221027222407618

    +

    genenv命令

      +
    • getenv group,查看系统全部的用户组

      +

      image-20221027222446514

      +
    • +
    • getenv passwd,查看系统全部的用户

      +

      image-20221027222512274

      +
    • +
    +

    env命令

    查看系统全部的环境变量

    +

    语法:env

    +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    \ No newline at end of file diff --git a/search.xml b/search.xml new file mode 100644 index 000000000..3e7ddead0 --- /dev/null +++ b/search.xml @@ -0,0 +1,8387 @@ + + + + Docker容器化技术 + /posts/19306.html + img

    +

    1.Docker概念

    • Docker 是一个开源的应用容器引擎

    +

    • 诞生于 2013 年初,基于 Go 语言实现, dotCloud 公司出品(后改名为Docker Inc)

    +

    • Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上。

    +

    • 容器是完全使用沙箱机制,相互隔离

    +

    • 容器性能开销极低。

    +

    • Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版)

    +

    2.安装Docker

    # 1、yum 包更新到最新 
    yum update
    # 2、安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
    yum install -y yum-utils device-mapper-persistent-data lvm2
    # 3、 设置yum源
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    # 4、 安装docker,出现输入的界面都按 y
    yum install -y docker-ce
    # 5、 查看docker版本,验证是否验证成功
    docker -v
    + +

    3.配置Docker镜像加速器

    提升下载速度

    +

    第一步:登录阿里云获取镜像加速地址

    每个人的加速器地址不一样

    +

    image-20230421103035776

    +

    第二步:Linux命令行执行如下命令

    直接将阿里云上提供的命令直接粘贴到命令行即可

    +

    image-20230421103610184

    +

    image-20230421103755984

    +

    第三步:查看是否配置成功

    # 查看刚才配置的文件是否存在
    cat /etc/docker/daemon.json
    + +

    出现下图所示的内容表示配置成功

    +

    image-20230421104103726

    +

    4.Docker相关命令

    4.1 Docker进程相关的命令

    # 启动docker服务:
    systemctl start docker

    # 停止docker服务:
    systemctl stop docker

    #重启docker服务:
    systemctl restart docker

    # 查看docker服务状态:
    systemctl status docker

    # 设置开机启动docker服务:
    systemctl enable docker

    + + + +

    4.2 Docker镜像相关的命令

    查看镜像对应版本号的网站: https://hub.docker.com/

    +

    image-20230421110034731

    +
    # 查看镜像: 查看本地所有的镜像
    docker images
    docker images –q # 查看所用镜像的id


    # 搜索镜像:从网络中查找需要的镜像
    docker search 镜像名称


    # 拉取镜像:从Docker仓库下载镜像到本地,镜像名称格式为 名称:版本号,如果版本号不指定则是最新的版本。
    # 如果不知道镜像版本,可以去docker hub 搜索对应镜像查看。
    docker pull 镜像名称 # 默认下载的是latest版本的镜像
    docker pull 镜像名称:版本号 #下载指定版本的镜像


    # 删除镜像: 删除本地镜像
    docker rmi 镜像id # 删除指定本地镜像
    docker rmi 镜像名称:版本号 #删除指定版本的镜像
    docker rmi `docker images -q` # 删除所有本地镜像
    + + + +

    4.3 容器相关的命令

    # 查看容器
    docker ps # 查看正在运行的容器
    docker ps –a # 查看所有容器

    # 创建并启动容器
    docker run 参数

    #删除所有的容器
    docker rm $(docker ps -a -q)
    + +

    参数说明:
    • -i:保持容器运行。通常与 -t 同时使用。加入it这两个参数后,容器创建后自动进入容器中,退出容器后,容器自动关闭。
    • -t:为容器重新分配一个伪输入终端,通常与 -i 同时使用。
    • -d:以守护(后台)模式运行容器。创建一个容器在后台运行,需要使用docker exec 进入容器。退出后,容器不会关闭。
    • -it 创建的容器一般称为交互式容器,-id 创建的容器一般称为守护式容器
    • –name:为创建的容器命名。

    +
    # 进入容器
    docker exec 参数 # 退出容器,容器不会关闭
    # 停止容器
    docker stop 容器名称
    # 启动容器
    docker start 容器名称
    # 删除容器:如果容器是运行状态则删除失败,需要停止容器才能删除
    docker rm 容器名称
    # 查看容器信息
    docker inspect 容器名称
    #查看容器的日志信息
    docker logs 容器名称
    + + + +

    5.Docker 应用部署

    Docker中常见的应用安装部署,详细内容请参考下面文章

    +

    Linux中开发环境的搭建 | The Blog (gitee.io)

    +
    + +

    开发环境的搭建 | The Blog (gitee.io)

    +

    5.1、部署MySQL

    +
      +
    1. 搜索mysql镜像
    2. +
    +
    docker search mysql
    + +
      +
    1. 拉取mysql镜像
    2. +
    +
    docker pull mysql:5.6
    + +
      +
    1. 创建容器,设置端口映射、目录映射
    2. +
    +
    # 在/root目录下创建mysql目录用于存储mysql数据信息
    mkdir ~/mysql
    cd ~/mysql
    + +
    docker run -id \
    -p 3307:3306 \
    --name=c_mysql \
    -v $PWD/conf:/etc/mysql/conf.d \
    -v $PWD/logs:/logs \
    -v $PWD/data:/var/lib/mysql \
    -e MYSQL_ROOT_PASSWORD=123456 \
    mysql:5.6
    + +
      +
    • 参数说明:
        +
      • -p 3307:3306:将容器的 3306 端口映射到宿主机的 3307 端口。
      • +
      • -v $PWD/conf:/etc/mysql/conf.d:将主机当前目录下的 conf/my.cnf 挂载到容器的 /etc/mysql/my.cnf。配置目录
      • +
      • -v $PWD/logs:/logs:将主机当前目录下的 logs 目录挂载到容器的 /logs。日志目录
      • +
      • -v $PWD/data:/var/lib/mysql :将主机当前目录下的data目录挂载到容器的 /var/lib/mysql 。数据目录
      • +
      • -e MYSQL_ROOT_PASSWORD=123456:初始化 root 用户的密码。
      • +
      +
    • +
    +
      +
    1. 进入容器,操作mysql
    2. +
    +
    docker exec –it c_mysql /bin/bash
    + +
      +
    1. 使用外部机器连接容器中的mysql
    2. +
    +

    1573636765632

    +

    5.2、部署Tomcat

    +
      +
    1. 搜索tomcat镜像
    2. +
    +
    docker search tomcat
    + +
      +
    1. 拉取tomcat镜像
    2. +
    +
    docker pull tomcat
    + +
      +
    1. 创建容器,设置端口映射、目录映射
    2. +
    +
    # 在/root目录下创建tomcat目录用于存储tomcat数据信息
    mkdir ~/tomcat
    cd ~/tomcat
    + +
    docker run -id --name=c_tomcat \
    -p 8080:8080 \
    -v $PWD:/usr/local/tomcat/webapps \
    tomcat
    + +
      +
    • 参数说明:

      +
        +
      • -p 8080:8080:将容器的8080端口映射到主机的8080端口

        +

        -v $PWD:/usr/local/tomcat/webapps:将主机中当前目录挂载到容器的webapps

        +
      • +
      +
    • +
    +
      +
    1. 使用外部机器访问tomcat
    2. +
    +

    1573649804623

    +

    5.3 部署Nginx

    +
      +
    1. 搜索nginx镜像
    2. +
    +
    docker search nginx
    + +
      +
    1. 拉取nginx镜像
    2. +
    +
    docker pull nginx
    + +
      +
    1. 创建容器,设置端口映射、目录映射
    2. +
    +
    # 在/root目录下创建nginx目录用于存储nginx数据信息
    mkdir ~/nginx
    cd ~/nginx
    mkdir conf
    cd conf
    # 在~/nginx/conf/下创建nginx.conf文件,粘贴下面内容
    vim nginx.conf
    + +
    user  nginx;
    worker_processes 1;

    error_log /var/log/nginx/error.log warn;
    pid /var/run/nginx.pid;


    events {
    worker_connections 1024;
    }


    http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    #tcp_nopush on;

    keepalive_timeout 65;

    #gzip on;

    include /etc/nginx/conf.d/*.conf;
    }


    + + + + +
    docker run -id --name=c_nginx \
    -p 80:80 \
    -v $PWD/conf/nginx.conf:/etc/nginx/nginx.conf \
    -v $PWD/logs:/var/log/nginx \
    -v $PWD/html:/usr/share/nginx/html \
    nginx
    + +
      +
    • 参数说明:
        +
      • -p 80:80:将容器的 80端口映射到宿主机的 80 端口。
      • +
      • -v $PWD/conf/nginx.conf:/etc/nginx/nginx.conf:将主机当前目录下的 /conf/nginx.conf 挂载到容器的 :/etc/nginx/nginx.conf。配置目录
      • +
      • -v $PWD/logs:/var/log/nginx:将主机当前目录下的 logs 目录挂载到容器的/var/log/nginx。日志目录
      • +
      +
    • +
    +
      +
    1. 使用外部机器访问nginx
    2. +
    +

    1573652396669

    +

    5.4 部署Redis

    +
      +
    1. 搜索redis镜像
    2. +
    +
    docker search redis
    + +
      +
    1. 拉取redis镜像
    2. +
    +
    docker pull redis:5.0
    + +
      +
    1. 创建容器,设置端口映射
    2. +
    +
    docker run -id --name=c_redis -p 6379:6379 redis:5.0
    + +
      +
    1. 使用外部机器连接redis
    2. +
    +
    ./redis-cli.exe -h 192.168.149.135 -p 6379
    + + + +]]>
    + + 运维 + + + Docker + +
    + + IDEA常用快捷键 + /posts/63724.html + 1.常用快捷键
      +
    1. 在写一个main主函数的时候可以直接在键盘上敲main ,然后根据提示补全全部(模板快捷键)

      +
    2. +
    3. 在写System.out.println();输出函数代码的时候可以直接在键盘上面敲sout,然后根据提示补全(模板快捷键)

      +
    4. +
    5. 在写for循环的时候,我们可以直接在键盘上面打出 fori 然后根据提示补全代码(模板快捷键)

      +
    6. +
    7. 删除当前行 Ctrl+D (并非默认Ctrl + D,默认为Ctrl + Y),需要我们自己设置

      +
    8. +
    9. 复制当前行,快速向下复制一行 Ctrl + Alt + 向下箭头(并非默认,需要我们自己设置)

      +
    10. +
    11. 代码补全 Alt + /

      +
    12. +
    13. 添加注释和取消注释 Ctrl + /

      +
    14. +
    15. 自动导入import java.util.Scanner;我们在键盘上敲Scanner in = new Scanner(System,in);的时候按快捷键Alt + Enter 可以自动导入import java.util.Scanner;

      +
    16. +
    17. 代码格式化(格式调整为正常格式,,让代码变整洁,并非删除代码) Ctrl + Alt +L

      +
    18. +
    19. 快速运行程序(并非默认,需要我们自己设置)Alt + R,如果要使用这个快捷键,我们要切换到当前的主类,否则运行的还是之前运行的程序

      +
    20. +
    21. 构造器快捷键 Alt + insert ,按住Ctrl可以选择任意个参数的构造器,也可以点击Select None选择无参构造器; 我们在写一个私有类的getXxx()和setXxx()方法的时候,我们可以使用这个快捷键,光标移动到”Getter and Setter“的位置并选择。

      +
    22. +
    23. 查看一个类的层级关系 Ctrl + H

      +
    24. +
    25. 将光标放在一个方法上,使用快捷键 Ctrl + B 可以快速定位到这个方法的代码

      +
    26. +
    27. 我们在键盘上面敲Scanner in = new Scanner(System.in);的时候,我们先敲new Scanner(System.in),再在后面加上new Scanner(System.in).var,按Enter,就会自动补全前面的代码;定义一个Person类对象的时候我们也可以使用这种方法,如new Person().var,按Enter,就会自动补全前面的代码,变成 Person person = new Person();

      +
    28. +
    29. 重写某个方法的时候我们可以直接打出这个方法名,然后根据提示,移动光标选择。

      +
    30. +
    31. Alt+insert移动光标选择toString,可以重写toString方法,并输出对象的属性。

      +
    32. +
    33. 断点调试的快捷键:F7(跳入),F8(跳过),shift+F8(跳出),F9(resume,执行到下一个断点)

      +

      F7:跳入方法内

      +

      F8:逐行执行代码

      +

      shift+F8:跳出方法

      +
    34. +
    35. 一个普通类实现接口,就必须将该接口的所有方法都实现,我们在实现这些接口的时候可以使用快捷键Alt+Enter

      +
    36. +
    37. Ctrl+Alt+t 可以快捷的使用try-catch

      +
    38. +
    39. 遍历一个数组,生成while循环,可以快捷生成,快捷键是 itit + 回车 ,增强的for循环的迭代器的快捷输入 是大写的 I + enter

      +
    40. +
    41. Ctrl + J 可以显示所有的快捷模板

      +
    42. +
    43. Ctrl + R 查找替换

      +
    44. +
    45. Shift+Enter 快速向下换一行

      +
    46. +
    47. Shift+Home 快速选中一行

      +
    48. +
    49. Ctrl + F 全局查找

      +
    50. +
    +]]>
    + + 后端 + + + 快捷键 + +
    + + Java爬虫 + /posts/60685.html + 视频教程1: 狂神说Java Jsoup爬虫入门实战 https://www.bilibili.com/video/BV1La4y1x7Wm?vd_source=aee5e475191b69e6c781059ab6662584

    +

    视频教程2:https://www.bilibili.com/video/BV1RU4y147eZ?vd_source=aee5e475191b69e6c781059ab6662584

    +

    具体的看视频 急速入门

    +

    入门实战教程

    1.引入依赖

    <!-- 爬取视频或者音频需要使用别的依赖 -->
    <dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.14.3</version>
    </dependency>
    + +

    2.编写测试代码

    下面我们以爬取京东上的商品的图片和价格为例

    +
    package com.jason;


    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;

    import java.io.IOException;
    import java.net.URL;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/9
    * @Description
    */
    public class HtmlParseUtil {
    public static void main(String[] args) throws IOException {
    HtmlParseUtil.parseJd("联想电脑");
    }

    /**
    * 爬取京东上的商品数据
    *
    * @param keywords 商品的品牌或者关键字 例如 联想电脑、华为手机、java书籍
    */
    public static void parseJd(String keywords) throws IOException {
    //获取请求
    String url = "https://search.jd.com/Search?keyword=" + keywords;//页面上的地址信息
    //解析网页
    //第一个参数是url地址
    //第二个参数是响应时间,超过30秒就报错
    //返回的document就是浏览器的Document对象
    Document document = Jsoup.parse(new URL(url), 30000);
    //所有在js中可以使用的方法,这里都可以使用
    Element element = document.getElementById("J_goodsList");//通过id获取元素
    //获取所有的li元素
    Elements elements = element.getElementsByTag("li");
    //遍历li元素的集合 获取每一个li元素
    for (Element el : elements) {
    //这种图片特别多的图片 会有一个懒加载
    String attr = el.getElementsByTag("img").eq(0).attr("data-lazy-img");//通过标签获取元素
    String title = el.getElementsByClass("p-name").eq(0).text();//通过class获取元素
    String price = el.getElementsByClass("p-price").eq(0).text();
    System.out.println("图片地址:https:" + attr);
    System.out.println("商品名称:" + title);
    System.out.println("商品价格:" + price);
    }
    }
    }
    + +

    3.运行测试

    image-20230509161845558

    +

    image-20230509161907139

    +

    示例

    爬取房天下上面的房价信息

    package com.jason;

    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;

    import java.io.IOException;
    import java.net.URL;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/9
    * @Description
    */
    public class ParseHouse {
    public static void main(String[] args) throws IOException {
    //爬起房天下上面的房源数据
    int i = 91;
    while (true){//换页读取 页面有分页条的情况
    String url = "https://newhouse.fang.com/house/s/b"+i;
    System.out.println("正在爬取第"+i+"页的数据.....");
    ParseHouse.selectHouses(url);
    i++;
    }

    }

    public static void selectHouses(String url) throws IOException {
    Document document = Jsoup.parse(new URL(url), 30000);
    Element element = document.getElementById("newhouse_loupan_list");
    Elements elements = element.getElementsByTag("li");
    for (Element el : elements) {
    String images = el.getElementsByTag("img").eq(1).attr("src");
    String alt = el.getElementsByTag("img").eq(1).attr("alt");
    String price = el.getElementsByClass("nhouse_price").text();
    System.out.println("图片:https:"+images);
    System.out.println("名称:"+alt);
    System.out.println("房价:"+price);
    }
    }
    }
    + +

    image-20230509180334969

    +

    image-20230509180435467

    +

    爬取豆瓣电影排行榜的信息

    image-20230509180547905

    +

    爬取全国各地房价的信息到excel表格中

    package com.jason;

    import com.alibaba.excel.EasyExcel;
    import com.jason.entity.HorseInfo;
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.select.Elements;

    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/9
    * @Description 爬取全国各地房价的信息到excel表格中
    */
    public class HousePriseList {
    public static void main(String[] args) throws IOException {
    //爬取全国房价排行榜
    String fileName = "C:\\AlYun\\work\\housePriceList.xlsx";
    //通过下面的中间表,我们可以实现向excel中追加数据 不是每次删除之前的数据再写入 或者追加的时候有多个表头信息
    String temp = "C:\\AlYun\\work\\temp.xlsx"; //中间表 会自动被删除
    int i = 1;
    while (true) {
    try {
    String url = "https://fangjia.gotohui.com/top/" + i + ".html";
    List<HorseInfo> horseInfos = HousePriseList.selectHorse(url);
    File file = new File(fileName);
    File tempFile = new File(temp);
    if (file.exists()) { //判断excel表中是否写入过数据 写入过数据就追加数据
    // 第二次按照原有格式,不需要表头,追加写入
    EasyExcel.write(file, HorseInfo.class).needHead(false).
    withTemplate(file).file(tempFile).sheet().doWrite(horseInfos);
    } else {
    // 第一次写入需要表头
    EasyExcel.write(file, HorseInfo.class).sheet().doWrite(horseInfos);
    }
    if (tempFile.exists()) {
    file.delete();
    tempFile.renameTo(file);
    }
    i++;
    Thread.sleep(2000);
    } catch (Exception e) {
    e.printStackTrace();
    break;
    }
    }
    }

    public static List<HorseInfo> selectHorse(String url) throws IOException {
    Document document = Jsoup.parse(new URL(url), 30000);
    Elements elements = document.getElementsByClass("ntable table-striped table-hover");
    ArrayList<HorseInfo> horseInfos = new ArrayList<>();
    for (Element element : elements) {
    Elements tr = element.getElementsByTag("tr");
    for (Element el : tr) {
    Elements td = el.getElementsByTag("td");
    System.out.println("-----------------------------------------");
    System.out.println("编号:" + td.eq(0).text());
    System.out.println("城市:" + td.eq(1).text());
    System.out.println("单价(元/㎡):" + td.eq(2).text());
    System.out.println("同比(去年):" + td.eq(3).text());
    System.out.println("环比(上月):" + td.eq(4).text());
    System.out.println("收入比:" + td.eq(5).text());
    if (!td.eq(0).text().equals("")) {
    horseInfos.add(new HorseInfo(
    td.eq(0).text(),
    td.eq(1).text(),
    td.eq(2).text(),
    td.eq(3).text(),
    td.eq(4).text(),
    td.eq(5).text()
    ));
    }

    }
    }
    return horseInfos;
    }
    }
    + +

    image-20230509233922789

    +

    image-20230509234019249

    +]]>
    + + 后端 + + + 爬虫 + +
    + + Git命令速查 + /posts/18459.html + 常用命令

    +

    image-20230520173824247

    +
    +

    安装教程

    +

    官网下载:https://git-scm.com/downloads

    +
    #以下的操作在下载安装完毕之后进行
    #1.鼠标在桌面右键 选择Git Bash Here 打开控制台

    #2.配置用户名和邮箱
    git config --global user.name "用户名" #随意
    git config --global user.email "邮箱" #自己的邮箱

    #3.配置SSH免密连接
    #生成密钥
    ssh-keygen -t rsa -C "在码云上注册的邮箱地址" #连续三次回车
    #查看密钥并复制公钥的内容
    cat ~/.ssh/id_rsa.pub

    #4.将密钥的复制到码云的SSH公钥中
    #4.1添加公钥 公钥名随意 公钥内容就是上面复制的内容

    #5.测试
    ssh -T git@gitee.com
    + +]]>
    + + 后端 + + + Git + +
    + + JWT-token生成工具 + /posts/28118.html + 1.介绍

    ​ JWT(Json Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源。比如用在用户登录上。JWT最重要的作用就是对 token信息的防伪作用。

    +

    ​ JWT的原理:

    +

    ​ 一个JWT由三个部分组成:公共部分、私有部分、签名部分。最后由这三者组合进行base64编码得到JWT。

    +

    image-20230420102523288

    +

    image-20230420103234232

    +

    1、 公共部分

    +

    ​ 主要是该JWT的相关配置参数,比如签名的加密算法、格式类型、过期时间等等。

    +

    ​ Key=ATGUIGU

    +

    2、 私有部分

    +

    ​ 用户自定义的内容,根据实际需要真正要封装的信息。

    +

    ​ userInfo{用户的Id,用户的昵称nickName}

    +

    3、 签名部分

    +

    ​ SaltiP: 当前服务器的Ip地址!{linux 中配置代理服务器的ip}

    +

    ​ 主要用户对JWT生成字符串的时候,进行加密{盐值}

    +

    最终组成 key+salt+userInfo -> token!

    +

    base64编码,并不是加密,只是把明文信息变成了不可见的字符串。但是其实只要用一些工具就可以把base64编码解成明文,所以不要在JWT中放入涉及私密的信息。

    +

    2.使用

    1.导入依赖

    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.7.0</version>
    </dependency>
    + +

    2.引入工具类

    以下工具类根据情况任选一种即可

    +

    一种是在请求头中获取token,然后再获取用户的信息,另一种直接传入token,然后获取用户信息

    +
    package com.atguigu.yygh.common.helper;

    import io.jsonwebtoken.*;
    import org.springframework.util.StringUtils;

    import java.util.Date;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/20
    * @Description
    */
    public class JwtHelper {
    private static long tokenExpiration = 24*60*60*1000; //token的过期时间
    private static String tokenSignKey = "123456"; //token 签名的密钥

    /**
    * 根据参数生成token
    * @param userId 用户的id
    * @param userName 用户名
    * @return token字符串
    */
    public static String createToken(Long userId, String userName) {
    String token = Jwts.builder()
    .setSubject("YYGH-USER") //分类,自定义
    .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))//设置过期时间
    .claim("userId", userId)//设置主体信息
    .claim("userName", userName)
    .signWith(SignatureAlgorithm.HS512, tokenSignKey)//签名hash
    .compressWith(CompressionCodecs.GZIP)
    .compact();
    return token;
    }

    /**
    * 根据token字符串得到用户的id
    */
    public static Long getUserId(String token) {
    if(StringUtils.isEmpty(token)) return null;
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
    Claims claims = claimsJws.getBody();
    Integer userId = (Integer)claims.get("userId");
    return userId.longValue();
    }

    /**
    * 根据token字符串得到用户名
    */
    public static String getUserName(String token) {
    if(StringUtils.isEmpty(token)) return "";
    Jws<Claims> claimsJws
    = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);
    Claims claims = claimsJws.getBody();
    return (String)claims.get("userName");
    }
    }
    + +
    package com.atguigu.commonutils;

    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jws;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.util.StringUtils;

    import javax.servlet.http.HttpServletRequest;
    import java.util.Date;

    /**
    * @author helen
    * @since 2019/10/16
    */
    public class JwtUtils {

    /**
    * 设置token的过期时间
    */
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    /**
    * 密钥
    */
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";


    /**
    * 生成token字符串
    */
    public static String getJwtToken(String id, String nickname){

    String JwtToken = Jwts.builder()
    .setHeaderParam("typ", "JWT")
    .setHeaderParam("alg", "HS256")
    .setSubject("guli-user")
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
    .claim("id", id)
    .claim("nickname", nickname)
    .signWith(SignatureAlgorithm.HS256, APP_SECRET)
    .compact();

    return JwtToken;
    }

    /**
    * 判断token是否存在与有效
    */
    public static boolean checkToken(String jwtToken) {
    if(StringUtils.isEmpty(jwtToken)) return false;
    try {
    Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    } catch (Exception e) {
    e.printStackTrace();
    return false;
    }
    return true;
    }

    /**
    * 判断token是否存在与有效
    */
    public static boolean checkToken(HttpServletRequest request) {
    try {
    String jwtToken = request.getHeader("token");
    if(StringUtils.isEmpty(jwtToken)) return false;
    Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    } catch (Exception e) {
    e.printStackTrace();
    return false;
    }
    return true;
    }

    /**
    * 根据token获取会员id
    */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
    String jwtToken = request.getHeader("token");
    if(StringUtils.isEmpty(jwtToken)) return "";
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    Claims claims = claimsJws.getBody();
    return (String)claims.get("id");
    }
    }
    + +

    3.在登录中使用Jwt工具

    1.登录成功之后返回token字符串

    +

    image-20230420110340570

    +

    2.前端处理的思路

    +

    获取token字符串 -> 将token字符串和用户信息存放在cokkie中 -> 页面在cokkie中获取用户信息显示在页面上

    +

    前端登录与否的校验、后端网关登录与否的校验…….

    +

    之后补充完整的代码示例 这里没有遇到好的登录示例 ……….

    +]]>
    + + 后端 + + + Java + +
    + + Java学习路线 + /posts/63587.html + + +
    +
    + + + + + +
    +
    +
    +]]> + + 后端 + + + 学习路线 + + + + Linux中开发环境的搭建 + /posts/20683.html + 一.Linux操作系统的安装与配置

    软件:VMware 、Linux镜像文件
    VMWare虚拟机安装Linux教程 | The Blog (gitee.io)

    +

    二.配置Java环境

    2.1.删除自带的JDK

    +
    #查看系统是否有java环境
    java -version
    # 查找Linux中已有的java环境的相关文件
    rpm -qa |grep java
    # 执行命令 删除上面的java文件
    rpm -e --nodeps +上面查找出来的文件
    + +

    2.2.安装JDK

    +
    #创建一个文件夹,将JDK8上传到这个文件夹中去
    mkdir /opt/jdk
    # 解压上传的压缩包 解压后得到一个名为jdk1.8.0_261的文件夹
    tar -zvxf jdk-8u261-linux-x64.tar.gz
    # 创建一个目录,作为java的安装目录
    mkdir /usr/local/java
    #将解压后的文件移动到这个安装目录中去
    mv jdk1.8.0_261/ /usr/local/java/
    #配置环境变量
    #进入环境变量的配置文件中
    vim /etc/profile
    #在文件的最后添加如下的配置
    export JAVA_HOME=/usr/local/java/jdk1.8.0_261
    export PATH=$JAVA_HOME/bin:$PATH
    #刷新配置文件,让新的环境变量生效
    source /etc/profile
    + + + +

    三.使用Docker安装常用软件

    Docker容器化技术 | The Blog (gitee.io)

    +

    3.1 Docker的安装

    3.1.1 安装Docker

    +
    # 1、yum 包更新到最新 
    yum update
    # 2、安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
    yum install -y yum-utils device-mapper-persistent-data lvm2
    # 3、 设置yum源
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    # 4、 安装docker,出现输入的界面都按 y
    yum install -y docker-ce
    # 5、 查看docker版本,验证是否验证成功
    docker -v
    + +

    3.1.2 配置镜像加速服务

    +
    # 1.创建相关的目录
    sudo mkdir -p /etc/docker
    # 2,加入配置
    sudo tee /etc/docker/daemon.json <<-'EOF'
    {
    "registry-mirrors": ["https://cagfgua5.mirror.aliyuncs.com"]
    }
    EOF
    # 3.重新加载,让配置文件生效
    sudo systemctl daemon-reload
    # 4.重新启动Docker服务
    sudo systemctl restart docker
    + +

    3.1.3 启动Docker服务

    +
    # 启动docker服务:
    systemctl start docker

    # 停止docker服务:
    systemctl stop docker

    #重启docker服务:
    systemctl restart docker

    # 查看docker服务状态:
    systemctl status docker

    # 设置开机启动docker服务:
    systemctl enable docker
    + +

    3.2 使用Docker安装Mysql

    #下载mysql的镜像文件 这里以下载mysql5.7为例
    docker pull mysql:5.7

    #查看是否下载成功
    docker images

    #使用MySQL的镜像启动一个容器
    docker run -p 3306:3306 --name mysql \
    -v /mydata/mysql/log:/var/log/mysql \
    -v /mydata/mysql/data:/var/lib/mysql \
    -v /mydata/mysql/conf:/etc/mysql \
    -e MYSQL_ROOT_PASSWORD=root \
    -d mysql:5.7
    #参数说明
    # -p 3306:3306:将容器的 3306 端口映射到主机的 3306 端口
    #-v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机
    #-v /mydata/mysql/log:/var/log/mysql:将日志文件夹挂载到主机
    #-v /mydata/mysql/data:/var/lib/mysql/:将配置文件夹挂载到主机
    #-e MYSQL_ROOT_PASSWORD=root:初始化 root 用户的密码

    #查看是否运行成功 这时使用Navicat就可以连接上了 连接不上 关闭防火墙
    docker ps #查看正在运行的容器

    #进入容器的内部
    docker exec -it mysql /bin/bash

    #查看容器内部的目录结构 我们可以看到容器的内部是一个小小的linux系统
    ls / #注意中间有一个空格

    #查看与mysql相关的目录
    whereis mysql

    #退出容器
    exit;

    #在容器外部查看mysql挂载在本地的三个文件
    #容器内部文件的变化会同步在这三个文件中 conf data log
    #外部修改 也会同步到内部
    cd /mydata/mysql
    ls

    #创建一个mysql的配置文件设置字符编码 将下面的内容粘贴到配置文件中
    #1.创建配置文件
    vi /mydata/mysql/conf/my.cnf
    #2.将以下的内容粘贴到配置文件配置文件内容
    [client]
    default-character-set=utf8
    [mysql]
    default-character-set=utf8
    [mysqld]
    init_connect='SET collation_connection = utf8_unicode_ci'
    init_connect='SET NAMES utf8'
    character-set-server=utf8
    collation-server=utf8_unicode_ci
    skip-character-set-client-handshake
    skip-name-resolve
    #3.重启MySQl的服务 让配置文件生效 这个配置文件也会同步到容器内部
    docker restart mysql

    #设置开机自启动
    docker update mysql --restart=always
    + +

    3.3 使用Docker安装Redis

    #下载redis的镜像文件 直接下载最新的
    docker pull redis

    #创建redis挂载在宿主机的配置文件和目录
    mkdir -p /mydata/redis/conf #创建目录
    touch /mydata/redis/conf/redis.conf #创建配置文件

    #创建并启动容器 这是以配置文件的方式启动 \前面有一个空格
    docker run -p 6379:6379 --name redis \
    -v /mydata/redis/data:/data \
    -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
    -d redis redis-server /etc/redis/redis.conf

    #检查容器的运行情况
    docker ps

    #测试客户端的使用
    docker exec -it redis redis-cli

    #可以在redis客户端中使用如下命令
    127.0.0.1:6379> set name tom #存值
    OK
    127.0.0.1:6379> get name #取值
    "tom"

    #redis中的数据不能持久化 关机重启之后数据会丢失
    #通过以下的配置 实现redis的持久化操作 AOF的持久化
    vi /mydata/redis/conf/redis.conf #修改之前创建的配置文件
    #添加如下配置
    appendonly yes

    #重启redis 让配置生效
    docker restart redis

    #设置开机自启动
    docker update redis --restart=always
    + +

    3.4 使用Docker安装ElasticSearch

    3.4.1 安装ElasticSearch

    +
    # 拉取ElasticSearch
    docker pull elasticsearch:7.4.2

    #ES的配置文件存放的位置
    mkdir -p /mydata/elasticsearch/config

    #ES相关的数据
    mkdir -p /mydata/elasticsearch/data

    #"http.host: 0.0.0.0"(可以被任何的机器访问)的配置写入elasticsearch.yml中(注意冒号后面的空格)
    echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

    #设置文件的权限
    # 递归更改权限,es需要访问
    chmod -R 777 /mydata/elasticsearch/

    #运行容器的命令 9200端口用于发送请求使用 9300端口用于集群中节点中的通信使用
    #单节点运行
    #初始占用64m,最大占用5m(不指定,ES会占用所有的内存)
    docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
    -e "discovery.type=single-node" \
    -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
    -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
    -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
    -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
    -d elasticsearch:7.4.2

    # 设置开机启动elasticsearch
    docker update elasticsearch --restart=always

    #访问端口 测试是否安装成功
    http://虚拟机的ip:9200/
    + +

    安装成功之后:

    +

    image-20230628191936254

    +

    3.4.2 安装Kibana(可视化界面)

    +
    #拉取Kibana
    docker pull kibana:7.4.2
    # 这里-e是自己的elasticsearch服务地址(这里的地址一定要改为自己虚拟机的地址)
    docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.195.100:9200 -p 5601:5601 -d kibana:7.4.2
    # 设置开机启动kibana
    docker update kibana --restart=always
    #访问对应的网址查看服务
    http://虚拟机的ip:5601
    + +

    安装成功之后

    +

    image-20230701104616032

    +

    3.4.3 安装IK分词器

    +

    安装地址: https://github.com/medcl/elasticsearch-analysis-ik

    +
    #切换到es挂载在注解的plugins目录
    cd /mydata/elasticsearch/plugins/
    #安装wget
    yum install wget
    #下载ik分词器的压缩包(下载的速度不是很快(5分钟左右),建议本地下载好了之后,上传上去)
    wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
    #进入容器内部查看一下文件是否同步到了容器的内部
    docker exec -it 容器Id /bin/bash
    #切换到plugins目录中去,这时目录中也存在elasticsearch-analysis-ik-7.4.2.zip这个压缩包
    cd plugins/ #使用ls查看一下
    #退出容器
    exit
    #在主机下解压elasticsearch-analysis-ik-7.4.2.zip这个压缩包
    #zip的解压需要使用unzip解压命令 这里我们可以直接在本地解压一下 上传到plugins目录(解压的文件名为ik)
    #安装unzip
    yum install -y unzip
    #查看ik分词器是否安装成功
    docker exec -it 容器Id /bin/bash
    #切换到bin目录
    cd bin/
    #执行下面的命令,有ik显示即为安装成功
    elasticsearch-plugin list
    #退出容器,重启一下
    docker restart elasticsearch
    + +

    3.5 使用Docker安装nginx

    #创建nginx容器挂载的目录
    mkdir /mydata/nginx/conf
    #拉取nginx的镜像
    docker pull nginx:1.10
    #随便启动一个nginx实例,为了复制其配置
    docker run -p 80:80 --name nginx -d nginx:1.10
    #将容器内的配置文件拷贝到conf目录(注意这里有个点和空格)
    #注意在nginx的目录下操作,(成功之后/mydata/nginx/conf目录下会有相应的配置文件和目录)
    docker container cp nginx:/etc/nginx /mydata/nginx/conf
    #停掉并删除之前的启动的nginx
    docker stop nginx
    docker rm nginx
    #正式的启动容器
    docker run -p 80:80 --name nginx \
    -v /mydata/nginx/html:/usr/share/nginx/html \
    -v /mydata/nginx/logs:/var/log/nginx \
    -v /mydata/nginx/conf:/etc/nginx \
    -d nginx:1.10
    # 设置开机启动nginx
    docker update nginx --restart=always
    + +

    3.6 使用Docker安装RabbitMQ

    #直接执行运行rabbitMQ容器的命令,没有镜像会自动下载
    docker run -d --name rabbitmq \
    -p 5671:5671 \
    -p 5672:5672 \
    -p 4369:4369 \
    -p 25672:25672 \
    -p 15671:15671 \
    -p 15672:15672 \
    rabbitmq:management
    # 设置开机启动rabbitmq
    docker update rabbitmq --restart=always
    #测试访问
    虚拟机的ip:15672
    + +

    账号:guest

    +

    密码:guest

    +

    image-20230717123217404

    +

    3.7 使用docker安装nacos

    #拉取镜像
    docker pull nacos/nacos-server:1.2.0
    #创建容器并运行
    #MODE=standalone 单机版
    docker run --env MODE=standalone --name nacos --restart=always -d -p 8848:8848 nacos/nacos-server:1.2.0
    + +

    测试访问:http://虚拟机ip:8848/nacos

    +

    image-20230811105925843

    +]]>
    + + 运维 + + + 环境搭建 + Linux + +
    + + Java程序设计基础 + /posts/48020.html + 韩顺平零基础学java完整版笔记 + +
    + +
    + + + +]]>
    + + 后端 + + + Java + +
    + + Linux从入门到进阶 + /posts/8957.html +

    本篇博客转载于传智播客黑马程序员,只作在线笔记使用,详细的课程资料请关注黑马程序员!

    + + +

    Linux相关的书籍: 技术书籍-Linux指令大全 | The Blog (gitee.io)

    +

    一.初识Linux

    ​ Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。它能运行主要的UNIX工具软件、应用程序和网络协议。支持32位和64位硬件。Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    +

    image-20230919214601245

    +

    视频链接: https://www.bilibili.com/video/BV1n84y1i7td/?share_source=copy_web&vd_source=aee5e475191b69e6c781059ab6662584

    +

    二.Linux的安装和配置教程

    1.通过虚拟机安装Linux

    Linux安装教程: VMWare虚拟机安装Linux教程 | The Blog (gitee.io)

    +

    Linux设置静态IP:Linux设置静态IP | The Blog (gitee.io)

    +

    2.通过WSL获取Linux环境

    安装教程: Windows10的Linux子系统WSL的安装和使用 | The Blog (gitee.io)

    +

    三.Linux的目录结构

    image-20230919230906304

    +

    image-20230919231127383

    +

    四.LInux基础命令

    ls命令

    功能:列出文件夹信息

    +

    语法:ls [-l -h -a] [参数]

    +
      +
    • 参数:被查看的文件夹,不提供参数,表示查看当前工作目录
    • +
    • -l,以列表形式查看
    • +
    • -h,配合-l,以更加人性化的方式显示文件大小
    • +
    • -a,显示隐藏文件
    • +
    +

    隐藏文件、文件夹

    在Linux中以 .开头的,均是隐藏的。

    +

    默认不显示出来,需要 -a选项才可查看到。

    +

    pwd命令

    功能:展示当前工作目录

    +

    语法:pwd

    +

    cd命令

    功能:切换工作目录

    +

    语法:cd [目标目录]

    +

    参数:目标目录,要切换去的地方,不提供默认切换到 当前登录用户HOME目录

    +

    HOME目录

    每一个用户在Linux系统中都有自己的专属工作目录,称之为HOME目录。

    +
      +
    • 普通用户的HOME目录,默认在:/home/用户名
    • +
    • root用户的HOME目录,在:/root
    • +
    +

    FinalShell登陆终端后,默认的工作目录就是用户的HOME目录

    +

    相对路径、绝对路径

      +
    • 相对路径,==非==/开头的称之为相对路径

      +

      相对路径表示以 当前目录作为起点,去描述路径,如 test/a.txt,表示当前工作目录内的test文件夹内的a.txt文件

      +
    • +
    • 绝对路径,==以==/开头的称之为绝对路径

      +

      绝对路径从 开始描述路径

      +
    • +
    +

    特殊路径符

      +
    • .,表示当前,比如./a.txt,表示当前文件夹内的 a.txt文件
    • +
    • ..,表示上级目录,比如 ../表示上级目录,../../表示上级的上级目录
    • +
    • ~,表示用户的HOME目录,比如 cd ~,即可切回用户HOME目录
    • +
    +

    mkdir命令

    功能:创建文件夹

    +

    语法:mkdir [-p] 参数

    +
      +
    • 参数:被创建文件夹的路径
    • +
    • 选项:-p,可选,表示创建前置路径
    • +
    +

    touch命令

    功能:创建文件

    +

    语法:touch 参数

    +
      +
    • 参数:被创建的文件路径
    • +
    +

    cat命令

    功能:查看文件内容

    +

    语法:cat 参数

    +
      +
    • 参数:被查看的文件路径
    • +
    +

    more命令

    功能:查看文件,可以支持翻页查看

    +

    语法:more 参数

    +
      +
    • 参数:被查看的文件路径
    • +
    • 在查看过程中:
        +
      • 空格键翻页
      • +
      • q退出查看
      • +
      +
    • +
    +

    cp命令

    功能:复制文件、文件夹

    +

    语法:cp [-r] 参数1 参数2

    +
      +
    • 参数1,被复制的
    • +
    • 参数2,要复制去的地方
    • +
    • 选项:-r,可选,复制文件夹使用
    • +
    +

    示例:

    +
      +
    • cp a.txt b.txt,复制当前目录下a.txt为b.txt
    • +
    • cp a.txt test/,复制当前目录a.txt到test文件夹内
    • +
    • cp -r test test2,复制文件夹test到当前文件夹内为test2存在
    • +
    +

    mv命令

    功能:移动文件、文件夹

    +

    语法:mv 参数1 参数2

    +
      +
    • 参数1:被移动的
    • +
    • 参数2:要移动去的地方,参数2如果不存在,则会进行改名
    • +
    +

    rm命令

    功能:删除文件、文件夹

    +

    语法:rm [-r -f] 参数...参数

    +
      +
    • 参数:支持多个,每一个表示被删除的,空格进行分隔
    • +
    • 选项:-r,删除文件夹使用
    • +
    • 选项:-f,强制删除,不会给出确认提示,一般root用户会用到
    • +
    +
    +

    rm命令很危险,一定要注意,特别是切换到root用户的时候。

    +
    +

    image-20230920231224351

    +

    which命令

    功能:查看命令的程序本体文件路径

    +

    语法:which 参数

    +
      +
    • 参数:被查看的命令
    • +
    +

    find命令

    功能:搜索文件

    +

    语法1按文件名搜索:find 路径 -name 参数

    +
      +
    • 路径,搜索的起始路径
    • +
    • 参数,搜索的关键字,支持通配符*, 比如:*test表示搜索任意以test结尾的文件
    • +
    +

    grep命令

    功能:过滤关键字

    +

    语法:grep [-n] 关键字 文件路径

    +
      +
    • 选项-n,可选,表示在结果中显示匹配的行的行号。
    • +
    • 参数,关键字,必填,表示过滤的关键字,带有空格或其它特殊符号,建议使用””将关键字包围起来
    • +
    • 参数,文件路径,必填,表示要过滤内容的文件路径,可作为内容输入端口
    • +
    +
    +

    参数文件路径,可以作为管道符的输入

    +
    +

    wc命令

    功能:统计

    +

    语法:wc [-c -m -l -w] 文件路径

    +
      +
    • 选项,-c,统计bytes数量
    • +
    • 选项,-m,统计字符数量
    • +
    • 选项,-l,统计行数
    • +
    • 选项,-w,统计单词数量
    • +
    • 参数,文件路径,被统计的文件,可作为内容输入端口
    • +
    +
    +

    参数文件路径,可作为管道符的输入

    +
    +

    管道符|

    写法:|

    +

    功能:将符号左边的结果,作为符号右边的输入

    +

    示例:

    +

    cat a.txt | grep itheima,将cat a.txt的结果,作为grep命令的输入,用来过滤 itheima关键字

    +

    可以支持嵌套:

    +

    cat a.txt | grep itheima | grep itcast

    +

    echo命令

    功能:输出内容

    +

    语法:echo 参数

    +
      +
    • 参数:被输出的内容
    • +
    +

    `反引号

    功能:被两个反引号包围的内容,会作为命令执行

    +

    示例:

    +
      +
    • echo `pwd`,会输出当前工作目录
    • +
    +

    tail命令

    功能:查看文件尾部内容

    +

    语法:tail [-f] 参数

    +
      +
    • 参数:被查看的文件
    • +
    • 选项:-f,持续跟踪文件修改
    • +
    +

    head命令

    功能:查看文件头部内容

    +

    语法:head [-n] 参数

    +
      +
    • 参数:被查看的文件
    • +
    • 选项:-n,查看的行数
    • +
    +

    重定向符

    功能:将符号左边的结果,输出到右边指定的文件中去

    +
      +
    • >,表示覆盖输出
    • +
    • >>,表示追加输出
    • +
    +

    vi编辑器

    命令模式快捷键

    image-20221027215841573

    +

    image-20221027215846581

    +

    image-20221027215849668

    +

    底线命令快捷键

    image-20221027215858967

    +

    命令的选项

    我们学习的一系列Linux命令,它们所拥有的选项都是非常多的。

    +

    比如,简单的ls命令就有:-a -A -b -c -C -d -D -f -F -g -G -h -H -i -I -k -l -L -m -n -N -o -p -q -Q -r-R -s -S -t -T -u -U -v -w -x -X -1等选项,可以发现选项是极其多的。

    +

    课程中, 并不会将全部的选项都进行讲解,否则,一个ls命令就可能讲解2小时之久。

    +

    课程中,会对常见的选项进行讲解, 足够满足绝大多数的学习、工作场景。

    +

    查看命令的帮助

    可以通过:命令 --help查看命令的帮助手册

    +

    image-20221027220005610

    +

    查看命令的详细手册

    可以通过:man 命令查看某命令的详细手册

    +

    image-20221027220009949

    +

    五.Linux常用操作

    软件安装

      +
    • CentOS系统使用:
        +
      • yum [install remove search] [-y] 软件名称
          +
        • install 安装
        • +
        • remove 卸载
        • +
        • search 搜索
        • +
        • -y,自动确认
        • +
        +
      • +
      +
    • +
    • Ubuntu系统使用
        +
      • apt [install remove search] [-y] 软件名称
          +
        • install 安装
        • +
        • remove 卸载
        • +
        • search 搜索
        • +
        • -y,自动确认
        • +
        +
      • +
      +
    • +
    +
    +

    yum 和 apt 均需要root权限

    +
    +

    systemctl

    功能:控制系统服务的启动关闭等

    +

    语法:systemctl start | stop | restart | disable | enable | status 服务名

    +
      +
    • start,启动
    • +
    • stop,停止
    • +
    • status,查看状态
    • +
    • disable,关闭开机自启
    • +
    • enable,开启开机自启
    • +
    • restart,重启
    • +
    +

    软链接

    功能:创建文件、文件夹软链接(快捷方式)

    +

    语法:ln -s 参数1 参数2

    +
      +
    • 参数1:被链接的
    • +
    • 参数2:要链接去的地方(快捷方式的名称和存放位置)
    • +
    +

    日期

    语法:date [-d] [+格式化字符串]

    +
      +
    • -d 按照给定的字符串显示日期,一般用于日期计算

      +
    • +
    • 格式化字符串:通过特定的字符串标记,来控制显示的日期格式

      +
        +
      • %Y 年%y 年份后两位数字 (00..99)
      • +
      • %m 月份 (01..12)
      • +
      • %d 日 (01..31)
      • +
      • %H 小时 (00..23)
      • +
      • %M 分钟 (00..59)
      • +
      • %S 秒 (00..60)
      • +
      • %s 自 1970-01-01 00:00:00 UTC 到现在的秒数
      • +
      +
    • +
    +

    示例:

    +
      +
    • 按照2022-01-01的格式显示日期

      +

      image-20221027220514640

      +
    • +
    • 按照2022-01-01 10:00:00的格式显示日期

      +

      image-20221027220525625

      +
    • +
    • -d选项日期计算

      +

      image-20221027220429831

      +
        +
      • 支持的时间标记为:

        +

        image-20221027220449312

        +
      • +
      +
    • +
    +

    时区

    修改时区为中国时区

    +

    image-20221027220554654

    +

    ntp

    功能:同步时间

    +

    安装:yum install -y ntp

    +

    启动管理:systemctl start | stop | restart | status | disable | enable ntpd

    +

    手动校准时间:ntpdate -u ntp.aliyun.com

    +

    ip地址

    格式:a.b.c.d

    +
      +
    • abcd为0~255的数字
    • +
    +

    特殊IP:

    +
      +
    • 127.0.0.1,表示本机
    • +
    • 0.0.0.0
        +
      • 可以表示本机
      • +
      • 也可以表示任意IP(看使用场景)
      • +
      +
    • +
    +

    查看ip:ifconfig

    +

    主机名

    功能:Linux系统的名称

    +

    查看:hostname

    +

    设置:hostnamectl set-hostname 主机名

    +

    配置VMware固定IP

      +
    1. 修改VMware网络,参阅PPT,图太多

      +
    2. +
    3. 设置Linux内部固定IP

      +

      修改文件:/etc/sysconfig/network-scripts/ifcfg-ens33

      +

      示例文件内容:

      +
      TYPE="Ethernet"
      PROXY_METHOD="none"
      BROWSER_ONLY="no"
      BOOTPROTO="static" # 改为static,固定IP
      DEFROUTE="yes"
      IPV4_FAILURE_FATAL="no"
      IPV6INIT="yes"
      IPV6_AUTOCONF="yes"
      IPV6_DEFROUTE="yes"
      IPV6_FAILURE_FATAL="no"
      IPV6_ADDR_GEN_MODE="stable-privacy"
      NAME="ens33"
      UUID="1b0011cb-0d2e-4eaa-8a11-af7d50ebc876"
      DEVICE="ens33"
      ONBOOT="yes"
      IPADDR="192.168.88.131" # IP地址,自己设置,要匹配网络范围
      NETMASK="255.255.255.0" # 子网掩码,固定写法255.255.255.0
      GATEWAY="192.168.88.2" # 网关,要和VMware中配置的一致
      DNS1="192.168.88.2" # DNS1服务器,和网关一致即可
    4. +
    +

    ps命令

    功能:查看进程信息

    +

    语法:ps -ef,查看全部进程信息,可以搭配grep做过滤:ps -ef | grep xxx

    +

    kill命令

    image-20221027221303037

    +

    nmap命令

    image-20221027221241123

    +

    netstat命令

    功能:查看端口占用

    +

    用法:netstat -anp | grep xxx

    +

    ping命令

    测试网络是否联通

    +

    语法:ping [-c num] 参数

    +

    image-20221027221129782

    +

    wget命令

    image-20221027221148964

    +

    curl命令

    image-20221027221201079

    +

    image-20221027221210518

    +

    top命令

    功能:查看主机运行状态

    +

    语法:top,查看基础信息

    +

    可用选项:

    +

    image-20221027221340729

    +

    交互式模式中,可用快捷键:

    +

    image-20221027221354137

    +

    df命令

    查看磁盘占用

    +

    image-20221027221413787

    +

    iostat命令

    查看CPU、磁盘的相关信息

    +

    image-20221027221439990

    +

    image-20221027221514237

    +

    sar命令

    查看网络统计

    +

    image-20221027221545822

    +

    环境变量

      +
    • 临时设置:export 变量名=变量值
    • +
    • 永久设置:
        +
      • 针对用户,设置用户HOME目录内:.bashrc文件
      • +
      • 针对全局,设置 /etc/profile
      • +
      +
    • +
    +

    PATH变量

    记录了执行程序的搜索路径

    +

    可以将自定义路径加入PATH内,实现自定义命令在任意地方均可执行的效果

    +

    $符号

    可以取出指定的环境变量的值

    +

    语法:$变量名

    +

    示例:

    +

    echo $PATH,输出PATH环境变量的值

    +

    echo ${PATH}ABC,输出PATH环境变量的值以及ABC

    +

    如果变量名和其它内容混淆在一起,可以使用${}

    +

    压缩解压

    压缩

    tar -zcvf 压缩包 被压缩1...被压缩2...被压缩N

    +
      +
    • -z表示使用gzip,可以不写
    • +
    +

    zip [-r] 参数1 参数2 参数N

    +

    image-20221027221906247

    +

    解压

    tar -zxvf 被解压的文件 -C 要解压去的地方

    +
      +
    • -z表示使用gzip,可以省略
    • +
    • -C,可以省略,指定要解压去的地方,不写解压到当前目录
    • +
    +

    unzip [-d] 参数

    +

    image-20221027221939899

    +

    su命令

    切换用户

    +

    语法:su [-] [用户]

    +

    image-20221027222021619

    +

    sudo命令

    image-20221027222035337

    +

    比如:

    +
    itheima ALL=(ALL)       NOPASSWD: ALL
    + +

    在visudo内配置如上内容,可以让itheima用户,无需密码直接使用 sudo

    +

    chmod命令

    修改文件、文件夹权限

    +

    语法:chmod [-R] 权限 参数

    +
      +
    • 权限,要设置的权限,比如755,表示:rwxr-xr-x

      +

      image-20221027222157276

      +
    • +
    • 参数,被修改的文件、文件夹

      +
    • +
    • 选项-R,设置文件夹和其内部全部内容一样生效

      +
    • +
    +

    chown命令

    修改文件、文件夹所属用户、组

    +

    语法:chown [-R] [用户][:][用户组] 文件或文件夹

    +

    image-20221027222326192

    +

    用户组管理

    image-20221027222354498

    +

    用户管理

    image-20221027222407618

    +

    genenv命令

      +
    • getenv group,查看系统全部的用户组

      +

      image-20221027222446514

      +
    • +
    • getenv passwd,查看系统全部的用户

      +

      image-20221027222512274

      +
    • +
    +

    env命令

    查看系统全部的环境变量

    +

    语法:env

    +]]>
    + + 运维 + + + Linux + +
    + + Linux设置静态IP + /posts/21883.html + 1.查看IP的状态
    #查看是否为静态ip
    ip addr
    + +

    如图所示的是动态IP,我们要配置静态IP

    +

    image-20230511230406172

    +

    2.设置网络

    2.1 进入配置文件对网络进行配置

    1.修改配置文件中的配置

    +
    #进入配置文件的命令
    vim /etc/sysconfig/network-scripts/ifcfg-ens33
    + +

    image-20230511233324998

    +

    2.重启网络

    +
    #重启网络的配置
    systemctl restart network
    #查看设置的IP情况
    ifconfig
    + +

    image-20230511233838595

    +

    在主机上使用ping命令ping 刚才的设置的静态IP也可以ping通

    +

    image-20230511234154523

    +

    虚拟机ping外部也可以ping通

    +

    image-20230511235621228

    +]]>
    + + 后端 + + + Linux + +
    + + MySQL5.7安装教程 + /posts/17259.html + 1.下载的地址

    下载之后直接解压使用(下载之前看电脑上有没有mysql的服务,如果有先删除),解压的文件路径最好不要有中文

    +

    查看的方式

    +
    Win + R   输入    services.msc  回车打开   找是否有一个名为mysqld的服务
    + +

    删除的指令

    +
    sc delete mysql
    + +

    Mysql5.7地址:https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.19-winx64.zip

    +

    2.配置环境变量

    添加环境变量 : 电脑-属性-高级系统设置-环境变量,在Path 环境变量增加mysql的安装目录\bin目录

    +

    3.在mysql的安装目录下创建my.ini文件

    image-20230312152323079

    +

    my.ini

    +
    [client]
    port=3306
    default-character-set=utf8
    [mysqld]
    # 设置为自己MYSQL的安装目录
    basedir=C:\mysql\mysql-5.7.19-winx64\
    # 设置为MYSQL的数据目录,这个目录系统帮我们创建
    datadir=C:\mysql\mysql-5.7.19-winx64\data\
    port=3306
    character_set_server=utf8
    #跳过安全检查,注释后需要输入正确的用户名和密码才能登录
    #第一登录不要注释掉,第一次进入没有设置密码,就可以不用输入密码了
    #skip-grant-tables

    + +

    4.下载服务和初始化数据库

    1.下载mysql的服务

    +

    ​ 使用管理员身份打开 cmd , 并切换到mysql-5.7.19-winx64\bin 目录下, 执行 mysqld -install

    +
    cd  **/**/**/mysql-5.7.19-winx64\bin
    mysqld -install
    + +

    2.初始化数据

    +
    mysqld --initialize-insecure --user=mysql
    + +

    5.启动和停止mysql的服务

    #开启mysql的服务
    net start mysql
    #停止mysql的服务
    net stop mysql
    + +

    6.修改账户和密码

    #9. 进入mysql 管理终端: 初次登录没有密码,直接按两下回车键进入
    mysql -u root -p
    进入之后执行:
    use mysql;
    update user set authentication_string=password('123456') where user='root' and Host='localhost';
    flush privileges; #刷新权限
    quit #退出
    + +

    注释掉my.ini中的跳过权限检查 #skip-grant-tables

    +

    7.重启mysql的服务

    net stop mysql
    net start mysql
    mysql -u root -p #输入刚才设置的密码进入mysql服务
    + +

    安装失败,删除服务,重新安装

    +

    8.可视化工具

    推荐SQLyog Navicat

    +

    网盘地址

    +

    PDF版本的教程(来源于韩顺平教育,转载注明出处)

    + +
    + +
    + + + +]]>
    + + 后端 + + + Mysql + +
    + + Map集合的遍历 + /posts/24637.html + 1.方法一

    lambda表达式遍历

    +
    Map<String, String> map = new HashMap<String, String>();
    map.forEach((key,value)->{
    System.out.println(key);
    System.out.println(value);
    });
    + + + +

    1.方法二

    在 for 循环中使用 entries 实现 Map 的遍历(最常见和最常用的)

    +
    Map<String, String> map = new HashMap<String, String>();
    for (Map.Entry<String, String> entry : map.entrySet()) {
    String mapKey = entry.getKey();
    String mapValue = entry.getValue();
    System.out.println(mapKey + ":" + mapValue);
    }
    + + + +

    2.方法三

    使用 for-each 循环遍历 key 或者 values,一般适用于只需要 Map 中的 key 或者 value 时使用,性能上比 entrySet 好

    +
    Map<String, String> map = new HashMap<String, String>();
    // 打印键集合
    for (String key : map.keySet()) {
    System.out.println(key);
    }
    // 打印值集合
    for (String value : map.values()) {
    System.out.println(value);
    }
    + + + +

    3.方法四

    使用迭代器(Iterator)遍历

    +
    Map<String, String> map = new HashMap<String, String>();
    Iterator<Entry<String, String>> entries = map.entrySet().iterator();
    while (entries.hasNext()) {
    Entry<String, String> entry = entries.next();
    String key = entry.getKey();
    String value = entry.getValue();
    System.out.println(key + ":" + value);
    }
    + + + +

    4.方法五

    通过键找值遍历,效率比较低,从键取值是耗时的操作

    +
    for(String key : map.keySet()){
    String value = map.get(key);
    System.out.println(key+":"+value);
    }
    + + + +

    …….

    ]]>
    + + 后端 + + + Java + +
    + + MySql进阶教程 + /posts/39654.html + 全部的PDF笔记: https://jasonsgong.gitee.io/posts/50465.html

    +

    原视频地址: 黑马程序员 MySQL数据库入门到精通,从mysql安装到mysql高级、mysql优化全囊括

    +

    一.基础回顾

    1.DDL、DML、DQL、DCL

    数据定义语言(DDL)

    +

    DDL全称是Data Definition Language,即数据定义语言,定义语言就是定义关系模式、删除关系、修改关系模式以及创建数据库中的各种对象,比如表、聚簇、索引、视图、函数、存储过程和触发器等等。数据定义语言是由SQL语言集中负责数据结构定义与数据库对象定义的语言,并且由CREATE、ALTER、DROP和TRUNCATE四个语法组成。

    +

    数据操纵语言(DML)

    +

    数据操纵语言全程是Data Manipulation Language,主要是进行插入元组、删除元组、修改元组的操作。主要有insert、update、delete语法组成。

    +

    数据查询语言(DQL)

    +

    数据查询语言全称是Data Query Language,所以是用来进行数据库中数据的查询的,即最常用的select语句。

    +

    数据控制语言(DCL)

    +

    数据控制语言:Data Control Language。用来授权或回收访问数据库的某种特权,并控制数据库操纵事务发生的时间及效果,能够对数据库进行监视。比如常见的授权、取消授权、回滚、提交等等操作。

    +

    二.MySql进阶

    1.存储引擎

    1.1 MySql的体系结构

    image-20230916152706301

    +

    image-20230916152828704

    +

    1.2 存储引擎简介

    简介

    +

    ​ 存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。存储引擎是基于表的,而不是 基于库的,所以存储引擎也可被称为表类型。我们可以在创建表的时候,来指定选择的存储引擎,如果 没有指定将自动选择默认的存储引擎。

    +

    查询一张数据库表的建表语句

    +
    -- 查询建表语句
    show create table book_info;
    + +

    image-20230916153448893

    +

    建表的时候指定存储引擎的语句

    +

    image-20230916153931010

    +

    1.3 存储引擎特点

    image-20230916154915533

    +

    image-20230916155113863

    +

    image-20230916155356755

    +

    image-20230916155525548

    +

    存储引擎的区别

    +

    image-20230916155637133

    +

    1.4 存储引擎的选择

    ​ 在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据 实际情况选择多种存储引擎进行组合。

    +
      +
    • InnoDB: 是Mysql的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要 求,在并发条件下要求数据的一致性,数据操作除了插入和查询之外,还包含很多的更新、删除操 作,那么InnoDB存储引擎是比较合适的选择。
    • +
    • MyISAM : 如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完 整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。(例如评论的数据、日志相关的数据、电商中足迹相关的数据)
    • +
    • MEMORY:将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是 对表的大小有限制,太大的表无法缓存在内存中,而且无法保障数据的安全性。
    • +
    +

    一句话:项目中绝大多数的时候使用的都是InnoDB

    +

    2.索引

    2.1 索引概述

    2.1.1 介绍

    ​ 索引(index)是帮助MySQL高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据, 这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。

    +

    2.1.2 演示

    注:下面的二叉树并不是一个真实的索引结构,而是一个举例

    +

    image-20230916150211012

    +

    2.1.3 优缺点

    image-20230916150430708

    +

    2.2 索引结构

    2.2.1 索引的数据结构有哪些

    默认的索引结构是B+树索引结构

    +

    image-20230916161524219

    +

    不同类型的存储引擎对不同的索引结构的支持情况

    +

    image-20230916161623546

    +

    2.2.2 B+树的索引数据结构

    二叉树和红黑树的不足

    +

    image-20230916162305576

    +

    B树

    +

    image-20230916163234103

    +

    B+树

    +

    image-20230916164042303

    +

    image-20230916164314700

    +

    MySql中的B+树结构

    +

    image-20230916164500444

    +

    2.2.3 hash的索引数据结构

    image-20230916164903702

    +

    image-20230916165007465

    +

    2.3.4 思考题

    image-20230916165329434

    2.3 索引分类

    image-20230916165516779

    +

    image-20230916165647255

    +

    image-20230916170012664

    +

    当我们要查询name值是Arm的数据的时候,会先通过二级索引查询,然后通过聚集索引查询这一行的信息(回表查询)

    +

    image-20230916170421435

    +

    思考题

    +

    image-20230916170537693

    +

    第一条的执行效率高,第二条要回表查询(先查询name所在行对应的主键id,然后通过主键id查询这一行的数据)

    +

    2.4 索引语法

    创建|查看|删除语法

    +

    image-20230917091746406

    +

    举例

    +

    image-20230917093020287

    +

    2.5 SQL性能分析

    2.5.1 SQL执行频率

    image-20230917093634100

    +
    -- 查看系统的状态信息
    show global status like 'Com_______';
    + +

    2.5.2 慢查询日志

    image-20230917094314803

    +
    -- 查询慢查询日志是否开启
    show variables like 'slow_query_log';
    + +

    2.5.3 profile详情

    image-20230917095232011

    +
    -- 是否支持profile详情功能
    select @@have_profiling;

    -- 查看是否开启该功能
    select @@profiling;

    -- 开启profile
    set profiling = 1;
    + +

    image-20230917095320953

    +

    2.5.4 explain执行计划

    image-20230917095911304

    +

    image-20230917101205097

    +

    image-20230917101337147

    +

    2.6 索引使用

    2.6.1 验证索引效率

    image-20230917141334774

    +

    现在有一个有着10000000数据的表(tb_sku),我们根据sn这一字段查询某一条具体的数据,没有建立索引之前的查询耗费的时间是20多秒,建立索引耗费大约90秒,为sn字段建立索引之后,查询耗费时间大概是零点几秒。

    +

    2.6.2 最左前缀法则

    image-20230917142410276

    +

    image-20230917142707646

    +

    2.6.3 索引失效的情况

    image-20230917142948227

    +

    image-20230917143143047

    +

    image-20230917143421975

    +

    image-20230917143842497

    +

    image-20230917144459567

    +

    2.6.4 SQL提示

    image-20230917145046280

    +

    2.6.5 覆盖索引

    image-20230917145643194

    +

    2.6.6 前缀索引

    image-20230917153757773

    +

    image-20230917154300147

    +

    2.6.7 单例索引与联合索引的选择问题

    image-20230917155017641

    +

    image-20230917155223679

    +

    2.7 索引设计原则

    image-20230917155748055

    +

    3.SQL优化

    3.1 插入数据的优化

    image-20230917181618942

    +

    image-20230917181842030

    +

    100万的数据如果使用insert插入的话需要10分钟左右,如果使用文件的方式导入,只需要10多秒。

    +

    3.2 主键优化

    image-20230917205500523

    +

    image-20230917205636137

    +

    image-20230917205833596

    +

    image-20230917210112389

    +

    image-20230917210303469

    +

    3.3 order by优化

    image-20230917210730392

    +

    image-20230917211254167

    +

    image-20230917210616858

    +

    image-20230917211543109

    +

    3.4 group by优化

    image-20230917212026101

    +

    3.5 limit优化

    image-20230917212530539

    +

    3.6 count优化

    image-20230917212709489

    +

    image-20230917213029734

    +

    image-20230917213316193

    +

    3.7 update优化

    image-20230917213859581

    +

    3.8 总结

    image-20230917214424722

    +]]>
    + + 后端 + + + Mysql + +
    + + Mybatis-Plus的使用教程 + /posts/53306.html + 官方文档:MyBatis-Plus

    +

    常用插件

    1.公共字段填充

    对于每个实体类共有的属性字段,例如创建时间、修改时间、创建人、修改人,我们可以使用公共字段填充,来统一填充这些字段,这样我们在创建这些实体类的对象的时候就不需要set这些属性,实现丝滑开发

    +
    @TableField(fill = FieldFill.INSERT) //插入的时候填充
    private String gmtCreate;
    @TableField(fill = FieldFill.INSERT_UPDATE)//插入和修改的时候填充
    private String gmtModified;
    + +

    第一种配置的方法

    +
    package com.itheima.config;

    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;

    import java.time.LocalDateTime;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/25
    * @Description 公共字段填充的配置
    */
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
    * 插入时的字段填充
    * @param metaObject 元对象
    */
    @Override
    public void insertFill(MetaObject metaObject) {
    //创建时间的字段填充
    metaObject.setValue("gmtCreate", LocalDateTime.now());
    //修改时间的字段填充
    metaObject.setValue("gmtModified", LocalDateTime.now());
    }

    /**
    * 修改时的字段填充
    * @param metaObject 元对象
    */
    @Override
    public void updateFill(MetaObject metaObject) {
    //修改时间的字段填充
    metaObject.setValue("gmtModified", LocalDateTime.now());
    }
    }
    + +

    第二种配置的方法

    +
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
    // 起始版本 3.3.0(推荐使用)
    this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    // 或者
    this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
    // 或者
    // 也可以使用(3.3.0 该方法有bug)
    this.fillStrategy(metaObject, "createTime", LocalDateTime.now());
    }

    @Override
    public void updateFill(MetaObject metaObject) {
    // 起始版本 3.3.0(推荐)
    this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    // 或者
    this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
    // 或者
    // 也可以使用(3.3.0 该方法有bug)
    this.fillStrategy(metaObject, "updateTime", LocalDateTime.now());
    }
    }
    + +

    2.逻辑删除

    1.配置全局的配置文件(可以省略)

    +
    mybatis-plus:
    global-config:
    db-config:
    logic-delete-value: 1 # 逻辑已删除值(默认为 1)
    logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    + +

    2.配置逻辑删除的组件(3.1.1版本之后不需要这一步,可以省略)

    +
    import com.baomidou.mybatisplus.core.injector.ISqlInjector;
    import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    public class MyBatisPlusConfiguration {

    @Bean
    public ISqlInjector sqlInjector() {
    return new LogicSqlInjector();
    }
    }
    + +

    3.在实体类上逻辑删除的字段上加上逻辑删除的注解

    +
    //value:表示不删除对应的值
    //delval:表示删除对应的值
    //正常的情况下1代表删除,0代表未删除,这里我们没有使用这个规则,下面设置的是自己的规则
    @TableLogic(value = "1",delval = "0")
    + + + +]]>
    + + 后端 + + + SSM + +
    + + MySql基础进阶运维篇PDF笔记 + /posts/50465.html + 1.PDF笔记

    1.1MySql基础篇PDF笔记

    + +
    + +
    + + + + +

    1.2MySql进阶篇PDF笔记

    + +
    + +
    + + + + +

    1.3MySql运维篇PDF笔记

    + +
    + +
    + + + + + + + + +]]>
    + + 后端 + + + Mysql + +
    + + MybatisX插件的使用 + /posts/24606.html + 代码生成器 根据数据库表生成Mapper接口,Mapper配置文件,service

    +

    注解 mybatisPlus提供的注解

    +

    公共字段的自动填充 逻辑删除 乐观锁 雪花算法生成主键

    +

    image-20230321181437910

    +

    image-20230321181452909

    +]]>
    + + 后端 + + + 插件 + +
    + + SSM框架基础知识及整合 + /posts/56742.html + PDF版本的笔记

    +

    Mybatis

    + + +
    + +
    + + + + +

    Spring SpringMVC MyBatis

    + + +
    + +
    + + + +]]>
    + + 后端 + + + SSM + +
    + + SSM常用配置 + /posts/32679.html + 1.pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.atguigu.ssm</groupId>
    <artifactId>ssm</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <!--管理版本信息,将版本信息提取出来,各个依赖的版本通过${spring.version}访问-->
    <properties>
    <spring.version>5.3.1</spring.version>
    </properties>

    <dependencies>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <!--springmvc-->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>${spring.version}</version>
    </dependency>

    <!-- Mybatis核心 -->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
    </dependency>

    <!--mybatis和spring的整合包-->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.6</version>
    </dependency>

    <!-- 连接池 -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.9</version>
    </dependency>

    <!-- junit测试 -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.3</version>
    </dependency>

    <!-- log4j日志 -->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>

    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
    </dependency>

    <!-- 日志 -->
    <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
    </dependency>

    <!-- ServletAPI -->
    <dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
    </dependency>

    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.1</version>
    </dependency>

    <dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
    </dependency>

    <!-- Spring5和Thymeleaf整合包 -->
    <dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.12.RELEASE</version>
    </dependency>

    </dependencies>

    </project>
    + +

    2.spring.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--扫描组件(除控制层)-->
    <context:component-scan base-package="com.atguigu.ssm">
    <!--通过控制层注解的全类名,排除控制层的扫描-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!--引入数据库的配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="username" value="${jdbc.username}"></property>
    <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

    <!--配置SqlSessionFactoryBean,可以直接在IOC容器中获取SqlSessionFactory-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--获取mybatis的核心配置文件-->
    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    <!--设置数据源,取代配置文件中的数据源的环境配置-->
    <property name="dataSource" ref="dataSource"></property>
    <!--设置类型别名所对应的包-->
    <property name="typeAliasesPackage" value="com.atguigu.ssm.pojo"></property>
    <!--设置mapper映射文件的路径,在这里我们不需要设置,只有在mapper接口和映射文件的路径不一致时设置-->
    <!-- <property name="mapperLocations" value="classpath:"></property>-->
    </bean>

    <!--
    配置mapper接口扫描,可以将指定包下的所有mapper接口,
    通过SqlSession创建代理实现类对象,
    并将这些对象交给ioc容器管理
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.atguigu.ssm.mapper"></property>
    </bean>

    </beans>
    + +

    3.springmvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!--组件扫描-->
    <context:component-scan base-package="com.atguigu.ssm.controller"></context:component-scan>

    <!--配置视图解析器-->
    <bean id="viewResolver"
    class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
    <property name="order" value="1"/>
    <property name="characterEncoding" value="UTF-8"/>
    <property name="templateEngine">
    <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
    <property name="templateResolver">
    <bean
    class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    <!-- 视图前缀 -->
    <property name="prefix" value="/WEB-INF/templates/"/>
    <!-- 视图后缀 -->
    <property name="suffix" value=".html"/>
    <property name="templateMode" value="HTML5"/>
    <property name="characterEncoding" value="UTF-8" />
    </bean>
    </property>
    </bean>
    </property>
    </bean>

    <!-- 配置访问首页的视图控制 -->
    <mvc:view-controller path="/" view-name="index"></mvc:view-controller>

    <!-- 配置默认的servlet处理静态资源 -->
    <mvc:default-servlet-handler />

    <!-- 开启MVC的注解驱动 -->
    <mvc:annotation-driven />

    <!--拦截器-->
    <!--
    SpringMVC中的拦截器用于拦截控制器方法的执行
    SpringMVC中的拦截器需要实现HandlerInterceptor
    SpringMVC的拦截器必须在SpringMVC的配置文件中进行配置:
    <bean class="com.atguigu.interceptor.FirstInterceptor"></bean>
    <ref bean="firstInterceptor"></ref>
    以上两种配置方式都是对DispatcherServlet所处理的所有的请求进行拦截
    <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/testRequestEntity"/>
    <ref bean="firstInterceptor"></ref>
    </mvc:interceptor>
    以上配置方式可以通过ref或bean标签设置拦截器,通过mvc:mapping设置需要拦截的请求,通过
    mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求
    -->

    <!--异常处理器-->
    <!--
    <bean
    class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <property name="exceptionMappings">
    <props>
    properties的键表示处理器方法执行过程中出现的异常
    properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
    <prop key="java.lang.ArithmeticException">error</prop>
    </props>
    </property>
    exceptionAttribute属性设置一个属性名,将出现的异常信息在请求域中进行共享
    <property name="exceptionAttribute" value="ex"></property>
    </bean>
    -->

    <!--文件上传解析器,是根据id获取目标对象的,id必须是multipartResolver-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
    </beans>
    + +

    4.mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>


    <!--解决数据库中的字段取名为emp_id,而在java中取名为empId,两者无法映射的问题-->
    <!--下划线映射为驼峰-->
    <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--设置分页插件-->
    <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

    </configuration>
    + +

    5.jdbc.properties

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/ssm
    jdbc.username=root
    jdbc.password=hsp
    + +

    6.log4j.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
    <param name="Encoding" value="UTF-8" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS}
    %m (%F:%L) \n" />
    </layout>
    </appender>
    <logger name="java.sql">
    <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
    <level value="info" />
    </logger>
    <root>
    <level value="debug" />
    <appender-ref ref="STDOUT" />
    </root>
    </log4j:configuration>
    + +

    7.log4j.properties

    log4j.rootLogger=debug, stdout, R

    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout

    # Pattern to output the caller's file name and line number.
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n

    log4j.appender.R=org.apache.log4j.RollingFileAppender
    log4j.appender.R.File=example.log

    log4j.appender.R.MaxFileSize=100KB
    # Keep one backup file
    log4j.appender.R.MaxBackupIndex=5

    log4j.appender.R.layout=org.apache.log4j.PatternLayout
    log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n
    +]]>
    + + 后端 + + + SSM + +
    + + SpringBoot中使用定时任务 + /posts/73.html + 1.在启动类上添加@EnableScheduling注解
    @EnableScheduling
    + +

    2.创建定时任务类,设置cron表达式

    定时任务可以单独建立一个包 package com.atguigu.schedule

    +

    加上@Component注解,交给spring管理,启动这个模块,定时任务就开启了

    +
    package com.atguigu.schedule;

    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/3/29
    * @Description 定时任务
    */
    @Component
    public class ScheduledTask {

    //每隔五秒执行这个方法
    //"0/5 * * * * ?" 是corn表达式
    @Scheduled(cron = "0/5 * * * * ?")
    public void tesSchedule(){
    System.out.println("定时任务执行了....");
    }
    }

    + +

    image-20230329184540977

    +

    3.Corn表达式(七子[七域]表达式)生成工具

    网址: https://cron.qqe2.com/

    +

    image-20230329184618098

    +]]>
    + + 后端 + + + SpringBoot + +
    + + SpringBoot中整合Swagger2 + /posts/32246.html + 1.Swagger2的介绍

    什么是swagger2

    +

    编写和维护接口文档是每个程序员的职责,根据Swagger2可以快速帮助我们编写最新的API接口文档,再也不用担心开会前仍忙于整理各种资料了,间接提升了团队开发的沟通效率。

    +

    常用注解

    +

    swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息等等

    +

    @Api:修饰整个类,描述Controller的作用

    +

    @ApiOperation:描述一个类的一个方法,或者说一个接口

    +

    @ApiParam:单个参数描述

    +

    @ApiModel:用对象来接收参数

    +

    @ApiModelProperty:用对象接收参数时,描述对象的一个字段

    +

    @ApiImplicitParam:一个请求参数

    +

    @ApiImplicitParams:多个请求参数

    +

    2.SpringBoot中使用Swagger

    2.1导入相关的依赖

    注意:Swagger2和SpringBoot存在版本兼容的问题,选择的时候要根据SpringBoot的版本进行选择

    +
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
    </dependency>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
    </dependency>
    + +

    2.2创建配置类

    @Configuration
    @EnableSwagger2
    public class Swagger2 {
    @Bean
    public Docket adminApiConfig(){

    return new Docket(DocumentationType.SWAGGER_2)
    .groupName("adminApi")
    .apiInfo(apiInfo())
    .select()
    //只显示admin路径下的页面
    .paths(Predicates.and(PathSelectors.regex("/admin/.*")))// .paths(PathSelectors.any())表示所有
    .build();

    }

    private ApiInfo apiInfo(){
    return new ApiInfoBuilder()
    .title("后台管理系统-API文档")
    .description("本文档描述了后台管理系统微服务接口定义")
    .version("1.0")
    .contact(new Contact("GongChangjiang", "https://gong-changjiang.gitee.io/", "2602183349@qq.com"))
    .build();
    }

    }
    + +

    2.3配置包扫描的规则

    //在项目的启动类加上该注解,swagger就会扫描如下包下的controller方法,生成接口文档
    @ComponentScan(basePackages = "com.xxxx")
    + +

    2.4使用接口文档

    //浏览器访问该网址
    //这里的端口号是要生成接口文档端口的端口号
    http://localhost:8080/swagger-ui.html
    + +

    image-20230412141010237

    +]]>
    + + 后端 + + + SpringBoot + +
    + + SpringBoot整合Knife4j + /posts/855.html + 1.介绍

    一句话介绍Knife4j: Swagger的增强版,界面更好看,功能更加的丰富

    +

    文档地址:https://doc.xiaominfo.com/

    +

    image-20230505222154471

    +

    image-20230505223651097

    +

    2.使用教程

    2.1 引入依赖

    <!--引入Knife4j的官方start包,该指南选择Spring Boot版本<3.0,开发者需要注意-->
    <dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
    <version>4.0.0</version>
    </dependency>
    + +

    2.2 编写配置类

    package com.atguigu.common.config.knife4j;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.ParameterBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.schema.ModelRef;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.Contact;
    import springfox.documentation.service.Parameter;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/5
    * @Description knife4j配置信息
    */

    @Configuration
    @EnableSwagger2WebMvc
    public class Knife4jConfig {

    @Bean
    public Docket adminApiConfig(){
    List<Parameter> pars = new ArrayList<>();
    ParameterBuilder tokenPar = new ParameterBuilder();
    tokenPar.name("token")
    .description("用户token")
    .defaultValue("")
    .modelRef(new ModelRef("string"))
    .parameterType("header")
    .required(false)
    .build();
    pars.add(tokenPar.build());
    //添加head参数end

    Docket adminApi = new Docket(DocumentationType.SWAGGER_2)
    .groupName("adminApi")
    .apiInfo(adminApiInfo())
    .select()
    //只显示admin路径下的页面
    .apis(RequestHandlerSelectors.basePackage("com.atguigu"))
    .paths(PathSelectors.regex("/admin/.*"))
    .build()
    .globalOperationParameters(pars);
    return adminApi;
    }

    private ApiInfo adminApiInfo(){

    return new ApiInfoBuilder()
    .title("后台管理系统-API文档")
    .description("本文档描述了后台管理系统微服务接口定义")
    .version("1.0")
    .contact(new Contact("JasonGong", "https://gong-changjiang.gitee.io/", "2602183349@qq.com"))
    .build();
    }
    }
    + +

    2.3 使用

    package com.atguigu.auth.controller;

    import com.atguigu.auth.service.SysRoleService;
    import com.atguigu.common.result.Result;
    import com.atguigu.model.system.SysRole;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/5
    * @Description 角色的控制器方法
    */
    @Api(tags = "角色管理的接口")
    @RestController
    @RequestMapping("/admin/system/sysRole")
    @CrossOrigin
    public class SysRoleController {

    @Autowired
    private SysRoleService sysRoleService;


    /**
    * 查询所有的方法
    */
    @ApiOperation("查询所有角色的接口")
    @GetMapping("/findAll")
    public Result<List<SysRole>> findAll(){
    List<SysRole> sysRoles = sysRoleService.list();
    return Result.ok(sysRoles);
    }

    }
    + +

    2.4接口文档的访问地址

    # 端口号根据项目的端口号进行改变
    http://localhost:8080/doc.html
    + +

    image-20230505223433369

    +

    +]]>
    + + 后端 + + + SpringBoot + +
    + + SpringBoot整合MongoDB + /posts/12929.html + 1.介绍

    ​ MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。
    MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

    +

    适用场景
    1、网站数据:Mongo非常适合实时的插入,更新与查询,并具备网站实时数据存储所需的复制及高度伸缩性。
    2、缓存:由于性能很高,Mongo也适合作为信息基础设施的缓存层。在系统重启之后,由M ongo搭建的持久化缓存层可以避免下层的数据源过载。
    3、大尺寸,低价值的数据:使用传统的关系型数据库存储一些数据时可能会比较昂贵, 在此之前,很多时候程序员往往会选择传统的文件进行存储。
    4、高伸缩性的场景:Mongo非常适合由数十或数百台服务器组成的数据库。Mongo的路线图中已经包含对Map Reduce弓摩的内置支持。
    5、用于对象及 JSON数据的存储:Mongo的BSON数据格式非常适合文档化格式的存储 及查询。
    不适用场合
    1、高度事务性的系统:例如银行或会计系统。传统的关系型数据库目前还是更适用于需要大量原子性复杂事务的应用程序。
    2、传统的商业智能应用:针对特定问题的BI数据库会对产生高度优化的查询方式。对于此类应用,数据仓库可能是更合适的选择。

    +

    中文文档:https://docs.mongoing.com/

    +

    2.入门

    ​ spring-data-mongodb提供了MongoTemplate与MongoRepository两种方式访问mongodb,MongoRepository操作简单,MongoTemplate操作灵活,我们在项目中可以灵活适用这两种方式操作mongodb,MongoRepository的缺点是不够灵活,MongoTemplate正好可以弥补不足

    +

    2.1引入依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    + +

    2.2 配置文件中添加MongoDB的配置

    spring.data.mongodb.uri=mongodb://localhost:27017/test
    + +

    2.3 创建对应的实体类

    @Data
    @Document("User") //指定实体类对应的MongoDB集合的名称
    public class User {
    @Id //自动生成的主键ID
    private String id;
    private String name;
    private Integer age;
    private String email;
    private String createDate;
    }
    + +

    2.4 常用的方法

    //常用方法
    mongoTemplate.findAll(User.class); //查询User文档的全部数据
    mongoTemplate.findById(<id>, User.class); //查询User文档id为id的数据
    mongoTemplate.find(query, User.class); //根据query内的查询条件查询
    mongoTemplate.upsert(query, update, User.class);// 修改
    mongoTemplate.remove(query, User.class);// 删除
    mongoTemplate.insert(User);//新增
    + +
    Query对象
    1、创建一个query对象(用来封装所有条件对象),再创建一个criteria对象(用来构建条件)

    2、精准条件:criteria.and(“key”).is(“条件”)
    模糊条件:criteria.and(“key”).regex(“条件”)

    3、封装条件:query.addCriteria(criteria)

    4、大于(创建新的criteria):Criteria gt = Criteria.where(“key”).gt(“条件”)
    小于(创建新的criteria):Criteria lt = Criteria.where(“key”).lt(“条件”)

    5、Query.addCriteria(new Criteria().andOperator(gt,lt));

    6、一个query中只能有一个andOperator() 其参数也可以是Criteria数组

    7、排序 :query.with(new Sort(Sort.Direction.ASC, "age"). and(new Sort(Sort.Direction.DESC, "date")))
    + +
    主要注解:

    @Document,文档是 MongoDB 中最基本的数据单元,由键值对组成,类似于 JSON 格式,可以存储不同字段,字段的值可以包括其他文档,数组和文档数组。

    @Id(主键):用来将成员变量的值映射为文档的_id的值

    @Indexed(索引): 索引是一种特殊的数据结构,存储在一个易于遍历读取的数据集合中,能够对数据库文档中的数据进行排序。索引能极大提高文档查询效率,如果没有设置索引,MongoDB 会遍历集合中的整个文档,选取符合查询条件的文档记录。这种查询效率是非常低的。

    @Field(字段): 文档中的字段,类似于 MySql 中的列。

    @Aggregation(聚合): 聚合主要用于数据处理,例如统计平均值、求和等。
    + +

    示例代码

    +

    2.4.1 使用MongoTemplate的方式访问MongoDB

    @SpringBootTest
    class DemomogoApplicationTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    //添加
    @Test
    public void createUser() {
    User user = new User();
    user.setAge(20);
    user.setName("test");
    user.setEmail("4932200@qq.com");
    User user1 = mongoTemplate.insert(user);
    System.out.println(user1);
    }

    //查询所有
    @Test
    public void findUser() {
    List<User> userList = mongoTemplate.findAll(User.class);
    System.out.println(userList);
    }

    //根据id查询
    @Test
    public void getById() {
    User user =
    mongoTemplate.findById("5ffbfa2ac290f356edf9b5aa", User.class);
    System.out.println(user);
    }

    //条件查询
    @Test
    public void findUserList() {
    Query query = new Query(Criteria
    .where("name").is("test")
    .and("age").is(20));
    List<User> userList = mongoTemplate.find(query, User.class);
    System.out.println(userList);
    }

    //模糊查询
    @Test
    public void findUsersLikeName() {
    String name = "est";
    String regex = String.format("%s%s%s", "^.*", name, ".*$");
    Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
    Query query = new Query(Criteria.where("name").regex(pattern));
    List<User> userList = mongoTemplate.find(query, User.class);
    System.out.println(userList);
    }

    //分页查询
    @Test
    public void findUsersPage() {
    String name = "est";
    int pageNo = 1;
    int pageSize = 10;

    //分页查询的条件
    Query query = new Query();
    String regex = String.format("%s%s%s", "^.*", name, ".*$");
    Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
    query.addCriteria(Criteria.where("name").regex(pattern));
    //总记录数
    int totalCount = (int) mongoTemplate.count(query, User.class);
    //分页查询
    List<User> userList = mongoTemplate.find(query.skip((pageNo - 1) * pageSize).limit(pageSize), User.class);

    //将分页的总记录数和分页数据封装在一个map集合中
    Map<String, Object> pageMap = new HashMap<>();
    pageMap.put("list", userList);
    pageMap.put("totalCount",totalCount);
    System.out.println(pageMap);
    }

    //修改
    @Test
    public void updateUser() {
    User user = mongoTemplate.findById("5ffbfa2ac290f356edf9b5aa", User.class);
    user.setName("test_1");
    user.setAge(25);
    user.setEmail("493220990@qq.com");
    Query query = new Query(Criteria.where("_id").is(user.getId()));
    Update update = new Update();
    update.set("name", user.getName());
    update.set("age", user.getAge());
    update.set("email", user.getEmail());
    UpdateResult result = mongoTemplate.upsert(query, update, User.class);
    //影响的行数
    long count = result.getModifiedCount();
    System.out.println(count);
    }

    //删除操作
    @Test
    public void delete() {
    Query query =
    new Query(Criteria.where("_id").is("5ffbfa2ac290f356edf9b5aa"));
    DeleteResult result = mongoTemplate.remove(query, User.class);
    long count = result.getDeletedCount();
    System.out.println(count);
    }
    }
    + +

    2.4.2 使用MongoRepository的方式访问MongoDB

    a.创建UserRepository类
    package com.atguigu.mongodb.repository;

    import com.atguigu.mongodb.entity.User;
    import org.springframework.data.mongodb.repository.MongoRepository;
    import org.springframework.stereotype.Repository;

    import java.util.List;

    @Repository
    public interface UserRepository extends MongoRepository<User, String> {

    }
    + +
    b.测试使用
    @SpringBootTest
    class DemomogoApplicationTests1 {

    @Autowired
    private UserRepository userRepository;

    //添加
    @Test
    public void createUser() {
    User user = new User();
    user.setAge(20);
    user.setName("张三");
    user.setEmail("3332200@qq.com");
    User user1 = userRepository.save(user);
    }

    //查询所有
    @Test
    public void findUser() {
    List<User> userList = userRepository.findAll();
    System.out.println(userList);
    }

    //id查询
    @Test
    public void getById() {
    User user = userRepository.findById("5ffbfe8197f24a07007bd6ce").get();
    System.out.println(user);
    }

    //条件查询
    @Test
    public void findUserList() {
    //查询条件 名字是张三 年龄是20
    User user = new User();
    user.setName("张三");
    user.setAge(20);
    Example<User> userExample = Example.of(user);
    List<User> userList = userRepository.findAll(userExample);
    System.out.println(userList);
    }

    //模糊查询
    @Test
    public void findUsersLikeName() {
    //创建匹配器,即如何使用查询条件
    ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
    .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
    .withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
    User user = new User();
    user.setName("三");
    Example<User> userExample = Example.of(user, matcher);
    List<User> userList = userRepository.findAll(userExample);
    System.out.println(userList);
    }

    //分页查询
    @Test
    public void findUsersPage() {
    //根据年龄降序
    Sort sort = Sort.by(Sort.Direction.DESC, "age");
    //0为第一页
    Pageable pageable = PageRequest.of(0, 10, sort);
    //创建匹配器,即如何使用查询条件
    ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
    .withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
    .withIgnoreCase(true); //改变默认大小写忽略方式:忽略大小写
    User user = new User();
    user.setName("三");//查询名字中包含 '三' 的数据
    Example<User> userExample = Example.of(user, matcher);
    //创建实例
    Example<User> example = Example.of(user, matcher);
    Page<User> pages = userRepository.findAll(example, pageable);
    System.out.println(pages);
    }

    //修改
    @Test
    public void updateUser() {
    User user = userRepository.findById("5ffbfe8197f24a07007bd6ce").get();
    user.setName("张三_1");
    user.setAge(25);
    user.setEmail("883220990@qq.com");
    User save = userRepository.save(user);//有id值就是修改 m
    System.out.println(save);
    }

    //删除
    @Test
    public void delete() {
    userRepository.deleteById("5ffbfe8197f24a07007bd6ce");
    }
    }
    + + + + + + + +]]>
    + + 后端 + + + SpringBoot + +
    + + SpringBoot整合EasyExcel + /posts/38823.html + 1.EasyExcel介绍

    ​ Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便

    +

    官网地址:https://easyexcel.opensource.alibaba.com/

    +

    2.入门

    2.1 引入依赖

    <!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.1.1</version>
    </dependency>
    + +

    2.2 创建与Excel表格对应的实体类,并在属性上加上注解

    不用自己设置excel表中的表头信息,easyexcel会帮我们设置

    +
    package test;

    import com.alibaba.excel.annotation.ExcelProperty;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description 与Excel表格对应的实体类
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UserData {

    /**
    * 注解中value 设置的是表头的信息
    * index 设置的是这个属性在excel表格中的索引位置 列索引
    */

    @ExcelProperty(value = "用户编号", index = 0)
    private int uid;

    @ExcelProperty(value = "用户名称", index = 1)
    private String username;


    }
    + +

    2.3 读写操作

    1.写操作

    package test;

    import com.alibaba.excel.EasyExcel;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description 测试对excel的操作
    */
    public class TestEasyExcel {
    public static void main(String[] args) {
    //设置excel文件的路径和名称
    String filename = "C:\\AlYun\\user.xlsx";

    //构建一个数据的list集合,存放要保存在excel表中的数据
    List<UserData> userDataList = new ArrayList<>();
    userDataList.add(new UserData(1,"张三"));
    userDataList.add(new UserData(2,"李四"));
    userDataList.add(new UserData(3,"王五"));
    userDataList.add(new UserData(4,"赵六"));

    //调用方法实现写的操作
    EasyExcel.write(filename,UserData.class)
    .sheet("用户信息")
    .doWrite(userDataList);

    }
    }

    + +

    执行之后,空的excel表中会添加如下的数据

    +

    image-20230414131020923

    +

    2.读操作

    1 .创建一个读操作的监听器

    继承AnalysisEventListener T表示实体类

    +
    package test;

    import com.alibaba.excel.context.AnalysisContext;
    import com.alibaba.excel.event.AnalysisEventListener;

    import java.util.Map;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description 读操作的监听器
    */
    public class ExcelLister extends AnalysisEventListener<UserData> {

    /**
    * 一行一行的读取 从第二行开始读取
    */
    @Override
    public void invoke(UserData data, AnalysisContext context) {
    //打印输出读取到的数据
    System.out.println(data);
    }

    /**
    * 读取之后执行
    */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

    }


    /**
    * 非必须实现的方法
    * 此方法可以读取表头的信息
    */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
    //打印读取到的表头信息
    System.out.println("表头信息:"+headMap);
    }
    }
    + +
    5.编写方法测试
    package test;

    import com.alibaba.excel.EasyExcel;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description
    */
    public class TestReadExcel {
    public static void main(String[] args) {
    //设置excel文件的路径和名称
    String filename = "C:\\AlYun\\user.xlsx";
    //调用方法实现读取操作
    //new ExcelLister()自己创建的监听器
    EasyExcel.read(filename,UserData.class,new ExcelLister())
    .sheet()
    .doRead();
    }
    }
    + +

    3.监听器中实现对数据库的操作

    解决listener中无法操作数据库的问题

    +

    3.1 通过构造器注入的方式

    在进行读操作创建Listener的时候,注入mapper

    +
    package com.atguigu.yygh.cmn.listener;

    import com.alibaba.excel.context.AnalysisContext;
    import com.alibaba.excel.event.AnalysisEventListener;
    import com.atguigu.yygh.cmn.mapper.DictMapper;
    import com.atguigu.yygh.model.cmn.Dict;
    import com.atguigu.yygh.vo.cmn.DictEeVo;
    import org.springframework.beans.BeanUtils;


    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description
    */
    public class DictListener extends AnalysisEventListener<DictEeVo> {

    private DictMapper dictMapper;

    public DictListener(DictMapper dictMapper) {
    this.dictMapper = dictMapper;
    }

    @Override
    public void invoke(DictEeVo data, AnalysisContext context) {
    //调用mapper 添加数据
    Dict dict = new Dict();
    BeanUtils.copyProperties(data,dict);
    dictMapper.insert(dict);

    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

    }
    }

    + +

    3.2 使用注解加属性注入的方式

    @Component
    +
    @Autowired
    + + + + + + + +]]>
    + + 后端 + + + SpringBoot + +
    + + SpringBoot整合Logback日志 + /posts/64205.html + 1.创建一个SpringBoot的工程

    2.在resources目录下创建logback-spring.xml的配置文件

    创建的时候要修改日志输出的路径

    +

    日志的级别根据需要自己修改

    +

    级别:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL

    +
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true" scanPeriod="10 seconds">
    <!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
    <!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
    <!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
    <!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->

    <contextName>logback</contextName>
    <!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
    <property name="log.path" value="C:/AlYun/log" />

    <!-- 彩色日志 -->
    <!-- 配置格式变量:CONSOLE_LOG_PATTERN 彩色日志格式 -->
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <property name="CONSOLE_LOG_PATTERN"
    value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>


    <!--输出到控制台-->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
    <!-- 例如:如果此处配置了INFO级别,则后面其他位置即使配置了DEBUG级别的日志,也不会被输出 -->
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
    <level>INFO</level>
    </filter>
    <encoder>
    <Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
    <!-- 设置字符集 -->
    <charset>UTF-8</charset>
    </encoder>
    </appender>


    <!--输出到文件-->

    <!-- 时间滚动输出 level为 INFO 日志 -->
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 正在记录的日志文件的路径及文件名 -->
    <file>${log.path}/log_info.log</file>
    <!--日志文件输出格式-->
    <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    <charset>UTF-8</charset>
    </encoder>
    <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <!-- 每天日志归档路径以及格式 -->
    <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <!--日志文件保留天数-->
    <maxHistory>15</maxHistory>
    </rollingPolicy>
    <!-- 此日志文件只记录info级别的 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>INFO</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>
    </appender>

    <!-- 时间滚动输出 level为 WARN 日志 -->
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 正在记录的日志文件的路径及文件名 -->
    <file>${log.path}/log_warn.log</file>
    <!--日志文件输出格式-->
    <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    <charset>UTF-8</charset> <!-- 此处设置字符集 -->
    </encoder>
    <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <!--日志文件保留天数-->
    <maxHistory>15</maxHistory>
    </rollingPolicy>
    <!-- 此日志文件只记录warn级别的 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>warn</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>
    </appender>


    <!-- 时间滚动输出 level为 ERROR 日志 -->
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 正在记录的日志文件的路径及文件名 -->
    <file>${log.path}/log_error.log</file>
    <!--日志文件输出格式-->
    <encoder>
    <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
    <charset>UTF-8</charset> <!-- 此处设置字符集 -->
    </encoder>
    <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
    <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    <maxFileSize>100MB</maxFileSize>
    </timeBasedFileNamingAndTriggeringPolicy>
    <!--日志文件保留天数-->
    <maxHistory>15</maxHistory>
    </rollingPolicy>
    <!-- 此日志文件只记录ERROR级别的 -->
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
    <level>ERROR</level>
    <onMatch>ACCEPT</onMatch>
    <onMismatch>DENY</onMismatch>
    </filter>
    </appender>

    <!--
    <logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
    <logger>仅有一个name属性,
    一个可选的level和一个可选的addtivity属性。
    name:用来指定受此logger约束的某一个包或者具体的某一个类。
    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
    如果未设置此属性,那么当前logger将会继承上级的级别。
    -->
    <!--
    使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
    第一种把<root level="INFO">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
    第二种就是单独给mapper下目录配置DEBUG模式,代码如下,这样配置sql语句会打印,其他还是正常DEBUG级别:
    -->
    <!--开发环境:打印控制台-->
    <springProfile name="dev">
    <!--可以输出项目中的debug日志,包括mybatis的sql日志-->
    <logger name="com.guli" level="INFO" />

    <!--
    root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
    level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,默认是DEBUG
    可以包含零个或多个appender元素。
    -->
    <root level="INFO">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="INFO_FILE" />
    <appender-ref ref="WARN_FILE" />
    <appender-ref ref="ERROR_FILE" />
    </root>
    </springProfile>


    <!--生产环境:输出到文件-->
    <springProfile name="pro">

    <root level="INFO">
    <appender-ref ref="CONSOLE" />
    <appender-ref ref="DEBUG_FILE" />
    <appender-ref ref="INFO_FILE" />
    <appender-ref ref="ERROR_FILE" />
    <appender-ref ref="WARN_FILE" />
    </root>
    </springProfile>

    </configuration>
    + +

    3.日志的样式如下图所示

    image-20230412171350769

    +]]>
    + + 后端 + + + SpringBoot + +
    + + VMWare虚拟机安装Linux教程 + /posts/47407.html + 1.下载CentOs镜像文件

    这里下载的是7版本的 如果有其他的需要 自行安装其他的版本

    +

    下载完成之后 记录好安装的位置

    +

    阿里镜像下载地址:https://mirrors.aliyun.com/centos/7/isos/x86_64/?spm=a2c6h.25603864.0.0.4eab4511uQRsgc

    +

    image-20230510224239950

    +

    2.下载VMWare 虚拟机

    这里下载的是17版本的VMWare ,可以免费试用30天,需要许可证的可自行百度搜索

    +

    官网下载地址: https://download3.vmware.com/software/WKST-1701-WIN/VMware-workstation-full-17.0.1-21139696.exe

    +

    安装步骤:

    +

    image-20230510222210848

    +

    3.配置虚拟机 (以CentOs为例)

    CentOs的不同版本在VmWare上面的配置略有不同

    +

    CentOs 7.6版本的配置教程

    + +
    + +
    + + + + +

    CentOs 8.1版本的配置教程

    + +
    + +
    + + + + + + +

    连接Linux 的远程工具

    其实本地安装Linux大可不必安装远程控制工具 直接进入Linux里面控制即可

    +

    image-20230510225825430

    +

    XShell6的安装和配置

    1.安装

    下载地址: https://wwvc.lanzouy.com/ipF2w0v6np8j

    +

    下载解压之后双击.exe文件 点击下一步安装 直到安装完成

    +

    2.配置

    1.获取Linux的ip地址信息 并记录ip的信息

    +
    # 命令 
    # 注意这里没有写错 就是ifconfig 不是ipconfig,ipconfig是windows系统的查看ip的命令
    ifconfig
    + +

    image-20230510230114550

    +

    2.打开xShell工具

    +

    2.1创建会话

    +

    image-20230510230758405

    +

    2.2 选择会话并连接

    +

    image-20230510230937843

    +

    image-20230510231329208

    +

    image-20230510231432103

    +

    2.3 用户名和密码正确之后就连接成功

    +

    连接成功如下图

    +

    image-20230510231607314

    +

    完整的配置过程

    +

    image-20230510232029231

    +

    FinalShell的下载地址

    下载地址: http://www.hostbuf.com/downloads/finalshell_install.exe

    +

    Tips:

    +

    ​ 这里我们没有设置Linux的静态IP,电脑每次网络变化之后,IP地址会发生变化,这时我们需要获取最新的IP地址,重新执行以上的连接步骤。如果想要IP地址固定,我们可以设置静态IP,设置静态的IP的方法如下:

    +

    Linux设置静态IP | The Blog (gitee.io)

    +]]>
    + + 后端 + + + Linux + +
    + + SpringBoot整合Redis + /posts/13813.html + 1.Redis的介绍

    ​ Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。

    +

    中文文档:https://www.redis.net.cn/

    +

    2.入门

    2.1 引入依赖

    <!-- redis -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- spring2.X集成redis所需common-pool2-->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
    </dependency>
    + + + +

    2.2 添加配置类

    package com.atguigu.yygh.common.config;

    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.cache.CacheManager;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.cache.interceptor.KeyGenerator;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

    import java.lang.reflect.Method;
    import java.time.Duration;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/14
    * @Description
    */
    @Configuration
    @EnableCaching
    public class RedisConfig {

    /**
    * 自定义key规则
    */
    @Bean
    public KeyGenerator keyGenerator() {
    return new KeyGenerator() {
    @Override
    public Object generate(Object target, Method method, Object... params) {
    StringBuilder sb = new StringBuilder();
    sb.append(target.getClass().getName());
    sb.append(method.getName());
    for (Object obj : params) {
    sb.append(obj.toString());
    }
    return sb.toString();
    }
    };
    }

    /**
    * 设置RedisTemplate规则
    *
    */
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

    //解决查询缓存转换异常的问题
    ObjectMapper om = new ObjectMapper();
    // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);

    //序列号key value
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

    redisTemplate.afterPropertiesSet();
    return redisTemplate;
    }

    /**
    * 设置CacheManager缓存规则
    * 缓存的时间
    * 乱码的处理
    */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

    //解决查询缓存转换异常的问题
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);

    // 配置序列化(解决乱码的问题),过期时间600秒
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofSeconds(600))
    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
    .disableCachingNullValues();

    RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
    .cacheDefaults(config)
    .build();
    return cacheManager;
    }

    }
    + +

    说明:
    @EnableCaching:标记注解 @EnableCaching,开启缓存,并配置Redis缓存管理器。@EnableCaching 注释触发后置处理器, 检查每一个Spring bean 的 public 方法是否存在缓存注解。如果找到这样的一个注释, 自动创建一个代理拦截方法调用和处理相应的缓存行为。

    +

    2.3 配置文件中加入redis的配置

    application.properties

    +
    spring.redis.host=localhost
    spring.redis.port=6379
    spring.redis.database= 0
    spring.redis.timeout=1800000

    spring.redis.lettuce.pool.max-active=20
    spring.redis.lettuce.pool.max-wait=-1
    #最大阻塞等待时间(负数表示没限制)
    spring.redis.lettuce.pool.max-idle=5
    spring.redis.lettuce.pool.min-idle=0
    + +

    2.4 使用Spring Cache + Redis执行缓存操作

    2.1.2 @Cacheable

    ​ 根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。

    + + + + + + + + + + + + + + + + + + + +
    属性/方法名解释
    value缓存名,必填,它指定了你的缓存存放在哪块命名空间
    cacheNames与 value 差不多,二选一即可
    key可选属性,可以使用 SpEL 标签自定义缓存的key
    +

    2.1.2 @CachePut

    ​ 使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。

    + + + + + + + + + + + + + + + + + + + +
    属性/方法名解释
    value缓存名,必填,它指定了你的缓存存放在哪块命名空间
    cacheNames与 value 差不多,二选一即可
    key可选属性,可以使用 SpEL 标签自定义缓存的key
    +

    2.1.3 @CacheEvict

    ​ 使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    属性/方法名解释
    value缓存名,必填,它指定了你的缓存存放在哪块命名空间
    cacheNames与 value 差不多,二选一即可
    key可选属性,可以使用 SpEL 标签自定义缓存的key
    allEntries是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存
    beforeInvocation是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存
    +

    3.示例

    下面的第一个方法是查询数据位list集合

    +

    ​ @Cacheable(value = “dict”,keyGenerator = “keyGenerator”) 对方法的结果进行缓存

    +

    ​ value属性表示key的前缀

    +

    ​ keyGenerator表示key的生成规则,生成规则在配置文件中配置,这里我们使用的是方法的全类名作为key的后缀

    +

    第二个方法是添加数据 添加数据会造成数据库中数据的变化 我们要清除缓存

    +

    ​ @CacheEvict(value = “dict”,allEntries = true) 清空指定的缓存

    +

    ​ value属性表示清空以dict为前缀的所有缓存

    +

    ​ allEntries 属性表示是否清空所有缓存,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存

    +
    @Override
    @Cacheable(value = "dict",keyGenerator = "keyGenerator")
    public List<Dict> findByParentId(Long parentId) {
    LambdaQueryWrapper<Dict> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(Dict::getParentId, parentId);
    List<Dict> dictList = dictMapper.selectList(queryWrapper);
    for (Dict dict : dictList) {
    dict.setHasChildren(this.isHasChildren(dict.getId()));
    }
    return dictList;
    }

    @CacheEvict(value = "dict",allEntries = true)
    public void importDictData(MultipartFile file) {
    try {
    EasyExcel.read(file.getInputStream(),DictEeVo.class,new DictListener(dictMapper)).sheet().doRead();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }



    + +

    key的生成规则如下图:

    +

    image-20230414152035347

    +]]>
    + + 后端 + + + SpringBoot + +
    + + Windows10的Linux子系统WSL的安装和使用 + /posts/25154.html + 一. WSL简介

    image-20230919221346055

    +

    image-20230919221906997

    +

    二.安装教程

    1.开启WSL相关功能

    +

    image-20230919222258965

    +

    2.在微软应用商店下载并安装Ubuntu

    +

    image-20230919224644515

    +

    3.下载完成后打开Ubuntu,设置用户名和密码就可以使用了,下次打开直接搜索Ubuntu这个应用,打开即可

    +

    用户名要小写,下面我们可以看到如果使用大写的用户名,会一直提示我们使用小写的用户名

    +

    image-20230920100712177

    +

    4.使用Windows Terminal操作Ubuntu

    +

    下载Windows Terminal

    +

    image-20230920101220932

    +

    使用Windows Terminal打开Ubuntu界面

    +

    image-20230920101719542

    +

    三.FinalShell连接本机Ubuntu

    1.先卸载重装一遍ssh服务,这里不是很确定是不是自带ssh服务有没有问题 ,这里使用root

    +
    sudo -s #切换到管理员用户
    apt-get remove openssh-server
    apt-get install openssh-server
    apt-get update #执行上面的指令出现404错误的时候可以执行一下这一条指令,然后再执行上面的命令
    + +

    2.编辑sshd_config文件

    +
    vim /etc/ssh/sshd_config

    Port 2222 #设置ssh的端口号, 由于22在windows中有别的用处, 尽量不修改系统的端口号
    PermitRootLogin yes # 可以root远程登录
    PasswordAuthentication yes # 密码验证登录
    + +

    3.重启服务

    +
    sudo service ssh --full-restart
    + +

    4.使用finall连接

    +

    注: 用户名填写设置的用户名,端口号是2222,如果是其它的主机连接本机的ubuntu,修改主机地址即可

    +

    image-20230920112553412

    +

    5.连接成功之后

    +

    image-20230920112730660

    +]]>
    + + 运维 + + + Linux + +
    + + Thymeleaf教程 + /posts/54835.html + Thymeleaf 是一个服务器端 Java 模板引擎,能够处理 HTML、XML、CSS、JAVASCRIPT 等模板文件。Thymeleaf 模板可以直接当作静态原型来使用,它主要目标是为开发者的开发工作流程带来优雅的自然模板,也是 Java 服务器端 HTML5 开发的理想选择。

    +

    image-20230706152329777

    +

    教程: https://www.thymeleaf.org

    +

    项目集成Thymeleaf

    1.导入依赖

    +
    <!-- thymeleaf-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    + +

    2.添加配置

    +
    spring.thymeleaf.cache=false #关闭缓存(后面使用devtools进行热更新也要关闭缓存)
    + +

    3.在HTML文件中引入名称空间

    +
    <html xmlns:th="http://www.thymeleaf.org">
    + +
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <title>标题</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
    <p th:text="#{home.welcome}">Welcome to our grocery store!</p>
    </body>
    </html>
    + +

    Thymeleaf

    devtools热部署

    Tips:在使用Thymeleaf的时候修改页面我们要频繁的重启项目,非常浪费时间,这时我们可以导入devtools进行热部署

    +
    <!--devtools-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    + +

    导入之后重启项目,会显示如下的标识

    +

    image-20230706154112026

    +

    每次修改页面之后,我们只需要在当前文件下重新build一下(Ctrl+Shift+F9),就可以实现热更新

    +

    image-20230706161401528

    +

    使用文档

    英文文档: https://www.thymeleaf.org/doc/tutorials/3.1/usingthymeleaf.html

    +

    博客文章: https://fanlychie.github.io/post/thymeleaf.html

    +

    中文PDF文档

    + + +
    + +
    + + + + + + + + + + + + + + +]]>
    + + 前端 + + + Thymeleaf + +
    + + gitHub上的优质课设项目 + /posts/19270.html + 1.学生成绩管理系统

    基于SpringBoot开发

    +

    包含:学生信息管理 班级信息管理 教师信息管理 课程信息管理 选课信息管理 考勤信息管理 请假信息管理 成绩信息管理 系统管理

    +

    项目代码截图:

    +

    image-20230311113425453

    +

    数据库:

    +

    image-20230311113536220

    +

    页面展示:

    +

    image-20230311113653032

    +

    image-20230311113841997

    +

    2.物流管理系统

    基于spring boot的中小型仓库物流管理系统(springboot+mybatis-plus+shiro+mysql+layui前端框架)

    +

    可以使用shiro进行权限管理

    +

    主要功能实现:

    +

    1.基础管理:商品管理、客户管理、供应商管理;

    +

    2.物流管理:进货管理、发货管理、交付管理;

    +

    3.系统管理:菜单管理、部门管理;

    +

    4.人事管理:权限管理、角色管理、用户管理;

    +

    5.其他:

    +

    后台代码部分截图:

    +

    image-20230311114451668

    +

    数据库:

    +

    image-20230311114537761

    +

    项目展示:

    +

    image-20230311114653791

    +

    image-20230311114849263

    +

    3.酒店管理系统

    一个精简的基于SSM框架开发的酒店后台管理系统

    +

    支持报表的导出和导入:EXCEL报表的导出和导入,项目的亮点

    +

    后台部分代码截图:

    +

    image-20230311115057351

    +

    数据库:

    +

    image-20230311115223537

    +

    页面展示:

    +

    image-20230311120902633

    +

    image-20230311121529641

    +

    注:

    需要更多的优质项目,收藏此网站的链接,后续会更新更多的课设项目,敬请期待!

    +]]>
    + + 后端 + + + GitHub + +
    + + xShell自定义配色方案 + /posts/60780.html + 自定义配色方案

    +

    20230321180350

    +

    20230321180405

    +

    字体大小的设置

    +

    20230321180420

    +]]>
    + + 后端 + + + Linux + +
    + + 个人简历 + /posts/53088.html + + +
    +
    + + + + + +
    +
    + +]]>
    + + 面试 + + + 简历 + +
    + + 使用Robot类编写自动化脚本 + /posts/30127.html + 一.概述

    ​ Java中Robot类位于java.awt.Robot,该类用于为测试自动化,自运行演示程序和其他需要控制鼠标和键盘的应用程序生成本机系统输入事件,Robot类的主要目的是便于Java平台实现自动测试

    +

    二.基本API

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    方法名使用说明
    delay(n)延迟电脑操作n毫秒,类似于Thread.sleep()
    keyPress()模拟手动按下电脑键盘上的某个键
    keyRelease()模拟手动松开电脑键盘上的某个键(与keyPress()对应,按下一个键必须松开这个键)
    mouseMove(x,y)将鼠标移动到指定的x,y位置
    mousePress()按下鼠标上的某个键
    mouseRelease()松开鼠标上的某个键
    getPixelColor(x,y)获取指定坐标处的像素颜色
    mouseWheel(int wheelAmt)鼠标滚动(参数小于0,表示向上滚动;参数大于0,表示向下滚动)
    +

    三.基本使用

    1.模拟按键

    import java.awt.*;
    import java.awt.event.KeyEvent;
    import java.util.Random;

    public class Test {
    public static void main(String[] args) throws AWTException {
    Robot robot = new Robot();
    //延时函数,延时5秒
    robot.delay(5000);
    //模拟按一下K键
    robot.keyPress(KeyEvent.VK_K);
    //产生一个随机的时间(0.2 ~ 0.4秒之间)
    Random random = new Random();
    double randomValue = random.nextDouble() * 0.2 + 0.2;
    robot.delay((int)randomValue * 1000);
    //释放k键
    robot.keyRelease(KeyEvent.VK_K);
    }
    }
    + +

    2.模拟鼠标

    import java.awt.*;
    import java.awt.event.InputEvent;

    public class Test {
    public static void main(String[] args) throws AWTException {
    Robot robot = new Robot();
    //鼠标的移动
    robot.mouseMove(800, 200);
    //按下左键
    robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
    //弹起左键
    robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    //鼠标滚轮(大于1 向上滚动)
    robot.mouseWheel(1);
    }
    }
    + +

    3.获取颜色

    import java.awt.*;

    public class Test {
    public static void main(String[] args) throws AWTException {
    Robot robot = new Robot();
    Color color = robot.getPixelColor(520, 70);
    }
    }
    + + + +]]>
    + + 后端 + + + 脚本 + +
    + + 内网穿透 + /posts/6932.html + 第一步 官网下载

    客户端下载 –> windows 64位 –>解压后得到.exe文件

    +

    image-20230409221503708

    +

    第二步 官网注册并登录进入后台

    官网注册登录 –> 购买免费隧道 –>获取authtoken

    +

    image-20230409221827414

    +

    image-20230409221954792

    +

    第三步 双击下载的.exe文件进入命令行页面输入命令

    natapp -authtoken=刚刚你申请的authtoken
    + +

    image-20230409222457135

    +

    image-20230409222628425

    +

    第四步 根据生成的域名访问服务

    image-20230409222923480

    +]]>
    + + 后端 + + + 网络 + +
    + + 免费域名注册教程 + /posts/5727.html + 一.域名注册

    免费域名注册网址:https://nic.eu.org

    +

    image-20230913123344514

    +

    1.注册账号

    第一步:点击: here

    +

    image-20230913123704246

    +

    第二步: 点击Register

    +

    image-20230913123809940

    +

    第三步:填写相关的信息

    +

    地址信息填写英国的地址,可以使用在线地址生成器生成:https://www.meiguodizhi.com/uk-address

    +

    image-20230913124042655

    +

    填写地址相关的信息,其余的信息根据自己的情况填写(fax可不填)

    +

    image-20230913124616244

    +

    显示下面的提示就是注册成功了

    +

    image-20230913124842536

    +

    这时在系统会给刚才注册的邮箱发送一个激活链接,点击激活

    +

    image-20230913125129535

    +

    打开链接之后点击的按钮Validate验证,验证之后显示如下页面表示成功

    +

    image-20230913125324974

    +

    2.登录

    登录的账号就在刚才发送的邮件里面,密码就是注册时设置的密码

    +

    image-20230913125539561

    +

    3.申请域名

    输入你要注册的域名,域名的格式是 xxxx.eu.org

    +

    image-20230913132602702

    +

    域名验证,在输入框中填写下面的信息

    +
    grannbo.ns.cloudflare.com
    sid.ns.cloudflare.com
    + +

    image-20230913132742999

    +

    点击Submit按钮之后显示如下的内容表示这个免费的域名是可以申请到的,然后等待人工审核通过

    +

    image-20230913132918535

    +

    审核通过之后,你注册时使用的邮箱就会收到信息

    +]]>
    + + 个人 + + + 域名注册 + +
    + + 信息系统分析与设计项目运行环境的搭建 + /posts/1530.html + ​ 安装时请仔细的看一遍需要安装的环境,避免因为错装版本导致项目无法运行,安装过程中遇到链接失效可以点击头像下方的图标联系我获取最新的链接

    +

    注:项目是基于java的项目,这些才需要安装,C或者C++,Python,Go,PHP等语言编写的项目,请移步

    +

    1.需要安装的环境(最小安装环境,不涉及微服务的项目,微服务项目环境安装私聊我)

    1.1:写代码的工具: Intellij IDEA

    1.2:Java运行环境:java8 (也叫JDK1.8,高版本的不推荐使用,可能出现项目运行错误的情况)

    1.3:数据库:MySQL 5.7(必须安装,数据的持久化需要用到)

    1.4:构建工具:Maven 3.6.3(用于安装依赖,IDEA中自带的有Maven构建工具,但是由于用的是国外的镜像,安装依赖可能失败,这根据具体的项目决定),不想安装可以先用用自带的maven,不行的话再安装。

    2.具体安装教程

    2.1:Intellij IDEA :安装社区版也就是免费版的(项目运行完全够用),收费版需要自助开源(自行百度搜索破解教程)

    安装教程:官网安装: https://www.jetbrains.com/idea/download/#section=windows

    +

    image-20230311104034350

    +

    2.2Java运行环境JDk的安装:这个必须安装,java程序运行的基础,推荐JDK1.8,不推荐高版本。必须安装成功。

    安装教程:1.下载安装 2.配置环境变量 3.测试安装是否成功(任意位置打开cmd窗口输入java -version都可以执行)

    +

    保姆级教程: https://blog.csdn.net/weixin_52161454/article/details/122467301

    +

    2.3:Mysql 5.7:最好是安装5.7版本的,8.0版本的mysql驱动与5.7版本不一样(5.7版本安装不了可以安装8.0版本的),需要更改驱动和链接url (这里安装mysql数据库就行了,Oracle数据库个人项目不常用)

    注意:安装最好一次成功,不然安装失败再安装会有很多的不便

    +

    以下保姆级教程二选一:

    +

    安装教程一: https://blog.csdn.net/m0_49284219/article/details/121972531

    +

    安装教程二: https://blog.csdn.net/qq_55660421/article/details/123023804

    +

    2.4:Maven3.6.3:可以安装最新版的maven ,这个是构建工具,用来安装项目运行所需要的依赖,安装成功后将配置文件中的安装源换成阿里源,安装依赖更快更稳定

    保姆级别教程: https://blog.csdn.net/tirster/article/details/123418269

    +

    注:

    ​ 安装的过程中出现错误的话可以通过百度解决,你遇到的错误大多数人也会遇到,要学会自己寻找解决这些问题的办法。以上安装的是本次课程设计的基本环境依赖(够用了),如果项目中包含缓存优化(redis),前后端分离开发,分布式,还需安装redis数据库,nginx,VsCode,Dubbo,Zookeeper等软件和环境。安装过程中遇到实在是无法解决的问题,可以联系我,在线解答。下期可能会出一期怎么在github上寻找优质的项目,敬请期待。

    +]]>
    + + 后端 + + + 环境搭建 + +
    + + 压力测试与性能监控 + /posts/1416.html + 一.压力测试

    性能指标

    +
      +
    • 响应时间(Response Time: RT)响应时间指用户从客户端发起一个请求开始,到客户端接收到从服务器端返回的响应结束,整个过程所耗费的时间。

      +
    • +
    • HPS(Hits Per Second) :每秒点击次数,单位是次/秒。

      +
    • +
    • TPS(Transaction per Second):系统每秒处理交易数,单位是笔/秒。

      +
    • +
    • QPS(Query per Second):系统每秒处理查询次数,单位是次/秒。对于互联网业务中,如果某些业务有且仅有一个请求连接,那么 TPS=QPS=HPS,一般情况下用 TPS 来衡量整个业务流程,用 QPS 来衡量接口查询次数,用 HPS 来表示对服务器单击请求。

      +
    • +
    • 无论 TPS、QPS、HPS,此指标是衡量系统处理能力非常重要的指标,越大越好,根据经验,一般情况下:

      +

      金融行业:1000TPS~50000TPS,不包括互联网化的活动

      +

      保险行业:100TPS~100000TPS,不包括互联网化的活动

      +

      制造行业:10TPS~5000TPS

      +

      互联网电子商务:10000TPS~1000000TPS

      +

      互联网中型网站:1000TPS~50000TPS

      +

      互联网小型网站:500TPS~10000TPS

      +
    • +
    • 最大响应时间(Max Response Time) 指用户发出请求或者指令到系统做出反应(响应)的最大时间。

      +
    • +
    • 最少响应时间(Mininum ResponseTime) 指用户发出请求或者指令到系统做出反应(响应)的最少时间。

      +
    • +
    • 90%响应时间(90% Response Time) 是指所有用户的响应时间进行排序,第 90%的响应时间。

      +
    • +
    • 从外部看,性能测试主要关注如下三个指标

      +

      吞吐量:每秒钟系统能够处理的请求数、任务数。

      +

      响应时间:服务处理一个请求或一个任务的耗时。

      +

      错误率:一批请求中结果出错的请求所占比例。

      +
    • +
    +

    1.JMeter的使用

    1.1 安装

    1.下载安装包

    +

    安装网址: https://jmeter.apache.org/download_jmeter.cgi

    +

    image-20230709161151542

    +

    2.解压并双击运行

    +

    image-20230709161346717

    +

    image-20230709161600398

    +

    1.2 设置

    设置为中文页面

    +

    点击Options->Language->选择简体中文

    +

    image-20230709161752103

    +

    1.3 压测示例

    1.新建线程组

    +

    右键点击测试计划->添加->线程->线程组

    +

    image-20230709162502383

    +

    线程组参数详解:

    +
      +
    • 线程数:虚拟用户数。一个虚拟用户占用一个进程或线程。设置多少虚拟用户数在这里也就是设置多少个线程数。

      +
    • +
    • 线程数:虚拟用户数。一个虚拟用户占用一个进程或线程。设置多少虚拟用户数在这里也就是设置多少个线程数。

      +
    • +
    • Ramp-Up Period(in seconds)准备时长:设置的虚拟用户数需要多长时间全部启动。如果线程数为 10,准备时长为 2,那么需要 2 秒钟启动10个线程,也就是每秒钟启动 5 个线程。

      +
    • +
    • 循环次数:每个线程发送请求的次数。如果线程数为 10,循环次数为 100,那么每个线程发送 100 次请求。总请求数为 10*100=1000 。如勾选了“永远”,那么所有线程会一直发送请求,一到选择停止运行脚本。

      +
    • +
    • Delay Thread creation until needed:直到需要时延迟线程的创建。

      +
    • +
    • 调度器:设置线程组启动的开始时间和结束时间(配置调度器时,需要勾选循环次数为永远)

      +
    • +
    • 持续时间(秒):测试持续时间,会覆盖结束时间

      +
    • +
    • 启动延迟(秒):测试延迟启动时间,会覆盖启动时间

      +
    • +
    • 启动时间:测试启动时间,启动延迟会覆盖它。当启动时间已过,手动只需测试时当前时间也会覆盖它。

      +
    • +
    • 结束时间:测试结束时间,持续时间会覆盖它。

      +
    • +
    +

    2.添加HTTP请求

    +

    image-20230709163303477

    +

    image-20230709163457833

    +

    3.添加监听器

    +

    image-20230709163639856

    +

    4.启动压测和查看分析结果

    +

    image-20230709164012746

    +

    image-20230709164208747

    +

    1.4 JMeter Address Already in use 错误解决

    windows 本身提供的端口访问机制的问题。
    Windows 提供给 TCP/IP 链接的端口为 1024-5000,并且要四分钟来循环回收他们。就导致
    我们在短时间内跑大量的请求时将端口占满了。
    1.cmd 中,用 regedit 命令打开注册表

    +

    2.在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 下,
    右击 parameters,添加一个新的 DWORD,名字为 MaxUserPort,然后双击 MaxUserPort,输入数值数据为 65534,基数选择十进制(如果是分布式运行的话,控制机器和负载机器都需要这样操作)

    +

    3.修改配置完毕之后记得重启机器才会生效
    官网解释:https://support.microsoft.com/zh-cn/help/196271/when-you-try-to-connect-from-tcp-ports-greater-than-5000-you-receive-t
    TCPTimedWaitDelay:30

    +

    二.性能监控

    Jdk 的两个小工具 jconsole、jvisualvm(升级版的 jconsole);通过命令行启动,可监控本地和远程应用。

    +

    1.jconsole

    image-20230709160343565

    +

    2.jvisualvm

    jvisualvm相比jconsole功能更全,一般使用jvisualvm

    +

    image-20230709160552461

    +

    Visual GC插件的安装

    +

    点击工具->插件->在可用插件中选择Visual GC安装

    +

    安装成功之后显示如下

    +

    image-20230709160932416

    +

    image-20230709170626946

    +]]>
    + + 测试 + + + 测试 + +
    + + 力扣(LeetCode)算法刷题 + /posts/13579.html + 刷题网站: 力扣 视频: AcWing

    +

    一.数组

    A.简单

    1.两数之和

    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。

    +

    示例 1:

    +
    输入:nums = [2,7,11,15], target = 9
    输出:[0,1]
    解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
    + +

    示例 2:

    +
    输入:nums = [3,2,4], target = 6
    输出:[1,2]
    + +

    示例 3:

    +
    输入:nums = [3,3], target = 6
    输出:[0,1]
    + +

    提示:

    +
      +
    • 2 <= nums.length <= 104
    • +
    • -109 <= nums[i] <= 109
    • +
    • -109 <= target <= 109
    • +
    • 只会存在一个有效答案
    • +
    +

    代码实现

    +

    自己的代码 暴力枚举

    +
    class Solution {
    public int[] twoSum(int[] nums, int target) {
    int[] result = new int[2];
    for (int i = 0; i < nums.length; i++) {
    for (int j = 0; j < nums.length; j++) {
    if (nums[i] + nums[j] == target && i != j) {
    result[0] = i;
    result[1] = j;
    }
    }
    }
    return result;
    }
    }
    + +

    2.删除有序数组中的重复项

    给你一个 升序排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

    +

    考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

    +
      +
    • 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
    • +
    • 返回 k
    • +
    +

    判题标准:

    +

    系统会用下面的代码来测试你的题解:

    +
    int[] nums = [...]; // 输入数组
    int[] expectedNums = [...]; // 长度正确的期望答案

    int k = removeDuplicates(nums); // 调用

    assert k == expectedNums.length;
    for (int i = 0; i < k; i++) {
    assert nums[i] == expectedNums[i];
    }
    + +

    如果所有断言都通过,那么您的题解将被 通过

    +

    示例 1:

    +
    输入:nums = [1,1,2]
    输出:2, nums = [1,2,_]
    解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
    + +

    示例 2:

    +
    输入:nums = [0,0,1,1,1,2,2,3,3,4]
    输出:5, nums = [0,1,2,3,4]
    解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
    + +

    提示:

    +
      +
    • 1 <= nums.length <= 3 * 104
    • +
    • -104 <= nums[i] <= 104
    • +
    • nums 已按 升序 排列
    • +
    +

    代码实现

    +
    package com.leetcode.array;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/16
    * @Description 删除有序数组中的重复项
    */
    public class RemoveDuplicates {
    public static void main(String[] args) {
    RemoveDuplicates removeDuplicates = new RemoveDuplicates();
    int[] arr = {0,0,1,1,1,2,2,3,3,4};
    System.out.println(removeDuplicates.removeDuplicates(arr));

    }

    /**
    * 数组去重
    */
    public int removeDuplicates(int[] nums) {
    int n = nums.length;//获取数组长度
    if (n == 0) { //数组的长度是零就直接的退出
    return 0;
    }
    int fast = 1, slow = 1; //定义两个快慢指针
    while (fast < n) {
    //如果后面一个数和前面的那个数相同 慢指针不动 快指针动
    //然后下一次判断的时候 慢指针就别快指针慢了一次 再将慢指针指向快指针
    //相同的数就后移了
    if (nums[fast] != nums[fast - 1]) {
    nums[slow] = nums[fast];
    ++slow;
    }
    ++fast;
    }
    return slow;
    }
    }
    + +

    3.移除元素

    给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

    +

    不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

    +

    元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

    +

    说明:

    +

    为什么返回数值是整数,但输出的答案是数组呢?

    +

    请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

    +

    你可以想象内部操作如下:

    +
    // nums 是以“引用”方式传递的。也就是说,不对实参作任何拷贝
    int len = removeElement(nums, val);

    // 在函数里修改输入数组对于调用者是可见的。
    // 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
    for (int i = 0; i < len; i++) {
    print(nums[i]);
    }
    + +

    示例 1:

    +
    输入:nums = [3,2,2,3], val = 3
    输出:2, nums = [2,2]
    解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
    + +

    示例 2:

    +
    输入:nums = [0,1,2,2,3,0,4,2], val = 2
    输出:5, nums = [0,1,4,0,3]
    解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
    + +

    提示:

    +
      +
    • 0 <= nums.length <= 100
    • +
    • 0 <= nums[i] <= 50
    • +
    • 0 <= val <= 100
    • +
    +

    代码实现

    +

    + + + +]]>
    + + 后端 + + + 数据结构与算法 + +
    + + 常用正则表达式大全 + /posts/47003.html + 一、校验数字的表达式
    1 数字:^[0-9]$
    2 n位的数字:^\d{n}$
    3 至少n位的数字:^\d{n,}$
    4 m-n位的数字:^\d{m,n}$
    5 零和非零开头的数字:^(0|[1-9][0-9])$
    6 非零开头的最多带两位小数的数字:^([1-9][0-9])+(.[0-9]{1,2})?$
    7 带1-2位小数的正数或负数:^(-)?\d+(.\d{1,2})?$
    8 正数、负数、和小数:^(-|+)?\d+(.\d+)?$
    9 有两位小数的正实数:^[0-9]+(.[0-9]{2})?$
    10 有1~3位小数的正实数:^[0-9]+(.[0-9]{1,3})?$
    11 非零的正整数:^[1-9]\d$ 或 ^([1-9][0-9]){1,3}$ 或 ^+?[1-9][0-9]$
    12 非零的负整数:^-[1-9][]0-9”$ 或 ^-[1-9]\d$
    13 非负整数:^\d+$ 或 ^[1-9]\d|0$
    14 非正整数:^-[1-9]\d|0$ 或 ^((-\d+)|(0+))$
    15 非负浮点数:^\d+(.\d+)?$ 或 ^[1-9]\d.\d|0.\d[1-9]\d|0?.0+|0$
    16 非正浮点数:^((-\d+(.\d+)?)|(0+(.0+)?))$ 或 ^(-([1-9]\d.\d|0.\d[1-9]\d))|0?.0+|0$
    17 正浮点数:^[1-9]\d.\d|0.\d[1-9]\d$ 或 ^(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9]))$
    18 负浮点数:^-([1-9]\d.\d|0.\d[1-9]\d)$ 或 ^(-(([0-9]+.[0-9][1-9][0-9])|([0-9][1-9][0-9].[0-9]+)|([0-9][1-9][0-9])))$
    19 浮点数:^(-?\d+)(.\d+)?$ 或 ^-?([1-9]\d.\d|0.\d[1-9]\d|0?.0+|0)$

    +

    二、校验字符的表达式
    1 汉字:^[\u4e00-\u9fa5]{0,}$
    2 英文和数字:^[A-Za-z0-9]+$ 或 ^[A-Za-z0-9]{4,40}$
    3 长度为3-20的所有字符:^.{3,20}$
    4 由26个英文字母组成的字符串:^[A-Za-z]+$
    5 由26个大写英文字母组成的字符串:^[A-Z]+$
    6 由26个小写英文字母组成的字符串:^[a-z]+$
    7 由数字和26个英文字母组成的字符串:^[A-Za-z0-9]+$
    8 由数字、26个英文字母或者下划线组成的字符串:^\w+$ 或 ^\w{3,20}$
    9 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_]+$
    10 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9]+$ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
    11 可以输入含有^%&’,;=?$"等字符:[^%&’,;=?$\x22]+
    12 禁止输入含有的字符:[^\x22]+

    +

    三、特殊需求表达式
    1 Email地址:^\w+([-+.]\w+)@\w+([-.]\w+).\w+([-.]\w+)$
    2 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+/.?
    3 InternetURL:[a-zA-z]+://[^\s] 或 ^https://([\w-]+.)+[\w-]+(/[\w-./?%&=])?$
    4 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
    5 电话号码(“XXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”和”XXXXXXXX):^((\d{3,4}-)|\d{3.4}-)?\d{7,8}$
    6 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
    7 身份证号:
    15或18位身份证:^\d{15}|\d{18}$
    15位身份证:^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$
    18位身份证:^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$
    8 短身份证号码(数字、字母x结尾):^([0-9]){7,18}(x|X)?$ 或 ^\d{8,18}|[0-9x]{8,18}|[0-9X]{8,18}?$
    9 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
    10 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
    11 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间):^(?=.\d)(?=.[a-z])(?=.[A-Z]).{8,10}$
    12 日期格式:^\d{4}-\d{1,2}-\d{1,2}
    13 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
    14 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
    15 钱的输入格式:
    16 1.有四种钱的表示形式我们可以接受:”10000.00” 和 “10,000.00”, 和没有 “分” 的 “10000” 和 “10,000”:^[1-9][0-9]$
    17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符”0”不通过,所以我们采用下面的形式:^(0|[1-9][0-9])$
    18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9])$
    19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分:^[0-9]+(.[0-9]+)?$
    20 5.必须说明的是,小数点后面至少应该有1位数,所以”10.”是不通过的,但是 “10” 和 “10.2” 是通过的:^[0-9]+(.[0-9]{2})?$
    21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9]+(.[0-9]{1,2})?$
    22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})(.[0-9]{1,2})?$
    23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须:^([0-9]+|[0-9]{1,3}(,[0-9]{3}))(.[0-9]{1,2})?$
    24 备注:这就是最终结果了,别忘了”+”可以用””替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
    25 xml文件:^([a-zA-Z]+-?)+[a-zA-Z0-9]+\.[x|X][m|M][l|L]$
    26 中文字符的正则表达式:[\u4e00-\u9fa5]
    27 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
    28 空白行的正则表达式:\n\s\r (可以用来删除空白行)
    29 HTML标记的正则表达式:<(\S?)[^>]>.?|<.? /> (网上流传的版本太糟糕,上面这个也仅仅能部分,对于复杂的嵌套标记依旧无能为力)
    30 首尾空白字符的正则表达式:^\s|\s$或(^\s)|(\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
    31 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
    32 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
    33 IP地址:\d+.\d+.\d+.\d+ (提取IP地址时有用)

    +]]>
    + + 后端 + + + 正则表达式 + +
    + + 常用的DOS命令 + /posts/51007.html + 1.一些基础的DOS命令
    c:\javacode>dir	  //查看C盘javacode目录下有那些文件
    + +
    c:\javacode>cd /D d:   //从C盘切换到D盘
    + +
    c:\javacode>help cd	  //解释cd是怎么使用的
    + +
    c:\javacode>cd ..     //切换到上一级目录
    + +
    c:\javacode>cd \      //切换到根目录
    + +
    C:\tree c:\javacode   //查看C盘下的子目录javacode下面的所有子目录,形成一个目录树
    + +
    >cls    //清空所有内容,清屏
    >exit //退出控制台
    md[创建目录],rd[删除目录],copy[拷贝文件],del[删除文件],echo[输入内容到文件],type,move[剪切]
    >echo ok >pic.txt //创建pic.txt并把ok输入到pic.txt中
    >echo nul >pic.txt //创建一个空文件pic.txt
    >copy ok.txt d:\ok.txt //把ok.txt从当前路径拷贝到D盘下面,可以更改第二个ok.txt的文件名
    >move ok.txt d:\ok.txt //把ok.txt剪切到D盘下面
    + +

    2.网络编程相关的DOS命令

    //获取IP相关的信息
    >ipconfig
    //端口监听和网络监听情况
    >netstat -an
    >netstat -an | more//分页显示
    >netstat -anb
    //ping命令,查看网络是否连通
    >ping IP地址或者域名
    + +

    3.MySql的相关DOS命令

    //开启MySQL和关闭MySQL服务
    >net start mysql //开
    >new stop mysql //关


    //登录mysql的命令
    >mysql -u 用户名 -p密码 //密码可以先不输入,直接回车输入,或者直接写在p的后面,中间不要空格



    //连接mysql服务(mysql数据库)的指令
    >mysql -h 主机ip -P端口 -u 用户名 -p密码
    //tips:
    //p和密码中间不要有空格
    //-p后面没有密码的话,会让回车的时候输入密码
    //如果没有写-h主机,默认就是本机
    //如果没有写-P端口,默认就是3306
    //在实际的工作中端口一般会修改,使用3306这一端口,容易被黑客发现并攻击





    //备份数据库的命令
    //备份整个数据库
    >mysqldump -u root -p -B 数据库 数据库 > c:\\test\\bak.sql
    //备份数据库的某个表
    >mysqldump -u root -p 数据库 表1 表2 表n > c:\\test\\bak.sql
    //这里就是把数据库hsp_db02 和数据库hsp_db03备份在C盘下面test目录中一个叫bak.sql的文件中,这里备份的是sql语句




    //根据备份的文件,恢复数据库
    //第一种方式
    >source c:\\test\\bak.sql //根据C盘下面的c:\\test\\bak.sql的文件恢复数据库
    //第二种方式
    //直接把这个.sql的文件再执行一遍,就会恢复两个数据库了

    + +]]>
    + + 后端 + + + 常用命令 + +
    + + 常见的问题及解决方法 + /posts/14438.html + 1.前后端时间格式的问题

    配置文件中设置时间的格式和时区

    +

    image-20230506130238222

    +

    2.MyBatis分页插件统计总记录数total失效的问题

    检查一下分页插件的配置 选择以下正确的分页插件配置

    +
    package com.atguigu.common.config.mp;

    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**
    * mp的分页插件
    */
    @Configuration
    @MapperScan("com.atguigu.auth.mapper")
    public class MybatisPlusConfig {

    /**
    * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
    */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
    }

    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
    return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
    }
    + +
    package com.atguigu.config;

    import com.baomidou.mybatisplus.core.injector.ISqlInjector;
    import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
    import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/3/4
    * @Description mybatis-plus的配置文件
    */
    @Configuration
    public class MpConfig {

    /**
    * 分页插件
    */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
    return new PaginationInterceptor();
    }
    }

    + +

    3.网关中的路由问题

    网关中的路由匹配问题,模糊的路由放在精确的路由前面,容易是精确的路由配置失效

    +
    spring:
    cloud:
    gateway:
    routes:
    #这个是精确的路由,下面的路由在前,特定的请求就不会转发到这个请求中来
    - id: product_route
    uri: lb://gulimall-product
    predicates:
    - Path=/api/product/**
    filters:
    - RewritePath=/api/(?<segment>.*),/$\{segment}

    #这个是模糊的路由,如果放在上面的那个路由配置的前面,会导致上面的那个路由配置失效
    - id: admin_route
    uri: lb://renren-fast
    predicates:
    - Path=/api/**
    filters:
    - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
    + +

    4.Docker中部署mysql后SpringBoot连接时提示表不存在(修改表名忽略大小写)解决方案

    使用Docer启动容器的时候设置忽略大小写,表不存在是因为表名大小写的原因。

    +

    方案一:启动的时候加上–lower_case_table_names= 1

    +
    docker run -p 3306:3306 --name mysql -v /usr/local/docker/mysql/conf:/etc/mysql -v /usr/local/docker/mysql/logs:/var/log/mysql -v /usr/local/docker/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql:5.7 --lower_case_table_names= 1
    + +

    方案二: 修改mysql挂载在宿主机的配置文件

    +
    # 在配置文件中添加如下的配置
    lower_case_table_names=1
    + +]]>
    + + 后端 + + + Java + +
    + + 常用网站及网址信息 + /posts/17772.html + 常用网站及网址信息

    +]]>
    + + 个人 + + + 常用网站 + +
    + + 开发环境的搭建 + /posts/63333.html + 1.后端环境搭建

    1.1 JDK环境的配置

    常规的安装,无需教程

    +

    IDEA中设置默认的JDK

    +

    image-20230604120610791

    +

    选中需要设置为默认JDK的JDK

    +

    image-20230604120654078

    +

    1.2 Maven的安装与配置

    环境变量的配置

    +

    MAVEN_HOME和Path 配置文件的配置

    +

    设置默认maven,每次创建新的工程的时候就会使用这个maven

    +

    image-20230604120307280

    +

    然后在弹出的页面中设置,这样每次创建新的工程就会使用当前设置的maven

    +

    image-20230604120436139

    +

    1.3 IDEA的配置

    展开目录树 取消勾选下面的选项

    +

    image-20230604151530534

    +

    快捷键的配置 IDEA中的全局查找的快捷键会和windows的中文的繁简字体切换的快捷键冲突,直接关掉系统的快捷键

    +

    image-20230604121126549

    +

    页面中字体的配置

    +image-20230603202245269 + +

    代码字体的配置

    +image-20230603154531129 + +

    设置代码粗体

    +image-20230603154622726 + +

    打开的多个文件多行显示,不在同一行显示

    +

    image-20230611211438017

    +

    浅色主题的配置

    +

    image-20230815171925527

    +

    修改注释的颜色

    +

    修改多行注释的颜色: 修改Doc comment -> Text的颜色

    +

    image-20230815172326472

    +

    1.4 mysql的安装

    具体的安装教程在《MySql5.7的安装教程的博客中》

    +

    1.5 Nacos的安装配置

    github下载之后,直接解压运行

    +

    下载的地址:https://github.com/alibaba/nacos/releases

    +

    踩坑:单节点部署的话,启动的时候需要配置一下,不然启动的时候会闪退

    +

    image-20230603193632659

    +

    2.前端环境的搭建

    2.1 Nodejs的安装与配置

    下载的网址:https://nodejs.org/zh-cn/download/releases

    +

    选择.msi的下载即可

    +

    不用配置环境变量,一直下一步,直到安装成功过,node自带npm包管理工具

    +
    # 检验命令
    node -v
    npm -v
    + +

    3.前后端公共环境的搭建

    1.Git环境的搭建

    #以下的操作在下载安装完毕之后进行
    #1.鼠标在桌面右键 选择Git Bash Here 打开控制台

    #2.配置用户名和邮箱
    git config --global user.name "用户名" #随意
    git config --global user.email "邮箱" #自己的邮箱

    #3.配置SSH免密连接
    #生成密钥
    ssh-keygen -t rsa -C "在码云上注册的邮箱地址" #连续三次回车
    #查看密钥并复制公钥的内容
    cat ~/.ssh/id_rsa.pub

    #4.将密钥的复制到码云的SSH公钥中
    #4.1添加公钥 公钥名随意 公钥内容就是上面复制的内容

    #5.测试
    ssh -T git@gitee.com
    + +]]>
    + + 后端 + + + 环境搭建 + +
    + + 技术书籍-Linux指令大全 + /posts/3661.html + Linux命令行与shell脚本编程大全 第三版 ,布鲁姆 ,P606

    PDF

    + +
    + +
    + + + + + + +

    精通Linux 第二版

    PDF

    + +
    + +
    + + + +]]>
    + + 后端 + + + 技术书籍 + +
    + + 接口测试工具 + /posts/35630.html + 一 Postman的使用

    官网网址: https://www.postman.com/

    +

    Postman的首页

    +

    image-20230813162510205

    +

    1 使用教程

    1.1 发送Post请求

    以一个普通的登录操作来演示Postman的使用教程

    +

    发送Post请求的: 1.修改请求的方式 2.设置请求体的数据类型

    +

    image-20230813163451814

    +

    请求数据的格式

    +
    {
    "phone":"13511223456",
    "password":"admin"
    }
    + +

    响应数据的格式

    +
    {
    "host": null,
    "code": 200,
    "errorMessage": "操作成功",
    "data": {
    "user": {
    "id": 4,
    "salt": "",
    "name": "admin",
    "password": "",
    "phone": "13511223456",
    "image": null,
    "sex": true,
    "certification": null,
    "identityAuthentication": null,
    "status": true,
    "flag": 1,
    "createdTime": "2020-03-30T08:36:32.000+00:00"
    },
    "token": "eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQrDMAzA_uJzc3DnBKe_cRKPpVAIOIWNsb_PPfQmIfSFfXbYgCqlEkmDVI6BHlJCrrWF9IzKiMKNERboMmHDlDFjTMgL2Fn8to9NPa5u5vrSfoibnM1NxnDW97jPvF5n90a_P2LDCJyAAAAA.Rk6wC_YpLmxrqylQxEgz19uKHkrBRV6_JmIqbHrSQENkURtavrzhLzr0D8mQf-wtKivaGGviDGHmdckw8IWi7g"
    }
    }
    + +

    二 Swagger的使用

    SpringBoot整合Swagger:https://jasonsgong.gitee.io/posts/32246.html

    +

    以一个普通的登录操作来演示Swagger的使用教程

    +

    image-20230813170950932

    +

    image-20230813171244848

    +

    三 knife4j的使用

    SpringBoot整合Knife4j教程: https://jasonsgong.gitee.io/posts/855.html

    +

    gitee地址:https://gitee.com/xiaoym/knife4j

    +

    官方文档:https://doc.xiaominfo.com/

    +

    核心功能 :文档说明 在线调试 个性化配置 离线文档 接口排序

    +

    image-20230813172211110

    +

    以一个普通的登录操作来演示knife4j的使用教程

    +

    image-20230813172355353

    +

    下载离线文档

    +

    image-20230813172605624

    +

    下载成功之后打开的效果

    +

    image-20230813172720116

    +]]>
    + + 后端 + + + 工具 + +
    + + 数据校验 + /posts/27166.html + ​ 在前端接收数据和前端向后端传递数据的时候,都需要进行数据校验,避免传入错误的信息,比如在需要传入一个非空的值时,传入了一个空字符串,需要传入邮箱号码的时候,传入的非邮箱格式的数据。同时在写接口时经常要写效验请求参数逻辑,这时候我们会常用做法是写大量的 if 与 if else 类似这样的代码,大量if-else代码看起来比较混乱,降低了代码的可读性。

    +

    一.JSR303数据校验

    1.引入依赖

    <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.7.Final</version>
    </dependency>
    + +

    2.给实体类添加校验注解,并定义自己的message提示

    常用的检验注解

    +image-20230612170704286 + +

    示例

    +
    package com.atguigu.gulimall.product.entity;

    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;

    import java.io.Serializable;

    import lombok.Data;
    import org.hibernate.validator.constraints.URL;

    import javax.validation.constraints.Min;
    import javax.validation.constraints.NotBlank;
    import javax.validation.constraints.NotEmpty;
    import javax.validation.constraints.Pattern;

    /**
    * 品牌
    *
    * @author JasonGong
    * @email JasonGong@gmail.com
    * @date 2023-05-19 00:23:36
    */
    @Data
    @TableName("pms_brand")
    public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
    * 品牌id
    */
    @TableId
    private Long brandId;
    /**
    * 品牌名
    */
    @NotBlank(message = "品牌名必须提交") //非空的校验
    private String name;
    /**
    * 品牌logo地址
    */
    @NotEmpty//既不能为空,也要符合URL的格式
    @URL(message = "logo必须是一个合法的URL地址")//URL的校验
    private String logo;
    /**
    * 介绍
    */
    private String descript;
    /**
    * 显示状态[0-不显示;1-显示]
    */
    private Integer showStatus;
    /**
    * 检索首字母
    */
    @NotEmpty
    @Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")//正则表达式的校验,在java中使用正则的时候不需要/符号
    private String firstLetter;
    /**
    * 排序
    */
    @NotNull //Integer类型不为空,使用@NotNull不能使用@NotEmpty
    @Min(value = 0, message = "排序必须大于等于0")//最小值是0
    private Integer sort;

    }
    + +

    3.开启校验功能@Valid

    /**
    * 保存
    * @Valid注解开启校验
    * @param result 检验之后响应的结果信息
    * 给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果
    */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result/*紧跟在需要校验的字段后面*/){
    if(result.hasErrors()){//判断有没有校验的错误
    Map<String,String> map = new HashMap<>();
    //获取检验的错误结果, result.getFieldErrors():校验出错的字段的集合
    for (FieldError fieldError : result.getFieldErrors()) {
    String defaultMessage = fieldError.getDefaultMessage();//获取到错误的提示
    String field = fieldError.getField();//获取错误的属性(字段)的名字
    //封装在一个集合中方便后期统一通过R返回结果
    map.put(field,defaultMessage);
    }
    return R.error(400,"提交的数据不合法").put("data",map);
    }else {
    //校验合格的话,就做进行业务的处理
    brandService.save(brand);
    }
    return R.ok();
    }
    + +

    测试查看返回的数据的格式,这里我们输入的都是不合法的数据格式,返回的结果如下

    +
    {
    "msg": "提交的数据不合法",
    "code": 400,
    "data": {
    "name": "品牌名必须提交",//字段:错误信息
    "logo": "logo必须是一个合法的URL地址",
    "sort": "排序必须大于等于0",
    "firstLetter": "检索首字母必须是一个字母"
    }
    }
    + +

    参数没有错误之后返回的数据

    +
    {
    "msg": "success",
    "code": 0
    }
    + +

    4.上面的代码过于冗余,我们可以直接使用统一异常处理处理数据校验的异常

    在统一异常处理类上加上数据校验异常的异常处理

    +
    package com.atguigu.gulimall.product.exception;

    import com.atguigu.common.utils.R;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/12
    * @Description 异常处理类
    */
    @Slf4j
    @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
    public class GulimallExceptionControllerAdvice {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
    log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
    Map<String,String> map = new HashMap<>();
    //获取检验的错误结果
    BindingResult result = e.getBindingResult();
    for (FieldError fieldError : result.getFieldErrors()) {
    //获取到错误的提示
    String defaultMessage = fieldError.getDefaultMessage();
    //获取错误的属性的名字
    String field = fieldError.getField();
    map.put(field,defaultMessage);
    }
    return R.error(400,"数据校验出现问题").put("data",map);
    }
    }
    + +

    这个时候上面的代码就可以简化为下面的格式,数据校验出现问题之后就直接在统一异常处理中处理了

    +
    /**
    * 保存
    */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand ){
    brandService.save(brand);
    return R.ok();
    }
    + + + +

    5.分组校验功能

     例如:当我们在添加一个品牌的时候,我们不需要传入这个品牌的id信息,需要这个品牌的品牌名信息,但是在修改这个品牌的时候,我们需要这个品牌的id信息和品牌名的信息,这时我们就需要使用分组校验了
    +
    +

    5.1 定义空接口,作为分组校验的组

    添加操作的组

    +
    package com.atguigu.common.valid;

    public interface AddGroup {
    }

    + +

    修改操作的组

    +
    package com.atguigu.common.valid;

    public interface UpdateGroup {
    }

    + +

    5.2 在实体类上添加上分组的信息

    注意:使用了分组校验之后,其余的字段也要加上分组信息,否则没有加上分组信息的会失效

    +
    package com.atguigu.gulimall.product.entity;

    import com.atguigu.common.valid.AddGroup;
    import com.atguigu.common.valid.UpdateGroup;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;

    import java.io.Serializable;

    import lombok.Data;
    import org.hibernate.validator.constraints.URL;

    import javax.validation.constraints.*;

    @Data
    @TableName("pms_brand")
    public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
    * 品牌id
    */
    @NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class})//在修改的时候进行校验
    @Null(message = "新增不能指定id", groups = {AddGroup.class})//在添加的时候进行校验
    @TableId
    private Long brandId;


    /**
    * 品牌名
    */
    @NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})//这个就是在添加和修改的组中都需要进行校验
    private String name;
    }

    + +

    5.3 在控制层添加上相应的注解信息,指定当前是操作属于的分组

    注意:这里如果产生数据校验的出现问题的异常,会由统一异常处理进行处理

    +

    保存的控制器方法上添加上添加分组信息

    +
    /**
    * 保存
    * @Validated({AddGroup.class}) 指定使用添加分组的校验
    */
    @RequestMapping("/save")
    public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand ){
    brandService.save(brand);
    return R.ok();
    }
    + +

    修改的分组上添加上修改的分组信息

    +
    /**
    * 修改
    */
    @RequestMapping("/update")
    public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
    brandService.updateById(brand);

    return R.ok();
    }
    + +

    测试:我们在新增的时候加上品牌的id,这时就会产生错误

    +

    image-20230614105450858

    +

    修改的时候不带品牌的id信息

    +

    image-20230614111024292

    +

    6.自定义校验

    这里我们以编写一个输入的值只能是指定值的注解为例

    +
     /**
    * 显示状态[0-不显示;1-显示]
    */
    //注意:我们使用了分组校验,所以这里也要加上分组groups
    @ListValue(vals = {0, 1}, groups = {AddGroup.class, UpdateGroup.class})//自定义的注解,输入的时候只能是0或者1
    private Integer showStatus;
    + +

    6.1 编写一个自定义的校验注解

    编写自定义注解

    +
    package com.atguigu.common.valid;

    import javax.validation.Constraint;
    import javax.validation.Payload;

    import java.lang.annotation.Documented;

    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;

    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.ElementType.TYPE_USE;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/15
    * @Description 自定义的校验注解
    */
    @Documented
    @Constraint(validatedBy = {})//校验注解使用的校验器
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})//注解可以标注的位置
    @Retention(RUNTIME)
    public @interface ListValue {

    String message() default "{com.atguigu.common.valid.ListValue.message}";//在配置文件中指明

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int[] vals() default {};//使用注解的时候会有vals属性 在属性里面指定值
    }
    + +

    编写注解中默认提示消息的配置文件

    +

    ValidationMessages.properties

    +
    com.atguigu.common.valid.ListValue.message=必须提交指定的值
    + +

    6.2 编写一个自定义的校验器

    package com.atguigu.common.valid;

    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.util.HashSet;
    import java.util.Set;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/15
    * @Description 自定义的校验器
    */
    public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
    private Set<Integer> set = new HashSet<>();//创建一个set集合 用于存储指定输入的值0和1
    //初始化方法
    @Override
    public void initialize(ListValue constraintAnnotation) {
    int[] vals = constraintAnnotation.vals();//@ListValue(vals = {0, 1})
    for (int val : vals) {
    set.add(val);
    }
    }

    //判断是否校验成功
    //value:需要校验的值
    @Override
    public boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {
    return set.contains(integer);//判断真实树的值在不在包含0和1的这个set集合中
    }
    }
    + +

    6.3 关联自定义的校验器和校验注解

    在自定义的注解上面关联上上面自定义的校验规则

    +

    image-20230615151144311

    +

    测试自定义的注解

    +

    image-20230615152331597

    +]]>
    + + 后端 + + + Java + +
    + + 技术书籍-java8实战 + /posts/46306.html + java 8实战 + +
    + +
    + + + +]]>
    + + 后端 + + + 技术书籍 + +
    + + 细节知识 + /posts/22202.html + Linux开启/关闭防火墙的命令

    +
    # 检查防火墙状态:
    systemctl status firewalld
    # 开启防火墙
    systemctl start firewalld
    # 关闭防火墙(有时间限制)
    systemctl stop firewalld
    # 设置开机禁用防火墙(先执行上面一条命令之后执行此命令,即可永久关闭)
    systemctl disable firewalld.service
    # 设置开机启用防火墙
    systemctl enable firewalld.service
    + + + +

    windows查看端口占用情况,杀死端口的命令

    +
    # 查询被占用的端口号的信息
    netstat -ano | findstr "8080"
    #根据端口号的PID杀死该端口的进程 其中 17156 是8080端口的PID值
    taskkill /pid 17156 /f
    + + + +

    Linux开启关闭键盘背光灯

    +
    #不是永久有效的
    # 开启键盘背光灯
    xset led named "Scroll Lock"
    #关闭键盘背光灯
    xset -led named "Scroll Lock"
    + + + +

    关闭Mysql的服务

    +
    #查找是否安装了mysql的服务
    rpm -qa | grep -i mysql
    #查看mysql服务的状态
    service mysqld status
    #关闭mysql的服务
    service mysqld stop
    #重启的命令
    service mysqld restart
    #关闭开机自启动
    systemctl disable mysqld
    + + + +

    前端向后端传递对象数据

    +

    传递普通对象参数的写法

    +

    params: searchObj

    +
    getPageList(current,limit,searchObj){
    return request({
    url: `${api_name}/${current}/${limit}`,
    method: 'get',
    params: searchObj //使用这种方式进行传递
    })
    }
    + +
    @GetMapping("/{page}/{limit}")
    public Result pageQueryRole(@PathVariable Long page,
    @PathVariable Integer limit,
    SysRoleQueryVo sysRoleQueryVo) { //普通的对象进行接收 没有添加注解

    Page<SysRole> pageParam = new Page<>(page, limit);
    LambdaQueryWrapper<SysRole> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.like(!StringUtils.isEmpty(sysRoleQueryVo.getRoleName()),SysRole::getRoleName, sysRoleQueryVo.getRoleName());
    Page<SysRole> sysRolePage = sysRoleService.page(pageParam, queryWrapper);
    return Result.ok(sysRolePage);
    }
    + +

    json格式的传递

    +

    data: searchObj

    +
    add(sysRole){
    return request({
    url: `${api_name}/save`,
    method: 'post',
    data: sysRole //使用这种方式进行传递
    })
    }
    + +
    @PostMapping("/save")
    public Result save(@RequestBody SysRole sysRole){ //JSON格式对象的传递 需要使用注解@RequestBody
    boolean isSave = sysRoleService.save(sysRole);
    return isSave? Result.ok() : Result.fail();
    }
    + + + +

    调整日志的级别

    +
    logging:
    level:
    # 注意这里包的路径,要根据实际情况t
    com.atguigu.gulimall: debug
    + + + +

    @JsonInclude注解

    +
    @JsonInclude(JsonInclude.Include.NON_EMPTY)//这里是当children为空的时候,向前端传递数据就不带这个
    private List<CategoryEntity> children;
    + + + +

    node相关的问题

    +
    #查看node的版本
    node -v
    #查看npm的版本
    npm -v
    #查看npm使用的镜像源(npm镜像源)
    npm get registry
    #设置淘宝镜像源
    npm config set registry http://registry.npm.taobao.org
    + + + +]]>
    + + 后端 + + + 细节知识 + +
    + + 谷粒学苑课程分类列表显示前后端代码 + /posts/7353.html + ​ 谷粒学苑的前端课程分类显示使用的是树形列表显示,如图所示:

    +

    image-20230310102929346

    +

    ​ Swagger测试时显示的数据格式

    +

    image-20230310103351729

    +

    ​ 前端代码代码具体的实现(部分代码):

    +

    image-20230310103126938

    +

    ​ 后端代码具体的实现(业务层):

    +

    image-20230310103430906

    +]]>
    + + 后端 + + + 项目实战 + +
    + + 简历模板 + /posts/29250.html + 黑马程序员新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题) https://www.bilibili.com/video/BV1yT411H7YK/?share_source=copy_web&vd_source=aee5e475191b69e6c781059ab6662584

    +

    Java批注简历标准

    image-20230508235133469

    +

    面试参考话术

    + +
    + +
    + + + + +

    模板

    全部的模板地址: https://gitee.com/JasonsGong/resume

    +

    模板一 本科一年经验

    + +
    + +
    + + + + +

    模板二 Java开发_AAA_N年

    + +
    + +
    + + + + +

    模板三 灰蓝色色时尚简历模板

    + +
    + +
    + + + + +

    模板四 灰色大气简约简历模板

    + +
    + +
    + + + + +

    模板五 简约大气橙色简历模板

    + +
    + +
    + + + + +

    模板六 经典风格简历模板

    + +
    + +
    + + + + +

    模板七 时尚线条简历模板

    + +
    + +
    + + + + +

    模板八 科技版简历模板

    + +
    + +
    + + + + +

    模板九 JAVA开发_李传播_5年

    + +
    + +
    + + + + +]]>
    + + 面试 + + + 简历 + +
    + + 统一异常处理 + /posts/31385.html + 我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要统一异常处理。

    +

    image-20230506153955544

    +

    1.全局异常处理

    异常返回的结果也为统一的返回结果的对象

    +
    package com.atguigu.common.exception;

    import com.atguigu.common.result.Result;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.ResponseBody;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/6
    * @Description 全局的异常处理器
    */
    @ControllerAdvice
    public class GlobalExceptionHandler {

    /**
    * 全局的异常处理
    */
    @ExceptionHandler(Exception.class) //标记什么异常使用该方法
    @ResponseBody //返回的是json数据
    public Result error(Exception e){
    e.printStackTrace();
    return Result.fail().message("全局异常处理器捕获到该异常");
    }


    /**
    * 特定的异常处理(先找特定的异常 再找全局的异常)
    */
    @ExceptionHandler(ArithmeticException.class) //标记什么异常使用该方法
    @ResponseBody //返回的是json数据
    public Result error(ArithmeticException e){
    e.printStackTrace();
    return Result.fail().message("数学运算异常");
    }

    /**
    * 自定义异常处理
    * GuiguException为自定义的异常
    */
    @ExceptionHandler(GuiguException.class) //标记什么异常使用该方法
    @ResponseBody //返回的是json数据
    public Result error(GuiguException e){
    e.printStackTrace();
    //获取抛出异常的时候填入的code和message的值并返回
    return Result.fail().code(e.getCode()).message(e.getMessage());
    }
    }
    + +

    2.自定义异常

    创建异常类 继承RuntimeException

    package com.atguigu.common.exception;

    import com.atguigu.common.result.ResultCodeEnum;
    import lombok.Data;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/6
    * @Description 自定义的异常
    */
    @Data
    public class GuiguException extends RuntimeException {
    /**
    * 异常的状态码
    */
    private Integer code;

    /**
    * 描述信息
    */
    private String message;

    /**
    * 通过状态码和错误消息创建异常对象
    */
    public GuiguException(Integer code, String message) {
    super(message);
    this.code = code;
    this.message = message;
    }

    /**
    * 接收枚举类型对象
    *
    * @param resultCodeEnum 枚举类型的结果信息
    */
    public GuiguException(ResultCodeEnum resultCodeEnum) {
    super(resultCodeEnum.getMessage());
    this.code = resultCodeEnum.getCode();
    this.message = resultCodeEnum.getMessage();
    }

    @Override
    public String toString() {
    return "GuliException{" +
    "code=" + code +
    ", message=" + this.getMessage() +
    '}';
    }


    }
    + +

    在需要抛出自定义异常的地方抛出异常

    /**
    * 查询所有的方法
    */
    @ApiOperation("查询所有角色的接口")
    @GetMapping("/findAll")
    public Result<List<SysRole>> findAll() {
    List<SysRole> sysRoles = sysRoleService.list();
    try {
    //这里我们使用除零模拟异常
    int i = 1/0;
    } catch (Exception e) {
    throw new GuiguException(2001, "抛出了自定义的异常");
    }
    return Result.ok(sysRoles);
    }
    + +

    全局异常处理器捕获异常并返回异常信息

    image-20230506154605339

    +]]>
    + + 后端 + + + Java + +
    + + 验证码服务 + /posts/11844.html + 1.使用邮件发送验证码

    1.1 引入依赖

    坑点:有时候遇到验证码发不出去的情况,要调整依赖的版本,更新为高版本的依赖

    +
    <!--javaMail-->
    <dependencies>
    <dependency>
    <groupId>javax.mail</groupId>
    <artifactId>javax.mail-api</artifactId>
    <version>1.5.6</version>
    </dependency>
    <dependency>
    <groupId>com.sun.mail</groupId>
    <artifactId>javax.mail</artifactId>
    <version>1.5.3</version>
    </dependency>
    </dependencies>
    + + + +

    1.2 导入发送验证码的工具类

    //读取配置文件中的值
    //静态属性只会加载一次 我们要在第一次加载的时候给静态属性赋上值
    //还要其他的方法 比如使用@Value注解
    static { //读取配置文件中的关于邮件的配置
    try {
    FileInputStream fileInputStream = new FileInputStream("src/main/resources/application.properties");
    Properties properties = new Properties();
    properties.load(fileInputStream);
    user = properties.getProperty("guli.user");
    password = properties.getProperty("guli.password");
    } catch (IOException e) {
    throw new RuntimeException(e);
    }

    }
    + +

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;

    import javax.annotation.PostConstruct;
    import javax.mail.*;
    import javax.mail.internet.InternetAddress;
    import javax.mail.internet.MimeMessage;
    import java.util.Properties;

    /**
    * 发邮件工具类
    */
    public class MailUtils {
    private static String USER = "xxxxxxxx"; // 发件人称号,同邮箱地址
    private static String PASSWORD = "xxxxxxxx"; // 授权码,开启SMTP时显示

    /**
    * @param to 收件人邮箱
    * @param text 邮件正文
    * @param title 标题
    */
    /* 发送验证信息的邮件 */
    public static boolean sendMail(String to, String text, String title) {
    try {
    final Properties props = new Properties();
    props.put("mail.smtp.auth", "true");
    //注意发送邮件的方法中,发送给谁的,发送给对应的app,※
    //要改成对应的app。扣扣的改成qq的,网易的要改成网易的。※
    //props.put("mail.smtp.host", "smtp.qq.com");
    props.put("mail.smtp.host", "smtp.qq.com");

    // 发件人的账号
    props.put("mail.user", USER);
    //发件人的密码
    props.put("mail.password", PASSWORD);

    // 构建授权信息,用于进行SMTP进行身份验证
    Authenticator authenticator = new Authenticator() {
    @Override
    protected PasswordAuthentication getPasswordAuthentication() {
    // 用户名、密码
    String userName = props.getProperty("mail.user");
    String password = props.getProperty("mail.password");
    return new PasswordAuthentication(userName, password);
    }
    };
    // 使用环境属性和授权信息,创建邮件会话
    Session mailSession = Session.getInstance(props, authenticator);
    // 创建邮件消息
    MimeMessage message = new MimeMessage(mailSession);
    // 设置发件人
    String username = props.getProperty("mail.user");
    InternetAddress form = new InternetAddress(username);
    message.setFrom(form);

    // 设置收件人
    InternetAddress toAddress = new InternetAddress(to);
    message.setRecipient(Message.RecipientType.TO, toAddress);

    // 设置邮件标题
    message.setSubject(title);

    // 设置邮件的内容体
    message.setContent(text, "text/html;charset=UTF-8");
    // 发送邮件
    Transport.send(message);
    return true;
    } catch (Exception e) {
    e.printStackTrace();
    }
    return false;
    }


    }
    + +

    1.3 导入随机生成验证码的工具类

    import java.util.Random;

    /**
    * 随机生成验证码工具类
    */
    public class ValidateCodeUtils {
    /**
    * 随机生成验证码
    *
    * @param length 长度为4位或者6位
    * @return
    */
    public static Integer generateValidateCode(int length) {
    Integer code = null;
    if (length == 4) {
    code = new Random().nextInt(9999);//生成随机数,最大为9999
    if (code < 1000) {
    code = code + 1000;//保证随机数为4位数字
    }
    } else if (length == 6) {
    code = new Random().nextInt(999999);//生成随机数,最大为999999
    if (code < 100000) {
    code = code + 100000;//保证随机数为6位数字
    }
    } else {
    throw new RuntimeException("只能生成4位或6位数字验证码");
    }
    return code;
    }

    /**
    * 随机生成指定长度字符串验证码
    *
    * @param length 长度
    * @return
    */
    public static String generateValidateCode4String(int length) {
    Random rdm = new Random();
    String hash1 = Integer.toHexString(rdm.nextInt());
    String capstr = hash1.substring(0, length);
    return capstr;
    }
    }
    + +

    1.4 测试使用

    1.设置发送邮件的账号和授权码信息
    2.编写测试类测试邮件是否可以正常的发送
    3.添加到业务代码中,实现验证码的发送功能
    + + + +

    2.使用阿里云的短信服务

    阿里云的短信服务不面向个人用户,无法使用,但是使用的基本逻辑和使用邮箱类似

    +

    3.Token字符串生成的工具类(JWT)

    package com.atguigu.commonutils;

    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jws;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.util.StringUtils;

    import javax.servlet.http.HttpServletRequest;
    import java.util.Date;

    /**
    * @author helen
    * @since 2019/10/16
    */
    public class JwtUtils {

    /**
    * 设置token的过期时间
    */
    public static final long EXPIRE = 1000 * 60 * 60 * 24;
    /**
    * 密钥
    */
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";


    /**
    * 生成token字符串
    */
    public static String getJwtToken(String id, String nickname){

    String JwtToken = Jwts.builder()
    .setHeaderParam("typ", "JWT")
    .setHeaderParam("alg", "HS256")
    .setSubject("guli-user")
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
    .claim("id", id)
    .claim("nickname", nickname)
    .signWith(SignatureAlgorithm.HS256, APP_SECRET)
    .compact();

    return JwtToken;
    }

    /**
    * 判断token是否存在与有效
    */
    public static boolean checkToken(String jwtToken) {
    if(StringUtils.isEmpty(jwtToken)) return false;
    try {
    Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    } catch (Exception e) {
    e.printStackTrace();
    return false;
    }
    return true;
    }

    /**
    * 判断token是否存在与有效
    */
    public static boolean checkToken(HttpServletRequest request) {
    try {
    String jwtToken = request.getHeader("token");
    if(StringUtils.isEmpty(jwtToken)) return false;
    Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    } catch (Exception e) {
    e.printStackTrace();
    return false;
    }
    return true;
    }

    /**
    * 根据token获取会员id
    */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
    String jwtToken = request.getHeader("token");
    if(StringUtils.isEmpty(jwtToken)) return "";
    Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
    Claims claims = claimsJws.getBody();
    return (String)claims.get("id");
    }
    }

    + +]]>
    + + 后端 + + + Java + +
    + + FreeMarker模板引擎 + /posts/29367.html + 一.FreeMarker介绍

    ​ FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

    +

    ​ 模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

    +

    image-20230816145012534

    +

    技术选型对比

    + + + + + + + + + + + + + + + + + + + + + + + +
    技术说明
    JspJsp 为 Servlet 专用,不能单独进行使用
    VelocityVelocity从2010年更新完 2.0 版本后,7年没有更新。Spring Boot 官方在 1.4 版本后对此也不在支持
    thmeleaf新技术,功能较为强大,但是执行的效率比较低
    freemarker性能好,强大的模板语言、轻量
    +

    二.快速入门

    1.环境搭建

    1.1 创建一个demo工程

    此处为了方便建的是子模块,测试使用可以创建一个单独的工程

    +

    image-20230816145923876

    +

    1.2 引入依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!-- lombok -->
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>

    <!-- apache 对 java io 的封装工具库 -->
    <dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-io</artifactId>
    <version>1.3.2</version>
    </dependency>
    </dependencies>
    + +

    1.3 添加配置

    server:
    port: 8881 #服务端口
    spring:
    application:
    name: freemarker-demo #指定服务名
    freemarker:
    cache: false #关闭模板缓存,方便测试
    settings:
    template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl #指定Freemarker模板文件的后缀名,通常都是以ftl作为扩展名,也可以为html、xml、jsp等!
    + +

    1.4 创建模板

    在resources下创建templates,此目录为freemarker的默认模板存放目录

    +

    image-20230816150936881

    +

    在templates下创建模板文件 01-basic.ftl ,模板中的插值表达式最终会被freemarker替换成具体的数据

    +
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>
    <b>普通文本 String 展示:</b><br><br>
    Hello ${name} <br>
    <hr>
    <b>对象Student中的数据展示:</b><br/>
    姓名:${stu.name}<br/>
    年龄:${stu.age}
    <hr>
    </body>
    </html>
    + +

    1.5 创建启动类和模型类

    package com.heima.freemarker;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/16
    * @Description
    */
    @SpringBootApplication
    public class FreeMarkerDemoApplication {
    public static void main(String[] args) {
    SpringApplication.run(FreeMarkerDemoApplication.class,args);
    }
    }
    + +
    package com.heima.freemarker.entity;

    import lombok.Data;

    import java.util.Date;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/16
    * @Description
    */
    @Data
    public class Student {
    private String name;//姓名
    private int age;//年龄
    private Date birthday;//生日
    private Float money;//钱包
    }
    + +

    1.6 创建Controller

    package com.heima.freemarker.controller;

    import com.heima.freemarker.entity.Student;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/16
    * @Description
    */
    @Controller
    public class HelloController {

    @GetMapping("/basic")
    public String test(Model model) {
    //1.纯文本形式的参数
    model.addAttribute("name", "freemarker");

    //2.实体类相关的参数
    Student student = new Student();
    student.setName("小明");
    student.setAge(18);
    model.addAttribute("stu", student);

    return "01-basic";
    }
    }
    + +

    1.7 测试访问

    访问 http://localhost:8881/basic

    +

    image-20230816152750639

    +

    三.FreeMarker语法基础

    1.基础语法种类

    1、注释,即<#– –>,介于其之间的内容会被freemarker忽略

    +
    <#--我是一个freemarker注释-->
    + +

    2、插值(Interpolation):即 ${..} 部分,freemarker会用真实的值代替**${..}**

    +
    Hello ${name}
    + +

    3、FTL指令:和HTML标记类似,名字前加#予以区分,Freemarker会解析标签中的表达式或逻辑。

    +
    <# >FTL指令</#> 
    + +

    4、文本,仅文本信息,这些不是freemarker的注释、插值、FTL指令的内容会被freemarker忽略解析,直接输出内容。

    +
    <#--freemarker中的普通文本-->
    我是一个普通的文本
    + +

    2.集合指令

    image-20230816170028648

    +

    image-20230816170704421

    +

    1、数据模型:

    +

    在HelloController中新增如下方法:

    +
    @GetMapping("/list")
    public String list(Model model){

    //------------------------------------
    Student stu1 = new Student();
    stu1.setName("小强");
    stu1.setAge(18);
    stu1.setMoney(1000.86f);
    stu1.setBirthday(new Date());

    //小红对象模型数据
    Student stu2 = new Student();
    stu2.setName("小红");
    stu2.setMoney(200.1f);
    stu2.setAge(19);

    //将两个对象模型数据存放到List集合中
    List<Student> stus = new ArrayList<>();
    stus.add(stu1);
    stus.add(stu2);

    //向model中存放List集合数据
    model.addAttribute("stus",stus);

    //------------------------------------

    //创建Map数据
    HashMap<String,Student> stuMap = new HashMap<>();
    stuMap.put("stu1",stu1);
    stuMap.put("stu2",stu2);
    // 3.1 向model中存放Map数据
    model.addAttribute("stuMap", stuMap);

    return "02-list";
    }
    + +

    2、模板:

    +

    在templates中新增02-list.ftl文件

    +
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>

    <#-- list 数据的展示 -->
    <b>展示list中的stu数据:</b>
    <br>
    <br>
    <table>
    <tr>
    <td>序号</td>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    </table>
    <hr>

    <#-- Map 数据的展示 -->
    <b>map数据的展示:</b>
    <br/><br/>
    <a href="###">方式一:通过map['keyname'].property</a><br/>
    输出stu1的学生信息:<br/>
    姓名:<br/>
    年龄:<br/>
    <br/>
    <a href="###">方式二:通过map.keyname.property</a><br/>
    输出stu2的学生信息:<br/>
    姓名:<br/>
    年龄:<br/>

    <br/>
    <a href="###">遍历map中两个学生信息:</a><br/>
    <table>
    <tr>
    <td>序号</td>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    </table>
    <hr>

    </body>
    </html>
    + +

    实例代码:

    +
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>

    <#-- list 数据的展示 -->
    <b>展示list中的stu数据:</b>
    <br>
    <br>
    <table>
    <tr>
    <td>序号</td>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    <#list stus as stu>
    <tr>
    <td>${stu_index+1}</td>
    <td>${stu.name}</td>
    <td>${stu.age}</td>
    <td>${stu.money}</td>
    </tr>
    </#list>

    </table>
    <hr>

    <#-- Map 数据的展示 -->
    <b>map数据的展示:</b>
    <br/><br/>
    <a href="###">方式一:通过map['keyname'].property</a><br/>
    输出stu1的学生信息:<br/>
    姓名:${stuMap['stu1'].name}<br/>
    年龄:${stuMap['stu1'].age}<br/>
    <br/>
    <a href="###">方式二:通过map.keyname.property</a><br/>
    输出stu2的学生信息:<br/>
    姓名:${stuMap.stu2.name}<br/>
    年龄:${stuMap.stu2.age}<br/>

    <br/>
    <a href="###">遍历map中两个学生信息:</a><br/>
    <table>
    <tr>
    <td>序号</td>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    <#list stuMap?keys as key >
    <tr>
    <td>${key_index}</td>
    <td>${stuMap[key].name}</td>
    <td>${stuMap[key].age}</td>
    <td>${stuMap[key].money}</td>
    </tr>
    </#list>
    </table>
    <hr>

    </body>
    </html>
    + +

    上面代码解释:

    +

    ${k_index}:index:得到循环的下标,使用方法是在stu后边加”_index”,它的值是从0开始,${stu_index+1}下标从1开始

    +

    展示效果:

    +

    image-20230816170550343

    +

    3.if指令

    image-20230816170909497

    +

    if 指令即判断指令,是常用的FTL指令,freemarker在解析时遇到if会进行判断,条件为真则输出if中间的内容,否则跳过内容不再输出。

    +
      +
    • 指令格式
    • +
    +
    <#if ></if>
    + +

    1、数据模型:

    +

    使用list指令中测试数据模型,判断名称为小红的数据字体显示为红色。

    +

    2、模板:

    +
    <table>
    <tr>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    <#list stus as stu>
    <tr>
    <td >${stu.name}</td>
    <td>${stu.age}</td>
    <td >${stu.mondy}</td>
    </tr>
    </#list>

    </table>
    + +

    实例代码:

    +
    <table>
    <tr>
    <td>姓名</td>
    <td>年龄</td>
    <td>钱包</td>
    </tr>
    <#list stus as stu >
    <#if stu.name='小红'>
    <tr style="color: red">
    <td>${stu_index}</td>
    <td>${stu.name}</td>
    <td>${stu.age}</td>
    <td>${stu.money}</td>
    </tr>
    <#else >
    <tr>
    <td>${stu_index}</td>
    <td>${stu.name}</td>
    <td>${stu.age}</td>
    <td>${stu.money}</td>
    </tr>
    </#if>
    </#list>
    </table>
    + +

    3、输出:

    +

    image-20230816171119081

    +

    4.运算符

    1、算数运算符

    +

    FreeMarker表达式中完全支持算术运算,FreeMarker支持的算术运算符包括:

    +
      +
    • 加法: +
    • +
    • 减法: -
    • +
    • 乘法: *
    • +
    • 除法: /
    • +
    • 求模 (求余): %
    • +
    +

    模板代码

    +
    <b>算数运算符</b>
    <br/><br/>
    100+5 运算: ${100 + 5 }<br/>
    100 - 5 * 5运算:${100 - 5 * 5}<br/>
    5 / 2运算:${5 / 2}<br/>
    12 % 10运算:${12 % 10}<br/>
    <hr>
    + +

    除了 + 运算以外,其他的运算只能和 number 数字类型的计算。

    +

    2、比较运算符

    +
      +
    • =或者==:判断两个值是否相等.
    • +
    • !=:判断两个值是否不等.
    • +
    • >或者gt:判断左边值是否大于右边值
    • +
    • >=或者gte:判断左边值是否大于等于右边值
    • +
    • <或者lt:判断左边值是否小于右边值
    • +
    • <=或者lte:判断左边值是否小于等于右边值
    • +
    +

    = 和 == 模板代码

    +
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>

    <b>比较运算符</b>
    <br/>
    <br/>

    <dl>
    <dt> =/== 和 != 比较:</dt>
    <dd>
    <#if "xiaoming" == "xiaoming">
    字符串的比较 "xiaoming" == "xiaoming"
    </#if>
    </dd>
    <dd>
    <#if 10 != 100>
    数值的比较 10 != 100
    </#if>
    </dd>
    </dl>



    <dl>
    <dt>其他比较</dt>
    <dd>
    <#if 10 gt 5 >
    形式一:使用特殊字符比较数值 10 gt 5
    </#if>
    </dd>
    <dd>
    <#-- 日期的比较需要通过?date将属性转为data类型才能进行比较 -->
    <#if (date1?date >= date2?date)>
    形式二:使用括号形式比较时间 date1?date >= date2?date
    </#if>
    </dd>
    </dl>

    <br/>
    <hr>
    </body>
    </html>
    + +

    Controller 的 数据模型代码

    +
    @GetMapping("operation")
    public String testOperation(Model model) {
    //构建 Date 数据
    Date now = new Date();
    model.addAttribute("date1", now);
    model.addAttribute("date2", now);

    return "03-operation";
    }
    + +

    比较运算符注意

    +
      +
    • =!=可以用于字符串、数值和日期来比较是否相等
    • +
    • =!=两边必须是相同类型的值,否则会产生错误
    • +
    • 字符串 "x""x ""X"比较是不等的.因为FreeMarker是精确比较
    • +
    • 其它的运行符可以作用于数字和日期,但不能作用于字符串
    • +
    • 使用gt等字母运算符代替>会有更好的效果,因为 FreeMarker会把>解释成FTL标签的结束字符
    • +
    • 可以使用括号来避免这种情况,如:<#if (x>y)>
    • +
    +

    3、逻辑运算符

    +
      +
    • 逻辑与:&&
    • +
    • 逻辑或:||
    • +
    • 逻辑非:!
    • +
    +

    逻辑运算符只能作用于布尔值,否则将产生错误 。

    +

    模板代码

    +
    <b>逻辑运算符</b>
    <br/>
    <br/>
    <#if (10 lt 12 )&&( 10 gt 5 ) >
    (10 lt 12 )&&( 10 gt 5 ) 显示为 true
    </#if>
    <br/>
    <br/>
    <#if !false>
    false 取反为true
    </#if>
    <hr>
    + +

    5.空值处理

    1、判断某变量是否存在使用 “??”

    +

    用法为:variable??,如果该变量存在,返回true,否则返回false

    +

    例:为防止stus为空报错可以加上判断如下:

    +
    <#if stus??>
    <#list stus as stu>
    ......
    </#list>
    </#if>
    + +

    2、缺失变量默认值使用 “!”

    +
      +
    • 使用!要以指定一个默认值,当变量为空时显示默认值

      +

      例: ${name!’’}表示如果name为空显示空字符串。

      +
    • +
    • 如果是嵌套对象则建议使用()括起来

      +

      例: ${(stu.bestFriend.name)!’’}表示,如果stu或bestFriend或name为空默认显示空字符串。

      +
    • +
    +

    6.内建函数

    内建函数语法格式: 变量+?+函数名称

    +

    1、某个集合的大小

    +

    ${集合名?size}

    +

    2、日期格式化

    +

    显示年月日: ${today?date}

    +

    显示时分秒:${today?time}

    +

    显示日期+时间:${today?datetime}

    +

    自定义格式化: ${today?string("yyyy年MM月")}

    +

    3、内建函数c

    +

    model.addAttribute(“point”, 102920122);

    +

    point是数字型,使用${point}会显示这个数字的值,每三位使用逗号分隔。

    +

    如果不想显示为每三位分隔的数字,可以使用c函数将数字型转成字符串输出

    +

    ${point?c}

    +

    4、将json字符串转成对象

    +

    一个例子:

    +

    其中用到了 assign标签,assign的作用是定义一个变量。

    +
    <#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
    <#assign data=text?eval />
    开户行:${data.bank} 账号:${data.account}
    + +

    模板代码:

    +
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>inner Function</title>
    </head>
    <body>

    <b>获得集合大小</b><br>

    集合大小:
    <hr>


    <b>获得日期</b><br>

    显示年月日: <br>

    显示时分秒:<br>

    显示日期+时间:<br>

    自定义格式化: <br>

    <hr>

    <b>内建函数C</b><br>
    没有C函数显示的数值: <br>

    有C函数显示的数值:

    <hr>

    <b>声明变量assign</b><br>


    <hr>
    </body>
    </html>
    + +

    内建函数模板页面:

    +
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>inner Function</title>
    </head>
    <body>

    <b>获得集合大小</b><br>

    集合大小:${stus?size}
    <hr>


    <b>获得日期</b><br>

    显示年月日: ${today?date} <br>

    显示时分秒:${today?time}<br>

    显示日期+时间:${today?datetime}<br>

    自定义格式化: ${today?string("yyyy年MM月")}<br>

    <hr>

    <b>内建函数C</b><br>
    没有C函数显示的数值:${point} <br>

    有C函数显示的数值:${point?c}

    <hr>

    <b>声明变量assign</b><br>
    <#assign text="{'bank':'工商银行','account':'10101920201920212'}" />
    <#assign data=text?eval />
    开户行:${data.bank} 账号:${data.account}

    <hr>
    </body>
    </html>
    + +

    内建函数Controller数据模型:

    +
    @GetMapping("innerFunc")
    public String testInnerFunc(Model model) {
    //1.1 小强对象模型数据
    Student stu1 = new Student();
    stu1.setName("小强");
    stu1.setAge(18);
    stu1.setMoney(1000.86f);
    stu1.setBirthday(new Date());
    //1.2 小红对象模型数据
    Student stu2 = new Student();
    stu2.setName("小红");
    stu2.setMoney(200.1f);
    stu2.setAge(19);
    //1.3 将两个对象模型数据存放到List集合中
    List<Student> stus = new ArrayList<>();
    stus.add(stu1);
    stus.add(stu2);
    model.addAttribute("stus", stus);
    // 2.1 添加日期
    Date date = new Date();
    model.addAttribute("today", date);
    // 3.1 添加数值
    model.addAttribute("point", 102920122);
    return "04-innerFunc";
    }
    + +

    四.静态化测试

    1.思路分析

    image-20230816172633599

    +

    2.实现步骤

    根据模板文件生成html文件

    +

    ①:修改application.yml文件,添加以下模板存放位置的配置信息,完整配置如下:

    +
    server:
    port: 8881 #服务端口
    spring:
    application:
    name: freemarker-demo #指定服务名
    freemarker:
    cache: false #关闭模板缓存,方便测试
    settings:
    template_update_delay: 0 #检查模板更新延迟时间,设置为0表示立即检查,如果时间大于0会有缓存不方便进行模板测试
    suffix: .ftl #指定Freemarker模板文件的后缀名
    template-loader-path: classpath:/templates #模板存放位置
    + +

    ②:在test下创建测试类

    +
    package com.heima.freemarker.test;

    import com.heima.freemarker.FreeMarkerDemoApplication;
    import com.heima.freemarker.entity.Student;
    import freemarker.template.Configuration;
    import freemarker.template.Template;
    import freemarker.template.TemplateException;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;

    import java.io.FileWriter;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/16
    * @Description
    */
    @SpringBootTest(classes = FreeMarkerDemoApplication.class)
    @RunWith(SpringRunner.class)
    public class FreeMarkerTest {
    @Autowired
    //freemarker.template.Configuration
    private Configuration configuration;

    @Test
    public void test() throws IOException, TemplateException {
    Template template = configuration.getTemplate("01-basic.ftl");
    /**
    * 第一个参数:数据模型
    * 第二个参数:输出流
    */
    template.process(getData(),new FileWriter("C:\\Gong\\data\\test.html"));

    }

    /**
    * 构建数据模型
    */
    private Map<String,Object> getData(){
    HashMap<String, Object> map = new HashMap<>();
    //1.纯文本形式的参数
    map.put("name", "freemarker");
    //2.实体类相关的参数
    Student student = new Student();
    student.setName("小明");
    student.setAge(18);
    map.put("stu", student);
    return map;
    }
    }
    + +

    ③ :运行结果

    +

    image-20230816174550953

    +]]>
    + + 前端 + + + FreeMarker + +
    + + ElementUI使用示例 + /posts/50908.html + ElementUI官网

    +

    一.树形显示-树形控件

    1.树形显示

    示例-以谷粒商城项目的商品分类为例

    +

    数据中商品分类的数据表,所有的分类数据在同一张表中

    +image-20230530174911409 + +

    商品分类的实体类

    +
    package com.atguigu.gulimall.product.entity;

    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;

    import java.io.Serializable;
    import java.util.Date;
    import java.util.List;

    import lombok.Data;

    /**
    * 商品三级分类
    *
    * @author JasonGong
    * @email JasonGong@gmail.com
    * @date 2023-05-19 00:23:36
    */
    @Data
    @TableName("pms_category")
    public class CategoryEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
    * 分类id
    */
    @TableId
    private Long catId;
    /**
    * 分类名称
    */
    private String name;
    /**
    * 父分类id
    */
    private Long parentCid;
    /**
    * 层级
    */
    private Integer catLevel;
    /**
    * 是否显示[0-不显示,1显示]
    */
    private Integer showStatus;
    /**
    * 排序
    */
    private Integer sort;
    /**
    * 图标地址
    */
    private String icon;
    /**
    * 计量单位
    */
    private String productUnit;
    /**
    * 商品数量
    */
    private Integer productCount;

    @TableField(exist = false)//这个注解要加,因为这个属性不是数据库中的字段
    private List<CategoryEntity> children;//这里的属性名不是瞎起的,与ElementUI中的显示相对应,这里改了,前端也要改

    }
    + +

    封装数据,供前端显示

    +

    controller层

    +
    /**
    * 查询所有的分类以及子分类,以树形结构组装起来
    */
    @RequestMapping("/list/tree")
    public R list(){
    List<CategoryEntity> entities = categoryService.listWithTree();
    return R.ok().put("data", entities);
    }
    + +

    service层,这里主要有一个单独的递归方法,使用的时候会改就行

    +
    /**
    * 查询所有分类,以树形结构显示
    */
    @Override
    public List<CategoryEntity> listWithTree() {
    //查出所有的分类
    List<CategoryEntity> entities = categoryDao.selectList(null);
    //组装成父子的树形结构
    //找到所有的一级分类
    List<CategoryEntity> level1List = entities.stream().filter(categoryEntity ->
    //父级分类的id等于0的就是一级分类
    categoryEntity.getParentCid() == 0
    ).map((menu) -> {
    //为当前分类的子分类赋值
    menu.setChildren(getChildren(menu, entities));
    return menu;
    }).sorted((menu1, menu2) -> {
    //排序
    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());

    return level1List;
    }

    /**
    * 递归查询所有菜单的子菜单
    *
    * @param root 当前分类
    * @param all 所有的分类数据
    * @return 子菜单的集合
    */
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
    List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
    return categoryEntity.getParentCid().equals(root.getCatId());
    }).map(categoryEntity -> {
    //1.递归找子菜单
    categoryEntity.setChildren(getChildren(categoryEntity, all));
    return categoryEntity;
    }).sorted((menu1, menu2) -> {
    //2.菜单的排序
    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());
    return children;
    }
    + +

    返回数据的格式

    +
    {
    "msg": "success",
    "code": 0,
    "data": [
    {
    "catId": 1,
    "name": "图书、音像、电子书刊",
    "parentCid": 0,
    "catLevel": 1,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": [
    {
    "catId": 22,
    "name": "电子书刊",
    "parentCid": 1,
    "catLevel": 2,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": [
    {
    "catId": 165,
    "name": "电子书",
    "parentCid": 22,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 166,
    "name": "网络原创",
    "parentCid": 22,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 167,
    "name": "数字杂志",
    "parentCid": 22,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 168,
    "name": "多媒体图书",
    "parentCid": 22,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    }
    ]
    },
    {
    "catId": 23,
    "name": "音像",
    "parentCid": 1,
    "catLevel": 2,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": [
    {
    "catId": 169,
    "name": "音乐",
    "parentCid": 23,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 170,
    "name": "影视",
    "parentCid": 23,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    },
    {
    "catId": 171,
    "name": "教育音像",
    "parentCid": 23,
    "catLevel": 3,
    "showStatus": 1,
    "sort": 0,
    "icon": null,
    "productUnit": null,
    "productCount": 0,
    "children": []
    }
    ]
    },
    略.............................
    + +

    前端代码

    +

    模板

    +
    <template>
    <div>
    <!-- 树形控件 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    @node-click="handleNodeClick"
    ></el-tree>
    </div>
    </template>

    <script>
    export default {
    data() {
    return {
    data: [],//发送请求获取的数据(比如 return R.ok().put("data", entities);里面的data)
    defaultProps: {
    children: 'children',//实体类中的属性(比如:private List<CategoryEntity> children;)
    label: 'label'//需要显示的属性的属性名(比如商品分类名,属性名是name,这里就填name)
    }
    };
    },
    methods: {
    handleNodeClick(data) {
    console.log(data);
    }
    }
    };
    </script>
    + +

    完整代码

    +
    <template>
    <div>
    <!-- 树形控件 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    @node-click="handleNodeClick"
    ></el-tree>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {//这里的配置看官方的文档
    children: "children",//父节点上的子节点
    label: "name",//每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    handleNodeClick(data) {
    console.log(data);
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    显示效果

    +image-20230530181102832 + +

    2.树形控件的删除、添加

    页面的显示效果

    +

    image-20230605145343384

    +

    使用的组件 在树形控件的基础上添加相关的代码

    +image-20230605145433594 + +

    添加的时候弹出的对话框使用的组件

    +image-20230605153935967 + +image-20230605154629290 + +

    前端代码的实现

    +

    Tips:删除成功之后依然展开删除的节点

    +

    删除和添加的前端代码

    +
    <template>
    <div>
    <!-- 树形控件-->
    <!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
    <!-- show-checkbox表示开启选择框,批量选择-->
    <!-- node-key表示在整个树中唯一的表示 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <= 2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >
    添加
    </el-button>
    <!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
    <el-button
    v-if="data.children.length <= 0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >
    删除
    </el-button>
    </span>
    </span></el-tree
    >
    <!-- 添加分类的时候弹出的对话框 -->
    <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
    <el-form :model="category">
    <el-form-item label="分类名称">
    <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCategory">确 定</el-button>
    </span>
    </el-dialog>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {
    //这里的配置看官方的文档
    children: "children", //父节点上的子节点
    label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    expandedKey: [], //需要展开的数组,用于删除之后树形控件仍然展开
    dialogVisible: false, //添加菜单的对话框的开启或者关闭
    //表单绑定的对象
    category: {
    name: "", //分类的名称
    parentCid: 0, //父类的id
    catLevel: 0, //分类的级别
    showStatus: 1, //是否显示该分类
    sort: 0, //排序
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    //添加分类(弹出的对话框确定按钮执行的方法)
    addCategory() {
    console.log("提交的数据:", this.category);
    //将添加的数据返回给后端
    this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单添加成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus()
    //设置需要默认展开的菜单(依然展开刚才添加的节点)
    this.expandedKey = [this.category.parentCid];
    });
    //关闭对话框
    this.dialogVisible = false;
    },
    //添加分类的方法
    append(data) {
    //清空一下表单的数据
    this.category.name = "";
    //打开添加的对话框
    this.dialogVisible = true;
    //为分类的对象赋值
    this.category.parentCid = data.catId;
    this.category.catLevel = data.catLevel * 1 + 1; //防止catLevel是一个字符串
    },
    //删除节点的方法
    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才删除的节点)
    this.expandedKey = [node.parent.data.catId];
    });
    })
    .catch(() => {
    this.$message({
    type: "info",
    message: "已取消删除",
    });
    });
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    后端代码

    +

    删除的后端代码

    +
    /**
    * 删除
    */
    @RequestMapping("/delete")
    //@RequiresPermissions("product:category:delete")
    public R delete(@RequestBody Long[] catIds) {
    //检查当前删除的菜单,是否被别的地方引用
    //categoryService.removeByIds(Arrays.asList(catIds));
    categoryService.removeMenuByIds(Arrays.asList(catIds));
    return R.ok();
    }
    + +

    添加的后端代码

    +
    /**
    * 保存
    */
    @RequestMapping("/save")
    //@RequiresPermissions("product:category:save")
    public R save(@RequestBody CategoryEntity category) {
    categoryService.save(category);

    return R.ok();
    }
    + +

    实现的效果

    +

    image-20230605163004850

    +]]>
    + + 前端 + + + 前端 + +
    + + Java生成二维码 + /posts/62439.html +

    SpringBoot + zxing 生成二维码

    +

    SpringBoot + qrcode生成二维码

    +

    原视频地址: Java生成二维码教程 | 两小时学会Java生成二维码

    +

    源码地址: https://gitee.com/JasonsGong/two-dimensional-code

    + + +

    一.谷歌zxing开源库生成二维码

    1.创建一个sprinBoot项目

    image-20230828215513289

    +

    2.引入相关的依赖

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>core</artifactId>
    <version>3.1.0</version>
    </dependency>

    <dependency>
    <groupId>com.google.zxing</groupId>
    <artifactId>javase</artifactId>
    <version>3.1.0</version>
    </dependency>

    <dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    </dependencies>
    + +

    3.在templates目录下新建index.html文件

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>

    </body>
    </html>
    + +

    4.编写controller跳转到该页面,完成后重启项目,访问localhost:8080 测试

    @Controller
    public class CodeController {

    @GetMapping("/")
    public String index(){
    return "index";
    }
    }
    + +

    5.打开index.html,编写前端页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>二维码</title>
    </head>
    <body>
    <h1>谷歌zxing开源库生成黑白二维码</h1>
    <hr>
    请输入文本内容:<input type="text" id="url"><button onclick="generateQRCode()">生成二维码</button><br>
    <img id="qrCodeImg">
    <script>
    function generateQRCode(){
    //获取url
    let url = document.getElementById("url").value
    //设置img标签的src属性
    let qrCodeImg = document.getElementById("qrCodeImg")
    qrCodeImg.src = "/generate?url="+ url
    }
    </script>
    </body>
    </html>
    + +

    由于后端部分没有编写,这时我们访问项目的时候会返回404

    +

    image-20230828223201038

    +

    6.后端代码的编写

    package com.mine.code.controller;

    import com.google.zxing.BarcodeFormat;
    import com.google.zxing.EncodeHintType;
    import com.google.zxing.MultiFormatWriter;
    import com.google.zxing.WriterException;
    import com.google.zxing.aztec.encoder.AztecCode;
    import com.google.zxing.common.BitMatrix;
    import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.ResponseBody;

    import javax.imageio.ImageIO;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/28
    * @Description
    */
    @Slf4j
    @Controller
    public class CodeController {

    @GetMapping("/")
    public String index() {
    return "index";
    }


    @GetMapping("/generate")
    public String generate(@RequestParam("url") String url, HttpServletResponse response) {
    log.info("文本内容:{}", url);
    try {
    //创建一个map集合,存储二维码的相关属性
    Map map = new HashMap<>();
    //EncodeHintType 编码的提示类型
    //设置二维码的误差校正级别 可选值有 L(7%) M(15%) Q(25%) H(30%)
    //选择L级别的容错率,相当于允许二维码在整体的颜色区域中,最多有7%的坏像素点;择H级别的容错率,相当于允许二维码在整体的颜色区域中,最多有30%的坏像素点
    map.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    //设置二维码的字符集
    map.put(EncodeHintType.CHARACTER_SET, "utf-8");
    //设置二维码四周的留白 1表示1像素
    map.put(EncodeHintType.MARGIN, 1);
    //创建zxing的核心对象MultiFormatWriter (多格式写入器)
    //通过MultiFormatWriter对象来生成二维码
    MultiFormatWriter writer = new MultiFormatWriter();
    //参数一:内容
    //参数二:二维码格式
    //BarcodeFormat(码格式) QR_CODE :常见的二维码格式之一,广泛应用于商品包装、扫码支付
    //AZTEC_CODE:高密度,可靠性很高 容错率更低 储存个人信息、证件信息、账户密码
    //PDF417 可以存储大量的信息 数据密度高 应用于航空机票、配送标签、法律文件
    //DATA_MATRIX: 小巧的二维码格式 编码格式类似于QR_CODE 但是优于QR_CODE 适合嵌入简单的产品标签 医疗图像 检测数据
    //参数三四:二维码的宽度和高度
    //参数五:二维码参数
    //位矩阵对象 (位矩阵对象对象的内部实际上是一个二位数组,二维数组中每一个元素是boolean类型 true代表黑色 false代表白色)
    BitMatrix bitMatrix = writer.encode(url, BarcodeFormat.QR_CODE, 300, 300, map);
    //获取矩阵的宽度和高度
    int width = bitMatrix.getWidth();
    int height = bitMatrix.getHeight();
    //生成二维码图片
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
    //遍历位矩阵对象
    for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
    //设置每一块的颜色值
    //0xFF000000表示黑色 0xFFFFFFFF表示白色
    image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
    }
    }
    //将图片响应到客户端
    ServletOutputStream out = response.getOutputStream();
    ImageIO.write(image,"png",out);
    out.flush();
    out.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    7.测试生成二维码

    image-20230828233052417

    +

    完整的项目结构

    +

    image-20230828233917645

    +

    8.生成一个带Logo的黑白二维码

    1.编写qrcode.html页面和跳转到该页面的controller

    +
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>生成带logo的黑白二维码</title>
    </head>
    <body>
    <form action="/generateWithLogo" method="post" enctype="multipart/form-data">
    请输入文本内容:<input type="text" name="url"><br>
    请选择logo图片:<input type="file" name="logo"><br>
    <input type="submit" value="生成二维码">
    </form>
    </body>
    </html>
    + +
    /**
    * 跳转到生成带logo的黑白二维码
    */
    @GetMapping("/logo")
    public String toLogo() {
    return "qrcode";
    }
    + +

    2.编写后端生成二维码逻辑

    +
    /**
    * 生成带logo的黑白二维码
    */
    @PostMapping("/generateWithLogo")
    public String generateWithLogo(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request) {
    try {
    Map map = new HashMap<>();
    map.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
    map.put(EncodeHintType.CHARACTER_SET, "utf-8");
    map.put(EncodeHintType.MARGIN, 1);
    MultiFormatWriter writer = new MultiFormatWriter();
    BitMatrix bitMatrix = writer.encode(url, BarcodeFormat.QR_CODE, 300, 300, map);
    int width = bitMatrix.getWidth();
    int height = bitMatrix.getHeight();
    BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
    for (int x = 0; x < width; x++) {
    for (int y = 0; y < height; y++) {
    image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
    }
    }
    //给二维码添加logo
    //1.获取logo
    Part logoPart = request.getPart("logo");
    InputStream inputStream = logoPart.getInputStream();
    BufferedImage logoImage = ImageIO.read(inputStream);
    //2.对获取的logo图片进行缩放
    int logoWidth = logoImage.getWidth(null);
    int logoHeight = logoImage.getHeight(null);
    if (logoWidth > 60){
    logoWidth = 60;
    }
    if (logoHeight > 60){
    logoHeight = 60;
    }
    //使用平滑缩放算法对原始的logo图像进行缩放到一个全新的图像
    Image scaledLogo = logoImage.getScaledInstance(logoWidth, logoHeight, Image.SCALE_SMOOTH);
    //3.将缩放的图片画在黑白的二维码上
    //获取一个画笔
    Graphics2D graphics2D = image.createGraphics();
    //计算从哪里开始画 300指的是二维码的宽度和高度
    int x = (300 - logoWidth) /2;
    int y = (300 - logoHeight) /2;
    //画上去
    graphics2D.drawImage(scaledLogo,x,y,null);
    //实现logo的圆角效果
    Shape shape = new RoundRectangle2D.Float(x, y, logoWidth, logoHeight, 10, 10);
    //使用一个宽度为4像素的基本笔触
    graphics2D.setStroke(new BasicStroke(4f));
    //给logo画圆角矩形
    graphics2D.draw(shape);
    //释放画笔
    graphics2D.dispose();
    //将二维码响应到浏览器
    ImageIO.write(image, "png", response.getOutputStream());
    //关闭流
    inputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    + +

    访问 http://localhost:8080/logo 测试

    +

    实现的效果

    +

    image-20230829160901904

    +

    image-20230829160938795

    +

    二.github开源项目qrcode生成二维码

    1.引入依赖文件

    这里就不在重新创建工程了,直接在上面工程的基础上操作

    +
    <dependency>
    <groupId>com.github.liuyueyi.media</groupId>
    <artifactId>qrcode-plugin</artifactId>
    <version>2.5.2</version>
    </dependency>
    + +

    2.生成黑白二维码

    1.创建github-qrcode.html文件

    +
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>qrcode生成黑白二维码</title>
    </head>
    <body>
    <h1>使用github上的开源项目qrcode生成二维码</h1>
    <hr>
    <form action="/generateWithQrCode" method="post" enctype="multipart/form-data">
    请输入文本内容:<input type="text" name="url"><br>
    请选择logo图片:<input type="file" name="logo"><br>
    <input type="submit" value="生成二维码">
    </form>
    </body>
    </html>
    + +

    2.编写controller处理请求

    +
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.image.BufferedImage;
    import java.io.IOException;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成二维码
    BufferedImage image = QrCodeGenWrapper.of(url).asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    3.访问测试

    +

    image-20230829223442421

    +

    3.生成带有logo的黑白二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成二维码
    //生成黑白的二维码
    //BufferedImage image = QrCodeGenWrapper.of(url).asBufferedImage();
    //生成带logo的黑白二维码
    InputStream inputStream = request.getPart("logo").getInputStream();
    BufferedImage image = QrCodeGenWrapper.of(url)
    .setLogo(inputStream)//logo图片的输入流
    .setLogoRate(7)//设置logo图片和二维码之间的比例,7表示logo的宽度等于二维码的1/7
    .setLogoStyle(QrCodeOptions.LogoStyle.ROUND)//设置logo图片的样式,将logo的边框形状设置成圆形
    .asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    //关闭流
    inputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    image-20230829224339809

    +

    4.生成彩色的二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成彩色的二维码
    BufferedImage image = QrCodeGenWrapper.of(url).setDrawPreColor(Color.GREEN).asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    image-20230829225017032

    +

    5.生成带有背景图的二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成带有背景图的二维码
    InputStream inputStream = request.getPart("logo").getInputStream();
    BufferedImage image = QrCodeGenWrapper.of(url)
    .setBgImg(inputStream)
    .setBgOpacity(0.5F)//设置透明度
    .asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    //关闭流
    inputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    image-20230829225740156

    +

    6.生成特殊形状的二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode(){
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request){
    try {
    //生成特殊形状的二维码
    BufferedImage image = QrCodeGenWrapper.of(url)
    .setDrawEnableScale(true)//启用二维码绘制时的缩放功能
    .setDrawStyle(QrCodeOptions.DrawStyle.DIAMOND)//绘制钻石形状的二维码
    .asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image,"png",response.getOutputStream());
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    image-20230829230236500

    +

    7.生成图片填充二维码

    前端页面延续使用上面的github-qrcode.html

    +

    1.后端代码的编写

    +
    package com.mine.code.controller;

    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeDeWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeGenWrapper;
    import com.github.hui.quick.plugin.qrcode.wrapper.QrCodeOptions;
    import com.google.zxing.WriterException;
    import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;

    import javax.imageio.ImageIO;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.IOException;
    import java.io.InputStream;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/29
    * @Description 使用qrcode生成二维码
    */
    @Controller
    public class GithubQrCodeController {
    /**
    * 编写请求跳转到使用qrcode生成二维码的页面
    */
    @GetMapping("/qrcode")
    public String toQrCode() {
    return "github-qrcode";
    }


    /**
    * 使用qrcode生成黑白二维码
    */
    @PostMapping("/generateWithQrCode")
    public String generateWithQrCode(@RequestParam("url") String url, HttpServletResponse response, HttpServletRequest request) {
    try {
    //生成图片填充二维码
    InputStream inputStream = request.getPart("logo").getInputStream();
    BufferedImage image = QrCodeGenWrapper.of(url)
    .setErrorCorrection(ErrorCorrectionLevel.H)//设置二维码的纠正级别
    .setDrawStyle(QrCodeOptions.DrawStyle.IMAGE)//绘制二维码的时候采用图片填充
    .addImg(1, 1, inputStream)//添加图片
    .asBufferedImage();
    //将生成的二维码响应到浏览器
    ImageIO.write(image, "png", response.getOutputStream());
    //关闭流
    inputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    }
    + +

    实现的效果

    +

    二维码中的每一个像素点都是上传的图片,生成的速度很慢

    +

    image-20230829230843477

    +]]>
    + + 后端 + + + 二维码 + +
    + + Maven配置文件settings.xml + /posts/26768.html + 更换IDEA中Maven配置文件实现不下载配置maven的阿里云镜像

    +

    设置本地仓库磁盘存储位置

    1.在C盘的根目录下创建如下的文件夹C:\maven\maven-repository

    +

    2.任选下面的两个配置文件中的一个放在C:\maven目录下 配置文件的名字为 settings.xml

    +

    3.在IDEA中配置本地仓库的位置和配置文件的位置

    +

    配置文件

    apache-maven-3.6.3

    <?xml version="1.0" encoding="UTF-8"?>

    <!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements. See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership. The ASF licenses this file
    to you 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.
    -->

    <!--
    | This is the configuration file for Maven. It can be specified at two levels:
    |
    | 1. User Level. This settings.xml file provides configuration for a single user,
    | and is normally provided in ${user.home}/.m2/settings.xml.
    |
    | NOTE: This location can be overridden with the CLI option:
    |
    | -s /path/to/user/settings.xml
    |
    | 2. Global Level. This settings.xml file provides configuration for all Maven
    | users on a machine (assuming they're all using the same Maven
    | installation). It's normally provided in
    | ${maven.conf}/settings.xml.
    |
    | NOTE: This location can be overridden with the CLI option:
    |
    | -gs /path/to/global/settings.xml
    |
    | The sections in this sample file are intended to give you a running start at
    | getting the most out of your Maven installation. Where appropriate, the default
    | values (values used when the setting is not specified) are provided.
    |
    |-->
    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <!-- localRepository
    | The path to the local repository maven will use to store artifacts.
    |
    | Default: ${user.home}/.m2/repository
    <localRepository>/path/to/local/repo</localRepository>
    -->
    <localRepository>C:\maven\maven-repository</localRepository>
    <!-- interactiveMode
    | This will determine whether maven prompts you when it needs input. If set to false,
    | maven will use a sensible default value, perhaps based on some other setting, for
    | the parameter in question.
    |
    | Default: true
    <interactiveMode>true</interactiveMode>
    -->

    <!-- offline
    | Determines whether maven should attempt to connect to the network when executing a build.
    | This will have an effect on artifact downloads, artifact deployment, and others.
    |
    | Default: false
    <offline>false</offline>
    -->

    <!-- pluginGroups
    | This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
    | when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
    | "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
    |-->
    <pluginGroups>
    <!-- pluginGroup
    | Specifies a further group identifier to use for plugin lookup.
    <pluginGroup>com.your.plugins</pluginGroup>
    -->
    </pluginGroups>

    <!-- proxies
    | This is a list of proxies which can be used on this machine to connect to the network.
    | Unless otherwise specified (by system property or command-line switch), the first proxy
    | specification in this list marked as active will be used.
    |-->
    <proxies>
    <!-- proxy
    | Specification for one proxy, to be used in connecting to the network.
    |
    <proxy>
    <id>optional</id>
    <active>true</active>
    <protocol>http</protocol>
    <username>proxyuser</username>
    <password>proxypass</password>
    <host>proxy.host.net</host>
    <port>80</port>
    <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
    </proxy>
    -->
    </proxies>

    <!-- servers
    | This is a list of authentication profiles, keyed by the server-id used within the system.
    | Authentication profiles can be used whenever maven must make a connection to a remote server.
    |-->
    <servers>
    <!-- server
    | Specifies the authentication information to use when connecting to a particular server, identified by
    | a unique name within the system (referred to by the 'id' attribute below).
    |
    | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
    | used together.
    |
    <server>
    <id>deploymentRepo</id>
    <username>repouser</username>
    <password>repopwd</password>
    </server>
    -->

    <!-- Another sample, using keys to authenticate.
    <server>
    <id>siteServer</id>
    <privateKey>/path/to/private/key</privateKey>
    <passphrase>optional; leave empty if not used.</passphrase>
    </server>
    -->
    </servers>

    <!-- mirrors
    | This is a list of mirrors to be used in downloading artifacts from remote repositories.
    |
    | It works like this: a POM may declare a repository to use in resolving certain artifacts.
    | However, this repository may have problems with heavy traffic at times, so people have mirrored
    | it to several places.
    |
    | That repository definition will have a unique id, so we can create a mirror reference for that
    | repository, to be used as an alternate download site. The mirror site will be the preferred
    | server for that repository.
    |-->
    <mirrors>
    <!-- mirror
    | Specifies a repository mirror site to use instead of a given repository. The repository that
    | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
    | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
    |
    <mirror>
    <id>mirrorId</id>
    <mirrorOf>repositoryId</mirrorOf>
    <name>Human Readable Name for this Mirror.</name>
    <url>http://my.repository.com/repo/path</url>
    </mirror>
    -->
    <!-- 阿里云仓库,个人配置 -->
    <mirror>
    <id>alimaven</id>
    <mirrorOf>central</mirrorOf>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
    </mirror>
    </mirrors>

    <!-- profiles
    | This is a list of profiles which can be activated in a variety of ways, and which can modify
    | the build process. Profiles provided in the settings.xml are intended to provide local machine-
    | specific paths and repository locations which allow the build to work in the local environment.
    |
    | For example, if you have an integration testing plugin - like cactus - that needs to know where
    | your Tomcat instance is installed, you can provide a variable here such that the variable is
    | dereferenced during the build process to configure the cactus plugin.
    |
    | As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
    | section of this document (settings.xml) - will be discussed later. Another way essentially
    | relies on the detection of a system property, either matching a particular value for the property,
    | or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
    | value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
    | Finally, the list of active profiles can be specified directly from the command line.
    |
    | NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
    | repositories, plugin repositories, and free-form properties to be used as configuration
    | variables for plugins in the POM.
    |
    |-->
    <profiles>
    <!-- profile
    | Specifies a set of introductions to the build process, to be activated using one or more of the
    | mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
    | or the command line, profiles have to have an ID that is unique.
    |
    | An encouraged best practice for profile identification is to use a consistent naming convention
    | for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
    | This will make it more intuitive to understand what the set of introduced profiles is attempting
    | to accomplish, particularly when you only have a list of profile id's for debug.
    |
    | This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
    <profile>
    <id>jdk-1.4</id>

    <activation>
    <jdk>1.4</jdk>
    </activation>

    <repositories>
    <repository>
    <id>jdk14</id>
    <name>Repository for JDK 1.4 builds</name>
    <url>http://www.myhost.com/maven/jdk14</url>
    <layout>default</layout>
    <snapshotPolicy>always</snapshotPolicy>
    </repository>
    </repositories>
    </profile>
    -->

    <!--
    | Here is another profile, activated by the system property 'target-env' with a value of 'dev',
    | which provides a specific path to the Tomcat instance. To use this, your plugin configuration
    | might hypothetically look like:
    |
    | ...
    | <plugin>
    | <groupId>org.myco.myplugins</groupId>
    | <artifactId>myplugin</artifactId>
    |
    | <configuration>
    | <tomcatLocation>${tomcatPath}</tomcatLocation>
    | </configuration>
    | </plugin>
    | ...
    |
    | NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
    | anything, you could just leave off the <value/> inside the activation-property.
    |
    <profile>
    <id>env-dev</id>

    <activation>
    <property>
    <name>target-env</name>
    <value>dev</value>
    </property>
    </activation>

    <properties>
    <tomcatPath>/path/to/tomcat/instance</tomcatPath>
    </properties>
    </profile>
    -->
    </profiles>

    <!-- activeProfiles
    | List of profiles that are active for all builds.
    |
    <activeProfiles>
    <activeProfile>alwaysActiveProfile</activeProfile>
    <activeProfile>anotherAlwaysActiveProfile</activeProfile>
    </activeProfiles>
    -->
    </settings>
    + +

    apache-maven-3.8.6

    <?xml version="1.0" encoding="UTF-8"?>

    <!--
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements. See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership. The ASF licenses this file
    to you 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.
    -->

    <!--
    | This is the configuration file for Maven. It can be specified at two levels:
    |
    | 1. User Level. This settings.xml file provides configuration for a single user,
    | and is normally provided in ${user.home}/.m2/settings.xml.
    |
    | NOTE: This location can be overridden with the CLI option:
    |
    | -s /path/to/user/settings.xml
    |
    | 2. Global Level. This settings.xml file provides configuration for all Maven
    | users on a machine (assuming they're all using the same Maven
    | installation). It's normally provided in
    | ${maven.conf}/settings.xml.
    |
    | NOTE: This location can be overridden with the CLI option:
    |
    | -gs /path/to/global/settings.xml
    |
    | The sections in this sample file are intended to give you a running start at
    | getting the most out of your Maven installation. Where appropriate, the default
    | values (values used when the setting is not specified) are provided.
    |
    |-->
    <settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
    <!-- localRepository
    | The path to the local repository maven will use to store artifacts.
    |
    | Default: ${user.home}/.m2/repository
    <localRepository>/path/to/local/repo</localRepository>
    -->
    <localRepository>C:\maven\maven-repository</localRepository>

    <!-- interactiveMode
    | This will determine whether maven prompts you when it needs input. If set to false,
    | maven will use a sensible default value, perhaps based on some other setting, for
    | the parameter in question.
    |
    | Default: true
    <interactiveMode>true</interactiveMode>
    -->

    <!-- offline
    | Determines whether maven should attempt to connect to the network when executing a build.
    | This will have an effect on artifact downloads, artifact deployment, and others.
    |
    | Default: false
    <offline>false</offline>
    -->

    <!-- pluginGroups
    | This is a list of additional group identifiers that will be searched when resolving plugins by their prefix, i.e.
    | when invoking a command line like "mvn prefix:goal". Maven will automatically add the group identifiers
    | "org.apache.maven.plugins" and "org.codehaus.mojo" if these are not already contained in the list.
    |-->
    <pluginGroups>
    <!-- pluginGroup
    | Specifies a further group identifier to use for plugin lookup.
    <pluginGroup>com.your.plugins</pluginGroup>
    -->
    </pluginGroups>

    <!-- proxies
    | This is a list of proxies which can be used on this machine to connect to the network.
    | Unless otherwise specified (by system property or command-line switch), the first proxy
    | specification in this list marked as active will be used.
    |-->
    <proxies>
    <!-- proxy
    | Specification for one proxy, to be used in connecting to the network.
    |
    <proxy>
    <id>optional</id>
    <active>true</active>
    <protocol>http</protocol>
    <username>proxyuser</username>
    <password>proxypass</password>
    <host>proxy.host.net</host>
    <port>80</port>
    <nonProxyHosts>local.net|some.host.com</nonProxyHosts>
    </proxy>
    -->
    </proxies>

    <!-- servers
    | This is a list of authentication profiles, keyed by the server-id used within the system.
    | Authentication profiles can be used whenever maven must make a connection to a remote server.
    |-->
    <servers>
    <!-- server
    | Specifies the authentication information to use when connecting to a particular server, identified by
    | a unique name within the system (referred to by the 'id' attribute below).
    |
    | NOTE: You should either specify username/password OR privateKey/passphrase, since these pairings are
    | used together.
    |
    <server>
    <id>deploymentRepo</id>
    <username>repouser</username>
    <password>repopwd</password>
    </server>
    -->

    <!-- Another sample, using keys to authenticate.
    <server>
    <id>siteServer</id>
    <privateKey>/path/to/private/key</privateKey>
    <passphrase>optional; leave empty if not used.</passphrase>
    </server>
    -->
    </servers>

    <!-- mirrors
    | This is a list of mirrors to be used in downloading artifacts from remote repositories.
    |
    | It works like this: a POM may declare a repository to use in resolving certain artifacts.
    | However, this repository may have problems with heavy traffic at times, so people have mirrored
    | it to several places.
    |
    | That repository definition will have a unique id, so we can create a mirror reference for that
    | repository, to be used as an alternate download site. The mirror site will be the preferred
    | server for that repository.
    |-->
    <mirrors>
    <!-- mirror
    | Specifies a repository mirror site to use instead of a given repository. The repository that
    | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
    | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
    |
    <mirror>
    <id>mirrorId</id>
    <mirrorOf>repositoryId</mirrorOf>
    <name>Human Readable Name for this Mirror.</name>
    <url>http://my.repository.com/repo/path</url>
    </mirror>
    -->
    <!--<mirror>
    <id>maven-default-http-blocker</id>
    <mirrorOf>external:http:*</mirrorOf>
    <name>Pseudo repository to mirror external repositories initially using HTTP.</name>
    <url>http://0.0.0.0/</url>
    <blocked>true</blocked>
    </mirror>-->
    <mirror>
    <id>nexus-aliyun</id>
    <mirrorOf>central</mirrorOf>
    <name>Nexus aliyun</name>
    <url>http://maven.aliyun.com/nexus/content/groups/public</url>
    </mirror>
    </mirrors>

    <!-- profiles
    | This is a list of profiles which can be activated in a variety of ways, and which can modify
    | the build process. Profiles provided in the settings.xml are intended to provide local machine-
    | specific paths and repository locations which allow the build to work in the local environment.
    |
    | For example, if you have an integration testing plugin - like cactus - that needs to know where
    | your Tomcat instance is installed, you can provide a variable here such that the variable is
    | dereferenced during the build process to configure the cactus plugin.
    |
    | As noted above, profiles can be activated in a variety of ways. One way - the activeProfiles
    | section of this document (settings.xml) - will be discussed later. Another way essentially
    | relies on the detection of a system property, either matching a particular value for the property,
    | or merely testing its existence. Profiles can also be activated by JDK version prefix, where a
    | value of '1.4' might activate a profile when the build is executed on a JDK version of '1.4.2_07'.
    | Finally, the list of active profiles can be specified directly from the command line.
    |
    | NOTE: For profiles defined in the settings.xml, you are restricted to specifying only artifact
    | repositories, plugin repositories, and free-form properties to be used as configuration
    | variables for plugins in the POM.
    |
    |-->
    <profiles>
    <!-- profile
    | Specifies a set of introductions to the build process, to be activated using one or more of the
    | mechanisms described above. For inheritance purposes, and to activate profiles via <activatedProfiles/>
    | or the command line, profiles have to have an ID that is unique.
    |
    | An encouraged best practice for profile identification is to use a consistent naming convention
    | for profiles, such as 'env-dev', 'env-test', 'env-production', 'user-jdcasey', 'user-brett', etc.
    | This will make it more intuitive to understand what the set of introduced profiles is attempting
    | to accomplish, particularly when you only have a list of profile id's for debug.
    |
    | This profile example uses the JDK version to trigger activation, and provides a JDK-specific repo.
    <profile>
    <id>jdk-1.4</id>

    <activation>
    <jdk>1.4</jdk>
    </activation>

    <repositories>
    <repository>
    <id>jdk14</id>
    <name>Repository for JDK 1.4 builds</name>
    <url>http://www.myhost.com/maven/jdk14</url>
    <layout>default</layout>
    <snapshotPolicy>always</snapshotPolicy>
    </repository>
    </repositories>
    </profile>
    -->

    <!--
    | Here is another profile, activated by the system property 'target-env' with a value of 'dev',
    | which provides a specific path to the Tomcat instance. To use this, your plugin configuration
    | might hypothetically look like:
    |
    | ...
    | <plugin>
    | <groupId>org.myco.myplugins</groupId>
    | <artifactId>myplugin</artifactId>
    |
    | <configuration>
    | <tomcatLocation>${tomcatPath}</tomcatLocation>
    | </configuration>
    | </plugin>
    | ...
    |
    | NOTE: If you just wanted to inject this configuration whenever someone set 'target-env' to
    | anything, you could just leave off the <value/> inside the activation-property.
    |
    <profile>
    <id>env-dev</id>

    <activation>
    <property>
    <name>target-env</name>
    <value>dev</value>
    </property>
    </activation>

    <properties>
    <tomcatPath>/path/to/tomcat/instance</tomcatPath>
    </properties>
    </profile>
    -->
    <profile>
    <id>jdk-1.8</id>
    <activation>
    <activeByDefault>true</activeByDefault>
    <jdk>1.8</jdk>
    </activation>
    <properties>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
    </properties>
    </profile>
    </profiles>

    <!-- activeProfiles
    | List of profiles that are active for all builds.
    |
    <activeProfiles>
    <activeProfile>alwaysActiveProfile</activeProfile>
    <activeProfile>anotherAlwaysActiveProfile</activeProfile>
    </activeProfiles>
    -->
    </settings>
    + +

    Linux中使用,去掉了注释

    <?xml version="1.0" encoding="UTF-8"?>

    <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">

    <localRepository>/root/.m2/repository</localRepository>
    <pluginGroups>

    </pluginGroups>

    <proxies>

    </proxies>

    <servers>

    </servers>

    <mirrors>

    <mirror>
    <id>alimaven</id>
    <mirrorOf>central</mirrorOf>
    <name>aliyun maven</name>
    <url>http://maven.aliyun.com/nexus/content/repositories/central/</url>
    </mirror>
    </mirrors>

    <profiles>

    </profiles>

    </settings>
    + + + + + +]]>
    + + 后端 + + + Java + +
    + + 代码注释模板 + /posts/1727.html + VScode插件推荐:koroFileHeader (用于生成文件头部注释和函数注释的插件)

    +

    完整的模板大全:佛祖保佑永无BUG、神兽护体、注释图案 · OBKoro1/koro1FileHeader Wiki (github.com)

    +

    image-20230912105041466

    +

    佛祖保佑一


    ////////////////////////////////////////////////////////////////////
    // _ooOoo_ //
    // o8888888o //
    // 88" . "88 //
    // (| ^_^ |) //
    // O\ = /O //
    // ____/`---'\____ //
    // .' \\| |// `. //
    // / \\||| : |||// \ //
    // / _||||| -:- |||||- \ //
    // | | \\\ - /// | | //
    // | \_| ''\---/'' | | //
    // \ .-\__ `-` ___/-. / //
    // ___`. .' /--.--\ `. . ___ //
    // ."" '< `.___\_<|>_/___.' >'"". //
    // | | : `- \`.;`\ _ /`;.`/ - ` : | | //
    // \ \ `-. \_ __\ /__ _/ .-` / / //
    // ========`-.____`-.___\_____/___.-`____.-'======== //
    // `=---=' //
    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //
    // 佛祖保佑 永无BUG 永不修改 //
    ////////////////////////////////////////////////////////////////////
    + +

    佛祖保佑一

    /**
    * _ooOoo_
    * o8888888o
    * 88" . "88
    * (| -_- |)
    * O\ = /O
    * ____/`---'\____
    * .' \\| |// `.
    * / \\||| : |||// \
    * / _||||| -:- |||||- \
    * | | \\\ - /// | |
    * | \_| ''\---/'' | |
    * \ .-\__ `-` ___/-. /
    * ___`. .' /--.--\ `. . __
    * ."" '< `.___\_<|>_/___.' >'"".
    * | | : `- \`.;`\ _ /`;.`/ - ` : | |
    * \ \ `-. \_ __\ /__ _/ .-` / /
    * ======`-.____`-.___\_____/___.-`____.-'======
    * `=---='
    * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    * 佛祖保佑 永无BUG
    * 佛曰:
    * 写字楼里写字间,写字间里程序员;
    * 程序人员写程序,又拿程序换酒钱。
    * 酒醒只在网上坐,酒醉还来网下眠;
    * 酒醉酒醒日复日,网上网下年复年。
    * 但愿老死电脑间,不愿鞠躬老板前;
    * 奔驰宝马贵者趣,公交自行程序员。
    * 别人笑我忒疯癫,我笑自己命太贱;
    * 不见满街漂亮妹,哪个归得程序员?
    */
    + +

    草泥马一

    /*
    *
    *   ┏┓   ┏┓+ +
    *  ┏┛┻━━━┛┻┓ + +
    *  ┃       ┃  
    *  ┃   ━   ┃ ++ + + +
    * ████━████ ┃+
    *  ┃       ┃ +
    *  ┃   ┻   ┃
    *  ┃       ┃ + +
    *  ┗━┓   ┏━┛
    *    ┃   ┃           
    *    ┃   ┃ + + + +
    *    ┃   ┃
    *    ┃   ┃ + 神兽保佑
    *    ┃   ┃ 代码无bug  
    *    ┃   ┃  +         
    *    ┃    ┗━━━┓ + +
    *    ┃        ┣┓
    *    ┃        ┏┛
    *    ┗┓┓┏━┳┓┏┛ + + + +
    *     ┃┫┫ ┃┫┫
    *     ┗┻┛ ┗┻┛+ + + +
    *
    */
    + +

    草泥马二

    /*
    *
    * ┏┓   ┏┓
    * ┏┛┻━━━┛┻┓
    * ┃       ┃
    * ┃   ━   ┃
    * ┃ >   < ┃
    * ┃       ┃
    * ┃... ⌒ ... ┃
    * ┃       ┃
    * ┗━┓   ┏━┛
    * ┃   ┃ 
    * ┃   ┃
    * ┃   ┃
    * ┃   ┃ 神兽保佑
    * ┃   ┃ 代码无bug  
    * ┃   ┃
    * ┃   ┗━━━┓
    * ┃       ┣┓
    * ┃       ┏┛
    * ┗┓┓┏━┳┓┏┛
    * ┃┫┫ ┃┫┫
    * ┗┻┛ ┗┻┛
    */
    + +

    全键盘

    /*
    * ┌───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┬───┐ ┌───┬───┬───┐
    * │Esc│ │ F1│ F2│ F3│ F4│ │ F5│ F6│ F7│ F8│ │ F9│F10│F11│F12│ │P/S│S L│P/B│ ┌┐ ┌┐ ┌┐
    * └───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┴───┘ └───┴───┴───┘ └┘ └┘ └┘
    * ┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───────┐ ┌───┬───┬───┐ ┌───┬───┬───┬───┐
    * │~ `│! 1│@ 2│# 3│$ 4│% 5│^ 6│& 7│* 8│( 9│) 0│_ -│+ =│ BacSp │ │Ins│Hom│PUp│ │N L│ / │ * │ - │
    * ├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─────┤ ├───┼───┼───┤ ├───┼───┼───┼───┤
    * │ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{ [│} ]│ | \ │ │Del│End│PDn│ │ 7 │ 8 │ 9 │ │
    * ├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤ └───┴───┴───┘ ├───┼───┼───┤ + │
    * │ Caps │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter │ │ 4 │ 5 │ 6 │ │
    * ├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────────┤ ┌───┐ ├───┼───┼───┼───┤
    * │ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│ Shift │ │ ↑ │ │ 1 │ 2 │ 3 │ │
    * ├─────┬──┴─┬─┴──┬┴───┴───┴───┴───┴───┴──┬┴───┼───┴┬────┬────┤ ┌───┼───┼───┐ ├───┴───┼───┤ E││
    * │ Ctrl│ │Alt │ Space │ Alt│ │ │Ctrl│ │ ← │ ↓ │ → │ │ 0 │ . │←─┘│
    * └─────┴────┴────┴───────────────────────┴────┴────┴────┴────┘ └───┴───┴───┘ └───────┴───┴───┘
    */
    + +

    小键盘

    /*
    * ┌─────────────────────────────────────────────────────────────┐
    * │┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐│
    * ││Esc│!1 │@2 │#3 │$4 │%5 │^6 │&7 │*8 │(9 │)0 │_- │+= │|\ │`~ ││
    * │├───┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴───┤│
    * ││ Tab │ Q │ W │ E │ R │ T │ Y │ U │ I │ O │ P │{[ │}] │ BS ││
    * │├─────┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴┬──┴─────┤│
    * ││ Ctrl │ A │ S │ D │ F │ G │ H │ J │ K │ L │: ;│" '│ Enter ││
    * │├──────┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴─┬─┴────┬───┤│
    * ││ Shift │ Z │ X │ C │ V │ B │ N │ M │< ,│> .│? /│Shift │Fn ││
    * │└─────┬──┴┬──┴──┬┴───┴───┴───┴───┴───┴──┬┴───┴┬──┴┬─────┴───┘│
    * │ │Fn │ Alt │ Space │ Alt │Win│ HHKB │
    * │ └───┴─────┴───────────────────────┴─────┴───┘ │
    * └─────────────────────────────────────────────────────────────┘
    */
    + +

    耶稣

    +
    /*
    * |~~~~~~~|
    * | |
    * | |
    * | |
    * | |
    * | |
    * |~.\\\_\~~~~~~~~~~~~~~xx~~~ ~~~~~~~~~~~~~~~~~~~~~/_//;~|
    * | \ o \_ ,XXXXX), _..-~ o / |
    * | ~~\ ~-. XXXXX`)))), _.--~~ .-~~~ |
    * ~~~~~~~`\ ~\~~~XXX' _/ ';)) |~~~~~~..-~ _.-~ ~~~~~~~
    * `\ ~~--`_\~\, ;;;\)__.---.~~~ _.-~
    * ~-. `:;;/;; \ _..-~~
    * ~-._ `'' /-~-~
    * `\ / /
    * | , | |
    * | ' / |
    * \/; |
    * ;; |
    * `; . |
    * |~~~-----.....|
    * | \ \
    * | /\~~--...__ |
    * (| `\ __-\|
    * || \_ /~ |
    * |) \~-' |
    * | | \ '
    * | | \ :
    * \ | | |
    * | ) ( )
    * \ /; /\ |
    * | |/ |
    * | | |
    * \ .' ||
    * | | | |
    * ( | | |
    * | \ \ |
    * || o `.)|
    * |`\\) |
    * | |
    * | |
    */
    + +

    美女

    /*
    * .::::.
    * .::::::::.
    * :::::::::::
    * ..:::::::::::'
    * '::::::::::::'
    * .::::::::::
    * '::::::::::::::..
    * ..::::::::::::.
    * ``::::::::::::::::
    * ::::``:::::::::' .:::.
    * ::::' ':::::' .::::::::.
    * .::::' :::: .:::::::'::::.
    * .:::' ::::: .:::::::::' ':::::.
    * .::' :::::.:::::::::' ':::::.
    * .::' ::::::::::::::' ``::::.
    * ...::: ::::::::::::' ``::.
    * ````':. ':::::::::' ::::..
    * '.:::::' ':'````..
    */
    + +

    程序员之歌

    /*
    * 江城子 . 程序员之歌
    *
    * 十年生死两茫茫,写程序,到天亮。
    * 千行代码,Bug何处藏。
    * 纵使上线又怎样,朝令改,夕断肠。
    *
    * 领导每天新想法,天天改,日日忙。
    * 相顾无言,惟有泪千行。
    * 每晚灯火阑珊处,夜难寐,加班狂。
    *
    */
    + +

    龙图腾

    /*
    * ......................................&&.........................
    * ....................................&&&..........................
    * .................................&&&&............................
    * ...............................&&&&..............................
    * .............................&&&&&&..............................
    * ...........................&&&&&&....&&&..&&&&&&&&&&&&&&&........
    * ..................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..............
    * ................&...&&&&&&&&&&&&&&&&&&&&&&&&&&&&.................
    * .......................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&.........
    * ...................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...............
    * ..................&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&............
    * ...............&&&&&@ &&&&&&&&&&..&&&&&&&&&&&&&&&&&&&...........
    * ..............&&&&&&&&&&&&&&&.&&....&&&&&&&&&&&&&..&&&&&.........
    * ..........&&&&&&&&&&&&&&&&&&...&.....&&&&&&&&&&&&&...&&&&........
    * ........&&&&&&&&&&&&&&&&&&&.........&&&&&&&&&&&&&&&....&&&.......
    * .......&&&&&&&&.....................&&&&&&&&&&&&&&&&.....&&......
    * ........&&&&&.....................&&&&&&&&&&&&&&&&&&.............
    * ..........&...................&&&&&&&&&&&&&&&&&&&&&&&............
    * ................&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&............
    * ..................&&&&&&&&&&&&&&&&&&&&&&&&&&&&..&&&&&............
    * ..............&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&....&&&&&............
    * ...........&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&......&&&&............
    * .........&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&.........&&&&............
    * .......&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...........&&&&............
    * ......&&&&&&&&&&&&&&&&&&&...&&&&&&...............&&&.............
    * .....&&&&&&&&&&&&&&&&............................&&..............
    * ....&&&&&&&&&&&&&&&.................&&...........................
    * ...&&&&&&&&&&&&&&&.....................&&&&......................
    * ...&&&&&&&&&&.&&&........................&&&&&...................
    * ..&&&&&&&&&&&..&&..........................&&&&&&&...............
    * ..&&&&&&&&&&&&...&............&&&.....&&&&...&&&&&&&.............
    * ..&&&&&&&&&&&&&.................&&&.....&&&&&&&&&&&&&&...........
    * ..&&&&&&&&&&&&&&&&..............&&&&&&&&&&&&&&&&&&&&&&&&.........
    * ..&&.&&&&&&&&&&&&&&&&&.........&&&&&&&&&&&&&&&&&&&&&&&&&&&.......
    * ...&&..&&&&&&&&&&&&.........&&&&&&&&&&&&&&&&...&&&&&&&&&&&&......
    * ....&..&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&...........&&&&&&&&.....
    * .......&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&..............&&&&&&&....
    * .......&&&&&.&&&&&&&&&&&&&&&&&&..&&&&&&&&...&..........&&&&&&....
    * ........&&&.....&&&&&&&&&&&&&.....&&&&&&&&&&...........&..&&&&...
    * .......&&&........&&&.&&&&&&&&&.....&&&&&.................&&&&...
    * .......&&&...............&&&&&&&.......&&&&&&&&............&&&...
    * ........&&...................&&&&&&.........................&&&..
    * .........&.....................&&&&........................&&....
    * ...............................&&&.......................&&......
    * ................................&&......................&&.......
    * .................................&&..............................
    * ..................................&..............................
    */
    + +

    蝙蝠

    /*
    * ___====-_ _-====___
    * _--^^^#####// \\#####^^^--_
    * _-^##########// ( ) \\##########^-_
    * -############// |\^^/| \\############-
    * _/############// (@::@) \############\_
    * /#############(( \\// ))#############\
    * -###############\\ (oo) //###############-
    * -#################\\ / VV \ //#################-
    * -###################\\/ \//###################-
    * _#/|##########/\######( /\ )######/\##########|\#_
    * |/ |#/\#/\#/\/ \#/\##\ | | /##/\#/ \/\#/\#/\#| \|
    * ` |/ V V ` V \#\| | | |/#/ V ' V V \| '
    * ` ` ` ` / | | | | \ ' ' ' '
    * ( | | | | )
    * __\ | | | | /__
    * (vvv(VVV)(VVV)vvv)
    *
    * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    *
    * 神兽保佑 永无BUG
    */
    + +

    喷火龙

    /*
    * __----~~~~~~~~~~~------___
    * . . ~~//====...... __--~ ~~
    * -. \_|// |||\\ ~~~~~~::::... /~
    * ___-==_ _-~o~ \/ ||| \\ _/~~-
    * __---~~~.==~||\=_ -_--~/_-~|- |\\ \\ _/~
    * _-~~ .=~ | \\-_ '-~7 /- / || \ /
    * .~ .~ | \\ -_ / /- / || \ /
    * / ____ / | \\ ~-_/ /|- _/ .|| \ /
    * |~~ ~~|--~~~~--_ \ ~==-/ | \~--===~~ .\
    * ' ~-| /| |-~\~~ __--~~
    * |-~~-_/ | | ~\_ _-~ /\
    * / \ \__ \/~ \__
    * _--~ _/ | .-~~____--~-/ ~~==.
    * ((->/~ '.|||' -_| ~~-/ , . _||
    * -_ ~\ ~~---l__i__i__i--~~_/
    * _-~-__ ~) \--______________--~~
    * //.-~~~-~_--~- |-------~~~~~~~~
    * //.-~~~--\
    * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    *
    * 神兽保佑 永无BUG
    */
    + + + +]]>
    + + 个人 + + + 注释模板 + +
    + + 任务进度 + /posts/24183.html + + +
    +
    + + + + + +
    +
    + +]]>
    + + 个人 + + + 任务进度 + +
    + + 对象存储服务MinIO + /posts/36397.html + 一.MinIo基本介绍

    1.简介

    ​ MinIO基于Apache License v2.0开源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档。由于采用Golang实现,服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令可以运行起来。MinIO兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。

    +

    S3 ( Simple Storage Service简单存储服务)

    +

    基本概念

    +
      +
    • bucket – 类比于文件系统的目录
    • +
    • Object – 类比文件系统的文件
    • +
    • Keys – 类比文件名
    • +
    +

    官网文档:http://docs.minio.org.cn/docs/

    +

    2.特点

      +
    • 数据保护

      +

      Minio使用Minio Erasure Code(纠删码)来防止硬件故障。即便损坏一半以上的driver,但是仍然可以从中恢复。

      +
    • +
    • 高性能

      +

      作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率

      +
    • +
    • 可扩容

      +

      不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心

      +
    • +
    • SDK支持

      +

      基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持

      +
    • +
    • 有操作页面

      +

      面向用户友好的简单操作界面,非常方便的管理Bucket及里面的文件资源

      +
    • +
    • 功能简单

      +

      这一设计原则让MinIO不容易出错、更快启动

      +
    • +
    • 丰富的API

      +

      支持文件资源的分享连接及分享链接的过期策略、存储桶操作、文件列表访问及文件上传下载的基本功能等。

      +
    • +
    • 文件变化主动通知

      +

      存储桶(Bucket)如果发生改变,比如上传对象和删除对象,可以使用存储桶事件通知机制进行监控,并通过以下方式发布出去:AMQP、MQTT、Elasticsearch、Redis、NATS、MySQL、Kafka、Webhooks等。

      +
    • +
    +

    3.对象存储方式比较

    + + + + + + + + + + + + + + + + + + + + + + +
    存储方式优点缺点
    服务器磁盘开发便捷,成本低扩展困难
    分布式文件系统 (如MinIo)容易实现扩容复杂度高
    第三方存储 (如阿里OSS)开发简单,功能强大,免维护收费
    +

    4.分布式文件系统比较

    + + + + + + + + + + + + + + + + + +
    存储方式优点缺点
    FastDFS1,主备服务,高可用 2,支持主从文件,支持自定义扩展名 3,支持动态扩容1,没有完备官方文档,近几年没有更新 2,环境搭建较为麻烦
    MinIO1,性能高,准硬件条件下它能达到55GB/s的读、35GB/s的写速率 2,部署自带管理界面 3,MinIO.Inc运营的开源项目,社区活跃度高 4,提供了所有主流开发语言的SDK1,不支持动态增加节点
    +

    二.MinIo安装教程

    使用Docker的方式安装MInIo,Docker使用教程 https://jasonsgong.gitee.io/posts/19306.html

    +
    #拉取minio的镜像
    docker pull minio/minio
    #创建并运行容器
    #设置用户名-e "MINIO_ACCESS_KEY=minio"
    #设置密码 -e "MINIO_SECRET_KEY=minio123"
    docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -v /home/data:/data -v /home/config:/root/.minio minio/minio server /data
    + +

    访问: http://192.168.200.130:9000

    +

    image-20230818145600002

    +

    Access Key为minio Secret_key 为minio123 进入系统后可以看到主界面

    +

    image-20230818145721071

    +

    三.快速入门

    基本概念

    +
      +
    • bucket – 类比于文件系统的目录
    • +
    • Object – 类比文件系统的文件
    • +
    • Keys – 类比文件名
    • +
    +

    1.创建一个bucket

    点击右下角的“+”号 ,创建一个桶

    +

    image-20230818150154249

    +

    创建成功后

    +

    image-20230818150902795

    +

    2.环境搭建

    2.1 创建一个Demo工程

    image-20230818151929679

    +

    2.2 导入依赖和创建启动类

    pom.xml

    +
    <dependencies>

    <dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.1.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    </dependencies>
    + +

    启动类

    +
    package com.heima.minio;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class MinIOApplication {

    public static void main(String[] args) {
    SpringApplication.run(MinIOApplication.class,args);
    }
    }
    + +

    3.测试案例

    3.1 上传文件进行静态访问

    目标:把test.html文件上传到minio中,并且可以在浏览器中访问

    +

    test.html测试文件

    +
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello World!</title>
    </head>
    <body>
    <b>普通文本 String 展示:</b><br><br>
    Hello freemarker <br>
    <hr>
    <b>对象Student中的数据展示:</b><br/>
    姓名:小明<br/>
    年龄:18
    <hr>
    </body>
    </html>
    + +

    文件上传代码

    +
    package com.heima.minio;


    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/18
    * @Description
    */
    @SpringBootTest(classes = MinIOApplication.class)
    @RunWith(SpringRunner.class)
    @Slf4j
    public class MinIOTest {

    /**
    * 把list.html文件上传到minio中,并且可以在浏览器中访问
    */
    @Test
    public void testMinIO(){
    try {
    //读取一个文件
    FileInputStream inputStream = new FileInputStream("C:\\Gong\\data\\test.html");
    //1.获取MinIO的连接信息,创建一个minio的客户端
    MinioClient minioClient = MinioClient.builder()
    .credentials("minio", "minio123")//minio的账号密码
    .endpoint("http://192.168.200.130:9000")//minio的地址
    .build();
    //2.上传
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object("test.html")//文件的名称
    .contentType("text/html")//文件的类型
    .bucket("leadnews")//桶的名称,与之前的minio管理界面创建的bucket名称一致即可
    .stream(inputStream,inputStream.available(),-1)
    .build();
    //3.访问的路径 minio的访问路径 + 桶的名称 + 文件的名称
    log.info("http://192.168.200.130:9000/leadnews/test.html");
    minioClient.putObject(putObjectArgs);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }
    + +

    执行成功之后可以在MinIO中找到该文件

    +

    image-20230818154409682

    +

    设置浏览器输入文件在minio中的地址可以直接访问文件的内容

    +

    设置bucket的访问权限

    +

    image-20230818154858988

    +

    设置完毕后访问文件的地址可以直接访问到

    +

    image-20230818155021308

    +

    4.封装MinIO为Starter-以黑马头条项目为例

    黑马头条项目gitee地址: https://gitee.com/JasonsGong/heima-leadnews

    +

    在开发的过程中,有很多模块需要使用到文件服务,我们直接把文件服务封装成一个Starter,方便其余的模块调用

    +

    image-20230818155501495

    +

    4.1 创建模块heima-file-starter

    导入依赖

    +
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>7.1.0</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    </dependencies>
    + +

    4.2 配置类

    MinIOConfigProperties

    +
    package com.heima.file.config;


    import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;

    import java.io.Serializable;

    @Data
    @ConfigurationProperties(prefix = "minio")
    public class MinIOConfigProperties implements Serializable {

    private String accessKey;
    private String secretKey;
    private String bucket;
    private String endpoint;
    private String readPath;
    }
    + +

    MinIOConfig

    +
    package com.heima.file.config;

    import com.heima.file.service.FileStorageService;
    import io.minio.MinioClient;
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;


    @Data
    @Configuration
    @EnableConfigurationProperties({MinIOConfigProperties.class})
    //当引入FileStorageService接口时
    @ConditionalOnClass(FileStorageService.class)
    public class MinIOConfig {

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    @Bean
    public MinioClient buildMinioClient(){
    return MinioClient
    .builder()
    .credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
    .endpoint(minIOConfigProperties.getEndpoint())
    .build();
    }
    }
    + +

    4.3 封装操作minIO类

    FileStorageService

    +
    package com.heima.file.service;

    import java.io.InputStream;

    /**
    * @author itheima
    */
    public interface FileStorageService {


    /**
    * 上传图片文件
    * @param prefix 文件前缀
    * @param filename 文件名
    * @param inputStream 文件流
    * @return 文件全路径
    */
    public String uploadImgFile(String prefix, String filename,InputStream inputStream);

    /**
    * 上传html文件
    * @param prefix 文件前缀
    * @param filename 文件名
    * @param inputStream 文件流
    * @return 文件全路径
    */
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream);

    /**
    * 删除文件
    * @param pathUrl 文件全路径
    */
    public void delete(String pathUrl);

    /**
    * 下载文件
    * @param pathUrl 文件全路径
    * @return
    *
    */
    public byte[] downLoadFile(String pathUrl);

    }
    + +

    MinIOFileStorageService

    +
    package com.heima.file.service.impl;


    import com.heima.file.config.MinIOConfig;
    import com.heima.file.config.MinIOConfigProperties;
    import com.heima.file.service.FileStorageService;
    import io.minio.GetObjectArgs;
    import io.minio.MinioClient;
    import io.minio.PutObjectArgs;
    import io.minio.RemoveObjectArgs;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Import;
    import org.springframework.util.StringUtils;

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.text.SimpleDateFormat;
    import java.util.Date;

    @Slf4j
    @EnableConfigurationProperties(MinIOConfigProperties.class)
    @Import(MinIOConfig.class)
    public class MinIOFileStorageService implements FileStorageService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    private final static String separator = "/";

    /**
    * @param dirPath
    * @param filename yyyy/mm/dd/file.jpg
    * @return
    */
    public String builderFilePath(String dirPath,String filename) {
    StringBuilder stringBuilder = new StringBuilder(50);
    if(!StringUtils.isEmpty(dirPath)){
    stringBuilder.append(dirPath).append(separator);
    }
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
    String todayStr = sdf.format(new Date());
    stringBuilder.append(todayStr).append(separator);
    stringBuilder.append(filename);
    return stringBuilder.toString();
    }

    /**
    * 上传图片文件
    * @param prefix 文件前缀
    * @param filename 文件名
    * @param inputStream 文件流
    * @return 文件全路径
    */
    @Override
    public String uploadImgFile(String prefix, String filename,InputStream inputStream) {
    String filePath = builderFilePath(prefix, filename);
    try {
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object(filePath)
    .contentType("image/jpg")
    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
    .build();
    minioClient.putObject(putObjectArgs);
    StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
    urlPath.append(separator+minIOConfigProperties.getBucket());
    urlPath.append(separator);
    urlPath.append(filePath);
    return urlPath.toString();
    }catch (Exception ex){
    log.error("minio put file error.",ex);
    throw new RuntimeException("上传文件失败");
    }
    }

    /**
    * 上传html文件
    * @param prefix 文件前缀
    * @param filename 文件名
    * @param inputStream 文件流
    * @return 文件全路径
    */
    @Override
    public String uploadHtmlFile(String prefix, String filename,InputStream inputStream) {
    String filePath = builderFilePath(prefix, filename);
    try {
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object(filePath)
    .contentType("text/html")
    .bucket(minIOConfigProperties.getBucket()).stream(inputStream,inputStream.available(),-1)
    .build();
    minioClient.putObject(putObjectArgs);
    StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
    urlPath.append(separator+minIOConfigProperties.getBucket());
    urlPath.append(separator);
    urlPath.append(filePath);
    return urlPath.toString();
    }catch (Exception ex){
    log.error("minio put file error.",ex);
    ex.printStackTrace();
    throw new RuntimeException("上传文件失败");
    }
    }

    /**
    * 删除文件
    * @param pathUrl 文件全路径
    */
    @Override
    public void delete(String pathUrl) {
    String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
    int index = key.indexOf(separator);
    String bucket = key.substring(0,index);
    String filePath = key.substring(index+1);
    // 删除Objects
    RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
    try {
    minioClient.removeObject(removeObjectArgs);
    } catch (Exception e) {
    log.error("minio remove file error. pathUrl:{}",pathUrl);
    e.printStackTrace();
    }
    }


    /**
    * 下载文件
    * @param pathUrl 文件全路径
    * @return 文件流
    *
    */
    @Override
    public byte[] downLoadFile(String pathUrl) {
    String key = pathUrl.replace(minIOConfigProperties.getEndpoint()+"/","");
    int index = key.indexOf(separator);
    String bucket = key.substring(0,index);
    String filePath = key.substring(index+1);
    InputStream inputStream = null;
    try {
    inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
    } catch (Exception e) {
    log.error("minio down file error. pathUrl:{}",pathUrl);
    e.printStackTrace();
    }

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    byte[] buff = new byte[100];
    int rc = 0;
    while (true) {
    try {
    if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
    } catch (IOException e) {
    e.printStackTrace();
    }
    byteArrayOutputStream.write(buff, 0, rc);
    }
    return byteArrayOutputStream.toByteArray();
    }
    }
    + +

    4.4 对外加入自动配置

    在resources中新建META-INF/spring.factories

    +
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.heima.file.service.impl.MinIOFileStorageService
    + +

    4.5 其他微服务使用

    第一,导入heima-file-starter的依赖

    +
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-file-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    + +

    第二,在微服务中添加minio所需要的配置

    +
    minio:
    accessKey: minio
    secretKey: minio123
    bucket: leadnews
    endpoint: http://192.168.200.130:9000
    readPath: http://192.168.200.130:9000
    + +

    第三,在对应使用的业务类中注入FileStorageService,样例如下:

    +
    @Autowired
    private FileStorageService fileStorageService;
    + +
    package com.heima.minio;


    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/18
    * @Description
    */
    @SpringBootTest(classes = MinIOApplication.class)
    @RunWith(SpringRunner.class)
    @Slf4j
    public class MinIOTest {

    @Autowired
    private FileStorageService fileStorageService;

    @Test
    public void testMinIOStarter(){
    //读取一个文件
    try {
    FileInputStream inputStream = new FileInputStream("C:\\Gong\\data\\test.html");
    String s = fileStorageService.uploadHtmlFile("", "test,html", inputStream);
    log.info(s);
    } catch (FileNotFoundException e) {
    e.printStackTrace();
    }
    }
    }
    + +

    image-20230818165138917

    +]]>
    + + 后端 + + + MinIo + +
    + + 微信登录 + /posts/45572.html + 1、OAuth2

    微信登录使用了OAuth2解决方案

    +

    1.1 OAuth2解决什么问题

    1.1.1 开放系统间授权

    照片拥有者想要在云冲印服务上打印照片,云冲印服务需要访问云存储服务上的资源

    +

    IMG_256

    +

    1.1.2图例

    资源拥有者:照片拥有者

    +

    客户应用:云冲印

    +

    受保护的资源:照片

    +

    IMG_256

    +

    1.1.3方式一:用户名密码复制

    IMG_258

    +

    用户将自己的”云存储”服务的用户名和密码,告诉”云冲印”,后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

    +

    (1)”云冲印”为了后续的服务,会保存用户的密码,这样很不安全。

    +

    (2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。

    +

    (3)”云冲印”拥有了获取用户储存在Google所有资料的权力,用户没法限制”云冲印”获得授权的范围和有效期。

    +

    (4)用户只有修改密码,才能收回赋予”云冲印”的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

    +

    (5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

    +

    总结:

    +

    将受保护的资源中的用户名和密码存储在客户应用的服务器上,使用时直接使用这个用户名和密码登录

    +

    适用于同一公司内部的多个系统,不适用于不受信的第三方应用

    +

    1.1.4方式二:通用开发者key

    适用于合作商或者授信的不同业务部门之间

    +

    IMG_259

    +

    1.1.5方式三:颁发令牌

    接近OAuth2方式,需要考虑如何管理令牌、颁发令牌、吊销令牌,需要统一的协议,因此就有了OAuth2协议

    +

    IMG_256

    +

    令牌类比仆从钥匙

    +

    IMG_256

    +

    1.2 OAuth2最简向导

    1.2.1 OAuth主要角色

    IMG_256

    +

    1.2.2最简向导

    川崎高彦:OAuth2领域专家,开发了一个OAuth2 sass服务,OAuth2 as Service,并且做成了一个公司

    +

    在融资的过程中为了向投资人解释OAuth2是什么,于是写了一篇文章,《OAuth2最简向导》

    +

    1.3 OAuth2的应用

    1.3.1 微服务安全

    现代微服务中系统微服务化以及应用的形态和设备类型增多,不能用传统的登录方式

    +

    核心的技术不是用户名和密码,而是token,由AuthServer颁发token,用户使用token进行登录

    +

    IMG_256

    +

    1.3.2 社交登录

    +

    2、微信登录介绍

    2.1 前期准备

    1、注册

    +

    微信开放平台:https://open.weixin.qq.com

    +

    2、邮箱激活

    +

    3、完善开发者资料

    +

    4、开发者资质认证

    +

    准备营业执照,1-2个工作日审批、300元

    +

    5、创建网站应用

    +

    提交审核,7个工作日审批

    +

    6、内网穿透

    +

    ngrok的使用

    +

    2.2 授权流程

    参考文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=e547653f995d8f402704d5cb2945177dc8aa4e7e&lang=zh_CN

    +

    获取access_token时序图

    +

    IMG_256

    +

    第一步:请求CODE(生成授权URL)

    +

    第二步:通过code获取access_token(开发回调URL)

    +

    3、使用教程

    由于微信登录需要企业用户才能注册,这里我们使用的是尚硅谷的密钥

    +

    当前项目的启动端口设置为 8160 才可以获取到登录的二维码

    +

    3.1 配置文件中添加配置

    # 微信开放平台appid
    wx.open.app_id=wxed9954c01bb89b47
    # 微信开放平台appsecret
    wx.open.app_secret=a7482517235173ddb4083788de60b90e
    #回调的地址
    wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback
    #前端项目的地址
    yygh.baseUrl=http://localhost:3000
    + +

    3.2 编写配置类读取配置文件中的值

    import org.springframework.beans.factory.InitializingBean;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;

    @Component
    public class ConstantPropertiesUtil implements InitializingBean {

    @Value("${wx.open.app_id}")
    private String appId;

    @Value("${wx.open.app_secret}")
    private String appSecret;

    @Value("${wx.open.redirect_url}")
    private String redirectUrl;

    @Value("${yygh.baseUrl}")
    private String yyghBaseUrl;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;
    public static String YYGH_BASE_URL;

    @Override
    public void afterPropertiesSet() throws Exception {
    WX_OPEN_APP_ID = appId;
    WX_OPEN_APP_SECRET = appSecret;
    WX_OPEN_REDIRECT_URL = redirectUrl;
    YYGH_BASE_URL = yyghBaseUrl;
    }
    }
    + +

    3.3 生成二维码和扫描二维码之后的回调操作

    3.3.1 生成二维码

    生成内嵌的二维码

    +

    返回给前端的相关参数

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    参数是否必须说明
    self_redirecttrue:手机点击确认登录后可以在 iframe 内跳转到 redirect_uri,false:手机点击确认登录后可以在 top window 跳转到 redirect_uri。默认为 false。
    id第三方页面显示二维码的容器id
    appid应用唯一标识,在微信开放平台提交应用审核通过后获得
    scope应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可
    redirect_uri重定向地址,需要进行UrlEncode
    state用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验
    style提供”black”、”white”可选,默认为黑色文字描述。详见文档底部FAQ
    href自定义样式链接,第三方可根据实际需求覆盖默认样式。详见文档底部FAQ
    +

    生成扫描二维码的后端部分

    +
    import com.atguigu.yygh.common.result.Result;
    import com.atguigu.yygh.user.utils.ConstantPropertiesUtil;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;

    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.HashMap;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/20
    * @Description 微信登录的控制器方法
    */
    @Controller
    @RequestMapping("/api/ucenter/wx")
    @CrossOrigin
    public class WxController {


    /**
    * 生成微信扫描二维码
    * 返回给前端一些生成二维码的参数
    */
    @GetMapping("/getLoginParameters")
    @ResponseBody
    public Result getLoginParameters() {
    try {
    HashMap<String, Object> map = new HashMap<>();
    map.put("appid", ConstantPropertiesUtil.WX_OPEN_APP_ID);
    map.put("scope", "snsapi_login");
    map.put("redirect_uri", URLEncoder.encode(ConstantPropertiesUtil.WX_OPEN_REDIRECT_URL, "utf-8"));
    map.put("state", System.currentTimeMillis() + "");//原样返回,可以没有
    return Result.ok(map);
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    return Result.fail();
    }
    }
    }
    + +

    生成扫描二维码的前端部分

    +

    JS部分

    +
    import request from '@/utils/request';

    export default {
    //获取生成二维码需要的参数信息
    getLoginParam() {
    return request({
    url: `/api/ucenter/wx/getLoginParam`,
    method: 'get'
    })
    }
    }
    + +

    引入api

    +
    import weixinApi from "@/api/weixin";
    + +

    在mounted()方法中初始化微信js

    +
    mounted() {
    //初始化微信js
    const script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = 'https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js'
    document.body.appendChild(script)
    },
    + +

    在页面中微信登录按钮绑定的是weixinLogin()点击事件,所以我们要在这个方法中初始化对象

    +

    向后端发起请求获取生成二维码需要的参数信息

    +
    weixinLogin() {
    weixinApi.getLoginParam().then((response) => {
    var obj = new WxLogin({
    self_redirect: true,
    id: "weixinLogin", // 需要显示的容器id <div id="weixinLogin"></div>
    appid: response.data.appid, // appid
    scope: response.data.scope, // 网页默认即可
    redirect_uri: response.data.redirectUri, // 授权成功后回调的url
    state: response.data.state, // 可设置为简单的随机数加session用来校验
    style: "black", //。二维码的样式, 提供"black"、"white"可选
    href: "", // 外部css文件url,需要https
    });
    });
    },
    + +

    点击微信登录之后,页面上的效果

    +

    image-20230421203717902

    +

    3.3.2 处理用户扫描成功之后的回调

    实现的思路:

    +

    用户扫描二维码 -> 点击确认 -> 会执行回调请求(请求配置文件中这个地址wx.open.redirect_url=http://localhost:8160/api/ucenter/wx/callback) -> 获取回调请求中携带的参数,再根据参数获取扫描用户的信息,执行相关的操作

    +

    扫描成功之后发起如下的请求:

    +

    image-20230421205258647

    +

    image-20230421210454720

    +

    引入httpclient(不依赖浏览器发起请求)依赖

    +
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.1</version>
    </dependency>
    + +

    导入HttpClientUtils工具类

    +
    import org.apache.commons.io.IOUtils;
    import org.apache.commons.lang.StringUtils;
    import org.apache.http.Consts;
    import org.apache.http.HttpEntity;
    import org.apache.http.HttpResponse;
    import org.apache.http.NameValuePair;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.config.RequestConfig;
    import org.apache.http.client.config.RequestConfig.Builder;
    import org.apache.http.client.entity.UrlEncodedFormEntity;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.client.methods.HttpPost;
    import org.apache.http.conn.ConnectTimeoutException;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.conn.ssl.SSLContextBuilder;
    import org.apache.http.conn.ssl.TrustStrategy;
    import org.apache.http.conn.ssl.X509HostnameVerifier;
    import org.apache.http.entity.ContentType;
    import org.apache.http.entity.StringEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
    import org.apache.http.message.BasicNameValuePair;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLException;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.SSLSocket;
    import java.io.IOException;
    import java.net.SocketTimeoutException;
    import java.security.GeneralSecurityException;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.Map.Entry;
    import java.util.Set;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/21
    * @Description
    */
    public class HttpClientUtils {

    public static final int connTimeout=10000;
    public static final int readTimeout=10000;
    public static final String charset="UTF-8";
    private static HttpClient client = null;

    static {
    PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
    cm.setMaxTotal(128);
    cm.setDefaultMaxPerRoute(128);
    client = HttpClients.custom().setConnectionManager(cm).build();
    }

    public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
    return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {
    return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {
    return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String get(String url) throws Exception {
    return get(url, charset, null, null);
    }

    public static String get(String url, String charset) throws Exception {
    return get(url, charset, connTimeout, readTimeout);
    }

    /**
    * 发送一个 Post 请求, 使用指定的字符集编码.
    *
    * @param url
    * @param body RequestBody
    * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
    * @param charset 编码
    * @param connTimeout 建立链接超时时间,毫秒.
    * @param readTimeout 响应超时时间,毫秒.
    * @return ResponseBody, 使用指定的字符集编码.
    * @throws ConnectTimeoutException 建立链接超时异常
    * @throws SocketTimeoutException 响应超时
    * @throws Exception
    */
    public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
    throws ConnectTimeoutException, SocketTimeoutException, Exception {
    HttpClient client = null;
    HttpPost post = new HttpPost(url);
    String result = "";
    try {
    if (StringUtils.isNotBlank(body)) {
    HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
    post.setEntity(entity);
    }
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    post.setConfig(customReqConf.build());

    HttpResponse res;
    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(post);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(post);
    }
    result = IOUtils.toString(res.getEntity().getContent(), charset);
    } finally {
    post.releaseConnection();
    if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    return result;
    }


    /**
    * 提交form表单
    *
    * @param url
    * @param params
    * @param connTimeout
    * @param readTimeout
    * @return
    * @throws ConnectTimeoutException
    * @throws SocketTimeoutException
    * @throws Exception
    */
    public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
    SocketTimeoutException, Exception {

    HttpClient client = null;
    HttpPost post = new HttpPost(url);
    try {
    if (params != null && !params.isEmpty()) {
    List<NameValuePair> formParams = new ArrayList<NameValuePair>();
    Set<Entry<String, String>> entrySet = params.entrySet();
    for (Entry<String, String> entry : entrySet) {
    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
    }
    UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
    post.setEntity(entity);
    }

    if (headers != null && !headers.isEmpty()) {
    for (Entry<String, String> entry : headers.entrySet()) {
    post.addHeader(entry.getKey(), entry.getValue());
    }
    }
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    post.setConfig(customReqConf.build());
    HttpResponse res = null;
    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(post);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(post);
    }
    return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
    } finally {
    post.releaseConnection();
    if (url.startsWith("https") && client != null
    && client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    }

    /**
    * 发送一个 GET 请求
    */
    public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
    throws ConnectTimeoutException,SocketTimeoutException, Exception {

    HttpClient client = null;
    HttpGet get = new HttpGet(url);
    String result = "";
    try {
    // 设置参数
    Builder customReqConf = RequestConfig.custom();
    if (connTimeout != null) {
    customReqConf.setConnectTimeout(connTimeout);
    }
    if (readTimeout != null) {
    customReqConf.setSocketTimeout(readTimeout);
    }
    get.setConfig(customReqConf.build());

    HttpResponse res = null;

    if (url.startsWith("https")) {
    // 执行 Https 请求.
    client = createSSLInsecureClient();
    res = client.execute(get);
    } else {
    // 执行 Http 请求.
    client = HttpClientUtils.client;
    res = client.execute(get);
    }

    result = IOUtils.toString(res.getEntity().getContent(), charset);
    } finally {
    get.releaseConnection();
    if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
    ((CloseableHttpClient) client).close();
    }
    }
    return result;
    }

    /**
    * 从 response 里获取 charset
    */
    @SuppressWarnings("unused")
    private static String getCharsetFromResponse(HttpResponse ressponse) {
    // Content-Type:text/html; charset=GBK
    if (ressponse.getEntity() != null && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
    String contentType = ressponse.getEntity().getContentType().getValue();
    if (contentType.contains("charset=")) {
    return contentType.substring(contentType.indexOf("charset=") + 8);
    }
    }
    return null;
    }

    /**
    * 创建 SSL连接
    * @return
    * @throws GeneralSecurityException
    */
    private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
    try {
    SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
    public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
    return true;
    }
    }).build();

    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

    @Override
    public boolean verify(String arg0, SSLSession arg1) {
    return true;
    }

    @Override
    public void verify(String host, SSLSocket ssl)
    throws IOException {
    }

    @Override
    public void verify(String host, X509Certificate cert)
    throws SSLException {
    }

    @Override
    public void verify(String host, String[] cns,
    String[] subjectAlts) throws SSLException {
    }
    });
    return HttpClients.custom().setSSLSocketFactory(sslsf).build();

    } catch (GeneralSecurityException e) {
    throw e;
    }
    }
    }


    + +

    Controller

    +
    package com.atguigu.yygh.user.controller;

    import com.alibaba.fastjson.JSONObject;
    import com.atguigu.yygh.common.result.Result;
    import com.atguigu.yygh.user.utils.ConstantPropertiesUtil;
    import com.atguigu.yygh.user.utils.HttpClientUtils;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.CrossOrigin;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;

    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.util.HashMap;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/20
    * @Description 微信登录的控制器方法
    */
    @Controller
    @RequestMapping("/api/ucenter/wx")
    @CrossOrigin
    public class WxController {


    /**
    * 生成微信扫描二维码
    * 返回给前端一些生成二维码的参数
    */
    @GetMapping("/getLoginParam")
    @ResponseBody
    public Result getLoginParameters() {
    try {
    HashMap<String, Object> map = new HashMap<>();
    map.put("appid", ConstantPropertiesUtil.WX_OPEN_APP_ID);
    map.put("scope", "snsapi_login");
    map.put("redirect_uri", URLEncoder.encode(ConstantPropertiesUtil.WX_OPEN_REDIRECT_URL, "utf-8"));
    map.put("state", System.currentTimeMillis() + "");//原样返回,可以没有
    return Result.ok(map);
    } catch (UnsupportedEncodingException e) {
    e.printStackTrace();
    return Result.fail();
    }
    }

    /**
    * 扫描之后回调的请求中携带了相关的参数,帮助我们获取用户的信息
    * 登录成功之后的回调方法
    * @param code 临时票据
    * @param state 生成微信二维码之前put进去的参数 原样返回
    * @return
    */
    @GetMapping("/callback")
    public String callback(String code ,String state){
    //通过code、appid和app_secret去请求固定地址换取access_token
    StringBuffer baseAccessTokenUrl = new StringBuffer()
    .append("https://api.weixin.qq.com/sns/oauth2/access_token") //请求的固定地址
    .append("?appid=%s") //%s占位符
    .append("&secret=%s")
    .append("&code=%s")
    .append("&grant_type=authorization_code");

    //拼接完整之后的地址
    String accessTokenUrl = String.format(baseAccessTokenUrl.toString(),
    ConstantPropertiesUtil.WX_OPEN_APP_ID,
    ConstantPropertiesUtil.WX_OPEN_APP_SECRET,
    code);

    //使用httpclient去请求这个地址换取access_token
    try {
    String accessTokenInfo = HttpClientUtils.get(accessTokenUrl);//json格式的字符串
    //从这个字符串中获取openid和 access_token的值
    JSONObject jsonObject = JSONObject.parseObject(accessTokenInfo);
    String accessToken = jsonObject.getString("access_token");
    String openid = jsonObject.getString("openid");
    //根据access_token和openid获取微信用户的基本信息
    String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
    "?access_token=%s" +
    "&openid=%s";
    String userInfoUrl = String.format(baseUserInfoUrl, accessToken, openid);
    //发起请求获取用户信息
    String userInfo = HttpClientUtils.get(userInfoUrl);//json格式的字符串
    JSONObject parseObject = JSONObject.parseObject(userInfo);
    //获取具体的用户信息
    String nickname = parseObject.getString("nickname");//用户昵称
    String sex = parseObject.getString("sex");//性别
    String language = parseObject.getString("language");//语言
    String city = parseObject.getString("city");//所在城市
    String province = parseObject.getString("province");//省份
    String country = parseObject.getString("country");//注册国家
    String headimgurl = parseObject.getString("headimgurl");//头像的url地址
    System.out.println("昵称:"+nickname+",性别:"+sex+",语言:"+language+",城市:"+city+",省份:"+province+",注册国家:"+country+",头像地址:"+headimgurl);
    //TODO 将扫码人的信息添加到数据库中
    } catch (Exception e) {
    e.printStackTrace();
    }
    return "redirect:地址信息&参数信息(用户信息或者token信息等)";
    }
    }
    + + + + + + + + + +]]>
    + + 后端 + + + Java + +
    + + 阿里云对象存储OSS + /posts/6319.html + 1.官网介绍

    ​ 阿里云对象存储OSS(Object Storage Service)是一款海量、安全、低成本、高可靠的云存储服务,可提供99.9999999999%(12个9)的数据持久性,99.995%的数据可用性。多种存储类型供选择,全面优化存储成本。OSS具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。您可以使用阿里云提供的API、SDK接口或者OSS迁移工具轻松地将海量数据移入或移出阿里云OSS。数据存储到阿里云OSS以后,您可以选择标准存储(Standard)作为移动应用、大型网站、图片分享或热点音视频的主要存储方式,也可以选择成本更低、存储期限更长的低频访问存储(Infrequent Access)、归档存储(Archive)、冷归档存储(Cold Archive)作为不经常访问数据的存储方式。

    +

    2.使用

    2.1 使用的前置准备

    (1)申请阿里云账号
    (2)实名认证
    (3)开通“对象存储OSS”服务
    (4)进入管理控制台

    +

    2.2 创建Bucket

    点击创建Bucket

    +

    image-20230422210404204

    +

    填写相关的信息

    +

    image-20230422211448747

    +

    2.3获取AccessKey保存备用

    AccessKey拥有对阿里云提供服务(对象存储、短信服务、视频点播服务等等)的控制权,要妥善保管

    +

    image-20230422211940485

    +

    2.4 查看开发文档

    刚开始不会使用的话,查看开发文档是入门的最好方式

    +

    image-20230422212508029

    +

    2.5 入门案例

    创建一个SpringBoot工程

    +

    1.引入依赖

    可以引入一个日期工具类joda-time 方便后面生成图片存储的路径

    +
    <dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.15.1</version>
    </dependency>
    + +

    2.修改配置文件

    入门案例中以下值会直接在代码中写死,先不从配置文件中获取

    +
    # 地域节点 bucket概览中查看
    aliyun.oss.endpoint=oss-cn-beijing.aliyuncs.com
    # accessKey
    aliyun.oss.accessKeyId=LTAI4G4SV6WtST7UYH77XXXX
    # secret
    aliyun.oss.secret=X9KHNYgztNr9MI5Zp8JffXXXXXXXX
    # bucket名
    aliyun.oss.bucket=yygh-atguigu
    + +

    3.创建读取配置文件的配置类

    @Component
    public class ConstantOssPropertiesUtils implements InitializingBean {

    @Value("${aliyun.oss.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;

    @Value("${aliyun.oss.secret}")
    private String secret;

    @Value("${aliyun.oss.bucket}")
    private String bucket;

    public static String EDNPOINT;
    public static String ACCESS_KEY_ID;
    public static String SECRECT;
    public static String BUCKET;

    @Override
    public void afterPropertiesSet() throws Exception {
    EDNPOINT=endpoint;
    ACCESS_KEY_ID=accessKeyId;
    SECRECT=secret;
    BUCKET=bucket;
    }
    }
    + +

    4.测试案例

    以下测试案例由官方开发文档提供

    +

    1.通过代码创建Bucket

    +
    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    // 创建存储空间。
    ossClient.createBucket(bucketName);

    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    2.上传文件

    +

    以下代码用于通过流式上传的方式将文件上传到OSS。

    +
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import java.io.ByteArrayInputStream;

    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";
    // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
    String objectName = "exampledir/exampleobject.txt";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    String content = "Hello OSS";
    ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));
    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    3.下载文件

    +

    以下代码用于通过流式下载方式从OSS下载文件。

    +
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.aliyun.oss.model.OSSObject;
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;

    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";
    // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
    String objectName = "exampledir/exampleobject.txt";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    // 调用ossClient.getObject返回一个OSSObject实例,该实例包含文件内容及文件元信息。
    OSSObject ossObject = ossClient.getObject(bucketName, objectName);
    // 调用ossObject.getObjectContent获取文件输入流,可读取此输入流获取其内容。
    InputStream content = ossObject.getObjectContent();
    if (content != null) {
    BufferedReader reader = new BufferedReader(new InputStreamReader(content));
    while (true) {
    String line = reader.readLine();
    if (line == null) break;
    System.out.println("\n" + line);
    }
    // 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
    content.close();
    }
    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    4.列举文件

    +

    以下代码用于列举examplebucket存储空间下的文件。默认列举100个文件。

    +
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.aliyun.oss.model.OSSObjectSummary;
    import com.aliyun.oss.model.ObjectListing;

    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    // ossClient.listObjects返回ObjectListing实例,包含此次listObject请求的返回结果。
    ObjectListing objectListing = ossClient.listObjects(bucketName);
    // objectListing.getObjectSummaries获取所有文件的描述信息。
    for (OSSObjectSummary objectSummary : objectListing.getObjectSummaries()) {
    System.out.println(" - " + objectSummary.getKey() + " " +
    "(size = " + objectSummary.getSize() + ")");
    }
    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    5.删除文件

    +

    以下代码用于删除指定文件。

    +
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;

    public class Demo {

    public static void main(String[] args) throws Exception {
    // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
    String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = "yourAccessKeyId";
    String accessKeySecret = "yourAccessKeySecret";
    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";
    // 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
    String objectName = "exampledir/exampleobject.txt";

    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

    try {
    // 删除文件。
    ossClient.deleteObject(bucketName, objectName);
    } catch (OSSException oe) {
    System.out.println("Caught an OSSException, which means your request made it to OSS, "
    + "but was rejected with an error response for some reason.");
    System.out.println("Error Message:" + oe.getErrorMessage());
    System.out.println("Error Code:" + oe.getErrorCode());
    System.out.println("Request ID:" + oe.getRequestId());
    System.out.println("Host ID:" + oe.getHostId());
    } catch (ClientException ce) {
    System.out.println("Caught an ClientException, which means the client encountered "
    + "a serious internal problem while trying to communicate with OSS, "
    + "such as not being able to access the network.");
    System.out.println("Error Message:" + ce.getMessage());
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    5.项目中使用OSS对象存储

    1.文件的上传操作

    controller层

    +
    import com.atguigu.yygh.common.result.Result;
    import com.atguigu.yygh.oss.service.OSSService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/22
    * @Description 对象存储的控制器方法
    */
    @RestController
    @RequestMapping("/api/oss/file")
    public class OSSController {

    @Autowired
    private OSSService ossService;

    /**
    * 上传文件到阿里云
    */
    @PostMapping("/fileUpload")
    public Result fileUpload(MultipartFile file){
    //上传文件,返回文件的url地址
    String url = ossService.upload(file);
    return Result.ok(url);
    }
    }
    + +

    service层

    +
    import com.aliyun.oss.ClientException;
    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.OSSException;
    import com.atguigu.yygh.oss.service.OSSService;
    import com.atguigu.yygh.oss.utils.ConstantOssPropertiesUtils;
    import org.joda.time.DateTime;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.UUID;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/22
    * @Description OSS文件相关操作
    */
    @Service
    public class OSSServiceImpl implements OSSService {
    /**
    * 上传文件
    *
    * @param file 文件
    * @return 文件的地址
    */
    @Override
    public String upload(MultipartFile file) {
    // Endpoint,地域节点信息
    String endpoint = ConstantOssPropertiesUtils.EDNPOINT;
    // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
    String accessKeyId = ConstantOssPropertiesUtils.ACCESS_KEY_ID;
    String accessKeySecret = ConstantOssPropertiesUtils.SECRECT;
    // Bucket名称
    String bucketName = ConstantOssPropertiesUtils.BUCKET;
    // 创建OSSClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    //生成文件名 生成文件名的策略是日期/文件名.jpg 文件名由UUID随机生成
    //获取原始的文件名
    String originalFilename = file.getOriginalFilename();
    //分割出扩展名
    String suffixFilename = originalFilename.substring(originalFilename.lastIndexOf("."));
    //得到随机的文件名
    String prefixFilename = UUID.randomUUID().toString();
    //得到文件名
    String filename = prefixFilename + suffixFilename;
    //将文件按照日期进行分类 日期/文件名.jpg
    //获取当前的日期 使用时间作为目录
    String filePath = new DateTime().toString("yyyy/MM/dd");
    //得到最终的文件名
    filename = filePath + "/" + filename;
    try {
    //获取上传文件的流
    InputStream inputStream = file.getInputStream();
    //上传文件
    ossClient.putObject(bucketName, filename, inputStream);
    //拼接文件的url地址并返回
    //https://edu-guli-0306.oss-cn-beijing.aliyuncs.com/81ea0d8d-0e52-4749-be58-6292410deec6.jpg
    return "https://" + bucketName + "." + endpoint + "/" + filename;
    } catch (IOException e) {
    e.printStackTrace();
    return null;
    } finally {
    if (ossClient != null) {
    ossClient.shutdown();
    }
    }
    }
    }
    + +

    3.Aliyun Spring Boot OSS

    [使用文档](aliyun-spring-boot/README-zh.md at master · alibaba/aliyun-spring-boot · GitHub)

    +

    相比与上面的Demo,这个Demo更加的简便,编写的代码比较少

    +

    项目说明

    +

    ​ 如果您的应用是 Spring Cloud 应用,且需要使用阿里云的 OSS 服务进行云端的文件存储,例如电商业务中常见的商品图片存储,那么您可以使用 OSS starter 完成 Spring Cloud 应用的对象存储。

    +

    阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。更多 OSS 相关的信息,请参考 OSS官网

    +

    1.引入依赖

    <!-- 阿里云的对象存储的依赖-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alicloud-oss</artifactId>
    <version>2.1.0.RELEASE</version>
    <type>pom</type>
    </dependency>
    + +

    2.在配置文件中增加配置

    spring.cloud.alicloud.access-key=************
    spring.cloud.alicloud.secret-key=************
    spring.cloud.alicloud.oss.endpoint=************
    + +

    3.测试使用

    @Autowired
    OSSClient ossClient;
    /**
    * 测试使用阿里云上传视频
    */
    @Test
    void testUpload() throws FileNotFoundException {
    String bucketName = "gulimall-0611";
    String objectName = "test1.jpg";
    InputStream inputStream = new FileInputStream("C:\\Gong\\data\\test.jpg");
    // 创建PutObjectRequest对象。
    ossClient.putObject(bucketName,objectName,inputStream);
    ossClient.shutdown();
    }
    + +

    4.在项目中使用OSS进行文件的上传和下载的操作

    服务端签名直传 使用这种方式可以减轻本地服务器的压力

    +

    参考文档:https://help.aliyun.com/document_detail/91868.htm?spm=a2c4g.31927.0.0.44193d70tJDLJb#concept-ahk-rfz-2fb

    +image-20230611214209712 + +

    4.1 获取文件上传相关的签名信息

    获取签名信息的接口

    +
    package com.atguigu.gulimall.thirdparty.controller;

    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.MatchMode;
    import com.aliyun.oss.model.PolicyConditions;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.LinkedHashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/11
    * @Description 阿里云对象存储的控制器方法
    */

    @RestController
    public class OSSController {

    @Autowired
    OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;
    @Value("${spring.cloud.alicloud.secret-key}")
    private String accessKey;

    @RequestMapping("/oss/policy")
    public Map<String, String> policy() {
    // 填写Host地址,格式为https://bucketname.endpoint。
    String host = "https://"+bucket+"."+endpoint;
    // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
    //String callbackUrl = "https://192.168.0.0:8888";
    // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
    String format = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
    String dir = format+"/";

    // 创建ossClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
    Map<String, String> respMap = null;
    try {
    long expireTime = 30;
    long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
    Date expiration = new Date(expireEndTime);
    PolicyConditions policyConds = new PolicyConditions();
    policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
    policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

    String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
    byte[] binaryData = postPolicy.getBytes("utf-8");
    String encodedPolicy = BinaryUtil.toBase64String(binaryData);
    String postSignature = ossClient.calculatePostSignature(postPolicy);

    respMap = new LinkedHashMap<String, String>();
    respMap.put("accessId", accessId);
    respMap.put("policy", encodedPolicy);
    respMap.put("signature", postSignature);
    respMap.put("dir", dir);
    respMap.put("host", host);
    respMap.put("expire", String.valueOf(expireEndTime / 1000));
    // respMap.put("expire", formatISO8601Date(expiration));
    } catch (Exception e) {
    System.out.println(e.getMessage());
    }
    return respMap;
    }
    }
    + +

    访问接口之后返回的数据格式

    +
    {
    "accessId": "LTAI5tDDwk18w2S7w1KF24oT",//access-key
    "policy": "eyJleHBpcmF0aW9uIjoiMjAyMy0wNi0xMVQxNDoxNzozMi43OTdaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIzLzA2LzExLyJdXX0=",//策略,加密之后的结果
    "signature": "n67QffiYHceO2Hf7g8MDXgWsfpw=",//签名
    "dir": "2023/06/11/",//上传到的文件的地址
    "host": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com",//上传到的主机的
    "expire": "1686493052"//过期时间
    }
    + +

    前端相关的代码

    +

    前端相关的代码可以借鉴一下谷粒商城的前端上传文件的组件,里面封装了多文件上传和单文件上传的功能

    +

    image-20230612111805518

    +

    出现跨域的问题

    +

    image-20230612114314757

    +

    解决方法

    +

    image-20230612114238391

    +]]>
    + + 后端 + + + 云计算 + +
    + + Blog + /posts/32696.html + + +
    +
    + + + + + +
    +
    + +]]>
    + + 个人 + + + Blog + +
    + + 前端基础知识 + /posts/432.html + 一.VSCode的使用

    常用插件

    image-20230521233651126

    +

    二. ES6

    菜鸟ES6教程教程

    +

    1.简介

    ​ ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。

    +

    2.基础语法

    img + +

    2.1 声明变量

      +
    • let 声明的变量有严格的作用域 var没有严格的作用域
    • +
    • let变量只能声明一次
    • +
    • let不存在变量提升 var存在变量提升(先使用后声明)
    • +
    • const声明的是常量,不能重新赋值
    • +
    • 总计:使用let声明变量
    • +
    +
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>声明变量</title>
    </head>

    <body>

    <script>
    //1.有严格的作用域
    //var 声明的变量往往会越域
    //let 声明的变量有严格的作用域
    {
    var a = 1;
    let b = 1;
    }
    console.log(a) //不会报错
    console.log(b) //ReferenceError: b is not defined,有严格的作用域,所以会报错



    //2.let的变量只能声明一次
    var m = 1
    var m = 2
    let n = 3
    // let n = 4
    console.log(m) //不会报错
    console.log(n) //Identifier 'n' has already been declared 不能声明两次



    //3.let不存在变量提升 var存在变量提升
    console,log(x)
    var x = 10; //这里不会报错,可以先使用后定义


    //4.const声明的是常量,不能重新赋值

    //总计:使用let声明变量

    </script>
    </body>
    </html>
    + +

    2.2 解构表达式和模板字符串

      +
    • 数组的解构
    • +
    • 对象的解构
    • +
    • 字符串API的扩展
    • +
    • 模板字符串
    • +
    +
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>解构表达式</title>
    </head>

    <body>
    <script>
    //解构表达式
    //1.原始的数组的赋值方式
    let arr = [1, 2, 3]
    // let a = arr[0]
    // let b = arr[1]
    // let c = arr[2]

    //2.使用解构表达式给数组赋值(数组解构)
    let [a, b, c] = arr;
    console.log(a, b, c);

    //3.使用解构表达式给对象赋值(对象解构)
    const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
    }
    //原始的赋值方式
    // const name = person.name
    // const age = person.age
    // const language = person.language

    //使用解构赋值(对象解构)
    //const { name, age, language } = person
    //更换属性名
    const { name: abc, age, language } = person
    //将属性名name更换为abc
    console.log(abc, age, language)


    //4.字符串扩展
    //几个新的字符串API
    let str = "hello.vue"
    //是不是以什么开头
    console.log(str.startsWith("hello"));//true
    //是不是以什么结束
    console.log(str.endsWith(".vue"));//true
    //是不是包含什么
    console.log(str.includes("e"));//true
    console.log(str.includes("hello"));//true


    //5.模板字符串
    //${}插值
    let name = "Tom"
    let result = `你好${name}`
    console.log(result)
    //可以直接调用字符串的返回值组成一个新的字符串
    function fun(){
    return "这是一个函数"
    }
    result = `你好${name},我想说:${fun()}`
    console.log(result)

    </script>
    </body>
    </html>
    + +

    2.3 函数优化

      +
    • 默认初始值
    • +
    • 不定参数
    • +
    • 箭头函数
    • +
    +
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>函数优化</title>
    </title>
    </head>

    <body>
    <script>
    //1.函数优化
    //在ES6以前,我们无法给一个函数参数设置默认值,只能采用变通的写法
    function add(a, b) {
    //判断b是否为空 如果为空就给默认值1
    b = b || 1
    return a + b
    }
    //传一个参数
    console.log(add(10)) //结果是11

    //优化后 直接在参数上写上默认的值 如果没有传就会自动使用默认值
    function add2(a, b = 1) {
    return a + b
    }
    console.log(add2(20)) //结果是21


    //2.不定参数 类似于java里面的可变形参
    function fun(...values) {
    console.log(values.length)
    }
    fun(1, 2)
    fun(1, 2, 3)
    fun(1, 2, 3, 4)


    //3.箭头函数
    //以前的声明方法
    var print = function (obj) {
    console.log(obj)
    }
    var print1 = obj => console.log(obj)
    print1("hello") //输出hello

    //求两数之和
    //以前的方法
    var sum = function (a, b) {
    return a + b
    }
    //使用箭头函数
    var sum2 = (a, b) => a + b //方法体中只有一行代码 不用写return
    console.log(sum2(11, 3))

    var sum3 = (a, b) => {//方法体中有多行代码 写法如下
    c = a + b;
    return a + c
    }
    console.log(sum3(11, 3))

    //复杂一点的箭头函数
    const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
    }
    //原始的方法
    function hello(person) {
    console.log("hello " + person.name)
    }

    //单纯的箭头函数
    var hello2 = (obj) => console.log("hello," + obj.name)
    hello2(person)

    //箭头函数加解构表达式
    var hello3 = ({name}) => console.log("hello," + name)
    hello3(person)
    </script>
    </body>

    </html>
    + +

    2.4 对象优化

      +
    • 对象新增的API
    • +
    • 声明对象的简写方式
    • +
    • 对象函数属性的简写
    • +
    +
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>对象优化</title>
    </head>

    <body>
    <script>
    //1.对象相关的API
    //定义一个person对象
    const person = {
    name: "jack",
    age: 21,
    language: ['java', 'js', 'css']
    }
    //测试对象新增的API
    //返回对象所有的属性信息
    console.log(Object.keys(person)) //['name', 'age', 'language']
    //返回对象中vaules
    console.log(Object.values(person))// ['jack', 21, Array(3)]
    //返回key-vaues
    console.log(Object.entries(person))// [Array(2), Array(2), Array(2)]


    //将source1和source2的属性合并到target中
    const target = { a: 1 }
    const source1 = { b: 2 }
    const source2 = { c: 3 }
    //将source1和source2中的属性合并在一起
    Object.assign(target, source1, source2)
    console.log(target) //{a: 1, b: 2, c: 3}


    //2.声明对象的简写方式
    //声明两个变量
    const age = 23;
    const name = "张三";
    //原始的方式然后声明一个对象,属性值为这两个变量的值
    const person1 = { age: age, name: name }
    console.log(person1)

    //ES6的方式
    const person2 = { age, name }// 属性名和属性值代表的变量的名字一样可以省写
    console.log(person2)

    //3.对象函数属性的简写
    let person3 = {
    name: "jack",//对象的属性
    //以前的语法格式
    eat: function (food) {//对象的方法
    console.log(this.name + "在吃" + food)
    },

    //使用箭头有函数简写 箭头函数中this.name无法使用
    eat2: food => console.log(person3.name + "在吃" + food),

    eat3(food) {
    console.log(this.name + "在吃" + food)
    }
    }
    person3.eat("香蕉")
    person3.eat2("苹果")
    person3.eat3("梨子")

    //4.对象的扩展运算符
    //拷贝对象(深拷贝)
    //将person01的值拷贝给someone
    let person01 = { name: "Amy", age: 15 }
    let someone = { ...person01 }
    console.log(someone)

    //合并对象
    let age1 = { age: 15 }
    let name1 = { name: "Amy" }
    let person03 = { ...age1, ...name1 }//两个对象属性的合并一个对象中,如果有相同的值,后一个覆盖前面的那一个属性的值
    console.log(person03)//{age: 15, name: 'Amy'}

    </script>
    </body>

    </html>
    + +

    2.5 数组的map、reduce方法

      +
    • map方法
    • +
    • reduce方法
    • +
    +
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>map和reduce</title>
    </head>

    <body>
    <script>
    //数组中新增了map和reduce方法
    //map():接收一个函数,将原数组中的所有元素用这个函数处理后放入新数组返回
    let arr = ['1', '20', '-5', '3']
    //需求:将原数组中的每一个值乘以2并返回
    //第一种方法
    arr1 = arr.map((item) => {//item为数组中的每一个元素
    return item * 2
    })
    console.log(arr1)
    //第二种方法
    arr2 = arr.map(item => item * 2)
    console.log(arr2)

    //reduce()方法:为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素
    arr3 = [2, 40, -10, 6]
    //arr.reduce(callback,[initialValue]) //可以传一个回调函数callback,也可以传一个初始值initialValue
    //传入的是回调函数 可以传入的值有
    /**
    * previousValue (上一次调用回调返回的值,或者是提供的初始值initialValue)
    * currentValue (数组中当前被处理的元素)
    * index (当前元素在数组中的索引)
    * array (调用reduce的数组)
    * */
    let result = arr3.reduce((a, b) => {//这个方法相当于求数组中每一个元素的和
    console.log("上一次处理后的值:" + a)
    console.log("当前处理的值:" + b)
    return a + b;
    })
    console.log(result)
    /**结果
    * 上一次处理后的值:2
    当前处理的值:40
    上一次处理后的值:42
    当前处理的值:-10
    上一次处理后的值:32
    当前处理的值:6
    38
    **/
    </script>
    </body>

    </html>
    + +

    2.6 promise异步编排

    解决回调地域的问题

    +

    ​ Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

    +
    let promise = new Promise(function(resolve, reject) {
    console.log('Promise');
    resolve();
    });

    promise.then(function() {
    console.log('resolved.');
    });

    console.log('Hi!');

    // Promise
    // Hi!
    // resolved
    + +

    原有的基础上进一步的封装,链式调用

    +

    3.模块化

    3.1 什么是模块化

    ​ 模块化就是把代码进行拆分,分辨重复使用。类似java中的导包:要使用一个包,必须先导包。而JS中没有包的概念,换来的是模块。

    +

    模块功能主要由两个命令构成: export和import

    +
      +
    • export命令用于规定模块的对外接口
    • +
    • import命令用于导入其他模块提供的功能
    • +
    +

    3.2 export

    export不仅可以导出对象,一切js对象都可以导出。比如:基本类型的变量、函数、数组、对象

    +

    比如我们定义了一个js文件hello.js,里面有一个对象

    +
    const util = {
    dsum(a, b) {
    return a + b;
    }
    }
    + +

    我们可以使用export将这个对象导出

    +
    const util = {
    dsum(a, b) {
    return a + b;
    }
    }

    //导出对象
    export { util }
    + +

    也可以简写成

    +
    export const util = {
    dsum(a, b) {
    return a + b;
    }
    }
    + +

    当要导出多个值时,还可以简写。比如我有一个文件: user.js

    +
    var name  = "jack"
    var age = 21

    export{name, age}
    + +

    3.3 import

    比如我们之前定义了两个JS文件

    +

    hello.js

    +
    const util = {
    dsum(a, b) {
    return a + b;
    }
    }

    //导出对象
    export { util }
    + +

    user.js

    +
    var name = "jack"
    var age = 21
    function add(a, b) {
    return a + b
    }

    export { name, age, add }
    + +

    我们在main.js文件中导入上面的两个文件,并使用上面的两个文件的方法

    +
    import util from "./hello"  //这里只能是util 不能是其他的名字
    import {name,age,add} from './user' //可以不用全部导入 只用导入需要的即可

    util.sum(1,2)
    console.log(name);
    add(1,2)
    + +

    3.4 注意

    当我们这样定义时,导入的时候可以自定义导入时使用的变量名

    +
    export default {  //导出的时候这样写
    dsum(a, b) {
    return a + b;
    }
    }
    + +
    import xxx from "./hello"  //这里使用可以随意的取名
    + +

    三.Vue

    1.MVVM思想

      +
    • M:即 Model,模型,包括数据和一些基本操作
    • +
    • V:即View视图,页面渲染结果
    • +
    • VM:即 View-Model,模型与视图间的双向操作(无需开发人员干涉)
    • +
    +

    ​ 在MVVM之前,开发人员从后端获取需要的数据模型,然后要通过DOM操作 Model渲染到View中。而后当用户操作视图,我们还需要通过DOM获取View中的数据,然后同步到Model中。而MVVM中的VM要做的事情就是把DOM操作完全封装起来,开发人员不用再关心Model和View之间是如何互相影响的:

    +

    2.vue的简介

    ​ Vue (发音为 /vjuː/,类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。

    +

    Vue2官方文档

    +

    3.语法基础

    3.1 快速入门

    1.创建一个文件夹,用VSCode打开

    +

    2.下载vue的依赖

    +
    # 初始化项目
    npm init -y
    # 安装vue的依赖,这里vue2和vue3版本语法有些差别,使用的时候要注意
    npm install vue@2
    + +

    3.创建一个index.html文件

    +
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue入门</title>
    </head>

    <body>
    <div id="app">
    <h1>{{name}},是个傻子</h1>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    //这里最新版的vue上面无法获取到name的值
    let vm = new Vue({
    el: "#app",
    data: {
    name:"张三"
    }
    });
    </script>
    </body>

    </html>
    + +

    3.2 基本的语法和插件的安装

    3.2.1双向绑定v-model

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue入门</title>
    </head>

    <body>
    <div id="app">
    <!-- 双向绑定 -->
    <input type="text" v-model="num">
    <h1>{{name}}今年{{num}}岁了</h1>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    //声明式渲染
    let vm = new Vue({
    el: "#app",
    data: {
    name:"张三",
    num: 1
    }
    });
    </script>
    </body>
    </html>
    + +

    3.2.2事件处理

    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue入门</title>
    </head>

    <body>
    <div id="app">
    <!-- 双向绑定 -->
    <input type="text" v-model="num">
    <!-- 点击事件绑定 -->
    <button v-on:click="num++">点赞</button>
    <!-- 取消点赞 -->
    <button v-on:click="cancle()">取消点赞</button></button>
    <h1>有{{num}}为{{name}}点赞!</h1>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    //声明式渲染
    let vm = new Vue({
    el: "#app", //绑定元素
    data: {//封装数据
    name:"张三",
    num: 1
    },
    methods:{//封装方法
    cancle(){
    this.num--;
    }
    }
    });
    //v-xx 相当于指令
    //总结
    //1.创建vue的实例,关联页面的模板,将自己的数据(data)渲染到关联的模板
    //2.使用指令简化对dom的操作 例如 v-XX
    </script>
    </body>

    </html>
    + +

    3.2.3VUE代码提示插件和浏览器插件

    浏览器插件地址 直接解压,打开浏览器插件的开发者模式,选择加载已解压的文件,选择chrome目录即可

    +

    image-20230525192244158

    +image-20230525191246045 + +

    3.2.4常用指令

    v-html 和 v-text
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>常用指令</title>
    </head>
    <body>
    <div id="app">
    <!-- 1.显示类的指令 -->
    <!-- 插值表达式会发生插值闪烁的问题,先显示{{msg}}后显示内容 -->
    <!-- 打印的是<h1>Hello</h1> -->
    {{msg}} <br>
    {{1+1}} <br>
    {{hello()}} <br>
    <span v-text="msg"></span>
    <!-- 直接显示h1的大标题 -->
    <span v-html="msg"></span>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el:"#app",
    data:{
    msg:"<h1>Hello</h1>"
    },
    methods: {
    hello(){
    return "hello";
    }
    },
    })
    </script>
    </body>
    </html>
    + +
    v-bind单向绑定
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>单向绑定</title>
    </head>
    <body>
    <div id = "app">
    <!-- 单向绑定 -->
    <a v-bind:href="link">百度</a>
    <!-- class style也可以通过标签进行单向绑定 -->
    <span v-bind:style="{color:color}">你好</span>
    <span :style="{color:color}">使用缩写形式的单向绑定</span>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    link:"http://www.baidu.com",
    color: 'green'
    }
    })
    </script>
    </body>
    </html>
    + +
    v-model 双向绑定
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>双向绑定</title>
    </head>
    <body>
    <div id = "app">
    精通的语言:<br>
    <input type="checkbox" v-model="language" value="java">JAVA <br>
    <input type="checkbox" v-model="language" value="php">PHP <br>
    <input type="checkbox" v-model="language" value="python">Python <br>
    选中了: {{language}}
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    language:[]
    }
    })
    </script>
    </body>
    </html>
    + +

    image-20230526164858205

    +
    v-on 事件绑定
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件绑定</title>
    </head>

    <body>
    <div id="app">
    <!-- 绑定一个单机事件 -->
    <button v-on:click="num++">v-on:click的方式点赞</button><br>
    <!-- 简写的方式实现点赞 -->
    <button @click="num++">@click的方式点赞</button><br>
    <!-- 点击事件指定一个回调函数 -->
    <button v-on:click="cancle()">取消点赞</button><br>
    点赞的数量:{{num}}


    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    num: 0
    },
    methods: {
    cancle(){
    this.num--;
    }
    },
    })

    </script>
    </body>
    </html>
    + +image-20230526170222378 + +
    v-for 循环显示
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>循环遍历</title>
    </head>

    <body>
    <div id="app">
    <ul>
    <!-- 1.遍历数组 -->
    <!-- 可以使用v-for="user in users"遍历用户信息 -->
    <!-- 获取下标的信息加上一个index -->
    <li v-for="(user,index) in users" :key="">
    序号:{{index+1}} <br>
    姓名:{{user.name}} <br>
    性别:{{user.gender}} <br>
    年龄:{{user.age}} <br>
    <!-- 2.遍历对象,获取每一个属性信息 -->
    对象信息:
    <span v-for="(v,k,i) in user">属性索引={{i}}|属性名={{k}}|属性值={{v}}|</span><br>
    </li>
    <!-- 遍历的时候加上 :key 提高渲染的效率 -->
    <!-- <li v-for="(user,index) in users" :key=""> -->
    </ul>

    <!-- 遍历数组 -->
    <ul>
    <li v-for="(item,index) in nums" ::key="index">
    {{item}}
    </li>
    </ul>

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    users: [
    { name: "mary", gender: '女', age: 21 },
    { name: "tom", gender: '男', age: 35 },
    { name: "mike", gender: '男', age: 21 },
    { name: "amy", gender: '女', age: 64 }
    ],
    nums:[1,2,3,4,5,6,7,8,9,10,11]
    }
    })

    </script>
    </body>

    </html>
    + +

    image-20230526172228532

    +
    v-if 和 v-show
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>判断</title>
    </head>
    <body>
    <!-- v-if 条件判断,当结果为true时,所有的元素才会被渲染 -->
    <!-- v-show 当得到的结果为true时,所有的元素才会被显示 -->
    <div id="app">
    <button v-on:click="show = !show">点击显示与否</button></button>
    <!-- 1.使用v-if显示 -->
    <h1 v-if="show">if=看到我...</h1>
    <!-- 2.使用v-show显示 -->
    <h1 v-show="show">show=显示...</h1></h1>
    <!-- 多重判断 -->
    <!-- v-if、v-else-if、v-else -->
    <!-- 循环的时候也可以加上判断 -->

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    show:true
    }
    })

    </script>
    </body>
    </html>
    + +

    3.2.5 计算属性和侦听器

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>计算属性和侦听器</title>
    </head>

    <body>
    <div id="app">
    <!-- 计算属性 -->
    <ul>
    <li>西游记;价格:{{xyjPrice}},数量:<input type="number" v-model="xyjNum"></li>
    <li>水浒传;价格:{{shzPrice}},数量:<input type="number" v-model="shzNum"></li>
    <li>总价:{{totalPrice}}</li>
    {{msg}}
    </ul>
    <!-- 侦听器,监听属性值的变化 -->
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    xyjPrice: 99.98, //西游记价格
    shzPrice: 99.00, //水浒传价格
    xyjNum: 0, //西游记的数量
    shzNum: 0, //水浒传的数量
    msg: "" //提示的消息
    },
    computed: {//计算属性
    totalPrice() {//计算总价
    return this.xyjPrice * this.xyjNum + this.shzPrice * this.shzNum
    }
    },
    watch: {//监听器
    //newVal:新值
    //oldVal:旧值
    //下面的这个方法是监听当购买的数量超出了3,就提示超库存了,并把输入框的值设置为最大的3
    xyjNum: function (newVal, oldVal) {//监听西游记数量的变化
    console.log("newVal:" + newVal + " | " + "oldVal:" + oldVal)
    if (newVal >= 3) {
    this.msg = "库存超出了限制"
    this.xyjNum = 3
    } else {
    this.msg = ""
    }
    }
    }

    })

    </script>

    </body>
    </html>
    + +

    3.2.6 过滤器

    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>过滤器</title>
    </head>
    <div id="app">
    <!-- 过滤器常用来处理文本格式化的操作,过滤器可以用在两个地方:插值表达式和v-bind表达式 -->
    <ul>
    <li v-for="user in userList">
    <!-- 显示性别的时候不用代号显示,使用男女显示 -->
    <!-- 方式一:通过三元运算符的形式显示 -->
    <!-- 姓名:{{user.name}} | 性别:{{user.gender== 1?"男":"女"}} -->
    <!-- 方式二:使用过滤器的方式 其中 | 是管道符 genderFilter是过滤器 -->
    姓名:{{user.name}} | 性别:{{user.gender | genderFilter}}
    </li>
    </ul>
    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>

    Vue.filter("gFilter", function (val) {//全局过滤器
    if (val == 1) {
    return "男··"
    } else {
    return "女··"
    }
    })


    new Vue({
    el: "#app",
    data: {
    userList: [
    { id: 1, name: "jacky", gender: 1 },
    { id: 2, name: "peter", gender: 0 },
    ]
    },
    filters: {//局部的过滤器
    genderFilter(val) {
    if (val == 1) {
    return "男"
    } else {
    return "女"
    }
    }
    }
    })

    </script>

    <body>

    </body>

    </html>
    + +

    3.3 组件化

    ​ 在大型应用开发的时候,页面可以划分成很多部分。往往不同的页面,也会有相同的部分。例如可能会有相同的头部导航。但是每个页面都都独自开发,这无疑增加了开发的成本。所以我们会把页面的不同部分拆分成独立的组件,然后在不同的页面就可以共享这些组件,避免重复开发。

    +image-20230527215736955 + +

    3.3.1 全局组件

    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>组件化</title>
    </head>

    <body>
    <div id="app">
    <button @click="count++">我被点击了{{count}}次</button>

    <!-- 组件的使用 -->
    <counter></counter>

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>
    //1.全局声明一个组件
    Vue.component("counter", {//counter是组件的名称,可以随便起名,见名知意就行
    template: `<button @click="count++">我被点击了{{count}}次</button>`,//模板
    data() { //模板中的属性信息,这里的data必须是一个函数
    return {
    count: 1
    }
    }
    });
    new Vue({
    el: "#app",
    data: {
    count: 0
    }
    })

    </script>
    </body>

    </html>
    + +image-20230527220905922 + +

    3.3.2 局部组件

    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>组件化</title>
    </head>

    <body>
    <div id="app">
    <button @click="count++">我被点击了{{count}}次</button>

    <!-- 局部组件的使用 -->
    <button-counter></button-counter>

    </div>
    <script src="./node_modules/vue/dist/vue.js"></script>
    <script>

    //声明一个局部的组件
    const buttonCounter = {
    template: `<button @click="count++">我被点击了{{count}}次</button>`,
    data() {
    return {
    count: 1
    }
    }
    }
    new Vue({
    el: "#app",
    data: {
    count: 0
    },
    //声明一个局部的组件
    components:{
    'button-counter':buttonCounter
    }
    })

    </script>
    </body>

    </html>
    + +

    3.3 生命周期和钩子函数

    生命周期图

    +

    image-20230527221756442

    +

    3.4 Vue模块化开发

    1.全局安装webpack

    +
    npm install webpack -g
    + +

    2.全局安装vue脚手架

    +
    npm install -g @vue/cli-init
    + +

    3.初始化vue的项目

    +

    创建一个文件夹,打开cmd,切换到这个文件夹,执行以下的命令

    +
    # vue脚手架使用webpacke模板初始化一个项目
    vue init webpack 项目名称
    + +

    4.启动vue的项目

    +

    项目中的package.json中有scripts,代表我们能运行的命令

    +
    # 启动项目
    npm start
    #或者
    npm run dev
    # 将项目打包
    npm run build
    + +

    4.Eelment-UI

    官方文档:https://element.eleme.cn/#/zh-CN/component/quickstart

    +]]>
    + + 前端 + + + 前端 + +
    + + 面试题集锦 + /posts/29985.html + 【Java工程师面试宝典】学习说明_互联网校招面试真题面经汇总_牛客网 (nowcoder.com)

    +

    一.Java基础面试题

    1.谈谈你对面向对象的理解

    ​ 对比面向过程,是两种不同处理问题的角度,面向过程更注重事情的每一步骤及顺序,面向对象更注重事情有哪些参与者(对象),以及各自需要做什么。面向过程比较直接高效,面向对象易于复用、扩展和维护。

    +

    面向对象的三大基本特征:封装、继承、多态(父类应用指向子类对象)

    +

    2.JDK、JRE、JVM之间的区别

    JDK java 开发工具

    +

    JRE(Java Runtime Environment Java 运行环境)

    +

    JVM java虚拟机

    +

    JDK = JRE + 开发工具集(例如 Javac,java 编译工具等)

    +

    JRE = JVM + Java SE 标准类库(java 核心类库)

    +

    3.==和equals⽅法的区别

    ==:如果是基本数据类型,⽐较是值,如果是引⽤类型,⽐较的是引⽤地址
    equals:具体看各个类重写equals⽅法之后的⽐较逻辑,⽐如String类,虽然是引⽤类型,但是String类中重写了equals⽅法,⽅法内部⽐较的是字符串中的各个字符是否全部相等。

    +

    4.String,StringBuffer,StringBuilder的区别

    1.String是不可变的,如果尝试去修改,会新⽣成⼀个字符串对象,StringBuffer和StringBuilder是可变的,修改是在原对象上操作的

    +

    2.StringBuffer是线程安全的,StringBuilder是线程不安全的,所以在单线程环境下StringBuilder效率会更⾼

    +

    性能: StringBuilder > StringBuffer > String

    +

    5.重载和重写的区别

    重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同、方法返回值和访问修饰符可以不同,发生在编译时。

    +

    重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类,抛出异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类访问修饰符为私有,子类不能重写该方法。

    +

    6.接口和抽象类的区别

    1.抽象类可以有实现的方法和抽象的方法

    +

    2.抽象类的成员变量可以是各种类型的,接口中的成员变量只能是public static final类型(默认)的

    +

    3.抽象类只能继承一个,接口可以实现多个

    +

    4.关键字不同,接口的关键字是interface,抽象类的关键字是abstract

    +

    7.List和set的区别

    List:有序,按对象进入的顺序保存对象,可重复,允许多个Null元素对象,可以使用iterator取出所有的元素,在逐一遍历,还可以使用get(int index)获取指定下标的元素。

    +

    Set:无序,不可重复,最多允许有一个Null元素对象,取元素时只能用iterator接口取得所有元素,再逐一遍历各个元素。

    +

    8.hashCode()与equals()之间的关系

    hashCode()的作用是获取哈希码,也称散列码,哈希码的作用是确定该对象在哈希表中的索引位置。

    +
      +
    • 如果两个对象相等,那么它们的hashCode()值一定相同

      +
    • +
    • 如果两个对象hashCode()相等,它们并不一定相等

      +
    • +
    +

    9.ArrayList和LinkedList的区别

    ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问)。扩容机制:因为数组长度固定,超出长度存数据时需要新建数组,将老数组的数据拷贝到新数组,然后插入需要加入到数组中的数据。

    +

    LinkedList:基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合查询。遍历LinkedList必须使用iterator不能使用for循环,因为for循环体内通过get(i)取得某一元素时都需要对list重新遍历,性能消耗大。

    +

    10.HashMap和HashTable的区别?底层实现是什么?

    区别:

    +

    1.HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全

    +

    2.HashMap允许key和value为null,而HashTable不允许

    +

    底层实现: 数组 + 链表

    +

    11.ConcurrentHashMap

    ConcurrentHashMap和HashTable都是线程安全的,但ConcurrentHashMap相比HashTable性能更高

    +

    12.如何实现一个IOC容器

    1.配置文件配置包扫描路径

    +

    2.递归包扫描获取.class文件

    +

    3.反射、确定需要交给IOC管理的类

    +

    4.对需要注入的类进行依赖注入

    +

    13.什么是字节码?采用字节码的好处是什么?

    什么是字节码?

    +

    Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转化为特定系统的机器码执行,在java中,这种供虚拟机理解的代码叫做字节码(即扩展名为.class的文件)。

    +

    采用字节码的好处是什么?

    +

    Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无需重新编译便可在多种计算机上运行。

    +

    14.Java类加载器有哪些

    JDK有三个类加载器:

    +
      +
    1. bootstrap ClassLoader是ExClassLoader 的父类加载器,默认负责加载%JAVA_HOME%bin下的jar包和class文件
    2. +
    3. ExClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/bin/ext文件夹下的jar包和class文件
    4. +
    5. AppClassLoader是自定义加载器的父类,负责加载classpath下的类文件,系统类加载器,线程上下文加载器
    6. +
    +

    自定义加载器的方法: 继承ClassLoader实现自定义加载器

    +

    15.双亲委派模型

    双亲委派模型的执行流程是这样的:

    +

    1、当加载一个类时,会先从应用程序类加载器的缓存里查找相应的类,如果能找到就返回对象,如果找不到就执行下面流程;

    +

    2、在扩展加载器缓存中查找相应的类,如果能找到就返回对象,如果找不到就继续下面流程;

    +

    3、在启动类加载器中查询相应的类,如果找到就返回对象,如果找不到就继续下面流程;

    +

    4、在扩展加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就继续下面流程;

    +

    5、在应用程序类加载器中查找并加载类,如果能找到就返回对象,并将对象加入到缓存中,如果找不到就返回 ClassNotFound 异常。

    +

    加载流程如下图所示:

    +

    image-20230904161407239

    +

    一般“双亲”指的是“父亲”和“母亲”,而在这里“双亲”指的是类加载类先向上找,再向下找的流程就叫做双亲委派模型。

    +

    双亲委派模型的好处

    +
      +
    1. 主要是为了安全性,避免用户自己编写的类动他替换java的一些核心类,比如String
    2. +
    3. 同时避免了类的重复加载,因为jvm中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载就是不同的两个类
    4. +
    +

    16.Java中的异常体系

      +
    1. Java中所有异常都来自顶级父类Throwable
    2. +
    3. Throwable下面有两个子类Exception和Error
    4. +
    5. Error是程序无法处理的错误,一旦出现这个错误,程序将被迫停止运行;Exception不会导致程序停止
    6. +
    7. Exception有分为两个部分RunTimeException运行时异常和CheckedException检查异常
    8. +
    9. RunTimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。CheckedException常常发生在程序编译过程中,会导致程序编译不通过。
    10. +
    +

    17.GC如何判断对象可以被回收

      +
    • 引用计数法:每一个对象有一个引用计数属性,新增一个引用计数加1,引用释放减1,计数为0时可以回收。(java中没有使用这个方法,原因:可能会出现A引用了B,B又引用了A,这时就算他们都不再使用了,但是因为他们互相引用,计数器=1永远无法被回收)
    • +
    • 可达性分析法:从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就可以判断是可回收对象。
    • +
    +

    18.线程的生命周期,线程有哪些状态

    线程通常有五种状态:创建、就绪、运行、阻塞、死亡状态

    +

    阻塞又分为三种情况:等待阻塞、同步阻塞、其他阻塞

    +
      +
    1. 新建状态:新创建了一个线程对象
    2. +
    3. 就绪状态:线程对象创建后,其他线程调用了该对象的start()方法。该线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
    4. +
    5. 运行状态:就绪状态的线程获取了CPU,执行了程序代码
    6. +
    7. 阻塞状态:阻塞状态是线程因为某种原因放弃了CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态
    8. +
    9. 死亡状态:线程执行完了或者因为异常退出了run()方法,该线程结束生命周期
    10. +
    +

    19.sleep(),wait(),join(),yield()的区别

    sleep(),wait()的区别

    +
      +
    1. sleep()是Thread类的静态本地方法,wait()是Object类的本地方法
    2. +
    3. sleep()不会释放锁(把锁带着进入冻结状态),wait会释放锁
    4. +
    5. sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字
    6. +
    7. sleep不需要被唤醒(休眠之后退出阻塞),但是wait需要(不指定时间需要被别人中断)
    8. +
    9. sleep一般用于当前线程休眠,或者轮循暂停操作,wait则用于多线程之间的通信
    10. +
    11. sleep会让出cpu执行时间且强制上下文切换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行
    12. +
    +

    yield()执行后线程直接进入就绪状态,马上释放了cpu,但是依然保留了cpu的执行资格,所有有可能cpu下次进行线程调度还会让这个线程取到执行权

    +

    join()执行后线程进入阻塞状态,例如在线程B中调用了A的join(),那线程B会进入到阻塞队列,直到线程A结束或者中断线程

    +

    20.说说你对线程安全的理解

    线程安全讲的不是线程安全,应该是内存安全堆是共享内存,可以被所有线程访问

    +

    线程安全的定义:当多个线程访问一个对象的时候,如果不用进行额外的同步控制或者其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个线程数是安全的。

    +

    产生线程安全问题的原因: 在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内所有线程都可以访问到该区域,这就是造成问题的潜在原因。

    +

    21.说说你对守护线程的理解

    守护线程:为非守护线程(用户线程)提供服务的线程,任何一个守护线程都是整个jvm中所有非守护线程的守护线程。

    +

    守护线程的作用:

    +

    举例:GC垃圾回收机制,就是一个经典的守护线程,当我们的程序不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就没事可做,所以当垃圾回收线程是jvm上仅剩的线程时,垃圾回收线程会自动离开,它始终再低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

    +

    应用场景:

    +
      +
    1. 为其他的线程提供服务支持情况
    2. +
    3. 或者在任何情况下,程序结束时,这个线程必须正常的且立刻关闭,就可以作为守护线程来使用
    4. +
    +

    22.ThreadLocal的原理和使用场景

    同一个线程中通过ThreadLocal存进去的数据,在任何位置取出来是一致的

    +

    原理

    +

    每一个Thread对象均含有一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及对应的值。

    +

    当执行set()方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

    +

    get方法的执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。

    +

    由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立,互不影响,因此不会存在线程安全性问题,从而无需使用同步机制来保证多条线程访问容器的互斥性。

    +

    使用场景

    +
      +
    1. 在进行对象的跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束
    2. +
    3. 线程间数据隔离
    4. +
    5. 进行事务操作,用于存储事务信息
    6. +
    7. 数据库连接,Session会话管理
    8. +
    +

    23.ThreadLocal内存泄漏原因,怎么避免

    内存泄漏:不会使用的对象或者变量占用的内存不能被回收,就是内存泄漏(内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏的危害可以忽略,但内存泄漏堆积后果很严重,无论多少内存,迟早会被占光,导致OOM。)

    +

    强引用:使用最普遍的引用(通过new),一个对象具有强应用,不会被垃圾回收器回收,即使是内存不足。我们想要取消强引用,可以显示的将引用赋值为null,jvm在合适的时间就会回收该对象。

    +

    ThreadLocal内存泄漏的根源

    +

    由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除对应key就会导致内存泄漏

    +

    怎么避免

    +
      +
    1. 每次使用ThreadLocal都调用它的remove()方法清除数据
    2. +
    3. 将ThreadLocal变量定义成private static,这样就一直村子ThreadLocal的强应用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉。
    4. +
    +

    24.产生内存泄漏的原因有哪些

      +
    1. 资源未关闭或释放导致内存泄露(io资源,数据库的连接)
    2. +
    3. 使用 ThreadLocal 造成内存泄露
    4. +
    5. 静态集合类引起内存泄漏,如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。生命周期长的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收。
    6. +
    7. 重写了 finalize() 的类,如果 finalize() 方法重写的不合理或 finalizer 队列无法跟上 Java 垃圾回收器的速度,那么迟早,应用程序会出现 OutOfMemoryError 异常
    8. +
    +

    25.并发、并行、串行的区别

    串行在时间上不可能发生重叠,前一个任务没有搞定,下一个任务只能等

    +

    并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行

    +

    并发允许两个任务彼此干扰,同一时间点,只有一个任务运行,交替执行

    +

    26.并发的三大特性

      +
    • 原子性:是指在在一个操作中,CPU不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
    • +
    • 可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他的线程能够立即看到修改的值。
    • +
    • 有序性:虚拟机再进行代码编译的时,对于那些改变顺序之后不会对最终的结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将它们重排序。
    • +
    +

    27.为什么用线程池?解释线程池参数?

    为什么

    +
      +
    1. 降低资源的消耗;提高线程的利用率,降低创建和销毁现成的消耗
    2. +
    3. 提高响应速度,任务来了,直接有线程可用执行,不是创建线程之后再执行
    4. +
    5. 提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控
    6. +
    +

    线程池参数

    +
      +
    • corePoolSize代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建之后不会消除,而是一种常驻线程。
    • +
    • maxnumPoolSize代表最大线程数,它与核心线程数相对应,表示最大允许被创建的线程数,比如当前任务较多,将核心线程数都用完了,还无法满足要求时,此时就会创建新的线程,但是线程池总数不会超过最大线程数
    • +
    • keepAliveTime空闲线程存活时间,当一个可被回收的线程的空闲时间大于keepAliveTime,就会被回收; unit:keepAliveTime的时间单位
    • +
    • workQueue工作队列,存放待执行任务的队列:当提交的任务数超过核心线程数大小后,再提交的任务就存放在工作队列,任务调度时再从队列中取出任务。它仅仅用来存放被execute()方法提交的Runnable任务。工作队列实现了BlockingQueue接口。
    • +
    • threadFactory线程工厂,创建线程的工厂,可以设定线程名、线程编号等。
    • +
    • handler拒绝策略,当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需实现RejectedExecutionHandler接口。
    • +
    +

    28.简述线程池的处理流程

    image-20230905204135018

    +

    29.线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程?

    线程池中阻塞队列的作用?

    +

    1、一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务。

    +

    2、阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源。

    +

    3、阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占着cpu资源。

    +

    为什么是先添加队列而不是先创建最大线程?

    +

    在创建新线程的时候。是要获取全局锁的,这个时候其它就得阻塞,影响了整体效率。

    +

    就好比一个企业里面有10个(core)正式工的名额,最多招10个正式工,要是任务超过正式工人数,(task > core)的情况下,工厂领导(线程池)不是首先扩招工人,还是这10个人,但是任务会稍微的积压一下,即先放到队列去(代价低),10个正式工慢慢干,迟早会干完的,要是任务还在继续增加,超过正式工的加班忍耐极限了(队列满了),就得招外包帮忙了(注意是临时工),要是正式工加上外包还是不能完成任务,那新来的任务就会被领导拒绝了(线程池的拒绝策略)。

    +

    30.线程池中线程复用原理

    线程池将线程和任务解耦,线程是线程,任务是任务,摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制。

    +

    在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行都会调用Thread.start()来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来。

    +

    31.Spring是什么

    轻量级的开源的j2EE框架。他是一个容器框架,用来装javabean(java对象),中间层框架(万能胶)可以起一个连接作用,比如说把struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁

    +

    Spring是一个轻量级的控制反转(ioc)和面向切面(aop)的容器框架

    +
      +
    • 从大小与开销两方面而言Spring都是轻量级的
    • +
    • 通过控制反转(ioc)的技术达到松耦合的目的
    • +
    • 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚的开发
    • +
    • 包含并管理应用对象(bean)的配置和生命周期,这个意义上是一个容器
    • +
    • 将简单的组件配置、组合成复杂的应用,这个意义上是一个框架
    • +
    +

    32.谈谈你对AOP的理解

    系统是由许多不同的组件所组成的,每一个组件各负责一块特定的功能。除了实现自身的核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为他们会跨域系统的多个组件。

    +

    当我们需要将分散的对象引入公共行为的时候,OOP则显得无能为力,也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能

    +

    AOP:将程序中的交叉业务逻辑(比如安全、日志、事务),封装成一个切面。然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或者某些功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外做一些事情,在某个方法执行之后额外做一些事情。

    +

    33.谈谈你对IOC的理解

    容器概念、控制反转、依赖注入三方面理解

    +

    容器概念

    +

    ioc容器:实际上就是个map(key、value),里面存的就是各种对象(在xml里配置bean节点、@Repository、@Service、@Controller),在项目启动的时候会读取配置文件路面的bean节点。

    +

    控制反转

    +

    在没有引入ioc容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

    +

    引入ioc容器之后,对象A和对象B之间失去了直接联系。当对象A运行到需要对象B的时候,ioc容器会主动创建一个对象B注入到对象A需要的地方。

    +

    通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变成了被动行为,控制权颠倒过来了,这就是控制反转这个名称的由来。

    +

    依赖注入

    +

    “获得依赖对象的过程被反转了”。控制器反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

    +

    34.BeanFactory和ApplicationContext有什么区别?

    ApplicationContext是BeanFactory的子接口

    +

    ApplicationContext提供了更完整的功能

    +

    1、继承了MessageSource,因此支持国际化

    +

    2、统一资源文件的访问方式

    +

    3、提供在监听器中注册bean的事件

    +

    4、同时加载多个配置文件

    +

    5、载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层

    +

    35.深拷贝和浅拷贝

    深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实列对象的引用

    +
      +
    • 浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是一个对象
    • +
    • 深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的类执行指向的不是同一个对象
    • +
    +

    举例:A对象中有一个user属性,A1拷贝A对象,两个user指向同一个对象的话,就是浅拷贝,反之是深拷贝

    +

    36.TCP和UDP有什么区别?TCP为什么是三次握手,而不是两次?

    TCP (Transfer Control Protocol)是一种面向连接的、可靠的、传输层通信协议

    +
      +
    • 面向连接的,点对点的通信,高可靠,效率比较低、占用系统的资源比较多(类似于打电话)
    • +
    +

    UDP (User Datagram Protocal) 是一种无连接、不可靠的、传输层通信协议

    +
      +
    • 不需要连接、发送方不管接收方有没有准备好,直接发消息;可以进行广播发送的;传输不可靠,有可能会丢失消息;效率比较高;协议比较简单,占用的系统资源少(类似于广播)
    • +
    +

    TCP为什么是三次握手,而不是两次?

    +

    如果是两次握手,可能会造成连接资源浪费的问题

    +

    37.JVM中有哪些垃圾回收算法?

    MarkSweep标记清除算法

    +

    Copying拷贝算法

    +

    MarkCompack标记压缩算法

    +

    二.框架篇相关面试题

    1.Spring Bean的生命周期

    1.创建前准备阶段

    +

    这个阶段主要是在开始Bean加载之前,从Spring上下文和相关配置中解析并查找Bean有关的配置内容,比如init-method-容器在初始化bean时调用的方法、destory-method,容器在销毁Bean时调用的方法。以及,BeanFactoryPostProcessor这类的bean加载过程中的前置和后置处理。这些类或者配置其实是Spring提供给开发者,用来实现Bean加载过程中的扩展机制,在很多和Spring集成的中间件经常使用,比如Dubbo。

    +

    2.创建实例阶段

    +

    这个阶段主要是通过反射来创建Bean的实例对象,并且扫描和解析Bean声明的一些属性。

    +

    3.依赖注入阶段

    +

    在这个阶段,会检测被实例化的Bean是否存在其他依赖,如果存在其他依赖,就需要对这些被依赖Bean进行注入。比如通过@Autowired、@Setter等依赖注入的配置。在这个阶段还会触发一些扩展的调用,比如常见的扩展类:BeanPostProcessors(用来实现Bean初始化前后的回调)、InitializingBean类(这个类有一个afterPropertiesSet()方法,给属性赋值)、还有BeanFactoryAware等等。

    +

    4.容器缓存阶段

    +

    容器缓存阶段主要是把Bean保存到IoC容器中缓存起来,到了这个阶段,Bean就可以被开发者使用了。这个阶段涉及到的操作,常见的有init-method这个属性配置的方法,会在这个阶段调用。比如BeanPostProcessors方法中的后置处理器方法postProcessAfterInitialization,也是在这个阶段触发的。

    +

    5.销毁实例阶段

    +

    这个阶段,是完成Spring应用上下文关闭时,将销毁Spring上下文中所有的Bean。如果Bean实现了DisposableBean接口,或者配置了destory-method属 性,将会在这个阶段被调用。

    +

    2.Spring框架中的单例Bean是线程安全的吗?

    不是线程安全的!

    +

    Sping中Bean默认是单例模式的,框架中并没有对Bean进行多线程的封装处理。

    +

    线程安全这个问题,要从单例与原型Bean分别进行说明。

    +

    「原型Bean」对于原型Bean,每次创建一个新对象,也就是线程之间并不存在Bean共享,自然是不会有线程安全的问题。

    +

    「单例Bean」对于单例Bean,所有线程都共享一个单例实例Bean,因此是存在资源的竞争。

    +

    如果单例Bean,是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行「查询」以外的操作,那么这个单例Bean是线程安全的。比如Spring mvc 的 Controller、Service、Dao等,这些Bean大多是无状态的,只关注于方法本身。

    +

    3.Spring Bean作用域

    Spring 的 bean 作用域(scope)类型有5种:

    +

    1、singleton:单例,默认作用域。

    +

    2、prototype:原型,每次创建一个新对象。

    +

    3、request:请求,每次Http请求创建一个新对象,适用于WebApplicationContext环境下。

    +

    4、session:会话,同一个会话共享一个实例,不同会话使用不用的实例。

    +

    5、global-session:全局会话,所有会话共享一个实例。

    +

    4.Spring框架中用到了哪些设计模式?

      +
    1. 简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。Spring中的BeanFactory就是简单工厂模式的体现,根据传入的一个唯一的标识来获得Bean对象,但是否在传入参数后创建还是传入参数前创建这个要根据具体情况来定。
    2. +
    3. 工厂方法:实现了FactoryBean接口的bean是一类叫做factory的bean.其特点是,spring会在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是这个bean.getObject()方法的返回值。
    4. +
    5. 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。spring对单例的实现:spring中的单例模式完成了后半句话,即提供了全局访问点BeanFactory,但没有从构造器级别去控制单例,这时因为spring管理的是任意的java对象。
    6. +
    7. 适配器模式:spring中定义了一个适配接口,使得每一种Controller有一种对应的适配器实现类,让适配器代替controller执行响应的方法。这样扩展controller时,只需要增加一个适配类就完成了springMvc的扩展了。
    8. +
    9. 装饰器模式:动态地给一个对象增加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。spring中用到的装饰器模式在类名上有两种表现:一是类名中含有wrapper,另一种时类名中含有Decorator.
    10. +
    11. 动态代理:切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面的。 织入:把切面应用到目标对象并创建新代理对象的过程。
    12. +
    13. 观察者模式:Spring的事件驱动模型使用的是观察者模式,spring中observer模式常用的地方是listener的实现。
    14. +
    15. 策略模式:spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,spring框架本身大量使用了Resource接口来访问底层资源。
    16. +
    +

    5.spring中事务实现方和原理式以及隔离级别?

    实现方式

    +

    在使用Spring框架时,可以有两种使用事务的方式,一种是编程式,一种是申明式的,@Transactional注解就是申明式的。

    +

    首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功失败。

    +

    原理

    +

    在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法存在@Transactional注解,那么代理逻辑会先把事务的自动提价设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。

    +

    隔离级别

    +

    spring的事务隔离级别就是数据库的隔离级别外加一个默认级别

    +
      +
    1. read uncommitted(未提交读)
    2. +
    3. read committed(提交读、不可重复读)
    4. +
    5. repeatable read (可重复读)
    6. +
    7. serializable(可串行化)
    8. +
    +

    注:数据库设置的隔离级别会被spring的配置覆盖

    +

    6.Spring事务传播机制

    多个事务方法相互调用的时,事务是如何在这些方法间传播

    +

    方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。

    +

    REQUIRED(Spring默认的事务传播类型)如果当前没有事务,则自己创建一个事务,如果当前存在事务,则加入这个事务

    +

    SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行

    +

    MANDATORY:当前存在事务,则加入当前事务,如果当前的事务不存在,则抛出异常

    +

    REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务

    +

    NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务

    +

    NEVER:不使用事务,如果当前事务存在,则抛出异常

    +

    NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)

    +

    7.Spring事务什么时候会失效?

    Spring事务原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了,常见情况有如下几种

    +
      +
    1. 发生自调用,类里面使用this调用本类的方法,此时这个this对象不是代理类,而是UserService对象本身
    2. +
    3. 方法不是public的,非要在非public上使用事务,可以开启Aspectj代理模式
    4. +
    5. 数据库不支持事务,例如使用的MyISAM存储引擎
    6. +
    7. 没有被spring管理
    8. +
    9. 异常被吃掉,事务不会回滚(或者抛出的异常没有定义,默认为RuntimeException)
    10. +
    +

    8.什么是bean的自动装配,有哪些方式?

    autowire属性有五种装配的方式

    +
      +
    • no – 缺省情况下,自动配置是通过“ref”属性手动设定
    • +
    +
    <!--手动装配:以value或ref的方式明确指定属性值都是手动装配。 需要通过‘ref’属性来连接bean-->
    + +
      +
    • byName-根据bean的属性名称进行自动装配
    • +
    +
    <!--Cutomer的属性名称是person,Spring会将bean id为person的bean通过setter方法进行自动装配-->
    < bean id=“cutomer” class=“com.xxx.xxx.Cutomer” autowire=“byName”/>
    < bean id=“person” class=“com.xxx.xxx.Person”/>
    + +
      +
    • byType-根据bean的类型进行自动装配
    • +
    +
    <!--Cutomer的属性person的类型为Person,Spirng会将Person类型通过setter方法进行自动装配-->
    < bean> id=“cutomer” class=“com.xxx.xxx.Cutomer” autowire=“byType”/>
    < bean> id=“person” class=“com.xxx.xxx.Person”/>
    + +
      +
    • constructor-类似byType,不过是应用于构造器的参数。如果一个bean与构造器参数的类型形
      同,则进行自动装配,否则导致异常
    • +
    +
    <!--Cutomer构造函数的参数person的类型为Person,Spirng会将Person类型通过构造方法进行自动装配-->
    < bean> id=“cutomer” class=“com.xxx.xxx.Cutomer” autowire=“construtor”/>
    < bean> id=“person” class=“com.xxx.xxx.Person”/>
    + +
      +
    • autodetect-如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式 进行自动装配
    • +
    +
    <!--如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配-->
    + +

    9.Spring Boot、SpringMVC和Spring有什么区别?

    Spring是一个IOC容器,用来管理Bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题,更方便将不同方法中的共同处理抽取成切面,自动注入给方法执行,比如日志、异常等

    +

    SpringMvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接受请求,然后定义了一套路由策略(url到handle的映射)及适配执行handle,将handle结果使用视图解析技术生成视图展现给前端

    +

    SpringBoot是Spring提供的一个快速开发工具包,让程序员更方便、更快速的开发spring + springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制),redis、mongodb、es可以开箱即用

    +

    10.SpringMVC的工作流程

      +
    1. 用户发送请求到前端控制器DispatcherServlet
    2. +
    3. DispatcherServlet收到请求调用HandlerMapping处理器映射器
    4. +
    5. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器及处理器拦截器(如果有则生成)一并返回给DispatcherServlet
    6. +
    7. DispatcherServlet调用HandlerAdapter处理器适配器
    8. +
    9. HandlerAdapter调用具体的处理器(controller,也叫后端控制器)
    10. +
    11. Controller执行完成返回ModelAndView
    12. +
    13. HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet
    14. +
    15. DispatcherServlet将ModelAndView传给ViewReslover视图解析器
    16. +
    17. ViewReslover解析后返回具体view
    18. +
    19. DispatcherServlet根据view进行渲染视图(即将模型数据填充到视图中)
    20. +
    21. DispatcherServlet响应给用户
    22. +
    +

    image-20230911141826287

    +

    11.SpringMvc中的九大组件

    Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人

    +
      +
    1. HandlerMapping是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理呢?这就是HandlerMapping需要做的事。

      +
    2. +
    3. HandlerAdapter,从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。

      +
    4. +
    5. HandlerExceptionResolver其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢?这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。具体来说,此组件的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染。

      +
    6. +
    7. ViewResolverViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

      +
    8. +
    9. RequestToViewNameTranslatorViewName是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。RequestToViewNameTranslator在Spring MVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现。

      +
    10. +
    11. LocaleResolver解析视图需要两个参数:一是视图名,另一个是Locale。视图名是处理器返回的,Locale是从哪里来的?这就是LocaleResolver要做的事情。LocaleResolver用于从request解析出Locale,Locale就是zh-cn之类,表示一个区域,有了这个就可以对不同区域的用户显示不同的结果。SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。

      +
    12. +
    13. ThemeResolver用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、如图片、css样式等。SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格。SpringMVC中跟主题相关的类有 ThemeResolver、ThemeSource和Theme。主题是通过一系列资源来具体体现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作。然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作。最后从主题中获取资源就可以了。

      +
    14. +
    15. MultipartResolver用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName->File结构的Map。此组件中一共有三个方法,作用分别是判断是不是上传请求,将request包装成MultipartHttpServletRequest、处理完后清理上传过程中产生的临时资源。

      +
    16. +
    17. FlashMapManager用来管理FlashMap的,FlashMap主要用在redirect中传递参数。

      +
    18. +
    +

    12.SpringBoot自动配置原理(简答的阐述见下面15条)

    自动装配,简单来说就是自动把第三方组件的Bean装载到Spring IOC器里面,不需要开发人员再去写Bean的装配配置。

    +

    在Spring Boot应用里面,只需要在启动类加上@SpringBootApplication注解就可以实现自动装配。@SpringBootApplication是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration。

    +

    image-20230911195638524

    +

    13.MyBatis的优缺点

    优点:

    +
      +
    1. 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用
    2. +
    3. 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
    4. +
    5. 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
    6. +
    7. 能够与Spring很好的集成
    8. +
    9. 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护
    10. +
    +

    缺点:

    +

    1.
    SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
    2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

    +

    14.MyBatis中#{}和${}的区别是什么?

      +
    1. 功能不同:${} 是直接替换,而 #{} 是预处理
    2. +
    3. 使用场景不同:普通参数使用 #{},如果传递的是 SQL 命令或 SQL 关键字,需要使用 ${},但在使用前一定要做好安全验证
    4. +
    5. 安全性不同:使用 ${} 存在安全问题(SQL注入),而 #{} 则不存在安全问题
    6. +
    +

    15.Spring Boot自动装配的过程

    image-20230921145126572

    +

    整个自动装配的过程是:Spring Boot通过@EnableAutoConfiguration注解开启自动配置,加载spring.factories中注册的各AutoConfiguration类,当某个AutoConfiguration类满足其注解@Conditional指定的生效条件(Starters提供的依赖、配置或Spring容器中是否存在某个Bean等)时,实例化该AutoConfiguration类中定义的Bean(组件等),并注入Spring容器,就可以完成依赖框架的自动配置。

    +

    二.Mysql相关面试题

    1.存储引擎InnoDB 与MyISAM 的区别

      +
    1. 数据存储的方式不同,MyISAM中的数据和索引是分开存储的,而InnoDB是把索引和数据存储在同一个文件里面
    2. +
    3. 对于事务的支持不同,MyISAM不支持事务,而InnoDB支持ACID特性的事务处理
    4. +
    5. 对于锁的支持不同,MyISAM只支持表锁,而InnoDB可以根据不同的情况,支持行锁,表锁,间隙锁,临键锁
    6. +
    7. MyISAM不支持外键,InnoDB支持外键因此基于这些特性,我们在实际应用中,可以根据不同的场景来选择合适的存储引擎
    8. +
    9. 比如如果需要支持事务,那必须要选择InnoDB。如果大部分的表操作都是查询,可以选择MyISAM
    10. +
    +

    2.索引的基本原理

    原理:把无序的数据变成有序的查询

    +
      +
    1. 把创建了索引的列的内容进行排序
    2. +
    3. 对排序结果生成倒排表
    4. +
    5. 在倒排表内容上拼上数据地址链
    6. +
    7. 在查询的时候,先拿到倒排表的内容,再取出数据地址链,从而拿到数据
    8. +
    +

    3.索引失效的场景

      +
    1. 索引在使用的时候没有遵循最左匹配法则.
    2. +
    3. 模糊查询,如果%号在前面也会导致索引失效。
    4. +
    5. 在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。
    6. +
    7. 如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效
    8. +
    9. 查询的时候发生了类型转换,在查询的时候做了运算的操作和模糊查询也会导致索引失效
    10. +
    +

    4.事务的隔离级别有哪些?MySQL 的默认隔离级别是什么?

    读未提交(Read Uncommitted)可能读到其他事务未提交的数据,也叫做脏读
    读已提交(Read Committed)两次读取结果不一致,叫做不可重复读
    可重复读(Repeatable Read)是mysql默认的隔离级别,每次读取的结果都一样,当时可能产生幻读
    串行化(Serializable)一般是不会使用的,他给每一行读取的数据加锁,会导致大量超时和锁竞争的问题
    Mysql 默认的事务隔离级别是可重复读(Repeatable Read)

    +

    5.事务的基本特性

    事务的基本特性ACID分别是:

    +
      +
    1. A 原子性:一个事务的操作要么全部成功,要么全部失败
    2. +
    3. C 一致性:数据库从一个一致性的状态转换到另一个一致性的状态
    4. +
    5. I 隔离性:一个事务的修改在最终提交前,对其他事务是不可见的
    6. +
    7. D 持久性 一旦事务提交,所做的修改就会永远的保存到数据库中
    8. +
    +

    6.怎么处理慢查询

    SQL查询慢的原因

    +
      +
    1. 查询没有命中索引
    2. +
    3. 查询了不需要的数据列
    4. +
    5. 数据量太大
    6. +
    +

    根据上面的原因给出优化的措施

    +
      +
    1. 分析语句的执行计划,然后获得其使用索引的情况,然后修改语句或者索引,使得语句可以尽可能的命中索引
    2. +
    3. 分析语句,是否查询了不需要的数据列
    4. +
    5. 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表
    6. +
    +

    7.ACID靠什么保证的

    A 原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql

    +

    C 一致性由其他的三大特性保证 程序代码要保证业务上的一致性

    +

    I 隔离性由MVCC来保证

    +

    D 持久性由内存 + redo log 来保证。Mysql修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redo log恢复

    +

    三.Redis相关面试题

    1.Redis过期键的删除策略

    redis是key-value数据库,我们可以设置redis中缓存的key的过期时间。redis的过期策略就是指当redis中缓存的key过期了,redis该如何处理。

    +
      +
    • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则删除。该策略可以最大化地节省CU资源,但是对内存非常的不友好。极端地情况可能出现大量地过期key没有再次被访问,从而不被清除,占用大量内存。
    • +
    • **定期过期:**每隔一定地时间,会扫描一定数量地数据库地expires字典中一定数量地key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过定时扫描的时间间隔和每次扫描的限定功耗,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
    • +
    +

    在Redis中同时使用了这两种策略

    +

    2.Redis的线程模型,单线程为什么快

    IO多路复用机制监听多个Socket

    +

    单线程快的原因:

    +
      +
    1. 纯内存操作
    2. +
    3. 核心是基于非阻塞的IO多路复用机制
    4. +
    5. 单线程反而避免了多线程的频繁上下文切换带来的性能问题
    6. +
    +

    3.缓存雪崩、缓存穿透、缓存击穿

    缓存雪崩 缓存在同一时间大面积失效,所以,后面的请求都会落在数据库上,造成数据库短时间内承受大量请求而崩掉。

    +

    解决方案:

    +
      +
    1. 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
    2. +
    3. 给每一个缓存数据增加响应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存
    4. +
    5. 缓存预热,启动系统之前先把热点数据放在缓存中去
    6. +
    7. 互斥锁
    8. +
    +

    缓存穿透 缓存和数据库中都没有数据,导致所有的请求都落在数据库上,造成数据库短时间内承受大量请求

    +

    解决方案:

    +
      +
    1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截掉
    2. +
    3. 从缓存取不到数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短一些,如30秒(设置太长会导致正常的情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
    4. +
    5. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
    6. +
    +

    缓存击穿 缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没有读到数据,又同时去数据库取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

    +

    解决方案:

    +
      +
    1. 设置热点数据永不过期
    2. +
    3. 加互斥锁
    4. +
    +

    4.Redis的数据结构

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(无序集合)及zset(有序集合)

    +

    image-20230914154911197

    +

    PDF

    + + +
    + +
    + + + + +]]>
    + + 面试 + + + 面试 + +
    + + 面试专题 + /posts/46317.html + 50W字的面试文档

    来源于:咕泡教育 个人使用 不外传

    +

    在线预览链接:https://www.aliyundrive.com/s/F2wn9fxYhFs

    +

    image-20230513134435330

    +

    黑马程序员Java面试视频教程

    黑马程序员新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题) https://www.bilibili.com/video/BV1yT411H7YK/?share_source=copy_web&vd_source=aee5e475191b69e6c781059ab6662584

    +

    1.Redis篇

    +

    面试官:什么是缓存穿透 ? 怎么解决 ?

    +

    候选人

    +

    嗯~~,我想一下

    +

    缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。这种情况大概率是遭到了攻击。

    +

    解决方案的话,我们通常都会用布隆过滤器来解决它

    +

    面试官:好的,你能介绍一下布隆过滤器吗?

    +

    候选人

    +

    嗯,是这样~

    +

    布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是redisson实现的布隆过滤器。

    +

    它的底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一个key的存在。查找的过程也是一样的。

    +

    当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置这个误判率,大概不会超过5%,其实这个误判是必然存在的,要不就得增加数组的长度,其实已经算是很划分了,5%以内的误判率一般的项目也能接受,不至于高并发下压倒数据库。

    +

    面试官:什么是缓存击穿 ? 怎么解决 ?

    +

    候选人

    +

    嗯!!

    +

    缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。

    +

    解决方案有两种方式:

    +

    第一可以使用互斥锁:当缓存失效时,不立即去load db,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行 load db的操作并回设缓存,否则重试get缓存的方法

    +

    第二种方案可以设置当前key逻辑过期,大概是思路如下:

    +

    ①:在设置key的时候,设置一个过期时间字段一块存入缓存中,不给当前key设置过期时间

    +

    ②:当查询的时候,从redis取出数据后判断时间是否过期

    +

    ③:如果过期则开通另外一个线程进行数据同步,当前线程正常返回数据,这个数据不是最新

    +

    当然两种方案各有利弊:===============

    +

    如果选择数据的强一致性,建议使用分布式锁的方案,性能上可能没那么高,锁需要等,也有可能产生死锁的问题

    +

    如果选择key的逻辑删除,则优先考虑的高可用性,性能比较高,但是数据同步这块做不到强一致。

    +

    面试官:什么是缓存雪崩 ? 怎么解决 ?

    +

    候选人

    +

    嗯!!

    +

    缓存雪崩意思是设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB 瞬时压力过重雪崩。与缓存击穿的区别:雪崩是很多key,击穿是某一个key缓存。

    +

    解决方案主要是可以将缓存失效时间分散开,比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

    +

    面试官:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

    +

    候选人:嗯!就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,需要让数据库与redis高度保持一致,因为要求时效性比较高,我们当时采用的读写锁保证的强一致性。

    +

    我们采用的是redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。

    +

    面试官:那这个排他锁是如何保证读写、读读互斥的呢?

    +

    候选人:其实排他锁底层使用也是setnx,保证了同时只能有一个线程操作锁住的方法

    +

    面试官:你听说过延时双删吗?为什么不用它呢?

    +

    候选人:延迟双删,如果是写操作,我们先把缓存中的数据删除,然后更新数据库,最后再延时删除缓存中的数据,其中这个延时多久不太好确定,在延时的过程中可能会出现脏数据,并不能保证强一致性,所以没有采用它。

    +

    面试官:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

    +

    候选人:嗯!就说我最近做的这个项目,里面有xxxx(根据自己的简历上写)的功能,数据同步可以有一定的延时(符合大部分业务)

    +

    我们当时采用的阿里的canal组件实现数据同步:不需要更改业务代码,部署一个canal服务。canal服务把自己伪装成mysql的一个从节点,当mysql数据更新以后,canal会读取binlog数据,然后在通过canal的客户端获取到数据,更新缓存即可。

    +

    面试官:redis做为缓存,数据的持久化是怎么做的?

    +

    候选人:在Redis中提供了两种数据持久化的方式:1、RDB 2、AOF

    +

    面试官:这两种持久化方式有什么区别呢?

    +

    候选人:RDB是一个快照文件,它是把redis内存存储的数据写到磁盘上,当redis实例宕机恢复数据的时候,方便从RDB的快照文件中恢复数据。

    +

    AOF的含义是追加文件,当redis操作写命令的时候,都会存储这个文件中,当redis实例宕机恢复数据的时候,会从这个文件中再次执行一遍命令来恢复数据

    +

    面试官:这两种方式,哪种恢复的比较快呢?

    +

    候选人:RDB因为是二进制文件,在保存的时候体积也是比较小的,它恢复的比较快,但是它有可能会丢数据,我们通常在项目中也会使用AOF来恢复数据,虽然AOF恢复的速度慢一些,但是它丢数据的风险要小很多,在AOF文件中可以设置刷盘策略,我们当时设置的就是每秒批量写入一次命令

    +

    面试官:Redis的数据过期策略有哪些 ?

    +

    候选人

    +

    嗯~,在redis中提供了两种数据过期删除策略

    +

    第一种是惰性删除,在设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。

    +

    第二种是 定期删除,就是说每隔一段时间,我们就对一些key进行检查,删除里面过期的key

    +

    定期清理的两种模式:

    +
      +
    • SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的 hz 选项来调整这个次数
    • +
    • FAST模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms
    • +
    +

    Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。

    +

    面试官:Redis的数据淘汰策略有哪些 ?

    +

    候选人

    +

    嗯,这个在redis中提供了很多种,默认是noeviction,不删除任何数据,内部不足直接报错

    +

    是可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFU

    +

    LRU的意思就是最少最近使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。

    +

    LFU的意思是最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高

    +

    我们在项目设置的allkeys-lru,挑选最近最少使用的数据淘汰,把一些经常访问的key留在redis中

    +

    面试官:数据库有1000万数据 ,Redis只能缓存20w数据, 如何保证Redis中的数据都是热点数据 ?

    +

    候选人

    +

    嗯,我想一下~~

    +

    可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据

    +

    面试官:Redis的内存用完了会发生什么?

    +

    候选人

    +

    嗯~,这个要看redis的数据淘汰策略是什么,如果是默认的配置,redis内存用完以后则直接报错。我们当时设置的 allkeys-lru 策略。把最近最常访问的数据留在缓存中。

    +

    面试官:Redis分布式锁如何实现 ?

    +

    候选人:嗯,在redis中提供了一个命令setnx(SET if not exists)

    +

    由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key的时候是其他客户端是不能设置这个key的

    +

    面试官:好的,那你如何控制Redis实现分布式锁有效时长呢?

    +

    候选人:嗯,的确,redis的setnx指令不好控制这个问题,我们当时采用的redis的一个框架redisson实现的。

    +

    在redisson中需要手动加锁,并且可以控制锁的失效时间和等待时间,当锁住的一个业务还没有执行完成的时候,在redisson中引入了一个看门狗机制,就是说每隔一段时间就检查当前业务是否还持有锁,如果持有就增加加锁的持有时间,当业务执行完成之后需要使用释放锁就可以了

    +

    还有一个好处就是,在高并发下,一个业务有可能会执行很快,先客户1持有锁的时候,客户2来了以后并不会马上拒绝,它会自旋不断尝试获取锁,如果客户1释放之后,客户2就可以马上持有锁,性能也得到了提升。

    +

    面试官:好的,redisson实现的分布式锁是可重入的吗?

    +

    候选人:嗯,是可以重入的。这样做是为了避免死锁的产生。这个重入其实在内部就是判断是否是当前线程持有的锁,如果是当前线程持有的锁就会计数,如果释放锁就会在计算上减一。在存储数据的时候采用的hash结构,大key可以按照自己的业务进行定制,其中小key是当前线程的唯一标识,value是当前线程重入的次数

    +

    面试官:redisson实现的分布式锁能解决主从一致性的问题吗

    +

    候选人:这个是不能的,比如,当线程1加锁成功后,master节点数据会异步复制到slave节点,此时当前持有Redis锁的master节点宕机,slave节点被提升为新的master节点,假如现在来了一个线程2,再次加锁,会在新的master节点上加锁成功,这个时候就会出现两个节点同时持有一把锁的问题。

    +

    我们可以利用redisson提供的红锁来解决这个问题,它的主要作用是,不能只在一个redis实例上创建锁,应该是在多个redis实例上创建锁,并且要求在大多数redis节点上都成功创建锁,红锁中要求是redis的节点数量要过半。这样就能避免线程1加锁成功后master节点宕机导致线程2成功加锁到新的master节点上的问题了。

    +

    但是,如果使用了红锁,因为需要同时在多个节点上都添加锁,性能就变的很低了,并且运维维护成本也非常高,所以,我们一般在项目中也不会直接使用红锁,并且官方也暂时废弃了这个红锁

    +

    面试官:好的,如果业务非要保证数据的强一致性,这个该怎么解决呢?

    +

    候选人:嗯~,redis本身就是支持高可用的,做到强一致性,就非常影响性能,所以,如果有强一致性要求高的业务,建议使用zookeeper实现的分布式锁,它是可以保证强一致性的。

    +

    面试官:Redis集群有哪些方案, 知道嘛 ?

    +

    候选人:嗯~~,在Redis中提供的集群方案总共有三种:主从复制、哨兵模式、Redis分片集群

    +

    面试官:那你来介绍一下主从同步

    +

    候选人:嗯,是这样的,单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,可以搭建主从集群,实现读写分离。一般都是一主多从,主节点负责写数据,从节点负责读数据,主节点写入数据之后,需要把数据同步到从节点中

    +

    面试官:能说一下,主从同步数据的流程

    +

    候选人:嗯~~,好!主从同步分为了两个阶段,一个是全量同步,一个是增量同步

    +

    全量同步是指从节点第一次与主节点建立连接的时候使用全量同步,流程是这样的:

    +

    第一:从节点请求主节点同步数据,其中从节点会携带自己的replication id和offset偏移量。

    +

    第二:主节点判断是否是第一次请求,主要判断的依据就是,主节点与从节点是否是同一个replication id,如果不是,就说明是第一次同步,那主节点就会把自己的replication id和offset发送给从节点,让从节点与主节点的信息保持一致。

    +

    第三:在同时主节点会执行bgsave,生成rdb文件后,发送给从节点去执行,从节点先把自己的数据清空,然后执行主节点发送过来的rdb文件,这样就保持了一致

    +

    当然,如果在rdb生成执行期间,依然有请求到了主节点,而主节点会以命令的方式记录到缓冲区,缓冲区是一个日志文件,最后把这个日志文件发送给从节点,这样就能保证主节点与从节点完全一致了,后期再同步数据的时候,都是依赖于这个日志文件,这个就是全量同步

    +

    增量同步指的是,当从节点服务重启之后,数据就不一致了,所以这个时候,从节点会请求主节点同步数据,主节点还是判断不是第一次请求,不是第一次就获取从节点的offset值,然后主节点从命令日志中获取offset值之后的数据,发送给从节点进行数据同步

    +

    面试官:怎么保证Redis的高并发高可用

    +

    候选人:首先可以搭建主从集群,再加上使用redis中的哨兵模式,哨兵模式可以实现主从集群的自动故障恢复,里面就包含了对主从服务的监控、自动故障恢复、通知;如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主;同时Sentinel也充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端,所以一般项目都会采用哨兵的模式来保证redis的高并发高可用

    +

    面试官:你们使用redis是单点还是集群,哪种集群

    +

    候选人:嗯!,我们当时使用的是主从(1主1从)加哨兵。一般单节点不超过10G内存,如果Redis内存不足则可以给不同服务分配独立的Redis主从节点。尽量不做分片集群。因为集群维护起来比较麻烦,并且集群之间的心跳检测和数据通信会消耗大量的网络带宽,也没有办法使用lua脚本和事务

    +

    面试官:redis集群脑裂,该怎么解决呢?

    +

    候选人:嗯! 这个在项目很少见,不过脑裂的问题是这样的,我们现在用的是redis的哨兵模式集群的

    +

    有的时候由于网络等原因可能会出现脑裂的情况,就是说,由于redis master节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将old master降为salve,这时再从新master同步数据,这会导致old master中的大量数据丢失。

    +

    关于解决的话,我记得在redis的配置中可以设置:第一可以设置最少的salve节点个数,比如设置至少要有一个从节点才能同步数据,第二个可以设置主从数据复制和同步的延迟时间,达不到要求就拒绝请求,就可以避免大量的数据丢失

    +

    面试官:redis的分片集群有什么作用

    +

    候选人:分片集群主要解决的是,海量数据存储的问题,集群中有多个master,每个master保存不同数据,并且还可以给每个master设置多个slave节点,就可以继续增大集群的高并发能力。同时每个master之间通过ping监测彼此健康状态,就类似于哨兵模式了。当客户端请求可以访问集群任意节点,最终都会被转发到正确节点

    +

    面试官:Redis分片集群中数据是怎么存储和读取的?

    +

    候选人

    +

    嗯~,在redis集群中是这样的

    +

    Redis 集群引入了哈希槽的概念,有 16384 个哈希槽,集群中每个主节点绑定了一定范围的哈希槽范围, key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,通过槽找到对应的节点进行存储。

    +

    取值的逻辑是一样的

    +

    面试官:Redis是单线程的,但是为什么还那么快?

    +

    候选人

    +

    嗯,这个有几个原因吧~~~

    +

    1、完全基于内存的,C语言编写

    +

    2、采用单线程,避免不必要的上下文切换可竞争条件

    +

    3、使用多路I/O复用模型,非阻塞IO

    +

    例如:bgsave 和 bgrewriteaof 都是在后台执行操作,不影响主线程的正常使用,不会产生阻塞

    +

    面试官:能解释一下I/O多路复用模型?

    +

    候选人:嗯~~,I/O多路复用是指利用单个线程来同时监听多个Socket ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。目前的I/O多路复用都是采用的epoll模式实现,它会在通知用户进程Socket就绪的同时,把已就绪的Socket写入用户空间,不需要挨个遍历Socket来判断是否就绪,提升了性能。

    +

    其中Redis的网络模型就是使用I/O多路复用结合事件的处理器来应对多个Socket请求,比如,提供了连接应答处理器、命令回复处理器,命令请求处理器;

    +

    在Redis6.0之后,为了提升更好的性能,在命令回复处理器使用了多线程来处理回复事件,在命令请求处理器中,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程

    +
    +

    pdf

    + + +
    + +
    + + + + + + +

    2.数据库篇

    +

    面试官:MySQL中,如何定位慢查询?

    +

    候选人:

    +

    嗯~,我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了2秒以上,因为我们当时的系统部署了运维的监控系统Skywalking ,在展示的报表中可以看到是哪一个接口比较慢,并且可以分析这个接口哪部分比较慢,这里可以看到SQL的具体的执行时间,所以可以定位是哪个sql出了问题

    +

    如果,项目中没有这种运维的监控系统,其实在MySQL中也提供了慢日志查询的功能,可以在MySQL的系统配置文件中开启这个慢日志的功能,并且也可以设置SQL执行超过多少时间来记录到一个日志文件中,我记得上一个项目配置的是2秒,只要SQL执行的时间超过了2秒就会记录到日志文件中,我们就可以在日志文件找到执行比较慢的SQL了。

    +

    面试官:那这个SQL语句执行很慢, 如何分析呢?

    +

    候选人:如果一条sql执行很慢的话,我们通常会使用mysql自动的执行计划explain来去查看这条sql的执行情况,比如在这里面可以通过key和key_len检查是否命中了索引,如果本身已经添加了索引,也可以判断索引是否有失效的情况,第二个,可以通过type字段查看sql是否有进一步的优化空间,是否存在全索引扫描或全盘扫描,第三个可以通过extra建议来判断,是否出现了回表的情况,如果出现了,可以尝试添加索引或修改返回字段来修复

    +

    面试官:了解过索引吗?(什么是索引)

    +

    候选人:嗯,索引在项目中还是比较常见的,它是帮助MySQL高效获取数据的数据结构,主要是用来提高数据检索的效率,降低数据库的IO成本,同时通过索引列对数据进行排序,降低数据排序的成本,也能降低了CPU的消耗

    +

    面试官:索引的底层数据结构了解过嘛 ?

    +

    候选人:MySQL的默认的存储引擎InnoDB采用的B+树的数据结构来存储索引,选择B+树的主要的原因是:第一阶数更多,路径更短,第二个磁盘读写代价B+树更低,非叶子节点只存储指针,叶子阶段存储数据,第三是B+树便于扫库和区间查询,叶子节点是一个双向链表

    +

    面试官:B树和B+树的区别是什么呢?

    +

    候选人:第一:在B树中,非叶子节点和叶子节点都会存放数据,而B+树的所有的数据都会出现在叶子节点,在查询的时候,B+树查找效率更加稳定

    +

    第二:在进行范围查询的时候,B+树效率更高,因为B+树都在叶子节点存储,并且叶子节点是一个双向链表

    +

    面试官:什么是聚簇索引什么是非聚簇索引 ?

    +

    候选人:

    +

    好的~,聚簇索引主要是指数据与索引放到一块,B+树的叶子节点保存了整行数据,有且只有一个,一般情况下主键在作为聚簇索引的

    +

    非聚簇索引值的是数据与索引分开存储,B+树的叶子节点保存对应的主键,可以有多个,一般我们自己定义的索引都是非聚簇索引

    +

    面试官:知道什么是回表查询嘛 ?

    +

    候选人:嗯,其实跟刚才介绍的聚簇索引和非聚簇索引是有关系的,回表的意思就是通过二级索引找到对应的主键值,然后再通过主键值找到聚集索引中所对应的整行数据,这个过程就是回表

    +

    备注:如果面试官直接问回表,则需要先介绍聚簇索引和非聚簇索引】

    +

    面试官:知道什么叫覆盖索引嘛 ?

    +

    候选人:嗯~,清楚的

    +

    覆盖索引是指select查询语句使用了索引,在返回的列,必须在索引中全部能够找到,如果我们使用id查询,它会直接走聚集索引查询,一次索引扫描,直接返回数据,性能高。

    +

    如果按照二级索引查询数据的时候,返回的列中没有创建索引,有可能会触发回表查询,尽量避免使用select *,尽量在返回的列中都包含添加索引的字段

    +

    面试官:MYSQL超大分页怎么处理 ?

    +

    候选人:嗯,超大分页一般都是在数据量比较大时,我们使用了limit分页查询,并且需要对数据进行排序,这个时候效率就很低,我们可以采用覆盖索引和子查询来解决

    +

    先分页查询数据的id字段,确定了id之后,再用子查询来过滤,只查询这个id列表中的数据就可以了

    +

    因为查询id的时候,走的覆盖索引,所以效率可以提升很多

    +

    面试官:索引创建原则有哪些?

    +

    候选人:嗯,这个情况有很多,不过都有一个大前提,就是表中的数据要超过10万以上,我们才会创建索引,并且添加索引的字段是查询比较频繁的字段,一般也是像作为查询条件,排序字段或分组的字段这些。

    +

    还有就是,我们通常创建索引的时候都是使用复合索引来创建,一条sql的返回值,尽量使用覆盖索引,如果字段的区分度不高的话,我们也会把它放在组合索引后面的字段。

    +

    如果某一个字段的内容较长,我们会考虑使用前缀索引来使用,当然并不是所有的字段都要添加索引,这个索引的数量也要控制,因为添加索引也会导致新增改的速度变慢。

    +

    面试官:什么情况下索引会失效 ?

    +

    候选人:嗯,这个情况比较多,我说一些自己的经验,以前遇到过的

    +

    比如,索引在使用的时候没有遵循最左匹配法则,第二个是,模糊查询,如果%号在前面也会导致索引失效。如果在添加索引的字段上进行了运算操作或者类型转换也都会导致索引失效。

    +

    我们之前还遇到过一个就是,如果使用了复合索引,中间使用了范围查询,右边的条件索引也会失效

    +

    查询的时候发生了类型转换,在查询的时候做了运算的操作和模糊查询也会导致索引失效

    +

    所以,通常情况下,想要判断出这条sql是否有索引失效的情况,可以使用explain执行计划来分析

    +

    面试官:sql的优化的经验

    +

    候选人:嗯,这个在项目还是挺常见的,当然如果直说sql优化的话,我们会从这几方面考虑,比如

    +

    建表的时候、使用索引、sql语句的编写、主从复制,读写分离,还有一个是如果量比较大的话,可以考虑分库分表

    +

    直接经验:

    +

    ①SELECT语句务必指明字段名称(避免直接使用select * )

    +

    ②SQL语句要避免造成索引失效的写法

    +

    ③尽量用union all代替union union会多一次过滤,效率低

    +

    ④避免在where子句中对字段进行表达式操作

    +

    ⑤Join优化 能用innerjoin 就不用left join right join,如必须使用 一定要以小表为驱动,内连接会对两个表进行优化,优先把小表放到外边,把大表放到里边。left join 或 right join,不会重新调整顺序

    +

    面试官:创建表的时候,你们是如何优化的呢?

    +

    候选人:这个我们主要参考的阿里出的那个开发手册《嵩山版》,就比如,在定义字段的时候需要结合字段的内容来选择合适的类型,如果是数值的话,像tinyint、int 、bigint这些类型,要根据实际情况选择。如果是字符串类型,也是结合存储的内容来选择char和varchar或者text类型

    +

    面试官:那在使用索引的时候,是如何优化呢?

    +

    候选人:【参考索引创建原则 进行描述】

    +

    1). 针对于数据量较大,且查询比较频繁的表建立索引。

    +

    2). 针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。

    +

    3). 尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。

    +

    4). 如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。

    +

    5). 尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。

    +

    6). 要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。

    +

    7). 如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。

    +

    面试官:你平时对sql语句做了哪些优化呢?

    +

    候选人:嗯,这个也有很多,比如SELECT语句务必指明字段名称,不要直接使用select * ,还有就是要注意SQL语句避免造成索引失效的写法;如果是聚合查询,尽量用union all代替union ,union会多一次过滤,效率比较低;如果是表关联的话,尽量使用innerjoin ,不要使用用left join right join,如必须使用 一定要以小表为驱动

    +

    面试官:事务的特性是什么?可以详细说一下吗?

    +

    候选人:嗯,这个比较清楚,ACID,分别指的是:原子性、一致性、隔离性、持久性;我举个例子:

    +

    A向B转账500,转账成功,A扣除500元,B增加500元,原子操作体现在要么都成功,要么都失败

    +

    在转账的过程中,数据要一致,A扣除了500,B必须增加500

    +

    在转账的过程中,隔离性体现在A像B转账,不能受其他事务干扰

    +

    在转账的过程中,持久性体现在事务提交后,要把数据持久化(可以说是落盘操作)

    +

    ACID介绍:

    +

    原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。

    +

    一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。

    +

    隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。

    +

    持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

    +

    面试官:并发事务带来哪些问题?

    +

    候选人

    +

    我们在项目开发中,多个事务并发进行是经常发生的,并发也是必然的,有可能导致一些问题

    +

    第一是脏读, 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

    +

    第二是不可重复读:比如在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

    +

    第三是幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

    +

    image-20230511172226172

    +

    面试官:怎么解决这些问题呢?MySQL的默认隔离级别是?

    +

    候选人:解决方案是对事务进行隔离

    +

    MySQL支持四种隔离级别,分别有:

    +

    第一个是,未提交读(read uncommitted)它解决不了刚才提出的所有问题,一般项目中也不用这个。第二个是读已提交(read committed)它能解决脏读的问题的,但是解决不了不可重复读和幻读。第三个是可重复读(repeatable read)它能解决脏读和不可重复读,但是解决不了幻读,这个也是mysql默认的隔离级别。第四个是串行化(serializable)它可以解决刚才提出来的所有问题,但是由于让是事务串行执行的,性能比较低。所以,我们一般使用的都是mysql默认的隔离级别:可重复读

    +

    image-20230511172259204

    +

    面试官:undo log和redo log的区别

    +

    候选人:好的,其中redo log日志记录的是数据页的物理变化,服务宕机可用来同步数据,而undo log 不同,它主要记录的是逻辑日志,当事务回滚时,通过逆操作恢复原来的数据,比如我们删除一条数据的时候,就会在undo log日志文件中新增一条delete语句,如果发生回滚就执行逆操作;

    +

    redo log保证了事务的持久性,undo log保证了事务的原子性和一致性

    +

    面试官:事务中的隔离性是如何保证的呢?(你解释一下MVCC)

    +

    候选人:事务的隔离性是由锁和mvcc实现的。

    +

    其中mvcc的意思是多版本并发控制。指维护一个数据的多个版本,使得读写操作没有冲突,它的底层实现主要是分为了三个部分,第一个是隐藏字段,第二个是undo log日志,第三个是readView读视图

    +

    隐藏字段是指:在mysql中给每个表都设置了隐藏字段,有一个是trx_id(事务id),记录每一次操作的事务id,是自增的;另一个字段是roll_pointer(回滚指针),指向上一个版本的事务版本记录地址

    +

    undo log主要的作用是记录回滚日志,存储老版本数据,在内部会形成一个版本链,在多个事务并行操作某一行记录,记录不同事务修改数据的版本,通过roll_pointer指针形成一个链表

    +

    readView解决的是一个事务查询选择版本的问题,在内部定义了一些匹配规则和当前的一些事务id判断该访问那个版本的数据,不同的隔离级别快照读是不一样的,最终的访问的结果不一样。如果是rc隔离级别,每一次执行快照读时生成ReadView,如果是rr隔离级别仅在事务中第一次执行快照读时生成ReadView,后续复用

    +

    面试官:MySQL主从同步原理

    +

    候选人:MySQL主从复制的核心就是二进制日志(DDL(数据定义语言)语句和 DML(数据操纵语言)语句),它的步骤是这样的:

    +

    第一:主库在事务提交时,会把数据变更记录在二进制日志文件 Binlog 中。

    +

    第二:从库读取主库的二进制日志文件 Binlog ,写入到从库的中继日志 Relay Log 。

    +

    第三:从库重做中继日志中的事件,将改变反映它自己的数据

    +

    面试官:你们项目用过MySQL的分库分表吗?

    +

    候选人

    +

    嗯,因为我们都是微服务开发,每个微服务对应了一个数据库,是根据业务进行拆分的,这个其实就是垂直拆分。

    +

    面试官:那你之前使用过水平分库吗?

    +

    候选人

    +

    嗯,这个是使用过的,我们当时的业务是(xxx),一开始,我们也是单库,后来这个业务逐渐发展,业务量上来的很迅速,其中(xx)表已经存放了超过1000万的数据,我们做了很多优化也不好使,性能依然很慢,所以当时就使用了水平分库。

    +

    我们一开始先做了3台服务器对应了3个数据库,由于库多了,需要分片,我们当时采用的mycat来作为数据库的中间件。数据都是按照id(自增)取模的方式来存取的。

    +

    当然一开始的时候,那些旧数据,我们做了一些清洗的工作,我们也是按照id取模规则分别存储到了各个数据库中,好处就是可以让各个数据库分摊存储和读取的压力,解决了我们当时性能的问题

    +
    +

    pdf

    + + +
    + +
    + + + + + + +

    3.框架篇

    +

    面试官:Spring框架中的单例bean是线程安全的吗?

    +

    候选人

    +

    嗯!

    +

    不是线程安全的,是这样的

    +

    当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。

    +

    Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。

    +

    比如:我们通常在项目中使用的Spring bean都是不可可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。

    +

    如果你的bean有多种状态的话(比如 View Model对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由“singleton”变更为“prototype”。

    +

    面试官:什么是AOP

    +

    候选人

    +

    aop是面向切面编程,在spring中用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合,一般比如可以做为公共日志保存,事务处理等

    +

    面试官:你们项目中有没有使用到AOP

    +

    候选人

    +

    我们当时在后台管理系统中,就是使用aop来记录了系统的操作日志

    +

    主要思路是这样的,使用aop中的环绕通知+切点表达式,这个表达式就是要找到要记录日志的方法,然后通过环绕通知的参数获取请求方法的参数,比如类信息、方法信息、注解、请求方式等,获取到这些参数以后,保存到数据库

    +

    面试官:Spring中的事务是如何实现的

    +

    候选人

    +

    spring实现的事务本质就是aop完成,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

    +

    面试官:Spring中事务失效的场景有哪些

    +

    候选人

    +

    嗯!这个在项目中之前遇到过,我想想啊

    +

    第一个,如果方法上异常捕获处理,自己处理了异常,没有抛出,就会导致事务失效,所以一般处理了异常以后,别忘了跑出去就行了

    +

    第二个,如果方法抛出检查异常,如果报错也会导致事务失效,最后在spring事务的注解上,就是@Transactional上配置rollbackFor属性为Exception,这样别管是什么异常,都会回滚事务

    +

    第三,我之前还遇到过一个,如果方法上不是public修饰的,也会导致事务失效

    +

    嗯,就能想起来那么多

    +

    面试官:Spring的bean的生命周期

    +

    候选人

    +

    嗯!,这个步骤还是挺多的,我之前看过一些源码,它大概流程是这样的

    +

    首先会通过一个非常重要的类,叫做BeanDefinition获取bean的定义信息,这里面就封装了bean的所有信息,比如,类的全路径,是否是延迟加载,是否是单例等等这些信息

    +

    在创建bean的时候,第一步是调用构造函数实例化bean

    +

    第二步是bean的依赖注入,比如一些set方法注入,像平时开发用的@Autowire都是这一步完成

    +

    第三步是处理Aware接口,如果某一个bean实现了Aware接口就会重写方法执行

    +

    第四步是bean的后置处理器BeanPostProcessor,这个是前置处理器

    +

    第五步是初始化方法,比如实现了接口InitializingBean或者自定义了方法init-method标签或@PostContruct

    +

    第六步是执行了bean的后置处理器BeanPostProcessor,主要是对bean进行增强,有可能在这里产生代理对象

    +

    最后一步是销毁bean

    +

    面试官:Spring中的循环引用

    +

    候选人

    +

    嗯,好的,我来解释一下

    +

    循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A

    +

    循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖

    +

    ①一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象

    +

    ②二级缓存:缓存早期的bean对象(生命周期还没走完)

    +

    ③三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

    +

    面试官:那具体解决流程清楚吗?

    +

    候选人

    +

    第一,先实例A对象,同时会创建ObjectFactory对象存入三级缓存singletonFactories

    +

    第二,A在初始化的时候需要B对象,这个走B的创建的逻辑

    +

    第三,B实例化完成,也会创建ObjectFactory对象存入三级缓存singletonFactories

    +

    第四,B需要注入A,通过三级缓存中获取ObjectFactory来生成一个A的对象同时存入二级缓存,这个是有两种情况,一个是可能是A的普通对象,另外一个是A的代理对象,都可以让ObjectFactory来生产对应的对象,这也是三级缓存的关键

    +

    第五,B通过从通过二级缓存earlySingletonObjects 获得到A的对象后可以正常注入,B创建成功,存入一级缓存singletonObjects

    +

    第六,回到A对象初始化,因为B对象已经创建完成,则可以直接注入B,A创建成功存入一次缓存singletonObjects

    +

    第七,二级缓存中的临时对象A清除

    +

    面试官:构造方法出现了循环依赖怎么解决?

    +

    候选人

    +

    由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入,可以使用@Lazy懒加载,什么时候需要对象再进行bean对象的创建

    +

    面试官:SpringMVC的执行流程知道嘛

    +

    候选人

    +

    嗯,这个知道的,它分了好多步骤

    +

    1、用户发送出请求到前端控制器DispatcherServlet,这是一个调度中心

    +

    2、DispatcherServlet收到请求调用HandlerMapping(处理器映射器)。

    +

    3、HandlerMapping找到具体的处理器(可查找xml配置或注解配置),生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。

    +

    4、DispatcherServlet调用HandlerAdapter(处理器适配器)。

    +

    5、HandlerAdapter经过适配调用具体的处理器(Handler/Controller)。

    +

    6、Controller执行完成返回ModelAndView对象。

    +

    7、HandlerAdapter将Controller执行结果ModelAndView返回给DispatcherServlet。

    +

    8、DispatcherServlet将ModelAndView传给ViewReslover(视图解析器)。

    +

    9、ViewReslover解析后返回具体View(视图)。

    +

    10、DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。

    +

    11、DispatcherServlet响应用户。

    +

    当然现在的开发,基本都是前后端分离的开发的,并没有视图这些,一般都是handler中使用Response直接结果返回

    +

    面试官:Springboot自动配置原理

    +

    候选人

    +

    嗯,好的,它是这样的。

    +

    在Spring Boot项目中的引导类上有一个注解@SpringBootApplication,这个注解是对三个注解进行了封装,分别是:

    +
      +
    • @SpringBootConfiguration
    • +
    • @EnableAutoConfiguration
    • +
    • @ComponentScan
    • +
    +

    其中@EnableAutoConfiguration是实现自动化配置的核心注解。

    +

    该注解通过@Import注解导入对应的配置选择器。关键的是内部就是读取了该项目和该项目引用的Jar包的的classpath路径下META-INF/spring.factories文件中的所配置的类的全类名。

    +

    在这些配置类中所定义的Bean会根据条件注解所指定的条件来决定是否需要将其导入到Spring容器中。

    +

    一般条件判断会有像@ConditionalOnClass这样的注解,判断是否有对应的class文件,如果有则加载该类,把这个配置类的所有的Bean放入spring容器中使用。

    +

    面试官:Spring 的常见注解有哪些?

    +

    候选人

    +

    嗯,这个就很多了

    +

    第一类是:声明bean,有@Component、@Service、@Repository、@Controller

    +

    第二类是:依赖注入相关的,有@Autowired、@Qualifier、@Resourse

    +

    第三类是:设置作用域 @Scope

    +

    第四类是:spring配置相关的,比如@Configuration,@ComponentScan 和 @Bean

    +

    第五类是:跟aop相关做增强的注解 @Aspect,@Before,@After,@Around,@Pointcut

    +

    面试官:SpringMVC常见的注解有哪些?

    +

    候选人

    +

    嗯,这个也很多的

    +

    有@RequestMapping:用于映射请求路径;

    +

    @RequestBody:注解实现接收http请求的json数据,将json转换为java对象;

    +

    @RequestParam:指定请求参数的名称;

    +

    @PathViriable:从请求路径下中获取请求参数(/user/{id}),传递给方法的形式参数;@ResponseBody:注解实现将controller方法返回对象转化为json对象响应给客户端。@RequestHeader:获取指定的请求头数据,还有像@PostMapping、@GetMapping这些。

    +

    面试官:Springboot常见注解有哪些?

    +

    候选人

    +

    嗯~~

    +

    Spring Boot的核心注解是@SpringBootApplication , 他由几个注解组成 :

    +
      +
    • @SpringBootConfiguration: 组合了- @Configuration注解,实现配置文件的功能;
    • +
    • @EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项
    • +
    • @ComponentScan:Spring组件扫描
    • +
    +

    面试官:MyBatis执行流程

    +

    候选人

    +

    好,这个知道的,不过步骤也很多

    +

    ①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件

    +

    ②构造会话工厂SqlSessionFactory,一个项目只需要一个,单例的,一般由spring进行管理

    +

    ③会话工厂创建SqlSession对象,这里面就含了执行SQL语句的所有方法

    +

    ④操作数据库的接口,Executor执行器,同时负责查询缓存的维护

    +

    ⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了映射信息

    +

    ⑥输入参数映射

    +

    ⑦输出结果映射

    +

    面试官:Mybatis是否支持延迟加载?

    +

    候选人

    +

    是支持的~

    +

    延迟加载的意思是:就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。

    +

    Mybatis支持一对一关联对象和一对多关联集合对象的延迟加载

    +

    在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false,默认是关闭的

    +

    面试官:延迟加载的底层原理知道吗?

    +

    候选人

    +

    嗯,我想想啊

    +

    延迟加载在底层主要使用的CGLIB动态代理完成的

    +

    第一是,使用CGLIB创建目标对象的代理对象,这里的目标对象就是开启了延迟加载的mapper

    +

    第二个是当调用目标方法时,进入拦截器invoke方法,发现目标方法是null值,再执行sql查询

    +

    第三个是获取数据以后,调用set方法设置属性值,再继续查询目标方法,就有值了

    +

    面试官:Mybatis的一级、二级缓存用过吗?

    +

    候选人

    +

    嗯~~,用过的~

    +

    mybatis的一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当Session进行flush或close之后,该Session中的所有Cache就将清空,默认打开一级缓存

    +

    关于二级缓存需要单独开启

    +

    二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。

    +

    如果想要开启二级缓存需要在全局配置文件和映射文件中开启配置才行。

    +

    面试官:Mybatis的二级缓存什么时候会清理缓存中的数据

    +

    候选人

    +

    嗯!!

    +

    当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。

    +
    +

    pdf

    + + +
    + +
    + + + + +

    4.微服务篇

    +

    面试官:Spring Cloud 5大组件有哪些?

    +

    候选人:

    +

    早期我们一般认为的Spring Cloud五大组件是

    +
      +
    • Eureka : 注册中心
    • +
    • Ribbon : 负载均衡
    • +
    • Feign : 远程调用
    • +
    • Hystrix : 服务熔断
    • +
    • Zuul/Gateway : 网关
    • +
    +

    随着SpringCloudAlibba在国内兴起 , 我们项目中使用了一些阿里巴巴的组件

    +
      +
    • 注册中心/配置中心 Nacos
    • +
    • 负载均衡 Ribbon
    • +
    • 服务调用 Feign
    • +
    • 服务保护 sentinel
    • +
    • 服务网关 Gateway
    • +
    +

    面试官:服务注册和发现是什么意思?Spring Cloud 如何实现服务注册发现?

    +

    候选人:

    +

    我理解的是主要三块大功能,分别是服务注册 、服务发现、服务状态监控

    +

    我们当时项目采用的eureka作为注册中心,这个也是spring cloud体系中的一个核心组件

    +

    服务注册:服务提供者需要把自己的信息注册到eureka,由eureka来保存这些信息,比如服务名称、ip、端口等等

    +

    服务发现:消费者向eureka拉取服务列表信息,如果服务提供者有集群,则消费者会利用负载均衡算法,选择一个发起调用

    +

    服务监控:服务提供者会每隔30秒向eureka发送心跳,报告健康状态,如果eureka服务90秒没接收到心跳,从eureka中剔除

    +

    面试官:我看你之前也用过nacos、你能说下nacos与eureka的区别?

    +

    候选人:

    +

    我们当时xx项目就是采用的nacos作为注册中心,选择nacos还要一个重要原因就是它支持配置中心,不过nacos作为注册中心,也比eureka要方便好用一些,主要相同不同点在于几点:

    +
      +
    • 共同点
    • +
    +

    Nacos与eureka都支持服务注册和服务拉取,都支持服务提供者心跳方式做健康检测

    +
      +
    • Nacos与Eureka的区别
    • +
    +

    ①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

    +

    ②临时实例心跳不正常会被剔除,非临时实例则不会被剔除

    +

    ③Nacos支持服务列表变更的消息推送模式,服务列表更新更及时

    +

    ④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

    +

    面试官:你们项目负载均衡如何实现的 ?

    +

    候选人:

    +

    是这样~~

    +

    在服务调用过程中的负载均衡一般使用SpringCloud的Ribbon 组件实现 , Feign的底层已经自动集成了Ribbon , 使用起来非常简单

    +

    当发起远程调用时,ribbon先从注册中心拉取服务地址列表,然后按照一定的路由策略选择一个发起远程调用,一般的调用策略是轮询

    +

    面试官:Ribbon负载均衡策略有哪些 ?

    +

    候选人:

    +

    我想想啊,有很多种,我记得几个:

    +
      +
    • RoundRobinRule:简单轮询服务列表来选择服务器
    • +
    • WeightedResponseTimeRule:按照权重来选择服务器,响应时间越长,权重越小
    • +
    • RandomRule:随机选择一个可用的服务器
    • +
    • ZoneAvoidanceRule:区域敏感策略,以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询(默认)
    • +
    +

    面试官:如果想自定义负载均衡策略如何实现 ?

    +

    候选人:

    +

    提供了两种方式:

    +

    1,创建类实现IRule接口,可以指定负载均衡策略,这个是全局的,对所有的远程调用都起作用

    +

    2,在客户端的配置文件中,可以配置某一个服务调用的负载均衡策略,只是对配置的这个服务生效远程调用

    +

    面试官:什么是服务雪崩,怎么解决这个问题?

    +

    候选人:

    +

    服务雪崩是指一个服务失败,导致整条链路的服务都失败的情形,一般我们在项目解决的话就是两种方案,第一个是服务降级,第二个是服务熔断,如果流量太大的话,可以考虑限流

    +

    服务降级:服务自我保护的一种方式,或者保护下游服务的一种方式,用于确保服务不会受请求突增影响变得不可用,确保服务不会崩溃,一般在实际开发中与feign接口整合,编写降级逻辑

    +

    服务熔断:默认关闭,需要手动打开,如果检测到 10 秒内请求的失败率超过 50%,就触发熔断机制。之后每隔 5 秒重新尝试请求微服务,如果微服务不能响应,继续走熔断机制。如果微服务可达,则关闭熔断机制,恢复正常请求

    +

    面试官:你们的微服务是怎么监控的?

    +

    候选人:

    +

    我们项目中采用的skywalking进行监控的

    +

    1,skywalking主要可以监控接口、服务、物理实例的一些状态。特别是在压测的时候可以看到众多服务中哪些服务和接口比较慢,我们可以针对性的分析和优化。

    +

    2,我们还在skywalking设置了告警规则,特别是在项目上线以后,如果报错,我们分别设置了可以给相关负责人发短信和发邮件,第一时间知道项目的bug情况,第一时间修复

    +

    面试官:你们项目中有没有做过限流 ? 怎么做的 ?

    +

    候选人:

    +

    我当时做的xx项目,采用就是微服务的架构,因为xx因为,应该会有突发流量,最大QPS可以达到2000,但是服务支撑不住,我们项目都通过压测最多可以支撑1200QPS。因为我们平时的QPS也就不到100,为了解决这些突发流量,所以采用了限流。

    +

    【版本1】

    +

    我们当时采用的nginx限流操作,nginx使用的漏桶算法来实现过滤,让请求以固定的速率处理请求,可以应对突发流量,我们控制的速率是按照ip进行限流,限制的流量是每秒20

    +

    【版本2】

    +

    我们当时采用的是spring cloud gateway中支持局部过滤器RequestRateLimiter来做限流,使用的是令牌桶算法,可以根据ip或路径进行限流,可以设置每秒填充平均速率,和令牌桶总容量

    +

    面试官:限流常见的算法有哪些呢?

    +

    候选人:

    +

    比较常见的限流算法有漏桶算法和令牌桶算法

    +

    漏桶算法是把请求存入到桶中,以固定速率从桶中流出,可以让我们的服务做到绝对的平均,起到很好的限流效果

    +

    令牌桶算法在桶中存储的是令牌,按照一定的速率生成令牌,每个请求都要先申请令牌,申请到令牌以后才能正常请求,也可以起到很好的限流作用

    +

    它们的区别是,漏桶和令牌桶都可以处理突发流量,其中漏桶可以做到绝对的平滑,令牌桶有可能会产生突发大量请求的情况,一般nginx限流采用的漏桶,spring cloud gateway中可以支持令牌桶算法

    +

    面试官:什么是CAP理论?

    +

    候选人

    +

    CAP主要是在分布式项目下的一个理论。包含了三项,一致性、可用性、分区容错性

    +
      +
    • 一致性(Consistency)是指更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致(强一致性),不能存在中间状态。
    • +
    • 可用性(Availability) 是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
    • +
    • 分区容错性(Partition tolerance) 是指分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
    • +
    +

    面试官:为什么分布式系统中无法同时保证一致性和可用性?

    +

    候选人

    +

    嗯,是这样的~~

    +

    首先一个前提,对于分布式系统而言,分区容错性是一个最基本的要求,因此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之间进行取舍。

    +

    如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。

    +

    如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据的话,这就违背了一致性的要求。

    +

    面试官:什么是BASE理论?

    +

    候选人

    +

    嗯,这个也是CAP分布式系统设计理论

    +

    BASE是CAP理论中AP方案的延伸,核心思想是即使无法做到强一致性(StrongConsistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。它的思想包含三方面:

    +

    1、Basically Available(基本可用):基本可用是指分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用。

    +

    2、Soft state(软状态):即是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。

    +

    3、Eventually consistent(最终一致性):强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

    +

    面试官:你们采用哪种分布式事务解决方案?

    +

    候选人:

    +

    我们当时是xx项目,主要使用到的seata的at模式解决的分布式事务

    +

    seata的AT模型分为两个阶段:

    +

    1、阶段一RM的工作:① 注册分支事务 ② 记录undo-log(数据快照)③ 执行业务sql并提交 ④报告事务状态

    +

    2、阶段二提交时RM的工作:删除undo-log即可

    +

    3、阶段二回滚时RM的工作:根据undo-log恢复数据到更新前

    +

    at模式牺牲了一致性,保证了可用性,不过,它保证的是最终一致性

    +

    面试官:分布式服务的接口幂等性如何设计?

    +

    候选人:

    +

    嗯,我们当时有一个xx项目的下单操作,采用的token+redis实现的,流程是这样的

    +

    第一次请求,也就是用户打开了商品详情页面,我们会发起一个请求,在后台生成一个唯一token存入redis,key就是用户的id,value就是这个token,同时把这个token返回前端

    +

    第二次请求,当用户点击了下单操作会后,会携带之前的token,后台先到redis进行验证,如果存在token,可以执行业务,同时删除token;如果不存在,则直接返回,不处理业务,就保证了同一个token只处理一次业务,就保证了幂等性

    +

    面试官:xxl-job路由策略有哪些?

    +

    候选人:

    +

    xxl-job提供了很多的路由策略,我们平时用的较多就是:轮询、故障转移、分片广播…

    +

    面试官:xxl-job任务执行失败怎么解决?

    +

    候选人:

    +

    有这么几个操作

    +

    第一:路由策略选择故障转移,优先使用健康的实例来执行任务

    +

    第二,如果还有失败的,我们在创建任务时,可以设置重试次数

    +

    第三,如果还有失败的,就可以查看日志或者配置邮件告警来通知相关负责人解决

    +

    面试官:如果有大数据量的任务同时都需要执行,怎么解决?

    +

    候选人:

    +

    我们会让部署多个实例,共同去执行这些批量的任务,其中任务的路由策略是分片广播

    +

    在任务执行的代码中可以获取分片总数和当前分片,按照取模的方式分摊到各个实例执行就可以了

    +
    +

    pdf

    + + +
    + +
    + + + + +

    5.消息中间件篇

    +

    面试官:RabbitMQ-如何保证消息不丢失

    +

    候选人

    +

    嗯!我们当时MYSQL和Redis的数据双写一致性就是采用RabbitMQ实现同步的,这里面就要求了消息的高可用性,我们要保证消息的不丢失。主要从三个层面考虑

    +

    第一个是开启生产者确认机制,确保生产者的消息能到达队列,如果报错可以先记录到日志中,再去修复数据

    +

    第二个是开启持久化功能,确保消息未消费前在队列中不会丢失,其中的交换机、队列、和消息都要做持久化

    +

    第三个是开启消费者确认机制为auto,由spring确认消息处理成功后完成ack,当然也需要设置一定的重试次数,我们当时设置了3次,如果重试3次还没有收到消息,就将失败后的消息投递到异常交换机,交由人工处理

    +

    面试官:RabbitMQ消息的重复消费问题如何解决的

    +

    候选人

    +

    嗯,这个我们还真遇到过,是这样的,我们当时消费者是设置了自动确认机制,当服务还没来得及给MQ确认的时候,服务宕机了,导致服务重启之后,又消费了一次消息。这样就重复消费了

    +

    因为我们当时处理的支付(订单|业务唯一标识),它有一个业务的唯一标识,我们再处理消息时,先到数据库查询一下,这个数据是否存在,如果不存在,说明没有处理过,这个时候就可以正常处理这个消息了。如果已经存在这个数据了,就说明消息重复消费了,我们就不需要再消费了

    +

    面试官:那你还知道其他的解决方案吗?

    +

    候选人

    +

    嗯,我想想~

    +

    其实这个就是典型的幂等的问题,比如,redis分布式锁、数据库的锁都是可以的

    +

    面试官:RabbitMQ中死信交换机 ? (RabbitMQ延迟队列有了解过嘛)

    +

    候选人

    +

    嗯!了解过!

    +

    我们当时的xx项目有一个xx业务,需要用到延迟队列,其中就是使用RabbitMQ来实现的。

    +

    延迟队列就是用到了死信交换机和TTL(消息存活时间)实现的。

    +

    如果消息超时未消费就会变成死信,在RabbitMQ中如果消息成为死信,队列可以绑定一个死信交换机,在死信交换机上可以绑定其他队列,在我们发消息的时候可以按照需求指定TTL的时间,这样就实现了延迟队列的功能了。

    +

    我记得RabbitMQ还有一种方式可以实现延迟队列,在RabbitMQ中安装一个死信插件,这样更方便一些,我们只需要在声明交互机的时候,指定这个就是死信交换机,然后在发送消息的时候直接指定超时时间就行了,相对于死信交换机+TTL要省略了一些步骤

    +

    面试官:如果有100万消息堆积在MQ , 如何解决 ?

    +

    候选人

    +

    我在实际的开发中,没遇到过这种情况,不过,如果发生了堆积的问题,解决方案也所有很多的

    +

    第一:提高消费者的消费能力 ,可以使用多线程消费任务

    +

    第二:增加更多消费者,提高消费速度

    +

    使用工作队列模式, 设置多个消费者消费消费同一个队列中的消息

    +

    第三:扩大队列容积,提高堆积上限

    +

    可以使用RabbitMQ惰性队列,惰性队列的好处主要是

    +

    ①接收到消息后直接存入磁盘而非内存

    +

    ②消费者要消费消息时才会从磁盘中读取并加载到内存

    +

    ③支持数百万条的消息存储

    +

    面试官:RabbitMQ的高可用机制有了解过嘛

    +

    候选人

    +

    嗯,熟悉的~

    +

    我们当时项目在生产环境下,使用的集群,当时搭建是镜像模式集群,使用了3台机器。

    +

    镜像队列结构是一主多从,所有操作都是主节点完成,然后同步给镜像节点,如果主节点宕机后,镜像节点会替代成新的主节点,不过在主从同步完成前,主节点就已经宕机,可能出现数据丢失

    +

    面试官:那出现丢数据怎么解决呢?

    +

    候选人

    +

    我们可以采用仲裁队列,与镜像队列一样,都是主从模式,支持主从数据同步,主从同步基于Raft协议,强一致。

    +

    并且使用起来也非常简单,不需要额外的配置,在声明队列的时候只要指定这个是仲裁队列即可

    +

    面试官:Kafka是如何保证消息不丢失

    +

    候选人

    +

    嗯,这个保证机制很多,在发送消息到消费者接收消息,在每个阶段都有可能会丢失消息,所以我们解决的话也是从多个方面考虑

    +

    第一个是生产者发送消息的时候,可以使用异步回调发送,如果消息发送失败,我们可以通过回调获取失败后的消息信息,可以考虑重试或记录日志,后边再做补偿都是可以的。同时在生产者这边还可以设置消息重试,有的时候是由于网络抖动的原因导致发送不成功,就可以使用重试机制来解决

    +

    第二个在broker中消息有可能会丢失,我们可以通过kafka的复制机制来确保消息不丢失,在生产者发送消息的时候,可以设置一个acks,就是确认机制。我们可以设置参数为all,这样的话,当生产者发送消息到了分区之后,不仅仅只在leader分区保存确认,在follwer分区也会保存确认,只有当所有的副本都保存确认以后才算是成功发送了消息,所以,这样设置就很大程度了保证了消息不会在broker丢失

    +

    第三个有可能是在消费者端丢失消息,kafka消费消息都是按照offset进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了

    +

    面试官:Kafka中消息的重复消费问题如何解决的

    +

    候选人

    +

    kafka消费消息都是按照offset进行标记消费的,消费者默认是自动按期提交已经消费的偏移量,默认是每隔5s提交一次,如果出现重平衡的情况,可能会重复消费或丢失数据。我们一般都会禁用掉自动提价偏移量,改为手动提交,当消费成功以后再报告给broker消费的位置,这样就可以避免消息丢失和重复消费了

    +

    为了消息的幂等,我们也可以设置唯一主键来进行区分,或者是加锁,数据库的锁,或者是redis分布式锁,都能解决幂等的问题

    +

    面试官:Kafka是如何保证消费的顺序性

    +

    候选人

    +

    kafka默认存储和消费消息,是不能保证顺序性的,因为一个topic数据可能存储在不同的分区中,每个分区都有一个按照顺序的存储的偏移量,如果消费者关联了多个分区不能保证顺序性

    +

    如果有这样的需求的话,我们是可以解决的,把消息都存储同一个分区下就行了,有两种方式都可以进行设置,第一个是发送消息时指定分区号,第二个是发送消息时按照相同的业务设置相同的key,因为默认情况下分区也是通过key的hashcode值来选择分区的,hash值如果一样的话,分区肯定也是一样的

    +

    面试官:Kafka的高可用机制有了解过嘛

    +

    候选人

    +

    嗯,主要是有两个层面,第一个是集群,第二个是提供了复制机制

    +

    kafka集群指的是由多个broker实例组成,即使某一台宕机,也不耽误其他broker继续对外提供服务

    +

    复制机制是可以保证kafka的高可用的,一个topic有多个分区,每个分区有多个副本,有一个leader,其余的是follower,副本存储在不同的broker中;所有的分区副本的内容是都是相同的,如果leader发生故障时,会自动将其中一个follower提升为leader,保证了系统的容错性、高可用性

    +

    面试官:解释一下复制机制中的ISR

    +

    候选人

    +

    ISR的意思是in-sync replica,就是需要同步复制保存的follower

    +

    其中分区副本有很多的follower,分为了两类,一个是ISR,与leader副本同步保存数据,另外一个普通的副本,是异步同步数据,当leader挂掉之后,会优先从ISR副本列表中选取一个作为leader,因为ISR是同步保存数据,数据更加的完整一些,所以优先选择ISR副本列表

    +

    面试官:Kafka数据清理机制了解过嘛

    +

    候选人

    +

    嗯,了解过~~

    +

    Kafka中topic的数据存储在分区上,分区如果文件过大会分段存储segment

    +

    每个分段都在磁盘上以索引(xxxx.index)和日志文件(xxxx.log)的形式存储,这样分段的好处是,第一能够减少单个文件内容的大小,查找数据方便,第二方便kafka进行日志清理。

    +

    在kafka中提供了两个日志的清理策略:

    +

    第一,根据消息的保留时间,当消息保存的时间超过了指定的时间,就会触发清理,默认是168小时( 7天)

    +

    第二是根据topic存储的数据大小,当topic所占的日志文件大小大于一定的阈值,则开始删除最久的消息。这个默认是关闭的

    +

    这两个策略都可以通过kafka的broker中的配置文件进行设置

    +

    面试官:Kafka中实现高性能的设计有了解过嘛

    +

    候选人

    +

    Kafka 高性能,是多方面协同的结果,包括宏观架构、分布式存储、ISR 数据同步、以及高效的利用磁盘、操作系统特性等。主要体现有这么几点:

    +

    消息分区:不受单台服务器的限制,可以不受限的处理更多的数据

    +

    顺序读写:磁盘顺序读写,提升读写效率

    +

    页缓存:把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问

    +

    零拷贝:减少上下文切换及数据拷贝

    +

    消息压缩:减少磁盘IO和网络IO

    +

    分批发送:将消息打包批量发送,减少网络开销

    +
    +

    pdf

    + + +
    + +
    + + + + + + +]]>
    + + 面试 + + + 面试 + +
    + + 常用类及其方法 + /posts/28687.html + 1.String类的常用方法
    package com.hspedu.string_;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class StringMethod01 {
    public static void main(String[] args) {
    //1. equals 前面已经讲过了. 比较内容是否相同,区分大小写
    String str1 = "hello";
    String str2 = "Hello";
    System.out.println(str1.equals(str2));//

    // 2.equalsIgnoreCase 忽略大小写的判断内容是否相等
    String username = "johN";
    if ("john".equalsIgnoreCase(username)) {
    System.out.println("Success!");
    } else {
    System.out.println("Failure!");
    }
    // 3.length 获取字符的个数,字符串的长度
    System.out.println("韩顺平".length());

    // 4.indexOf 获取字符在字符串对象中第一次出现的索引,索引从0开始,如果找不到,返回-1
    String s1 = "wer@terwe@g";
    int index = s1.indexOf('@');
    System.out.println(index);// 3
    System.out.println("weIndex=" + s1.indexOf("we"));//0

    // 5.lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1
    s1 = "wer@terwe@g@";
    index = s1.lastIndexOf('@');
    System.out.println(index);//11
    System.out.println("ter的位置=" + s1.lastIndexOf("ter"));//4

    // 6.substring 截取指定范围的子串
    String name = "hello,张三";
    //下面name.substring(6) 从索引6开始截取后面所有的内容
    System.out.println(name.substring(6));//截取后面的字符
    //name.substring(0,5)表示从索引0开始截取,截取到索引 5-1=4位置
    System.out.println(name.substring(2,5));//llo

    }
    }
    + +
    package com.hspedu.string_;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class StringMethod02 {
    public static void main(String[] args) {
    // 1.toUpperCase转换成大写
    String s = "heLLo";
    System.out.println(s.toUpperCase());//HELLO

    // 2.toLowerCase
    System.out.println(s.toLowerCase());//hello

    // 3.concat拼接字符串
    String s1 = "宝玉";
    s1 = s1.concat("林黛玉").concat("薛宝钗").concat("together");
    System.out.println(s1);//宝玉林黛玉薛宝钗together

    // 4.replace 替换字符串中的字符
    s1 = "宝玉 and 林黛玉 林黛玉 林黛玉";
    //在s1中,将 所有的 林黛玉 替换成薛宝钗
    // 老韩解读: s1.replace() 方法执行后,返回的结果才是替换过的.
    // 注意对 s1没有任何影响
    String s11 = s1.replace("宝玉", "jack");
    System.out.println(s1);//宝玉 and 林黛玉 林黛玉 林黛玉
    System.out.println(s11);//jack and 林黛玉 林黛玉 林黛玉

    // 5.split 分割字符串, 对于某些分割字符,我们需要 转义比如 | \\等
    String poem = "锄禾日当午,汗滴禾下土,谁知盘中餐,粒粒皆辛苦";
    //老韩解读:
    // 1. 以 , 为标准对 poem 进行分割 , 返回一个数组
    // 2. 在对字符串进行分割时,如果有特殊字符,需要加入 转义符 \
    String[] split = poem.split(",");
    poem = "E:\\aaa\\bbb";
    split = poem.split("\\\\");
    System.out.println("==分割后内容===");
    for (int i = 0; i < split.length; i++) {
    System.out.println(split[i]);
    }

    // 6.toCharArray 转换成字符数组
    s = "happy";
    char[] chs = s.toCharArray();
    for (int i = 0; i < chs.length; i++) {
    System.out.println(chs[i]);
    }

    // 7.compareTo 比较两个字符串的大小,如果前者大,
    // 则返回正数,后者大,则返回负数,如果相等,返回0
    // 老韩解读
    // (1) 如果长度相同,并且每个字符也相同,就返回 0
    // (2) 如果长度相同或者不相同,但是在进行比较时,可以区分大小
    // 就返回 if (c1 != c2) {
    // return c1 - c2;
    // }
    // (3) 如果前面的部分都相同,就返回 str1.len - str2.len
    String a = "jcck";// len = 3
    String b = "jack";// len = 4
    System.out.println(a.compareTo(b)); // 返回值是 'c' - 'a' = 2的值

    // 8.format 格式字符串
    /* 占位符有:
    * %s 字符串 %c 字符 %d 整型 %.2f 浮点型
    *
    */
    String name = "john";
    int age = 10;
    double score = 56.857;
    char gender = '男';
    //将所有的信息都拼接在一个字符串.
    String info =
    "我的姓名是" + name + "年龄是" + age + ",成绩是" + score + "性别是" + gender + "。希望大家喜欢我!";

    System.out.println(info);


    //老韩解读
    //1. %s , %d , %.2f %c 称为占位符
    //2. 这些占位符由后面变量来替换
    //3. %s 表示后面由 字符串来替换
    //4. %d 是整数来替换
    //5. %.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
    //6. %c 使用char 类型来替换
    String formatStr = "我的姓名是%s 年龄是%d,成绩是%.2f 性别是%c.希望大家喜欢我!";

    String info2 = String.format(formatStr, name, age, score, gender);

    System.out.println("info2=" + info2);
    }
    }
    + +

    2.StringBufferd类的常用方法

    package com.hspedu.stringbuffer_;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class StringBufferMethod {
    public static void main(String[] args) {

    StringBuffer s = new StringBuffer("hello");
    //增
    s.append(',');// "hello,"
    s.append("张三丰");//"hello,张三丰"
    s.append("赵敏").append(100).append(true).append(10.5);//"hello,张三丰赵敏100true10.5"
    System.out.println(s);//"hello,张三丰赵敏100true10.5"


    //删
    /*
    * 删除索引为>=start && <end 处的字符
    * 解读: 删除 11~14的字符 [11, 14)
    */
    s.delete(11, 14);
    System.out.println(s);//"hello,张三丰赵敏true10.5"

    //改
    //老韩解读,使用 周芷若 替换 索引9-11的字符 [9,11)
    s.replace(9, 11, "周芷若");
    System.out.println(s);//"hello,张三丰周芷若true10.5"
    //查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
    int indexOf = s.indexOf("张三丰");
    System.out.println(indexOf);//6

    //插
    //老韩解读,在索引为9的位置插入 "赵敏",原来索引为9的内容自动后移
    s.insert(9, "赵敏");
    System.out.println(s);//"hello,张三丰赵敏周芷若true10.5"
    //长度
    System.out.println(s.length());//22
    System.out.println(s);

    }
    }
    + +

    3.Math类的常用方法

    package com.hspedu.math_;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class MathMethod {
    public static void main(String[] args) {
    //看看Math常用的方法(静态方法)
    //1.abs 绝对值
    int abs = Math.abs(-9);
    System.out.println(abs);//9

    //2.pow 求幂
    double pow = Math.pow(2, 4);//2的4次方
    System.out.println(pow);//16

    //3.ceil 向上取整,返回>=该参数的最小整数(转成double);
    double ceil = Math.ceil(3.9);
    System.out.println(ceil);//4.0

    //4.floor 向下取整,返回<=该参数的最大整数(转成double)
    double floor = Math.floor(4.001);
    System.out.println(floor);//4.0

    //5.round 四舍五入 Math.floor(该参数+0.5)
    long round = Math.round(5.51);
    System.out.println(round);//6

    //6.sqrt 求开方
    double sqrt = Math.sqrt(9.0);
    System.out.println(sqrt);//3.0

    //7.random 求随机数
    // random 返回的是 0 <= x < 1 之间的一个随机小数
    // 思考:请写出获取 a-b之间的一个随机整数,a,b均为整数 ,比如 a = 2, b=7
    // 即返回一个数 x 2 <= x <= 7
    // 老韩解读 Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a
    // (1) (int)(a) <= x <= (int)(a + Math.random() * (b-a +1) )
    // (2) 使用具体的数给小伙伴介绍 a = 2 b = 7
    // (int)(a + Math.random() * (b-a +1) ) = (int)( 2 + Math.random()*6)
    // Math.random()*6 返回的是 0 <= x < 6 小数
    // 2 + Math.random()*6 返回的就是 2<= x < 8 小数
    // (int)(2 + Math.random()*6) = 2 <= x <= 7
    // (3) 公式就是 (int)(a + Math.random() * (b-a +1) )
    for(int i = 0; i < 100; i++) {
    System.out.println((int)(2 + Math.random() * (7 - 2 + 1)));
    }

    //8.max , min 返回最大值和最小值
    int min = Math.min(1, 9);
    int max = Math.max(45, 90);
    System.out.println("min=" + min);
    System.out.println("max=" + max);

    }
    }
    + +

    4.Arrays类的常用方法

    package com.hspedu.arrays_;

    import java.util.Arrays;
    import java.util.Comparator;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class ArraysMethod01 {
    public static void main(String[] args) {

    Integer[] integers = {1, 20, 90};
    //遍历数组
    // for(int i = 0; i < integers.length; i++) {
    // System.out.println(integers[i]);
    // }
    //直接使用Arrays.toString方法,显示数组
    // System.out.println(Arrays.toString(integers));//

    //演示 sort方法的使用

    Integer arr[] = {1, -1, 7, 0, 89};
    //进行排序
    //老韩解读
    //1. 可以直接使用冒泡排序 , 也可以直接使用Arrays提供的sort方法排序
    //2. 因为数组是引用类型,所以通过sort排序后,会直接影响到 实参 arr
    //3. sort重载的,也可以通过传入一个接口 Comparator 实现定制排序
    //4. 调用 定制排序 时,传入两个参数 (1) 排序的数组 arr
    // (2) 实现了Comparator接口的匿名内部类 , 要求实现 compare方法
    //5. 先演示效果,再解释
    //6. 这里体现了接口编程的方式 , 看看源码,就明白
    // 源码分析
    //(1) Arrays.sort(arr, new Comparator()
    //(2) 最终到 TimSort类的 private static <T> void binarySort(T[] a, int lo, int hi, int start,
    // Comparator<? super T> c)()
    //(3) 执行到 binarySort方法的代码, 会根据动态绑定机制 c.compare()执行我们传入的
    // 匿名内部类的 compare ()
    // while (left < right) {
    // int mid = (left + right) >>> 1;
    // if (c.compare(pivot, a[mid]) < 0)
    // right = mid;
    // else
    // left = mid + 1;
    // }
    //(4) new Comparator() {
    // @Override
    // public int compare(Object o1, Object o2) {
    // Integer i1 = (Integer) o1;
    // Integer i2 = (Integer) o2;
    // return i2 - i1;
    // }
    // }
    //(5) public int compare(Object o1, Object o2) 返回的值>0 还是 <0
    // 会影响整个排序结果, 这就充分体现了 接口编程+动态绑定+匿名内部类的综合使用
    // 将来的底层框架和源码的使用方式,会非常常见
    //Arrays.sort(arr); // 默认排序方法
    //定制排序
    Arrays.sort(arr, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
    Integer i1 = (Integer) o1;
    Integer i2 = (Integer) o2;
    return i2 - i1;
    }
    });
    System.out.println("===排序后===");
    System.out.println(Arrays.toString(arr));//



    }
    }
    + +
    package com.hspedu.arrays_;

    import java.util.Arrays;
    import java.util.List;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class ArraysMethod02 {
    public static void main(String[] args) {
    Integer[] arr = {1, 2, 90, 123, 567};
    // binarySearch 通过二分搜索法进行查找,要求必须排好
    // 老韩解读
    //1. 使用 binarySearch 二叉查找
    //2. 要求该数组是有序的. 如果该数组是无序的,不能使用binarySearch
    //3. 如果数组中不存在该元素,就返回 return -(low + 1); // key not found.
    int index = Arrays.binarySearch(arr, 567);
    System.out.println("index=" + index);

    //copyOf 数组元素的复制
    // 老韩解读
    //1. 从 arr 数组中,拷贝 arr.length个元素到 newArr数组中
    //2. 如果拷贝的长度 > arr.length 就在新数组的后面 增加 null
    //3. 如果拷贝长度 < 0 就抛出异常NegativeArraySizeException
    //4. 该方法的底层使用的是 System.arraycopy()
    Integer[] newArr = Arrays.copyOf(arr, arr.length);
    System.out.println("==拷贝执行完毕后==");
    System.out.println(Arrays.toString(newArr));

    //ill 数组元素的填充
    Integer[] num = new Integer[]{9,3,2};
    //老韩解读
    //1. 使用 99 去填充 num数组,可以理解成是替换原理的元素
    Arrays.fill(num, 99);
    System.out.println("==num数组填充后==");
    System.out.println(Arrays.toString(num));

    //equals 比较两个数组元素内容是否完全一致
    Integer[] arr2 = {1, 2, 90, 123};
    //老韩解读
    //1. 如果arr 和 arr2 数组的元素一样,则方法true;
    //2. 如果不是完全一样,就返回 false
    boolean equals = Arrays.equals(arr, arr2);
    System.out.println("equals=" + equals);

    //asList 将一组值,转换成list
    //老韩解读
    //1. asList方法,会将 (2,3,4,5,6,1)数据转成一个List集合
    //2. 返回的 asList 编译类型 List(接口)
    //3. asList 运行类型 java.util.Arrays#ArrayList, 是Arrays类的
    // 静态内部类 private static class ArrayList<E> extends AbstractList<E>
    // implements RandomAccess, java.io.Serializable
    List asList = Arrays.asList(2,3,4,5,6,1);
    System.out.println("asList=" + asList);
    System.out.println("asList的运行类型" + asList.getClass());



    }
    }
    + +

    5.System类的常用方法

    package com.hspedu.system_;

    import java.util.Arrays;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class System_ {
    public static void main(String[] args) {

    //exit 退出当前程序

    // System.out.println("ok1");
    // //老韩解读
    // //1. exit(0) 表示程序退出
    // //2. 0 表示一个状态 , 正常的状态
    // System.exit(0);//
    // System.out.println("ok2");

    //arraycopy :复制数组元素,比较适合底层调用,
    // 一般使用Arrays.copyOf完成复制数组

    int[] src={1,2,3};
    int[] dest = new int[3];// dest 当前是 {0,0,0}

    //老韩解读
    //1. 主要是搞清楚这五个参数的含义
    //2.
    // 源数组
    // * @param src the source array.
    // srcPos: 从源数组的哪个索引位置开始拷贝
    // * @param srcPos starting position in the source array.
    // dest : 目标数组,即把源数组的数据拷贝到哪个数组
    // * @param dest the destination array.
    // destPos: 把源数组的数据拷贝到 目标数组的哪个索引
    // * @param destPos starting position in the destination data.
    // length: 从源数组拷贝多少个数据到目标数组
    // * @param length the number of array elements to be copied.
    System.arraycopy(src, 0, dest, 0, src.length);
    // int[] src={1,2,3};
    System.out.println("dest=" + Arrays.toString(dest));//[1, 2, 3]

    //currentTimeMillens:返回当前时间距离1970-1-1 的毫秒数
    // 老韩解读:
    System.out.println(System.currentTimeMillis());


    }
    }
    + +

    6.BigDecimal类和BigInteger类使用(大数处理)

    package com.hspedu.bignum;

    import java.math.BigDecimal;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class BigDecimal_ {
    public static void main(String[] args) {
    //当我们需要保存一个精度很高的数时,double 不够用
    //可以是 BigDecimal
    // double d = 1999.11111111111999999999999977788d;
    // System.out.println(d);
    BigDecimal bigDecimal = new BigDecimal("1999.11");
    BigDecimal bigDecimal2 = new BigDecimal("3");
    System.out.println(bigDecimal);

    //老韩解读
    //1. 如果对 BigDecimal进行运算,比如加减乘除,需要使用对应的方法
    //2. 创建一个需要操作的 BigDecimal 然后调用相应的方法即可
    System.out.println(bigDecimal.add(bigDecimal2));
    System.out.println(bigDecimal.subtract(bigDecimal2));
    System.out.println(bigDecimal.multiply(bigDecimal2));
    //System.out.println(bigDecimal.divide(bigDecimal2));//可能抛出异常ArithmeticException
    //在调用divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING
    //如果有无限循环小数,就会保留 分子 的精度
    System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING));
    }
    }
    + +
    package com.hspedu.bignum;

    import java.math.BigInteger;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class BigInteger_ {
    public static void main(String[] args) {

    //当我们编程中,需要处理很大的整数,long 不够用
    //可以使用BigInteger的类来搞定
    // long l = 23788888899999999999999999999l;
    // System.out.println("l=" + l);

    BigInteger bigInteger = new BigInteger("23788888899999999999999999999");
    BigInteger bigInteger2 = new BigInteger("10099999999999999999999999999999999999999999999999999999999999999999999999999999999");
    System.out.println(bigInteger);
    //老韩解读
    //1. 在对 BigInteger 进行加减乘除的时候,需要使用对应的方法,不能直接进行 + - * /
    //2. 可以创建一个 要操作的 BigInteger 然后进行相应操作
    BigInteger add = bigInteger.add(bigInteger2);
    System.out.println(add);//
    BigInteger subtract = bigInteger.subtract(bigInteger2);
    System.out.println(subtract);//减
    BigInteger multiply = bigInteger.multiply(bigInteger2);
    System.out.println(multiply);//乘
    BigInteger divide = bigInteger.divide(bigInteger2);
    System.out.println(divide);//除


    }
    }
    + +

    7.时间类、日期类的常用方法(Date、Calendar,第三代日期类)

    第一代日期类:

    +
    package com.hspedu.date_;

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;


    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class Date01 {
    public static void main(String[] args) throws ParseException {

    //老韩解读
    //1. 获取当前系统时间
    //2. 这里的Date 类是在java.util包
    //3. 默认输出的日期格式是国外的方式, 因此通常需要对格式进行转换
    Date d1 = new Date(); //获取当前系统时间
    System.out.println("当前日期=" + d1);
    Date d2 = new Date(9234567); //通过指定毫秒数得到时间
    System.out.println("d2=" + d2); //获取某个时间对应的毫秒数
    //

    //老韩解读
    //1. 创建 SimpleDateFormat对象,可以指定相应的格式
    //2. 这里的格式使用的字母是规定好,不能乱写

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss E");
    String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
    System.out.println("当前日期=" + format);

    //老韩解读
    //1. 可以把一个格式化的String 转成对应的 Date
    //2. 得到Date 仍然在输出时,还是按照国外的形式,如果希望指定格式输出,需要转换
    //3. 在把String -> Date , 使用的 sdf 格式需要和你给的String的格式一样,否则会抛出转换异常
    String s = "1996年01月01日 10:20:30 星期一";
    Date parse = sdf.parse(s);
    System.out.println("parse=" + sdf.format(parse));

    }
    }
    + +

    第二代日期类

    +
    package com.hspedu.date_;

    import java.util.Calendar;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class Calendar_ {
    public static void main(String[] args) {
    //老韩解读
    //1. Calendar是一个抽象类, 并且构造器是private,不能实例化对象
    //2. 可以通过 getInstance() 来获取实例
    //3. 提供大量的方法和字段提供给程序员
    //4. Calendar没有提供对应的格式化的类,因此需要程序员自己组合来输出(灵活)
    //5. 如果我们需要按照 24小时进制来获取时间, Calendar.HOUR ==改成=> Calendar.HOUR_OF_DAY
    Calendar c = Calendar.getInstance(); //创建日历类对象//比较简单,自由
    System.out.println("c=" + c);
    //2.获取日历对象的某个日历字段
    System.out.println("年:" + c.get(Calendar.YEAR));
    // 这里为什么要 + 1, 因为Calendar 返回月时候,是按照 0 开始编号
    System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
    System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
    System.out.println("小时:" + c.get(Calendar.HOUR));
    System.out.println("分钟:" + c.get(Calendar.MINUTE));
    System.out.println("秒:" + c.get(Calendar.SECOND));
    //Calender 没有专门的格式化方法,所以需要程序员自己来组合显示
    System.out.println(c.get(Calendar.YEAR) + "-" + (c.get(Calendar.MONTH) + 1) + "-" + c.get(Calendar.DAY_OF_MONTH) +
    " " + c.get(Calendar.HOUR_OF_DAY) + ":" + c.get(Calendar.MINUTE) + ":" + c.get(Calendar.SECOND) );

    }
    }
    + +

    第三代日期类(开发中尽量使用第三代日期类)

    +
    package com.hspedu.date_;

    import java.time.Instant;
    import java.time.LocalDate;
    import java.time.LocalDateTime;
    import java.time.LocalTime;
    import java.time.format.DateTimeFormatter;
    import java.util.ArrayList;
    import java.util.Collection;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class LocalDate_ {
    public static void main(String[] args) {
    //第三代日期
    //老韩解读
    //1. 使用now() 返回表示当前日期时间的 对象
    LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
    System.out.println(ldt);

    //2. 使用DateTimeFormatter 对象来进行格式化
    // 创建 DateTimeFormatter对象
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String format = dateTimeFormatter.format(ldt);
    System.out.println("格式化的日期=" + format);

    System.out.println("年=" + ldt.getYear());
    System.out.println("月=" + ldt.getMonth());
    System.out.println("月=" + ldt.getMonthValue());
    System.out.println("日=" + ldt.getDayOfMonth());
    System.out.println("时=" + ldt.getHour());
    System.out.println("分=" + ldt.getMinute());
    System.out.println("秒=" + ldt.getSecond());

    LocalDate now = LocalDate.now(); //可以获取年月日
    LocalTime now2 = LocalTime.now();//获取到时分秒


    //提供 plus 和 minus方法可以对当前时间进行加或者减
    //看看890天后,是什么时候 把 年月日-时分秒
    LocalDateTime localDateTime = ldt.plusDays(890);
    System.out.println("890天后=" + dateTimeFormatter.format(localDateTime));

    //看看在 3456分钟前是什么时候,把 年月日-时分秒输出
    LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
    System.out.println("3456分钟前 日期=" + dateTimeFormatter.format(localDateTime2));

    }
    }

    + +
    package com.hspedu.date_;

    import java.time.Instant;
    import java.util.Date;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class Instant_ {
    public static void main(String[] args) {

    //1.通过 静态方法 now() 获取表示当前时间戳的对象
    Instant now = Instant.now();//获取时间戳对象,时间更加精准
    System.out.println(now);
    //2. 通过 from 可以把 Instant转成 Date
    Date date = Date.from(now);
    //3. 通过 date的toInstant() 可以把 date 转成Instant对象
    Instant instant = date.toInstant();

    }
    }
    + +

    8.实现List接口实现类的常用方法

    package com.hspedu.list_;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class ListMethod {
    @SuppressWarnings({"all"})
    public static void main(String[] args) {
    List list = new ArrayList();
    list.add("张三丰");
    list.add("贾宝玉");
    // void add(int index, Object ele):在index位置插入ele元素
    //在index = 1的位置插入一个对象
    list.add(1, "韩顺平");
    System.out.println("list=" + list);
    // boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
    List list2 = new ArrayList();
    list2.add("jack");
    list2.add("tom");
    list.addAll(1, list2);
    System.out.println("list=" + list);
    // Object get(int index):获取指定index位置的元素
    //说过
    // int indexOf(Object obj):返回obj在集合中首次出现的位置
    System.out.println(list.indexOf("tom"));//2
    // int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
    list.add("韩顺平");
    System.out.println("list=" + list);
    System.out.println(list.lastIndexOf("韩顺平"));
    // Object remove(int index):移除指定index位置的元素,并返回此元素
    list.remove(0);
    System.out.println("list=" + list);
    // Object set(int index, Object ele):设置指定index位置的元素为ele , 相当于是替换.
    list.set(1, "玛丽");
    System.out.println("list=" + list);
    // List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
    // 注意返回的子集合 fromIndex <= subList < toIndex
    List returnlist = list.subList(0, 2);
    System.out.println("returnlist=" + returnlist);

    }
    }
    + +

    9.Map接口的常用方法以及遍历方式

    package com.hspedu.map_;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    @SuppressWarnings({"all"})
    public class MapMethod {
    public static void main(String[] args) {
    //演示map接口常用方法

    Map map = new HashMap();
    map.put("邓超", new Book("", 100));//OK
    map.put("邓超", "孙俪");//替换-> 一会分析源码
    map.put("王宝强", "马蓉");//OK
    map.put("宋喆", "马蓉");//OK
    map.put("刘令博", null);//OK
    map.put(null, "刘亦菲");//OK
    map.put("鹿晗", "关晓彤");//OK
    map.put("hsp", "hsp的老婆");

    System.out.println("map=" + map);

    // remove:根据键删除映射关系
    map.remove(null);
    System.out.println("map=" + map);
    // get:根据键获取值
    Object val = map.get("鹿晗");
    System.out.println("val=" + val);
    // size:获取元素个数
    System.out.println("k-v=" + map.size());
    // isEmpty:判断个数是否为0
    System.out.println(map.isEmpty());//F
    // clear:清除k-v
    //map.clear();
    System.out.println("map=" + map);
    // containsKey:查找键是否存在
    System.out.println("结果=" + map.containsKey("hsp"));//T


    }
    }

    class Book {
    private String name;
    private int num;

    public Book(String name, int num) {
    this.name = name;
    this.num = num;
    }
    }
    + +

    遍历方式

    +
    package com.hspedu.map_;

    import java.util.*;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    @SuppressWarnings({"all"})
    public class MapFor {
    public static void main(String[] args) {

    Map map = new HashMap();
    map.put("邓超", "孙俪");
    map.put("王宝强", "马蓉");
    map.put("宋喆", "马蓉");
    map.put("刘令博", null);
    map.put(null, "刘亦菲");
    map.put("鹿晗", "关晓彤");

    //第一组: 先取出 所有的Key , 通过Key 取出对应的Value

    //着重记住第一种方式,其他的方式开发中用到了,可以查
    Set keyset = map.keySet();
    //(1) 增强for
    System.out.println("-----第一种方式-------");
    for (Object key : keyset) {
    System.out.println(key + "-" + map.get(key));
    }


    //(2) 迭代器
    System.out.println("----第二种方式--------");
    Iterator iterator = keyset.iterator();
    while (iterator.hasNext()) {
    Object key = iterator.next();
    System.out.println(key + "-" + map.get(key));
    }

    //第二组: 把所有的values取出
    Collection values = map.values();
    //这里可以使用所有的Collections使用的遍历方法
    //(1) 增强for
    System.out.println("---取出所有的value 增强for----");
    for (Object value : values) {
    System.out.println(value);
    }
    //(2) 迭代器
    System.out.println("---取出所有的value 迭代器----");
    Iterator iterator2 = values.iterator();
    while (iterator2.hasNext()) {
    Object value = iterator2.next();
    System.out.println(value);

    }

    //第三组: 通过EntrySet 来获取 k-v
    Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
    //(1) 增强for
    System.out.println("----使用EntrySet 的 for增强(第3种)----");
    for (Object entry : entrySet) {
    //将entry 转成 Map.Entry
    Map.Entry m = (Map.Entry) entry;
    System.out.println(m.getKey() + "-" + m.getValue());
    }
    //(2) 迭代器
    System.out.println("----使用EntrySet 的 迭代器(第4种)----");
    Iterator iterator3 = entrySet.iterator();
    while (iterator3.hasNext()) {
    Object entry = iterator3.next();
    //System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
    //向下转型 Map.Entry
    Map.Entry m = (Map.Entry) entry;
    System.out.println(m.getKey() + "-" + m.getValue());
    }


    }
    }
    + +

    9.Collections工具类

    package com.hspedu.collections_;

    import java.util.*;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    @SuppressWarnings({"all"})
    public class Collections_ {
    public static void main(String[] args) {

    //创建ArrayList 集合,用于测试.
    List list = new ArrayList();
    list.add("tom");
    list.add("smith");
    list.add("king");
    list.add("milan");
    list.add("tom");


    // reverse(List):反转 List 中元素的顺序
    Collections.reverse(list);
    System.out.println("list=" + list);
    // shuffle(List):对 List 集合元素进行随机排序
    // for (int i = 0; i < 5; i++) {
    // Collections.shuffle(list);
    // System.out.println("list=" + list);
    // }

    // sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
    Collections.sort(list);
    System.out.println("自然排序后");
    System.out.println("list=" + list);
    // sort(List,Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
    //我们希望按照 字符串的长度大小排序
    Collections.sort(list, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
    //可以加入校验代码.
    return ((String) o2).length() - ((String) o1).length();
    }
    });
    System.out.println("字符串长度大小排序=" + list);
    // swap(List,int, int):将指定 list 集合中的 i 处元素和 j 处元素进行交换

    //比如
    Collections.swap(list, 0, 1);
    System.out.println("交换后的情况");
    System.out.println("list=" + list);

    //Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
    System.out.println("自然顺序最大元素=" + Collections.max(list));
    //Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
    //比如,我们要返回长度最大的元素
    Object maxObject = Collections.max(list, new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
    return ((String)o1).length() - ((String)o2).length();
    }
    });
    System.out.println("长度最大的元素=" + maxObject);


    //Object min(Collection)
    //Object min(Collection,Comparator)
    //上面的两个方法,参考max即可

    //int frequency(Collection,Object):返回指定集合中指定元素的出现次数
    System.out.println("tom出现的次数=" + Collections.frequency(list, "tom"));

    //void copy(List dest,List src):将src中的内容复制到dest中

    ArrayList dest = new ArrayList();
    //为了完成一个完整拷贝,我们需要先给dest 赋值,大小和list.size()一样
    for(int i = 0; i < list.size(); i++) {
    dest.add("");
    }
    //拷贝
    Collections.copy(dest, list);
    System.out.println("dest=" + dest);//把后一个集合的数据拷贝到前一个集合

    //boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
    //如果list中,有tom 就替换成 汤姆
    Collections.replaceAll(list, "tom", "汤姆");
    System.out.println("list替换后=" + list);


    }
    }
    + +

    10.创建文件的三种方式以及文件的相关操作(相关方法)

    package com.hspedu.file;

    import org.junit.jupiter.api.Test;

    import java.io.File;
    import java.io.IOException;

    /**
    * @author GongChangjiang
    * @version 1.0
    * 我亦无他 惟手熟尔
    * 创建文件的方式
    */
    public class FileCreate {
    public static void main(String[] args) {

    }
    @Test
    //第一种方式:new File(fileName);
    public void create01(){
    String filePath = "c:\\news1.txt";//也可以写成"c:/news1.txt"
    File file = new File(filePath);
    try {
    file.createNewFile();
    System.out.println("文件创建成功");
    } catch (IOException e) {
    e.printStackTrace();
    }
    }



    //第二种方式:new File(File parent,String child)//根据父目录文件+子路径构建
    @Test
    public void create02(){
    File parentFile = new File("c:\\");//也可以写成"c:/"
    String fileName = "news2.txt";
    File file = new File(parentFile, fileName);
    try {
    file.createNewFile();
    System.out.println(fileName+" 文件创建成功");
    } catch (IOException e) {
    e.printStackTrace();
    }
    }



    //第三种方式:new File(String parent,String child)//根据父目录+子路径构建
    @Test
    public void create03(){
    String parentPath = "c:\\";//也可以写成"c:/"
    String fileName = "news3.txt";
    File file = new File(parentPath, fileName);
    try {
    file.createNewFile();
    System.out.println(fileName+"文件创建成功");
    } catch (IOException e) {
    e.printStackTrace();
    }
    }



    }
    + +
    package com.hspedu.file;

    import org.junit.jupiter.api.Test;

    import java.io.File;

    /**
    * @author GongChangjiang
    * @version 1.0
    * 我亦无他 惟手熟尔
    */
    public class FileInformation {
    public static void main(String[] args) {

    }
    //获取文件信息
    @Test
    public void info(){
    //先创建文件的对象,这里并没有创建文件,有点像是读取文件
    File file = new File("c:\\news1.txt");

    //调用相应的方法,得到对应的信息
    //得到文件名
    System.out.println("文件名="+file.getName());
    //文件的绝对路径
    System.out.println("文件的绝对路径="+file.getAbsolutePath());
    //文件的父级目录
    System.out.println("文件父级目录="+file.getParent());
    //文件的大小,返回的是字节(一个英文字母对应的是一个字节,一个汉字对应的是三个字节)
    System.out.println("文件的大小(字节)="+file.length());
    //文件是否存在,返回的是true /false
    System.out.println("文件是否存在="+file.exists());
    //是不是一个文件
    System.out.println("是不是一个文件="+file.isFile());
    //是不是一个目录
    System.out.println("是不是一个目录="+file.isDirectory());

    }
    }
    + +
    package com.hspedu.file;

    import org.junit.jupiter.api.Test;

    import java.io.File;
    import java.io.InputStream;
    import java.io.OutputStream;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class Directory_ {
    public static void main(String[] args) {

    //
    }

    //判断 d:\\news1.txt 是否存在,如果存在就删除
    @Test
    public void m1() {

    String filePath = "e:\\news1.txt";
    File file = new File(filePath);
    if (file.exists()) {
    if (file.delete()) {
    System.out.println(filePath + "删除成功");
    } else {
    System.out.println(filePath + "删除失败");
    }
    } else {
    System.out.println("该文件不存在...");
    }

    }

    //判断 D:\\demo02 是否存在,存在就删除,否则提示不存在
    //这里我们需要体会到,在java编程中,目录也被当做文件
    @Test
    public void m2() {

    String filePath = "D:\\demo02";
    File file = new File(filePath);
    if (file.exists()) {
    if (file.delete()) {
    System.out.println(filePath + "删除成功");
    } else {
    System.out.println(filePath + "删除失败");
    }
    } else {
    System.out.println("该目录不存在...");
    }

    }

    //判断 D:\\demo\\a\\b\\c 目录是否存在,如果存在就提示已经存在,否则就创建
    @Test
    public void m3() {

    String directoryPath = "D:\\demo\\a\\b\\c";
    File file = new File(directoryPath);
    if (file.exists()) {
    System.out.println(directoryPath + "存在..");
    } else {
    if (file.mkdirs()) { //创建一级目录使用mkdir() ,创建多级目录使用mkdirs()
    System.out.println(directoryPath + "创建成功..");
    } else {
    System.out.println(directoryPath + "创建失败...");
    }
    }



    }
    }
    + +

    11.FileInputStream 字节输入流

    package com.hspedu.inputstream_;

    import org.junit.jupiter.api.Test;

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示FileInputStream的使用(字节输入流 文件--> 程序)
    */
    public class FileInputStream_ {
    public static void main(String[] args) {

    }

    /**
    * 演示读取文件...
    * 单个字节的读取,效率比较低
    * -> 使用 read(byte[] b)
    */
    @Test
    public void readFile01() {
    String filePath = "e:\\hello.txt";
    int readData = 0;
    FileInputStream fileInputStream = null;
    try {
    //创建 FileInputStream 对象,用于读取 文件
    fileInputStream = new FileInputStream(filePath);
    //从该输入流读取一个字节的数据。 如果没有输入可用,此方法将阻止。
    //如果返回-1 , 表示读取完毕
    while ((readData = fileInputStream.read()) != -1) {//返回的是一个int类型的字节值
    System.out.print((char)readData);//转成char显示
    }

    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    //关闭文件流,释放资源.
    try {
    fileInputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    }

    /**
    * 使用 read(byte[] b) 读取文件,提高效率
    */
    @Test
    public void readFile02() {
    String filePath = "e:\\hello.txt";
    //字节数组
    byte[] buf = new byte[8]; //一次读取8个字节.
    int readLen = 0;
    FileInputStream fileInputStream = null;
    try {
    //创建 FileInputStream 对象,用于读取 文件
    fileInputStream = new FileInputStream(filePath);
    //从该输入流读取最多b.length字节的数据到字节数组。 此方法将阻塞,直到某些输入可用。
    //如果返回-1 , 表示读取完毕
    //如果读取正常, 返回实际读取的字节数
    while ((readLen = fileInputStream.read(buf)) != -1) {//返回的是读取的字节数
    System.out.print(new String(buf, 0, readLen));//显示
    }

    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    //关闭文件流,释放资源.
    try {
    fileInputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    }
    }
    + +

    12.FileOutputStream 字节输出流

    package com.hspedu.outputstream_;

    import org.junit.jupiter.api.Test;

    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class FileOutputStream01 {
    public static void main(String[] args) {

    }

    /**
    * 演示使用FileOutputStream 将数据写到文件中,
    * 如果该文件不存在,则创建该文件
    */
    @Test
    public void writeFile() {

    //创建 FileOutputStream对象
    String filePath = "e:\\a.txt";
    FileOutputStream fileOutputStream = null;
    try {
    //得到 FileOutputStream对象 对象
    //老师说明
    //1. new FileOutputStream(filePath) 创建方式,当写入内容是,会覆盖原来的内容
    //2. new FileOutputStream(filePath, true) 创建方式,当写入内容是,是追加到文件后面
    fileOutputStream = new FileOutputStream(filePath, true);
    //写入一个字节
    //fileOutputStream.write('H');//
    //写入字符串
    String str = "hsp,world!";
    //str.getBytes() 可以把 字符串-> 字节数组
    //fileOutputStream.write(str.getBytes());
    /*
    write(byte[] b, int off, int len) 将 len字节从位于偏移量 off的指定字节数组写入此文件输出流
    从off开始,写len个字节写入到文件中
    */
    fileOutputStream.write(str.getBytes(), 0, 3);

    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    fileOutputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
    + +

    13.文件拷贝(FileInputStream和FileOutputStream)

    package com.hspedu.outputstream_;

    import com.hspedu.inputstream_.FileInputStream_;

    import java.io.*;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class FileCopy {
    public static void main(String[] args) {
    //完成 文件拷贝,将 e:\\Koala.jpg 拷贝 c:\\
    //思路分析
    //1. 创建文件的输入流 , 将文件读入到程序
    //2. 创建文件的输出流, 将读取到的文件数据,写入到指定的文件.
    String srcFilePath = "e:\\Koala.jpg";
    String destFilePath = "e:\\Koala3.jpg";
    FileInputStream fileInputStream = null;
    FileOutputStream fileOutputStream = null;

    try {

    fileInputStream = new FileInputStream(srcFilePath);
    fileOutputStream = new FileOutputStream(destFilePath);
    //定义一个字节数组,提高读取效果
    byte[] buf = new byte[1024];
    int readLen = 0;
    while ((readLen = fileInputStream.read(buf)) != -1) {
    //读取到后,就写入到文件 通过 fileOutputStream
    //即,是一边读,一边写
    fileOutputStream.write(buf, 0, readLen);//一定要使用这个方法

    }
    System.out.println("拷贝ok~");


    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    //关闭输入流和输出流,释放资源
    if (fileInputStream != null) {
    fileInputStream.close();
    }
    if (fileOutputStream != null) {
    fileOutputStream.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }


    }
    }
    + +
    package com.hspedu.writer_;

    import java.io.*;

    /**
    * @author GongChangjiang
    * @version 1.0
    * 我亦无他 惟手熟尔
    * 拷贝文件
    */
    public class BufferedCopy_ {
    public static void main(String[] args) throws Exception {
    //源文件
    String srcFilePath = "c:\\story.txt";
    //目标文件
    String destFilePath = "c:\\dest.txt";
    BufferedReader bufferedReader = new BufferedReader(new FileReader(srcFilePath));
    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(destFilePath));
    String line;
    while ((line = bufferedReader.readLine()) != null){
    //取到的每一行加入到目标文件中
    bufferedWriter.write(line);
    //换行符
    bufferedWriter.newLine();
    }
    //关闭流
    bufferedReader.close();
    bufferedWriter.close();
    }
    }
    + +

    14.FileReader 字符输入流

    package com.hspedu.reader_;

    import org.junit.jupiter.api.Test;

    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.FileWriter;
    import java.io.IOException;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class FileReader_ {
    public static void main(String[] args) {


    }

    /**
    * 单个字符读取文件
    */
    @Test
    public void readFile01() {
    String filePath = "e:\\story.txt";
    FileReader fileReader = null;
    int data = 0;
    //1. 创建FileReader对象
    try {
    fileReader = new FileReader(filePath);
    //循环读取 使用read, 单个字符读取
    while ((data = fileReader.read()) != -1) {
    System.out.print((char) data);
    }

    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if (fileReader != null) {
    fileReader.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

    /**
    * 字符数组读取文件
    */
    @Test
    public void readFile02() {
    System.out.println("~~~readFile02 ~~~");
    String filePath = "e:\\story.txt";
    FileReader fileReader = null;

    int readLen = 0;
    char[] buf = new char[8];
    //1. 创建FileReader对象
    try {
    fileReader = new FileReader(filePath);
    //循环读取 使用read(buf), 返回的是实际读取到的字符数
    //如果返回-1, 说明到文件结束
    while ((readLen = fileReader.read(buf)) != -1) {
    System.out.print(new String(buf, 0, readLen));
    }

    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if (fileReader != null) {
    fileReader.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

    }
    + +

    15.FileWriter 字符输出流

    package com.hspedu.writer_;

    import java.io.FileWriter;
    import java.io.IOException;

    /**
    * @author GongChangjiang
    * @version 1.0
    * 我亦无他 惟手熟尔
    */
    public class FileWriter_ {
    public static void main(String[] args) {
    String filePath = "c:\\note.txt";
    FileWriter fileWriter = null;
    char[] chars = {'a','b','c'};
    try {
    fileWriter = new FileWriter(filePath);
    fileWriter.write('H');//写入单个字符
    fileWriter.write(chars);//写入字符数组
    fileWriter.write("韩顺平教育".toCharArray(),0,3);//写入数组的指定部分
    fileWriter.write(" 你好北京~");//写入整个字符串
    fileWriter.write("上海你好",0,2);//写入字符串的指定部分
    } catch (IOException e) {
    e.printStackTrace();
    }finally {
    try {
    //下面的两种方式二选一,必须选一个,否则文件无法写入
    fileWriter.flush();
    fileWriter.close();//等价于 flush() + 关闭
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
    + +

    16.BufferedReader 字符处理流(包装流)

    package com.hspedu.reader_;

    import java.io.BufferedReader;
    import java.io.FileReader;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示bufferedReader 使用
    */
    public class BufferedReader_ {
    public static void main(String[] args) throws Exception {

    String filePath = "e:\\a.java";
    //创建bufferedReader
    BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
    //读取
    String line; //按行读取, 效率高
    //说明
    //1. bufferedReader.readLine() 是按行读取文件
    //2. 当返回null 时,表示文件读取完毕
    while ((line = bufferedReader.readLine()) != null) {
    System.out.println(line);
    }

    //关闭流, 这里注意,只需要关闭 BufferedReader ,因为底层会自动的去关闭 节点流
    //FileReader。
    /*
    public void close() throws IOException {
    synchronized (lock) {
    if (in == null)
    return;
    try {
    in.close();//in 就是我们传入的 new FileReader(filePath), 关闭了.
    } finally {
    in = null;
    cb = null;
    }
    }
    }

    */
    bufferedReader.close();

    }
    }
    + +

    17.BufferedWriter 字符处理流(包装流)

    package com.hspedu.writer_;

    import java.io.BufferedWriter;
    import java.io.FileWriter;
    import java.io.IOException;

    /**
    * @author GongChangjiang
    * @version 1.0
    * 我亦无他 惟手熟尔
    */
    public class BufferWriter_ {
    public static void main(String[] args) throws IOException {
    String filePath = "c:\\note.txt";
    //new FileWriter(filePath,true);表示以追加的方式加入
    //new FileWriter(filePath);表示以覆盖的方式加入
    BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(filePath,true));
    bufferedWriter.write("你好,韩顺平!");
    bufferedWriter.newLine();//插入一个和系统相关的换行
    bufferedWriter.write("你好,韩顺平!");
    bufferedWriter.newLine();
    bufferedWriter.write("你好,韩顺平!");
    //关闭的是外层流(BufferedWriter),在底层也关闭了FileWriter节点流
    bufferedWriter.close();
    }
    }
    + +

    18.文件拷贝(BufferedInputStream和BufferedOutputStream)

    package com.hspedu.outputstream_;

    import java.io.*;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示使用BufferedOutputStream 和 BufferedInputStream使用
    * 使用他们,可以完成二进制文件拷贝.
    * 思考:字节流可以操作二进制文件,可以操作文本文件吗?当然可以
    */
    public class BufferedCopy02 {
    public static void main(String[] args) {

    // String srcFilePath = "e:\\Koala.jpg";
    // String destFilePath = "e:\\hsp.jpg";
    // String srcFilePath = "e:\\0245_韩顺平零基础学Java_引出this.avi";
    // String destFilePath = "e:\\hsp.avi";
    String srcFilePath = "e:\\a.java";
    String destFilePath = "e:\\a3.java";

    //创建BufferedOutputStream对象BufferedInputStream对象
    BufferedInputStream bis = null;
    BufferedOutputStream bos = null;

    try {
    //因为 FileInputStream 是 InputStream 子类
    bis = new BufferedInputStream(new FileInputStream(srcFilePath));
    bos = new BufferedOutputStream(new FileOutputStream(destFilePath));

    //循环的读取文件,并写入到 destFilePath
    byte[] buff = new byte[1024];
    int readLen = 0;
    //当返回 -1 时,就表示文件读取完毕
    while ((readLen = bis.read(buff)) != -1) {
    bos.write(buff, 0, readLen);
    }

    System.out.println("文件拷贝完毕~~~");

    } catch (IOException e) {
    e.printStackTrace();
    } finally {

    //关闭流 , 关闭外层的处理流即可,底层会去关闭节点流
    try {
    if(bis != null) {
    bis.close();
    }
    if(bos != null) {
    bos.close();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

    }


    }
    }
    + +

    19. ObjectOutputStream 序列化操作(对象字节输出流 )保存数据类型和数据

    package com.hspedu.outputstream_;

    import java.io.FileOutputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示ObjectOutputStream的使用, 完成数据的序列化
    */
    public class ObjectOutStream_ {
    public static void main(String[] args) throws Exception {
    //序列化后,保存的文件格式,不是纯文本,而是按照他的格式来保存
    String filePath = "e:\\data.dat";

    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));

    //序列化数据到 e:\data.dat
    oos.writeInt(100);// int -> Integer (实现了 Serializable)
    oos.writeBoolean(true);// boolean -> Boolean (实现了 Serializable)
    oos.writeChar('a');// char -> Character (实现了 Serializable)
    oos.writeDouble(9.5);// double -> Double (实现了 Serializable)
    oos.writeUTF("韩顺平教育");//String (实现了 Serializable)
    //保存一个dog对象
    //Dog也要实现序列化接口
    oos.writeObject(new Dog("旺财", 10, "日本", "白色"));
    oos.close();
    System.out.println("数据保存完毕(序列化形式)");


    }
    }
    + +

    20.ObjectInputStream 反序列化操作(对象字节输入流 )恢复数据类型和数据

    package com.hspedu.inputstream_;
    import com.hspedu.outputstream_.Dog;
    import java.io.*;
    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class ObjectInputStream_ {
    public static void main(String[] args) throws IOException, ClassNotFoundException {

    //指定反序列化的文件
    String filePath = "e:\\data.dat";

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));

    //读取
    //老师解读
    //1. 读取(反序列化)的顺序需要和你保存数据(序列化)的顺序一致
    //2. 否则会出现异常

    System.out.println(ois.readInt());
    System.out.println(ois.readBoolean());

    System.out.println(ois.readChar());
    System.out.println(ois.readDouble());
    System.out.println(ois.readUTF());


    //dog 的编译类型是 Object , dog 的运行类型是 Dog
    Object dog = ois.readObject();
    System.out.println("运行类型=" + dog.getClass());
    System.out.println("dog信息=" + dog);//底层 Object -> Dog

    //这里是特别重要的细节:

    //1. 如果我们希望调用Dog的方法, 需要向下转型
    Dog dog2 = (Dog)dog;
    System.out.println(dog2.getName()); //旺财..

    //关闭流, 关闭外层流即可,底层会关闭 FileInputStream 流
    ois.close();


    }
    }
    + +

    21.InputStreamReader (转换流 字节流转化成字符流)

    package com.hspedu.transformation;

    import java.io.*;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示使用 InputStreamReader 转换流解决中文乱码问题
    * 将字节流 FileInputStream 转成字符流 InputStreamReader, 指定编码 gbk/utf-8
    */
    public class InputStreamReader_ {
    public static void main(String[] args) throws IOException {

    String filePath = "e:\\a.txt";
    //解读
    //1. 把 FileInputStream 转成 InputStreamReader
    //2. 指定编码 gbk
    //InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "gbk");
    //3. 把 InputStreamReader 传入 BufferedReader
    //BufferedReader br = new BufferedReader(isr);

    //将2 和 3 合在一起
    BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "gbk"));

    //4. 读取
    String s = br.readLine();
    System.out.println("读取内容=" + s);
    //5. 关闭外层流
    br.close();

    }


    }
    + +

    22.OutputStreamWriter(转换流 字节流转成成字符流)

    package com.hspedu.transformation;

    import java.io.*;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示 OutputStreamWriter 使用
    * 把FileOutputStream 字节流,转成字符流 OutputStreamWriter
    * 指定处理的编码 gbk/utf-8/utf8
    */
    public class OutputStreamWriter_ {
    public static void main(String[] args) throws IOException {
    String filePath = "e:\\hsp.txt";
    String charSet = "utf-8";
    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath), charSet);
    osw.write("hi, 韩顺平教育");
    osw.close();
    System.out.println("按照 " + charSet + " 保存文件成功~");


    }
    }
    + +

    23.PrintStream(字节打印流)只有输出流,没有输入流

    package com.hspedu.printstream;

    import java.io.IOException;
    import java.io.PrintStream;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示PrintStream (字节打印流/输出流)
    */
    public class PrintStream_ {
    public static void main(String[] args) throws IOException {

    PrintStream out = System.out;
    //在默认情况下,PrintStream 输出数据的位置是 标准输出,即显示器
    /*
    public void print(String s) {
    if (s == null) {
    s = "null";
    }
    write(s);
    }

    */
    out.print("john, hello");
    //因为print底层使用的是write , 所以我们可以直接调用write进行打印/输出
    out.write("韩顺平,你好".getBytes());
    out.close();

    //我们可以去修改打印流输出的位置/设备
    //1. 输出修改成到 "e:\\f1.txt"
    //2. "hello, 韩顺平教育~" 就会输出到 e:\f1.txt
    //3. public static void setOut(PrintStream out) {
    // checkIO();
    // setOut0(out); // native 方法,修改了out
    // }
    System.setOut(new PrintStream("e:\\f1.txt"));
    System.out.println("hello, 韩顺平教育~");


    }
    }
    + +

    24.PrintWriter (字符打印流)只有输出流,没有输入流

    package com.hspedu.transformation;

    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.PrintWriter;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示 PrintWriter 使用方式
    */
    public class PrintWriter_ {
    public static void main(String[] args) throws IOException {

    //PrintWriter printWriter = new PrintWriter(System.out);//默认显示在显示器上
    PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt"));
    printWriter.print("hi, 北京你好~~~~");
    //关闭或者刷新才会写入
    printWriter.close();//flush + 关闭流, 才会将数据写入到文件..

    }
    }
    + +

    25.Properties读取修改配置文件

    package com.hspedu.properties_;

    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.Properties;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class Properties02 {
    public static void main(String[] args) throws IOException {
    //使用Properties 类来读取mysql.properties 文件

    //1. 创建Properties 对象
    Properties properties = new Properties();
    //2. 加载指定配置文件
    properties.load(new FileReader("src\\mysql.properties"));
    //3. 把k-v显示控制台
    properties.list(System.out);
    //4. 根据key 获取对应的值
    String user = properties.getProperty("user");
    String pwd = properties.getProperty("pwd");
    System.out.println("用户名=" + user);
    System.out.println("密码是=" + pwd);



    }
    }
    + +
    package com.hspedu.properties_;

    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.util.Properties;

    /**
    * @author 韩顺平
    * @version 1.0
    */
    public class Properties03 {
    public static void main(String[] args) throws IOException {
    //使用Properties 类来创建 配置文件, 修改配置文件内容

    Properties properties = new Properties();
    //创建
    //1.如果该文件没有key 就是创建
    //2.如果该文件有key ,就是修改
    /*
    Properties 父类是 Hashtable , 底层就是Hashtable 核心方法
    public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
    throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
    if ((entry.hash == hash) && entry.key.equals(key)) {
    V old = entry.value;
    entry.value = value;//如果key 存在,就替换
    return old;
    }
    }

    addEntry(hash, key, value, index);//如果是新k, 就addEntry
    return null;
    }

    */
    properties.setProperty("charset", "utf8");
    properties.setProperty("user", "汤姆");//注意保存时,是中文的 unicode码值
    properties.setProperty("pwd", "888888");

    //将k-v 存储文件中即可
    properties.store(new FileOutputStream("src\\mysql2.properties"), null);
    System.out.println("保存配置文件成功~");

    }
    }
    + +

    26.InetAddress类的常用方法

    package com.hspedu.api;
    import java.net.InetAddress;
    import java.net.UnknownHostException;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示InetAddress 类的使用
    */
    public class API_ {
    public static void main(String[] args) throws UnknownHostException {

    //1. 获取本机的InetAddress 对象
    InetAddress localHost = InetAddress.getLocalHost();
    System.out.println(localHost);//DESKTOP-S4MP84S/192.168.12.1

    //2. 根据指定主机名 获取 InetAddress对象
    InetAddress host1 = InetAddress.getByName("DESKTOP-S4MP84S");
    System.out.println("host1=" + host1);//DESKTOP-S4MP84S/192.168.12.1

    //3. 根据域名返回 InetAddress对象, 比如 www.baidu.com 对应
    InetAddress host2 = InetAddress.getByName("www.baidu.com");
    System.out.println("host2=" + host2);//www.baidu.com / 110.242.68.4

    //4. 通过 InetAddress 对象,获取对应的地址
    String hostAddress = host2.getHostAddress();//IP 110.242.68.4
    System.out.println("host2 对应的ip = " + hostAddress);//110.242.68.4

    //5. 通过 InetAddress 对象,获取对应的主机名/或者的域名
    String hostName = host2.getHostName();
    System.out.println("host2对应的主机名/域名=" + hostName); // www.baidu.com

    }
    }
    + +

    27.网络编程相关题目的源码

    /*
    题目
    (1)编写客户端程序和服务器端程序
    (2)客户端可以输入一个 音乐 的文件名,比如 高山流水,服务端 收到音乐后,可以给客户端 返回这个 音乐文件
    如果服务器没有这文件,返回一个默认的音乐即可。
    (3)客户端收到文件后,保存在本地 c:\\
    (4)该程序可以使用StreamUtils.java
    */



    package com.hspedu.homework;

    import java.io.*;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.util.Scanner;

    /**
    * @author GongChangjiang
    * @version 1.0
    * 我亦无他 惟手熟尔
    * 客户端
    */
    public class Homework03Client {
    public static void main(String[] args) throws Exception {
    //通过对方的IP地址和端口向对方发送数据
    Socket socket = new Socket(InetAddress.getLocalHost(), 9898);
    BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入一个歌曲名:");
    String songName = scanner.next();
    bufferedWriter.write(songName);
    bufferedWriter.flush();
    socket.shutdownOutput();
    //接收相应的歌曲
    String destFilePath = "src\\歌曲.mp3";
    InputStream inputStream = socket.getInputStream();
    byte[] bytes = StreamUtils.streamToByteArray(inputStream);
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(destFilePath));
    bufferedOutputStream.write(bytes);
    bufferedOutputStream.flush();


    //关闭对应的流和socket
    System.out.println("客户端退出");
    socket.close();
    bufferedWriter.close();
    bufferedOutputStream.close();


    }
    }
    + +
    package com.hspedu.homework;

    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;

    /**
    * @author GongChangjiang
    * @version 1.0
    * 我亦无他 惟手熟尔
    * 服务端
    */
    public class Homework03Server {
    public static void main(String[] args) throws Exception {
    System.out.println("服务器端正在9898端口监听......");
    ServerSocket serverSocket = new ServerSocket(9898);
    Socket socket = serverSocket.accept();
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    String s = bufferedReader.readLine();
    System.out.println(s);
    //根据输入的歌曲名做出相应的操作

    if("高山流水".equals(s)){
    String filePath = "c:\\AlYun\\高山流水.mp3";
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
    byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
    OutputStream outputStream = socket.getOutputStream();
    outputStream.write(bytes);
    socket.shutdownOutput();
    }else {
    String filePath = "c:\\AlYun\\无名.mp3";
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
    byte[] bytes = StreamUtils.streamToByteArray(bufferedInputStream);
    OutputStream outputStream = socket.getOutputStream();
    outputStream.write(bytes);
    socket.shutdownOutput();
    }

    //关闭相应的流和socket
    System.out.println("服务端退出");
    serverSocket.close();
    socket.close();
    bufferedReader.close();

    }
    }
    + +
    package com.hspedu.homework;

    import java.io.BufferedReader;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;

    /**
    * 此类用于演示关于流的读写方法
    *
    */
    public class StreamUtils {
    /**
    * 功能:将输入流转换成byte[], 即可以把文件的内容读入到byte[]
    * @param is
    * @return
    * @throws Exception
    */
    public static byte[] streamToByteArray(InputStream is) throws Exception{
    ByteArrayOutputStream bos = new ByteArrayOutputStream();//创建输出流对象
    byte[] b = new byte[1024];//字节数组
    int len;
    while((len=is.read(b))!=-1){//循环读取
    bos.write(b, 0, len);//把读取到的数据,写入bos
    }
    byte[] array = bos.toByteArray();//然后将bos 转成字节数组
    bos.close();
    return array;
    }
    /**
    * 功能:将InputStream转换成String
    * @param is
    * @return
    * @throws Exception
    */

    public static String streamToString(InputStream is) throws Exception{
    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    StringBuilder builder= new StringBuilder();
    String line;
    while((line=reader.readLine())!=null){
    builder.append(line+"\r\n");
    }
    return builder.toString();

    }

    }
    + +

    28.Class类对象的常用方法举例

    package com.hspedu.reflection.class_;

    import com.hspedu.Car;

    import java.lang.reflect.Field;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示Class类的常用方法
    */
    public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {

    String classAllPath = "com.hspedu.Car";
    //1 . 获取到Car类 对应的 Class对象
    //<?> 表示不确定的Java类型
    Class<?> cls = Class.forName(classAllPath);
    //2. 输出cls
    System.out.println(cls); //显示cls对象, 是哪个类的Class对象 com.hspedu.Car
    System.out.println(cls.getClass());//输出cls运行类型 java.lang.Class
    //3. 得到包名
    System.out.println(cls.getPackage().getName());//包名
    //4. 得到全类名
    System.out.println(cls.getName());
    //5. 通过cls创建对象实例
    Car car = (Car) cls.newInstance();
    System.out.println(car);//car.toString()
    //6. 通过反射获取属性 brand
    Field brand = cls.getField("brand");
    System.out.println(brand.get(car));//宝马
    //7. 通过反射给属性赋值
    brand.set(car, "奔驰");
    System.out.println(brand.get(car));//奔驰
    //8 我希望大家可以得到所有的属性(字段)
    System.out.println("=======所有的字段属性====");
    Field[] fields = cls.getFields();
    for (Field f : fields) {
    System.out.println(f.getName());//名称
    }


    }
    }
    //用于举例的辅助类
    public class Car {
    public String brand = "宝马";//品牌
    public int price = 500000;
    public String color = "白色";

    @Override
    public String toString() {
    return "Car{" +
    "brand='" + brand + '\'' +
    ", price=" + price +
    ", color='" + color + '\'' +
    '}';
    }
    }

    + +

    29.获取Class对象的六种方式

    package com.hspedu.reflection.class_;

    import com.hspedu.Car;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示得到Class对象的各种方式(6)
    */
    public class GetClass_ {
    public static void main(String[] args) throws ClassNotFoundException {

    //1. Class.forName
    String classAllPath = "com.hspedu.Car"; //通过读取配置文件获取
    Class<?> cls1 = Class.forName(classAllPath);
    System.out.println(cls1);

    //2. 类名.class , 应用场景: 用于参数传递
    Class cls2 = Car.class;
    System.out.println(cls2);

    //3. 对象.getClass(), 应用场景,有对象实例
    Car car = new Car();
    Class cls3 = car.getClass();
    System.out.println(cls3);

    //4. 通过类加载器【4种】来获取到类的Class对象
    //(1)先得到类加载器 car
    ClassLoader classLoader = car.getClass().getClassLoader();
    //(2)通过类加载器得到Class对象
    Class cls4 = classLoader.loadClass(classAllPath);
    System.out.println(cls4);

    //cls1 , cls2 , cls3 , cls4 其实是同一个对象
    System.out.println(cls1.hashCode());
    System.out.println(cls2.hashCode());
    System.out.println(cls3.hashCode());
    System.out.println(cls4.hashCode());

    //5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class类对象
    Class<Integer> integerClass = int.class;
    Class<Character> characterClass = char.class;
    Class<Boolean> booleanClass = boolean.class;
    System.out.println(integerClass);//int

    //6. 基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
    Class<Integer> type1 = Integer.TYPE;
    Class<Character> type2 = Character.TYPE; //其它包装类BOOLEAN, DOUBLE, LONG,BYTE等待
    System.out.println(type1);

    System.out.println(integerClass.hashCode());//?
    System.out.println(type1.hashCode());//?




    }
    }
    + +

    30.通过Class对象获取类的结构信息的相关方法

    package com.hspedu.reflection;

    import org.junit.jupiter.api.Test;

    import java.lang.annotation.Annotation;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示如何通过反射获取类的结构信息
    */
    public class ReflectionUtils {
    public static void main(String[] args) {

    }
    //第二组方法API
    @Test
    public void api_02() throws ClassNotFoundException, NoSuchMethodException {
    //得到Class对象
    Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
    //getDeclaredFields:获取本类中所有属性
    //规定 说明: 默认修饰符 是0 , public 是1 ,private 是 2 ,protected 是 4 , static 是 8 ,final 是 16
    Field[] declaredFields = personCls.getDeclaredFields();
    for (Field declaredField : declaredFields) {
    System.out.println("本类中所有属性=" + declaredField.getName()
    + " 该属性的修饰符值=" + declaredField.getModifiers()
    + " 该属性的类型=" + declaredField.getType());
    }

    //getDeclaredMethods:获取本类中所有方法
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
    System.out.println("本类中所有方法=" + declaredMethod.getName()
    + " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
    + " 该方法返回类型" + declaredMethod.getReturnType());

    //输出当前这个方法的形参数组情况
    Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
    System.out.println("该方法的形参类型=" + parameterType);
    }
    }

    //getDeclaredConstructors:获取本类中所有构造器
    Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
    System.out.println("====================");
    System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名

    Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
    System.out.println("该构造器的形参类型=" + parameterType);
    }



    }

    }

    //第一组方法API
    @Test
    public void api_01() throws ClassNotFoundException, NoSuchMethodException {

    //得到Class对象
    Class<?> personCls = Class.forName("com.hspedu.reflection.Person");
    //getName:获取全类名
    System.out.println(personCls.getName());//com.hspedu.reflection.Person
    //getSimpleName:获取简单类名
    System.out.println(personCls.getSimpleName());//Person
    //getFields:获取所有public修饰的属性,包含本类以及父类的
    Field[] fields = personCls.getFields();
    for (Field field : fields) {//增强for
    System.out.println("本类以及父类的属性=" + field.getName());
    }
    //getDeclaredFields:获取本类中所有属性
    Field[] declaredFields = personCls.getDeclaredFields();
    for (Field declaredField : declaredFields) {
    System.out.println("本类中所有属性=" + declaredField.getName());
    }
    //getMethods:获取所有public修饰的方法,包含本类以及父类的
    Method[] methods = personCls.getMethods();
    for (Method method : methods) {
    System.out.println("本类以及父类的方法=" + method.getName());
    }
    //getDeclaredMethods:获取本类中所有方法
    Method[] declaredMethods = personCls.getDeclaredMethods();
    for (Method declaredMethod : declaredMethods) {
    System.out.println("本类中所有方法=" + declaredMethod.getName());
    }
    //getConstructors: 获取所有public修饰的构造器,包含本类
    Constructor<?>[] constructors = personCls.getConstructors();
    for (Constructor<?> constructor : constructors) {
    System.out.println("本类的构造器=" + constructor.getName());
    }
    //getDeclaredConstructors:获取本类中所有构造器
    Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
    for (Constructor<?> declaredConstructor : declaredConstructors) {
    System.out.println("本类中所有构造器=" + declaredConstructor.getName());//这里老师只是输出名
    }
    //getPackage:以Package形式返回 包信息
    System.out.println(personCls.getPackage());//com.hspedu.reflection
    //getSuperClass:以Class形式返回父类信息
    Class<?> superclass = personCls.getSuperclass();
    System.out.println("父类的class对象=" + superclass);//
    //getInterfaces:以Class[]形式返回接口信息
    Class<?>[] interfaces = personCls.getInterfaces();
    for (Class<?> anInterface : interfaces) {
    System.out.println("接口信息=" + anInterface);
    }
    //getAnnotations:以Annotation[] 形式返回注解信息
    Annotation[] annotations = personCls.getAnnotations();
    for (Annotation annotation : annotations) {
    System.out.println("注解信息=" + annotation);//注解
    }


    }
    }

    //用于举例的类、接口和方法
    class A {
    public String hobby;

    public void hi() {

    }

    public A() {
    }

    public A(String name) {
    }
    }

    interface IA {
    }

    interface IB {

    }

    @Deprecated
    class Person extends A implements IA, IB {
    //属性
    public String name;
    protected static int age; // 4 + 8 = 12
    String job;
    private double sal;

    //构造器
    public Person() {
    }

    public Person(String name) {
    }

    //私有的
    private Person(String name, int age) {

    }

    //方法
    public void m1(String name, int age, double sal) {

    }

    protected String m2() {
    return null;
    }

    void m3() {

    }

    private void m4() {

    }
    }
    + +

    31.反射暴破创建实例/操作属性/操作方法

    暴破创建实例

    +
    package com.hspedu.reflection;

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示通过反射机制创建实例
    */
    public class ReflecCreateInstance {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

    //1. 先获取到User类的Class对象
    Class<?> userClass = Class.forName("com.hspedu.reflection.User");
    //2. 通过public的无参构造器创建实例
    Object o = userClass.newInstance();
    System.out.println(o);
    //3. 通过public的有参构造器创建实例
    /*
    constructor 对象就是
    public User(String name) {//public的有参构造器
    this.name = name;
    }
    */
    //3.1 先得到对应构造器
    Constructor<?> constructor = userClass.getConstructor(String.class);
    //3.2 创建实例,并传入实参
    Object hsp = constructor.newInstance("hsp");
    System.out.println("hsp=" + hsp);
    //4. 通过非public的有参构造器创建实例
    //4.1 得到private的构造器对象
    Constructor<?> constructor1 = userClass.getDeclaredConstructor(int.class, String.class);
    //4.2 创建实例
    //暴破【暴力破解】 , 使用反射可以访问private构造器/方法/属性, 反射面前,都是纸老虎
    constructor1.setAccessible(true);
    Object user2 = constructor1.newInstance(100, "张三丰");
    System.out.println("user2=" + user2);
    }
    }

    class User { //User类
    private int age = 10;
    private String name = "韩顺平教育";

    public User() {//无参 public
    }

    public User(String name) {//public的有参构造器
    this.name = name;
    }

    private User(int age, String name) {//private 有参构造器
    this.age = age;
    this.name = name;
    }

    public String toString() {
    return "User [age=" + age + ", name=" + name + "]";
    }
    }
    + +

    暴破操作属性

    +
    package com.hspedu.reflection;

    import java.lang.reflect.Field;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示反射操作属性
    */
    public class ReflecAccessProperty {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {

    //1. 得到Student类对应的 Class对象
    Class<?> stuClass = Class.forName("com.hspedu.reflection.Student");
    //2. 创建对象
    Object o = stuClass.newInstance();//o 的运行类型就是Student
    System.out.println(o.getClass());//Student
    //3. 使用反射得到age 属性对象
    Field age = stuClass.getField("age");
    age.set(o, 88);//通过反射来操作属性
    System.out.println(o);//
    System.out.println(age.get(o));//返回age属性的值

    //4. 使用反射操作name 属性
    Field name = stuClass.getDeclaredField("name");
    //对name 进行暴破, 可以操作private 属性
    name.setAccessible(true);
    //name.set(o, "老韩");
    name.set(null, "老韩~");//因为name是static属性,因此 o 也可以写出null
    System.out.println(o);
    System.out.println(name.get(o)); //获取属性值
    System.out.println(name.get(null));//获取属性值, 要求name是static

    }
    }

    class Student {//类
    public int age;
    private static String name;

    public Student() {//构造器
    }

    public String toString() {
    return "Student [age=" + age + ", name=" + name + "]";
    }
    }
    + +

    暴破操作方法

    +
    package com.hspedu.reflection;

    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;

    /**
    * @author 韩顺平
    * @version 1.0
    * 演示通过反射调用方法
    */
    public class ReflecAccessMethod {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {

    //1. 得到Boss类对应的Class对象
    Class<?> bossCls = Class.forName("com.hspedu.reflection.Boss");
    //2. 创建对象
    Object o = bossCls.newInstance();
    //3. 调用public的hi方法
    //Method hi = bossCls.getMethod("hi", String.class);//OK
    //3.1 得到hi方法对象
    Method hi = bossCls.getDeclaredMethod("hi", String.class);//OK
    //3.2 调用
    hi.invoke(o, "韩顺平教育~");

    //4. 调用private static 方法
    //4.1 得到 say 方法对象
    Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
    //4.2 因为say方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样
    say.setAccessible(true);
    System.out.println(say.invoke(o, 100, "张三", '男'));
    //4.3 因为say方法是static的,还可以这样调用 ,可以传入null
    System.out.println(say.invoke(null, 200, "李四", '女'));

    //5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
    Object reVal = say.invoke(null, 300, "王五", '男');
    System.out.println("reVal 的运行类型=" + reVal.getClass());//String


    //在演示一个返回的案例
    Method m1 = bossCls.getDeclaredMethod("m1");
    Object reVal2 = m1.invoke(o);
    System.out.println("reVal2的运行类型=" + reVal2.getClass());//Monster


    }
    }

    class Monster {}
    class Boss {//类
    public int age;
    private static String name;

    public Boss() {//构造器
    }

    public Monster m1() {
    return new Monster();
    }

    private static String say(int n, String s, char c) {//静态方法
    return n + " " + s + " " + c;
    }

    public void hi(String s) {//普通public方法
    System.out.println("hi " + s);
    }
    }
    + +

    ]]>
    + + 后端 + + + Java + +
    + + 项目实战-黑马头条 + /posts/64695.html +

    本项目基于黑马程序员黑马头条项目,编写此文档的目的是锻炼文档编写能力,完整正确的项目文档请关注黑马程序员!

    +

    项目文档或有欠缺,不对项目文档的完整性负责!

    + + +

    一.项目介绍

    1.项目概述

    ​ 随着智能手机的普及,人们更加习惯于通过手机来看新闻。由于生活节奏的加快,很多人只能利用碎片时间来获取信息,因此,对于移动资讯客户端的需求也越来越高。黑马头条项目正是在这样背景下开发出来。黑马头条项目采用当下火热的微服务+大数据技术架构实现。本项目主要着手于获取最新最热新闻资讯,通过大数据分析用户喜好精确推送咨询新闻。

    +

    image-20230810153039562

    +

    2.业务说明

    功能架构图

    +

    image-20230810153700690

    +

    3.技术栈

      +
    • Spring-Cloud-Gateway : 微服务之前架设的网关服务,实现服务注册中的API请求路由,以及控制流速控制和熔断处理都是常用的架构手段,而这些功能Gateway天然支持
    • +
    • 运用Spring Boot快速开发框架,构建项目工程;并结合Spring Cloud全家桶技术,实现后端个人中心、自媒体、管理中心等微服务。
    • +
    • 运用Spring Cloud Alibaba Nacos作为项目中的注册中心和配置中心
    • +
    • 运用mybatis-plus作为持久层提升开发效率
    • +
    • 运用Kafka完成内部系统消息通知;与客户端系统消息通知;以及实时数据计算
    • +
    • 运用Redis缓存技术,实现热数据的计算,提升系统性能指标
    • +
    • 使用Mysql存储用户数据,以保证上层数据查询的高性能
    • +
    • 使用Mongo存储用户热数据,以保证用户热数据高扩展和高性能指标
    • +
    • 使用FastDFS作为静态资源存储器,在其上实现热静态资源缓存、淘汰等功能
    • +
    • 运用Hbase技术,存储系统中的冷数据,保证系统数据的可靠性
    • +
    • 运用ES搜索技术,对冷数据、文章数据建立索引,以保证冷数据、文章查询性能
    • +
    • 运用AI技术,来完成系统自动化功能,以提升效率及节省成本。比如实名认证自动化
    • +
    • PMD&P3C : 静态代码扫描工具,在项目中扫描项目代码,检查异常点、优化点、代码规范等,为开发团队提供规范统一,提升项目代码质量
    • +
    +

    技术栈

    +

    image-20230809170653756

    +

    解决方案

    +

    image-20230809170929986

    +

    二.环境搭建

    1.Linxu环境的搭建

    1.1 虚拟机的安装

    1.解压分享的虚拟机镜像文件

    +

    image-20230811103629846

    +

    2.使用VmWare打开.vmx文件
    image-20230811103800217

    +

    3.配置虚拟机的网络环境

    +

    image-20230811104150226

    +

    image-20230811104320075

    +

    4.开启虚拟机

    +

    image-20230811104523506

    +

    5.使用FinalShell连接此虚拟机

    +

    用户名: root 密码:root IP地址: 192.168.200.130

    +

    image-20230811104741394

    +

    1.2 Linux软件安装

    Linux中开发环境的搭建 | The Blog (gitee.io)

    +

    2.开发环境的配置

    2.1 项目依赖的环境

      +
    • JDK1.8

      +
    • +
    • Intellij Idea

      +
    • +
    • maven-3.6.1

      +
    • +
    • Git

      +
    • +
    +

    2.2 后端工程的搭建

    image-20230811160530674

    +

    解压heima-leadnews.zip文件并用IDEA工具打开

    +

    image-20230811152915886

    +

    编码格式的设置

    +

    image-20230811154426723

    +

    三.app端功能开发

    1.app登录

    登录相关的表结构

    + + + + + + + + + + + + + + + + + + + + + + + +
    表名称说明
    ap_userAPP用户信息表
    ap_user_fanAPP用户粉丝信息表
    ap_user_followAPP用户关注信息表
    ap_user_realnameAPP实名认证信息表
    +

    ap_user表对应的实体类

    +
    package com.heima.model.user.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * <p>
    * APP用户信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("ap_user")
    public class ApUser implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
    * 主键
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 密码、通信等加密盐
    */
    @TableField("salt")
    private String salt;

    /**
    * 用户名
    */
    @TableField("name")
    private String name;

    /**
    * 密码,md5加密
    */
    @TableField("password")
    private String password;

    /**
    * 手机号
    */
    @TableField("phone")
    private String phone;

    /**
    * 头像
    */
    @TableField("image")
    private String image;

    /**
    * 0 男
    1 女
    2 未知
    */
    @TableField("sex")
    private Boolean sex;

    /**
    * 0 未
    1 是
    */
    @TableField("is_certification")
    private Boolean certification;

    /**
    * 是否身份认证
    */
    @TableField("is_identity_authentication")
    private Boolean identityAuthentication;

    /**
    * 0正常
    1锁定
    */
    @TableField("status")
    private Boolean status;

    /**
    * 0 普通用户
    1 自媒体人
    2 大V
    */
    @TableField("flag")
    private Short flag;

    /**
    * 注册时间
    */
    @TableField("created_time")
    private Date createdTime;

    }
    + +

    1.1 用户登录逻辑

    注册加盐的过程

    +

    ​ 用户在登录的时候会生成一个随机的字符串(salt),这个随机的字符串会加到密码后面然后连同密码加密存储到数据库。

    +

    image-20230811165505210

    +

    登录加盐的过程

    +

    ​ 先根据账号查询是否存在该用户,如果存在的话,根据用户输入的密码和数据库中的salt进行md5加密,并和数据库中的密码比对,一致的话,比对通过,不一样的话不通过。

    +

    image-20230811165808912

    +

    1.2 用户模块搭建

    heima-leadnews-service父工程依赖文件

    +
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
    <artifactId>heima-leadnews</artifactId>
    <groupId>com.heima</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <packaging>pom</packaging>
    <modules>
    <module>heima-leadnews-user</module>
    </modules>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>heima-leadnews-service</artifactId>

    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
    <!-- 数据模型子模块 -->
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-leadnews-model</artifactId>
    </dependency>

    <!-- 公共子模块 -->
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-leadnews-common</artifactId>
    </dependency>

    <!-- 远程调用子模块 -->
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-leadnews-feign-api</artifactId>
    </dependency>

    <!-- Spring Boot Web starter -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot Test测试 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>

    <!-- Nacos注册中心 -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>

    <!-- Nacos配置中心 -->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>

    <!-- Feign远程调用客户端 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    </dependencies>

    </project>
    + +
      +
    1. 创建子模块并创建出对应的目录结构

      +

      在heima-leadnews-service父工程下创建工程heima-leadnews-user

      +

      image-20230812172036450

      +
    2. +
    3. 编写用户模块的配置文件

      +
      server:
      port: 51801
      spring:
      application:
      name: leadnews-user
      cloud:
      nacos:
      discovery:
      server-addr: 192.168.200.130:8848
      config:
      server-addr: 192.168.200.130:8848
      file-extension: yml
      +
    4. +
    5. 在配置中心中添加数据库等相关的配置

      +

      image-20230812173024036

      +
    6. +
    7. 在resources目录下添加日志的配置文件

      +

      logback.xml

      +
      <?xml version="1.0" encoding="UTF-8"?>

      <configuration>
      <!--定义日志文件的存储地址,使用绝对路径-->
      <property name="LOG_HOME" value="e:/logs"/>

      <!-- Console 输出设置 -->
      <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
      <encoder>
      <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      <charset>utf8</charset>
      </encoder>
      </appender>

      <!-- 按照每天生成日志文件 -->
      <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
      <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <!--日志文件输出的文件名-->
      <fileNamePattern>${LOG_HOME}/leadnews.%d{yyyy-MM-dd}.log</fileNamePattern>
      </rollingPolicy>
      <encoder>
      <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
      </encoder>
      </appender>

      <!-- 异步输出 -->
      <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
      <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
      <discardingThreshold>0</discardingThreshold>
      <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
      <queueSize>512</queueSize>
      <!-- 添加附加的appender,最多只能添加一个 -->
      <appender-ref ref="FILE"/>
      </appender>


      <logger name="org.apache.ibatis.cache.decorators.LoggingCache" level="DEBUG" additivity="false">
      <appender-ref ref="CONSOLE"/>
      </logger>
      <logger name="org.springframework.boot" level="debug"/>
      <root level="info">
      <!--<appender-ref ref="ASYNC"/>-->
      <appender-ref ref="FILE"/>
      <appender-ref ref="CONSOLE"/>
      </root>
      </configuration>
    8. +
    +

    遇到的问题:

    +
      +
    1. 问题一:引入@EnableDiscoveryClient注解的时候爆红

      +

      解决方案:在heima-leadnews-service父工程下加入如下的注解

      +
      <!-- Feign远程调用客户端 -->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign</artifactId>
      </dependency>
    2. +
    +

    1.3 登录功能实现

    1.3.1 接口定义

    + + + + + + + + + + + + + + + + + + +
    接口路径/api/v1/login/login_auth
    请求方式POST
    参数LoginDto
    响应结果ResponseResult
    +

    LoginDto

    +
    @Data
    public class LoginDto {

    /**
    * 手机号
    */
    @ApiModelProperty(value = "手机号",required = true)
    private String phone;

    /**
    * 密码
    */
    @ApiModelProperty(value = "密码",required = true)
    private String password;
    }
    + +

    统一返回结果类

    +
    package com.heima.model.common.dtos;

    import com.alibaba.fastjson.JSON;
    import com.heima.model.common.enums.AppHttpCodeEnum;

    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    /**
    * 通用的结果返回类
    * @param <T>
    */
    public class ResponseResult<T> implements Serializable {

    private String host;

    private Integer code;

    private String errorMessage;

    private T data;

    public ResponseResult() {
    this.code = 200;
    }

    public ResponseResult(Integer code, T data) {
    this.code = code;
    this.data = data;
    }

    public ResponseResult(Integer code, String msg, T data) {
    this.code = code;
    this.errorMessage = msg;
    this.data = data;
    }

    public ResponseResult(Integer code, String msg) {
    this.code = code;
    this.errorMessage = msg;
    }

    public static ResponseResult errorResult(int code, String msg) {
    ResponseResult result = new ResponseResult();
    return result.error(code, msg);
    }

    public static ResponseResult okResult(int code, String msg) {
    ResponseResult result = new ResponseResult();
    return result.ok(code, null, msg);
    }

    public static ResponseResult okResult(Object data) {
    ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getErrorMessage());
    if(data!=null) {
    result.setData(data);
    }
    return result;
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums){
    return setAppHttpCodeEnum(enums,enums.getErrorMessage());
    }

    public static ResponseResult errorResult(AppHttpCodeEnum enums, String errorMessage){
    return setAppHttpCodeEnum(enums,errorMessage);
    }

    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
    return okResult(enums.getCode(),enums.getErrorMessage());
    }

    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String errorMessage){
    return okResult(enums.getCode(),errorMessage);
    }

    public ResponseResult<?> error(Integer code, String msg) {
    this.code = code;
    this.errorMessage = msg;
    return this;
    }

    public ResponseResult<?> ok(Integer code, T data) {
    this.code = code;
    this.data = data;
    return this;
    }

    public ResponseResult<?> ok(Integer code, T data, String msg) {
    this.code = code;
    this.data = data;
    this.errorMessage = msg;
    return this;
    }

    public ResponseResult<?> ok(T data) {
    this.data = data;
    return this;
    }

    public Integer getCode() {
    return code;
    }

    public void setCode(Integer code) {
    this.code = code;
    }

    public String getErrorMessage() {
    return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
    this.errorMessage = errorMessage;
    }

    public T getData() {
    return data;
    }

    public void setData(T data) {
    this.data = data;
    }

    public String getHost() {
    return host;
    }

    public void setHost(String host) {
    this.host = host;
    }


    public static void main(String[] args) {
    //前置
    /*AppHttpCodeEnum success = AppHttpCodeEnum.SUCCESS;
    System.out.println(success.getCode());
    System.out.println(success.getErrorMessage());*/

    //查询一个对象
    /*Map map = new HashMap();
    map.put("name","zhangsan");
    map.put("age",18);
    ResponseResult result = ResponseResult.okResult(map);
    System.out.println(JSON.toJSONString(result));*/


    //新增,修改,删除 在项目中统一返回成功即可
    /* ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.SUCCESS);
    System.out.println(JSON.toJSONString(result));*/


    //根据不用的业务返回不同的提示信息 比如:当前操作需要登录、参数错误
    /*ResponseResult result = ResponseResult.errorResult(AppHttpCodeEnum.NEED_LOGIN);
    System.out.println(JSON.toJSONString(result));*/

    //查询分页信息
    PageResponseResult responseResult = new PageResponseResult(1,5,50);
    List list = new ArrayList();
    list.add("itcast");
    list.add("itheima");
    responseResult.setData(list);
    System.out.println(JSON.toJSONString(responseResult));

    }

    }
    + +

    1.3.2 登录思路分析

    image-20230812180918302

    +

    1.3.3 登录关键代码实现

    package com.heima.user.service.impl;

    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.toolkit.Wrappers;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.heima.model.common.dtos.ResponseResult;
    import com.heima.model.common.enums.AppHttpCodeEnum;
    import com.heima.model.user.dtos.LoginDto;
    import com.heima.model.user.pojos.ApUser;
    import com.heima.user.mapper.ApUserMapper;
    import com.heima.user.service.ApUserService;
    import com.heima.utils.common.AppJwtUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.util.DigestUtils;

    import java.util.HashMap;
    import java.util.Map;


    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/12
    * @Description
    */
    @Service
    @Transactional
    @Slf4j
    public class ApUserServiceImpl extends ServiceImpl<ApUserMapper, ApUser> implements ApUserService {
    @Override
    public ResponseResult login(LoginDto loginDto) {
    //1 正常的登录 用户名和密码
    if(StringUtils.isNotBlank(loginDto.getPhone()) && StringUtils.isNotBlank(loginDto.getPassword())) {
    //1.1 根据手机号查询用户的信息
    ApUser dbUser = getOne(new LambdaQueryWrapper<ApUser>().eq(ApUser::getPhone, loginDto.getPhone()));
    if(dbUser == null){
    return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST,"用户信息不存在");
    }
    String salt = dbUser.getSalt();
    String password = loginDto.getPassword();
    String pwd = DigestUtils.md5DigestAsHex((password + salt).getBytes());
    //1.2 比对密码
    if(!pwd.equals(dbUser.getPassword())){
    return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
    }
    //1.3 生成token
    String token = AppJwtUtil.getToken(dbUser.getId().longValue());
    Map<String,Object> map = new HashMap<>();
    map.put("token",token);
    dbUser.setSalt("");
    dbUser.setPassword("");
    map.put("user",dbUser);
    return ResponseResult.okResult(map);
    }else {
    //2.游客登录
    String token = AppJwtUtil.getToken(0L);
    Map<String,Object> map = new HashMap<>();
    map.put("token",token);
    return ResponseResult.okResult(map);
    }
    }
    }
    + +

    1.3.4 使用接口工具测试

    接口测试工具使用教程:https://jasonsgong.gitee.io/posts/35630.html

    +

    image-20230813165436808

    +

    2. app端网关搭建

    网关的概述

    +

    image-20230814142355139

    +

    项目中搭建的网关

    +

    image-20230814142700349

    +

    2.1 搭建过程

    1.在heima-leadnews-gateway导入以下依赖

    +
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    </dependency>
    </dependencies>
    + +

    2.创建网关的模块

    +

    image-20230814143113564

    +

    3.创建启动类和bootstrap.yml配置文件

    +
    @EnableDiscoveryClient
    @SpringBootApplication
    public class AppGatewayApplication {
    public static void main(String[] args) {
    SpringApplication.run(AppGatewayApplication.class,args);
    }
    }
    + +
    server:
    port: 51601
    spring:
    application:
    name: leadnews-app-gateway
    cloud:
    nacos:
    discovery:
    server-addr: 192.168.200.130:8848
    config:
    server-addr: 192.168.200.130:8848
    file-extension: yml
    + +

    4.在nacos中创建app端网关的配置

    +

    image-20230814143939252

    +
    spring:
    cloud:
    gateway:
    globalcors:
    add-to-simple-url-handler-mapping: true
    corsConfigurations:
    '[/**]':
    allowedHeaders: "*"
    allowedOrigins: "*"
    allowedMethods:
    - GET
    - POST
    - DELETE
    - PUT
    - OPTION
    routes:
    # 平台管理
    - id: user
    uri: lb://leadnews-user
    predicates:
    - Path=/user/**
    filters:
    - StripPrefix= 1
    + +

    5.使用Postman测试网关

    +

    http://localhost:51601/user/api/v1/login/login_auth

    +

    image-20230814144314358

    +

    2.2 全局过滤器实现jwt校验

    网关的过滤流程

    +

    image-20230814144806282

    +

    思路分析:

    +
      +
    1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录
    2. +
    3. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户
    4. +
    5. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN
    6. +
    7. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误
    8. +
    +

    JWT认证的过滤器

    +
    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/14
    * @Description 权限认证的过滤器
    */
    @Component
    @Slf4j
    public class AuthorizeFilter implements Ordered, GlobalFilter {


    /**
    * 过滤的设置
    */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //1.获取request和response对象
    ServerHttpRequest request = exchange.getRequest();
    ServerHttpResponse response = exchange.getResponse();

    //2.判断是否登录
    //通过请求的路径的url判断
    if (request.getURI().getPath().contains("/login")) {
    //放行
    return chain.filter(exchange);
    }
    //3.获取token
    String token = request.getHeaders().getFirst("token");

    //4.判断token是否存在
    if (StringUtils.isBlank(token)) {
    //设置401的状态码
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }

    //5.判断token是否有效
    try {
    //获取token中的数据
    Claims claims = AppJwtUtil.getClaimsBody(token);
    //判断token是否过期 -1:有效,0:有效,1:过期,2:过期
    int res = AppJwtUtil.verifyToken(claims);
    if (res == 1 || res == 2) {
    //设置401的状态码
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }
    } catch (Exception e) {
    e.printStackTrace();
    //设置401的状态码
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }
    //6.放行
    return chain.filter(exchange);
    }

    /**
    * 优先级设置 值越小 优先值越高
    */
    @Override
    public int getOrder() {
    return 0;
    }
    }
    + +

    3.app前端项目集成

    image-20230814151319382

    +

    通过nginx来进行配置,功能如下

    +
      +
    • 通过nginx的反向代理功能访问后台的网关资源
    • +
    • 通过nginx的静态服务器功能访问前端静态页面
    • +
    +

    3.1 Nginx集成前端项目步骤

    ①:解压资料文件夹中的压缩包nginx-1.18.0.zip

    +

    cmd切换到nginx所有的目录输入nginx启动nginx

    +

    image-20230814152058708

    +

    image-20230814152144765

    +

    ②:解压资料文件夹中的前端项目app-web.zip

    +

    解压到一个没有中文的文件夹中,后面nginx配置中会指向这个目录

    +

    ③:配置nginx.conf文件

    +

    在nginx安装的conf目录下新建一个文件夹leadnews.conf,在当前文件夹中新建heima-leadnews-app.conf文件

    +

    heima-leadnews-app.conf配置如下:

    +
    upstream  heima-app-gateway{
    #APP端网关所在的端口
    server localhost:51601;
    }

    server {
    listen 8801;
    location / {
    root C:/Gong/data/app-web/;
    index index.html;
    }

    location ~/app/(.*) {
    proxy_pass http://heima-app-gateway/$1;
    proxy_set_header HOST $host; # 不改变源请求头的值
    proxy_pass_request_body on; #开启获取请求体
    proxy_pass_request_headers on; #开启获取请求头
    proxy_set_header X-Real-IP $remote_addr; # 记录真实发出请求的客户端IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #记录代理信息
    }
    }
    + +

    nginx.conf 把里面注释的内容和静态资源配置相关删除,引入heima-leadnews-app.conf文件加载

    +
    #user  nobody;
    worker_processes 1;

    events {
    worker_connections 1024;
    }
    http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;
    # 引入自定义配置文件
    include leadnews.conf/*.conf;
    }
    + +

    ④ :启动nginx

    +

    ​ 在nginx安装包中使用命令提示符打开,输入命令nginx启动项目

    +

    ​ 可查看进程,检查nginx是否启动

    +

    ​ 重新加载配置文件:nginx -s reload

    +

    ⑤:打开前端项目进行测试 – > http://localhost:8801

    +

    ​ 用谷歌浏览器打开,调试移动端模式进行访问

    +

    image-20230814153335349

    +

    4.app端文章列表功能

    开发前app的首页面

    +

    image-20230814154205134

    +

    文章的布局展示

    +

    image-20230814154405531

    +

    4.1 数据库表的创建

    文章的相关的数据库

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    表名称说明
    ap_article文章信息表,存储已发布的文章
    ap_article_configAPP已发布文章配置表
    ap_article_contentAPP已发布文章内容表
    ap_authorAPP文章作者信息表
    ap_collectionAPP收藏信息表
    +

    导入资料中的sql文件创建相关的数据库表

    +

    image-20230814160312382

    +

    关键的数据库表

    +

    文章基本信息表

    +

    image-20230814160547893

    +

    APP已发布文章配置表

    +

    image-20230814160652330

    +

    APP已发布文章内容表

    +

    image-20230814160728715

    +

    APP文章作者信息表

    +

    image-20230814160805398

    +

    APP收藏信息表

    +

    image-20230814160823771

    +

    垂直分表

    +

    将文章相关的表分成文章配置表和文章内容表和文章信息表

    +

    image-20230814161909312

    +

    垂直分表:将一个表的字段分散到多个表中,每个表存储其中一部分字段

    +

    优势:

    +
      +
    1. 减少IO争抢,减少锁表的几率,查看文章概述与文章详情互不影响

      +
    2. +
    3. 充分发挥高频数据的操作效率,对文章概述数据操作的高效率不会被操作文章详情数据的低效率所拖累

      +
    4. +
    +

    拆分规则:

    +

    1.把不常用的字段单独放在一张表

    +

    2.把text,blob等大字段拆分出来单独放在一张表

    +

    3.经常组合查询的字段单独放在一张表中

    +

    4.2 文章模块搭建

    导入资料中的模块

    +

    image-20230815184540157

    +

    在nacos中添加配置

    +
    spring:
    datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_article?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: 123456
    # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
    mybatis-plus:
    mapper-locations: classpath*:mapper/*.xml
    # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
    type-aliases-package: com.heima.model.article.pojos
    + +

    image-20230815162119814

    +

    踩坑: 在导入项目的时候提示Caused by: java.io.FileNotFoundException: class path resource [com/heima/apis/article/IArticleClient.class] cannot be opened because it does not exist ,先删除这个项目,手动创建这个项目,然后复制资料里面文件到这个项目即可,直接复制整个项目可能会报这个错!

    +

    4.3 首页文章的列表显示

    首页上拉和下拉的实现思路

    +

    image-20230814163019582

    +

    Sql语句实现

    +
    #按照发布时间倒序查询十条文章
    select * from ap_article aa order by aa.publish_time desc limit 10
    #频道筛选
    select * from ap_article aa where aa.channel_id = 1 order by aa.publish_time desc limit 10
    #加载首页
    select * from ap_article aa where aa.channel_id = 1 and aa.publish_time < '2063-09-08 10:20:12' order by aa.publish_time desc limit 10
    #加载更多
    select * from ap_article aa where aa.channel_id = 1 and aa.publish_time < '2020-09-07 22:30:09' order by aa.publish_time desc limit 10
    #加载最新
    select * from ap_article aa where aa.channel_id = 1 and aa.publish_time > '2020-09-07 22:30:09' order by aa.publish_time desc limit 10
    + +

    image-20230815160034436

    +

    4.2.1 接口定义

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    加载首页加载更多加载最新
    接口路径/api/v1/article/load/api/v1/article/loadmore/api/v1/article/loadnew
    请求方式POSTPOSTPOST
    参数ArticleHomeDtoArticleHomeDtoArticleHomeDto
    响应结果ResponseResultResponseResultResponseResult
    +

    ArticleHomeDto

    +
    package com.heima.model.article.dtos;
    import lombok.Data;
    import java.util.Date;
    @Data
    public class ArticleHomeDto {

    // 最大时间
    Date maxBehotTime;
    // 最小时间
    Date minBehotTime;
    // 分页size
    Integer size;
    // 频道ID
    String tag;
    }
    + +

    4.2.2 实现思路

    ①:导入heima-leadnews-article微服务,资料在当天的文件夹中

    +

    需要在nacos中添加对应的配置

    +

    ②:定义接口

    +

    接口路径、请求方式、入参、出参

    +

    ③:编写mapper文件

    +

    文章表与文章配置表多表查询

    +

    ④:编写业务层代码

    +

    ⑤:编写控制器代码

    +

    ⑥:swagger测试或前后端联调测试

    +

    4.2.3 功能的关键代码实现

    mapper层的代码

    +

    image-20230816142705510

    +
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.heima.article.mapper.ApArticleMapper">
    <resultMap id="resultMap" type="com.heima.model.article.pojos.ApArticle">
    <id column="id" property="id"/>
    <result column="title" property="title"/>
    <result column="author_id" property="authorId"/>
    <result column="author_name" property="authorName"/>
    <result column="channel_id" property="channelId"/>
    <result column="channel_name" property="channelName"/>
    <result column="layout" property="layout"/>
    <result column="flag" property="flag"/>
    <result column="images" property="images"/>
    <result column="labels" property="labels"/>
    <result column="likes" property="likes"/>
    <result column="collection" property="collection"/>
    <result column="comment" property="comment"/>
    <result column="views" property="views"/>
    <result column="province_id" property="provinceId"/>
    <result column="city_id" property="cityId"/>
    <result column="county_id" property="countyId"/>
    <result column="created_time" property="createdTime"/>
    <result column="publish_time" property="publishTime"/>
    <result column="sync_status" property="syncStatus"/>
    <result column="static_url" property="staticUrl"/>
    </resultMap>
    <select id="loadArticleList" resultMap="resultMap">
    SELECT
    aa.*
    FROM
    `ap_article` aa
    LEFT JOIN ap_article_config aac ON aa.id = aac.article_id
    <where>
    and aac.is_delete != 1
    and aac.is_down != 1
    <!-- loadmore -->
    <if test="type != null and type == 1">
    and aa.publish_time <![CDATA[<]]> #{dto.minBehotTime}
    </if>
    <if test="type != null and type == 2">
    and aa.publish_time <![CDATA[>]]> #{dto.maxBehotTime}
    </if>
    <if test="dto.tag != '__all__'">
    and aa.channel_id = #{dto.tag}
    </if>
    </where>
    order by aa.publish_time desc
    limit #{dto.size}
    </select>
    </mapper>
    + +

    service层的代码

    +
    package com.heima.article.service.Impl;

    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.heima.article.mapper.ApArticleMapper;
    import com.heima.article.service.ApArticleService;
    import com.heima.common.constants.ArticleConstants;
    import com.heima.model.article.dtos.ArticleHomeDto;
    import com.heima.model.article.pojos.ApArticle;
    import com.heima.model.common.dtos.ResponseResult;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.util.Date;
    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/15
    * @Description
    */
    @Service
    @Transactional
    @Slf4j
    public class ApArticleServiceImpl extends ServiceImpl<ApArticleMapper, ApArticle> implements ApArticleService {
    @Autowired
    private ApArticleMapper apArticleMapper;

    private static final Integer MAX_PAGE_SIZE = 50;

    @Override
    public ResponseResult load(ArticleHomeDto dto, Short type) {
    //1.参数校验
    //分页条数的校验
    Integer size = dto.getSize();
    if (size == null || size == 0) {
    size = 0;
    }
    //最大分页条数的限制
    size = Math.min(size, 50);

    //type参数的校验
    if (!type.equals(ArticleConstants.LOADTYPE_LOAD_MORE) && !type.equals(ArticleConstants.LOADTYPE_LOAD_NEW)) {
    type = 1;
    }

    //频道参数校验
    if (StringUtils.isBlank(dto.getTag())) {
    dto.setTag(ArticleConstants.DEFAULT_TAG);
    }

    //时间检验
    if (dto.getMinBehotTime() == null) dto.setMinBehotTime(new Date());
    if (dto.getMaxBehotTime() == null) dto.setMaxBehotTime(new Date());

    //2.查询
    List<ApArticle> apArticles = apArticleMapper.loadArticleList(dto, type);
    //3.数据返回
    return ResponseResult.okResult(apArticles);
    }
    }
    + +

    controller层的代码

    +
    package com.heima.article.controller.v1;

    import com.heima.article.service.ApArticleService;
    import com.heima.common.constants.ArticleConstants;
    import com.heima.model.article.dtos.ArticleHomeDto;
    import com.heima.model.common.dtos.ResponseResult;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/15
    * @Description
    */
    @RestController
    @RequestMapping("/api/v1/article")
    public class ArticleHomeController {

    @Autowired
    private ApArticleService apArticleService;

    /**
    * 加载首页
    */
    @PostMapping("/load")
    public ResponseResult<Object> load(@RequestBody ArticleHomeDto articleHomeDto){
    return apArticleService.load(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_MORE);
    }

    /**
    * 加载更多
    */
    @PostMapping("/loadmore")
    public ResponseResult<Object> loadmore(@RequestBody ArticleHomeDto articleHomeDto){
    return apArticleService.load(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_MORE);
    }


    /**
    * 加载更新
    */
    @PostMapping("/loadnew")
    public ResponseResult<Object> loadnew(@RequestBody ArticleHomeDto articleHomeDto){
    return apArticleService.load(articleHomeDto, ArticleConstants.LOADTYPE_LOAD_NEW);
    }
    }
    + +

    网关中增加文章模块的路由

    +
    spring:
    cloud:
    gateway:
    globalcors:
    add-to-simple-url-handler-mapping: true
    corsConfigurations:
    '[/**]':
    allowedHeaders: "*"
    allowedOrigins: "*"
    allowedMethods:
    - GET
    - POST
    - DELETE
    - PUT
    - OPTION
    routes:
    # 平台管理
    - id: user
    uri: lb://leadnews-user
    predicates:
    - Path=/user/**
    filters:
    - StripPrefix= 1
    # 文章管理
    - id: article
    uri: lb://leadnews-article
    predicates:
    - Path=/article/**
    filters:
    - StripPrefix= 1
    + +

    5. app端文章详情功能

    5.1 需求分析

    image-20230816143201242

    +

    5.2 实现方案-静态模板展示

    image-20230816143544351

    +

    静态模板展示关键技术-Freemarker

    +

    Freemarker教程: https://jasonsgong.gitee.io/posts/29367.html

    +

    5.3 对象存储服务MinIO

    MinIO 教程: https://jasonsgong.gitee.io/posts/36397.html

    +

    5.4 实现思路以及代码实现

    image-20230818210507999

    +

    image-20230818210638036

    +

    代码实现

    +

    1.在文章模块的pom.xml文件中加入以下的依赖

    +
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-file-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    </dependencies>
    + +

    2.在nacos中有关文章模块的配置中添加以下的内容

    +
    minio:
    accessKey: minio
    secretKey: minio123
    bucket: leadnews
    endpoint: http://192.168.200.130:9000
    readPath: http://192.168.200.130:9000
    + +

    image-20230818211310728

    +

    3.将资料中的article.ftl文件拷贝到文章模块的templates目录下,article.ftl文件内容如下

    +
    <!DOCTYPE html>
    <html>

    <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport"
    content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
    <title>黑马头条</title>
    <!-- 引入样式文件 -->
    <link rel="stylesheet" href="https://fastly.jsdelivr.net/npm/vant@2.12.20/lib/index.css">
    <!-- 页面样式 -->
    <link rel="stylesheet" href="../../../plugins/css/index.css">
    </head>

    <body>
    <div id="app">
    <div class="article">
    <van-row>
    <van-col span="24" class="article-title" v-html="title"></van-col>
    </van-row>

    <van-row type="flex" align="center" class="article-header">
    <van-col span="3">
    <van-image round class="article-avatar" src="https://p3.pstatp.com/thumb/1480/7186611868"></van-image>
    </van-col>
    <van-col span="16">
    <div v-html="authorName"></div>
    <div>{{ publishTime | timestampToDateTime }}</div>
    </van-col>
    <van-col span="5">
    <van-button round :icon="relation.isfollow ? '' : 'plus'" type="info" class="article-focus"
    :text="relation.isfollow ? '取消关注' : '关注'" :loading="followLoading" @click="handleClickArticleFollow">
    </van-button>
    </van-col>
    </van-row>

    <van-row class="article-content">
    <#if content??>
    <#list content as item>
    <#if item.type='text'>
    <van-col span="24" class="article-text">${item.value}</van-col>
    <#else>
    <van-col span="24" class="article-image">
    <van-image width="100%" src="${item.value}"></van-image>
    </van-col>
    </#if>
    </#list>
    </#if>
    </van-row>

    <van-row type="flex" justify="center" class="article-action">
    <van-col>
    <van-button round :icon="relation.islike ? 'good-job' : 'good-job-o'" class="article-like"
    :loading="likeLoading" :text="relation.islike ? '取消赞' : '点赞'" @click="handleClickArticleLike"></van-button>
    <van-button round :icon="relation.isunlike ? 'delete' : 'delete-o'" class="article-unlike"
    :loading="unlikeLoading" @click="handleClickArticleUnlike">不喜欢</van-button>
    </van-col>
    </van-row>

    <!-- 文章评论列表 -->
    <van-list v-model="commentsLoading" :finished="commentsFinished" finished-text="没有更多了"
    @load="onLoadArticleComments">
    <van-row id="#comment-view" type="flex" class="article-comment" v-for="(item, index) in comments" :key="index">
    <van-col span="3">
    <van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
    </van-col>
    <van-col span="21">
    <van-row type="flex" align="center" justify="space-between">
    <van-col class="comment-author" v-html="item.authorName"></van-col>
    <van-col>
    <van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
    @click="handleClickCommentLike(item)">{{ item.likes || '' }}
    </van-button>
    </van-col>
    </van-row>

    <van-row>
    <van-col class="comment-content" v-html="item.content"></van-col>
    </van-row>
    <van-row type="flex" align="center">
    <van-col span="10" class="comment-time">
    {{ item.createdTime | timestampToDateTime }}
    </van-col>
    <van-col span="3">
    <van-button round size="normal" v-html="item.reply" @click="showCommentRepliesPopup(item.id)">回复 {{
    item.reply || '' }}
    </van-button>
    </van-col>
    </van-row>
    </van-col>
    </van-row>
    </van-list>
    </div>
    <!-- 文章底部栏 -->
    <van-row type="flex" justify="space-around" align="center" class="article-bottom-bar">
    <van-col span="13">
    <van-field v-model="commentValue" placeholder="写评论">
    <template #button>
    <van-button icon="back-top" @click="handleSaveComment"></van-button>
    </template>
    </van-field>
    </van-col>
    <van-col span="3">
    <van-button icon="comment-o" @click="handleScrollIntoCommentView"></van-button>
    </van-col>
    <van-col span="3">
    <van-button :icon="relation.iscollection ? 'star' : 'star-o'" :loading="collectionLoading"
    @click="handleClickArticleCollection"></van-button>
    </van-col>
    <van-col span="3">
    <van-button icon="share-o"></van-button>
    </van-col>
    </van-row>

    <!-- 评论Popup 弹出层 -->
    <van-popup v-model="showPopup" closeable position="bottom"
    :style="{ width: '750px', height: '60%', left: '50%', 'margin-left': '-375px' }">
    <!-- 评论回复列表 -->
    <van-list v-model="commentRepliesLoading" :finished="commentRepliesFinished" finished-text="没有更多了"
    @load="onLoadCommentReplies">
    <van-row id="#comment-reply-view" type="flex" class="article-comment-reply"
    v-for="(item, index) in commentReplies" :key="index">
    <van-col span="3">
    <van-image round src="https://p3.pstatp.com/thumb/1480/7186611868" class="article-avatar"></van-image>
    </van-col>
    <van-col span="21">
    <van-row type="flex" align="center" justify="space-between">
    <van-col class="comment-author" v-html="item.authorName"></van-col>
    <van-col>
    <van-button round :icon="item.operation === 0 ? 'good-job' : 'good-job-o'" size="normal"
    @click="handleClickCommentReplyLike(item)">{{ item.likes || '' }}
    </van-button>
    </van-col>
    </van-row>

    <van-row>
    <van-col class="comment-content" v-html="item.content"></van-col>
    </van-row>
    <van-row type="flex" align="center">
    <!-- TODO: js计算时间差 -->
    <van-col span="10" class="comment-time">
    {{ item.createdTime | timestampToDateTime }}
    </van-col>
    </van-row>
    </van-col>
    </van-row>
    </van-list>
    <!-- 评论回复底部栏 -->
    <van-row type="flex" justify="space-around" align="center" class="comment-reply-bottom-bar">
    <van-col span="13">
    <van-field v-model="commentReplyValue" placeholder="写评论">
    <template #button>
    <van-button icon="back-top" @click="handleSaveCommentReply"></van-button>
    </template>
    </van-field>
    </van-col>
    <van-col span="3">
    <van-button icon="comment-o"></van-button>
    </van-col>
    <van-col span="3">
    <van-button icon="star-o"></van-button>
    </van-col>
    <van-col span="3">
    <van-button icon="share-o"></van-button>
    </van-col>
    </van-row>
    </van-popup>
    </div>

    <!-- 引入 Vue 和 Vant 的 JS 文件 -->
    <script src=" https://fastly.jsdelivr.net/npm/vue/dist/vue.min.js">
    </script>
    <script src="https://fastly.jsdelivr.net/npm/vant@2.12.20/lib/vant.min.js"></script>
    <!-- 引入 Axios 的 JS 文件 -->
    <#--<script src="https://unpkg.com/axios/dist/axios.min.js"></script>-->
    <script src="../../../plugins/js/axios.min.js"></script>
    <!-- 页面逻辑 -->
    <script src="../../../plugins/js/index.js"></script>
    </body>

    </html>
    + +

    4.手动上传资料中index.js和index.css两个文件到MinIO中

    +

    上传index.js

    +
    @Test
    public void testMinIO(){
    try {
    //读取一个文件
    FileInputStream inputStream = new FileInputStream("C:\\Gong\\java\\黑马头条\\day02-app端文章查看,静态化freemarker,分布式文件系统minIO\\资料\\模板文件\\plugins\\js\\index.js");
    //1.获取MinIO的连接信息,创建一个minio的客户端
    MinioClient minioClient = MinioClient.builder()
    .credentials("minio", "minio123")//minio的账号密码
    .endpoint("http://192.168.200.130:9000")//minio的地址
    .build();
    //2.上传
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object("plugins/js/index.js")//文件的名称
    .contentType("text/js")//文件的类型
    .bucket("leadnews")//桶的名称,与之前的minio管理界面创建的bucket名称一致即可
    .stream(inputStream,inputStream.available(),-1)
    .build();
    minioClient.putObject(putObjectArgs);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    + +

    上传index.css

    +
    @Test
    public void testMinIO(){
    try {
    //读取一个文件
    FileInputStream inputStream = new FileInputStream("C:\\Gong\\java\\黑马头条\\day02-app端文章查看,静态化freemarker,分布式文件系统minIO\\资料\\模板文件\\plugins\\css\\index.css");
    //1.获取MinIO的连接信息,创建一个minio的客户端
    MinioClient minioClient = MinioClient.builder()
    .credentials("minio", "minio123")//minio的账号密码
    .endpoint("http://192.168.200.130:9000")//minio的地址
    .build();
    //2.上传
    PutObjectArgs putObjectArgs = PutObjectArgs.builder()
    .object("plugins/css/index.css")//文件的名称
    .contentType("text/css")//文件的类型
    .bucket("leadnews")//桶的名称,与之前的minio管理界面创建的bucket名称一致即可
    .stream(inputStream,inputStream.available(),-1)
    .build();
    minioClient.putObject(putObjectArgs);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    + +

    5.测试根据文章的内容生成html文件上传到minio中

    +
    package com.heima.article.test;

    import com.alibaba.fastjson.JSONArray;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.heima.article.ArticleApplication;
    import com.heima.article.mapper.ApArticleContentMapper;
    import com.heima.article.service.ApArticleService;
    import com.heima.file.service.FileStorageService;
    import com.heima.model.article.pojos.ApArticle;
    import com.heima.model.article.pojos.ApArticleContent;
    import freemarker.template.Configuration;
    import freemarker.template.Template;
    import freemarker.template.TemplateException;
    import org.apache.commons.lang.StringUtils;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;

    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.StringWriter;
    import java.util.HashMap;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/19
    * @Description
    */
    @SpringBootTest(classes = ArticleApplication.class)
    @RunWith(SpringRunner.class)
    public class ArticleFreeMarkerTest {

    @Autowired
    private ApArticleContentMapper apArticleContentMapper;
    @Autowired
    private Configuration configuration;
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private ApArticleService apArticleService;

    @Test
    public void createStaticUrlTest() throws IOException, TemplateException {
    //已知文章的id
    //1.获取文章的内容
    ApArticleContent apArticleContent = apArticleContentMapper.selectOne(new LambdaQueryWrapper<ApArticleContent>().eq(ApArticleContent::getArticleId, "1383827995813531650L"));
    if (apArticleContent != null && StringUtils.isNotBlank(apArticleContent.getContent())) {
    //2.文章内容通过freemarker生成html文件 详细教程见:https://jasonsgong.gitee.io/posts/29367.html
    Template template = configuration.getTemplate("article.ftl");
    //构建数据模型
    HashMap<String, Object> map = new HashMap<>();
    map.put("content", JSONArray.parseArray(apArticleContent.getContent()));
    //输出流
    StringWriter out = new StringWriter();
    //合成html文件
    template.process(map, out);
    //3.把html文件上传到minio中
    //构建一个输入流
    InputStream in = new ByteArrayInputStream(out.toString().getBytes());
    //上传到minio并返回访问的路径
    String path = fileStorageService.uploadHtmlFile("", apArticleContent.getArticleId() + ".html", in);
    System.out.println("文件在minio中的路径:"+path);
    //4.修改ap_article表,保存static_url字段
    ApArticle apArticle = new ApArticle();
    apArticle.setId(apArticleContent.getArticleId());
    apArticle.setStaticUrl(path);
    boolean isSuccess = apArticleService.updateById(apArticle);
    System.out.println(isSuccess ? "文件上传成功" : "文件上传失败");
    }

    }
    }
    + +

    6.实现效果

    +

    image-20230819153507360

    +

    四.自媒体端功能开发

    1.后端环境搭建

    需要搭建的模块

    +

    image-20230819155333131

    +

    搭建步骤

    +

    image-20230819155229550

    +

    1.创建自媒体模块的数据库

    +

    image-20230819155853252

    +

    2.导入相应的工程文件

    +

    image-20230819161650118

    +

    3.配置自媒体模块和网关模块在nacos中的配置

    +

    自媒体模块的配置

    +
    spring:
    datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/leadnews_wemedia?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
    username: root
    password: root
    # 设置Mapper接口所对应的XML文件位置,如果你在Mapper接口中有自定义方法,需要进行该配置
    mybatis-plus:
    mapper-locations: classpath*:mapper/*.xml
    # 设置别名包扫描路径,通过该属性可以给包中的类注册别名
    type-aliases-package: com.heima.model.media.pojos
    + +

    image-20230819161148830

    +

    网关模块的配置

    +
    spring:
    cloud:
    gateway:
    globalcors:
    cors-configurations:
    '[/**]': # 匹配所有请求
    allowedOrigins: "*" #跨域处理 允许所有的域
    allowedMethods: # 支持的方法
    - GET
    - POST
    - PUT
    - DELETE
    routes:
    # 平台管理
    - id: wemedia
    uri: lb://leadnews-wemedia
    predicates:
    - Path=/wemedia/**
    filters:
    - StripPrefix= 1
    + +

    image-20230819161529210

    +

    2.前端环境搭建

    搭建思路

    +

    image-20230819162116240

    +

    搭建步骤

    +

    image-20230819162230133

    +

    heima-leadnews-wemedia配置文件

    +
    upstream  heima-wemedia-gateway{
    #APP端网关所在的端口
    server localhost:51602;
    }

    server {
    listen 8802;
    location / {
    root C:/Gong/data/wemedia-web/;
    index index.html;
    }

    location ~/wemedia/MEDIA/(.*) {
    proxy_pass http://heima-wemedia-gateway/$1;
    proxy_set_header HOST $host; # 不改变源请求头的值
    proxy_pass_request_body on; #开启获取请求体
    proxy_pass_request_headers on; #开启获取请求头
    proxy_set_header X-Real-IP $remote_addr; # 记录真实发出请求的客户端IP
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #记录代理信息
    }
    }
    + +

    重启nginx访问

    +

    image-20230902145814017

    +

    3.自媒体素材管理功能

    3.1 素材管理-图片上传

    图片素材相关的实体类

    +
    package com.heima.model.wemedia.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * <p>
    * 自媒体图文素材信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("wm_material")
    public class WmMaterial implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
    * 主键
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 自媒体用户ID
    */
    @TableField("user_id")
    private Integer userId;

    /**
    * 图片地址
    */
    @TableField("url")
    private String url;

    /**
    * 素材类型
    0 图片
    1 视频
    */
    @TableField("type")
    private Short type;

    /**
    * 是否收藏
    */
    @TableField("is_collection")
    private Short isCollection;

    /**
    * 创建时间
    */
    @TableField("created_time")
    private Date createdTime;

    }
    + +

    3.1.1 解决图片素材实体类中获取图片userId的问题

    实现思路

    +

    image-20230902154806019

    +

    1.token解析为用户存入header

    +
    package com.heima.wemedia.gateway.filter;


    import com.heima.wemedia.gateway.util.AppJwtUtil;
    import io.jsonwebtoken.Claims;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang.StringUtils;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.http.server.reactive.ServerHttpResponse;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;

    @Component
    @Slf4j
    public class AuthorizeFilter implements Ordered, GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    //1.获取request和response对象
    ServerHttpRequest request = exchange.getRequest();
    ServerHttpResponse response = exchange.getResponse();

    //2.判断是否是登录
    if(request.getURI().getPath().contains("/login")){
    //放行
    return chain.filter(exchange);
    }

    //3.获取token
    String token = request.getHeaders().getFirst("token");

    //4.判断token是否存在
    if(StringUtils.isBlank(token)){
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }

    //5.判断token是否有效
    try {
    Claims claimsBody = AppJwtUtil.getClaimsBody(token);
    //是否是过期
    int result = AppJwtUtil.verifyToken(claimsBody);
    if(result == 1 || result == 2){
    response.setStatusCode(HttpStatus.UNAUTHORIZED);
    return response.setComplete();
    }
    //获取用户信息
    Object userId = claimsBody.get("id");
    //存入header中
    ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
    httpHeaders.add("userId", userId + "");
    }).build();
    //重置请求
    exchange.mutate().request(serverHttpRequest);

    } catch (Exception e) {
    e.printStackTrace();
    }

    //6.放行
    return chain.filter(exchange);
    }

    /**
    * 优先级设置 值越小 优先级越高
    * @return
    */
    @Override
    public int getOrder() {
    return 0;
    }
    }
    + +

    2.创建拦截器

    +
    package com.heima.wemedia.interceptor;

    import com.heima.model.wemedia.pojos.WmUser;
    import com.heima.utils.thread.WmThreadLocalUtil;
    import io.swagger.models.auth.In;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.Enumeration;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/9/2
    * @Description
    */
    public class WmTokenInterceptor implements HandlerInterceptor {

    /**
    * 获取header中的信息 存入当前的线程中
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //获取header中的userId信息
    String userId = request.getHeader("userId");
    //判断userId是否为空
    if(userId != null){
    //将userId存入当前的线程中,我们可以在任何位置获取
    WmUser wmUser = new WmUser();
    wmUser.setId(Integer.valueOf(userId));
    WmThreadLocalUtil.setUser(wmUser);
    }
    return true;
    }


    /**
    * 清理线程中的数据
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    WmThreadLocalUtil.clear();
    }
    }
    + +

    ThreadLocal工具类,实现在线程中存储、获取、清理用户信息

    +
    package com.heima.utils.thread;

    import com.heima.model.wemedia.pojos.WmUser;

    public class WmThreadLocalUtil {

    private final static ThreadLocal<WmUser> WM_USER_THREAD_LOCAL = new ThreadLocal<>();

    /**
    * 添加用户
    * @param wmUser
    */
    public static void setUser(WmUser wmUser){
    WM_USER_THREAD_LOCAL.set(wmUser);
    }

    /**
    * 获取用户
    */
    public static WmUser getUser(){
    return WM_USER_THREAD_LOCAL.get();
    }

    /**
    * 清理用户
    */
    public static void clear(){
    WM_USER_THREAD_LOCAL.remove();
    }
    }
    + +

    3.让拦截器生效

    +
    package com.heima.wemedia.config;

    import com.heima.wemedia.interceptor.WmTokenInterceptor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/9/2
    * @Description
    */
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new WmTokenInterceptor()).addPathPatterns("/**");
    }
    }
    + +

    3.1.2 图片上传接口的定义

    + + + + + + + + + + + + + + + + + + + + + + +
    说明
    接口路径/api/v1/material/upload_picture
    请求方式POST
    参数MultipartFile
    响应结果ResponseResult
    +

    3.1.3 代码实现

    1.在pom.xml中引入自定义minio的starter依赖

    +
    <dependency>
    <groupId>com.heima</groupId>
    <artifactId>heima-file-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    + +

    2.在项目中添加minio的配置(在nacos中配置)

    +
    minio:
    accessKey: minio
    secretKey: minio123
    bucket: leadnews
    endpoint: http://192.168.200.130:9000
    readPath: http://192.168.200.130:9000
    + +

    3.上传图片的关键代码

    +
    package com.heima.wemedia.service.impl;

    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.heima.file.service.FileStorageService;
    import com.heima.model.common.dtos.ResponseResult;
    import com.heima.model.common.enums.AppHttpCodeEnum;
    import com.heima.model.wemedia.pojos.WmMaterial;
    import com.heima.utils.thread.WmThreadLocalUtil;
    import com.heima.wemedia.mapper.WmMaterialMapper;
    import com.heima.wemedia.service.WmMaterialService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.IOException;
    import java.util.Date;
    import java.util.UUID;


    @Slf4j
    @Service
    @Transactional
    public class WmMaterialServiceImpl extends ServiceImpl<WmMaterialMapper, WmMaterial> implements WmMaterialService {

    @Autowired
    private FileStorageService fileStorageService; //使用minio

    @Override
    public ResponseResult uploadPicture(MultipartFile multipartFile) {
    //1.检查参数
    if(multipartFile == null || multipartFile.getSize() == 0){
    return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
    }
    //2.上传图片到minio中
    String fileName = UUID.randomUUID().toString().replace("-", "");
    String originalFilename = multipartFile.getOriginalFilename();
    String postfix = originalFilename.substring(originalFilename.lastIndexOf("."));
    String fileId = null;
    try {
    fileId = fileStorageService.uploadImgFile("", fileName + postfix, multipartFile.getInputStream());
    log.info("上传图片到Minio中,fileId:{}",fileId);
    } catch (IOException e) {
    log.info("WmMaterialServiceImpl-上传图片失败");
    e.printStackTrace();
    }
    //3.将图片的信息保存在数据库中
    WmMaterial wmMaterial = new WmMaterial();
    wmMaterial.setUserId(WmThreadLocalUtil.getUser().getId());
    wmMaterial.setUrl(fileId);
    wmMaterial.setType((short)0);
    wmMaterial.setIsCollection((short)0);
    wmMaterial.setCreatedTime(new Date());
    save(wmMaterial);
    //4.返回参数
    return ResponseResult.okResult(wmMaterial);
    }
    }
    + +

    4.测试上传功能

    +

    image-20230902220022201

    +

    3.2 素材管理-图片列表

    接口定义

    + + + + + + + + + + + + + + + + + + + +
    接口路径/api//v1/material/list
    请求方式POST
    参数WmMaterialDto
    响应结果ResponseResult
    +

    请求参数的DTO

    +
    @Data
    public class WmMaterialDto extends PageRequestDto {

    /**
    * 1 收藏
    * 0 未收藏
    */
    private Short isCollection;
    }
    + +

    关键代码

    +
    /**
    * 素材图片的列表显示
    */
    @Override
    public ResponseResult findList(WmMaterialDto wmMaterialDto) {
    //1.检查参数
    wmMaterialDto.checkParam();
    //2.分页查询
    IPage page = new Page(wmMaterialDto.getPage(),wmMaterialDto.getSize());
    LambdaQueryWrapper<WmMaterial> queryWrapper = new LambdaQueryWrapper<>();
    //是否收藏
    if(wmMaterialDto.getIsCollection() != null && wmMaterialDto.getIsCollection() == 1){
    queryWrapper.eq(WmMaterial::getIsCollection,wmMaterialDto.getIsCollection());
    }
    //按照用户查询
    queryWrapper.eq(WmMaterial::getUserId,WmThreadLocalUtil.getUser().getId());
    //按照时间查询
    queryWrapper.orderByDesc(WmMaterial::getCreatedTime);
    page = page(page,queryWrapper);
    //3.结果返回
    ResponseResult responseResult = new PageResponseResult(wmMaterialDto.getPage(), wmMaterialDto.getSize(), (int) page.getTotal());
    responseResult.setData(page.getRecords());
    return responseResult;
    }
    + +

    MP的配置

    +
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
    }
    + +

    实现效果

    +

    image-20230903125528872

    +

    4.自媒体文章管理功能

    4.1 频道列表查询

    频道对应的实体类

    +
    package com.heima.model.wemedia.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * <p>
    * 频道信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("wm_channel")
    public class WmChannel implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 频道名称
    */
    @TableField("name")
    private String name;

    /**
    * 频道描述
    */
    @TableField("description")
    private String description;

    /**
    * 是否默认频道
    * 1:默认 true
    * 0:非默认 false
    */
    @TableField("is_default")
    private Boolean isDefault;

    /**
    * 是否启用
    * 1:启用 true
    * 0:禁用 false
    */
    @TableField("status")
    private Boolean status;

    /**
    * 默认排序
    */
    @TableField("ord")
    private Integer ord;

    /**
    * 创建时间
    */
    @TableField("created_time")
    private Date createdTime;
    }
    + +

    接口定义

    + + + + + + + + + + + + + + + + + + + + + + + +
    说明
    接口路径/api/v1/channel/channels
    请求方式GET
    参数
    响应结果ResponseResult
    +

    关键代码

    +
    /**
    * 查询所有的频道信息
    */
    @GetMapping("/channels")
    public ResponseResult findAllChannels(){
    List<WmChannel> channels = wmChannelService.list();
    return ResponseResult.okResult(channels);
    }
    + +

    实现效果

    +

    image-20230903151216766

    +

    4.2 文章列表加载

    文章表对应的实体类

    +
    package com.heima.model.wemedia.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;
    import org.apache.ibatis.type.Alias;

    import java.io.Serializable;
    import java.util.Date;

    /**
    * <p>
    * 自媒体图文内容信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("wm_news")
    public class WmNews implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
    * 主键
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 自媒体用户ID
    */
    @TableField("user_id")
    private Integer userId;

    /**
    * 标题
    */
    @TableField("title")
    private String title;

    /**
    * 图文内容
    */
    @TableField("content")
    private String content;

    /**
    * 文章布局
    0 无图文章
    1 单图文章
    3 多图文章
    */
    @TableField("type")
    private Short type;

    /**
    * 图文频道ID
    */
    @TableField("channel_id")
    private Integer channelId;

    @TableField("labels")
    private String labels;

    /**
    * 创建时间
    */
    @TableField("created_time")
    private Date createdTime;

    /**
    * 提交时间
    */
    @TableField("submited_time")
    private Date submitedTime;

    /**
    * 当前状态
    0 草稿
    1 提交(待审核)
    2 审核失败
    3 人工审核
    4 人工审核通过
    8 审核通过(待发布)
    9 已发布
    */
    @TableField("status")
    private Short status;

    /**
    * 定时发布时间,不定时则为空
    */
    @TableField("publish_time")
    private Date publishTime;

    /**
    * 拒绝理由
    */
    @TableField("reason")
    private String reason;

    /**
    * 发布库文章ID
    */
    @TableField("article_id")
    private Long articleId;

    /**
    * //图片用逗号分隔
    */
    @TableField("images")
    private String images;

    @TableField("enable")
    private Short enable;

    //状态枚举类
    @Alias("WmNewsStatus")
    public enum Status{
    NORMAL((short)0),SUBMIT((short)1),FAIL((short)2),ADMIN_AUTH((short)3),ADMIN_SUCCESS((short)4),SUCCESS((short)8),PUBLISHED((short)9);
    short code;
    Status(short code){
    this.code = code;
    }
    public short getCode(){
    return this.code;
    }
    }

    }
    + +

    接口定义

    + + + + + + + + + + + + + + + + + + + +
    接口路径/api//v1/news/list
    请求方式POST
    参数WmNewsPageReqDto
    响应结果ResponseResult
    +

    关键代码

    +
    @Override
    public ResponseResult findNewsList(WmNewsPageReqDto wmNewsPageReqDto) {
    //1.检查参数
    wmNewsPageReqDto.checkParam();
    //2.根据条件查询
    IPage page = new Page(wmNewsPageReqDto.getPage(), wmNewsPageReqDto.getSize());
    LambdaQueryWrapper<WmNews> queryWrapper = new LambdaQueryWrapper<>();
    //状态
    if(wmNewsPageReqDto.getStatus() != null){
    queryWrapper.eq(WmNews::getStatus,wmNewsPageReqDto.getStatus());
    }
    //开始时间 结束时间
    if(wmNewsPageReqDto.getBeginPubDate() != null && wmNewsPageReqDto.getEndPubDate()!=null){
    queryWrapper.between(WmNews::getPublishTime,wmNewsPageReqDto.getBeginPubDate(),wmNewsPageReqDto.getEndPubDate());
    }
    //所属频道id
    if(wmNewsPageReqDto.getChannelId() != null){
    queryWrapper.eq(WmNews::getChannelId,wmNewsPageReqDto.getChannelId());
    }
    //关键字
    if(wmNewsPageReqDto.getKeyword() != null){
    queryWrapper.like(WmNews::getTitle,wmNewsPageReqDto.getKeyword());
    }
    //查询当前登录人的文章
    queryWrapper.eq(WmNews::getUserId, WmThreadLocalUtil.getUser().getId());
    //按照发布时间倒序查询
    queryWrapper.orderByDesc(WmNews::getPublishTime);
    page = page(page,queryWrapper);
    //3.结果返回
    ResponseResult responseResult = new PageResponseResult(wmNewsPageReqDto.getPage(), wmNewsPageReqDto.getSize(), (int) page.getTotal());
    responseResult.setData(page.getRecords());
    return responseResult;
    }
    + +

    实现效果

    +

    image-20230903185956428

    +

    4.3 发布文章功能(核心功能)

    文章和素材对应关系表

    +
    package com.heima.model.wemedia.pojos;

    import com.baomidou.mybatisplus.annotation.IdType;
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;
    import lombok.Data;

    import java.io.Serializable;

    /**
    * <p>
    * 自媒体图文引用素材信息表
    * </p>
    *
    * @author itheima
    */
    @Data
    @TableName("wm_news_material")
    public class WmNewsMaterial implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
    * 主键
    */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
    * 素材ID
    */
    @TableField("material_id")
    private Integer materialId;

    /**
    * 图文ID
    */
    @TableField("news_id")
    private Integer newsId;

    /**
    * 引用类型
    0 内容引用
    1 主图引用
    */
    @TableField("type")
    private Short type;

    /**
    * 引用排序
    */
    @TableField("ord")
    private Short ord;

    }
    + +

    实现流程

    +

    image-20230904151910210

    +

    接口定义

    + + + + + + + + + + + + + + + + + + + + + + + +
    说明
    接口路径/api/v1/news/submit
    请求方式POST
    参数WmNewsDto
    响应结果ResponseResult
    +

    WmNewsDto

    +
    package com.heima.model.wemedia.dtos;

    import lombok.Data;

    import java.util.Date;
    import java.util.List;

    @Data
    public class WmNewsDto {

    private Integer id;
    /**
    * 标题
    */
    private String title;
    /**
    * 频道id
    */
    private Integer channelId;
    /**
    * 标签
    */
    private String labels;
    /**
    * 发布时间
    */
    private Date publishTime;
    /**
    * 文章内容
    */
    private String content;
    /**
    * 文章封面类型 0 无图 1 单图 3 多图 -1 自动
    */
    private Short type;
    /**
    * 提交时间
    */
    private Date submitedTime;
    /**
    * 状态 提交为1 草稿为0
    */
    private Short status;

    /**
    * 封面图片列表 多张图以逗号隔开
    */
    private List<String> images;
    }
    + +

    前端接受数据格式

    +
    {
    "title":"黑马头条项目背景",
    "type":"1",//这个 0 是无图 1 是单图 3 是多图 -1 是自动
    "labels":"黑马头条",
    "publishTime":"2020-03-14T11:35:49.000Z",
    "channelId":1,
    "images":[
    "http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
    ],
    "status":1,
    "content":"[
    {
    "type":"text",
    "value":"随着智能手机的普及,人们更加习惯于通过手机来看新闻。由于生活节奏的加快,很多人只能利用碎片时间来获取信息,因此,对于移动资讯客户端的需求也越来越高。黑马头条项目正是在这样背景下开发出来。黑马头条项目采用当下火热的微服务+大数据技术架构实现。本项目主要着手于获取最新最热新闻资讯,通过大数据分析用户喜好精确推送咨询新闻"
    },
    {
    "type":"image",
    "value":"http://192.168.200.130/group1/M00/00/00/wKjIgl5swbGATaSAAAEPfZfx6Iw790.png"
    }
    ]"
    }
    + +

    保存文章和素材对应关系mapper接口

    +

    mapper接口

    +
    @Mapper
    public interface WmNewsMaterialMapper extends BaseMapper<WmNewsMaterial> {


    /**
    * 批量保存文章和素材之间的关系
    * @param materialIds 素材的id
    * @param newsId 文章的id
    * @param type 类型
    */
    void saveRelations(@Param("materialIds") List<Integer> materialIds,@Param("newsId") Integer newsId, @Param("type")Short type);
    }
    + +

    xml文件

    +
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.heima.wemedia.mapper.WmNewsMaterialMapper">

    <insert id="saveRelations">
    insert into wm_news_material (material_id,news_id,type,ord)
    values
    <foreach collection="materialIds" index="ord" item="mid" separator=",">
    (#{mid},#{newsId},#{type},#{ord})
    </foreach>
    </insert>

    </mapper>
    + +

    使用到的常量类

    +
    package com.heima.common.constants;

    public class WemediaConstants {

    public static final Short COLLECT_MATERIAL = 1;//收藏

    public static final Short CANCEL_COLLECT_MATERIAL = 0;//取消收藏

    public static final String WM_NEWS_TYPE_IMAGE = "image";

    public static final Short WM_NEWS_NONE_IMAGE = 0;
    public static final Short WM_NEWS_SINGLE_IMAGE = 1;
    public static final Short WM_NEWS_MANY_IMAGE = 3;
    public static final Short WM_NEWS_TYPE_AUTO = -1;

    public static final Short WM_CONTENT_REFERENCE = 0;
    public static final Short WM_COVER_REFERENCE = 1;
    }
    + +]]>
    + + 后端 + + + 项目实战 + +
    + + 数据结构与算法 + /posts/40445.html + 代码仓库的地址:https://gitee.com/JasonsGong/DataStructures

    +

    一.经典算法问题

    字符串匹配 KMP算法

    +

    汉诺塔问题 分治算法

    +

    八皇后问题 回溯算法

    +

    马踏棋盘问题 图的深度优化遍历算法(DFS)和 贪心算法优化

    +

    二.数据结构与算法的概述

    2.1 数据结构与算法的关系

    (1)数据(data)结构(structure)是一门研究组织数据方式的学科,有了编程语言也就有了数据结构,学好了数据结构可以编写出更加漂亮,更加有效率的代码。

    +

    (2)程序=数据结构+算法

    +

    (3)数据结构是算法的基础

    +

    2.2解决实际的问题

    五子棋程序 稀疏数组(压缩存档) 二维数组->转化成稀疏数组->存档 读档反之

    +

    约瑟夫问题(丢手帕问题) 单向环形列表

    +

    修路问题 求最小生成树 + 普利姆算法

    +

    最短路径问题 图+弗洛伊德算法

    +

    2.3 数据结构

    线性结构:

    +

    ​ 线性结构作为最常用的数据结构,其特点是数据元素之间存在一对一的线性关系

    +

    ​ 线性结构有两种不同的存储结构,即顺序存储结构和链式存储结构。顺序存储的线性表称为顺序表,顺序表中的存储元素是连续的

    +

    ​ 链式存储的线性表称为链表,链表中的存储元素不一定是连续的,元素节点中存放数据元素以及相邻元素的地址信息

    +

    ​ 线性结构常见的有:数组、队列、链表和栈

    +

    非线性结构:

    +

    ​ 二维数组,多维数组,广义表,树结构,图结构

    +

    三.稀疏数组和队列

    3.1稀疏数组

    3.1.1 基本的介绍

    ​ 当一个数组中大部分元素为0,或者为同一个值的数组时,可以使用稀疏数组来保存该数组。

    +

    稀疏数组的处理方法是:

    +

    ​ 1)记录数组一共有几行几列,有多少个不同的值

    +

    ​ 2)把具有不同值的元素的行列及值记录在一个小规模的数组中,从而缩小程序的规模

    +

    3.1.2使用的场景

    ​ 五子棋程序的存档和退出功能的实现

    +

    3.1.3图解

    第一行记录的是原始的数组有几行几列有几个非零的值 例如下面的数组是一个6行7列有8个非零值的数组

    +

    image-20230313124552408

    +

    3.1.5实现的思路

    image-20230318100138197

    +

    3.1.5代码实现

    package com.atguigu.sparsearray;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/3/18
    * @Description 稀疏数组的代码实现 稀疏数组和二维数组之间的互转
    */
    public class SparseArray {

    public static void main(String[] args) {
    //创建一个原始的二维数组
    //0:表示没有棋子 1:表示黑子 2:表示白子
    int[][] chessArr1 = new int[11][11];
    chessArr1[1][2] = 1;
    chessArr1[2][3] = 2;
    chessArr1[4][5] = 1;
    //遍历输出
    System.out.println("原始的二维数组");
    for (int[] row : chessArr1) {
    for (int num : row) {
    System.out.print(num + " ");
    }
    System.out.println();
    }


    //将二维数组转化成稀疏数组
    //获取原始二维数组中非零元素的个数
    int sum = 0;
    for (int[] row : chessArr1) {
    for (int num : row) {
    if (num != 0) {
    sum++;
    }
    }
    }
    System.out.println("非零元素的个数:" + sum);
    //根据非零的元素的个数创建对应的稀疏数组
    int[][] sparseArr2 = new int[sum + 1][3];
    //给稀疏数组赋值
    //第一行的赋值操作
    sparseArr2[0][0] = 11;
    sparseArr2[0][1] = 11;
    sparseArr2[0][2] = sum;
    //其余行的赋值操作
    int row = 1; //充当计数器的作用
    for (int i = 0; i < chessArr1.length; i++) {
    for (int j = 0; j < chessArr1[i].length; j++) {
    if (chessArr1[i][j] != 0) {
    sparseArr2[row][0] = i;
    sparseArr2[row][1] = j;
    sparseArr2[row][2] = chessArr1[i][j];
    row++;
    }
    }
    }


    //遍历稀疏数组
    System.out.println("得到的稀疏数组如下");
    for (int[] ints : sparseArr2) {
    for (int anInt : ints) {
    System.out.print(anInt + " ");
    }
    System.out.println();
    }


    //将稀疏数组转化成二维数组
    //获取原先的二维数组的大小
    //读取第一行 获取原始二维数组的行列值
    int[][] sparseArr3 = new int[sparseArr2[0][0]][sparseArr2[0][1]];
    //遍历稀疏数组第二行之后的值 赋值给原始的数组 从第二行开始算
    for (int i = 1; i < sparseArr2.length; i++) {
    sparseArr3[sparseArr2[i][0]][sparseArr2[i][1]] = sparseArr2[i][2];
    }

    //遍历还原之后的二维数组
    System.out.println("稀疏数组还原成二维数组");
    for (int[] ints : sparseArr3) {
    for (int anInt : ints) {
    System.out.print(anInt+" ");
    }
    System.out.println();
    }


    }
    }

    + + + +

    3.2队列

    3.2.1基本的介绍

    先进先出

    +

    队列是一个有序列表,可以通过数组或者链表实现。

    +

    遵循先入先出的原则。即:先存入队列的数据,要先取出。后存入队列的数据要后取出。

    +

    3.2.2代码实现

    使用数组模拟队列

    +
    package com.atguigu.queue;

    import java.util.Scanner;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/3/18
    * @Description 使用数组模拟队列
    */
    public class ArrayQueueDemo {
    public static void main(String[] args) {
    //测试一把
    //创建一个队列
    ArrayQueue queue = new ArrayQueue(3);
    char key = ' '; //接收用户输入
    Scanner scanner = new Scanner(System.in);//
    boolean loop = true;
    //输出一个菜单
    while (loop) {
    System.out.println("s(show): 显示队列");
    System.out.println("e(exit): 退出程序");
    System.out.println("a(add): 添加数据到队列");
    System.out.println("g(get): 从队列取出数据");
    System.out.println("h(head): 查看队列头的数据");
    key = scanner.next().charAt(0);//接收一个字符
    switch (key) {
    case 's':
    queue.showQueue();
    break;
    case 'a':
    System.out.println("输出一个数");
    int value = scanner.nextInt();
    queue.addQueue(value);
    break;
    case 'g': //取出数据
    try {
    int res = queue.getQueue();
    System.out.printf("取出的数据是%d\n", res);
    } catch (Exception e) {
    // TODO: handle exception
    System.out.println(e.getMessage());
    }
    break;
    case 'h': //查看队列头的数据
    try {
    int res = queue.headQueue();
    System.out.printf("队列头的数据是%d\n", res);
    } catch (Exception e) {
    // TODO: handle exception
    System.out.println(e.getMessage());
    }
    break;
    case 'e': //退出
    scanner.close();
    loop = false;
    break;
    default:
    break;
    }
    }

    System.out.println("程序退出~~");
    }

    }

    // 使用数组模拟队列-编写一个ArrayQueue类
    class ArrayQueue {
    private int maxSize; // 表示数组的最大容量
    private int front; // 队列头
    private int rear; // 队列尾
    private int[] arr; // 该数据用于存放数据, 模拟队列

    // 创建队列的构造器
    public ArrayQueue(int arrMaxSize) {
    maxSize = arrMaxSize;
    arr = new int[maxSize];
    front = -1; // 指向队列头部,分析出front是指向队列头的前一个位置.
    rear = -1; // 指向队列尾,指向队列尾的数据(即就是队列最后一个数据)
    }

    // 判断队列是否满
    public boolean isFull() {
    return rear == maxSize - 1;
    }

    // 判断队列是否为空
    public boolean isEmpty() {
    return rear == front;
    }

    // 添加数据到队列
    public void addQueue(int n) {
    // 判断队列是否满
    if (isFull()) {
    System.out.println("队列满,不能加入数据~");
    return;
    }
    rear++; // 让rear 后移
    arr[rear] = n;
    }

    // 获取队列的数据, 出队列
    public int getQueue() {
    // 判断队列是否空
    if (isEmpty()) {
    // 通过抛出异常
    throw new RuntimeException("队列空,不能取数据");
    }
    front++; // front后移
    return arr[front];

    }

    // 显示队列的所有数据
    public void showQueue() {
    // 遍历
    if (isEmpty()) {
    System.out.println("队列空的,没有数据~~");
    return;
    }
    for (int i = 0; i < arr.length; i++) {
    System.out.printf("arr[%d]=%d\n", i, arr[i]);
    }
    }

    // 显示队列的头数据, 注意不是取出数据
    public int headQueue() {
    // 判断
    if (isEmpty()) {
    throw new RuntimeException("队列空的,没有数据~~");
    }
    return arr[front + 1];
    }
    }
    + + + +

    3.2.3 数组模拟环形队列

    环形队列的思路分析

    +
      +
    • 环形队列满的条件:(rear + 1) % maxSize == front
    • +
    • 环形队列空的条件:rear == front
    • +
    • 环形队列中有效数据的个数: (rear + maxSize - front) % maxSize
    • +
    +

    image-20230428124659873

    +
    package com.atguigu.queue;

    import java.util.Scanner;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/28
    * @Description 模拟环形队列
    * <p>
    * 环形队列满的条件:(rear + 1) % maxSize == front
    * 环形队列空的条件:rear == front
    * 环形队列中有效数据的个数: (rear + maxSize - front) % maxSize
    */

    public class CircleArrayQueueDemo {
    public static void main(String[] args) {
    //创建一个队列
    System.out.println("测试环形队列");
    CircleArray queue = new CircleArray(3);
    char key = ' '; //接收用户输入
    Scanner scanner = new Scanner(System.in);//
    boolean loop = true;
    //输出一个菜单
    while (loop) {
    System.out.println("s(show): 显示队列");
    System.out.println("e(exit): 退出程序");
    System.out.println("a(add): 添加数据到队列");
    System.out.println("g(get): 从队列取出数据");
    System.out.println("h(head): 查看队列头的数据");
    key = scanner.next().charAt(0);//接收一个字符
    switch (key) {
    case 's':
    queue.showQueue();
    break;
    case 'a'://添加数据
    System.out.println("输出一个数");
    int value = scanner.nextInt();
    queue.addQueue(value);
    break;
    case 'g': //取出数据
    try {
    int res = queue.getQueue();
    System.out.printf("取出的数据是%d\n", res);
    } catch (Exception e) {
    // TODO: handle exception
    System.out.println(e.getMessage());
    }
    break;
    case 'h': //查看队列头的数据
    try {
    int res = queue.headQueue();
    System.out.printf("队列头的数据是%d\n", res);
    } catch (Exception e) {
    // TODO: handle exception
    System.out.println(e.getMessage());
    }
    break;
    case 'e': //退出
    scanner.close();
    loop = false;
    break;
    default:
    break;
    }
    }

    System.out.println("程序退出~~");
    }
    }

    class CircleArray {
    private int maxSize; // 表示数组的最大容量 因为预留了一个空间 实际上maxSize=3 只能存储两个元素
    private int front; // 队列头 初始值是0
    //rear 变量的含义做一个调整:rear 指向队列的最后一个元素的后一个位置. 因为希望空出一个空间做为约定
    private int rear; // 队列尾
    private int[] arr; // 该数据用于存放数据, 模拟队列

    /**
    * 构造方法
    *
    * @param arrMaxSize 环形队列的最大容量
    */
    public CircleArray(int arrMaxSize) {
    maxSize = arrMaxSize;
    arr = new int[maxSize];
    }

    /**
    * 判断队列是否为满
    */
    public boolean isFull() {
    //因为是环形队列 如果尾部队列的后一个指向队列的头部 那么我们认为队列是满的
    return (rear + 1) % maxSize == front; //这里是一个理解点
    }

    /**
    * 判断队列是否为空
    */
    public boolean isEmpty() {
    return rear == front;
    }


    /**
    * 添加数据
    */
    public void addQueue(int n) {
    //先判断队列是否为满 满了就不添加
    if (this.isFull()) {
    System.out.println("队列为满,不能添加数据!");
    return;
    }
    //添加数据
    arr[rear] = n;
    //将人rear向后移
    rear = (rear + 1) % maxSize; //这里是一个理解点
    }


    /**
    * 取出数据
    */
    public int getQueue() {
    //先判断队列是否为空 空了就无法取出数据
    if (this.isEmpty()) {
    System.out.println("队列为空,无法取出数据!");
    throw new RuntimeException("队列空,不能取数据");
    }
    //不为空 取出数据
    int result = arr[front];
    front = (front + 1) % maxSize; //这里是一个理解点
    return result;
    }


    /**
    * 显示对垒数据的方法
    */
    public void showQueue() {
    //判断队列是否为空 如果队列为空就不遍历
    if (this.isEmpty()) {
    System.out.println("队列为空!");
    return;
    }
    //遍历的方式 从front开始遍历 遍历多少个元素
    int count = this.size();
    for (int i = front; i < front + count; i++) {//主要作用是遍历多少次
    System.out.println("arr[" + i % maxSize + "]=" + arr[i % maxSize]);
    }
    }

    /**
    * 求出当前数列中有效数据的个数
    */
    public int size() {
    return (rear + maxSize - front) % maxSize; //这是一个理解点
    }

    /**
    * 显示队列的头元素
    */
    public int headQueue() {
    // 判断
    if (isEmpty()) {
    throw new RuntimeException("队列空的,没有数据~~");
    }
    return arr[front];
    }

    }
    + + + + + +

    四.链表

    4.1 链表(Linked List)介绍

    在内存中不是连续存储的

    +

    链表的逻辑结构

    +

    image-20230428135032883

    +

    4.2单链表的应用实例(CRUD)

    使用带head头的单向链表实现 –水浒英雄排行榜管理 完成对英雄人物的增删改查操作

    +

    4.2.1向单链表中添加数据

    package com.atguigu.linkedlist;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/28
    * @Description 单链表的实现
    * 使用带head头的单向链表实现 –水浒英雄排行榜管理
    * 完成对英雄人物的增删改查操作
    */
    public class SingleLinkedListDemo {
    public static void main(String[] args) {
    SingleLinkedList singleLinkedList = new SingleLinkedList();
    singleLinkedList.add(new HeroNode(1, "宋江", "及时雨"));
    singleLinkedList.add(new HeroNode(2, "卢俊义", "玉麒麟"));
    singleLinkedList.add(new HeroNode(3, "吴用", "智多星"));
    singleLinkedList.add(new HeroNode(4, "林冲", "豹子头"));
    //显示链表
    singleLinkedList.list();
    }
    }

    //定义SingleLinkedList来管理我们的英雄任务
    class SingleLinkedList {
    //初始化一个头节点 不存储具体的数据
    private HeroNode head = new HeroNode(0, "", "");

    /**
    * 添加节点到单向链表
    */
    public void add(HeroNode heroNode) {
    //找到当前链表的最后一个节点
    HeroNode temp = head;
    while (temp.next != null) {
    temp = temp.next;
    }
    temp.next = heroNode;
    }

    /**
    * 显示链表
    */
    public void list() {
    //先判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空!");
    return;
    }
    HeroNode temp = head;
    while (temp.next != null) {
    System.out.println(temp.next);
    temp = temp.next;
    }
    }

    }

    //定义一个heroNode,每个heroNode对象都是一个节点
    class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点

    //构造器
    public HeroNode(int no, String name, String nickname) {
    this.no = no;
    this.name = name;
    this.nickname = nickname;
    }

    @Override
    public String toString() {
    return "HeroNode{" +
    "no=" + no +
    ", name='" + name + '\'' +
    ", nickname='" + nickname + '\'' +
    '}';
    }
    }
    + +

    4.2.2修改单链表中节点的方法

    /**
    * 修改节点的信息
    * 根据新的节点的编号进行修改
    */
    public void update(HeroNode newHeroNode) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法修改该节点!");
    return;
    }
    //找到需要修改的节点
    //首先找到头节点的位置
    HeroNode temp = head.next;
    boolean idFind = false; //是否找到节点的信息
    while (true) {
    if (temp == null) {
    break;//已经到了链表的结尾,就退出循环
    }
    if (temp.no == newHeroNode.no) {//找到修改的节点
    temp.name = newHeroNode.name;
    temp.nickname = newHeroNode.nickname;
    idFind = true; //找到了节点并且修改了节点的信息
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(idFind ? "修改成功" : "没有找到该节点的信息");
    }
    + +

    4.2.3删除单链表中节点的方法

    /**
    * 删除链表中的节点
    * 根据节点的编号删除节点的信息
    */
    public void delete(int no) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法删除!");
    return;
    }
    //找到要删除的节点的信息
    HeroNode temp = head;
    boolean isDelete = false;//是否删除
    while (true) {
    if (temp.next == null) {
    break;
    }
    //这里的temp指的是要删除节点的上一个节点 temp.next.next指的是要删除节点的下一个节点
    if (temp.next.no == no) {
    //将要删除的节点的上一个节点指向要删除节点的下一个节点 实现删除的操作
    //这样要删除的节点没有引用指向,会被垃圾回收机制回收
    temp.next = temp.next.next;
    isDelete = true;
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(isDelete ? "删除成功" : "没有找到该节点的信息");
    }
    + +

    4.2.4完整代码

    package com.atguigu.linkedlist;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/28
    * @Description 单链表的实现
    * 使用带head头的单向链表实现 –水浒英雄排行榜管理
    * 完成对英雄人物的增删改查操作
    */
    public class SingleLinkedListDemo {
    public static void main(String[] args) {
    SingleLinkedList singleLinkedList = new SingleLinkedList();
    singleLinkedList.add(new HeroNode(1, "宋江", "及时雨"));
    singleLinkedList.add(new HeroNode(2, "卢俊义", "玉麒麟"));
    singleLinkedList.add(new HeroNode(3, "吴用", "智多星"));
    singleLinkedList.add(new HeroNode(4, "林冲", "豹子头"));
    //显示链表
    singleLinkedList.list();
    //测试修改节点的功能
    singleLinkedList.update(new HeroNode(2, "卢俊义", "玉麒麟增强版"));
    //显示链表的信息
    singleLinkedList.list();
    //测试删除节点
    singleLinkedList.delete(2);
    //显示链表的信息
    singleLinkedList.list();
    }
    }

    //定义SingleLinkedList来管理我们的英雄任务
    class SingleLinkedList {
    //初始化一个头节点 不存储具体的数据
    private HeroNode head = new HeroNode(0, "", "");


    /**
    * 删除链表中的节点
    * 根据节点的编号删除节点的信息
    */
    public void delete(int no) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法删除!");
    return;
    }
    //找到要删除的节点的信息
    HeroNode temp = head;
    boolean isDelete = false;//是否删除
    while (true) {
    if (temp.next == null) {
    break;
    }
    if (temp.next.no == no) {//这里的temp指的是要删除节点的上一个节点 temp.next.next指的是要删除节点的下一个节点
    //将当前节点的上一个节点指向当前节点的下一个节点
    temp.next = temp.next.next; //将要删除的节点的上一个节点指向要删除节点的下一个节点 这样要删除的节点没有引用指向 会被垃圾回收机制回收
    isDelete = true;
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(isDelete ? "删除成功" : "没有找到该节点的信息");
    }


    /**
    * 修改节点的信息
    * 根据新的节点的编号进行修改
    */
    public void update(HeroNode newHeroNode) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法修改该节点!");
    return;
    }
    //找到需要修改的节点
    //首先找到头节点的位置
    HeroNode temp = head.next;
    boolean idFind = false; //是否找到节点的信息
    while (true) {
    if (temp == null) {
    break;//已经到了链表的结尾,就退出循环
    }
    if (temp.no == newHeroNode.no) {//找到修改的节点
    temp.name = newHeroNode.name;
    temp.nickname = newHeroNode.nickname;
    idFind = true; //找到了节点并且修改了节点的信息
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(idFind ? "修改成功" : "没有找到该节点的信息");
    }


    /**
    * 添加节点到单向链表
    */
    public void add(HeroNode heroNode) {
    //找到当前链表的最后一个节点
    HeroNode temp = head;
    while (temp.next != null) {
    temp = temp.next;
    }
    temp.next = heroNode;
    }


    /**
    * 显示链表
    */
    public void list() {
    //先判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空!");
    return;
    }
    HeroNode temp = head;
    while (temp.next != null) {
    System.out.println(temp.next);
    temp = temp.next;
    }
    }

    }

    //定义一个heroNode,每个heroNode对象都是一个节点
    class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点

    //构造器
    public HeroNode(int no, String name, String nickname) {
    this.no = no;
    this.name = name;
    this.nickname = nickname;
    }

    @Override
    public String toString() {
    return "HeroNode{" +
    "no=" + no +
    ", name='" + name + '\'' +
    ", nickname='" + nickname + '\'' +
    '}';
    }
    }
    + +

    4.2.5 单链表的面试题(新浪,百度,腾讯)

    1.求单链表中有效节点的个数
    /**
    * 获取有效节点的个数(如果是带头节点的链表,需求不统计头节点)
    * 注意:这里是有头节点的情况下统计节点的个数 (这里我们去除了头节点)
    * @param heroNode 头节点
    * @return 有效节点的个数
    */
    public int getLength(HeroNode heroNode){
    //先判断节点是否为空
    if(heroNode.next == null){
    return 0;
    }
    //遍历节点进行统计
    int length = 0;//有效节点的个数
    HeroNode temp = heroNode.next;//这里没有统计头节点
    while (temp != null){//遍历 统计个数
    length++;
    temp = temp.next;
    }
    return length;
    }
    + +
    2.查找单链表中的倒数第K个节点(新浪面试题)
    /**
    * 查找单链表中的倒数第K个节点
    *
    * @param index 倒数第几
    * @param heroNode 头节点
    * @return 倒数第K个节点的信息
    */
    public HeroNode getHeroNodeByLastIndex(int index, HeroNode heroNode) {
    //先判断节点是否为空
    if (heroNode.next == null) {
    return null;
    }
    //首先获取有效节点的个数
    int length = this.getLength(heroNode);//使用求节点有效个数的方法
    //求倒数第k个节点就是在求正数第(length - index + 1)节点
    index = length - index; //求出正向的索引位置
    if (index < 0 || index > length) {
    System.out.println("所求的在链表中不存在");
    return null;
    }
    int count = 0;
    HeroNode temp = heroNode.next;
    while (true) {
    if (count == index) {
    return temp;
    }
    count++;
    temp = temp.next;
    }
    }
    + +
    3.单链表的反转(腾讯面试题)

    image-20230506105453269

    +
    /**
    * 单链表的反转
    * @param heroNode 需要反转单链表的头节点
    */
    public void reverseHeroNode(HeroNode heroNode) {
    //先判断当前的链表是否为空 或者当前的链表只有一个节点
    if(head.next == null || head.next.next == null){
    System.out.println("当前链表为空或者只有一个节点,无需反转");
    return;
    }
    //定义一个辅助的变量 帮助我们遍历原来的链表
    HeroNode cur = head.next;
    HeroNode next = null;//指向当前节点的下一个节点 我们要在挪动当前节点之前帮当前指针 下移
    HeroNode reverseHeroNode = new HeroNode(0, "", "");//新链表的头节点
    //遍历原先的列表 确定每一个链表的指向关系
    while (cur != null){
    next = cur.next;//保存当前节点的下一个节点
    //reverseHeroNode.next表示头节点的下一个节点 我们让cur的下一个节点(cur.next)指向头节点的下一个节点 //(reverseHeroNode.next) 就把当前的节点(cur)穿插进去了
    //下一句将头节点和当前节点建立关系reverseHeroNode.next = cur 整个节点就连接起来了
    cur.next = reverseHeroNode.next;//将cur的下一个节点指向新的链表的最前端 相当于在头节点和头节点的下一个节点 //之间穿插了一个节点
    reverseHeroNode.next = cur;//将cur连接到新的链表上
    cur = next; //cur像后移动
    }
    //将head.next指向reverseHeroNode.next 实现单链表的反转
    head.next = reverseHeroNode.next;
    }
    + +
    4.从尾到头打印单链表(百度面试题)

    不改变链表本身的结构(不是通过链表的反转之后再打印的)

    +
    /**
    * (通过栈的方式实现单链表的逆序打印)
    * 栈的特点是先入后出 正好可以满足逆序打印
    * 单链表的逆序打印
    */
    public void reversePrint(HeroNode heroNode) {
    //先判断当前的链表是否为空 或者当前的链表只有一个节点
    if (head.next == null) {
    System.out.println("当前链表为空,无法逆序打印!");
    return;
    }
    //创建一个栈,将各个节点压入栈中
    //先创建一个栈
    Stack<HeroNode> stack = new Stack<>();
    HeroNode cur = heroNode.next;
    //循环将每一个节点添加进栈中
    while (cur != null) {
    stack.push(cur);//将节点压入栈中
    cur = cur.next;//cur后移 压入下一个节点
    }
    //将栈中节点打印
    while (stack.size() > 0) {
    System.out.println(stack.pop());
    }
    }
    + +
    5.合并两个有序的单链表,合并之后的链表依然有序

    以下代码有一些bug 后期修改

    +
    /**
    * 合并两个有序的单链表,合并之后的链表依然有序
    */
    public static void mergeHeroNode(HeroNode oneHead, HeroNode twoHead ,HeroNode head) {
    //先判断两个链表是否为空
    if (oneHead.next == null || twoHead.next == null) {
    System.out.println("其中有一个(两个)链表为空,无法合并");
    return;
    }
    //合并
    HeroNode oneCur = oneHead.next;
    HeroNode twoCur = twoHead.next;
    HeroNode oneNext = null;
    HeroNode twoNext = null;
    HeroNode finalHeroHead = new HeroNode(0, "", "");
    while (oneCur != null && twoCur != null) {
    oneNext = oneCur.next;
    twoNext = twoCur.next;
    if(oneCur.no <= twoCur.no) {
    oneCur.next = finalHeroHead.next;
    finalHeroHead.next = oneCur;
    oneCur = oneNext;
    }else {
    twoCur.next = finalHeroHead.next;
    finalHeroHead.next = twoCur;
    twoCur = twoNext;
    }
    }
    head.next = finalHeroHead.next;
    }
    + +
    全部代码
    package com.atguigu.linkedlist;

    import java.util.Stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/4/28
    * @Description 单链表的实现
    * 使用带head头的单向链表实现 –水浒英雄排行榜管理
    * 完成对英雄人物的增删改查操作
    */
    public class SingleLinkedListDemo {
    public static void main(String[] args) {
    SingleLinkedList singleLinkedList = new SingleLinkedList();
    // singleLinkedList.add(new HeroNode(1, "宋江", "及时雨"));
    // singleLinkedList.add(new HeroNode(2, "卢俊义", "玉麒麟"));
    // singleLinkedList.add(new HeroNode(3, "吴用", "智多星"));
    // singleLinkedList.add(new HeroNode(4, "林冲", "豹子头"));
    // //显示链表
    // singleLinkedList.list();
    // //测试修改节点的功能
    // singleLinkedList.update(new HeroNode(2, "卢俊义", "玉麒麟增强版"));
    // //显示链表的信息
    // singleLinkedList.list();
    // //测试删除节点
    // singleLinkedList.delete(2);
    // //显示链表的信息
    // singleLinkedList.list();
    // //统计有效节点的个数
    // System.out.println("有效节点的个数" + singleLinkedList.getLength(singleLinkedList.getHead()));
    // //查找单链表中的倒数第K个节点
    // int index = 2;
    // System.out.println("倒数第" + index + "个节点的位置:" + singleLinkedList.getHeroNodeByLastIndex(index, singleLinkedList.getHead()));
    // //测试单链表的反转
    // singleLinkedList.reverseHeroNode(singleLinkedList.getHead());
    // singleLinkedList.list();
    // //测试通过栈的方式逆序打印
    // System.out.println("测试通过栈的方式逆序打印头节点");
    // singleLinkedList.reversePrint(singleLinkedList.getHead());

    //测试合并两个有序的链表
    SingleLinkedList singleLinkedList1 = new SingleLinkedList();
    singleLinkedList1.add(new HeroNode(1, "宋江", "及时雨"));
    singleLinkedList1.add(new HeroNode(3, "卢俊义", "玉麒麟"));
    singleLinkedList1.add(new HeroNode(5, "吴用", "智多星"));
    singleLinkedList1.add(new HeroNode(7, "林冲", "豹子头"));
    SingleLinkedList singleLinkedList2 = new SingleLinkedList();
    singleLinkedList2.add(new HeroNode(2, "宋江", "及时雨"));
    singleLinkedList2.add(new HeroNode(4, "卢俊义", "玉麒麟"));
    singleLinkedList2.add(new HeroNode(6, "吴用", "智多星"));
    singleLinkedList2.add(new HeroNode(8, "林冲", "豹子头"));
    mergeHeroNode(singleLinkedList1.getHead(),singleLinkedList2.getHead(),singleLinkedList.getHead());
    singleLinkedList.list();
    }
    /**
    * 合并两个有序的单链表,合并之后的链表依然有序
    */
    public static void mergeHeroNode(HeroNode oneHead, HeroNode twoHead ,HeroNode head) {
    //先判断两个链表是否为空
    if (oneHead.next == null || twoHead.next == null) {
    System.out.println("其中有一个(两个)链表为空,无法合并");
    return;
    }
    //合并
    HeroNode oneCur = oneHead.next;
    HeroNode twoCur = twoHead.next;
    HeroNode oneNext = null;
    HeroNode twoNext = null;
    HeroNode finalHeroHead = new HeroNode(0, "", "");
    while (oneCur != null && twoCur != null) {
    oneNext = oneCur.next;
    twoNext = twoCur.next;
    if(oneCur.no <= twoCur.no) {
    oneCur.next = finalHeroHead.next;
    finalHeroHead.next = oneCur;
    oneCur = oneNext;
    }else {
    twoCur.next = finalHeroHead.next;
    finalHeroHead.next = twoCur;
    twoCur = twoNext;
    }
    }
    head.next = finalHeroHead.next;
    }
    }

    //定义SingleLinkedList来管理我们的英雄任务
    class SingleLinkedList {
    //初始化一个头节点 不存储具体的数据
    private HeroNode head = new HeroNode(0, "", "");

    public HeroNode getHead() {
    return head;
    }





    /**
    * (通过栈的方式实现单链表的逆序打印)
    * 栈的特点是先入后出 正好可以满足逆序打印
    * 单链表的逆序打印
    */
    public void reversePrint(HeroNode heroNode) {
    //先判断当前的链表是否为空 或者当前的链表只有一个节点
    if (heroNode.next == null) {
    System.out.println("当前链表为空,无法逆序打印!");
    return;
    }
    //创建一个栈,将各个节点压入栈中
    //先创建一个栈
    Stack<HeroNode> stack = new Stack<>();
    HeroNode cur = heroNode.next;
    //循环将每一个节点添加进栈中
    while (cur != null) {
    stack.push(cur);//将节点压入栈中
    cur = cur.next;//cur后移 压入下一个节点
    }
    //将栈中节点打印
    while (stack.size() > 0) {
    System.out.println(stack.pop());
    }
    }


    /**
    * 单链表的反转
    *
    * @param heroNode 需要反转单链表的头节点
    */
    public void reverseHeroNode(HeroNode heroNode) {
    //先判断当前的链表是否为空 或者当前的链表只有一个节点
    if (heroNode.next == null || heroNode.next.next == null) {
    System.out.println("当前链表为空或者只有一个节点,无需反转");
    return;
    }
    //定义一个辅助的变量 帮助我们遍历原来的链表
    HeroNode cur = heroNode.next;
    HeroNode next = null;//指向当前节点的下一个节点 我们要在挪动当前节点之前帮当前指针 下移
    HeroNode reverseHeroNode = new HeroNode(0, "", "");//新链表的头节点
    //遍历原先的列表 确定每一个链表的指向关系
    while (cur != null) {
    next = cur.next;//保存当前节点的下一个节点
    //reverseHeroNode.next表示头节点的下一个节点 我们让cur的下一个节点(cur.next)指向头节点的下一个节点(reverseHeroNode.next) 就把当前的节点(cur)穿插进去了
    //下一句将头节点和当前节点建立关系reverseHeroNode.next = cur 整个节点就连接起来了
    cur.next = reverseHeroNode.next;//将cur的下一个节点指向新的链表的最前端 相当于在头节点和头节点的下一个节点之间穿插了一个节点
    reverseHeroNode.next = cur;//将cur连接到新的链表上
    cur = next; //cur像后移动
    }
    //将head.next指向reverseHeroNode.next 实现单链表的反转
    heroNode.next = reverseHeroNode.next;
    }


    /**
    * 查找单链表中的倒数第K个节点
    *
    * @param index 倒数第几
    * @param heroNode 头节点
    * @return 倒数第K个节点的信息
    */
    public HeroNode getHeroNodeByLastIndex(int index, HeroNode heroNode) {
    //先判断节点是否为空
    if (heroNode.next == null) {
    return null;
    }
    //首先获取有效节点的个数
    int length = this.getLength(heroNode);//使用求节点有效个数的方法
    //求倒数第k个节点就是在求正数第(length - index + 1)节点
    index = length - index; //求出正向的索引位置
    if (index < 0 || index > length) {
    System.out.println("所求的在链表中不存在");
    return null;
    }
    int count = 0;
    HeroNode temp = heroNode.next;
    while (true) {
    if (count == index) {
    return temp;
    }
    count++;
    temp = temp.next;
    }
    }

    /**
    * 获取有效节点的个数(如果是带头节点的链表,需求不统计头节点)
    *
    * @param heroNode 头节点
    * @return 有效节点的个数
    */
    public int getLength(HeroNode heroNode) {
    //先判断节点是否为空
    if (heroNode.next == null) {
    return 0;
    }
    //遍历节点进行统计
    int length = 0;//有效节点的个数
    HeroNode temp = heroNode.next;//这里没有统计头节点
    while (temp != null) {//遍历 统计个数
    length++;
    temp = temp.next;
    }
    return length;
    }


    /**
    * 删除链表中的节点
    * 根据节点的编号删除节点的信息
    */
    public void delete(int no) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法删除!");
    return;
    }
    //找到要删除的节点的信息
    HeroNode temp = head;
    boolean isDelete = false;//是否删除
    while (true) {
    if (temp.next == null) {
    break;
    }
    if (temp.next.no == no) {//这里的temp指的是要删除节点的上一个节点 temp.next.next指的是要删除节点的下一个节点
    //将当前节点的上一个节点指向当前节点的下一个节点
    temp.next = temp.next.next; //将要删除的节点的上一个节点指向要删除节点的下一个节点 这样要删除的节点没有引用指向 会被垃圾回收机制回收
    isDelete = true;
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(isDelete ? "删除成功" : "没有找到该节点的信息");
    }


    /**
    * 修改节点的信息
    * 根据新的节点的编号进行修改
    */
    public void update(HeroNode newHeroNode) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法修改该节点!");
    return;
    }
    //找到需要修改的节点
    //首先找到头节点的位置
    HeroNode temp = head.next;
    boolean idFind = false; //是否找到节点的信息
    while (true) {
    if (temp == null) {
    break;//已经到了链表的结尾,就退出循环
    }
    if (temp.no == newHeroNode.no) {//找到修改的节点
    temp.name = newHeroNode.name;
    temp.nickname = newHeroNode.nickname;
    idFind = true; //找到了节点并且修改了节点的信息
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(idFind ? "修改成功" : "没有找到该节点的信息");
    }


    /**
    * 添加节点到单向链表
    */
    public void add(HeroNode heroNode) {
    //找到当前链表的最后一个节点
    HeroNode temp = head;
    while (temp.next != null) {
    temp = temp.next;
    }
    temp.next = heroNode;
    }


    /**
    * 显示链表
    */
    public void list() {
    //先判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空!");
    return;
    }
    HeroNode temp = head;
    while (temp.next != null) {
    System.out.println(temp.next);
    temp = temp.next;
    }
    }


    }

    //定义一个heroNode,每个heroNode对象都是一个节点
    class HeroNode {
    public int no;
    public String name;
    public String nickname;
    public HeroNode next; //指向下一个节点

    //构造器
    public HeroNode(int no, String name, String nickname) {
    this.no = no;
    this.name = name;
    this.nickname = nickname;
    }

    @Override
    public String toString() {
    return "HeroNode{" +
    "no=" + no +
    ", name='" + name + '\'' +
    ", nickname='" + nickname + '\'' +
    '}';
    }
    }
    + + + +

    4.3 双向链表

    双向的链表的CRUD操作于单向链表的CRUD操作类似

    +

    image-20230507100730848

    +

    完整的增删改查代码

    +
    package com.atguigu.linkedlist;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/7
    * @Description 双向链表的使用
    */
    public class DoubleLinkedListDemo {
    public static void main(String[] args) {
    DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
    //测试添加链表的功能
    doubleLinkedList.add(new HeroNode2(1, "宋江", "及时雨"));
    doubleLinkedList.add(new HeroNode2(2, "卢俊义", "玉麒麟"));
    doubleLinkedList.add(new HeroNode2(3, "吴用", "智多星"));
    doubleLinkedList.add(new HeroNode2(4, "林冲", "豹子头"));
    //测试显示链表的功能
    doubleLinkedList.list();
    //测试删除双向链表中的节点
    doubleLinkedList.delete(2);
    doubleLinkedList.list();
    //测试修改双向链表中的节点
    doubleLinkedList.update(new HeroNode2(2, "卢俊义修改版", "玉麒麟"));
    doubleLinkedList.list();
    System.out.println("--------------");
    doubleLinkedList.orderAdd(new HeroNode2(3, "小卢", "玉麒麟"));
    doubleLinkedList.list();
    }
    }

    class DoubleLinkedList {
    //初始化一个头节点 不存储具体的数据
    private HeroNode2 head = new HeroNode2(0, "", "");

    //返回头节点的信息
    public HeroNode2 getHead() {
    return head;
    }


    /**
    * 双向链表的顺序添加
    * 应该有问题 不是参考答案
    */
    public void orderAdd(HeroNode2 heroNode) {
    //找到当前链表的最后一个节点
    HeroNode2 temp = head;
    boolean isSuccess = false;
    while (temp.next != null) {
    if (temp.next.no >= heroNode.no) {
    heroNode.next = temp.next;
    temp.next.pre = heroNode;
    temp.next = heroNode;
    heroNode.pre = temp;
    isSuccess = true;
    break;
    }
    temp = temp.next;
    }
    if (!isSuccess) {//没有添加成功 说明当前链表的序号超过了链表里面已有节点序号的最高值 直接添加到最后
    this.add(heroNode);
    }

    }


    /**
    * 删除双向链表中的一个节点
    */
    public void delete(int no) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法删除!");
    return;
    }
    //找到要删除的节点的信息
    HeroNode2 temp = head.next;
    boolean isDelete = false;//是否删除
    while (true) {
    if (temp == null) {
    break;
    }
    if (temp.no == no) {
    temp.pre.next = temp.next;
    if (temp.next != null) {
    temp.next.pre = temp.pre;//这句话有个条件 temp不能是最后一个节点
    }
    isDelete = true;
    break;
    }
    temp = temp.next;
    }
    System.out.println(isDelete ? "删除成功" : "没有找到该节点的信息");
    }


    /**
    * 修改双向链表的信息
    */
    public void update(HeroNode2 newHeroNode) {
    //判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空,无法修改该节点!");
    return;
    }
    //找到需要修改的节点
    //首先找到头节点的位置
    HeroNode2 temp = head.next;
    boolean idFind = false; //是否找到节点的信息
    while (true) {
    if (temp == null) {
    break;//已经到了链表的结尾,就退出循环
    }
    if (temp.no == newHeroNode.no) {//找到修改的节点
    temp.name = newHeroNode.name;
    temp.nickname = newHeroNode.nickname;
    idFind = true; //找到了节点并且修改了节点的信息
    break;
    }
    temp = temp.next;
    }
    //做出判断,返回结果
    System.out.println(idFind ? "修改成功" : "没有找到该节点的信息");
    }


    /**
    * 向双向链表中添加节点信息
    */
    public void add(HeroNode2 heroNode) {
    //找到当前链表的最后一个节点
    HeroNode2 temp = head;
    while (temp.next != null) {
    temp = temp.next;
    }
    temp.next = heroNode;
    heroNode.pre = temp;
    }


    /**
    * 显示链表
    */
    public void list() {
    //先判断链表是否为空
    if (head.next == null) {
    System.out.println("链表为空!");
    return;
    }
    HeroNode2 temp = head;
    while (temp.next != null) {
    System.out.println(temp.next);
    temp = temp.next;
    }
    }


    }

    //定义一个heroNode,每个heroNode对象都是一个节点
    class HeroNode2 {
    public int no;
    public String name;
    public String nickname;
    public HeroNode2 next; //指向下一个节点
    public HeroNode2 pre;//指向上一个节点

    //构造器
    public HeroNode2(int no, String name, String nickname) {
    this.no = no;
    this.name = name;
    this.nickname = nickname;
    }

    @Override
    public String toString() {
    return "HeroNode{" +
    "no=" + no +
    ", name='" + name + '\'' +
    ", nickname='" + nickname + '\'' +
    '}';
    }
    }
    + +

    4.4 单向环形链表的应用场景(Josephu问题)

    ​ Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

    +

    ​ 解决的方案:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。

    +

    image-20230508104207815

    +

    image-20230508121934697

    +

    完整的代码

    +
    package com.atguigu.linkedlist;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/8
    * @Description 约瑟夫问题
    */
    public class Josephu {
    public static void main(String[] args) {
    //测试构建环形链表
    CircleSingleLinkedList circleSingleLinkedList = new CircleSingleLinkedList();
    circleSingleLinkedList.addBoy(5);
    //测试遍历环形单向链表
    circleSingleLinkedList.showBoy();
    //测试生成小孩出圈的序列
    circleSingleLinkedList.countBoy(1,2,5);
    }

    }


    //创建一个环形的单向链表
    class CircleSingleLinkedList {
    //创建一个first节点,当前没有编号
    private Boy first = null;

    /**
    * 根据用户的输入产生一个出队编号的序列
    *
    * @param startNo 表示从第几个小孩开始数
    * @param countNum 表示数几下
    * @param nums 传入几个小孩
    */
    public void countBoy(int startNo, int countNum, int nums) {
    //先对输入的数据进行校验
    if (first == null || startNo < 0 || startNo > nums) {
    System.out.println("参数输入有误,请重新输入!");
    return;
    }
    //创建一个辅助指针
    Boy helper = first;
    //将helper指向环形链表的最后一个节点
    while (true) {
    if (helper.getNext() == first) {
    break;
    }
    helper = helper.getNext();
    }
    //将first和helper移动到指定的位置 考虑从第几个小孩开始数
    //小孩报数前,先让 first 和 helper 移动 k - 1次
    for (int i = 0; i < startNo - 1; i++) {
    first = first.getNext();
    helper = helper.getNext();
    }
    //让first和helper移动 m-1次,然后出圈
    //循环操作 直到圈中只有一个小孩节点
    while (true){
    if(helper == first){//说明圈中只有一个节点
    break;
    }
    //让first和helper移动 countNum - 1
    for (int i = 0; i < countNum - 1; i++) {
    first = first.getNext();
    helper = helper.getNext();
    }
    //这时first指向的节点就是要出圈的小孩
    System.out.printf("小孩%d出圈\n",first.getNo());
    //将first指向的小孩出圈
    first = first.getNext();
    helper.setNext(first);
    }
    System.out.printf("最后留在圈中小孩编号是:%d \n",helper.getNo());
    }


    /**
    * 遍历当前的环形链表
    */
    public void showBoy() {
    //先判断链表是不是为空
    if (first == null) {
    System.out.println("环形链表为空!");
    return;
    }
    //创建一个辅助指针
    Boy curBoy = first;
    while (true) {
    System.out.printf("小孩的编号:%d \n", curBoy.getNo());
    if (curBoy.getNext() == first) {//说明已经循环一圈了 就不在循环 退出了
    break;
    }
    //向后移动
    curBoy = curBoy.getNext();
    }
    }


    /**
    * 添加小孩节点 构建成一个环形的链表
    *
    * @param nums 传入几个小孩
    */
    public void addBoy(int nums) {
    //将传入的数据进行一个校验
    if (nums < 1) {//传入小孩的的数量不能小于一
    System.out.println("输入的值不正确!");
    return;
    }
    Boy curBoy = null;//辅助指针,帮助构建环形链表
    //使用循环创建我们的环形链表
    for (int i = 1; i <= nums; i++) { //要加入几个小孩 就循环几次
    //根据编号创建小孩节点
    Boy boy = new Boy(i);
    //将小孩加入到环形链表中
    if (i == 1) {//说明是第一个小孩
    first = boy;
    first.setNext(first);//添加的第一个节点 先自己指向自己 构建成一个环
    curBoy = first; //first节点我们不能动 使用辅助节点
    } else {
    //这是在循环里面 每次循环之后 小孩节点都不一样
    curBoy.setNext(boy);
    boy.setNext(first); //形成回路
    curBoy = boy;//curBoy每次指向的都是最后一个小孩节点
    }
    }
    }

    }

    //创建一个boy类 表示一个节点
    class Boy {
    private int no; //编号
    private Boy next;//指向下一个节点 默认是null

    public Boy(int no) {
    this.no = no;
    }

    public int getNo() {
    return no;
    }

    public void setNo(int no) {
    this.no = no;
    }

    public Boy getNext() {
    return next;
    }

    public void setNext(Boy next) {
    this.next = next;
    }
    }
    + + + + + +

    五.栈

    1.介绍

    1)栈的英文为(stack)

    +

    2)栈是一个先入后出(FILO-First In Last Out)的有序列表。

    +

    3)栈(stack)是限制线性表中元素的插入和删除只能在线性表的同一端进行的一种特殊线性表。允许插入和删除的一端,为变化的一端,称为栈顶(Top),另一端为固定的一端,称为栈底(Bottom)。

    +

    4)根据栈的定义可知,最先放入栈中元素在栈底,最后放入的元素在栈顶,而删除元素刚好相反,最后放入的元素最先删除,最先放入的元素最后删除

    +

    2.应用场景

    1)子程序的调用:在跳往子程序前,会先将下个指令的地址存到堆栈中,直到子程序执行完后再将地址取出,以回到原来的程序中。

    +

    2)处理递归调用:和子程序的调用类似,只是除了储存下一个指令的地址外,也将参数、区域变量等数据存入堆栈中。

    +

    3)表达式的转换[中缀表达式转后缀表达式]与求值(实际解决)。

    +

    4)二叉树的遍历。

    +

    5)图形的深度优先(depth一first)搜索法。

    +

    3.图解

    image-20230510101713914

    +

    image-20230510101722707

    +

    4.使用数组模拟栈

    image-20230510101916230

    +
    package com.atguigu.stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/10
    * @Description 使用数组模拟栈
    */
    public class ArrayStackDemo {
    public static void main(String[] args) {
    ArrayStack stack = new ArrayStack(5);
    //入栈
    stack.push(1);
    stack.push(2);
    stack.push(3);
    //出栈
    System.out.println("出栈的数是:"+stack.pop());
    //显示栈中所有的数据
    stack.list();
    }
    }

    //定义一个ArrayStack表示栈
    class ArrayStack {
    private int maxSize;//栈的大小
    private int[] stack;//数组 数组模拟栈 数据放在数组中
    private int top = -1; //top表示栈顶 初始化为1

    //构造器
    public ArrayStack(int maxSize) {
    this.maxSize = maxSize;
    //完成数组的初始化
    stack = new int[maxSize];
    }

    /**
    * 判断是否栈满
    */
    public boolean isFull() {
    return top == maxSize - 1;
    }

    /**
    * 判断栈是否为空
    */
    public boolean isEmpty() {
    return top == -1;
    }


    /**
    * 入栈的操作
    */
    public void push(int value) {
    //先判断栈是否为满
    if (isFull()) {
    System.out.println("栈满,无法添加数据!");
    return;
    }
    top++;
    stack[top] = value;
    }

    /**
    * 出栈的操作
    * 将栈顶的数据返回
    */
    public int pop() {
    //先判断栈是否为空
    if (isEmpty()) {
    throw new RuntimeException("栈空,无法取出数据!");
    }
    //取出数据
    int value = stack[top];
    top--;
    return value;
    }

    /**
    * 显示栈的情况
    */
    public void list() {
    //先判断栈是否为空
    if (isEmpty()) {
    System.out.println("栈空,无法取出数据!");
    return;
    }
    for (int i = top; i >= 0; i--) {
    System.out.printf("stack[%d] = %d \n", i, stack[i]);
    }
    }

    }
    + +

    5.栈实现综合计算器

    中缀表达式

    +

    image-20230510105251076

    +

    image-20230510110403055

    +
    package com.atguigu.stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/10
    * @Description 综合计算器
    */
    public class Calculator {
    public static void main(String[] args) {
    /**
    * 1. 通过一个 index 值(索引),来遍历我们的表达式
    * 2. 如果我们发现是一个数字, 就直接入数栈
    * 3. 如果发现扫描到的是一个符号, 就分如下情况
    * 3.1 如果发现当前的符号栈为 空,就直接入栈
    * 3.2 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符, 就需要从数栈中pop出两个数,在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈, 如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
    * 4. 当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
    * 5. 最后在数栈只有一个数字,就是表达式的结果
    */
    String expression = "30+2*6-2";
    //创建两个栈 一个是数栈 一个是符号栈
    ArrayStack2 numStack = new ArrayStack2(10);
    ArrayStack2 operStack = new ArrayStack2(10);
    //定义需要的相关变量
    int index = 0;//用于扫描
    int num1 = 0;
    int num2 = 0;
    int oper = 0;
    int result = 0;
    char ch = ' '; //将每次扫描得到的char保存在ch中
    String keepNum = "";//用于拼接多位数的
    while (true) {
    //得到expression的每一个字符
    ch = expression.substring(index, index + 1).charAt(0);
    //判断ch是什么 做相应的处理
    if (operStack.isOper(ch)) {
    //先符号栈判断是不是为空
    if (!operStack.isEmpty()) { //判断符号栈是否为空
    //不为空的情况
    if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
    //如果当前的操作符的优先级小于或者等于栈中的操作符, 就需要从数栈中pop出两个数,
    // 在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈
    num1 = numStack.pop();
    num2 = numStack.pop();
    oper = operStack.pop();
    result = numStack.cal(num1, num2, oper);
    //将运算的结果入数栈
    numStack.push(result);
    //将当前的操作符入符号栈
    operStack.push(ch);
    } else {
    //如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
    operStack.push(ch);
    }
    } else {
    //如果为空 直接入栈
    operStack.push(ch);
    }
    } else {
    //如果是数字 就直接入数栈
    //要考虑是多位数的情况
    keepNum += ch;
    //如果ch是表达式的最后一位 就直接入栈
    if (index == expression.length() - 1) {
    numStack.push(Integer.parseInt(keepNum));
    } else {
    //判断下一个字符是不是数字 如果是数字 就继续扫描 如果是运算符 则入栈
    if (operStack.isOper(expression.substring(index + 1, index + 2).charAt(0))) {//下一位是操作符
    //直接将当前为添加到数栈
    numStack.push(Integer.parseInt(keepNum));
    //清空keepNum
    keepNum = "";
    }
    }
    }
    //让index + 1 ,并判断是否扫描到表达式的最后
    index++;
    if (index >= expression.length()) {
    break;
    }
    }
    //当表达式扫描完毕,就顺序的从 数栈和符号栈中pop出相应的数和符号,并运行.
    while (true) {
    //如果符号栈为空 则计算结束 数栈中只有 一个数字
    if (operStack.isEmpty()) {
    break;
    }
    num1 = numStack.pop();
    num2 = numStack.pop();
    oper = operStack.pop();
    result = numStack.cal(num1, num2, oper);
    numStack.push(result);
    }
    System.out.printf("表达式:%s = %d", expression, numStack.pop());
    }
    }

    //定义一个ArrayStack表示栈
    class ArrayStack2 {
    private int maxSize;//栈的大小
    private int[] stack;//数组 数组模拟栈 数据放在数组中
    private int top = -1; //top表示栈顶 初始化为1

    //构造器
    public ArrayStack2(int maxSize) {
    this.maxSize = maxSize;
    //完成数组的初始化
    stack = new int[maxSize];
    }

    /**
    * 判断是否栈满
    */
    public boolean isFull() {
    return top == maxSize - 1;
    }

    /**
    * 判断栈是否为空
    */
    public boolean isEmpty() {
    return top == -1;
    }


    /**
    * 入栈的操作
    */
    public void push(int value) {
    //先判断栈是否为满
    if (isFull()) {
    System.out.println("栈满,无法添加数据!");
    return;
    }
    top++;
    stack[top] = value;
    }

    /**
    * 出栈的操作
    * 将栈顶的数据返回
    */
    public int pop() {
    //先判断栈是否为空
    if (isEmpty()) {
    throw new RuntimeException("栈空,无法取出数据!");
    }
    //取出数据
    int value = stack[top];
    top--;
    return value;
    }

    /**
    * 显示栈的情况
    */
    public void list() {
    //先判断栈是否为空
    if (isEmpty()) {
    System.out.println("栈空,无法取出数据!");
    return;
    }
    for (int i = top; i >= 0; i--) {
    System.out.printf("stack[%d] = %d \n", i, stack[i]);
    }
    }


    /**
    * 返回运算符的优先级
    * 优先级由数字表示
    * 数字大 优先级高
    */
    public int priority(int oper) {
    if (oper == '*' || oper == '/') {
    return 1;
    } else if (oper == '+' || oper == '-') {
    return 0;
    } else {
    return -1;
    }
    }


    /**
    * 判断是不是一个运算符
    */
    public boolean isOper(char val) {
    return val == '+' || val == '-' || val == '*' || val == '/';
    }

    /**
    * 计算方法
    */
    public int cal(int num1, int num2, int oper) {
    int result = 0;
    switch (oper) {
    case '+':
    result = num1 + num2;
    break;
    case '-':
    result = num2 - num1; //注意顺序
    break;
    case '*':
    result = num1 * num2;
    break;
    case '/':
    result = num2 / num1;
    }
    return result;
    }

    /**
    * 查看栈顶数据的方法
    * 返回栈顶的值 不是pop出来
    */
    public int peek() {
    return stack[top];
    }


    }
    + +

    6.逆波兰计算器的设计与实现

    逆波兰表达式(后缀表达式)

    +

    1)输入一个逆波兰表达式(后缀表达式),使用栈(Stack),计算其结果

    +

    2)支持小括号和多位数整数,因为这里我们主要讲的是数据结构,因此计算器进行简化,只支持对整数的计算。

    +
    package com.atguigu.stack;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/11
    * @Description 逆波兰计算器的设计与实现
    */
    public class PolandNotation {
    public static void main(String[] args) {
    //先定义一个逆波兰表达式
    //(3+4)×5-6 的逆波兰表达式是 3 4 + 5 × 6 -
    //为了方便 逆波兰表达式的数字和符号有空格隔开
    String suffixExpression = "3 4 + 5 * 6 -";//中间有空格隔开
    //1.先将suffixExpression放入到List集合中
    //2.将List集合传递给一个方法 配合栈 完成计算
    List<String> rpnList = getListString(suffixExpression);
    System.out.println("计算的结果是:"+calculate(rpnList));


    }

    //将一个逆波兰表达式依次将数据和运算符放入到List集合中
    public static List<String> getListString(String suffixExpression) {
    //将suffixExpression按照空格分割
    String[] strings = suffixExpression.split(" ");
    List<String> list = new ArrayList<>();
    for (String string : strings) {
    list.add(string);
    }
    return list;
    }

    /**
    * 从左至右扫描,将3和4压入堆栈;
    * 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
    * 将5入栈;
    * 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
    * 将6入栈;
    * 最后是-运算符,计算出35-6的值,即29,由此得出最终结果
    */
    public static int calculate(List<String> list) {
    //创建一个栈
    Stack<String> stack = new Stack<>();
    //遍历list
    for (String item : list) {
    //使用正则表达式取出数字
    if (item.matches("\\d+")) { //匹配的是多位数
    //入栈
    stack.push(item);
    } else {
    //弹出两个数 并运算 再入栈
    int num2 = Integer.parseInt(stack.pop());
    int num1 = Integer.parseInt(stack.pop());
    //运算
    int res = 0;
    if (item.equals("+")) {
    res = num1 + num2;
    } else if (item.equals("-")) {
    res = num1 - num2;
    } else if (item.equals("*")) {
    res = num1 * num2;
    } else if (item.equals("/")) {
    res = num1 / num2;
    }else {
    throw new RuntimeException("运算符有误!");
    }
    //把结果入栈
    stack.push(res + "");
    }
    }
    //最后留在stack中的数就是运算结果
    return Integer.parseInt(stack.pop());
    }
    }
    + +

    将中缀表达式转化为后缀表达式(逆波兰表达式)

    +

    解题的步骤:

    +

    1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;

    +

    2)从左至右扫描中缀表达式;

    +

    3)遇到操作数时,将其压s2;

    +

    4)遇到运算符时,比较其与s1栈顶运算符的优先级:

    +

    ​ (1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;

    +

    ​ (2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;

    +

    ​ (3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较

    +

    5)遇到括号时:
    (1) 如果是左括号“(”,则直接压入s1
    (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃

    +

    6)重复步骤2至5,直到表达式的最右边

    +

    7)将s1中剩余的运算符依次弹出并压入s2

    +

    8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

    +

    image-20230511094011546

    +

    先将中缀表达式转化成list集合

    +
    /**
    * 将中缀表达式转化成对应的list
    * @param expression 中缀表达式
    * @return 中缀表达式转化成的一个list集合
    */
    public static List<String> toInfixExpressionList(String expression) {
    //定义一个集合,存储中缀表达式对应的list集合
    List<String> list = new ArrayList<>();
    int i = 0;//这个是一个指针 用于遍历中缀表达式的字符串
    String str;//做多位数的拼接工作 ,因为我们要考虑多位数的情况
    char c;//每遍历一个字符就放入到c中
    do {
    if ((c = expression.charAt(i)) < 48 || (c = expression.charAt(i)) > 57) { //如果c是一个非数字的字符需要加入到ls中
    list.add(c + ""); //直接将这个字符添加到list集合中
    i++;
    } else {//如果是一个数 要考虑多位数的问题
    str = "";//先将str置空
    while (i < expression.length() && (c = expression.charAt(i)) >= 48 && (c = expression.charAt(i)) <= 57) {
    str += c;
    i++;
    }
    list.add(str);
    }
    } while (i < expression.length());
    return list;
    }
    + +

    将中缀表达式的集合转化成逆波兰表达式(后缀表达式)的集合

    +
    /**
    * 将中缀表达式对应的List转化成后缀表达式对应的list
    * tips:
    * 1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;
    * 2)从左至右扫描中缀表达式;
    * 3)遇到操作数时,将其压s2;
    * 4)遇到运算符时,比较其与s1栈顶运算符的优先级:
    * (1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    * (2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    * (3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较
    * 5)遇到括号时:
    * (1) 如果是左括号“(”,则直接压入s1
    * (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    * 6)重复步骤2至5,直到表达式的最右边
    * 7)将s1中剩余的运算符依次弹出并压入s2
    * 8)依次弹出s2中的元素并输出,**结果的逆序即为中缀表达式对应的后缀表达式**
    *
    * @param ls 中缀表达式对应的list
    * @return 后缀表达式对应的list
    */
    public static List<String> parseSuffixExpressionList(List<String> ls) {
    //定义栈
    Stack<String> s1 = new Stack<>();//符号栈 s1栈
    //说明:因为思路分析中使用的s2的栈在整个的运算的过程中没有进行pop的操作 我们直接使用list集合代替s2栈 (同时也为了方便后面逆序的输出)
    ArrayList<String> s2 = new ArrayList<>();//用于存储中间结果的list
    //遍历ls
    for (String item : ls) {
    //如果是一个数 就加入到s2栈
    if (item.matches("\\d+")) {//正则匹配
    s2.add(item);
    } else if (item.equals("(")) { //如果是s1的话就直接入符号栈
    s1.push(item);
    } else if (item.equals(")")) {
    //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    while (!s1.peek().equals("(")) {
    s2.add(s1.pop());
    }
    s1.pop();//将(弹出s1栈,这里的操作就是消除括号
    } else {//加减乘除的操作
    //当item的优先级小于栈顶的优先级,
    // 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
    while (s1.size() != 0 && getVal(s1.peek()) >= getVal(item)) {
    s2.add(s1.pop());
    }
    //还需要将item压入栈中
    s1.push(item);
    }
    }
    //将s1中剩余的运算符依次弹出并加入s2
    while (s1.size() != 0) {
    s2.add(s1.pop());
    }
    return s2;
    }
    + +

    通过逆波兰表达式(后缀表达式)的集合得到最终的计算结果

    +
    /**
    * 通过一个后缀表达式的list集合得到表达式最终的计算结果
    * @param list 后缀表达式的list集合
    * @return 根据后缀表达式的集合计算出的表达式的结果信息
    */
    public static int calculate(List<String> list) {
    //创建一个栈
    Stack<String> stack = new Stack<>();
    //遍历list
    for (String item : list) {
    //使用正则表达式取出数字
    if (item.matches("\\d+")) { //匹配的是多位数
    //入栈
    stack.push(item);
    } else {
    //弹出两个数 并运算 再入栈
    int num2 = Integer.parseInt(stack.pop());
    int num1 = Integer.parseInt(stack.pop());
    //运算
    int res = 0;
    if (item.equals("+")) {
    res = num1 + num2;
    } else if (item.equals("-")) {
    res = num1 - num2;
    } else if (item.equals("*")) {
    res = num1 * num2;
    } else if (item.equals("/")) {
    res = num1 / num2;
    } else {
    throw new RuntimeException("运算符有误!");
    }
    //把结果入栈
    stack.push(res + "");
    }
    }
    //最后留在stack中的数就是运算结果
    return Integer.parseInt(stack.pop());
    }
    + +

    完整的代码

    +

    (不支持小数)

    +
    package com.atguigu.stack;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Stack;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/11
    * @Description 逆波兰计算器的设计与实现
    */
    public class PolandNotation {
    public static void main(String[] args) {
    //先定义一个逆波兰表达式
    //(3+4)×5-6 的逆波兰表达式是 3 4 + 5 × 6 -
    //为了方便 逆波兰表达式的数字和符号有空格隔开
    String suffixExpression = "3 4 + 5 * 6 -";//中间有空格隔开
    //1.先将suffixExpression放入到List集合中
    //2.将List集合传递给一个方法 配合栈 完成计算
    List<String> rpnList = getListString(suffixExpression);
    System.out.println("计算的结果是:" + calculate(rpnList));

    //测试将中缀表达式转化成逆波兰表达式
    //完成一个将中缀表达式 转换为一个后缀表达式
    //1. 1+((2+3)×4)-5(中缀表达式) -> 1 2 3 + 4 × + 5 –(后缀表达式)
    //2. 将中缀表达式转化成List集合
    String expression = "1+((2+3)*4)-5";
    List<String> list = toInfixExpressionList(expression);
    System.out.println("中缀表达式对应的List:" + list);
    //3.将得到的中缀表达式对应的List 转换成一个逆波兰表达式(后缀表达式)对应的List
    List<String> stringList = parseSuffixExpressionList(list);
    System.out.println("中缀表达式对应的List:" + stringList);
    System.out.println("计算的结果:" + calculate(stringList));


    }

    /**
    * 输入一个运算符 返回对应的优先级
    */
    public static int getVal(String operation) {
    int res = 0;
    switch (operation) {
    case "+":
    case "-":
    res = 1;
    break;
    case "/":
    case "*":
    res = 2;
    break;
    default:
    break;
    }
    return res;
    }

    /**
    * 将中缀表达式对应的List转化成后缀表达式对应的list
    * tips:
    * 1)初始化两个栈:运算符栈s1和储存中间结果的栈s2;
    * 2)从左至右扫描中缀表达式;
    * 3)遇到操作数时,将其压s2;
    * 4)遇到运算符时,比较其与s1栈顶运算符的优先级:
    * (1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    * (2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
    * (3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较
    * 5)遇到括号时:
    * (1) 如果是左括号“(”,则直接压入s1
    * (2) 如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    * 6)重复步骤2至5,直到表达式的最右边
    * 7)将s1中剩余的运算符依次弹出并压入s2
    * 8)依次弹出s2中的元素并输出,**结果的逆序即为中缀表达式对应的后缀表达式**
    *
    * @param ls 中缀表达式对应的list
    * @return 后缀表达式对应的list
    */
    public static List<String> parseSuffixExpressionList(List<String> ls) {
    //定义栈
    Stack<String> s1 = new Stack<>();//符号栈 s1栈
    //说明:因为思路分析中使用的s2的栈在整个的运算的过程中没有进行pop的操作 我们直接使用list集合代替s2栈 (同时也为了方便后面逆序的输出)
    ArrayList<String> s2 = new ArrayList<>();//用于存储中间结果的list
    //遍历ls
    for (String item : ls) {
    //如果是一个数 就加入到s2栈
    if (item.matches("\\d+")) {//正则匹配
    s2.add(item);
    } else if (item.equals("(")) { //如果是s1的话就直接入符号栈
    s1.push(item);
    } else if (item.equals(")")) {
    //如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
    while (!s1.peek().equals("(")) {
    s2.add(s1.pop());
    }
    s1.pop();//将(弹出s1栈,这里的操作就是消除括号
    } else {//加减乘除的操作
    //当item的优先级小于栈顶的优先级,
    // 将s1栈顶的运算符弹出并加入到s2中,再次转到(4.1)与s1中新的栈顶运算符相比较
    while (s1.size() != 0 && getVal(s1.peek()) >= getVal(item)) {
    s2.add(s1.pop());
    }
    //还需要将item压入栈中
    s1.push(item);
    }
    }
    //将s1中剩余的运算符依次弹出并加入s2
    while (s1.size() != 0) {
    s2.add(s1.pop());
    }
    return s2;
    }

    /**
    * 将中缀表达式转化成对应的list
    *
    * @param expression 中缀表达式
    * @return 中缀表达式转化成的一个list集合
    */
    public static List<String> toInfixExpressionList(String expression) {
    //定义一个集合,存储中缀表达式对应的list集合
    List<String> list = new ArrayList<>();
    int i = 0;//这个是一个指针 用于遍历中缀表达式的字符串
    String str;//做多位数的拼接工作 ,因为我们要考虑多位数的情况
    char c;//每遍历一个字符就放入到c中
    do {
    if ((c = expression.charAt(i)) < 48 || (c = expression.charAt(i)) > 57) { //如果c是一个非数字的字符需要加入到ls中
    list.add(c + ""); //直接将这个字符添加到list集合中
    i++;
    } else {//如果是一个数 要考虑多位数的问题
    str = "";//先将str置空
    //这个只要遍历的有一个字符不是数字 就会退出while循环
    while (i < expression.length() && (c = expression.charAt(i)) >= 48 && (c = expression.charAt(i)) <= 57) {
    str += c;
    i++;
    }
    list.add(str);
    }
    } while (i < expression.length());
    return list;
    }


    //将一个逆波兰表达式依次将数据和运算符放入到List集合中
    public static List<String> getListString(String suffixExpression) {
    //将suffixExpression按照空格分割
    String[] strings = suffixExpression.split(" ");
    List<String> list = new ArrayList<>();
    for (String string : strings) {
    list.add(string);
    }
    return list;
    }

    /**
    * 通过一个后缀表达式的list集合得到表达式最终的计算结果
    *
    * @param list 后缀表达式的list集合
    * @return 根据后缀表达式的集合计算出的表达式的结果信息
    */
    public static int calculate(List<String> list) {
    //创建一个栈
    Stack<String> stack = new Stack<>();
    //遍历list
    for (String item : list) {
    //使用正则表达式取出数字
    if (item.matches("\\d+")) { //匹配的是多位数
    //入栈
    stack.push(item);
    } else {
    //弹出两个数 并运算 再入栈
    int num2 = Integer.parseInt(stack.pop());
    int num1 = Integer.parseInt(stack.pop());
    //运算
    int res = 0;
    if (item.equals("+")) {
    res = num1 + num2;
    } else if (item.equals("-")) {
    res = num1 - num2;
    } else if (item.equals("*")) {
    res = num1 * num2;
    } else if (item.equals("/")) {
    res = num1 / num2;
    } else {
    throw new RuntimeException("运算符有误!");
    }
    //把结果入栈
    stack.push(res + "");
    }
    }
    //最后留在stack中的数就是运算结果
    return Integer.parseInt(stack.pop());
    }
    }
    + +

    完整的逆波兰计算器,含小数点的计算

    +

    (老师的代码)

    +
    package com.atguigu.stack;


    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.Stack;
    import java.util.regex.Pattern;

    public class ReversePolishMultiCalc {

    /**
    * 匹配 + - * / ( ) 运算符
    */
    static final String SYMBOL = "\\+|-|\\*|/|\\(|\\)";

    static final String LEFT = "(";
    static final String RIGHT = ")";
    static final String ADD = "+";
    static final String MINUS= "-";
    static final String TIMES = "*";
    static final String DIVISION = "/";

    /**
    * 加減 + -
    */
    static final int LEVEL_01 = 1;
    /**
    * 乘除 * /
    */
    static final int LEVEL_02 = 2;

    /**
    * 括号
    */
    static final int LEVEL_HIGH = Integer.MAX_VALUE;


    static Stack<String> stack = new Stack<>();
    static List<String> data = Collections.synchronizedList(new ArrayList<String>());

    /**
    * 去除所有空白符
    * @param s
    * @return
    */
    public static String replaceAllBlank(String s ){
    // \\s+ 匹配任何空白字符,包括空格、制表符、换页符等等, 等价于[ \f\n\r\t\v]
    return s.replaceAll("\\s+","");
    }

    /**
    * 判断是不是数字 int double long float
    * @param s
    * @return
    */
    public static boolean isNumber(String s){
    Pattern pattern = Pattern.compile("^[-\\+]?[.\\d]*$");
    return pattern.matcher(s).matches();
    }

    /**
    * 判断是不是运算符
    * @param s
    * @return
    */
    public static boolean isSymbol(String s){
    return s.matches(SYMBOL);
    }

    /**
    * 匹配运算等级
    * @param s
    * @return
    */
    public static int calcLevel(String s){
    if("+".equals(s) || "-".equals(s)){
    return LEVEL_01;
    } else if("*".equals(s) || "/".equals(s)){
    return LEVEL_02;
    }
    return LEVEL_HIGH;
    }

    /**
    * 匹配
    * @param s
    * @throws Exception
    */
    public static List<String> doMatch (String s) throws Exception{
    if(s == null || "".equals(s.trim())) throw new RuntimeException("data is empty");
    if(!isNumber(s.charAt(0)+"")) throw new RuntimeException("data illeagle,start not with a number");

    s = replaceAllBlank(s);

    String each;
    int start = 0;

    for (int i = 0; i < s.length(); i++) {
    if(isSymbol(s.charAt(i)+"")){
    each = s.charAt(i)+"";
    //栈为空,(操作符,或者 操作符优先级大于栈顶优先级 && 操作符优先级不是( )的优先级 及是 ) 不能直接入栈
    if(stack.isEmpty() || LEFT.equals(each)
    || ((calcLevel(each) > calcLevel(stack.peek())) && calcLevel(each) < LEVEL_HIGH)){
    stack.push(each);
    }else if( !stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek())){
    //栈非空,操作符优先级小于等于栈顶优先级时出栈入列,直到栈为空,或者遇到了(,最后操作符入栈
    while (!stack.isEmpty() && calcLevel(each) <= calcLevel(stack.peek()) ){
    if(calcLevel(stack.peek()) == LEVEL_HIGH){
    break;
    }
    data.add(stack.pop());
    }
    stack.push(each);
    }else if(RIGHT.equals(each)){
    // ) 操作符,依次出栈入列直到空栈或者遇到了第一个)操作符,此时)出栈
    while (!stack.isEmpty() && LEVEL_HIGH >= calcLevel(stack.peek())){
    if(LEVEL_HIGH == calcLevel(stack.peek())){
    stack.pop();
    break;
    }
    data.add(stack.pop());
    }
    }
    start = i ; //前一个运算符的位置
    }else if( i == s.length()-1 || isSymbol(s.charAt(i+1)+"") ){
    each = start == 0 ? s.substring(start,i+1) : s.substring(start+1,i+1);
    if(isNumber(each)) {
    data.add(each);
    continue;
    }
    throw new RuntimeException("data not match number");
    }
    }
    //如果栈里还有元素,此时元素需要依次出栈入列,可以想象栈里剩下栈顶为/,栈底为+,应该依次出栈入列,可以直接翻转整个stack 添加到队列
    Collections.reverse(stack);
    data.addAll(new ArrayList<>(stack));

    System.out.println(data);
    return data;
    }

    /**
    * 算出结果
    * @param list
    * @return
    */
    public static Double doCalc(List<String> list){
    Double d = 0d;
    if(list == null || list.isEmpty()){
    return null;
    }
    if (list.size() == 1){
    System.out.println(list);
    d = Double.valueOf(list.get(0));
    return d;
    }
    ArrayList<String> list1 = new ArrayList<>();
    for (int i = 0; i < list.size(); i++) {
    list1.add(list.get(i));
    if(isSymbol(list.get(i))){
    Double d1 = doTheMath(list.get(i - 2), list.get(i - 1), list.get(i));
    list1.remove(i);
    list1.remove(i-1);
    list1.set(i-2,d1+"");
    list1.addAll(list.subList(i+1,list.size()));
    break;
    }
    }
    doCalc(list1);
    return d;
    }

    /**
    * 运算
    * @param s1
    * @param s2
    * @param symbol
    * @return
    */
    public static Double doTheMath(String s1,String s2,String symbol){
    Double result ;
    switch (symbol){
    case ADD : result = Double.valueOf(s1) + Double.valueOf(s2); break;
    case MINUS : result = Double.valueOf(s1) - Double.valueOf(s2); break;
    case TIMES : result = Double.valueOf(s1) * Double.valueOf(s2); break;
    case DIVISION : result = Double.valueOf(s1) / Double.valueOf(s2); break;
    default : result = null;
    }
    return result;

    }

    public static void main(String[] args) {
    //String math = "9+(3-1)*3+10/2";
    String math = "12.8 + (2 - 3.55)*4+10/5.0";
    try {
    doCalc(doMatch(math));
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }
    + +

    六.递归

    1.简单介绍

    递归就是方法自己调用自己,每次调用时传入不同的变量,递归有助于编程者解决复杂的问题,同时可以让代码变得简洁

    +

    image-20230511123527496

    +

    2.入门案例

    image-20230511123245408

    +

    3.递归解决的问题

    1)各种数学问题如: 8皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google编程大赛)

    +

    2)各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.

    +

    3)将用栈解决的问题–>第归代码比较简洁

    +

    4.递归遵循的规则

    1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)

    +

    2)方法的局部变量是独立的,不会相互影响, 比如n变量

    +

    3)如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.

    +

    4)递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,死龟了:)

    +

    5)当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。

    +

    5.递归的实际应用

    5.1递归解决迷宫问题

    image-20230512092144712

    +

    代码实现

    +
    package com.atguigu.recursion;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/12
    * @Description 递归解决迷宫问题
    */
    public class MiGong {
    public static void main(String[] args) {
    //创建一个二维数组模拟迷宫
    //地图
    int[][] map = new int[8][7];
    //使用1表示墙的位置
    //把四周变成墙
    //将上下变成墙
    for (int i = 0; i < 7; i++) {
    map[0][i] = 1;
    map[7][i] = 1;
    }
    //将左右变成墙
    for (int i = 0; i < 8; i++) {
    map[i][0] = 1;
    map[i][6] = 1;
    }
    //设置自定义的墙的位置
    map[3][1] = 1;
    map[3][2] = 1;

    //输出地图
    showMap(map);
    //使用递归回溯给小球找路
    getWay(map, 1, 1);
    System.out.println("标识过的路");
    showMap(map);


    }

    /**
    * 遍历输出地图的信息
    *
    * @param map 地图组成的二维数组
    */
    public static void showMap(int[][] map) {
    for (int i = 0; i < map.length; i++) {
    for (int j = 0; j < map[i].length; j++) {
    System.out.print(map[i][j] + " ");
    }
    System.out.println();
    }
    }


    /**
    * 1.表示墙 2.表示通路 可以走 3.表示该位置已经走过 但是走不通
    * 在就走迷宫的时候我们要确定一个策略 比如下->右->上->左 如果该点走不通 再回溯
    * 使用递归的方法给小球找路
    *
    * @param map 传进来的地图信息
    * @param i 从哪个位置开始找
    * @param j 从哪个位置开始找
    * @return 是否找到路
    */
    public static boolean getWay(int[][] map, int i, int j) {
    if (map[6][5] == 2) { //说明通路已经找到了 就直接退出
    return true;
    } else {
    if (map[i][j] == 0) {//如果当前这个点还没有走过
    //按照策略走 下->右->上->左
    map[i][j] = 2;//先假定这个点可以走通
    if (getWay(map, i + 1, j)) { //向下走
    return true;
    } else if (getWay(map, i, j + 1)) {//向右走
    return true;
    } else if (getWay(map, i - 1, j)) {//向上走
    return true;
    } else if (getWay(map, i, j - 1)) {//向左走
    return true;
    } else {//四个方向都走不通的话说明不是一个通路 设置成false
    map[i][j] = 3;
    return false;
    }
    } else {//不为0 可以是1,2,3
    //可以理解为只走没有走过的
    return false;
    }

    }
    }


    }
    + + + +

    5.2 递归解决八皇后问题

    使用回溯算法解决 类似于穷举法 后期使用别的算法优化

    +

    问题介绍

    +

    image-20230512112050729

    +

    思路分析

    +

    image-20230512112456731

    +

    代码实现

    +
    package com.atguigu.recursion;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/13
    * @Description 八皇后问题的解题思路及代码实现
    */
    public class Queue8 {
    public static void main(String[] args) {
    Queue8 queue8 = new Queue8();
    queue8.check(0);
    }

    //定义一个max表示共有多少个皇后
    int max = 8;
    //定义一个一维数组 记录皇后在列上的位置 arr[8] = {0 , 4, 7, 5, 2, 6, 1, 3}
    int[] array = new int[max];
    //记录摆法的次数
    int count = 0;

    /**
    * 编写一个方法 放置n个皇后
    * 会自己回溯
    */
    public void check(int n) {
    if (n == max) { //说明已经放到了第九个皇后了
    print(); //打印
    return;
    }
    //依次放入皇后 判断是否冲突
    for (int i = 0; i < max; i++) {
    //先把当前的这个皇后n,放到该行的第i列
    array[n] = i;
    //判断当前放置第n个皇后到i列时,与前面放置的皇后是否冲突
    if (judge(n)) { //不冲突
    //接着放n+1个皇后
    check(n + 1);
    }
    //如果冲突 就继续执行 array[n] = i 就皇后在本行后移一个位置
    //因为在循环里面 i++会自增 n会后移
    }
    }

    /**
    * 当我们放置了n个皇后之后 就去检测
    * 该皇后是否和前面已经摆放的皇后冲突
    *
    * @param n 表示第n个皇后
    */
    public boolean judge(int n) {
    for (int i = 0; i < n; i++) {
    // array[8] = {0 , 4, 7, 5, 2, 6, 1, 3} 再次注意这里数组记录的是每个皇后在列上的值
    //array[i] == array[i] 判断是不是在同一列
    //Math.abs(n - i) == Math.abs(array[n] - array[i]) 判断是不是在同一斜线上
    //自己的理解:(n -i) 相当于在行上的距离 array[n] - array[i]相当于在列上的距离 行距离等于列距离 说明二者在同一斜线上
    if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {//与前面放置的位置是否冲突
    return false;
    }
    }
    return true;
    }

    /**
    * 定义一个数组 打印皇后拜访的位置
    */
    public void print() {
    System.out.println("第" + (++count) + "中摆法");
    int[][] arr = new int[max][max];
    for (int i = 0; i < array.length; i++) {
    arr[i][array[i]] = 1;
    }
    for (int[] ints : arr) {
    for (int i : ints) {
    System.out.print(i + " ");
    }
    System.out.println();
    }
    }
    }
    + + + +

    七.排序算法

    十大经典的排序算法:菜鸟教程排序算法

    +

    1.介绍

    image-20230513115319799

    +

    2.时间复杂度

    度量时间复杂度的两种方法

    +

    事后统计法的不足:

    +
      +
    • 需要实际的运行程序 比较耗时
    • +
    • 受计算机硬件和软件的影响
    • +
    +

    image-20230513115606521

    +

    时间频度

    +

    ​ 基本的介绍:一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)

    +

    时间复杂度介绍

    +

    image-20230515093520587

    +

    常见的时间复杂度

    +

    image-20230515093753893

    +

    3.空间复杂度

    image-20230515095247319

    +

    排序算法的时间和空间复杂度

    +

    image-20230516083738747

    +

    4.冒泡排序

    冒泡排序的时间复杂度:o(n^2)

    +

    同一台电脑 8万个数据 十几秒左右

    +

    4.1 基本介绍

    image-20230515100345339

    +

    4.2 图解

    image-20230515100754827

    +

    4.3 代码实现

    优化之前的代码

    +
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/15
    * @Description 冒泡排序
    */
    public class BubbleSort {
    public static void main(String[] args) {
    int arr[] = {3, 9, -1, 10, -2};
    BubbleSort.sort(arr);
    }

    public static void sort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
    int temp = 0; //临时变量
    for (int j = 0; j < arr.length - 1 - i; j++) {
    //如果前面的数字大于后面的数字就交换
    if (arr[j] > arr[j + 1]) {
    temp = arr[j];
    arr[j] = arr[j + 1];
    arr[j + 1] = temp;
    }
    }
    System.out.println("第" + (i + 1) + "次排序:" + Arrays.toString(arr));
    }
    System.out.println("排序之后的数组:" + Arrays.toString(arr));
    }
    }
    + +

    优化之后的代码(如果排序的过程中代码有序就不在排序)

    +
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/15
    * @Description 冒泡排序
    */
    public class BubbleSort {
    public static void main(String[] args) {
    int arr[] = {3, 9, -1, 10, -2};
    BubbleSort.sort(arr);
    }

    /**
    * 冒泡排序
    * @param arr 需要排序的数组
    */
    public static void sort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
    int temp = 0; //临时变量
    boolean flag = false; //表示是否进行过排序
    for (int j = 0; j < arr.length - 1 - i; j++) {
    //如果前面的数字大于后面的数字就交换
    if (arr[j] > arr[j + 1]) {
    flag = true;
    temp = arr[j];
    arr[j] = arr[j + 1];
    arr[j + 1] = temp;
    }
    }
    System.out.println("第" + (i + 1) + "次排序:" + Arrays.toString(arr));
    if(!flag){ //说明一次都没有交换 说明已经有序
    break;
    }
    }
    System.out.println("排序之后的数组:" + Arrays.toString(arr));
    }
    }

    + +

    image-20230516091218716

    +

    5.选择排序

    5.1 基本介绍

    同一台电脑 8万个数据 两秒左右

    +

    选择式排序也属于内部排序法,是从欲排序的数据中,按指定的规则选出某一元素,再依规定交换位置后达到排序的目的

    +

    image-20230515105359552

    +

    5.2图解

    img

    +

    image-20230516083148055

    +

    5.3代码实现

    两种方法 一个是自己的 一个是老师的

    +

    使用老师的代码 老师的代码验证过

    +
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/16
    * @Description 选择排序
    */
    public class SelectSort {
    public static void main(String[] args) {
    int[] arr = {101,34,119,1};
    int[] arr1 = {3, 9, -1, 10, -2};
    int[] arr2 = {3, 9, -1, 10, -2};
    System.out.println("自己的代码");
    SelectSort.selectSort(arr2);
    System.out.println("老师的代码");
    SelectSort.selectSortByTeacher(arr1);
    }

    /**
    * 选择排序
    * 自己写的 中间的过程和老师的不一样 不确定是不是选择排序
    */
    public static void selectSort(int[] arr) {
    for (int i = 0; i < arr.length - 1; i++) {
    for (int j = i + 1; j < arr.length; j++) {//从第一个数字开始 依次找到最小值
    int temp = 0;
    if(arr[i] > arr[j]) {
    temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
    }
    }
    System.out.println("第次"+(i+1)+"排序的结果:"+Arrays.toString(arr));
    }
    System.out.println("最终的结果:"+Arrays.toString(arr));
    }

    /**
    * 老师的代码
    */
    public static void selectSortByTeacher(int[] arr){

    for (int i = 0; i < arr.length - 1; i++) {
    int minIndex = i;
    int min = arr[i];
    for (int j = i + 1; j < arr.length; j++) {//从第一个数字开始 依次找到最小值
    if(min > arr[j]){
    min = arr[j];
    minIndex = j;
    }
    }
    //交换
    arr[minIndex] = arr[i];
    arr[i] = min;
    System.out.println("第次"+(i+1)+"排序的结果:"+Arrays.toString(arr));
    }
    System.out.println("最终的结果:"+Arrays.toString(arr));
    }
    }
    + +

    image-20230516093143656

    +

    6.插入排序

    6.1 基本介绍

    同一台电脑 8万个数据 五秒左右

    +

    插入式排序属于内部排序法,是对于欲排序的元素以插入的方式找寻该元素的适当位置,以达到排序的目的。

    +

    image-20230516100256880

    +

    6.2 图解

    img + +

    6.3 代码实现

    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/16
    * @Description 插入排序
    */
    public class InsertSort {
    public static void main(String[] args) {
    int[] arr = {101, 34, 119, 1,-1,89};
    System.out.println("插入前的数组:"+Arrays.toString(arr));
    InsertSort.insertSort(arr);
    }

    /**
    * 插入排序
    */
    public static void insertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
    //定义待插入的数
    int insertVal = arr[i];
    //待插入的数的前一个数的下标
    int insertIndex = i - 1;
    while (insertIndex >= 0 && insertVal < arr[insertIndex]) {
    arr[insertIndex + 1] = arr[insertIndex];
    insertIndex--;
    }
    //退出循环之后 代表找到了要插入数据的位置
    //插入数据
    arr[insertIndex + 1] = insertVal;
    System.out.println("第" + i + "次插入的结果:" + Arrays.toString(arr));
    }
    System.out.println("最终的结果:" + Arrays.toString(arr));
    }
    }
    + +

    image-20230516105517026

    +

    7.希尔排序

    7.1基本介绍

    同一台电脑 8万个数据 十七秒左右(交换法)

    +

    同一台电脑 8万个数据 一秒左右(移动法)

    +

    image-20230517091741355

    +

    image-20230517091841366

    +

    7.2.图解

    img

    +image-20230517102649272 + +

    7.3 代码实现

    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/17
    * @Description 希尔排序
    */
    public class ShellSort {
    public static void main(String[] args) {
    int[] arr = {8, 9, 1, 7, 2, 3, 5, 4, 6, 0};
    //shellSort(arr);
    shellSort2(arr);
    }

    /**
    * 希尔排序 使用的是交换法 这个很慢 比直接的插入排序还慢
    */
    public static void shellSort(int[] arr) {
    int temp = 0;
    int count = 0;
    //用于分组 10个数据 第一次分5组 第二次分2组 第三次分1组 每组分别插入排序
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
    for (int i = gap; i < arr.length; i++) {
    for (int j = i - gap; j >= 0; j -= gap) {
    //如果当前的那个元素大于加上步长后的那个元素,就交换
    if (arr[j] > arr[j + gap]) {
    temp = arr[j];
    arr[j] = arr[j + gap];
    arr[j + gap] = temp;
    }
    }
    }
    System.out.println("排序第" + (++count) + "轮的结果:" + Arrays.toString(arr));
    }
    System.out.println("最终的结果:" + Arrays.toString(arr));
    }

    /**
    * 使用移动法的希尔排序 这个更快
    */
    public static void shellSort2(int[] arr) {
    //用于分组 10个数据 第一次分5组 第二次分2组 第三次分1组 每组分别插入排序
    int count = 0;
    for (int gap = arr.length / 2; gap > 0; gap /= 2) {
    for (int i = gap; i < arr.length; i++) {
    int j = i;
    int temp = arr[j];
    if (arr[j] < arr[j - gap]) {
    while (j - gap >= 0 && temp < arr[j - gap]) {
    arr[j] = arr[j - gap];
    j -= gap;
    }
    arr[j] = temp; }
    }
    System.out.println("排序第" + (++count) + "轮的结果:" + Arrays.toString(arr));
    }
    System.out.println("最终的结果:" + Arrays.toString(arr));
    }
    }
    + +

    8.快速排序

    8.1 基本介绍

    同一台电脑 8万个数据 不到一秒 800万个数据两秒钟

    +

    快速排序(Quicksort)是对冒泡排序的一种改进。基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列

    +

    8.2 图解

    img + +image-20230517103139900 + +

    8.3 代码实现

    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/17
    * @Description 快速排序
    */
    public class QuickSort {
    public static void main(String[] args) {
    int[] arr = {-9, 78, 0, 23, -567, 70};
    quickSort(arr, 0, arr.length - 1);
    System.out.println(Arrays.toString(arr));
    }

    /**
    * @param arr 需要排序的数组
    * @param left 左下标
    * @param right 右下标
    */
    public static void quickSort(int[] arr, int left, int right) {
    int l = left;
    int r = right;
    //pivot 中轴
    int pivot = arr[(left + right) / 2];
    int temp = 0;
    //while循环的目的是让比pivot小的放到左边 大的放在右边
    while (l < r) {
    //在pivot的左边一直找 找到大于等于pivot的值 才退出
    while (arr[l] < pivot) {
    l += 1;
    }
    //在pivot的左边一直找 找到大于等于pivot的值 才退出
    while (arr[r] > pivot) {
    r -= 1;
    }
    //如果l >= r成立 说明pivot的左右两边的值 已经是按照左边全部是
    //小于等于pivot值 右边全部是大于等于pivot值
    if (l >= r) {
    break;
    }
    //交换
    temp = arr[l];
    arr[l] = arr[r];
    arr[r] = temp;
    //如果交换完之后发现arr[l] = pivot值
    if (arr[l] == pivot) {
    r--;
    }
    //如果交换完之后发现arr[r] = pivot值
    if (arr[r] == pivot) {
    l++;
    }
    }

    //如果l == r 必须l++ r--
    if (l == r) {
    l += 1;
    r -= 1;
    }
    //向左递归
    if (left < r) {//左边的数全部有序
    quickSort(arr, left, r);
    }
    //向右递归
    if (right > l) {
    quickSort(arr, l, right);
    }
    }
    }
    + + + +

    9. 归并排序

    9.1基本介绍

    同一台电脑 8万个数据 大约一秒钟 800万个数据三秒

    +

    image-20230518111233009

    +

    9.2 图解

    img + +

    image-20230518111454775

    +

    image-20230518111536026

    +

    9.3代码实现

    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/18
    * @Description 归并排序
    */
    public class MergeSort {
    public static void main(String[] args) {
    int[] arr = {8, 4, 5, 7, 1, 3, 6, 2};
    int[] temp = new int[arr.length];
    mergeSort(arr,0, arr.length -1,temp);
    System.out.println("归并排序之后的数组:"+ Arrays.toString(arr));
    }

    /**
    * 分治的过程
    */
    public static void mergeSort(int[] arr, int left, int right, int[] temp){
    if(left < right){
    int mid = (left + right) / 2;
    //向左递归分解
    mergeSort(arr,left,mid,temp);
    //向右递归分解
    mergeSort(arr,mid+1,right,temp);
    //合并
    merge(arr,left,mid,right,temp);
    }
    }

    /**
    * 合并的方法 治
    *
    * @param arr 需要排序的数组
    * @param left 右边有序序列的初始索引
    * @param mid 中间索引
    * @param right 右边索引
    * @param temp 中转的数组
    */
    public static void merge(int[] arr, int left, int mid, int right, int[] temp) {
    int i = left;//左边有序序列的初始索引
    int j = mid + 1;//右边有序序列的初始化索引
    int t = 0;//指向temp数组的当前索引

    //1.
    //先把左右两边(有序)的数据填充到temp数组
    //直到左右两边的数据有一边处理完毕为止
    while (i <= mid && j <= right) {//相当于左右两边的数组都有一个指针
    if (arr[i] <= arr[j]) {
    temp[t] = arr[i];
    t++;
    i++;
    } else {//反之将右边有序序列的当前元素 填充到temp数组
    temp[t] = arr[j];
    t++;
    j++;
    }
    }
    //2.
    //把有剩余数据的一边的数据依次全部填充到temp
    while (i <= mid) {//说明左边的有序序列还有剩余的元素
    temp[t] = arr[i];
    t++;
    i++;
    }
    while (j <= right) {
    temp[t] = arr[j];
    t++;
    j++;
    }

    //3.
    //将temp数组的元素拷贝到arr
    //并不是每次都拷贝
    t = 0;
    int tempLeft = left;
    while (tempLeft <= right) {
    arr[tempLeft] = temp[t];
    t++;
    tempLeft++;
    }
    }
    }
    + +

    10.基数排序

    同一台电脑8万个数据 一秒左右 8千万个数据会报内存不足

    +

    10.1 基本介绍

    image-20230525195008102

    +

    image-20230526094945578

    +

    image-20230526113739081

    +

    10.2 图解

    img

    +

    10.3 代码实现

    推导代码

    +
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/26
    * @Description 基数排序
    */
    public class RadixSort {
    public static void main(String[] args) {
    //定义一个待排序的数组
    int arr[] = {53, 3, 542, 748, 14, 214};
    RadixSort.radixSort(arr);
    }

    //基数排序
    public static void radixSort(int[] arr) {
    //1.第一轮(针对每个元素的个位进行排序处理)
    //定义一个二维数组,表示十个桶,每个桶就是一个一维数组
    //为了防止数据的溢出,这里每个桶的大小我们设置的大一些 大小定为arr.length
    int[][] bucket = new int[10][arr.length];
    //为了记录每个桶中实际存放了多少个数据,我们定义一个一维数组来记录每个桶中依次放入数据的个数
    //bucketElementCounts[0]记录的就是第0个桶的数据的个数
    int[] bucketElementCounts = new int[10];
    for (int j = 0; j < arr.length; j++) {
    //取出每个元素的个位数的值
    int digitOfElement = arr[j] % 10;
    //放入对应的桶中
    bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
    //[bucketElementCounts[digitOfElement]]++的意思是在digitOfElement对应的桶中数据个数加一
    bucketElementCounts[digitOfElement]++;
    }
    //按照这个桶中顺序取出数据
    int index = 0;
    for (int k = 0; k < bucketElementCounts.length; k++) {
    //如果桶中有数据,我们才放入到原数组
    if (bucketElementCounts[k] != 0) {
    //循环该桶即第K个桶,放入
    for (int l = 0; l < bucketElementCounts[k]; l++) {
    arr[index++] = bucket[k][l];
    }
    }
    bucketElementCounts[k] = 0;
    }
    System.out.println("第一轮:" + Arrays.toString(arr));

    //2.第2轮
    for (int j = 0; j < arr.length; j++) {
    //取出每个元素的十位数的值
    int digitOfElement = arr[j] / 10 % 10;
    //放入对应的桶中
    bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
    //[bucketElementCounts[digitOfElement]]++的意思是在digitOfElement对应的桶中数据个数加一
    bucketElementCounts[digitOfElement]++;
    }
    //按照这个桶中顺序取出数据
    index = 0;
    for (int k = 0; k < bucketElementCounts.length; k++) {
    //如果桶中有数据,我们才放入到原数组
    if (bucketElementCounts[k] != 0) {
    //循环该桶即第K个桶,放入
    for (int l = 0; l < bucketElementCounts[k]; l++) {
    arr[index++] = bucket[k][l];
    }
    }
    bucketElementCounts[k] = 0;
    }
    System.out.println("第2轮:" + Arrays.toString(arr));

    //3.第3轮
    for (int j = 0; j < arr.length; j++) {
    //取出每个元素的百位数的值
    int digitOfElement = arr[j] / 100 % 10;
    //放入对应的桶中
    bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
    //[bucketElementCounts[digitOfElement]]++的意思是在digitOfElement对应的桶中数据个数加一
    bucketElementCounts[digitOfElement]++;
    }
    //按照这个桶中顺序取出数据
    index = 0;
    for (int k = 0; k < bucketElementCounts.length; k++) {
    //如果桶中有数据,我们才放入到原数组
    if (bucketElementCounts[k] != 0) {
    //循环该桶即第K个桶,放入
    for (int l = 0; l < bucketElementCounts[k]; l++) {
    arr[index++] = bucket[k][l];
    }
    }
    }
    System.out.println("第3轮:" + Arrays.toString(arr));

    }
    }
    + +

    最终代码

    +
    package com.atguigu.sort;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/26
    * @Description 基数排序
    */
    public class RadixSort {
    public static void main(String[] args) {
    //定义一个待排序的数组
    int arr[] = {53, 3, 542, 748, 14, 214};
    RadixSort.radixSort(arr);
    }

    //基数排序
    public static void radixSort(int[] arr) {
    //得到数组中最大数的位数
    int max = arr[0];
    for (int i = 0; i < arr.length; i++) {
    if (arr[i] > max) {
    max = arr[i];
    }
    }
    //最大数的位数
    int maxLength = (max + "").length();
    //定义一个二维数组,表示十个桶,每个桶就是一个一维数组
    int[][] bucket = new int[10][arr.length];
    //为了记录每个桶中实际存放了多少个数据,我们定义一个一维数组来记录每个桶中依次放入数据的个数
    int[] bucketElementCounts = new int[10];
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {//循环的是每一轮,第一次是个位。第二次是百位
    for (int j = 0; j < arr.length; j++) {
    //取出每个元素的对应位数的值
    int digitOfElement = arr[j] / n % 10;
    //放入对应的桶中
    bucket[digitOfElement][bucketElementCounts[digitOfElement]] = arr[j];
    //[bucketElementCounts[digitOfElement]]++的意思是在digitOfElement对应的桶中数据个数加一
    bucketElementCounts[digitOfElement]++;
    }
    //按照这个桶中顺序取出数据
    int index = 0;
    for (int k = 0; k < bucketElementCounts.length; k++) {
    //如果桶中有数据,我们才放入到原数组
    if (bucketElementCounts[k] != 0) {
    //循环该桶即第K个桶,放入
    for (int l = 0; l < bucketElementCounts[k]; l++) {
    arr[index++] = bucket[k][l];
    }
    }
    bucketElementCounts[k] = 0;
    }
    System.out.println("第" + (i + 1) + "轮:" + Arrays.toString(arr));
    }
    }
    }
    + +

    11.常用排序算法总结和对比

    image-20230526114127402

    +

    八.查找算法

    1.简单介绍

    image-20230526115027518 + +

    2.线性查找算法

    2.1代码实现

    package com.atguigu.search;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/29
    * @Description 线性查找
    */
    public class SeqSearch {
    public static void main(String[] args) {
    int[] arr = {1,9,11,-1,34,89};//没有顺序的数组
    System.out.println("目标值的下标:"+seqSearch(arr, 11));
    }

    /**
    * 找到一个满足条件的值就返回
    * 线性查找是逐一比对,发现有相同的值时就返回这个值的下标
    * @param arr 需要在这里面找目标值的数组
    * @param value 目标值
    * @return 目标值的下标
    */
    public static int seqSearch(int[] arr,int value){
    for (int i = 0; i < arr.length; i++) {
    if(arr[i] == value){
    return i;
    }
    }
    return -1;
    }
    }

    + +

    3.二分查找算法

    3.1 图解

    image-20230529104112317 + +

    3.2 代码实现

    递归的方式解决

    +

    基本的写法

    +
    package com.atguigu.search;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/29
    * @Description 二分查找
    */
    public class BinarySearch {
    public static void main(String[] args) {
    int[] arr = {1, 8, 10, 89, 1000, 1234};
    System.out.println("目标值的下标:" + binarySearch(arr, 0, arr.length - 1, 3));
    }

    /**
    * 二分查找
    *
    * @param arr 需要在这里面查找目标值的数组
    * @param left 左边的索引
    * @param right 右边的索引
    * @param findVal 要查找的值
    * @return 目标值在数组中的下标,找到就返回下标,没有就返回-1
    */
    public static int binarySearch(int[] arr, int left, int right, int findVal) {
    //结束递归的条件
    if (left > right) {
    return -1;
    }
    int mid = (left + right) / 2;
    int midVal = arr[mid];
    if (findVal > midVal) {//向右递归
    return binarySearch(arr, mid + 1, right, findVal);
    } else if (findVal < midVal) {//向左递归
    return binarySearch(arr, left, mid - 1, findVal);
    } else {
    return mid;
    }
    }

    }
    + +

    完整的代码

    +
    package com.atguigu.search;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/29
    * @Description 二分查找
    */
    public class BinarySearch {
    public static void main(String[] args) {
    int[] arr = {1, 8, 10, 89, 1000, 1234};
    System.out.println("目标值的下标:" + binarySearch(arr, 0, arr.length - 1, 3));
    int[] arr2 = {1, 8, 10, 89, 89, 89, 1000, 1234};
    System.out.println("目标值在数组中下标的集合:" + binarySearch2(arr2, 0, arr2.length - 1, 89));
    }

    /**
    * 二分查找
    *
    * @param arr 需要在这里面查找目标值的数组
    * @param left 左边的索引
    * @param right 右边的索引
    * @param findVal 要查找的值
    * @return 目标值在数组中的下标,找到就返回下标,没有就返回-1
    */
    public static int binarySearch(int[] arr, int left, int right, int findVal) {
    //结束递归的条件
    if (left > right) {
    return -1;
    }
    int mid = (left + right) / 2;
    int midVal = arr[mid];
    if (findVal > midVal) {//向右递归
    return binarySearch(arr, mid + 1, right, findVal);
    } else if (findVal < midVal) {//向左递归
    return binarySearch(arr, left, mid - 1, findVal);
    } else {
    return mid;
    }
    }

    /**
    * 二分查找查找出所有与目标值相同的数组的下标
    *
    * @param arr 需要在这里面查找目标值的数组
    * @param left 左边的索引
    * @param right 右边的索引
    * @param findVal 要查找的的值
    * @return 目标值在数组中的下标的集合
    */
    public static List<Integer> binarySearch2(int[] arr, int left, int right, int findVal) {
    //结束递归的条件
    if (left > right) {
    return new ArrayList<>();
    }
    int mid = (left + right) / 2;
    int midVal = arr[mid];
    if (findVal > midVal) {//向右递归
    return binarySearch2(arr, mid + 1, right, findVal);
    } else if (findVal < midVal) {//向左递归
    return binarySearch2(arr, left, mid - 1, findVal);
    } else {
    List<Integer> resList = new ArrayList<>();
    int temp = mid - 1;
    while (true) { //向左边探测相同值的元素
    if (temp < 0 || arr[temp] != findVal) {
    break;
    }
    //否则就把temp放入到集合中
    resList.add(temp);
    temp--;//左移
    }
    resList.add(mid);
    //向右边探测
    temp = mid + 1;
    while (true) { //向右边探测相同值的元素
    if (temp > arr.length - 1 || arr[temp] != findVal) {
    break;
    }
    //否则就把temp放入到集合中
    resList.add(temp);
    temp++;//右移
    }
    return resList;
    }
    }

    }
    + +

    4.插值查找算法

    4.1 图解

    +image-20230530101803392 + +

    4.2 代码实现

    package com.atguigu.search;

    import java.util.Arrays;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/30
    * @Description 插值查找算法
    */
    public class InsertValueSearch {
    public static void main(String[] args) {
    //创建一个数组 用于模拟需要查找数据的数组
    int[] arr = new int[100];
    for (int i = 0; i < 100; i++) {
    arr[i] = i + 1;
    }
    System.out.println(Arrays.toString(arr));
    System.out.println("查找的数的下标为:" + insertValueSearch(arr, 0, arr.length - 1, 30));
    }

    /**
    * 插值查找算法
    *
    * @param arr 目标数组(有序的)等差序列的最好,1-100有序的数组,找一个数只用找一次就行了
    * @param left 左边索引
    * @param right 右边索引
    * @param findVal 需要查找的值
    * @return 查找的值在数组中的索引
    */
    public static int insertValueSearch(int[] arr, int left, int right, int findVal) {
    System.out.println("方法被调用了");
    //退出的条件和防止数组越界
    if (left > right || findVal < arr[0] || findVal > arr[arr.length - 1]) {
    return -1;
    }
    //求出mid,这个是插值查找算法的灵魂,自适应的查找
    int mid = left + (right - left) * (findVal - arr[left]) / (arr[right] - arr[left]);
    int midVal = arr[mid];
    if (findVal > midVal) { //应向右递归
    return insertValueSearch(arr, mid + 1, right, findVal);
    } else if (findVal < midVal) {//向左边递归
    return insertValueSearch(arr, left, mid - 1, findVal);
    } else {
    return mid;
    }
    }
    }
    + +

    4.3 注意事项

    image-20230530105652529

    +

    5.斐波那契(黄金分割法)查找算法

    5.1 基本介绍

    image-20230530110145048 + +

    5.2 原理介绍

    image-20230530110511769

    +
    +

    PDF笔记

    + +
    + +
    + + + + + + + + + + + + + + + + + + + + +]]>
    + + 后端 + + + 数据结构与算法 + +
    + + SpringBoot入门教程 + /posts/60684.html + 尚硅谷雷丰阳的SpringBoot零基础入门教程

    +

    本视频笔记地址:https://yuque.com/atguigu/springboot

    +

    本视频源码地址:https://gitee.com/leifengyang/springboot2

    +

    Spring官网: https://spring.io/

    +

    SpringBoot2的环境要求

    +

    image-20230620092919922

    +

    一.SpringBoot2核心技术-基础入门

    1.Spring能做什么

    这里的Spring指的是整个Spring生态

    +

    image-20230620093420150

    +

    微服务 响应式编程 分布式 WEB开发 无服务开发 事件驱动 批处理

    +

    2.SpringBoot

    Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.

    +

    能快速创建出生产级别的Spring应用

    +

    SpringBoot是整合Spring技术栈的一站式框架

    +

    SpringBoot是简化Spring技术栈的快速开发脚手架

    +

    2.1 SpringBoot的优点

    ● Create stand-alone Spring applications
    ○ 创建独立Spring应用
    ● Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
    ○ 内嵌web服务器
    ● Provide opinionated ‘starter’ dependencies to simplify your build configuration
    ○ 自动starter依赖,简化构建配置
    ● Automatically configure Spring and 3rd party libraries whenever possible
    ○ 自动配置Spring以及第三方功能
    ● Provide production-ready features such as metrics, health checks, and externalized configuration
    ○ 提供生产级别的监控、健康检查及外部化配置
    ● Absolutely no code generation and no requirement for XML configuration
    ○ 无代码生成、无需编写XML

    +

    2.2、SpringBoot缺点

      +
    • 人称版本帝,迭代快,需要时刻关注变化(说明社区活跃,也是个优点)
    • +
    • 封装太深,内部原理复杂,不容易精通
    • +
    +

    2.3 官方文档

    进入官网 找到SpringBoot 点击Reference Doc进入官方文档

    +

    image-20230620101533445

    +

    3.SpringBoot入门

    1.系统的要求

      +
    • Java 8 & 兼容java14 .
    • +
    • Maven 3.3+
    • +
    • idea 2019.1.2
    • +
    +

    **Maven的配置教程: **

    +

    Maven配置文件settings.xml | The Blog (gitee.io)

    +

    开发环境的搭建 | The Blog (gitee.io)

    +

    2.HelloWorld

    需求:浏览发送/hello请求,响应 Hello,Spring Boot 2

    +

    1创建maven工程

    +

    image-20230725160846882

    +

    2.引入依赖

    +
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
    </parent>
    <!-- WEB场景的启动器 -->
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    </dependencies>
    + +

    3.创建主程序

    +
    package com.atguigu.boot;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/25
    * @Description
    */
    @SpringBootApplication //标注这是一个SpringBoot应用
    public class MainApplication {
    public static void main(String[] args) {
    SpringApplication.run(MainApplication.class,args);
    }
    }
    + +

    4.编写Controller

    +
    package com.atguigu.boot.controller;

    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/25
    * @Description
    */
    @RestController
    public class HelloController {

    @RequestMapping
    public String handle01(){
    return "Hello,Spring Boot2";
    }
    }
    + +image-20230725162201612 + +

    3.简化配置

    官网地址:https://docs.spring.io/spring-boot/docs/2.7.14/reference/html/application-properties.html#appendix.application-properties

    +
    server.port=8888
    + +

    4.简化部署

    通过在pom.xml文件中插入这个插件,可以直接将项目打成jar包简化部署

    +
     <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>
    + +

    image-20230725163026091

    +

    可以直接使用cmd切换到target目录下使用java -jar 命令运行jar包

    +

    image-20230725163212347

    +

    注意点

    +

    去掉cmd的快速编辑模式(不去掉启动的时候点击屏幕会停止)

    +

    image-20230725164124574

    +

    4.了解自动装配原理

    4.1 依赖管理

    1.SpringBoot的自动版本仲裁机制

    <!-- 项目的继承的父项目-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
    </parent>
    <!-- 父项目继承的父项目-->
    <!--几乎声明了开发中常用依赖的版本号-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.4.RELEASE</version>
    </parent>
    + +

    image-20230725165414051

    +

    自定义mysql版本驱动

    +

    在父依赖中mysql是8版本的

    +

    image-20230725170329032

    +

    可以在pom.xml中通过自定义properties修改版本

    +
    <properties>
    <!--重写mysql的版本号-->
    <mysql.version>5.1.43</mysql.version>
    </properties>
    + +

    image-20230725170257030

    +

    2.Starters场景启动器

    官网: https://docs.spring.io/spring-boot/docs/2.7.14/reference/html/using.html#using.build-systems.starters

    +

    spring-boot-starter-*起头的starter为官方提供的starter,*-spring-boot-starter为第三方提供的starter

    +

    常见的Starter

    +

    image-20230725171130925

    +

    以引入spring-boot-starter-web为例,引入该starter它会帮我们引入以下的其它依赖

    +

    image-20230725171550618

    +

    4.2 自动配置

      +
    • 自动配置Tomcat

      +
        +
      1. 引入依赖

        +

        image-20230725172719608

        +
      2. +
      3. 配置Tomcat

        +
      4. +
      +
    • +
    • 自动配置SpringMvc

      +
        +
      1. 引入springMvc全套组件

        +
      2. +
      3. 自动配置好springMvc的常用组件(字符编码,文件上传解析器,dispatcherServlet等)

        +
        @SpringBootApplication
        public class MainApplication {
        public static void main(String[] args) {
        //返回ioc容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        //获取容器里面组件的名字
        String[] beanDefinitionNames = run.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
        //打印容器内的组件
        System.out.println(beanDefinitionName);
        }
        }
        }
        + +

        image-20230725172834666

        +
      4. +
      +
    • +
    • 默认的包结构

      +

      主程序所在的包以及其的子包都可以被扫描到,无需配置包扫描

      +
      com
      +- example
      +- myapplication
      +- MyApplication.java #主程序
      |
      +- customer
      | +- Customer.java
      | +- CustomerController.java
      | +- CustomerService.java
      | +- CustomerRepository.java
      |
      +- order
      +- Order.java
      +- OrderController.java
      +- OrderService.java
      +- OrderRepository.java
      + +

      如果需要扫描的文件在主程序的上级目录,我们也想扫描到它,我们需要扩大一下包的扫描范围

      +
      @SpringBootApplication(scanBasePackages = "com.atguigu")
      或者
      @ComponentScan("com.atguigu")
      +
    • +
    • 各种配置拥有默认值

      +

      image-20230725174350129

      +
    • +
    • 按需加载所有的自动配置项

      +

      spingboot的所有配置存在于下面的这个依赖中

      +
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.3.4.RELEASE</version>
      <scope>compile</scope>
      </dependency>
      + +

      image-20230725175010683

      +
    • +
    +

    4.3 容器功能

    1.组件添加

    1.1 @Configuration
      +
    • 基本使用

      +
    • +
    • Full模式与Lite模式

      +
    • +
      • +
      • 示例
      • +
      • 最佳实战
      • +
      +
    • +
      • +
        • +
        • 配置 类组件之间无依赖关系用Lite模式加速容器启动过程,减少判断
        • +
        • 配置类组件之间有依赖关系,方法会被调用得到之前单实例组件,用Full模式
        • +
        +
      • +
      +
    • +
    +
    /**
    * 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
    * 2、配置类本身也是组件
    * 3、proxyBeanMethods:代理bean的方法
    * Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
    * Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
    * 组件依赖必须使用Full模式默认。其他默认是否Lite模式
    */
    @Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
    public class MyConfig {

    /**
    * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
    * @return
    */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
    User zhangsan = new User("zhangsan", 18);
    //user组件依赖了Pet组件
    zhangsan.setPet(tomcatPet());
    return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
    return new Pet("tomcat");
    }
    }
    + +
    @SpringBootConfiguration
    @EnableAutoConfiguration
    @ComponentScan("com.atguigu.boot")
    public class MainApplication {

    public static void main(String[] args) {
    //1、返回我们IOC容器
    ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);

    //2、查看容器里面的组件
    String[] names = run.getBeanDefinitionNames();
    for (String name : names) {
    System.out.println(name);
    }

    //3、从容器中获取组件

    Pet tom01 = run.getBean("tom", Pet.class);

    Pet tom02 = run.getBean("tom", Pet.class);

    System.out.println("组件:"+(tom01 == tom02));


    //4、com.atguigu.boot.config.MyConfig$$EnhancerBySpringCGLIB$$51f1e1ca@1654a892
    MyConfig bean = run.getBean(MyConfig.class);
    System.out.println(bean);

    //如果@Configuration(proxyBeanMethods = true)代理对象调用方法。SpringBoot总会检查这个组件是否在容器中有。
    //保持组件单实例
    User user = bean.user01();
    User user1 = bean.user01();
    System.out.println(user == user1);


    User user01 = run.getBean("user01", User.class);
    Pet tom = run.getBean("tom", Pet.class);

    System.out.println("用户的宠物:"+(user01.getPet() == tom));
    }
    }
    + +
    1.2 @Bean @Component @Controller @Service @Repository
    //默认是单实例的
    @Bean//给容器中添加组件,以方法名作为组件的id(组件名),返回类型就是组件类型,返回的值,就是组件在容器中的实例,@Bean("user") 是自定义组件名为user
    public User user01(){
    return new User("Tom",100);
    }
    + +
    1.3 @ComponentScan @Import
    /*
    * @Import({User.class, DBHelper.class}) 给容器中自动创建出这两个类型的组件、默认组件的名字就是全类名
    */
    @Import({User.class, DBHelper.class})
    @Configuration(proxyBeanMethods = false)
    public class MyConfig {
    }
    + +
    1.4 @Conditional 条件装配:满足Conditional指定的条件,则进行组件注入

    image-20230802141422751

    +
    //放在类上面,条件成立,这个类下面的所有配置生效,放在方法上面,条件成立,这个方法下的配置才会成立
    @ConditionalOnBean(name = "tom") //存在一个id为tom的组件的时候才会生效
    @ConditionalOnMissingBean(name = "tom")
    + +

    2.原生配置文件的引入

    2.1、@ImportResource

    beans.xml*)

    +
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="haha" class="com.atguigu.boot.bean.User">
    <property name="name" value="zhangsan"></property>
    <property name="age" value="18"></property>
    </bean>

    <bean id="hehe" class="com.atguigu.boot.bean.Pet">
    <property name="name" value="tomcat"></property>
    </bean>
    </beans>
    + +

    在java类中红使用@ImportResource(“classpath:beans.xml”)导入上面的配置文件

    +
    @ImportResource("classpath:beans.xml")
    public class MyConfig {}
    + +

    3.配置绑定

    3.1 @ConfigurationProperties
    /**
    * 只有在容器中的组件,才会拥有SpringBoot提供的强大功能
    */
    @Component //或者在配置类上添加@EnableConfigurationProperties(Car.class)注解,不使用@Component
    @ConfigurationProperties(prefix = "mycar")
    public class Car {

    private String brand;
    private Integer price;

    public String getBrand() {
    return brand;
    }

    public void setBrand(String brand) {
    this.brand = brand;
    }

    public Integer getPrice() {
    return price;
    }

    public void setPrice(Integer price) {
    this.price = price;
    }

    @Override
    public String toString() {
    return "Car{" +
    "brand='" + brand + '\'' +
    ", price=" + price +
    '}';
    }
    }
    + +

    配置文件中的值

    +
    mycar.brand=BYD
    mycar.price=120000
    + +

    编写测试的controller

    +
    @Autowired
    private Car car;
    @RequestMapping("/car")
    public Car ggetCar(){
    return car;
    }
    + +image-20230802153951542 + +

    4.4 自动配置原理入门

    1.引导加载自动配置类

    @SpringBootApplication

    +

    image-20230802155222746

    +
    @SpringBootConfiguration //代表当前是一个配置类
    @EnableAutoConfiguration
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })//指定扫描那些spring注解
    public @interface SpringBootApplication{}
    + +

    @EnableAutoConfiguration

    +
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {}
    + +

    @AutoConfigurationPackage

    +
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {}
    + +

    Registrar.class

    +

    image-20230802160618158

    +

    利用Registrar给容器中导入com.atguigu.boot(主程序所在的包)包下的一系列组件

    +

    image-20230802161004735

    +

    @Import(AutoConfigurationImportSelector.class)

    +
    1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
    2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
    3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
    4、从META-INF/spring.factories位置来加载一个文件。
    默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
    spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
    + +

    AutoConfigurationImportSelector.java

    +
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
    return NO_IMPORTS;
    }
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    + +

    getAutoConfigurationEntry()方法

    +

    利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

    +
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
    return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
    }
    + +

    **getCandidateConfigurations()**获取所有候选的配置

    +

    调用List configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类

    +

    image-20230802162236987

    +

    loadSpringFactories()

    +

    利用工厂加载 Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件

    +
    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
    MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
    if (result != null) {
    return result;
    } else {
    try {
    Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
    MultiValueMap<String, String> result = new LinkedMultiValueMap();

    while(urls.hasMoreElements()) {
    URL url = (URL)urls.nextElement();
    UrlResource resource = new UrlResource(url);
    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
    Iterator var6 = properties.entrySet().iterator();

    while(var6.hasNext()) {
    Map.Entry<?, ?> entry = (Map.Entry)var6.next();
    String factoryTypeName = ((String)entry.getKey()).trim();
    String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
    int var10 = var9.length;

    for(int var11 = 0; var11 < var10; ++var11) {
    String factoryImplementationName = var9[var11];
    result.add(factoryTypeName, factoryImplementationName.trim());
    }
    }
    }

    cache.put(classLoader, result);
    return result;
    } catch (IOException var13) {
    throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
    }
    }
    }
    + +

    从META-INF/spring.factories位置来加载一个文件。默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件,spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

    +

    image-20230802163429366

    +

    在spring.factories这个配置文件中写死了springBoot一启动就要给容器加载的所有配置类,一共127个

    +

    image-20230802163708198

    +
    # Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
    org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

    # Application Listeners
    org.springframework.context.ApplicationListener=\
    org.springframework.boot.autoconfigure.BackgroundPreinitializer

    # Auto Configuration Import Listeners
    org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
    org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

    # Auto Configuration Import Filters
    org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
    org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
    org.springframework.boot.autoconfigure.condition.OnClassCondition,\
    org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
    org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
    org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.elasticsearch.ReactiveElasticsearchRestClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.r2dbc.R2dbcDataAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.r2dbc.R2dbcRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.r2dbc.R2dbcTransactionManagerAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
    org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
    org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
    org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
    org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
    org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
    org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
    org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
    org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
    org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
    org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
    org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
    org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
    org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
    org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
    org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
    org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
    org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
    org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration,\
    org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
    org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
    org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
    org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
    org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
    org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
    org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
    org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration,\
    org.springframework.boot.autoconfigure.rsocket.RSocketMessagingAutoConfiguration,\
    org.springframework.boot.autoconfigure.rsocket.RSocketRequesterAutoConfiguration,\
    org.springframework.boot.autoconfigure.rsocket.RSocketServerAutoConfiguration,\
    org.springframework.boot.autoconfigure.rsocket.RSocketStrategiesAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.rsocket.RSocketSecurityAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.saml2.Saml2RelyingPartyAutoConfiguration,\
    org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
    org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
    org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
    org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
    org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
    org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
    org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
    org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
    org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
    org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
    org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
    org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
    org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration

    # Failure analyzers
    org.springframework.boot.diagnostics.FailureAnalyzer=\
    org.springframework.boot.autoconfigure.data.redis.RedisUrlSyntaxFailureAnalyzer,\
    org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
    org.springframework.boot.autoconfigure.flyway.FlywayMigrationScriptMissingFailureAnalyzer,\
    org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryBeanCreationFailureAnalyzer,\
    org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer

    # Template availability providers
    org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
    org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
    org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
    + +

    2.按需开启自动配置项

    ​ 虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration,按照条件装配规则(@Conditional),最终会按需配置,比如下面的配置是否生效还要通过@Conditional的条件判断。

    +

    简单的一句话:启动的时候加载所有,使用的时候按照条件(@Conditional)进行装配

    +

    image-20230802164424689

    +

    3.修改默认配置

    ​ 相当于当我们在容器中注入了类型为MultipartResolver但是id不为multipartResolver的组件的时候,会帮我们规范一下命名,命名为multipartResolver。

    +
    @Bean
    @ConditionalOnBean(MultipartResolver.class) //存在MultipartResolver类型的组件
    @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)//不存在以multipartResolver为id的组件
    public MultipartResolver multipartResolver(MultipartResolver resolver) {
    // Detect if the user has created a MultipartResolver but named it incorrectly
    return resolver;
    }
    + +

    SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

    +
    @Bean
    @ConditionalOnMissingBean //没有的话就将这个配置注入到容器中
    public CharacterEncodingFilter characterEncodingFilter() {
    CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
    filter.setEncoding(this.properties.getCharset().name());
    filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
    filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
    return filter;
    }
    + +

    总结:

    +
      +
    • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

      +
    • +
    • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

      +
    • +
    • 生效的配置类就会给容器中装配很多组件

      +
    • +
    • 只要容器中有这些组件,相当于这些功能就有了

      +
    • +
    • 定制化配置

      +
    • +
      • +
      • 用户直接自己@Bean替换底层的组件
      • +
      • 用户去看这个组件是获取的配置文件什么值就去修改。
      • +
      +
    • +
    +

    xxxxxAutoConfiguration —> 组件 —> xxxxProperties取值 —-> application.properties

    +

    3.4.最佳实践

    +

    4.5 简化开发

    1.Lombok

    引入依赖(使用前需要安装插件)

    +
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>
    + +

    注解

    +
    @NoArgsConstructor  //无参构造器
    @AllArgsConstructor //有参构造器
    @Data //getter和setter方法
    @ToString
    @EqualsAndHashCode
    + +

    2.简化日志开发

    @Slf4j
    + +

    3.dev-tools

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    + +

    项目或者页面修改以后:Ctrl+F9;

    +

    4.Spring Initailizr(项目初始化向导)

    image-20230803103657995

    +

    image-20230803104032505

    +

    image-20230803104655578

    +

    二.SpringBoot2核心技术-核心功能

    image-20230803104919434

    +

    一.配置文件

    1、文件类型

    1.1、properties

    同以前的properties用法

    +

    1.2、yaml

    1.2.1、简介

    YAML 是 “YAML Ain’t Markup Language”(YAML 不是一种标记语言)的递归缩写。在开发的这种语言时,YAML 的意思其实是:”Yet Another Markup Language”(仍是一种标记语言)。

    +

    非常适合用来做以数据为中心的配置文件

    +
    1.2.2、基本语法
      +
    • key: value;kv之间有空格
    • +
    • 大小写敏感
    • +
    • 使用缩进表示层级关系
    • +
    • 缩进不允许使用tab,只允许空格
    • +
    • 缩进的空格数不重要,只要相同层级的元素左对齐即可
    • +
    • ‘#’表示注释
    • +
    • 字符串无需加引号,如果要加,’’与””表示字符串内容 会被 转义/不转义
    • +
    +
    1.2.3、数据类型
      +
    • 字面量:单个的、不可再分的值。date、boolean、string、number、null
    • +
    +
    k: v
    + +
      +
    • 对象:键值对的集合。map、hash、set、object
    • +
    +
    行内写法:  k: {k1:v1,k2:v2,k3:v3}
    #或
    k:
    k1: v1
    k2: v2
    k3: v3
    + +
      +
    • 数组:一组按次序排列的值。array、list、queue
    • +
    +
    行内写法:  k: [v1,v2,v3]
    #或者
    k:
    - v1
    - v2
    - v3
    + +
    1.2.4、示例
    @ConfigurationProperties(prefix="person")//和以person开头的配置文件绑定
    @Data
    public class Person {

    private String userName;
    private Boolean boss;
    private Date birth;
    private Integer age;
    private Pet pet;
    private String[] interests;
    private List<String> animal;
    private Map<String, Object> score;
    private Set<Double> salarys;
    private Map<String, List<Pet>> allPets;
    }

    @Data
    public class Pet {
    private String name;
    private Double weight;
    }
    + +
    # yaml表示以上对象
    person:
    # 单引号会将 \n作为字符串输出 双引号会将\n 作为换行输出
    # 双引号不会转义,单引号会转义
    userName: zhangsan
    boss: false
    birth: 2019/12/12 20:12:33
    age: 18
    pet:
    name: tomcat
    weight: 23.4
    interests: [篮球,游泳]
    animal:
    - jerry
    - mario
    score:
    english:
    first: 30
    second: 40
    third: 50
    math: [131,140,148]
    chinese: {first: 128,second: 136}
    salarys: [3999,4999.98,5999.99]
    allPets:
    sick:
    - {name: tom}
    - {name: jerry,weight: 47}
    health: [{name: mario,weight: 47}]
    + +

    2、配置提示

    自定义的类和配置文件绑定一般没有提示

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
    </dependency>

    <!--打包的时候去除这个配置提示器-->
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
    <excludes>
    <exclude>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    </exclude>
    </excludes>
    </configuration>
    </plugin>
    </plugins>
    </build>
    + +

    二.Web开发

    image-20230803134143859

    +

    1.简单的功能分析

    1.1 静态资源访问

    1.静态资源目录

    只要静态资源放在类路径下: /static (or /public or /resources or /META-INF/resources

    +

    访问 : 当前项目根路径/ + 静态资源名

    +

    原理: 静态映射/**

    +

    请求进来,先去找Controller看能不能处理,不能处理的所有请求又都交给静态资源处理器,静态资源也找不到则响应404页面

    +

    改变默认的静态资源路径

    +
    spring:
    resources:
    static-locations: [classpath:/haha/] #静态资源的目录
    + +
    2.静态资源访问前缀

    默认无前缀

    +

    默认的访问路径: localhost:8080/xxx.png

    +

    设置访问前缀之后: localhost:8080/res/xxx.png (文件在目录中的位置没有改变,只是访问的时候加上了res这一层路径)

    +
    spring:
    mvc:
    static-path-pattern: /res/**
    + +

    当前项目 + static-path-pattern + 静态资源名 = 静态资源文件夹下找

    +
    3.webjar

    自动映射 /webjars/**

    +

    https://www.webjars.org/

    +
    <dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.1</version>
    </dependency>
    + +

    访问地址:http://localhost:8080/webjars/jquery/3.5.1/jquery.js 后面地址要按照依赖里面的包路径

    +

    1.2 欢迎页支持

      +
    • 静态资源路径下 index.html

      +
    • +
      • +
      • 可以配置静态资源路径
      • +
      • 但是不可以配置静态资源的访问前缀。否则导致 index.html不能被默认访问
      • +
      +
    • +
    +
    spring:
    # mvc:
    # static-path-pattern: /res/** 这个会导致welcome page功能失效

    resources:
    static-locations: [classpath:/haha/]
    + +
      +
    • controller能处理/index
    • +
    +

    1.3 自定义 Favicon

    favicon.ico 放在静态资源目录下即可(名称为favicon.ico)

    +
    spring:
    # mvc:
    # static-path-pattern: /res/** 这个会导致 Favicon 功能失效
    + +

    1.4 静态资源配置原理

    1.SpringMvc的自动配置类WebMvcAutoConfiguration

    +

    image-20230803165113057

    +
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
    @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
    @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
    @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class })
    public class WebMvcAutoConfiguration {}
    + +

    2.SpringMvc给容器中配置的组件

    +

    image-20230803180257123

    +
    @Configuration(proxyBeanMethods = false)
    @Import(EnableWebMvcConfiguration.class)
    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
    @Order(0)
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
    + +

    3.绑定的配置

    +

    @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })

    +

    WebMvcProperties.class

    +

    image-20230803180600438

    +

    ResourceProperties.class

    +

    image-20230803180633611

    +

    2.请求参数处理与数据响应

    2.1、请求映射

    1、rest使用与原理
      +
    • @xxxMapping;

      +
    • +
    • Rest风格支持(使用HTTP请求方式动词来表示对资源的操作

      +
    • +
      • +
      • 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
      • +
      • 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
      • +
      • 核心Filter;HiddenHttpMethodFilter
      • +
      +
    • +
      • +
        • +
        • 用法: 表单method=post,隐藏域 _method=put
        • +
        • SpringBoot中手动开启
        • +
        +
        #开启使用rest风格的注解
        spring:
        mvc:
        hiddenmethod:
        filter:
        enabled: true
        + +
        <!--发送put或者delete请求-->
        <form action="/user" method="post">
        <input name="_method" type="hidden" value="delete"/>
        <input name="_m" type="hidden" value="delete"/>
        <input value="REST-DELETE 提交" type="submit"/>
        </form>
        <form action="/user" method="post">
        <input name="_method" type="hidden" value="PUT"/>
        <input value="REST-PUT 提交" type="submit"/>
        </form>
      • +
      +
    • +
      • +
      • 扩展:如何把_method 这个名字换成我们自己喜欢的。
      • +
      +
    • +
    +
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
    return "GET-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.POST)
    public String saveUser(){
    return "POST-张三";
    }


    @RequestMapping(value = "/user",method = RequestMethod.PUT)
    public String putUser(){
    return "PUT-张三";
    }

    @RequestMapping(value = "/user",method = RequestMethod.DELETE)
    public String deleteUser(){
    return "DELETE-张三";
    }



    //自定义filter
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m");
    return methodFilter;
    }
    + +

    Rest原理(表单提交要使用REST的时候)

    +
      +
    • 表单提交会带上_method=PUT

      +
      <!--发送put或者delete请求-->
      <form action="/user" method="post">
      <input name="_method" type="hidden" value="delete"/>
      <input name="_m" type="hidden" value="delete"/>
      <input value="REST-DELETE 提交" type="submit"/>
      </form>
      <form action="/user" method="post">
      <input name="_method" type="hidden" value="PUT"/>
      <input value="REST-PUT 提交" type="submit"/>
      </form>
      +
    • +
    • 请求过来被HiddenHttpMethodFilter拦截

      +

      WebMvcAutoConfiguration

      +
      @Bean
      @ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
      @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
      public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
      return new OrderedHiddenHttpMethodFilter();
      }
      +
    • +
      • +
      • 请求是否正常,并且是POST
      • +
      +
    • +
      • +
        • +
        • 获取到_method的值。

          +
          @Override
          protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
          throws ServletException, IOException {

          HttpServletRequest requestToUse = request;

          if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
          String paramValue = request.getParameter(this.methodParam);
          if (StringUtils.hasLength(paramValue)) {
          String method = paramValue.toUpperCase(Locale.ENGLISH);
          if (ALLOWED_METHODS.contains(method)) {
          requestToUse = new HttpMethodRequestWrapper(request, method);
          }
          }
          }

          filterChain.doFilter(requestToUse, response);
          }
          +
        • +
        • 兼容以下请求;PUT.DELETE.PATCH

          +
          private static final List<String> ALLOWED_METHODS =
          Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
          HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
          +
        • +
        • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值

          +
        • +
        • 过滤器链放行的时候用wrapper,以后的方法调用getMethod是调用requesWrapper的

          +
        • +
        +
      • +
      +
    • +
    +

    Tips:

    +

    Rest使用客户端工具:如PostMan直接发送put、delete等方式请求,无需Filter,不需要转换

    +

    扩展点:

    +

    如何将<input name="_method" type="hidden" value="delete"/>中的name值_method改为自定义的值?

    +
    @Configuration(proxyBeanMethods = false) //组件中没有依赖关系,设置成false直接放
    public class WebConfig{
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
    HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
    methodFilter.setMethodParam("_m"); //将name="_method"改成name="_m"
    return methodFilter;
    }
    }
    + +
    2、请求映射原理

    DispatcherServlet 继承 FrameworkServlet

    +

    FrameworkServlet 继承 HttpServletBean

    +

    FrameworkServlet 重写了doGet和doPost方法

    +

    image-20230809133846464

    +

    DispatcherServlet的doDispatch方法

    +
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
    processedRequest = checkMultipart(request);
    multipartRequestParsed = (processedRequest != request);

    // Determine handler for the current request.
    //找到请求使用那个hander(contrller)处理
    /**
    * getHandler()方法
    */
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);
    return;
    }

    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // Process last-modified header, if supported by the handler.
    String method = request.getMethod();
    boolean isGet = "GET".equals(method);
    if (isGet || "HEAD".equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    return;
    }
    }

    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
    }

    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    if (asyncManager.isConcurrentHandlingStarted()) {
    return;
    }

    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
    dispatchException = ex;
    }
    catch (Throwable err) {
    // As of 4.3, we're processing Errors thrown from handler methods as well,
    // making them available for @ExceptionHandler methods and other scenarios.
    dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
    new NestedServletException("Handler processing failed", err));
    }
    finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
    // Instead of postHandle and afterCompletion
    if (mappedHandler != null) {
    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    }
    }
    else {
    // Clean up any resources used by a multipart request.
    if (multipartRequestParsed) {
    cleanupMultipart(processedRequest);
    }
    }
    }
    }
    + +

    getHandler()方法 遍历查找可以处理请求的方法

    +
    @Nullable
    protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
    for (HandlerMapping mapping : this.handlerMappings) {
    HandlerExecutionChain handler = mapping.getHandler(request);
    if (handler != null) {
    return handler;
    }
    }
    }
    return null;
    }
    + +

    **HandlerMapping ** 处理器映射

    +

    默认有5个HandlerMapping

    +

    image-20230809150915450

    +

    RequestMappingHandlerMapping:保存了所有@RequestMapping 和handler的映射规则

    +

    image-20230809151427867

    +

    所有的请求映射都在HandlerMapping中

    +
      +
    • SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 访问 /能访问到index.html;

      +
    • +
    • SpringBoot自动配置了默认的 RequestMappingHandlerMapping

      +
    • +
    • 请求进来,挨个尝试所有的HandlerMapping看是否有请求信息

      +
    • +
      • +
      • 如果有就找到这个请求对应的handler
      • +
      • 如果没有就是下一个 HandlerMapping
      • +
      +
    • +
    • 我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping 自定义 HandlerMapping

      +
    • +
    +

    2.2、普通参数与基本注解

    1.注解
    @PathVariable  //获取路径中的参数信息(restful风格)
    @RequestHeader //获取请求头的信息
    @RequestAttribute //获取请求域中的数据
    @RequestParam //获取路径中拼接的请求参数(/info?age=12&name=lisi)
    @MatrixVariable //矩阵变量 /cars/sell;low=34;brand=byd,audi,yd
    @CookieValue //获取cookie的值
    @RequestBody //获取请求体的值(Post请求)
    + +

    @PathVariable

    +

    接受路径中的参数也可以使用map集合接受,但是map集合必须是Map<String, String>的形式

    +

    image-20230809155753715

    +

    @RequestHeader

    +

    获取指定的请求头或者所有的请求头信息

    +

    image-20230809160406516

    +

    image-20230809160846527

    +

    @RequestParam

    +

    可以使用map接受所有的请求参数

    +

    image-20230809161533834

    +

    @CookieValue

    +

    获取cookie的值,可以直接获取字符串的值也可以封装成cookie的对象

    +

    image-20230809164101374

    +

    @RequestBody

    +

    这里用一个字符串接收表单数据和平时使用的不一样,开发中使用一个对象接受表单的数据

    +
    /**
    * ·@RequestBody注解
    */
    @PostMapping("/save")
    public Map postMethod(@RequestBody String content){
    Map<String,Object> map = new HashMap<>();
    map.put("content",content);
    return map;
    }
    + +

    image-20230809165149278

    +

    @RequestAttribute

    +
    package com.atguigu.boot.controller;

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestAttribute;
    import org.springframework.web.bind.annotation.ResponseBody;

    import javax.servlet.http.HttpServletRequest;
    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/11
    * @Description
    */
    @Controller
    public class RequestController {


    @GetMapping("/goto")
    public String goToPage(HttpServletRequest request){

    request.setAttribute("msg","获取请求域中的数据成功");
    request.setAttribute("code",200);
    return "forward:/success"; //转发到下面的/success请求
    }

    @ResponseBody
    @GetMapping("/success")
    public Map success(@RequestAttribute(value = "msg",required = false) String msg,
    @RequestAttribute(value = "code",required = false)Integer code,//通过注解的方式获取上面请求域的值
    HttpServletRequest request){//通过原生的request获取
    //Object msg1 = request.getAttribute("msg");
    Map<String,Object> map = new HashMap<>();
    map.put("msg",msg);
    map.put("code",code);
    return map;
    }
    }
    + +

    image-20230811092618793

    +

    @MatrixVariable

    +

    开启SpringBoot的矩阵变量功能

    +
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
    UrlPathHelper urlPathHelper = new UrlPathHelper();
    // 不移除 后面的内容 矩阵变量功能就可以生效
    urlPathHelper.setRemoveSemicolonContent(false);
    configurer.setUrlPathHelper(urlPathHelper);
    }
    };
    }
    + +

    测试

    +
    //1、语法: 请求路径:/cars/sell;low=34;brand=byd,audi,yd
    //2、SpringBoot默认是禁用了矩阵变量的功能
    // 手动开启:原理。对于路径的处理。UrlPathHelper进行解析。
    // removeSemicolonContent(移除分号内容)支持矩阵变量的
    //3、矩阵变量必须有url路径变量才能被解析
    @GetMapping("/cars/{path}")
    public Map carsSell(@MatrixVariable("low") Integer low,
    @MatrixVariable("brand") List<String> brand,
    @PathVariable("path") String path){
    Map<String,Object> map = new HashMap<>();

    map.put("low",low);
    map.put("brand",brand);
    map.put("path",path);
    return map;
    }

    // /boss/1;age=20/2;age=10
    @GetMapping("/boss/{bossId}/{empId}")
    public Map boss(@MatrixVariable(value = "age",pathVar = "bossId") Integer bossAge,
    @MatrixVariable(value = "age",pathVar = "empId") Integer empAge){
    Map<String,Object> map = new HashMap<>();
    map.put("bossAge",bossAge);
    map.put("empAge",empAge);
    return map;
    }
    + +

    image-20230811095858532

    +

    image-20230811095834803

    +
    2.Servlet API

    WebRequest、ServletRequest、MultipartRequest、 HttpSession、javax.servlet.http.PushBuilder、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId

    +

    ServletRequestMethodArgumentResolver 以上的部分参数

    +
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
    Class<?> paramType = parameter.getParameterType();
    return (WebRequest.class.isAssignableFrom(paramType) ||
    ServletRequest.class.isAssignableFrom(paramType) ||
    MultipartRequest.class.isAssignableFrom(paramType) ||
    HttpSession.class.isAssignableFrom(paramType) ||
    (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
    Principal.class.isAssignableFrom(paramType) ||
    InputStream.class.isAssignableFrom(paramType) ||
    Reader.class.isAssignableFrom(paramType) ||
    HttpMethod.class == paramType ||
    Locale.class == paramType ||
    TimeZone.class == paramType ||
    ZoneId.class == paramType);
    }
    + +
    3.复杂参数:

    Map、Model(map、model里面的数据会被放在request的请求域 request.setAttribute)、Errors/BindingResult、RedirectAttributes( 重定向携带数据)、ServletResponse(response)、SessionStatus、UriComponentsBuilder、ServletUriComponentsBuilder

    +

    向请求域中共享数据

    +
    /**
    * ra数据通过 map model HttpServletRequest HttpServletResponse 等方式共享到请求域中,在success页面中取出来
    */
    @GetMapping("/params")
    public String testParam(Map<String,Object> map,
    Model model,
    HttpServletRequest request,
    HttpServletResponse response){
    map.put("hello","world666");
    model.addAttribute("world","hello666");
    request.setAttribute("message","HelloWorld");

    Cookie cookie = new Cookie("c1","v1");
    response.addCookie(cookie);
    return "forward:/success";
    }
    + +
    4.自定义对象参数
    /**
    * 姓名: <input name="userName"/> <br/>
    * 年龄: <input name="age"/> <br/>
    * 生日: <input name="birth"/> <br/>
    * 宠物姓名:<input name="pet.name"/><br/>
    * 宠物年龄:<input name="pet.age"/>
    */
    @Data
    public class Person {

    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;

    }

    @Data
    public class Pet {

    private String name;
    private String age;

    }
    + +

    页面测试代码

    +

    index.html

    +
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1>atguigu,欢迎您</h1>
    测试REST风格;
    <form action="/user" method="get">
    <input value="REST-GET 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
    <input value="REST-POST 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
    <input name="_method" type="hidden" value="delete"/>
    <input name="_m" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
    </form>
    <form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT"/>
    <input value="REST-PUT 提交" type="submit"/>
    </form>
    <hr/>
    测试基本注解:
    <ul>
    <a href="car/3/owner/lisi?age=18&inters=basketball&inters=game">car/{id}/owner/{username}</a>
    <li>@PathVariable(路径变量)</li>
    <li>@RequestHeader(获取请求头)</li>
    <li>@RequestParam(获取请求参数)</li>
    <li>@CookieValue(获取cookie值)</li>
    <li>@RequestBody(获取请求体[POST])</li>

    <li>@RequestAttribute(获取request域属性)</li>
    <li>@MatrixVariable(矩阵变量)</li>
    </ul>

    /cars/{path}?xxx=xxx&aaa=ccc queryString 查询字符串。@RequestParam;<br/>
    /cars/sell;low=34;brand=byd,audi,yd ;矩阵变量 <br/>
    页面开发,cookie禁用了,session里面的内容怎么使用;
    session.set(a,b)---> jsessionid ---> cookie ----> 每次发请求携带。
    url重写:/abc;jsesssionid=xxxx 把cookie的值使用矩阵变量的方式进行传递.

    /boss/1/2

    /boss/1;age=20/2;age=20

    <a href="/cars/sell;low=34;brand=byd,audi,yd">@MatrixVariable(矩阵变量)</a>
    <a href="/cars/sell;low=34;brand=byd;brand=audi;brand=yd">@MatrixVariable(矩阵变量)</a>
    <a href="/boss/1;age=20/2;age=10">@MatrixVariable(矩阵变量)/boss/{bossId}/{empId}</a>
    <br/>
    <form action="/save" method="post">
    测试@RequestBody获取数据 <br/>
    用户名:<input name="userName"/> <br>
    邮箱:<input name="email"/>
    <input type="submit" value="提交"/>
    </form>
    <ol>
    <li>矩阵变量需要在SpringBoot中手动开启</li>
    <li>根据RFC3986的规范,矩阵变量应当绑定在路径变量中!</li>
    <li>若是有多个矩阵变量,应当使用英文符号;进行分隔。</li>
    <li>若是一个矩阵变量有多个值,应当使用英文符号,进行分隔,或之命名多个重复的key即可。</li>
    <li>如:/cars/sell;low=34;brand=byd,audi,yd</li>
    </ol>
    <hr/>
    测试原生API:
    <a href="/testapi">测试原生API</a>
    <hr/>
    测试复杂类型:<hr/>
    测试封装POJO;
    <form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="2019/12/10"/> <br/>
    <!-- 宠物姓名:<input name="pet.name" value="阿猫"/><br/>-->
    <!-- 宠物年龄:<input name="pet.age" value="5"/>-->
    宠物: <input name="pet" value="小猫"/>
    <input type="submit" value="保存"/>
    </form>
    <br>
    </body>
    </html>
    + + + +

    2.3 参数处理原理

    1.页面发送请求

    +
    http://localhost:8080/car/3/owner/lisi  
    + +

    2.进入DispatcherServlet的doDispatch( )方法

    +
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
    ModelAndView mv = null;
    Exception dispatchException = null;

    try {
    processedRequest = checkMultipart(request);
    multipartRequestParsed = (processedRequest != request);

    // Determine handler for the current request.
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
    noHandlerFound(processedRequest, response);
    return;
    }

    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // Process last-modified header, if supported by the handler.
    String method = request.getMethod();
    boolean isGet = "GET".equals(method);
    if (isGet || "HEAD".equals(method)) {
    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
    return;
    }
    }

    if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
    }

    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

    if (asyncManager.isConcurrentHandlingStarted()) {
    return;
    }

    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
    dispatchException = ex;
    }
    catch (Throwable err) {
    // As of 4.3, we're processing Errors thrown from handler methods as well,
    // making them available for @ExceptionHandler methods and other scenarios.
    dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }
    catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }
    catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
    new NestedServletException("Handler processing failed", err));
    }
    finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
    // Instead of postHandle and afterCompletion
    if (mappedHandler != null) {
    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
    }
    }
    else {
    // Clean up any resources used by a multipart request.
    if (multipartRequestParsed) {
    cleanupMultipart(processedRequest);
    }
    }
    }
    }
    + +

    3.封装一个方法获取对应的处理器适配器

    +
    //方法的调用
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
    + +

    4.遍历所有的处理器适配器(一共有四种,如下图)找到支持的适配器

    +
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
    for (HandlerAdapter adapter : this.handlerAdapters) {
    if (adapter.supports(handler)) {
    return adapter;
    }
    }
    }
    throw new ServletException("No adapter for handler [" + handler +
    "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
    + +

    image-20230812091950114

    +

    上图中四个处理器适配器中前两个是常用的处理器适配器: 0 - 支持方法上标注@RequestMapping 1 - 支持函数式编程的

    +

    5.查看当前的处理器适配器是否支持的方法

    +
    @Override
    public final boolean supports(Object handler) {
    //条件一 handler instanceof HandlerMethod
    //条件二 supportsInternal((HandlerMethod) handler)
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }
    + +

    遍历查找之后返回的适配器是RequestMappingHandlerAdapter

    +

    image-20230812092825385

    +

    6.回到DispatcherServlet执行下面的方法

    +
    //根据上面获取到的处理器适配器真正的执行处理器方法
    // Actually invoke the handler.
    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    + +

    7.内部的处理过程

    +

    handle( )方法

    +
    @Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {
    return handleInternal(request, response, (HandlerMethod) handler);
    }
    + +

    进入当前处理器适配器的handleInternal( )方法

    +
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    checkRequest(request);

    // Execute invokeHandlerMethod in synchronized block if required.
    if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);
    if (session != null) {
    Object mutex = WebUtils.getSessionMutex(session);
    synchronized (mutex) {
    mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    }
    else {
    // No HttpSession available -> no mutex necessary
    mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    }
    else {
    // No synchronization on session demanded at all...
    mav = invokeHandlerMethod(request, response, handlerMethod);
    }

    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
    applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    }
    else {
    prepareResponse(response);
    }
    }

    return mav;
    }
    + +

    执行invokeHandlerMethod( )方法

    +
    mav = invokeHandlerMethod(request, response, handlerMethod);
    + +

    进入invokeHandlerMethod()方法

    +
    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    invocableMethod.setDataBinderFactory(binderFactory);
    invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

    ModelAndViewContainer mavContainer = new ModelAndViewContainer();
    mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    modelFactory.initModel(webRequest, mavContainer, invocableMethod);
    mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.setTaskExecutor(this.taskExecutor);
    asyncManager.setAsyncWebRequest(asyncWebRequest);
    asyncManager.registerCallableInterceptors(this.callableInterceptors);
    asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

    if (asyncManager.hasConcurrentResult()) {
    Object result = asyncManager.getConcurrentResult();
    mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
    asyncManager.clearConcurrentResult();
    LogFormatUtils.traceDebug(logger, traceOn -> {
    String formatted = LogFormatUtils.formatValue(result, !traceOn);
    return "Resume with async result [" + formatted + "]";
    });
    invocableMethod = invocableMethod.wrapConcurrentResult(result);
    }

    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    if (asyncManager.isConcurrentHandlingStarted()) {
    return null;
    }

    return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
    webRequest.requestCompleted();
    }
    }
    + +

    注意这里的26个参数解析器,参数解析器的作用是确定将要执行的目标方法的每一个参数的值是什么

    +

    这里的每一个参数解析器都对应了我们标注在参数上的注解

    +

    SpringMVC目标方法能写多少种参数类型取决于参数解析器

    +

    image-20230812094224699

    +

    参数解析器的接口设计

    +

    image-20230812094755583

    +
      +
    • 当前解析器是否支持解析这种参数
    • +
    • 支持就调用 resolveArgument
    • +
    +

    返回值处理器

    +

    定义了controller返回值的类型

    +image-20230812095137195 + +

    RequestMappingHandlerAdapter的invokeAndHandle( )方法真正的执行请求

    +
    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    + +
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {

    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    //上面一行代码执行完毕之后会直接跳到controller方法,然后执行下面的代码
    setResponseStatus(webRequest);

    if (returnValue == null) {
    if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
    disableContentCachingIfNecessary(webRequest);
    mavContainer.setRequestHandled(true);
    return;
    }
    }
    else if (StringUtils.hasText(getResponseStatusReason())) {
    mavContainer.setRequestHandled(true);
    return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
    this.returnValueHandlers.handleReturnValue(
    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
    if (logger.isTraceEnabled()) {
    logger.trace(formatErrorForReturnValue(returnValue), ex);
    }
    throw ex;
    }
    }
    + +

    执行的过程

    +
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    + +
    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {

    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    //这个args就记录了方法上所有的参数
    if (logger.isTraceEnabled()) {
    logger.trace("Arguments: " + Arrays.toString(args));
    }
    return doInvoke(args);
    }
    + +

    image-20230812100059378

    +

    获取方法参数值的过程

    +
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    + +
    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
    Object... providedArgs) throws Exception {


    //获取所有参数的参数声明 参数类型 标注的注解
    MethodParameter[] parameters = getMethodParameters();
    if (ObjectUtils.isEmpty(parameters)) {
    return EMPTY_ARGS;
    }

    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
    MethodParameter parameter = parameters[i];
    parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
    args[i] = findProvidedArgument(parameter, providedArgs);
    if (args[i] != null) {
    continue;
    }
    if (!this.resolvers.supportsParameter(parameter)) {
    throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
    }
    try {
    args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
    }
    catch (Exception ex) {
    // Leave stack trace for later, exception may actually be resolved and handled...
    if (logger.isDebugEnabled()) {
    String exMsg = ex.getMessage();
    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
    logger.debug(formatArgumentError(parameter, exMsg));
    }
    }
    throw ex;
    }
    }
    return args;
    }
    + +

    遍历判断获取支持解析该参数的参数解析器

    +
    @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
    for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
    if (resolver.supportsParameter(parameter)) {
    result = resolver;
    this.argumentResolverCache.put(parameter, result);
    break;
    }
    }
    }
    return result;
    }
    + +

    解析参数的值通过调用各自 HandlerMethodArgumentResolver 的 resolveArgument 方法

    +

    2.4 数据响应与内容协商

    返回值的处理逻辑

    +
    2.4.1 相关依赖的引入

    创建springBoot项目的时候导入了spring-boot-starter-web依赖会自动的帮我们导入相关的json依赖,便于json数据的前后端传递

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    + +

    自动的帮我们导入相关的json的starter

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.3.4.RELEASE</version>
    <scope>compile</scope>
    </dependency>
    + +

    在spring-boot-starter-json中引入了jackson相关的依赖

    +
    <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.11.2</version>
    <scope>compile</scope>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jdk8</artifactId>
    <version>2.11.2</version>
    <scope>compile</scope>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.11.2</version>
    <scope>compile</scope>
    </dependency>
    <dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-parameter-names</artifactId>
    <version>2.11.2</version>
    <scope>compile</scope>
    </dependency>
    + +

    返回值解析器的原理

    +
      +
    • 1、返回值处理器判断是否支持这种类型返回值 supportsReturnType

      +
    • +
    • 2、返回值处理器调用 handleReturnValue 进行处理

      +
    • +
    • 3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。

      +
    • +
      • +
        1. +
        2. 利用 MessageConverters 进行处理 将数据写为json
        3. +
        +
      • +
      +
    • +
      • +
        • +
        • 1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
        • +
        • 2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据,
        • +
        • 3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?
        • +
        +
      • +
      +
    • +
      • +
        • +
          • +
          • 1、得到MappingJackson2HttpMessageConverter可以将对象写为json
          • +
          • 2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
          • +
          +
        • +
        +
      • +
      +
    • +
    +
    2.4.2 内容协商原理

    引入jackson-dataformat-xml,测试返回是xml格式的数据

    +
    <dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    </dependency>
    + +

    开启基于请求参数的内容协商功能

    +

    发请求: http://localhost:8080/test/person?format=json //指定格式是json

    +

    http://localhost:8080/test/person?format=xml //指定格式是xml

    +
    spring:
    contentnegotiation:
    favor-parameter: true #开启请求参数内容协商模式
    + +

    1、Parameter策略优先确定是要返回json数据(获取请求头中的format的值)

    +

    2、最终进行内容协商返回给客户端json即可

    +

    3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

    +

    4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。

    +

    5、客户端需要【application/xml】。服务端能力【10种、json、xml】

    +

    6、进行内容协商的最佳匹配媒体类型

    +

    7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。

    +
    2.4.3 自定义MessageConverter

    1、@ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理

    +

    2、Processor 处理方法返回值。通过 MessageConverter 处理

    +

    3、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)

    +

    4、内容协商找到最终的 messageConverter;

    +

    步骤:
    1、添加自定义的MessageConverter进系统底层

    +

    2、系统底层就会统计出所有MessageConverter能操作哪些类型

    +

    3、客户端内容协商 [guigu—>guigu]

    +

    编写自定义的的converter

    +
    package com.atguigu.boot.converter;

    import com.atguigu.boot.bean.Person;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.List;

    /**
    * 自定义的Converter
    */
    public class GuiguMessageConverter implements HttpMessageConverter<Person> {

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
    return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
    return clazz.isAssignableFrom(Person.class);
    }

    /**
    * 服务器要统计所有MessageConverter都能写出哪些内容类型
    *
    * application/x-guigu
    * @return
    */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
    return MediaType.parseMediaTypes("application/x-guigu");
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    //自定义协议数据的写出
    String data = person.getUserName()+";"+person.getAge()+";"+person.getBirth();
    //写出去
    OutputStream body = outputMessage.getBody();
    body.write(data.getBytes());
    }
    }
    + +

    在配置文件中加入自定义的converter

    +
    package com.atguigu.boot.config;


    import com.atguigu.boot.bean.Person;
    import com.atguigu.boot.bean.Pet;
    import com.atguigu.boot.converter.GuiguMessageConverter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.format.FormatterRegistry;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import org.springframework.util.StringUtils;
    import org.springframework.web.accept.HeaderContentNegotiationStrategy;
    import org.springframework.web.accept.ParameterContentNegotiationStrategy;
    import org.springframework.web.filter.HiddenHttpMethodFilter;
    import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
    import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import org.springframework.web.util.UrlPathHelper;

    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.*;

    @Configuration(proxyBeanMethods = false)
    public class WebConfig {
    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {
    //加入自定义的converter
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new GuiguMessageConverter());
    }
    };
    }
    }
    + +

    在浏览器url上拼接参数的形式实现内容协商

    +
    package com.atguigu.boot.config;

    import com.atguigu.boot.bean.Person;
    import com.atguigu.boot.bean.Pet;
    import com.atguigu.boot.converter.GuiguMessageConverter;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.format.FormatterRegistry;
    import org.springframework.http.HttpInputMessage;
    import org.springframework.http.HttpOutputMessage;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.converter.HttpMessageNotReadableException;
    import org.springframework.http.converter.HttpMessageNotWritableException;
    import org.springframework.util.StringUtils;
    import org.springframework.web.accept.HeaderContentNegotiationStrategy;
    import org.springframework.web.accept.ParameterContentNegotiationStrategy;
    import org.springframework.web.filter.HiddenHttpMethodFilter;
    import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
    import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import org.springframework.web.util.UrlPathHelper;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.*;

    @Configuration(proxyBeanMethods = false)
    public class WebConfig{

    @Bean
    public WebMvcConfigurer webMvcConfigurer(){
    return new WebMvcConfigurer() {

    /**
    * 自定义内容协商策略
    * @param configurer
    */
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    Map<String, MediaType> mediaTypes = new HashMap<>();
    mediaTypes.put("json",MediaType.APPLICATION_JSON);
    mediaTypes.put("xml",MediaType.APPLICATION_XML);
    //在浏览器url上拼接参数的形式实现内容协商
    //http://localhost:8080/test/person?format=gg
    mediaTypes.put("gg",MediaType.parseMediaType("application/x-guigu"));
    //指定支持解析哪些参数对应的哪些媒体类型
    ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
    //parameterStrategy.setParameterName("ff");

    HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();

    configurer.strategies(Arrays.asList(parameterStrategy,headeStrategy));
    }

    //加入自定义的converter
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(new GuiguMessageConverter());
    }
    };
    }
    }
    + +

    3.视图解析与模板引擎

    视图解析:SpringBoot默认不支持 JSP,需要引入第三方模板引擎技术实现页面渲染

    +

    3.1 视图解析

    image-20230815091741019

    +

    3.2 模板引擎-Thymeleaf

    详细的使用教程: https://jasonsgong.gitee.io/posts/54835.html

    +
    3.2.1 Thymeleaf的使用

    1.引入依赖

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    + +

    2.编写测试controller

    +
    @Controller
    public class ViewTestController {
    @GetMapping("/view")
    public String hello(Model model){
    //向请求域中共享数据
    model.addAttribute("keys","Thymeleaf");
    //转发到success页面中
    return "success";
    }
    }
    + +

    3.页面上渲染

    +
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    </head>
    <body>
    <h1 th:text="${keys}">Hello</h1>
    </body>
    </html>
    + +

    image-20230815153318041

    +

    3.3 后台管理系统注意点

    templates目录的访问规则

    +

    templates目录下的所有页面资源只能通过请求访问到

    +

    表单重复提交的问题

    +
    package com.atguigu.admin.controller;


    @Slf4j
    @Controller
    public class IndexController {

    /**
    * 来登录页
    * @return
    */
    @GetMapping(value = {"/","/login"})
    public String loginPage(){

    return "login";
    }


    @PostMapping("/login")
    public String main(String username, String password){
    //登录成功重定向到main.html; 重定向防止表单重复提交
    //这里如果直接写 return "main"的话,页面在mian.html的情况下,url路径还是localhost:8080/login,如果再次刷新页面会重复提交表单
    //使用下面这个写法,执行"/login"这个请求之后,路径的url会变成localhost:8080/main.html
    return "redirect:/main.html";
    }

    /**
    * 去main页面
    * @return
    */
    @GetMapping("/main.html")
    public String mainPage(){
    return "main";
    }
    }
    + +

    4.拦截器

    1.HandlerInterceptor接口

    自定义拦截器需要实现的接口,以及需要实现接口中的方法

    +

    image-20230821105529796

    +

    2.拦截器实现登录检查操作

    登录检查的业务逻辑

    +
    /**
    * 登录检查
    * 1、配置好拦截器要拦截哪些请求
    * 2、把这些配置放在容器中
    */
    @Slf4j
    public class LoginInterceptor implements HandlerInterceptor {

    /**
    * 目标方法执行之前
    * @param request
    * @param response
    * @param handler
    * @return
    * @throws Exception
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    String requestURI = request.getRequestURI();
    log.info("preHandle拦截的请求路径是{}",requestURI);

    //登录检查逻辑
    HttpSession session = request.getSession();

    Object loginUser = session.getAttribute("loginUser");

    if(loginUser != null){
    //放行
    return true;
    }

    //拦截住 未登录 跳转到登录页
    request.setAttribute("msg","请先登录");
    request.getRequestDispatcher("/").forward(request,response);
    return false;
    }

    /**
    * 目标方法执行完成以后
    * @param request
    * @param response
    * @param handler
    * @param modelAndView
    * @throws Exception
    */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    log.info("postHandle执行{}",modelAndView);
    }

    /**
    * 页面渲染以后
    * @param request
    * @param response
    * @param handler
    * @param ex
    * @throws Exception
    */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    log.info("afterCompletion执行异常{}",ex);
    }
    }
    + +

    拦截器的配置

    +
    /**
    * 1、编写一个拦截器实现HandlerInterceptor接口
    * 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
    * 3、指定拦截规则[如果是拦截所有,静态资源也会被拦截]
    */
    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())
    .addPathPatterns("/**") //所有请求都被拦截包括静态资源
    .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
    }
    }
    + +

    3.拦截器原理

    /**
    * preHandle() :该方法在控制器方法之前执行,如果返回false,说明拦截器不放行该请求,自己处理或者结束该请求
    * 如果返回true,说明拦截器放该请求,由其它匹配的拦截器或者控制器继续处理
    * postHandle():该方法在控制器方法调用之后,且解析视图之前执行,可以通过此方法对请求域中的模型和视图做出进一步的修改
    * afterCompletion(): 该方法会在整个请求完成,即视图渲染结束之后执行,多用于资源的清理工作
    */
    + +

    1、根据当前请求,找到HandlerExecutionChain[可以处理请求的handler以及handler的所有 拦截器]

    +

    2、先来顺序执行 所有拦截器的 preHandle方法

    +
      +
    • 1、如果当前拦截器prehandler返回为true,则执行下一个拦截器的preHandle
    • +
    • 2、如果当前拦截器返回为false。直接倒序执行所有已经执行了的拦截器的afterCompletion方法
    • +
    +

    3、如果任何一个拦截器返回false,直接跳出不执行目标方法

    +

    4、所有拦截器都返回true,执行目标方法

    +

    5、倒序执行所有拦截器的postHandle方法

    +

    6、前面的步骤有任何异常都会直接倒序触发 afterCompletion

    +

    7、页面成功渲染完成以后,也会倒序触发 afterCompletion

    +

    image-20230821112246355

    +

    5.文件上传

    1.页面表单

    <form method="post" action="/upload" enctype="multipart/form-data">
    <input type="file" name="file"><br>
    <input type="submit" value="提交">
    </form>
    + +

    2.文件上传代码

    /**
    * MultipartFile 自动封装上传过来的文件
    */
    @PostMapping("/upload")
    public String upload(@RequestParam("email") String email,
    @RequestParam("username") String username,
    @RequestPart("headerImg") MultipartFile headerImg,//单个文件
    @RequestPart("photos") MultipartFile[] photos) throws IOException {//多个文件

    log.info("上传的信息:email={},username={},headerImg={},photos={}",
    email, username, headerImg.getSize(), photos.length);

    if (!headerImg.isEmpty()) {
    //保存到文件服务器,OSS服务器
    String originalFilename = headerImg.getOriginalFilename();
    headerImg.transferTo(new File("H:\\cache\\" + originalFilename));
    }

    if (photos.length > 0) {
    for (MultipartFile photo : photos) {
    if (!photo.isEmpty()) {
    String originalFilename = photo.getOriginalFilename();
    photo.transferTo(new File("H:\\cache\\" + originalFilename));
    }
    }
    }
    return "main";
    }
    + +

    3.MultipartAutoConfiguration

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
    @ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
    @ConditionalOnWebApplication(type = Type.SERVLET)
    @EnableConfigurationProperties(MultipartProperties.class)
    public class MultipartAutoConfiguration {}
    + +

    6.异常处理

    开发中异常处理方法: https://jasonsgong.gitee.io/posts/31385.html

    +

    1.默认规则

      +
    • 默认情况下,Spring Boot提供/error处理所有错误的映射
    • +
    • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据
    • +
    +

    2.自定义错误页面

    templates/error/下的4xx,5xx页面会被自动解析

    +

    7.Web原生组件注入(Servlet、Filter、Listener)

    7.1 使用Servlet Api

    1.原生的Servlet的使用

    1.编写自定义的servlet继承HttpServlet

    +
    package com.atguigu.boot.servlet;

    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/26
    * @Description
    */
    @WebServlet(urlPatterns = "/my")
    public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.getWriter().write("6666");
    }
    }
    + +

    2.在启动类上添加自定义servlet的包扫描注解

    +
    package com.atguigu.boot;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.boot.web.servlet.ServletComponentScan;

    @ServletComponentScan("com.atguigu.boot") //指定原生的servlet的位置
    @SpringBootApplication
    public class Boot05Web01Application {

    public static void main(String[] args) {
    SpringApplication.run(Boot05Web01Application.class, args);
    }

    }
    + +

    3.启动程序测试

    +

    image-20230826211541424

    +
    2.原生的Filter的使用

    1.编写自定义的Filter实现Filter接口

    +
    package com.atguigu.boot.servlet;

    import lombok.extern.slf4j.Slf4j;

    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import java.io.IOException;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/26
    * @Description
    */
    @Slf4j
    @WebFilter(urlPatterns={"/css/*","/images/*","/my"}) //设置拦截的路径
    public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    log.info("MyFilter初始化完成");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    log.info("MyFilter工作");
    chain.doFilter(request,response);
    }

    @Override
    public void destroy() {
    log.info("MyFilter销毁");
    }
    }
    + +

    2.启动程序测试

    +

    image-20230826212400849

    +
    3.原生的Listener的使用

    1.编写自定义的MyServletContextListener实现ServletContextListener

    +
    package com.atguigu.boot.servlet;

    import lombok.extern.slf4j.Slf4j;

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;
    import javax.servlet.annotation.WebListener;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/26
    * @Description
    */
    @Slf4j
    @WebListener
    public class MyServletContextListener implements ServletContextListener {


    @Override
    public void contextInitialized(ServletContextEvent sce) {
    log.info("MyServletContextListener监听到项目初始化完成");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
    log.info("MyServletContextListener监听到项目销毁");
    }
    }
    + +

    2.启动程序测试

    +

    image-20230826213125490

    +

    7.2 使用RegistrationBean

    不使用@WebServlet、@WebFilter、@WebListener注解的方式(注释掉自定义servlet、Filter、Listener上的注解)

    +
    package com.atguigu.boot.servlet;

    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
    import org.springframework.boot.web.servlet.ServletRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    import java.util.Arrays;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/26
    * @Description
    */
    @Configuration
    public class MyRegisterConfig {
    @Bean
    public ServletRegistrationBean myServlet(){
    MyServlet myServlet = new MyServlet();

    return new ServletRegistrationBean(myServlet,"/my","/my02");
    }


    @Bean
    public FilterRegistrationBean myFilter(){

    MyFilter myFilter = new MyFilter();
    //return new FilterRegistrationBean(myFilter,myServlet());
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/my","/css/*"));
    return filterRegistrationBean;
    }

    @Bean
    public ServletListenerRegistrationBean myListener(){
    MyServletContextListener mySwervletContextListener = new MyServletContextListener();
    return new ServletListenerRegistrationBean(mySwervletContextListener);
    }
    }
    + +

    重新启动项目我们可以看到依然生效

    +

    image-20230826214253187

    +

    8.嵌入式Servlet容器

    8.1 切换嵌入式Servlet容器

      +
    • 默认支持的webServer

      +
    • +
      • +
      • Tomcat, Jetty, or Undertow
      • +
      • ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
      • +
      +
    • +
    • 切换服务器

      +

      1.排除tomcat的依赖

      +
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
      <exclusion>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
      </exclusions>
      </dependency>
      + +

      2.引入需要的服务器场景

      +
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
      </dependency>
      + +

      3.重启项目,可以看到服务器由tomcat变成了undertow

      +

      image-20230826221229898

      +
    • +
    • 原理

      +
    • +
      • +
      • SpringBoot应用启动发现当前是Web应用。web场景包-导入tomcat
      • +
      • web应用会创建一个web版的ioc容器 ServletWebServerApplicationContext
      • +
      • ServletWebServerApplicationContext 启动的时候寻找 **ServletWebServerFactory**``(Servlet 的web服务器工厂---> Servlet 的web服务器)
      • +
      • SpringBoot底层默认有很多的WebServer工厂;TomcatServletWebServerFactory, JettyServletWebServerFactory, or UndertowServletWebServerFactory
      • +
      • 底层直接会有一个自动配置类ServletWebServerFactoryAutoConfiguration
      • +
      • ServletWebServerFactoryAutoConfiguration导入了ServletWebServerFactoryConfiguration(配置类)
      • +
      • ServletWebServerFactoryConfiguration 配置类 根据动态判断系统中到底导入了那个Web服务器的包(默认是web-starter导入tomcat包),容器中就有 TomcatServletWebServerFactory
      • +
      • TomcatServletWebServerFactory 创建出Tomcat服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize---this.tomcat.start();
      • +
      • 内嵌服务器,就是手动把启动服务器的代码调用(tomcat核心jar包存在)
      • +
      +
    • +
    +

    8.2 定制Servlet容器

    修改配置文件中以server.xxx打头的属性信息

    +

    image-20230826222050817

    +

    三.数据访问

    1.SQL

    1.1 JDBC的使用

    1.导入JDBC的场景

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
    + +

    2.导入自己使用的数据库的驱动(Mysql或者Oracle)版本号不写也可以,官方做了版本仲裁(但是要与实际安装的版本对应)

    +
    想要修改版本
    1、直接依赖引入具体版本(maven的就近依赖原则)
    2、重新声明版本(maven的属性的就近优先原则)
    <properties>
    <java.version>1.8</java.version>
    <mysql.version>5.1.45</mysql.version>
    </properties>
    或者
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.45</version>
    </dependency>
    + +

    3.添加配置

    +
    spring:
    datasource:
    url: jdbc:mysql://localhost:3306/db_account
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    # type: com.zaxxer.hikari.HikariDataSource #配置数据源
    + +

    4.测试

    +
    package com.atguigu.boot;

    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;

    @Slf4j
    @SpringBootTest
    class Boot05Web01ApplicationTests {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    void contextLoads() {
    Long count = jdbcTemplate.queryForObject("select count(*) from `role` ", Long.class);
    log.info("记录数:{}",count);
    }
    }
    + +

    1.2 分析自动配置

    自动配置的类

    +

    image-20230826224503510

    +
      +
    • DataSourceAutoConfiguration : 数据源的自动配置

      +
    • +
      • +
      • 修改数据源相关的配置:spring.datasource
      • +
      • 数据库连接池的配置,是自己容器中没有DataSource才自动配置的
      • +
      • 底层配置好的连接池是:HikariDataSource
      • +
      +
    • +
    • DataSourceTransactionManagerAutoConfiguration: 事务管理器的自动配置

      +
    • +
    • JdbcTemplateAutoConfiguration: JdbcTemplate的自动配置,可以来对数据库进行crud

      +
    • +
      • +
      • 可以修改这个配置项@ConfigurationProperties(prefix = “spring.jdbc”) 来修改JdbcTemplate
      • +
      +
    • +
    • JndiDataSourceAutoConfiguration: jndi的自动配置

      +
    • +
    • XADataSourceAutoConfiguration: 分布式事务相关的

      +
    • +
    +

    四.单元测试

    1.JUnit5的变化

    Spring Boot 2.2.0 版本开始引入 JUnit 5 作为单元测试默认库

    +

    作为最新版本的JUnit框架,JUnit5与之前版本的Junit框架有很大的不同。由三个不同子项目的几个不同模块组成

    +

    JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

    +

    JUnit Platform: Junit Platform是在JVM上启动测试框架的基础,不仅支持Junit自制的测试引擎,其他测试引擎也都可以接入

    +

    JUnit Jupiter: JUnit Jupiter提供了JUnit5的新的编程模型,是JUnit5新特性的核心。内部 包含了一个测试引擎,用于在Junit Platform上运行

    +

    JUnit Vintage: 由于JUint已经发展多年,为了照顾老的项目,JUnit Vintage提供了兼容JUnit4.x,Junit3.x的测试引擎

    +

    注意:

    +

    SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖。如果需要兼容junit4需要自行引入(不能使用junit4的功能 @Test)

    +

    以前的版本

    +

    兼容以前的版本(junit4)需要额外导入的依赖(SpringBoot 2.4 以上版本移除了默认对 Vintage 的依赖)

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
    <exclusion>
    <groupId>org.hamcrest</groupId>
    <artifactId>hamcrest-core</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    + +

    标注的注解

    +

    @SpringBootTest + @RunWith(SpringRunner.class)+@Test(4版本的@Test)

    +

    现在的版本

    +

    需要导入的依赖

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    + +

    标注的注解

    +

    @SpringBootTest + @Test

    +

    两个不同版本的 @Test 注解

    +

    image-20230827112742347

    +

    2.JUnit5常用注解

    JUnit5的注解与JUnit4的注解有所变化

    +

    https://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

    +
      +
    • @Test :表示方法是测试方法。但是与JUnit4的@Test不同,他的职责非常单一不能声明任何属性,拓展的测试将会由Jupiter提供额外测试
    • +
    • @ParameterizedTest :表示方法是参数化测试,下方会有详细介绍
    • +
    • @RepeatedTest :表示方法可重复执行,下方会有详细介绍
    • +
    • @DisplayName :为测试类或者测试方法设置展示名称
    • +
    +

    image-20230827114256749

    +
      +
    • @BeforeEach :表示在每个单元测试之前执行

      +
    • +
    • @AfterEach :表示在每个单元测试之后执行

      +
    • +
    • @BeforeAll :表示在所有单元测试之前执行

      +
    • +
    • @AfterAll :表示在所有单元测试之后执行

      +
    • +
    +
    package com.atguigu.boot;

    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/27
    * @Description 测试junit5的相关注解
    */
    @Slf4j
    @DisplayName("Junit5Test测试类")
    @SpringBootTest
    public class Junit5Test {
    /**
    * @DisplayName 加在方法或者类上,为测试类或者测试方法设置展示名称
    */
    @Disabled
    @DisplayName("测试@DisplayName注解")
    @Test
    void testDisplayName(){
    log.info("@DisplayName注解的测试");
    }

    /**
    * 在执行的单元测试方法之前运行
    */
    @BeforeEach
    void testBeforeEach(){
    log.info("执行了testBeforeEach()方法");
    }

    /**
    * 在执行的单元测试方法之后运行
    */
    @AfterEach
    void testAfterEach() {
    log.info("执行了testAfterEach()方法");
    }

    /**
    * 在所有的单元测试执行之后执行
    * 注意是静态方法
    */
    @BeforeAll
    static void testBeforeAll() {
    log.info("执行了testBeforeAll()方法");
    }

    /**
    * 在所有的单元测试执行之前执行
    * 注意是静态方法
    */
    @AfterAll
    static void testAfterAll() {
    log.info("执行了testAfterAll()方法");
    }
    }
    + +
      +
    • @Tag :表示单元测试类别,类似于JUnit4中的@Categories
    • +
    • @Disabled :表示测试类或测试方法不执行,类似于JUnit4中的@Ignore
    • +
    +

    image-20230827115900097

    +
      +
    • @Timeout :表示测试方法运行如果超过了指定时间将会返回错误
    • +
    +
    /**
    * 规定方法超时时间 超出时间测试出异常
    */
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
    Thread.sleep(600);
    }
    + +
      +
    • @ExtendWith :为测试类或测试方法提供扩展类引用
    • +
    +
    /**
    * 重复测试
    */
    @RepeatedTest(5)
    @Test
    void testRepeatedTest() {
    log.info("重复测试中");
    }
    + +

    image-20230828095915653

    +

    全部的代码

    +
    package com.atguigu.boot;

    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;

    import java.util.concurrent.TimeUnit;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/8/27
    * @Description 测试junit5的相关注解
    */
    @Slf4j
    @DisplayName("Junit5Test测试类")
    @SpringBootTest
    public class Junit5Test {
    /**
    * @DisplayName 加在方法或者类上,为测试类或者测试方法设置展示名称
    */
    @Disabled
    @DisplayName("测试@DisplayName注解")
    @Test
    void testDisplayName(){
    log.info("@DisplayName注解的测试");
    }

    /**
    * 在执行的单元测试方法之前运行
    */
    @BeforeEach
    void testBeforeEach(){
    log.info("执行了testBeforeEach()方法");
    }

    /**
    * 在执行的单元测试方法之后运行
    */
    @AfterEach
    void testAfterEach() {
    log.info("执行了testAfterEach()方法");
    }

    /**
    * 在所有的单元测试执行之后执行
    * 注意是静态方法
    */
    @BeforeAll
    static void testBeforeAll() {
    log.info("执行了testBeforeAll()方法");
    }

    /**
    * 在所有的单元测试执行之前执行
    * 注意是静态方法
    */
    @AfterAll
    static void testAfterAll() {
    log.info("执行了testAfterAll()方法");
    }
    /**
    * 规定方法超时时间 超出时间测试出异常
    */
    @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
    @Test
    void testTimeout() throws InterruptedException {
    Thread.sleep(600);
    }

    /**
    * 重复测试
    */
    @RepeatedTest(5)
    @Test
    void testRepeatedTest() {
    log.info("重复测试中");
    }


    }
    + +

    3.断言

    ​ 断言(assertions)是测试方法中的核心部分,用来对测试需要满足的条件进行验证。这些断言方法都是 org.junit.jupiter.api.Assertions 的静态方法。检查业务逻辑返回的数据是否合理。所有的测试运行结束以后,会有一个详细的测试报告。

    +

    3.1 简单断言

    用来对单个值进行简单的验证

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    方法说明
    assertEquals判断两个对象或两个原始类型是否相等
    assertNotEquals判断两个对象或两个原始类型是否不相等
    assertSame判断两个对象引用是否指向同一个对象
    assertNotSame判断两个对象引用是否指向不同的对象
    assertTrue判断给定的布尔值是否为 true
    assertFalse判断给定的布尔值是否为 false
    assertNull判断给定的对象引用是否为 null
    assertNotNull判断给定的对象引用是否不为 null
    +
    @Test
    @DisplayName("简单断言")
    public void simple() {
    assertEquals(3, 1 + 2, "simple math");
    assertNotEquals(3, 1 + 1);

    assertNotSame(new Object(), new Object());
    Object obj = new Object();
    assertSame(obj, obj);

    assertFalse(1 > 2);
    assertTrue(1 < 2);

    assertNull(null);
    assertNotNull(new Object());
    }
    + +

    3.2 数组断言

    通过 assertArrayEquals 方法来判断两个对象或原始类型的数组是否相等

    +
    @Test
    @DisplayName("数组断言")
    public void array() {
    assertArrayEquals(new int[]{1, 2}, new int[] {1, 2});
    }
    + +

    3.3 组合断言

    assertAll 方法接受多个 org.junit.jupiter.api.Executable 函数式接口的实例作为要验证的断言,可以通过 lambda 表达式很容易的提供这些断言

    +
    @Test
    @DisplayName("组合断言")
    public void all() {
    assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
    );
    }
    + +

    3.4 异常断言

    在JUnit4时期,想要测试方法的异常情况时,需要用@Rule注解的ExpectedException变量还是比较麻烦的。而JUnit5提供了一种新的断言方式Assertions.assertThrows() ,配合函数式编程就可以进行使用。

    +
    @Test
    @DisplayName("异常测试")
    public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
    //扔出断言异常
    ArithmeticException.class, () -> System.out.println(1 % 0));

    }
    + +

    3.5 超时断言

    Junit5还提供了Assertions.assertTimeout() 为测试方法设置了超时时间

    +
    @Test
    @DisplayName("超时测试")
    public void timeoutTest() {
    //如果测试方法时间超过1s将会异常
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
    }
    + +

    3.6 快速失败

    通过 fail 方法直接使得测试失败

    +
    @Test
    @DisplayName("快速失败")
    public void shouldFail() {
    fail("This should fail");
    }
    + +

    全部代码

    +
    package com.atguigu.admin;

    import org.junit.jupiter.api.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.jdbc.core.JdbcTemplate;

    import java.util.concurrent.TimeUnit;

    import static org.junit.jupiter.api.Assertions.*;



    /*@RunWith*/

    /**
    * @BootstrapWith(SpringBootTestContextBootstrapper.class)
    * @ExtendWith(SpringExtension.class)
    */
    //@SpringBootTest
    @DisplayName("junit5功能测试类")
    public class Junit5Test {

    /**
    * 断言:前面断言失败,后面的代码都不会执行
    */
    @DisplayName("简单断言")
    @Test
    void testSimpleAssertions() {
    int cal = cal(3, 2);
    //相等
    assertEquals(6, cal, "业务逻辑计算失败");
    Object obj1 = new Object();
    Object obj2 = new Object();
    assertSame(obj1, obj2, "两个对象不一样");

    }

    @Test
    @DisplayName("数组断言")
    void array() {
    assertArrayEquals(new int[]{1, 2}, new int[]{1, 2}, "数组内容不相等");
    }

    @Test
    @DisplayName("组合断言")
    void all() {
    /**
    * 所有断言全部需要成功
    */
    assertAll("test",
    () -> assertTrue(true && true, "结果不为true"),
    () -> assertEquals(1, 2, "结果不是1"));

    System.out.println("=====");
    }

    @DisplayName("异常断言")
    @Test
    void testException() {

    //断定业务逻辑一定出现异常
    assertThrows(ArithmeticException.class, () -> {
    int i = 10 / 2;
    }, "业务逻辑居然正常运行?");
    }

    @DisplayName("快速失败")
    @Test
    void testFail(){
    if(1 == 2){
    fail("测试失败");
    }

    }

    int cal(int i, int j) {
    return i + j;
    }
    }
    + +

    4.前置条件

    ​ JUnit 5 中的前置条件(assumptions[假设])类似于断言,不同之处在于不满足的断言会使得测试方法失败,而不满足的前置条件只会使得测试方法的执行终止。前置条件可以看成是测试方法执行的前提,当该前提不满足时,就没有继续执行的必要

    +
    @DisplayName("前置条件")
    public class AssumptionsTest {
    private final String environment = "DEV";

    @Test
    @DisplayName("simple")
    public void simpleAssume() {
    assumeTrue(Objects.equals(this.environment, "DEV"));
    assumeFalse(() -> Objects.equals(this.environment, "PROD"));
    }

    @Test
    @DisplayName("assume then do")
    public void assumeThenDo() {
    assumingThat(
    Objects.equals(this.environment, "DEV"),
    () -> System.out.println("In DEV")
    );
    }
    }
    + +

    assumeTrue 和 assumFalse 确保给定的条件为 true 或 false,不满足条件会使得测试执行终止

    +

    assumingThat 的参数是表示条件的布尔值和对应的 Executable 接口的实现对象

    +

    只有条件满足时,Executable 对象才会被执行;当条件不满足时,测试执行并不会终止

    +

    5.嵌套测试

    ​ JUnit 5 可以通过 Java 中的内部类和@Nested 注解实现嵌套测试,从而可以更好的把相关的测试方法组织在一起。在内部类中可以使用@BeforeEach 和@AfterEach 注解,而且嵌套的层次没有限制

    +
    @DisplayName("A stack")
    class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
    new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

    @BeforeEach
    void createNewStack() {
    stack = new Stack<>();
    }

    @Test
    @DisplayName("is empty")
    void isEmpty() {
    assertTrue(stack.isEmpty());
    }

    @Test
    @DisplayName("throws EmptyStackException when popped")
    void throwsExceptionWhenPopped() {
    assertThrows(EmptyStackException.class, stack::pop);
    }

    @Test
    @DisplayName("throws EmptyStackException when peeked")
    void throwsExceptionWhenPeeked() {
    assertThrows(EmptyStackException.class, stack::peek);
    }

    @Nested//b
    @DisplayName("after pushing an element")
    class AfterPushing {

    String anElement = "an element";

    @BeforeEach
    void pushAnElement() {
    stack.push(anElement);
    }

    @Test
    @DisplayName("it is no longer empty")
    void isNotEmpty() {
    assertFalse(stack.isEmpty());
    }

    @Test
    @DisplayName("returns the element when popped and is empty")
    void returnElementWhenPopped() {
    assertEquals(anElement, stack.pop());
    assertTrue(stack.isEmpty());
    }

    @Test
    @DisplayName("returns the element when peeked but remains not empty")
    void returnElementWhenPeeked() {
    assertEquals(anElement, stack.peek());
    assertFalse(stack.isEmpty());
    }
    }
    }
    }
    + +

    6.参数化测试

    参数化测试是JUnit5很重要的一个新特性,它使得用不同的参数多次运行测试成为了可能,也为我们的单元测试带来许多便利

    +

    利用@ValueSource等注解,指定入参,我们将可以使用不同的参数进行多次单元测试,而不需要每新增一个参数就新增一个单元测试,省去了很多冗余代码

    +

    @ValueSource: 为参数化测试指定入参来源,支持八大基础类以及String类型,Class类型

    +

    @NullSource: 表示为参数化测试提供一个null的入参

    +

    @EnumSource: 表示为参数化测试提供一个枚举入参

    +

    @CsvFileSource:表示读取指定CSV文件内容作为参数化测试入参

    +

    @MethodSource:表示读取指定方法的返回值作为参数化测试入参(注意方法返回需要是一个流)

    +

    当然如果参数化测试仅仅只能做到指定普通的入参还达不到让我觉得惊艳的地步。让我真正感到他的强大之处的地方在于他可以支持外部的各类入参。如:CSV,YML,JSON 文件甚至方法的返回值也可以作为入参。只需要去实现ArgumentsProvider接口,任何外部文件都可以作为它的入参

    +
    @ParameterizedTest
    @ValueSource(strings = {"one", "two", "three"})
    @DisplayName("参数化测试")
    public void parameterizedTest(String string) {
    System.out.println(string);
    Assertions.assertTrue(StringUtils.isNotBlank(string));
    }


    @ParameterizedTest
    @MethodSource("method") //指定方法名
    @DisplayName("方法来源参数")
    public void testWithExplicitLocalMethodSource(String name) {//方法的参数来源于一个方法
    System.out.println(name);
    Assertions.assertNotNull(name);
    }

    static Stream<String> method() {
    return Stream.of("apple", "banana");
    }
    + +

    7.迁移指南

    在进行迁移(Junit4迁移到Junit5)的时候需要注意如下的变化:

    +
      +
    • 注解在 org.junit.jupiter.api 包中,断言在 org.junit.jupiter.api.Assertions 类中,前置条件在 org.junit.jupiter.api.Assumptions 类中。
    • +
    • 把@Before 和@After 替换成@BeforeEach 和@AfterEach。
    • +
    • 把@BeforeClass 和@AfterClass 替换成@BeforeAll 和@AfterAll。
    • +
    • 把@Ignore 替换成@Disabled。
    • +
    • 把@Category 替换成@Tag。
    • +
    • 把@RunWith、@Rule 和@ClassRule 替换成@ExtendWith。
    • +
    +

    五.指标监控

    1.SpringBoot Actuator

    ​ 未来每一个微服务在云上部署以后,我们都需要对其进行监控、追踪、审计、控制等。SpringBoot就抽取了Actuator场景,使得我们每个微服务快速引用即可获得生产级别的应用监控、审计等功能

    +

    1.1 如何使用

    1.引入依赖

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    + +

    2.测试访问 http://localhost:8080/actuator/ endpoint节点

    +

    image-20230828110908056

    +

    image-20230828110928951

    +

    3.设置以web的方式访问所有的端点

    +
    management:
    endpoints:
    enabled-by-default: true #暴露所有端点信息
    web:
    exposure:
    include: '*' #以web方式暴露所有的端点
    + +

    设置之后就可以通过web(访问 http://localhost:8080/actuator/beans)的方式访问beans端点了

    +

    image-20230828113335033

    +

    支持的暴露方式

    +
      +
    • HTTP(web):默认只暴露health和info Endpoint,只能访问 http://localhost:8080/actuator/health和 http://localhost:8080/actuator/info
    • +
    • JMX(如Jconsole):默认暴露所有Endpoint,类似于cmd下打开jconsole,可以访问下面所有的endPoints
    • +
    • 除过health和info,剩下的Endpoint都应该进行保护访问。如果引入SpringSecurity,则会默认配置安全访问规则
    • +
    +

    是通过web还是jmx方式访问的端点一览表

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    IDJMXWeb
    auditeventsYesNo
    beansYesNo
    cachesYesNo
    conditionsYesNo
    configpropsYesNo
    envYesNo
    flywayYesNo
    healthYesYes
    heapdumpN/ANo
    httptraceYesNo
    infoYesYes
    integrationgraphYesNo
    jolokiaN/ANo
    logfileN/ANo
    loggersYesNo
    liquibaseYesNo
    metricsYesNo
    mappingsYesNo
    prometheusN/ANo
    scheduledtasksYesNo
    sessionsYesNo
    shutdownYesNo
    startupYesNo
    threaddumpYesNo
    +

    1.2 常用的EndPoints

    http://localhost:8080/actuator/endPoint

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ID描述
    auditevents暴露当前应用程序的审核事件信息。需要一个AuditEventRepository组件
    beans显示应用程序中所有Spring Bean的完整列表。
    caches暴露可用的缓存。
    conditions显示自动配置的所有条件信息,包括匹配或不匹配的原因。
    configprops显示所有@ConfigurationProperties
    env暴露Spring的属性ConfigurableEnvironment
    flyway显示已应用的所有Flyway数据库迁移。 需要一个或多个Flyway组件。
    health显示应用程序运行状况信息。
    httptrace显示HTTP跟踪信息(默认情况下,最近100个HTTP请求-响应)。需要一个HttpTraceRepository组件。
    info显示应用程序信息。
    integrationgraph显示Spring integrationgraph 。需要依赖spring-integration-core
    loggers显示和修改应用程序中日志的配置。
    liquibase显示已应用的所有Liquibase数据库迁移。需要一个或多个Liquibase组件。
    metrics显示当前应用程序的“指标”信息。
    mappings显示所有@RequestMapping路径列表。
    scheduledtasks显示应用程序中的计划任务。
    sessions允许从Spring Session支持的会话存储中检索和删除用户会话。需要使用Spring Session的基于Servlet的Web应用程序。
    shutdown使应用程序正常关闭。默认禁用。
    startup显示由ApplicationStartup收集的启动步骤数据。需要使用SpringApplication进行配置BufferingApplicationStartup
    threaddump执行线程转储。
    +

    如果您的应用程序是Web应用程序(Spring MVC,Spring WebFlux或Jersey),则可以使用以下附加端点:

    + + + + + + + + + + + + + + + + + + + + + + + +
    ID描述
    heapdump返回hprof堆转储文件。
    jolokia通过HTTP暴露JMX bean(需要引入Jolokia,不适用于WebFlux)。需要引入依赖jolokia-core
    logfile返回日志文件的内容(如果已设置logging.file.namelogging.file.path属性)。支持使用HTTPRange标头来检索部分日志文件的内容。
    prometheus以Prometheus服务器可以抓取的格式公开指标。需要依赖micrometer-registry-prometheus
    +

    最常用的Endpoint

    +
      +
    • Health:监控状况
    • +
    • Metrics:运行时指标
    • +
    • Loggers:日志记录
    • +
    +

    1.3 Health Endpoint

    健康检查端点,我们一般用于在云平台,平台会定时的检查应用的健康状况,我们就需要Health Endpoint可以为平台返回当前应用的一系列组件健康状况的集合。

    +

    重要的几点:

    +
      +
    • health endpoint返回的结果,应该是一系列健康检查后的一个汇总报告
    • +
    • 很多的健康检查默认已经自动配置好了,比如:数据库、redis等
    • +
    • 可以很容易的添加自定义的健康检查机制
    • +
    +

    设置显示health端点的详细信息

    +
    management:
    endpoint:
    health:
    show-details: always
    + +

    image-20230828142631189

    +
    {
    "status": "UP",
    "components": {
    "db": {
    "status": "UP",
    "details": {
    "database": "MySQL",
    "validationQuery": "isValid()"
    }
    },
    "diskSpace": {
    "status": "UP",
    "details": {
    "total": 510769754112,
    "free": 342642921472,
    "threshold": 10485760,
    "exists": true
    }
    },
    "ping": {
    "status": "UP"
    }
    }
    }
    + +

    1.4 Metrics Endpoint

    提供详细的、层级的、空间指标信息,这些信息可以被pull(主动推送)或者push(被动获取)方式得到;

    +
      +
    • 通过Metrics对接多种监控系统
    • +
    • 简化核心Metrics开发
    • +
    • 添加自定义Metrics或者扩展已有Metrics
    • +
    +

    查看完整的监控指标信息 http://localhost:8080/actuator/metrics

    +
    {
    "names": [
    "hikaricp.connections",
    "hikaricp.connections.acquire",
    "hikaricp.connections.active",
    "hikaricp.connections.creation",
    "hikaricp.connections.idle",
    "hikaricp.connections.max",
    "hikaricp.connections.min",
    "hikaricp.connections.pending",
    "hikaricp.connections.timeout",
    "hikaricp.connections.usage",
    "http.server.requests",
    "jdbc.connections.max",
    "jdbc.connections.min",
    "jvm.buffer.count",
    "jvm.buffer.memory.used",
    "jvm.buffer.total.capacity",
    "jvm.classes.loaded",
    "jvm.classes.unloaded",
    "jvm.gc.live.data.size",
    "jvm.gc.max.data.size",
    "jvm.gc.memory.allocated",
    "jvm.gc.memory.promoted",
    "jvm.gc.pause",
    "jvm.memory.committed",
    "jvm.memory.max",
    "jvm.memory.used",
    "jvm.threads.daemon",
    "jvm.threads.live",
    "jvm.threads.peak",
    "jvm.threads.states",
    "logback.events",
    "process.cpu.usage",
    "process.start.time",
    "process.uptime",
    "system.cpu.count",
    "system.cpu.usage"
    ]
    }
    + +

    image-20230828143224633

    +

    查看某一个监控指标的详细信息(例如查看hikaricp.connections的详细信息) http://localhost:8080/actuator/metrics/hikaricp.connections

    +
    {
    "name": "hikaricp.connections",
    "description": "Total connections",
    "baseUnit": null,
    "measurements": [
    {
    "statistic": "VALUE",
    "value": 10
    }
    ],
    "availableTags": [
    {
    "tag": "pool",
    "values": [
    "HikariPool-1"
    ]
    }
    ]
    }
    + +

    1.5 管理Endpoint

    开启与禁用Endpoints

    +
      +
    • 默认所有的Endpoint除过shutdown都是开启的。
    • +
    • 需要开启或者禁用某个Endpoint 配置模式为 management.endpoint.endpointName.enabled = true
    • +
    +
    management:
    endpoint:
    beans: #可以写health、metrics等端点
    enabled: true
    + +
      +
    • 或者禁用所有的Endpoint然后手动开启指定的Endpoint
    • +
    +
    management:
    endpoints:
    enabled-by-default: false #禁用所有的端点(jconsole中也无法使用)
    endpoint:
    beans:
    enabled: true #逐一开启端点
    health:
    enabled: true
    + +

    1.6 定制Endpoint

    1.6.1 定制health endpoint

    1.编写MyComHealthIndicator类继承AbstractHealthIndicator

    +
    package com.atguigu.boot.acutuator.health;

    import org.springframework.boot.actuate.health.AbstractHealthIndicator;
    import org.springframework.boot.actuate.health.Health;
    import org.springframework.boot.actuate.health.Status;
    import org.springframework.stereotype.Component;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * 自定义端点
    * 类的后缀必须为HealthIndicator
    */
    @Component
    public class MyComHealthIndicator extends AbstractHealthIndicator {

    /**
    * 真实的检查方法 检查健康的方法
    */
    @Override
    protected void doHealthCheck(Health.Builder builder) throws Exception {
    Map<String,Object> map = new HashMap<>();
    // 检查完成
    if(1 == 1){ //这里我们可以编写一个业务逻辑,例如数据库是否连接成功的业务逻辑
    // builder.up(); //健康
    builder.status(Status.UP);
    map.put("count",1);
    map.put("ms",100);
    }else {
    //builder.down();
    builder.status(Status.OUT_OF_SERVICE);
    map.put("err","连接超时");
    map.put("ms",3000);
    }
    builder.withDetail("code",100)
    .withDetails(map);

    }
    }
    + +

    myCom = MyComHealthIndicator - HealthIndicator 所以类的名字一定要以HealthIndicator为后缀

    +

    image-20230828150633322

    +
    1.6.2 定制 info endpoint

    方式一: 编写配置文件

    +
    info:
    appName: boot-admin #应用名
    version: 2.0.1 #版本
    mavenProjectName: @project.artifactId@ #使用@@可以获取maven的pom文件值
    mavenProjectVersion: @project.version@
    + +

    image-20230828151416487

    +

    方式二: 编写InfoContributor

    +
    package com.atguigu.boot.acutuator.info;

    import org.springframework.boot.actuate.info.Info;
    import org.springframework.boot.actuate.info.InfoContributor;
    import org.springframework.stereotype.Component;

    import java.util.Collections;


    @Component
    public class AppInfoInfoContributor implements InfoContributor {

    @Override
    public void contribute(Info.Builder builder) {

    builder.withDetail("msg","你好")
    .withDetail("hello","atguigu")
    .withDetails(Collections.singletonMap("world","666600"));
    }
    }
    + +

    如果我们没有删除上面配置文件的配置,这时我们访问 http://localhost:8080/actuator/info 的时候会将配置文件和InfoContributor中的信息共同返回

    +

    image-20230828152006549

    +
    1.6.3 定制Metrics信息
    class MyService{

    Counter counter;

    //构造器注入MeterRegistry
    public MyService(MeterRegistry meterRegistry){
    //访问 http://localhost:8080/actuator/metrics 的时候会多一个监控项myservice.method.running.counter
    counter = meterRegistry.counter("myservice.method.running.counter");
    }

    public void hello() {
    //调用hello方法的时候counter加1
    counter.increment();
    }
    }

    //也可以使用下面的方式
    @Bean
    MeterBinder queueSize(Queue queue) {
    return (registry) -> Gauge.builder("queueSize", queue::size).register(registry);
    }
    + +

    1.7 新增Endpoint

    package com.atguigu.boot.acutuator.endpoint;


    import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
    import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
    import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
    import org.springframework.stereotype.Component;

    import java.util.Collections;
    import java.util.Map;


    @Component
    @Endpoint(id = "myservice")
    public class MyServiceEndPoint {


    @ReadOperation
    public Map getDockerInfo(){
    //端点的读操作 http://localhost:8080/actuator/myservice
    return Collections.singletonMap("dockerInfo","docker started.....");
    }

    @WriteOperation
    public void stopDocker(){
    System.out.println("docker stopped.....");
    }
    }
    + +

    image-20230828153914619

    +

    image-20230828153902598

    +

    1.8 可视化

    github地址: https://github.com/codecentric/spring-boot-admin

    +

    项目文档: https://docs.spring-boot-admin.com/current/getting-started.html

    +

    1.创建一个新的springBoot项目用作服务端(场景只需要选择web场景即可)

    +

    image-20230828154818630

    +

    2.引入依赖

    +
    <dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.3.1</version>
    </dependency>
    + +

    3.在启动类上添加注解

    +
    @EnableAdminServer
    + +

    4.修改以下端口号以防和业务的端口冲突

    +
    server.port=8888
    + +

    5.访问http://localhost:8888

    +

    image-20230828155537932

    +

    6.客户端上引入依赖

    +
    <dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.3.1</version>
    </dependency>
    + +

    7.客户端的配置文件添加以下的配置

    +
    spring.boot.admin.client.url=http://localhost:8888 #服务端的地址
    management.endpoints.web.exposure.include=*
    + +

    8.重启一下客户端,然后访问服务端的localhost:8080

    +

    image-20230828160606081

    +

    点击应用墙,点击需要查看信息的应用就可以看到应用的详细信息

    +

    image-20230828160731314

    +

    六.原理解析

    1.Profile功能

    为了方便多环境适配,springboot简化了profile功能

    +
      +
    • 默认配置文件 application.yaml;任何时候都会加载

      +
    • +
    • 指定环境配置文件 application-{env}.yaml

      +
    • +
    • 激活指定环境

      +
    • +
      • +
      • 配置文件激活,在默认配置文件 application.yaml 中添加spring.profiles.active=xxxx
      • +
      • 命令行激活:java -jar xxx.jar –spring.profiles.active=prod
      • +
      +
    • +
      • +
        • +
        • 修改配置文件的任意值,命令行优先
        • +
        +
      • +
      +
    • +
    • 默认配置与环境配置同时生效

      +
    • +
    • 同名配置项,profile配置优先

      +
    • +
    +

    @Profile注解

    +
    //@Profile("prod")
    //可以标注在类和方法上
    //标注在类和方法上表示在prod环境下启用
    + +

    外部配置源

    +

    常用:Java属性文件、YAML文件、环境变量、命令行参数;

    +

    配置文件查找位置

    +

    下面的优先级覆盖上面的优先级

    +

    (1) classpath 根路径

    +

    (2) classpath 根路径下config目录

    +

    (3) jar包当前目录

    +

    (4) jar包当前目录的config目录

    +

    (5) /config子目录的直接子目录

    +

    配置文件加载顺序:

    +
      +
    1.  当前jar包内部的application.properties和application.yml
    2. +
    3.  当前jar包内部的application-{profile}.properties 和 application-{profile}.yml
    4. +
    5.  引用的外部jar包的application.properties和application.yml
    6. +
    7.  引用的外部jar包的application-{profile}.properties 和 application-{profile}.yml
    8. +
    +

    总结: 指定环境优先,外部优先,后面的可以覆盖前面的同名配置项

    +]]>
    + + 后端 + + + SpringBoot + +
    + + ElasticSearch + /posts/22654.html + 官网: https://www.elastic.co/cn/elasticsearch/

    +

    简介

    ​ Elasticsearch 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。作为 Elastic Stack 的核心,Elasticsearch 会集中存储您的数据,让您飞快完成搜索,微调相关性,进行强大的分析,并轻松缩放规模。

    +

    一.基本概念

    1.Index(索引)

    +

    动词:相当于mysql的insert

    +

    名词:相当于mysql的database

    +

    2.Type(类型)

    +

    在index中可以定义一个或者多个类型

    +

    类似于mysql中的Table;每一种类型的数据放在一起

    +

    3.Document(文档)

    +

    保存在某个索引(index)下,某种类型(Type)的一个数据(Document),文档是JSON格式的,

    +

    Document就是像mysql中的某个Table里面的内容。

    +

    4.倒排索引

    +

    image-20230628180000365

    +

    二.Docker安装ElasticSearch

    1.安装ElasticSearch(存储和检索数据)

    # 拉取ElasticSearch
    docker pull elasticsearch:7.4.2

    #ES的配置文件存放的位置
    mkdir -p /mydata/elasticsearch/config

    #ES相关的数据
    mkdir -p /mydata/elasticsearch/data

    #"http.host: 0.0.0.0"(可以被任何的机器访问)的配置写入elasticsearch.yml中(注意冒号后面的空格)
    echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

    #设置文件的权限
    # 递归更改权限,es需要访问
    chmod -R 777 /mydata/elasticsearch/

    #运行容器的命令 9200端口用于发送请求使用 9300端口用于集群中节点中的通信使用
    #单节点运行
    #初始占用64m,最大占用512m(不指定,ES会占用所有的内存)
    docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
    -e "discovery.type=single-node" \
    -e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
    -v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
    -v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
    -v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
    -d elasticsearch:7.4.2

    # 设置开机启动elasticsearch
    docker update elasticsearch --restart=always

    #访问端口 测试是否安装成功
    http://虚拟机的ip:9200/
    + +

    image-20230628191936254

    +

    2.安装Kibana(可视化检索数据)

    #拉取Kibana
    docker pull kibana:7.4.2
    # 这里-e是自己的elasticsearch服务地址(这里的地址一定要改为自己虚拟机的地址)
    docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.195.100:9200 -p 5601:5601 -d kibana:7.4.2
    # 设置开机启动kibana
    docker update kibana --restart=always
    #访问对应的网址查看服务
    http://虚拟机的ip:5601
    + +

    image-20230701104616032

    +

    三.初步检索

    1._Cat

    GET/_cat/nodes 查看所有节点

    +

    GET/_cat/health 查看es的健康状态

    +

    GET/_cat/master 查看主节点

    +

    GET/_cat/indices 查看所有索引

    +

    2.索引文档(保存)

    保存操作可以发送PUT或者POST请求

    +

    put需要在路径上指定id

    +

    post请求可以不指定id,自动生成一个唯一标识

    +

    例:PUT customer/external/1 在customer索引下的external类型下保存1号数据为

    +
    -> PUT customer/external/1
    //请求体
    {
    "name":"John Doe"
    }
    + +

    image-20230701144040577

    +

    返回的数据解析

    +
    //_开头的数据称为元数据
    {
    "_index": "customer",//数据存在的数据库
    "_type": "external",//保存数据的类型
    "_id": "1",//数据的id
    "_version": 1,//数据的版本
    "result": "created",//保存为"created",修改为"updated",发送多次为更新的操作
    "_shards": { //分片
    "total": 2,
    "successful": 1,
    "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
    }
    + +

    3.查询文档

    -> GET customer/external/1
    + +

    image-20230701145518348

    +

    返回数据解析

    +
    {
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 1,
    "_seq_no": 0,//并发控制的字段,每次更新就会+1,用来做乐观锁
    "_primary_term": 1,//同上,主分片重新分配,如重启,就会有变化
    "found": true,//是否找到
    "_source": {//数据真正的内容
    "name": "John Doe"
    }
    }
    + +

    4.更新文档

    POST customer/external/1/_update  //对比原先的数据,和原先的一样_version和_seq_no不会发生改变
    {
    "doc":{
    "name": "John Doe"
    }
    }
    或者
    POST customer/external/1 //和之前的数据一样的话也会执行更新操作
    {
    "name": "John Doe"
    }
    或者
    PUT customer/external/1
    {
    "name": "John Doe"
    }
    + +

    ​ 使用POST customer/external/1/_update 的方式进行更新会对比原先的数据,和原先的一样version和_seq_no不会发生改变,下面是我们使用同样的数据发送两次更新请求,第二次的result显示”noop”,没有进行操作

    +

    image-20230701151445190

    +

    5.删除文档|索引

    DELETE customer/external/1
    DELETE customer
    + +

    删除文档

    +

    image-20230701152501989

    +

    删除索引

    +

    image-20230701152648756

    +

    6.bulk批量API

    Kibana的使用

    +

    image-20230701153754438

    +

    批量保存数据

    +
    POST /customer/external/_bulk
    {"index":{"_id":"1"}}
    {"name":"a"}
    {"index":{"_id":"2"}}
    {"name":"b"}
    新增两条数据
    新增id为1的 对应 name为a
    新增Id为2的 对应 name为b
    + +

    image-20230701154217181

    +

    复杂批量操作

    +
    POST /_bulk
    {"delete":{"_index":"website","_type":"blog","_id":"123"}}
    {"create":{"_index":"website","_type":"blog","_id":"123"}}
    {"title":"my first blog post"}
    {"index":{"_index":"website","_type":"blog"}}
    {"title":"my second blog post"}
    {"update":{"_index":"website","_type":"blog","_id":"123"}}
    {"doc":{"title":"my updated blog post"}}
    + +

    image-20230701154839574

    +

    7.样本测试数据

    POST /bank/account/_bulk
    {"index":{"_id":"1"}}
    {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
    {"index":{"_id":"6"}}
    {"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"}
    {"index":{"_id":"13"}}
    {"account_number":13,"balance":32838,"firstname":"Nanette","lastname":"Bates","age":28,"gender":"F","address":"789 Madison Street","employer":"Quility","email":"nanettebates@quility.com","city":"Nogal","state":"VA"}
    {"index":{"_id":"18"}}
    {"account_number":18,"balance":4180,"firstname":"Dale","lastname":"Adams","age":33,"gender":"M","address":"467 Hutchinson Court","employer":"Boink","email":"daleadams@boink.com","city":"Orick","state":"MD"}
    {"index":{"_id":"20"}}
    {"account_number":20,"balance":16418,"firstname":"Elinor","lastname":"Ratliff","age":36,"gender":"M","address":"282 Kings Place","employer":"Scentric","email":"elinorratliff@scentric.com","city":"Ribera","state":"WA"}
    {"index":{"_id":"25"}}
    {"account_number":25,"balance":40540,"firstname":"Virginia","lastname":"Ayala","age":39,"gender":"F","address":"171 Putnam Avenue","employer":"Filodyne","email":"virginiaayala@filodyne.com","city":"Nicholson","state":"PA"}
    {"index":{"_id":"32"}}
    {"account_number":32,"balance":48086,"firstname":"Dillard","lastname":"Mcpherson","age":34,"gender":"F","address":"702 Quentin Street","employer":"Quailcom","email":"dillardmcpherson@quailcom.com","city":"Veguita","state":"IN"}
    {"index":{"_id":"37"}}
    {"account_number":37,"balance":18612,"firstname":"Mcgee","lastname":"Mooney","age":39,"gender":"M","address":"826 Fillmore Place","employer":"Reversus","email":"mcgeemooney@reversus.com","city":"Tooleville","state":"OK"}
    {"index":{"_id":"44"}}
    {"account_number":44,"balance":34487,"firstname":"Aurelia","lastname":"Harding","age":37,"gender":"M","address":"502 Baycliff Terrace","employer":"Orbalix","email":"aureliaharding@orbalix.com","city":"Yardville","state":"DE"}
    {"index":{"_id":"49"}}
    {"account_number":49,"balance":29104,"firstname":"Fulton","lastname":"Holt","age":23,"gender":"F","address":"451 Humboldt Street","employer":"Anocha","email":"fultonholt@anocha.com","city":"Sunriver","state":"RI"}
    {"index":{"_id":"51"}}
    {"account_number":51,"balance":14097,"firstname":"Burton","lastname":"Meyers","age":31,"gender":"F","address":"334 River Street","employer":"Bezal","email":"burtonmeyers@bezal.com","city":"Jacksonburg","state":"MO"}
    {"index":{"_id":"56"}}
    {"account_number":56,"balance":14992,"firstname":"Josie","lastname":"Nelson","age":32,"gender":"M","address":"857 Tabor Court","employer":"Emtrac","email":"josienelson@emtrac.com","city":"Sunnyside","state":"UT"}
    {"index":{"_id":"63"}}
    {"account_number":63,"balance":6077,"firstname":"Hughes","lastname":"Owens","age":30,"gender":"F","address":"510 Sedgwick Street","employer":"Valpreal","email":"hughesowens@valpreal.com","city":"Guilford","state":"KS"}
    {"index":{"_id":"68"}}
    {"account_number":68,"balance":44214,"firstname":"Hall","lastname":"Key","age":25,"gender":"F","address":"927 Bay Parkway","employer":"Eventex","email":"hallkey@eventex.com","city":"Shawmut","state":"CA"}
    {"index":{"_id":"70"}}
    {"account_number":70,"balance":38172,"firstname":"Deidre","lastname":"Thompson","age":33,"gender":"F","address":"685 School Lane","employer":"Netplode","email":"deidrethompson@netplode.com","city":"Chestnut","state":"GA"}
    {"index":{"_id":"75"}}
    {"account_number":75,"balance":40500,"firstname":"Sandoval","lastname":"Kramer","age":22,"gender":"F","address":"166 Irvington Place","employer":"Overfork","email":"sandovalkramer@overfork.com","city":"Limestone","state":"NH"}
    {"index":{"_id":"82"}}
    {"account_number":82,"balance":41412,"firstname":"Concetta","lastname":"Barnes","age":39,"gender":"F","address":"195 Bayview Place","employer":"Fitcore","email":"concettabarnes@fitcore.com","city":"Summerfield","state":"NC"}
    {"index":{"_id":"87"}}
    {"account_number":87,"balance":1133,"firstname":"Hewitt","lastname":"Kidd","age":22,"gender":"M","address":"446 Halleck Street","employer":"Isologics","email":"hewittkidd@isologics.com","city":"Coalmont","state":"ME"}
    {"index":{"_id":"94"}}
    {"account_number":94,"balance":41060,"firstname":"Brittany","lastname":"Cabrera","age":30,"gender":"F","address":"183 Kathleen Court","employer":"Mixers","email":"brittanycabrera@mixers.com","city":"Cornucopia","state":"AZ"}
    {"index":{"_id":"99"}}
    {"account_number":99,"balance":47159,"firstname":"Ratliff","lastname":"Heath","age":39,"gender":"F","address":"806 Rockwell Place","employer":"Zappix","email":"ratliffheath@zappix.com","city":"Shaft","state":"ND"}
    {"index":{"_id":"102"}}
    {"account_number":102,"balance":29712,"firstname":"Dena","lastname":"Olson","age":27,"gender":"F","address":"759 Newkirk Avenue","employer":"Hinway","email":"denaolson@hinway.com","city":"Choctaw","state":"NJ"}
    {"index":{"_id":"107"}}
    {"account_number":107,"balance":48844,"firstname":"Randi","lastname":"Rich","age":28,"gender":"M","address":"694 Jefferson Street","employer":"Netplax","email":"randirich@netplax.com","city":"Bellfountain","state":"SC"}
    {"index":{"_id":"114"}}
    {"account_number":114,"balance":43045,"firstname":"Josephine","lastname":"Joseph","age":31,"gender":"F","address":"451 Oriental Court","employer":"Turnabout","email":"josephinejoseph@turnabout.com","city":"Sedley","state":"AL"}
    {"index":{"_id":"119"}}
    {"account_number":119,"balance":49222,"firstname":"Laverne","lastname":"Johnson","age":28,"gender":"F","address":"302 Howard Place","employer":"Senmei","email":"lavernejohnson@senmei.com","city":"Herlong","state":"DC"}
    {"index":{"_id":"121"}}
    {"account_number":121,"balance":19594,"firstname":"Acevedo","lastname":"Dorsey","age":32,"gender":"M","address":"479 Nova Court","employer":"Netropic","email":"acevedodorsey@netropic.com","city":"Islandia","state":"CT"}
    {"index":{"_id":"126"}}
    {"account_number":126,"balance":3607,"firstname":"Effie","lastname":"Gates","age":39,"gender":"F","address":"620 National Drive","employer":"Digitalus","email":"effiegates@digitalus.com","city":"Blodgett","state":"MD"}
    {"index":{"_id":"133"}}
    {"account_number":133,"balance":26135,"firstname":"Deena","lastname":"Richmond","age":36,"gender":"F","address":"646 Underhill Avenue","employer":"Sunclipse","email":"deenarichmond@sunclipse.com","city":"Austinburg","state":"SC"}
    {"index":{"_id":"138"}}
    {"account_number":138,"balance":9006,"firstname":"Daniel","lastname":"Arnold","age":39,"gender":"F","address":"422 Malbone Street","employer":"Ecstasia","email":"danielarnold@ecstasia.com","city":"Gardiner","state":"MO"}
    {"index":{"_id":"140"}}
    {"account_number":140,"balance":26696,"firstname":"Cotton","lastname":"Christensen","age":32,"gender":"M","address":"878 Schermerhorn Street","employer":"Prowaste","email":"cottonchristensen@prowaste.com","city":"Mayfair","state":"LA"}
    {"index":{"_id":"145"}}
    {"account_number":145,"balance":47406,"firstname":"Rowena","lastname":"Wilkinson","age":32,"gender":"M","address":"891 Elton Street","employer":"Asimiline","email":"rowenawilkinson@asimiline.com","city":"Ripley","state":"NH"}
    {"index":{"_id":"152"}}
    {"account_number":152,"balance":8088,"firstname":"Wolfe","lastname":"Rocha","age":21,"gender":"M","address":"457 Guernsey Street","employer":"Hivedom","email":"wolferocha@hivedom.com","city":"Adelino","state":"MS"}
    {"index":{"_id":"157"}}
    {"account_number":157,"balance":39868,"firstname":"Claudia","lastname":"Terry","age":20,"gender":"F","address":"132 Gunnison Court","employer":"Lumbrex","email":"claudiaterry@lumbrex.com","city":"Castleton","state":"MD"}
    {"index":{"_id":"164"}}
    {"account_number":164,"balance":9101,"firstname":"Cummings","lastname":"Little","age":26,"gender":"F","address":"308 Schaefer Street","employer":"Comtrak","email":"cummingslittle@comtrak.com","city":"Chaparrito","state":"WI"}
    {"index":{"_id":"169"}}
    {"account_number":169,"balance":45953,"firstname":"Hollie","lastname":"Osborn","age":34,"gender":"M","address":"671 Seaview Court","employer":"Musaphics","email":"hollieosborn@musaphics.com","city":"Hanover","state":"GA"}
    {"index":{"_id":"171"}}
    {"account_number":171,"balance":7091,"firstname":"Nelda","lastname":"Hopper","age":39,"gender":"M","address":"742 Prospect Place","employer":"Equicom","email":"neldahopper@equicom.com","city":"Finderne","state":"SC"}
    {"index":{"_id":"176"}}
    {"account_number":176,"balance":18607,"firstname":"Kemp","lastname":"Walters","age":28,"gender":"F","address":"906 Howard Avenue","employer":"Eyewax","email":"kempwalters@eyewax.com","city":"Why","state":"KY"}
    {"index":{"_id":"183"}}
    {"account_number":183,"balance":14223,"firstname":"Hudson","lastname":"English","age":26,"gender":"F","address":"823 Herkimer Place","employer":"Xinware","email":"hudsonenglish@xinware.com","city":"Robbins","state":"ND"}
    {"index":{"_id":"188"}}
    {"account_number":188,"balance":41504,"firstname":"Tia","lastname":"Miranda","age":24,"gender":"F","address":"583 Ainslie Street","employer":"Jasper","email":"tiamiranda@jasper.com","city":"Summerset","state":"UT"}
    {"index":{"_id":"190"}}
    {"account_number":190,"balance":3150,"firstname":"Blake","lastname":"Davidson","age":30,"gender":"F","address":"636 Diamond Street","employer":"Quantasis","email":"blakedavidson@quantasis.com","city":"Crumpler","state":"KY"}
    {"index":{"_id":"195"}}
    {"account_number":195,"balance":5025,"firstname":"Kaye","lastname":"Gibson","age":31,"gender":"M","address":"955 Hopkins Street","employer":"Zork","email":"kayegibson@zork.com","city":"Ola","state":"WY"}
    {"index":{"_id":"203"}}
    {"account_number":203,"balance":21890,"firstname":"Eve","lastname":"Wyatt","age":33,"gender":"M","address":"435 Furman Street","employer":"Assitia","email":"evewyatt@assitia.com","city":"Jamestown","state":"MN"}
    {"index":{"_id":"208"}}
    {"account_number":208,"balance":40760,"firstname":"Garcia","lastname":"Hess","age":26,"gender":"F","address":"810 Nostrand Avenue","employer":"Quiltigen","email":"garciahess@quiltigen.com","city":"Brooktrails","state":"GA"}
    {"index":{"_id":"210"}}
    {"account_number":210,"balance":33946,"firstname":"Cherry","lastname":"Carey","age":24,"gender":"M","address":"539 Tiffany Place","employer":"Martgo","email":"cherrycarey@martgo.com","city":"Fairacres","state":"AK"}
    {"index":{"_id":"215"}}
    {"account_number":215,"balance":37427,"firstname":"Copeland","lastname":"Solomon","age":20,"gender":"M","address":"741 McDonald Avenue","employer":"Recognia","email":"copelandsolomon@recognia.com","city":"Edmund","state":"ME"}
    {"index":{"_id":"222"}}
    {"account_number":222,"balance":14764,"firstname":"Rachelle","lastname":"Rice","age":36,"gender":"M","address":"333 Narrows Avenue","employer":"Enaut","email":"rachellerice@enaut.com","city":"Wright","state":"AZ"}
    {"index":{"_id":"227"}}
    {"account_number":227,"balance":19780,"firstname":"Coleman","lastname":"Berg","age":22,"gender":"M","address":"776 Little Street","employer":"Exoteric","email":"colemanberg@exoteric.com","city":"Eagleville","state":"WV"}
    {"index":{"_id":"234"}}
    {"account_number":234,"balance":44207,"firstname":"Betty","lastname":"Hall","age":37,"gender":"F","address":"709 Garfield Place","employer":"Miraclis","email":"bettyhall@miraclis.com","city":"Bendon","state":"NY"}
    {"index":{"_id":"239"}}
    {"account_number":239,"balance":25719,"firstname":"Chang","lastname":"Boyer","age":36,"gender":"M","address":"895 Brigham Street","employer":"Qaboos","email":"changboyer@qaboos.com","city":"Belgreen","state":"NH"}
    {"index":{"_id":"241"}}
    {"account_number":241,"balance":25379,"firstname":"Schroeder","lastname":"Harrington","age":26,"gender":"M","address":"610 Tapscott Avenue","employer":"Otherway","email":"schroederharrington@otherway.com","city":"Ebro","state":"TX"}
    {"index":{"_id":"246"}}
    {"account_number":246,"balance":28405,"firstname":"Katheryn","lastname":"Foster","age":21,"gender":"F","address":"259 Kane Street","employer":"Quantalia","email":"katherynfoster@quantalia.com","city":"Bath","state":"TX"}
    {"index":{"_id":"253"}}
    {"account_number":253,"balance":20240,"firstname":"Melissa","lastname":"Gould","age":31,"gender":"M","address":"440 Fuller Place","employer":"Buzzopia","email":"melissagould@buzzopia.com","city":"Lumberton","state":"MD"}
    {"index":{"_id":"258"}}
    {"account_number":258,"balance":5712,"firstname":"Lindsey","lastname":"Hawkins","age":37,"gender":"M","address":"706 Frost Street","employer":"Enormo","email":"lindseyhawkins@enormo.com","city":"Gardners","state":"AK"}
    {"index":{"_id":"260"}}
    {"account_number":260,"balance":2726,"firstname":"Kari","lastname":"Skinner","age":30,"gender":"F","address":"735 Losee Terrace","employer":"Singavera","email":"kariskinner@singavera.com","city":"Rushford","state":"WV"}
    {"index":{"_id":"265"}}
    {"account_number":265,"balance":46910,"firstname":"Marion","lastname":"Schneider","age":26,"gender":"F","address":"574 Everett Avenue","employer":"Evidends","email":"marionschneider@evidends.com","city":"Maplewood","state":"WY"}
    {"index":{"_id":"272"}}
    {"account_number":272,"balance":19253,"firstname":"Lilly","lastname":"Morgan","age":25,"gender":"F","address":"689 Fleet Street","employer":"Biolive","email":"lillymorgan@biolive.com","city":"Sunbury","state":"OH"}
    {"index":{"_id":"277"}}
    {"account_number":277,"balance":29564,"firstname":"Romero","lastname":"Lott","age":31,"gender":"M","address":"456 Danforth Street","employer":"Plasto","email":"romerolott@plasto.com","city":"Vincent","state":"VT"}
    {"index":{"_id":"284"}}
    {"account_number":284,"balance":22806,"firstname":"Randolph","lastname":"Banks","age":29,"gender":"M","address":"875 Hamilton Avenue","employer":"Caxt","email":"randolphbanks@caxt.com","city":"Crawfordsville","state":"WA"}
    {"index":{"_id":"289"}}
    {"account_number":289,"balance":7798,"firstname":"Blair","lastname":"Church","age":29,"gender":"M","address":"370 Sutton Street","employer":"Cubix","email":"blairchurch@cubix.com","city":"Nile","state":"NH"}
    {"index":{"_id":"291"}}
    {"account_number":291,"balance":19955,"firstname":"Lynn","lastname":"Pollard","age":40,"gender":"F","address":"685 Pierrepont Street","employer":"Slambda","email":"lynnpollard@slambda.com","city":"Mappsville","state":"ID"}
    {"index":{"_id":"296"}}
    {"account_number":296,"balance":24606,"firstname":"Rosa","lastname":"Oliver","age":34,"gender":"M","address":"168 Woodbine Street","employer":"Idetica","email":"rosaoliver@idetica.com","city":"Robinson","state":"WY"}
    {"index":{"_id":"304"}}
    {"account_number":304,"balance":28647,"firstname":"Palmer","lastname":"Clark","age":35,"gender":"M","address":"866 Boulevard Court","employer":"Maximind","email":"palmerclark@maximind.com","city":"Avalon","state":"NH"}
    {"index":{"_id":"309"}}
    {"account_number":309,"balance":3830,"firstname":"Rosemarie","lastname":"Nieves","age":30,"gender":"M","address":"206 Alice Court","employer":"Zounds","email":"rosemarienieves@zounds.com","city":"Ferney","state":"AR"}
    {"index":{"_id":"311"}}
    {"account_number":311,"balance":13388,"firstname":"Vinson","lastname":"Ballard","age":23,"gender":"F","address":"960 Glendale Court","employer":"Gynk","email":"vinsonballard@gynk.com","city":"Fairforest","state":"WY"}
    {"index":{"_id":"316"}}
    {"account_number":316,"balance":8214,"firstname":"Anita","lastname":"Ewing","age":32,"gender":"M","address":"396 Lombardy Street","employer":"Panzent","email":"anitaewing@panzent.com","city":"Neahkahnie","state":"WY"}
    {"index":{"_id":"323"}}
    {"account_number":323,"balance":42230,"firstname":"Chelsea","lastname":"Gamble","age":34,"gender":"F","address":"356 Dare Court","employer":"Isosphere","email":"chelseagamble@isosphere.com","city":"Dundee","state":"MD"}
    {"index":{"_id":"328"}}
    {"account_number":328,"balance":12523,"firstname":"Good","lastname":"Campbell","age":27,"gender":"F","address":"438 Hicks Street","employer":"Gracker","email":"goodcampbell@gracker.com","city":"Marion","state":"CA"}
    {"index":{"_id":"330"}}
    {"account_number":330,"balance":41620,"firstname":"Yvette","lastname":"Browning","age":34,"gender":"F","address":"431 Beekman Place","employer":"Marketoid","email":"yvettebrowning@marketoid.com","city":"Talpa","state":"CO"}
    {"index":{"_id":"335"}}
    {"account_number":335,"balance":35433,"firstname":"Vera","lastname":"Hansen","age":24,"gender":"M","address":"252 Bushwick Avenue","employer":"Zanilla","email":"verahansen@zanilla.com","city":"Manila","state":"TN"}
    {"index":{"_id":"342"}}
    {"account_number":342,"balance":33670,"firstname":"Vivian","lastname":"Wells","age":36,"gender":"M","address":"570 Cobek Court","employer":"Nutralab","email":"vivianwells@nutralab.com","city":"Fontanelle","state":"OK"}
    {"index":{"_id":"347"}}
    {"account_number":347,"balance":36038,"firstname":"Gould","lastname":"Carson","age":24,"gender":"F","address":"784 Pulaski Street","employer":"Mobildata","email":"gouldcarson@mobildata.com","city":"Goochland","state":"MI"}
    {"index":{"_id":"354"}}
    {"account_number":354,"balance":21294,"firstname":"Kidd","lastname":"Mclean","age":22,"gender":"M","address":"691 Saratoga Avenue","employer":"Ronbert","email":"kiddmclean@ronbert.com","city":"Tioga","state":"ME"}
    {"index":{"_id":"359"}}
    {"account_number":359,"balance":29927,"firstname":"Vanessa","lastname":"Harvey","age":28,"gender":"F","address":"679 Rutledge Street","employer":"Zentime","email":"vanessaharvey@zentime.com","city":"Williston","state":"IL"}
    {"index":{"_id":"361"}}
    {"account_number":361,"balance":23659,"firstname":"Noreen","lastname":"Shelton","age":36,"gender":"M","address":"702 Tillary Street","employer":"Medmex","email":"noreenshelton@medmex.com","city":"Derwood","state":"NH"}
    {"index":{"_id":"366"}}
    {"account_number":366,"balance":42368,"firstname":"Lydia","lastname":"Cooke","age":31,"gender":"M","address":"470 Coleman Street","employer":"Comstar","email":"lydiacooke@comstar.com","city":"Datil","state":"TN"}
    {"index":{"_id":"373"}}
    {"account_number":373,"balance":9671,"firstname":"Simpson","lastname":"Carpenter","age":21,"gender":"M","address":"837 Horace Court","employer":"Snips","email":"simpsoncarpenter@snips.com","city":"Tolu","state":"MA"}
    {"index":{"_id":"378"}}
    {"account_number":378,"balance":27100,"firstname":"Watson","lastname":"Simpson","age":36,"gender":"F","address":"644 Thomas Street","employer":"Wrapture","email":"watsonsimpson@wrapture.com","city":"Keller","state":"TX"}
    {"index":{"_id":"380"}}
    {"account_number":380,"balance":35628,"firstname":"Fernandez","lastname":"Reid","age":33,"gender":"F","address":"154 Melba Court","employer":"Cosmosis","email":"fernandezreid@cosmosis.com","city":"Boyd","state":"NE"}
    {"index":{"_id":"385"}}
    {"account_number":385,"balance":11022,"firstname":"Rosalinda","lastname":"Valencia","age":22,"gender":"M","address":"933 Lloyd Street","employer":"Zoarere","email":"rosalindavalencia@zoarere.com","city":"Waverly","state":"GA"}
    {"index":{"_id":"392"}}
    {"account_number":392,"balance":31613,"firstname":"Dotson","lastname":"Dean","age":35,"gender":"M","address":"136 Ford Street","employer":"Petigems","email":"dotsondean@petigems.com","city":"Chical","state":"SD"}
    {"index":{"_id":"397"}}
    {"account_number":397,"balance":37418,"firstname":"Leonard","lastname":"Gray","age":36,"gender":"F","address":"840 Morgan Avenue","employer":"Recritube","email":"leonardgray@recritube.com","city":"Edenburg","state":"AL"}
    {"index":{"_id":"400"}}
    {"account_number":400,"balance":20685,"firstname":"Kane","lastname":"King","age":21,"gender":"F","address":"405 Cornelia Street","employer":"Tri@Tribalog","email":"kaneking@tri@tribalog.com","city":"Gulf","state":"VT"}
    {"index":{"_id":"405"}}
    {"account_number":405,"balance":5679,"firstname":"Strickland","lastname":"Fuller","age":26,"gender":"M","address":"990 Concord Street","employer":"Digique","email":"stricklandfuller@digique.com","city":"Southmont","state":"NV"}
    {"index":{"_id":"412"}}
    {"account_number":412,"balance":27436,"firstname":"Ilene","lastname":"Abbott","age":26,"gender":"M","address":"846 Vine Street","employer":"Typhonica","email":"ileneabbott@typhonica.com","city":"Cedarville","state":"VT"}
    {"index":{"_id":"417"}}
    {"account_number":417,"balance":1788,"firstname":"Wheeler","lastname":"Ayers","age":35,"gender":"F","address":"677 Hope Street","employer":"Fortean","email":"wheelerayers@fortean.com","city":"Ironton","state":"PA"}
    {"index":{"_id":"424"}}
    {"account_number":424,"balance":36818,"firstname":"Tracie","lastname":"Gregory","age":34,"gender":"M","address":"112 Hunterfly Place","employer":"Comstruct","email":"traciegregory@comstruct.com","city":"Onton","state":"TN"}
    {"index":{"_id":"429"}}
    {"account_number":429,"balance":46970,"firstname":"Cantu","lastname":"Lindsey","age":31,"gender":"M","address":"404 Willoughby Avenue","employer":"Inquala","email":"cantulindsey@inquala.com","city":"Cowiche","state":"IA"}
    {"index":{"_id":"431"}}
    {"account_number":431,"balance":13136,"firstname":"Laurie","lastname":"Shaw","age":26,"gender":"F","address":"263 Aviation Road","employer":"Zillanet","email":"laurieshaw@zillanet.com","city":"Harmon","state":"WV"}
    {"index":{"_id":"436"}}
    {"account_number":436,"balance":27585,"firstname":"Alexander","lastname":"Sargent","age":23,"gender":"M","address":"363 Albemarle Road","employer":"Fangold","email":"alexandersargent@fangold.com","city":"Calpine","state":"OR"}
    {"index":{"_id":"443"}}
    {"account_number":443,"balance":7588,"firstname":"Huff","lastname":"Thomas","age":23,"gender":"M","address":"538 Erskine Loop","employer":"Accufarm","email":"huffthomas@accufarm.com","city":"Corinne","state":"AL"}
    {"index":{"_id":"448"}}
    {"account_number":448,"balance":22776,"firstname":"Adriana","lastname":"Mcfadden","age":35,"gender":"F","address":"984 Woodside Avenue","employer":"Telequiet","email":"adrianamcfadden@telequiet.com","city":"Darrtown","state":"WI"}
    {"index":{"_id":"450"}}
    {"account_number":450,"balance":2643,"firstname":"Bradford","lastname":"Nielsen","age":25,"gender":"M","address":"487 Keen Court","employer":"Exovent","email":"bradfordnielsen@exovent.com","city":"Hamilton","state":"DE"}
    {"index":{"_id":"455"}}
    {"account_number":455,"balance":39556,"firstname":"Lynn","lastname":"Tran","age":36,"gender":"M","address":"741 Richmond Street","employer":"Optyk","email":"lynntran@optyk.com","city":"Clinton","state":"WV"}
    {"index":{"_id":"462"}}
    {"account_number":462,"balance":10871,"firstname":"Calderon","lastname":"Day","age":27,"gender":"M","address":"810 Milford Street","employer":"Cofine","email":"calderonday@cofine.com","city":"Kula","state":"OK"}
    {"index":{"_id":"467"}}
    {"account_number":467,"balance":6312,"firstname":"Angelica","lastname":"May","age":32,"gender":"F","address":"384 Karweg Place","employer":"Keeg","email":"angelicamay@keeg.com","city":"Tetherow","state":"IA"}
    {"index":{"_id":"474"}}
    {"account_number":474,"balance":35896,"firstname":"Obrien","lastname":"Walton","age":40,"gender":"F","address":"192 Ide Court","employer":"Suremax","email":"obrienwalton@suremax.com","city":"Crucible","state":"UT"}
    {"index":{"_id":"479"}}
    {"account_number":479,"balance":31865,"firstname":"Cameron","lastname":"Ross","age":40,"gender":"M","address":"904 Bouck Court","employer":"Telpod","email":"cameronross@telpod.com","city":"Nord","state":"MO"}
    {"index":{"_id":"481"}}
    {"account_number":481,"balance":20024,"firstname":"Lina","lastname":"Stanley","age":33,"gender":"M","address":"361 Hanover Place","employer":"Strozen","email":"linastanley@strozen.com","city":"Wyoming","state":"NC"}
    {"index":{"_id":"486"}}
    {"account_number":486,"balance":35902,"firstname":"Dixie","lastname":"Fuentes","age":22,"gender":"F","address":"991 Applegate Court","employer":"Portico","email":"dixiefuentes@portico.com","city":"Salix","state":"VA"}
    {"index":{"_id":"493"}}
    {"account_number":493,"balance":5871,"firstname":"Campbell","lastname":"Best","age":24,"gender":"M","address":"297 Friel Place","employer":"Fanfare","email":"campbellbest@fanfare.com","city":"Kidder","state":"GA"}
    {"index":{"_id":"498"}}
    {"account_number":498,"balance":10516,"firstname":"Stella","lastname":"Hinton","age":39,"gender":"F","address":"649 Columbia Place","employer":"Flyboyz","email":"stellahinton@flyboyz.com","city":"Crenshaw","state":"SC"}
    {"index":{"_id":"501"}}
    {"account_number":501,"balance":16572,"firstname":"Kelley","lastname":"Ochoa","age":36,"gender":"M","address":"451 Clifton Place","employer":"Bluplanet","email":"kelleyochoa@bluplanet.com","city":"Gouglersville","state":"CT"}
    {"index":{"_id":"506"}}
    {"account_number":506,"balance":43440,"firstname":"Davidson","lastname":"Salas","age":28,"gender":"M","address":"731 Cleveland Street","employer":"Sequitur","email":"davidsonsalas@sequitur.com","city":"Lloyd","state":"ME"}
    {"index":{"_id":"513"}}
    {"account_number":513,"balance":30040,"firstname":"Maryellen","lastname":"Rose","age":37,"gender":"F","address":"428 Durland Place","employer":"Waterbaby","email":"maryellenrose@waterbaby.com","city":"Kiskimere","state":"RI"}
    {"index":{"_id":"518"}}
    {"account_number":518,"balance":48954,"firstname":"Finch","lastname":"Curtis","age":29,"gender":"F","address":"137 Ryder Street","employer":"Viagrand","email":"finchcurtis@viagrand.com","city":"Riverton","state":"MO"}
    {"index":{"_id":"520"}}
    {"account_number":520,"balance":27987,"firstname":"Brandy","lastname":"Calhoun","age":32,"gender":"M","address":"818 Harden Street","employer":"Maxemia","email":"brandycalhoun@maxemia.com","city":"Sidman","state":"OR"}
    {"index":{"_id":"525"}}
    {"account_number":525,"balance":23545,"firstname":"Holly","lastname":"Miles","age":25,"gender":"M","address":"746 Ludlam Place","employer":"Xurban","email":"hollymiles@xurban.com","city":"Harold","state":"AR"}
    {"index":{"_id":"532"}}
    {"account_number":532,"balance":17207,"firstname":"Hardin","lastname":"Kirk","age":26,"gender":"M","address":"268 Canarsie Road","employer":"Exposa","email":"hardinkirk@exposa.com","city":"Stouchsburg","state":"IL"}
    {"index":{"_id":"537"}}
    {"account_number":537,"balance":31069,"firstname":"Morin","lastname":"Frost","age":29,"gender":"M","address":"910 Lake Street","employer":"Primordia","email":"morinfrost@primordia.com","city":"Rivera","state":"DE"}
    {"index":{"_id":"544"}}
    {"account_number":544,"balance":41735,"firstname":"Short","lastname":"Dennis","age":21,"gender":"F","address":"908 Glen Street","employer":"Minga","email":"shortdennis@minga.com","city":"Dale","state":"KY"}
    {"index":{"_id":"549"}}
    {"account_number":549,"balance":1932,"firstname":"Jacqueline","lastname":"Maxwell","age":40,"gender":"M","address":"444 Schenck Place","employer":"Fuelworks","email":"jacquelinemaxwell@fuelworks.com","city":"Oretta","state":"OR"}
    {"index":{"_id":"551"}}
    {"account_number":551,"balance":21732,"firstname":"Milagros","lastname":"Travis","age":27,"gender":"F","address":"380 Murdock Court","employer":"Sloganaut","email":"milagrostravis@sloganaut.com","city":"Homeland","state":"AR"}
    {"index":{"_id":"556"}}
    {"account_number":556,"balance":36420,"firstname":"Collier","lastname":"Odonnell","age":35,"gender":"M","address":"591 Nolans Lane","employer":"Sultraxin","email":"collierodonnell@sultraxin.com","city":"Fulford","state":"MD"}
    {"index":{"_id":"563"}}
    {"account_number":563,"balance":43403,"firstname":"Morgan","lastname":"Torres","age":30,"gender":"F","address":"672 Belvidere Street","employer":"Quonata","email":"morgantorres@quonata.com","city":"Hollymead","state":"KY"}
    {"index":{"_id":"568"}}
    {"account_number":568,"balance":36628,"firstname":"Lesa","lastname":"Maynard","age":29,"gender":"F","address":"295 Whitty Lane","employer":"Coash","email":"lesamaynard@coash.com","city":"Broadlands","state":"VT"}
    {"index":{"_id":"570"}}
    {"account_number":570,"balance":26751,"firstname":"Church","lastname":"Mercado","age":24,"gender":"F","address":"892 Wyckoff Street","employer":"Xymonk","email":"churchmercado@xymonk.com","city":"Gloucester","state":"KY"}
    {"index":{"_id":"575"}}
    {"account_number":575,"balance":12588,"firstname":"Buchanan","lastname":"Pope","age":39,"gender":"M","address":"581 Sumner Place","employer":"Stucco","email":"buchananpope@stucco.com","city":"Ellerslie","state":"MD"}
    {"index":{"_id":"582"}}
    {"account_number":582,"balance":33371,"firstname":"Manning","lastname":"Guthrie","age":24,"gender":"F","address":"271 Jodie Court","employer":"Xerex","email":"manningguthrie@xerex.com","city":"Breinigsville","state":"NM"}
    {"index":{"_id":"587"}}
    {"account_number":587,"balance":3468,"firstname":"Carly","lastname":"Johns","age":33,"gender":"M","address":"390 Noll Street","employer":"Gallaxia","email":"carlyjohns@gallaxia.com","city":"Emison","state":"DC"}
    {"index":{"_id":"594"}}
    {"account_number":594,"balance":28194,"firstname":"Golden","lastname":"Donovan","age":26,"gender":"M","address":"199 Jewel Street","employer":"Organica","email":"goldendonovan@organica.com","city":"Macdona","state":"RI"}
    {"index":{"_id":"599"}}
    {"account_number":599,"balance":11944,"firstname":"Joanna","lastname":"Jennings","age":36,"gender":"F","address":"318 Irving Street","employer":"Extremo","email":"joannajennings@extremo.com","city":"Bartley","state":"MI"}
    {"index":{"_id":"602"}}
    {"account_number":602,"balance":38699,"firstname":"Mcgowan","lastname":"Mcclain","age":33,"gender":"M","address":"361 Stoddard Place","employer":"Oatfarm","email":"mcgowanmcclain@oatfarm.com","city":"Kapowsin","state":"MI"}
    {"index":{"_id":"607"}}
    {"account_number":607,"balance":38350,"firstname":"White","lastname":"Small","age":38,"gender":"F","address":"736 Judge Street","employer":"Immunics","email":"whitesmall@immunics.com","city":"Fairfield","state":"HI"}
    {"index":{"_id":"614"}}
    {"account_number":614,"balance":13157,"firstname":"Salazar","lastname":"Howard","age":35,"gender":"F","address":"847 Imlay Street","employer":"Retrack","email":"salazarhoward@retrack.com","city":"Grill","state":"FL"}
    {"index":{"_id":"619"}}
    {"account_number":619,"balance":48755,"firstname":"Grimes","lastname":"Reynolds","age":36,"gender":"M","address":"378 Denton Place","employer":"Frenex","email":"grimesreynolds@frenex.com","city":"Murillo","state":"LA"}
    {"index":{"_id":"621"}}
    {"account_number":621,"balance":35480,"firstname":"Leslie","lastname":"Sloan","age":26,"gender":"F","address":"336 Kansas Place","employer":"Dancity","email":"lesliesloan@dancity.com","city":"Corriganville","state":"AR"}
    {"index":{"_id":"626"}}
    {"account_number":626,"balance":19498,"firstname":"Ava","lastname":"Richardson","age":31,"gender":"F","address":"666 Nautilus Avenue","employer":"Cinaster","email":"avarichardson@cinaster.com","city":"Sutton","state":"AL"}
    {"index":{"_id":"633"}}
    {"account_number":633,"balance":35874,"firstname":"Conner","lastname":"Ramos","age":34,"gender":"M","address":"575 Agate Court","employer":"Insource","email":"connerramos@insource.com","city":"Madaket","state":"OK"}
    {"index":{"_id":"638"}}
    {"account_number":638,"balance":2658,"firstname":"Bridget","lastname":"Gallegos","age":31,"gender":"M","address":"383 Wogan Terrace","employer":"Songlines","email":"bridgetgallegos@songlines.com","city":"Linganore","state":"WA"}
    {"index":{"_id":"640"}}
    {"account_number":640,"balance":35596,"firstname":"Candace","lastname":"Hancock","age":25,"gender":"M","address":"574 Riverdale Avenue","employer":"Animalia","email":"candacehancock@animalia.com","city":"Blandburg","state":"KY"}
    {"index":{"_id":"645"}}
    {"account_number":645,"balance":29362,"firstname":"Edwina","lastname":"Hutchinson","age":26,"gender":"F","address":"892 Pacific Street","employer":"Essensia","email":"edwinahutchinson@essensia.com","city":"Dowling","state":"NE"}
    {"index":{"_id":"652"}}
    {"account_number":652,"balance":17363,"firstname":"Bonner","lastname":"Garner","age":26,"gender":"M","address":"219 Grafton Street","employer":"Utarian","email":"bonnergarner@utarian.com","city":"Vandiver","state":"PA"}
    {"index":{"_id":"657"}}
    {"account_number":657,"balance":40475,"firstname":"Kathleen","lastname":"Wilder","age":34,"gender":"F","address":"286 Sutter Avenue","employer":"Solgan","email":"kathleenwilder@solgan.com","city":"Graniteville","state":"MI"}
    {"index":{"_id":"664"}}
    {"account_number":664,"balance":16163,"firstname":"Hart","lastname":"Mccormick","age":40,"gender":"M","address":"144 Guider Avenue","employer":"Dyno","email":"hartmccormick@dyno.com","city":"Carbonville","state":"ID"}
    {"index":{"_id":"669"}}
    {"account_number":669,"balance":16934,"firstname":"Jewel","lastname":"Estrada","age":28,"gender":"M","address":"896 Meeker Avenue","employer":"Zilla","email":"jewelestrada@zilla.com","city":"Goodville","state":"PA"}
    {"index":{"_id":"671"}}
    {"account_number":671,"balance":29029,"firstname":"Antoinette","lastname":"Cook","age":34,"gender":"M","address":"375 Cumberland Street","employer":"Harmoney","email":"antoinettecook@harmoney.com","city":"Bergoo","state":"VT"}
    {"index":{"_id":"676"}}
    {"account_number":676,"balance":23842,"firstname":"Lisa","lastname":"Dudley","age":34,"gender":"M","address":"506 Vanderveer Street","employer":"Tropoli","email":"lisadudley@tropoli.com","city":"Konterra","state":"NY"}
    {"index":{"_id":"683"}}
    {"account_number":683,"balance":4381,"firstname":"Matilda","lastname":"Berger","age":39,"gender":"M","address":"884 Noble Street","employer":"Fibrodyne","email":"matildaberger@fibrodyne.com","city":"Shepardsville","state":"TN"}
    {"index":{"_id":"688"}}
    {"account_number":688,"balance":17931,"firstname":"Freeman","lastname":"Zamora","age":22,"gender":"F","address":"114 Herzl Street","employer":"Elemantra","email":"freemanzamora@elemantra.com","city":"Libertytown","state":"NM"}
    {"index":{"_id":"690"}}
    {"account_number":690,"balance":18127,"firstname":"Russo","lastname":"Swanson","age":35,"gender":"F","address":"256 Roebling Street","employer":"Zaj","email":"russoswanson@zaj.com","city":"Hoagland","state":"MI"}
    {"index":{"_id":"695"}}
    {"account_number":695,"balance":36800,"firstname":"Gonzales","lastname":"Mcfarland","age":26,"gender":"F","address":"647 Louisa Street","employer":"Songbird","email":"gonzalesmcfarland@songbird.com","city":"Crisman","state":"ID"}
    {"index":{"_id":"703"}}
    {"account_number":703,"balance":27443,"firstname":"Dona","lastname":"Burton","age":29,"gender":"M","address":"489 Flatlands Avenue","employer":"Cytrex","email":"donaburton@cytrex.com","city":"Reno","state":"VA"}
    {"index":{"_id":"708"}}
    {"account_number":708,"balance":34002,"firstname":"May","lastname":"Ortiz","age":28,"gender":"F","address":"244 Chauncey Street","employer":"Syntac","email":"mayortiz@syntac.com","city":"Munjor","state":"ID"}
    {"index":{"_id":"710"}}
    {"account_number":710,"balance":33650,"firstname":"Shelton","lastname":"Stark","age":37,"gender":"M","address":"404 Ovington Avenue","employer":"Kraggle","email":"sheltonstark@kraggle.com","city":"Ogema","state":"TN"}
    {"index":{"_id":"715"}}
    {"account_number":715,"balance":23734,"firstname":"Tammi","lastname":"Hodge","age":24,"gender":"M","address":"865 Church Lane","employer":"Netur","email":"tammihodge@netur.com","city":"Lacomb","state":"KS"}
    {"index":{"_id":"722"}}
    {"account_number":722,"balance":27256,"firstname":"Roberts","lastname":"Beasley","age":34,"gender":"F","address":"305 Kings Hwy","employer":"Quintity","email":"robertsbeasley@quintity.com","city":"Hayden","state":"PA"}
    {"index":{"_id":"727"}}
    {"account_number":727,"balance":27263,"firstname":"Natasha","lastname":"Knapp","age":36,"gender":"M","address":"723 Hubbard Street","employer":"Exostream","email":"natashaknapp@exostream.com","city":"Trexlertown","state":"LA"}
    {"index":{"_id":"734"}}
    {"account_number":734,"balance":20325,"firstname":"Keri","lastname":"Kinney","age":23,"gender":"M","address":"490 Balfour Place","employer":"Retrotex","email":"kerikinney@retrotex.com","city":"Salunga","state":"PA"}
    {"index":{"_id":"739"}}
    {"account_number":739,"balance":39063,"firstname":"Gwen","lastname":"Hardy","age":33,"gender":"F","address":"733 Stuart Street","employer":"Exozent","email":"gwenhardy@exozent.com","city":"Drytown","state":"NY"}
    {"index":{"_id":"741"}}
    {"account_number":741,"balance":33074,"firstname":"Nielsen","lastname":"Good","age":22,"gender":"M","address":"404 Norfolk Street","employer":"Kiggle","email":"nielsengood@kiggle.com","city":"Cumberland","state":"WA"}
    {"index":{"_id":"746"}}
    {"account_number":746,"balance":15970,"firstname":"Marguerite","lastname":"Wall","age":28,"gender":"F","address":"364 Crosby Avenue","employer":"Aquoavo","email":"margueritewall@aquoavo.com","city":"Jeff","state":"MI"}
    {"index":{"_id":"753"}}
    {"account_number":753,"balance":33340,"firstname":"Katina","lastname":"Alford","age":21,"gender":"F","address":"690 Ross Street","employer":"Intrawear","email":"katinaalford@intrawear.com","city":"Grimsley","state":"OK"}
    {"index":{"_id":"758"}}
    {"account_number":758,"balance":15739,"firstname":"Berta","lastname":"Short","age":28,"gender":"M","address":"149 Surf Avenue","employer":"Ozean","email":"bertashort@ozean.com","city":"Odessa","state":"UT"}
    {"index":{"_id":"760"}}
    {"account_number":760,"balance":40996,"firstname":"Rhea","lastname":"Blair","age":37,"gender":"F","address":"440 Hubbard Place","employer":"Bicol","email":"rheablair@bicol.com","city":"Stockwell","state":"LA"}
    {"index":{"_id":"765"}}
    {"account_number":765,"balance":31278,"firstname":"Knowles","lastname":"Cunningham","age":23,"gender":"M","address":"753 Macdougal Street","employer":"Thredz","email":"knowlescunningham@thredz.com","city":"Thomasville","state":"WA"}
    {"index":{"_id":"772"}}
    {"account_number":772,"balance":37849,"firstname":"Eloise","lastname":"Sparks","age":21,"gender":"M","address":"608 Willow Street","employer":"Satiance","email":"eloisesparks@satiance.com","city":"Richford","state":"NY"}
    {"index":{"_id":"777"}}
    {"account_number":777,"balance":48294,"firstname":"Adkins","lastname":"Mejia","age":32,"gender":"M","address":"186 Oxford Walk","employer":"Datagen","email":"adkinsmejia@datagen.com","city":"Faywood","state":"OK"}
    {"index":{"_id":"784"}}
    {"account_number":784,"balance":25291,"firstname":"Mabel","lastname":"Thornton","age":21,"gender":"M","address":"124 Louisiana Avenue","employer":"Zolavo","email":"mabelthornton@zolavo.com","city":"Lynn","state":"AL"}
    {"index":{"_id":"789"}}
    {"account_number":789,"balance":8760,"firstname":"Cunningham","lastname":"Kerr","age":27,"gender":"F","address":"154 Sharon Street","employer":"Polarium","email":"cunninghamkerr@polarium.com","city":"Tuskahoma","state":"MS"}
    {"index":{"_id":"791"}}
    {"account_number":791,"balance":48249,"firstname":"Janine","lastname":"Huber","age":38,"gender":"F","address":"348 Porter Avenue","employer":"Viocular","email":"janinehuber@viocular.com","city":"Fivepointville","state":"MA"}
    {"index":{"_id":"796"}}
    {"account_number":796,"balance":23503,"firstname":"Mona","lastname":"Craft","age":35,"gender":"F","address":"511 Henry Street","employer":"Opticom","email":"monacraft@opticom.com","city":"Websterville","state":"IN"}
    {"index":{"_id":"804"}}
    {"account_number":804,"balance":23610,"firstname":"Rojas","lastname":"Oneal","age":27,"gender":"M","address":"669 Sandford Street","employer":"Glukgluk","email":"rojasoneal@glukgluk.com","city":"Wheaton","state":"ME"}
    {"index":{"_id":"809"}}
    {"account_number":809,"balance":47812,"firstname":"Christie","lastname":"Strickland","age":30,"gender":"M","address":"346 Bancroft Place","employer":"Anarco","email":"christiestrickland@anarco.com","city":"Baden","state":"NV"}
    {"index":{"_id":"811"}}
    {"account_number":811,"balance":26007,"firstname":"Walls","lastname":"Rogers","age":28,"gender":"F","address":"352 Freeman Street","employer":"Geekmosis","email":"wallsrogers@geekmosis.com","city":"Caroleen","state":"NV"}
    {"index":{"_id":"816"}}
    {"account_number":816,"balance":9567,"firstname":"Cornelia","lastname":"Lane","age":20,"gender":"F","address":"384 Bainbridge Street","employer":"Sulfax","email":"cornelialane@sulfax.com","city":"Elizaville","state":"MS"}
    {"index":{"_id":"823"}}
    {"account_number":823,"balance":48726,"firstname":"Celia","lastname":"Bernard","age":33,"gender":"F","address":"466 Amboy Street","employer":"Mitroc","email":"celiabernard@mitroc.com","city":"Skyland","state":"GA"}
    {"index":{"_id":"828"}}
    {"account_number":828,"balance":44890,"firstname":"Blanche","lastname":"Holmes","age":33,"gender":"F","address":"605 Stryker Court","employer":"Motovate","email":"blancheholmes@motovate.com","city":"Loomis","state":"KS"}
    {"index":{"_id":"830"}}
    {"account_number":830,"balance":45210,"firstname":"Louella","lastname":"Chan","age":23,"gender":"M","address":"511 Heath Place","employer":"Conferia","email":"louellachan@conferia.com","city":"Brookfield","state":"OK"}
    {"index":{"_id":"835"}}
    {"account_number":835,"balance":46558,"firstname":"Glover","lastname":"Rutledge","age":25,"gender":"F","address":"641 Royce Street","employer":"Ginkogene","email":"gloverrutledge@ginkogene.com","city":"Dixonville","state":"VA"}
    {"index":{"_id":"842"}}
    {"account_number":842,"balance":49587,"firstname":"Meagan","lastname":"Buckner","age":23,"gender":"F","address":"833 Bushwick Court","employer":"Biospan","email":"meaganbuckner@biospan.com","city":"Craig","state":"TX"}
    {"index":{"_id":"847"}}
    {"account_number":847,"balance":8652,"firstname":"Antonia","lastname":"Duncan","age":23,"gender":"M","address":"644 Stryker Street","employer":"Talae","email":"antoniaduncan@talae.com","city":"Dawn","state":"MO"}
    {"index":{"_id":"854"}}
    {"account_number":854,"balance":49795,"firstname":"Jimenez","lastname":"Barry","age":25,"gender":"F","address":"603 Cooper Street","employer":"Verton","email":"jimenezbarry@verton.com","city":"Moscow","state":"AL"}
    {"index":{"_id":"859"}}
    {"account_number":859,"balance":20734,"firstname":"Beulah","lastname":"Stuart","age":24,"gender":"F","address":"651 Albemarle Terrace","employer":"Hatology","email":"beulahstuart@hatology.com","city":"Waiohinu","state":"RI"}
    {"index":{"_id":"861"}}
    {"account_number":861,"balance":44173,"firstname":"Jaime","lastname":"Wilson","age":35,"gender":"M","address":"680 Richardson Street","employer":"Temorak","email":"jaimewilson@temorak.com","city":"Fidelis","state":"FL"}
    {"index":{"_id":"866"}}
    {"account_number":866,"balance":45565,"firstname":"Araceli","lastname":"Woodward","age":28,"gender":"M","address":"326 Meadow Street","employer":"Olympix","email":"araceliwoodward@olympix.com","city":"Dana","state":"KS"}
    {"index":{"_id":"873"}}
    {"account_number":873,"balance":43931,"firstname":"Tisha","lastname":"Cotton","age":39,"gender":"F","address":"432 Lincoln Road","employer":"Buzzmaker","email":"tishacotton@buzzmaker.com","city":"Bluetown","state":"GA"}
    {"index":{"_id":"878"}}
    {"account_number":878,"balance":49159,"firstname":"Battle","lastname":"Blackburn","age":40,"gender":"F","address":"234 Hendrix Street","employer":"Zilphur","email":"battleblackburn@zilphur.com","city":"Wanamie","state":"PA"}
    {"index":{"_id":"880"}}
    {"account_number":880,"balance":22575,"firstname":"Christian","lastname":"Myers","age":35,"gender":"M","address":"737 Crown Street","employer":"Combogen","email":"christianmyers@combogen.com","city":"Abrams","state":"OK"}
    {"index":{"_id":"885"}}
    {"account_number":885,"balance":31661,"firstname":"Valdez","lastname":"Roberson","age":40,"gender":"F","address":"227 Scholes Street","employer":"Delphide","email":"valdezroberson@delphide.com","city":"Chilton","state":"MT"}
    {"index":{"_id":"892"}}
    {"account_number":892,"balance":44974,"firstname":"Hill","lastname":"Hayes","age":29,"gender":"M","address":"721 Dooley Street","employer":"Fuelton","email":"hillhayes@fuelton.com","city":"Orason","state":"MT"}
    {"index":{"_id":"897"}}
    {"account_number":897,"balance":45973,"firstname":"Alyson","lastname":"Irwin","age":25,"gender":"M","address":"731 Poplar Street","employer":"Quizka","email":"alysonirwin@quizka.com","city":"Singer","state":"VA"}
    {"index":{"_id":"900"}}
    {"account_number":900,"balance":6124,"firstname":"Gonzalez","lastname":"Watson","age":23,"gender":"M","address":"624 Sullivan Street","employer":"Marvane","email":"gonzalezwatson@marvane.com","city":"Wikieup","state":"IL"}
    {"index":{"_id":"905"}}
    {"account_number":905,"balance":29438,"firstname":"Schultz","lastname":"Moreno","age":20,"gender":"F","address":"761 Cedar Street","employer":"Paragonia","email":"schultzmoreno@paragonia.com","city":"Glenshaw","state":"SC"}
    {"index":{"_id":"912"}}
    {"account_number":912,"balance":13675,"firstname":"Flora","lastname":"Alvarado","age":26,"gender":"M","address":"771 Vandervoort Avenue","employer":"Boilicon","email":"floraalvarado@boilicon.com","city":"Vivian","state":"ID"}
    {"index":{"_id":"917"}}
    {"account_number":917,"balance":47782,"firstname":"Parks","lastname":"Hurst","age":24,"gender":"M","address":"933 Cozine Avenue","employer":"Pyramis","email":"parkshurst@pyramis.com","city":"Lindcove","state":"GA"}
    {"index":{"_id":"924"}}
    {"account_number":924,"balance":3811,"firstname":"Hilary","lastname":"Leonard","age":24,"gender":"M","address":"235 Hegeman Avenue","employer":"Metroz","email":"hilaryleonard@metroz.com","city":"Roosevelt","state":"ME"}
    {"index":{"_id":"929"}}
    {"account_number":929,"balance":34708,"firstname":"Willie","lastname":"Hickman","age":35,"gender":"M","address":"430 Devoe Street","employer":"Apextri","email":"williehickman@apextri.com","city":"Clay","state":"MS"}
    {"index":{"_id":"931"}}
    {"account_number":931,"balance":8244,"firstname":"Ingrid","lastname":"Garcia","age":23,"gender":"F","address":"674 Indiana Place","employer":"Balooba","email":"ingridgarcia@balooba.com","city":"Interlochen","state":"AZ"}
    {"index":{"_id":"936"}}
    {"account_number":936,"balance":22430,"firstname":"Beth","lastname":"Frye","age":36,"gender":"M","address":"462 Thatford Avenue","employer":"Puria","email":"bethfrye@puria.com","city":"Hiseville","state":"LA"}
    {"index":{"_id":"943"}}
    {"account_number":943,"balance":24187,"firstname":"Wagner","lastname":"Griffin","age":23,"gender":"M","address":"489 Ellery Street","employer":"Gazak","email":"wagnergriffin@gazak.com","city":"Lorraine","state":"HI"}
    {"index":{"_id":"948"}}
    {"account_number":948,"balance":37074,"firstname":"Sargent","lastname":"Powers","age":40,"gender":"M","address":"532 Fiske Place","employer":"Accuprint","email":"sargentpowers@accuprint.com","city":"Umapine","state":"AK"}
    {"index":{"_id":"950"}}
    {"account_number":950,"balance":30916,"firstname":"Sherrie","lastname":"Patel","age":32,"gender":"F","address":"658 Langham Street","employer":"Futurize","email":"sherriepatel@futurize.com","city":"Garfield","state":"OR"}
    {"index":{"_id":"955"}}
    {"account_number":955,"balance":41621,"firstname":"Klein","lastname":"Kemp","age":33,"gender":"M","address":"370 Vanderbilt Avenue","employer":"Synkgen","email":"kleinkemp@synkgen.com","city":"Bonanza","state":"FL"}
    {"index":{"_id":"962"}}
    {"account_number":962,"balance":32096,"firstname":"Trujillo","lastname":"Wilcox","age":21,"gender":"F","address":"914 Duffield Street","employer":"Extragene","email":"trujillowilcox@extragene.com","city":"Golconda","state":"MA"}
    {"index":{"_id":"967"}}
    {"account_number":967,"balance":19161,"firstname":"Carrie","lastname":"Huffman","age":36,"gender":"F","address":"240 Sands Street","employer":"Injoy","email":"carriehuffman@injoy.com","city":"Leroy","state":"CA"}
    {"index":{"_id":"974"}}
    {"account_number":974,"balance":38082,"firstname":"Deborah","lastname":"Yang","age":26,"gender":"F","address":"463 Goodwin Place","employer":"Entogrok","email":"deborahyang@entogrok.com","city":"Herald","state":"KY"}
    {"index":{"_id":"979"}}
    {"account_number":979,"balance":43130,"firstname":"Vaughn","lastname":"Pittman","age":29,"gender":"M","address":"446 Tompkins Place","employer":"Phormula","email":"vaughnpittman@phormula.com","city":"Fingerville","state":"WI"}
    {"index":{"_id":"981"}}
    {"account_number":981,"balance":20278,"firstname":"Nolan","lastname":"Warner","age":29,"gender":"F","address":"753 Channel Avenue","employer":"Interodeo","email":"nolanwarner@interodeo.com","city":"Layhill","state":"MT"}
    {"index":{"_id":"986"}}
    {"account_number":986,"balance":35086,"firstname":"Norris","lastname":"Hubbard","age":31,"gender":"M","address":"600 Celeste Court","employer":"Printspan","email":"norrishubbard@printspan.com","city":"Cassel","state":"MI"}
    {"index":{"_id":"993"}}
    {"account_number":993,"balance":26487,"firstname":"Campos","lastname":"Olsen","age":37,"gender":"M","address":"873 Covert Street","employer":"Isbol","email":"camposolsen@isbol.com","city":"Glendale","state":"AK"}
    {"index":{"_id":"998"}}
    {"account_number":998,"balance":16869,"firstname":"Letha","lastname":"Baker","age":40,"gender":"F","address":"206 Llama Court","employer":"Dognosis","email":"lethabaker@dognosis.com","city":"Dunlo","state":"WV"}
    {"index":{"_id":"2"}}
    {"account_number":2,"balance":28838,"firstname":"Roberta","lastname":"Bender","age":22,"gender":"F","address":"560 Kingsway Place","employer":"Chillium","email":"robertabender@chillium.com","city":"Bennett","state":"LA"}
    {"index":{"_id":"7"}}
    {"account_number":7,"balance":39121,"firstname":"Levy","lastname":"Richard","age":22,"gender":"M","address":"820 Logan Street","employer":"Teraprene","email":"levyrichard@teraprene.com","city":"Shrewsbury","state":"MO"}
    {"index":{"_id":"14"}}
    {"account_number":14,"balance":20480,"firstname":"Erma","lastname":"Kane","age":39,"gender":"F","address":"661 Vista Place","employer":"Stockpost","email":"ermakane@stockpost.com","city":"Chamizal","state":"NY"}
    {"index":{"_id":"19"}}
    {"account_number":19,"balance":27894,"firstname":"Schwartz","lastname":"Buchanan","age":28,"gender":"F","address":"449 Mersereau Court","employer":"Sybixtex","email":"schwartzbuchanan@sybixtex.com","city":"Greenwich","state":"KS"}
    {"index":{"_id":"21"}}
    {"account_number":21,"balance":7004,"firstname":"Estella","lastname":"Paul","age":38,"gender":"M","address":"859 Portal Street","employer":"Zillatide","email":"estellapaul@zillatide.com","city":"Churchill","state":"WV"}
    {"index":{"_id":"26"}}
    {"account_number":26,"balance":14127,"firstname":"Lorraine","lastname":"Mccullough","age":39,"gender":"F","address":"157 Dupont Street","employer":"Zosis","email":"lorrainemccullough@zosis.com","city":"Dennard","state":"NH"}
    {"index":{"_id":"33"}}
    {"account_number":33,"balance":35439,"firstname":"Savannah","lastname":"Kirby","age":30,"gender":"F","address":"372 Malta Street","employer":"Musanpoly","email":"savannahkirby@musanpoly.com","city":"Muse","state":"AK"}
    {"index":{"_id":"38"}}
    {"account_number":38,"balance":10511,"firstname":"Erna","lastname":"Fields","age":32,"gender":"M","address":"357 Maple Street","employer":"Eweville","email":"ernafields@eweville.com","city":"Twilight","state":"MS"}
    {"index":{"_id":"40"}}
    {"account_number":40,"balance":33882,"firstname":"Pace","lastname":"Molina","age":40,"gender":"M","address":"263 Ovington Court","employer":"Cytrak","email":"pacemolina@cytrak.com","city":"Silkworth","state":"OR"}
    {"index":{"_id":"45"}}
    {"account_number":45,"balance":44478,"firstname":"Geneva","lastname":"Morin","age":21,"gender":"F","address":"357 Herkimer Street","employer":"Ezent","email":"genevamorin@ezent.com","city":"Blanco","state":"AZ"}
    {"index":{"_id":"52"}}
    {"account_number":52,"balance":46425,"firstname":"Kayla","lastname":"Bradshaw","age":31,"gender":"M","address":"449 Barlow Drive","employer":"Magnemo","email":"kaylabradshaw@magnemo.com","city":"Wawona","state":"AZ"}
    {"index":{"_id":"57"}}
    {"account_number":57,"balance":8705,"firstname":"Powell","lastname":"Herring","age":21,"gender":"M","address":"263 Merit Court","employer":"Digiprint","email":"powellherring@digiprint.com","city":"Coral","state":"MT"}
    {"index":{"_id":"64"}}
    {"account_number":64,"balance":44036,"firstname":"Miles","lastname":"Battle","age":35,"gender":"F","address":"988 Homecrest Avenue","employer":"Koffee","email":"milesbattle@koffee.com","city":"Motley","state":"ID"}
    {"index":{"_id":"69"}}
    {"account_number":69,"balance":14253,"firstname":"Desiree","lastname":"Harrison","age":24,"gender":"M","address":"694 Garland Court","employer":"Barkarama","email":"desireeharrison@barkarama.com","city":"Hackneyville","state":"GA"}
    {"index":{"_id":"71"}}
    {"account_number":71,"balance":38201,"firstname":"Sharpe","lastname":"Hoffman","age":39,"gender":"F","address":"450 Conklin Avenue","employer":"Centree","email":"sharpehoffman@centree.com","city":"Urbana","state":"WY"}
    {"index":{"_id":"76"}}
    {"account_number":76,"balance":38345,"firstname":"Claudette","lastname":"Beard","age":24,"gender":"F","address":"748 Dorset Street","employer":"Repetwire","email":"claudettebeard@repetwire.com","city":"Caln","state":"TX"}
    {"index":{"_id":"83"}}
    {"account_number":83,"balance":35928,"firstname":"Mayo","lastname":"Cleveland","age":28,"gender":"M","address":"720 Brooklyn Road","employer":"Indexia","email":"mayocleveland@indexia.com","city":"Roberts","state":"ND"}
    {"index":{"_id":"88"}}
    {"account_number":88,"balance":26418,"firstname":"Adela","lastname":"Tyler","age":21,"gender":"F","address":"737 Clove Road","employer":"Surelogic","email":"adelatyler@surelogic.com","city":"Boling","state":"SD"}
    {"index":{"_id":"90"}}
    {"account_number":90,"balance":25332,"firstname":"Herman","lastname":"Snyder","age":22,"gender":"F","address":"737 College Place","employer":"Lunchpod","email":"hermansnyder@lunchpod.com","city":"Flintville","state":"IA"}
    {"index":{"_id":"95"}}
    {"account_number":95,"balance":1650,"firstname":"Dominguez","lastname":"Le","age":20,"gender":"M","address":"539 Grace Court","employer":"Portica","email":"dominguezle@portica.com","city":"Wollochet","state":"KS"}
    {"index":{"_id":"103"}}
    {"account_number":103,"balance":11253,"firstname":"Calhoun","lastname":"Bruce","age":33,"gender":"F","address":"731 Clarkson Avenue","employer":"Automon","email":"calhounbruce@automon.com","city":"Marienthal","state":"IL"}
    {"index":{"_id":"108"}}
    {"account_number":108,"balance":19015,"firstname":"Christensen","lastname":"Weaver","age":21,"gender":"M","address":"398 Dearborn Court","employer":"Quilk","email":"christensenweaver@quilk.com","city":"Belvoir","state":"TX"}
    {"index":{"_id":"110"}}
    {"account_number":110,"balance":4850,"firstname":"Daphne","lastname":"Byrd","age":23,"gender":"F","address":"239 Conover Street","employer":"Freakin","email":"daphnebyrd@freakin.com","city":"Taft","state":"MN"}
    {"index":{"_id":"115"}}
    {"account_number":115,"balance":18750,"firstname":"Nikki","lastname":"Doyle","age":31,"gender":"F","address":"537 Clara Street","employer":"Fossiel","email":"nikkidoyle@fossiel.com","city":"Caron","state":"MS"}
    {"index":{"_id":"122"}}
    {"account_number":122,"balance":17128,"firstname":"Aurora","lastname":"Fry","age":31,"gender":"F","address":"227 Knapp Street","employer":"Makingway","email":"aurorafry@makingway.com","city":"Maybell","state":"NE"}
    {"index":{"_id":"127"}}
    {"account_number":127,"balance":48734,"firstname":"Diann","lastname":"Mclaughlin","age":33,"gender":"F","address":"340 Clermont Avenue","employer":"Enomen","email":"diannmclaughlin@enomen.com","city":"Rutherford","state":"ND"}
    {"index":{"_id":"134"}}
    {"account_number":134,"balance":33829,"firstname":"Madelyn","lastname":"Norris","age":30,"gender":"F","address":"176 Noel Avenue","employer":"Endicil","email":"madelynnorris@endicil.com","city":"Walker","state":"NE"}
    {"index":{"_id":"139"}}
    {"account_number":139,"balance":18444,"firstname":"Rios","lastname":"Todd","age":35,"gender":"F","address":"281 Georgia Avenue","employer":"Uberlux","email":"riostodd@uberlux.com","city":"Hannasville","state":"PA"}
    {"index":{"_id":"141"}}
    {"account_number":141,"balance":20790,"firstname":"Liliana","lastname":"Caldwell","age":29,"gender":"M","address":"414 Huron Street","employer":"Rubadub","email":"lilianacaldwell@rubadub.com","city":"Hiwasse","state":"OK"}
    {"index":{"_id":"146"}}
    {"account_number":146,"balance":39078,"firstname":"Lang","lastname":"Kaufman","age":32,"gender":"F","address":"626 Beverley Road","employer":"Rodeomad","email":"langkaufman@rodeomad.com","city":"Mahtowa","state":"RI"}
    {"index":{"_id":"153"}}
    {"account_number":153,"balance":32074,"firstname":"Bird","lastname":"Cochran","age":31,"gender":"F","address":"691 Bokee Court","employer":"Supremia","email":"birdcochran@supremia.com","city":"Barrelville","state":"NE"}
    {"index":{"_id":"158"}}
    {"account_number":158,"balance":9380,"firstname":"Natalie","lastname":"Mcdowell","age":27,"gender":"M","address":"953 Roder Avenue","employer":"Myopium","email":"nataliemcdowell@myopium.com","city":"Savage","state":"ND"}
    {"index":{"_id":"160"}}
    {"account_number":160,"balance":48974,"firstname":"Hull","lastname":"Cherry","age":23,"gender":"F","address":"275 Beaumont Street","employer":"Noralex","email":"hullcherry@noralex.com","city":"Whipholt","state":"WA"}
    {"index":{"_id":"165"}}
    {"account_number":165,"balance":18956,"firstname":"Sims","lastname":"Mckay","age":40,"gender":"F","address":"205 Jackson Street","employer":"Comtour","email":"simsmckay@comtour.com","city":"Tilden","state":"DC"}
    {"index":{"_id":"172"}}
    {"account_number":172,"balance":18356,"firstname":"Marie","lastname":"Whitehead","age":20,"gender":"M","address":"704 Monaco Place","employer":"Sultrax","email":"mariewhitehead@sultrax.com","city":"Dragoon","state":"IL"}
    {"index":{"_id":"177"}}
    {"account_number":177,"balance":48972,"firstname":"Harris","lastname":"Gross","age":40,"gender":"F","address":"468 Suydam Street","employer":"Kidstock","email":"harrisgross@kidstock.com","city":"Yettem","state":"KY"}
    {"index":{"_id":"184"}}
    {"account_number":184,"balance":9157,"firstname":"Cathy","lastname":"Morrison","age":27,"gender":"M","address":"882 Pine Street","employer":"Zytrek","email":"cathymorrison@zytrek.com","city":"Fedora","state":"FL"}
    {"index":{"_id":"189"}}
    {"account_number":189,"balance":20167,"firstname":"Ada","lastname":"Cortez","age":38,"gender":"F","address":"700 Forest Place","employer":"Micronaut","email":"adacortez@micronaut.com","city":"Eagletown","state":"TX"}
    {"index":{"_id":"191"}}
    {"account_number":191,"balance":26172,"firstname":"Barr","lastname":"Sharpe","age":28,"gender":"M","address":"428 Auburn Place","employer":"Ziggles","email":"barrsharpe@ziggles.com","city":"Springdale","state":"KS"}
    {"index":{"_id":"196"}}
    {"account_number":196,"balance":29931,"firstname":"Caldwell","lastname":"Daniel","age":28,"gender":"F","address":"405 Oliver Street","employer":"Furnigeer","email":"caldwelldaniel@furnigeer.com","city":"Zortman","state":"NE"}
    {"index":{"_id":"204"}}
    {"account_number":204,"balance":27714,"firstname":"Mavis","lastname":"Deleon","age":39,"gender":"F","address":"400 Waldane Court","employer":"Lotron","email":"mavisdeleon@lotron.com","city":"Stollings","state":"LA"}
    {"index":{"_id":"209"}}
    {"account_number":209,"balance":31052,"firstname":"Myers","lastname":"Noel","age":30,"gender":"F","address":"691 Alton Place","employer":"Greeker","email":"myersnoel@greeker.com","city":"Hinsdale","state":"KY"}
    {"index":{"_id":"211"}}
    {"account_number":211,"balance":21539,"firstname":"Graciela","lastname":"Vaughan","age":22,"gender":"M","address":"558 Montauk Court","employer":"Fishland","email":"gracielavaughan@fishland.com","city":"Madrid","state":"PA"}
    {"index":{"_id":"216"}}
    {"account_number":216,"balance":11422,"firstname":"Price","lastname":"Haley","age":35,"gender":"M","address":"233 Portland Avenue","employer":"Zeam","email":"pricehaley@zeam.com","city":"Titanic","state":"UT"}
    {"index":{"_id":"223"}}
    {"account_number":223,"balance":9528,"firstname":"Newton","lastname":"Fletcher","age":26,"gender":"F","address":"654 Dewitt Avenue","employer":"Assistia","email":"newtonfletcher@assistia.com","city":"Nipinnawasee","state":"AK"}
    {"index":{"_id":"228"}}
    {"account_number":228,"balance":10543,"firstname":"Rosella","lastname":"Albert","age":20,"gender":"M","address":"185 Gotham Avenue","employer":"Isoplex","email":"rosellaalbert@isoplex.com","city":"Finzel","state":"NY"}
    {"index":{"_id":"230"}}
    {"account_number":230,"balance":10829,"firstname":"Chris","lastname":"Raymond","age":28,"gender":"F","address":"464 Remsen Street","employer":"Cogentry","email":"chrisraymond@cogentry.com","city":"Bowmansville","state":"SD"}
    {"index":{"_id":"235"}}
    {"account_number":235,"balance":17729,"firstname":"Mcpherson","lastname":"Mueller","age":31,"gender":"M","address":"541 Strong Place","employer":"Tingles","email":"mcphersonmueller@tingles.com","city":"Brantleyville","state":"AR"}
    {"index":{"_id":"242"}}
    {"account_number":242,"balance":42318,"firstname":"Berger","lastname":"Roach","age":21,"gender":"M","address":"125 Wakeman Place","employer":"Ovium","email":"bergerroach@ovium.com","city":"Hessville","state":"WI"}
    {"index":{"_id":"247"}}
    {"account_number":247,"balance":45123,"firstname":"Mccormick","lastname":"Moon","age":37,"gender":"M","address":"582 Brighton Avenue","employer":"Norsup","email":"mccormickmoon@norsup.com","city":"Forestburg","state":"DE"}
    {"index":{"_id":"254"}}
    {"account_number":254,"balance":35104,"firstname":"Yang","lastname":"Dodson","age":21,"gender":"M","address":"531 Lott Street","employer":"Mondicil","email":"yangdodson@mondicil.com","city":"Enoree","state":"UT"}
    {"index":{"_id":"259"}}
    {"account_number":259,"balance":41877,"firstname":"Eleanor","lastname":"Gonzalez","age":30,"gender":"M","address":"800 Sumpter Street","employer":"Futuris","email":"eleanorgonzalez@futuris.com","city":"Jenkinsville","state":"ID"}
    {"index":{"_id":"261"}}
    {"account_number":261,"balance":39998,"firstname":"Millicent","lastname":"Pickett","age":34,"gender":"F","address":"722 Montieth Street","employer":"Gushkool","email":"millicentpickett@gushkool.com","city":"Norwood","state":"MS"}
    {"index":{"_id":"266"}}
    {"account_number":266,"balance":2777,"firstname":"Monique","lastname":"Conner","age":35,"gender":"F","address":"489 Metrotech Courtr","employer":"Flotonic","email":"moniqueconner@flotonic.com","city":"Retsof","state":"MD"}
    {"index":{"_id":"273"}}
    {"account_number":273,"balance":11181,"firstname":"Murphy","lastname":"Chandler","age":20,"gender":"F","address":"569 Bradford Street","employer":"Zilch","email":"murphychandler@zilch.com","city":"Vicksburg","state":"FL"}
    {"index":{"_id":"278"}}
    {"account_number":278,"balance":22530,"firstname":"Tamra","lastname":"Navarro","age":27,"gender":"F","address":"175 Woodruff Avenue","employer":"Norsul","email":"tamranavarro@norsul.com","city":"Glasgow","state":"VT"}
    {"index":{"_id":"280"}}
    {"account_number":280,"balance":3380,"firstname":"Vilma","lastname":"Shields","age":26,"gender":"F","address":"133 Berriman Street","employer":"Applidec","email":"vilmashields@applidec.com","city":"Adamstown","state":"ME"}
    {"index":{"_id":"285"}}
    {"account_number":285,"balance":47369,"firstname":"Hilda","lastname":"Phillips","age":28,"gender":"F","address":"618 Nixon Court","employer":"Comcur","email":"hildaphillips@comcur.com","city":"Siglerville","state":"NC"}
    {"index":{"_id":"292"}}
    {"account_number":292,"balance":26679,"firstname":"Morrow","lastname":"Greene","age":20,"gender":"F","address":"691 Nassau Street","employer":"Columella","email":"morrowgreene@columella.com","city":"Sanborn","state":"FL"}
    {"index":{"_id":"297"}}
    {"account_number":297,"balance":20508,"firstname":"Tucker","lastname":"Patrick","age":35,"gender":"F","address":"978 Whitwell Place","employer":"Valreda","email":"tuckerpatrick@valreda.com","city":"Deseret","state":"CO"}
    {"index":{"_id":"300"}}
    {"account_number":300,"balance":25654,"firstname":"Lane","lastname":"Tate","age":26,"gender":"F","address":"632 Kay Court","employer":"Genesynk","email":"lanetate@genesynk.com","city":"Lowell","state":"MO"}
    {"index":{"_id":"305"}}
    {"account_number":305,"balance":11655,"firstname":"Augusta","lastname":"Winters","age":29,"gender":"F","address":"377 Paerdegat Avenue","employer":"Vendblend","email":"augustawinters@vendblend.com","city":"Gwynn","state":"MA"}
    {"index":{"_id":"312"}}
    {"account_number":312,"balance":8511,"firstname":"Burgess","lastname":"Gentry","age":25,"gender":"F","address":"382 Bergen Court","employer":"Orbixtar","email":"burgessgentry@orbixtar.com","city":"Conestoga","state":"WI"}
    {"index":{"_id":"317"}}
    {"account_number":317,"balance":31968,"firstname":"Ruiz","lastname":"Morris","age":31,"gender":"F","address":"972 Dean Street","employer":"Apex","email":"ruizmorris@apex.com","city":"Jacksonwald","state":"WV"}
    {"index":{"_id":"324"}}
    {"account_number":324,"balance":44976,"firstname":"Gladys","lastname":"Erickson","age":22,"gender":"M","address":"250 Battery Avenue","employer":"Eternis","email":"gladyserickson@eternis.com","city":"Marne","state":"IA"}
    {"index":{"_id":"329"}}
    {"account_number":329,"balance":31138,"firstname":"Nellie","lastname":"Mercer","age":25,"gender":"M","address":"967 Ebony Court","employer":"Scenty","email":"nelliemercer@scenty.com","city":"Jardine","state":"AK"}
    {"index":{"_id":"331"}}
    {"account_number":331,"balance":46004,"firstname":"Gibson","lastname":"Potts","age":34,"gender":"F","address":"994 Dahill Road","employer":"Zensus","email":"gibsonpotts@zensus.com","city":"Frizzleburg","state":"CO"}
    {"index":{"_id":"336"}}
    {"account_number":336,"balance":40891,"firstname":"Dudley","lastname":"Avery","age":25,"gender":"M","address":"405 Powers Street","employer":"Genmom","email":"dudleyavery@genmom.com","city":"Clarksburg","state":"CO"}
    {"index":{"_id":"343"}}
    {"account_number":343,"balance":37684,"firstname":"Robbie","lastname":"Logan","age":29,"gender":"M","address":"488 Linden Boulevard","employer":"Hydrocom","email":"robbielogan@hydrocom.com","city":"Stockdale","state":"TN"}
    {"index":{"_id":"348"}}
    {"account_number":348,"balance":1360,"firstname":"Karina","lastname":"Russell","age":37,"gender":"M","address":"797 Moffat Street","employer":"Limozen","email":"karinarussell@limozen.com","city":"Riegelwood","state":"RI"}
    {"index":{"_id":"350"}}
    {"account_number":350,"balance":4267,"firstname":"Wyatt","lastname":"Wise","age":22,"gender":"F","address":"896 Bleecker Street","employer":"Rockyard","email":"wyattwise@rockyard.com","city":"Joes","state":"MS"}
    {"index":{"_id":"355"}}
    {"account_number":355,"balance":40961,"firstname":"Gregory","lastname":"Delacruz","age":38,"gender":"M","address":"876 Cortelyou Road","employer":"Oulu","email":"gregorydelacruz@oulu.com","city":"Waterloo","state":"WV"}
    {"index":{"_id":"362"}}
    {"account_number":362,"balance":14938,"firstname":"Jimmie","lastname":"Dejesus","age":26,"gender":"M","address":"351 Navy Walk","employer":"Ecolight","email":"jimmiedejesus@ecolight.com","city":"Berlin","state":"ME"}
    {"index":{"_id":"367"}}
    {"account_number":367,"balance":40458,"firstname":"Elaine","lastname":"Workman","age":20,"gender":"M","address":"188 Ridge Boulevard","employer":"Colaire","email":"elaineworkman@colaire.com","city":"Herbster","state":"AK"}
    {"index":{"_id":"374"}}
    {"account_number":374,"balance":19521,"firstname":"Blanchard","lastname":"Stein","age":30,"gender":"M","address":"313 Bartlett Street","employer":"Cujo","email":"blanchardstein@cujo.com","city":"Cascades","state":"OR"}
    {"index":{"_id":"379"}}
    {"account_number":379,"balance":12962,"firstname":"Ruthie","lastname":"Lamb","age":21,"gender":"M","address":"796 Rockaway Avenue","employer":"Incubus","email":"ruthielamb@incubus.com","city":"Hickory","state":"TX"}
    {"index":{"_id":"381"}}
    {"account_number":381,"balance":40978,"firstname":"Sophie","lastname":"Mays","age":31,"gender":"M","address":"261 Varanda Place","employer":"Uneeq","email":"sophiemays@uneeq.com","city":"Cressey","state":"AR"}
    {"index":{"_id":"386"}}
    {"account_number":386,"balance":42588,"firstname":"Wallace","lastname":"Barr","age":39,"gender":"F","address":"246 Beverly Road","employer":"Concility","email":"wallacebarr@concility.com","city":"Durham","state":"IN"}
    {"index":{"_id":"393"}}
    {"account_number":393,"balance":43936,"firstname":"William","lastname":"Kelly","age":24,"gender":"M","address":"178 Lawrence Avenue","employer":"Techtrix","email":"williamkelly@techtrix.com","city":"Orin","state":"PA"}
    {"index":{"_id":"398"}}
    {"account_number":398,"balance":8543,"firstname":"Leticia","lastname":"Duran","age":35,"gender":"F","address":"305 Senator Street","employer":"Xleen","email":"leticiaduran@xleen.com","city":"Cavalero","state":"PA"}
    {"index":{"_id":"401"}}
    {"account_number":401,"balance":29408,"firstname":"Contreras","lastname":"Randolph","age":38,"gender":"M","address":"104 Lewis Avenue","employer":"Inrt","email":"contrerasrandolph@inrt.com","city":"Chesapeake","state":"CT"}
    {"index":{"_id":"406"}}
    {"account_number":406,"balance":28127,"firstname":"Mccarthy","lastname":"Dunlap","age":28,"gender":"F","address":"684 Seacoast Terrace","employer":"Canopoly","email":"mccarthydunlap@canopoly.com","city":"Elliott","state":"NC"}
    {"index":{"_id":"413"}}
    {"account_number":413,"balance":15631,"firstname":"Pugh","lastname":"Hamilton","age":39,"gender":"F","address":"124 Euclid Avenue","employer":"Techade","email":"pughhamilton@techade.com","city":"Beaulieu","state":"CA"}
    {"index":{"_id":"418"}}
    {"account_number":418,"balance":10207,"firstname":"Reed","lastname":"Goff","age":32,"gender":"M","address":"959 Everit Street","employer":"Zillan","email":"reedgoff@zillan.com","city":"Hiko","state":"WV"}
    {"index":{"_id":"420"}}
    {"account_number":420,"balance":44699,"firstname":"Brandie","lastname":"Hayden","age":22,"gender":"M","address":"291 Ash Street","employer":"Digifad","email":"brandiehayden@digifad.com","city":"Spelter","state":"NM"}
    {"index":{"_id":"425"}}
    {"account_number":425,"balance":41308,"firstname":"Queen","lastname":"Leach","age":30,"gender":"M","address":"105 Fair Street","employer":"Magneato","email":"queenleach@magneato.com","city":"Barronett","state":"NH"}
    {"index":{"_id":"432"}}
    {"account_number":432,"balance":28969,"firstname":"Preston","lastname":"Ferguson","age":40,"gender":"F","address":"239 Greenwood Avenue","employer":"Bitendrex","email":"prestonferguson@bitendrex.com","city":"Idledale","state":"ND"}
    {"index":{"_id":"437"}}
    {"account_number":437,"balance":41225,"firstname":"Rosales","lastname":"Marquez","age":29,"gender":"M","address":"873 Ryerson Street","employer":"Ronelon","email":"rosalesmarquez@ronelon.com","city":"Allendale","state":"CA"}
    {"index":{"_id":"444"}}
    {"account_number":444,"balance":44219,"firstname":"Dolly","lastname":"Finch","age":24,"gender":"F","address":"974 Interborough Parkway","employer":"Zytrac","email":"dollyfinch@zytrac.com","city":"Vowinckel","state":"WY"}
    {"index":{"_id":"449"}}
    {"account_number":449,"balance":41950,"firstname":"Barnett","lastname":"Cantrell","age":39,"gender":"F","address":"945 Bedell Lane","employer":"Zentility","email":"barnettcantrell@zentility.com","city":"Swartzville","state":"ND"}
    {"index":{"_id":"451"}}
    {"account_number":451,"balance":31950,"firstname":"Mason","lastname":"Mcleod","age":31,"gender":"F","address":"438 Havemeyer Street","employer":"Omatom","email":"masonmcleod@omatom.com","city":"Ryderwood","state":"NE"}
    {"index":{"_id":"456"}}
    {"account_number":456,"balance":21419,"firstname":"Solis","lastname":"Kline","age":33,"gender":"M","address":"818 Ashford Street","employer":"Vetron","email":"soliskline@vetron.com","city":"Ruffin","state":"NY"}
    {"index":{"_id":"463"}}
    {"account_number":463,"balance":36672,"firstname":"Heidi","lastname":"Acosta","age":20,"gender":"F","address":"692 Kenmore Terrace","employer":"Elpro","email":"heidiacosta@elpro.com","city":"Ezel","state":"SD"}
    {"index":{"_id":"468"}}
    {"account_number":468,"balance":18400,"firstname":"Foreman","lastname":"Fowler","age":40,"gender":"M","address":"443 Jackson Court","employer":"Zillactic","email":"foremanfowler@zillactic.com","city":"Wakarusa","state":"WA"}
    {"index":{"_id":"470"}}
    {"account_number":470,"balance":20455,"firstname":"Schneider","lastname":"Hull","age":35,"gender":"M","address":"724 Apollo Street","employer":"Exospeed","email":"schneiderhull@exospeed.com","city":"Watchtower","state":"ID"}
    {"index":{"_id":"475"}}
    {"account_number":475,"balance":24427,"firstname":"Morales","lastname":"Jacobs","age":22,"gender":"F","address":"225 Desmond Court","employer":"Oronoko","email":"moralesjacobs@oronoko.com","city":"Clayville","state":"CT"}
    {"index":{"_id":"482"}}
    {"account_number":482,"balance":14834,"firstname":"Janie","lastname":"Bass","age":39,"gender":"M","address":"781 Grattan Street","employer":"Manglo","email":"janiebass@manglo.com","city":"Kenwood","state":"IA"}
    {"index":{"_id":"487"}}
    {"account_number":487,"balance":30718,"firstname":"Sawyer","lastname":"Vincent","age":26,"gender":"F","address":"238 Lancaster Avenue","employer":"Brainquil","email":"sawyervincent@brainquil.com","city":"Galesville","state":"MS"}
    {"index":{"_id":"494"}}
    {"account_number":494,"balance":3592,"firstname":"Holden","lastname":"Bowen","age":30,"gender":"M","address":"374 Elmwood Avenue","employer":"Endipine","email":"holdenbowen@endipine.com","city":"Rosine","state":"ID"}
    {"index":{"_id":"499"}}
    {"account_number":499,"balance":26060,"firstname":"Lara","lastname":"Perkins","age":26,"gender":"M","address":"703 Monroe Street","employer":"Paprikut","email":"laraperkins@paprikut.com","city":"Barstow","state":"NY"}
    {"index":{"_id":"502"}}
    {"account_number":502,"balance":31898,"firstname":"Woodard","lastname":"Bailey","age":31,"gender":"F","address":"585 Albee Square","employer":"Imperium","email":"woodardbailey@imperium.com","city":"Matheny","state":"MT"}
    {"index":{"_id":"507"}}
    {"account_number":507,"balance":27675,"firstname":"Blankenship","lastname":"Ramirez","age":31,"gender":"M","address":"630 Graham Avenue","employer":"Bytrex","email":"blankenshipramirez@bytrex.com","city":"Bancroft","state":"CT"}
    {"index":{"_id":"514"}}
    {"account_number":514,"balance":30125,"firstname":"Solomon","lastname":"Bush","age":34,"gender":"M","address":"409 Harkness Avenue","employer":"Snacktion","email":"solomonbush@snacktion.com","city":"Grayhawk","state":"TX"}
    {"index":{"_id":"519"}}
    {"account_number":519,"balance":3282,"firstname":"Lorna","lastname":"Franco","age":31,"gender":"F","address":"722 Schenck Court","employer":"Zentia","email":"lornafranco@zentia.com","city":"National","state":"FL"}
    {"index":{"_id":"521"}}
    {"account_number":521,"balance":16348,"firstname":"Josefa","lastname":"Buckley","age":34,"gender":"F","address":"848 Taylor Street","employer":"Mazuda","email":"josefabuckley@mazuda.com","city":"Saranap","state":"NM"}
    {"index":{"_id":"526"}}
    {"account_number":526,"balance":35375,"firstname":"Sweeney","lastname":"Fulton","age":33,"gender":"F","address":"550 Martense Street","employer":"Cormoran","email":"sweeneyfulton@cormoran.com","city":"Chalfant","state":"IA"}
    {"index":{"_id":"533"}}
    {"account_number":533,"balance":13761,"firstname":"Margarita","lastname":"Diaz","age":23,"gender":"M","address":"295 Tapscott Street","employer":"Zilodyne","email":"margaritadiaz@zilodyne.com","city":"Hondah","state":"ID"}
    {"index":{"_id":"538"}}
    {"account_number":538,"balance":16416,"firstname":"Koch","lastname":"Barker","age":21,"gender":"M","address":"919 Gerry Street","employer":"Xplor","email":"kochbarker@xplor.com","city":"Dixie","state":"WY"}
    {"index":{"_id":"540"}}
    {"account_number":540,"balance":40235,"firstname":"Tammy","lastname":"Wiggins","age":32,"gender":"F","address":"186 Schenectady Avenue","employer":"Speedbolt","email":"tammywiggins@speedbolt.com","city":"Salvo","state":"LA"}
    {"index":{"_id":"545"}}
    {"account_number":545,"balance":27011,"firstname":"Lena","lastname":"Lucas","age":20,"gender":"M","address":"110 Lamont Court","employer":"Kindaloo","email":"lenalucas@kindaloo.com","city":"Harleigh","state":"KY"}
    {"index":{"_id":"552"}}
    {"account_number":552,"balance":14727,"firstname":"Kate","lastname":"Estes","age":39,"gender":"M","address":"785 Willmohr Street","employer":"Rodeocean","email":"kateestes@rodeocean.com","city":"Elfrida","state":"HI"}
    {"index":{"_id":"557"}}
    {"account_number":557,"balance":3119,"firstname":"Landry","lastname":"Buck","age":20,"gender":"M","address":"558 Schweikerts Walk","employer":"Protodyne","email":"landrybuck@protodyne.com","city":"Edneyville","state":"AL"}
    {"index":{"_id":"564"}}
    {"account_number":564,"balance":43631,"firstname":"Owens","lastname":"Bowers","age":22,"gender":"M","address":"842 Congress Street","employer":"Nspire","email":"owensbowers@nspire.com","city":"Machias","state":"VA"}
    {"index":{"_id":"569"}}
    {"account_number":569,"balance":40019,"firstname":"Sherri","lastname":"Rowe","age":39,"gender":"F","address":"591 Arlington Place","employer":"Netility","email":"sherrirowe@netility.com","city":"Bridgetown","state":"SC"}
    {"index":{"_id":"571"}}
    {"account_number":571,"balance":3014,"firstname":"Ayers","lastname":"Duffy","age":28,"gender":"F","address":"721 Wortman Avenue","employer":"Aquasseur","email":"ayersduffy@aquasseur.com","city":"Tilleda","state":"MS"}
    {"index":{"_id":"576"}}
    {"account_number":576,"balance":29682,"firstname":"Helena","lastname":"Robertson","age":33,"gender":"F","address":"774 Devon Avenue","employer":"Vicon","email":"helenarobertson@vicon.com","city":"Dyckesville","state":"NV"}
    {"index":{"_id":"583"}}
    {"account_number":583,"balance":26558,"firstname":"Castro","lastname":"West","age":34,"gender":"F","address":"814 Williams Avenue","employer":"Cipromox","email":"castrowest@cipromox.com","city":"Nescatunga","state":"IL"}
    {"index":{"_id":"588"}}
    {"account_number":588,"balance":43531,"firstname":"Martina","lastname":"Collins","age":31,"gender":"M","address":"301 Anna Court","employer":"Geekwagon","email":"martinacollins@geekwagon.com","city":"Oneida","state":"VA"}
    {"index":{"_id":"590"}}
    {"account_number":590,"balance":4652,"firstname":"Ladonna","lastname":"Tucker","age":31,"gender":"F","address":"162 Kane Place","employer":"Infotrips","email":"ladonnatucker@infotrips.com","city":"Utting","state":"IA"}
    {"index":{"_id":"595"}}
    {"account_number":595,"balance":12478,"firstname":"Mccall","lastname":"Britt","age":36,"gender":"F","address":"823 Hill Street","employer":"Cablam","email":"mccallbritt@cablam.com","city":"Vernon","state":"CA"}
    {"index":{"_id":"603"}}
    {"account_number":603,"balance":28145,"firstname":"Janette","lastname":"Guzman","age":31,"gender":"F","address":"976 Kingston Avenue","employer":"Splinx","email":"janetteguzman@splinx.com","city":"Boomer","state":"NC"}
    {"index":{"_id":"608"}}
    {"account_number":608,"balance":47091,"firstname":"Carey","lastname":"Whitley","age":32,"gender":"F","address":"976 Lawrence Street","employer":"Poshome","email":"careywhitley@poshome.com","city":"Weogufka","state":"NE"}
    {"index":{"_id":"610"}}
    {"account_number":610,"balance":40571,"firstname":"Foster","lastname":"Weber","age":24,"gender":"F","address":"323 Rochester Avenue","employer":"Firewax","email":"fosterweber@firewax.com","city":"Winston","state":"NY"}
    {"index":{"_id":"615"}}
    {"account_number":615,"balance":28726,"firstname":"Delgado","lastname":"Curry","age":28,"gender":"F","address":"706 Butler Street","employer":"Zoxy","email":"delgadocurry@zoxy.com","city":"Gracey","state":"SD"}
    {"index":{"_id":"622"}}
    {"account_number":622,"balance":9661,"firstname":"Paulette","lastname":"Hartman","age":38,"gender":"M","address":"375 Emerald Street","employer":"Locazone","email":"paulettehartman@locazone.com","city":"Canterwood","state":"OH"}
    {"index":{"_id":"627"}}
    {"account_number":627,"balance":47546,"firstname":"Crawford","lastname":"Sears","age":37,"gender":"F","address":"686 Eastern Parkway","employer":"Updat","email":"crawfordsears@updat.com","city":"Bison","state":"VT"}
    {"index":{"_id":"634"}}
    {"account_number":634,"balance":29805,"firstname":"Deloris","lastname":"Levy","age":38,"gender":"M","address":"838 Foster Avenue","employer":"Homelux","email":"delorislevy@homelux.com","city":"Kempton","state":"PA"}
    {"index":{"_id":"639"}}
    {"account_number":639,"balance":28875,"firstname":"Caitlin","lastname":"Clements","age":32,"gender":"F","address":"627 Aster Court","employer":"Bunga","email":"caitlinclements@bunga.com","city":"Cetronia","state":"SC"}
    {"index":{"_id":"641"}}
    {"account_number":641,"balance":18345,"firstname":"Sheppard","lastname":"Everett","age":39,"gender":"F","address":"791 Norwood Avenue","employer":"Roboid","email":"sheppardeverett@roboid.com","city":"Selma","state":"AK"}
    {"index":{"_id":"646"}}
    {"account_number":646,"balance":15559,"firstname":"Lavonne","lastname":"Reyes","age":31,"gender":"F","address":"983 Newport Street","employer":"Parcoe","email":"lavonnereyes@parcoe.com","city":"Monument","state":"LA"}
    {"index":{"_id":"653"}}
    {"account_number":653,"balance":7606,"firstname":"Marcia","lastname":"Bennett","age":33,"gender":"F","address":"455 Bragg Street","employer":"Opticall","email":"marciabennett@opticall.com","city":"Magnolia","state":"NC"}
    {"index":{"_id":"658"}}
    {"account_number":658,"balance":10210,"firstname":"Bass","lastname":"Mcconnell","age":32,"gender":"F","address":"274 Ocean Avenue","employer":"Combot","email":"bassmcconnell@combot.com","city":"Beyerville","state":"OH"}
    {"index":{"_id":"660"}}
    {"account_number":660,"balance":46427,"firstname":"Moon","lastname":"Wood","age":33,"gender":"F","address":"916 Amersfort Place","employer":"Olucore","email":"moonwood@olucore.com","city":"Como","state":"VA"}
    {"index":{"_id":"665"}}
    {"account_number":665,"balance":15215,"firstname":"Britney","lastname":"Young","age":36,"gender":"M","address":"766 Sackman Street","employer":"Geoforma","email":"britneyyoung@geoforma.com","city":"Tuttle","state":"WI"}
    {"index":{"_id":"672"}}
    {"account_number":672,"balance":12621,"firstname":"Camille","lastname":"Munoz","age":36,"gender":"F","address":"959 Lewis Place","employer":"Vantage","email":"camillemunoz@vantage.com","city":"Whitmer","state":"IN"}
    {"index":{"_id":"677"}}
    {"account_number":677,"balance":8491,"firstname":"Snider","lastname":"Benton","age":26,"gender":"M","address":"827 Evans Street","employer":"Medicroix","email":"sniderbenton@medicroix.com","city":"Kaka","state":"UT"}
    {"index":{"_id":"684"}}
    {"account_number":684,"balance":46091,"firstname":"Warren","lastname":"Snow","age":25,"gender":"M","address":"756 Oakland Place","employer":"Bizmatic","email":"warrensnow@bizmatic.com","city":"Hatteras","state":"NE"}
    {"index":{"_id":"689"}}
    {"account_number":689,"balance":14985,"firstname":"Ines","lastname":"Chaney","age":28,"gender":"M","address":"137 Dikeman Street","employer":"Zidant","email":"ineschaney@zidant.com","city":"Nettie","state":"DC"}
    {"index":{"_id":"691"}}
    {"account_number":691,"balance":10792,"firstname":"Mclean","lastname":"Colon","age":22,"gender":"M","address":"876 Classon Avenue","employer":"Elentrix","email":"mcleancolon@elentrix.com","city":"Unionville","state":"OK"}
    {"index":{"_id":"696"}}
    {"account_number":696,"balance":17568,"firstname":"Crane","lastname":"Matthews","age":32,"gender":"F","address":"721 Gerritsen Avenue","employer":"Intradisk","email":"cranematthews@intradisk.com","city":"Brewster","state":"WV"}
    {"index":{"_id":"704"}}
    {"account_number":704,"balance":45347,"firstname":"Peters","lastname":"Kent","age":22,"gender":"F","address":"871 Independence Avenue","employer":"Extragen","email":"peterskent@extragen.com","city":"Morriston","state":"CA"}
    {"index":{"_id":"709"}}
    {"account_number":709,"balance":11015,"firstname":"Abbott","lastname":"Odom","age":29,"gender":"M","address":"893 Union Street","employer":"Jimbies","email":"abbottodom@jimbies.com","city":"Leeper","state":"NJ"}
    {"index":{"_id":"711"}}
    {"account_number":711,"balance":26939,"firstname":"Villarreal","lastname":"Horton","age":35,"gender":"F","address":"861 Creamer Street","employer":"Lexicondo","email":"villarrealhorton@lexicondo.com","city":"Lydia","state":"MS"}
    {"index":{"_id":"716"}}
    {"account_number":716,"balance":19789,"firstname":"Paul","lastname":"Mason","age":34,"gender":"F","address":"618 Nichols Avenue","employer":"Slax","email":"paulmason@slax.com","city":"Snowville","state":"OK"}
    {"index":{"_id":"723"}}
    {"account_number":723,"balance":16421,"firstname":"Nixon","lastname":"Moran","age":27,"gender":"M","address":"569 Campus Place","employer":"Cuizine","email":"nixonmoran@cuizine.com","city":"Buxton","state":"DC"}
    {"index":{"_id":"728"}}
    {"account_number":728,"balance":44818,"firstname":"Conley","lastname":"Preston","age":28,"gender":"M","address":"450 Coventry Road","employer":"Obones","email":"conleypreston@obones.com","city":"Alden","state":"CO"}
    {"index":{"_id":"730"}}
    {"account_number":730,"balance":41299,"firstname":"Moore","lastname":"Lee","age":30,"gender":"M","address":"797 Turner Place","employer":"Orbean","email":"moorelee@orbean.com","city":"Highland","state":"DE"}
    {"index":{"_id":"735"}}
    {"account_number":735,"balance":3984,"firstname":"Loraine","lastname":"Willis","age":32,"gender":"F","address":"928 Grove Street","employer":"Gadtron","email":"lorainewillis@gadtron.com","city":"Lowgap","state":"NY"}
    {"index":{"_id":"742"}}
    {"account_number":742,"balance":24765,"firstname":"Merle","lastname":"Wooten","age":26,"gender":"M","address":"317 Pooles Lane","employer":"Tropolis","email":"merlewooten@tropolis.com","city":"Bentley","state":"ND"}
    {"index":{"_id":"747"}}
    {"account_number":747,"balance":16617,"firstname":"Diaz","lastname":"Austin","age":38,"gender":"M","address":"676 Harway Avenue","employer":"Irack","email":"diazaustin@irack.com","city":"Cliff","state":"HI"}
    {"index":{"_id":"754"}}
    {"account_number":754,"balance":10779,"firstname":"Jones","lastname":"Vega","age":25,"gender":"F","address":"795 India Street","employer":"Gluid","email":"jonesvega@gluid.com","city":"Tyhee","state":"FL"}
    {"index":{"_id":"759"}}
    {"account_number":759,"balance":38007,"firstname":"Rose","lastname":"Carlson","age":27,"gender":"M","address":"987 Navy Street","employer":"Aquasure","email":"rosecarlson@aquasure.com","city":"Carlton","state":"CT"}
    {"index":{"_id":"761"}}
    {"account_number":761,"balance":7663,"firstname":"Rae","lastname":"Juarez","age":34,"gender":"F","address":"560 Gilmore Court","employer":"Entropix","email":"raejuarez@entropix.com","city":"Northchase","state":"ID"}
    {"index":{"_id":"766"}}
    {"account_number":766,"balance":21957,"firstname":"Thomas","lastname":"Gillespie","age":38,"gender":"M","address":"993 Williams Place","employer":"Octocore","email":"thomasgillespie@octocore.com","city":"Defiance","state":"MS"}
    {"index":{"_id":"773"}}
    {"account_number":773,"balance":31126,"firstname":"Liza","lastname":"Coffey","age":36,"gender":"F","address":"540 Bulwer Place","employer":"Assurity","email":"lizacoffey@assurity.com","city":"Gilgo","state":"WV"}
    {"index":{"_id":"778"}}
    {"account_number":778,"balance":46007,"firstname":"Underwood","lastname":"Wheeler","age":28,"gender":"M","address":"477 Provost Street","employer":"Decratex","email":"underwoodwheeler@decratex.com","city":"Sardis","state":"ID"}
    {"index":{"_id":"780"}}
    {"account_number":780,"balance":4682,"firstname":"Maryanne","lastname":"Hendricks","age":26,"gender":"F","address":"709 Wolcott Street","employer":"Sarasonic","email":"maryannehendricks@sarasonic.com","city":"Santel","state":"NH"}
    {"index":{"_id":"785"}}
    {"account_number":785,"balance":25078,"firstname":"Fields","lastname":"Lester","age":29,"gender":"M","address":"808 Chestnut Avenue","employer":"Visualix","email":"fieldslester@visualix.com","city":"Rowe","state":"PA"}
    {"index":{"_id":"792"}}
    {"account_number":792,"balance":13109,"firstname":"Becky","lastname":"Jimenez","age":40,"gender":"F","address":"539 Front Street","employer":"Isologia","email":"beckyjimenez@isologia.com","city":"Summertown","state":"MI"}
    {"index":{"_id":"797"}}
    {"account_number":797,"balance":6854,"firstname":"Lindsay","lastname":"Mills","age":26,"gender":"F","address":"919 Quay Street","employer":"Zoinage","email":"lindsaymills@zoinage.com","city":"Elliston","state":"VA"}
    {"index":{"_id":"800"}}
    {"account_number":800,"balance":26217,"firstname":"Candy","lastname":"Oconnor","age":28,"gender":"M","address":"200 Newel Street","employer":"Radiantix","email":"candyoconnor@radiantix.com","city":"Sandston","state":"OH"}
    {"index":{"_id":"805"}}
    {"account_number":805,"balance":18426,"firstname":"Jackson","lastname":"Sampson","age":27,"gender":"F","address":"722 Kenmore Court","employer":"Daido","email":"jacksonsampson@daido.com","city":"Bellamy","state":"ME"}
    {"index":{"_id":"812"}}
    {"account_number":812,"balance":42593,"firstname":"Graves","lastname":"Newman","age":32,"gender":"F","address":"916 Joralemon Street","employer":"Ecrater","email":"gravesnewman@ecrater.com","city":"Crown","state":"PA"}
    {"index":{"_id":"817"}}
    {"account_number":817,"balance":36582,"firstname":"Padilla","lastname":"Bauer","age":36,"gender":"F","address":"310 Cadman Plaza","employer":"Exoblue","email":"padillabauer@exoblue.com","city":"Ahwahnee","state":"MN"}
    {"index":{"_id":"824"}}
    {"account_number":824,"balance":6053,"firstname":"Dyer","lastname":"Henson","age":33,"gender":"M","address":"650 Seaview Avenue","employer":"Nitracyr","email":"dyerhenson@nitracyr.com","city":"Gibsonia","state":"KS"}
    {"index":{"_id":"829"}}
    {"account_number":829,"balance":20263,"firstname":"Althea","lastname":"Bell","age":37,"gender":"M","address":"319 Cook Street","employer":"Hyplex","email":"altheabell@hyplex.com","city":"Wadsworth","state":"DC"}
    {"index":{"_id":"831"}}
    {"account_number":831,"balance":25375,"firstname":"Wendy","lastname":"Savage","age":37,"gender":"M","address":"421 Veranda Place","employer":"Neurocell","email":"wendysavage@neurocell.com","city":"Fresno","state":"MS"}
    {"index":{"_id":"836"}}
    {"account_number":836,"balance":20797,"firstname":"Lloyd","lastname":"Lindsay","age":25,"gender":"F","address":"953 Dinsmore Place","employer":"Suretech","email":"lloydlindsay@suretech.com","city":"Conway","state":"VA"}
    {"index":{"_id":"843"}}
    {"account_number":843,"balance":15555,"firstname":"Patricia","lastname":"Barton","age":34,"gender":"F","address":"406 Seabring Street","employer":"Providco","email":"patriciabarton@providco.com","city":"Avoca","state":"RI"}
    {"index":{"_id":"848"}}
    {"account_number":848,"balance":15443,"firstname":"Carmella","lastname":"Cash","age":38,"gender":"M","address":"988 Exeter Street","employer":"Bristo","email":"carmellacash@bristo.com","city":"Northridge","state":"ID"}
    {"index":{"_id":"850"}}
    {"account_number":850,"balance":6531,"firstname":"Carlene","lastname":"Gaines","age":37,"gender":"F","address":"753 Monroe Place","employer":"Naxdis","email":"carlenegaines@naxdis.com","city":"Genoa","state":"OR"}
    {"index":{"_id":"855"}}
    {"account_number":855,"balance":40170,"firstname":"Mia","lastname":"Stevens","age":31,"gender":"F","address":"326 Driggs Avenue","employer":"Aeora","email":"miastevens@aeora.com","city":"Delwood","state":"IL"}
    {"index":{"_id":"862"}}
    {"account_number":862,"balance":38792,"firstname":"Clayton","lastname":"Golden","age":38,"gender":"F","address":"620 Regent Place","employer":"Accusage","email":"claytongolden@accusage.com","city":"Ona","state":"NC"}
    {"index":{"_id":"867"}}
    {"account_number":867,"balance":45453,"firstname":"Blanca","lastname":"Ellison","age":23,"gender":"F","address":"593 McKibben Street","employer":"Koogle","email":"blancaellison@koogle.com","city":"Frystown","state":"WY"}
    {"index":{"_id":"874"}}
    {"account_number":874,"balance":23079,"firstname":"Lynette","lastname":"Higgins","age":22,"gender":"M","address":"377 McKinley Avenue","employer":"Menbrain","email":"lynettehiggins@menbrain.com","city":"Manitou","state":"TX"}
    {"index":{"_id":"879"}}
    {"account_number":879,"balance":48332,"firstname":"Sabrina","lastname":"Lancaster","age":31,"gender":"F","address":"382 Oak Street","employer":"Webiotic","email":"sabrinalancaster@webiotic.com","city":"Lindisfarne","state":"AZ"}
    {"index":{"_id":"881"}}
    {"account_number":881,"balance":26684,"firstname":"Barnes","lastname":"Ware","age":38,"gender":"F","address":"666 Hooper Street","employer":"Norali","email":"barnesware@norali.com","city":"Cazadero","state":"GA"}
    {"index":{"_id":"886"}}
    {"account_number":886,"balance":14867,"firstname":"Willa","lastname":"Leblanc","age":38,"gender":"F","address":"773 Bergen Street","employer":"Nurali","email":"willaleblanc@nurali.com","city":"Hilltop","state":"NC"}
    {"index":{"_id":"893"}}
    {"account_number":893,"balance":42584,"firstname":"Moses","lastname":"Campos","age":38,"gender":"F","address":"991 Bevy Court","employer":"Trollery","email":"mosescampos@trollery.com","city":"Freetown","state":"AK"}
    {"index":{"_id":"898"}}
    {"account_number":898,"balance":12019,"firstname":"Lori","lastname":"Stevenson","age":29,"gender":"M","address":"910 Coles Street","employer":"Honotron","email":"loristevenson@honotron.com","city":"Shindler","state":"VT"}
    {"index":{"_id":"901"}}
    {"account_number":901,"balance":35038,"firstname":"Irma","lastname":"Dotson","age":23,"gender":"F","address":"245 Mayfair Drive","employer":"Bleeko","email":"irmadotson@bleeko.com","city":"Lodoga","state":"UT"}
    {"index":{"_id":"906"}}
    {"account_number":906,"balance":24073,"firstname":"Vicki","lastname":"Suarez","age":36,"gender":"M","address":"829 Roosevelt Place","employer":"Utara","email":"vickisuarez@utara.com","city":"Albrightsville","state":"AR"}
    {"index":{"_id":"913"}}
    {"account_number":913,"balance":47657,"firstname":"Margery","lastname":"Monroe","age":25,"gender":"M","address":"941 Fanchon Place","employer":"Exerta","email":"margerymonroe@exerta.com","city":"Bannock","state":"MD"}
    {"index":{"_id":"918"}}
    {"account_number":918,"balance":36776,"firstname":"Dianna","lastname":"Hernandez","age":25,"gender":"M","address":"499 Moultrie Street","employer":"Isologica","email":"diannahernandez@isologica.com","city":"Falconaire","state":"ID"}
    {"index":{"_id":"920"}}
    {"account_number":920,"balance":41513,"firstname":"Jerri","lastname":"Mitchell","age":26,"gender":"M","address":"831 Kent Street","employer":"Tasmania","email":"jerrimitchell@tasmania.com","city":"Cotopaxi","state":"IA"}
    {"index":{"_id":"925"}}
    {"account_number":925,"balance":18295,"firstname":"Rosario","lastname":"Jackson","age":24,"gender":"M","address":"178 Leonora Court","employer":"Progenex","email":"rosariojackson@progenex.com","city":"Rivereno","state":"DE"}
    {"index":{"_id":"932"}}
    {"account_number":932,"balance":3111,"firstname":"Summer","lastname":"Porter","age":33,"gender":"F","address":"949 Grand Avenue","employer":"Multiflex","email":"summerporter@multiflex.com","city":"Spokane","state":"OK"}
    {"index":{"_id":"937"}}
    {"account_number":937,"balance":43491,"firstname":"Selma","lastname":"Anderson","age":24,"gender":"M","address":"205 Reed Street","employer":"Dadabase","email":"selmaanderson@dadabase.com","city":"Malo","state":"AL"}
    {"index":{"_id":"944"}}
    {"account_number":944,"balance":46478,"firstname":"Donaldson","lastname":"Woodard","age":38,"gender":"F","address":"498 Laurel Avenue","employer":"Zogak","email":"donaldsonwoodard@zogak.com","city":"Hasty","state":"ID"}
    {"index":{"_id":"949"}}
    {"account_number":949,"balance":48703,"firstname":"Latasha","lastname":"Mullins","age":29,"gender":"F","address":"272 Lefferts Place","employer":"Zenolux","email":"latashamullins@zenolux.com","city":"Kieler","state":"MN"}
    {"index":{"_id":"951"}}
    {"account_number":951,"balance":36337,"firstname":"Tran","lastname":"Burris","age":25,"gender":"F","address":"561 Rutland Road","employer":"Geoform","email":"tranburris@geoform.com","city":"Longbranch","state":"IL"}
    {"index":{"_id":"956"}}
    {"account_number":956,"balance":19477,"firstname":"Randall","lastname":"Lynch","age":22,"gender":"F","address":"490 Madison Place","employer":"Cosmetex","email":"randalllynch@cosmetex.com","city":"Wells","state":"SD"}
    {"index":{"_id":"963"}}
    {"account_number":963,"balance":30461,"firstname":"Griffin","lastname":"Sheppard","age":20,"gender":"M","address":"682 Linden Street","employer":"Zanymax","email":"griffinsheppard@zanymax.com","city":"Fannett","state":"NM"}
    {"index":{"_id":"968"}}
    {"account_number":968,"balance":32371,"firstname":"Luella","lastname":"Burch","age":39,"gender":"M","address":"684 Arkansas Drive","employer":"Krag","email":"luellaburch@krag.com","city":"Brambleton","state":"SD"}
    {"index":{"_id":"970"}}
    {"account_number":970,"balance":19648,"firstname":"Forbes","lastname":"Wallace","age":28,"gender":"M","address":"990 Mill Road","employer":"Pheast","email":"forbeswallace@pheast.com","city":"Lopezo","state":"AK"}
    {"index":{"_id":"975"}}
    {"account_number":975,"balance":5239,"firstname":"Delores","lastname":"Booker","age":27,"gender":"F","address":"328 Conselyea Street","employer":"Centice","email":"deloresbooker@centice.com","city":"Williams","state":"HI"}
    {"index":{"_id":"982"}}
    {"account_number":982,"balance":16511,"firstname":"Buck","lastname":"Robinson","age":24,"gender":"M","address":"301 Melrose Street","employer":"Calcu","email":"buckrobinson@calcu.com","city":"Welch","state":"PA"}
    {"index":{"_id":"987"}}
    {"account_number":987,"balance":4072,"firstname":"Brock","lastname":"Sandoval","age":20,"gender":"F","address":"977 Gem Street","employer":"Fiberox","email":"brocksandoval@fiberox.com","city":"Celeryville","state":"NY"}
    {"index":{"_id":"994"}}
    {"account_number":994,"balance":33298,"firstname":"Madge","lastname":"Holcomb","age":31,"gender":"M","address":"612 Hawthorne Street","employer":"Escenta","email":"madgeholcomb@escenta.com","city":"Alafaya","state":"OR"}
    {"index":{"_id":"999"}}
    {"account_number":999,"balance":6087,"firstname":"Dorothy","lastname":"Barron","age":22,"gender":"F","address":"499 Laurel Avenue","employer":"Xurban","email":"dorothybarron@xurban.com","city":"Belvoir","state":"CA"}
    {"index":{"_id":"4"}}
    {"account_number":4,"balance":27658,"firstname":"Rodriquez","lastname":"Flores","age":31,"gender":"F","address":"986 Wyckoff Avenue","employer":"Tourmania","email":"rodriquezflores@tourmania.com","city":"Eastvale","state":"HI"}
    {"index":{"_id":"9"}}
    {"account_number":9,"balance":24776,"firstname":"Opal","lastname":"Meadows","age":39,"gender":"M","address":"963 Neptune Avenue","employer":"Cedward","email":"opalmeadows@cedward.com","city":"Olney","state":"OH"}
    {"index":{"_id":"11"}}
    {"account_number":11,"balance":20203,"firstname":"Jenkins","lastname":"Haney","age":20,"gender":"M","address":"740 Ferry Place","employer":"Qimonk","email":"jenkinshaney@qimonk.com","city":"Steinhatchee","state":"GA"}
    {"index":{"_id":"16"}}
    {"account_number":16,"balance":35883,"firstname":"Adrian","lastname":"Pitts","age":34,"gender":"F","address":"963 Fay Court","employer":"Combogene","email":"adrianpitts@combogene.com","city":"Remington","state":"SD"}
    {"index":{"_id":"23"}}
    {"account_number":23,"balance":42374,"firstname":"Kirsten","lastname":"Fox","age":20,"gender":"M","address":"330 Dumont Avenue","employer":"Codax","email":"kirstenfox@codax.com","city":"Walton","state":"AK"}
    {"index":{"_id":"28"}}
    {"account_number":28,"balance":42112,"firstname":"Vega","lastname":"Flynn","age":20,"gender":"M","address":"647 Hyman Court","employer":"Accupharm","email":"vegaflynn@accupharm.com","city":"Masthope","state":"OH"}
    {"index":{"_id":"30"}}
    {"account_number":30,"balance":19087,"firstname":"Lamb","lastname":"Townsend","age":26,"gender":"M","address":"169 Lyme Avenue","employer":"Geeknet","email":"lambtownsend@geeknet.com","city":"Epworth","state":"AL"}
    {"index":{"_id":"35"}}
    {"account_number":35,"balance":42039,"firstname":"Darla","lastname":"Bridges","age":27,"gender":"F","address":"315 Central Avenue","employer":"Xeronk","email":"darlabridges@xeronk.com","city":"Woodlake","state":"RI"}
    {"index":{"_id":"42"}}
    {"account_number":42,"balance":21137,"firstname":"Harding","lastname":"Hobbs","age":26,"gender":"F","address":"474 Ridgewood Place","employer":"Xth","email":"hardinghobbs@xth.com","city":"Heil","state":"ND"}
    {"index":{"_id":"47"}}
    {"account_number":47,"balance":33044,"firstname":"Georgia","lastname":"Wilkerson","age":23,"gender":"M","address":"369 Herbert Street","employer":"Endipin","email":"georgiawilkerson@endipin.com","city":"Dellview","state":"WI"}
    {"index":{"_id":"54"}}
    {"account_number":54,"balance":23406,"firstname":"Angel","lastname":"Mann","age":22,"gender":"F","address":"229 Ferris Street","employer":"Amtas","email":"angelmann@amtas.com","city":"Calverton","state":"WA"}
    {"index":{"_id":"59"}}
    {"account_number":59,"balance":37728,"firstname":"Malone","lastname":"Justice","age":37,"gender":"F","address":"721 Russell Street","employer":"Emoltra","email":"malonejustice@emoltra.com","city":"Trucksville","state":"HI"}
    {"index":{"_id":"61"}}
    {"account_number":61,"balance":6856,"firstname":"Shawn","lastname":"Baird","age":20,"gender":"M","address":"605 Monument Walk","employer":"Moltonic","email":"shawnbaird@moltonic.com","city":"Darlington","state":"MN"}
    {"index":{"_id":"66"}}
    {"account_number":66,"balance":25939,"firstname":"Franks","lastname":"Salinas","age":28,"gender":"M","address":"437 Hamilton Walk","employer":"Cowtown","email":"frankssalinas@cowtown.com","city":"Chase","state":"VT"}
    {"index":{"_id":"73"}}
    {"account_number":73,"balance":33457,"firstname":"Irene","lastname":"Stephenson","age":32,"gender":"M","address":"684 Miller Avenue","employer":"Hawkster","email":"irenestephenson@hawkster.com","city":"Levant","state":"AR"}
    {"index":{"_id":"78"}}
    {"account_number":78,"balance":48656,"firstname":"Elvira","lastname":"Patterson","age":23,"gender":"F","address":"834 Amber Street","employer":"Assistix","email":"elvirapatterson@assistix.com","city":"Dunbar","state":"TN"}
    {"index":{"_id":"80"}}
    {"account_number":80,"balance":13445,"firstname":"Lacey","lastname":"Blanchard","age":30,"gender":"F","address":"823 Himrod Street","employer":"Comdom","email":"laceyblanchard@comdom.com","city":"Matthews","state":"MO"}
    {"index":{"_id":"85"}}
    {"account_number":85,"balance":48735,"firstname":"Wilcox","lastname":"Sellers","age":20,"gender":"M","address":"212 Irving Avenue","employer":"Confrenzy","email":"wilcoxsellers@confrenzy.com","city":"Kipp","state":"MT"}
    {"index":{"_id":"92"}}
    {"account_number":92,"balance":26753,"firstname":"Gay","lastname":"Brewer","age":34,"gender":"M","address":"369 Ditmars Street","employer":"Savvy","email":"gaybrewer@savvy.com","city":"Moquino","state":"HI"}
    {"index":{"_id":"97"}}
    {"account_number":97,"balance":49671,"firstname":"Karen","lastname":"Trujillo","age":40,"gender":"F","address":"512 Cumberland Walk","employer":"Tsunamia","email":"karentrujillo@tsunamia.com","city":"Fredericktown","state":"MO"}
    {"index":{"_id":"100"}}
    {"account_number":100,"balance":29869,"firstname":"Madden","lastname":"Woods","age":32,"gender":"F","address":"696 Ryder Avenue","employer":"Slumberia","email":"maddenwoods@slumberia.com","city":"Deercroft","state":"ME"}
    {"index":{"_id":"105"}}
    {"account_number":105,"balance":29654,"firstname":"Castillo","lastname":"Dickerson","age":33,"gender":"F","address":"673 Oxford Street","employer":"Tellifly","email":"castillodickerson@tellifly.com","city":"Succasunna","state":"NY"}
    {"index":{"_id":"112"}}
    {"account_number":112,"balance":38395,"firstname":"Frederick","lastname":"Case","age":30,"gender":"F","address":"580 Lexington Avenue","employer":"Talkalot","email":"frederickcase@talkalot.com","city":"Orovada","state":"MA"}
    {"index":{"_id":"117"}}
    {"account_number":117,"balance":48831,"firstname":"Robin","lastname":"Hays","age":38,"gender":"F","address":"347 Hornell Loop","employer":"Pasturia","email":"robinhays@pasturia.com","city":"Sims","state":"WY"}
    {"index":{"_id":"124"}}
    {"account_number":124,"balance":16425,"firstname":"Fern","lastname":"Lambert","age":20,"gender":"M","address":"511 Jay Street","employer":"Furnitech","email":"fernlambert@furnitech.com","city":"Cloverdale","state":"FL"}
    {"index":{"_id":"129"}}
    {"account_number":129,"balance":42409,"firstname":"Alexandria","lastname":"Sanford","age":33,"gender":"F","address":"934 Ridgecrest Terrace","employer":"Kyagoro","email":"alexandriasanford@kyagoro.com","city":"Concho","state":"UT"}
    {"index":{"_id":"131"}}
    {"account_number":131,"balance":28030,"firstname":"Dollie","lastname":"Koch","age":22,"gender":"F","address":"287 Manhattan Avenue","employer":"Skinserve","email":"dolliekoch@skinserve.com","city":"Shasta","state":"PA"}
    {"index":{"_id":"136"}}
    {"account_number":136,"balance":45801,"firstname":"Winnie","lastname":"Holland","age":38,"gender":"M","address":"198 Mill Lane","employer":"Neteria","email":"winnieholland@neteria.com","city":"Urie","state":"IL"}
    {"index":{"_id":"143"}}
    {"account_number":143,"balance":43093,"firstname":"Cohen","lastname":"Noble","age":39,"gender":"M","address":"454 Nelson Street","employer":"Buzzworks","email":"cohennoble@buzzworks.com","city":"Norvelt","state":"CO"}
    {"index":{"_id":"148"}}
    {"account_number":148,"balance":3662,"firstname":"Annmarie","lastname":"Snider","age":34,"gender":"F","address":"857 Lafayette Walk","employer":"Edecine","email":"annmariesnider@edecine.com","city":"Hollins","state":"OH"}
    {"index":{"_id":"150"}}
    {"account_number":150,"balance":15306,"firstname":"Ortega","lastname":"Dalton","age":20,"gender":"M","address":"237 Mermaid Avenue","employer":"Rameon","email":"ortegadalton@rameon.com","city":"Maxville","state":"NH"}
    {"index":{"_id":"155"}}
    {"account_number":155,"balance":27878,"firstname":"Atkinson","lastname":"Hudson","age":39,"gender":"F","address":"434 Colin Place","employer":"Qualitern","email":"atkinsonhudson@qualitern.com","city":"Hoehne","state":"OH"}
    {"index":{"_id":"162"}}
    {"account_number":162,"balance":6302,"firstname":"Griffith","lastname":"Calderon","age":35,"gender":"M","address":"871 Vandervoort Place","employer":"Quotezart","email":"griffithcalderon@quotezart.com","city":"Barclay","state":"FL"}
    {"index":{"_id":"167"}}
    {"account_number":167,"balance":42051,"firstname":"Hampton","lastname":"Ryan","age":20,"gender":"M","address":"618 Fleet Place","employer":"Zipak","email":"hamptonryan@zipak.com","city":"Irwin","state":"KS"}
    {"index":{"_id":"174"}}
    {"account_number":174,"balance":1464,"firstname":"Gamble","lastname":"Pierce","age":23,"gender":"F","address":"650 Eagle Street","employer":"Matrixity","email":"gamblepierce@matrixity.com","city":"Abiquiu","state":"OR"}
    {"index":{"_id":"179"}}
    {"account_number":179,"balance":13265,"firstname":"Elise","lastname":"Drake","age":25,"gender":"M","address":"305 Christopher Avenue","employer":"Turnling","email":"elisedrake@turnling.com","city":"Loretto","state":"LA"}
    {"index":{"_id":"181"}}
    {"account_number":181,"balance":27983,"firstname":"Bennett","lastname":"Hampton","age":22,"gender":"F","address":"435 Billings Place","employer":"Voipa","email":"bennetthampton@voipa.com","city":"Rodman","state":"WY"}
    {"index":{"_id":"186"}}
    {"account_number":186,"balance":18373,"firstname":"Kline","lastname":"Joyce","age":32,"gender":"M","address":"285 Falmouth Street","employer":"Tetratrex","email":"klinejoyce@tetratrex.com","city":"Klondike","state":"SD"}
    {"index":{"_id":"193"}}
    {"account_number":193,"balance":13412,"firstname":"Patty","lastname":"Petty","age":34,"gender":"F","address":"251 Vermont Street","employer":"Kinetica","email":"pattypetty@kinetica.com","city":"Grantville","state":"MS"}
    {"index":{"_id":"198"}}
    {"account_number":198,"balance":19686,"firstname":"Rachael","lastname":"Sharp","age":38,"gender":"F","address":"443 Vernon Avenue","employer":"Powernet","email":"rachaelsharp@powernet.com","city":"Canoochee","state":"UT"}
    {"index":{"_id":"201"}}
    {"account_number":201,"balance":14586,"firstname":"Ronda","lastname":"Perry","age":25,"gender":"F","address":"856 Downing Street","employer":"Artiq","email":"rondaperry@artiq.com","city":"Colton","state":"WV"}
    {"index":{"_id":"206"}}
    {"account_number":206,"balance":47423,"firstname":"Kelli","lastname":"Francis","age":20,"gender":"M","address":"671 George Street","employer":"Exoswitch","email":"kellifrancis@exoswitch.com","city":"Babb","state":"NJ"}
    {"index":{"_id":"213"}}
    {"account_number":213,"balance":34172,"firstname":"Bauer","lastname":"Summers","age":27,"gender":"M","address":"257 Boynton Place","employer":"Voratak","email":"bauersummers@voratak.com","city":"Oceola","state":"NC"}
    {"index":{"_id":"218"}}
    {"account_number":218,"balance":26702,"firstname":"Garrison","lastname":"Bryan","age":24,"gender":"F","address":"478 Greenpoint Avenue","employer":"Uniworld","email":"garrisonbryan@uniworld.com","city":"Comptche","state":"WI"}
    {"index":{"_id":"220"}}
    {"account_number":220,"balance":3086,"firstname":"Tania","lastname":"Middleton","age":22,"gender":"F","address":"541 Gunther Place","employer":"Zerology","email":"taniamiddleton@zerology.com","city":"Linwood","state":"IN"}
    {"index":{"_id":"225"}}
    {"account_number":225,"balance":21949,"firstname":"Maryann","lastname":"Murphy","age":24,"gender":"F","address":"894 Bridgewater Street","employer":"Cinesanct","email":"maryannmurphy@cinesanct.com","city":"Cartwright","state":"RI"}
    {"index":{"_id":"232"}}
    {"account_number":232,"balance":11984,"firstname":"Carr","lastname":"Jensen","age":34,"gender":"F","address":"995 Micieli Place","employer":"Biohab","email":"carrjensen@biohab.com","city":"Waikele","state":"OH"}
    {"index":{"_id":"237"}}
    {"account_number":237,"balance":5603,"firstname":"Kirby","lastname":"Watkins","age":27,"gender":"F","address":"348 Blake Court","employer":"Sonique","email":"kirbywatkins@sonique.com","city":"Freelandville","state":"PA"}
    {"index":{"_id":"244"}}
    {"account_number":244,"balance":8048,"firstname":"Judith","lastname":"Riggs","age":27,"gender":"F","address":"590 Kosciusko Street","employer":"Arctiq","email":"judithriggs@arctiq.com","city":"Gorham","state":"DC"}
    {"index":{"_id":"249"}}
    {"account_number":249,"balance":16822,"firstname":"Mckinney","lastname":"Gallagher","age":38,"gender":"F","address":"939 Seigel Court","employer":"Premiant","email":"mckinneygallagher@premiant.com","city":"Catharine","state":"NH"}
    {"index":{"_id":"251"}}
    {"account_number":251,"balance":13475,"firstname":"Marks","lastname":"Graves","age":39,"gender":"F","address":"427 Lawn Court","employer":"Dentrex","email":"marksgraves@dentrex.com","city":"Waukeenah","state":"IL"}
    {"index":{"_id":"256"}}
    {"account_number":256,"balance":48318,"firstname":"Simon","lastname":"Hogan","age":31,"gender":"M","address":"789 Suydam Place","employer":"Dancerity","email":"simonhogan@dancerity.com","city":"Dargan","state":"GA"}
    {"index":{"_id":"263"}}
    {"account_number":263,"balance":12837,"firstname":"Thornton","lastname":"Meyer","age":29,"gender":"M","address":"575 Elliott Place","employer":"Peticular","email":"thorntonmeyer@peticular.com","city":"Dotsero","state":"NH"}
    {"index":{"_id":"268"}}
    {"account_number":268,"balance":20925,"firstname":"Avis","lastname":"Blackwell","age":36,"gender":"M","address":"569 Jerome Avenue","employer":"Magnina","email":"avisblackwell@magnina.com","city":"Bethany","state":"MD"}
    {"index":{"_id":"270"}}
    {"account_number":270,"balance":43951,"firstname":"Moody","lastname":"Harmon","age":39,"gender":"F","address":"233 Vanderbilt Street","employer":"Otherside","email":"moodyharmon@otherside.com","city":"Elwood","state":"MT"}
    {"index":{"_id":"275"}}
    {"account_number":275,"balance":2384,"firstname":"Reynolds","lastname":"Barnett","age":31,"gender":"M","address":"394 Stockton Street","employer":"Austex","email":"reynoldsbarnett@austex.com","city":"Grandview","state":"MS"}
    {"index":{"_id":"282"}}
    {"account_number":282,"balance":38540,"firstname":"Gay","lastname":"Schultz","age":25,"gender":"F","address":"805 Claver Place","employer":"Handshake","email":"gayschultz@handshake.com","city":"Tampico","state":"MA"}
    {"index":{"_id":"287"}}
    {"account_number":287,"balance":10845,"firstname":"Valerie","lastname":"Lang","age":35,"gender":"F","address":"423 Midwood Street","employer":"Quarx","email":"valerielang@quarx.com","city":"Cannondale","state":"VT"}
    {"index":{"_id":"294"}}
    {"account_number":294,"balance":29582,"firstname":"Pitts","lastname":"Haynes","age":26,"gender":"M","address":"901 Broome Street","employer":"Aquazure","email":"pittshaynes@aquazure.com","city":"Turah","state":"SD"}
    {"index":{"_id":"299"}}
    {"account_number":299,"balance":40825,"firstname":"Angela","lastname":"Talley","age":36,"gender":"F","address":"822 Bills Place","employer":"Remold","email":"angelatalley@remold.com","city":"Bethpage","state":"DC"}
    {"index":{"_id":"302"}}
    {"account_number":302,"balance":11298,"firstname":"Isabella","lastname":"Hewitt","age":40,"gender":"M","address":"455 Bedford Avenue","employer":"Cincyr","email":"isabellahewitt@cincyr.com","city":"Blanford","state":"IN"}
    {"index":{"_id":"307"}}
    {"account_number":307,"balance":43355,"firstname":"Enid","lastname":"Ashley","age":23,"gender":"M","address":"412 Emerson Place","employer":"Avenetro","email":"enidashley@avenetro.com","city":"Catherine","state":"WI"}
    {"index":{"_id":"314"}}
    {"account_number":314,"balance":5848,"firstname":"Norton","lastname":"Norton","age":35,"gender":"M","address":"252 Ditmas Avenue","employer":"Talkola","email":"nortonnorton@talkola.com","city":"Veyo","state":"SC"}
    {"index":{"_id":"319"}}
    {"account_number":319,"balance":15430,"firstname":"Ferrell","lastname":"Mckinney","age":36,"gender":"M","address":"874 Cranberry Street","employer":"Portaline","email":"ferrellmckinney@portaline.com","city":"Rose","state":"WV"}
    {"index":{"_id":"321"}}
    {"account_number":321,"balance":43370,"firstname":"Marta","lastname":"Larsen","age":35,"gender":"M","address":"617 Williams Court","employer":"Manufact","email":"martalarsen@manufact.com","city":"Sisquoc","state":"MA"}
    {"index":{"_id":"326"}}
    {"account_number":326,"balance":9692,"firstname":"Pearl","lastname":"Reese","age":30,"gender":"F","address":"451 Colonial Court","employer":"Accruex","email":"pearlreese@accruex.com","city":"Westmoreland","state":"MD"}
    {"index":{"_id":"333"}}
    {"account_number":333,"balance":22778,"firstname":"Trudy","lastname":"Sweet","age":27,"gender":"F","address":"881 Kiely Place","employer":"Acumentor","email":"trudysweet@acumentor.com","city":"Kent","state":"IA"}
    {"index":{"_id":"338"}}
    {"account_number":338,"balance":6969,"firstname":"Pierce","lastname":"Lawrence","age":35,"gender":"M","address":"318 Gallatin Place","employer":"Lunchpad","email":"piercelawrence@lunchpad.com","city":"Iola","state":"MD"}
    {"index":{"_id":"340"}}
    {"account_number":340,"balance":42072,"firstname":"Juarez","lastname":"Gutierrez","age":40,"gender":"F","address":"802 Seba Avenue","employer":"Billmed","email":"juarezgutierrez@billmed.com","city":"Malott","state":"OH"}
    {"index":{"_id":"345"}}
    {"account_number":345,"balance":9812,"firstname":"Parker","lastname":"Hines","age":38,"gender":"M","address":"715 Mill Avenue","employer":"Baluba","email":"parkerhines@baluba.com","city":"Blackgum","state":"KY"}
    {"index":{"_id":"352"}}
    {"account_number":352,"balance":20290,"firstname":"Kendra","lastname":"Mcintosh","age":31,"gender":"F","address":"963 Wolf Place","employer":"Orboid","email":"kendramcintosh@orboid.com","city":"Bladensburg","state":"AK"}
    {"index":{"_id":"357"}}
    {"account_number":357,"balance":15102,"firstname":"Adele","lastname":"Carroll","age":39,"gender":"F","address":"381 Arion Place","employer":"Aquafire","email":"adelecarroll@aquafire.com","city":"Springville","state":"RI"}
    {"index":{"_id":"364"}}
    {"account_number":364,"balance":35247,"firstname":"Felicia","lastname":"Merrill","age":40,"gender":"F","address":"229 Branton Street","employer":"Prosely","email":"feliciamerrill@prosely.com","city":"Dola","state":"MA"}
    {"index":{"_id":"369"}}
    {"account_number":369,"balance":17047,"firstname":"Mcfadden","lastname":"Guy","age":28,"gender":"F","address":"445 Lott Avenue","employer":"Kangle","email":"mcfaddenguy@kangle.com","city":"Greenbackville","state":"DE"}
    {"index":{"_id":"371"}}
    {"account_number":371,"balance":19751,"firstname":"Barker","lastname":"Allen","age":32,"gender":"F","address":"295 Wallabout Street","employer":"Nexgene","email":"barkerallen@nexgene.com","city":"Nanafalia","state":"NE"}
    {"index":{"_id":"376"}}
    {"account_number":376,"balance":44407,"firstname":"Mcmillan","lastname":"Dunn","age":21,"gender":"F","address":"771 Dorchester Road","employer":"Eargo","email":"mcmillandunn@eargo.com","city":"Yogaville","state":"RI"}
    {"index":{"_id":"383"}}
    {"account_number":383,"balance":48889,"firstname":"Knox","lastname":"Larson","age":28,"gender":"F","address":"962 Bartlett Place","employer":"Bostonic","email":"knoxlarson@bostonic.com","city":"Smeltertown","state":"TX"}
    {"index":{"_id":"388"}}
    {"account_number":388,"balance":9606,"firstname":"Julianne","lastname":"Nicholson","age":26,"gender":"F","address":"338 Crescent Street","employer":"Viasia","email":"juliannenicholson@viasia.com","city":"Alleghenyville","state":"MO"}
    {"index":{"_id":"390"}}
    {"account_number":390,"balance":7464,"firstname":"Ramona","lastname":"Roy","age":32,"gender":"M","address":"135 Banner Avenue","employer":"Deminimum","email":"ramonaroy@deminimum.com","city":"Dodge","state":"ID"}
    {"index":{"_id":"395"}}
    {"account_number":395,"balance":18679,"firstname":"Juliet","lastname":"Whitaker","age":31,"gender":"M","address":"128 Remsen Avenue","employer":"Toyletry","email":"julietwhitaker@toyletry.com","city":"Yonah","state":"LA"}
    {"index":{"_id":"403"}}
    {"account_number":403,"balance":18833,"firstname":"Williamson","lastname":"Horn","age":32,"gender":"M","address":"223 Strickland Avenue","employer":"Nimon","email":"williamsonhorn@nimon.com","city":"Bawcomville","state":"NJ"}
    {"index":{"_id":"408"}}
    {"account_number":408,"balance":34666,"firstname":"Lidia","lastname":"Guerrero","age":30,"gender":"M","address":"254 Stratford Road","employer":"Snowpoke","email":"lidiaguerrero@snowpoke.com","city":"Fairlee","state":"LA"}
    {"index":{"_id":"410"}}
    {"account_number":410,"balance":31200,"firstname":"Fox","lastname":"Cardenas","age":39,"gender":"M","address":"987 Monitor Street","employer":"Corpulse","email":"foxcardenas@corpulse.com","city":"Southview","state":"NE"}
    {"index":{"_id":"415"}}
    {"account_number":415,"balance":19449,"firstname":"Martinez","lastname":"Benson","age":36,"gender":"M","address":"172 Berkeley Place","employer":"Enersol","email":"martinezbenson@enersol.com","city":"Chumuckla","state":"AL"}
    {"index":{"_id":"422"}}
    {"account_number":422,"balance":40162,"firstname":"Brigitte","lastname":"Scott","age":26,"gender":"M","address":"662 Vermont Court","employer":"Waretel","email":"brigittescott@waretel.com","city":"Elrama","state":"VA"}
    {"index":{"_id":"427"}}
    {"account_number":427,"balance":1463,"firstname":"Rebekah","lastname":"Garrison","age":36,"gender":"F","address":"837 Hampton Avenue","employer":"Niquent","email":"rebekahgarrison@niquent.com","city":"Zarephath","state":"NY"}
    {"index":{"_id":"434"}}
    {"account_number":434,"balance":11329,"firstname":"Christa","lastname":"Huff","age":25,"gender":"M","address":"454 Oriental Boulevard","employer":"Earthpure","email":"christahuff@earthpure.com","city":"Stevens","state":"DC"}
    {"index":{"_id":"439"}}
    {"account_number":439,"balance":22752,"firstname":"Lula","lastname":"Williams","age":35,"gender":"M","address":"630 Furman Avenue","employer":"Vinch","email":"lulawilliams@vinch.com","city":"Newcastle","state":"ME"}
    {"index":{"_id":"441"}}
    {"account_number":441,"balance":47947,"firstname":"Dickson","lastname":"Mcgee","age":29,"gender":"M","address":"478 Knight Court","employer":"Gogol","email":"dicksonmcgee@gogol.com","city":"Laurelton","state":"AR"}
    {"index":{"_id":"446"}}
    {"account_number":446,"balance":23071,"firstname":"Lolita","lastname":"Fleming","age":32,"gender":"F","address":"918 Bridge Street","employer":"Vidto","email":"lolitafleming@vidto.com","city":"Brownlee","state":"HI"}
    {"index":{"_id":"453"}}
    {"account_number":453,"balance":21520,"firstname":"Hood","lastname":"Powell","age":24,"gender":"F","address":"479 Brevoort Place","employer":"Vortexaco","email":"hoodpowell@vortexaco.com","city":"Alderpoint","state":"CT"}
    {"index":{"_id":"458"}}
    {"account_number":458,"balance":8865,"firstname":"Aida","lastname":"Wolf","age":21,"gender":"F","address":"403 Thames Street","employer":"Isis","email":"aidawolf@isis.com","city":"Bordelonville","state":"ME"}
    {"index":{"_id":"460"}}
    {"account_number":460,"balance":37734,"firstname":"Aguirre","lastname":"White","age":21,"gender":"F","address":"190 Crooke Avenue","employer":"Unq","email":"aguirrewhite@unq.com","city":"Albany","state":"NJ"}
    {"index":{"_id":"465"}}
    {"account_number":465,"balance":10681,"firstname":"Pearlie","lastname":"Holman","age":29,"gender":"M","address":"916 Evergreen Avenue","employer":"Hometown","email":"pearlieholman@hometown.com","city":"Needmore","state":"UT"}
    {"index":{"_id":"472"}}
    {"account_number":472,"balance":25571,"firstname":"Lee","lastname":"Long","age":32,"gender":"F","address":"288 Mill Street","employer":"Comverges","email":"leelong@comverges.com","city":"Movico","state":"MT"}
    {"index":{"_id":"477"}}
    {"account_number":477,"balance":25892,"firstname":"Holcomb","lastname":"Cobb","age":40,"gender":"M","address":"369 Marconi Place","employer":"Steeltab","email":"holcombcobb@steeltab.com","city":"Byrnedale","state":"CA"}
    {"index":{"_id":"484"}}
    {"account_number":484,"balance":3274,"firstname":"Staci","lastname":"Melendez","age":35,"gender":"F","address":"751 Otsego Street","employer":"Namebox","email":"stacimelendez@namebox.com","city":"Harborton","state":"NV"}
    {"index":{"_id":"489"}}
    {"account_number":489,"balance":7879,"firstname":"Garrett","lastname":"Langley","age":36,"gender":"M","address":"331 Bowne Street","employer":"Zillidium","email":"garrettlangley@zillidium.com","city":"Riviera","state":"LA"}
    {"index":{"_id":"491"}}
    {"account_number":491,"balance":42942,"firstname":"Teresa","lastname":"Owen","age":24,"gender":"F","address":"713 Canton Court","employer":"Plasmos","email":"teresaowen@plasmos.com","city":"Bartonsville","state":"NH"}
    {"index":{"_id":"496"}}
    {"account_number":496,"balance":14869,"firstname":"Alison","lastname":"Conrad","age":35,"gender":"F","address":"347 Varet Street","employer":"Perkle","email":"alisonconrad@perkle.com","city":"Cliffside","state":"OH"}
    {"index":{"_id":"504"}}
    {"account_number":504,"balance":49205,"firstname":"Shanna","lastname":"Chambers","age":23,"gender":"M","address":"220 Beard Street","employer":"Corporana","email":"shannachambers@corporana.com","city":"Cashtown","state":"AZ"}
    {"index":{"_id":"509"}}
    {"account_number":509,"balance":34754,"firstname":"Durham","lastname":"Pacheco","age":40,"gender":"M","address":"129 Plymouth Street","employer":"Datacator","email":"durhampacheco@datacator.com","city":"Loveland","state":"NC"}
    {"index":{"_id":"511"}}
    {"account_number":511,"balance":40908,"firstname":"Elba","lastname":"Grant","age":24,"gender":"F","address":"157 Bijou Avenue","employer":"Dognost","email":"elbagrant@dognost.com","city":"Coyote","state":"MT"}
    {"index":{"_id":"516"}}
    {"account_number":516,"balance":44940,"firstname":"Roy","lastname":"Smith","age":37,"gender":"M","address":"770 Cherry Street","employer":"Parleynet","email":"roysmith@parleynet.com","city":"Carrsville","state":"RI"}
    {"index":{"_id":"523"}}
    {"account_number":523,"balance":28729,"firstname":"Amalia","lastname":"Benjamin","age":40,"gender":"F","address":"173 Bushwick Place","employer":"Sentia","email":"amaliabenjamin@sentia.com","city":"Jacumba","state":"OK"}
    {"index":{"_id":"528"}}
    {"account_number":528,"balance":4071,"firstname":"Thompson","lastname":"Hoover","age":27,"gender":"F","address":"580 Garden Street","employer":"Portalis","email":"thompsonhoover@portalis.com","city":"Knowlton","state":"AL"}
    {"index":{"_id":"530"}}
    {"account_number":530,"balance":8840,"firstname":"Kathrine","lastname":"Evans","age":37,"gender":"M","address":"422 Division Place","employer":"Spherix","email":"kathrineevans@spherix.com","city":"Biddle","state":"CO"}
    {"index":{"_id":"535"}}
    {"account_number":535,"balance":8715,"firstname":"Fry","lastname":"George","age":34,"gender":"M","address":"722 Green Street","employer":"Ewaves","email":"frygeorge@ewaves.com","city":"Kenmar","state":"DE"}
    {"index":{"_id":"542"}}
    {"account_number":542,"balance":23285,"firstname":"Michelle","lastname":"Mayo","age":35,"gender":"M","address":"657 Caton Place","employer":"Biflex","email":"michellemayo@biflex.com","city":"Beaverdale","state":"WY"}
    {"index":{"_id":"547"}}
    {"account_number":547,"balance":12870,"firstname":"Eaton","lastname":"Rios","age":32,"gender":"M","address":"744 Withers Street","employer":"Podunk","email":"eatonrios@podunk.com","city":"Chelsea","state":"IA"}
    {"index":{"_id":"554"}}
    {"account_number":554,"balance":33163,"firstname":"Townsend","lastname":"Atkins","age":39,"gender":"M","address":"566 Ira Court","employer":"Acruex","email":"townsendatkins@acruex.com","city":"Valle","state":"IA"}
    {"index":{"_id":"559"}}
    {"account_number":559,"balance":11450,"firstname":"Tonia","lastname":"Schmidt","age":38,"gender":"F","address":"508 Sheffield Avenue","employer":"Extro","email":"toniaschmidt@extro.com","city":"Newry","state":"CT"}
    {"index":{"_id":"561"}}
    {"account_number":561,"balance":12370,"firstname":"Sellers","lastname":"Davis","age":30,"gender":"M","address":"860 Madoc Avenue","employer":"Isodrive","email":"sellersdavis@isodrive.com","city":"Trail","state":"KS"}
    {"index":{"_id":"566"}}
    {"account_number":566,"balance":6183,"firstname":"Cox","lastname":"Roman","age":37,"gender":"M","address":"349 Winthrop Street","employer":"Medcom","email":"coxroman@medcom.com","city":"Rosewood","state":"WY"}
    {"index":{"_id":"573"}}
    {"account_number":573,"balance":32171,"firstname":"Callie","lastname":"Castaneda","age":36,"gender":"M","address":"799 Scott Avenue","employer":"Earthwax","email":"calliecastaneda@earthwax.com","city":"Marshall","state":"NH"}
    {"index":{"_id":"578"}}
    {"account_number":578,"balance":34259,"firstname":"Holmes","lastname":"Mcknight","age":37,"gender":"M","address":"969 Metropolitan Avenue","employer":"Cubicide","email":"holmesmcknight@cubicide.com","city":"Aguila","state":"PA"}
    {"index":{"_id":"580"}}
    {"account_number":580,"balance":13716,"firstname":"Mcmahon","lastname":"York","age":34,"gender":"M","address":"475 Beacon Court","employer":"Zillar","email":"mcmahonyork@zillar.com","city":"Farmington","state":"MO"}
    {"index":{"_id":"585"}}
    {"account_number":585,"balance":26745,"firstname":"Nieves","lastname":"Nolan","age":32,"gender":"M","address":"115 Seagate Terrace","employer":"Jumpstack","email":"nievesnolan@jumpstack.com","city":"Eastmont","state":"UT"}
    {"index":{"_id":"592"}}
    {"account_number":592,"balance":32968,"firstname":"Head","lastname":"Webster","age":36,"gender":"F","address":"987 Lefferts Avenue","employer":"Empirica","email":"headwebster@empirica.com","city":"Rockingham","state":"TN"}
    {"index":{"_id":"597"}}
    {"account_number":597,"balance":11246,"firstname":"Penny","lastname":"Knowles","age":33,"gender":"M","address":"139 Forbell Street","employer":"Ersum","email":"pennyknowles@ersum.com","city":"Vallonia","state":"IA"}
    {"index":{"_id":"600"}}
    {"account_number":600,"balance":10336,"firstname":"Simmons","lastname":"Byers","age":37,"gender":"M","address":"250 Dictum Court","employer":"Qualitex","email":"simmonsbyers@qualitex.com","city":"Wanship","state":"OH"}
    {"index":{"_id":"605"}}
    {"account_number":605,"balance":38427,"firstname":"Mcclain","lastname":"Manning","age":24,"gender":"M","address":"832 Leonard Street","employer":"Qiao","email":"mcclainmanning@qiao.com","city":"Calvary","state":"TX"}
    {"index":{"_id":"612"}}
    {"account_number":612,"balance":11868,"firstname":"Dunn","lastname":"Cameron","age":32,"gender":"F","address":"156 Lorimer Street","employer":"Isonus","email":"dunncameron@isonus.com","city":"Virgie","state":"ND"}
    {"index":{"_id":"617"}}
    {"account_number":617,"balance":35445,"firstname":"Kitty","lastname":"Cooley","age":22,"gender":"M","address":"788 Seagate Avenue","employer":"Ultrimax","email":"kittycooley@ultrimax.com","city":"Clarktown","state":"MD"}
    {"index":{"_id":"624"}}
    {"account_number":624,"balance":27538,"firstname":"Roxanne","lastname":"Franklin","age":39,"gender":"F","address":"299 Woodrow Court","employer":"Silodyne","email":"roxannefranklin@silodyne.com","city":"Roulette","state":"VA"}
    {"index":{"_id":"629"}}
    {"account_number":629,"balance":32987,"firstname":"Mcclure","lastname":"Rodgers","age":26,"gender":"M","address":"806 Pierrepont Place","employer":"Elita","email":"mcclurerodgers@elita.com","city":"Brownsville","state":"MI"}
    {"index":{"_id":"631"}}
    {"account_number":631,"balance":21657,"firstname":"Corrine","lastname":"Barber","age":32,"gender":"F","address":"447 Hunts Lane","employer":"Quarmony","email":"corrinebarber@quarmony.com","city":"Wyano","state":"IL"}
    {"index":{"_id":"636"}}
    {"account_number":636,"balance":8036,"firstname":"Agnes","lastname":"Hooper","age":25,"gender":"M","address":"865 Hanson Place","employer":"Digial","email":"agneshooper@digial.com","city":"Sperryville","state":"OK"}
    {"index":{"_id":"643"}}
    {"account_number":643,"balance":8057,"firstname":"Hendricks","lastname":"Stokes","age":23,"gender":"F","address":"142 Barbey Street","employer":"Remotion","email":"hendricksstokes@remotion.com","city":"Lewis","state":"MA"}
    {"index":{"_id":"648"}}
    {"account_number":648,"balance":11506,"firstname":"Terry","lastname":"Montgomery","age":21,"gender":"F","address":"115 Franklin Avenue","employer":"Enervate","email":"terrymontgomery@enervate.com","city":"Bascom","state":"MA"}
    {"index":{"_id":"650"}}
    {"account_number":650,"balance":18091,"firstname":"Benton","lastname":"Knight","age":28,"gender":"F","address":"850 Aitken Place","employer":"Pholio","email":"bentonknight@pholio.com","city":"Cobbtown","state":"AL"}
    {"index":{"_id":"655"}}
    {"account_number":655,"balance":22912,"firstname":"Eula","lastname":"Taylor","age":30,"gender":"M","address":"520 Orient Avenue","employer":"Miracula","email":"eulataylor@miracula.com","city":"Wacissa","state":"IN"}
    {"index":{"_id":"662"}}
    {"account_number":662,"balance":10138,"firstname":"Daisy","lastname":"Burnett","age":33,"gender":"M","address":"114 Norman Avenue","employer":"Liquicom","email":"daisyburnett@liquicom.com","city":"Grahamtown","state":"MD"}
    {"index":{"_id":"667"}}
    {"account_number":667,"balance":22559,"firstname":"Juliana","lastname":"Chase","age":32,"gender":"M","address":"496 Coleridge Street","employer":"Comtract","email":"julianachase@comtract.com","city":"Wilsonia","state":"NJ"}
    {"index":{"_id":"674"}}
    {"account_number":674,"balance":36038,"firstname":"Watts","lastname":"Shannon","age":22,"gender":"F","address":"600 Story Street","employer":"Joviold","email":"wattsshannon@joviold.com","city":"Fairhaven","state":"ID"}
    {"index":{"_id":"679"}}
    {"account_number":679,"balance":20149,"firstname":"Henrietta","lastname":"Bonner","age":33,"gender":"M","address":"461 Bond Street","employer":"Geekol","email":"henriettabonner@geekol.com","city":"Richville","state":"WA"}
    {"index":{"_id":"681"}}
    {"account_number":681,"balance":34244,"firstname":"Velazquez","lastname":"Wolfe","age":33,"gender":"M","address":"773 Eckford Street","employer":"Zisis","email":"velazquezwolfe@zisis.com","city":"Smock","state":"ME"}
    {"index":{"_id":"686"}}
    {"account_number":686,"balance":10116,"firstname":"Decker","lastname":"Mcclure","age":30,"gender":"F","address":"236 Commerce Street","employer":"Everest","email":"deckermcclure@everest.com","city":"Gibbsville","state":"TN"}
    {"index":{"_id":"693"}}
    {"account_number":693,"balance":31233,"firstname":"Tabatha","lastname":"Zimmerman","age":30,"gender":"F","address":"284 Emmons Avenue","employer":"Pushcart","email":"tabathazimmerman@pushcart.com","city":"Esmont","state":"NC"}
    {"index":{"_id":"698"}}
    {"account_number":698,"balance":14965,"firstname":"Baker","lastname":"Armstrong","age":36,"gender":"F","address":"796 Tehama Street","employer":"Nurplex","email":"bakerarmstrong@nurplex.com","city":"Starks","state":"UT"}
    {"index":{"_id":"701"}}
    {"account_number":701,"balance":23772,"firstname":"Gardner","lastname":"Griffith","age":27,"gender":"M","address":"187 Moore Place","employer":"Vertide","email":"gardnergriffith@vertide.com","city":"Coventry","state":"NV"}
    {"index":{"_id":"706"}}
    {"account_number":706,"balance":5282,"firstname":"Eliza","lastname":"Potter","age":39,"gender":"M","address":"945 Dunham Place","employer":"Playce","email":"elizapotter@playce.com","city":"Woodruff","state":"AK"}
    {"index":{"_id":"713"}}
    {"account_number":713,"balance":20054,"firstname":"Iris","lastname":"Mcguire","age":21,"gender":"F","address":"508 Benson Avenue","employer":"Duflex","email":"irismcguire@duflex.com","city":"Hillsboro","state":"MO"}
    {"index":{"_id":"718"}}
    {"account_number":718,"balance":13876,"firstname":"Hickman","lastname":"Dillard","age":22,"gender":"F","address":"132 Etna Street","employer":"Genmy","email":"hickmandillard@genmy.com","city":"Curtice","state":"NV"}
    {"index":{"_id":"720"}}
    {"account_number":720,"balance":31356,"firstname":"Ruth","lastname":"Vance","age":32,"gender":"F","address":"229 Adams Street","employer":"Zilidium","email":"ruthvance@zilidium.com","city":"Allison","state":"IA"}
    {"index":{"_id":"725"}}
    {"account_number":725,"balance":14677,"firstname":"Reeves","lastname":"Tillman","age":26,"gender":"M","address":"674 Ivan Court","employer":"Cemention","email":"reevestillman@cemention.com","city":"Navarre","state":"MA"}
    {"index":{"_id":"732"}}
    {"account_number":732,"balance":38445,"firstname":"Delia","lastname":"Cruz","age":37,"gender":"F","address":"870 Cheever Place","employer":"Multron","email":"deliacruz@multron.com","city":"Cresaptown","state":"NH"}
    {"index":{"_id":"737"}}
    {"account_number":737,"balance":40431,"firstname":"Sampson","lastname":"Yates","age":23,"gender":"F","address":"214 Cox Place","employer":"Signidyne","email":"sampsonyates@signidyne.com","city":"Brazos","state":"GA"}
    {"index":{"_id":"744"}}
    {"account_number":744,"balance":8690,"firstname":"Bernard","lastname":"Martinez","age":21,"gender":"M","address":"148 Dunne Place","employer":"Dragbot","email":"bernardmartinez@dragbot.com","city":"Moraida","state":"MN"}
    {"index":{"_id":"749"}}
    {"account_number":749,"balance":1249,"firstname":"Rush","lastname":"Boyle","age":36,"gender":"M","address":"310 Argyle Road","employer":"Sportan","email":"rushboyle@sportan.com","city":"Brady","state":"WA"}
    {"index":{"_id":"751"}}
    {"account_number":751,"balance":49252,"firstname":"Patrick","lastname":"Osborne","age":23,"gender":"M","address":"915 Prospect Avenue","employer":"Gynko","email":"patrickosborne@gynko.com","city":"Takilma","state":"MO"}
    {"index":{"_id":"756"}}
    {"account_number":756,"balance":40006,"firstname":"Jasmine","lastname":"Howell","age":32,"gender":"M","address":"605 Elliott Walk","employer":"Ecratic","email":"jasminehowell@ecratic.com","city":"Harrodsburg","state":"OH"}
    {"index":{"_id":"763"}}
    {"account_number":763,"balance":12091,"firstname":"Liz","lastname":"Bentley","age":22,"gender":"F","address":"933 Debevoise Avenue","employer":"Nipaz","email":"lizbentley@nipaz.com","city":"Glenville","state":"NJ"}
    {"index":{"_id":"768"}}
    {"account_number":768,"balance":2213,"firstname":"Sondra","lastname":"Soto","age":21,"gender":"M","address":"625 Colonial Road","employer":"Navir","email":"sondrasoto@navir.com","city":"Benson","state":"VA"}
    {"index":{"_id":"770"}}
    {"account_number":770,"balance":39505,"firstname":"Joann","lastname":"Crane","age":26,"gender":"M","address":"798 Farragut Place","employer":"Lingoage","email":"joanncrane@lingoage.com","city":"Kirk","state":"MA"}
    {"index":{"_id":"775"}}
    {"account_number":775,"balance":27943,"firstname":"Wilson","lastname":"Merritt","age":33,"gender":"F","address":"288 Thornton Street","employer":"Geeky","email":"wilsonmerritt@geeky.com","city":"Holtville","state":"HI"}
    {"index":{"_id":"782"}}
    {"account_number":782,"balance":3960,"firstname":"Maldonado","lastname":"Craig","age":36,"gender":"F","address":"345 Myrtle Avenue","employer":"Zilencio","email":"maldonadocraig@zilencio.com","city":"Yukon","state":"ID"}
    {"index":{"_id":"787"}}
    {"account_number":787,"balance":11876,"firstname":"Harper","lastname":"Wynn","age":21,"gender":"F","address":"139 Oceanic Avenue","employer":"Interfind","email":"harperwynn@interfind.com","city":"Gerber","state":"ND"}
    {"index":{"_id":"794"}}
    {"account_number":794,"balance":16491,"firstname":"Walker","lastname":"Charles","age":32,"gender":"M","address":"215 Kenilworth Place","employer":"Orbin","email":"walkercharles@orbin.com","city":"Rivers","state":"WI"}
    {"index":{"_id":"799"}}
    {"account_number":799,"balance":2889,"firstname":"Myra","lastname":"Guerra","age":28,"gender":"F","address":"625 Dahlgreen Place","employer":"Digigene","email":"myraguerra@digigene.com","city":"Draper","state":"CA"}
    {"index":{"_id":"802"}}
    {"account_number":802,"balance":19630,"firstname":"Gracie","lastname":"Foreman","age":40,"gender":"F","address":"219 Kent Avenue","employer":"Supportal","email":"gracieforeman@supportal.com","city":"Westboro","state":"NH"}
    {"index":{"_id":"807"}}
    {"account_number":807,"balance":29206,"firstname":"Hatfield","lastname":"Lowe","age":23,"gender":"M","address":"499 Adler Place","employer":"Lovepad","email":"hatfieldlowe@lovepad.com","city":"Wiscon","state":"DC"}
    {"index":{"_id":"814"}}
    {"account_number":814,"balance":9838,"firstname":"Morse","lastname":"Mcbride","age":26,"gender":"F","address":"776 Calyer Street","employer":"Inear","email":"morsemcbride@inear.com","city":"Kingstowne","state":"ND"}
    {"index":{"_id":"819"}}
    {"account_number":819,"balance":3971,"firstname":"Karyn","lastname":"Medina","age":24,"gender":"F","address":"417 Utica Avenue","employer":"Qnekt","email":"karynmedina@qnekt.com","city":"Kerby","state":"WY"}
    {"index":{"_id":"821"}}
    {"account_number":821,"balance":33271,"firstname":"Trisha","lastname":"Blankenship","age":22,"gender":"M","address":"329 Jamaica Avenue","employer":"Chorizon","email":"trishablankenship@chorizon.com","city":"Sexton","state":"VT"}
    {"index":{"_id":"826"}}
    {"account_number":826,"balance":11548,"firstname":"Summers","lastname":"Vinson","age":22,"gender":"F","address":"742 Irwin Street","employer":"Globoil","email":"summersvinson@globoil.com","city":"Callaghan","state":"WY"}
    {"index":{"_id":"833"}}
    {"account_number":833,"balance":46154,"firstname":"Woodward","lastname":"Hood","age":22,"gender":"M","address":"398 Atkins Avenue","employer":"Zedalis","email":"woodwardhood@zedalis.com","city":"Stonybrook","state":"NE"}
    {"index":{"_id":"838"}}
    {"account_number":838,"balance":24629,"firstname":"Latonya","lastname":"Blake","age":37,"gender":"F","address":"531 Milton Street","employer":"Rugstars","email":"latonyablake@rugstars.com","city":"Tedrow","state":"WA"}
    {"index":{"_id":"840"}}
    {"account_number":840,"balance":39615,"firstname":"Boone","lastname":"Gomez","age":38,"gender":"M","address":"256 Hampton Place","employer":"Geekular","email":"boonegomez@geekular.com","city":"Westerville","state":"HI"}
    {"index":{"_id":"845"}}
    {"account_number":845,"balance":35422,"firstname":"Tracy","lastname":"Vaughn","age":39,"gender":"M","address":"645 Rockaway Parkway","employer":"Andryx","email":"tracyvaughn@andryx.com","city":"Wilmington","state":"ME"}
    {"index":{"_id":"852"}}
    {"account_number":852,"balance":6041,"firstname":"Allen","lastname":"Hammond","age":26,"gender":"M","address":"793 Essex Street","employer":"Tersanki","email":"allenhammond@tersanki.com","city":"Osmond","state":"NC"}
    {"index":{"_id":"857"}}
    {"account_number":857,"balance":39678,"firstname":"Alyce","lastname":"Douglas","age":23,"gender":"M","address":"326 Robert Street","employer":"Earbang","email":"alycedouglas@earbang.com","city":"Thornport","state":"GA"}
    {"index":{"_id":"864"}}
    {"account_number":864,"balance":21804,"firstname":"Duffy","lastname":"Anthony","age":23,"gender":"M","address":"582 Cooke Court","employer":"Schoolio","email":"duffyanthony@schoolio.com","city":"Brenton","state":"CO"}
    {"index":{"_id":"869"}}
    {"account_number":869,"balance":43544,"firstname":"Corinne","lastname":"Robbins","age":25,"gender":"F","address":"732 Quentin Road","employer":"Orbaxter","email":"corinnerobbins@orbaxter.com","city":"Roy","state":"TN"}
    {"index":{"_id":"871"}}
    {"account_number":871,"balance":35854,"firstname":"Norma","lastname":"Burt","age":32,"gender":"M","address":"934 Cyrus Avenue","employer":"Magnafone","email":"normaburt@magnafone.com","city":"Eden","state":"TN"}
    {"index":{"_id":"876"}}
    {"account_number":876,"balance":48568,"firstname":"Brady","lastname":"Glover","age":21,"gender":"F","address":"565 Oceanview Avenue","employer":"Comvex","email":"bradyglover@comvex.com","city":"Noblestown","state":"ID"}
    {"index":{"_id":"883"}}
    {"account_number":883,"balance":33679,"firstname":"Austin","lastname":"Jefferson","age":34,"gender":"M","address":"846 Lincoln Avenue","employer":"Polarax","email":"austinjefferson@polarax.com","city":"Savannah","state":"CT"}
    {"index":{"_id":"888"}}
    {"account_number":888,"balance":22277,"firstname":"Myrna","lastname":"Herman","age":39,"gender":"F","address":"649 Harwood Place","employer":"Enthaze","email":"myrnaherman@enthaze.com","city":"Idamay","state":"AR"}
    {"index":{"_id":"890"}}
    {"account_number":890,"balance":31198,"firstname":"Alvarado","lastname":"Pate","age":25,"gender":"M","address":"269 Ashland Place","employer":"Ovolo","email":"alvaradopate@ovolo.com","city":"Volta","state":"MI"}
    {"index":{"_id":"895"}}
    {"account_number":895,"balance":7327,"firstname":"Lara","lastname":"Mcdaniel","age":36,"gender":"M","address":"854 Willow Place","employer":"Acusage","email":"laramcdaniel@acusage.com","city":"Imperial","state":"NC"}
    {"index":{"_id":"903"}}
    {"account_number":903,"balance":10238,"firstname":"Wade","lastname":"Page","age":35,"gender":"F","address":"685 Waldorf Court","employer":"Eplosion","email":"wadepage@eplosion.com","city":"Welda","state":"AL"}
    {"index":{"_id":"908"}}
    {"account_number":908,"balance":45975,"firstname":"Mosley","lastname":"Holloway","age":31,"gender":"M","address":"929 Eldert Lane","employer":"Anivet","email":"mosleyholloway@anivet.com","city":"Biehle","state":"MS"}
    {"index":{"_id":"910"}}
    {"account_number":910,"balance":36831,"firstname":"Esmeralda","lastname":"James","age":23,"gender":"F","address":"535 High Street","employer":"Terrasys","email":"esmeraldajames@terrasys.com","city":"Dubois","state":"IN"}
    {"index":{"_id":"915"}}
    {"account_number":915,"balance":19816,"firstname":"Farrell","lastname":"French","age":35,"gender":"F","address":"126 McKibbin Street","employer":"Techmania","email":"farrellfrench@techmania.com","city":"Wescosville","state":"AL"}
    {"index":{"_id":"922"}}
    {"account_number":922,"balance":39347,"firstname":"Irwin","lastname":"Pugh","age":32,"gender":"M","address":"463 Shale Street","employer":"Idego","email":"irwinpugh@idego.com","city":"Ivanhoe","state":"ID"}
    {"index":{"_id":"927"}}
    {"account_number":927,"balance":19976,"firstname":"Jeanette","lastname":"Acevedo","age":26,"gender":"M","address":"694 Polhemus Place","employer":"Halap","email":"jeanetteacevedo@halap.com","city":"Harrison","state":"MO"}
    {"index":{"_id":"934"}}
    {"account_number":934,"balance":43987,"firstname":"Freida","lastname":"Daniels","age":34,"gender":"M","address":"448 Cove Lane","employer":"Vurbo","email":"freidadaniels@vurbo.com","city":"Snelling","state":"NJ"}
    {"index":{"_id":"939"}}
    {"account_number":939,"balance":31228,"firstname":"Hodges","lastname":"Massey","age":37,"gender":"F","address":"431 Dahl Court","employer":"Kegular","email":"hodgesmassey@kegular.com","city":"Katonah","state":"MD"}
    {"index":{"_id":"941"}}
    {"account_number":941,"balance":38796,"firstname":"Kim","lastname":"Moss","age":28,"gender":"F","address":"105 Onderdonk Avenue","employer":"Digirang","email":"kimmoss@digirang.com","city":"Centerville","state":"TX"}
    {"index":{"_id":"946"}}
    {"account_number":946,"balance":42794,"firstname":"Ina","lastname":"Obrien","age":36,"gender":"M","address":"339 Rewe Street","employer":"Eclipsent","email":"inaobrien@eclipsent.com","city":"Soham","state":"RI"}
    {"index":{"_id":"953"}}
    {"account_number":953,"balance":1110,"firstname":"Baxter","lastname":"Black","age":27,"gender":"M","address":"720 Stillwell Avenue","employer":"Uplinx","email":"baxterblack@uplinx.com","city":"Drummond","state":"MN"}
    {"index":{"_id":"958"}}
    {"account_number":958,"balance":32849,"firstname":"Brown","lastname":"Wilkins","age":40,"gender":"M","address":"686 Delmonico Place","employer":"Medesign","email":"brownwilkins@medesign.com","city":"Shelby","state":"WY"}
    {"index":{"_id":"960"}}
    {"account_number":960,"balance":2905,"firstname":"Curry","lastname":"Vargas","age":40,"gender":"M","address":"242 Blake Avenue","employer":"Pearlesex","email":"curryvargas@pearlesex.com","city":"Henrietta","state":"NH"}
    {"index":{"_id":"965"}}
    {"account_number":965,"balance":21882,"firstname":"Patrica","lastname":"Melton","age":28,"gender":"M","address":"141 Rodney Street","employer":"Flexigen","email":"patricamelton@flexigen.com","city":"Klagetoh","state":"MD"}
    {"index":{"_id":"972"}}
    {"account_number":972,"balance":24719,"firstname":"Leona","lastname":"Christian","age":26,"gender":"F","address":"900 Woodpoint Road","employer":"Extrawear","email":"leonachristian@extrawear.com","city":"Roderfield","state":"MA"}
    {"index":{"_id":"977"}}
    {"account_number":977,"balance":6744,"firstname":"Rodgers","lastname":"Mccray","age":21,"gender":"F","address":"612 Duryea Place","employer":"Papricut","email":"rodgersmccray@papricut.com","city":"Marenisco","state":"MD"}
    {"index":{"_id":"984"}}
    {"account_number":984,"balance":1904,"firstname":"Viola","lastname":"Crawford","age":35,"gender":"F","address":"354 Linwood Street","employer":"Ginkle","email":"violacrawford@ginkle.com","city":"Witmer","state":"AR"}
    {"index":{"_id":"989"}}
    {"account_number":989,"balance":48622,"firstname":"Franklin","lastname":"Frank","age":38,"gender":"M","address":"270 Carlton Avenue","employer":"Shopabout","email":"franklinfrank@shopabout.com","city":"Guthrie","state":"NC"}
    {"index":{"_id":"991"}}
    {"account_number":991,"balance":4239,"firstname":"Connie","lastname":"Berry","age":28,"gender":"F","address":"647 Gardner Avenue","employer":"Flumbo","email":"connieberry@flumbo.com","city":"Frierson","state":"MO"}
    {"index":{"_id":"996"}}
    {"account_number":996,"balance":17541,"firstname":"Andrews","lastname":"Herrera","age":30,"gender":"F","address":"570 Vandam Street","employer":"Klugger","email":"andrewsherrera@klugger.com","city":"Whitehaven","state":"MN"}
    {"index":{"_id":"0"}}
    {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
    {"index":{"_id":"5"}}
    {"account_number":5,"balance":29342,"firstname":"Leola","lastname":"Stewart","age":30,"gender":"F","address":"311 Elm Place","employer":"Diginetic","email":"leolastewart@diginetic.com","city":"Fairview","state":"NJ"}
    {"index":{"_id":"12"}}
    {"account_number":12,"balance":37055,"firstname":"Stafford","lastname":"Brock","age":20,"gender":"F","address":"296 Wythe Avenue","employer":"Uncorp","email":"staffordbrock@uncorp.com","city":"Bend","state":"AL"}
    {"index":{"_id":"17"}}
    {"account_number":17,"balance":7831,"firstname":"Bessie","lastname":"Orr","age":31,"gender":"F","address":"239 Hinsdale Street","employer":"Skyplex","email":"bessieorr@skyplex.com","city":"Graball","state":"FL"}
    {"index":{"_id":"24"}}
    {"account_number":24,"balance":44182,"firstname":"Wood","lastname":"Dale","age":39,"gender":"M","address":"582 Gelston Avenue","employer":"Besto","email":"wooddale@besto.com","city":"Juntura","state":"MI"}
    {"index":{"_id":"29"}}
    {"account_number":29,"balance":27323,"firstname":"Leah","lastname":"Santiago","age":33,"gender":"M","address":"193 Schenck Avenue","employer":"Isologix","email":"leahsantiago@isologix.com","city":"Gerton","state":"ND"}
    {"index":{"_id":"31"}}
    {"account_number":31,"balance":30443,"firstname":"Kristen","lastname":"Santana","age":22,"gender":"F","address":"130 Middagh Street","employer":"Dogspa","email":"kristensantana@dogspa.com","city":"Vale","state":"MA"}
    {"index":{"_id":"36"}}
    {"account_number":36,"balance":15902,"firstname":"Alexandra","lastname":"Nguyen","age":39,"gender":"F","address":"389 Elizabeth Place","employer":"Bittor","email":"alexandranguyen@bittor.com","city":"Hemlock","state":"KY"}
    {"index":{"_id":"43"}}
    {"account_number":43,"balance":33474,"firstname":"Ryan","lastname":"Howe","age":25,"gender":"M","address":"660 Huntington Street","employer":"Microluxe","email":"ryanhowe@microluxe.com","city":"Clara","state":"CT"}
    {"index":{"_id":"48"}}
    {"account_number":48,"balance":40608,"firstname":"Peck","lastname":"Downs","age":39,"gender":"F","address":"594 Dwight Street","employer":"Ramjob","email":"peckdowns@ramjob.com","city":"Coloma","state":"WA"}
    {"index":{"_id":"50"}}
    {"account_number":50,"balance":43695,"firstname":"Sheena","lastname":"Kirkland","age":33,"gender":"M","address":"598 Bank Street","employer":"Zerbina","email":"sheenakirkland@zerbina.com","city":"Walland","state":"IN"}
    {"index":{"_id":"55"}}
    {"account_number":55,"balance":22020,"firstname":"Shelia","lastname":"Puckett","age":33,"gender":"M","address":"265 Royce Place","employer":"Izzby","email":"sheliapuckett@izzby.com","city":"Slovan","state":"HI"}
    {"index":{"_id":"62"}}
    {"account_number":62,"balance":43065,"firstname":"Lester","lastname":"Stanton","age":37,"gender":"M","address":"969 Doughty Street","employer":"Geekko","email":"lesterstanton@geekko.com","city":"Itmann","state":"DC"}
    {"index":{"_id":"67"}}
    {"account_number":67,"balance":39430,"firstname":"Isabelle","lastname":"Spence","age":39,"gender":"M","address":"718 Troy Avenue","employer":"Geeketron","email":"isabellespence@geeketron.com","city":"Camptown","state":"WA"}
    {"index":{"_id":"74"}}
    {"account_number":74,"balance":47167,"firstname":"Lauri","lastname":"Saunders","age":38,"gender":"F","address":"768 Lynch Street","employer":"Securia","email":"laurisaunders@securia.com","city":"Caroline","state":"TN"}
    {"index":{"_id":"79"}}
    {"account_number":79,"balance":28185,"firstname":"Booker","lastname":"Lowery","age":29,"gender":"M","address":"817 Campus Road","employer":"Sensate","email":"bookerlowery@sensate.com","city":"Carlos","state":"MT"}
    {"index":{"_id":"81"}}
    {"account_number":81,"balance":46568,"firstname":"Dennis","lastname":"Gilbert","age":40,"gender":"M","address":"619 Minna Street","employer":"Melbacor","email":"dennisgilbert@melbacor.com","city":"Kersey","state":"ND"}
    {"index":{"_id":"86"}}
    {"account_number":86,"balance":15428,"firstname":"Walton","lastname":"Butler","age":36,"gender":"M","address":"999 Schenck Street","employer":"Unisure","email":"waltonbutler@unisure.com","city":"Bentonville","state":"IL"}
    {"index":{"_id":"93"}}
    {"account_number":93,"balance":17728,"firstname":"Jeri","lastname":"Booth","age":31,"gender":"M","address":"322 Roosevelt Court","employer":"Geekology","email":"jeribooth@geekology.com","city":"Leming","state":"ND"}
    {"index":{"_id":"98"}}
    {"account_number":98,"balance":15085,"firstname":"Cora","lastname":"Barrett","age":24,"gender":"F","address":"555 Neptune Court","employer":"Kiosk","email":"corabarrett@kiosk.com","city":"Independence","state":"MN"}
    {"index":{"_id":"101"}}
    {"account_number":101,"balance":43400,"firstname":"Cecelia","lastname":"Grimes","age":31,"gender":"M","address":"972 Lincoln Place","employer":"Ecosys","email":"ceceliagrimes@ecosys.com","city":"Manchester","state":"AR"}
    {"index":{"_id":"106"}}
    {"account_number":106,"balance":8212,"firstname":"Josefina","lastname":"Wagner","age":36,"gender":"M","address":"418 Estate Road","employer":"Kyaguru","email":"josefinawagner@kyaguru.com","city":"Darbydale","state":"FL"}
    {"index":{"_id":"113"}}
    {"account_number":113,"balance":41652,"firstname":"Burt","lastname":"Moses","age":27,"gender":"M","address":"633 Berry Street","employer":"Uni","email":"burtmoses@uni.com","city":"Russellville","state":"CT"}
    {"index":{"_id":"118"}}
    {"account_number":118,"balance":2223,"firstname":"Ballard","lastname":"Vasquez","age":33,"gender":"F","address":"101 Bush Street","employer":"Intergeek","email":"ballardvasquez@intergeek.com","city":"Century","state":"MN"}
    {"index":{"_id":"120"}}
    {"account_number":120,"balance":38565,"firstname":"Browning","lastname":"Rodriquez","age":33,"gender":"M","address":"910 Moore Street","employer":"Opportech","email":"browningrodriquez@opportech.com","city":"Cutter","state":"ND"}
    {"index":{"_id":"125"}}
    {"account_number":125,"balance":5396,"firstname":"Tanisha","lastname":"Dixon","age":30,"gender":"M","address":"482 Hancock Street","employer":"Junipoor","email":"tanishadixon@junipoor.com","city":"Wauhillau","state":"IA"}
    {"index":{"_id":"132"}}
    {"account_number":132,"balance":37707,"firstname":"Horton","lastname":"Romero","age":35,"gender":"M","address":"427 Rutherford Place","employer":"Affluex","email":"hortonromero@affluex.com","city":"Hall","state":"AK"}
    {"index":{"_id":"137"}}
    {"account_number":137,"balance":3596,"firstname":"Frost","lastname":"Freeman","age":29,"gender":"F","address":"191 Dennett Place","employer":"Beadzza","email":"frostfreeman@beadzza.com","city":"Sabillasville","state":"HI"}
    {"index":{"_id":"144"}}
    {"account_number":144,"balance":43257,"firstname":"Evans","lastname":"Dyer","age":30,"gender":"F","address":"912 Post Court","employer":"Magmina","email":"evansdyer@magmina.com","city":"Gordon","state":"HI"}
    {"index":{"_id":"149"}}
    {"account_number":149,"balance":22994,"firstname":"Megan","lastname":"Gonzales","age":21,"gender":"M","address":"836 Tampa Court","employer":"Andershun","email":"megangonzales@andershun.com","city":"Rockhill","state":"AL"}
    {"index":{"_id":"151"}}
    {"account_number":151,"balance":34473,"firstname":"Kent","lastname":"Joyner","age":20,"gender":"F","address":"799 Truxton Street","employer":"Kozgene","email":"kentjoyner@kozgene.com","city":"Allamuchy","state":"DC"}
    {"index":{"_id":"156"}}
    {"account_number":156,"balance":40185,"firstname":"Sloan","lastname":"Pennington","age":24,"gender":"F","address":"573 Opal Court","employer":"Hopeli","email":"sloanpennington@hopeli.com","city":"Evergreen","state":"CT"}
    {"index":{"_id":"163"}}
    {"account_number":163,"balance":43075,"firstname":"Wilda","lastname":"Norman","age":33,"gender":"F","address":"173 Beadel Street","employer":"Kog","email":"wildanorman@kog.com","city":"Bodega","state":"ME"}
    {"index":{"_id":"168"}}
    {"account_number":168,"balance":49568,"firstname":"Carissa","lastname":"Simon","age":20,"gender":"M","address":"975 Flatbush Avenue","employer":"Zillacom","email":"carissasimon@zillacom.com","city":"Neibert","state":"IL"}
    {"index":{"_id":"170"}}
    {"account_number":170,"balance":6025,"firstname":"Mann","lastname":"Madden","age":36,"gender":"F","address":"161 Radde Place","employer":"Farmex","email":"mannmadden@farmex.com","city":"Thermal","state":"LA"}
    {"index":{"_id":"175"}}
    {"account_number":175,"balance":16213,"firstname":"Montoya","lastname":"Donaldson","age":28,"gender":"F","address":"481 Morton Street","employer":"Envire","email":"montoyadonaldson@envire.com","city":"Delco","state":"MA"}
    {"index":{"_id":"182"}}
    {"account_number":182,"balance":7803,"firstname":"Manuela","lastname":"Dillon","age":21,"gender":"M","address":"742 Garnet Street","employer":"Moreganic","email":"manueladillon@moreganic.com","city":"Ilchester","state":"TX"}
    {"index":{"_id":"187"}}
    {"account_number":187,"balance":26581,"firstname":"Autumn","lastname":"Hodges","age":35,"gender":"M","address":"757 Granite Street","employer":"Ezentia","email":"autumnhodges@ezentia.com","city":"Martinsville","state":"KY"}
    {"index":{"_id":"194"}}
    {"account_number":194,"balance":16311,"firstname":"Beck","lastname":"Rosario","age":39,"gender":"M","address":"721 Cambridge Place","employer":"Zoid","email":"beckrosario@zoid.com","city":"Efland","state":"ID"}
    {"index":{"_id":"199"}}
    {"account_number":199,"balance":18086,"firstname":"Branch","lastname":"Love","age":26,"gender":"M","address":"458 Commercial Street","employer":"Frolix","email":"branchlove@frolix.com","city":"Caspar","state":"NC"}
    {"index":{"_id":"202"}}
    {"account_number":202,"balance":26466,"firstname":"Medina","lastname":"Brown","age":31,"gender":"F","address":"519 Sunnyside Court","employer":"Bleendot","email":"medinabrown@bleendot.com","city":"Winfred","state":"MI"}
    {"index":{"_id":"207"}}
    {"account_number":207,"balance":45535,"firstname":"Evelyn","lastname":"Lara","age":35,"gender":"F","address":"636 Chestnut Street","employer":"Ultrasure","email":"evelynlara@ultrasure.com","city":"Logan","state":"MI"}
    {"index":{"_id":"214"}}
    {"account_number":214,"balance":24418,"firstname":"Luann","lastname":"Faulkner","age":37,"gender":"F","address":"697 Hazel Court","employer":"Zolar","email":"luannfaulkner@zolar.com","city":"Ticonderoga","state":"TX"}
    {"index":{"_id":"219"}}
    {"account_number":219,"balance":17127,"firstname":"Edwards","lastname":"Hurley","age":25,"gender":"M","address":"834 Stockholm Street","employer":"Austech","email":"edwardshurley@austech.com","city":"Bayview","state":"NV"}
    {"index":{"_id":"221"}}
    {"account_number":221,"balance":15803,"firstname":"Benjamin","lastname":"Barrera","age":34,"gender":"M","address":"568 Main Street","employer":"Zaphire","email":"benjaminbarrera@zaphire.com","city":"Germanton","state":"WY"}
    {"index":{"_id":"226"}}
    {"account_number":226,"balance":37720,"firstname":"Wilkins","lastname":"Brady","age":40,"gender":"F","address":"486 Baltic Street","employer":"Dogtown","email":"wilkinsbrady@dogtown.com","city":"Condon","state":"MT"}
    {"index":{"_id":"233"}}
    {"account_number":233,"balance":23020,"firstname":"Washington","lastname":"Walsh","age":27,"gender":"M","address":"366 Church Avenue","employer":"Candecor","email":"washingtonwalsh@candecor.com","city":"Westphalia","state":"MA"}
    {"index":{"_id":"238"}}
    {"account_number":238,"balance":21287,"firstname":"Constance","lastname":"Wong","age":28,"gender":"M","address":"496 Brown Street","employer":"Grainspot","email":"constancewong@grainspot.com","city":"Cecilia","state":"IN"}
    {"index":{"_id":"240"}}
    {"account_number":240,"balance":49741,"firstname":"Oconnor","lastname":"Clay","age":35,"gender":"F","address":"659 Highland Boulevard","employer":"Franscene","email":"oconnorclay@franscene.com","city":"Kilbourne","state":"NH"}
    {"index":{"_id":"245"}}
    {"account_number":245,"balance":22026,"firstname":"Fran","lastname":"Bolton","age":28,"gender":"F","address":"147 Jerome Street","employer":"Solaren","email":"franbolton@solaren.com","city":"Nash","state":"RI"}
    {"index":{"_id":"252"}}
    {"account_number":252,"balance":18831,"firstname":"Elvia","lastname":"Poole","age":22,"gender":"F","address":"836 Delevan Street","employer":"Velity","email":"elviapoole@velity.com","city":"Groveville","state":"MI"}
    {"index":{"_id":"257"}}
    {"account_number":257,"balance":5318,"firstname":"Olive","lastname":"Oneil","age":35,"gender":"F","address":"457 Decatur Street","employer":"Helixo","email":"oliveoneil@helixo.com","city":"Chicopee","state":"MI"}
    {"index":{"_id":"264"}}
    {"account_number":264,"balance":22084,"firstname":"Samantha","lastname":"Ferrell","age":35,"gender":"F","address":"488 Fulton Street","employer":"Flum","email":"samanthaferrell@flum.com","city":"Brandywine","state":"MT"}
    {"index":{"_id":"269"}}
    {"account_number":269,"balance":43317,"firstname":"Crosby","lastname":"Figueroa","age":34,"gender":"M","address":"910 Aurelia Court","employer":"Pyramia","email":"crosbyfigueroa@pyramia.com","city":"Leyner","state":"OH"}
    {"index":{"_id":"271"}}
    {"account_number":271,"balance":11864,"firstname":"Holt","lastname":"Walter","age":30,"gender":"F","address":"645 Poplar Avenue","employer":"Grupoli","email":"holtwalter@grupoli.com","city":"Mansfield","state":"OR"}
    {"index":{"_id":"276"}}
    {"account_number":276,"balance":11606,"firstname":"Pittman","lastname":"Mathis","age":23,"gender":"F","address":"567 Charles Place","employer":"Zuvy","email":"pittmanmathis@zuvy.com","city":"Roeville","state":"KY"}
    {"index":{"_id":"283"}}
    {"account_number":283,"balance":24070,"firstname":"Fuentes","lastname":"Foley","age":30,"gender":"M","address":"729 Walker Court","employer":"Knowlysis","email":"fuentesfoley@knowlysis.com","city":"Tryon","state":"TN"}
    {"index":{"_id":"288"}}
    {"account_number":288,"balance":27243,"firstname":"Wong","lastname":"Stone","age":39,"gender":"F","address":"440 Willoughby Street","employer":"Zentix","email":"wongstone@zentix.com","city":"Wheatfields","state":"DC"}
    {"index":{"_id":"290"}}
    {"account_number":290,"balance":26103,"firstname":"Neva","lastname":"Burgess","age":37,"gender":"F","address":"985 Wyona Street","employer":"Slofast","email":"nevaburgess@slofast.com","city":"Cawood","state":"DC"}
    {"index":{"_id":"295"}}
    {"account_number":295,"balance":37358,"firstname":"Howe","lastname":"Nash","age":20,"gender":"M","address":"833 Union Avenue","employer":"Aquacine","email":"howenash@aquacine.com","city":"Indio","state":"MN"}
    {"index":{"_id":"303"}}
    {"account_number":303,"balance":21976,"firstname":"Huffman","lastname":"Green","age":24,"gender":"F","address":"455 Colby Court","employer":"Comtest","email":"huffmangreen@comtest.com","city":"Weeksville","state":"UT"}
    {"index":{"_id":"308"}}
    {"account_number":308,"balance":33989,"firstname":"Glass","lastname":"Schroeder","age":25,"gender":"F","address":"670 Veterans Avenue","employer":"Realmo","email":"glassschroeder@realmo.com","city":"Gratton","state":"NY"}
    {"index":{"_id":"310"}}
    {"account_number":310,"balance":23049,"firstname":"Shannon","lastname":"Morton","age":39,"gender":"F","address":"412 Pleasant Place","employer":"Ovation","email":"shannonmorton@ovation.com","city":"Edgar","state":"AZ"}
    {"index":{"_id":"315"}}
    {"account_number":315,"balance":1314,"firstname":"Clare","lastname":"Morrow","age":33,"gender":"F","address":"728 Madeline Court","employer":"Gaptec","email":"claremorrow@gaptec.com","city":"Mapletown","state":"PA"}
    {"index":{"_id":"322"}}
    {"account_number":322,"balance":6303,"firstname":"Gilliam","lastname":"Horne","age":27,"gender":"M","address":"414 Florence Avenue","employer":"Shepard","email":"gilliamhorne@shepard.com","city":"Winesburg","state":"WY"}
    {"index":{"_id":"327"}}
    {"account_number":327,"balance":29294,"firstname":"Nell","lastname":"Contreras","age":27,"gender":"M","address":"694 Gold Street","employer":"Momentia","email":"nellcontreras@momentia.com","city":"Cumminsville","state":"AL"}
    {"index":{"_id":"334"}}
    {"account_number":334,"balance":9178,"firstname":"Cross","lastname":"Floyd","age":21,"gender":"F","address":"815 Herkimer Court","employer":"Maroptic","email":"crossfloyd@maroptic.com","city":"Kraemer","state":"AK"}
    {"index":{"_id":"339"}}
    {"account_number":339,"balance":3992,"firstname":"Franco","lastname":"Welch","age":38,"gender":"F","address":"776 Brightwater Court","employer":"Earthplex","email":"francowelch@earthplex.com","city":"Naomi","state":"ME"}
    {"index":{"_id":"341"}}
    {"account_number":341,"balance":44367,"firstname":"Alberta","lastname":"Bradford","age":30,"gender":"F","address":"670 Grant Avenue","employer":"Bugsall","email":"albertabradford@bugsall.com","city":"Romeville","state":"MT"}
    {"index":{"_id":"346"}}
    {"account_number":346,"balance":26594,"firstname":"Shelby","lastname":"Sanchez","age":36,"gender":"F","address":"257 Fillmore Avenue","employer":"Geekus","email":"shelbysanchez@geekus.com","city":"Seymour","state":"CO"}
    {"index":{"_id":"353"}}
    {"account_number":353,"balance":45182,"firstname":"Rivera","lastname":"Sherman","age":37,"gender":"M","address":"603 Garden Place","employer":"Bovis","email":"riverasherman@bovis.com","city":"Otranto","state":"CA"}
    {"index":{"_id":"358"}}
    {"account_number":358,"balance":44043,"firstname":"Hale","lastname":"Baldwin","age":40,"gender":"F","address":"845 Menahan Street","employer":"Kidgrease","email":"halebaldwin@kidgrease.com","city":"Day","state":"AK"}
    {"index":{"_id":"360"}}
    {"account_number":360,"balance":26651,"firstname":"Ward","lastname":"Hicks","age":34,"gender":"F","address":"592 Brighton Court","employer":"Biotica","email":"wardhicks@biotica.com","city":"Kanauga","state":"VT"}
    {"index":{"_id":"365"}}
    {"account_number":365,"balance":3176,"firstname":"Sanders","lastname":"Holder","age":31,"gender":"F","address":"453 Cypress Court","employer":"Geekola","email":"sandersholder@geekola.com","city":"Staples","state":"TN"}
    {"index":{"_id":"372"}}
    {"account_number":372,"balance":28566,"firstname":"Alba","lastname":"Forbes","age":24,"gender":"M","address":"814 Meserole Avenue","employer":"Isostream","email":"albaforbes@isostream.com","city":"Clarence","state":"OR"}
    {"index":{"_id":"377"}}
    {"account_number":377,"balance":5374,"firstname":"Margo","lastname":"Gay","age":34,"gender":"F","address":"613 Chase Court","employer":"Rotodyne","email":"margogay@rotodyne.com","city":"Waumandee","state":"KS"}
    {"index":{"_id":"384"}}
    {"account_number":384,"balance":48758,"firstname":"Sallie","lastname":"Houston","age":31,"gender":"F","address":"836 Polar Street","employer":"Squish","email":"salliehouston@squish.com","city":"Morningside","state":"NC"}
    {"index":{"_id":"389"}}
    {"account_number":389,"balance":8839,"firstname":"York","lastname":"Cummings","age":27,"gender":"M","address":"778 Centre Street","employer":"Insurity","email":"yorkcummings@insurity.com","city":"Freeburn","state":"RI"}
    {"index":{"_id":"391"}}
    {"account_number":391,"balance":14733,"firstname":"Holman","lastname":"Jordan","age":30,"gender":"M","address":"391 Forrest Street","employer":"Maineland","email":"holmanjordan@maineland.com","city":"Cade","state":"CT"}
    {"index":{"_id":"396"}}
    {"account_number":396,"balance":14613,"firstname":"Marsha","lastname":"Elliott","age":38,"gender":"F","address":"297 Liberty Avenue","employer":"Orbiflex","email":"marshaelliott@orbiflex.com","city":"Windsor","state":"TX"}
    {"index":{"_id":"404"}}
    {"account_number":404,"balance":34978,"firstname":"Massey","lastname":"Becker","age":26,"gender":"F","address":"930 Pitkin Avenue","employer":"Genekom","email":"masseybecker@genekom.com","city":"Blairstown","state":"OR"}
    {"index":{"_id":"409"}}
    {"account_number":409,"balance":36960,"firstname":"Maura","lastname":"Glenn","age":31,"gender":"M","address":"183 Poly Place","employer":"Viagreat","email":"mauraglenn@viagreat.com","city":"Foscoe","state":"DE"}
    {"index":{"_id":"411"}}
    {"account_number":411,"balance":1172,"firstname":"Guzman","lastname":"Whitfield","age":22,"gender":"M","address":"181 Perry Terrace","employer":"Springbee","email":"guzmanwhitfield@springbee.com","city":"Balm","state":"IN"}
    {"index":{"_id":"416"}}
    {"account_number":416,"balance":27169,"firstname":"Hunt","lastname":"Schwartz","age":28,"gender":"F","address":"461 Havens Place","employer":"Danja","email":"huntschwartz@danja.com","city":"Grenelefe","state":"NV"}
    {"index":{"_id":"423"}}
    {"account_number":423,"balance":38852,"firstname":"Hines","lastname":"Underwood","age":21,"gender":"F","address":"284 Louise Terrace","employer":"Namegen","email":"hinesunderwood@namegen.com","city":"Downsville","state":"CO"}
    {"index":{"_id":"428"}}
    {"account_number":428,"balance":13925,"firstname":"Stephens","lastname":"Cain","age":20,"gender":"F","address":"189 Summit Street","employer":"Rocklogic","email":"stephenscain@rocklogic.com","city":"Bourg","state":"HI"}
    {"index":{"_id":"430"}}
    {"account_number":430,"balance":15251,"firstname":"Alejandra","lastname":"Chavez","age":34,"gender":"M","address":"651 Butler Place","employer":"Gology","email":"alejandrachavez@gology.com","city":"Allensworth","state":"VT"}
    {"index":{"_id":"435"}}
    {"account_number":435,"balance":14654,"firstname":"Sue","lastname":"Lopez","age":22,"gender":"F","address":"632 Stone Avenue","employer":"Emergent","email":"suelopez@emergent.com","city":"Waterford","state":"TN"}
    {"index":{"_id":"442"}}
    {"account_number":442,"balance":36211,"firstname":"Lawanda","lastname":"Leon","age":27,"gender":"F","address":"126 Canal Avenue","employer":"Xixan","email":"lawandaleon@xixan.com","city":"Berwind","state":"TN"}
    {"index":{"_id":"447"}}
    {"account_number":447,"balance":11402,"firstname":"Lucia","lastname":"Livingston","age":35,"gender":"M","address":"773 Lake Avenue","employer":"Soprano","email":"lucialivingston@soprano.com","city":"Edgewater","state":"TN"}
    {"index":{"_id":"454"}}
    {"account_number":454,"balance":31687,"firstname":"Alicia","lastname":"Rollins","age":22,"gender":"F","address":"483 Verona Place","employer":"Boilcat","email":"aliciarollins@boilcat.com","city":"Lutsen","state":"MD"}
    {"index":{"_id":"459"}}
    {"account_number":459,"balance":18869,"firstname":"Pamela","lastname":"Henry","age":20,"gender":"F","address":"361 Locust Avenue","employer":"Imageflow","email":"pamelahenry@imageflow.com","city":"Greenfields","state":"OH"}
    {"index":{"_id":"461"}}
    {"account_number":461,"balance":38807,"firstname":"Mcbride","lastname":"Padilla","age":34,"gender":"F","address":"550 Borinquen Pl","employer":"Zepitope","email":"mcbridepadilla@zepitope.com","city":"Emory","state":"AZ"}
    {"index":{"_id":"466"}}
    {"account_number":466,"balance":25109,"firstname":"Marcie","lastname":"Mcmillan","age":30,"gender":"F","address":"947 Gain Court","employer":"Entroflex","email":"marciemcmillan@entroflex.com","city":"Ronco","state":"ND"}
    {"index":{"_id":"473"}}
    {"account_number":473,"balance":5391,"firstname":"Susan","lastname":"Luna","age":25,"gender":"F","address":"521 Bogart Street","employer":"Zaya","email":"susanluna@zaya.com","city":"Grazierville","state":"MI"}
    {"index":{"_id":"478"}}
    {"account_number":478,"balance":28044,"firstname":"Dana","lastname":"Decker","age":35,"gender":"M","address":"627 Dobbin Street","employer":"Acrodance","email":"danadecker@acrodance.com","city":"Sharon","state":"MN"}
    {"index":{"_id":"480"}}
    {"account_number":480,"balance":40807,"firstname":"Anastasia","lastname":"Parker","age":24,"gender":"M","address":"650 Folsom Place","employer":"Zilladyne","email":"anastasiaparker@zilladyne.com","city":"Oberlin","state":"WY"}
    {"index":{"_id":"485"}}
    {"account_number":485,"balance":44235,"firstname":"Albert","lastname":"Roberts","age":40,"gender":"M","address":"385 Harman Street","employer":"Stralum","email":"albertroberts@stralum.com","city":"Watrous","state":"NM"}
    {"index":{"_id":"492"}}
    {"account_number":492,"balance":31055,"firstname":"Burnett","lastname":"Briggs","age":35,"gender":"M","address":"987 Cass Place","employer":"Pharmex","email":"burnettbriggs@pharmex.com","city":"Cornfields","state":"TX"}
    {"index":{"_id":"497"}}
    {"account_number":497,"balance":13493,"firstname":"Doyle","lastname":"Jenkins","age":30,"gender":"M","address":"205 Nevins Street","employer":"Unia","email":"doylejenkins@unia.com","city":"Nicut","state":"DC"}
    {"index":{"_id":"500"}}
    {"account_number":500,"balance":39143,"firstname":"Pope","lastname":"Keith","age":28,"gender":"F","address":"537 Fane Court","employer":"Zboo","email":"popekeith@zboo.com","city":"Courtland","state":"AL"}
    {"index":{"_id":"505"}}
    {"account_number":505,"balance":45493,"firstname":"Shelley","lastname":"Webb","age":29,"gender":"M","address":"873 Crawford Avenue","employer":"Quadeebo","email":"shelleywebb@quadeebo.com","city":"Topanga","state":"IL"}
    {"index":{"_id":"512"}}
    {"account_number":512,"balance":47432,"firstname":"Alisha","lastname":"Morales","age":29,"gender":"M","address":"623 Batchelder Street","employer":"Terragen","email":"alishamorales@terragen.com","city":"Gilmore","state":"VA"}
    {"index":{"_id":"517"}}
    {"account_number":517,"balance":3022,"firstname":"Allyson","lastname":"Walls","age":38,"gender":"F","address":"334 Coffey Street","employer":"Gorganic","email":"allysonwalls@gorganic.com","city":"Dahlen","state":"GA"}
    {"index":{"_id":"524"}}
    {"account_number":524,"balance":49334,"firstname":"Salas","lastname":"Farley","age":30,"gender":"F","address":"499 Trucklemans Lane","employer":"Xumonk","email":"salasfarley@xumonk.com","city":"Noxen","state":"AL"}
    {"index":{"_id":"529"}}
    {"account_number":529,"balance":21788,"firstname":"Deann","lastname":"Fisher","age":23,"gender":"F","address":"511 Buffalo Avenue","employer":"Twiist","email":"deannfisher@twiist.com","city":"Templeton","state":"WA"}
    {"index":{"_id":"531"}}
    {"account_number":531,"balance":39770,"firstname":"Janet","lastname":"Pena","age":38,"gender":"M","address":"645 Livonia Avenue","employer":"Corecom","email":"janetpena@corecom.com","city":"Garberville","state":"OK"}
    {"index":{"_id":"536"}}
    {"account_number":536,"balance":6255,"firstname":"Emma","lastname":"Adkins","age":33,"gender":"F","address":"971 Calder Place","employer":"Ontagene","email":"emmaadkins@ontagene.com","city":"Ruckersville","state":"GA"}
    {"index":{"_id":"543"}}
    {"account_number":543,"balance":48022,"firstname":"Marina","lastname":"Rasmussen","age":31,"gender":"M","address":"446 Love Lane","employer":"Crustatia","email":"marinarasmussen@crustatia.com","city":"Statenville","state":"MD"}
    {"index":{"_id":"548"}}
    {"account_number":548,"balance":36930,"firstname":"Sandra","lastname":"Andrews","age":37,"gender":"M","address":"973 Prospect Street","employer":"Datagene","email":"sandraandrews@datagene.com","city":"Inkerman","state":"MO"}
    {"index":{"_id":"550"}}
    {"account_number":550,"balance":32238,"firstname":"Walsh","lastname":"Goodwin","age":22,"gender":"M","address":"953 Canda Avenue","employer":"Proflex","email":"walshgoodwin@proflex.com","city":"Ypsilanti","state":"MT"}
    {"index":{"_id":"555"}}
    {"account_number":555,"balance":10750,"firstname":"Fannie","lastname":"Slater","age":31,"gender":"M","address":"457 Tech Place","employer":"Kineticut","email":"fannieslater@kineticut.com","city":"Basye","state":"MO"}
    {"index":{"_id":"562"}}
    {"account_number":562,"balance":10737,"firstname":"Sarah","lastname":"Strong","age":39,"gender":"F","address":"177 Pioneer Street","employer":"Megall","email":"sarahstrong@megall.com","city":"Ladera","state":"WY"}
    {"index":{"_id":"567"}}
    {"account_number":567,"balance":6507,"firstname":"Diana","lastname":"Dominguez","age":40,"gender":"M","address":"419 Albany Avenue","employer":"Ohmnet","email":"dianadominguez@ohmnet.com","city":"Wildwood","state":"TX"}
    {"index":{"_id":"574"}}
    {"account_number":574,"balance":32954,"firstname":"Andrea","lastname":"Mosley","age":24,"gender":"M","address":"368 Throop Avenue","employer":"Musix","email":"andreamosley@musix.com","city":"Blende","state":"DC"}
    {"index":{"_id":"579"}}
    {"account_number":579,"balance":12044,"firstname":"Banks","lastname":"Sawyer","age":36,"gender":"M","address":"652 Doone Court","employer":"Rooforia","email":"bankssawyer@rooforia.com","city":"Foxworth","state":"ND"}
    {"index":{"_id":"581"}}
    {"account_number":581,"balance":16525,"firstname":"Fuller","lastname":"Mcintyre","age":32,"gender":"M","address":"169 Bergen Place","employer":"Applideck","email":"fullermcintyre@applideck.com","city":"Kenvil","state":"NY"}
    {"index":{"_id":"586"}}
    {"account_number":586,"balance":13644,"firstname":"Love","lastname":"Velasquez","age":26,"gender":"F","address":"290 Girard Street","employer":"Zomboid","email":"lovevelasquez@zomboid.com","city":"Villarreal","state":"SD"}
    {"index":{"_id":"593"}}
    {"account_number":593,"balance":41230,"firstname":"Muriel","lastname":"Vazquez","age":37,"gender":"M","address":"395 Montgomery Street","employer":"Sustenza","email":"murielvazquez@sustenza.com","city":"Strykersville","state":"OK"}
    {"index":{"_id":"598"}}
    {"account_number":598,"balance":33251,"firstname":"Morgan","lastname":"Coleman","age":33,"gender":"M","address":"324 McClancy Place","employer":"Aclima","email":"morgancoleman@aclima.com","city":"Bowden","state":"WA"}
    {"index":{"_id":"601"}}
    {"account_number":601,"balance":20796,"firstname":"Vickie","lastname":"Valentine","age":34,"gender":"F","address":"432 Bassett Avenue","employer":"Comvene","email":"vickievalentine@comvene.com","city":"Teasdale","state":"UT"}
    {"index":{"_id":"606"}}
    {"account_number":606,"balance":28770,"firstname":"Michael","lastname":"Bray","age":31,"gender":"M","address":"935 Lake Place","employer":"Telepark","email":"michaelbray@telepark.com","city":"Lemoyne","state":"CT"}
    {"index":{"_id":"613"}}
    {"account_number":613,"balance":39340,"firstname":"Eddie","lastname":"Mccarty","age":34,"gender":"F","address":"971 Richards Street","employer":"Bisba","email":"eddiemccarty@bisba.com","city":"Fruitdale","state":"NY"}
    {"index":{"_id":"618"}}
    {"account_number":618,"balance":8976,"firstname":"Cheri","lastname":"Ford","age":30,"gender":"F","address":"803 Ridgewood Avenue","employer":"Zorromop","email":"cheriford@zorromop.com","city":"Gambrills","state":"VT"}
    {"index":{"_id":"620"}}
    {"account_number":620,"balance":7224,"firstname":"Coleen","lastname":"Bartlett","age":38,"gender":"M","address":"761 Carroll Street","employer":"Idealis","email":"coleenbartlett@idealis.com","city":"Mathews","state":"DE"}
    {"index":{"_id":"625"}}
    {"account_number":625,"balance":46010,"firstname":"Cynthia","lastname":"Johnston","age":23,"gender":"M","address":"142 Box Street","employer":"Zentry","email":"cynthiajohnston@zentry.com","city":"Makena","state":"MA"}
    {"index":{"_id":"632"}}
    {"account_number":632,"balance":40470,"firstname":"Kay","lastname":"Warren","age":20,"gender":"F","address":"422 Alabama Avenue","employer":"Realysis","email":"kaywarren@realysis.com","city":"Homestead","state":"HI"}
    {"index":{"_id":"637"}}
    {"account_number":637,"balance":3169,"firstname":"Kathy","lastname":"Carter","age":27,"gender":"F","address":"410 Jamison Lane","employer":"Limage","email":"kathycarter@limage.com","city":"Ernstville","state":"WA"}
    {"index":{"_id":"644"}}
    {"account_number":644,"balance":44021,"firstname":"Etta","lastname":"Miller","age":21,"gender":"F","address":"376 Lawton Street","employer":"Bluegrain","email":"ettamiller@bluegrain.com","city":"Baker","state":"MD"}
    {"index":{"_id":"649"}}
    {"account_number":649,"balance":20275,"firstname":"Jeanine","lastname":"Malone","age":26,"gender":"F","address":"114 Dodworth Street","employer":"Nixelt","email":"jeaninemalone@nixelt.com","city":"Keyport","state":"AK"}
    {"index":{"_id":"651"}}
    {"account_number":651,"balance":18360,"firstname":"Young","lastname":"Reeves","age":34,"gender":"M","address":"581 Plaza Street","employer":"Krog","email":"youngreeves@krog.com","city":"Sussex","state":"WY"}
    {"index":{"_id":"656"}}
    {"account_number":656,"balance":21632,"firstname":"Olson","lastname":"Hunt","age":36,"gender":"M","address":"342 Jaffray Street","employer":"Volax","email":"olsonhunt@volax.com","city":"Bangor","state":"WA"}
    {"index":{"_id":"663"}}
    {"account_number":663,"balance":2456,"firstname":"Rollins","lastname":"Richards","age":37,"gender":"M","address":"129 Sullivan Place","employer":"Geostele","email":"rollinsrichards@geostele.com","city":"Morgandale","state":"FL"}
    {"index":{"_id":"668"}}
    {"account_number":668,"balance":45069,"firstname":"Potter","lastname":"Michael","age":27,"gender":"M","address":"803 Glenmore Avenue","employer":"Ontality","email":"pottermichael@ontality.com","city":"Newkirk","state":"KS"}
    {"index":{"_id":"670"}}
    {"account_number":670,"balance":10178,"firstname":"Ollie","lastname":"Riley","age":22,"gender":"M","address":"252 Jackson Place","employer":"Adornica","email":"ollieriley@adornica.com","city":"Brethren","state":"WI"}
    {"index":{"_id":"675"}}
    {"account_number":675,"balance":36102,"firstname":"Fisher","lastname":"Shepard","age":27,"gender":"F","address":"859 Varick Street","employer":"Qot","email":"fishershepard@qot.com","city":"Diaperville","state":"MD"}
    {"index":{"_id":"682"}}
    {"account_number":682,"balance":14168,"firstname":"Anne","lastname":"Hale","age":22,"gender":"F","address":"708 Anthony Street","employer":"Cytrek","email":"annehale@cytrek.com","city":"Beechmont","state":"WV"}
    {"index":{"_id":"687"}}
    {"account_number":687,"balance":48630,"firstname":"Caroline","lastname":"Cox","age":31,"gender":"M","address":"626 Hillel Place","employer":"Opticon","email":"carolinecox@opticon.com","city":"Loma","state":"ND"}
    {"index":{"_id":"694"}}
    {"account_number":694,"balance":33125,"firstname":"Craig","lastname":"Palmer","age":31,"gender":"F","address":"273 Montrose Avenue","employer":"Comvey","email":"craigpalmer@comvey.com","city":"Cleary","state":"OK"}
    {"index":{"_id":"699"}}
    {"account_number":699,"balance":4156,"firstname":"Gallagher","lastname":"Marshall","age":37,"gender":"F","address":"648 Clifford Place","employer":"Exiand","email":"gallaghermarshall@exiand.com","city":"Belfair","state":"KY"}
    {"index":{"_id":"702"}}
    {"account_number":702,"balance":46490,"firstname":"Meadows","lastname":"Delgado","age":26,"gender":"M","address":"612 Jardine Place","employer":"Daisu","email":"meadowsdelgado@daisu.com","city":"Venice","state":"AR"}
    {"index":{"_id":"707"}}
    {"account_number":707,"balance":30325,"firstname":"Sonya","lastname":"Trevino","age":30,"gender":"F","address":"181 Irving Place","employer":"Atgen","email":"sonyatrevino@atgen.com","city":"Enetai","state":"TN"}
    {"index":{"_id":"714"}}
    {"account_number":714,"balance":16602,"firstname":"Socorro","lastname":"Murray","age":34,"gender":"F","address":"810 Manhattan Court","employer":"Isoswitch","email":"socorromurray@isoswitch.com","city":"Jugtown","state":"AZ"}
    {"index":{"_id":"719"}}
    {"account_number":719,"balance":33107,"firstname":"Leanna","lastname":"Reed","age":25,"gender":"F","address":"528 Krier Place","employer":"Rodeology","email":"leannareed@rodeology.com","city":"Carrizo","state":"WI"}
    {"index":{"_id":"721"}}
    {"account_number":721,"balance":32958,"firstname":"Mara","lastname":"Dickson","age":26,"gender":"M","address":"810 Harrison Avenue","employer":"Comtours","email":"maradickson@comtours.com","city":"Thynedale","state":"DE"}
    {"index":{"_id":"726"}}
    {"account_number":726,"balance":44737,"firstname":"Rosemary","lastname":"Salazar","age":21,"gender":"M","address":"290 Croton Loop","employer":"Rockabye","email":"rosemarysalazar@rockabye.com","city":"Helen","state":"IA"}
    {"index":{"_id":"733"}}
    {"account_number":733,"balance":15722,"firstname":"Lakeisha","lastname":"Mccarthy","age":37,"gender":"M","address":"782 Turnbull Avenue","employer":"Exosis","email":"lakeishamccarthy@exosis.com","city":"Caberfae","state":"NM"}
    {"index":{"_id":"738"}}
    {"account_number":738,"balance":44936,"firstname":"Rosalind","lastname":"Hunter","age":32,"gender":"M","address":"644 Eaton Court","employer":"Zolarity","email":"rosalindhunter@zolarity.com","city":"Cataract","state":"SD"}
    {"index":{"_id":"740"}}
    {"account_number":740,"balance":6143,"firstname":"Chambers","lastname":"Hahn","age":22,"gender":"M","address":"937 Windsor Place","employer":"Medalert","email":"chambershahn@medalert.com","city":"Dorneyville","state":"DC"}
    {"index":{"_id":"745"}}
    {"account_number":745,"balance":4572,"firstname":"Jacobs","lastname":"Sweeney","age":32,"gender":"M","address":"189 Lott Place","employer":"Comtent","email":"jacobssweeney@comtent.com","city":"Advance","state":"NJ"}
    {"index":{"_id":"752"}}
    {"account_number":752,"balance":14039,"firstname":"Jerry","lastname":"Rush","age":31,"gender":"M","address":"632 Dank Court","employer":"Ebidco","email":"jerryrush@ebidco.com","city":"Geyserville","state":"AR"}
    {"index":{"_id":"757"}}
    {"account_number":757,"balance":34628,"firstname":"Mccullough","lastname":"Moore","age":30,"gender":"F","address":"304 Hastings Street","employer":"Nikuda","email":"mcculloughmoore@nikuda.com","city":"Charco","state":"DC"}
    {"index":{"_id":"764"}}
    {"account_number":764,"balance":3728,"firstname":"Noemi","lastname":"Gill","age":30,"gender":"M","address":"427 Chester Street","employer":"Avit","email":"noemigill@avit.com","city":"Chesterfield","state":"AL"}
    {"index":{"_id":"769"}}
    {"account_number":769,"balance":15362,"firstname":"Francis","lastname":"Beck","age":28,"gender":"M","address":"454 Livingston Street","employer":"Furnafix","email":"francisbeck@furnafix.com","city":"Dunnavant","state":"HI"}
    {"index":{"_id":"771"}}
    {"account_number":771,"balance":32784,"firstname":"Jocelyn","lastname":"Boone","age":23,"gender":"M","address":"513 Division Avenue","employer":"Collaire","email":"jocelynboone@collaire.com","city":"Lisco","state":"VT"}
    {"index":{"_id":"776"}}
    {"account_number":776,"balance":29177,"firstname":"Duke","lastname":"Atkinson","age":24,"gender":"M","address":"520 Doscher Street","employer":"Tripsch","email":"dukeatkinson@tripsch.com","city":"Lafferty","state":"NC"}
    {"index":{"_id":"783"}}
    {"account_number":783,"balance":11911,"firstname":"Faith","lastname":"Cooper","age":25,"gender":"F","address":"539 Rapelye Street","employer":"Insuron","email":"faithcooper@insuron.com","city":"Jennings","state":"MN"}
    {"index":{"_id":"788"}}
    {"account_number":788,"balance":12473,"firstname":"Marianne","lastname":"Aguilar","age":39,"gender":"F","address":"213 Holly Street","employer":"Marqet","email":"marianneaguilar@marqet.com","city":"Alfarata","state":"HI"}
    {"index":{"_id":"790"}}
    {"account_number":790,"balance":29912,"firstname":"Ellis","lastname":"Sullivan","age":39,"gender":"F","address":"877 Coyle Street","employer":"Enersave","email":"ellissullivan@enersave.com","city":"Canby","state":"MS"}
    {"index":{"_id":"795"}}
    {"account_number":795,"balance":31450,"firstname":"Bruce","lastname":"Avila","age":34,"gender":"M","address":"865 Newkirk Placez","employer":"Plasmosis","email":"bruceavila@plasmosis.com","city":"Ada","state":"ID"}
    {"index":{"_id":"803"}}
    {"account_number":803,"balance":49567,"firstname":"Marissa","lastname":"Spears","age":25,"gender":"M","address":"963 Highland Avenue","employer":"Centregy","email":"marissaspears@centregy.com","city":"Bloomington","state":"MS"}
    {"index":{"_id":"808"}}
    {"account_number":808,"balance":11251,"firstname":"Nola","lastname":"Quinn","age":20,"gender":"M","address":"863 Wythe Place","employer":"Iplax","email":"nolaquinn@iplax.com","city":"Cuylerville","state":"NH"}
    {"index":{"_id":"810"}}
    {"account_number":810,"balance":10563,"firstname":"Alyssa","lastname":"Ortega","age":40,"gender":"M","address":"977 Clymer Street","employer":"Eventage","email":"alyssaortega@eventage.com","city":"Convent","state":"SC"}
    {"index":{"_id":"815"}}
    {"account_number":815,"balance":19336,"firstname":"Guthrie","lastname":"Morse","age":30,"gender":"M","address":"685 Vandalia Avenue","employer":"Gronk","email":"guthriemorse@gronk.com","city":"Fowlerville","state":"OR"}
    {"index":{"_id":"822"}}
    {"account_number":822,"balance":13024,"firstname":"Hicks","lastname":"Farrell","age":25,"gender":"M","address":"468 Middleton Street","employer":"Zolarex","email":"hicksfarrell@zolarex.com","city":"Columbus","state":"OR"}
    {"index":{"_id":"827"}}
    {"account_number":827,"balance":37536,"firstname":"Naomi","lastname":"Ball","age":29,"gender":"F","address":"319 Stewart Street","employer":"Isotronic","email":"naomiball@isotronic.com","city":"Trona","state":"NM"}
    {"index":{"_id":"834"}}
    {"account_number":834,"balance":38049,"firstname":"Sybil","lastname":"Carrillo","age":25,"gender":"M","address":"359 Baughman Place","employer":"Phuel","email":"sybilcarrillo@phuel.com","city":"Kohatk","state":"CT"}
    {"index":{"_id":"839"}}
    {"account_number":839,"balance":38292,"firstname":"Langley","lastname":"Neal","age":39,"gender":"F","address":"565 Newton Street","employer":"Liquidoc","email":"langleyneal@liquidoc.com","city":"Osage","state":"AL"}
    {"index":{"_id":"841"}}
    {"account_number":841,"balance":28291,"firstname":"Dalton","lastname":"Waters","age":21,"gender":"M","address":"859 Grand Street","employer":"Malathion","email":"daltonwaters@malathion.com","city":"Tonopah","state":"AZ"}
    {"index":{"_id":"846"}}
    {"account_number":846,"balance":35099,"firstname":"Maureen","lastname":"Glass","age":22,"gender":"M","address":"140 Amherst Street","employer":"Stelaecor","email":"maureenglass@stelaecor.com","city":"Cucumber","state":"IL"}
    {"index":{"_id":"853"}}
    {"account_number":853,"balance":38353,"firstname":"Travis","lastname":"Parks","age":40,"gender":"M","address":"930 Bay Avenue","employer":"Pyramax","email":"travisparks@pyramax.com","city":"Gadsden","state":"ND"}
    {"index":{"_id":"858"}}
    {"account_number":858,"balance":23194,"firstname":"Small","lastname":"Hatfield","age":36,"gender":"M","address":"593 Tennis Court","employer":"Letpro","email":"smallhatfield@letpro.com","city":"Haena","state":"KS"}
    {"index":{"_id":"860"}}
    {"account_number":860,"balance":23613,"firstname":"Clark","lastname":"Boyd","age":37,"gender":"M","address":"501 Rock Street","employer":"Deepends","email":"clarkboyd@deepends.com","city":"Whitewater","state":"MA"}
    {"index":{"_id":"865"}}
    {"account_number":865,"balance":10574,"firstname":"Cook","lastname":"Kelley","age":28,"gender":"F","address":"865 Lincoln Terrace","employer":"Quizmo","email":"cookkelley@quizmo.com","city":"Kansas","state":"KY"}
    {"index":{"_id":"872"}}
    {"account_number":872,"balance":26314,"firstname":"Jane","lastname":"Greer","age":36,"gender":"F","address":"717 Hewes Street","employer":"Newcube","email":"janegreer@newcube.com","city":"Delshire","state":"DE"}
    {"index":{"_id":"877"}}
    {"account_number":877,"balance":42879,"firstname":"Tracey","lastname":"Ruiz","age":34,"gender":"F","address":"141 Tompkins Avenue","employer":"Waab","email":"traceyruiz@waab.com","city":"Zeba","state":"NM"}
    {"index":{"_id":"884"}}
    {"account_number":884,"balance":29316,"firstname":"Reva","lastname":"Rosa","age":40,"gender":"M","address":"784 Greene Avenue","employer":"Urbanshee","email":"revarosa@urbanshee.com","city":"Bakersville","state":"MS"}
    {"index":{"_id":"889"}}
    {"account_number":889,"balance":26464,"firstname":"Fischer","lastname":"Klein","age":38,"gender":"F","address":"948 Juliana Place","employer":"Comtext","email":"fischerklein@comtext.com","city":"Jackpot","state":"PA"}
    {"index":{"_id":"891"}}
    {"account_number":891,"balance":34829,"firstname":"Jacobson","lastname":"Clemons","age":24,"gender":"F","address":"507 Wilson Street","employer":"Quilm","email":"jacobsonclemons@quilm.com","city":"Muir","state":"TX"}
    {"index":{"_id":"896"}}
    {"account_number":896,"balance":31947,"firstname":"Buckley","lastname":"Peterson","age":26,"gender":"M","address":"217 Beayer Place","employer":"Earwax","email":"buckleypeterson@earwax.com","city":"Franklin","state":"DE"}
    {"index":{"_id":"904"}}
    {"account_number":904,"balance":27707,"firstname":"Mendez","lastname":"Mcneil","age":26,"gender":"M","address":"431 Halsey Street","employer":"Macronaut","email":"mendezmcneil@macronaut.com","city":"Troy","state":"OK"}
    {"index":{"_id":"909"}}
    {"account_number":909,"balance":18421,"firstname":"Stark","lastname":"Lewis","age":36,"gender":"M","address":"409 Tilden Avenue","employer":"Frosnex","email":"starklewis@frosnex.com","city":"Axis","state":"CA"}
    {"index":{"_id":"911"}}
    {"account_number":911,"balance":42655,"firstname":"Annie","lastname":"Lyons","age":21,"gender":"M","address":"518 Woods Place","employer":"Enerforce","email":"annielyons@enerforce.com","city":"Stagecoach","state":"MA"}
    {"index":{"_id":"916"}}
    {"account_number":916,"balance":47887,"firstname":"Jarvis","lastname":"Alexander","age":40,"gender":"M","address":"406 Bergen Avenue","employer":"Equitax","email":"jarvisalexander@equitax.com","city":"Haring","state":"KY"}
    {"index":{"_id":"923"}}
    {"account_number":923,"balance":48466,"firstname":"Mueller","lastname":"Mckee","age":26,"gender":"M","address":"298 Ruby Street","employer":"Luxuria","email":"muellermckee@luxuria.com","city":"Coleville","state":"TN"}
    {"index":{"_id":"928"}}
    {"account_number":928,"balance":19611,"firstname":"Hester","lastname":"Copeland","age":22,"gender":"F","address":"425 Cropsey Avenue","employer":"Dymi","email":"hestercopeland@dymi.com","city":"Wolcott","state":"NE"}
    {"index":{"_id":"930"}}
    {"account_number":930,"balance":47257,"firstname":"Kinney","lastname":"Lawson","age":39,"gender":"M","address":"501 Raleigh Place","employer":"Neptide","email":"kinneylawson@neptide.com","city":"Deltaville","state":"MD"}
    {"index":{"_id":"935"}}
    {"account_number":935,"balance":4959,"firstname":"Flowers","lastname":"Robles","age":30,"gender":"M","address":"201 Hull Street","employer":"Xelegyl","email":"flowersrobles@xelegyl.com","city":"Rehrersburg","state":"AL"}
    {"index":{"_id":"942"}}
    {"account_number":942,"balance":21299,"firstname":"Hamilton","lastname":"Clayton","age":26,"gender":"M","address":"413 Debevoise Street","employer":"Architax","email":"hamiltonclayton@architax.com","city":"Terlingua","state":"NM"}
    {"index":{"_id":"947"}}
    {"account_number":947,"balance":22039,"firstname":"Virgie","lastname":"Garza","age":30,"gender":"M","address":"903 Matthews Court","employer":"Plasmox","email":"virgiegarza@plasmox.com","city":"Somerset","state":"WY"}
    {"index":{"_id":"954"}}
    {"account_number":954,"balance":49404,"firstname":"Jenna","lastname":"Martin","age":22,"gender":"M","address":"688 Hart Street","employer":"Zinca","email":"jennamartin@zinca.com","city":"Oasis","state":"MD"}
    {"index":{"_id":"959"}}
    {"account_number":959,"balance":34743,"firstname":"Shaffer","lastname":"Cervantes","age":40,"gender":"M","address":"931 Varick Avenue","employer":"Oceanica","email":"shaffercervantes@oceanica.com","city":"Bowie","state":"AL"}
    {"index":{"_id":"961"}}
    {"account_number":961,"balance":43219,"firstname":"Betsy","lastname":"Hyde","age":27,"gender":"F","address":"183 Junius Street","employer":"Tubalum","email":"betsyhyde@tubalum.com","city":"Driftwood","state":"TX"}
    {"index":{"_id":"966"}}
    {"account_number":966,"balance":20619,"firstname":"Susanne","lastname":"Rodriguez","age":35,"gender":"F","address":"255 Knickerbocker Avenue","employer":"Comtrek","email":"susannerodriguez@comtrek.com","city":"Trinway","state":"TX"}
    {"index":{"_id":"973"}}
    {"account_number":973,"balance":45756,"firstname":"Rice","lastname":"Farmer","age":31,"gender":"M","address":"476 Nassau Avenue","employer":"Photobin","email":"ricefarmer@photobin.com","city":"Suitland","state":"ME"}
    {"index":{"_id":"978"}}
    {"account_number":978,"balance":21459,"firstname":"Melanie","lastname":"Rojas","age":33,"gender":"M","address":"991 Java Street","employer":"Kage","email":"melanierojas@kage.com","city":"Greenock","state":"VT"}
    {"index":{"_id":"980"}}
    {"account_number":980,"balance":42436,"firstname":"Cash","lastname":"Collier","age":33,"gender":"F","address":"999 Sapphire Street","employer":"Ceprene","email":"cashcollier@ceprene.com","city":"Glidden","state":"AK"}
    {"index":{"_id":"985"}}
    {"account_number":985,"balance":20083,"firstname":"Martin","lastname":"Gardner","age":28,"gender":"F","address":"644 Fairview Place","employer":"Golistic","email":"martingardner@golistic.com","city":"Connerton","state":"NJ"}
    {"index":{"_id":"992"}}
    {"account_number":992,"balance":11413,"firstname":"Kristie","lastname":"Kennedy","age":33,"gender":"F","address":"750 Hudson Avenue","employer":"Ludak","email":"kristiekennedy@ludak.com","city":"Warsaw","state":"WY"}
    {"index":{"_id":"997"}}
    {"account_number":997,"balance":25311,"firstname":"Combs","lastname":"Frederick","age":20,"gender":"M","address":"586 Lloyd Court","employer":"Pathways","email":"combsfrederick@pathways.com","city":"Williamson","state":"CA"}
    {"index":{"_id":"3"}}
    {"account_number":3,"balance":44947,"firstname":"Levine","lastname":"Burks","age":26,"gender":"F","address":"328 Wilson Avenue","employer":"Amtap","email":"levineburks@amtap.com","city":"Cochranville","state":"HI"}
    {"index":{"_id":"8"}}
    {"account_number":8,"balance":48868,"firstname":"Jan","lastname":"Burns","age":35,"gender":"M","address":"699 Visitation Place","employer":"Glasstep","email":"janburns@glasstep.com","city":"Wakulla","state":"AZ"}
    {"index":{"_id":"10"}}
    {"account_number":10,"balance":46170,"firstname":"Dominique","lastname":"Park","age":37,"gender":"F","address":"100 Gatling Place","employer":"Conjurica","email":"dominiquepark@conjurica.com","city":"Omar","state":"NJ"}
    {"index":{"_id":"15"}}
    {"account_number":15,"balance":43456,"firstname":"Bobbie","lastname":"Sexton","age":21,"gender":"M","address":"232 Sedgwick Place","employer":"Zytrex","email":"bobbiesexton@zytrex.com","city":"Hendersonville","state":"CA"}
    {"index":{"_id":"22"}}
    {"account_number":22,"balance":40283,"firstname":"Barrera","lastname":"Terrell","age":23,"gender":"F","address":"292 Orange Street","employer":"Steelfab","email":"barreraterrell@steelfab.com","city":"Bynum","state":"ME"}
    {"index":{"_id":"27"}}
    {"account_number":27,"balance":6176,"firstname":"Meyers","lastname":"Williamson","age":26,"gender":"F","address":"675 Henderson Walk","employer":"Plexia","email":"meyerswilliamson@plexia.com","city":"Richmond","state":"AZ"}
    {"index":{"_id":"34"}}
    {"account_number":34,"balance":35379,"firstname":"Ellison","lastname":"Kim","age":30,"gender":"F","address":"986 Revere Place","employer":"Signity","email":"ellisonkim@signity.com","city":"Sehili","state":"IL"}
    {"index":{"_id":"39"}}
    {"account_number":39,"balance":38688,"firstname":"Bowers","lastname":"Mendez","age":22,"gender":"F","address":"665 Bennet Court","employer":"Farmage","email":"bowersmendez@farmage.com","city":"Duryea","state":"PA"}
    {"index":{"_id":"41"}}
    {"account_number":41,"balance":36060,"firstname":"Hancock","lastname":"Holden","age":20,"gender":"M","address":"625 Gaylord Drive","employer":"Poochies","email":"hancockholden@poochies.com","city":"Alamo","state":"KS"}
    {"index":{"_id":"46"}}
    {"account_number":46,"balance":12351,"firstname":"Karla","lastname":"Bowman","age":23,"gender":"M","address":"554 Chapel Street","employer":"Undertap","email":"karlabowman@undertap.com","city":"Sylvanite","state":"DC"}
    {"index":{"_id":"53"}}
    {"account_number":53,"balance":28101,"firstname":"Kathryn","lastname":"Payne","age":29,"gender":"F","address":"467 Louis Place","employer":"Katakana","email":"kathrynpayne@katakana.com","city":"Harviell","state":"SD"}
    {"index":{"_id":"58"}}
    {"account_number":58,"balance":31697,"firstname":"Marva","lastname":"Cannon","age":40,"gender":"M","address":"993 Highland Place","employer":"Comcubine","email":"marvacannon@comcubine.com","city":"Orviston","state":"MO"}
    {"index":{"_id":"60"}}
    {"account_number":60,"balance":45955,"firstname":"Maude","lastname":"Casey","age":31,"gender":"F","address":"566 Strauss Street","employer":"Quilch","email":"maudecasey@quilch.com","city":"Enlow","state":"GA"}
    {"index":{"_id":"65"}}
    {"account_number":65,"balance":23282,"firstname":"Leonor","lastname":"Pruitt","age":24,"gender":"M","address":"974 Terrace Place","employer":"Velos","email":"leonorpruitt@velos.com","city":"Devon","state":"WI"}
    {"index":{"_id":"72"}}
    {"account_number":72,"balance":9732,"firstname":"Barlow","lastname":"Rhodes","age":25,"gender":"F","address":"891 Clinton Avenue","employer":"Zialactic","email":"barlowrhodes@zialactic.com","city":"Echo","state":"TN"}
    {"index":{"_id":"77"}}
    {"account_number":77,"balance":5724,"firstname":"Byrd","lastname":"Conley","age":24,"gender":"F","address":"698 Belmont Avenue","employer":"Zidox","email":"byrdconley@zidox.com","city":"Rockbridge","state":"SC"}
    {"index":{"_id":"84"}}
    {"account_number":84,"balance":3001,"firstname":"Hutchinson","lastname":"Newton","age":34,"gender":"F","address":"553 Locust Street","employer":"Zaggles","email":"hutchinsonnewton@zaggles.com","city":"Snyderville","state":"DC"}
    {"index":{"_id":"89"}}
    {"account_number":89,"balance":13263,"firstname":"Mcdowell","lastname":"Bradley","age":28,"gender":"M","address":"960 Howard Alley","employer":"Grok","email":"mcdowellbradley@grok.com","city":"Toftrees","state":"TX"}
    {"index":{"_id":"91"}}
    {"account_number":91,"balance":29799,"firstname":"Vonda","lastname":"Galloway","age":20,"gender":"M","address":"988 Voorhies Avenue","employer":"Illumity","email":"vondagalloway@illumity.com","city":"Holcombe","state":"HI"}
    {"index":{"_id":"96"}}
    {"account_number":96,"balance":15933,"firstname":"Shirley","lastname":"Edwards","age":38,"gender":"M","address":"817 Caton Avenue","employer":"Equitox","email":"shirleyedwards@equitox.com","city":"Nelson","state":"MA"}
    {"index":{"_id":"104"}}
    {"account_number":104,"balance":32619,"firstname":"Casey","lastname":"Roth","age":29,"gender":"M","address":"963 Railroad Avenue","employer":"Hotcakes","email":"caseyroth@hotcakes.com","city":"Davenport","state":"OH"}
    {"index":{"_id":"109"}}
    {"account_number":109,"balance":25812,"firstname":"Gretchen","lastname":"Dawson","age":31,"gender":"M","address":"610 Bethel Loop","employer":"Tetak","email":"gretchendawson@tetak.com","city":"Hailesboro","state":"CO"}
    {"index":{"_id":"111"}}
    {"account_number":111,"balance":1481,"firstname":"Traci","lastname":"Allison","age":35,"gender":"M","address":"922 Bryant Street","employer":"Enjola","email":"traciallison@enjola.com","city":"Robinette","state":"OR"}
    {"index":{"_id":"116"}}
    {"account_number":116,"balance":21335,"firstname":"Hobbs","lastname":"Wright","age":24,"gender":"M","address":"965 Temple Court","employer":"Netbook","email":"hobbswright@netbook.com","city":"Strong","state":"CA"}
    {"index":{"_id":"123"}}
    {"account_number":123,"balance":3079,"firstname":"Cleo","lastname":"Beach","age":27,"gender":"F","address":"653 Haring Street","employer":"Proxsoft","email":"cleobeach@proxsoft.com","city":"Greensburg","state":"ME"}
    {"index":{"_id":"128"}}
    {"account_number":128,"balance":3556,"firstname":"Mack","lastname":"Bullock","age":34,"gender":"F","address":"462 Ingraham Street","employer":"Terascape","email":"mackbullock@terascape.com","city":"Eureka","state":"PA"}
    {"index":{"_id":"130"}}
    {"account_number":130,"balance":24171,"firstname":"Roxie","lastname":"Cantu","age":33,"gender":"M","address":"841 Catherine Street","employer":"Skybold","email":"roxiecantu@skybold.com","city":"Deputy","state":"NE"}
    {"index":{"_id":"135"}}
    {"account_number":135,"balance":24885,"firstname":"Stevenson","lastname":"Crosby","age":40,"gender":"F","address":"473 Boardwalk ","employer":"Accel","email":"stevensoncrosby@accel.com","city":"Norris","state":"OK"}
    {"index":{"_id":"142"}}
    {"account_number":142,"balance":4544,"firstname":"Vang","lastname":"Hughes","age":27,"gender":"M","address":"357 Landis Court","employer":"Bolax","email":"vanghughes@bolax.com","city":"Emerald","state":"WY"}
    {"index":{"_id":"147"}}
    {"account_number":147,"balance":35921,"firstname":"Charmaine","lastname":"Whitney","age":28,"gender":"F","address":"484 Seton Place","employer":"Comveyer","email":"charmainewhitney@comveyer.com","city":"Dexter","state":"DC"}
    {"index":{"_id":"154"}}
    {"account_number":154,"balance":40945,"firstname":"Burns","lastname":"Solis","age":31,"gender":"M","address":"274 Lorraine Street","employer":"Rodemco","email":"burnssolis@rodemco.com","city":"Ballico","state":"WI"}
    {"index":{"_id":"159"}}
    {"account_number":159,"balance":1696,"firstname":"Alvarez","lastname":"Mack","age":22,"gender":"F","address":"897 Manor Court","employer":"Snorus","email":"alvarezmack@snorus.com","city":"Rosedale","state":"CA"}
    {"index":{"_id":"161"}}
    {"account_number":161,"balance":4659,"firstname":"Doreen","lastname":"Randall","age":37,"gender":"F","address":"178 Court Street","employer":"Calcula","email":"doreenrandall@calcula.com","city":"Belmont","state":"TX"}
    {"index":{"_id":"166"}}
    {"account_number":166,"balance":33847,"firstname":"Rutledge","lastname":"Rivas","age":23,"gender":"M","address":"352 Verona Street","employer":"Virxo","email":"rutledgerivas@virxo.com","city":"Brandermill","state":"NE"}
    {"index":{"_id":"173"}}
    {"account_number":173,"balance":5989,"firstname":"Whitley","lastname":"Blevins","age":32,"gender":"M","address":"127 Brooklyn Avenue","employer":"Pawnagra","email":"whitleyblevins@pawnagra.com","city":"Rodanthe","state":"ND"}
    {"index":{"_id":"178"}}
    {"account_number":178,"balance":36735,"firstname":"Clements","lastname":"Finley","age":39,"gender":"F","address":"270 Story Court","employer":"Imaginart","email":"clementsfinley@imaginart.com","city":"Lookingglass","state":"MN"}
    {"index":{"_id":"180"}}
    {"account_number":180,"balance":34236,"firstname":"Ursula","lastname":"Goodman","age":32,"gender":"F","address":"414 Clinton Street","employer":"Earthmark","email":"ursulagoodman@earthmark.com","city":"Rote","state":"AR"}
    {"index":{"_id":"185"}}
    {"account_number":185,"balance":43532,"firstname":"Laurel","lastname":"Cline","age":40,"gender":"M","address":"788 Fenimore Street","employer":"Prismatic","email":"laurelcline@prismatic.com","city":"Frank","state":"UT"}
    {"index":{"_id":"192"}}
    {"account_number":192,"balance":23508,"firstname":"Ramsey","lastname":"Carr","age":31,"gender":"F","address":"209 Williamsburg Street","employer":"Strezzo","email":"ramseycarr@strezzo.com","city":"Grapeview","state":"NM"}
    {"index":{"_id":"197"}}
    {"account_number":197,"balance":17246,"firstname":"Sweet","lastname":"Sanders","age":33,"gender":"F","address":"712 Homecrest Court","employer":"Isosure","email":"sweetsanders@isosure.com","city":"Sheatown","state":"VT"}
    {"index":{"_id":"200"}}
    {"account_number":200,"balance":26210,"firstname":"Teri","lastname":"Hester","age":39,"gender":"M","address":"653 Abbey Court","employer":"Electonic","email":"terihester@electonic.com","city":"Martell","state":"MD"}
    {"index":{"_id":"205"}}
    {"account_number":205,"balance":45493,"firstname":"Johnson","lastname":"Chang","age":28,"gender":"F","address":"331 John Street","employer":"Gleamink","email":"johnsonchang@gleamink.com","city":"Sultana","state":"KS"}
    {"index":{"_id":"212"}}
    {"account_number":212,"balance":10299,"firstname":"Marisol","lastname":"Fischer","age":39,"gender":"M","address":"362 Prince Street","employer":"Autograte","email":"marisolfischer@autograte.com","city":"Oley","state":"SC"}
    {"index":{"_id":"217"}}
    {"account_number":217,"balance":33730,"firstname":"Sally","lastname":"Mccoy","age":38,"gender":"F","address":"854 Corbin Place","employer":"Omnigog","email":"sallymccoy@omnigog.com","city":"Escondida","state":"FL"}
    {"index":{"_id":"224"}}
    {"account_number":224,"balance":42708,"firstname":"Billie","lastname":"Nixon","age":28,"gender":"F","address":"241 Kaufman Place","employer":"Xanide","email":"billienixon@xanide.com","city":"Chapin","state":"NY"}
    {"index":{"_id":"229"}}
    {"account_number":229,"balance":2740,"firstname":"Jana","lastname":"Hensley","age":30,"gender":"M","address":"176 Erasmus Street","employer":"Isotrack","email":"janahensley@isotrack.com","city":"Caledonia","state":"ME"}
    {"index":{"_id":"231"}}
    {"account_number":231,"balance":46180,"firstname":"Essie","lastname":"Clarke","age":34,"gender":"F","address":"308 Harbor Lane","employer":"Pharmacon","email":"essieclarke@pharmacon.com","city":"Fillmore","state":"MS"}
    {"index":{"_id":"236"}}
    {"account_number":236,"balance":41200,"firstname":"Suzanne","lastname":"Bird","age":39,"gender":"F","address":"219 Luquer Street","employer":"Imant","email":"suzannebird@imant.com","city":"Bainbridge","state":"NY"}
    {"index":{"_id":"243"}}
    {"account_number":243,"balance":29902,"firstname":"Evangelina","lastname":"Perez","age":20,"gender":"M","address":"787 Joval Court","employer":"Keengen","email":"evangelinaperez@keengen.com","city":"Mulberry","state":"SD"}
    {"index":{"_id":"248"}}
    {"account_number":248,"balance":49989,"firstname":"West","lastname":"England","age":36,"gender":"M","address":"717 Hendrickson Place","employer":"Obliq","email":"westengland@obliq.com","city":"Maury","state":"WA"}
    {"index":{"_id":"250"}}
    {"account_number":250,"balance":27893,"firstname":"Earlene","lastname":"Ellis","age":39,"gender":"F","address":"512 Bay Street","employer":"Codact","email":"earleneellis@codact.com","city":"Sunwest","state":"GA"}
    {"index":{"_id":"255"}}
    {"account_number":255,"balance":49339,"firstname":"Iva","lastname":"Rivers","age":38,"gender":"M","address":"470 Rost Place","employer":"Mantrix","email":"ivarivers@mantrix.com","city":"Disautel","state":"MD"}
    {"index":{"_id":"262"}}
    {"account_number":262,"balance":30289,"firstname":"Tameka","lastname":"Levine","age":36,"gender":"F","address":"815 Atlantic Avenue","employer":"Acium","email":"tamekalevine@acium.com","city":"Winchester","state":"SD"}
    {"index":{"_id":"267"}}
    {"account_number":267,"balance":42753,"firstname":"Weeks","lastname":"Castillo","age":21,"gender":"F","address":"526 Holt Court","employer":"Talendula","email":"weekscastillo@talendula.com","city":"Washington","state":"NV"}
    {"index":{"_id":"274"}}
    {"account_number":274,"balance":12104,"firstname":"Frieda","lastname":"House","age":33,"gender":"F","address":"171 Banker Street","employer":"Quonk","email":"friedahouse@quonk.com","city":"Aberdeen","state":"NJ"}
    {"index":{"_id":"279"}}
    {"account_number":279,"balance":15904,"firstname":"Chapman","lastname":"Hart","age":32,"gender":"F","address":"902 Bliss Terrace","employer":"Kongene","email":"chapmanhart@kongene.com","city":"Bradenville","state":"NJ"}
    {"index":{"_id":"281"}}
    {"account_number":281,"balance":39830,"firstname":"Bean","lastname":"Aguirre","age":20,"gender":"F","address":"133 Pilling Street","employer":"Amril","email":"beanaguirre@amril.com","city":"Waterview","state":"TX"}
    {"index":{"_id":"286"}}
    {"account_number":286,"balance":39063,"firstname":"Rosetta","lastname":"Turner","age":35,"gender":"M","address":"169 Jefferson Avenue","employer":"Spacewax","email":"rosettaturner@spacewax.com","city":"Stewart","state":"MO"}
    {"index":{"_id":"293"}}
    {"account_number":293,"balance":29867,"firstname":"Cruz","lastname":"Carver","age":28,"gender":"F","address":"465 Boerum Place","employer":"Vitricomp","email":"cruzcarver@vitricomp.com","city":"Crayne","state":"CO"}
    {"index":{"_id":"298"}}
    {"account_number":298,"balance":34334,"firstname":"Bullock","lastname":"Marsh","age":20,"gender":"M","address":"589 Virginia Place","employer":"Renovize","email":"bullockmarsh@renovize.com","city":"Coinjock","state":"UT"}
    {"index":{"_id":"301"}}
    {"account_number":301,"balance":16782,"firstname":"Minerva","lastname":"Graham","age":35,"gender":"M","address":"532 Harrison Place","employer":"Sureplex","email":"minervagraham@sureplex.com","city":"Belleview","state":"GA"}
    {"index":{"_id":"306"}}
    {"account_number":306,"balance":2171,"firstname":"Hensley","lastname":"Hardin","age":40,"gender":"M","address":"196 Maujer Street","employer":"Neocent","email":"hensleyhardin@neocent.com","city":"Reinerton","state":"HI"}
    {"index":{"_id":"313"}}
    {"account_number":313,"balance":34108,"firstname":"Alston","lastname":"Henderson","age":36,"gender":"F","address":"132 Prescott Place","employer":"Prosure","email":"alstonhenderson@prosure.com","city":"Worton","state":"IA"}
    {"index":{"_id":"318"}}
    {"account_number":318,"balance":8512,"firstname":"Nichole","lastname":"Pearson","age":34,"gender":"F","address":"656 Lacon Court","employer":"Yurture","email":"nicholepearson@yurture.com","city":"Juarez","state":"MO"}
    {"index":{"_id":"320"}}
    {"account_number":320,"balance":34521,"firstname":"Patti","lastname":"Brennan","age":37,"gender":"F","address":"870 Degraw Street","employer":"Cognicode","email":"pattibrennan@cognicode.com","city":"Torboy","state":"FL"}
    {"index":{"_id":"325"}}
    {"account_number":325,"balance":1956,"firstname":"Magdalena","lastname":"Simmons","age":25,"gender":"F","address":"681 Townsend Street","employer":"Geekosis","email":"magdalenasimmons@geekosis.com","city":"Sterling","state":"CA"}
    {"index":{"_id":"332"}}
    {"account_number":332,"balance":37770,"firstname":"Shepherd","lastname":"Davenport","age":28,"gender":"F","address":"586 Montague Terrace","employer":"Ecraze","email":"shepherddavenport@ecraze.com","city":"Accoville","state":"NM"}
    {"index":{"_id":"337"}}
    {"account_number":337,"balance":43432,"firstname":"Monroe","lastname":"Stafford","age":37,"gender":"F","address":"183 Seigel Street","employer":"Centuria","email":"monroestafford@centuria.com","city":"Camino","state":"DE"}
    {"index":{"_id":"344"}}
    {"account_number":344,"balance":42654,"firstname":"Sasha","lastname":"Baxter","age":35,"gender":"F","address":"700 Bedford Place","employer":"Callflex","email":"sashabaxter@callflex.com","city":"Campo","state":"MI"}
    {"index":{"_id":"349"}}
    {"account_number":349,"balance":24180,"firstname":"Allison","lastname":"Fitzpatrick","age":22,"gender":"F","address":"913 Arlington Avenue","employer":"Veraq","email":"allisonfitzpatrick@veraq.com","city":"Marbury","state":"TX"}
    {"index":{"_id":"351"}}
    {"account_number":351,"balance":47089,"firstname":"Hendrix","lastname":"Stephens","age":29,"gender":"M","address":"181 Beaver Street","employer":"Recrisys","email":"hendrixstephens@recrisys.com","city":"Denio","state":"OR"}
    {"index":{"_id":"356"}}
    {"account_number":356,"balance":34540,"firstname":"Lourdes","lastname":"Valdez","age":20,"gender":"F","address":"700 Anchorage Place","employer":"Interloo","email":"lourdesvaldez@interloo.com","city":"Goldfield","state":"OK"}
    {"index":{"_id":"363"}}
    {"account_number":363,"balance":34007,"firstname":"Peggy","lastname":"Bright","age":21,"gender":"M","address":"613 Engert Avenue","employer":"Inventure","email":"peggybright@inventure.com","city":"Chautauqua","state":"ME"}
    {"index":{"_id":"368"}}
    {"account_number":368,"balance":23535,"firstname":"Hooper","lastname":"Tyson","age":39,"gender":"M","address":"892 Taaffe Place","employer":"Zaggle","email":"hoopertyson@zaggle.com","city":"Nutrioso","state":"ME"}
    {"index":{"_id":"370"}}
    {"account_number":370,"balance":28499,"firstname":"Oneill","lastname":"Carney","age":25,"gender":"F","address":"773 Adelphi Street","employer":"Bedder","email":"oneillcarney@bedder.com","city":"Yorklyn","state":"FL"}
    {"index":{"_id":"375"}}
    {"account_number":375,"balance":23860,"firstname":"Phoebe","lastname":"Patton","age":25,"gender":"M","address":"564 Hale Avenue","employer":"Xoggle","email":"phoebepatton@xoggle.com","city":"Brule","state":"NM"}
    {"index":{"_id":"382"}}
    {"account_number":382,"balance":42061,"firstname":"Finley","lastname":"Singleton","age":37,"gender":"F","address":"407 Clay Street","employer":"Quarex","email":"finleysingleton@quarex.com","city":"Bedias","state":"LA"}
    {"index":{"_id":"387"}}
    {"account_number":387,"balance":35916,"firstname":"April","lastname":"Hill","age":29,"gender":"M","address":"818 Bayard Street","employer":"Kengen","email":"aprilhill@kengen.com","city":"Chloride","state":"NC"}
    {"index":{"_id":"394"}}
    {"account_number":394,"balance":6121,"firstname":"Lorrie","lastname":"Nunez","age":38,"gender":"M","address":"221 Ralph Avenue","employer":"Bullzone","email":"lorrienunez@bullzone.com","city":"Longoria","state":"ID"}
    {"index":{"_id":"399"}}
    {"account_number":399,"balance":32587,"firstname":"Carmela","lastname":"Franks","age":23,"gender":"M","address":"617 Dewey Place","employer":"Zensure","email":"carmelafranks@zensure.com","city":"Sanders","state":"DC"}
    {"index":{"_id":"402"}}
    {"account_number":402,"balance":1282,"firstname":"Pacheco","lastname":"Rosales","age":32,"gender":"M","address":"538 Pershing Loop","employer":"Circum","email":"pachecorosales@circum.com","city":"Elbert","state":"ID"}
    {"index":{"_id":"407"}}
    {"account_number":407,"balance":36417,"firstname":"Gilda","lastname":"Jacobson","age":29,"gender":"F","address":"883 Loring Avenue","employer":"Comveyor","email":"gildajacobson@comveyor.com","city":"Topaz","state":"NH"}
    {"index":{"_id":"414"}}
    {"account_number":414,"balance":17506,"firstname":"Conway","lastname":"Daugherty","age":37,"gender":"F","address":"643 Kermit Place","employer":"Lyria","email":"conwaydaugherty@lyria.com","city":"Vaughn","state":"NV"}
    {"index":{"_id":"419"}}
    {"account_number":419,"balance":34847,"firstname":"Helen","lastname":"Montoya","age":29,"gender":"F","address":"736 Kingsland Avenue","employer":"Hairport","email":"helenmontoya@hairport.com","city":"Edinburg","state":"NE"}
    {"index":{"_id":"421"}}
    {"account_number":421,"balance":46868,"firstname":"Tamika","lastname":"Mccall","age":27,"gender":"F","address":"764 Bragg Court","employer":"Eventix","email":"tamikamccall@eventix.com","city":"Tivoli","state":"RI"}
    {"index":{"_id":"426"}}
    {"account_number":426,"balance":4499,"firstname":"Julie","lastname":"Parsons","age":31,"gender":"M","address":"768 Keap Street","employer":"Goko","email":"julieparsons@goko.com","city":"Coldiron","state":"VA"}
    {"index":{"_id":"433"}}
    {"account_number":433,"balance":19266,"firstname":"Wilkinson","lastname":"Flowers","age":39,"gender":"M","address":"154 Douglass Street","employer":"Xsports","email":"wilkinsonflowers@xsports.com","city":"Coultervillle","state":"MN"}
    {"index":{"_id":"438"}}
    {"account_number":438,"balance":16367,"firstname":"Walter","lastname":"Velez","age":27,"gender":"F","address":"931 Farragut Road","employer":"Virva","email":"waltervelez@virva.com","city":"Tyro","state":"WV"}
    {"index":{"_id":"440"}}
    {"account_number":440,"balance":41590,"firstname":"Ray","lastname":"Wiley","age":31,"gender":"F","address":"102 Barwell Terrace","employer":"Polaria","email":"raywiley@polaria.com","city":"Hardyville","state":"IA"}
    {"index":{"_id":"445"}}
    {"account_number":445,"balance":41178,"firstname":"Rodriguez","lastname":"Macias","age":34,"gender":"M","address":"164 Boerum Street","employer":"Xylar","email":"rodriguezmacias@xylar.com","city":"Riner","state":"AL"}
    {"index":{"_id":"452"}}
    {"account_number":452,"balance":3589,"firstname":"Blackwell","lastname":"Delaney","age":39,"gender":"F","address":"443 Sackett Street","employer":"Imkan","email":"blackwelldelaney@imkan.com","city":"Gasquet","state":"DC"}
    {"index":{"_id":"457"}}
    {"account_number":457,"balance":14057,"firstname":"Bush","lastname":"Gordon","age":34,"gender":"M","address":"975 Dakota Place","employer":"Softmicro","email":"bushgordon@softmicro.com","city":"Chemung","state":"PA"}
    {"index":{"_id":"464"}}
    {"account_number":464,"balance":20504,"firstname":"Cobb","lastname":"Humphrey","age":21,"gender":"M","address":"823 Sunnyside Avenue","employer":"Apexia","email":"cobbhumphrey@apexia.com","city":"Wintersburg","state":"NY"}
    {"index":{"_id":"469"}}
    {"account_number":469,"balance":26509,"firstname":"Marci","lastname":"Shepherd","age":26,"gender":"M","address":"565 Hall Street","employer":"Shadease","email":"marcishepherd@shadease.com","city":"Springhill","state":"IL"}
    {"index":{"_id":"471"}}
    {"account_number":471,"balance":7629,"firstname":"Juana","lastname":"Silva","age":36,"gender":"M","address":"249 Amity Street","employer":"Artworlds","email":"juanasilva@artworlds.com","city":"Norfolk","state":"TX"}
    {"index":{"_id":"476"}}
    {"account_number":476,"balance":33386,"firstname":"Silva","lastname":"Marks","age":31,"gender":"F","address":"183 Eldert Street","employer":"Medifax","email":"silvamarks@medifax.com","city":"Hachita","state":"RI"}
    {"index":{"_id":"483"}}
    {"account_number":483,"balance":6344,"firstname":"Kelley","lastname":"Harper","age":29,"gender":"M","address":"758 Preston Court","employer":"Xyqag","email":"kelleyharper@xyqag.com","city":"Healy","state":"IA"}
    {"index":{"_id":"488"}}
    {"account_number":488,"balance":6289,"firstname":"Wilma","lastname":"Hopkins","age":38,"gender":"M","address":"428 Lee Avenue","employer":"Entality","email":"wilmahopkins@entality.com","city":"Englevale","state":"WI"}
    {"index":{"_id":"490"}}
    {"account_number":490,"balance":1447,"firstname":"Strong","lastname":"Hendrix","age":26,"gender":"F","address":"134 Beach Place","employer":"Duoflex","email":"stronghendrix@duoflex.com","city":"Allentown","state":"ND"}
    {"index":{"_id":"495"}}
    {"account_number":495,"balance":13478,"firstname":"Abigail","lastname":"Nichols","age":40,"gender":"F","address":"887 President Street","employer":"Enquility","email":"abigailnichols@enquility.com","city":"Bagtown","state":"NM"}
    {"index":{"_id":"503"}}
    {"account_number":503,"balance":42649,"firstname":"Leta","lastname":"Stout","age":39,"gender":"F","address":"518 Bowery Street","employer":"Pivitol","email":"letastout@pivitol.com","city":"Boonville","state":"ND"}
    {"index":{"_id":"508"}}
    {"account_number":508,"balance":41300,"firstname":"Lawrence","lastname":"Mathews","age":27,"gender":"F","address":"987 Rose Street","employer":"Deviltoe","email":"lawrencemathews@deviltoe.com","city":"Woodburn","state":"FL"}
    {"index":{"_id":"510"}}
    {"account_number":510,"balance":48504,"firstname":"Petty","lastname":"Sykes","age":28,"gender":"M","address":"566 Village Road","employer":"Nebulean","email":"pettysykes@nebulean.com","city":"Wedgewood","state":"MO"}
    {"index":{"_id":"515"}}
    {"account_number":515,"balance":18531,"firstname":"Lott","lastname":"Keller","age":27,"gender":"M","address":"827 Miami Court","employer":"Translink","email":"lottkeller@translink.com","city":"Gila","state":"TX"}
    {"index":{"_id":"522"}}
    {"account_number":522,"balance":19879,"firstname":"Faulkner","lastname":"Garrett","age":29,"gender":"F","address":"396 Grove Place","employer":"Pigzart","email":"faulknergarrett@pigzart.com","city":"Felt","state":"AR"}
    {"index":{"_id":"527"}}
    {"account_number":527,"balance":2028,"firstname":"Carver","lastname":"Peters","age":35,"gender":"M","address":"816 Victor Road","employer":"Housedown","email":"carverpeters@housedown.com","city":"Nadine","state":"MD"}
    {"index":{"_id":"534"}}
    {"account_number":534,"balance":20470,"firstname":"Cristina","lastname":"Russo","age":25,"gender":"F","address":"500 Highlawn Avenue","employer":"Cyclonica","email":"cristinarusso@cyclonica.com","city":"Gorst","state":"KS"}
    {"index":{"_id":"539"}}
    {"account_number":539,"balance":24560,"firstname":"Tami","lastname":"Maddox","age":23,"gender":"F","address":"741 Pineapple Street","employer":"Accidency","email":"tamimaddox@accidency.com","city":"Kennedyville","state":"OH"}
    {"index":{"_id":"541"}}
    {"account_number":541,"balance":42915,"firstname":"Logan","lastname":"Burke","age":32,"gender":"M","address":"904 Clarendon Road","employer":"Overplex","email":"loganburke@overplex.com","city":"Johnsonburg","state":"OH"}
    {"index":{"_id":"546"}}
    {"account_number":546,"balance":43242,"firstname":"Bernice","lastname":"Sims","age":33,"gender":"M","address":"382 Columbia Street","employer":"Verbus","email":"bernicesims@verbus.com","city":"Sena","state":"KY"}
    {"index":{"_id":"553"}}
    {"account_number":553,"balance":28390,"firstname":"Aimee","lastname":"Cohen","age":28,"gender":"M","address":"396 Lafayette Avenue","employer":"Eplode","email":"aimeecohen@eplode.com","city":"Thatcher","state":"NJ"}
    {"index":{"_id":"558"}}
    {"account_number":558,"balance":8922,"firstname":"Horne","lastname":"Valenzuela","age":20,"gender":"F","address":"979 Kensington Street","employer":"Isoternia","email":"hornevalenzuela@isoternia.com","city":"Greenbush","state":"NC"}
    {"index":{"_id":"560"}}
    {"account_number":560,"balance":24514,"firstname":"Felecia","lastname":"Oneill","age":26,"gender":"M","address":"995 Autumn Avenue","employer":"Mediot","email":"feleciaoneill@mediot.com","city":"Joppa","state":"IN"}
    {"index":{"_id":"565"}}
    {"account_number":565,"balance":15197,"firstname":"Taylor","lastname":"Ingram","age":37,"gender":"F","address":"113 Will Place","employer":"Lyrichord","email":"tayloringram@lyrichord.com","city":"Collins","state":"ME"}
    {"index":{"_id":"572"}}
    {"account_number":572,"balance":49355,"firstname":"Therese","lastname":"Espinoza","age":20,"gender":"M","address":"994 Chester Court","employer":"Gonkle","email":"thereseespinoza@gonkle.com","city":"Hayes","state":"UT"}
    {"index":{"_id":"577"}}
    {"account_number":577,"balance":21398,"firstname":"Gilbert","lastname":"Serrano","age":38,"gender":"F","address":"294 Troutman Street","employer":"Senmao","email":"gilbertserrano@senmao.com","city":"Greer","state":"MT"}
    {"index":{"_id":"584"}}
    {"account_number":584,"balance":5346,"firstname":"Pearson","lastname":"Bryant","age":40,"gender":"F","address":"971 Heyward Street","employer":"Anacho","email":"pearsonbryant@anacho.com","city":"Bluffview","state":"MN"}
    {"index":{"_id":"589"}}
    {"account_number":589,"balance":33260,"firstname":"Ericka","lastname":"Cote","age":39,"gender":"F","address":"425 Bath Avenue","employer":"Venoflex","email":"erickacote@venoflex.com","city":"Blue","state":"CT"}
    {"index":{"_id":"591"}}
    {"account_number":591,"balance":48997,"firstname":"Rivers","lastname":"Macdonald","age":34,"gender":"F","address":"919 Johnson Street","employer":"Ziore","email":"riversmacdonald@ziore.com","city":"Townsend","state":"IL"}
    {"index":{"_id":"596"}}
    {"account_number":596,"balance":4063,"firstname":"Letitia","lastname":"Walker","age":26,"gender":"F","address":"963 Vanderveer Place","employer":"Zizzle","email":"letitiawalker@zizzle.com","city":"Rossmore","state":"ID"}
    {"index":{"_id":"604"}}
    {"account_number":604,"balance":10675,"firstname":"Isabel","lastname":"Gilliam","age":23,"gender":"M","address":"854 Broadway ","employer":"Zenthall","email":"isabelgilliam@zenthall.com","city":"Ventress","state":"WI"}
    {"index":{"_id":"609"}}
    {"account_number":609,"balance":28586,"firstname":"Montgomery","lastname":"Washington","age":30,"gender":"M","address":"169 Schroeders Avenue","employer":"Kongle","email":"montgomerywashington@kongle.com","city":"Croom","state":"AZ"}
    {"index":{"_id":"611"}}
    {"account_number":611,"balance":17528,"firstname":"Katherine","lastname":"Prince","age":33,"gender":"F","address":"705 Elm Avenue","employer":"Zillacon","email":"katherineprince@zillacon.com","city":"Rew","state":"MI"}
    {"index":{"_id":"616"}}
    {"account_number":616,"balance":25276,"firstname":"Jessie","lastname":"Mayer","age":35,"gender":"F","address":"683 Chester Avenue","employer":"Emtrak","email":"jessiemayer@emtrak.com","city":"Marysville","state":"HI"}
    {"index":{"_id":"623"}}
    {"account_number":623,"balance":20514,"firstname":"Rose","lastname":"Combs","age":32,"gender":"F","address":"312 Grimes Road","employer":"Aquamate","email":"rosecombs@aquamate.com","city":"Fostoria","state":"OH"}
    {"index":{"_id":"628"}}
    {"account_number":628,"balance":42736,"firstname":"Buckner","lastname":"Chen","age":37,"gender":"M","address":"863 Rugby Road","employer":"Jamnation","email":"bucknerchen@jamnation.com","city":"Camas","state":"TX"}
    {"index":{"_id":"630"}}
    {"account_number":630,"balance":46060,"firstname":"Leanne","lastname":"Jones","age":31,"gender":"M","address":"451 Bayview Avenue","employer":"Wazzu","email":"leannejones@wazzu.com","city":"Kylertown","state":"OK"}
    {"index":{"_id":"635"}}
    {"account_number":635,"balance":44705,"firstname":"Norman","lastname":"Gilmore","age":33,"gender":"M","address":"330 Gates Avenue","employer":"Comfirm","email":"normangilmore@comfirm.com","city":"Riceville","state":"TN"}
    {"index":{"_id":"642"}}
    {"account_number":642,"balance":32852,"firstname":"Reyna","lastname":"Harris","age":35,"gender":"M","address":"305 Powell Street","employer":"Bedlam","email":"reynaharris@bedlam.com","city":"Florence","state":"KS"}
    {"index":{"_id":"647"}}
    {"account_number":647,"balance":10147,"firstname":"Annabelle","lastname":"Velazquez","age":30,"gender":"M","address":"299 Kensington Walk","employer":"Sealoud","email":"annabellevelazquez@sealoud.com","city":"Soudan","state":"ME"}
    {"index":{"_id":"654"}}
    {"account_number":654,"balance":38695,"firstname":"Armstrong","lastname":"Frazier","age":25,"gender":"M","address":"899 Seeley Street","employer":"Zensor","email":"armstrongfrazier@zensor.com","city":"Cherokee","state":"UT"}
    {"index":{"_id":"659"}}
    {"account_number":659,"balance":29648,"firstname":"Dorsey","lastname":"Sosa","age":40,"gender":"M","address":"270 Aberdeen Street","employer":"Daycore","email":"dorseysosa@daycore.com","city":"Chamberino","state":"SC"}
    {"index":{"_id":"661"}}
    {"account_number":661,"balance":3679,"firstname":"Joanne","lastname":"Spencer","age":39,"gender":"F","address":"910 Montauk Avenue","employer":"Visalia","email":"joannespencer@visalia.com","city":"Valmy","state":"NH"}
    {"index":{"_id":"666"}}
    {"account_number":666,"balance":13880,"firstname":"Mcguire","lastname":"Lloyd","age":40,"gender":"F","address":"658 Just Court","employer":"Centrexin","email":"mcguirelloyd@centrexin.com","city":"Warren","state":"MT"}
    {"index":{"_id":"673"}}
    {"account_number":673,"balance":11303,"firstname":"Mcdaniel","lastname":"Harrell","age":33,"gender":"M","address":"565 Montgomery Place","employer":"Eyeris","email":"mcdanielharrell@eyeris.com","city":"Garnet","state":"NV"}
    {"index":{"_id":"678"}}
    {"account_number":678,"balance":43663,"firstname":"Ruby","lastname":"Shaffer","age":28,"gender":"M","address":"350 Clark Street","employer":"Comtrail","email":"rubyshaffer@comtrail.com","city":"Aurora","state":"MA"}
    {"index":{"_id":"680"}}
    {"account_number":680,"balance":31561,"firstname":"Melton","lastname":"Camacho","age":32,"gender":"F","address":"771 Montana Place","employer":"Insuresys","email":"meltoncamacho@insuresys.com","city":"Sparkill","state":"IN"}
    {"index":{"_id":"685"}}
    {"account_number":685,"balance":22249,"firstname":"Yesenia","lastname":"Rowland","age":24,"gender":"F","address":"193 Dekalb Avenue","employer":"Coriander","email":"yeseniarowland@coriander.com","city":"Lupton","state":"NC"}
    {"index":{"_id":"692"}}
    {"account_number":692,"balance":10435,"firstname":"Haney","lastname":"Barlow","age":21,"gender":"F","address":"267 Lenox Road","employer":"Egypto","email":"haneybarlow@egypto.com","city":"Detroit","state":"IN"}
    {"index":{"_id":"697"}}
    {"account_number":697,"balance":48745,"firstname":"Mallory","lastname":"Emerson","age":24,"gender":"F","address":"318 Dunne Court","employer":"Exoplode","email":"malloryemerson@exoplode.com","city":"Montura","state":"LA"}
    {"index":{"_id":"700"}}
    {"account_number":700,"balance":19164,"firstname":"Patel","lastname":"Durham","age":21,"gender":"F","address":"440 King Street","employer":"Icology","email":"pateldurham@icology.com","city":"Mammoth","state":"IL"}
    {"index":{"_id":"705"}}
    {"account_number":705,"balance":28415,"firstname":"Krystal","lastname":"Cross","age":22,"gender":"M","address":"604 Drew Street","employer":"Tubesys","email":"krystalcross@tubesys.com","city":"Dalton","state":"MO"}
    {"index":{"_id":"712"}}
    {"account_number":712,"balance":12459,"firstname":"Butler","lastname":"Alston","age":37,"gender":"M","address":"486 Hemlock Street","employer":"Quordate","email":"butleralston@quordate.com","city":"Verdi","state":"MS"}
    {"index":{"_id":"717"}}
    {"account_number":717,"balance":29270,"firstname":"Erickson","lastname":"Mcdonald","age":31,"gender":"M","address":"873 Franklin Street","employer":"Exotechno","email":"ericksonmcdonald@exotechno.com","city":"Jessie","state":"MS"}
    {"index":{"_id":"724"}}
    {"account_number":724,"balance":12548,"firstname":"Hopper","lastname":"Peck","age":31,"gender":"M","address":"849 Hendrickson Street","employer":"Uxmox","email":"hopperpeck@uxmox.com","city":"Faxon","state":"UT"}
    {"index":{"_id":"729"}}
    {"account_number":729,"balance":41812,"firstname":"Katy","lastname":"Rivera","age":36,"gender":"F","address":"791 Olive Street","employer":"Blurrybus","email":"katyrivera@blurrybus.com","city":"Innsbrook","state":"MI"}
    {"index":{"_id":"731"}}
    {"account_number":731,"balance":4994,"firstname":"Lorene","lastname":"Weiss","age":35,"gender":"M","address":"990 Ocean Court","employer":"Comvoy","email":"loreneweiss@comvoy.com","city":"Lavalette","state":"WI"}
    {"index":{"_id":"736"}}
    {"account_number":736,"balance":28677,"firstname":"Rogers","lastname":"Mcmahon","age":21,"gender":"F","address":"423 Cameron Court","employer":"Brainclip","email":"rogersmcmahon@brainclip.com","city":"Saddlebrooke","state":"FL"}
    {"index":{"_id":"743"}}
    {"account_number":743,"balance":14077,"firstname":"Susana","lastname":"Moody","age":23,"gender":"M","address":"842 Fountain Avenue","employer":"Bitrex","email":"susanamoody@bitrex.com","city":"Temperanceville","state":"TN"}
    {"index":{"_id":"748"}}
    {"account_number":748,"balance":38060,"firstname":"Ford","lastname":"Branch","age":25,"gender":"M","address":"926 Cypress Avenue","employer":"Buzzness","email":"fordbranch@buzzness.com","city":"Beason","state":"DC"}
    {"index":{"_id":"750"}}
    {"account_number":750,"balance":40481,"firstname":"Cherie","lastname":"Brooks","age":20,"gender":"F","address":"601 Woodhull Street","employer":"Kaggle","email":"cheriebrooks@kaggle.com","city":"Groton","state":"MA"}
    {"index":{"_id":"755"}}
    {"account_number":755,"balance":43878,"firstname":"Bartlett","lastname":"Conway","age":22,"gender":"M","address":"453 Times Placez","employer":"Konnect","email":"bartlettconway@konnect.com","city":"Belva","state":"VT"}
    {"index":{"_id":"762"}}
    {"account_number":762,"balance":10291,"firstname":"Amanda","lastname":"Head","age":20,"gender":"F","address":"990 Ocean Parkway","employer":"Zentury","email":"amandahead@zentury.com","city":"Hegins","state":"AR"}
    {"index":{"_id":"767"}}
    {"account_number":767,"balance":26220,"firstname":"Anthony","lastname":"Sutton","age":27,"gender":"F","address":"179 Fayette Street","employer":"Xiix","email":"anthonysutton@xiix.com","city":"Iberia","state":"TN"}
    {"index":{"_id":"774"}}
    {"account_number":774,"balance":35287,"firstname":"Lynnette","lastname":"Alvarez","age":38,"gender":"F","address":"991 Brightwater Avenue","employer":"Gink","email":"lynnettealvarez@gink.com","city":"Leola","state":"NC"}
    {"index":{"_id":"779"}}
    {"account_number":779,"balance":40983,"firstname":"Maggie","lastname":"Pace","age":32,"gender":"F","address":"104 Harbor Court","employer":"Bulljuice","email":"maggiepace@bulljuice.com","city":"Floris","state":"MA"}
    {"index":{"_id":"781"}}
    {"account_number":781,"balance":29961,"firstname":"Sanford","lastname":"Mullen","age":26,"gender":"F","address":"879 Dover Street","employer":"Zanity","email":"sanfordmullen@zanity.com","city":"Martinez","state":"TX"}
    {"index":{"_id":"786"}}
    {"account_number":786,"balance":3024,"firstname":"Rene","lastname":"Vang","age":33,"gender":"M","address":"506 Randolph Street","employer":"Isopop","email":"renevang@isopop.com","city":"Vienna","state":"NJ"}
    {"index":{"_id":"793"}}
    {"account_number":793,"balance":16911,"firstname":"Alford","lastname":"Compton","age":36,"gender":"M","address":"186 Veronica Place","employer":"Zyple","email":"alfordcompton@zyple.com","city":"Sugartown","state":"AK"}
    {"index":{"_id":"798"}}
    {"account_number":798,"balance":3165,"firstname":"Catherine","lastname":"Ward","age":30,"gender":"F","address":"325 Burnett Street","employer":"Dreamia","email":"catherineward@dreamia.com","city":"Glenbrook","state":"SD"}
    {"index":{"_id":"801"}}
    {"account_number":801,"balance":14954,"firstname":"Molly","lastname":"Maldonado","age":37,"gender":"M","address":"518 Maple Avenue","employer":"Straloy","email":"mollymaldonado@straloy.com","city":"Hebron","state":"WI"}
    {"index":{"_id":"806"}}
    {"account_number":806,"balance":36492,"firstname":"Carson","lastname":"Riddle","age":31,"gender":"M","address":"984 Lois Avenue","employer":"Terrago","email":"carsonriddle@terrago.com","city":"Leland","state":"MN"}
    {"index":{"_id":"813"}}
    {"account_number":813,"balance":30833,"firstname":"Ebony","lastname":"Bishop","age":20,"gender":"M","address":"487 Ridge Court","employer":"Optique","email":"ebonybishop@optique.com","city":"Fairmount","state":"WA"}
    {"index":{"_id":"818"}}
    {"account_number":818,"balance":24433,"firstname":"Espinoza","lastname":"Petersen","age":26,"gender":"M","address":"641 Glenwood Road","employer":"Futurity","email":"espinozapetersen@futurity.com","city":"Floriston","state":"MD"}
    {"index":{"_id":"820"}}
    {"account_number":820,"balance":1011,"firstname":"Shepard","lastname":"Ramsey","age":24,"gender":"F","address":"806 Village Court","employer":"Mantro","email":"shepardramsey@mantro.com","city":"Tibbie","state":"NV"}
    {"index":{"_id":"825"}}
    {"account_number":825,"balance":49000,"firstname":"Terra","lastname":"Witt","age":21,"gender":"F","address":"590 Conway Street","employer":"Insectus","email":"terrawitt@insectus.com","city":"Forbestown","state":"AR"}
    {"index":{"_id":"832"}}
    {"account_number":832,"balance":8582,"firstname":"Laura","lastname":"Gibbs","age":39,"gender":"F","address":"511 Osborn Street","employer":"Corepan","email":"lauragibbs@corepan.com","city":"Worcester","state":"KS"}
    {"index":{"_id":"837"}}
    {"account_number":837,"balance":14485,"firstname":"Amy","lastname":"Villarreal","age":35,"gender":"M","address":"381 Stillwell Place","employer":"Fleetmix","email":"amyvillarreal@fleetmix.com","city":"Sanford","state":"IA"}
    {"index":{"_id":"844"}}
    {"account_number":844,"balance":26840,"firstname":"Jill","lastname":"David","age":31,"gender":"M","address":"346 Legion Street","employer":"Zytrax","email":"jilldavid@zytrax.com","city":"Saticoy","state":"SC"}
    {"index":{"_id":"849"}}
    {"account_number":849,"balance":16200,"firstname":"Barry","lastname":"Chapman","age":26,"gender":"M","address":"931 Dekoven Court","employer":"Darwinium","email":"barrychapman@darwinium.com","city":"Whitestone","state":"WY"}
    {"index":{"_id":"851"}}
    {"account_number":851,"balance":22026,"firstname":"Henderson","lastname":"Price","age":33,"gender":"F","address":"530 Hausman Street","employer":"Plutorque","email":"hendersonprice@plutorque.com","city":"Brutus","state":"RI"}
    {"index":{"_id":"856"}}
    {"account_number":856,"balance":27583,"firstname":"Alissa","lastname":"Knox","age":25,"gender":"M","address":"258 Empire Boulevard","employer":"Geologix","email":"alissaknox@geologix.com","city":"Hartsville/Hartley","state":"MN"}
    {"index":{"_id":"863"}}
    {"account_number":863,"balance":23165,"firstname":"Melendez","lastname":"Fernandez","age":40,"gender":"M","address":"661 Johnson Avenue","employer":"Vixo","email":"melendezfernandez@vixo.com","city":"Farmers","state":"IL"}
    {"index":{"_id":"868"}}
    {"account_number":868,"balance":27624,"firstname":"Polly","lastname":"Barron","age":22,"gender":"M","address":"129 Frank Court","employer":"Geofarm","email":"pollybarron@geofarm.com","city":"Loyalhanna","state":"ND"}
    {"index":{"_id":"870"}}
    {"account_number":870,"balance":43882,"firstname":"Goff","lastname":"Phelps","age":21,"gender":"M","address":"164 Montague Street","employer":"Digigen","email":"goffphelps@digigen.com","city":"Weedville","state":"IL"}
    {"index":{"_id":"875"}}
    {"account_number":875,"balance":19655,"firstname":"Mercer","lastname":"Pratt","age":24,"gender":"M","address":"608 Perry Place","employer":"Twiggery","email":"mercerpratt@twiggery.com","city":"Eggertsville","state":"MO"}
    {"index":{"_id":"882"}}
    {"account_number":882,"balance":10895,"firstname":"Mari","lastname":"Landry","age":39,"gender":"M","address":"963 Gerald Court","employer":"Kenegy","email":"marilandry@kenegy.com","city":"Lithium","state":"NC"}
    {"index":{"_id":"887"}}
    {"account_number":887,"balance":31772,"firstname":"Eunice","lastname":"Watts","age":36,"gender":"F","address":"707 Stuyvesant Avenue","employer":"Memora","email":"eunicewatts@memora.com","city":"Westwood","state":"TN"}
    {"index":{"_id":"894"}}
    {"account_number":894,"balance":1031,"firstname":"Tyler","lastname":"Fitzgerald","age":32,"gender":"M","address":"787 Meserole Street","employer":"Jetsilk","email":"tylerfitzgerald@jetsilk.com","city":"Woodlands","state":"WV"}
    {"index":{"_id":"899"}}
    {"account_number":899,"balance":32953,"firstname":"Carney","lastname":"Callahan","age":23,"gender":"M","address":"724 Kimball Street","employer":"Mangelica","email":"carneycallahan@mangelica.com","city":"Tecolotito","state":"MT"}
    {"index":{"_id":"902"}}
    {"account_number":902,"balance":13345,"firstname":"Hallie","lastname":"Jarvis","age":23,"gender":"F","address":"237 Duryea Court","employer":"Anixang","email":"halliejarvis@anixang.com","city":"Boykin","state":"IN"}
    {"index":{"_id":"907"}}
    {"account_number":907,"balance":12961,"firstname":"Ingram","lastname":"William","age":36,"gender":"M","address":"826 Overbaugh Place","employer":"Genmex","email":"ingramwilliam@genmex.com","city":"Kimmell","state":"AK"}
    {"index":{"_id":"914"}}
    {"account_number":914,"balance":7120,"firstname":"Esther","lastname":"Bean","age":32,"gender":"F","address":"583 Macon Street","employer":"Applica","email":"estherbean@applica.com","city":"Homeworth","state":"MN"}
    {"index":{"_id":"919"}}
    {"account_number":919,"balance":39655,"firstname":"Shauna","lastname":"Hanson","age":27,"gender":"M","address":"557 Hart Place","employer":"Exospace","email":"shaunahanson@exospace.com","city":"Outlook","state":"LA"}
    {"index":{"_id":"921"}}
    {"account_number":921,"balance":49119,"firstname":"Barbara","lastname":"Wade","age":29,"gender":"M","address":"687 Hoyts Lane","employer":"Roughies","email":"barbarawade@roughies.com","city":"Sattley","state":"CO"}
    {"index":{"_id":"926"}}
    {"account_number":926,"balance":49433,"firstname":"Welch","lastname":"Mcgowan","age":21,"gender":"M","address":"833 Quincy Street","employer":"Atomica","email":"welchmcgowan@atomica.com","city":"Hampstead","state":"VT"}
    {"index":{"_id":"933"}}
    {"account_number":933,"balance":18071,"firstname":"Tabitha","lastname":"Cole","age":21,"gender":"F","address":"916 Rogers Avenue","employer":"Eclipto","email":"tabithacole@eclipto.com","city":"Lawrence","state":"TX"}
    {"index":{"_id":"938"}}
    {"account_number":938,"balance":9597,"firstname":"Sharron","lastname":"Santos","age":40,"gender":"F","address":"215 Matthews Place","employer":"Zenco","email":"sharronsantos@zenco.com","city":"Wattsville","state":"VT"}
    {"index":{"_id":"940"}}
    {"account_number":940,"balance":23285,"firstname":"Melinda","lastname":"Mendoza","age":38,"gender":"M","address":"806 Kossuth Place","employer":"Kneedles","email":"melindamendoza@kneedles.com","city":"Coaldale","state":"OK"}
    {"index":{"_id":"945"}}
    {"account_number":945,"balance":23085,"firstname":"Hansen","lastname":"Hebert","age":33,"gender":"F","address":"287 Conduit Boulevard","employer":"Capscreen","email":"hansenhebert@capscreen.com","city":"Taycheedah","state":"AK"}
    {"index":{"_id":"952"}}
    {"account_number":952,"balance":21430,"firstname":"Angelique","lastname":"Weeks","age":33,"gender":"M","address":"659 Reeve Place","employer":"Exodoc","email":"angeliqueweeks@exodoc.com","city":"Turpin","state":"MD"}
    {"index":{"_id":"957"}}
    {"account_number":957,"balance":11373,"firstname":"Michael","lastname":"Giles","age":31,"gender":"M","address":"668 Court Square","employer":"Yogasm","email":"michaelgiles@yogasm.com","city":"Rosburg","state":"WV"}
    {"index":{"_id":"964"}}
    {"account_number":964,"balance":26154,"firstname":"Elena","lastname":"Waller","age":34,"gender":"F","address":"618 Crystal Street","employer":"Insurety","email":"elenawaller@insurety.com","city":"Gallina","state":"NY"}
    {"index":{"_id":"969"}}
    {"account_number":969,"balance":22214,"firstname":"Briggs","lastname":"Lynn","age":30,"gender":"M","address":"952 Lester Court","employer":"Quinex","email":"briggslynn@quinex.com","city":"Roland","state":"ID"}
    {"index":{"_id":"971"}}
    {"account_number":971,"balance":22772,"firstname":"Gabrielle","lastname":"Reilly","age":32,"gender":"F","address":"964 Tudor Terrace","employer":"Blanet","email":"gabriellereilly@blanet.com","city":"Falmouth","state":"AL"}
    {"index":{"_id":"976"}}
    {"account_number":976,"balance":31707,"firstname":"Mullen","lastname":"Tanner","age":26,"gender":"M","address":"711 Whitney Avenue","employer":"Pulze","email":"mullentanner@pulze.com","city":"Mooresburg","state":"MA"}
    {"index":{"_id":"983"}}
    {"account_number":983,"balance":47205,"firstname":"Mattie","lastname":"Eaton","age":24,"gender":"F","address":"418 Allen Avenue","employer":"Trasola","email":"mattieeaton@trasola.com","city":"Dupuyer","state":"NJ"}
    {"index":{"_id":"988"}}
    {"account_number":988,"balance":17803,"firstname":"Lucy","lastname":"Castro","age":34,"gender":"F","address":"425 Fleet Walk","employer":"Geekfarm","email":"lucycastro@geekfarm.com","city":"Mulino","state":"VA"}
    {"index":{"_id":"990"}}
    {"account_number":990,"balance":44456,"firstname":"Kelly","lastname":"Steele","age":35,"gender":"M","address":"809 Hoyt Street","employer":"Eschoir","email":"kellysteele@eschoir.com","city":"Stewartville","state":"ID"}
    {"index":{"_id":"995"}}
    {"account_number":995,"balance":21153,"firstname":"Phelps","lastname":"Parrish","age":25,"gender":"M","address":"666 Miller Place","employer":"Pearlessa","email":"phelpsparrish@pearlessa.com","city":"Brecon","state":"ME"}
    + +

    image-20230701155812781

    +

    四.进阶检索

    1.SearchAPI

    ES支持两种基本的方式检索:

    +
      +
    • 一个是通过使用REST request URI 发送搜索参数(uri+检索参数)
    • +
    • 另一个是通过使用 REST request body来发送它们(uri+请求体)
    • +
    +

    1.1 检索信息

    一切检索从_search开始

    +

    1.1.1 使用uri+检索参数的方式进行检索

    GET bank/_search //检索bank下所有信息,包含type和docs
    GET bank/_search?q=*&sort=account_number:asc //请求参数方式检索
    //q=*表示查询所有
    //sort=account_number:asc 按照account_number进行升序排序
    //相应的结果解析
    {
    "took":29,//执行搜索的时间
    "time_out":false, //搜索是否超时
    "_shards":{.....},//有多少个分片被搜索了,以及统计成功/失败的搜索分片
    "hits":{.......}//搜索的结果
    }
    + +

    image-20230701162247192

    +

    完整的响应结果

    +
    {
    "took" : 29,
    "timed_out" : false,
    "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
    },
    "hits" : {
    "total" : {
    "value" : 1000,
    "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "0",
    "_score" : null,
    "_source" : {
    "account_number" : 0,
    "balance" : 16623,
    "firstname" : "Bradshaw",
    "lastname" : "Mckenzie",
    "age" : 29,
    "gender" : "F",
    "address" : "244 Columbus Place",
    "employer" : "Euron",
    "email" : "bradshawmckenzie@euron.com",
    "city" : "Hobucken",
    "state" : "CO"
    },
    "sort" : [
    0
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "1",
    "_score" : null,
    "_source" : {
    "account_number" : 1,
    "balance" : 39225,
    "firstname" : "Amber",
    "lastname" : "Duke",
    "age" : 32,
    "gender" : "M",
    "address" : "880 Holmes Lane",
    "employer" : "Pyrami",
    "email" : "amberduke@pyrami.com",
    "city" : "Brogan",
    "state" : "IL"
    },
    "sort" : [
    1
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "2",
    "_score" : null,
    "_source" : {
    "account_number" : 2,
    "balance" : 28838,
    "firstname" : "Roberta",
    "lastname" : "Bender",
    "age" : 22,
    "gender" : "F",
    "address" : "560 Kingsway Place",
    "employer" : "Chillium",
    "email" : "robertabender@chillium.com",
    "city" : "Bennett",
    "state" : "LA"
    },
    "sort" : [
    2
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "3",
    "_score" : null,
    "_source" : {
    "account_number" : 3,
    "balance" : 44947,
    "firstname" : "Levine",
    "lastname" : "Burks",
    "age" : 26,
    "gender" : "F",
    "address" : "328 Wilson Avenue",
    "employer" : "Amtap",
    "email" : "levineburks@amtap.com",
    "city" : "Cochranville",
    "state" : "HI"
    },
    "sort" : [
    3
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "4",
    "_score" : null,
    "_source" : {
    "account_number" : 4,
    "balance" : 27658,
    "firstname" : "Rodriquez",
    "lastname" : "Flores",
    "age" : 31,
    "gender" : "F",
    "address" : "986 Wyckoff Avenue",
    "employer" : "Tourmania",
    "email" : "rodriquezflores@tourmania.com",
    "city" : "Eastvale",
    "state" : "HI"
    },
    "sort" : [
    4
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "5",
    "_score" : null,
    "_source" : {
    "account_number" : 5,
    "balance" : 29342,
    "firstname" : "Leola",
    "lastname" : "Stewart",
    "age" : 30,
    "gender" : "F",
    "address" : "311 Elm Place",
    "employer" : "Diginetic",
    "email" : "leolastewart@diginetic.com",
    "city" : "Fairview",
    "state" : "NJ"
    },
    "sort" : [
    5
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "6",
    "_score" : null,
    "_source" : {
    "account_number" : 6,
    "balance" : 5686,
    "firstname" : "Hattie",
    "lastname" : "Bond",
    "age" : 36,
    "gender" : "M",
    "address" : "671 Bristol Street",
    "employer" : "Netagy",
    "email" : "hattiebond@netagy.com",
    "city" : "Dante",
    "state" : "TN"
    },
    "sort" : [
    6
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "7",
    "_score" : null,
    "_source" : {
    "account_number" : 7,
    "balance" : 39121,
    "firstname" : "Levy",
    "lastname" : "Richard",
    "age" : 22,
    "gender" : "M",
    "address" : "820 Logan Street",
    "employer" : "Teraprene",
    "email" : "levyrichard@teraprene.com",
    "city" : "Shrewsbury",
    "state" : "MO"
    },
    "sort" : [
    7
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "8",
    "_score" : null,
    "_source" : {
    "account_number" : 8,
    "balance" : 48868,
    "firstname" : "Jan",
    "lastname" : "Burns",
    "age" : 35,
    "gender" : "M",
    "address" : "699 Visitation Place",
    "employer" : "Glasstep",
    "email" : "janburns@glasstep.com",
    "city" : "Wakulla",
    "state" : "AZ"
    },
    "sort" : [
    8
    ]
    },
    {
    "_index" : "bank",
    "_type" : "account",
    "_id" : "9",
    "_score" : null,
    "_source" : {
    "account_number" : 9,
    "balance" : 24776,
    "firstname" : "Opal",
    "lastname" : "Meadows",
    "age" : 39,
    "gender" : "M",
    "address" : "963 Neptune Avenue",
    "employer" : "Cedward",
    "email" : "opalmeadows@cedward.com",
    "city" : "Olney",
    "state" : "OH"
    },
    "sort" : [
    9
    ]
    }
    ]
    }
    }
    + +

    1.1.2 使用uri+请求体的方式进行检索

    GET /bank/_search 
    {
    "query": {
    "match_all": {} //查询所有(匹配条件)
    },
    "sort": [//排序规则
    {
    "account_number": "asc"//根据account_number升序
    },
    {
    "balance":"desc" //根据balance降序
    }
    ]
    }
    + +

    image-20230701163210869

    +

    2.Query DSL

    2.1 基本的语法

    GET /bank/_search
    {
    "query": { //查询操作
    "match_all": {} //查询的条件
    },
    "sort": [ //排序的条件(数组,可以指定多个排序规则)
    {
    "balance": { //排序的字段
    "order": "desc" //排序的规则(升序或者降序)
    }
    }
    ],
    "from": 0,//分页字段,从第几页开始
    "size": 5,//每页的记录数
    "_source": ["balance","firstname"] //返回指定的字段
    }
    + +

    2.2 match查询[匹配查询]

    根据某个字段的值进行匹配查询

    +
    //查询account_number是20的数据
    GET /bank/_search
    {
    "query": {
    "match": { //查询account_number是20的数据
    "account_number": "20"
    }
    }
    }
    + +

    模糊匹配

    +

    对检索的条件进行分词匹配,按照评分的进行排序

    +
    //查询地址中包含mill或者lane或者mill lane的数据
    GET /bank/_search
    {
    "query": {
    "match": {
    "address": "Mill lane" //查询地址中包含Mill或者lane中一个或者多个的数据,然后按照命中的评分排序
    }
    }
    }
    + +

    2.3 match_phrase[短语匹配]

    匹配的短语不分割进行匹配,一整个进行匹配

    +
    //查询地址中包含Mill lane短语的数据
    GET /bank/_search
    {
    "query": {
    "match_phrase": {
    "address": "Mill lane" //查询包含整个Mill lane的数据
    }
    }
    }
    + +

    2.4 multi_match[多字段匹配]

    //查询state和address中包含mill的数据
    GET /bank/_search
    {
    "query": {
    "multi_match": { //查询在state或者在address字段中包含mill的数据
    "query": "mill",//多字段匹配在查询的时候也会分词进行匹配
    "fields": ["state","address"]
    }
    }
    }
    + +

    2.5 bool[复合查询]

    //查询性别是M和地址中包含mill并且年龄不是28,lastname最好是Hines的数据
    GET /bank/_search
    {
    "query": {
    "bool": { //多条件复合查询
    "must": [ //必须符合的条件
    {"match": {
    "gender": "M"
    }},
    {"match": {
    "address": "mill"
    }}
    ],
    "must_not": [//必须不符合的条件
    {"match": {
    "age": "38"
    }}
    ],
    "should": [
    {"match": {
    "lastname": "Hines" //最好lastname是Hines,满足这个条件的得分会高一些
    }}
    ]
    }
    }
    }
    + +

    2.6 Filter[结果过滤]

    使用Filter进行检索相比上面的检索,不会影响过滤结果的得分(上面的检索都可以通过Filter实现)

    +

    举例:未使用filter的查询:

    +
    //查询年龄在18-20之间的所有数据
    GET /bank/_search
    {
    "query": {
    "bool": {
    "must": [
    {"range": {
    "age": {
    "gte": 18,
    "lte": 30
    }
    }}
    ]
    }
    }
    }
    + +

    有得分的变化

    +

    image-20230702111451660

    +

    使用了Filter的查询:

    +
    GET /bank/_search
    {
    "query": {
    "bool": {
    "filter": {
    "range": {
    "age": {
    "gte": 18,
    "lte": 30
    }
    }
    }
    }
    }
    }
    + +

    没有得分的变化

    +

    image-20230702111554667

    +

    Filter可以和上面的检索条件一起使用

    +
    GET /bank/_search
    {
    "query": {

    "bool": {

    "must": [
    {"match": {
    "gender": "M"
    }},
    {"match": {
    "address": "mill"
    }}
    ],

    "must_not": [
    {"match": {
    "age": "18"
    }}
    ],

    "should": [
    {"match": {
    "lastname": "Wallace"
    }}
    ],

    "filter": [
    {"range": {
    "age": {
    "gte": 18,
    "lte": 30
    }
    }}
    ]
    }
    }
    }
    + +

    2.7 term

    和match一样,匹配某个属性的值。全文检索字段用match,其他非text字段匹配用term(精确性字段推荐使用term,例如年龄,数量等)

    +
    GET /bank/_search
    {
    "query": {
    "term": {//精确的使用term,全文检索字段用match
    "age": "28"
    }
    }
    }
    + +

    2.8 aggregations[执行聚合]

    聚合提供了从数据中分组和提取数据的能力.类似于MySql中的Group by函数

    +

    搜索address中包含mill的所有人的年龄分布以及平均年龄

    +
    //搜索address中包含mill的所有人的年龄分布以及平均年龄
    GET /bank/_search
    {
    "query": {
    "match": {
    "address": "mill"
    }
    },
    "aggs": {
    "ageAgg": {
    "terms": {//查看年龄的分布情况
    "field": "age",
    "size": 10
    }
    },
    "ageAvg":{//年龄平均值
    "avg": {
    "field": "age"
    }
    },
    "balanceAvg":{//工资的平均值
    "avg": {
    "field": "balance"
    }
    }
    }
    }
    + +

    image-20230703101958163

    +

    按照年龄聚合,并且获取这些年龄段的这些人的平均薪资[子聚合]

    +
    //按照年龄聚合,并且获取这些年龄段的这些人的平均薪资
    GET /bank/_search
    {
    "query": {
    "match_all": {}
    },
    "aggs": {
    "ageAgg": {//年龄段的聚合
    "terms": {
    "field": "age",
    "size": 100
    },
    "aggs": {
    "ageAvg": {//年龄段的聚合里面嵌套一个求年龄平均值的聚合
    "avg": {
    "field": "balance"
    }
    }
    }
    }
    }
    }
    + +

    image-20230703102805047

    +

    查询所有年龄分布,并且这些年龄段中男的平均工资和女的平均工资以及这个年龄段的总体平均薪资

    +
    GET /bank/_search
    {
    "query": {
    "match_all": {}
    },
    "aggs": {
    "avgAgg": {
    "terms": {//根据年龄进行分段
    "field": "age",
    "size": 100
    },
    "aggs": {
    "genderAgg": {
    "terms": {//根据性别进行分段
    "field": "gender.keyword"
    },
    "aggs": {
    "balanceAvg": {
    "avg": {//求出每个年龄段对应的不同性别的平均工资
    "field": "balance"
    }
    }
    }
    },
    "ageBalanceAvg": {//计算每个年龄段对应的平均工资
    "avg": {
    "field": "balance"
    }
    }
    }
    }
    }
    }
    + +

    image-20230703105247964

    +

    3.Mapping

    指定ES中数据的数据类型

    +

    3.1 创建映射

    在创建索引的时候我们可以指定属性名的数据类型

    +
    ##创建一个新的索引,指定索引下字段的映射关系
    PUT /my_index
    {
    "mappings": {
    "properties": {
    "age":{"type": "integer"},
    "email":{"type": "keyword"},
    "name":{"type": "text"}
    }
    }
    }
    + +

    image-20230703112342368

    +

    3.2 添加新的映射

    ##添加一个映射
    PUT /my_index/_mapping
    {
    "properties": {
    "employee-id": {
    "type": "keyword",
    "index": false //表示这个字段不参与检索
    }
    }
    }
    + +

    image-20230703113121234

    +

    3.3 更新映射

    对于已经存在的映射字段,我们不能更新,更新必须创建新的索引进行数据迁移

    +

    3.4 数据迁移

    1.创建一个新的索引并指定映射

    +
    PUT /new_bank
    {
    "mappings": {
    "properties": {
    "account_number": {
    "type": "long"
    },
    "address": {
    "type": "text"
    },
    "age": {
    "type": "integer"
    },
    "balance": {
    "type": "long"
    },
    "city": {
    "type": "keyword"
    },
    "email": {
    "type": "keyword"
    },
    "employer": {
    "type": "keyword"
    },
    "firstname": {
    "type": "text"
    },
    "gender": {
    "type": "keyword"
    },
    "lastname": {
    "type": "keyword"
    },
    "state": {
    "type": "keyword"
    }
    }
    }
    }
    + +

    2.数据迁移

    +
    //新版本的写法(6.0以后)
    POST _reindex
    {
    "source":{
    "index":"twitter" //老的索引
    },
    "dest":{
    "index":"new_twitter" //新的索引
    }
    }
    //旧版本需要指定数据类型
    #数据迁移
    POST _reindex
    {
    "source": {
    "index": "bank",
    "type": "account"
    },
    "dest": {
    "index": "new_bank"
    }
    }
    + +

    image-20230703115140810

    +

    4.分词

    4.1 安装分词器

    安装地址: https://github.com/medcl/elasticsearch-analysis-ik

    +
    #切换到es挂载在注解的plugins目录
    cd /mydata/elasticsearch/plugins/
    #安装wget
    yum install wget
    #下载ik分词器的压缩包(下载的速度不是很快(5分钟左右),建议本地下载好了之后,上传上去)
    wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
    #进入容器内部查看一下文件是否同步到了容器的内部
    docker exec -it 容器Id /bin/bash
    #切换到plugins目录中去,这时目录中也存在elasticsearch-analysis-ik-7.4.2.zip这个压缩包
    cd plugins/ #使用ls查看一下
    #退出容器
    exit
    #在主机下解压elasticsearch-analysis-ik-7.4.2.zip这个压缩包
    #zip的解压需要使用unzip解压命令 这里我们直接在本地解压一下 上传到plugins目录(解压的文件名为ik)
    #安装unzip
    yum install -y unzip
    #查看ik分词器是否安装成功
    docker exec -it 容器Id /bin/bash
    #切换到bin目录
    cd bin/
    #执行下面的命令,有ik显示即为安装成功
    elasticsearch-plugin list
    #退出容器,重启一下
    docker restart elasticsearch
    + +

    测试使用

    +

    image-20230703152110823

    +

    4.2 自定义词库

    1.安装nginx,用作设置ik的远程分词库

    +
    #创建nginx容器挂载的目录
    mkdir /mydata/nginx/conf
    #拉取nginx的镜像
    docker pull nginx:1.10
    #随便启动一个nginx实例,为了复制其配置
    docker run -p 80:80 --name nginx -d nginx:1.10
    #将容器内的配置文件拷贝到conf目录(注意这里有个点和空格)
    #注意在nginx的目录下操作,(成功之后/mydata/nginx/conf目录下会有相应的配置文件和目录)
    docker container cp nginx:/etc/nginx /mydata/nginx/conf
    #停掉并删除之前的启动的nginx
    docker stop nginx
    docker rm nginx
    #正式的启动容器
    docker run -p 80:80 --name nginx \
    -v /mydata/nginx/html:/usr/share/nginx/html \
    -v /mydata/nginx/logs:/var/log/nginx \
    -v /mydata/nginx/conf:/etc/nginx \
    -d nginx:1.10
    #设置开机自启
    docker update nginx --restart=always
    + +

    2.配置自定义的词库

    +
    #在nginx外部挂载的html目录下创建相应的目录
    mkdir es
    #创建分词需要使用的文本文件
    vim fenci.txt
    #在这个文本文件中加入我们需要添加的新词
    #例如:奥里给 老铁之类的
    #在浏览器上可以通过http://192.168.195.100/es/fenci.txt访问到里面的内容
    + +

    3.配置es远程词库的地址

    +
    #切换到ik分词器配置文件所在的目录
    cd elasticsearch/plugins/ik/config/
    #修改配置文件,设置远程分词库的地址
    vim IKAnalyzer.cfg.xml
    + +

    image-20230703170719714

    +
    #重启es
    docker restart elasticsearch
    + +

    4.测试一下(这时我们使用网络中新颖的词就可以识别成一个词语)

    +

    image-20230703171420471

    +

    五.Elasticsearch-Rest-Client

    1.环境配置

    1.导入依赖

    +
    <!--指定版本-->
    <properties>
    <elasticsearch.version>7.4.2</elasticsearch.version>
    </properties>
    <!--ES的依赖-->
    <dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.4.2</version>
    </dependency>
    + +

    2.编写配置文件

    +

    import org.apache.http.HttpHost;
    import org.apache.http.HttpResponse;
    import org.apache.http.nio.protocol.HttpAsyncResponseConsumer;
    import org.elasticsearch.client.HttpAsyncResponseConsumerFactory;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestClient;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/7/3
    * @Description ES的配置文件
    */
    @Configuration
    public class ElasticSearchConfig {


    //通用设置项
    public static final RequestOptions COMMON_OPTIONS;
    static {
    RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();

    COMMON_OPTIONS = builder.build();
    }


    @Bean
    public RestHighLevelClient esRestClient() {
    RestHighLevelClient client = new RestHighLevelClient(
    RestClient.builder(
    new HttpHost("192.168.195.100", 9200, "http")
    )
    );
    return client;
    }
    }
    + +

    3.测试使用

    +
    package com.atguigu.gulimall.search;

    import org.elasticsearch.client.RestHighLevelClient;
    import org.junit.jupiter.api.AfterAll;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;

    @SpringBootTest
    class GulimallSearchApplicationTests {

    @Autowired
    private RestHighLevelClient client;
    @Test
    void contextLoads() {
    System.out.println(client);
    }

    }
    + +

    2.代码操作ES

    2.1 创建索引

    /**
    * 创建一个索引
    */
    @Test
    void testCreateIndex() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("test_create");
    CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
    log.info("创建一个索引:{}",createIndexResponse);
    }
    + +

    2.2 判断索引是否存在

    /**
    * 判断索引是否存在
    */
    @Test
    void testExistIndex() throws IOException {
    GetIndexRequest request = new GetIndexRequest("users");
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    log.info("索引是否存在:{}",exists);
    }
    + +

    2.3 删除索引

    /**
    * 删除索引
    */
    @Test
    void testDeleteIndex() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest("test_create");
    AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
    System.out.println(delete.isAcknowledged());
    }
    + +

    2.4 保存文档

     /**
    * 测试向es中存储数据
    * 保存和修改的操作
    */
    @Test
    void indexData() throws IOException {
    //在users索引下存储数据
    IndexRequest indexRequest = new IndexRequest("users");
    //存储的数据的id
    indexRequest.id("1");
    //第一种方式
    //indexRequest.source("userName","zhangSan","age",18,"gender","男");
    //第二种方式
    User user = new User("tom","男",22);
    String jsonString = JSON.toJSONString(user);
    indexRequest.source(jsonString, XContentType.JSON);
    //执行真正的保存操作
    //下面方法的第二个参数也可以使用这个参数GulimallElasticSearchConfig.COMMON_OPTIONS
    IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
    log.info("保存之后响应的数据:{}",index);
    }
    + +

    2.5 判断文档是否存在

    /**
    * 判断文档是否存在
    */
    @Test
    void testIsExists() throws IOException {
    GetRequest getRequest = new GetRequest("users", "1");
    getRequest.fetchSourceContext(new FetchSourceContext(false));
    getRequest.storedFields("_none_");
    boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
    System.out.println(exists);
    }
    + +

    2.6 获取文档信息

    /**
    * 获取文档的信息
    */
    @Test
    void testGetDocument() throws IOException {
    GetRequest getRequest = new GetRequest("users", "1");
    GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
    System.out.println(getResponse.getSourceAsString());
    System.out.println(getResponse);
    }
    + +

    2.7 修改文档信息

    /**
    * 更新文档的信息
    */
    @Test
    void testUpdateRequest() throws IOException {
    UpdateRequest updateRequest = new UpdateRequest("users", "1");
    updateRequest.timeout("1s");
    User user = new User("修改之后的用户名", "男",23);
    updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
    UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
    System.out.println(updateResponse.status());
    }
    + +

    2.8 删除文档记录

    /**
    * 删除文档记录
    */
    @Test
    void testDeleteRequest() throws IOException {
    DeleteRequest request = new DeleteRequest("users", "1");
    request.timeout("1s");
    DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
    System.out.println(deleteResponse.status());
    }
    + +

    2.9 批量添加

    /**
    * 批量添加文档
    */
    @Test
    void testBulkRequest() throws IOException {
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");
    ArrayList<User> userList = new ArrayList<>();
    userList.add(new User("A", "男",23));
    userList.add(new User("B", "男",32));
    userList.add(new User("C", "女",34));
    userList.add(new User("D", "女",56));
    userList.add(new User("E", "男",34));
    userList.add(new User("F", "女",65));
    // 批处理请求
    for (int i = 0; i < userList.size(); i++) {
    // 批量更新和批量删除,就在这里修改对应的请求就可以了
    bulkRequest.add(new IndexRequest("users").id("" + (i + 1))
    .source(JSON.toJSONString(userList.get(i)), XContentType.JSON));
    }
    BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    System.out.println("是否批量添加成功:"+!bulkResponse.hasFailures());
    }
    + +

    2.10 条件查询

    /**
    * 检索的方法
    */
    @Test
    void testSearchRequest() throws IOException {
    //1.创建一个检索请求
    SearchRequest searchRequest = new SearchRequest();
    //设置检索的索引
    searchRequest.indices("bank");
    //指定DSL,检索条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));//查询地址中包含mill的数据
    sourceBuilder.aggregation(AggregationBuilders.terms("ageAgg").field("age").size(10));//聚合名ageAgg,根据age字段聚合,只取10条
    sourceBuilder.aggregation(AggregationBuilders.avg("balanceAvg").field("balance"));//求平均工资
    System.out.println("检索的条件:" + sourceBuilder);
    searchRequest.source(sourceBuilder);

    //2.执行检索
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

    //3.分析检索的结果
    System.out.println("查询到的数据:" + searchResponse.toString());
    //Map map = JSON.parseObject(searchResponse.toString(), Map.class);//可以转换成一个map
    SearchHits hits = searchResponse.getHits();//拿到命中的记录
    for (SearchHit hit : hits) {
    String index = hit.getIndex();//获取索引信息
    String type = hit.getType();//类型信息
    String id = hit.getId();//id信息
    float score = hit.getScore();//得分信息
    TimeValue took = searchResponse.getTook();
    String sourceAsString = hit.getSourceAsString();//直接转化成对应的java对象
    //转换成Account对象
    Account account = JSON.parseObject(sourceAsString, Account.class);
    System.out.println("Account:" + account);
    }

    //获取聚合分析的信息
    //获取年龄的分段信息
    Aggregations aggregations = searchResponse.getAggregations();
    Terms ageAgg = aggregations.get("ageAgg");
    for (Terms.Bucket bucket : ageAgg.getBuckets()) {

    System.out.println("年龄:" + bucket.getKeyAsString() + "有" + bucket.getDocCount() + "人");
    }
    //获取薪资的平均值
    Avg balanceAvg = aggregations.get("balanceAvg");
    System.out.println("平均薪资:" + balanceAvg.getValue());
    }
    + +

    示例的完整代码

    +
    package com.atguigu.gulimall.search;

    import com.alibaba.fastjson.JSON;
    import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
    import lombok.Data;
    import lombok.ToString;
    import lombok.extern.slf4j.Slf4j;
    import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
    import org.elasticsearch.action.bulk.BulkRequest;
    import org.elasticsearch.action.bulk.BulkResponse;
    import org.elasticsearch.action.delete.DeleteRequest;
    import org.elasticsearch.action.delete.DeleteResponse;
    import org.elasticsearch.action.get.GetRequest;
    import org.elasticsearch.action.get.GetResponse;
    import org.elasticsearch.action.index.IndexRequest;
    import org.elasticsearch.action.index.IndexResponse;
    import org.elasticsearch.action.search.SearchRequest;
    import org.elasticsearch.action.search.SearchResponse;
    import org.elasticsearch.action.support.master.AcknowledgedResponse;
    import org.elasticsearch.action.update.UpdateRequest;
    import org.elasticsearch.action.update.UpdateResponse;
    import org.elasticsearch.client.RequestOptions;
    import org.elasticsearch.client.RestHighLevelClient;
    import org.elasticsearch.client.indices.CreateIndexRequest;
    import org.elasticsearch.client.indices.CreateIndexResponse;
    import org.elasticsearch.client.indices.GetIndexRequest;
    import org.elasticsearch.common.unit.TimeValue;
    import org.elasticsearch.common.xcontent.XContentType;
    import org.elasticsearch.index.query.QueryBuilders;
    import org.elasticsearch.search.SearchHit;
    import org.elasticsearch.search.SearchHits;
    import org.elasticsearch.search.aggregations.Aggregation;
    import org.elasticsearch.search.aggregations.AggregationBuilder;
    import org.elasticsearch.search.aggregations.AggregationBuilders;
    import org.elasticsearch.search.aggregations.Aggregations;
    import org.elasticsearch.search.aggregations.bucket.terms.Terms;
    import org.elasticsearch.search.aggregations.metrics.Avg;
    import org.elasticsearch.search.builder.SearchSourceBuilder;
    import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;

    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;

    @Slf4j
    @SpringBootTest
    class GulimallSearchApplicationTests {

    @Autowired
    private RestHighLevelClient client;

    @Test
    void contextLoads() {
    System.out.println(client);
    }

    /**
    * 测试向es中存储数据
    * 保存和修改的操作
    */
    @Test
    void indexData() throws IOException {
    //在users索引下存储数据
    IndexRequest indexRequest = new IndexRequest("users");
    //存储的数据的id
    indexRequest.id("1");
    //第一种方式
    //indexRequest.source("userName","zhangSan","age",18,"gender","男");
    //第二种方式
    User user = new User("tom", "男", 22);
    String jsonString = JSON.toJSONString(user);
    indexRequest.source(jsonString, XContentType.JSON);
    //执行真正的保存操作
    //下面方法的第二个参数也可以使用这个参数GulimallElasticSearchConfig.COMMON_OPTIONS
    IndexResponse index = client.index(indexRequest, RequestOptions.DEFAULT);
    log.info("保存之后响应的数据:{}", index);
    }

    /**
    * 判断索引是否存在
    */
    @Test
    void testExistIndex() throws IOException {
    GetIndexRequest request = new GetIndexRequest("users");
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    log.info("索引是否存在:{}", exists);
    }

    /**
    * 创建一个索引
    */
    @Test
    void testCreateIndex() throws IOException {
    CreateIndexRequest request = new CreateIndexRequest("test_create");
    CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
    log.info("创建一个索引:{}", createIndexResponse);
    }


    /**
    * 删除索引
    */
    @Test
    void testDeleteIndex() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest("users");
    AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
    System.out.println(delete.isAcknowledged());
    }


    /**
    * 判断文档是否存在
    */
    @Test
    void testIsExists() throws IOException {
    GetRequest getRequest = new GetRequest("users", "1");
    getRequest.fetchSourceContext(new FetchSourceContext(false));
    getRequest.storedFields("_none_");
    boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
    System.out.println(exists);
    }

    /**
    * 获取文档的信息
    */
    @Test
    void testGetDocument() throws IOException {
    GetRequest getRequest = new GetRequest("users", "1");
    GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
    System.out.println(getResponse.getSourceAsString());
    System.out.println(getResponse);
    }


    /**
    * 更新文档的信息
    */
    @Test
    void testUpdateRequest() throws IOException {
    UpdateRequest updateRequest = new UpdateRequest("users", "1");
    updateRequest.timeout("1s");
    User user = new User("修改之后的用户名", "男", 23);
    updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
    UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
    System.out.println(updateResponse.status());
    }

    /**
    * 删除文档记录
    */
    @Test
    void testDeleteRequest() throws IOException {
    DeleteRequest request = new DeleteRequest("users", "1");
    request.timeout("1s");
    DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
    System.out.println(deleteResponse.status());
    }

    /**
    * 批量添加文档
    */
    @Test
    void testBulkRequest() throws IOException {
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");
    ArrayList<User> userList = new ArrayList<>();
    userList.add(new User("A", "男", 23));
    userList.add(new User("B", "男", 32));
    userList.add(new User("C", "女", 34));
    userList.add(new User("D", "女", 56));
    userList.add(new User("E", "男", 34));
    userList.add(new User("F", "女", 65));
    // 批处理请求
    for (int i = 0; i < userList.size(); i++) {
    // 批量更新和批量删除,就在这里修改对应的请求就可以了
    bulkRequest.add(new IndexRequest("users").id("" + (i + 1))
    .source(JSON.toJSONString(userList.get(i)), XContentType.JSON));
    }
    BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    System.out.println("是否批量添加成功:" + !bulkResponse.hasFailures());
    }


    /**
    * 检索的方法
    */
    @Test
    void testSearchRequest() throws IOException {
    //1.创建一个检索请求
    SearchRequest searchRequest = new SearchRequest();
    //设置检索的索引
    searchRequest.indices("bank");
    //指定DSL,检索条件
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    sourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));//查询地址中包含mill的数据
    sourceBuilder.aggregation(AggregationBuilders.terms("ageAgg").field("age").size(10));//聚合名ageAgg,根据age字段聚合,只取10条
    sourceBuilder.aggregation(AggregationBuilders.avg("balanceAvg").field("balance"));//求平均工资
    System.out.println("检索的条件:" + sourceBuilder);
    searchRequest.source(sourceBuilder);

    //2.执行检索
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);

    //3.分析检索的结果
    System.out.println("查询到的数据:" + searchResponse.toString());
    //Map map = JSON.parseObject(searchResponse.toString(), Map.class);//可以转换成一个map
    SearchHits hits = searchResponse.getHits();//拿到命中的记录
    for (SearchHit hit : hits) {
    String index = hit.getIndex();//获取索引信息
    String type = hit.getType();//类型信息
    String id = hit.getId();//id信息
    float score = hit.getScore();//得分信息
    TimeValue took = searchResponse.getTook();
    String sourceAsString = hit.getSourceAsString();//直接转化成对应的java对象
    //转换成Account对象
    Account account = JSON.parseObject(sourceAsString, Account.class);
    System.out.println("Account:" + account);
    }

    //获取聚合分析的信息
    //获取年龄的分段信息
    Aggregations aggregations = searchResponse.getAggregations();
    Terms ageAgg = aggregations.get("ageAgg");
    for (Terms.Bucket bucket : ageAgg.getBuckets()) {

    System.out.println("年龄:" + bucket.getKeyAsString() + "有" + bucket.getDocCount() + "人");
    }
    //获取薪资的平均值
    Avg balanceAvg = aggregations.get("balanceAvg");
    System.out.println("平均薪资:" + balanceAvg.getValue());
    }

    @ToString
    @Data
    static class Account {
    private int account_number;
    private int balance;
    private String firstname;
    private String lastname;
    private int age;
    private String gender;
    private String address;
    private String employer;
    private String email;
    private String city;
    private String state;
    }


    @Data
    static class User {
    private String userName;
    private String gender;
    private Integer age;

    public User(String userName, String gender, Integer age) {
    this.userName = userName;
    this.gender = gender;
    this.age = age;
    }
    }

    }
    + + + + + + + + + + + + + + + + + + + +]]>
    + + 后端 + + + ElasticSearch + +
    + + 项目实战-谷粒商城 + /posts/45726.html + 一.项目简介

    1.项目背景

    电商模式 谷粒商城使用的B2C模式,销售自营的商品给客户

    +

    市面上5种常见的电商模式B2B、B2C、C2B、C2C、O2O

    +

    1、B2B模式:
    business to business,是指商家与商家建立的商业关系。如阿里巴巴。

    +

    2、B2C模式:
    business to consumer,商对客模式。即通常说的商业零售,供应商直接把商品卖给用户。如:苏宁易购、京东、天猫、小米商城。

    +

    3、C2B模式:
    customer to business,即消费者对企业。先有消费者需求而后有企业生产。例如众筹类的商城。

    +

    4、C2C模式:
    customer to consumer,客户之间直接把东西放上网上去卖。如:淘宝、闲鱼。

    +

    5、O2O模式:
    online to offline,线上线下。线上快速支付,线下优质服务。如:饿了么、美团、京东到家。

    +

    2.项目架构图

    项目的架构图

    +

    +

    微服务划分图

    +

    image-20230518015831413

    +

    3.项目技术和特色

      +
    • 前后端分离开发,开发基于VUE的后台管理系统
    • +
    • SpringCloud全新解决方案
    • +
    • 应用监控、限流、网关、熔断降级等分布式方案 全方位涉及
    • +
    • 包含分布式事务、分布式锁等技术难点
    • +
    • 分析高并发场景的编码方式、线程池、异步编排等使用
    • +
    • 压力测试与性能优化
    • +
    • 各种集群技术的区别已经使用
    • +
    • CI/CD的使用
    • +
    +

    4.项目的前置要求

      +
    • 熟悉SpringBoot以及常见的整合方案
    • +
    • 了解SpringCloud
    • +
    • 熟悉Maven git
    • +
    • 熟悉linux、redis、docker基本操作
    • +
    • 了解html、css、js、vue
    • +
    • 熟练使用idea开发项目
    • +
    +

    二.分布式基础概念

    1.微服务

    微服务架构风格,就像是一个单独的应用程序开发为一套小服务,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API。这些服务围绕业务能力来构建,并通过完全自动化部署机制来独立部署。这些服务使用不同的编程语言编写,以及不同的数据存储技术,并保持最低限度的集中式管理。

    +

    简而言之:拒绝大型单体应用,基于业务边界进行服务微化拆分,各个应用独立部署运行。

    +

    2.集群|分布式|节点

    集群是一个物理形态 ,分布式是个工作方式

    +

    分布式: 将不同的业务分布在不同的地方

    +

    集群: 集群指的是将几台服务器集中在一起,实现同一业务

    +

    分布式中的每一个节点,都可以做集群。而集群不一定是分布式的。

    +

    节点: 集群中的一个服务器

    +

    3.远程调用

    在分布式系统中,各个服务可能处于不同主机,但是服务之间不可避免的需要相互调用,我们称之为远程调用。

    +

    SpringCloud中使用HTTP+JSON的方式完成远程调用

    +

    image-20230518010847966

    +

    4.负载均衡

    image-20230518011030343 + +
    分布式系统中,A服务需要调用B服务,B服务在多台机器上都存在,A调用任意一个服务均可完成功能。为了使每一个服务器都不要太忙或者太闲,我们可以使用负载均衡的调用每一个服务器,提升网站的健壮性。
    +
    +

    常见的负载均衡算法:

    +

    轮询: 为第一个请求选择健康池中的第一台后端服务器,然后按顺序往后依次选择,直到最后一个,然后循环。

    +

    最小连接: 有限选择连接数量少,也就是压力最小的后端服务器,在会话较长的情况下可以考虑采取这种方式。

    +

    随机法:通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。

    +

    源地址哈希法:源地址哈希的思想是根据获取客户端的IP地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。

    +

    加权轮询法:不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。

    +

    加权随机法:与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。

    +

    5.服务注册|发现|注册中心

    image-20230518012211042

    +

    6.配置中心

    每一个服务最终会有大量的配置,并且每个服务都可能部署在多台机器上。我们经常需要变更配置,我们可以让每个服务在配置中心获取自己的配置。
    +
    +

    image-20230518012538349

    +

    7.服务熔断和服务降级

    在微服务的架构中,微服务之间通过网络进行通信,存在相互依赖,当其中的一个服务不可用时,有可能会造成雪崩效应。要防止这样的情况,必须要有容错机制来保护服务。
    +
    +

    服务熔断: 设置服务的超时,当被调用的服务经常失败到达某个阈值,我们可以开启短路保护机制,后来的请求不再去调用这个服务。本地直接返回默认的数据。

    +

    服务降级: 在运维期间,当系统处于高峰期,系统资源紧张,我们可以让非核心的业务降级运行。降级:某些业务不处理,或者简单处理[抛异常、返回NULL、调用mock数据、调用Fallback处理逻辑]。

    +

    image-20230518013254404

    +

    image-20230518013624395

    +

    8.API网关

    在微服务的架构中,API Gateway作为整体架构的重要组件,他抽象了微服务中都需要的公共功能,同时提供了客户端的负载均衡,服务自动熔断,灰度发布、统一认证、限流流控、日志统计等丰富功能,帮助我们解决很多API管理难题。
    +
    +image-20230518014732955 + +

    三.环境搭建

    1.安装虚拟机

    需要使用虚拟机安装相关的软件和部署集群

    +

    VMWare虚拟机安装Linux教程 | The Blog (gitee.io)

    +

    将Linxu的IP地址固定下来

    +

    Linux设置静态IP | The Blog (gitee.io)

    +

    2.Docker安装与配置

    包含docker安装、开启开机自启动、配置镜像加速服务

    +

    Docker容器化技术 | The Blog (gitee.io)image-20230518141429712

    +

    image-20230518145456236

    +

    3.Docker安装mysql

    #下载mysql的镜像文件 这里以下载mysql5.7为例
    docker pull mysql:5.7

    #查看是否下载成功
    docker images

    #使用MySQL的镜像启动一个容器
    docker run -p 3306:3306 --name mysql \
    -v /mydata/mysql/log:/var/log/mysql \
    -v /mydata/mysql/data:/var/lib/mysql \
    -v /mydata/mysql/conf:/etc/mysql \
    -e MYSQL_ROOT_PASSWORD=root \
    -d mysql:5.7
    #参数说明
    # -p 3306:3306:将容器的 3306 端口映射到主机的 3306 端口
    #-v /mydata/mysql/conf:/etc/mysql:将配置文件夹挂载到主机
    #-v /mydata/mysql/log:/var/log/mysql:将日志文件夹挂载到主机
    #-v /mydata/mysql/data:/var/lib/mysql/:将配置文件夹挂载到主机
    #-e MYSQL_ROOT_PASSWORD=root:初始化 root 用户的密码

    #查看是否运行成功 这时使用Navicat就可以连接上了 连接不上 关闭防火墙
    docker ps #查看正在运行的容器

    #进入容器的内部
    docker exec -it mysql /bin/bash

    #查看容器内部的目录结构 我们可以看到容器的内部是一个小小的linux系统
    ls / #注意中间有一个空格

    #查看与mysql相关的目录
    whereis mysql

    #退出容器
    exit;

    #在容器外部查看mysql挂载在本地的三个文件
    #容器内部文件的变化会同步在这三个文件中 conf data log
    #外部修改 也会同步到内部
    cd /mydata/mysql
    ls

    #创建一个mysql的配置文件设置字符编码 将下面的内容粘贴到配置文件中
    #1.创建配置文件
    vi /mydata/mysql/conf/my.cnf
    #2.将以下的内容粘贴到配置文件配置文件内容
    [client]
    default-character-set=utf8
    [mysql]
    default-character-set=utf8
    [mysqld]
    init_connect='SET collation_connection = utf8_unicode_ci'
    init_connect='SET NAMES utf8'
    character-set-server=utf8
    collation-server=utf8_unicode_ci
    skip-character-set-client-handshake
    skip-name-resolve
    #3.重启MySQl的服务 让配置文件生效 这个配置文件也会同步到容器内部
    docker restart mysql

    #设置开机自启动
    docker update mysql --restart=always
    + +

    4.Docker安装redis

    #下载redis的镜像文件 直接下载最新的
    docker pull redis

    #创建redis挂载在宿主机的配置文件和目录
    mkdir -p /mydata/redis/conf #创建目录
    touch /mydata/redis/conf/redis.conf #创建配置文件

    #创建并启动容器 这是以配置文件的方式启动 \前面有一个空格
    docker run -p 6379:6379 --name redis \
    -v /mydata/redis/data:/data \
    -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
    -d redis redis-server /etc/redis/redis.conf

    #检查容器的运行情况
    docker ps

    #测试客户端的使用
    docker exec -it redis redis-cli

    #可以在redis客户端中使用如下命令
    127.0.0.1:6379> set name tom #存值
    OK
    127.0.0.1:6379> get name #取值
    "tom"

    #redis中的数据不能持久化 关机重启之后数据会丢失
    #通过以下的配置 实现redis的持久化操作 AOF的持久化
    vi /mydata/redis/conf/redis.conf #修改之前创建的配置文件
    #添加如下配置
    appendonly yes

    #重启redis 让配置生效
    docker restart redis

    #设置开机自启动
    docker update redis --restart=always
    + +

    5.统一开发环境

    其中Java使用的是java8及以上

    +

    Maven 配置阿里云的镜像 profiles

    +

    开发环境的搭建 | The Blog (gitee.io)

    +

    IDEA中修改使用自己安装的Maven

    +

    IDEA插件:Lombok|MybatisX

    +

    VSCode插件:

    +

    image-20230518160444927

    +

    image-20230518155308167

    +

    6.配置Git

    官网下载:https://git-scm.com/downloads

    +
    #以下的操作在下载安装完毕之后进行
    #1.鼠标在桌面右键 选择Git Bash Here 打开控制台

    #2.配置用户名和邮箱
    git config --global user.name "用户名" #随意
    git config --global user.email "邮箱" #自己的邮箱

    #3.配置SSH免密连接
    #生成密钥
    ssh-keygen -t rsa -C "在码云上注册的邮箱地址" #连续三次回车
    #查看密钥并复制公钥的内容
    cat ~/.ssh/id_rsa.pub

    #4.将密钥的复制到码云的SSH公钥中
    #4.1添加公钥 公钥名随意 公钥内容就是上面复制的内容

    #5.测试
    ssh -T git@gitee.com
    + +

    常用命令

    +image-20230518161145808 + +

    7.从Gitee上初始化一个项目

    我们是先从码云上初始化一个项目 然后拉取到本地

    +

    创建一个仓库

    +

    image-20230518163703370

    +

    复制刚才创建仓库的地址

    +

    image-20230518163838162

    +

    将工程导入到IDEA中

    +

    image-20230518164005690

    +

    导入成功

    +

    image-20230518164217773

    +

    8.创建微服务模块

    image-20230518171355588 + +

    9.数据库设计

    image-20230518171546236

    +image-20230518173203605 + +

    10.后台管理系统的搭建

    10.1.后端的搭建

    后台管理系统使用的是人人开源提供的一个脚手架:

    +

    仓库的地址信息:

    +

    仓库主页:https://gitee.com/renrenio

    +

    后端renren-fast:https://gitee.com/renrenio/renren-fast.git

    +

    前端renren-fast-vue:https://gitee.com/renrenio/renren-fast-vue.git

    +

    将其作为一个模块集成到gulimall中

    +

    image-20230518221222845

    +

    同时创建好这个脚手架需要的数据库

    +

    image-20230518221433314

    +

    修改配置文件中数据库账号密码ip的设置,启动项目看有没有报错

    +

    10.2 前端的搭建

    安装node.js 和 npm包管理工具 (node.js自带npm包管理工具)

    +
    #检查是否安装成功
    #出现版本号就是安装成功了
    node -v
    npm -v

    #npm配置淘宝的镜像 下载依赖的速度更快
    npm config set registry https://registry.npmmirror.com
    + +

    将项目导入到vscode中,并安装依赖

    +
    #安装依赖的命令
    npm install
    + +

    image-20230518223021928

    +

    下载完成之后,运行前端的项目

    +
    #运行命令
    npm run dev
    + +

    运行成功过之后访问 http://localhost:8001

    +

    成功的页面显示如下

    +

    image-20230518223525151

    +

    **使用账号:admin密码:admin 登录 **

    +

    可以登录成功并显示以下的页面说明前后端的联调通过

    +

    image-20230518223921179

    +

    11.快速开发-逆向工程的搭建

    1.代码生成器快速使用案例

    人人开源代码生成器的地址:https://gitee.com/renrenio/renren-generator.git

    +

    集成代码生成器

    +

    image-20230518230016421

    +

    修改代码生成器的配置文件

    +

    1.修改yml配置文件中数据库的连接信息

    +

    2.修改properties中与代码生成相关的配置信息

    +

    配置举例 根据不同的模块 不同的数据库 配置不同

    +
    #代码生成器配置信息

    mainPath=com.atguigu
    #包名
    package=com.atguigu.gulimall
    moduleName=product
    #作者
    author=JasonGong
    #Email
    email=JasonGong@gmail.com
    #表前缀(类名不会包含表前缀)
    tablePrefix=pms_


    #以下的配置信息一般不动
    #类型转换配置信息
    tinyint=Integer
    smallint=Integer
    mediumint=Integer
    int=Integer
    integer=Integer
    bigint=Long
    float=Float
    double=Double
    decimal=BigDecimal
    bit=Boolean

    char=String
    varchar=String
    tinytext=String
    text=String
    mediumtext=String
    longtext=String


    date=Date
    datetime=Date
    timestamp=Date

    NUMBER=Integer
    INT=Integer
    INTEGER=Integer
    BINARY_INTEGER=Integer
    LONG=String
    FLOAT=Float
    BINARY_FLOAT=Float
    DOUBLE=Double
    BINARY_DOUBLE=Double
    DECIMAL=BigDecimal
    CHAR=String
    VARCHAR=String
    VARCHAR2=String
    NVARCHAR=String
    NVARCHAR2=String
    CLOB=String
    BLOB=String
    DATE=Date
    DATETIME=Date
    TIMESTAMP=Date
    TIMESTAMP(6)=Date

    int8=Long
    int4=Integer
    int2=Integer
    numeric=BigDecimal

    nvarchar=String
    + +

    3.运行项目

    +

    通过http://localhost:81 访问

    +

    image-20230518234030568

    +

    4.使用生成代码的功能生成代码

    +

    image-20230518234311272

    +

    解压之后的文件如下

    +

    image-20230518234941961

    +

    压缩包中的文件如下所示

    +

    image-20230518234922087

    +

    由于项目中没有使用shiro安全框架,我们注释掉安全框架的注解生成模板

    +

    image-20230519002146272

    +

    配置每个模块的配置文件

    +
    server:
    port: 8080
    spring:
    datasource:
    url: jdbc:mysql://192.168.111.100:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    mybatis-plus:
    mapper-locations: classpath*:/mapper/**/*.xml #定义mapper.xml文件的位置
    global-config:
    db-config:
    id-type: auto #主键的生成规则
    + +

    测试生成的代码

    +

    image-20230519005131468

    +

    2.代码生成器的使用步骤

    1.修改代码生成器的配置文件generator.properties

    +
    #代码生成器配置信息

    mainPath=com.atguigu
    #包名
    package=com.atguigu.gulimall
    moduleName=ware #1.修改模块名
    #作者
    author=JasonGong
    #Email
    email=JasonGong@gmail.com
    #表前缀(类名不会包含表前缀)
    tablePrefix=wms_ #2.数据库表的前缀


    #以下的配置信息一般不动
    #类型转换配置信息
    tinyint=Integer
    smallint=Integer
    mediumint=Integer
    int=Integer
    integer=Integer
    bigint=Long
    float=Float
    double=Double
    decimal=BigDecimal
    bit=Boolean

    char=String
    varchar=String
    tinytext=String
    text=String
    mediumtext=String
    longtext=String


    date=Date
    datetime=Date
    timestamp=Date

    NUMBER=Integer
    INT=Integer
    INTEGER=Integer
    BINARY_INTEGER=Integer
    LONG=String
    FLOAT=Float
    BINARY_FLOAT=Float
    DOUBLE=Double
    BINARY_DOUBLE=Double
    DECIMAL=BigDecimal
    CHAR=String
    VARCHAR=String
    VARCHAR2=String
    NVARCHAR=String
    NVARCHAR2=String
    CLOB=String
    BLOB=String
    DATE=Date
    DATETIME=Date
    TIMESTAMP=Date
    TIMESTAMP(6)=Date

    int8=Long
    int4=Integer
    int2=Integer
    numeric=BigDecimal

    nvarchar=String
    + +

    2修改要生成增删改查代码的数据库的连接配置application.yml

    +

    只用配置数据库的连接信息即可

    +
    server:
    port: 81

    # mysql
    spring:
    main:
    allow-circular-references: true
    datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    #MySQL配置
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.111.100:3306/gulimall_wms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    #oracle配置
    # driverClassName: oracle.jdbc.OracleDriver
    # url: jdbc:oracle:thin:@47.100.206.162:1521:xe
    # username: renren
    # password: 123456
    #SQLServer配置
    # driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
    # url: jdbc:sqlserver://192.168.10.10:1433;DatabaseName=renren_fast
    # username: sa
    # password: 123456
    #PostgreSQL配置
    # driverClassName: org.postgresql.Driver
    # url: jdbc:postgresql://192.168.10.10:5432/renren_fast
    # username: postgres
    # password: 123456



    jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
    web:
    resources:
    static-locations: classpath:/static/,classpath:/views/

    #mongodb:
    # host: localhost
    # port: 27017
    # auth: false #是否使用密码验证
    # username: tincery
    # password: renren
    # source: 123456
    # database: test

    mybatis-plus:
    mapperLocations: classpath:mapper/**/*.xml


    pagehelper:
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql


    #指定数据库,可选值有【mysql、oracle、sqlserver、postgresql、mongodb】
    renren:
    database: mysql
    + +

    3.启动服务生成代码,将生成的代码解压并粘贴到对应的模块中去

    +

    将main这个目录替换原来模块中的main目录即可,sql文件暂时不用管

    +

    4.创建模块的配置文件

    +
    server:
    port: 8080
    spring:
    datasource:
    url: jdbc:mysql://192.168.111.100:3306/gulimall_pms?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    mybatis-plus:
    mapper-locations: classpath*:/mapper/**/*.xml #定义mapper.xml文件的位置
    global-config:
    db-config:
    id-type: auto #主键的生成规则
    + +

    5.启动测试生成的代码

    +
    在启动的时候,我们要处理好代码依赖的包,这些包都存在与生成代码的模块中,我们在生成代码的模块中复制过来即可,然后手动的引入相应的包,处理好报错信息。
    +
    +

    3.后台搭建完成之后的项目树

    C:.
    │ .gitignore
    │ LICENSE
    │ pom.xml

    ├─.idea
    │ │ .gitignore
    │ │ compiler.xml
    │ │ encodings.xml
    │ │ jarRepositories.xml
    │ │ misc.xml
    │ │ uiDesigner.xml
    │ │ vcs.xml
    │ │ workspace.xml
    │ │
    │ └─codeStyles
    │ codeStyleConfig.xml
    │ Project.xml

    ├─gulimall-common
    │ │ pom.xml
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─common
    │ │ │ │ ├─exception
    │ │ │ │ │ RRException.java
    │ │ │ │ │
    │ │ │ │ ├─utils
    │ │ │ │ │ Constant.java
    │ │ │ │ │ PageUtils.java
    │ │ │ │ │ Query.java
    │ │ │ │ │ R.java
    │ │ │ │ │
    │ │ │ │ └─xss
    │ │ │ │ HTMLFilter.java
    │ │ │ │ SQLFilter.java
    │ │ │ │
    │ │ │ └─resources
    │ │ └─test
    │ │ └─java
    │ └─target
    │ ├─classes
    │ │ └─com
    │ │ └─atguigu
    │ │ └─common
    │ │ ├─exception
    │ │ │ RRException.class
    │ │ │
    │ │ ├─utils
    │ │ │ Constant$MenuType.class
    │ │ │ Constant$ScheduleStatus.class
    │ │ │ Constant.class
    │ │ │ PageUtils.class
    │ │ │ Query.class
    │ │ │ R.class
    │ │ │
    │ │ └─xss
    │ │ HTMLFilter.class
    │ │ SQLFilter.class
    │ │
    │ └─generated-sources
    │ └─annotations
    ├─gulimall-coupon
    │ │ .gitignore
    │ │ gulimall-coupon.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─coupon
    │ │ │ │ │ GulimallCouponApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ CouponController.java
    │ │ │ │ │ CouponHistoryController.java
    │ │ │ │ │ CouponSpuCategoryRelationController.java
    │ │ │ │ │ CouponSpuRelationController.java
    │ │ │ │ │ HomeAdvController.java
    │ │ │ │ │ HomeSubjectController.java
    │ │ │ │ │ HomeSubjectSpuController.java
    │ │ │ │ │ MemberPriceController.java
    │ │ │ │ │ SeckillPromotionController.java
    │ │ │ │ │ SeckillSessionController.java
    │ │ │ │ │ SeckillSkuNoticeController.java
    │ │ │ │ │ SeckillSkuRelationController.java
    │ │ │ │ │ SkuFullReductionController.java
    │ │ │ │ │ SkuLadderController.java
    │ │ │ │ │ SpuBoundsController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ CouponDao.java
    │ │ │ │ │ CouponHistoryDao.java
    │ │ │ │ │ CouponSpuCategoryRelationDao.java
    │ │ │ │ │ CouponSpuRelationDao.java
    │ │ │ │ │ HomeAdvDao.java
    │ │ │ │ │ HomeSubjectDao.java
    │ │ │ │ │ HomeSubjectSpuDao.java
    │ │ │ │ │ MemberPriceDao.java
    │ │ │ │ │ SeckillPromotionDao.java
    │ │ │ │ │ SeckillSessionDao.java
    │ │ │ │ │ SeckillSkuNoticeDao.java
    │ │ │ │ │ SeckillSkuRelationDao.java
    │ │ │ │ │ SkuFullReductionDao.java
    │ │ │ │ │ SkuLadderDao.java
    │ │ │ │ │ SpuBoundsDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ CouponEntity.java
    │ │ │ │ │ CouponHistoryEntity.java
    │ │ │ │ │ CouponSpuCategoryRelationEntity.java
    │ │ │ │ │ CouponSpuRelationEntity.java
    │ │ │ │ │ HomeAdvEntity.java
    │ │ │ │ │ HomeSubjectEntity.java
    │ │ │ │ │ HomeSubjectSpuEntity.java
    │ │ │ │ │ MemberPriceEntity.java
    │ │ │ │ │ SeckillPromotionEntity.java
    │ │ │ │ │ SeckillSessionEntity.java
    │ │ │ │ │ SeckillSkuNoticeEntity.java
    │ │ │ │ │ SeckillSkuRelationEntity.java
    │ │ │ │ │ SkuFullReductionEntity.java
    │ │ │ │ │ SkuLadderEntity.java
    │ │ │ │ │ SpuBoundsEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ CouponHistoryService.java
    │ │ │ │ │ CouponService.java
    │ │ │ │ │ CouponSpuCategoryRelationService.java
    │ │ │ │ │ CouponSpuRelationService.java
    │ │ │ │ │ HomeAdvService.java
    │ │ │ │ │ HomeSubjectService.java
    │ │ │ │ │ HomeSubjectSpuService.java
    │ │ │ │ │ MemberPriceService.java
    │ │ │ │ │ SeckillPromotionService.java
    │ │ │ │ │ SeckillSessionService.java
    │ │ │ │ │ SeckillSkuNoticeService.java
    │ │ │ │ │ SeckillSkuRelationService.java
    │ │ │ │ │ SkuFullReductionService.java
    │ │ │ │ │ SkuLadderService.java
    │ │ │ │ │ SpuBoundsService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ CouponHistoryServiceImpl.java
    │ │ │ │ CouponServiceImpl.java
    │ │ │ │ CouponSpuCategoryRelationServiceImpl.java
    │ │ │ │ CouponSpuRelationServiceImpl.java
    │ │ │ │ HomeAdvServiceImpl.java
    │ │ │ │ HomeSubjectServiceImpl.java
    │ │ │ │ HomeSubjectSpuServiceImpl.java
    │ │ │ │ MemberPriceServiceImpl.java
    │ │ │ │ SeckillPromotionServiceImpl.java
    │ │ │ │ SeckillSessionServiceImpl.java
    │ │ │ │ SeckillSkuNoticeServiceImpl.java
    │ │ │ │ SeckillSkuRelationServiceImpl.java
    │ │ │ │ SkuFullReductionServiceImpl.java
    │ │ │ │ SkuLadderServiceImpl.java
    │ │ │ │ SpuBoundsServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.properties
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─coupon
    │ │ │ │ CouponDao.xml
    │ │ │ │ CouponHistoryDao.xml
    │ │ │ │ CouponSpuCategoryRelationDao.xml
    │ │ │ │ CouponSpuRelationDao.xml
    │ │ │ │ HomeAdvDao.xml
    │ │ │ │ HomeSubjectDao.xml
    │ │ │ │ HomeSubjectSpuDao.xml
    │ │ │ │ MemberPriceDao.xml
    │ │ │ │ SeckillPromotionDao.xml
    │ │ │ │ SeckillSessionDao.xml
    │ │ │ │ SeckillSkuNoticeDao.xml
    │ │ │ │ SeckillSkuRelationDao.xml
    │ │ │ │ SkuFullReductionDao.xml
    │ │ │ │ SkuLadderDao.xml
    │ │ │ │ SpuBoundsDao.xml
    │ │ │ │
    │ │ │ ├─src
    │ │ │ │ └─views
    │ │ │ │ └─modules
    │ │ │ │ └─coupon
    │ │ │ │ coupon-add-or-update.vue
    │ │ │ │ coupon.vue
    │ │ │ │ couponhistory-add-or-update.vue
    │ │ │ │ couponhistory.vue
    │ │ │ │ couponspucategoryrelation-add-or-update.vue
    │ │ │ │ couponspucategoryrelation.vue
    │ │ │ │ couponspurelation-add-or-update.vue
    │ │ │ │ couponspurelation.vue
    │ │ │ │ homeadv-add-or-update.vue
    │ │ │ │ homeadv.vue
    │ │ │ │ homesubject-add-or-update.vue
    │ │ │ │ homesubject.vue
    │ │ │ │ homesubjectspu-add-or-update.vue
    │ │ │ │ homesubjectspu.vue
    │ │ │ │ memberprice-add-or-update.vue
    │ │ │ │ memberprice.vue
    │ │ │ │ seckillpromotion-add-or-update.vue
    │ │ │ │ seckillpromotion.vue
    │ │ │ │ seckillsession-add-or-update.vue
    │ │ │ │ seckillsession.vue
    │ │ │ │ seckillskunotice-add-or-update.vue
    │ │ │ │ seckillskunotice.vue
    │ │ │ │ seckillskurelation-add-or-update.vue
    │ │ │ │ seckillskurelation.vue
    │ │ │ │ skufullreduction-add-or-update.vue
    │ │ │ │ skufullreduction.vue
    │ │ │ │ skuladder-add-or-update.vue
    │ │ │ │ skuladder.vue
    │ │ │ │ spubounds-add-or-update.vue
    │ │ │ │ spubounds.vue
    │ │ │ │
    │ │ │ ├─static
    │ │ │ └─templates
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─coupon
    │ │ GulimallCouponApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.properties
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ └─coupon
    │ │ │ │ GulimallCouponApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ CouponController.class
    │ │ │ │ CouponHistoryController.class
    │ │ │ │ CouponSpuCategoryRelationController.class
    │ │ │ │ CouponSpuRelationController.class
    │ │ │ │ HomeAdvController.class
    │ │ │ │ HomeSubjectController.class
    │ │ │ │ HomeSubjectSpuController.class
    │ │ │ │ MemberPriceController.class
    │ │ │ │ SeckillPromotionController.class
    │ │ │ │ SeckillSessionController.class
    │ │ │ │ SeckillSkuNoticeController.class
    │ │ │ │ SeckillSkuRelationController.class
    │ │ │ │ SkuFullReductionController.class
    │ │ │ │ SkuLadderController.class
    │ │ │ │ SpuBoundsController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ CouponDao.class
    │ │ │ │ CouponHistoryDao.class
    │ │ │ │ CouponSpuCategoryRelationDao.class
    │ │ │ │ CouponSpuRelationDao.class
    │ │ │ │ HomeAdvDao.class
    │ │ │ │ HomeSubjectDao.class
    │ │ │ │ HomeSubjectSpuDao.class
    │ │ │ │ MemberPriceDao.class
    │ │ │ │ SeckillPromotionDao.class
    │ │ │ │ SeckillSessionDao.class
    │ │ │ │ SeckillSkuNoticeDao.class
    │ │ │ │ SeckillSkuRelationDao.class
    │ │ │ │ SkuFullReductionDao.class
    │ │ │ │ SkuLadderDao.class
    │ │ │ │ SpuBoundsDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ CouponEntity.class
    │ │ │ │ CouponHistoryEntity.class
    │ │ │ │ CouponSpuCategoryRelationEntity.class
    │ │ │ │ CouponSpuRelationEntity.class
    │ │ │ │ HomeAdvEntity.class
    │ │ │ │ HomeSubjectEntity.class
    │ │ │ │ HomeSubjectSpuEntity.class
    │ │ │ │ MemberPriceEntity.class
    │ │ │ │ SeckillPromotionEntity.class
    │ │ │ │ SeckillSessionEntity.class
    │ │ │ │ SeckillSkuNoticeEntity.class
    │ │ │ │ SeckillSkuRelationEntity.class
    │ │ │ │ SkuFullReductionEntity.class
    │ │ │ │ SkuLadderEntity.class
    │ │ │ │ SpuBoundsEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ CouponHistoryService.class
    │ │ │ │ CouponService.class
    │ │ │ │ CouponSpuCategoryRelationService.class
    │ │ │ │ CouponSpuRelationService.class
    │ │ │ │ HomeAdvService.class
    │ │ │ │ HomeSubjectService.class
    │ │ │ │ HomeSubjectSpuService.class
    │ │ │ │ MemberPriceService.class
    │ │ │ │ SeckillPromotionService.class
    │ │ │ │ SeckillSessionService.class
    │ │ │ │ SeckillSkuNoticeService.class
    │ │ │ │ SeckillSkuRelationService.class
    │ │ │ │ SkuFullReductionService.class
    │ │ │ │ SkuLadderService.class
    │ │ │ │ SpuBoundsService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ CouponHistoryServiceImpl.class
    │ │ │ CouponServiceImpl.class
    │ │ │ CouponSpuCategoryRelationServiceImpl.class
    │ │ │ CouponSpuRelationServiceImpl.class
    │ │ │ HomeAdvServiceImpl.class
    │ │ │ HomeSubjectServiceImpl.class
    │ │ │ HomeSubjectSpuServiceImpl.class
    │ │ │ MemberPriceServiceImpl.class
    │ │ │ SeckillPromotionServiceImpl.class
    │ │ │ SeckillSessionServiceImpl.class
    │ │ │ SeckillSkuNoticeServiceImpl.class
    │ │ │ SeckillSkuRelationServiceImpl.class
    │ │ │ SkuFullReductionServiceImpl.class
    │ │ │ SkuLadderServiceImpl.class
    │ │ │ SpuBoundsServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─coupon
    │ │ │ CouponDao.xml
    │ │ │ CouponHistoryDao.xml
    │ │ │ CouponSpuCategoryRelationDao.xml
    │ │ │ CouponSpuRelationDao.xml
    │ │ │ HomeAdvDao.xml
    │ │ │ HomeSubjectDao.xml
    │ │ │ HomeSubjectSpuDao.xml
    │ │ │ MemberPriceDao.xml
    │ │ │ SeckillPromotionDao.xml
    │ │ │ SeckillSessionDao.xml
    │ │ │ SeckillSkuNoticeDao.xml
    │ │ │ SeckillSkuRelationDao.xml
    │ │ │ SkuFullReductionDao.xml
    │ │ │ SkuLadderDao.xml
    │ │ │ SpuBoundsDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─coupon
    │ │ coupon-add-or-update.vue
    │ │ coupon.vue
    │ │ couponhistory-add-or-update.vue
    │ │ couponhistory.vue
    │ │ couponspucategoryrelation-add-or-update.vue
    │ │ couponspucategoryrelation.vue
    │ │ couponspurelation-add-or-update.vue
    │ │ couponspurelation.vue
    │ │ homeadv-add-or-update.vue
    │ │ homeadv.vue
    │ │ homesubject-add-or-update.vue
    │ │ homesubject.vue
    │ │ homesubjectspu-add-or-update.vue
    │ │ homesubjectspu.vue
    │ │ memberprice-add-or-update.vue
    │ │ memberprice.vue
    │ │ seckillpromotion-add-or-update.vue
    │ │ seckillpromotion.vue
    │ │ seckillsession-add-or-update.vue
    │ │ seckillsession.vue
    │ │ seckillskunotice-add-or-update.vue
    │ │ seckillskunotice.vue
    │ │ seckillskurelation-add-or-update.vue
    │ │ seckillskurelation.vue
    │ │ skufullreduction-add-or-update.vue
    │ │ skufullreduction.vue
    │ │ skuladder-add-or-update.vue
    │ │ skuladder.vue
    │ │ spubounds-add-or-update.vue
    │ │ spubounds.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ └─maven-status
    │ └─maven-compiler-plugin
    │ └─compile
    │ └─default-compile
    │ createdFiles.lst
    │ inputFiles.lst

    ├─gulimall-member
    │ │ .gitignore
    │ │ gulimall-member.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─member
    │ │ │ │ │ GulimallMemberApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ GrowthChangeHistoryController.java
    │ │ │ │ │ IntegrationChangeHistoryController.java
    │ │ │ │ │ MemberCollectSpuController.java
    │ │ │ │ │ MemberCollectSubjectController.java
    │ │ │ │ │ MemberController.java
    │ │ │ │ │ MemberLevelController.java
    │ │ │ │ │ MemberLoginLogController.java
    │ │ │ │ │ MemberReceiveAddressController.java
    │ │ │ │ │ MemberStatisticsInfoController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ GrowthChangeHistoryDao.java
    │ │ │ │ │ IntegrationChangeHistoryDao.java
    │ │ │ │ │ MemberCollectSpuDao.java
    │ │ │ │ │ MemberCollectSubjectDao.java
    │ │ │ │ │ MemberDao.java
    │ │ │ │ │ MemberLevelDao.java
    │ │ │ │ │ MemberLoginLogDao.java
    │ │ │ │ │ MemberReceiveAddressDao.java
    │ │ │ │ │ MemberStatisticsInfoDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ GrowthChangeHistoryEntity.java
    │ │ │ │ │ IntegrationChangeHistoryEntity.java
    │ │ │ │ │ MemberCollectSpuEntity.java
    │ │ │ │ │ MemberCollectSubjectEntity.java
    │ │ │ │ │ MemberEntity.java
    │ │ │ │ │ MemberLevelEntity.java
    │ │ │ │ │ MemberLoginLogEntity.java
    │ │ │ │ │ MemberReceiveAddressEntity.java
    │ │ │ │ │ MemberStatisticsInfoEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ GrowthChangeHistoryService.java
    │ │ │ │ │ IntegrationChangeHistoryService.java
    │ │ │ │ │ MemberCollectSpuService.java
    │ │ │ │ │ MemberCollectSubjectService.java
    │ │ │ │ │ MemberLevelService.java
    │ │ │ │ │ MemberLoginLogService.java
    │ │ │ │ │ MemberReceiveAddressService.java
    │ │ │ │ │ MemberService.java
    │ │ │ │ │ MemberStatisticsInfoService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ GrowthChangeHistoryServiceImpl.java
    │ │ │ │ IntegrationChangeHistoryServiceImpl.java
    │ │ │ │ MemberCollectSpuServiceImpl.java
    │ │ │ │ MemberCollectSubjectServiceImpl.java
    │ │ │ │ MemberLevelServiceImpl.java
    │ │ │ │ MemberLoginLogServiceImpl.java
    │ │ │ │ MemberReceiveAddressServiceImpl.java
    │ │ │ │ MemberServiceImpl.java
    │ │ │ │ MemberStatisticsInfoServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.properties
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─member
    │ │ │ │ GrowthChangeHistoryDao.xml
    │ │ │ │ IntegrationChangeHistoryDao.xml
    │ │ │ │ MemberCollectSpuDao.xml
    │ │ │ │ MemberCollectSubjectDao.xml
    │ │ │ │ MemberDao.xml
    │ │ │ │ MemberLevelDao.xml
    │ │ │ │ MemberLoginLogDao.xml
    │ │ │ │ MemberReceiveAddressDao.xml
    │ │ │ │ MemberStatisticsInfoDao.xml
    │ │ │ │
    │ │ │ ├─src
    │ │ │ │ └─views
    │ │ │ │ └─modules
    │ │ │ │ └─member
    │ │ │ │ growthchangehistory-add-or-update.vue
    │ │ │ │ growthchangehistory.vue
    │ │ │ │ integrationchangehistory-add-or-update.vue
    │ │ │ │ integrationchangehistory.vue
    │ │ │ │ member-add-or-update.vue
    │ │ │ │ member.vue
    │ │ │ │ membercollectspu-add-or-update.vue
    │ │ │ │ membercollectspu.vue
    │ │ │ │ membercollectsubject-add-or-update.vue
    │ │ │ │ membercollectsubject.vue
    │ │ │ │ memberlevel-add-or-update.vue
    │ │ │ │ memberlevel.vue
    │ │ │ │ memberloginlog-add-or-update.vue
    │ │ │ │ memberloginlog.vue
    │ │ │ │ memberreceiveaddress-add-or-update.vue
    │ │ │ │ memberreceiveaddress.vue
    │ │ │ │ memberstatisticsinfo-add-or-update.vue
    │ │ │ │ memberstatisticsinfo.vue
    │ │ │ │
    │ │ │ ├─static
    │ │ │ └─templates
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─member
    │ │ GulimallMemberApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.properties
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ └─member
    │ │ │ │ GulimallMemberApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ GrowthChangeHistoryController.class
    │ │ │ │ IntegrationChangeHistoryController.class
    │ │ │ │ MemberCollectSpuController.class
    │ │ │ │ MemberCollectSubjectController.class
    │ │ │ │ MemberController.class
    │ │ │ │ MemberLevelController.class
    │ │ │ │ MemberLoginLogController.class
    │ │ │ │ MemberReceiveAddressController.class
    │ │ │ │ MemberStatisticsInfoController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ GrowthChangeHistoryDao.class
    │ │ │ │ IntegrationChangeHistoryDao.class
    │ │ │ │ MemberCollectSpuDao.class
    │ │ │ │ MemberCollectSubjectDao.class
    │ │ │ │ MemberDao.class
    │ │ │ │ MemberLevelDao.class
    │ │ │ │ MemberLoginLogDao.class
    │ │ │ │ MemberReceiveAddressDao.class
    │ │ │ │ MemberStatisticsInfoDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ GrowthChangeHistoryEntity.class
    │ │ │ │ IntegrationChangeHistoryEntity.class
    │ │ │ │ MemberCollectSpuEntity.class
    │ │ │ │ MemberCollectSubjectEntity.class
    │ │ │ │ MemberEntity.class
    │ │ │ │ MemberLevelEntity.class
    │ │ │ │ MemberLoginLogEntity.class
    │ │ │ │ MemberReceiveAddressEntity.class
    │ │ │ │ MemberStatisticsInfoEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ GrowthChangeHistoryService.class
    │ │ │ │ IntegrationChangeHistoryService.class
    │ │ │ │ MemberCollectSpuService.class
    │ │ │ │ MemberCollectSubjectService.class
    │ │ │ │ MemberLevelService.class
    │ │ │ │ MemberLoginLogService.class
    │ │ │ │ MemberReceiveAddressService.class
    │ │ │ │ MemberService.class
    │ │ │ │ MemberStatisticsInfoService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ GrowthChangeHistoryServiceImpl.class
    │ │ │ IntegrationChangeHistoryServiceImpl.class
    │ │ │ MemberCollectSpuServiceImpl.class
    │ │ │ MemberCollectSubjectServiceImpl.class
    │ │ │ MemberLevelServiceImpl.class
    │ │ │ MemberLoginLogServiceImpl.class
    │ │ │ MemberReceiveAddressServiceImpl.class
    │ │ │ MemberServiceImpl.class
    │ │ │ MemberStatisticsInfoServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─member
    │ │ │ GrowthChangeHistoryDao.xml
    │ │ │ IntegrationChangeHistoryDao.xml
    │ │ │ MemberCollectSpuDao.xml
    │ │ │ MemberCollectSubjectDao.xml
    │ │ │ MemberDao.xml
    │ │ │ MemberLevelDao.xml
    │ │ │ MemberLoginLogDao.xml
    │ │ │ MemberReceiveAddressDao.xml
    │ │ │ MemberStatisticsInfoDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─member
    │ │ growthchangehistory-add-or-update.vue
    │ │ growthchangehistory.vue
    │ │ integrationchangehistory-add-or-update.vue
    │ │ integrationchangehistory.vue
    │ │ member-add-or-update.vue
    │ │ member.vue
    │ │ membercollectspu-add-or-update.vue
    │ │ membercollectspu.vue
    │ │ membercollectsubject-add-or-update.vue
    │ │ membercollectsubject.vue
    │ │ memberlevel-add-or-update.vue
    │ │ memberlevel.vue
    │ │ memberloginlog-add-or-update.vue
    │ │ memberloginlog.vue
    │ │ memberreceiveaddress-add-or-update.vue
    │ │ memberreceiveaddress.vue
    │ │ memberstatisticsinfo-add-or-update.vue
    │ │ memberstatisticsinfo.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ └─maven-status
    │ └─maven-compiler-plugin
    │ └─compile
    │ └─default-compile
    │ createdFiles.lst
    │ inputFiles.lst

    ├─gulimall-order
    │ │ .gitignore
    │ │ gulimall-order.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─order
    │ │ │ │ │ GulimallOrderApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ OrderController.java
    │ │ │ │ │ OrderItemController.java
    │ │ │ │ │ OrderOperateHistoryController.java
    │ │ │ │ │ OrderReturnApplyController.java
    │ │ │ │ │ OrderReturnReasonController.java
    │ │ │ │ │ OrderSettingController.java
    │ │ │ │ │ PaymentInfoController.java
    │ │ │ │ │ RefundInfoController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ OrderDao.java
    │ │ │ │ │ OrderItemDao.java
    │ │ │ │ │ OrderOperateHistoryDao.java
    │ │ │ │ │ OrderReturnApplyDao.java
    │ │ │ │ │ OrderReturnReasonDao.java
    │ │ │ │ │ OrderSettingDao.java
    │ │ │ │ │ PaymentInfoDao.java
    │ │ │ │ │ RefundInfoDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ OrderEntity.java
    │ │ │ │ │ OrderItemEntity.java
    │ │ │ │ │ OrderOperateHistoryEntity.java
    │ │ │ │ │ OrderReturnApplyEntity.java
    │ │ │ │ │ OrderReturnReasonEntity.java
    │ │ │ │ │ OrderSettingEntity.java
    │ │ │ │ │ PaymentInfoEntity.java
    │ │ │ │ │ RefundInfoEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ OrderItemService.java
    │ │ │ │ │ OrderOperateHistoryService.java
    │ │ │ │ │ OrderReturnApplyService.java
    │ │ │ │ │ OrderReturnReasonService.java
    │ │ │ │ │ OrderService.java
    │ │ │ │ │ OrderSettingService.java
    │ │ │ │ │ PaymentInfoService.java
    │ │ │ │ │ RefundInfoService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ OrderItemServiceImpl.java
    │ │ │ │ OrderOperateHistoryServiceImpl.java
    │ │ │ │ OrderReturnApplyServiceImpl.java
    │ │ │ │ OrderReturnReasonServiceImpl.java
    │ │ │ │ OrderServiceImpl.java
    │ │ │ │ OrderSettingServiceImpl.java
    │ │ │ │ PaymentInfoServiceImpl.java
    │ │ │ │ RefundInfoServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.properties
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─order
    │ │ │ │ OrderDao.xml
    │ │ │ │ OrderItemDao.xml
    │ │ │ │ OrderOperateHistoryDao.xml
    │ │ │ │ OrderReturnApplyDao.xml
    │ │ │ │ OrderReturnReasonDao.xml
    │ │ │ │ OrderSettingDao.xml
    │ │ │ │ PaymentInfoDao.xml
    │ │ │ │ RefundInfoDao.xml
    │ │ │ │
    │ │ │ ├─src
    │ │ │ │ └─views
    │ │ │ │ └─modules
    │ │ │ │ └─order
    │ │ │ │ order-add-or-update.vue
    │ │ │ │ order.vue
    │ │ │ │ orderitem-add-or-update.vue
    │ │ │ │ orderitem.vue
    │ │ │ │ orderoperatehistory-add-or-update.vue
    │ │ │ │ orderoperatehistory.vue
    │ │ │ │ orderreturnapply-add-or-update.vue
    │ │ │ │ orderreturnapply.vue
    │ │ │ │ orderreturnreason-add-or-update.vue
    │ │ │ │ orderreturnreason.vue
    │ │ │ │ ordersetting-add-or-update.vue
    │ │ │ │ ordersetting.vue
    │ │ │ │ paymentinfo-add-or-update.vue
    │ │ │ │ paymentinfo.vue
    │ │ │ │ refundinfo-add-or-update.vue
    │ │ │ │ refundinfo.vue
    │ │ │ │
    │ │ │ ├─static
    │ │ │ └─templates
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─order
    │ │ GulimallOrderApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.properties
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ ├─gulimallorder
    │ │ │ │ GulimallOrderApplication.class
    │ │ │ │
    │ │ │ └─order
    │ │ │ │ GulimallOrderApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ OrderController.class
    │ │ │ │ OrderItemController.class
    │ │ │ │ OrderOperateHistoryController.class
    │ │ │ │ OrderReturnApplyController.class
    │ │ │ │ OrderReturnReasonController.class
    │ │ │ │ OrderSettingController.class
    │ │ │ │ PaymentInfoController.class
    │ │ │ │ RefundInfoController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ OrderDao.class
    │ │ │ │ OrderItemDao.class
    │ │ │ │ OrderOperateHistoryDao.class
    │ │ │ │ OrderReturnApplyDao.class
    │ │ │ │ OrderReturnReasonDao.class
    │ │ │ │ OrderSettingDao.class
    │ │ │ │ PaymentInfoDao.class
    │ │ │ │ RefundInfoDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ OrderEntity.class
    │ │ │ │ OrderItemEntity.class
    │ │ │ │ OrderOperateHistoryEntity.class
    │ │ │ │ OrderReturnApplyEntity.class
    │ │ │ │ OrderReturnReasonEntity.class
    │ │ │ │ OrderSettingEntity.class
    │ │ │ │ PaymentInfoEntity.class
    │ │ │ │ RefundInfoEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ OrderItemService.class
    │ │ │ │ OrderOperateHistoryService.class
    │ │ │ │ OrderReturnApplyService.class
    │ │ │ │ OrderReturnReasonService.class
    │ │ │ │ OrderService.class
    │ │ │ │ OrderSettingService.class
    │ │ │ │ PaymentInfoService.class
    │ │ │ │ RefundInfoService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ OrderItemServiceImpl.class
    │ │ │ OrderOperateHistoryServiceImpl.class
    │ │ │ OrderReturnApplyServiceImpl.class
    │ │ │ OrderReturnReasonServiceImpl.class
    │ │ │ OrderServiceImpl.class
    │ │ │ OrderSettingServiceImpl.class
    │ │ │ PaymentInfoServiceImpl.class
    │ │ │ RefundInfoServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─order
    │ │ │ OrderDao.xml
    │ │ │ OrderItemDao.xml
    │ │ │ OrderOperateHistoryDao.xml
    │ │ │ OrderReturnApplyDao.xml
    │ │ │ OrderReturnReasonDao.xml
    │ │ │ OrderSettingDao.xml
    │ │ │ PaymentInfoDao.xml
    │ │ │ RefundInfoDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─order
    │ │ order-add-or-update.vue
    │ │ order.vue
    │ │ orderitem-add-or-update.vue
    │ │ orderitem.vue
    │ │ orderoperatehistory-add-or-update.vue
    │ │ orderoperatehistory.vue
    │ │ orderreturnapply-add-or-update.vue
    │ │ orderreturnapply.vue
    │ │ orderreturnreason-add-or-update.vue
    │ │ orderreturnreason.vue
    │ │ ordersetting-add-or-update.vue
    │ │ ordersetting.vue
    │ │ paymentinfo-add-or-update.vue
    │ │ paymentinfo.vue
    │ │ refundinfo-add-or-update.vue
    │ │ refundinfo.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ └─maven-status
    │ └─maven-compiler-plugin
    │ └─compile
    │ └─default-compile
    │ createdFiles.lst
    │ inputFiles.lst

    ├─gulimall-product
    │ │ .gitignore
    │ │ gulimall-product.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─product
    │ │ │ │ │ GulimallProductApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ AttrAttrgroupRelationController.java
    │ │ │ │ │ AttrController.java
    │ │ │ │ │ AttrGroupController.java
    │ │ │ │ │ BrandController.java
    │ │ │ │ │ CategoryBrandRelationController.java
    │ │ │ │ │ CategoryController.java
    │ │ │ │ │ CommentReplayController.java
    │ │ │ │ │ ProductAttrValueController.java
    │ │ │ │ │ SkuImagesController.java
    │ │ │ │ │ SkuInfoController.java
    │ │ │ │ │ SkuSaleAttrValueController.java
    │ │ │ │ │ SpuCommentController.java
    │ │ │ │ │ SpuImagesController.java
    │ │ │ │ │ SpuInfoController.java
    │ │ │ │ │ SpuInfoDescController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ AttrAttrgroupRelationDao.java
    │ │ │ │ │ AttrDao.java
    │ │ │ │ │ AttrGroupDao.java
    │ │ │ │ │ BrandDao.java
    │ │ │ │ │ CategoryBrandRelationDao.java
    │ │ │ │ │ CategoryDao.java
    │ │ │ │ │ CommentReplayDao.java
    │ │ │ │ │ ProductAttrValueDao.java
    │ │ │ │ │ SkuImagesDao.java
    │ │ │ │ │ SkuInfoDao.java
    │ │ │ │ │ SkuSaleAttrValueDao.java
    │ │ │ │ │ SpuCommentDao.java
    │ │ │ │ │ SpuImagesDao.java
    │ │ │ │ │ SpuInfoDao.java
    │ │ │ │ │ SpuInfoDescDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ AttrAttrgroupRelationEntity.java
    │ │ │ │ │ AttrEntity.java
    │ │ │ │ │ AttrGroupEntity.java
    │ │ │ │ │ BrandEntity.java
    │ │ │ │ │ CategoryBrandRelationEntity.java
    │ │ │ │ │ CategoryEntity.java
    │ │ │ │ │ CommentReplayEntity.java
    │ │ │ │ │ ProductAttrValueEntity.java
    │ │ │ │ │ SkuImagesEntity.java
    │ │ │ │ │ SkuInfoEntity.java
    │ │ │ │ │ SkuSaleAttrValueEntity.java
    │ │ │ │ │ SpuCommentEntity.java
    │ │ │ │ │ SpuImagesEntity.java
    │ │ │ │ │ SpuInfoDescEntity.java
    │ │ │ │ │ SpuInfoEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ AttrAttrgroupRelationService.java
    │ │ │ │ │ AttrGroupService.java
    │ │ │ │ │ AttrService.java
    │ │ │ │ │ BrandService.java
    │ │ │ │ │ CategoryBrandRelationService.java
    │ │ │ │ │ CategoryService.java
    │ │ │ │ │ CommentReplayService.java
    │ │ │ │ │ ProductAttrValueService.java
    │ │ │ │ │ SkuImagesService.java
    │ │ │ │ │ SkuInfoService.java
    │ │ │ │ │ SkuSaleAttrValueService.java
    │ │ │ │ │ SpuCommentService.java
    │ │ │ │ │ SpuImagesService.java
    │ │ │ │ │ SpuInfoDescService.java
    │ │ │ │ │ SpuInfoService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ AttrAttrgroupRelationServiceImpl.java
    │ │ │ │ AttrGroupServiceImpl.java
    │ │ │ │ AttrServiceImpl.java
    │ │ │ │ BrandServiceImpl.java
    │ │ │ │ CategoryBrandRelationServiceImpl.java
    │ │ │ │ CategoryServiceImpl.java
    │ │ │ │ CommentReplayServiceImpl.java
    │ │ │ │ ProductAttrValueServiceImpl.java
    │ │ │ │ SkuImagesServiceImpl.java
    │ │ │ │ SkuInfoServiceImpl.java
    │ │ │ │ SkuSaleAttrValueServiceImpl.java
    │ │ │ │ SpuCommentServiceImpl.java
    │ │ │ │ SpuImagesServiceImpl.java
    │ │ │ │ SpuInfoDescServiceImpl.java
    │ │ │ │ SpuInfoServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.properties
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─product
    │ │ │ │ AttrAttrgroupRelationDao.xml
    │ │ │ │ AttrDao.xml
    │ │ │ │ AttrGroupDao.xml
    │ │ │ │ BrandDao.xml
    │ │ │ │ CategoryBrandRelationDao.xml
    │ │ │ │ CategoryDao.xml
    │ │ │ │ CommentReplayDao.xml
    │ │ │ │ ProductAttrValueDao.xml
    │ │ │ │ SkuImagesDao.xml
    │ │ │ │ SkuInfoDao.xml
    │ │ │ │ SkuSaleAttrValueDao.xml
    │ │ │ │ SpuCommentDao.xml
    │ │ │ │ SpuImagesDao.xml
    │ │ │ │ SpuInfoDao.xml
    │ │ │ │ SpuInfoDescDao.xml
    │ │ │ │
    │ │ │ ├─src
    │ │ │ │ └─views
    │ │ │ │ └─modules
    │ │ │ │ └─product
    │ │ │ │ attr-add-or-update.vue
    │ │ │ │ attr.vue
    │ │ │ │ attrattrgrouprelation-add-or-update.vue
    │ │ │ │ attrattrgrouprelation.vue
    │ │ │ │ attrgroup-add-or-update.vue
    │ │ │ │ attrgroup.vue
    │ │ │ │ brand-add-or-update.vue
    │ │ │ │ brand.vue
    │ │ │ │ category-add-or-update.vue
    │ │ │ │ category.vue
    │ │ │ │ categorybrandrelation-add-or-update.vue
    │ │ │ │ categorybrandrelation.vue
    │ │ │ │ commentreplay-add-or-update.vue
    │ │ │ │ commentreplay.vue
    │ │ │ │ productattrvalue-add-or-update.vue
    │ │ │ │ productattrvalue.vue
    │ │ │ │ skuimages-add-or-update.vue
    │ │ │ │ skuimages.vue
    │ │ │ │ skuinfo-add-or-update.vue
    │ │ │ │ skuinfo.vue
    │ │ │ │ skusaleattrvalue-add-or-update.vue
    │ │ │ │ skusaleattrvalue.vue
    │ │ │ │ spucomment-add-or-update.vue
    │ │ │ │ spucomment.vue
    │ │ │ │ spuimages-add-or-update.vue
    │ │ │ │ spuimages.vue
    │ │ │ │ spuinfo-add-or-update.vue
    │ │ │ │ spuinfo.vue
    │ │ │ │ spuinfodesc-add-or-update.vue
    │ │ │ │ spuinfodesc.vue
    │ │ │ │
    │ │ │ ├─static
    │ │ │ └─templates
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─product
    │ │ GulimallProductApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.properties
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ └─product
    │ │ │ │ GulimallProductApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ AttrAttrgroupRelationController.class
    │ │ │ │ AttrController.class
    │ │ │ │ AttrGroupController.class
    │ │ │ │ BrandController.class
    │ │ │ │ CategoryBrandRelationController.class
    │ │ │ │ CategoryController.class
    │ │ │ │ CommentReplayController.class
    │ │ │ │ ProductAttrValueController.class
    │ │ │ │ SkuImagesController.class
    │ │ │ │ SkuInfoController.class
    │ │ │ │ SkuSaleAttrValueController.class
    │ │ │ │ SpuCommentController.class
    │ │ │ │ SpuImagesController.class
    │ │ │ │ SpuInfoController.class
    │ │ │ │ SpuInfoDescController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ AttrAttrgroupRelationDao.class
    │ │ │ │ AttrDao.class
    │ │ │ │ AttrGroupDao.class
    │ │ │ │ BrandDao.class
    │ │ │ │ CategoryBrandRelationDao.class
    │ │ │ │ CategoryDao.class
    │ │ │ │ CommentReplayDao.class
    │ │ │ │ ProductAttrValueDao.class
    │ │ │ │ SkuImagesDao.class
    │ │ │ │ SkuInfoDao.class
    │ │ │ │ SkuSaleAttrValueDao.class
    │ │ │ │ SpuCommentDao.class
    │ │ │ │ SpuImagesDao.class
    │ │ │ │ SpuInfoDao.class
    │ │ │ │ SpuInfoDescDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ AttrAttrgroupRelationEntity.class
    │ │ │ │ AttrEntity.class
    │ │ │ │ AttrGroupEntity.class
    │ │ │ │ BrandEntity.class
    │ │ │ │ CategoryBrandRelationEntity.class
    │ │ │ │ CategoryEntity.class
    │ │ │ │ CommentReplayEntity.class
    │ │ │ │ ProductAttrValueEntity.class
    │ │ │ │ SkuImagesEntity.class
    │ │ │ │ SkuInfoEntity.class
    │ │ │ │ SkuSaleAttrValueEntity.class
    │ │ │ │ SpuCommentEntity.class
    │ │ │ │ SpuImagesEntity.class
    │ │ │ │ SpuInfoDescEntity.class
    │ │ │ │ SpuInfoEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ AttrAttrgroupRelationService.class
    │ │ │ │ AttrGroupService.class
    │ │ │ │ AttrService.class
    │ │ │ │ BrandService.class
    │ │ │ │ CategoryBrandRelationService.class
    │ │ │ │ CategoryService.class
    │ │ │ │ CommentReplayService.class
    │ │ │ │ ProductAttrValueService.class
    │ │ │ │ SkuImagesService.class
    │ │ │ │ SkuInfoService.class
    │ │ │ │ SkuSaleAttrValueService.class
    │ │ │ │ SpuCommentService.class
    │ │ │ │ SpuImagesService.class
    │ │ │ │ SpuInfoDescService.class
    │ │ │ │ SpuInfoService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ AttrAttrgroupRelationServiceImpl.class
    │ │ │ AttrGroupServiceImpl.class
    │ │ │ AttrServiceImpl.class
    │ │ │ BrandServiceImpl.class
    │ │ │ CategoryBrandRelationServiceImpl.class
    │ │ │ CategoryServiceImpl.class
    │ │ │ CommentReplayServiceImpl.class
    │ │ │ ProductAttrValueServiceImpl.class
    │ │ │ SkuImagesServiceImpl.class
    │ │ │ SkuInfoServiceImpl.class
    │ │ │ SkuSaleAttrValueServiceImpl.class
    │ │ │ SpuCommentServiceImpl.class
    │ │ │ SpuImagesServiceImpl.class
    │ │ │ SpuInfoDescServiceImpl.class
    │ │ │ SpuInfoServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─product
    │ │ │ AttrAttrgroupRelationDao.xml
    │ │ │ AttrDao.xml
    │ │ │ AttrGroupDao.xml
    │ │ │ BrandDao.xml
    │ │ │ CategoryBrandRelationDao.xml
    │ │ │ CategoryDao.xml
    │ │ │ CommentReplayDao.xml
    │ │ │ ProductAttrValueDao.xml
    │ │ │ SkuImagesDao.xml
    │ │ │ SkuInfoDao.xml
    │ │ │ SkuSaleAttrValueDao.xml
    │ │ │ SpuCommentDao.xml
    │ │ │ SpuImagesDao.xml
    │ │ │ SpuInfoDao.xml
    │ │ │ SpuInfoDescDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─product
    │ │ attr-add-or-update.vue
    │ │ attr.vue
    │ │ attrattrgrouprelation-add-or-update.vue
    │ │ attrattrgrouprelation.vue
    │ │ attrgroup-add-or-update.vue
    │ │ attrgroup.vue
    │ │ brand-add-or-update.vue
    │ │ brand.vue
    │ │ category-add-or-update.vue
    │ │ category.vue
    │ │ categorybrandrelation-add-or-update.vue
    │ │ categorybrandrelation.vue
    │ │ commentreplay-add-or-update.vue
    │ │ commentreplay.vue
    │ │ productattrvalue-add-or-update.vue
    │ │ productattrvalue.vue
    │ │ skuimages-add-or-update.vue
    │ │ skuimages.vue
    │ │ skuinfo-add-or-update.vue
    │ │ skuinfo.vue
    │ │ skusaleattrvalue-add-or-update.vue
    │ │ skusaleattrvalue.vue
    │ │ spucomment-add-or-update.vue
    │ │ spucomment.vue
    │ │ spuimages-add-or-update.vue
    │ │ spuimages.vue
    │ │ spuinfo-add-or-update.vue
    │ │ spuinfo.vue
    │ │ spuinfodesc-add-or-update.vue
    │ │ spuinfodesc.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ ├─generated-test-sources
    │ │ └─test-annotations
    │ ├─maven-status
    │ │ └─maven-compiler-plugin
    │ │ └─compile
    │ │ └─default-compile
    │ │ createdFiles.lst
    │ │ inputFiles.lst
    │ │
    │ └─test-classes
    │ └─com
    │ └─atguigu
    │ └─gulimall
    │ └─product
    │ GulimallProductApplicationTests.class

    ├─gulimall-ware
    │ │ .gitignore
    │ │ gulimall-ware.iml
    │ │ HELP.md
    │ │ mvnw
    │ │ mvnw.cmd
    │ │ pom.xml
    │ │
    │ ├─.mvn
    │ │ └─wrapper
    │ │ maven-wrapper.jar
    │ │ maven-wrapper.properties
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─com
    │ │ │ │ └─atguigu
    │ │ │ │ └─gulimall
    │ │ │ │ └─ware
    │ │ │ │ │ GulimallWareApplication.java
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ WareInfoController.java
    │ │ │ │ │ WareOrderTaskController.java
    │ │ │ │ │ WareOrderTaskDetailController.java
    │ │ │ │ │ WareSkuController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ WareInfoDao.java
    │ │ │ │ │ WareOrderTaskDao.java
    │ │ │ │ │ WareOrderTaskDetailDao.java
    │ │ │ │ │ WareSkuDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ WareInfoEntity.java
    │ │ │ │ │ WareOrderTaskDetailEntity.java
    │ │ │ │ │ WareOrderTaskEntity.java
    │ │ │ │ │ WareSkuEntity.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ WareInfoService.java
    │ │ │ │ │ WareOrderTaskDetailService.java
    │ │ │ │ │ WareOrderTaskService.java
    │ │ │ │ │ WareSkuService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ WareInfoServiceImpl.java
    │ │ │ │ WareOrderTaskDetailServiceImpl.java
    │ │ │ │ WareOrderTaskServiceImpl.java
    │ │ │ │ WareSkuServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application.yml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ └─ware
    │ │ │ │ WareInfoDao.xml
    │ │ │ │ WareOrderTaskDao.xml
    │ │ │ │ WareOrderTaskDetailDao.xml
    │ │ │ │ WareSkuDao.xml
    │ │ │ │
    │ │ │ └─src
    │ │ │ └─views
    │ │ │ └─modules
    │ │ │ └─ware
    │ │ │ wareinfo-add-or-update.vue
    │ │ │ wareinfo.vue
    │ │ │ wareordertask-add-or-update.vue
    │ │ │ wareordertask.vue
    │ │ │ wareordertaskdetail-add-or-update.vue
    │ │ │ wareordertaskdetail.vue
    │ │ │ waresku-add-or-update.vue
    │ │ │ waresku.vue
    │ │ │
    │ │ └─test
    │ │ └─java
    │ │ └─com
    │ │ └─atguigu
    │ │ └─gulimall
    │ │ └─gulimallware
    │ │ GulimallWareApplicationTests.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application.yml
    │ │ │
    │ │ ├─com
    │ │ │ └─atguigu
    │ │ │ └─gulimall
    │ │ │ ├─gulimallware
    │ │ │ │ GulimallWareApplication.class
    │ │ │ │
    │ │ │ └─ware
    │ │ │ │ GulimallWareApplication.class
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ WareInfoController.class
    │ │ │ │ WareOrderTaskController.class
    │ │ │ │ WareOrderTaskDetailController.class
    │ │ │ │ WareSkuController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ WareInfoDao.class
    │ │ │ │ WareOrderTaskDao.class
    │ │ │ │ WareOrderTaskDetailDao.class
    │ │ │ │ WareSkuDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ WareInfoEntity.class
    │ │ │ │ WareOrderTaskDetailEntity.class
    │ │ │ │ WareOrderTaskEntity.class
    │ │ │ │ WareSkuEntity.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ WareInfoService.class
    │ │ │ │ WareOrderTaskDetailService.class
    │ │ │ │ WareOrderTaskService.class
    │ │ │ │ WareSkuService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ WareInfoServiceImpl.class
    │ │ │ WareOrderTaskDetailServiceImpl.class
    │ │ │ WareOrderTaskServiceImpl.class
    │ │ │ WareSkuServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ └─ware
    │ │ │ WareInfoDao.xml
    │ │ │ WareOrderTaskDao.xml
    │ │ │ WareOrderTaskDetailDao.xml
    │ │ │ WareSkuDao.xml
    │ │ │
    │ │ └─src
    │ │ └─views
    │ │ └─modules
    │ │ └─ware
    │ │ wareinfo-add-or-update.vue
    │ │ wareinfo.vue
    │ │ wareordertask-add-or-update.vue
    │ │ wareordertask.vue
    │ │ wareordertaskdetail-add-or-update.vue
    │ │ wareordertaskdetail.vue
    │ │ waresku-add-or-update.vue
    │ │ waresku.vue
    │ │
    │ ├─generated-sources
    │ │ └─annotations
    │ ├─generated-test-sources
    │ │ └─test-annotations
    │ ├─maven-status
    │ │ └─maven-compiler-plugin
    │ │ └─compile
    │ │ └─default-compile
    │ │ createdFiles.lst
    │ │ inputFiles.lst
    │ │
    │ └─test-classes
    │ └─com
    │ └─atguigu
    │ └─gulimall
    │ └─gulimallware
    │ GulimallWareApplicationTests.class

    ├─renren-fast
    │ │ .gitignore
    │ │ docker-compose.yml
    │ │ Dockerfile
    │ │ LICENSE
    │ │ pom.xml
    │ │ README.md
    │ │ renren-fast.iml
    │ │
    │ ├─db
    │ │ mysql.sql
    │ │ oracle.sql
    │ │ postgresql.sql
    │ │ sqlserver.sql
    │ │
    │ ├─src
    │ │ ├─main
    │ │ │ ├─java
    │ │ │ │ └─io
    │ │ │ │ └─renren
    │ │ │ │ │ RenrenApplication.java
    │ │ │ │ │
    │ │ │ │ ├─common
    │ │ │ │ │ ├─annotation
    │ │ │ │ │ │ SysLog.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─aspect
    │ │ │ │ │ │ RedisAspect.java
    │ │ │ │ │ │ SysLogAspect.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─exception
    │ │ │ │ │ │ RRException.java
    │ │ │ │ │ │ RRExceptionHandler.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─utils
    │ │ │ │ │ │ ConfigConstant.java
    │ │ │ │ │ │ Constant.java
    │ │ │ │ │ │ DateUtils.java
    │ │ │ │ │ │ HttpContextUtils.java
    │ │ │ │ │ │ IPUtils.java
    │ │ │ │ │ │ MapUtils.java
    │ │ │ │ │ │ PageUtils.java
    │ │ │ │ │ │ Query.java
    │ │ │ │ │ │ R.java
    │ │ │ │ │ │ RedisKeys.java
    │ │ │ │ │ │ RedisUtils.java
    │ │ │ │ │ │ ShiroUtils.java
    │ │ │ │ │ │ SpringContextUtils.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─validator
    │ │ │ │ │ │ │ Assert.java
    │ │ │ │ │ │ │ ValidatorUtils.java
    │ │ │ │ │ │ │
    │ │ │ │ │ │ └─group
    │ │ │ │ │ │ AddGroup.java
    │ │ │ │ │ │ AliyunGroup.java
    │ │ │ │ │ │ Group.java
    │ │ │ │ │ │ QcloudGroup.java
    │ │ │ │ │ │ QiniuGroup.java
    │ │ │ │ │ │ UpdateGroup.java
    │ │ │ │ │ │
    │ │ │ │ │ └─xss
    │ │ │ │ │ HTMLFilter.java
    │ │ │ │ │ SQLFilter.java
    │ │ │ │ │ XssFilter.java
    │ │ │ │ │ XssHttpServletRequestWrapper.java
    │ │ │ │ │
    │ │ │ │ ├─config
    │ │ │ │ │ CorsConfig.java
    │ │ │ │ │ FilterConfig.java
    │ │ │ │ │ KaptchaConfig.java
    │ │ │ │ │ MybatisPlusConfig.java
    │ │ │ │ │ RedisConfig.java
    │ │ │ │ │ ShiroConfig.java
    │ │ │ │ │ SwaggerConfig.java
    │ │ │ │ │
    │ │ │ │ ├─datasource
    │ │ │ │ │ ├─annotation
    │ │ │ │ │ │ DataSource.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─aspect
    │ │ │ │ │ │ DataSourceAspect.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─config
    │ │ │ │ │ │ DynamicContextHolder.java
    │ │ │ │ │ │ DynamicDataSource.java
    │ │ │ │ │ │ DynamicDataSourceConfig.java
    │ │ │ │ │ │ DynamicDataSourceFactory.java
    │ │ │ │ │ │
    │ │ │ │ │ └─properties
    │ │ │ │ │ DataSourceProperties.java
    │ │ │ │ │ DynamicDataSourceProperties.java
    │ │ │ │ │
    │ │ │ │ └─modules
    │ │ │ │ ├─app
    │ │ │ │ │ ├─annotation
    │ │ │ │ │ │ Login.java
    │ │ │ │ │ │ LoginUser.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─config
    │ │ │ │ │ │ WebMvcConfig.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─controller
    │ │ │ │ │ │ AppLoginController.java
    │ │ │ │ │ │ AppRegisterController.java
    │ │ │ │ │ │ AppTestController.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─dao
    │ │ │ │ │ │ UserDao.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─entity
    │ │ │ │ │ │ UserEntity.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─form
    │ │ │ │ │ │ LoginForm.java
    │ │ │ │ │ │ RegisterForm.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─interceptor
    │ │ │ │ │ │ AuthorizationInterceptor.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─resolver
    │ │ │ │ │ │ LoginUserHandlerMethodArgumentResolver.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─service
    │ │ │ │ │ │ │ UserService.java
    │ │ │ │ │ │ │
    │ │ │ │ │ │ └─impl
    │ │ │ │ │ │ UserServiceImpl.java
    │ │ │ │ │ │
    │ │ │ │ │ └─utils
    │ │ │ │ │ JwtUtils.java
    │ │ │ │ │
    │ │ │ │ ├─job
    │ │ │ │ │ ├─config
    │ │ │ │ │ │ ScheduleConfig.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─controller
    │ │ │ │ │ │ ScheduleJobController.java
    │ │ │ │ │ │ ScheduleJobLogController.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─dao
    │ │ │ │ │ │ ScheduleJobDao.java
    │ │ │ │ │ │ ScheduleJobLogDao.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─entity
    │ │ │ │ │ │ ScheduleJobEntity.java
    │ │ │ │ │ │ ScheduleJobLogEntity.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─service
    │ │ │ │ │ │ │ ScheduleJobLogService.java
    │ │ │ │ │ │ │ ScheduleJobService.java
    │ │ │ │ │ │ │
    │ │ │ │ │ │ └─impl
    │ │ │ │ │ │ ScheduleJobLogServiceImpl.java
    │ │ │ │ │ │ ScheduleJobServiceImpl.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─task
    │ │ │ │ │ │ ITask.java
    │ │ │ │ │ │ TestTask.java
    │ │ │ │ │ │
    │ │ │ │ │ └─utils
    │ │ │ │ │ ScheduleJob.java
    │ │ │ │ │ ScheduleUtils.java
    │ │ │ │ │
    │ │ │ │ ├─oss
    │ │ │ │ │ ├─cloud
    │ │ │ │ │ │ AliyunCloudStorageService.java
    │ │ │ │ │ │ CloudStorageConfig.java
    │ │ │ │ │ │ CloudStorageService.java
    │ │ │ │ │ │ OSSFactory.java
    │ │ │ │ │ │ QcloudCloudStorageService.java
    │ │ │ │ │ │ QiniuCloudStorageService.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─controller
    │ │ │ │ │ │ SysOssController.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─dao
    │ │ │ │ │ │ SysOssDao.java
    │ │ │ │ │ │
    │ │ │ │ │ ├─entity
    │ │ │ │ │ │ SysOssEntity.java
    │ │ │ │ │ │
    │ │ │ │ │ └─service
    │ │ │ │ │ │ SysOssService.java
    │ │ │ │ │ │
    │ │ │ │ │ └─impl
    │ │ │ │ │ SysOssServiceImpl.java
    │ │ │ │ │
    │ │ │ │ └─sys
    │ │ │ │ ├─controller
    │ │ │ │ │ AbstractController.java
    │ │ │ │ │ SysConfigController.java
    │ │ │ │ │ SysLogController.java
    │ │ │ │ │ SysLoginController.java
    │ │ │ │ │ SysMenuController.java
    │ │ │ │ │ SysRoleController.java
    │ │ │ │ │ SysUserController.java
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ SysCaptchaDao.java
    │ │ │ │ │ SysConfigDao.java
    │ │ │ │ │ SysLogDao.java
    │ │ │ │ │ SysMenuDao.java
    │ │ │ │ │ SysRoleDao.java
    │ │ │ │ │ SysRoleMenuDao.java
    │ │ │ │ │ SysUserDao.java
    │ │ │ │ │ SysUserRoleDao.java
    │ │ │ │ │ SysUserTokenDao.java
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ SysCaptchaEntity.java
    │ │ │ │ │ SysConfigEntity.java
    │ │ │ │ │ SysLogEntity.java
    │ │ │ │ │ SysMenuEntity.java
    │ │ │ │ │ SysRoleEntity.java
    │ │ │ │ │ SysRoleMenuEntity.java
    │ │ │ │ │ SysUserEntity.java
    │ │ │ │ │ SysUserRoleEntity.java
    │ │ │ │ │ SysUserTokenEntity.java
    │ │ │ │ │
    │ │ │ │ ├─form
    │ │ │ │ │ PasswordForm.java
    │ │ │ │ │ SysLoginForm.java
    │ │ │ │ │
    │ │ │ │ ├─oauth2
    │ │ │ │ │ OAuth2Filter.java
    │ │ │ │ │ OAuth2Realm.java
    │ │ │ │ │ OAuth2Token.java
    │ │ │ │ │ TokenGenerator.java
    │ │ │ │ │
    │ │ │ │ ├─redis
    │ │ │ │ │ SysConfigRedis.java
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ ShiroService.java
    │ │ │ │ │ SysCaptchaService.java
    │ │ │ │ │ SysConfigService.java
    │ │ │ │ │ SysLogService.java
    │ │ │ │ │ SysMenuService.java
    │ │ │ │ │ SysRoleMenuService.java
    │ │ │ │ │ SysRoleService.java
    │ │ │ │ │ SysUserRoleService.java
    │ │ │ │ │ SysUserService.java
    │ │ │ │ │ SysUserTokenService.java
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ ShiroServiceImpl.java
    │ │ │ │ SysCaptchaServiceImpl.java
    │ │ │ │ SysConfigServiceImpl.java
    │ │ │ │ SysLogServiceImpl.java
    │ │ │ │ SysMenuServiceImpl.java
    │ │ │ │ SysRoleMenuServiceImpl.java
    │ │ │ │ SysRoleServiceImpl.java
    │ │ │ │ SysUserRoleServiceImpl.java
    │ │ │ │ SysUserServiceImpl.java
    │ │ │ │ SysUserTokenServiceImpl.java
    │ │ │ │
    │ │ │ └─resources
    │ │ │ │ application-dev.yml
    │ │ │ │ application-prod.yml
    │ │ │ │ application-test.yml
    │ │ │ │ application.yml
    │ │ │ │ banner.txt
    │ │ │ │ logback-spring.xml
    │ │ │ │
    │ │ │ ├─mapper
    │ │ │ │ ├─app
    │ │ │ │ │ UserDao.xml
    │ │ │ │ │
    │ │ │ │ ├─job
    │ │ │ │ │ ScheduleJobDao.xml
    │ │ │ │ │ ScheduleJobLogDao.xml
    │ │ │ │ │
    │ │ │ │ ├─oss
    │ │ │ │ │ SysOssDao.xml
    │ │ │ │ │
    │ │ │ │ └─sys
    │ │ │ │ SysConfigDao.xml
    │ │ │ │ SysLogDao.xml
    │ │ │ │ SysMenuDao.xml
    │ │ │ │ SysRoleDao.xml
    │ │ │ │ SysRoleMenuDao.xml
    │ │ │ │ SysUserDao.xml
    │ │ │ │ SysUserRoleDao.xml
    │ │ │ │ SysUserTokenDao.xml
    │ │ │ │
    │ │ │ └─static
    │ │ │ │ favicon.ico
    │ │ │ │
    │ │ │ └─swagger
    │ │ │ │ favicon-16x16.png
    │ │ │ │ favicon-32x32.png
    │ │ │ │ index.html
    │ │ │ │ index.yaml
    │ │ │ │ o2c.html
    │ │ │ │ oauth2-redirect.html
    │ │ │ │ swagger-ui-bundle.js
    │ │ │ │ swagger-ui-bundle.js.map
    │ │ │ │ swagger-ui-standalone-preset.js
    │ │ │ │ swagger-ui-standalone-preset.js.map
    │ │ │ │ swagger-ui.css
    │ │ │ │ swagger-ui.css.map
    │ │ │ │ swagger-ui.js
    │ │ │ │ swagger-ui.js.map
    │ │ │ │ swagger-ui.min.js
    │ │ │ │
    │ │ │ ├─css
    │ │ │ │ print.css
    │ │ │ │ reset.css
    │ │ │ │ screen.css
    │ │ │ │ style.css
    │ │ │ │ typography.css
    │ │ │ │
    │ │ │ ├─fonts
    │ │ │ │ DroidSans-Bold.ttf
    │ │ │ │ DroidSans.ttf
    │ │ │ │
    │ │ │ ├─images
    │ │ │ │ collapse.gif
    │ │ │ │ expand.gif
    │ │ │ │ explorer_icons.png
    │ │ │ │ favicon-16x16.png
    │ │ │ │ favicon-32x32.png
    │ │ │ │ favicon.ico
    │ │ │ │ logo_small.png
    │ │ │ │ pet_store_api.png
    │ │ │ │ throbber.gif
    │ │ │ │ wordnik_api.png
    │ │ │ │
    │ │ │ ├─lang
    │ │ │ │ en.js
    │ │ │ │ translator.js
    │ │ │ │ zh-cn.js
    │ │ │ │
    │ │ │ └─lib
    │ │ │ backbone-min.js
    │ │ │ es5-shim.js
    │ │ │ handlebars-4.0.5.js
    │ │ │ highlight.9.1.0.pack.js
    │ │ │ highlight.9.1.0.pack_extended.js
    │ │ │ jquery-1.8.0.min.js
    │ │ │ jquery.ba-bbq.min.js
    │ │ │ jquery.slideto.min.js
    │ │ │ jquery.wiggle.min.js
    │ │ │ js-yaml.min.js
    │ │ │ jsoneditor.min.js
    │ │ │ lodash.min.js
    │ │ │ marked.js
    │ │ │ object-assign-pollyfill.js
    │ │ │ sanitize-html.min.js
    │ │ │ swagger-oauth.js
    │ │ │
    │ │ └─test
    │ │ └─java
    │ │ └─io
    │ │ └─renren
    │ │ │ DynamicDataSourceTest.java
    │ │ │ JwtTest.java
    │ │ │ RedisTest.java
    │ │ │
    │ │ └─service
    │ │ DynamicDataSourceTestService.java
    │ │
    │ └─target
    │ ├─classes
    │ │ │ application-dev.yml
    │ │ │ application-prod.yml
    │ │ │ application-test.yml
    │ │ │ application.yml
    │ │ │ banner.txt
    │ │ │ logback-spring.xml
    │ │ │
    │ │ ├─io
    │ │ │ └─renren
    │ │ │ │ RenrenApplication.class
    │ │ │ │
    │ │ │ ├─common
    │ │ │ │ ├─annotation
    │ │ │ │ │ SysLog.class
    │ │ │ │ │
    │ │ │ │ ├─aspect
    │ │ │ │ │ RedisAspect.class
    │ │ │ │ │ SysLogAspect.class
    │ │ │ │ │
    │ │ │ │ ├─exception
    │ │ │ │ │ RRException.class
    │ │ │ │ │ RRExceptionHandler.class
    │ │ │ │ │
    │ │ │ │ ├─utils
    │ │ │ │ │ ConfigConstant.class
    │ │ │ │ │ Constant$CloudService.class
    │ │ │ │ │ Constant$MenuType.class
    │ │ │ │ │ Constant$ScheduleStatus.class
    │ │ │ │ │ Constant.class
    │ │ │ │ │ DateUtils.class
    │ │ │ │ │ HttpContextUtils.class
    │ │ │ │ │ IPUtils.class
    │ │ │ │ │ MapUtils.class
    │ │ │ │ │ PageUtils.class
    │ │ │ │ │ Query.class
    │ │ │ │ │ R.class
    │ │ │ │ │ RedisKeys.class
    │ │ │ │ │ RedisUtils.class
    │ │ │ │ │ ShiroUtils.class
    │ │ │ │ │ SpringContextUtils.class
    │ │ │ │ │
    │ │ │ │ ├─validator
    │ │ │ │ │ │ Assert.class
    │ │ │ │ │ │ ValidatorUtils.class
    │ │ │ │ │ │
    │ │ │ │ │ └─group
    │ │ │ │ │ AddGroup.class
    │ │ │ │ │ AliyunGroup.class
    │ │ │ │ │ Group.class
    │ │ │ │ │ QcloudGroup.class
    │ │ │ │ │ QiniuGroup.class
    │ │ │ │ │ UpdateGroup.class
    │ │ │ │ │
    │ │ │ │ └─xss
    │ │ │ │ HTMLFilter.class
    │ │ │ │ SQLFilter.class
    │ │ │ │ XssFilter.class
    │ │ │ │ XssHttpServletRequestWrapper$1.class
    │ │ │ │ XssHttpServletRequestWrapper.class
    │ │ │ │
    │ │ │ ├─config
    │ │ │ │ CorsConfig.class
    │ │ │ │ FilterConfig.class
    │ │ │ │ KaptchaConfig.class
    │ │ │ │ MybatisPlusConfig.class
    │ │ │ │ RedisConfig.class
    │ │ │ │ ShiroConfig.class
    │ │ │ │ SwaggerConfig.class
    │ │ │ │
    │ │ │ ├─datasource
    │ │ │ │ ├─annotation
    │ │ │ │ │ DataSource.class
    │ │ │ │ │
    │ │ │ │ ├─aspect
    │ │ │ │ │ DataSourceAspect.class
    │ │ │ │ │
    │ │ │ │ ├─config
    │ │ │ │ │ DynamicContextHolder$1.class
    │ │ │ │ │ DynamicContextHolder.class
    │ │ │ │ │ DynamicDataSource.class
    │ │ │ │ │ DynamicDataSourceConfig.class
    │ │ │ │ │ DynamicDataSourceFactory.class
    │ │ │ │ │
    │ │ │ │ └─properties
    │ │ │ │ DataSourceProperties.class
    │ │ │ │ DynamicDataSourceProperties.class
    │ │ │ │
    │ │ │ └─modules
    │ │ │ ├─app
    │ │ │ │ ├─annotation
    │ │ │ │ │ Login.class
    │ │ │ │ │ LoginUser.class
    │ │ │ │ │
    │ │ │ │ ├─config
    │ │ │ │ │ WebMvcConfig.class
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ AppLoginController.class
    │ │ │ │ │ AppRegisterController.class
    │ │ │ │ │ AppTestController.class
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ UserDao.class
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ UserEntity.class
    │ │ │ │ │
    │ │ │ │ ├─form
    │ │ │ │ │ LoginForm.class
    │ │ │ │ │ RegisterForm.class
    │ │ │ │ │
    │ │ │ │ ├─interceptor
    │ │ │ │ │ AuthorizationInterceptor.class
    │ │ │ │ │
    │ │ │ │ ├─resolver
    │ │ │ │ │ LoginUserHandlerMethodArgumentResolver.class
    │ │ │ │ │
    │ │ │ │ ├─service
    │ │ │ │ │ │ UserService.class
    │ │ │ │ │ │
    │ │ │ │ │ └─impl
    │ │ │ │ │ UserServiceImpl.class
    │ │ │ │ │
    │ │ │ │ └─utils
    │ │ │ │ JwtUtils.class
    │ │ │ │
    │ │ │ ├─job
    │ │ │ │ ├─config
    │ │ │ │ │ ScheduleConfig.class
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ ScheduleJobController.class
    │ │ │ │ │ ScheduleJobLogController.class
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ ScheduleJobDao.class
    │ │ │ │ │ ScheduleJobLogDao.class
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ ScheduleJobEntity.class
    │ │ │ │ │ ScheduleJobLogEntity.class
    │ │ │ │ │
    │ │ │ │ ├─service
    │ │ │ │ │ │ ScheduleJobLogService.class
    │ │ │ │ │ │ ScheduleJobService.class
    │ │ │ │ │ │
    │ │ │ │ │ └─impl
    │ │ │ │ │ ScheduleJobLogServiceImpl.class
    │ │ │ │ │ ScheduleJobServiceImpl.class
    │ │ │ │ │
    │ │ │ │ ├─task
    │ │ │ │ │ ITask.class
    │ │ │ │ │ TestTask.class
    │ │ │ │ │
    │ │ │ │ └─utils
    │ │ │ │ ScheduleJob.class
    │ │ │ │ ScheduleUtils.class
    │ │ │ │
    │ │ │ ├─oss
    │ │ │ │ ├─cloud
    │ │ │ │ │ AliyunCloudStorageService.class
    │ │ │ │ │ CloudStorageConfig.class
    │ │ │ │ │ CloudStorageService.class
    │ │ │ │ │ OSSFactory.class
    │ │ │ │ │ QcloudCloudStorageService.class
    │ │ │ │ │ QiniuCloudStorageService.class
    │ │ │ │ │
    │ │ │ │ ├─controller
    │ │ │ │ │ SysOssController.class
    │ │ │ │ │
    │ │ │ │ ├─dao
    │ │ │ │ │ SysOssDao.class
    │ │ │ │ │
    │ │ │ │ ├─entity
    │ │ │ │ │ SysOssEntity.class
    │ │ │ │ │
    │ │ │ │ └─service
    │ │ │ │ │ SysOssService.class
    │ │ │ │ │
    │ │ │ │ └─impl
    │ │ │ │ SysOssServiceImpl.class
    │ │ │ │
    │ │ │ └─sys
    │ │ │ ├─controller
    │ │ │ │ AbstractController.class
    │ │ │ │ SysConfigController.class
    │ │ │ │ SysLogController.class
    │ │ │ │ SysLoginController.class
    │ │ │ │ SysMenuController.class
    │ │ │ │ SysRoleController.class
    │ │ │ │ SysUserController.class
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ SysCaptchaDao.class
    │ │ │ │ SysConfigDao.class
    │ │ │ │ SysLogDao.class
    │ │ │ │ SysMenuDao.class
    │ │ │ │ SysRoleDao.class
    │ │ │ │ SysRoleMenuDao.class
    │ │ │ │ SysUserDao.class
    │ │ │ │ SysUserRoleDao.class
    │ │ │ │ SysUserTokenDao.class
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ SysCaptchaEntity.class
    │ │ │ │ SysConfigEntity.class
    │ │ │ │ SysLogEntity.class
    │ │ │ │ SysMenuEntity.class
    │ │ │ │ SysRoleEntity.class
    │ │ │ │ SysRoleMenuEntity.class
    │ │ │ │ SysUserEntity.class
    │ │ │ │ SysUserRoleEntity.class
    │ │ │ │ SysUserTokenEntity.class
    │ │ │ │
    │ │ │ ├─form
    │ │ │ │ PasswordForm.class
    │ │ │ │ SysLoginForm.class
    │ │ │ │
    │ │ │ ├─oauth2
    │ │ │ │ OAuth2Filter.class
    │ │ │ │ OAuth2Realm.class
    │ │ │ │ OAuth2Token.class
    │ │ │ │ TokenGenerator.class
    │ │ │ │
    │ │ │ ├─redis
    │ │ │ │ SysConfigRedis.class
    │ │ │ │
    │ │ │ └─service
    │ │ │ │ ShiroService.class
    │ │ │ │ SysCaptchaService.class
    │ │ │ │ SysConfigService.class
    │ │ │ │ SysLogService.class
    │ │ │ │ SysMenuService.class
    │ │ │ │ SysRoleMenuService.class
    │ │ │ │ SysRoleService.class
    │ │ │ │ SysUserRoleService.class
    │ │ │ │ SysUserService.class
    │ │ │ │ SysUserTokenService.class
    │ │ │ │
    │ │ │ └─impl
    │ │ │ ShiroServiceImpl.class
    │ │ │ SysCaptchaServiceImpl.class
    │ │ │ SysConfigServiceImpl.class
    │ │ │ SysLogServiceImpl.class
    │ │ │ SysMenuServiceImpl.class
    │ │ │ SysRoleMenuServiceImpl.class
    │ │ │ SysRoleServiceImpl.class
    │ │ │ SysUserRoleServiceImpl.class
    │ │ │ SysUserServiceImpl.class
    │ │ │ SysUserTokenServiceImpl.class
    │ │ │
    │ │ ├─mapper
    │ │ │ ├─app
    │ │ │ │ UserDao.xml
    │ │ │ │
    │ │ │ ├─job
    │ │ │ │ ScheduleJobDao.xml
    │ │ │ │ ScheduleJobLogDao.xml
    │ │ │ │
    │ │ │ ├─oss
    │ │ │ │ SysOssDao.xml
    │ │ │ │
    │ │ │ └─sys
    │ │ │ SysConfigDao.xml
    │ │ │ SysLogDao.xml
    │ │ │ SysMenuDao.xml
    │ │ │ SysRoleDao.xml
    │ │ │ SysRoleMenuDao.xml
    │ │ │ SysUserDao.xml
    │ │ │ SysUserRoleDao.xml
    │ │ │ SysUserTokenDao.xml
    │ │ │
    │ │ ├─META-INF
    │ │ │ spring-configuration-metadata.json
    │ │ │
    │ │ └─static
    │ │ │ favicon.ico
    │ │ │
    │ │ └─swagger
    │ │ │ favicon-16x16.png
    │ │ │ favicon-32x32.png
    │ │ │ index.html
    │ │ │ index.yaml
    │ │ │ o2c.html
    │ │ │ oauth2-redirect.html
    │ │ │ swagger-ui-bundle.js
    │ │ │ swagger-ui-bundle.js.map
    │ │ │ swagger-ui-standalone-preset.js
    │ │ │ swagger-ui-standalone-preset.js.map
    │ │ │ swagger-ui.css
    │ │ │ swagger-ui.css.map
    │ │ │ swagger-ui.js
    │ │ │ swagger-ui.js.map
    │ │ │ swagger-ui.min.js
    │ │ │
    │ │ ├─css
    │ │ │ print.css
    │ │ │ reset.css
    │ │ │ screen.css
    │ │ │ style.css
    │ │ │ typography.css
    │ │ │
    │ │ ├─fonts
    │ │ │ DroidSans-Bold.ttf
    │ │ │ DroidSans.ttf
    │ │ │
    │ │ ├─images
    │ │ │ collapse.gif
    │ │ │ expand.gif
    │ │ │ explorer_icons.png
    │ │ │ favicon-16x16.png
    │ │ │ favicon-32x32.png
    │ │ │ favicon.ico
    │ │ │ logo_small.png
    │ │ │ pet_store_api.png
    │ │ │ throbber.gif
    │ │ │ wordnik_api.png
    │ │ │
    │ │ ├─lang
    │ │ │ en.js
    │ │ │ translator.js
    │ │ │ zh-cn.js
    │ │ │
    │ │ └─lib
    │ │ backbone-min.js
    │ │ es5-shim.js
    │ │ handlebars-4.0.5.js
    │ │ highlight.9.1.0.pack.js
    │ │ highlight.9.1.0.pack_extended.js
    │ │ jquery-1.8.0.min.js
    │ │ jquery.ba-bbq.min.js
    │ │ jquery.slideto.min.js
    │ │ jquery.wiggle.min.js
    │ │ js-yaml.min.js
    │ │ jsoneditor.min.js
    │ │ lodash.min.js
    │ │ marked.js
    │ │ object-assign-pollyfill.js
    │ │ sanitize-html.min.js
    │ │ swagger-oauth.js
    │ │
    │ └─generated-sources
    │ └─annotations
    └─renren-generator
    │ .gitignore
    │ LICENSE
    │ pom.xml
    │ README.md
    │ renren-generator.iml

    ├─src
    │ ├─main
    │ │ ├─java
    │ │ │ └─io
    │ │ │ └─renren
    │ │ │ │ RenrenApplicationGenerator.java
    │ │ │ │
    │ │ │ ├─adaptor
    │ │ │ │ MongoTableInfoAdaptor.java
    │ │ │ │
    │ │ │ ├─config
    │ │ │ │ DbConfig.java
    │ │ │ │ MongoCondition.java
    │ │ │ │ MongoConfig.java
    │ │ │ │ MongoManager.java
    │ │ │ │ MongoNullCondition.java
    │ │ │ │
    │ │ │ ├─controller
    │ │ │ │ SysGeneratorController.java
    │ │ │ │
    │ │ │ ├─dao
    │ │ │ │ GeneratorDao.java
    │ │ │ │ MongoDBGeneratorDao.java
    │ │ │ │ MySQLGeneratorDao.java
    │ │ │ │ OracleGeneratorDao.java
    │ │ │ │ PostgreSQLGeneratorDao.java
    │ │ │ │ SQLServerGeneratorDao.java
    │ │ │ │
    │ │ │ ├─entity
    │ │ │ │ │ ColumnEntity.java
    │ │ │ │ │ TableEntity.java
    │ │ │ │ │
    │ │ │ │ └─mongo
    │ │ │ │ MongoDefinition.java
    │ │ │ │ MongoGeneratorEntity.java
    │ │ │ │ Type.java
    │ │ │ │
    │ │ │ ├─factory
    │ │ │ │ MongoDBCollectionFactory.java
    │ │ │ │
    │ │ │ ├─service
    │ │ │ │ SysGeneratorService.java
    │ │ │ │
    │ │ │ └─utils
    │ │ │ Constant.java
    │ │ │ DateUtils.java
    │ │ │ GenUtils.java
    │ │ │ MongoScanner.java
    │ │ │ PageUtils.java
    │ │ │ Query.java
    │ │ │ R.java
    │ │ │ RRException.java
    │ │ │ RRExceptionHandler.java
    │ │ │
    │ │ └─resources
    │ │ │ application.yml
    │ │ │ generator.properties
    │ │ │
    │ │ ├─mapper
    │ │ │ MySQLGeneratorDao.xml
    │ │ │ OracleGeneratorDao.xml
    │ │ │ PostgreSQLGeneratorDao.xml
    │ │ │ SQLServerGeneratorDao.xml
    │ │ │
    │ │ ├─static
    │ │ │ │ favicon.ico
    │ │ │ │
    │ │ │ ├─css
    │ │ │ │ AdminLTE.min.css
    │ │ │ │ all-skins.min.css
    │ │ │ │ bootstrap.min.css
    │ │ │ │ font-awesome.min.css
    │ │ │ │ main.css
    │ │ │ │
    │ │ │ ├─fonts
    │ │ │ │ fontawesome-webfont.eot
    │ │ │ │ fontawesome-webfont.svg
    │ │ │ │ fontawesome-webfont.ttf
    │ │ │ │ fontawesome-webfont.woff
    │ │ │ │ fontawesome-webfont.woff2
    │ │ │ │ FontAwesome.otf
    │ │ │ │ glyphicons-halflings-regular.eot
    │ │ │ │ glyphicons-halflings-regular.svg
    │ │ │ │ glyphicons-halflings-regular.ttf
    │ │ │ │ glyphicons-halflings-regular.woff
    │ │ │ │ glyphicons-halflings-regular.woff2
    │ │ │ │
    │ │ │ ├─js
    │ │ │ │ common.js
    │ │ │ │ generator.js
    │ │ │ │ index.js
    │ │ │ │
    │ │ │ ├─libs
    │ │ │ │ app.js
    │ │ │ │ app.min.js
    │ │ │ │ bootstrap.min.js
    │ │ │ │ fastclick.min.js
    │ │ │ │ jquery.min.js
    │ │ │ │ jquery.slimscroll.min.js
    │ │ │ │ router.js
    │ │ │ │ vue.min.js
    │ │ │ │
    │ │ │ └─plugins
    │ │ │ ├─jqgrid
    │ │ │ │ grid.locale-cn.js
    │ │ │ │ jquery.jqGrid.min.js
    │ │ │ │ ui.jqgrid-bootstrap-ui.css
    │ │ │ │ ui.jqgrid-bootstrap.css
    │ │ │ │ ui.jqgrid.css
    │ │ │ │
    │ │ │ └─layer
    │ │ │ │ layer.js
    │ │ │ │
    │ │ │ ├─mobile
    │ │ │ │ │ layer.js
    │ │ │ │ │
    │ │ │ │ └─need
    │ │ │ │ layer.css
    │ │ │ │
    │ │ │ └─skin
    │ │ │ ├─default
    │ │ │ │ icon-ext.png
    │ │ │ │ icon.png
    │ │ │ │ layer.css
    │ │ │ │ loading-0.gif
    │ │ │ │ loading-1.gif
    │ │ │ │ loading-2.gif
    │ │ │ │
    │ │ │ └─moon
    │ │ │ default.png
    │ │ │ style.css
    │ │ │
    │ │ ├─template
    │ │ │ add-or-update.vue.vm
    │ │ │ Controller.java.vm
    │ │ │ Dao.java.vm
    │ │ │ Dao.xml.vm
    │ │ │ Entity.java.vm
    │ │ │ index.vue.vm
    │ │ │ menu.sql.vm
    │ │ │ MongoChildrenEntity.java.vm
    │ │ │ MongoEntity.java.vm
    │ │ │ Service.java.vm
    │ │ │ ServiceImpl.java.vm
    │ │ │
    │ │ └─views
    │ │ generator.html
    │ │ index.html
    │ │ main.html
    │ │
    │ └─test
    │ └─java
    │ └─io
    │ └─renren
    │ RenrenApplicationTests.java

    └─target
    ├─classes
    │ │ application.yml
    │ │ generator.properties
    │ │
    │ ├─io
    │ │ └─renren
    │ │ │ RenrenApplicationGenerator.class
    │ │ │
    │ │ ├─adaptor
    │ │ │ MongoTableInfoAdaptor.class
    │ │ │
    │ │ ├─config
    │ │ │ DbConfig.class
    │ │ │ MongoCondition.class
    │ │ │ MongoConfig.class
    │ │ │ MongoManager.class
    │ │ │ MongoNullCondition.class
    │ │ │
    │ │ ├─controller
    │ │ │ SysGeneratorController.class
    │ │ │
    │ │ ├─dao
    │ │ │ GeneratorDao.class
    │ │ │ MongoDBGeneratorDao.class
    │ │ │ MySQLGeneratorDao.class
    │ │ │ OracleGeneratorDao.class
    │ │ │ PostgreSQLGeneratorDao.class
    │ │ │ SQLServerGeneratorDao.class
    │ │ │
    │ │ ├─entity
    │ │ │ │ ColumnEntity.class
    │ │ │ │ TableEntity.class
    │ │ │ │
    │ │ │ └─mongo
    │ │ │ MongoDefinition.class
    │ │ │ MongoGeneratorEntity.class
    │ │ │ Type.class
    │ │ │
    │ │ ├─factory
    │ │ │ MongoDBCollectionFactory.class
    │ │ │
    │ │ ├─service
    │ │ │ SysGeneratorService.class
    │ │ │
    │ │ └─utils
    │ │ Constant.class
    │ │ DateUtils.class
    │ │ GenUtils.class
    │ │ MongoScanner$ForkJoinGetProcessName.class
    │ │ MongoScanner$ForkJoinProcessType.class
    │ │ MongoScanner.class
    │ │ PageUtils.class
    │ │ Query.class
    │ │ R.class
    │ │ RRException.class
    │ │ RRExceptionHandler.class
    │ │
    │ ├─mapper
    │ │ MySQLGeneratorDao.xml
    │ │ OracleGeneratorDao.xml
    │ │ PostgreSQLGeneratorDao.xml
    │ │ SQLServerGeneratorDao.xml
    │ │
    │ ├─static
    │ │ │ favicon.ico
    │ │ │
    │ │ ├─css
    │ │ │ AdminLTE.min.css
    │ │ │ all-skins.min.css
    │ │ │ bootstrap.min.css
    │ │ │ font-awesome.min.css
    │ │ │ main.css
    │ │ │
    │ │ ├─fonts
    │ │ │ fontawesome-webfont.eot
    │ │ │ fontawesome-webfont.svg
    │ │ │ fontawesome-webfont.ttf
    │ │ │ fontawesome-webfont.woff
    │ │ │ fontawesome-webfont.woff2
    │ │ │ FontAwesome.otf
    │ │ │ glyphicons-halflings-regular.eot
    │ │ │ glyphicons-halflings-regular.svg
    │ │ │ glyphicons-halflings-regular.ttf
    │ │ │ glyphicons-halflings-regular.woff
    │ │ │ glyphicons-halflings-regular.woff2
    │ │ │
    │ │ ├─js
    │ │ │ common.js
    │ │ │ generator.js
    │ │ │ index.js
    │ │ │
    │ │ ├─libs
    │ │ │ app.js
    │ │ │ app.min.js
    │ │ │ bootstrap.min.js
    │ │ │ fastclick.min.js
    │ │ │ jquery.min.js
    │ │ │ jquery.slimscroll.min.js
    │ │ │ router.js
    │ │ │ vue.min.js
    │ │ │
    │ │ └─plugins
    │ │ ├─jqgrid
    │ │ │ grid.locale-cn.js
    │ │ │ jquery.jqGrid.min.js
    │ │ │ ui.jqgrid-bootstrap-ui.css
    │ │ │ ui.jqgrid-bootstrap.css
    │ │ │ ui.jqgrid.css
    │ │ │
    │ │ └─layer
    │ │ │ layer.js
    │ │ │
    │ │ ├─mobile
    │ │ │ │ layer.js
    │ │ │ │
    │ │ │ └─need
    │ │ │ layer.css
    │ │ │
    │ │ └─skin
    │ │ ├─default
    │ │ │ icon-ext.png
    │ │ │ icon.png
    │ │ │ layer.css
    │ │ │ loading-0.gif
    │ │ │ loading-1.gif
    │ │ │ loading-2.gif
    │ │ │
    │ │ └─moon
    │ │ default.png
    │ │ style.css
    │ │
    │ ├─template
    │ │ add-or-update.vue.vm
    │ │ Controller.java.vm
    │ │ Dao.java.vm
    │ │ Dao.xml.vm
    │ │ Entity.java.vm
    │ │ index.vue.vm
    │ │ menu.sql.vm
    │ │ MongoChildrenEntity.java.vm
    │ │ MongoEntity.java.vm
    │ │ Service.java.vm
    │ │ ServiceImpl.java.vm
    │ │
    │ └─views
    │ generator.html
    │ index.html
    │ main.html

    └─generated-sources
    └─annotations

    + +

    四.项目前置知识

    1.SpringCloud Alibaba

    1.1简介

    SpringCloud Alibaba致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务必须的组件,方便开发者通过SpringCloud编程模型轻松使用这些组件来开发分布式的应用服务。
    +
    +依托SpringCloud Alibaba,你只需要添加一些注解和少量的配置,就可以将SpringCloud应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
    +
    +

    github的地址:https://github.com/alibaba/spring-cloud-alibaba

    +

    1.2为什么使用SpringCloud Alibaba?

    SpringCloud的不足:

    +
      +
    • 部分组件停止维护,给开发带来不便
    • +
    • 环境搭建复杂,没有完善的可视化界面,需要大量的二次开发和定制
    • +
    • 配置复杂,难以上手,部分配置差别难以区分和合理应用
    • +
    +

    SpringCloud Alibaba的优势:

    +

    阿里使用过的组件经历了考验,性能强悍,设计合理,开源成套的搭配和可视化的界面给开发带来极大的便利,搭建简单,学习成本低。

    +

    1.3 SpringCloud Alibaba最终的技术搭配方案

    SpringCloud Alibaba - Nacos :注册中心(服务发现/注册)

    +

    SpringCloud Alibaba - Nacos :配置中心(动态的配置管理)

    +

    SpringCloud - Ribbon: 负载均衡

    +

    SpringCloud - Feign: 声明式HTTP客户端(远程调用服务/服务调用)

    +

    SpringCloud Alibaba - Sentinel:服务容错(限流、降级、熔断)

    +

    SpringCloud - Gateway: API网关(webflux编程模式)

    +

    SpringCloud - Sleuth:调用链监控

    +

    SpringCloud Alibaba - Seata:原Fescar,即分布式事务解决方案

    +image-20230519161707857 + +

    1.4 项目中使用SpringCloud Alibaba

    在common模块中添加如下的依赖

    +
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.0.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
    + +

    1.5 SpringCloud Alibaba的组件

    1.Nacos注册中心

    image-20230519162859782 + +

    简介

    +

    Nacos 是阿里巴巴推出来的一个新开源项目,这是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。

    +

    Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

    +

    Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施

    +

    Nacos 支持如下核心特性

    +

    1)服务发现: 支持 DNS 与 RPC 服务发现,也提供原生 SDK 、OpenAPI 等多种服务注册方式和 DNS、HTTP 与 API 等多种服务发现方式。
    2)服务健康监测: Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。
    3)动态配置服务: Nacos 提供配置统一管理功能,能够帮助我们将配置以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
    4)动态 DNS 服务: Nacos 支持动态 DNS 服务权重路由,能够让我们很容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单 DNS 解析服务。
    5)服务及其元数据管理: Nacos 支持从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

    +

    使用nacos

    +

    使用之前需要下载nacos并启动nacos的服务

    +

    image-20230519164012950

    +

    在common工程中添加依赖

    +
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    + +

    配置中心中配置Nacos的地址

    +
    spring:
    application:
    name: gulimall-coupon #指定服务的名字,指定了名字才能注册到注册中心中
    cloud:
    nacos:
    server-addr: 127.0.0.1:8848 #指定注册中心的地址信息
    + +

    在每个启动类上使用注解开启服务的注册与发现功能

    +
    //开启服务的注册与发现功能
    @EnableDiscoveryClient
    + +

    查看注册服务列表

    +
    通过访问: http://localhost:8848/nacos
    + +

    image-20230519165606784

    +

    2.Feign声明式远程调用

    简介

    +
    Feign是一个声明式的HTTP客户端,它的目的是让远程调用变得更简单。Feign提供了HTTP请求的模板,通过**编写简单的接口和插入注解**,就可以定义好HTTP请求的参数、格式、地址等信息。
    +
    +Feign整合了Ribbon(负载均衡)和Hystrix(服务熔断),可以让我们不再需要显式的使用这两个组件。
    +
    +

    使用

    +

    引入依赖,每个模块都需要引入这个依赖

    +
     <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    + +

    编写代码测试远程调用,这里我们以用户模块调用优惠卷模块来获取用户的拥有的优惠卷为例来测试远程调用。

    +

    会员模块提供获取用户名下优惠卷的方法

    +
      /**
    * 获取用户明名下的优惠卷
    */
    @RequestMapping("member/list")
    public R membercoupons(){
    CouponEntity couponEntity = new CouponEntity();
    couponEntity.setCouponName("满一百减五十");
    return R.ok().put("coupons",Arrays.asList(couponEntity));
    }
    + +

    用户模块远程调用优惠卷模块

    +

    远程调用别的服务端步骤

    +
      +
    1. 引入open-feign的依赖

      +
    2. +
    3. 调用端编写一个接口,告诉SpringCloud这个接口需要调用远程服务,

      +

      声明接口的每一个方法都是调用哪个远程服务的那个请求

      +
    4. +
    5. 开启远程调用的功能,在调用端的启动类上添加**@EnableFeignClients**注解

      +
    6. +
    +

    在用户模块创建一个Feign包(维护的时候见名知义),声明调用的接口,在启动类上添加注解

    +
    //注解
    //basePackages声明的远程调用的接口所在的包
    @EnableFeignClients(basePackages = "com.atguigu.gulimall.member.feign")
    + +
    package com.atguigu.gulimall.member.feign;

    import com.atguigu.common.utils.R;
    import org.springframework.cloud.openfeign.FeignClient;
    import org.springframework.web.bind.annotation.RequestMapping;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/19
    * @Description 远程调用优惠卷服务
    */
    @FeignClient("gulimall-coupon")//告诉springCloud这是一个远程客户端,并指定要调用服务的服务名
    public interface CouponFeignService {


    /**
    * 这里需要写全路径
    * 远程调用优惠卷模块中获取优惠卷信息的方法
    */
    @RequestMapping("/coupon/coupon/member/list")
    public R membercoupons();
    }
    + +

    测试远程调用

    +
    @Autowired
    private CouponFeignService couponFeignService;

    /**
    * 测试远程调用优惠卷模块的服务
    */
    @RequestMapping("/coupons")
    public R test(){
    MemberEntity memberEntity = new MemberEntity();
    memberEntity.setNickname("张三");
    R membercoupons = couponFeignService.membercoupons();
    return R.ok().put("member", memberEntity).put("coupons", membercoupons.get("coupons"));
    }
    + +

    测试的时候出现的小问题

    +
    <!--No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalanc
    出现这个问题是没有loadbalanc,但是nacos中ribbon会造成loadbalanc包失效,在common的pom中的依赖改成如下的依赖-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <exclusions>
    <exclusion>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
    <version>2.2.2.RELEASE</version>
    </dependency>
    + +

    处理完上面的报错之后,浏览器访问 http://localhost:8000/member/member/coupons ,显示如下的内容,说明远程调用成功

    +

    image-20230519210917423

    +

    3.Nacos配置中心

    简介

    +

    简介见上面的nacos注册中心,这里的Nacos配置中心和上面的nacos注册中心是同一个东西

    +

    使用

    +

    1.在common中引入nacos配置中心的依赖,和上面的注册中心的依赖不一样

    +
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    + +

    2.给需要配置中心管理的模块下创建配置文件bootstrap.properties,这个文件会优先于application.properties读取

    +
    #服务名
    spring.application.name=gulimall-coupon
    #Nacos配置中心的地址的地址
    #注意看这里多了一个config,和模块中nacos的地址还是有区别的
    spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    + +

    3.测试

    +

    3.1.在application.properties配置文件中添加如下的配置

    +
    coupon.user.name=zhansan
    coupon.user.age=18
    + +

    3.2 编写接口并访问测试

    +

    /**
    * 获取配置文件中的配置信息
    */
    @Value("${user.name}")
    private String name;
    @Value("${user.age}")
    private Integer age;

    /**
    * 测试优先获取哪个配置文件的值
    */
    @RequestMapping("/test")
    public R test(){
    return R.ok().put("name",name).put("age",age);
    }
    + +

    测试的结果如下:

    +image-20230519213945320 + +

    3.3 需求:应用不重启,实时的修改配置文件中的值

    +

    3.3.1 在Nacos配置中心中创建一个名为 应用名.properties(例如:gulimall-coupon.properties) 的配置文件

    +

    进入nacos管理的可视化页面,点击配置列表,点击 + ,新建一个配置 ,同时将配置文件中name值改为lihua

    +

    image-20230519215420623

    +

    发布之后,就可以在配置列表中看到这个配置了

    +

    image-20230519215232782

    +

    重启项目测试,配置中心的配置是否生效

    +

    没有生效的话,添加这个依赖,springBoot2.4以上的版本需要

    +
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-bootstrap</artifactId>
    <version>4.0.1</version>
    </dependency>
    + +

    生效的话,这时刷新页面,可以看到name已经由zhangsan变成lihua了

    +image-20230519220357933 + +

    3.3.2 想让配置文件实时生效的话,这时我们还要添加以下的注解,然后重启应用,让注解生效

    +

    注意: @RefreshScope 这里的注解添加在Controller上,不是添加在启动类上

    +
    //给需要配置中心统一配置的模块的Controller上上添加@RefreshScope的注解
    @RefreshScope
    + +

    最终测试

    +

    这时我们在配置中心中修改配置并发布,这些配置就可以实时的生效了

    +

    我们在配置中心修改name和age的值,并发布

    +

    image-20230519220922701

    +

    这时刷新请求接口的页面,我们可以看到页面中的数据发生了实时的变化

    +image-20230519223403139 + +

    总结

    +

    实现nacos配置中心统一配置的流程

    +
      +
    1. 引入依赖

      +
      <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
      </dependency>
    2. +
    3. 创建一个bootstrap.properties的配置文件,添加如下的内容:

      +
      #服务名
      spring.application.name=gulimall-coupon
      #Nacos配置中心的地址
      #注意看这里多了一个config,和模块中nacos的地址还是有区别的
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
    4. +
    5. 需要给配置中心中新建一个数据集(Data Id)为 应用名.properties(例如:gulimall-coupon.properties)的配置。默认规则: 应用名.properties

      +
    6. +
    7. 给 应用名.properties 添加任何配置 这些配置都可以在配置中心配置生效

      +
    8. +
    9. 在Controller上添加**@RefreshScope**注解就可以实时的动态刷新配置,配置中心更新了配置并发布了之后,配置实时的生效。

      +
    10. +
    +

    注意

    +

    如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置

    +

    细节

    +

    命名空间: 配置隔离

    +

    默认:public(保留空间)

    +

    1.可以分别设置开发、测试、生产环境的命名空间,不同的环境下使用不同的命名空间中的配置

    +

    通过命令空间实现环境隔离

    +

    bootstrap.properties设置使用哪个命名空间,可以在配置文件中添加如下的配置

    +

    image-20230519225637848

    +
    # 切换名称空间
    # 可以选择对应的命名空间 # 写上对应环境的命名空间ID
    # 使用唯一的命名空间ID
    spring.cloud.nacos.config.namespace=8a84e478-92d0-4581-a316-b0274fc76eb8
    + +

    2.可以为每一个微服务可以创建自己的命名空间,让每个服务的配置放在每个服务的命名空间下

    +

    配置集:所有的配置的集合

    +

    配置集ID: 类似文件名

    +
    Data Id: 就是配置集ID
    +
    +

    配置分组:

    +

    默认所有的配置集都属于 : DEFAULT_GROUP

    +

    例如在生产环境的命名空间下,淘宝的配置分组有以下几个: 双十一,688,平时

    +
    #指定配置分组
    spring.cloud.nacos.config.group=1111
    + +

    Tips:同一个命名空间下可能会有不同的配置分组

    +

    每个微服务创建自己的命令空间,使用配置分组区分环境 dev、test、prod

    +

    从配置中心中读取多个配置集(配置文件):

    +

    可以在bootstrap.properties添加以下的配置

    +
    #第一个配置文件
    #配置文件名
    spring.cloud.nacos.config.ext-config[0].data-id=datasource.yml
    #配置文件的对应的配置分组
    spring.cloud.nacos.config.ext-config[0].group=dev
    #是否动态的刷新
    spring.cloud.nacos.config.ext-config[0].refresh=true

    #第二个配置文件
    spring.cloud.nacos.config.ext-config[1].data-id=mybatis.yml
    spring.cloud.nacos.config.ext-config[1].group=dev
    spring.cloud.nacos.config.ext-config[1].refresh=true

    #第三个配置文件
    spring.cloud.nacos.config.ext-config[2].data-id=others.yml
    spring.cloud.nacos.config.ext-config[2].group=dev
    spring.cloud.nacos.config.ext-config[2].refresh=true
    + +

    总结:

    +

    1.微服务的任何配置信息,任何配置文件都可以放在配置中心中

    +

    2.加载配置中心的哪些配置文件,只需要在bootstrap.properties说明加载哪些配置文件即可

    +

    3.如果配置中心和当前应用的配置文件都配置了相同的项,优先使用配置中心的配置

    +

    4.Gateway网关

    image-20230519233741029 + +

    简介

    +
    网关最为流量的入口,常用的功能包括**路由转发**、**权限校验**、**限流控制**等。SpringCloud gateway是springcloud官方提供的第二代网关框架,取代了Zull网关。
    +
    +

    网关的功能

    +
      +
    • 针对所有请求进行登陆统一鉴权(登录态)限流缓存日志(用户打点)
    • +
    • 可以根据不同的请求路径pattern,来进行请求的鉴权、转发、和拒绝。
    • +
    • 协议转化。针对后段多种不同的协议,在网关层统一处理后以HTTP对外提供服务。
    • +
    • 提供统一的错误码
    • +
    • 请求转发,并且可以基于网关实现内网与外网的隔离
    • +
    +

    使用

    +
      +
    1. 创建一个springboot initializr的工程 ,勾选上Gateway的依赖

      +
    2. +
    3. 在网关模块的pom.xml文件中添加上对common工程的依赖

      +
    4. +
    5. 在网关的启动类上添加上**@EnableDiscoveryClient**的注解,把网关注册到注册中心

      +
    6. +
    7. 配置nacos的地址和服务名信息

      +
      #配置网关的地址
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
      #服务名
      spring.application.name=gulimall-gateway
    8. +
    9. 创建bootstrap.properties配置文件,将网关模块的配置通过交给nacos配置中心管理

      +

      image-20230520205015056

      +
      spring.application.name=gulimall-gateway
      #配置配置中心的地址
      spring.cloud.nacos.config.server-addr=127.0.0.1:8848
      #名称空间
      spring.cloud.nacos.config.namespace=4a8e80ba-3d3c-40ef-897f-d400797c4676
    10. +
    11. 启动测试

      +

      启动之后出现的问题及解决方案:

      +

      a.单元测试@Test注解爆红,是我们更换了springboot的版本的原因,删除爆红的地方,重新导包即可

      +

      b.数据库相关的报错,是因为网关中没有使用数据库,就没有配置数据库的配置,我们通过@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})解决。

      +
    12. +
    13. 转发测试

      +

      测试案例:我们在浏览器的地址栏输入localhost:88?url=baidu,就给我们转发到百度,输入localhost:88?url=qq就给我们转发到腾讯qq

      +

      实现:创建一个yml的配置文件,在配置文件中添加如下的配置

      +
      spring:
      cloud:
      gateway:
      routes:
      # 路由的唯一id
      - id: baidu_route
      uri: https://www.baidu.com
      # 断言,什么时候去上面那个url
      predicates:
      # 下面的这句话的意思是如果请求中存在请求参数url=baidu,我们就去 https://www.baidu.com这个url
      - Query=url,baidu
      - id: qq_route
      uri: https://www.qq.com
      predicates:
      # 下面的这句话的意思是如果请求中存在请求参数url=qq,我们就去 https://www.qq.com这个url
      - Query=url,qq
      + +image-20230520212001874
    14. +
    +

    2.前端开发的基础知识

    前端基础知识 | The Blog (gitee.io)

    +

    前后端技术类比

    +

    image-20230520212351174

    +

    五.基础篇程序设计

    提示:所有的API路径一定要和老师的一样,不然在后面会吃亏!!!

    +

    1.商品服务

    1.1 三级分类

    1.1.1 查询-递归树形结构数据获取

    controller

    +
    /**
    * 查询所有的分类以及子分类,以树形结构组装起来
    */
    @RequestMapping("/list/tree")
    public R list(){
    List<CategoryEntity> entities = categoryService.listWithTree();
    return R.ok().put("data", entities);
    }
    + +

    service

    +
    package com.atguigu.gulimall.product.service.impl;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;

    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;

    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.atguigu.common.utils.PageUtils;
    import com.atguigu.common.utils.Query;

    import com.atguigu.gulimall.product.dao.CategoryDao;
    import com.atguigu.gulimall.product.entity.CategoryEntity;
    import com.atguigu.gulimall.product.service.CategoryService;


    @Service("categoryService")
    public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    private CategoryDao categoryDao;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
    IPage<CategoryEntity> page = this.page(
    new Query<CategoryEntity>().getPage(params),
    new QueryWrapper<CategoryEntity>()
    );

    return new PageUtils(page);
    }

    /**
    * 查询所有分类,以树形结构显示
    */
    @Override
    public List<CategoryEntity> listWithTree() {
    //查出所有的分类
    List<CategoryEntity> entities = categoryDao.selectList(null);
    //组装成父子的树形结构
    //找到所有的一级分类
    List<CategoryEntity> level1List = entities.stream().filter(categoryEntity ->
    //父级分类的id等于0的就是一级分类
    categoryEntity.getParentCid() == 0
    ).map((menu) -> {
    //为当前分类的子分类赋值
    menu.setChildren(getChildren(menu, entities));
    return menu;
    }).sorted((menu1, menu2) -> {
    //排序
    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());

    return level1List;
    }

    /**
    * 递归查询所有菜单的子菜单
    *
    * @param root 当前分类
    * @param all 所有的分类数据
    * @return 子菜单的集合
    */
    private List<CategoryEntity> getChildren(CategoryEntity root, List<CategoryEntity> all) {
    List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
    return categoryEntity.getParentCid().equals(root.getCatId());
    }).map(categoryEntity -> {
    //1.递归找子菜单
    categoryEntity.setChildren(getChildren(categoryEntity, all));
    return categoryEntity;
    }).sorted((menu1, menu2) -> {
    //2.菜单的排序
    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
    }).collect(Collectors.toList());
    return children;
    }

    }
    + +

    1.1.2 配置路由和网关和路径重写

    前端访问的地址

    +

    image-20230529170214769

    +

    前端代码中的配置

    +

    image-20230529170149102

    +

    修改访问的基准路径,统一访问网关,再由网关做请求的转发,转发到指定的服务中去(renren-fast-vue\static\config\index.js文件)

    +
    /**
    * 开发环境
    */
    ;(function () {
    window.SITE_CONFIG = {};

    // api接口请求地址
    window.SITE_CONFIG['baseUrl'] = 'http://localhost:88';

    // cdn地址 = 域名 + 版本号
    window.SITE_CONFIG['domain'] = './'; // 域名
    window.SITE_CONFIG['version'] = ''; // 版本号(年月日时分)
    window.SITE_CONFIG['cdnUrl'] = window.SITE_CONFIG.domain + window.SITE_CONFIG.version;
    })();

    + +

    将renren-fast注册到注册中心中去

    +

    修改pom.xml中的依赖

    +
    <!-- 修改springBoot的版本 -->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.6.RELEASE</version>
    <relativePath/>
    </parent>
    <!-- 依赖common文件,获取nacos相关的注解 -->
    <dependency>
    <groupId>com.atguigu</groupId>
    <artifactId>gulimall-common</artifactId>
    <version>1.0-SNAPSHOT</version>
    </dependency>
    + +

    在启动类上添加@EnableDiscoveryClient注解,在配置文件中添加上nacos地址的配置

    +
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    + +

    修改网关中的断言,让前台的请求转发到正确的路径上

    +
    spring:
    cloud:
    gateway:
    routes:
    # 路由的唯一id
    - id: admin_route
    uri: lb://renren-fast
    predicates:
    - Path=/api/**
    # 路径重写
    filters:
    - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}
    # 前端项目中的基准路径 http://localhost:88/api
    # http://localhost:88/api/captcha.jpg 路径重写为 http://localhost:8080/renren-fast/captcha.jpg
    + +

    解决登录的时候的跨域问题

    +

    浏览器中的跨域的报错信息

    +

    image-20230530111856491

    +

    跨域问题的介绍

    +

    image-20230530112115261

    +

    跨域流程

    +

    文档介绍

    +

    image-20230530112630678

    +

    跨域的解决方案

    +
      +
    1. 使用nginx部署为同一域
    2. +
    3. 配置当前请求允许跨域 (在网关中通过过滤器给请求添加响应头)
    4. +
    +

    跨域的配置

    +

    添加跨域的配置类

    +
    package com.eatguigu.gulimall.gateway.config;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.reactive.CorsWebFilter;
    import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

    /**
    * @author GongChangjiang
    * @version 1.0
    * @Date 2023/5/30
    * @Description 跨域配置类
    */
    @Configuration
    public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter(){
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    //1.配置跨域
    //允许所有的请求头跨域
    corsConfiguration.addAllowedHeader("*");
    //允许所有的请求方式跨域
    corsConfiguration.addAllowedMethod("*");
    //任意请求来源都可以跨域
    //corsConfiguration.addAllowedOrigin("*"); 老版本的springBoot使用这个,新版本的使用下面这个
    corsConfiguration.addAllowedOriginPattern("*");
    //允许携带cookie跨域
    corsConfiguration.setAllowCredentials(true);
    source.registerCorsConfiguration("/**",corsConfiguration);
    return new CorsWebFilter(source);
    }
    }

    + +

    注掉renren-fast工程中的跨域配置,避免产生两次跨域的问题

    +
    /**
    * Copyright (c) 2016-2019 人人开源 All rights reserved.
    *
    * https://www.renren.io
    *
    * 版权所有,侵权必究!
    */

    package io.renren.config;

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.CorsRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

    @Configuration
    public class CorsConfig implements WebMvcConfigurer {

    // @Override
    // public void addCorsMappings(CorsRegistry registry) {
    // registry.addMapping("/**")
    // .allowedOrigins("*")
    // .allowCredentials(true)
    // .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
    // .maxAge(3600);
    // }
    }
    + +

    再次测试,成功的进入后台管理系统

    +

    1.2.3 查询-页面中树形显示

    新增一个商品系统的目录,并在该目录下创建一个分类维护的二级分类

    +

    1.添加一个一级分类

    +

    image-20230529161308671

    +

    image-20230529161317694

    +

    2.在商品系统的一级分类下添加一个分类维护的二级分类

    +

    image-20230529161648257

    +

    3.路径的问题

    +

    image-20230529162235482

    +

    4.路径与文件的对应关系

    +

    image-20230529162946584

    +

    5.发送请求的示例

    +
    <template>
    <div>
    <!-- 树形控件 -->
    <el-tree
    :data="data"
    :props="defaultProps"
    @node-click="handleNodeClick"
    ></el-tree>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    data: [],
    defaultProps: {
    children: "children",
    label: "label",
    },
    };
    },
    //钩子函数
    created() {
    this.getMenus();
    },

    //方法
    methods: {
    handleNodeClick(data) {
    console.log(data);
    },
    //获取所有的菜单
    getMenus() {
    //正式的发送请求
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    console.log(data);
    });
    },
    },
    };
    </script>
    <style>
    </style>
    + +

    6.树形显示商品的分类信息

    +

    前端修改网关商品模块的路由(注意:匹配精确的路由放在模糊的路由的上面)

    +
    spring:
    cloud:
    gateway:
    routes:
    #这个路由精确一些,放在上面,避免被下面模糊的路由吞掉
    - id: product_route
    uri: lb://gulimall-product
    predicates:
    - Path=/api/product/**
    filters:
    - RewritePath=/api/(?<segment>.*),/$\{segment}

    # 路由的唯一id
    - id: admin_route
    uri: lb://renren-fast
    predicates:
    - Path=/api/**
    # 路径重写
    filters:
    - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}


    # 前端项目中的基准路径 http://localhost:88/api
    # http://localhost:88/api/captcha.jpg 路径重写为 http://localhost:8080/renren-fast/captcha.jpg
    + +

    前端发送请求,正确地获取数据

    +

    image-20230530172846989

    +

    在页面上显示数据

    +

    Element的配置

    +

    image-20230530173658950

    +

    前端代码

    +
    <template>
    <div>
    <!-- 树形控件 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    @node-click="handleNodeClick"
    ></el-tree>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {//这里的配置看官方的文档
    children: "children",//父节点上的子节点
    label: "name",//每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    handleNodeClick(data) {
    console.log(data);
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    显示的效果

    +

    image-20230530173801824

    +

    1.2.4 删除-删除商品分类

    前端页面修改

    +
    <template>
    <!-- 树形控件-->
    <!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
    <!-- show-checkbox表示开启选择框,批量选择-->
    <!-- node-key表示在整个树中唯一的表示 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <= 2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >
    添加
    </el-button>
    <!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
    <el-button
    v-if="data.children.length <= 0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >
    删除
    </el-button>
    </span>
    </span></el-tree
    >
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {
    //这里的配置看官方的文档
    children: "children", //父节点上的子节点
    label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    expandedKey:[] //需要展开的数组,用于删除之后树形控件仍然展开
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    append(data) {
    console.log("append: ", data);
    },
    //删除节点的方法
    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才删除的节点)
    this.expandedKey = [node.parent.data.catId]
    });
    })
    .catch(() => {
    this.$message({
    type: "info",
    message: "已取消删除",
    });
    });
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    配置逻辑删除

    +

    配置全局的逻辑删除规则

    +
    mybatis-plus:
    global-config:
    db-config:
    logic-delete-value: 1 # 逻辑已删除值(默认为 1)
    logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    + +

    在showStatus上添加上逻辑删除的注解

    +
    @TableLogic(value = "1",delval = "0")
    + +

    后端代码的实现

    +

    controlller

    +
    /**
    * 删除
    */
    @RequestMapping("/delete")
    //@RequiresPermissions("product:category:delete")
    public R delete(@RequestBody Long[] catIds) {
    //检查当前删除的菜单,是否被别的地方引用
    //categoryService.removeByIds(Arrays.asList(catIds));
    categoryService.removeMenuByIds(Arrays.asList(catIds));
    return R.ok();
    }
    + +

    service 这里后面需要优化

    +
    /**
    * 删除分类的方法
    * @param list 分类id的集合
    */
    @Override
    public void removeMenuByIds(List<Long> list) {
    //TODO 检查当前删除的菜单是否被其他的地方引用 后面优化
    //逻辑删除
    categoryDao.deleteBatchIds(list);
    }
    + +

    1.2.5 添加-添加商品分类

    前端代码

    +
    <template>
    <div>
    <!-- 树形控件-->
    <!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
    <!-- show-checkbox表示开启选择框,批量选择-->
    <!-- node-key表示在整个树中唯一的表示 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <= 2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >
    添加
    </el-button>
    <!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
    <el-button
    v-if="data.children.length <= 0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >
    删除
    </el-button>
    </span>
    </span></el-tree
    >
    <!-- 添加分类的时候弹出的对话框 -->
    <el-dialog title="提示" :visible.sync="dialogVisible" width="30%">
    <el-form :model="category">
    <el-form-item label="分类名称">
    <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="addCategory">确 定</el-button>
    </span>
    </el-dialog>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    menus: [],
    defaultProps: {
    //这里的配置看官方的文档
    children: "children", //父节点上的子节点
    label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    expandedKey: [], //需要展开的数组,用于删除之后树形控件仍然展开
    dialogVisible: false, //添加菜单的对话框的开启或者关闭
    //表单绑定的对象
    category: {
    name: "", //分类的名称
    parentCid: 0, //父类的id
    catLevel: 0, //分类的级别
    showStatus: 1, //是否显示该分类
    sort: 0, //排序
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    //添加分类(弹出的对话框确定按钮执行的方法)
    addCategory() {
    console.log("提交的数据:", this.category);
    //将添加的数据返回给后端
    this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单添加成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus()
    //设置需要默认展开的菜单(依然展开刚才添加的节点)
    this.expandedKey = [this.category.parentCid];
    });
    //关闭对话框
    this.dialogVisible = false;
    },
    //添加分类的方法
    append(data) {
    //清空一下表单的数据
    this.category.name = "";
    //打开添加的对话框
    this.dialogVisible = true;
    //为分类的对象赋值
    this.category.parentCid = data.catId;
    this.category.catLevel = data.catLevel * 1 + 1; //防止catLevel是一个字符串
    },
    //删除节点的方法
    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才删除的节点)
    this.expandedKey = [node.parent.data.catId];
    });
    })
    .catch(() => {
    this.$message({
    type: "info",
    message: "已取消删除",
    });
    });
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    后端代码

    +
     /**
    * 保存
    */
    @RequestMapping("/save")
    //@RequiresPermissions("product:category:save")
    public R save(@RequestBody CategoryEntity category) {
    categoryService.save(category);

    return R.ok();
    }
    + +

    实现的效果

    +

    image-20230605163004850

    +

    1.2.6 修改-修改商品的分类

    前端的代码(太复杂了,没有写完)

    +
    <template>
    <div>
    <!-- 树形控件-->
    <!-- :expand-on-click-node="false"设置为点击箭头的时候才会展开,避免点击删除的按钮的时候展开 -->
    <!-- show-checkbox表示开启选择框,批量选择-->
    <!-- node-key表示在整个树中唯一的表示 -->
    <!-- draggable 实现节点自由拖拽的效果 -->
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    draggable
    :allow-drop="allowDrop"
    @node-drop="handleDrop"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <= 2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >
    添加
    </el-button>
    <el-button type="text" size="mini" @click="() => edit(data)">
    修改
    </el-button>
    <!-- 添加判断,没有下一级节点的时候就显示删除的按钮 -->
    <el-button
    v-if="data.children.length <= 0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >
    删除
    </el-button>
    </span>
    </span></el-tree
    >
    <!-- 添加分类的时候弹出的对话框 -->
    <el-dialog
    :title="title"
    :visible.sync="dialogVisible"
    width="30%"
    :close-on-click-modal="false"
    >
    <el-form :model="category">
    <el-form-item label="分类名称">
    <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="图标">
    <el-input v-model="category.icon" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="计量单位">
    <el-input
    v-model="category.productUnit"
    autocomplete="off"
    ></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="submitDate">确 定</el-button>
    </span>
    </el-dialog>
    </div>
    </template>

    <script>
    export default {
    //定义变量
    data() {
    return {
    updateNodes: [], //经过修改之后的节点的信息
    maxLevel: 0, //最大的层级
    title: "", //弹出框的提示信息
    dialogType: "", //弹出的对话框的类型 edit/add
    menus: [],
    defaultProps: {
    //这里的配置看官方的文档
    children: "children", //父节点上的子节点
    label: "name", //每个对象要显示的属性值,比如我们当前要显示商品分类中的分类名的值,这里就改成name
    },
    expandedKey: [], //需要展开的数组,用于删除之后树形控件仍然展开
    dialogVisible: false, //添加菜单的对话框的开启或者关闭
    //表单绑定的对象
    category: {
    name: "", //分类的名称
    parentCid: 0, //父类的id
    catLevel: 0, //分类的级别
    showStatus: 1, //是否显示该分类
    sort: 0, //排序
    catId: null, //分类的id信息
    icon: "", //商品分类的图标
    productUnit: "", //计量单位
    },
    };
    },
    //钩子函数
    created() {
    //调用获取商品分类数据的方法
    this.getMenus();
    },

    //方法
    methods: {
    //拖拽成功之后调用这个函数进行回调的处理
    handleDrop(draggingNode, dropNode, dropType, ev) {
    console.log("handleDrop: ", draggingNode, dropNode, dropType);
    //当前的节点的最新父节点id
    let pCid = 0; //当前节点的组最新父节点id
    let siblings = null; //当前的节点的兄弟节点
    //判断的节点的进入的方式
    if (dropType == "before" || dropType == "after") {
    pCid =
    dropNode.parent.data.catId == undefined
    ? 0
    : dropNode.parent.data.catId;
    siblings = dropNode.parent.childNodes;
    } else {
    pCid = dropNode.data.catId;
    siblings = dropNode.childNodes;
    }
    //当前的节点的最新的顺序
    for (let i = 0; i < siblings.length; i++) {
    if (siblings[i].data.catId == draggingNode.data.catId) {
    //如果当前遍历的是正在拖拽的节点
    let catLevel = draggingNode.catLevel;
    if (siblings[i].level != draggingNode.level) {
    //当前的节点的层级发生的变化
    catLevel = siblings[i].level;
    //修改子节点的层级
    this.updateChildNodeLevel(siblings[i]);
    }
    this.updateNodes.push({
    catId: siblings[i].data.catId,
    sort: i,
    parentCid: pCid,
    catLevel: catLevel,
    });
    } else {
    this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
    }
    }
    //当前的拖拽节点的最新的层级
    console.log("updateNodes:", this.updateNodes);
    },
    //递归修改子节点的层级
    updateChildNodeLevel(node) {
    if ((node.childNodes, length > 0)) {
    for (let i = 0; i < node.childNodes.length; i++) {
    var cNode = node.childNodes[i].data;
    this.updateNodes.push({
    catId: cNode.catId,
    catLevel: node.childNodes[i].level,
    });
    //递归调用
    this.updateChildNodeLevel(node.childNodes[i]);
    }
    }
    },
    //拖拽之后节点的位置的判断 是否的可以放在该位置上面
    allowDrop(draggingNode, dropNode, type) {
    //判断被拖动的当前节点以及所在的父节点的总层数不能大于三
    //被拖动的当前节点的总层数
    console.log("allowDrop:", draggingNode, dropNode, type);
    //获取当前节点的总层数
    this.countNodeLevel(draggingNode.data);
    let deep = this.maxLevel - draggingNode.data.catLevel + 1;
    console.log("深度:", deep);
    if ((type = "inner")) {
    return deep + dropNode.level <= 3;
    } else {
    return deep + dropNode.parent.level <= 3;
    }
    },
    //统计当前的节点的总层数
    countNodeLevel(node) {
    //找到所有子节点 求出最大深度
    if (node.children != null && node.children.length > 0) {
    for (let i = 0; i < node.children.length; i++) {
    if (node.children[i].catLevel > this.maxLevel) {
    this.maxLevel = node.children[i].catLevel;
    }
    //递归一下
    this.countNodeLevel(node.children[i]);
    }
    }
    },
    //弹出修改的对话框,并做数据的回显
    edit(data) {
    //修改的title
    this.title = "修改分类";
    //设置弹出的对话框为修改的类型
    this.dialogType = "edit";
    //发送请求回显数据
    this.$http({
    url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
    method: "get",
    }).then(({ data }) => {
    //请求成功 回显数据
    console.log("回显的数据:", data);
    this.category.name = data.data.name;
    this.category.catId = data.data.catId;
    this.category.icon = data.data.icon;
    this.category.productUnit = data.data.productUnit;
    this.category.parentCid = data.data.parentCid;
    });
    //弹出对话框
    this.dialogVisible = true;
    },
    //保存或者添加数据
    submitDate() {
    if (this.dialogType == "add") {
    //调用添加的方法
    this.addCategory();
    }
    if (this.dialogType == "edit") {
    //修改的方法
    this.editCategory();
    }
    },
    //修改商品的分类信息
    editCategory() {
    //只发送我们要修改的数据
    var { catId, name, icon, productUnit } = this.category;
    this.$http({
    url: this.$http.adornUrl("/product/category/update"),
    method: "post",
    data: this.$http.adornData({ catId, name, icon, productUnit }, false),
    }).then(({ data }) => {
    this.$message({
    message: "分类修改成功",
    type: "success",
    });
    //关闭弹出框
    this.dialogVisible = false;
    //刷新一下表单
    this.getMenus();
    //设置默认展开的菜单
    this.expandedKey = [this.category.parentCid];
    });
    },
    //添加分类(弹出的对话框确定按钮执行的方法)
    addCategory() {
    console.log("提交的数据:", this.category);
    //将添加的数据返回给后端
    this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false),
    }).then(({ data }) => {
    this.$message({
    message: "分类添加成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才添加的节点)
    this.expandedKey = [this.category.parentCid];
    });
    //关闭对话框
    this.dialogVisible = false;
    },
    //添加分类的方法
    append(data) {
    //添加的title
    this.title = "添加分类";
    //设置当前提交的对话框为添加的对话框
    this.dialogType = "add";
    //清空一下表单的数据
    this.category.name = "";
    //打开添加的对话框
    this.dialogVisible = true;
    //为分类的对象赋值
    this.category.parentCid = data.catId;
    this.category.catLevel = data.catLevel * 1 + 1; //防止catLevel是一个字符串
    this.catId = null;
    this.category.icon = "";
    this.category.productUnit = "";
    this.category.sort = 0;
    this.category.showStatus = 1;
    },
    //删除节点的方法
    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否要删除[${data.name}]菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success",
    });
    //刷新一下页面
    this.getMenus();
    //设置需要默认展开的菜单(依然展开刚才删除的节点)
    this.expandedKey = [node.parent.data.catId];
    });
    })
    .catch(() => {
    this.$message({
    type: "info",
    message: "已取消删除",
    });
    });
    },
    //获取所有的菜单
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    老师的前端的代码

    +
    <template>
    <div>
    <el-switch v-model="draggable" active-text="开启拖拽" inactive-text="关闭拖拽"></el-switch>
    <el-button v-if="draggable" @click="batchSave">批量保存</el-button>
    <el-button type="danger" @click="batchDelete">批量删除</el-button>
    <el-tree
    :data="menus"
    :props="defaultProps"
    :expand-on-click-node="false"
    show-checkbox
    node-key="catId"
    :default-expanded-keys="expandedKey"
    :draggable="draggable"
    :allow-drop="allowDrop"
    @node-drop="handleDrop"
    ref="menuTree"
    >
    <span class="custom-tree-node" slot-scope="{ node, data }">
    <span>{{ node.label }}</span>
    <span>
    <el-button
    v-if="node.level <=2"
    type="text"
    size="mini"
    @click="() => append(data)"
    >Append</el-button>
    <el-button type="text" size="mini" @click="edit(data)">edit</el-button>
    <el-button
    v-if="node.childNodes.length==0"
    type="text"
    size="mini"
    @click="() => remove(node, data)"
    >Delete</el-button>
    </span>
    </span>
    </el-tree>

    <el-dialog
    :title="title"
    :visible.sync="dialogVisible"
    width="30%"
    :close-on-click-modal="false"
    >
    <el-form :model="category">
    <el-form-item label="分类名称">
    <el-input v-model="category.name" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="图标">
    <el-input v-model="category.icon" autocomplete="off"></el-input>
    </el-form-item>
    <el-form-item label="计量单位">
    <el-input v-model="category.productUnit" autocomplete="off"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="dialogVisible = false">取 消</el-button>
    <el-button type="primary" @click="submitData">确 定</el-button>
    </span>
    </el-dialog>
    </div>
    </template>

    <script>
    //这里可以导入其他文件(比如:组件,工具js,第三方插件js,json文件,图片文件等等)
    //例如:import 《组件名称》 from '《组件路径》';

    export default {
    //import引入的组件需要注入到对象中才能使用
    components: {},
    props: {},
    data() {
    return {
    pCid: [],
    draggable: false,
    updateNodes: [],
    maxLevel: 0,
    title: "",
    dialogType: "", //edit,add
    category: {
    name: "",
    parentCid: 0,
    catLevel: 0,
    showStatus: 1,
    sort: 0,
    productUnit: "",
    icon: "",
    catId: null
    },
    dialogVisible: false,
    menus: [],
    expandedKey: [],
    defaultProps: {
    children: "children",
    label: "name"
    }
    };
    },

    //计算属性 类似于data概念
    computed: {},
    //监控data中的数据变化
    watch: {},
    //方法集合
    methods: {
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get"
    }).then(({ data }) => {
    console.log("成功获取到菜单数据...", data.data);
    this.menus = data.data;
    });
    },
    batchDelete() {
    let catIds = [];
    let checkedNodes = this.$refs.menuTree.getCheckedNodes();
    console.log("被选中的元素", checkedNodes);
    for (let i = 0; i < checkedNodes.length; i++) {
    catIds.push(checkedNodes[i].catId);
    }
    this.$confirm(`是否批量删除【${catIds}】菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning"
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(catIds, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单批量删除成功",
    type: "success"
    });
    this.getMenus();
    });
    })
    .catch(() => {});
    },
    batchSave() {
    this.$http({
    url: this.$http.adornUrl("/product/category/update/sort"),
    method: "post",
    data: this.$http.adornData(this.updateNodes, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单顺序等修改成功",
    type: "success"
    });
    //刷新出新的菜单
    this.getMenus();
    //设置需要默认展开的菜单
    this.expandedKey = this.pCid;
    this.updateNodes = [];
    this.maxLevel = 0;
    // this.pCid = 0;
    });
    },
    handleDrop(draggingNode, dropNode, dropType, ev) {
    console.log("handleDrop: ", draggingNode, dropNode, dropType);
    //1、当前节点最新的父节点id
    let pCid = 0;
    let siblings = null;
    if (dropType == "before" || dropType == "after") {
    pCid =
    dropNode.parent.data.catId == undefined
    ? 0
    : dropNode.parent.data.catId;
    siblings = dropNode.parent.childNodes;
    } else {
    pCid = dropNode.data.catId;
    siblings = dropNode.childNodes;
    }
    this.pCid.push(pCid);

    //2、当前拖拽节点的最新顺序,
    for (let i = 0; i < siblings.length; i++) {
    if (siblings[i].data.catId == draggingNode.data.catId) {
    //如果遍历的是当前正在拖拽的节点
    let catLevel = draggingNode.level;
    if (siblings[i].level != draggingNode.level) {
    //当前节点的层级发生变化
    catLevel = siblings[i].level;
    //修改他子节点的层级
    this.updateChildNodeLevel(siblings[i]);
    }
    this.updateNodes.push({
    catId: siblings[i].data.catId,
    sort: i,
    parentCid: pCid,
    catLevel: catLevel
    });
    } else {
    this.updateNodes.push({ catId: siblings[i].data.catId, sort: i });
    }
    }

    //3、当前拖拽节点的最新层级
    console.log("updateNodes", this.updateNodes);
    },
    updateChildNodeLevel(node) {
    if (node.childNodes.length > 0) {
    for (let i = 0; i < node.childNodes.length; i++) {
    var cNode = node.childNodes[i].data;
    this.updateNodes.push({
    catId: cNode.catId,
    catLevel: node.childNodes[i].level
    });
    this.updateChildNodeLevel(node.childNodes[i]);
    }
    }
    },
    allowDrop(draggingNode, dropNode, type) {
    //1、被拖动的当前节点以及所在的父节点总层数不能大于3

    //1)、被拖动的当前节点总层数
    console.log("allowDrop:", draggingNode, dropNode, type);
    //
    this.countNodeLevel(draggingNode);
    //当前正在拖动的节点+父节点所在的深度不大于3即可
    let deep = Math.abs(this.maxLevel - draggingNode.level) + 1;
    console.log("深度:", deep);

    // this.maxLevel
    if (type == "inner") {
    // console.log(
    // `this.maxLevel:${this.maxLevel};draggingNode.data.catLevel:${draggingNode.data.catLevel};dropNode.level:${dropNode.level}`
    // );
    return deep + dropNode.level <= 3;
    } else {
    return deep + dropNode.parent.level <= 3;
    }
    },
    countNodeLevel(node) {
    //找到所有子节点,求出最大深度
    if (node.childNodes != null && node.childNodes.length > 0) {
    for (let i = 0; i < node.childNodes.length; i++) {
    if (node.childNodes[i].level > this.maxLevel) {
    this.maxLevel = node.childNodes[i].level;
    }
    this.countNodeLevel(node.childNodes[i]);
    }
    }
    },
    edit(data) {
    console.log("要修改的数据", data);
    this.dialogType = "edit";
    this.title = "修改分类";
    this.dialogVisible = true;

    //发送请求获取当前节点最新的数据
    this.$http({
    url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
    method: "get"
    }).then(({ data }) => {
    //请求成功
    console.log("要回显的数据", data);
    this.category.name = data.data.name;
    this.category.catId = data.data.catId;
    this.category.icon = data.data.icon;
    this.category.productUnit = data.data.productUnit;
    this.category.parentCid = data.data.parentCid;
    this.category.catLevel = data.data.catLevel;
    this.category.sort = data.data.sort;
    this.category.showStatus = data.data.showStatus;
    /**
    * parentCid: 0,
    catLevel: 0,
    showStatus: 1,
    sort: 0,
    */
    });
    },
    append(data) {
    console.log("append", data);
    this.dialogType = "add";
    this.title = "添加分类";
    this.dialogVisible = true;
    this.category.parentCid = data.catId;
    this.category.catLevel = data.catLevel * 1 + 1;
    this.category.catId = null;
    this.category.name = "";
    this.category.icon = "";
    this.category.productUnit = "";
    this.category.sort = 0;
    this.category.showStatus = 1;
    },

    submitData() {
    if (this.dialogType == "add") {
    this.addCategory();
    }
    if (this.dialogType == "edit") {
    this.editCategory();
    }
    },
    //修改三级分类数据
    editCategory() {
    var { catId, name, icon, productUnit } = this.category;
    this.$http({
    url: this.$http.adornUrl("/product/category/update"),
    method: "post",
    data: this.$http.adornData({ catId, name, icon, productUnit }, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单修改成功",
    type: "success"
    });
    //关闭对话框
    this.dialogVisible = false;
    //刷新出新的菜单
    this.getMenus();
    //设置需要默认展开的菜单
    this.expandedKey = [this.category.parentCid];
    });
    },
    //添加三级分类
    addCategory() {
    console.log("提交的三级分类数据", this.category);
    this.$http({
    url: this.$http.adornUrl("/product/category/save"),
    method: "post",
    data: this.$http.adornData(this.category, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单保存成功",
    type: "success"
    });
    //关闭对话框
    this.dialogVisible = false;
    //刷新出新的菜单
    this.getMenus();
    //设置需要默认展开的菜单
    this.expandedKey = [this.category.parentCid];
    });
    },

    remove(node, data) {
    var ids = [data.catId];
    this.$confirm(`是否删除【${data.name}】菜单?`, "提示", {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning"
    })
    .then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/category/delete"),
    method: "post",
    data: this.$http.adornData(ids, false)
    }).then(({ data }) => {
    this.$message({
    message: "菜单删除成功",
    type: "success"
    });
    //刷新出新的菜单
    this.getMenus();
    //设置需要默认展开的菜单
    this.expandedKey = [node.parent.data.catId];
    });
    })
    .catch(() => {});

    console.log("remove", node, data);
    }
    },
    //生命周期 - 创建完成(可以访问当前this实例)
    created() {
    this.getMenus();
    },
    //生命周期 - 挂载完成(可以访问DOM元素)
    mounted() {},
    beforeCreate() {}, //生命周期 - 创建之前
    beforeMount() {}, //生命周期 - 挂载之前
    beforeUpdate() {}, //生命周期 - 更新之前
    updated() {}, //生命周期 - 更新之后
    beforeDestroy() {}, //生命周期 - 销毁之前
    destroyed() {}, //生命周期 - 销毁完成
    activated() {} //如果页面有keep-alive缓存功能,这个函数会触发
    };
    </script>
    <style scoped>
    </style>
    + +

    后端的代码

    +
    /**
    * 批量修改商品的分类信息
    */
    @RequestMapping("/update/sort")
    public R updateSort(@RequestBody CategoryEntity[] categoryEntities) {
    categoryService.updateBatchById(Arrays.asList(categoryEntities));
    return R.ok();
    }
    + +

    1.2 品牌管理

    1.2.1 品牌的增删改查功能

    这里前端的代码是和先前生成的后端的代码一起生成的

    +

    image-20230611111440999

    +

    商品品牌的列表显示

    +

    优化之后的前端的代码 (表头的优化 需要按钮显示的使用按钮进行显示)

    +
    <template>
    <div class="mod-config">
    <el-form
    :inline="true"
    :model="dataForm"
    @keyup.enter.native="getDataList()"
    >
    <el-form-item>
    <el-input
    v-model="dataForm.key"
    placeholder="参数名"
    clearable
    ></el-input>
    </el-form-item>
    <el-form-item>
    <el-button @click="getDataList()">查询</el-button>
    <el-button
    v-if="isAuth('product:brand:save')"
    type="primary"
    @click="addOrUpdateHandle()"
    >新增</el-button
    >
    <el-button
    v-if="isAuth('product:brand:delete')"
    type="danger"
    @click="deleteHandle()"
    :disabled="dataListSelections.length <= 0"
    >批量删除</el-button
    >
    </el-form-item>
    </el-form>
    <el-table
    :data="dataList"
    border
    v-loading="dataListLoading"
    @selection-change="selectionChangeHandle"
    style="width: 100%"
    >
    <el-table-column
    type="selection"
    header-align="center"
    align="center"
    width="50"
    >
    </el-table-column>
    <el-table-column
    prop="brandId"
    header-align="center"
    align="center"
    label="品牌id"
    >
    </el-table-column>
    <el-table-column
    prop="name"
    header-align="center"
    align="center"
    label="品牌名"
    >
    </el-table-column>
    <el-table-column
    prop="logo"
    header-align="center"
    align="center"
    label="品牌logo地址"
    >
    </el-table-column>
    <el-table-column
    prop="descript"
    header-align="center"
    align="center"
    label="介绍"
    >
    </el-table-column>
    <el-table-column
    prop="showStatus"
    header-align="center"
    align="center"
    label="显示状态"
    >
    <template slot-scope="scope">
    <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    :active-value="1"
    :inactive-value="0"
    @change="updateBrandStatus(scope.row)"
    >
    </el-switch>
    </template>
    </el-table-column>
    <el-table-column
    prop="firstLetter"
    header-align="center"
    align="center"
    label="检索首字母"
    >
    </el-table-column>
    <el-table-column
    prop="sort"
    header-align="center"
    align="center"
    label="排序"
    >
    </el-table-column>
    <el-table-column
    fixed="right"
    header-align="center"
    align="center"
    width="150"
    label="操作"
    >
    <template slot-scope="scope">
    <el-button
    type="text"
    size="small"
    @click="addOrUpdateHandle(scope.row.brandId)"
    >修改</el-button
    >
    <el-button
    type="text"
    size="small"
    @click="deleteHandle(scope.row.brandId)"
    >删除</el-button
    >
    </template>
    </el-table-column>
    </el-table>
    <el-pagination
    @size-change="sizeChangeHandle"
    @current-change="currentChangeHandle"
    :current-page="pageIndex"
    :page-sizes="[10, 20, 50, 100]"
    :page-size="pageSize"
    :total="totalPage"
    layout="total, sizes, prev, pager, next, jumper"
    >
    </el-pagination>
    <!-- 弹窗, 新增 / 修改 -->
    <add-or-update
    v-if="addOrUpdateVisible"
    ref="addOrUpdate"
    @refreshDataList="getDataList"
    ></add-or-update>
    </div>
    </template>

    <script>
    import AddOrUpdate from "./brand-add-or-update";
    export default {
    data() {
    return {
    dataForm: {
    key: "",
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false,
    };
    },
    components: {
    AddOrUpdate,
    },
    activated() {
    this.getDataList();
    },
    methods: {
    //修改品牌的显示的状态的
    updateBrandStatus(data) {
    console.log("修改的状态信息:", data);
    let { brandId, showStatus } = data;
    //发送修改状态的请求
    this.$http({
    url: this.$http.adornUrl(`/product/brand/update`),
    method: "post",
    data: this.$http.adornData(
    { brandId: brandId, showStatus: showStatus },
    false
    ),
    }).then(({ data }) => {
    this.$message({
    message: "状态更新成功",
    type: "success",
    });
    });
    },
    // 获取数据列表
    getDataList() {
    this.dataListLoading = true;
    this.$http({
    url: this.$http.adornUrl("/product/brand/list"),
    method: "get",
    params: this.$http.adornParams({
    page: this.pageIndex,
    limit: this.pageSize,
    key: this.dataForm.key,
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataList = data.page.list;
    this.totalPage = data.page.totalCount;
    } else {
    this.dataList = [];
    this.totalPage = 0;
    }
    this.dataListLoading = false;
    });
    },
    // 每页数
    sizeChangeHandle(val) {
    this.pageSize = val;
    this.pageIndex = 1;
    this.getDataList();
    },
    // 当前页
    currentChangeHandle(val) {
    this.pageIndex = val;
    this.getDataList();
    },
    // 多选
    selectionChangeHandle(val) {
    this.dataListSelections = val;
    },
    // 新增 / 修改
    addOrUpdateHandle(id) {
    this.addOrUpdateVisible = true;
    this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id);
    });
    },
    // 删除
    deleteHandle(id) {
    var ids = id
    ? [id]
    : this.dataListSelections.map((item) => {
    return item.brandId;
    });
    this.$confirm(
    `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
    "提示",
    {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    }
    ).then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/brand/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.getDataList();
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    });
    },
    },
    };
    </script>
    + +

    后端代码(修改品牌的显示状态)

    +
     /**
    * 修改
    */
    @RequestMapping("/update")
    //@RequiresPermissions("product:brand:update")
    public R update(@RequestBody BrandEntity brand){
    brandService.updateById(brand);

    return R.ok();
    }
    + +

    品牌添加功能

    +

    阿里云对象存储OSS | The Blog (gitee.io)

    +

    image-20230611153418426

    +

    前端的代码

    +

    品牌管理的列表页面

    +
    <template>
    <div class="mod-config">
    <el-form
    :inline="true"
    :model="dataForm"
    @keyup.enter.native="getDataList()"
    >
    <el-form-item>
    <el-input
    v-model="dataForm.key"
    placeholder="参数名"
    clearable
    ></el-input>
    </el-form-item>
    <el-form-item>
    <el-button @click="getDataList()">查询</el-button>
    <el-button
    v-if="isAuth('product:brand:save')"
    type="primary"
    @click="addOrUpdateHandle()"
    >新增</el-button
    >
    <el-button
    v-if="isAuth('product:brand:delete')"
    type="danger"
    @click="deleteHandle()"
    :disabled="dataListSelections.length <= 0"
    >批量删除</el-button
    >
    </el-form-item>
    </el-form>
    <el-table
    :data="dataList"
    border
    v-loading="dataListLoading"
    @selection-change="selectionChangeHandle"
    style="width: 100%"
    >
    <el-table-column
    type="selection"
    header-align="center"
    align="center"
    width="50"
    >
    </el-table-column>
    <el-table-column
    prop="brandId"
    header-align="center"
    align="center"
    label="品牌id"
    >
    </el-table-column>
    <el-table-column
    prop="name"
    header-align="center"
    align="center"
    label="品牌名"
    >
    </el-table-column>
    <el-table-column
    prop="logo"
    header-align="center"
    align="center"
    label="品牌logo地址"
    >
    <template slot-scope="scope">
    <img :src="scope.row.logo" style="width: 100px; height: 80px">
    </template>
    </el-table-column>
    <el-table-column
    prop="descript"
    header-align="center"
    align="center"
    label="介绍"
    >
    </el-table-column>
    <el-table-column
    prop="showStatus"
    header-align="center"
    align="center"
    label="显示状态"
    >
    <template slot-scope="scope">
    <el-switch
    v-model="scope.row.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    :active-value="1"
    :inactive-value="0"
    @change="updateBrandStatus(scope.row)"
    >
    </el-switch>
    </template>
    </el-table-column>
    <el-table-column
    prop="firstLetter"
    header-align="center"
    align="center"
    label="检索首字母"
    >
    </el-table-column>
    <el-table-column
    prop="sort"
    header-align="center"
    align="center"
    label="排序"
    >
    </el-table-column>
    <el-table-column
    fixed="right"
    header-align="center"
    align="center"
    width="150"
    label="操作"
    >
    <template slot-scope="scope">
    <el-button
    type="text"
    size="small"
    @click="addOrUpdateHandle(scope.row.brandId)"
    >修改</el-button
    >
    <el-button
    type="text"
    size="small"
    @click="deleteHandle(scope.row.brandId)"
    >删除</el-button
    >
    </template>
    </el-table-column>
    </el-table>
    <el-pagination
    @size-change="sizeChangeHandle"
    @current-change="currentChangeHandle"
    :current-page="pageIndex"
    :page-sizes="[10, 20, 50, 100]"
    :page-size="pageSize"
    :total="totalPage"
    layout="total, sizes, prev, pager, next, jumper"
    >
    </el-pagination>
    <!-- 弹窗, 新增 / 修改 -->
    <add-or-update
    v-if="addOrUpdateVisible"
    ref="addOrUpdate"
    @refreshDataList="getDataList"
    ></add-or-update>
    </div>
    </template>

    <script>
    import AddOrUpdate from "./brand-add-or-update";
    export default {
    data() {
    return {
    dataForm: {
    key: "",
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false,
    };
    },
    components: {
    AddOrUpdate,
    },
    activated() {
    this.getDataList();
    },
    methods: {
    //修改品牌的显示的状态的
    updateBrandStatus(data) {
    console.log("修改的状态信息:", data);
    let { brandId, showStatus } = data;
    //发送修改状态的请求
    this.$http({
    url: this.$http.adornUrl(`/product/brand/update`),
    method: "post",
    data: this.$http.adornData(
    { brandId: brandId, showStatus: showStatus },
    false
    ),
    }).then(({ data }) => {
    this.$message({
    message: "状态更新成功",
    type: "success",
    });
    });
    },
    // 获取数据列表
    getDataList() {
    this.dataListLoading = true;
    this.$http({
    url: this.$http.adornUrl("/product/brand/list"),
    method: "get",
    params: this.$http.adornParams({
    page: this.pageIndex,
    limit: this.pageSize,
    key: this.dataForm.key,
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataList = data.page.list;
    this.totalPage = data.page.totalCount;
    } else {
    this.dataList = [];
    this.totalPage = 0;
    }
    this.dataListLoading = false;
    });
    },
    // 每页数
    sizeChangeHandle(val) {
    this.pageSize = val;
    this.pageIndex = 1;
    this.getDataList();
    },
    // 当前页
    currentChangeHandle(val) {
    this.pageIndex = val;
    this.getDataList();
    },
    // 多选
    selectionChangeHandle(val) {
    this.dataListSelections = val;
    },
    // 新增 / 修改
    addOrUpdateHandle(id) {
    this.addOrUpdateVisible = true;
    this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id);
    });
    },
    // 删除
    deleteHandle(id) {
    var ids = id
    ? [id]
    : this.dataListSelections.map((item) => {
    return item.brandId;
    });
    this.$confirm(
    `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
    "提示",
    {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    }
    ).then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/brand/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.getDataList();
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    });
    },
    },
    };
    </script>

    + +

    品牌管理的添加和修改组件

    +
    <template>
    <el-dialog
    :title="!dataForm.brandId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible"
    >
    <el-form
    :model="dataForm"
    :rules="dataRule"
    ref="dataForm"
    @keyup.enter.native="dataFormSubmit()"
    label-width="140px"
    >
    <el-form-item label="品牌名" prop="name">
    <el-input v-model="dataForm.name" placeholder="品牌名"></el-input>
    </el-form-item>
    <el-form-item label="品牌logo地址" prop="logo">
    <!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> -->
    <!-- 自定义的组件 -->
    <SingleUpload v-model="dataForm.logo"></SingleUpload>
    </el-form-item>
    <el-form-item label="介绍" prop="descript">
    <el-input v-model="dataForm.descript" placeholder="介绍"></el-input>
    </el-form-item>
    <el-form-item label="显示状态" prop="showStatus">
    <el-switch
    v-model="dataForm.showStatus"
    active-color="#13ce66"
    inactive-color="#ff4949"
    :active-value="1"
    :inactive-value="0"
    >
    </el-switch>
    </el-form-item>
    <el-form-item label="检索首字母" prop="firstLetter">
    <el-input
    v-model="dataForm.firstLetter"
    placeholder="检索首字母"
    ></el-input>
    </el-form-item>
    <el-form-item label="排序" prop="sort">
    <el-input v-model.number="dataForm.sort" placeholder="排序"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="visible = false">取消</el-button>
    <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
    </el-dialog>
    </template>

    <script>
    import SingleUpload from "@/components/upload/singleUpload";
    export default {
    components: { SingleUpload }, //声明一下组件
    data() {
    return {
    visible: false,
    dataForm: {
    brandId: 0,
    name: "",
    logo: "",
    descript: "",
    showStatus: 1,
    firstLetter: "",
    sort: 0,
    },
    dataRule: {
    name: [{ required: true, message: "品牌名不能为空", trigger: "blur" }],
    logo: [
    { required: true, message: "品牌logo地址不能为空", trigger: "blur" },
    ],
    descript: [
    { required: true, message: "介绍不能为空", trigger: "blur" },
    ],
    showStatus: [
    {
    required: true,
    message: "显示状态[0-不显示;1-显示]不能为空",
    trigger: "blur",
    },
    ],
    firstLetter: [
    {
    validator: (rule, value, callback) => {
    if (value == "") {
    //检测是不是空串
    callback(new Error("检索首字母不能为空"));
    } else if (!/^[a-zA-Z]$/.test(value)) {
    //检测输入的是不是字母
    callback(new Error("首字母必须是a-z或者A-Z"));
    } else {
    //成功
    callback();
    }
    },
    trigger: "blur",
    },
    ],
    sort: [
    {
    validator: (rule, value, callback) => {
    if (value === "") {
    //检测是不是空串
    callback(new Error("排序不能为空"));
    } else if (!Number.isInteger(value) || value < 0) {
    //检测输入的是不是字母
    callback(new Error("排序必须为整数并且大于0"));
    } else {
    //成功
    callback();
    }
    },
    trigger: "blur",
    },
    ],
    },
    };
    },
    methods: {
    init(id) {
    this.dataForm.brandId = id || 0;
    this.visible = true;
    this.$nextTick(() => {
    this.$refs["dataForm"].resetFields();
    if (this.dataForm.brandId) {
    this.$http({
    url: this.$http.adornUrl(
    `/product/brand/info/${this.dataForm.brandId}`
    ),
    method: "get",
    params: this.$http.adornParams(),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataForm.name = data.brand.name;
    this.dataForm.logo = data.brand.logo;
    this.dataForm.descript = data.brand.descript;
    this.dataForm.showStatus = data.brand.showStatus;
    this.dataForm.firstLetter = data.brand.firstLetter;
    this.dataForm.sort = data.brand.sort;
    }
    });
    }
    });
    },
    // 表单提交
    dataFormSubmit() {
    this.$refs["dataForm"].validate((valid) => {
    if (valid) {
    this.$http({
    url: this.$http.adornUrl(
    `/product/brand/${!this.dataForm.brandId ? "save" : "update"}`
    ),
    method: "post",
    data: this.$http.adornData({
    brandId: this.dataForm.brandId || undefined,
    name: this.dataForm.name,
    logo: this.dataForm.logo,
    descript: this.dataForm.descript,
    showStatus: this.dataForm.showStatus,
    firstLetter: this.dataForm.firstLetter,
    sort: this.dataForm.sort,
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.visible = false;
    this.$emit("refreshDataList");
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    }
    });
    },
    },
    };
    </script>
    + +

    后端的代码

    +

    图片上传相关的后台接口

    +

    配置文件
    apllication.yml

    +
    spring:
    cloud:
    nacos:
    discovery:
    server-addr: 127.0.0.1:8848
    alicloud:
    access-key: XXXXXXXXXXXXXXXXXXXX
    secret-key: XXXXXXXXXXXXXXXXXXXX
    oss:
    endpoint: oss-cn-beijing.aliyuncs.com
    bucket: gulimall-0611 #因为是公用的配置,这个配置是自己配置的

    application:
    name: gulimall-third-party

    server:
    port: 30000
    + +

    Controller

    +
    package com.atguigu.gulimall.thirdparty.controller;

    import com.aliyun.oss.OSS;
    import com.aliyun.oss.OSSClientBuilder;
    import com.aliyun.oss.common.utils.BinaryUtil;
    import com.aliyun.oss.model.MatchMode;
    import com.aliyun.oss.model.PolicyConditions;
    import com.atguigu.common.utils.R;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.LinkedHashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/11
    * @Description 阿里云对象存储的控制器方法
    */

    @RestController
    public class OSSController {

    @Autowired
    OSS ossClient;

    @Value("${spring.cloud.alicloud.oss.endpoint}")
    private String endpoint;
    @Value("${spring.cloud.alicloud.oss.bucket}")
    private String bucket;
    @Value("${spring.cloud.alicloud.access-key}")
    private String accessId;
    @Value("${spring.cloud.alicloud.secret-key}")
    private String accessKey;

    @RequestMapping("/oss/policy")
    public R policy() {
    // 填写Host地址,格式为https://bucketname.endpoint。
    String host = "https://"+bucket+"."+endpoint;
    // 设置上传回调URL,即回调服务器地址,用于处理应用服务器与OSS之间的通信。OSS会在文件上传完成后,把文件上传信息通过此回调URL发送给应用服务器。
    //String callbackUrl = "https://192.168.0.0:8888";
    // 设置上传到OSS文件的前缀,可置空此项。置空后,文件将上传至Bucket的根目录下。
    String format = new SimpleDateFormat("yyyy/MM/dd").format(new Date());
    String dir = format+"/";

    // 创建ossClient实例。
    OSS ossClient = new OSSClientBuilder().build(endpoint, accessId, accessKey);
    Map<String, String> respMap = null;
    try {
    long expireTime = 30;
    long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
    Date expiration = new Date(expireEndTime);
    PolicyConditions policyConds = new PolicyConditions();
    policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
    policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);

    String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
    byte[] binaryData = postPolicy.getBytes("utf-8");
    String encodedPolicy = BinaryUtil.toBase64String(binaryData);
    String postSignature = ossClient.calculatePostSignature(postPolicy);

    respMap = new LinkedHashMap<String, String>();
    respMap.put("accessId", accessId);
    respMap.put("policy", encodedPolicy);
    respMap.put("signature", postSignature);
    respMap.put("dir", dir);
    respMap.put("host", host);
    respMap.put("expire", String.valueOf(expireEndTime / 1000));
    // respMap.put("expire", formatISO8601Date(expiration));
    } catch (Exception e) {
    System.out.println(e.getMessage());
    }
    return R.ok().put("data",respMap);
    }
    }

    + +

    配置网关,通过网关访问到该接口

    +
    - id: third_party_route
    uri: lb://gulimall-third-party
    predicates:
    - Path=/api/thirdparty/**
    filters:
    - RewritePath=/api/thirdparty/(?<segment>.*),/$\{segment}
    + +

    1.2.2 JSR303-数据校验

    数据校验 | The Blog (gitee.io)

    +

    1.依赖文件

    +
    <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.7.Final</version>
    </dependency>
    + +

    2.在实体类上添加上校验的规则

    +
    package com.atguigu.gulimall.product.entity;

    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableName;

    import java.io.Serializable;

    import lombok.Data;
    import org.hibernate.validator.constraints.URL;

    import javax.validation.constraints.*;

    /**
    * 品牌
    *
    * @author JasonGong
    * @email JasonGong@gmail.com
    * @date 2023-05-19 00:23:36
    */
    @Data
    @TableName("pms_brand")
    public class BrandEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
    * 品牌id
    */
    @TableId
    private Long brandId;
    /**
    * 品牌名
    */
    @NotBlank(message = "品牌名必须提交") //非空的校验
    private String name;
    /**
    * 品牌logo地址
    */
    @NotEmpty
    @URL(message = "logo必须是一个合法的URL地址")//URL的校验
    private String logo;
    /**
    * 介绍
    */
    private String descript;
    /**
    * 显示状态[0-不显示;1-显示]
    */
    private Integer showStatus;
    /**
    * 检索首字母
    */
    @NotEmpty
    @Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母")//正则表达式的校验
    private String firstLetter;
    /**
    * 排序
    */
    @NotNull
    @Min(value = 0, message = "排序必须大于等于0")//最小值是0
    private Integer sort;

    }
    + +

    3.接口上开启检验,并自定义校验出错时的返回值

    +
    /**
    * 保存
    *"@Valid" 开启校验
    * @param result 检验之后响应的结果信息
    */
    @RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
    if(result.hasErrors()){
    Map<String,String> map = new HashMap<>();
    //获取检验的错误结果
    for (FieldError fieldError : result.getFieldErrors()) {
    //获取到错误的提示
    String defaultMessage = fieldError.getDefaultMessage();
    //获取错误的属性的名字
    String field = fieldError.getField();
    map.put(field,defaultMessage);
    }
    return R.error(400,"提交的数据不合法").put("data",map);
    }else {
    brandService.save(brand);
    }
    return R.ok();
    }
    + +

    返回的json格式示例

    +

    image-20230612165021780

    +

    1.2.3 统一异常处理

    统一异常处理 | The Blog (gitee.io)

    +

    统一异常返回状态码

    +
    package com.atguigu.common.exception;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/12
    * @Description 返回的状态码信息
    */
    public enum BizCodeEnum {
    UNKNOWN_EXCEPTION(10000,"系统未知异常"),
    VALID_EXCEPTION(10001,"参数格式校验");

    private Integer code;
    private String msg;

    BizCodeEnum(Integer code, String msg) {
    this.code = code;
    this.msg = msg;
    }

    public Integer getCode() {
    return code;
    }

    public String getMsg() {
    return msg;
    }
    }
    + +

    创建异常处理类

    +
    package com.atguigu.gulimall.product.exception;

    import com.atguigu.common.exception.BizCodeEnum;
    import com.atguigu.common.utils.R;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/6/12
    * @Description 异常处理类
    */
    @Slf4j
    @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
    public class GulimallExceptionControllerAdvice {

    /**
    * 统一处理数据校验的异常
    * @param e 数据校验的异常
    */
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
    log.error("数据校验出现问题:{},异常类型:{}",e.getMessage(),e.getClass());
    Map<String,String> map = new HashMap<>();
    //获取检验的错误结果
    BindingResult result = e.getBindingResult();
    for (FieldError fieldError : result.getFieldErrors()) {
    //获取到错误的提示
    String defaultMessage = fieldError.getDefaultMessage();
    //获取错误的属性的名字
    String field = fieldError.getField();
    map.put(field,defaultMessage);
    }
    return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",map);
    }

    /**
    * 捕获任意的异常
    */
    @ExceptionHandler(value = Throwable.class)
    public R handleException(){
    return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(), BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
    }
    }
    + +

    1.2.4 关联分类功能

    功能截图

    +

    image-20230618232437618

    +

    后端代码的实现

    +

    查询品牌和分类的关联关系

    +
    /**
    * 查询商品的关联分类数据
    * @param brandId 品牌的id
    * @return 关联的分类数据
    */
    @GetMapping("/catelog/list")
    public R catelogList(@RequestParam("brandId") Long brandId){
    LambdaQueryWrapper<CategoryBrandRelationEntity> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(CategoryBrandRelationEntity::getBrandId,brandId);
    List<CategoryBrandRelationEntity> categoryBrandRelationEntityList = categoryBrandRelationService.list(queryWrapper);
    return R.ok().put("data", categoryBrandRelationEntityList);
    }
    + +

    新增品牌和商品的关联关系

    +

    controller

    +
    /**
    * 保存商品的关联的分类数据信息
    */
    @PostMapping("/save")
    public R saveCategoryBrandRelation(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
    categoryBrandRelationService.saveDetail(categoryBrandRelation);
    return R.ok();
    }
    + +

    service

    +
    /**
    * 保存商品和分类的关联数据
    *
    * @param categoryBrandRelation 商品和分类的关联数据表
    */
    @Override
    public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
    //获取品牌的id和关联分类的id
    Long brandId = categoryBrandRelation.getBrandId();
    Long catelogId = categoryBrandRelation.getCatelogId();
    //根据id查询对应的品牌名和分类名
    BrandEntity brand = brandDao.selectById(brandId);
    CategoryEntity category = categoryDao.selectById(catelogId);
    //设置值并保存
    categoryBrandRelation.setBrandName(brand.getName());
    categoryBrandRelation.setCatelogName(category.getName());
    this.save(categoryBrandRelation);
    }
    + +

    注意:这里品牌和关联分类之间的关系表中使用了冗余的字段,所以在修改品牌的时候,关系表中的字段也要修改一下

    +
    @Override
    public void updateDetail(BrandEntity brand) {
    //保证冗余字段的数据的一致性
    //更新品牌表中的数据
    this.updateById(brand);
    if (!StringUtils.isEmpty(brand.getName())) {
    //同步更新关联表中的数据
    categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
    //TODO 更新其他的关联
    }
    }
    + +

    关联的分类也是冗余的字段 也要同步修改

    +
    /**
    * 级联更新所有的关联的数据
    * @param category 商品分类的实体
    */
    @Override
    public void updateCascade(CategoryEntity category) {
    //更新分类表中的数据
    this.updateById(category);
    //更新分类与品牌名关联表中分类的名称
    categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
    }
    + +

    功能实现之后的截图

    +

    image-20230619111515367

    +

    1.3 平台属性

    创建属性管理的菜单(通过sql导入所有的菜单,导入所有以后需要使用的菜单)

    +

    资料中的sys_menus.sql文件保存的是所有的菜单信息

    +

    image-20230616152846339

    +

    谷粒商城的接口的在线文档:https://easydoc.net/s/78237135

    +

    image-20230616153304953

    +

    Object的划分

    +
      +
    1. PO 持久对象

      +

      PO就是对应数据库中某个表的一条记录,多个记录可以用PO的集合。PO中应该不包含对数据库的操作。

      +
    2. +
    3. DO 领域对象

      +

      就是从现实世界中抽象出来的有形或者无形的业务实体。

      +
    4. +
    5. TO 数据传输对象

      +

      不同的应用程序之间传输的对象

      +
    6. +
    7. DTO 数据传输对象

      +

      泛指展示层与服务层之间的数据传输对象

      +
    8. +
    9. VO 值对象

      +

      视图对象 接收页面传入过来的数据,封装对象;将业务处理完成的对象,封装成页面需要的数据

      +
    10. +
    11. BO 业务对象

      +
    12. +
    13. POJO 简单无规则的java对象

      +
    14. +
    15. DAO 数据访问对象

      +
    16. +
    +

    1.3.1 属性分组功能

    实现一个分类 联动的显示相应的分组信息

    +

    抽取商品分类的组件

    +
    <template>
    <el-tree
    :data="menus"
    :props="defaultProps"
    node-key="catId"
    ref="menuTree"
    ></el-tree>
    </template>
    <script>
    export default {
    components: {},
    data() {
    return {
    menus: [],
    expandedKey: [],
    defaultProps: {
    children: "children",
    label: "name",
    },
    };
    },
    created() {
    this.getMenus();
    },
    methods: {
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    console.log("商品分类数据:", data.data);
    this.menus = data.data;
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    属性分组页面

    +
    <template>
    <el-row :gutter="20">
    <el-col :span="6"> <category></category></el-col>
    <el-col :span="18">
    <div class="mod-config">
    <el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
    <el-form-item>
    <el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
    </el-form-item>
    <el-form-item>
    <el-button @click="getDataList()">查询</el-button>
    <el-button v-if="isAuth('product:attrgroup:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
    <el-button v-if="isAuth('product:attrgroup:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
    </el-form-item>
    </el-form>
    <el-table
    :data="dataList"
    border
    v-loading="dataListLoading"
    @selection-change="selectionChangeHandle"
    style="width: 100%;">
    <el-table-column
    type="selection"
    header-align="center"
    align="center"
    width="50">
    </el-table-column>
    <el-table-column
    prop="attrGroupId"
    header-align="center"
    align="center"
    label="分组id">
    </el-table-column>
    <el-table-column
    prop="attrGroupName"
    header-align="center"
    align="center"
    label="组名">
    </el-table-column>
    <el-table-column
    prop="sort"
    header-align="center"
    align="center"
    label="排序">
    </el-table-column>
    <el-table-column
    prop="descript"
    header-align="center"
    align="center"
    label="描述">
    </el-table-column>
    <el-table-column
    prop="icon"
    header-align="center"
    align="center"
    label="组图标">
    </el-table-column>
    <el-table-column
    prop="catelogId"
    header-align="center"
    align="center"
    label="所属分类id">
    </el-table-column>
    <el-table-column
    fixed="right"
    header-align="center"
    align="center"
    width="150"
    label="操作">
    <template slot-scope="scope">
    <el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.attrGroupId)">修改</el-button>
    <el-button type="text" size="small" @click="deleteHandle(scope.row.attrGroupId)">删除</el-button>
    </template>
    </el-table-column>
    </el-table>
    <el-pagination
    @size-change="sizeChangeHandle"
    @current-change="currentChangeHandle"
    :current-page="pageIndex"
    :page-sizes="[10, 20, 50, 100]"
    :page-size="pageSize"
    :total="totalPage"
    layout="total, sizes, prev, pager, next, jumper">
    </el-pagination>
    <!-- 弹窗, 新增 / 修改 -->
    <add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
    </div></el-col>
    </el-row>
    </template>
    <script>
    import Category from "../common/category.vue";
    import AddOrUpdate from './attrgroup-add-or-update'
    export default {
    data () {
    return {
    dataForm: {
    key: ''
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false
    }
    },
    components: {
    AddOrUpdate,Category
    },
    activated () {
    this.getDataList()
    },
    methods: {
    // 获取数据列表
    getDataList () {
    this.dataListLoading = true
    this.$http({
    url: this.$http.adornUrl('/product/attrgroup/list'),
    method: 'get',
    params: this.$http.adornParams({
    'page': this.pageIndex,
    'limit': this.pageSize,
    'key': this.dataForm.key
    })
    }).then(({data}) => {
    if (data && data.code === 0) {
    this.dataList = data.page.list
    this.totalPage = data.page.totalCount
    } else {
    this.dataList = []
    this.totalPage = 0
    }
    this.dataListLoading = false
    })
    },
    // 每页数
    sizeChangeHandle (val) {
    this.pageSize = val
    this.pageIndex = 1
    this.getDataList()
    },
    // 当前页
    currentChangeHandle (val) {
    this.pageIndex = val
    this.getDataList()
    },
    // 多选
    selectionChangeHandle (val) {
    this.dataListSelections = val
    },
    // 新增 / 修改
    addOrUpdateHandle (id) {
    this.addOrUpdateVisible = true
    this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id)
    })
    },
    // 删除
    deleteHandle (id) {
    var ids = id ? [id] : this.dataListSelections.map(item => {
    return item.attrGroupId
    })
    this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
    }).then(() => {
    this.$http({
    url: this.$http.adornUrl('/product/attrgroup/delete'),
    method: 'post',
    data: this.$http.adornData(ids, false)
    }).then(({data}) => {
    if (data && data.code === 0) {
    this.$message({
    message: '操作成功',
    type: 'success',
    duration: 1500,
    onClose: () => {
    this.getDataList()
    }
    })
    } else {
    this.$message.error(data.msg)
    }
    })
    })
    }
    }
    }
    </script>
    <style>
    </style>
    + +

    页面效果

    +

    image-20230617173759895

    +

    父子组件中数据的传递

    +

    子组件给父组件传递的数据,事件传递,子组件给父组件发送一个事件,事件传递

    +

    子组件调整

    +
    <template>
    <el-tree
    :data="menus"
    :props="defaultProps"
    node-key="catId"
    ref="menuTree"
    @node-click="nodeclick"
    ></el-tree>
    </template>
    <script>
    export default {
    components: {},
    data() {
    return {
    menus: [],
    expandedKey: [],
    defaultProps: {
    children: "children",
    label: "name",
    },
    };
    },
    created() {
    this.getMenus();
    },
    methods: {
    getMenus() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    console.log("商品分类数据:", data.data);
    this.menus = data.data;
    });
    },
    nodeclick(data,node,component) {
    console.log("子组件被点击,传递的数据有:data:",data,"node:",node,"component:",component);
    //子组件向父组件传递数据
    this.$emit("tree-node-click",data,node,component);
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    父组件接受相应的数据

    +
    <template>
    <el-row :gutter="20">
    <el-col :span="6">
    <category @tree-node-click="treeNodeClick"></category
    ></el-col>
    <el-col :span="18">
    <div class="mod-config">
    <el-form
    :inline="true"
    :model="dataForm"
    @keyup.enter.native="getDataList()"
    >
    <el-form-item>
    <el-input
    v-model="dataForm.key"
    placeholder="参数名"
    clearable
    ></el-input>
    </el-form-item>
    <el-form-item>
    <el-button @click="getDataList()">查询</el-button>
    <el-button
    v-if="isAuth('product:attrgroup:save')"
    type="primary"
    @click="addOrUpdateHandle()"
    >新增</el-button
    >
    <el-button
    v-if="isAuth('product:attrgroup:delete')"
    type="danger"
    @click="deleteHandle()"
    :disabled="dataListSelections.length <= 0"
    >批量删除</el-button
    >
    </el-form-item>
    </el-form>
    <el-table
    :data="dataList"
    border
    v-loading="dataListLoading"
    @selection-change="selectionChangeHandle"
    style="width: 100%"
    >
    <el-table-column
    type="selection"
    header-align="center"
    align="center"
    width="50"
    >
    </el-table-column>
    <el-table-column
    prop="attrGroupId"
    header-align="center"
    align="center"
    label="分组id"
    >
    </el-table-column>
    <el-table-column
    prop="attrGroupName"
    header-align="center"
    align="center"
    label="组名"
    >
    </el-table-column>
    <el-table-column
    prop="sort"
    header-align="center"
    align="center"
    label="排序"
    >
    </el-table-column>
    <el-table-column
    prop="descript"
    header-align="center"
    align="center"
    label="描述"
    >
    </el-table-column>
    <el-table-column
    prop="icon"
    header-align="center"
    align="center"
    label="组图标"
    >
    </el-table-column>
    <el-table-column
    prop="catelogId"
    header-align="center"
    align="center"
    label="所属分类id"
    >
    </el-table-column>
    <el-table-column
    fixed="right"
    header-align="center"
    align="center"
    width="150"
    label="操作"
    >
    <template slot-scope="scope">
    <el-button
    type="text"
    size="small"
    @click="addOrUpdateHandle(scope.row.attrGroupId)"
    >修改</el-button
    >
    <el-button
    type="text"
    size="small"
    @click="deleteHandle(scope.row.attrGroupId)"
    >删除</el-button
    >
    </template>
    </el-table-column>
    </el-table>
    <el-pagination
    @size-change="sizeChangeHandle"
    @current-change="currentChangeHandle"
    :current-page="pageIndex"
    :page-sizes="[10, 20, 50, 100]"
    :page-size="pageSize"
    :total="totalPage"
    layout="total, sizes, prev, pager, next, jumper"
    >
    </el-pagination>
    <!-- 弹窗, 新增 / 修改 -->
    <add-or-update
    v-if="addOrUpdateVisible"
    ref="addOrUpdate"
    @refreshDataList="getDataList"
    ></add-or-update></div
    ></el-col>
    </el-row>
    </template>
    <script>
    import Category from "../common/category.vue";
    import AddOrUpdate from "./attrgroup-add-or-update";
    export default {
    data() {
    return {
    catId: 0,
    dataForm: {
    key: "",
    },
    dataList: [],
    pageIndex: 1,
    pageSize: 10,
    totalPage: 0,
    dataListLoading: false,
    dataListSelections: [],
    addOrUpdateVisible: false,
    };
    },
    components: {
    AddOrUpdate,
    Category,
    },
    activated() {
    this.getDataList();
    },
    methods: {
    treeNodeClick(data, node, component) {
    //获取子组件传递过来的值
    console.log(
    "获取子组件传递过来的值:data:",
    data,
    "node",
    node,
    "component:",
    component
    );
    //查询三级分类信息 当点击三级分类id的时候 才查询相关的信息
    if(node.level == 3){
    this.catId = data.catId;
    this.getDataList();
    }
    },
    // 获取数据列表
    getDataList() {
    this.dataListLoading = true;
    this.$http({
    url: this.$http.adornUrl(`/product/attrgroup/list/${this.catId}`),
    method: "get",
    params: this.$http.adornParams({
    page: this.pageIndex,
    limit: this.pageSize,
    key: this.dataForm.key,
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataList = data.page.list;
    this.totalPage = data.page.totalCount;
    } else {
    this.dataList = [];
    this.totalPage = 0;
    }
    this.dataListLoading = false;
    });
    },
    // 每页数
    sizeChangeHandle(val) {
    this.pageSize = val;
    this.pageIndex = 1;
    this.getDataList();
    },
    // 当前页
    currentChangeHandle(val) {
    this.pageIndex = val;
    this.getDataList();
    },
    // 多选
    selectionChangeHandle(val) {
    this.dataListSelections = val;
    },
    // 新增 / 修改
    addOrUpdateHandle(id) {
    this.addOrUpdateVisible = true;
    this.$nextTick(() => {
    this.$refs.addOrUpdate.init(id);
    });
    },
    // 删除
    deleteHandle(id) {
    var ids = id
    ? [id]
    : this.dataListSelections.map((item) => {
    return item.attrGroupId;
    });
    this.$confirm(
    `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
    "提示",
    {
    confirmButtonText: "确定",
    cancelButtonText: "取消",
    type: "warning",
    }
    ).then(() => {
    this.$http({
    url: this.$http.adornUrl("/product/attrgroup/delete"),
    method: "post",
    data: this.$http.adornData(ids, false),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.getDataList();
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    });
    },
    },
    };
    </script>

    <style>
    </style>
    + +

    后端代码

    +
    @Override
    public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
    //首先先判断catelogId是不是0 如果是0的话就查询所有的属性分组信息
    if (catelogId == 0) {
    IPage<AttrGroupEntity> page = this.page(
    new Query<AttrGroupEntity>().getPage(params),
    new QueryWrapper<AttrGroupEntity>()
    );
    return new PageUtils(page);
    } else {
    //查询的时候传递过来的关键词
    String key = (String) params.get("key");
    //SQL语句:select * from pms_attr_group where catelog_id = ? and (attr_group_id = key or attr_group_name = %key%)
    LambdaQueryWrapper<AttrGroupEntity> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(AttrGroupEntity::getCatelogId, catelogId);
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.and((obj) -> {
    obj.eq(AttrGroupEntity::getAttrGroupId, key).or().like(AttrGroupEntity::getAttrGroupName, key);
    });
    }
    IPage<AttrGroupEntity> page = this.page(
    new Query<AttrGroupEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    }
    + +

    属性分组的添加功能

    +

    前端代码

    +
    <template>
    <el-dialog
    :title="!dataForm.attrGroupId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible"
    @closed="dialogClose"
    >
    <el-form
    :model="dataForm"
    :rules="dataRule"
    ref="dataForm"
    @keyup.enter.native="dataFormSubmit()"
    label-width="100px"
    >
    <el-form-item label="组名" prop="attrGroupName">
    <el-input
    v-model="dataForm.attrGroupName"
    placeholder="组名"
    ></el-input>
    </el-form-item>
    <el-form-item label="排序" prop="sort">
    <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
    </el-form-item>
    <el-form-item label="描述" prop="descript">
    <el-input v-model="dataForm.descript" placeholder="描述"></el-input>
    </el-form-item>
    <el-form-item label="组图标" prop="icon">
    <el-input v-model="dataForm.icon" placeholder="组图标"></el-input>
    </el-form-item>
    <el-form-item label="所属分类id" prop="catelogId">
    <!-- <el-input v-model="dataForm.catelogId" placeholder="所属分类id"></el-input> -->
    <el-cascader
    v-model="dataForm.catelogPath"
    :options="categorys"
    :props="props"
    placeholder="试试搜索:手机"
    filterable
    ></el-cascader>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
    <el-button @click="visible = false">取消</el-button>
    <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
    </el-dialog>
    </template>

    <script>
    import Category from "../common/category.vue";

    export default {
    data() {
    return {
    props: {
    value: "catId",
    label: "name",
    children: "children",
    },
    categorys: [], //所有的三级分类信息
    visible: false,
    dataForm: {
    attrGroupId: 0,
    attrGroupName: "",
    sort: "",
    descript: "",
    icon: "",
    catelogPath: [],
    catelogId: 0,
    },
    dataRule: {
    attrGroupName: [
    { required: true, message: "组名不能为空", trigger: "blur" },
    ],
    sort: [{ required: true, message: "排序不能为空", trigger: "blur" }],
    descript: [
    { required: true, message: "描述不能为空", trigger: "blur" },
    ],
    icon: [{ required: true, message: "组图标不能为空", trigger: "blur" }],
    catelogId: [
    { required: true, message: "所属分类id不能为空", trigger: "blur" },
    ],
    },
    };
    },
    created() {
    this.getCategorys();
    },
    methods: {
    dialogClose() {
    this.dataForm.catelogPath = [];
    },
    getCategorys() {
    this.$http({
    url: this.$http.adornUrl("/product/category/list/tree"),
    method: "get",
    }).then(({ data }) => {
    this.categorys = data.data;
    });
    },
    init(id) {
    this.dataForm.attrGroupId = id || 0;
    this.visible = true;
    this.$nextTick(() => {
    this.$refs["dataForm"].resetFields();
    if (this.dataForm.attrGroupId) {
    this.$http({
    url: this.$http.adornUrl(
    `/product/attrgroup/info/${this.dataForm.attrGroupId}`
    ),
    method: "get",
    params: this.$http.adornParams(),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.dataForm.attrGroupName = data.attrGroup.attrGroupName;
    this.dataForm.sort = data.attrGroup.sort;
    this.dataForm.descript = data.attrGroup.descript;
    this.dataForm.icon = data.attrGroup.icon;
    this.dataForm.catelogId = data.attrGroup.catelogId;
    //查出catelog的完整路径
    this.dataForm.catelogPath = data.attrGroup.catelogPath;
    }
    });
    }
    });
    },
    // 表单提交
    dataFormSubmit() {
    this.$refs["dataForm"].validate((valid) => {
    if (valid) {
    this.$http({
    url: this.$http.adornUrl(
    `/product/attrgroup/${
    !this.dataForm.attrGroupId ? "save" : "update"
    }`
    ),
    method: "post",
    data: this.$http.adornData({
    attrGroupId: this.dataForm.attrGroupId || undefined,
    attrGroupName: this.dataForm.attrGroupName,
    sort: this.dataForm.sort,
    descript: this.dataForm.descript,
    icon: this.dataForm.icon,
    catelogId:
    this.dataForm.catelogPath[this.dataForm.catelogPath.length - 1],
    }),
    }).then(({ data }) => {
    if (data && data.code === 0) {
    this.$message({
    message: "操作成功",
    type: "success",
    duration: 1500,
    onClose: () => {
    this.visible = false;
    this.$emit("refreshDataList");
    },
    });
    } else {
    this.$message.error(data.msg);
    }
    });
    }
    });
    },
    },
    };
    </script>
    + +

    后端代码

    +

    在商品分类的children上加上@JsonInclude注解,解决后面的级联选择多出一个空白的选择的问题

    +
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    @TableField(exist = false)//这个注解要加,因为这个属性不是数据库中的字段
    private List<CategoryEntity> children;//这里的属性名不是瞎起的,与ElementUI中的显示相对应,这里改了,前端也要改
    + +

    修改的时候,级联选择部分数据的回显

    +
    /**
    * 找到catelog的完整路径
    * @param catelogId 路径的最后一个catelog的id
    * @return 完整的路径
    */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
    List<Long> paths = new ArrayList<>();
    List<Long> parentPath = findParentPath(catelogId, paths);
    Collections.reverse(parentPath);
    return (Long[]) parentPath.toArray(new Long[parentPath.size()]);
    }
    /**
    * 递归找出所有的路径
    */
    private List<Long> findParentPath(Long catelogId,List<Long> paths){
    paths.add(catelogId);
    //根据id查询相关的信息
    CategoryEntity category = this.getById(catelogId);
    if(category.getParentCid()!=0){
    findParentPath(category.getParentCid(),paths);
    }
    return paths;
    }
    + +

    属性关联商品的规格参数和基本属性信息

    +

    功能描述

    +

    image-20230625090118059

    +

    查询商品下关联的属性分组信息

    +

    查询商品关联属性分组的后端关键代码

    +
    /**
    * 根据分组的id查询关联的所有属性
    *
    * @param attrgroupId 分组的id
    * @return 关联的所有属性组成集合
    */
    @Override
    public List<AttrEntity> getRelationAttr(Long attrgroupId) {
    //根据中间表查询出属性分组的id信息
    List<AttrAttrgroupRelationEntity> relationEntityList = relationDao.selectList(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrGroupId, attrgroupId));
    List<Long> attrIds = new ArrayList<>();
    for (AttrAttrgroupRelationEntity relationEntity : relationEntityList) {
    attrIds.add(relationEntity.getAttrId());
    }
    //根据属性分组的id查询属性分组的详细信息
    return (List<AttrEntity>) this.listByIds(attrIds);
    }
    + +

    移除关联属性分组的功能后端的关键代码

    +

    service层的代码

    +
    /**
    * 批量删除商品关联的属性分组信息
    *
    * @param vos 属性id和属性分组id组成的集合
    */
    @Override
    public void deleteRelation(AttrGroupRelationVo[] vos) {
    List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {
    AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
    BeanUtils.copyProperties(item, relationEntity);
    return relationEntity;
    }).collect(Collectors.toList());
    relationDao.deleteBatchRelation(entities);
    }
    + +

    dao层的代码

    +

    使用动态的sql批量的删除属性和分组的关联关系

    +
    <!--void deleteBatchRelation(List<AttrAttrgroupRelationEntity> entities);-->
    <delete id="deleteBatchRelation">
    delete from `pms_attr_attrgroup_relation` where
    <foreach collection="entities" item="item" separator=" OR ">
    (attr_id = #{item.attrId}) and attr_group_id = #{item.attrGroupId}
    </foreach>
    </delete>
    + +

    查询属性分组没有关联的属性

    +
    /**
    * 获取当前分组没有关联的所有属性
    *
    * @param params 分页相关的参数
    * @param attrgroupId 属性分组的id
    * @return 分页相关的数据
    */
    @Override
    public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {
    //当前的分组只能关联自己所属的分类里面的所有属性 例如手机的分类里面只能关联手机的所有属性
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupId);
    Long catelogId = attrGroupEntity.getCatelogId();
    //当前分组只能关联别的分组没有引用的属性
    List<AttrGroupEntity> groups = attrGroupDao.selectList(
    new LambdaQueryWrapper<AttrGroupEntity>().eq(AttrGroupEntity::getCatelogId, catelogId));
    List<Long> collect = groups.stream().map((item) -> {
    return item.getAttrGroupId();
    }).collect(Collectors.toList());
    List<AttrAttrgroupRelationEntity> groupId = relationDao.selectList(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().in(AttrAttrgroupRelationEntity::getAttrGroupId, collect));
    List<Long> attrIds = groupId.stream().map((item) -> {
    return item.getAttrId();
    }).collect(Collectors.toList());
    LambdaQueryWrapper<AttrEntity> queryWrapper =
    new LambdaQueryWrapper<AttrEntity>().eq(AttrEntity::getCatelogId, catelogId).eq(AttrEntity::getAttrType,ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
    if (attrIds != null && attrIds.size() > 0) {
    queryWrapper.notIn(AttrEntity::getAttrId, attrIds);
    }
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.eq(AttrEntity::getAttrId, key).or().like(AttrEntity::getAttrName, key);
    }
    IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), queryWrapper);
    return new PageUtils(page);
    }
    + +

    给属性关联相关的规格参数和销售属性

    +
    @Override
    public void saveBatch(List<AttrGroupRelationVo> vos) {
    List<AttrAttrgroupRelationEntity> entities = vos.stream().map((item) -> {
    AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
    BeanUtils.copyProperties(item, relationEntity);
    return relationEntity;
    }).collect(Collectors.toList());
    this.saveBatch(entities);
    }
    + +

    1.3.2 规格参数功能

    功能截图

    +

    image-20230619111338153

    +

    新增功能的后端关键代码

    +
    /**
    * 保存规格参数信息
    * @param attr 页面中提交的规格参数相关的信息组成的实体
    */
    @Transactional
    @Override
    public void saveAttr(AttrVo attr) {
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attr,attrEntity);
    //保存基本的数据
    this.save(attrEntity);
    //保存关联关系
    AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
    relationEntity.setAttrGroupId(attr.getAttrGroupId());
    relationEntity.setAttrId(attrEntity.getAttrId());
    relationDao.insert(relationEntity);
    }
    + +

    查询列表后端的关键代码

    +

    这里使用的stream流处理数据,没有使用多表联合查询来查询数据

    +
    @Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
    LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<>();
    if (catelogId != 0) {//等于0就是查询所有的分类信息下的规格参数信息
    queryWrapper.eq(AttrEntity::getCatelogId, catelogId);
    }
    //获取检索的条件
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.like(AttrEntity::getAttrName, key).or().eq(AttrEntity::getAttrId, key);
    }
    IPage<AttrEntity> page = this.page(
    new Query<AttrEntity>().getPage(params),
    queryWrapper
    );
    PageUtils pageUtils = new PageUtils(page);
    List<AttrEntity> records = page.getRecords();
    List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
    AttrRespVo attrRespVo = new AttrRespVo();
    BeanUtils.copyProperties(attrEntity, attrRespVo);
    //设置三级商品分类名和分组名
    //设置所属的商品的分类名
    CategoryEntity categoryEntity = categoryDao.selectById(attrRespVo.getCatelogId());
    if (categoryEntity != null) {
    attrRespVo.setCatelogName(categoryEntity.getName());
    }
    //设置分组名(需要从中间表查询分组的id,再查询分组名)
    //先从中间表查询到分组的id
    AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
    //从分组的表中查询分组名
    if (relationEntity != null) {
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
    }
    return attrRespVo;
    }).collect(Collectors.toList());
    pageUtils.setList(respVos);
    return pageUtils;
    }
    + +

    规格参数修改功能后端的关键代码

    +

    修改前的数据回显

    +

    查询属性的详情信息

    +
    /**
    * 查询属性的详细信息
    *
    * @param attrId 属性的id
    * @return 属性的详细信息封装的集合
    */
    @Override
    public AttrRespVo getAttrInfo(Long attrId) {
    AttrRespVo attrRespVo = new AttrRespVo();
    //查询属性的基本信息
    AttrEntity attr = this.getById(attrId);
    BeanUtils.copyProperties(attr, attrRespVo);
    //查询分组id和分类的详细路径
    //分组信息的设置
    AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
    if (relationEntity != null) {
    attrRespVo.setAttrGroupId(relationEntity.getAttrGroupId());
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
    if (attrGroupEntity != null) {
    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
    }
    }
    //设置分类信息
    Long catelogId = attr.getCatelogId();
    //查询分类的完整路径
    Long[] catelogPath = categoryService.findCatelogPath(catelogId);
    if (catelogPath != null) {
    attrRespVo.setCatelogPath(catelogPath);
    }
    //设置分类名
    CategoryEntity category = categoryDao.selectById(catelogId);
    if (category != null) {
    attrRespVo.setCatelogName(category.getName());
    }
    return attrRespVo;
    }
    + +

    修改的时候页面回显的效果

    +

    image-20230624192906124

    +

    修改功能后端的关键代码

    +
    /**
    * 修改属性的方法
    *
    * @param attr 页面传输过来的商品属性的实体
    */
    @Transactional
    @Override
    public void updateAttr(AttrVo attr) {
    //修改基本的数据
    AttrEntity attrEntity = new AttrEntity();
    BeanUtils.copyProperties(attr, attrEntity);
    this.updateById(attrEntity);
    //修改关联表的数据
    AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
    BeanUtils.copyProperties(attr, relationEntity);
    LambdaQueryWrapper<AttrAttrgroupRelationEntity> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(AttrAttrgroupRelationEntity::getAttrId, relationEntity.getAttrId());
    Integer count = relationDao.selectCount(queryWrapper);
    if (count > 0) {//判断是修改还是添加的操作
    relationDao.update(relationEntity, queryWrapper);
    } else {
    relationDao.insert(relationEntity);
    }
    }
    + +

    1.3.3 销售属性功能

    分类属性列表显示

    +

    controller层的代码

    +
    /**
    * /product/attr/sale/list/{catelogId}
    * 路径 /product/attr/base/list/{catelogId}
    * 规格参数的列表显示
    *
    * @param params 条件查询的参数
    * @param catelogId 商品分类的id
    * @return 规格参数的分页数据
    */
    @GetMapping("/{attrType}/list/{catelogId}")
    public R baseAttrList(@RequestParam Map<String, Object> params, @PathVariable Long catelogId, @PathVariable("attrType") String type) {
    log.warn("规格参数分页的查询条件:{},商品分类的id:{}", params, catelogId);
    PageUtils page = attrService.queryBaseAttrPage(params, catelogId,type);
    return R.ok().put("page", page);
    }
    + +

    service层的代码

    +
    @Override
    public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String type) {
    LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<>();
    //路径中属性的attrType是base就是基本属性,否则就代表的是销售属性
    queryWrapper.eq(AttrEntity::getAttrType, "base".equalsIgnoreCase(type) ? 1 : 0);
    if (catelogId != 0) {//等于0就是查询所有的分类信息下的规格参数信息
    queryWrapper.eq(AttrEntity::getCatelogId, catelogId);
    }
    //获取检索的条件
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.like(AttrEntity::getAttrName, key).or().eq(AttrEntity::getAttrId, key);
    }
    IPage<AttrEntity> page = this.page(
    new Query<AttrEntity>().getPage(params),
    queryWrapper
    );
    PageUtils pageUtils = new PageUtils(page);
    List<AttrEntity> records = page.getRecords();
    List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
    AttrRespVo attrRespVo = new AttrRespVo();
    BeanUtils.copyProperties(attrEntity, attrRespVo);
    //设置三级商品分类名和分组名
    //设置所属的商品的分类名
    CategoryEntity categoryEntity = categoryDao.selectById(attrRespVo.getCatelogId());
    if (categoryEntity != null) {
    attrRespVo.setCatelogName(categoryEntity.getName());
    }
    //设置分组名(需要从中间表查询分组的id,再查询分组名)
    //先从中间表查询到分组的id
    AttrAttrgroupRelationEntity relationEntity = relationDao.selectOne(
    new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId, attrRespVo.getAttrId()));
    //从分组的表中查询分组名
    if (relationEntity != null) {
    AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(relationEntity.getAttrGroupId());
    if (attrGroupEntity != null) {
    attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
    }
    }
    return attrRespVo;
    }).collect(Collectors.toList());
    pageUtils.setList(respVos);
    return pageUtils;
    }
    + +

    完整的功能展示

    +

    image-20230625090037616

    +

    1.4 商品维护

    1.4.1 SPU管理

    条件查询功能(Spu检索)

    +
    /**
    * Spu检索功能
    *
    * @param params 分页相关的数据
    * @return 分页对象
    */
    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
    LambdaQueryWrapper<SpuInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
    //关键字检索
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    //这里使用了or,为了和下面的区分开来,要使用and,将下面这个条件用括号括起来
    //SELECT COUNT(1) FROM pms_spu_info WHERE (((spu_name LIKE ? OR id = ?)) AND catalog_id = ? AND brand_id = ? AND publish_status = ?)
    queryWrapper.and((wrapper) -> {
    wrapper.like(SpuInfoEntity::getSpuName, key).or().eq(SpuInfoEntity::getId, key);
    });
    }
    //分类
    String catelogId = (String) params.get("catelogId");
    if (!StringUtils.isEmpty(catelogId)) {
    queryWrapper.eq(SpuInfoEntity::getCatalogId, catelogId);
    }
    //品牌
    String brandId = (String) params.get("brandId");
    if (!StringUtils.isEmpty(brandId)) {
    queryWrapper.eq(SpuInfoEntity::getBrandId, brandId);
    }
    //状态
    String status = (String) params.get("status");
    if (!StringUtils.isEmpty(status)) {
    queryWrapper.eq(SpuInfoEntity::getPublishStatus, status);
    }

    IPage<SpuInfoEntity> page = this.page(
    new Query<SpuInfoEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的功能

    +

    image-20230627102918192

    +

    前端显示时间格式的数据有问题

    +

    配置文件中的配置

    +
    jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    + +

    商品规格的回显

    +
    /**
    * 查询商品的规格属性
    * /product/attr/base/listforspu/{spuId}
    */
    @GetMapping("/base/listforspu/{spuId}")
    public R baseAttrList(@PathVariable("spuId") Long spuId){
    List<ProductAttrValueEntity> entities = productAttrValueService.baseAttrListForSpu(spuId);
    return R.ok().put("data",entities);
    }
    + +

    修改商品的规格

    +
    /**
    * 修改商品的规格参数
    * @param spuId 商品的id
    * @param productAttrValueEntities 提交的修改之后的商品规格参数组成的集合
    */
    @Transactional
    @Override
    public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> productAttrValueEntities) {
    //1.删除这个spuId之前对应的所有属性
    this.baseMapper.delete(new LambdaQueryWrapper<ProductAttrValueEntity>().eq(ProductAttrValueEntity::getSpuId,spuId));
    //2.添加修改之后的商品的属性信息
    List<ProductAttrValueEntity> collect = productAttrValueEntities.stream().map(item -> {
    item.setSpuId(spuId);
    return item;
    }).collect(Collectors.toList());
    this.saveBatch(collect);
    }
    + +

    实现的功能

    +

    image-20230628165931139

    +

    中间遇到的问题,点击规格,页面显示400的问题

    +

    image-20230628171558364

    +

    解决方案

    +

    第一步:在gulimall-admin数据库中执行以下的sql语句

    +
    INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num) VALUES (76, 37, '规格维护', 'product/attrupdate', '', 2, 'log', 0);
    + +

    第二步:将前端src/router/index.js替换成如下的内容

    +
    /**
    * 全站路由配置
    *
    * 建议:
    * 1. 代码中路由统一使用name属性跳转(不使用path属性)
    */
    import Vue from 'vue'
    import Router from 'vue-router'
    import http from '@/utils/httpRequest'
    import { isURL } from '@/utils/validate'
    import { clearLoginInfo } from '@/utils'

    Vue.use(Router)

    // 开发环境不使用懒加载, 因为懒加载页面太多的话会造成webpack热更新太慢, 所以只有生产环境使用懒加载
    const _import = require('./import-' + process.env.NODE_ENV)

    // 全局路由(无需嵌套上左右整体布局)
    const globalRoutes = [
    { path: '/404', component: _import('common/404'), name: '404', meta: { title: '404未找到' } },
    { path: '/login', component: _import('common/login'), name: 'login', meta: { title: '登录' } }
    ]

    // 主入口路由(需嵌套上左右整体布局)
    const mainRoutes = {
    path: '/',
    component: _import('main'),
    name: 'main',
    redirect: { name: 'home' },
    meta: { title: '主入口整体布局' },
    children: [
    // 通过meta对象设置路由展示方式
    // 1. isTab: 是否通过tab展示内容, true: 是, false: 否
    // 2. iframeUrl: 是否通过iframe嵌套展示内容, '以http[s]://开头': 是, '': 否
    // 提示: 如需要通过iframe嵌套展示内容, 但不通过tab打开, 请自行创建组件使用iframe处理!
    { path: '/home', component: _import('common/home'), name: 'home', meta: { title: '首页' } },
    { path: '/theme', component: _import('common/theme'), name: 'theme', meta: { title: '主题' } },
    { path: '/demo-echarts', component: _import('demo/echarts'), name: 'demo-echarts', meta: { title: 'demo-echarts', isTab: true } },
    { path: '/demo-ueditor', component: _import('demo/ueditor'), name: 'demo-ueditor', meta: { title: 'demo-ueditor', isTab: true } },
    { path: '/product-attrupdate', component: _import('modules/product/attrupdate'), name: 'attr-update', meta: { title: '规格维护', isTab: true } }
    ],
    beforeEnter(to, from, next) {
    let token = Vue.cookie.get('token')
    if (!token || !/\S/.test(token)) {
    clearLoginInfo()
    next({ name: 'login' })
    }
    next()
    }
    }

    const router = new Router({
    mode: 'hash',
    scrollBehavior: () => ({ y: 0 }),
    isAddDynamicMenuRoutes: false, // 是否已经添加动态(菜单)路由
    routes: globalRoutes.concat(mainRoutes)
    })

    router.beforeEach((to, from, next) => {
    // 添加动态(菜单)路由
    // 1. 已经添加 or 全局路由, 直接访问
    // 2. 获取菜单列表, 添加并保存本地存储
    if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {
    next()
    } else {
    http({
    url: http.adornUrl('/sys/menu/nav'),
    method: 'get',
    params: http.adornParams()
    }).then(({ data }) => {
    if (data && data.code === 0) {
    fnAddDynamicMenuRoutes(data.menuList)
    router.options.isAddDynamicMenuRoutes = true
    sessionStorage.setItem('menuList', JSON.stringify(data.menuList || '[]'))
    sessionStorage.setItem('permissions', JSON.stringify(data.permissions || '[]'))
    next({ ...to, replace: true })
    } else {
    sessionStorage.setItem('menuList', '[]')
    sessionStorage.setItem('permissions', '[]')
    next()
    }
    }).catch((e) => {
    console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue')
    router.push({ name: 'login' })
    })
    }
    })

    /**
    * 判断当前路由类型, global: 全局路由, main: 主入口路由
    * @param {*} route 当前路由
    */
    function fnCurrentRouteType(route, globalRoutes = []) {
    var temp = []
    for (var i = 0; i < globalRoutes.length; i++) {
    if (route.path === globalRoutes[i].path) {
    return 'global'
    } else if (globalRoutes[i].children && globalRoutes[i].children.length >= 1) {
    temp = temp.concat(globalRoutes[i].children)
    }
    }
    return temp.length >= 1 ? fnCurrentRouteType(route, temp) : 'main'
    }

    /**
    * 添加动态(菜单)路由
    * @param {*} menuList 菜单列表
    * @param {*} routes 递归创建的动态(菜单)路由
    */
    function fnAddDynamicMenuRoutes(menuList = [], routes = []) {
    var temp = []
    for (var i = 0; i < menuList.length; i++) {
    if (menuList[i].list && menuList[i].list.length >= 1) {
    temp = temp.concat(menuList[i].list)
    } else if (menuList[i].url && /\S/.test(menuList[i].url)) {
    menuList[i].url = menuList[i].url.replace(/^\//, '')
    var route = {
    path: menuList[i].url.replace('/', '-'),
    component: null,
    name: menuList[i].url.replace('/', '-'),
    meta: {
    menuId: menuList[i].menuId,
    title: menuList[i].name,
    isDynamic: true,
    isTab: true,
    iframeUrl: ''
    }
    }
    // url以http[s]://开头, 通过iframe展示
    if (isURL(menuList[i].url)) {
    route['path'] = `i-${menuList[i].menuId}`
    route['name'] = `i-${menuList[i].menuId}`
    route['meta']['iframeUrl'] = menuList[i].url
    } else {
    try {
    route['component'] = _import(`modules/${menuList[i].url}`) || null
    } catch (e) { }
    }
    routes.push(route)
    }
    }
    if (temp.length >= 1) {
    fnAddDynamicMenuRoutes(temp, routes)
    } else {
    mainRoutes.name = 'main-dynamic'
    mainRoutes.children = routes
    router.addRoutes([
    mainRoutes,
    { path: '*', redirect: { name: '404' } }
    ])
    sessionStorage.setItem('dynamicMenuRoutes', JSON.stringify(mainRoutes.children || '[]'))
    console.log('\n')
    console.log('%c!<-------------------- 动态(菜单)路由 s -------------------->', 'color:blue')
    console.log(mainRoutes.children)
    console.log('%c!<-------------------- 动态(菜单)路由 e -------------------->', 'color:blue')
    }
    }

    export default router
    + +

    1.4.2 发布商品

    根据商品的分类联动的查询商品的品牌

    +

    image-20230626101113941

    +

    后端关键部分的代码

    +
    /**
    * 查询商品分类关联的品牌信息
    *
    * @return 某一商品分类下关联的品牌信息组成的集合
    */
    @Override
    public List<BrandEntity> getBrandsByCatId(Long catId) {
    List<CategoryBrandRelationEntity> categoryBrandRelationEntityList = relationDao.selectList(
    new LambdaQueryWrapper<CategoryBrandRelationEntity>().eq(CategoryBrandRelationEntity::getCatelogId, catId));
    List<BrandEntity> brandEntityList = categoryBrandRelationEntityList.stream().map(item -> {
    Long brandId = item.getBrandId();
    return brandService.getById(brandId);
    }).collect(Collectors.toList());
    return brandEntityList;
    }
    + +

    根据JSON数据生成实体类

    +

    生成工具: https://www.bejson.com/

    +

    image-20230626142854091

    +

    前端提交的发布商品的详细信息

    +
    {
    "spuName": "华为Mate30 Pro",
    "spuDescription": "华为手机",
    "catalogId": 225,
    "brandId": 1,
    "weight": 0.198,
    "publishStatus": 0,
    "decript": ["https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7b9f237d-9d37-4087-83bd-13d0ea76eda4_73366cc235d68202.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5c4fbe5-0685-4a12-8623-a4972c623d6c_528211b97272d88a.jpg"],
    "images": ["https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7627fc94-1ac7-4406-8e1c-3866a002918b_0d40c24b264aa511.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/788f45d7-d399-4482-88db-4dd939a51f4a_2b1837c6c50add30.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/b18dd3f9-3a38-4e9a-86a0-8c24db552a09_3c24f9cd69534030.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/d19426e9-c518-4fc8-85dd-7fae758a4000_23d9fbb256ea5d4a.jpg", "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7200dec5-af25-4264-87dc-19985c4cbe23_335b2c690e43a8f8.jpg"],
    "bounds": {
    "buyBounds": 500,
    "growBounds": 500
    },
    "baseAttrs": [{
    "attrId": 3,
    "attrValues": "2019",
    "showDesc": 1
    }, {
    "attrId": 4,
    "attrValues": "mate30pro",
    "showDesc": 1
    }, {
    "attrId": 10,
    "attrValues": "45W",
    "showDesc": 1
    }, {
    "attrId": 11,
    "attrValues": "LCD;OLED",
    "showDesc": 1
    }, {
    "attrId": 8,
    "attrValues": "16GB",
    "showDesc": 1
    }],
    "skus": [{
    "attr": [{
    "attrId": 7,
    "attrName": "手机颜色",
    "attrValue": "星河银"
    }, {
    "attrId": 9,
    "attrName": "商品产地",
    "attrValue": "中国"
    }],
    "skuName": "华为Mate30 Pro 星河银 中国",
    "price": "5799",
    "skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
    "skuSubtitle": "星河银的副标题",
    "images": [{
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7627fc94-1ac7-4406-8e1c-3866a002918b_0d40c24b264aa511.jpg",
    "defaultImg": 1
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/788f45d7-d399-4482-88db-4dd939a51f4a_2b1837c6c50add30.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/b18dd3f9-3a38-4e9a-86a0-8c24db552a09_3c24f9cd69534030.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }],
    "descar": ["星河银", "中国"],
    "fullCount": 3,
    "discount": 0.98,
    "countStatus": 0,
    "fullPrice": 10000,
    "reducePrice": 50,
    "priceStatus": 0,
    "memberPrice": [{
    "id": 2,
    "name": "铜牌会员",
    "price": 5788
    }, {
    "id": 3,
    "name": "银牌会员",
    "price": 5699
    }]
    }, {
    "attr": [{
    "attrId": 7,
    "attrName": "手机颜色",
    "attrValue": "亮黑色"
    }, {
    "attrId": 9,
    "attrName": "商品产地",
    "attrValue": "中国"
    }],
    "skuName": "华为Mate30 Pro 亮黑色 中国",
    "price": "6299",
    "skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
    "skuSubtitle": "亮黑色的副标题",
    "images": [{
    "imgUrl": "",
    "defaultImg": 1
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/7200dec5-af25-4264-87dc-19985c4cbe23_335b2c690e43a8f8.jpg",
    "defaultImg": 0
    }],
    "descar": ["亮黑色", "中国"],
    "fullCount": 0,
    "discount": 0,
    "countStatus": 0,
    "fullPrice": 0,
    "reducePrice": 0,
    "priceStatus": 0,
    "memberPrice": [{
    "id": 2,
    "name": "铜牌会员",
    "price": 0
    }, {
    "id": 3,
    "name": "银牌会员",
    "price": 0
    }]
    }, {
    "attr": [{
    "attrId": 7,
    "attrName": "手机颜色",
    "attrValue": "翡冷翠"
    }, {
    "attrId": 9,
    "attrName": "商品产地",
    "attrValue": "中国"
    }],
    "skuName": "华为Mate30 Pro 翡冷翠 中国",
    "price": "6299",
    "skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
    "skuSubtitle": "翡冷翠的副标题",
    "images": [{
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/d19426e9-c518-4fc8-85dd-7fae758a4000_23d9fbb256ea5d4a.jpg",
    "defaultImg": 1
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }],
    "descar": ["翡冷翠", "中国"],
    "fullCount": 0,
    "discount": 0,
    "countStatus": 0,
    "fullPrice": 0,
    "reducePrice": 0,
    "priceStatus": 0,
    "memberPrice": [{
    "id": 2,
    "name": "铜牌会员",
    "price": 0
    }, {
    "id": 3,
    "name": "银牌会员",
    "price": 0
    }]
    }, {
    "attr": [{
    "attrId": 7,
    "attrName": "手机颜色",
    "attrValue": "罗兰紫"
    }, {
    "attrId": 9,
    "attrName": "商品产地",
    "attrValue": "中国"
    }],
    "skuName": "华为Mate30 Pro 罗兰紫 中国",
    "price": "5799",
    "skuTitle": "华为(HUAWEI) 华为Mate30 Pro全网通智能手机 (5G/4G)",
    "skuSubtitle": "罗兰紫的副标题",
    "images": [{
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/e5364a2e-9f46-433b-827b-33c1ff8c991c_919c850652e98031.jpg",
    "defaultImg": 0
    }, {
    "imgUrl": "https://gulimall-0611.oss-cn-beijing.aliyuncs.com/2023/06/26/997dd5dd-be60-4547-871c-23b1273df596_1f15cdbcf9e1273c.jpg",
    "defaultImg": 1
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }, {
    "imgUrl": "",
    "defaultImg": 0
    }],
    "descar": ["罗兰紫", "中国"],
    "fullCount": 0,
    "discount": 0,
    "countStatus": 0,
    "fullPrice": 0,
    "reducePrice": 0,
    "priceStatus": 0,
    "memberPrice": [{
    "id": 2,
    "name": "铜牌会员",
    "price": 0
    }, {
    "id": 3,
    "name": "银牌会员",
    "price": 0
    }]
    }]
    }
    + +

    下载生成的实体类代码

    +

    image-20230626143051042

    +

    生成的实体类(将生成的实体类复制到项目中指定的位置)

    +

    image-20230626143228035

    +

    发布商品信息(核心业务)

    +

    后端关键代码

    +
    /**
    * 发布商品的功能
    *
    * @param vo 需要保存的商品信息封装的数据
    */
    @Transactional
    @Override
    public void saveSpuInfo(SpuSaveVo vo) {
    //保存spu的基本信息 pms_spu_info
    SpuInfoEntity infoEntity = new SpuInfoEntity();
    BeanUtils.copyProperties(vo, infoEntity);
    infoEntity.setCreateTime(new Date());
    infoEntity.setUpdateTime(new Date());
    this.saveBaseSpuInfo(infoEntity);
    //保存spu的描述图片 pms_spu_info_desc
    List<String> decript = vo.getDecript();
    SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
    descEntity.setSpuId(infoEntity.getId());
    descEntity.setDecript(String.join(",", decript));
    spuInfoDescService.saveSpuInfoDecript(descEntity);
    //保存spu的图片集 pms_spu_images
    List<String> images = vo.getImages();
    spuImagesService.saveImages(infoEntity.getId(), images);
    //保存spu的规格参数 pms_product_attr_value
    List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
    List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
    ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
    valueEntity.setAttrId(attr.getAttrId());
    AttrEntity attrEntity = attrService.getById(attr.getAttrId());
    valueEntity.setAttrName(attrEntity.getAttrName());
    valueEntity.setAttrValue(attr.getAttrValues());
    valueEntity.setQuickShow(attr.getShowDesc());
    valueEntity.setSpuId(infoEntity.getId());
    return valueEntity;
    }).collect(Collectors.toList());
    productAttrValueService.saveProductAttr(collect);
    //保存spu的积分信息
    Bounds bounds = vo.getBounds();
    SpuBoundTo spuBoundTo = new SpuBoundTo();
    spuBoundTo.setSpuId(infoEntity.getId());
    BeanUtils.copyProperties(bounds, spuBoundTo);
    R r = couponFeignService.saveSpuBounds(spuBoundTo);
    if (r.getCode() != 0) {
    log.error("远程保存spu的积分信息失败");
    }
    //保存当前spu对应的sku信息
    List<Skus> skus = vo.getSkus();
    if (skus != null && skus.size() != 0) {
    skus.forEach(item -> {
    String defaultImg = "";
    for (Images img : item.getImages()) {
    if (img.getDefaultImg() == 1) {
    defaultImg = img.getImgUrl();
    }
    }
    SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
    BeanUtils.copyProperties(item, skuInfoEntity);
    skuInfoEntity.setBrandId(infoEntity.getBrandId());
    skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
    skuInfoEntity.setSaleCount(0L);
    skuInfoEntity.setSpuId(infoEntity.getId());
    skuInfoEntity.setSkuDefaultImg(defaultImg);
    // 保存sku的基本信息pms_sku_info
    skuInfoService.saveSkuInfo(skuInfoEntity);
    Long skuId = skuInfoEntity.getSkuId();
    List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
    skuImagesEntity.setSkuId(skuId);
    skuImagesEntity.setImgUrl(img.getImgUrl());
    skuImagesEntity.setDefaultImg(img.getDefaultImg());
    return skuImagesEntity;
    }).filter(entity -> {
    //返回true就添加上去
    return !StringUtils.isEmpty(entity.getImgUrl());
    }).collect(Collectors.toList());
    // 保存sku的图片信息pms_sku_images
    skuImagesService.saveBatch(imagesEntities);
    // sku的销售属性信息pms_sku_sale_attr_value
    List<Attr> attrs = item.getAttr();
    List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attrs.stream().map(attr -> {
    SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
    BeanUtils.copyProperties(attr, skuSaleAttrValueEntity);
    skuSaleAttrValueEntity.setSkuId(skuId);
    return skuSaleAttrValueEntity;
    }).collect(Collectors.toList());
    skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);
    // sku的优惠信息、满减等信息gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
    SkuReductionTo skuReductionTo = new SkuReductionTo();
    BeanUtils.copyProperties(item, skuReductionTo);
    List<MemberPrice> memberPrices = item.getMemberPrice().stream().map(memberPrice -> {
    MemberPrice memberPriceTo = new MemberPrice();
    BeanUtils.copyProperties(memberPrice, memberPriceTo);
    return memberPriceTo;
    }).collect(Collectors.toList());
    skuReductionTo.setMemberPrice(memberPrices);
    skuReductionTo.setSkuId(skuId);
    if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1) {
    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
    if (r1.getCode() != 0) {
    log.error("远程调用sku的优惠信息、满减信息失败");
    }
    }
    });
    }
    }
    + +

    最终的实现效果

    +

    image-20230626184303186

    +

    image-20230626182832804

    +

    1.4.3 商品管理

    Sku检索

    +
    /**
    * 检索条件
    * {
    * page: 1,//当前页码
    * limit: 10,//每页记录数
    * sidx: 'id',//排序字段
    * order: 'asc/desc',//排序方式
    * key: '华为',//检索关键字
    * catelogId: 0,
    * brandId: 0,
    * min: 0,
    * max: 0
    * }
    */
    @Override
    public PageUtils queryPageByCondition(Map<String, Object> params) {
    LambdaQueryWrapper<SkuInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
    //Preparing: SELECT sku_id,sku_name,sku_title,catalog_id,sale_count,price,brand_id,sku_default_img,sku_subtitle,sku_desc,spu_id
    // FROM pms_sku_info
    // WHERE (( (sku_id = ? OR sku_name LIKE ?) ) AND catalog_id = ? AND brand_id = ? AND price >= ? AND price <= ?) LIMIT ?,?
    //检索关键字
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.and((wrapper) -> {
    wrapper.eq(SkuInfoEntity::getSkuId, key).or().like(SkuInfoEntity::getSkuName, key);
    });
    }
    //分类
    String catelogId = (String) params.get("catelogId");
    if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) {
    queryWrapper.eq(SkuInfoEntity::getCatalogId, catelogId);
    }
    //品牌
    String brandId = (String) params.get("brandId");
    if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {
    queryWrapper.eq(SkuInfoEntity::getBrandId, brandId);
    }
    //价格区间
    String min = (String) params.get("min");
    if (!StringUtils.isEmpty(min)) {
    queryWrapper.ge(SkuInfoEntity::getPrice, min);
    }
    String max = (String) params.get("max");
    if (!StringUtils.isEmpty(max)) {
    try {
    BigDecimal bigDecimal = new BigDecimal(max);
    if(bigDecimal.compareTo(new BigDecimal("0"))==1){//说明大于0
    queryWrapper.le(SkuInfoEntity::getPrice, max);
    }
    } catch (Exception e) {
    log.error("com/atguigu/gulimall/product/service/impl/SkuInfoServiceImpl");
    e.printStackTrace();
    throw new RuntimeException(e);
    }
    }
    IPage<SkuInfoEntity> page = this.page(
    new Query<SkuInfoEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的效果

    +

    image-20230627112218894

    +

    2.库存系统

    2.1 仓库维护

    仓库的分页加条件查询

    +
    @Override
    public PageUtils queryPage(Map<String, Object> params) {

    //分页查询的条件
    LambdaQueryWrapper<WareInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.like(WareInfoEntity::getName, key).or()
    .eq(WareInfoEntity::getId, key)
    .or().like(WareInfoEntity::getAddress, key)
    .or().like(WareInfoEntity::getAreacode, key);
    }
    IPage<WareInfoEntity> page = this.page(
    new Query<WareInfoEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的效果

    +

    image-20230627153518692

    +

    2.2 库存工作单

    2.3 商品库存

    商品库存的条件查询

    +
    /**
    * {
    * page: 1,//当前页码
    * limit: 10,//每页记录数
    * sidx: 'id',//排序字段
    * order: 'asc/desc',//排序方式
    * wareId: 123,//仓库id
    * skuId: 123//商品id
    * }
    */
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
    LambdaQueryWrapper<WareSkuEntity> queryWrapper = new LambdaQueryWrapper<>();
    String wareId = (String) params.get("wareId");
    if (!StringUtils.isEmpty(wareId)) {
    queryWrapper.eq(WareSkuEntity::getWareId, wareId);
    }
    String skuId = (String) params.get("skuId");
    if (!StringUtils.isEmpty(skuId)) {
    queryWrapper.eq(WareSkuEntity::getSkuId, skuId);
    }
    IPage<WareSkuEntity> page = this.page(
    new Query<WareSkuEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的效果

    +

    image-20230627160605717

    +

    2.4 采购单维护

    2.4.1 采购需求

    采购需求的分页加条件查询

    +
    /**
    * status: 0,//状态
    * wareId: 1,//仓库id
    */
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
    LambdaQueryWrapper<PurchaseDetailEntity> queryWrapper = new LambdaQueryWrapper<>();
    String status = (String) params.get("status");
    if (!StringUtils.isEmpty(status)) {
    queryWrapper.eq(PurchaseDetailEntity::getStatus, status);
    }
    String wareId = (String) params.get("wareId");
    if (!StringUtils.isEmpty(wareId)) {
    queryWrapper.eq(PurchaseDetailEntity::getWareId, wareId);
    }
    String key = (String) params.get("key");
    if (!StringUtils.isEmpty(key)) {
    queryWrapper.and((wrapper->{
    wrapper.eq(PurchaseDetailEntity::getPurchaseId, key).or().like(PurchaseDetailEntity::getSkuId,key);
    }));
    }
    IPage<PurchaseDetailEntity> page = this.page(
    new Query<PurchaseDetailEntity>().getPage(params),
    queryWrapper
    );
    return new PageUtils(page);
    }
    + +

    实现的效果

    +

    image-20230627161955351

    +

    合并采购单

    +

    业务需求介绍

    +

    image-20230627162707377

    +

    获取没有领取的采购单,将采购需求合并到这个采购单

    +
    /**
    * 获取没有领取的采购单
    */
    @Override
    public PageUtils queryPageUnreceivePurchase(Map<String, Object> params) {
    IPage<PurchaseEntity> page = this.page(
    new Query<PurchaseEntity>().getPage(params),
    new LambdaQueryWrapper<PurchaseEntity>().eq(PurchaseEntity::getStatus,0).or().eq(PurchaseEntity::getStatus,1)
    );

    return new PageUtils(page);
    }
    + +

    合并采购需求

    +
    /**
    * 合并采购需求
    *
    * @param mergeVo 前端传递的需要合并的采购需求
    */
    @Transactional
    @Override
    public void mergePurchase(MergeVo mergeVo) {
    //判断采购单是不是存在
    Long purchaseId = mergeVo.getPurchaseId();
    if (purchaseId == null) {//说明没有选择需要合并的采购单,需要先创建一个采购单
    PurchaseEntity purchaseEntity = new PurchaseEntity();
    purchaseEntity.setStatus(WareConstant.PurchaseStatesEnum.CREATED.getCode());
    purchaseEntity.setCreateTime(new Date());
    purchaseEntity.setUpdateTime(new Date());
    this.save(purchaseEntity);
    purchaseId = purchaseEntity.getId();
    }
    List<Long> items = mergeVo.getItems();
    Long finalPurchaseId = purchaseId;
    List<PurchaseDetailEntity> detailEntityList = items.stream().map(item -> {
    PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
    detailEntity.setId(item);
    detailEntity.setPurchaseId(finalPurchaseId);
    detailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.ASSIGNED.getCode());
    return detailEntity;
    }).collect(Collectors.toList());
    detailService.updateBatchById(detailEntityList);
    //更新一下时间
    PurchaseEntity purchaseEntity = new PurchaseEntity();
    purchaseEntity.setId(purchaseId);
    purchaseEntity.setUpdateTime(new Date());
    this.updateById(purchaseEntity);
    }

    + +

    2.4.2 采购单

    领取采购单

    +
    /**
    * @param ids 采购单id
    */
    @Override
    public void received(List<Long> ids) {
    //确认当前的采购单是新建或者分配状态
    List<PurchaseEntity> purchaseEntities = ids.stream().map(id -> {
    PurchaseEntity byId = this.getById(id);
    return byId;
    }).filter(item -> {
    if (item.getStatus() == WareConstant.PurchaseStatesEnum.CREATED.getCode() ||
    item.getStatus() == WareConstant.PurchaseDeatilStatesEnum.ASSIGNED.getCode()) {
    return true;
    }
    return false;
    }).map(item->{
    //改变采购单状态
    item.setStatus(WareConstant.PurchaseStatesEnum.RECEIVE.getCode());
    item.setUpdateTime(new Date());
    return item;
    }).collect(Collectors.toList());
    this.updateBatchById(purchaseEntities);
    //改变采购项状态
    purchaseEntities.forEach(item->{
    List<PurchaseDetailEntity> entities = detailService.listDetailByPurchaseId(item.getId());
    List<PurchaseDetailEntity> detailEntityList = entities.stream().map(entity -> {
    PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();
    purchaseDetailEntity.setId(entity.getId());
    purchaseDetailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.BUYING.getCode());
    return purchaseDetailEntity;
    }).collect(Collectors.toList());
    detailService.updateBatchById(detailEntityList);
    });
    }
    + +

    完成采购的功能

    +
    @Transactional
    @Override
    public void done(PurchaseDoneVo doneVo) {
    Long id = doneVo.getId();
    //改变采购项的状态
    Boolean flag = true;
    List<PurchaseItemDoneVo> items = doneVo.getItems();
    List<PurchaseDetailEntity> purchaseItemDoneVos = new ArrayList<>();
    for (PurchaseItemDoneVo item : items) {
    PurchaseDetailEntity detailEntity = new PurchaseDetailEntity();
    if (item.getStatus() == WareConstant.PurchaseDeatilStatesEnum.HASERROR.getCode()) {
    flag = false;
    detailEntity.setStatus(item.getStatus());
    } else {
    detailEntity.setStatus(WareConstant.PurchaseDeatilStatesEnum.FINISH.getCode());
    //将成功采购的进行入库
    PurchaseDetailEntity purchaseDetailEntity = detailService.getById(item.getItemId());
    wareSkuService.addStock(purchaseDetailEntity.getSkuId(),purchaseDetailEntity.getWareId(),purchaseDetailEntity.getSkuNum());
    }
    detailEntity.setId(item.getItemId());
    purchaseItemDoneVos.add(detailEntity);
    }
    detailService.updateBatchById(purchaseItemDoneVos);
    //改变采购单的状态
    PurchaseEntity purchaseEntity = new PurchaseEntity();
    purchaseEntity.setId(id);
    purchaseEntity.setStatus(flag ? WareConstant.PurchaseStatesEnum.FINISH.getCode() : WareConstant.PurchaseStatesEnum.HASERROR.getCode());
    purchaseEntity.setUpdateTime(new Date());
    this.updateById(purchaseEntity);
    }
    + +

    修改库存的功能

    +
    @Transactional
    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
    //判断是添加的操作还是修改的操作
    List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new LambdaQueryWrapper<WareSkuEntity>().eq(WareSkuEntity::getSkuId, skuId).eq(WareSkuEntity::getWareId, wareId));
    if (wareSkuEntities == null || wareSkuEntities.size() == 0) {
    WareSkuEntity wareSkuEntity = new WareSkuEntity();
    wareSkuEntity.setSkuId(skuId);
    wareSkuEntity.setWareId(wareId);
    wareSkuEntity.setStock(skuNum);
    wareSkuEntity.setStockLocked(0);
    //远程查询sku的名字
    try {
    R r = productFeignService.info(skuId);
    if(r.getCode() == 0){
    Map<String,Object> skuInfo = (Map<String, Object>) r.get("skuInfo");
    wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
    }
    } catch (Exception e) {
    log.warn("远程获取商品的Sku信息失败!");
    }
    wareSkuDao.insert(wareSkuEntity);
    } else {
    wareSkuDao.addStock(skuId, wareId, skuNum);
    }
    }
    + +

    sql语句

    +
     <!--void addStock(@Param("skuId") Long skuId, @Param("wareId") Long wareId, @Param("skuNum") Integer skuNum);-->
    <insert id="addStock">
    update `wms_ware_sku`
    set stock = stock + #{skuNum}
    where sku_id = #{skuId}
    and ware_id = #{wareId}
    </insert>
    + +

    3.基础篇总结

    image-20230628170218776 + +

    个人总结:

    +
      +
    • 细节很重要,编写代码的时候要注意细节问题(比如网关的配置,模糊路径和精确路径先后配置的问题)
    • +
    • 要有良好的编码习惯,注意代码解耦和代码复用
    • +
    • 业务逻辑很重要,编码之前要搞懂了业务逻辑之后再开始写代码
    • +
    • 学习技术的时候要融会贯通,学习编程的思想
    • +
    • 学技术一定要学会看开发文档(技术说明书),可以先从开发文档提供的demo开始
    • +
    +

    六.高级篇程序设计

    1.ElasticSearch

    详细的教程

    +

    ElasticSearch | The Blog (gitee.io)

    +

    2.商城业务

    2.1 商品上架

    1.商品Mapping

    +

    创建一个product索引和指定映射关系

    +
    PUT product
    {
    "mappings": {
    "properties": {
    "skuId": {
    "type": "long"
    },
    "spuId": {
    "type": "keyword"
    },
    "skuTitle": {
    "type": "text",
    "analyzer": "ik_smart"
    },
    "skuPrice": {
    "type": "keyword"
    },
    "skuImg": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "saleCount": {
    "type": "long"
    },
    "hasStock": {
    "type": "boolean"
    },
    "hotScore": {
    "type": "long"
    },
    "brandId": {
    "type": "long"
    },
    "catalogId": {
    "type": "long"
    },
    "brandName": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "brandImg": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "catalogName": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "attrs": {
    "type": "nested",
    "properties": {
    "attrId": {
    "type": "long"
    },
    "attrName": {
    "type": "keyword",
    "index": false,
    "doc_values": false
    },
    "attrValue": {
    "type": "keyword"
    }
    }
    }
    }
    }
    }
    + +

    2.上架功能后端代码

    +

    所需的实体类

    +
    package com.atguigu.common.to.es;

    import lombok.Data;

    import java.math.BigDecimal;
    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @Date 2023/7/5
    * @Description 对应ES中保存的文档的实体类
    */
    @Data
    public class SkuEsModel {
    private Long skuId;

    private Long spuId;

    private String skuTitle;

    private BigDecimal skuPrice;

    private String skuImg;

    private Long saleCount;

    private Boolean hasStock;

    private Long hotScore;

    private Long brandId;

    private Long catalogId;

    private String brandName;

    private String brandImg;

    private String catalogName;

    private List<Attrs> attrs;

    @Data
    public static class Attrs {

    private Long attrId;

    private String attrName;

    private String attrValue;

    }

    }
    + +

    商品上架的后端关键代码

    +
    /**
    * 商品上架的功能
    */
    @Override
    public void up(Long spuId) {
    //查询当前spuId对应的所有sku信息
    List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);
    List<Long> skuIds = skus.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
    //规格属性信息的封装(可以被检索的规格属性信息)
    List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListForSpu(spuId);
    List<Long> attrIds = baseAttrs.stream().map(attr -> {
    return attr.getAttrId();
    }).collect(Collectors.toList());
    //获取可以检索的属性id的集合
    List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);
    //将其转换成set集合
    Set<Long> idSet = new HashSet<>(searchAttrIds);
    //最终需要封装的attrs
    List<SkuEsModel.Attrs> attrs = baseAttrs.stream().filter(item -> {
    return idSet.contains(item.getAttrId());
    }).map(item -> {
    SkuEsModel.Attrs skuAttr = new SkuEsModel.Attrs();
    BeanUtils.copyProperties(item, skuAttr);
    return skuAttr;
    }).collect(Collectors.toList());
    //库存
    Map<Long, Boolean> stockMap = null;
    try {
    R r = wareFeignService.getSkusHasStock(skuIds);
    List<SkuHasStockVo> skuHasStockVos = r.getData(new TypeReference<List<SkuHasStockVo>>(){});
    stockMap = skuHasStockVos.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
    } catch (Exception e) {
    log.error("库存服务查询异常:原因{}",e);
    }
    //封装sku的信息
    Map<Long, Boolean> finalStockMap = stockMap;
    List<SkuEsModel> upProducts = skus.stream().map(sku -> {
    //组装需要的数据
    SkuEsModel skuEsModel = new SkuEsModel();
    BeanUtils.copyProperties(sku, skuEsModel);
    //价格 图片
    skuEsModel.setSkuPrice(sku.getPrice());
    skuEsModel.setSkuImg(sku.getSkuDefaultImg());
    //设置库存信息
    if(finalStockMap == null){
    skuEsModel.setHasStock(true);
    }else {
    skuEsModel.setHasStock(finalStockMap.get(sku.getSkuId()));
    }
    //热度评分信息
    skuEsModel.setHotScore(0L);
    //品牌名 分类名 品牌价格
    BrandEntity brand = brandService.getById(sku.getBrandId());
    skuEsModel.setBrandName(brand.getName());
    skuEsModel.setBrandImg(brand.getLogo());
    CategoryEntity category = categoryService.getById(sku.getCatalogId());
    skuEsModel.setCatalogName(category.getName());
    //设置attrs
    skuEsModel.setAttrs(attrs);
    return skuEsModel;
    }).collect(Collectors.toList());
    R r = searchFeignService.productStatusUp(upProducts);
    if(r.getCode() == 0){
    //远程调用成功
    spuInfoDao.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
    }else {
    //远程调用失败
    //TODO 重复调用的问题
    /**
    * feign的调用流程
    * 1.构造请求的数据,将对象转化成json
    * 2.发送请求进行执行(执行成功之后会解码响应数据)
    * 3.执行请求会有重试机制
    */
    }
    }
    + +
    /**
    * 过滤出属性id中可以被检索的属性的id
    * @param attrIds 原属性id的集合
    * @return 可以被检索的属性id的集合
    */
    @Override
    public List<Long> selectSearchAttrs(List<Long> attrIds) {
    //select attr_id from `pms_attr` where attr_id in (?) and search_type=1
    return attrDao.selectSearchAttrs(attrIds);
    }
    + +
    <select id="selectSearchAttrs" resultType="java.lang.Long">
    select `attr_id`from `pms_attr` where `attr_id` in
    <foreach collection="attrIds" item="attr" separator="," open="(" close=")">
    #{attr}
    </foreach>
    and `search_type`=1
    </select>
    + +
    /**
    * 查询库存的方法
    */
    @Override
    public List<SkuHasStockVo> getSkusHasStock(List<Long> skuIds) {
    List<SkuHasStockVo> skuHasStockVos = skuIds.stream().map(skuId -> {
    SkuHasStockVo skuHasStockVo = new SkuHasStockVo();
    Long count = wareSkuDao.getSkuStock(skuId);
    skuHasStockVo.setSkuId(skuId);
    if(count != null){
    skuHasStockVo.setHasStock(count > 0);
    }else {
    skuHasStockVo.setHasStock(false);
    }
    return skuHasStockVo;
    }).collect(Collectors.toList());
    return skuHasStockVos;
    }
    + +
    /**
    * 存储商品数据
    */
    @Override
    public Boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
    //将数据保存到ES中
    //在ES中建立好索引
    BulkRequest bulkRequest = new BulkRequest();
    for (SkuEsModel skuEsModel : skuEsModels) {
    IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
    indexRequest.id(skuEsModel.getSkuId().toString());
    indexRequest.source(JSON.toJSONString(skuEsModel), XContentType.JSON);
    bulkRequest.add(indexRequest);
    }
    BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
    boolean hasFailure = bulk.hasFailures();
    List<String> ids = Arrays.stream(bulk.getItems()).map(item -> {
    return item.getId();
    }).collect(Collectors.toList());
    log.info("上架商品的id:{}",ids);
    return !hasFailure;
    }
    + +

    2.2 首页

    项目的页面视图部署在对应的微服务项目的静态资源中

    +

    image-20230706112521327

    +

    2.2.1整合thymeleaf

    添加thymeleaf的依赖

    +
    <!-- thymeleaf-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    + +

    将资料中的静态资源放在微服务工程的对应位置,并在配置文件中加入对应的配置

    +

    image-20230706114304695

    +

    首页页面

    +

    image-20230706145050684

    +

    在index.html上加上thymeleaf的名称空间

    +
    <html xmlns:th="http://www.thymeleaf.org">
    + +

    修改页面的时候需要频繁的重启项目,使用热更新来解决该问题

    +

    导入devtools的依赖

    +
    <!--devtools-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
    </dependency>
    + +

    重启项目之后会显示如下的效果

    +

    image-20230706154112026

    +

    这时我们在每次修改页面之后使用Ctrl+Shift+F9键可以热更新

    +

    image-20230706161401528

    +

    2.2.2 首页面三级分类显示

    首页面的跳转以及一级分类的显示

    +
    /**
    * 首页面的跳转
    */
    @GetMapping({"/","index.html"})
    public String indexPage(Model model){
    //查询所有的一级分类
    List<CategoryEntity> categorys = categoryService.getLevelOneCategorys();
    model.addAttribute("categorys",categorys);
    return "index";
    }
    + +

    用于用户端首页面三级分类封装的数据

    +
    package com.atguigu.gulimall.product.vo;

    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/7
    * @Description 用户端首页面三级分类封装的数据
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Catelog2Vo {

    /**
    * 一级父分类的id
    */
    private String catalog1Id;

    /**
    * 三级子分类
    */
    private List<Category3Vo> catalog3List;

    private String id;

    private String name;


    /**
    * 三级分类vo
    * 静态内部类
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Category3Vo {

    /**
    * 父分类、二级分类id
    */
    private String catalog2Id;

    private String id;

    private String name;
    }
    }
    + +

    前端相关的修改

    +
     <li th:each=" category:${categorys}">
    <a href="#" class="header_main_left_a" ctg-data="3" th:attr="ctg-data=${category.catId}"><b th:text="${category.name}"></b></a>
    </li>
    + +

    image-20230707151505310

    +

    controller层代码

    +
    /**
    * 首页面的商品分分类信息
    */
    @ResponseBody
    @GetMapping("/index/catalog.json")
    public Map<String, List<Catelog2Vo>> getCatalogJson(){
    return categoryService.getCatalogJson();
    }
    + +

    service层代码

    +
    @Override
    public Map<String, List<Catelog2Vo>> getCatalogJson() {
    //查出所有的一级分类
    List<CategoryEntity> levelOneCategorys = getLevelOneCategorys();
    //封装数据
    Map<String, List<Catelog2Vo>> catelog2VoMap = levelOneCategorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
    //查询以及分类下的二级分类
    List<CategoryEntity> categoryEntities = categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, v.getCatId()));
    List<Catelog2Vo> catelog2Vos = null;
    if (categoryEntities != null) {
    catelog2Vos = categoryEntities.stream().map(item -> {
    Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, item.getCatId().toString(), item.getName());
    //当前二级分类的三级分类
    List<CategoryEntity> levelThreeCatelog = categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, item.getCatId()));
    if(levelThreeCatelog != null){
    //封装成指定的数据
    List<Catelog2Vo.Category3Vo> category3VoList = levelThreeCatelog.stream().map(threeCatelog -> {
    Catelog2Vo.Category3Vo category3Vo = new Catelog2Vo.Category3Vo();
    category3Vo.setCatalog2Id(item.getCatId().toString());
    category3Vo.setName(threeCatelog.getName());
    category3Vo.setId(threeCatelog.getCatId().toString());
    return category3Vo;
    }).collect(Collectors.toList());
    catelog2Vo.setCatalog3List(category3VoList);
    }
    return catelog2Vo;
    }).collect(Collectors.toList());
    }
    return catelog2Vos;
    }));
    return catelog2VoMap;
    }
    + +

    页面实现的效果

    +

    image-20230707151543593

    +

    2.3 搭建域名访问环境

    1.安装SwitchHosts软件

    +

    可以快捷的编辑操作hosts文件

    +

    打开的时候以管理员的身份打开

    +

    image-20230707153628394

    +

    image-20230707154052952

    +

    在添加的方案中添加如下的内容,点击右下角的保存按钮保存(保存失败的更改hosts文件的权限,将只读勾选掉)

    +
    192.168.195.100 gulimall.com
    + +

    保存成功之后可以直接访问域名,解析到虚拟机的ip

    +

    Tips:有科学上网的测试的时候要先关闭科学上网的软件

    +

    安装了ES的访问gulimall.com:9200可以看到相应的内容

    +

    image-20230707160328001

    +

    2.正式搭建项目的域名访问环境

    +

    image-20230708092745456

    +

    nginx的配置文件

    +

    image-20230708093600923

    +

    配置nginx

    +
    #切换到nginx挂载在本机的配置文件所在的目录
    cd /mydata/nginx/conf
    #切换到nginx的分配置文件下复制一份 定义gulimall的配置,主配置文件中有include /etc/nginx/conf.d/*.conf;这么一段配置,主配置文件
    #会包含conf.d目录下的所有配置
    cd /mydata/nginx/conf/conf.d
    cp default.conf gulimall.conf 复制一份
    #在gulimall.conf配置文件中配置我们的配置
    #配置如下配置
    + +

    niginx的配置文件如下

    +
    server {
    listen 80;
    server_name gulimall.com;

    location / {
    #服务的IP 192.168.0.112
    #服务的端口: 10000
    proxy_pass http://192.168.0.112:10000;
    }
    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    }
    + +

    这时我们在访问gulimall.com的时候就可以访问一下的页面了

    +

    image-20230708095940126

    +

    3.让nginx代理到网关

    +

    在nginx.conf配置文件中加上如下的配置

    +
    #配置上游服务器
    #gulimall是上游服务器的组名
    upstream gulimall{
    #网关的地址
    server 192.168.0.112:88;
    }
    + +

    修改gulimall.conf中的配置

    +
    server {
    listen 80;
    server_name gulimall.com;

    location / {
    proxy_pass http://gulimall;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }
    }
    + +

    在网关的配置文件中加上一下的配置

    +
    # nginx代理相关的断言
    - id: gulimall_host_route
    uri: lb://gulimall-product
    predicates:
    - Host=**.gulimall.com,gulimall.com
    + +

    这时我们重启网关的时候发现无法访问到相应的页面 是由于nginx访问网关的时候丢了host

    +

    这时我们需要加上如下的配置

    +
    proxy_set_header Host $host;
    + +

    image-20230708104321024

    +image-20230708104848501 + +

    2.4 性能压测

    压力测试与性能监控 | The Blog (gitee.io)

    +

    JMeter压力测试

    +

    image-20230708152102009

    +

    性能监控

    +
    jconsole
    jvisualvm
    + +

    image-20230709155555491

    +

    2.4.1优化中间件对性能的影响

    网关的优化、数据库索引的优化、thymeleaf的优化、静态资源的优化、nginx动静分离

    +

    image-20230710111805705

    +

    2.5 缓存使用

    2.5.1 本地缓存与分布式缓存

    为了系统性能的提升,我们一般都会将部分数据放入缓存中,加速访问。而 db 承担数据落盘工作。
    哪些数据适合放入缓存?
    1.即时性、数据一致性要求不高的
    2.访问量大且更新频率不高的数据(读多,写少)

    +

    本地缓存

    +

    image-20230710143906286

    +

    分布式缓存

    +

    image-20230710143341531

    +

    2.5.2 整合redis

    1.安装Redis

    +

    image-20230710144052877

    +

    2.项目中使用Redis

    +

    SpringBoot整合Redis | The Blog (gitee.io)

    +

    2.1 引入依赖

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    + +

    2.2 添加配置

    +
    spring:
    redis:
    host: 192.168.195.100
    port: 6379
    + +

    2.3 测试使用

    +
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testRedis(){
    ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
    ops.set("hello","word_"+ UUID.randomUUID().toString());
    String hello = ops.get("hello");
    System.out.println(hello);
    }
    + +

    image-20230710145648233

    +

    lettuce堆外内存溢出bug

    +

    产生原因:

    +

    1)、springboot2.0以后默认使用 lettuce作为操作redis的客户端,它使用netty进行网络通信

    +

    2)、lettuce的bug导致netty堆外内存溢出。netty如果没有指定堆外内存,默认使用Xms的值,可以使用-Dio.netty.maxDirectMemory进行设置

    +

    解决方案:由于是lettuce的bug造成,不要直接使用-Dio.netty.maxDirectMemory去调大虚拟机堆外内存,治标不治本。

    +
      +
    • 1)、升级lettuce客户端
    • +
    • 2)、切换使用jedis
    • +
    +

    修改依赖文件,使用jedis作为客户端工具

    +
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
    <exclusion>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    </dependency>
    + +

    2.5.3 高并发下缓存失效问题

    缓存穿透

    +image-20230710153635804 + +

    缓存雪崩

    +image-20230710153651752 + +

    缓存击穿

    +image-20230710153709464 + +

    分布式下如何加锁?

    +image-20230710163735955 + +

    锁-时序问题

    +image-20230710165738655 + +

    分布式锁演进-基本原理

    +

    image-20230714163437984

    +

    分布式锁的最终形态

    +

    image-20230714171221296

    +

    2.5.4 分布式锁-Redisson

    github地址: https://github.com/redisson/redisson

    +

    1.引入依赖

    +
    <!--redisson-->
    <dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.12.0</version>
    </dependency>
    + +

    2.配置redisson

    +
    package com.atguigu.gulimall.product.config;

    import org.redisson.Redisson;
    import org.redisson.api.RedissonClient;
    import org.redisson.config.Config;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;

    import java.io.IOException;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/14
    * @Description Redisson的配置
    */
    @Configuration
    public class MyRedissonConfig {

    /**
    * 所有对redisson的操作通过RedissonClient
    */
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws IOException {
    //创建配置
    Config config = new Config();
    config.useSingleServer().setAddress("redis://192.168.195.100:6379");
    //根据配置config创建RedissonClient实例
    return Redisson.create(config);
    }
    }
    + +

    3.测试使用

    +
    @Autowired
    private RedissonClient redissonClient;

    @Test
    void testRedisson(){
    System.out.println(redissonClient);
    }
    + +

    image-20230714175205635

    +

    4.简单的使用分布式锁

    +

    上一个线程加锁之后没有解锁的话,后面的线程会一直等待前面的线程释放锁,自己再加锁。

    +
    @ResponseBody
    @GetMapping("/hello")
    public String hello() {
    //打印时间需要
    SimpleDateFormat sdf = new SimpleDateFormat();// 格式化时间
    sdf.applyPattern("yyyy-MM-dd HH:mm:ss");// a为am/pm的标记
    Date date = new Date();// 获取当前时间

    //获取同一把锁,只要锁的名字一样,就是同一把锁
    RLock lock = redissonClient.getLock("my-lock");
    //加锁
    lock.lock();//阻塞式等待,锁没有释放的情况下会一直等待下去
    //1.锁的自动续期,如果业务耗费的时间超长,运行时会自动给锁续上新的30s(默认时续期30s),不必担心业务时间长,锁自动过期被删掉
    //2.加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动s
    System.out.println("现在时间:" + sdf.format(date));
    try {
    //执行业务
    System.out.println("加锁成功,执行业务,线程Id:"+Thread.currentThread().getId());
    Thread.sleep(10000);//等待10秒
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    } finally {
    //解锁
    System.out.println("释放锁,线程Id:"+Thread.currentThread().getId());
    lock.unlock();
    }
    return "hello";
    }
    + +

    image-20230714181620263

    +

    my-lock对应的值会跟随线程的变化而变化

    +

    image-20230714182057982

    +

    读写锁

    +
    /**
    * 写锁没有释放的情况下,读锁必须等待写锁的释放,才能进行读的操作
    * 读写锁的测试
    * 写锁
    */
    @ResponseBody
    @GetMapping("/write")
    public String writeValue(){
    RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
    String s = "";
    RLock rLock = lock.writeLock();//写锁
    try {
    rLock.lock();
    s = UUID.randomUUID().toString();
    Thread.sleep(30000);
    //写的操作
    redisTemplate.opsForValue().set("writeValue",s);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally {
    rLock.unlock();
    }
    return s;
    }

    /**
    * 读写锁的测试
    * 读锁
    */
    @GetMapping("/read")
    @ResponseBody
    public String readValue(){
    RReadWriteLock lock = redissonClient.getReadWriteLock("rw-lock");
    String s = "";
    RLock rLock = lock.readLock();//读锁
    try {
    rLock.lock();
    //读的操作
    s = redisTemplate.opsForValue().get("writeValue");
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    rLock.unlock();
    }
    return s;
    }
    + +

    2.5.5 分布式缓存一致性问题

    双写模式

    +

    image-20230716100845389

    +

    失效模式

    +

    image-20230716100909465

    +

    缓存数据一致性-解决方案

    +

    image-20230716101939869

    +

    缓存数据一致性-解决-Canal

    +

    image-20230716102510738

    +

    2.5.6 缓存-SpringCache

    1、简介

    +
      +
    • Spring 从 3.1 开始定义了 org.springframework.cache.Cache和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术;并支持使用 JCache(JSR-107)注解简化我们开发;

      +
    • +
    • Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
      Cache 接 口 下 Spring 提 供 了 各 种 xxxCache 的 实 现 ; 如 RedisCache , EhCacheCache ,ConcurrentMapCache 等;

      +
    • +
    • 每次调用需要缓存功能的方法时,Spring 会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。

      +
    • +
    • 使用 Spring 缓存抽象时我们需要关注以下两点;

      +

      1、确定方法需要被缓存以及他们的缓存策略

      +

      2、从缓存中读取之前缓存存储的数据

      +
    • +
    +

    2.以前的笔记教程

    +

    SpringBoot整合Redis | The Blog (gitee.io)

    +

    3.项目中整合SpringCache简化开发

    +

    3.1.引入依赖

    +
    <!--我们使redis作为缓存的使用场景,在前面我们还要引入redis的依赖-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    + +

    3.2.编写配置文件

    +
    #使用缓存的类型
    spring.cache.type=redis
    + +

    3.3.测试使用缓存

    +

    常用的缓存注解

    +

    @Cacheable 触发将数据保存到缓存的操作

    +

    @CachePut 不影响方法执行更新缓存

    +

    @CacheEvict 触发将数据从缓存中删除的操作

    +

    @Caching 组合多个缓存操作

    +

    @CacheConfig 在类级别上共享缓存的相关配置

    +

    1.在启动类上添加缓存的注解

    +
    @EnableCaching //开启缓存功能
    + +

    2.测试使用

    +
    //每一个需要缓存的数据需要我们指定放到那个名字的缓存[缓存的分区(按照业务类型分)]
    @Cacheable({"category"}) //代表当前方法的结果需要缓存,如果缓存中有,方法就不需要调用了,如果缓存中没有,就会调用方法,将方法的结果放入到缓存
    @Override
    public List<CategoryEntity> getLevelOneCategorys() {
    return categoryDao.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getParentCid, 0));
    }
    + +

    image-20230716112105848

    +

    自定义数据的存储

    +

    设置自定义的key值

    +
    @Cacheable(value = {"category"},key = "'levelOneCategorys'")  //自定义key值
    + +

    tips: #root.method.name是SpEL表达式

    +
    @Cacheable(value = {"category"},key = "#root.method.name")   //使用方法名作为key值
    + +

    设置缓存数据的存活时间

    +
    #设置一个小时的存活时间
    spring.cache.redis.time-to-live=3600000
    + +

    image-20230716152821097

    +

    将数据保存为json的格式

    +

    添加配置类

    +
    package com.atguigu.gulimall.product.config;

    import org.springframework.boot.autoconfigure.cache.CacheProperties;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.StringRedisSerializer;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/16
    * @Description 缓存的序列化的配置
    */
    @EnableConfigurationProperties(CacheProperties.class)
    @Configuration
    public class MyCacheConfig {
    @Bean
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
    config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
    config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    //让配置文件中所有的配置生效(下面的不配置的话,缓存的时间会失效)
    CacheProperties.Redis redisProperties = cacheProperties.getRedis();
    if (redisProperties.getTimeToLive() != null) {
    config = config.entryTtl(redisProperties.getTimeToLive());
    }
    if (redisProperties.getKeyPrefix() != null) {
    config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
    }
    if (!redisProperties.isCacheNullValues()) {
    config = config.disableCachingNullValues();
    }
    if (!redisProperties.isUseKeyPrefix()) {
    config = config.disableKeyPrefix();
    }
    return config;
    }
    }
    + +

    效果

    +

    image-20230716160027391

    +

    其它的常用配置

    +
    #缓存的前缀,如果指定了前缀就使用指定的前缀,没有指定前缀就使用缓存的名字作为前缀
    spring.cache.redis.key-prefix=CACHE_
    #是否使用缓存的前缀
    spring.cache.redis.use-key-prefix=true
    #是否缓存空值(缓存穿透的问题的解决)
    spring.cache.redis.cache-null-values=true
    + +

    2.6 检索服务

    2.6.1 整合页面

    image-20230716173751202

    +

    2.6.2 配置域名访问

    image-20230716173453604

    +

    Nginx的配置

    +
    server {
    listen 80;
    server_name *.gulimall.com;

    location / {
    proxy_set_header Host $host;
    proxy_pass http://gulimall;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx/html;
    }

    }
    + +

    网关中配置断言

    +
    #搜索
    - id: gulimall_search_route
    uri: lb://gulimall-search
    predicates:
    - Host=search.gulimall.com
    + +

    image-20230716173628024

    +

    2.6.3 首页和搜索页相互跳转

    首页无法跳转到搜索页的原因,gumall改为gulimall即可

    +

    image-20230716181052580

    +

    搜索框跳转到搜索页,修改页面中的代码

    +

    image-20230717094656565

    +

    2.6.4 商品的检索

    image-20230717095740594

    +

    检索条件的VO

    +
    package com.atguigu.gulimall.search.vo;

    import lombok.Data;

    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/17
    * @Description 检索条件的vo
    */
    @Data
    public class SearchParam {
    /**
    * 页面传递过来的全文匹配关键字
    */
    private String keyword;

    /**
    * 品牌id,可以多选
    */
    private List<Long> brandId;

    /**
    * 三级分类id
    */
    private Long catalog3Id;

    /**
    * 排序条件:sort=price/salecount/hotscore_desc/asc
    */
    private String sort;

    /**
    * 是否显示有货
    */
    private Integer hasStock;

    /**
    * 价格区间查询
    */
    private String skuPrice;

    /**
    * 按照属性进行筛选
    */
    private List<String> attrs;

    /**
    * 页码
    */
    private Integer pageNum = 1;

    /**
    * 原生的所有查询条件
    */
    private String _queryString;
    }
    + +

    检索的结果

    +
    package com.atguigu.gulimall.search.vo;

    import com.atguigu.common.to.es.SkuEsModel;
    import lombok.Data;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @author Jason Gong
    * @version 1.0
    * @website https://jasonsgong.gitee.io
    * @Date 2023/7/17
    * @Description 根据检索条件返回给页面的商品信息
    */
    @Data
    public class SearchResult {


    /**
    * 查询到的所有商品信息
    */
    private List<SkuEsModel> product;


    /**
    * 当前页码
    */
    private Integer pageNum;

    /**
    * 总记录数
    */
    private Long total;

    /**
    * 总页码
    */
    private Integer totalPages;

    private List<Integer> pageNavs;

    /**
    * 当前查询到的结果,所有涉及到的品牌
    */
    private List<BrandVo> brands;

    /**
    * 当前查询到的结果,所有涉及到的所有属性
    */
    private List<AttrVo> attrs;

    /**
    * 当前查询到的结果,所有涉及到的所有分类
    */
    private List<CatalogVo> catalogs;

    private List<NavVo> navs;

    @Data
    public static class NavVo {
    private String navName;
    private String navValue;
    private String link;
    }


    @Data
    public static class BrandVo {

    private Long brandId;

    private String brandName;

    private String brandImg;
    }


    @Data
    public static class AttrVo {

    private Long attrId;

    private String attrName;

    private List<String> attrValue;
    }


    @Data
    public static class CatalogVo {

    private Long catalogId;

    private String catalogName;
    }
    }
    + +

    未完待续(学习到177)……

    +]]>
    + + 后端 + + + 项目实战 + +
    + diff --git a/sitemap.txt b/sitemap.txt new file mode 100644 index 000000000..00d6f65fd --- /dev/null +++ b/sitemap.txt @@ -0,0 +1,118 @@ +https://jasonsgong.gitee.io/posts/32696.html +https://jasonsgong.gitee.io/posts/24183.html +https://jasonsgong.gitee.io/posts/8957.html +https://jasonsgong.gitee.io/posts/29985.html +https://jasonsgong.gitee.io/posts/25154.html +https://jasonsgong.gitee.io/posts/39654.html +https://jasonsgong.gitee.io/posts/53088.html +https://jasonsgong.gitee.io/posts/64695.html +https://jasonsgong.gitee.io/posts/5727.html +https://jasonsgong.gitee.io/tags/index.html +https://jasonsgong.gitee.io/notice/index.html +https://jasonsgong.gitee.io/posts/19306.html +https://jasonsgong.gitee.io/posts/63724.html +https://jasonsgong.gitee.io/posts/60685.html +https://jasonsgong.gitee.io/posts/18459.html +https://jasonsgong.gitee.io/posts/28118.html +https://jasonsgong.gitee.io/posts/63587.html +https://jasonsgong.gitee.io/posts/20683.html +https://jasonsgong.gitee.io/posts/48020.html +https://jasonsgong.gitee.io/posts/21883.html +https://jasonsgong.gitee.io/posts/17259.html +https://jasonsgong.gitee.io/posts/24637.html +https://jasonsgong.gitee.io/posts/53306.html +https://jasonsgong.gitee.io/posts/50465.html +https://jasonsgong.gitee.io/posts/24606.html +https://jasonsgong.gitee.io/posts/73.html +https://jasonsgong.gitee.io/posts/19270.html +https://jasonsgong.gitee.io/posts/29367.html +https://jasonsgong.gitee.io/posts/50908.html +https://jasonsgong.gitee.io/posts/62439.html +https://jasonsgong.gitee.io/posts/26768.html +https://jasonsgong.gitee.io/posts/22654.html +https://jasonsgong.gitee.io/posts/56742.html +https://jasonsgong.gitee.io/posts/32679.html +https://jasonsgong.gitee.io/posts/32246.html +https://jasonsgong.gitee.io/posts/855.html +https://jasonsgong.gitee.io/posts/12929.html +https://jasonsgong.gitee.io/posts/38823.html +https://jasonsgong.gitee.io/posts/64205.html +https://jasonsgong.gitee.io/posts/47407.html +https://jasonsgong.gitee.io/posts/13813.html +https://jasonsgong.gitee.io/posts/54835.html +https://jasonsgong.gitee.io/posts/60780.html +https://jasonsgong.gitee.io/posts/30127.html +https://jasonsgong.gitee.io/posts/6932.html +https://jasonsgong.gitee.io/posts/1530.html +https://jasonsgong.gitee.io/posts/1416.html +https://jasonsgong.gitee.io/posts/13579.html +https://jasonsgong.gitee.io/posts/47003.html +https://jasonsgong.gitee.io/posts/51007.html +https://jasonsgong.gitee.io/posts/14438.html +https://jasonsgong.gitee.io/posts/17772.html +https://jasonsgong.gitee.io/posts/63333.html +https://jasonsgong.gitee.io/posts/1727.html +https://jasonsgong.gitee.io/posts/36397.html +https://jasonsgong.gitee.io/posts/45572.html +https://jasonsgong.gitee.io/posts/432.html +https://jasonsgong.gitee.io/posts/28687.html +https://jasonsgong.gitee.io/posts/60684.html +https://jasonsgong.gitee.io/posts/3661.html +https://jasonsgong.gitee.io/posts/35630.html +https://jasonsgong.gitee.io/posts/27166.html +https://jasonsgong.gitee.io/posts/46306.html +https://jasonsgong.gitee.io/posts/22202.html +https://jasonsgong.gitee.io/posts/7353.html +https://jasonsgong.gitee.io/posts/29250.html +https://jasonsgong.gitee.io/posts/31385.html +https://jasonsgong.gitee.io/posts/11844.html +https://jasonsgong.gitee.io/posts/6319.html +https://jasonsgong.gitee.io/posts/46317.html +https://jasonsgong.gitee.io/posts/40445.html +https://jasonsgong.gitee.io/posts/45726.html +https://jasonsgong.gitee.io/website/bookmarks.html +https://jasonsgong.gitee.io/categories/index.html +https://jasonsgong.gitee.io/ +https://jasonsgong.gitee.io/tags/Docker/ +https://jasonsgong.gitee.io/tags/%E5%BF%AB%E6%8D%B7%E9%94%AE/ +https://jasonsgong.gitee.io/tags/%E7%88%AC%E8%99%AB/ +https://jasonsgong.gitee.io/tags/Git/ +https://jasonsgong.gitee.io/tags/Java/ +https://jasonsgong.gitee.io/tags/%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF/ +https://jasonsgong.gitee.io/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/ +https://jasonsgong.gitee.io/tags/Linux/ +https://jasonsgong.gitee.io/tags/Mysql/ +https://jasonsgong.gitee.io/tags/SSM/ +https://jasonsgong.gitee.io/tags/%E6%8F%92%E4%BB%B6/ +https://jasonsgong.gitee.io/tags/SpringBoot/ +https://jasonsgong.gitee.io/tags/Thymeleaf/ +https://jasonsgong.gitee.io/tags/GitHub/ +https://jasonsgong.gitee.io/tags/%E7%AE%80%E5%8E%86/ +https://jasonsgong.gitee.io/tags/%E8%84%9A%E6%9C%AC/ +https://jasonsgong.gitee.io/tags/%E7%BD%91%E7%BB%9C/ +https://jasonsgong.gitee.io/tags/%E5%9F%9F%E5%90%8D%E6%B3%A8%E5%86%8C/ +https://jasonsgong.gitee.io/tags/%E6%B5%8B%E8%AF%95/ +https://jasonsgong.gitee.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/ +https://jasonsgong.gitee.io/tags/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/ +https://jasonsgong.gitee.io/tags/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/ +https://jasonsgong.gitee.io/tags/%E5%B8%B8%E7%94%A8%E7%BD%91%E7%AB%99/ +https://jasonsgong.gitee.io/tags/%E6%8A%80%E6%9C%AF%E4%B9%A6%E7%B1%8D/ +https://jasonsgong.gitee.io/tags/%E5%B7%A5%E5%85%B7/ +https://jasonsgong.gitee.io/tags/%E7%BB%86%E8%8A%82%E7%9F%A5%E8%AF%86/ +https://jasonsgong.gitee.io/tags/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/ +https://jasonsgong.gitee.io/tags/FreeMarker/ +https://jasonsgong.gitee.io/tags/%E5%89%8D%E7%AB%AF/ +https://jasonsgong.gitee.io/tags/%E4%BA%8C%E7%BB%B4%E7%A0%81/ +https://jasonsgong.gitee.io/tags/%E6%B3%A8%E9%87%8A%E6%A8%A1%E6%9D%BF/ +https://jasonsgong.gitee.io/tags/%E4%BB%BB%E5%8A%A1%E8%BF%9B%E5%BA%A6/ +https://jasonsgong.gitee.io/tags/MinIo/ +https://jasonsgong.gitee.io/tags/%E4%BA%91%E8%AE%A1%E7%AE%97/ +https://jasonsgong.gitee.io/tags/Blog/ +https://jasonsgong.gitee.io/tags/%E9%9D%A2%E8%AF%95/ +https://jasonsgong.gitee.io/tags/ElasticSearch/ +https://jasonsgong.gitee.io/categories/%E8%BF%90%E7%BB%B4/ +https://jasonsgong.gitee.io/categories/%E5%90%8E%E7%AB%AF/ +https://jasonsgong.gitee.io/categories/%E5%89%8D%E7%AB%AF/ +https://jasonsgong.gitee.io/categories/%E9%9D%A2%E8%AF%95/ +https://jasonsgong.gitee.io/categories/%E4%B8%AA%E4%BA%BA/ +https://jasonsgong.gitee.io/categories/%E6%B5%8B%E8%AF%95/ diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 000000000..df9a29a02 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,982 @@ + + + + + https://jasonsgong.gitee.io/posts/32696.html + + 2023-09-22 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/24183.html + + 2023-09-22 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/8957.html + + 2023-09-21 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/29985.html + + 2023-09-21 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/25154.html + + 2023-09-19 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/39654.html + + 2023-09-18 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/53088.html + + 2023-09-14 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/64695.html + + 2023-09-14 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/5727.html + + 2023-09-12 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/tags/index.html + + 2023-09-12 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/notice/index.html + + 2023-09-12 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/19306.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/63724.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/60685.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/18459.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/28118.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/63587.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/20683.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/48020.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/21883.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/17259.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/24637.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/53306.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/50465.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/24606.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/73.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/19270.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/29367.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/50908.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/62439.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/26768.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/22654.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/56742.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/32679.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/32246.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/855.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/12929.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/38823.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/64205.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/47407.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/13813.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/54835.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/60780.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/30127.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/6932.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/1530.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/1416.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/13579.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/47003.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/51007.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/14438.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/17772.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/63333.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/1727.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/36397.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/45572.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/432.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/28687.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/60684.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/3661.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/35630.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/27166.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/46306.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/22202.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/7353.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/29250.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/31385.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/11844.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/6319.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/46317.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/40445.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/posts/45726.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/website/bookmarks.html + + 2023-09-11 + + monthly + 0.6 + + + + https://jasonsgong.gitee.io/categories/index.html + + 2023-07-21 + + monthly + 0.6 + + + + + https://jasonsgong.gitee.io/ + 2023-09-22 + daily + 1.0 + + + + + https://jasonsgong.gitee.io/tags/Docker/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E5%BF%AB%E6%8D%B7%E9%94%AE/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E7%88%AC%E8%99%AB/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/Git/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/Java/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/Linux/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/Mysql/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/SSM/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E6%8F%92%E4%BB%B6/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/SpringBoot/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/Thymeleaf/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/GitHub/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E7%AE%80%E5%8E%86/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E8%84%9A%E6%9C%AC/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E7%BD%91%E7%BB%9C/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E5%9F%9F%E5%90%8D%E6%B3%A8%E5%86%8C/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E6%B5%8B%E8%AF%95/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E5%B8%B8%E7%94%A8%E7%BD%91%E7%AB%99/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E6%8A%80%E6%9C%AF%E4%B9%A6%E7%B1%8D/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E5%B7%A5%E5%85%B7/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E7%BB%86%E8%8A%82%E7%9F%A5%E8%AF%86/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E9%A1%B9%E7%9B%AE%E5%AE%9E%E6%88%98/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/FreeMarker/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E5%89%8D%E7%AB%AF/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E4%BA%8C%E7%BB%B4%E7%A0%81/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E6%B3%A8%E9%87%8A%E6%A8%A1%E6%9D%BF/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E4%BB%BB%E5%8A%A1%E8%BF%9B%E5%BA%A6/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/MinIo/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E4%BA%91%E8%AE%A1%E7%AE%97/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/Blog/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/%E9%9D%A2%E8%AF%95/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/tags/ElasticSearch/ + 2023-09-22 + weekly + 0.2 + + + + + + https://jasonsgong.gitee.io/categories/%E8%BF%90%E7%BB%B4/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/categories/%E5%90%8E%E7%AB%AF/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/categories/%E5%89%8D%E7%AB%AF/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/categories/%E9%9D%A2%E8%AF%95/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/categories/%E4%B8%AA%E4%BA%BA/ + 2023-09-22 + weekly + 0.2 + + + + https://jasonsgong.gitee.io/categories/%E6%B5%8B%E8%AF%95/ + 2023-09-22 + weekly + 0.2 + + + diff --git a/tags/Blog/index.html b/tags/Blog/index.html new file mode 100644 index 000000000..dc1135eac --- /dev/null +++ b/tags/Blog/index.html @@ -0,0 +1,192 @@ +标签: Blog | The Blog + + + + + + + + + +
    标签 - Blog
    2023
    Blog
    Blog
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/Docker/index.html b/tags/Docker/index.html new file mode 100644 index 000000000..98a6592bb --- /dev/null +++ b/tags/Docker/index.html @@ -0,0 +1,192 @@ +标签: Docker | The Blog + + + + + + + + + +
    标签 - Docker
    2023
    Docker容器化技术
    Docker容器化技术
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/ElasticSearch/index.html b/tags/ElasticSearch/index.html new file mode 100644 index 000000000..f0bd37cb5 --- /dev/null +++ b/tags/ElasticSearch/index.html @@ -0,0 +1,192 @@ +标签: ElasticSearch | The Blog + + + + + + + + + +
    标签 - ElasticSearch
    2023
    ElasticSearch
    ElasticSearch
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/FreeMarker/index.html b/tags/FreeMarker/index.html new file mode 100644 index 000000000..a4f863cc1 --- /dev/null +++ b/tags/FreeMarker/index.html @@ -0,0 +1,192 @@ +标签: FreeMarker | The Blog + + + + + + + + + +
    标签 - FreeMarker
    2023
    FreeMarker模板引擎
    FreeMarker模板引擎
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/Git/index.html b/tags/Git/index.html new file mode 100644 index 000000000..b2a47d475 --- /dev/null +++ b/tags/Git/index.html @@ -0,0 +1,192 @@ +标签: Git | The Blog + + + + + + + + + +
    标签 - Git
    2023
    Git命令速查
    Git命令速查
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/GitHub/index.html b/tags/GitHub/index.html new file mode 100644 index 000000000..9820c01b9 --- /dev/null +++ b/tags/GitHub/index.html @@ -0,0 +1,192 @@ +标签: GitHub | The Blog + + + + + + + + + +
    标签 - GitHub
    2023
    gitHub上的优质课设项目
    gitHub上的优质课设项目
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/Java/index.html b/tags/Java/index.html new file mode 100644 index 000000000..1dddd0f84 --- /dev/null +++ b/tags/Java/index.html @@ -0,0 +1,192 @@ +标签: Java | The Blog + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/Linux/index.html b/tags/Linux/index.html new file mode 100644 index 000000000..85bcc478e --- /dev/null +++ b/tags/Linux/index.html @@ -0,0 +1,192 @@ +标签: Linux | The Blog + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/MinIo/index.html b/tags/MinIo/index.html new file mode 100644 index 000000000..1f1207f70 --- /dev/null +++ b/tags/MinIo/index.html @@ -0,0 +1,192 @@ +标签: MinIo | The Blog + + + + + + + + + +
    标签 - MinIo
    2023
    对象存储服务MinIO
    对象存储服务MinIO
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/Mysql/index.html b/tags/Mysql/index.html new file mode 100644 index 000000000..aef65107a --- /dev/null +++ b/tags/Mysql/index.html @@ -0,0 +1,192 @@ +标签: Mysql | The Blog + + + + + + + + + +
    标签 - Mysql
    2023
    MySql进阶教程
    MySql进阶教程
    MySql基础进阶运维篇PDF笔记
    MySql基础进阶运维篇PDF笔记
    MySQL5.7安装教程
    MySQL5.7安装教程
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/SSM/index.html b/tags/SSM/index.html new file mode 100644 index 000000000..fbf33eb7c --- /dev/null +++ b/tags/SSM/index.html @@ -0,0 +1,192 @@ +标签: SSM | The Blog + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/SpringBoot/index.html b/tags/SpringBoot/index.html new file mode 100644 index 000000000..0cd1f4bfe --- /dev/null +++ b/tags/SpringBoot/index.html @@ -0,0 +1,192 @@ +标签: SpringBoot | The Blog + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/Thymeleaf/index.html b/tags/Thymeleaf/index.html new file mode 100644 index 000000000..fe3a0038a --- /dev/null +++ b/tags/Thymeleaf/index.html @@ -0,0 +1,192 @@ +标签: Thymeleaf | The Blog + + + + + + + + + +
    标签 - Thymeleaf
    2023
    Thymeleaf教程
    Thymeleaf教程
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 000000000..89d26d1ed --- /dev/null +++ b/tags/index.html @@ -0,0 +1,194 @@ +标签 | The Blog + + + + + + + + + + + +
    \ No newline at end of file diff --git a/tags/二维码/index.html b/tags/二维码/index.html new file mode 100644 index 000000000..d33282abe --- /dev/null +++ b/tags/二维码/index.html @@ -0,0 +1,192 @@ +标签: 二维码 | The Blog + + + + + + + + + +
    标签 - 二维码
    2023
    Java生成二维码
    Java生成二维码
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/云计算/index.html b/tags/云计算/index.html new file mode 100644 index 000000000..3338a537b --- /dev/null +++ b/tags/云计算/index.html @@ -0,0 +1,192 @@ +标签: 云计算 | The Blog + + + + + + + + + +
    标签 - 云计算
    2023
    阿里云对象存储OSS
    阿里云对象存储OSS
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/任务进度/index.html b/tags/任务进度/index.html new file mode 100644 index 000000000..bbb986fd5 --- /dev/null +++ b/tags/任务进度/index.html @@ -0,0 +1,192 @@ +标签: 任务进度 | The Blog + + + + + + + + + +
    标签 - 任务进度
    2023
    任务进度
    任务进度
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/前端/index.html b/tags/前端/index.html new file mode 100644 index 000000000..b2ad00848 --- /dev/null +++ b/tags/前端/index.html @@ -0,0 +1,192 @@ +标签: 前端 | The Blog + + + + + + + + + +
    标签 - 前端
    2023
    ElementUI使用示例
    ElementUI使用示例
    前端基础知识
    前端基础知识
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/域名注册/index.html b/tags/域名注册/index.html new file mode 100644 index 000000000..ffd09b2ae --- /dev/null +++ b/tags/域名注册/index.html @@ -0,0 +1,192 @@ +标签: 域名注册 | The Blog + + + + + + + + + +
    标签 - 域名注册
    2023
    免费域名注册教程
    免费域名注册教程
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/学习路线/index.html b/tags/学习路线/index.html new file mode 100644 index 000000000..b4c765d29 --- /dev/null +++ b/tags/学习路线/index.html @@ -0,0 +1,192 @@ +标签: 学习路线 | The Blog + + + + + + + + + +
    标签 - 学习路线
    2023
    Java学习路线
    Java学习路线
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/工具/index.html b/tags/工具/index.html new file mode 100644 index 000000000..61e531d0e --- /dev/null +++ b/tags/工具/index.html @@ -0,0 +1,192 @@ +标签: 工具 | The Blog + + + + + + + + + +
    标签 - 工具
    2023
    接口测试工具
    接口测试工具
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/常用命令/index.html b/tags/常用命令/index.html new file mode 100644 index 000000000..41fdcb2ff --- /dev/null +++ b/tags/常用命令/index.html @@ -0,0 +1,192 @@ +标签: 常用命令 | The Blog + + + + + + + + + +
    标签 - 常用命令
    2023
    常用的DOS命令
    常用的DOS命令
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/常用网站/index.html b/tags/常用网站/index.html new file mode 100644 index 000000000..97179367a --- /dev/null +++ b/tags/常用网站/index.html @@ -0,0 +1,192 @@ +标签: 常用网站 | The Blog + + + + + + + + + +
    标签 - 常用网站
    2023
    常用网站及网址信息
    常用网站及网址信息
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/快捷键/index.html b/tags/快捷键/index.html new file mode 100644 index 000000000..c839b2b9a --- /dev/null +++ b/tags/快捷键/index.html @@ -0,0 +1,192 @@ +标签: 快捷键 | The Blog + + + + + + + + + +
    标签 - 快捷键
    2023
    IDEA常用快捷键
    IDEA常用快捷键
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/技术书籍/index.html b/tags/技术书籍/index.html new file mode 100644 index 000000000..13c16b80f --- /dev/null +++ b/tags/技术书籍/index.html @@ -0,0 +1,192 @@ +标签: 技术书籍 | The Blog + + + + + + + + + +
    标签 - 技术书籍
    2023
    技术书籍-Linux指令大全
    技术书籍-Linux指令大全
    技术书籍-java8实战
    技术书籍-java8实战
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/插件/index.html b/tags/插件/index.html new file mode 100644 index 000000000..c4fb3c60a --- /dev/null +++ b/tags/插件/index.html @@ -0,0 +1,192 @@ +标签: 插件 | The Blog + + + + + + + + + +
    标签 - 插件
    2023
    MybatisX插件的使用
    MybatisX插件的使用
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/数据结构与算法/index.html b/tags/数据结构与算法/index.html new file mode 100644 index 000000000..67b0b7745 --- /dev/null +++ b/tags/数据结构与算法/index.html @@ -0,0 +1,192 @@ +标签: 数据结构与算法 | The Blog + + + + + + + + + +
    标签 - 数据结构与算法
    2023
    力扣(LeetCode)算法刷题
    力扣(LeetCode)算法刷题
    数据结构与算法
    数据结构与算法
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/正则表达式/index.html b/tags/正则表达式/index.html new file mode 100644 index 000000000..b1a2b2a66 --- /dev/null +++ b/tags/正则表达式/index.html @@ -0,0 +1,192 @@ +标签: 正则表达式 | The Blog + + + + + + + + + +
    标签 - 正则表达式
    2023
    常用正则表达式大全
    常用正则表达式大全
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/注释模板/index.html b/tags/注释模板/index.html new file mode 100644 index 000000000..abcb270ca --- /dev/null +++ b/tags/注释模板/index.html @@ -0,0 +1,192 @@ +标签: 注释模板 | The Blog + + + + + + + + + +
    标签 - 注释模板
    2023
    代码注释模板
    代码注释模板
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/测试/index.html b/tags/测试/index.html new file mode 100644 index 000000000..6f5b4fa9e --- /dev/null +++ b/tags/测试/index.html @@ -0,0 +1,192 @@ +标签: 测试 | The Blog + + + + + + + + + +
    标签 - 测试
    2023
    压力测试与性能监控
    压力测试与性能监控
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/爬虫/index.html b/tags/爬虫/index.html new file mode 100644 index 000000000..9b238cfa4 --- /dev/null +++ b/tags/爬虫/index.html @@ -0,0 +1,192 @@ +标签: 爬虫 | The Blog + + + + + + + + + +
    标签 - 爬虫
    2023
    Java爬虫
    Java爬虫
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/环境搭建/index.html b/tags/环境搭建/index.html new file mode 100644 index 000000000..7d2357e13 --- /dev/null +++ b/tags/环境搭建/index.html @@ -0,0 +1,192 @@ +标签: 环境搭建 | The Blog + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/简历/index.html b/tags/简历/index.html new file mode 100644 index 000000000..23d68e12e --- /dev/null +++ b/tags/简历/index.html @@ -0,0 +1,192 @@ +标签: 简历 | The Blog + + + + + + + + + +
    标签 - 简历
    2023
    简历模板
    简历模板
    个人简历
    个人简历
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/细节知识/index.html b/tags/细节知识/index.html new file mode 100644 index 000000000..2fd958d30 --- /dev/null +++ b/tags/细节知识/index.html @@ -0,0 +1,192 @@ +标签: 细节知识 | The Blog + + + + + + + + + +
    标签 - 细节知识
    2023
    细节知识
    细节知识
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/网络/index.html b/tags/网络/index.html new file mode 100644 index 000000000..628a36e08 --- /dev/null +++ b/tags/网络/index.html @@ -0,0 +1,192 @@ +标签: 网络 | The Blog + + + + + + + + + +
    标签 - 网络
    2023
    内网穿透
    内网穿透
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/脚本/index.html b/tags/脚本/index.html new file mode 100644 index 000000000..3e86612a3 --- /dev/null +++ b/tags/脚本/index.html @@ -0,0 +1,192 @@ +标签: 脚本 | The Blog + + + + + + + + + +
    标签 - 脚本
    2023
    使用Robot类编写自动化脚本
    使用Robot类编写自动化脚本
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/面试/index.html b/tags/面试/index.html new file mode 100644 index 000000000..17d8d1bed --- /dev/null +++ b/tags/面试/index.html @@ -0,0 +1,192 @@ +标签: 面试 | The Blog + + + + + + + + + +
    标签 - 面试
    2023
    面试题集锦
    面试题集锦
    面试专题
    面试专题
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/tags/项目实战/index.html b/tags/项目实战/index.html new file mode 100644 index 000000000..94bc60345 --- /dev/null +++ b/tags/项目实战/index.html @@ -0,0 +1,192 @@ +标签: 项目实战 | The Blog + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file diff --git a/website/bookmarks.html b/website/bookmarks.html new file mode 100644 index 000000000..1764f66af --- /dev/null +++ b/website/bookmarks.html @@ -0,0 +1,309 @@ +The Blog | The Blog + + + + + + + + + + + +
    avatar
    Jason
    Debug the world!
    公告
    本网站是静态网站,更新页面资源请使用Ctrl+F5;若网站内文章对你有帮助,请使用Ctrl+D收藏该网站!
    天气
    \ No newline at end of file