diff --git a/README.md b/README.md
index 868440e..3d12432 100644
--- a/README.md
+++ b/README.md
@@ -11,19 +11,27 @@
-目录中没有链接的部分,后续更新,感谢你的 STAR ,有问题或者建议可以[一起完善](Accept#-%E8%B4%A1%E7%8C%AE%E4%B8%8E%E5%BB%BA%E8%AE%AE)。
-文章内容也都可以访问网站 [https://www.wdbyte.com](https://www.wdbyte.com) 进行阅读。
+目录中没有链接的部分,后续更新,感谢你的关注 ,有问题或者建议可以[一起完善](#🗺-贡献与建议)。
+> Hi there 👋 我是阿朗, 一名 Java 开发者,热衷于分享一些通俗易懂的技术文章。 分享几句鸡汤,长寿在于生活规律;成功在于坚持不懈。 做好的事情,而不是好做的事情。
+
+
+## AI 开发
+
+- [MCP Streamable HTTP 协议入门与 100 行代码实现](https://wdbyte.com/spring-mcp-server-manual/)
## ⏳ Java 开发
+
+- [如何破解滑动验证码?](https://www.wdbyte.com/java/img-verification/)
+- [你好 ChatGPT, 帮我看下这段代码有什么问题?](https://www.wdbyte.com/java/chatgpt-files-list.html)
- [JUnit5 单元测试教程](https://www.wdbyte.com/java/junit5.html)
- [使用 StringUtils.split 的坑](https://www.wdbyte.com/java/stringutils_split.html)
- [必应壁纸,我的第一个 400 Star 开源项目](https://www.wdbyte.com/bing-wallpaper-400.html)
- [Java 中的对象池化](https://www.wdbyte.com/java/object-pool.html)
- [5种限流算法,7种限流方式,挡住突发流量?](https://www.wdbyte.com/java/rate-limiter.html)
- [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html)
-- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html)
+- [字符作画,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html)
- [Java 中 RMI 的使用](https://www.wdbyte.com/2021/05/java/java-rmi/)
- [如何使用 Github Actions 自动抓取每日必应壁纸?](https://www.wdbyte.com/2021/03/bing-wallpaper-github-action/)
- [三种骚操作绕过迭代器遍历时的数据修改异常](https://www.wdbyte.com/2021/02/develop/interator-update/)
@@ -35,28 +43,52 @@
- [如何使用 Lombok 进行优雅的编码](https://www.wdbyte.com/2018/12/develop/tool-lombok/)
- [使用MyBatis Generator自动生成Model、Dao、Mapper相关代码](https://www.wdbyte.com/2017/11/develop/tool-mybatis-generator/)
-## 🌿 Java 基础教程
+## 😍 Java 基础教程
+
- [JDK、JRE、JVM 的区别](https://www.wdbyte.com/java/jdk-jre-jvm/)
- [Java 数据类型](https://www.wdbyte.com/java/data-type/)
- [Java 流程控制](https://www.wdbyte.com/java/flow-control/)
- [Java String 字符串](https://www.wdbyte.com/java/java-string/)
-- [Java Array 数组](https://www.wdbyte.com/java/java-array/)
-- [Java 多维数组](https://www.wdbyte.com/java/java-array-mul/)
- [Java StringBuilder](https://www.wdbyte.com/java/java-stringbuilder/)
-- [Java 继承](https://www.wdbyte.com/java/extends/)
-- [Java 接口](https://www.wdbyte.com/java/interface/)
-- [Java 抽象类](https://www.wdbyte.com/java/abstract/)
+- [Java Scanner](https://www.wdbyte.com/java/scanner/)
- [Java 多态](https://www.wdbyte.com/java/polymorphism/)
- [Java Scanner](https://www.wdbyte.com/java/scanner/)
-- [Java 日期时间 Date](https://www.wdbyte.com/java/date/)
+- [Java 日期时间Date](https://www.wdbyte.com/java/date/)
- [Java 异常处理](https://www.wdbyte.com/java/exception/)
- [Java 枚举](https://www.wdbyte.com/java/enum/)
-- [Java 注释](https://www.wdbyte.com/java/comment/)
+- [Java 注释](*https://www.wdbyte.com/java/comment/*)
+
+### Java 数组
+- [Java Array 数组](https://www.wdbyte.com/java/java-array/)
+- [Java 多维数组](https://www.wdbyte.com/java/java-array-mul/)
+- [Java Arrays 教程](https://www.wdbyte.com/java/arrays/)
+
+### Java 面向对象
+- [Java 继承](https://www.wdbyte.com/java/extends/)
+- [Java 接口](https://www.wdbyte.com/java/interface/)
+- [Java 抽象类](https://www.wdbyte.com/java/abstract/)
+- [抽象类和接口的区别](https://www.wdbyte.com/java/abs-interface/)
+
+### Java 集合
- [Java 集合框架](https://www.wdbyte.com/java/collection/)
-- [Java 中使用 List](https://www.wdbyte.com/java/list/)
+- [Java 中使用 List ](https://www.wdbyte.com/java/list/)
+
+### 代码测试
+- [Java 断言 Assert 使用教程与最佳实践](https://www.wdbyte.com/java/assert/)
+
+## 😃Java I/O 教程
+
+- [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/)
+- [Java 读取文件](https://www.wdbyte.com/java/io/file-read/)
+- [Java 追加内容到文件](https://www.wdbyte.com/java/io/file-append/)
+- [Java 如何删除文件](https://www.wdbyte.com/java/io/file-delete/)
+
+## 🎉 Java 进阶教程
-## Java 进阶
- [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/)
+- [Java 热加载手动实现](https://www.wdbyte.com/2019/10/jvm/java-hotput/)
+- [Jpackage - 制作无需预装 Java 环境的 Jar 可执行程序](https://www.wdbyte.com/java/jpackage/)
+
## 🌿 SpringBoot 2.x 教程
@@ -66,9 +98,9 @@
- [Spring Boot 系列(二)Spring Boot 配置文件](https://www.wdbyte.com/2019/01/springboot/springboot01-config/)
- [Spring Boot 系列(三)Spring Boot 自动配置](https://www.wdbyte.com/2019/01/springboot/springboot03-auto-config/)
- [Spring Boot 系列(四)Spring Boot 日志框架](https://www.wdbyte.com/2019/01/springboot/springboot04-log/)
-- [Spring Boot 系列(五)web 开发之静态资源和模版引擎](https://www.wdbyte.com/2019/02/springboot/springboot-05-web-static-template/)
-- [Spring Boot 系列(六)web 开发之拦截器和三大组件](https://www.wdbyte.com/2019/02/springboot/springboot-06-web-filter-apo-webbase/)
-- [Spring Boot 系列(七)web 开发之异常错误处理机制剖析](https://www.wdbyte.com/2019/02/springboot/springboot-07-web-exception/)
+- [Spring Boot 系列(五)Web 开发之静态资源和模版引擎](https://www.wdbyte.com/2019/02/springboot/springboot-05-web-static-template/)
+- [Spring Boot 系列(六)Web 开发之拦截器和三大组件](https://www.wdbyte.com/2019/02/springboot/springboot-06-web-filter-apo-webbase/)
+- [Spring Boot 系列(七)Web 开发之异常错误处理机制剖析](https://www.wdbyte.com/2019/02/springboot/springboot-07-web-exception/)
- [Spring Boot 系列(八)动态 Banner 与图片转字符图案的手动实现](https://www.wdbyte.com/2019/02/springboot/springboot-08-banner/)
- [Spring Boot 系列(九)使用 Spring JDBC 和 Druid 数据源监控](https://www.wdbyte.com/2019/02/springboot/springboot-09-data-jdbc/)
- [Spring Boot 系列(十)使用 Spring data jpa 访问数据库](https://www.wdbyte.com/2019/03/springboot/springboot-10-data-jpa/)
@@ -81,6 +113,7 @@
- [Spring Boot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序](https://www.wdbyte.com/2019/12/springboot/springboot-17-admin/)
- [Spring Boot 系列(十八)最详细的 Spring Boot 多模块开发与排坑指南](https://www.wdbyte.com/2020/03/springboot/springboot-18-module/)
- [Spring Boot 系列(十九)SpringBoot 的多数据源配置](https://www.wdbyte.com/2020/12/springboot/springboot-multiple-datasource/)
+- [Spring Boot 系列(二十)三分钟,Spring Boot、JPA 与 SQLite 的快速启动](https://www.wdbyte.com/springboot/sqlite/)
以上 Spring Boot 文章源码:[Github.com/niumoo/springboot](https://github.com/niumoo/springboot/)
@@ -93,11 +126,11 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错
- [Java 20 新功能介绍](https://www.wdbyte.com/java/java-20/)
- [Java 19 新功能介绍](https://www.wdbyte.com/java/java-19/)
- [Java 18 新功能介绍](https://www.wdbyte.com/java/java-18/)
-- [Java 17 新功能介绍 (LTS)](https://www.wdbyte.com/java/java-17/)
+- [Java 17 新功能介绍](https://www.wdbyte.com/java/java-17/)
- [Java 16 新功能介绍](https://www.wdbyte.com/java/java-16/)
- [Java 15 新功能介绍](https://www.wdbyte.com/java/java-15/)
-- [Java 14 新特性讲解](https://www.wdbyte.com/java/java-14/)
-- [Java 13 新特性讲解](https://www.wdbyte.com/java/java-13/)
+- [Java 14 新特性介绍](https://www.wdbyte.com/java/java-14/)
+- [Java 13 新特性介绍](https://www.wdbyte.com/java/java-13/)
- [Java 12 新特性介绍](https://www.wdbyte.com/2020/02/jdk/jdk12-feature/)
- [Java 11 新特性介绍](https://www.wdbyte.com/2020/03/jdk/jdk11-feature/)
- [Java 10 新特性介绍](https://www.wdbyte.com/2020/02/jdk/jdk10-feature/)
@@ -108,8 +141,8 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错
- [Java 8 函数接口 Supplier ](https://www.wdbyte.com/java8/java8-supplier/)
- [Java 8 函数接口 Predicate ](https://www.wdbyte.com/java8/java8-predicate/)
- [Java 8 函数接口 Function ](https://www.wdbyte.com/java8/java8-function/)
+- [Java 8 Lambda 和 Comparator 排序](https://www.wdbyte.com/java8/comparator/)
- [Java 8 新特性 - forEach 遍历](https://www.wdbyte.com/java8/java8-foreach/)
-
- [Java 8 新特性 - LocalDate、LocalDateTime 时间处理介绍](https://www.wdbyte.com/2019/10/jdk/jdk8-time/)
- [Java 8 新特性 - 使用 Optional优雅的处理空指针](https://www.wdbyte.com/2019/11/jdk/jdk8-optional/)
- [Java 8 新特性 - Lambda 表达式、函数接口了解一下](https://www.wdbyte.com/2019/11/jdk/jdk8-lambda/)
@@ -117,30 +150,19 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错
- [Java 7 新特性 - 和低效 IO 说再见,Files,Paths,Path 文件操作介绍](https://www.wdbyte.com/2020/09/jdk/jdk7-file-pahs/)
- [Java 7 新特性 - 新特性 - 快来补一波 Java 7 语法特性](https://www.wdbyte.com/2020/01/jdk/jdk7-start/)
-以上 Java 新功能文章源码: [Github.com/niumoo/jdk-feature](https://github.com/niumoo/jdk-feature)
-
-## 🔬 JDK 源码分析
-
-面试必备的 JDK 源码分析。探寻 JDK 大佬的设计思路。没有链接部分为后续更新内容,持续更新中。
-
-- [集合 -「源码分析」CopyOnWriteArrayList 中的隐藏的知识,你Get了吗?](https://www.wdbyte.com/2020/10/jdk/src-copyonwritearraylist/)
-- [集合 -「源码分析」ArrayList和LinkedList如何实现的?我看你还有机会!](https://www.wdbyte.com/2020/08/jdk/src-arraylist-linkedlist/)
-- [集合 -「源码分析」还不懂 ConcurrentHashMap ?这份源码分析了解一下](https://www.wdbyte.com/2020/04/jdk/concurrent-hashmap/)
-- [集合 -「源码分析」最通俗易懂的 HashMap 源码分析解读](https://www.wdbyte.com/2020/03/jdk/hashmap/)
-- 集合 -「源码分析」TreeSet
-- 集合 -「源码分析」LinkedHashSet
-- 基础类 - Object
-- 基础类 - String
-- 基础类 - StringBuffer & StringBuilder
-
-
## 💻 Java 并发编程
-- 线程基础之通知、等待、休眠、让行、中断
-- ThreadLocal
+- Java 线程创建与运行
+- Java 线程通知与等待
+- Java 线程休眠与让行
+- Java 线程中断与停止
+- Java 线程死锁
+- Java 线程的上下文切换
+- Java 守护线程与用户线程
+- Java ThreadLocal
- 内存可见性、伪共享
-- synchronized
-- volatile
+- Java synchronized
+- Java volatile
- 原子操作
- 排它锁、悲观锁、乐观锁、公平锁、非公平锁、独占锁、共享锁、重入锁、自旋锁
- ThreadLocalRandom
@@ -166,14 +188,35 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错
- 锁的自动优化升级策略
## 🔍 Java 性能分析
-
- [Java 中的5个代码性能提升技巧,最高提升9.5倍](https://www.wdbyte.com/java/code-5-tips.html)
+- [JMC 使用教程](https://www.wdbyte.com/java/performance/jmc.html)
+- [JFR 使用教程](https://www.wdbyte.com/java/performance/jfr.html)
- [使用 JMX 监控和管理 Java 程序](https://www.wdbyte.com/java/jmx.html)
- [Java 中的监控与管理原理概述](https://www.wdbyte.com/java/monitoring.html)
- [JMH-大厂是如何使用JMH进行Java代码性能测试的?必须掌握!](https://www.wdbyte.com/2020/08/develop/tool-jmh/)
- [Arthas - Java 线上问题定位处理的终极利器](https://www.wdbyte.com/2019/11/arthas/)
- [超好用的自带火焰图的 Java 性能分析工具 Async-profiler 了解一下](https://www.wdbyte.com/2019/12/async-profiler/)
+## 🔬 JDK 源码分析
+
+面试必备的 JDK 源码分析。探寻 JDK 大佬的设计思路。没有链接部分为后续更新内容,持续更新中。
+
+- [集合 - CopyOnWriteArrayList 实现原理和源码分析](https://www.wdbyte.com/2020/10/jdk/src-copyonwritearraylist/)
+- [集合 - ArrayList和LinkedList 实现原理和源码分析](https://www.wdbyte.com/2020/08/jdk/src-arraylist-linkedlist/)
+- 集合 -「源码分析」Vector
+- [集合 - ConcurrentHashMap 实现原理和源码分析](https://www.wdbyte.com/2020/04/jdk/concurrent-hashmap/)
+- [集合 - HashMap 实现原理和源码分析](https://www.wdbyte.com/2020/03/jdk/hashmap/)
+- 集合 - TreeMap 实现原理和源码分析
+- 集合 - TreeSet 实现原理和源码分析
+- 集合 - LinkedHashSet 实现原理和源码分析
+- 基础类 - Object
+- 基础类 - String
+- 基础类 - StringBuffer & StringBuilder
+
+## 认证授权
+- [JSON Web Token 入门教程](https://www.wdbyte.com/auth/jwt/)
+
+
## 🧱 数据结构
- 数组
@@ -185,6 +228,9 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错
- 堆
- 图
+## 🍔 数据库
+- [SQLite 入门教程](https://www.wdbyte.com/db/sqlite/)
+
## 🧰 工具技巧
>“工欲善其事,必先利其器”
@@ -197,12 +243,11 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错
- [Apache HttpClient 5 使用详细教程](https://www.wdbyte.com/tool/httpclient5.html)
- [Jackson 解析 JSON 详细教程](https://www.wdbyte.com/tool/jackson.html)
- [Java 反编译工具的使用与对比分析](https://www.wdbyte.com/2021/05/java-decompiler/)
-- [可以Postman,也可以cURL.进来领略下cURL的独门绝技](https://www.wdbyte.com/2020/06/tool/curl/)
-- [抛弃Eclipse,投入IDEA 的独孤求败江湖](https://www.wdbyte.com/2019/10/develop/idea-skill/)
+- [cURL 使用教程](https://www.wdbyte.com/2020/06/tool/curl/)
+- [Java IDEA 使用教程](https://www.wdbyte.com/2019/10/develop/idea-skill/)
- [使用Apache Ant 进行Java web项目打包并部署至TOMCAT](https://www.wdbyte.com/2017/11/develop/tool-apache-ant/)
- [Linux配置Tomcat的单机多实例](https://www.wdbyte.com/2018/08/develop/install-tomcat-many-instance/)
- [Linux定时任务crontab的使用](https://www.wdbyte.com/2018/05/linux/linux-crontab/)
-- [原来热加载如此简单,手动写一个 Java 热加载吧](https://www.wdbyte.com/2019/10/jvm/java-hotput/)
- [Manjaro Linux 入门使用教程](https://www.wdbyte.com/2020/04/linux/linux-manjaro/)
- [Ubuntu18 的超详细常用软件安装](https://www.wdbyte.com/2018/11/linux/start-ubuntu/)
@@ -219,35 +264,26 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错
## 🗺 贡献与建议
-1. 内容难免存在笔误,一个错别字,一个语法错误,都是贡献。
+反馈地址:[https://github.com/niumoo/JavaNotes/issues](https://github.com/niumoo/JavaNotes/issues)
+
+1. 内容难免存在笔误,一个错别字,一个语法错误,都是建议。
2. 文章中的错误和不足,或者不完善的地方都可以进行补充或者修改。
3. 我没有涉及到的知识点,也可以进行补充。
-## 🏃 我的痕迹
-
-1. 我的网站:[https://www.wdbyte.com/](https://www.wdbyte.com/)
-2. GitHub:[https://github.com/niumoo](https://github.com/niumoo)
+## 赏个 Star
-3. C SDN:[https://blog.csdn.net/u013735734](https://blog.csdn.net/u013735734)
+[](https://starchart.cc/niumoo/JavaNotes)
-4. 博客园:[https://www.cnblogs.com/niumoo/](https://www.cnblogs.com/niumoo/)
-5. 掘 金:[https://juejin.im/user/5a62d481f265da3e2a0dac9b](https://juejin.im/user/5a62d481f265da3e2a0dac9b)
+### 公众号
-6. 知 乎:[https://www.zhihu.com/people/bpdwn](https://www.zhihu.com/people/bpdwn)
+可以关注「 **程序猿阿朗** 」公众号。即使查看更新的文章以及分享的干货。
+
### 联系我
-可以添加我的微信 wn8398 一起交流。
-
-
-
-### 公众号
-
-有帮助可以点「**赞**」在看或 :star: **Star**,谢谢你!
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注「 **未读代码** 」公众号。
+等不及了,还不添加我微信一起交个朋友。
-
+
diff --git a/core-java-modules/core-java-22/pom.xml b/core-java-modules/core-java-22/pom.xml
new file mode 100644
index 0000000..4fc6401
--- /dev/null
+++ b/core-java-modules/core-java-22/pom.xml
@@ -0,0 +1,14 @@
+
+
+ 4.0.0
+ com.wdbyte.core-java-modules
+ core-java-22
+ 1.0.0-SNAPSHOT
+
+ 22
+ 22
+ UTF-8
+
+
\ No newline at end of file
diff --git a/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java b/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java
new file mode 100644
index 0000000..cbe7f6f
--- /dev/null
+++ b/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java
@@ -0,0 +1,21 @@
+package com.wdbyte;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2025/04/28
+ */
+//TIP To Run code, press or
+// click the icon in the gutter.
+public class Main {
+ public static void main(String[] args) {
+ //TIP Press with your caret at the highlighted text
+ // to see how IntelliJ IDEA suggests fixing it.
+ System.out.printf("Hello and welcome!");
+
+ for (int i = 1; i <= 5; i++) {
+ //TIP Press to start debugging your code. We have set one breakpoint
+ // for you, but you can always add more by pressing .
+ System.out.println("i = " + i);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-java-modules/core-java-8/pom.xml b/core-java-modules/core-java-8/pom.xml
index 2ae8b99..132cc2e 100644
--- a/core-java-modules/core-java-8/pom.xml
+++ b/core-java-modules/core-java-8/pom.xml
@@ -35,11 +35,6 @@
org.junit.jupiter
junit-jupiter
-
- org.projectlombok
- lombok
- 1.18.22
-
\ No newline at end of file
diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java
index 357f967..cbd5029 100644
--- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java
+++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java
@@ -1,9 +1,5 @@
package com.wdbyte;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.ToString;
import org.junit.jupiter.api.Test;
import java.util.*;
@@ -70,13 +66,33 @@ public void functionLambdaTest() {
}
- @Getter
- @Setter
- @ToString
- @AllArgsConstructor
static class User {
private String name;
private Integer age;
+
+ public User() {
+ }
+
+ public User(String name, Integer age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
}
public static List userList = new ArrayList();
diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java
index ec3fa57..fc080a8 100644
--- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java
+++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java
@@ -2,8 +2,6 @@
import java.util.Optional;
-
-import lombok.Data;
import org.junit.jupiter.api.Test;
/**
@@ -182,23 +180,44 @@ public void optionalTest() {
/**
* 计算机
*/
-@Data
class Computer {
private Optional soundCard;
+
+ public Optional getSoundCard() {
+ return soundCard;
+ }
+
+ public void setSoundCard(Optional soundCard) {
+ this.soundCard = soundCard;
+ }
}
/**
* 声卡
*/
-@Data
class SoundCard {
private Optional usb;
+
+ public Optional getUsb() {
+ return usb;
+ }
+
+ public void setUsb(Optional usb) {
+ this.usb = usb;
+ }
}
/**
* USB
*/
-@Data
class Usb {
private String version;
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
}
diff --git a/core-java-modules/core-java-base/README.md b/core-java-modules/core-java-base/README.md
index 0071ce6..5b2d151 100644
--- a/core-java-modules/core-java-base/README.md
+++ b/core-java-modules/core-java-base/README.md
@@ -22,4 +22,5 @@
- [Java 枚举](https://www.wdbyte.com/java/enum/)
- [Java 注释](*https://www.wdbyte.com/java/comment/*)
- [Java 集合框架](https://www.wdbyte.com/java/collection/)
-- [Java 中使用 List ](https://www.wdbyte.com/java/list/)
\ No newline at end of file
+- [Java 中使用 List ](https://www.wdbyte.com/java/list/)
+- [Java 断言 Assert 使用教程与最佳实践](https://www.wdbyte.com/java/assert/)
\ No newline at end of file
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java
new file mode 100644
index 0000000..50490a5
--- /dev/null
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java
@@ -0,0 +1,29 @@
+package com.wdbyte.assert1;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2024/04/22
+ */
+public class AssertDemo1 {
+ public static void main(String[] args) {
+
+ List list = Arrays.asList("1", "2");
+ boolean result = list.remove("x");
+ //assert result;
+ assert result : "移除失败";
+ System.out.println(calc(100, 10));
+
+ // 手动开启断言
+ //ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
+ //System.out.println(calc(100, 0));
+ }
+
+ public static int calc(int a, int b) {
+ assert b != 0 : "除数不能为0";
+ return a / b;
+
+ }
+}
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java
new file mode 100644
index 0000000..735c2bb
--- /dev/null
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java
@@ -0,0 +1,24 @@
+package com.wdbyte.assert1;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.base.Verify.*;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2024/04/22
+ */
+public class AssertDemo2 {
+ public static void main(String[] args) {
+ int x = 100;
+ verifyNotNull(x != 0);
+ System.out.println(calc(100, 10));
+ System.out.println(calc(100, 0));
+ }
+
+ public static int calc(int a, int b) {
+ verify(b != 0, "除数不能为0");
+ return a / b;
+ }
+}
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java
new file mode 100644
index 0000000..09e2272
--- /dev/null
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java
@@ -0,0 +1,20 @@
+package com.wdbyte.assert1;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2024/04/22
+ */
+public class AssertDemo3 {
+ static final boolean asserts = false; // 设置为 false 来消除断言
+
+ public static void main(String[] args) {
+ List list = Arrays.asList("1", "2");
+ boolean result = list.remove("x");
+ if (asserts) {
+ assert result : "移除失败";
+ }
+ }
+}
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java
new file mode 100644
index 0000000..565de43
--- /dev/null
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java
@@ -0,0 +1,25 @@
+package com.wdbyte.assert1;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2024/04/22
+ */
+public class AssertDemo4 {
+
+ static {
+ boolean assertsEnabled = false;
+ assert assertsEnabled = true; // 故意产生副作用!!!
+ if (!assertsEnabled) {
+ throw new RuntimeException("必须启用断言!!!");
+ }
+ }
+
+ public static void main(String[] args) {
+ List list = Arrays.asList("1", "2");
+ boolean result = list.remove("x");
+ assert result : "移除失败";
+ }
+}
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java
new file mode 100644
index 0000000..4c37dde
--- /dev/null
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java
@@ -0,0 +1,25 @@
+package com.wdbyte.assert1;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
+import org.apache.commons.lang3.Validate;
+import org.junit.jupiter.api.Assertions;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2024/04/22
+ */
+public class AssertDemo5 {
+
+ public static void main(String[] args) {
+ List list = Arrays.asList("1", "2");
+ boolean result = list.remove("x");
+ Assertions.assertTrue(result);
+ Preconditions.checkNotNull("","msg");
+ Validate.isTrue(list.isEmpty(),"msg");
+ Verify.verify(list.isEmpty(),"msg");
+ }
+}
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/InitializationDemo.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/InitializationDemo.java
new file mode 100644
index 0000000..cfebe99
--- /dev/null
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/InitializationDemo.java
@@ -0,0 +1,28 @@
+package com.wdbyte.assert1;
+
+public class InitializationDemo {
+
+ static {
+ init();
+ }
+
+ static void init() {
+ System.out.println("Static initialization block called");
+ // 假设这里有一个重要的初始化逻辑
+ // 这个方法错误地在静态初始化之前被调用了
+ assert isProperlyInitialized() : "System not properly initialized";
+ }
+
+ static boolean isProperlyInitialized() {
+ // 这里返回 false 模拟系统未被正确初始化
+ return false;
+ }
+
+ public InitializationDemo() {
+ System.out.println("Constructor called");
+ }
+
+ public static void main(String[] args) {
+ new InitializationDemo();
+ }
+}
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java
index 54032e0..582a6f3 100644
--- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java
@@ -8,7 +8,7 @@
import java.util.stream.Collectors;
/**
- * @author niulang
+ * @author www.wdbyte.com
* @date 2023/10/19
*/
public class ArrayListTest {
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java
index 357e0f2..f18c9b1 100644
--- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java
@@ -6,7 +6,7 @@
import java.util.Vector;
/**
- * @author niulang
+ * @author www.wdbyte.com
* @date 2023/10/19
*/
public class ArrayListTest2 {
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java
index 02b189f..a88fd7a 100644
--- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java
@@ -9,7 +9,7 @@
import com.google.common.collect.Lists;
/**
- * @author niulang
+ * @author www.wdbyte.com
* @date 2023/10/19
*/
public class ArrayListTest3 {
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java
index 98200cf..93d09e4 100644
--- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java
@@ -3,6 +3,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -11,7 +12,7 @@
import com.google.common.collect.Lists;
/**
- * @author niulang
+ * @author www.wdbyte.com
* @date 2023/10/19
*/
public class ArrayListTest4 {
@@ -31,6 +32,18 @@ public static void main(String[] args) {
}
System.out.println(dogMap.get("大黄"));
+ List arrayList = new ArrayList();
+ arrayList.add("a");
+ arrayList.add("b");
+
+ Iterator iterator = arrayList.iterator();
+ while (iterator.hasNext()) {
+ String next = iterator.next();
+ if ("a".equals(next)) {
+ iterator.remove();
+ }
+ }
+ System.out.println(arrayList);
}
}
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java
index 6d83265..30e6fd8 100644
--- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java
@@ -11,7 +11,6 @@ public static void main(String[] args) {
System.out.println("Today is Monday.");
}
-
Weekday[] weekdays = Weekday.values();
for (Weekday weekday : weekdays) {
System.out.println(weekday);
diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java
new file mode 100644
index 0000000..7887015
--- /dev/null
+++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java
@@ -0,0 +1,74 @@
+package com.wdbyte.thread;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2023/11/01
+ */
+public class CompletableFutureTest {
+
+ /**
+ * 异步执行程序后,对正常响应和异常响应进行处理
+ */
+ @Test
+ public void completableFutureTest1() {
+ CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> {
+ sleep(2000);
+ System.out.println("do.....");
+ return 1;
+ });
+
+ completableFuture1.thenAccept(res -> {
+ System.out.println("收到结果:" + res
+ );
+ });
+
+ System.out.println("等待");
+ sleep(10 * 1000);
+ }
+ @Test
+ public void completableFutureTest2() {
+ CompletableFuture completableFuture2 = CompletableFuture.supplyAsync(() -> {
+ sleep(2000);
+ System.out.println("do2.....");
+ return 10 / 0;
+ });
+ completableFuture2.exceptionally(except -> {
+ System.out.println("发生异常:" + except.getMessage());
+ return 0;
+ });
+
+ System.out.println("等待");
+ sleep(10 * 1000);
+ }
+
+ @Test
+ public void completableFutureTest3() {
+ CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> {
+ sleep(2000);
+ System.out.println("do.....");
+ return 1;
+ });
+
+ completableFuture1.thenAccept(res -> {
+ System.out.println("收到结果:" + res
+ );
+ });
+
+ System.out.println("等待");
+ sleep(10 * 1000);
+ }
+
+ void sleep(long millis){
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java
new file mode 100644
index 0000000..1370449
--- /dev/null
+++ b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java
@@ -0,0 +1,25 @@
+package com.wdbyte.collection;
+
+import java.util.EnumMap;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2023/10/20
+ */
+public class EnumMapTest {
+ public static void main(String[] args) {
+ EnumMap enumMap = new EnumMap(Week.class);
+ enumMap.put(Week.a,null);
+ System.out.println(enumMap.get(Week.a));
+ }
+}
+
+enum Week {
+ a,
+ b,
+ c,
+ d,
+ e,
+ f,
+ g
+}
diff --git a/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java
new file mode 100644
index 0000000..144cdcb
--- /dev/null
+++ b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java
@@ -0,0 +1,146 @@
+package com.wdbyte.collection;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import java.util.function.IntFunction;
+import java.util.function.ToIntFunction;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2024/03/04
+ */
+public class JavaArrays {
+
+ @Test
+ public void print() {
+ String[] arr = new String[] {"a", "b", "c", "d"};
+ System.out.println(Arrays.toString(arr));
+ }
+
+ @Test
+ public void init() {
+ String[] arr = new String[] {"a", "b", "c", "d"};
+ String[] copyOf2 = Arrays.copyOf(arr, 2);
+ String[] copyOfRange = Arrays.copyOfRange(arr, 1, 3);
+
+ System.out.println(Arrays.toString(arr));
+ System.out.println(Arrays.toString(copyOf2));
+ System.out.println(Arrays.toString(copyOfRange));
+
+ // 目标大小大于原始大小,则copyOf 会用null填充数组 。
+ String[] copyOf10 = Arrays.copyOf(arr, 10);
+ System.out.println(Arrays.toString(copyOf10));
+ }
+
+ @Test
+ public void fill() {
+ String[] arr = new String[5];
+ Arrays.fill(arr, "java");
+ System.out.println(Arrays.toString(arr));
+
+ // 生成 100以内的 随机数
+ IntFunction intFunction = i -> new Random().nextInt(100);
+ Integer[] intArr = new Integer[5];
+ Arrays.setAll(intArr, intFunction);
+ System.out.println(Arrays.toString(intArr));
+ }
+
+ @Test
+ public void diff() {
+ String[] arr = new String[] {"a", "b", "c", "d"};
+ Object[] arr1 = new Object[] {arr, new String[] {"a", "b", "c", "d"}};
+ Object[] arr2 = new Object[] {arr, arr};
+
+ System.out.println(Arrays.equals(arr1, arr2));
+ System.out.println(Arrays.deepEquals(arr1, arr2));
+
+ // hashCode
+ System.out.println(Arrays.hashCode(arr2));
+ System.out.println(Arrays.deepHashCode(arr2));
+
+ arr[0] = null;
+ System.out.println(Arrays.hashCode(arr2));
+ System.out.println(Arrays.deepHashCode(arr2));
+ }
+
+ @Test
+ public void sort() {
+ // 生成 100以内的 随机数
+ IntFunction intFunction = i -> new Random().nextInt(100);
+ Integer[] intArr = new Integer[5];
+ Arrays.setAll(intArr, intFunction);
+ System.out.println(Arrays.toString(intArr));
+
+ Arrays.sort(intArr);
+ System.out.println(Arrays.toString(intArr));
+
+ //long cost = 0;
+ //for (int ii = 0; ii < 10; ii++) {
+ // IntFunction intFunction = i -> new Random().nextInt(100000);
+ // Integer[] intArr = new Integer[100000];
+ // Arrays.setAll(intArr, intFunction);
+ //
+ // long start = System.currentTimeMillis();
+ // Arrays.sort(intArr);
+ // long end = System.currentTimeMillis();
+ // cost = cost + (end - start);
+ //}
+ //System.out.println(cost / 10);
+ //
+ //cost = 0;
+ //for (int ii = 0; ii < 10; ii++) {
+ // IntFunction intFunction = i -> new Random().nextInt(100000);
+ // Integer[] intArr = new Integer[100000];
+ // Arrays.setAll(intArr, intFunction);
+ //
+ // long start = System.currentTimeMillis();
+ // Arrays.parallelSort(intArr);
+ // long end = System.currentTimeMillis();
+ // cost = cost + (end - start);
+ //}
+ //System.out.println(cost / 10);
+ }
+
+ @Test
+ public void search() {
+ Integer[] intArr = new Integer[] {2, 3, 4, 5, 6, 7, 8, 9};
+ int index = Arrays.binarySearch(intArr, 3);
+ System.out.println("index:"+index);
+ System.out.println(intArr[index]);
+ }
+
+ @Test
+ public void stream() {
+ Integer[] intArr = new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ System.out.println(Arrays.stream(intArr).count());
+ ToIntFunction toIntFunction = i -> (int)i;
+ System.out.println(Arrays.stream(intArr).mapToInt(toIntFunction).sum());
+ }
+
+ @Test
+ public void cast() {
+ String[] arr = new String[] {"a", "b", "c", "d"};
+ Object[] arr2 = new Object[] {arr, arr};
+
+ System.out.println(Arrays.toString(arr));
+ System.out.println(Arrays.toString(arr2));
+
+ System.out.println(Arrays.deepToString(arr));
+ System.out.println(Arrays.deepToString(arr2));
+
+ List list = Arrays.asList(arr);
+ System.out.println(list);
+ System.out.println(list.getClass());
+ // list.add("e"); 报错
+ }
+
+ @Test
+ public void prefix() {
+ Integer[] intArr = new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ Arrays.parallelPrefix(intArr, (left, right) -> left + right);
+ System.out.println(Arrays.toString(intArr));
+ }
+}
diff --git a/core-java-modules/core-java-io/README.md b/core-java-modules/core-java-io/README.md
index 5d1fb00..85360a7 100644
--- a/core-java-modules/core-java-io/README.md
+++ b/core-java-modules/core-java-io/README.md
@@ -2,5 +2,8 @@
当前模块包含 IO 相关代码
### 相关文章
-
+- [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/)
+- [Java 读取文件](https://www.wdbyte.com/java/io/file-read/)
+- [Java 追加内容到文件](https://www.wdbyte.com/java/io/file-append/)
+- [Java 如何删除文件](https://www.wdbyte.com/java/io/file-delete/)
- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html)
\ No newline at end of file
diff --git a/core-java-modules/core-java-io/pom.xml b/core-java-modules/core-java-io/pom.xml
index 2fb5dba..30c75c1 100644
--- a/core-java-modules/core-java-io/pom.xml
+++ b/core-java-modules/core-java-io/pom.xml
@@ -14,18 +14,22 @@
jar
- 1.8
- 1.8
+ 21
+ 21
com.google.guava
guava
- 31.0.1-jre
- jar
- provided
+ 32.1.3-jre
+
+ commons-io
+ commons-io
+ 2.7
+
+
diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java
new file mode 100644
index 0000000..53879c8
--- /dev/null
+++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java
@@ -0,0 +1,179 @@
+package com.wdbyte.io.file;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.FileWriteMode;
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2023/12/12
+ */
+public class FileAppendDemo {
+
+ @Test
+ public void appendLine1() throws IOException {
+ Path path = Paths.get("/Users/darcy/wdbyte/test1.txt");
+ // 写入一行数据
+ Files.write(path, "line append1".getBytes(StandardCharsets.UTF_8),
+ StandardOpenOption.CREATE, StandardOpenOption.APPEND);
+ // 写入一个换行符
+ Files.write(path, System.lineSeparator().getBytes(StandardCharsets.UTF_8),
+ StandardOpenOption.APPEND);
+ }
+
+ @Test
+ public void appendLine2() throws IOException {
+ String path = "/Users/darcy/wdbyte/test1.txt";
+ try (FileWriter fileWriter = new FileWriter(path, true)) {
+ fileWriter.write("line append2");
+ fileWriter.write(System.lineSeparator());
+ }
+ }
+ @Test
+ public void appendLine3() throws IOException {
+ String path = "/Users/darcy/wdbyte/test1.txt";
+ try (FileWriter fileWriter = new FileWriter(path, true)) {
+ BufferedWriter bw = new BufferedWriter(fileWriter);
+ bw.write("line append3");
+ bw.newLine();
+ }
+ }
+
+ @Test
+ public void appendLine4() throws IOException {
+ String path = "/Users/darcy/wdbyte/test1.txt";
+ try (FileOutputStream fileOutputStream = new FileOutputStream(path, true)) {
+ fileOutputStream.write("line append4".getBytes(StandardCharsets.UTF_8));
+ fileOutputStream.write(System.lineSeparator().getBytes(StandardCharsets.UTF_8));
+ }
+ }
+
+ /**
+ * java 11 Files.writeString
+ * @throws IOException
+ */
+ public void appendLineJava11() throws IOException {
+ Path path = Paths.get("/Users/darcy/wdbyte/test1.txt");
+ Files.writeString(path, "line appendLineJava11",
+ StandardOpenOption.CREATE,
+ StandardOpenOption.APPEND);
+ }
+
+
+ /**
+ * Java 8
+ *
+ * @throws IOException
+ */
+ @Test
+ public void appendLineJava8() throws IOException {
+ Path path = Paths.get("/Users/darcy/wdbyte/test1.txt");
+ List dataList = new ArrayList<>();
+ dataList.add("line1 appendLineJava8");
+ dataList.add("line2 appendLineJava8");
+ dataList.add("line3 appendLineJava8");
+
+ Files.write(path, dataList,
+ StandardOpenOption.CREATE,
+ StandardOpenOption.APPEND);
+
+ Files.write(path, System.lineSeparator().getBytes(StandardCharsets.UTF_8),
+ StandardOpenOption.CREATE,
+ StandardOpenOption.APPEND);
+ }
+
+ @Test
+ public void appendLineList() throws IOException {
+ String path = "/Users/darcy/wdbyte/test1.txt";
+ List dataList = new ArrayList<>();
+ dataList.add("line1 appendLineList");
+ dataList.add("line2 appendLineList");
+ dataList.add("line3 appendLineList");
+
+ try (FileWriter fileWriter = new FileWriter(path, true)) {
+ BufferedWriter bw = new BufferedWriter(fileWriter);
+ for (String data : dataList) {
+ bw.write(data);
+ bw.newLine();
+ }
+ }
+ }
+
+ @Test
+ public void appendByCommonsIo() throws IOException {
+ File file = new File("/Users/darcy/wdbyte/test1.txt");
+ String content = "hello commons io,appendByCommonsIo";
+ FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8, true);
+ FileUtils.writeStringToFile(file, System.lineSeparator(), StandardCharsets.UTF_8, true);
+ }
+
+ @Test
+ public void appendListByCommonsIo() throws IOException {
+ File file = new File("/Users/darcy/wdbyte/test1.txt");
+ List dataList = new ArrayList<>();
+ dataList.add("line1 hello commons io,appendListByCommonsIo");
+ dataList.add("line2 hello commons io,appendListByCommonsIo");
+ dataList.add("line3 hello commons io,appendListByCommonsIo");
+ FileUtils.writeLines(file, dataList, true);
+ }
+
+ @Test
+ public void appendData(){
+ // 创建File对象指向要追加内容的文件
+ File file = new File("/Users/darcy/wdbyte/test1.txt");
+ // 要追加的内容
+ String contentToAppend = "Hello, Guava!";
+ try {
+ // 使用Guava的Files类以追加模式写入内容
+ com.google.common.io.Files.asCharSink(file, Charsets.UTF_8, FileWriteMode.APPEND)
+ .write(contentToAppend);
+
+ // 如果需要追加新行,可以使用下面的代码
+ com.google.common.io.Files.asCharSink(file, Charsets.UTF_8, com.google.common.io.FileWriteMode.APPEND)
+ .write(contentToAppend + System.lineSeparator());
+ System.out.println("Content appended successfully.");
+ } catch (IOException e) {
+ System.err.println("Error appending content to file: " + e.getMessage());
+ }
+ }
+
+ @Test
+ public void appendDataListByGuava(){
+ // 创建File对象指向要追加内容的文件
+ File file = new File("/Users/darcy/wdbyte/test1.txt");
+
+ // 要追加的内容
+ List dataList = new ArrayList<>();
+ dataList.add("line1 hello commons io,appendDataListByGuava");
+ dataList.add("line2 hello commons io,appendDataListByGuava");
+ dataList.add("line3 hello commons io,appendDataListByGuava");
+
+ try {
+ // 使用Guava的Files类以追加模式写入内容
+ com.google.common.io.Files.asCharSink(file, Charsets.UTF_8, FileWriteMode.APPEND)
+ .writeLines(dataList);
+
+ System.out.println("Content appended successfully.");
+ } catch (IOException e) {
+ System.err.println("Error appending content to file: " + e.getMessage());
+ }
+ }
+
+
+
+
+}
diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java
new file mode 100644
index 0000000..158942d
--- /dev/null
+++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java
@@ -0,0 +1,108 @@
+package com.wdbyte.io.file;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2023/11/06
+ */
+public class FileCreateAndWriteDemo {
+
+ @Test
+ public void test0() throws IOException {
+ String content = "www.wdbyte.com,java 7";
+ String filePath = "/Users/darcy/wdbyte/test0.txt";
+ try (FileWriter fw = new FileWriter(filePath);
+ BufferedWriter bw = new BufferedWriter(fw)) {
+ bw.write(content);
+ bw.newLine();
+ }
+
+ // 追加模式
+ try (FileWriter fw = new FileWriter(filePath, true);
+ BufferedWriter bw = new BufferedWriter(fw)) {
+ bw.write(content);
+ bw.newLine();
+ }
+ }
+
+ /**
+ * JDK 7
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test1() throws IOException {
+ String content = "www.wdbyte.com, java 7";
+ Path path = Paths.get("/Users/darcy/wdbyte/test1.txt");
+ // string -> bytes
+ Files.write(path, content.getBytes(StandardCharsets.UTF_8));
+ }
+
+ /**
+ * JDK 7 追加
+ * @throws IOException
+ */
+ @Test
+ public void test2() throws IOException {
+ String content = "www.wdbyte.com,java 7" + System.lineSeparator();
+ Path path = Paths.get("/Users/darcy/wdbyte/test2.txt");
+ Files.write(path, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE,
+ StandardOpenOption.APPEND);
+ Files.write(path, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE,
+ StandardOpenOption.APPEND);
+ }
+
+ /**
+ * JDK 7 写入 List
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test3() throws IOException {
+ List dataList = Arrays.asList("www", "wdbyte", "com", "java 7");
+ Path path = Paths.get("/Users/darcy/wdbyte/test3.txt");
+ Files.write(path, dataList);
+ }
+
+ @Test
+ public void test4() throws IOException {
+ String content = "www.wdbyte.com,java 7";
+ Path path = Paths.get("/Users/darcy/wdbyte/test4.txt");
+ // 默认 utf_8
+ try (BufferedWriter bw = Files.newBufferedWriter(path)) {
+ bw.write(content);
+ bw.newLine();
+ }
+ // 追加模式
+ try (BufferedWriter bw = Files.newBufferedWriter(path,
+ StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
+ bw.write(content);
+ bw.newLine();
+ }
+ }
+
+ /**
+ * JDK 11
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test5() throws IOException {
+ Path path = Paths.get("/Users/darcy/wdbyte/test5.txt");
+ Files.writeString(path, "www.wdbyte.com,java 11");
+ }
+
+
+}
diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java
new file mode 100644
index 0000000..f865382
--- /dev/null
+++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java
@@ -0,0 +1,73 @@
+package com.wdbyte.io.file;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2023/12/18
+ */
+public class FileDelete {
+
+ @Test
+ public void deleteFile() {
+ Path path = Paths.get("/Users/darcy/wdbyte/test.txt");
+ try {
+ Files.delete(path);
+ System.out.println("文件删除成功");
+ } catch (Exception e) {
+ System.out.println("文件删除失败");
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void deleteIfExists() {
+ Path path = Paths.get("/Users/darcy/wdbyte/test.txt");
+ try {
+ boolean deleted = Files.deleteIfExists(path);
+ if (deleted) {
+ System.out.println("文件删除成功");
+ } else {
+ System.out.println("文件不存在或者无法删除");
+ }
+ } catch (Exception e) {
+ System.out.println("文件删除失败");
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void delete() {
+ File file = new File("/Users/darcy/wdbyte/test.txt");
+ if (file.delete()) {
+ System.out.println("文件删除成功");
+ } else {
+ System.out.println("文件删除失败");
+ }
+ }
+
+ @Test
+ public void deleteOnExit() {
+ File file = new File("/Users/darcy/wdbyte/test.txt");
+ file.deleteOnExit();
+ }
+
+ @Test
+ public void forceDelete() {
+ File file = new File("/Users/darcy/wdbyte/test.txt");
+ try {
+ FileUtils.forceDelete(file);
+ System.out.println("文件删除成功");
+ } catch (Exception e) {
+ System.out.println("文件删除失败");
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java
new file mode 100644
index 0000000..276e0e8
--- /dev/null
+++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java
@@ -0,0 +1,178 @@
+package com.wdbyte.io.file;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Scanner;
+import java.util.stream.Stream;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author www.wdbyte.com
+ * @date 2023/11/08
+ */
+public class FileReadDemo {
+
+ public static void main(String[] args) {
+ Path path = Paths.get("/Users/darcy/wdbyte");
+ System.out.println(Files.isDirectory(path));
+ System.out.println(path.toFile().isFile());
+ System.out.println(path.toFile().isDirectory());
+ }
+
+ /**
+ * java 8
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test1() throws IOException {
+ Path path = Paths.get("/Users/darcy/wdbyte/log.txt");
+ List lines = Files.readAllLines(path, StandardCharsets.UTF_8);
+ for (String line : lines) {
+ System.out.println(line);
+ }
+ }
+
+ /**
+ * java 8
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test2() throws IOException {
+ Path path = Paths.get("/Users/darcy/wdbyte/log.txt");
+ byte[] bytes = Files.readAllBytes(path);
+ String content = new String(bytes, StandardCharsets.UTF_8);
+ System.out.println(content);
+ }
+
+ /**
+ * java 8
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test3() throws IOException, InterruptedException {
+ Path path = Paths.get("/Users/darcy/wdbyte/log.txt");
+ try (Stream stream = Files.lines(path);) {
+ stream.forEach(System.out::println);
+ }
+ try (Stream stream = Files.lines(path);) {
+ stream.parallel().forEachOrdered(System.out::println);
+ }
+ }
+
+ /**
+ * java 11
+ * 要求:文件小于2G
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test4() throws IOException {
+ Path path = Paths.get("/Users/darcy/wdbyte/log.txt");
+ String content = Files.readString(path, StandardCharsets.UTF_8);
+ System.out.println(content);
+ }
+
+ @Test
+ public void test5() {
+ File file = new File("/Users/darcy/wdbyte/log.txt");
+ String line;
+ // 默认读取缓冲区大小 8K
+ try (BufferedReader br = new BufferedReader(new FileReader(file))) {
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ // 定义缓冲区大小为 128 K
+ int buffer = 128 * 1024;
+ try (BufferedReader br = new BufferedReader(new FileReader(file), buffer)) {
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * java 8
+ *
+ * @throws IOException
+ */
+ @Test
+ public void test6() throws IOException {
+ Path path = Paths.get("/Users/darcy/wdbyte/log.txt");
+ String line;
+ try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8);) {
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ }
+ }
+
+ /**
+ * 自古以来
+ */
+ @Test
+ public void test7() {
+ File file = new File("/Users/darcy/wdbyte/log.txt");
+ try (Scanner sc = new Scanner(new FileReader(file))) {
+ while (sc.hasNextLine()) {
+ String line = sc.nextLine();
+ System.out.println(line);
+ }
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 自古以来
+ */
+ @Test
+ public void test8() {
+ File file = new File("/Users/darcy/wdbyte/log.txt");
+ try (FileInputStream fis = new FileInputStream(file);
+ BufferedInputStream bis = new BufferedInputStream(fis);) {
+
+ StringBuilder content = new StringBuilder();
+ int data;
+ while ((data = bis.read()) != -1) {
+ content.append((char)data);
+ }
+ System.out.println(content.toString());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void readFileByApacheCommons() throws IOException {
+ File file = new File("/Users/darcy/wdbyte/test.txt");
+ List list = FileUtils.readLines(file, StandardCharsets.UTF_8);
+ for (String data : list) {
+ System.out.println(data);
+ }
+ }
+}
diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml
index ed84483..b3a49a2 100644
--- a/core-java-modules/pom.xml
+++ b/core-java-modules/pom.xml
@@ -33,4 +33,13 @@
core-java-os
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.9.1
+
+
+
+
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 0e2c7f6..b55b3df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,10 +45,5 @@
commons-lang3
${commons-lang3.version}
-
- org.projectlombok
- lombok
- ${lombok.version}
-
diff --git a/spring-ai/spring-mcp-server-manual/.mvn/wrapper/maven-wrapper.properties b/spring-ai/spring-mcp-server-manual/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..8dea6c2
--- /dev/null
+++ b/spring-ai/spring-mcp-server-manual/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,3 @@
+wrapperVersion=3.3.4
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip
diff --git a/spring-ai/spring-mcp-server-manual/README.md b/spring-ai/spring-mcp-server-manual/README.md
new file mode 100644
index 0000000..af5c1b2
--- /dev/null
+++ b/spring-ai/spring-mcp-server-manual/README.md
@@ -0,0 +1,26 @@
+这个基于 Spring Boot 和 Fastjson 的 MCP(Model Context Protocol)服务实现,是一个遵循 JSON-RPC 2.0 规范的轻量级工具服务端,旨在为大语言模型(LLM)提供标准化的外部能力接口 36。
+
+以下是该实现的核心架构与技术特点介绍:
+
+### 1. 核心协议架构
+该实现采用了 MCP 的 Streamable HTTP 传输机制,通过单一的 HTTP POST 端点(/mcp)处理所有交互逻辑 26。
+
+消息格式:所有请求和响应均严格遵循 JSON-RPC 2.0 结构,包含 jsonrpc、id、method 和 params 字段 6。
+生命周期管理:手动实现了 MCP 协议定义的完整链路,包括初始化握手(initialize)、初始化完成通知(notifications/initialized)、工具发现(tools/list)以及工具执行(tools/call) 56。
+### 2. Java 21 技术优化
+利用 Java 21 的现代语法特性极大简化了协议模版代码:
+
+Record 记录类:使用 record 定义 JsonRpcRequest、Tool 和 ToolCallResult 等数据模型。这消除了 Getter/Setter 等样板代码,确保了消息对象的不可变性,并自动支持 JSON 序列化。
+Switch 表达式:在控制器中使用增强的 switch 表达式处理 method 分发。这种方式比传统的 if-else 更具读性,且利用 yield 关键字实现了逻辑的紧凑闭环。
+文本块(Text Blocks):利用 """ 语法定义工具的 JSON Schema。这使得复杂的输入参数描述(如 inputSchema)在代码中能以原始 JSON 格式直观呈现,便于维护 5。
+### 3. 工具定义与执行逻辑
+该服务模拟了一个名为 getWeather 的城市天气查询工具:
+
+工具发现:在 tools/list 阶段,服务端会返回该工具的名称、描述以及基于 JSON Schema 的参数定义(要求必填 city 字符串),以便 LLM 理解如何调用该工具 56。
+参数解析:通过 Fastjson 的 JSONObject 直接处理动态参数。在 tools/call 触发时,程序会从 arguments 映射中提取城市名称,并返回标准化的内容结构。
+响应规范:响应体封装在 content 数组中,并包含 isError 标识,这符合 MCP 对工具执行结果的标准化要求 6。
+### 4. 最佳实践体现
+轻量化:不依赖于复杂的 MCP 官方 SDK,仅通过 Spring Boot 基础框架和 Fastjson 实现,适合快速集成到现有生产微服务中 2。
+无状态处理:服务设计为无状态,符合 MCP Streamable HTTP 的简化模式,便于水平扩展。
+错误处理基础:虽然为简易版,但结构上预留了 isError 字段,允许在工具内部出错时让 LLM 感知并尝试自我修正 6。
+这种实现方式展示了如何通过极简的代码量构建符合开放协议标准的 AI 插件系统,降低了 LLM 与私有数据源及外部工具对接的复杂度 13。
\ No newline at end of file
diff --git a/spring-ai/spring-mcp-server-manual/mvnw b/spring-ai/spring-mcp-server-manual/mvnw
new file mode 100755
index 0000000..bd8896b
--- /dev/null
+++ b/spring-ai/spring-mcp-server-manual/mvnw
@@ -0,0 +1,295 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.3.4
+#
+# Optional ENV vars
+# -----------------
+# JAVA_HOME - location of a JDK home dir, required when download maven via java source
+# MVNW_REPOURL - repo url base for downloading maven distribution
+# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
+# ----------------------------------------------------------------------------
+
+set -euf
+[ "${MVNW_VERBOSE-}" != debug ] || set -x
+
+# OS specific support.
+native_path() { printf %s\\n "$1"; }
+case "$(uname)" in
+CYGWIN* | MINGW*)
+ [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
+ native_path() { cygpath --path --windows "$1"; }
+ ;;
+esac
+
+# set JAVACMD and JAVACCMD
+set_java_home() {
+ # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
+ if [ -n "${JAVA_HOME-}" ]; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ]; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACCMD="$JAVA_HOME/jre/sh/javac"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ JAVACCMD="$JAVA_HOME/bin/javac"
+
+ if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
+ echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
+ echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
+ return 1
+ fi
+ fi
+ else
+ JAVACMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v java
+ )" || :
+ JAVACCMD="$(
+ 'set' +e
+ 'unset' -f command 2>/dev/null
+ 'command' -v javac
+ )" || :
+
+ if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
+ echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
+ return 1
+ fi
+ fi
+}
+
+# hash string like Java String::hashCode
+hash_string() {
+ str="${1:-}" h=0
+ while [ -n "$str" ]; do
+ char="${str%"${str#?}"}"
+ h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
+ str="${str#?}"
+ done
+ printf %x\\n $h
+}
+
+verbose() { :; }
+[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
+
+die() {
+ printf %s\\n "$1" >&2
+ exit 1
+}
+
+trim() {
+ # MWRAPPER-139:
+ # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
+ # Needed for removing poorly interpreted newline sequences when running in more
+ # exotic environments such as mingw bash on Windows.
+ printf "%s" "${1}" | tr -d '[:space:]'
+}
+
+scriptDir="$(dirname "$0")"
+scriptName="$(basename "$0")"
+
+# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
+while IFS="=" read -r key value; do
+ case "${key-}" in
+ distributionUrl) distributionUrl=$(trim "${value-}") ;;
+ distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
+ esac
+done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties"
+[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+
+case "${distributionUrl##*/}" in
+maven-mvnd-*bin.*)
+ MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
+ case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
+ *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
+ :Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
+ :Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
+ :Linux*x86_64*) distributionPlatform=linux-amd64 ;;
+ *)
+ echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
+ distributionPlatform=linux-amd64
+ ;;
+ esac
+ distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
+ ;;
+maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
+*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
+esac
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
+distributionUrlName="${distributionUrl##*/}"
+distributionUrlNameMain="${distributionUrlName%.*}"
+distributionUrlNameMain="${distributionUrlNameMain%-bin}"
+MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
+MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
+
+exec_maven() {
+ unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
+ exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
+}
+
+if [ -d "$MAVEN_HOME" ]; then
+ verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ exec_maven "$@"
+fi
+
+case "${distributionUrl-}" in
+*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
+*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
+esac
+
+# prepare tmp dir
+if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
+ clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
+ trap clean HUP INT TERM EXIT
+else
+ die "cannot create temp dir"
+fi
+
+mkdir -p -- "${MAVEN_HOME%/*}"
+
+# Download and Install Apache Maven
+verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+verbose "Downloading from: $distributionUrl"
+verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+# select .zip or .tar.gz
+if ! command -v unzip >/dev/null; then
+ distributionUrl="${distributionUrl%.zip}.tar.gz"
+ distributionUrlName="${distributionUrl##*/}"
+fi
+
+# verbose opt
+__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
+[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
+
+# normalize http auth
+case "${MVNW_PASSWORD:+has-password}" in
+'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
+esac
+
+if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
+ verbose "Found wget ... using wget"
+ wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
+elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
+ verbose "Found curl ... using curl"
+ curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
+elif set_java_home; then
+ verbose "Falling back to use Java to download"
+ javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
+ targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
+ cat >"$javaSource" <<-END
+ public class Downloader extends java.net.Authenticator
+ {
+ protected java.net.PasswordAuthentication getPasswordAuthentication()
+ {
+ return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
+ }
+ public static void main( String[] args ) throws Exception
+ {
+ setDefault( new Downloader() );
+ java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
+ }
+ }
+ END
+ # For Cygwin/MinGW, switch paths to Windows format before running javac and java
+ verbose " - Compiling Downloader.java ..."
+ "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
+ verbose " - Running Downloader.java ..."
+ "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
+fi
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+if [ -n "${distributionSha256Sum-}" ]; then
+ distributionSha256Result=false
+ if [ "$MVN_CMD" = mvnd.sh ]; then
+ echo "Checksum validation is not supported for maven-mvnd." >&2
+ echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ elif command -v sha256sum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ elif command -v shasum >/dev/null; then
+ if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
+ distributionSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
+ echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
+ exit 1
+ fi
+ if [ $distributionSha256Result = false ]; then
+ echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
+ echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+# unzip and move
+if command -v unzip >/dev/null; then
+ unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
+else
+ tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
+fi
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+actualDistributionDir=""
+
+# First try the expected directory name (for regular distributions)
+if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then
+ if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$distributionUrlNameMain"
+ fi
+fi
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if [ -z "$actualDistributionDir" ]; then
+ # enable globbing to iterate over items
+ set +f
+ for dir in "$TMP_DOWNLOAD_DIR"/*; do
+ if [ -d "$dir" ]; then
+ if [ -f "$dir/bin/$MVN_CMD" ]; then
+ actualDistributionDir="$(basename "$dir")"
+ break
+ fi
+ fi
+ done
+ set -f
+fi
+
+if [ -z "$actualDistributionDir" ]; then
+ verbose "Contents of $TMP_DOWNLOAD_DIR:"
+ verbose "$(ls -la "$TMP_DOWNLOAD_DIR")"
+ die "Could not find Maven distribution directory in extracted archive"
+fi
+
+verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url"
+mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
+
+clean || :
+exec_maven "$@"
diff --git a/spring-ai/spring-mcp-server-manual/mvnw.cmd b/spring-ai/spring-mcp-server-manual/mvnw.cmd
new file mode 100644
index 0000000..92450f9
--- /dev/null
+++ b/spring-ai/spring-mcp-server-manual/mvnw.cmd
@@ -0,0 +1,189 @@
+<# : batch portion
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.3.4
+@REM
+@REM Optional ENV vars
+@REM MVNW_REPOURL - repo url base for downloading maven distribution
+@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
+@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
+@REM ----------------------------------------------------------------------------
+
+@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
+@SET __MVNW_CMD__=
+@SET __MVNW_ERROR__=
+@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
+@SET PSModulePath=
+@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
+ IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
+)
+@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
+@SET __MVNW_PSMODULEP_SAVE=
+@SET __MVNW_ARG0_NAME__=
+@SET MVNW_USERNAME=
+@SET MVNW_PASSWORD=
+@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*)
+@echo Cannot start maven from wrapper >&2 && exit /b 1
+@GOTO :EOF
+: end batch / begin powershell #>
+
+$ErrorActionPreference = "Stop"
+if ($env:MVNW_VERBOSE -eq "true") {
+ $VerbosePreference = "Continue"
+}
+
+# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
+$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
+if (!$distributionUrl) {
+ Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
+}
+
+switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
+ "maven-mvnd-*" {
+ $USE_MVND = $true
+ $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
+ $MVN_CMD = "mvnd.cmd"
+ break
+ }
+ default {
+ $USE_MVND = $false
+ $MVN_CMD = $script -replace '^mvnw','mvn'
+ break
+ }
+}
+
+# apply MVNW_REPOURL and calculate MAVEN_HOME
+# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/
+if ($env:MVNW_REPOURL) {
+ $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" }
+ $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')"
+}
+$distributionUrlName = $distributionUrl -replace '^.*/',''
+$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
+
+$MAVEN_M2_PATH = "$HOME/.m2"
+if ($env:MAVEN_USER_HOME) {
+ $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME"
+}
+
+if (-not (Test-Path -Path $MAVEN_M2_PATH)) {
+ New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null
+}
+
+$MAVEN_WRAPPER_DISTS = $null
+if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) {
+ $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists"
+} else {
+ $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists"
+}
+
+$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain"
+$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
+$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
+
+if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
+ Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
+ Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
+ exit $?
+}
+
+if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
+ Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
+}
+
+# prepare tmp dir
+$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
+$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
+$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
+trap {
+ if ($TMP_DOWNLOAD_DIR.Exists) {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+ }
+}
+
+New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
+
+# Download and Install Apache Maven
+Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
+Write-Verbose "Downloading from: $distributionUrl"
+Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
+
+$webclient = New-Object System.Net.WebClient
+if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
+ $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
+}
+[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
+$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
+
+# If specified, validate the SHA-256 sum of the Maven distribution zip file
+$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
+if ($distributionSha256Sum) {
+ if ($USE_MVND) {
+ Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
+ }
+ Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
+ if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
+ Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
+ }
+}
+
+# unzip and move
+Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
+
+# Find the actual extracted directory name (handles snapshots where filename != directory name)
+$actualDistributionDir = ""
+
+# First try the expected directory name (for regular distributions)
+$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain"
+$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD"
+if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) {
+ $actualDistributionDir = $distributionUrlNameMain
+}
+
+# If not found, search for any directory with the Maven executable (for snapshots)
+if (!$actualDistributionDir) {
+ Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object {
+ $testPath = Join-Path $_.FullName "bin/$MVN_CMD"
+ if (Test-Path -Path $testPath -PathType Leaf) {
+ $actualDistributionDir = $_.Name
+ }
+ }
+}
+
+if (!$actualDistributionDir) {
+ Write-Error "Could not find Maven distribution directory in extracted archive"
+}
+
+Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir"
+Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null
+try {
+ Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
+} catch {
+ if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
+ Write-Error "fail to move MAVEN_HOME"
+ }
+} finally {
+ try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
+ catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
+}
+
+Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
diff --git a/spring-ai/spring-mcp-server-manual/pom.xml b/spring-ai/spring-mcp-server-manual/pom.xml
new file mode 100644
index 0000000..1e9f19e
--- /dev/null
+++ b/spring-ai/spring-mcp-server-manual/pom.xml
@@ -0,0 +1,42 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.0.1
+
+
+ com.wdbyte.ai.mcp.manual
+ spring-mcp-server-manual
+ 0.0.1-SNAPSHOT
+ spring-mcp-server-manual
+ spring-mcp-server-manual
+
+ 21
+
+
+
+ org.springframework.boot
+ spring-boot-starter-webmvc
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ 2.0.60
+ compile
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/McpWeatherController.java b/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/McpWeatherController.java
new file mode 100644
index 0000000..76f2cb8
--- /dev/null
+++ b/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/McpWeatherController.java
@@ -0,0 +1,119 @@
+package com.wdbyte.ai.mcp.manual;
+
+import java.util.List;
+import java.util.Map;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+import com.alibaba.fastjson2.JSONWriter.Feature;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/mcp")
+public class McpWeatherController {
+
+ private static final Logger log = LoggerFactory.getLogger(McpWeatherController.class);
+
+ // 1. 静态化工具定义,使 tools/list 极其简洁
+ private static final List AVAILABLE_TOOLS = List.of(
+ new Tool("getWeather", "获取指定城市的天气预报",
+ JSONObject.parseObject("""
+ {
+ "type": "object",
+ "properties": { "city": { "type": "string", "description": "城市名" } },
+ "required": ["city"],
+ "additionalProperties": false
+ }
+ """))
+ );
+
+ @PostMapping(consumes = "application/json", produces = "application/json")
+ public ResponseEntity> handleMcpRequest(@RequestBody JsonRpcRequest request) {
+ Object id = request.id();
+
+ var response = switch (request.method()) {
+ case "initialize" -> ok(id, new InitializeResult());
+ case "notifications/initialized" -> accepted();
+ case "ping" -> ok(id, Map.of());
+ case "tools/list" -> ok(id, Map.of("tools", AVAILABLE_TOOLS));
+ case "tools/call" -> handleToolCall(id, request.params());
+ default -> ResponseEntity.notFound().build();
+ };
+ log.info("\nrequest: {}\nresponse: {}", JSON.toJSONString(request), JSON.toJSONString(response.getBody()));
+ return response;
+ }
+
+ /**
+ * 优雅处理工具调用:直接通过 JSONObject 转换,无需 String 二次中转
+ */
+ private ResponseEntity> handleToolCall(Object id, JSONObject params) {
+ if (params == null) return badRequest();
+
+ var callParams = params.toJavaObject(ToolCallParams.class);
+
+ // 使用 switch 处理多工具扩展性更好
+ return switch (callParams.name()) {
+ case "getWeather" -> {
+ String city = String.valueOf(callParams.arguments().getOrDefault("city", "未知城市"));
+ yield ok(id, new ToolCallResult(city + "今日雷暴雨,建议居家"));
+ }
+ default -> badRequest();
+ };
+ }
+
+ // --- 辅助方法 ---
+ private static ResponseEntity ok(Object id, Object result) {
+ return ResponseEntity.ok(new JsonRpcResponse(id, result));
+ }
+
+ private static ResponseEntity accepted() {
+ return ResponseEntity.status(202).build();
+ }
+
+ private static ResponseEntity badRequest() {
+ return ResponseEntity.badRequest().build();
+ }
+
+ // --- MCP 协议 Records (Java 21) ---
+
+ // 将 params 定义为 JSONObject,方便后续 toJavaObject 转换
+ public record JsonRpcRequest(String jsonrpc, Object id, String method, JSONObject params) {}
+
+ public record JsonRpcResponse(String jsonrpc, Object id, Object result) {
+ public JsonRpcResponse(Object id, Object result) {
+ this("2.0", id, result);
+ }
+ }
+
+ // 初始化结果模型
+ public record InitializeResult(String protocolVersion, Capabilities capabilities, ServerInfo serverInfo) {
+ public InitializeResult() {
+ this("2025-06-18", new Capabilities(new Tools(false)), new ServerInfo("mcp-weather-server", "1.0.0"));
+ }
+ }
+
+ public record ServerInfo(String name, String version) {}
+ public record Capabilities(Tools tools) {}
+ public record Tools(boolean listChanged) {}
+
+ // 工具定义模型
+ public record Tool(String name, String description, Object inputSchema) {}
+
+ // 工具调用参数模型
+ public record ToolCallParams(String name, Map arguments) {}
+
+ // 响应内容模型
+ public record Content(String type, String text) {
+ public Content(String text) { this("text", text); }
+ }
+
+ public record ToolCallResult(List content, boolean isError) {
+ public ToolCallResult(String text) {
+ this(List.of(new Content(text)), false);
+ }
+ }
+}
diff --git a/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/SpringMcpServerManualApplication.java b/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/SpringMcpServerManualApplication.java
new file mode 100644
index 0000000..48f4ffe
--- /dev/null
+++ b/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/SpringMcpServerManualApplication.java
@@ -0,0 +1,13 @@
+package com.wdbyte.ai.mcp.manual;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SpringMcpServerManualApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringMcpServerManualApplication.class, args);
+ }
+
+}
diff --git a/spring-ai/spring-mcp-server-manual/src/main/resources/application.properties b/spring-ai/spring-mcp-server-manual/src/main/resources/application.properties
new file mode 100644
index 0000000..bbc9400
--- /dev/null
+++ b/spring-ai/spring-mcp-server-manual/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.application.name=spring-mcp-server-manual
diff --git a/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.jar b/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..cb28b0e
Binary files /dev/null and b/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.properties b/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..5f0536e
--- /dev/null
+++ b/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/springboot/springboot-hello/README.md b/springboot/springboot-hello/README.md
new file mode 100644
index 0000000..c038db4
--- /dev/null
+++ b/springboot/springboot-hello/README.md
@@ -0,0 +1 @@
+# Srping Boot 简单 Web 接口
diff --git a/springboot/springboot-hello/mvnw b/springboot/springboot-hello/mvnw
new file mode 100755
index 0000000..66df285
--- /dev/null
+++ b/springboot/springboot-hello/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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
+#
+# https://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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/springboot/springboot-hello/mvnw.cmd b/springboot/springboot-hello/mvnw.cmd
new file mode 100644
index 0000000..95ba6f5
--- /dev/null
+++ b/springboot/springboot-hello/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/springboot/springboot-hello/pom.xml b/springboot/springboot-hello/pom.xml
new file mode 100644
index 0000000..343a330
--- /dev/null
+++ b/springboot/springboot-hello/pom.xml
@@ -0,0 +1,35 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.3
+
+
+ com.wdbyte
+ springboot-hello
+ 0.0.1-SNAPSHOT
+ springboot-hello
+ Demo project for Spring Boot
+
+ 21
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/springboot/springboot-hello/src/main/java/com/wdbyte/start/HelloController.java b/springboot/springboot-hello/src/main/java/com/wdbyte/start/HelloController.java
new file mode 100644
index 0000000..e887c46
--- /dev/null
+++ b/springboot/springboot-hello/src/main/java/com/wdbyte/start/HelloController.java
@@ -0,0 +1,20 @@
+package com.wdbyte.start;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@RestController
+public class HelloController {
+
+ @GetMapping("/hello")
+ public String hello(String username) {
+ if (username == null) {
+ return "Hello,Who are you?";
+ }
+ return "Hello," + username;
+ }
+
+}
diff --git a/springboot/springboot-hello/src/main/java/com/wdbyte/start/SpringBootApp.java b/springboot/springboot-hello/src/main/java/com/wdbyte/start/SpringBootApp.java
new file mode 100644
index 0000000..6f46204
--- /dev/null
+++ b/springboot/springboot-hello/src/main/java/com/wdbyte/start/SpringBootApp.java
@@ -0,0 +1,16 @@
+package com.wdbyte.start;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@SpringBootApplication
+public class SpringBootApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootApp.class, args);
+ }
+
+}
diff --git a/springboot/springboot-hello/src/main/resources/application.properties b/springboot/springboot-hello/src/main/resources/application.properties
new file mode 100644
index 0000000..a3ac65c
--- /dev/null
+++ b/springboot/springboot-hello/src/main/resources/application.properties
@@ -0,0 +1 @@
+server.port=8080
\ No newline at end of file
diff --git a/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.jar b/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..cb28b0e
Binary files /dev/null and b/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.properties b/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..5f0536e
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/springboot/springboot-sqlite-jpa/README.md b/springboot/springboot-sqlite-jpa/README.md
new file mode 100644
index 0000000..5be62f4
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/README.md
@@ -0,0 +1,42 @@
+# Srping Boot + JPA + Sqlite
+
+使用 **Spring Boot** 可以快速的创建一个基于Spring 的、独立的、生产级的应用程序,并且可以直接运行。Spring Boot 采用习惯性配置,整合大量 Spring 组建和第三方库,让你只需要少量的修改就可以轻松上手。
+
+- [Spring Boot 系列(一)Spring Boot 入门篇](https://www.wdbyte.com/2019/01/springboot/springboot01-quick-start/)
+- [Spring Boot 系列(二)Spring Boot 配置文件](https://www.wdbyte.com/2019/01/springboot/springboot01-config/)
+- [Spring Boot 系列(三)Spring Boot 自动配置](https://www.wdbyte.com/2019/01/springboot/springboot03-auto-config/)
+- [Spring Boot 系列(四)Spring Boot 日志框架](https://www.wdbyte.com/2019/01/springboot/springboot04-log/)
+- [Spring Boot 系列(五)Web 开发之静态资源和模版引擎](https://www.wdbyte.com/2019/02/springboot/springboot-05-web-static-template/)
+- [Spring Boot 系列(六)Web 开发之拦截器和三大组件](https://www.wdbyte.com/2019/02/springboot/springboot-06-web-filter-apo-webbase/)
+- [Spring Boot 系列(七)Web 开发之异常错误处理机制剖析](https://www.wdbyte.com/2019/02/springboot/springboot-07-web-exception/)
+- [Spring Boot 系列(八)动态 Banner 与图片转字符图案的手动实现](https://www.wdbyte.com/2019/02/springboot/springboot-08-banner/)
+- [Spring Boot 系列(九)使用 Spring JDBC 和 Druid 数据源监控](https://www.wdbyte.com/2019/02/springboot/springboot-09-data-jdbc/)
+- [Spring Boot 系列(十)使用 Spring data jpa 访问数据库](https://www.wdbyte.com/2019/03/springboot/springboot-10-data-jpa/)
+- [Spring Boot 系列(十一)使用 Mybatis(自动生成插件) 访问数据库](https://www.wdbyte.com/2019/03/springboot/springboot-11-data-mybatis/)
+- [Spring Boot 系列(十二)使用 Mybatis 集成 pagehelper 分页插件和 mapper 插件](https://www.wdbyte.com/2019/03/springboot/springboot-12-data-mybatis-page/)
+- [Spring Boot 系列(十三)使用邮件服务](https://www.wdbyte.com/2019/03/springboot/springboot-13-email/)
+- [Spring Boot 系列(十四)迅速启用 HTTPS 加密你的网站](https://www.wdbyte.com/2019/08/springboot/springboot-14-https/)
+- [Spring Boot 系列(十五)如何编写自己的 Springboot starter](https://www.wdbyte.com/2019/11/springboot/springboot-15-my-starter/)
+- [Spring Boot 系列(十六)你真的了解 Swagger 文档吗?](https://www.wdbyte.com/2019/11/springboot/springboot-16-web-swagger/)
+- [Spring Boot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序](https://www.wdbyte.com/2019/12/springboot/springboot-17-admin/)
+- [Spring Boot 系列(十八)最详细的 Spring Boot 多模块开发与排坑指南](https://www.wdbyte.com/2020/03/springboot/springboot-18-module/)
+- [Spring Boot 系列(十九)SpringBoot 的多数据源配置](https://www.wdbyte.com/2020/12/springboot/springboot-multiple-datasource/)
+- [Spring Boot 系列(二十)Spring Boot,JPA与SQLite 的快速启动](https://www.wdbyte.com/springboot/sqlite/)
+-
+### Reference Documentation
+For further reference, please consider the following sections:
+
+* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html)
+* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.2.3/maven-plugin/reference/html/)
+* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.2.3/maven-plugin/reference/html/#build-image)
+* [Spring Web](https://docs.spring.io/spring-boot/docs/3.2.3/reference/htmlsingle/index.html#web)
+* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.2.3/reference/htmlsingle/index.html#data.sql.jpa-and-spring-data)
+
+### Guides
+The following guides illustrate how to use some features concretely:
+
+* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
+* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/)
+* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/)
+* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/)
+
diff --git a/springboot/springboot-sqlite-jpa/mvnw b/springboot/springboot-sqlite-jpa/mvnw
new file mode 100755
index 0000000..66df285
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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
+#
+# https://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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/springboot/springboot-sqlite-jpa/mvnw.cmd b/springboot/springboot-sqlite-jpa/mvnw.cmd
new file mode 100644
index 0000000..95ba6f5
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/springboot/springboot-sqlite-jpa/pom.xml b/springboot/springboot-sqlite-jpa/pom.xml
new file mode 100644
index 0000000..47d85ed
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/pom.xml
@@ -0,0 +1,81 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.3
+
+
+ com.wdbyte
+ springboot-sqlite-jpa
+ 0.0.1-SNAPSHOT
+ springboot-sqlite-jpa
+ Demo project for Spring Boot
+
+ 21
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+ org.hibernate.orm
+ hibernate-community-dialects
+ 6.4.3.Final
+
+
+
+ org.xerial
+ sqlite-jdbc
+ 3.45.1.0
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.apache.commons
+ commons-lang3
+ 3.13.0
+
+
+ commons-codec
+ commons-codec
+ 1.16.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java
new file mode 100644
index 0000000..f54d63a
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java
@@ -0,0 +1,16 @@
+package com.wdbyte.springsqlite;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@SpringBootApplication
+public class SpringBootSqliteApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootSqliteApp.class, args);
+ }
+
+}
diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java
new file mode 100644
index 0000000..b78d228
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java
@@ -0,0 +1,68 @@
+package com.wdbyte.springsqlite.controller;
+
+import java.time.LocalDateTime;
+
+import com.wdbyte.springsqlite.model.WebsiteUser;
+import com.wdbyte.springsqlite.repository.WebsiteUserRepository;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@Slf4j
+@RestController
+public class SqliteController {
+
+ @Autowired
+ private WebsiteUserRepository userRepository;
+
+ @GetMapping("/sqlite/init")
+ public String init() {
+ for (int i = 0; i < 10; i++) {
+ WebsiteUser websiteUser = new WebsiteUser();
+ // 随机4个字母
+ websiteUser.setUsername(RandomStringUtils.randomAlphabetic(4));
+ // 随机16个字符用于密码加盐加密
+ websiteUser.setSalt(RandomStringUtils.randomAlphanumeric(16));
+ String password = "123456";
+ // 密码存储 = md5(密码+盐)
+ password = password + websiteUser.getSalt();
+ websiteUser.setPassword(DigestUtils.md5Hex(password));
+ websiteUser.setCreatedAt(LocalDateTime.now());
+ websiteUser.setUpdatedAt(LocalDateTime.now());
+ websiteUser.setStatus("active");
+ WebsiteUser saved = userRepository.save(websiteUser);
+ log.info("init user {}", saved.getUsername());
+ }
+ return "init success";
+ }
+
+ @GetMapping("/sqlite/find")
+ public String findByUsername(String username) {
+ WebsiteUser websiteUser = userRepository.findByUsername(username);
+ if (websiteUser == null) {
+ return null;
+ }
+ return websiteUser.toString();
+ }
+
+ @GetMapping("/sqlite/login")
+ public String findByUsername(String username, String password) {
+ WebsiteUser websiteUser = userRepository.findByUsername(username);
+ if (websiteUser == null) {
+ return "login failed";
+ }
+ password = password + websiteUser.getSalt();
+ if (StringUtils.equals(DigestUtils.md5Hex(password), websiteUser.getPassword())) {
+ return "login succeeded";
+ } else {
+ return "login failed";
+ }
+ }
+}
diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/model/WebsiteUser.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/model/WebsiteUser.java
new file mode 100644
index 0000000..264eec0
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/model/WebsiteUser.java
@@ -0,0 +1,43 @@
+package com.wdbyte.springsqlite.model;
+
+import java.time.LocalDateTime;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+@Entity
+@Getter
+@Setter
+@ToString
+@Table(name = "website_user")
+public class WebsiteUser {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Integer id;
+
+ @Column(name = "username", nullable = false, unique = true, length = 64)
+ private String username;
+
+ @Column(name = "password", nullable = false, length = 255)
+ private String password;
+
+ @Column(name = "salt", nullable = false, length = 16)
+ private String salt;
+
+ @Column(name = "status", nullable = false, length = 16, columnDefinition = "VARCHAR(16) DEFAULT 'active'")
+ private String status;
+
+ @Column(name = "created_at", nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
+ private LocalDateTime createdAt;
+
+ @Column(name = "updated_at", nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
+ private LocalDateTime updatedAt;
+}
diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java
new file mode 100644
index 0000000..12b6af0
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java
@@ -0,0 +1,16 @@
+package com.wdbyte.springsqlite.repository;
+
+import com.wdbyte.springsqlite.model.WebsiteUser;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Repository;
+
+@Repository
+public interface WebsiteUserRepository extends CrudRepository {
+
+ /**
+ * 根据 username 查询数据
+ * @param name
+ * @return
+ */
+ WebsiteUser findByUsername(String name);
+}
\ No newline at end of file
diff --git a/springboot/springboot-sqlite-jpa/src/main/resources/application.properties b/springboot/springboot-sqlite-jpa/src/main/resources/application.properties
new file mode 100644
index 0000000..3ee254a
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/src/main/resources/application.properties
@@ -0,0 +1,7 @@
+spring.datasource.url=jdbc:sqlite:springboot-sqlite-jpa.db
+spring.datasource.driver-class-name=org.sqlite.JDBC
+# JPA Properties
+spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
+# create ?????????update?????????
+spring.jpa.hibernate.ddl-auto=update
+spring.jpa.show-sql=true
\ No newline at end of file
diff --git a/springboot/springboot-sqlite-jpa/src/test/java/com/wdbyte/springbootsqlitejpa/SpringbootSqliteJpaApplicationTests.java b/springboot/springboot-sqlite-jpa/src/test/java/com/wdbyte/springbootsqlitejpa/SpringbootSqliteJpaApplicationTests.java
new file mode 100644
index 0000000..7d48850
--- /dev/null
+++ b/springboot/springboot-sqlite-jpa/src/test/java/com/wdbyte/springbootsqlitejpa/SpringbootSqliteJpaApplicationTests.java
@@ -0,0 +1,13 @@
+package com.wdbyte.springbootsqlitejpa;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class SpringbootSqliteJpaApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.jar b/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..cb28b0e
Binary files /dev/null and b/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.properties b/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..5f0536e
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/springboot/springboot-weixin-qrcode-login/README.md b/springboot/springboot-weixin-qrcode-login/README.md
new file mode 100644
index 0000000..30d0f64
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/README.md
@@ -0,0 +1 @@
+# Srping Boot 微信扫码登录示例
diff --git a/springboot/springboot-weixin-qrcode-login/mvnw b/springboot/springboot-weixin-qrcode-login/mvnw
new file mode 100755
index 0000000..66df285
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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
+#
+# https://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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/springboot/springboot-weixin-qrcode-login/mvnw.cmd b/springboot/springboot-weixin-qrcode-login/mvnw.cmd
new file mode 100644
index 0000000..95ba6f5
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/springboot/springboot-weixin-qrcode-login/pom.xml b/springboot/springboot-weixin-qrcode-login/pom.xml
new file mode 100644
index 0000000..4c1a316
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/pom.xml
@@ -0,0 +1,98 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.3
+
+
+ com.wdbyte
+ springboot-weixin-qrcode-login
+ 0.0.1-SNAPSHOT
+ springboot-weixin-qrcode-login
+ Demo project for Spring Boot
+
+ 21
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+ com.fasterxml.jackson.dataformat
+ jackson-dataformat-xml
+ 2.15.2
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.14.0
+
+
+
+ commons-codec
+ commons-codec
+ 1.16.0
+
+
+
+
+ org.apache.httpcomponents.client5
+ httpclient5
+ 5.1.3
+
+
+
+ org.apache.httpcomponents.client5
+ httpclient5-fluent
+ 5.1.3
+
+
+
+ com.alibaba.fastjson2
+ fastjson2
+ 2.0.45
+
+
+
+ com.google.guava
+ guava
+ 33.0.0-jre
+
+
+
+
+ com.auth0
+ java-jwt
+ 4.4.0
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java
new file mode 100644
index 0000000..5720502
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java
@@ -0,0 +1,18 @@
+package com.wdbyte.weixin;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.ServletComponentScan;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@SpringBootApplication
+@ServletComponentScan(basePackages = "com.wdbyte.weixin.config")
+public class SpringBootApp {
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootApp.class, args);
+ }
+
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java
new file mode 100644
index 0000000..03de5c5
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java
@@ -0,0 +1,91 @@
+package com.wdbyte.weixin.config;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.auth0.jwt.interfaces.Claim;
+import com.wdbyte.weixin.util.ApiResultUtil;
+import com.wdbyte.weixin.util.JwtUtil;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.annotation.WebFilter;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * JWT过滤器,拦截 /user/* 请求
+ */
+@Slf4j
+@WebFilter(filterName = "JwtFilter", urlPatterns = {"/user/*"})
+public class JwtFilter implements Filter {
+ private static List EXCLUDE_PATH_LIST = new ArrayList<>();
+
+ static {
+ EXCLUDE_PATH_LIST.add("/user/qrcode");
+ EXCLUDE_PATH_LIST.add("/user/login/qrcode");
+ EXCLUDE_PATH_LIST.add("/weixin/check");
+ }
+
+ private JwtUtil jwtUtil;
+
+ public JwtFilter(JwtUtil jwtUtil) {
+ this.jwtUtil = jwtUtil;
+ }
+
+ @Override
+ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+ throws IOException, ServletException {
+
+ final HttpServletRequest request = (HttpServletRequest)req;
+ final HttpServletResponse response = (HttpServletResponse)res;
+ log.info("session id:{}", request.getRequestedSessionId());
+
+ response.setCharacterEncoding("UTF-8");
+ String path = request.getRequestURI();
+ for (String excludePath : EXCLUDE_PATH_LIST) {
+ if (excludePath.startsWith(path)) {
+ chain.doFilter(request, response);
+ return;
+ }
+ }
+ // 除OPTIONS外,其他请求应由JWT进行检查
+ if ("OPTIONS".equals(request.getMethod())) {
+ response.setStatus(HttpServletResponse.SC_OK);
+ chain.doFilter(request, response);
+ return;
+ }
+
+ //获取请求头里的token
+ String authorization = request.getHeader("Authorization");
+ if (authorization == null || !StringUtils.startsWith(authorization, "Bearer ")) {
+ response.getWriter().write(ApiResultUtil.error("authentication failed"));
+ return;
+ }
+ authorization = StringUtils.replaceOnce(authorization, "Bearer ", StringUtils.EMPTY);
+ Map userData = jwtUtil.verifyToken(authorization);
+ if (userData == null) {
+ response.getWriter().write(ApiResultUtil.error("authentication failed"));
+ return;
+ }
+ String openId = userData.get(JwtUtil.OPEN_ID).asString();
+ //拦截器 拿到用户信息,放到request中
+ request.setAttribute(JwtUtil.OPEN_ID, openId);
+ chain.doFilter(req, res);
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
\ No newline at end of file
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java
new file mode 100644
index 0000000..95740bc
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java
@@ -0,0 +1,51 @@
+package com.wdbyte.weixin.controller;
+
+import com.wdbyte.weixin.service.WeixinUserService;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@Slf4j
+@RestController
+public class WeixinServerController {
+
+ @Autowired
+ private WeixinUserService weixinUserService;
+
+ @GetMapping(value = "/weixin/check")
+ public String weixinCheck(HttpServletRequest request) {
+ String signature = request.getParameter("signature");
+ String timestamp = request.getParameter("timestamp");
+ String nonce = request.getParameter("nonce");
+ String echostr = request.getParameter("echostr");
+
+ if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce)) {
+ return "";
+ }
+ weixinUserService.checkSignature(signature, timestamp, nonce);
+ return echostr;
+ }
+
+ @PostMapping(value = "/weixin/check")
+ public String weixinMsg(@RequestBody String requestBody, @RequestParam("signature") String signature,
+ @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) {
+
+ log.debug("requestBody:{}", requestBody);
+ log.debug("signature:{}", signature);
+ log.debug("timestamp:{}", timestamp);
+ log.debug("nonce:{}", nonce);
+
+ weixinUserService.checkSignature(signature, timestamp, nonce);
+ return weixinUserService.handleWeixinMsg(requestBody);
+ }
+
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java
new file mode 100644
index 0000000..074022d
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java
@@ -0,0 +1,56 @@
+package com.wdbyte.weixin.controller;
+
+import com.wdbyte.weixin.model.WeixinQrCode;
+import com.wdbyte.weixin.util.ApiResultUtil;
+import com.wdbyte.weixin.util.JwtUtil;
+import com.wdbyte.weixin.util.WeixinApiUtil;
+import com.wdbyte.weixin.util.WeixinQrCodeCacheUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+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.RestController;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@CrossOrigin(origins = {"https://www.wdbyte.com", "https://bing.wdbyte.com", "http://localhost:8000"})
+@Slf4j
+@RestController
+public class WeixinUserController {
+
+ @Autowired
+ private WeixinApiUtil weixinApiUtil;
+
+ @Autowired
+ private JwtUtil jwtUtil;
+
+ @GetMapping(value = "/user/qrcode")
+ public String getQrCode() {
+ WeixinQrCode qrCode = weixinApiUtil.getQrCode();
+ qrCode.setUrl(null);
+ qrCode.setExpireSeconds(null);
+ return ApiResultUtil.success(qrCode);
+ }
+
+ /**
+ * 校验是否扫描完成
+ * 完成,返回 JWT
+ * 未完成,返回 check faild
+ *
+ * @param ticket
+ * @return
+ */
+ @GetMapping(value = "/user/login/qrcode")
+ public String userLogin(String ticket) {
+ String openId = WeixinQrCodeCacheUtil.get(ticket);
+ if (StringUtils.isNotEmpty(openId)) {
+ log.info("login success,open id:{}", openId);
+ return ApiResultUtil.success(jwtUtil.createToken(openId));
+ }
+ log.info("login error,ticket:{}", ticket);
+ return ApiResultUtil.error("check faild");
+ }
+
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java
new file mode 100644
index 0000000..83c4279
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java
@@ -0,0 +1,31 @@
+package com.wdbyte.weixin.model;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@Slf4j
+@Getter
+@Setter
+public class ApiResult {
+ private Integer code;
+ private String message;
+ private Object data;
+
+ public ApiResult() {
+ }
+
+ public ApiResult(Integer code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+
+ public ApiResult(Integer code, String message, Object data) {
+ this.code = code;
+ this.message = message;
+ this.data = data;
+ }
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ReceiveMessage.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ReceiveMessage.java
new file mode 100644
index 0000000..38f8d13
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ReceiveMessage.java
@@ -0,0 +1,58 @@
+package com.wdbyte.weixin.model;
+
+import lombok.Data;
+
+@Data
+public class ReceiveMessage {
+ /**
+ * 开发者微信号
+ */
+ private String toUserName;
+ /**
+ * 发送方账号(一个openid)
+ */
+ private String fromUserName;
+ /**
+ * 消息创建时间(整形)
+ */
+ private String createTime;
+ /**
+ * 消息类型
+ */
+ private String msgType;
+ /**
+ * 文本消息内容
+ */
+ private String content;
+ /**
+ * 消息ID 64位
+ */
+ String msgId;
+ /**
+ * 消息的数据ID 消息来自文章才有
+ */
+ private String msgDataId;
+ /**
+ * 多图文时第几篇文章,从1开始 消息如果来自文章才有
+ */
+ private String idx;
+ /**
+ * 订阅事件 subscribe 订阅 unsbscribe 取消订阅
+ */
+ private String event;
+ /**
+ * 扫码 - ticket
+ */
+ private String ticket;
+
+ public String getReplyTextMsg(String msg) {
+ String xml = "\n"
+ + " \n"
+ + " \n"
+ + " " + System.currentTimeMillis() + "\n"
+ + " \n"
+ + " \n"
+ + " ";
+ return xml;
+ }
+}
\ No newline at end of file
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java
new file mode 100644
index 0000000..66bdf65
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java
@@ -0,0 +1,15 @@
+package com.wdbyte.weixin.model;
+
+import lombok.Data;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@Data
+public class WeixinQrCode {
+
+ private String ticket;
+ private Long expireSeconds;
+ private String url;
+ private String qrCodeUrl;
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java
new file mode 100644
index 0000000..2d1cd71
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java
@@ -0,0 +1,12 @@
+package com.wdbyte.weixin.service;
+
+/**
+ * @Author 公众号:程序猿阿朗
+ */
+public interface WeixinUserService {
+
+ void checkSignature(String signature, String timestamp, String nonce);
+
+ String handleWeixinMsg(String body);
+
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java
new file mode 100644
index 0000000..78939d0
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java
@@ -0,0 +1,69 @@
+package com.wdbyte.weixin.service.impl;
+
+import java.util.Arrays;
+
+import com.wdbyte.weixin.model.ReceiveMessage;
+import com.wdbyte.weixin.service.WeixinUserService;
+import com.wdbyte.weixin.util.WeixinMsgUtil;
+import com.wdbyte.weixin.util.WeixinQrCodeCacheUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+/**
+ * @Author 公众号:程序猿阿朗
+ */
+@Slf4j
+@Service
+public class WeixinUserServiceImpl implements WeixinUserService {
+
+ @Value("${weixin.token}")
+ private String token;
+
+ @Override
+ public void checkSignature(String signature, String timestamp, String nonce) {
+ String[] arr = new String[] {token, timestamp, nonce};
+ Arrays.sort(arr);
+ StringBuilder content = new StringBuilder();
+ for (String str : arr) {
+ content.append(str);
+ }
+ String tmpStr = DigestUtils.sha1Hex(content.toString());
+ if (tmpStr.equals(signature)) {
+ log.info("check success");
+ return;
+ }
+ log.error("check fail");
+ throw new RuntimeException("check fail");
+ }
+
+ @Override
+ public String handleWeixinMsg(String requestBody) {
+ ReceiveMessage receiveMessage = WeixinMsgUtil.msgToReceiveMessage(requestBody);
+ // 扫码登录
+ if (WeixinMsgUtil.isScanQrCode(receiveMessage)) {
+ return handleScanLogin(receiveMessage);
+ }
+ // 关注
+ if (WeixinMsgUtil.isEventAndSubscribe(receiveMessage)) {
+ return receiveMessage.getReplyTextMsg("欢迎关注【程序猿阿朗】公众号");
+ }
+ return receiveMessage.getReplyTextMsg("收到(自动回复)");
+ }
+
+ /**
+ * 处理扫码登录
+ *
+ * @param receiveMessage
+ * @return
+ */
+ private String handleScanLogin(ReceiveMessage receiveMessage) {
+ String qrCodeTicket = WeixinMsgUtil.getQrCodeTicket(receiveMessage);
+ if (WeixinQrCodeCacheUtil.get(qrCodeTicket) == null) {
+ String openId = receiveMessage.getFromUserName();
+ WeixinQrCodeCacheUtil.put(qrCodeTicket, openId);
+ }
+ return receiveMessage.getReplyTextMsg("你已成功登录!");
+ }
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java
new file mode 100644
index 0000000..5c1cd39
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java
@@ -0,0 +1,64 @@
+package com.wdbyte.weixin.util;
+
+import java.util.Base64;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+
+import lombok.SneakyThrows;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * Java 使用 AES 加密算法进行加密解密
+ */
+@Component
+public class AesUtils {
+
+ /**
+ * 秘钥(需要使用长度为16、24或32的字节数组作为AES算法的密钥,否则就会遇到java.security.InvalidKeyException异常)
+ */
+ @Value("${ase.util.secret}")
+ public String key;
+
+ /**
+ * AES算法加密
+ *
+ * @Param:text原文
+ * @Param:key密钥
+ */
+
+ @SneakyThrows
+ public String aesEncrypt(String text) {
+ // 创建AES加密算法实例(根据传入指定的秘钥进行加密)
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+ SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
+ // 初始化为加密模式,并将密钥注入到算法中
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec);
+ // 将传入的文本加密
+ byte[] encrypted = cipher.doFinal(text.getBytes());
+ //生成密文
+ // 将密文进行Base64编码,方便传输
+ return Base64.getEncoder().encodeToString(encrypted);
+ }
+
+ /**
+ * AES算法解密
+ *
+ * @Param:base64Encrypted密文
+ * @Param:key密钥
+ */
+ public String aesDecrypt(String base64Encrypted) throws Exception {
+ // 创建AES解密算法实例
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+ SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES");
+
+ // 初始化为解密模式,并将密钥注入到算法中
+ cipher.init(Cipher.DECRYPT_MODE, keySpec);
+ // 将Base64编码的密文解码
+ byte[] encrypted = Base64.getDecoder().decode(base64Encrypted);
+ // 解密
+ byte[] decrypted = cipher.doFinal(encrypted);
+ return new String(decrypted);
+ }
+}
\ No newline at end of file
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java
new file mode 100644
index 0000000..d68f622
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java
@@ -0,0 +1,29 @@
+package com.wdbyte.weixin.util;
+
+import com.alibaba.fastjson2.JSON;
+
+import com.wdbyte.weixin.model.ApiResult;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+public class ApiResultUtil {
+
+ private static Integer SUCCESS_CODE = 200;
+ private static String SUCCESS_MESSAGE = "success";
+ private static Integer ERROR_CODE = -1;
+ private static String ERROR_MESSAGE = "error";
+
+ public static String success() {
+ return JSON.toJSONString(new ApiResult(SUCCESS_CODE, SUCCESS_MESSAGE, new Object()));
+ }
+
+ public static String success(Object data) {
+ return JSON.toJSONString(new ApiResult(SUCCESS_CODE, SUCCESS_MESSAGE, data));
+ }
+
+ public static String error(Object data) {
+ return JSON.toJSONString(new ApiResult(ERROR_CODE, ERROR_MESSAGE, data));
+ }
+
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/HttpUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/HttpUtil.java
new file mode 100644
index 0000000..d536530
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/HttpUtil.java
@@ -0,0 +1,44 @@
+package com.wdbyte.weixin.util;
+
+import java.io.IOException;
+
+import org.apache.hc.client5.http.classic.methods.HttpPost;
+import org.apache.hc.client5.http.fluent.Request;
+import org.apache.hc.client5.http.fluent.Response;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
+import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
+import org.apache.hc.client5.http.impl.classic.HttpClients;
+import org.apache.hc.core5.http.ContentType;
+import org.apache.hc.core5.http.ParseException;
+import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.apache.hc.core5.http.io.entity.StringEntity;
+
+public class HttpUtil {
+
+ public static String get(String url) {
+ String result = null;
+ try {
+ Response response = Request.get(url).execute();
+ result = response.returnContent().asString();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ public static String post(String url, String jsonBody) {
+ String result = null;
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON));
+ try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
+ try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
+ // 获取响应信息
+ result = EntityUtils.toString(response.getEntity());
+ }
+ } catch (IOException | ParseException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/JwtUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/JwtUtil.java
new file mode 100644
index 0000000..581a0a3
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/JwtUtil.java
@@ -0,0 +1,88 @@
+package com.wdbyte.weixin.util;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.TokenExpiredException;
+import com.auth0.jwt.interfaces.Claim;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import jakarta.annotation.PostConstruct;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * Jwt工具类,生成JWT和认证
+ */
+@Slf4j
+@Component
+public class JwtUtil {
+
+ @Autowired
+ private AesUtils aesUtils;
+
+ @Value("${key.jwt.secret}")
+ public String secret;
+
+ private JWTVerifier verifier;
+
+ @PostConstruct
+ public synchronized void init() {
+ log.info("init jwt verifier");
+ verifier = JWT.require(Algorithm.HMAC256(secret)).build();
+ }
+
+ /**
+ * 过期时间,单位为秒,3天
+ **/
+ private static final long EXPIRATION = 3 * 24 * 3600L;
+ /**
+ * open id
+ */
+ public static final String OPEN_ID = "oid";
+
+ /**
+ * 生成用户token,设置token超时时间
+ */
+ public String createToken(String openId) {
+ Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000);
+ Map map = new HashMap<>();
+ map.put("alg", "HS256");
+ map.put("typ", "JWT");
+ String token = JWT.create()
+ // 添加头部
+ .withHeader(map)
+ // 可以将基本信息放到claims中
+ .withClaim(OPEN_ID, openId)
+ //超时设置,设置过期的日期
+ .withExpiresAt(expireDate)
+ //签发时间
+ .withIssuedAt(new Date())
+ //SECRET加密
+ .sign(Algorithm.HMAC256(secret));
+ return aesUtils.aesEncrypt(token);
+ }
+
+ /**
+ * 校验token并解析token
+ */
+ public Map verifyToken(String token) {
+ try {
+ token = aesUtils.aesDecrypt(token);
+ DecodedJWT jwt = verifier.verify(token);
+ return jwt.getClaims();
+ } catch (Exception e) {
+ log.error(String.format("verifyToken error,token:%s,msg:%s", token, e.getMessage()), e);
+ if (e instanceof TokenExpiredException) {
+ throw new RuntimeException("jwt verify token error");
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java
new file mode 100644
index 0000000..d56ebe0
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java
@@ -0,0 +1,24 @@
+package com.wdbyte.weixin.util;
+
+import java.util.UUID;
+
+import org.apache.commons.lang3.RandomStringUtils;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+public class KeyUtils {
+
+ public synchronized static String key6() {
+ return RandomStringUtils.randomAlphanumeric(6);
+ }
+
+ public synchronized static String key16() {
+ return RandomStringUtils.randomAlphanumeric(16);
+ }
+
+ public static String uuid32() {
+ return UUID.randomUUID().toString().replace("-", "");
+ }
+
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java
new file mode 100644
index 0000000..6a3f8b0
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java
@@ -0,0 +1,79 @@
+package com.wdbyte.weixin.util;
+
+import java.net.URI;
+import java.time.LocalDateTime;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+
+import com.wdbyte.weixin.model.WeixinQrCode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@Slf4j
+@Component
+public class WeixinApiUtil {
+
+ @Value("${weixin.appid}")
+ public String appId;
+
+ @Value("${weixin.appsecret}")
+ public String appSecret;
+
+ private static String QR_CODE_URL_PREFIX = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=";
+
+ private static String ACCESS_TOKEN = null;
+ private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null;
+ /**
+ * 二维码 Ticket 过期时间
+ */
+ private static int QR_CODE_TICKET_TIMEOUT = 10 * 60;
+
+ /**
+ * 获取 access token
+ *
+ * @return
+ */
+ public synchronized String getAccessToken() {
+ if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) {
+ return ACCESS_TOKEN;
+ }
+ String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret="
+ + appSecret;
+ String result = HttpUtil.get(api);
+ JSONObject jsonObject = JSON.parseObject(result);
+ ACCESS_TOKEN = jsonObject.getString("access_token");
+ ACCESS_TOKEN_EXPIRE_TIME = LocalDateTime.now().plusSeconds(jsonObject.getLong("expires_in") - 10);
+ return ACCESS_TOKEN;
+ }
+
+ /**
+ * 获取二维码 Ticket
+ *
+ * https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
+ *
+ * @return
+ */
+ public WeixinQrCode getQrCode() {
+ String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken();
+ String jsonBody = String.format("{\n"
+ + " \"expire_seconds\": %d,\n"
+ + " \"action_name\": \"QR_STR_SCENE\",\n"
+ + " \"action_info\": {\n"
+ + " \"scene\": {\n"
+ + " \"scene_str\": \"%s\"\n"
+ + " }\n"
+ + " }\n"
+ + "}", QR_CODE_TICKET_TIMEOUT, KeyUtils.uuid32());
+ String result = HttpUtil.post(api, jsonBody);
+ log.info("get qr code params:{}", jsonBody);
+ log.info("get qr code result:{}", result);
+ WeixinQrCode weixinQrCode = JSON.parseObject(result, WeixinQrCode.class);
+ weixinQrCode.setQrCodeUrl(QR_CODE_URL_PREFIX + URI.create(weixinQrCode.getTicket()).toASCIIString());
+ return weixinQrCode;
+ }
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java
new file mode 100644
index 0000000..a48c0cb
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java
@@ -0,0 +1,67 @@
+package com.wdbyte.weixin.util;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONObject;
+
+import com.wdbyte.weixin.model.ReceiveMessage;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+public class WeixinMsgUtil {
+
+ // 事件-关注
+ private static String EVENT_SUBSCRIBE = "subscribe";
+
+ /**
+ * 微信消息转对象
+ *
+ * @param xml
+ * @return
+ */
+ public static ReceiveMessage msgToReceiveMessage(String xml) {
+ JSONObject jsonObject = JSON.parseObject(XmlUtil.xml2json(xml));
+ ReceiveMessage receiveMessage = new ReceiveMessage();
+ receiveMessage.setToUserName(jsonObject.getString("ToUserName"));
+ receiveMessage.setFromUserName(jsonObject.getString("FromUserName"));
+ receiveMessage.setCreateTime(jsonObject.getString("CreateTime"));
+ receiveMessage.setMsgType(jsonObject.getString("MsgType"));
+ receiveMessage.setContent(jsonObject.getString("Content"));
+ receiveMessage.setMsgId(jsonObject.getString("MsgId"));
+ receiveMessage.setEvent(jsonObject.getString("Event"));
+ receiveMessage.setTicket(jsonObject.getString("Ticket"));
+ return receiveMessage;
+ }
+
+ /**
+ * 是否是订阅事件
+ *
+ * @param receiveMessage
+ * @return
+ */
+ public static boolean isEventAndSubscribe(ReceiveMessage receiveMessage) {
+ return StringUtils.equals(receiveMessage.getEvent(), EVENT_SUBSCRIBE);
+ }
+
+ /**
+ * 是否是二维码扫描事件
+ *
+ * @param receiveMessage
+ * @return
+ */
+ public static boolean isScanQrCode(ReceiveMessage receiveMessage) {
+ return StringUtils.isNotEmpty(receiveMessage.getTicket());
+ }
+
+ /**
+ * 获取扫描的二维码 Ticket
+ *
+ * @param receiveMessage
+ * @return
+ */
+ public static String getQrCodeTicket(ReceiveMessage receiveMessage) {
+ return receiveMessage.getTicket();
+ }
+
+}
\ No newline at end of file
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java
new file mode 100644
index 0000000..1e55d9e
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java
@@ -0,0 +1,34 @@
+package com.wdbyte.weixin.util;
+
+import java.util.LinkedHashMap;
+
+/**
+ * 微信二维码缓存工具类
+ *
+ * @author https://www.wdbyte.com
+ */
+public class WeixinQrCodeCacheUtil {
+ private static long MAX_CACHE_SIZE = 10000;
+ private static LinkedHashMap QR_CODE_TICKET_MAP = new LinkedHashMap<>();
+
+ /**
+ * 增加一个 Ticket
+ * 首次 put:value 为 ""
+ * 再次 put: value 有 openId,若openId已经存在,则已被扫码
+ *
+ * @param key
+ * @param value
+ */
+ public synchronized static void put(String key, String value) {
+ QR_CODE_TICKET_MAP.put(key, value);
+ if (QR_CODE_TICKET_MAP.size() > MAX_CACHE_SIZE) {
+ String first = QR_CODE_TICKET_MAP.keySet().stream().findFirst().get();
+ QR_CODE_TICKET_MAP.remove(first);
+ }
+ }
+
+ public synchronized static String get(String key) {
+ return QR_CODE_TICKET_MAP.remove(key);
+ }
+
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java
new file mode 100644
index 0000000..bc56199
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java
@@ -0,0 +1,28 @@
+package com.wdbyte.weixin.util;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.xml.XmlMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * @author https://www.wdbyte.com
+ */
+@Slf4j
+public class XmlUtil {
+
+ public static String xml2json(String requestBody) {
+ requestBody = StringUtils.trim(requestBody);
+ XmlMapper xmlMapper = new XmlMapper();
+ JsonNode node = null;
+ try {
+ node = xmlMapper.readTree(requestBody.getBytes());
+ ObjectMapper jsonMapper = new ObjectMapper();
+ return jsonMapper.writeValueAsString(node);
+ } catch (Exception e) {
+ log.error("xml 2 json error,msg:" + e.getMessage(), e);
+ }
+ return null;
+ }
+}
diff --git a/springboot/springboot-weixin-qrcode-login/src/main/resources/application.properties b/springboot/springboot-weixin-qrcode-login/src/main/resources/application.properties
new file mode 100644
index 0000000..453f434
--- /dev/null
+++ b/springboot/springboot-weixin-qrcode-login/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+server.port=
+weixin.appid=
+weixin.appsecret=
+weixin.token=
+ase.util.secret=
+key.jwt.secret=
\ No newline at end of file
diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java
index 5492a46..be544cd 100644
--- a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java
+++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java
@@ -1,15 +1,12 @@
package com.wdbyte.jackson;
import com.fasterxml.jackson.annotation.JsonGetter;
-import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonSetter;
-import lombok.Data;
/**
* @author https://www.wdbyte.com
* @date 2022/07/17
*/
-@Data
public class Cat {
@JsonSetter(value = "catName")
@@ -21,4 +18,24 @@ public class Cat {
public String getName() {
return name;
}
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Cat() {
+ }
+
+ public Cat(String name, Integer age) {
+ this.name = name;
+ this.age = age;
+ }
}
diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java
index 6318eb3..38eb08d 100644
--- a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java
+++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java
@@ -1,24 +1,15 @@
package com.wdbyte.jackson;
-import java.time.LocalDateTime;
-import java.util.Date;
-
import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonSetter;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.ToString;
+
+import java.time.LocalDateTime;
+import java.util.Date;
/**
* @author https://www.wdbyte.com
* @date 2022/07/17
*/
-//@Data
-@AllArgsConstructor
-@NoArgsConstructor
-@ToString
public class Order {
@JsonSetter(value = "orderId")
@@ -54,4 +45,22 @@ public LocalDateTime getUpdateTime() {
public void setUpdateTime(LocalDateTime updateTime) {
this.updateTime = updateTime;
}
+
+ @Override
+ public String toString() {
+ return "Order{" +
+ "id=" + id +
+ ", createTime=" + createTime +
+ ", updateTime=" + updateTime +
+ '}';
+ }
+
+ public Order() {
+ }
+
+ public Order(Integer id, Date createTime, LocalDateTime updateTime) {
+ this.id = id;
+ this.createTime = createTime;
+ this.updateTime = updateTime;
+ }
}
diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java
index 3bc1210..b1f2a72 100644
--- a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java
+++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java
@@ -2,16 +2,55 @@
import java.util.List;
-import lombok.Data;
-
/**
* @author https://www.wdbyte.com
* @date 2022/07/16
*/
-@Data
public class Person {
private String name;
private Integer age;
private List skillList;
+
+ @Override
+ public String toString() {
+ return "Person{" +
+ "name='" + name + '\'' +
+ ", age=" + age +
+ ", skillList=" + skillList +
+ '}';
+ }
+
+ public Person(String name, Integer age, List skillList) {
+ this.name = name;
+ this.age = age;
+ this.skillList = skillList;
+ }
+
+ public Person() {
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public List getSkillList() {
+ return skillList;
+ }
+
+ public void setSkillList(List skillList) {
+ this.skillList = skillList;
+ }
}
diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java
index 377253d..f5baec4 100644
--- a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java
+++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java
@@ -1,36 +1,20 @@
package com.wdbyte.jackson;
-import java.util.HashMap;
-import java.util.Map;
-
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
-import com.google.common.collect.Maps;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-import lombok.ToString;
+
+import java.util.HashMap;
+import java.util.Map;
/**
* @author https://www.wdbyte.com
* @date 2022/07/17
*/
-@ToString
-@AllArgsConstructor
-@NoArgsConstructor
public class Student {
- @Getter
- @Setter
private String name;
- @Getter
- @Setter
private Integer age;
- @Getter
- @Setter
private Map diyMap = new HashMap<>();
@JsonAnyGetter
@@ -45,4 +29,55 @@ public void otherField(String key, String value) {
this.diyMap.put(key, value);
}
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Map getDiyMap() {
+ return diyMap;
+ }
+
+ public void setDiyMap(Map diyMap) {
+ this.diyMap = diyMap;
+ }
+
+ public Map getInitMap() {
+ return initMap;
+ }
+
+ public void setInitMap(Map initMap) {
+ this.initMap = initMap;
+ }
+
+ public Student() {
+ }
+
+ public Student(String name, Integer age, Map diyMap, Map initMap) {
+ this.name = name;
+ this.age = age;
+ this.diyMap = diyMap;
+ this.initMap = initMap;
+ }
+
+ @Override
+ public String toString() {
+ return "Student{" +
+ "name='" + name + '\'' +
+ ", age=" + age +
+ ", diyMap=" + diyMap +
+ ", initMap=" + initMap +
+ '}';
+ }
}