diff --git a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
index 6a5dae969987ce553cff93b556dd1953d18bfa95..2cb5ddcbdf7296180bd46bfa94af7b5ae821f8d3 100644
--- a/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
+++ b/.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md
@@ -7,4 +7,13 @@
### 修改描述(包括说明bug修复或者添加新特性)
1. [bug修复] balabala……
-2. [新特性] balabala……
\ No newline at end of file
+2. [新特性] balabala……
+
+### 提交前自测
+> 请在提交前自测确保代码没有问题,提交新代码应包含:测试用例、通过(mvn javadoc:javadoc)检验详细注释。
+
+1. 本地如有多个JDK版本,可以设置临时JDk版本,如:`export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_331.jdk/Contents/Home`,具体替换为本地jdk目录
+2. 确保本地测试使用JDK8最新版本,`echo $JAVA_HOME`、`mvn -v`、`java -version`均正确。
+3. 执行打包生成文档,使用`mvn clean package -Dmaven.test.skip=true -U`,并确认通过,会自动执行打包、生成文档
+4. 如需要单独执行文档生成,执行:`mvn javadoc:javadoc `,并确认通过
+5. 如需要单独执行测试用例,执行:`mvn clean test`,并确认通过
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 7c3886b87e271b59a40e4983db434a4ee6898fd1..ac5917c2f568c9b0748845e852a1ac8302811c67 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,4 +1,4 @@
# These are supported funding model platforms
github: [looly]
-custom: ['https://gitee.com/dromara/hutool', 'https://dromara.gitee.io/donate.html']
+custom: ['https://gitee.com/chinabugotech/hutool']
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 6a5dae969987ce553cff93b556dd1953d18bfa95..6f6bbf520596595d5921a27ab8cc09f26ba0fbe7 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -7,4 +7,13 @@
### 修改描述(包括说明bug修复或者添加新特性)
1. [bug修复] balabala……
-2. [新特性] balabala……
\ No newline at end of file
+2. [新特性] balabala……
+
+### 提交前自测
+> 请在提交前自测确保代码没有问题,提交新代码应包含:测试用例、通过(mvn javadoc:javadoc)检验详细注释。
+
+1. 本地如有多个JDK版本,可以设置临时JDk版本,如:`export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_331.jdk/Contents/Home`,具体替换为本地jdk目录
+2. 确保本地测试使用JDK8最新版本,`echo $JAVA_HOME`、`mvn -v`、`java -version`均正确。
+3. 执行打包生成文档,使用`mvn clean package -Dmaven.test.skip=true -U`,并确认通过,会自动执行打包、生成文档
+4. 如需要单独执行文档生成,执行:`mvn javadoc:javadoc `,并确认通过
+5. 如需要单独执行测试用例,执行:`mvn clean test`,并确认通过
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14c1f65d87d37a89f7dc524084d82cfbd7f52c6b..4f840970e1eae1ba8c53505aab609d2fe4810de3 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,13 +2,1061 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
+# 5.8.41(2025-10-12)
-# 5.8.0.M5 (2022-04-27)
+### 🐣新特性
+* 【core 】 增加`WeakKeyValueConcurrentMap`及其关联类,同时废弃`WeakConcurrentMap`并替换(issue#4039@Github)
+* 【core 】 `MapUtil`增加`removeByValue`和`removeIf`方法
+* 【core 】 `ObjectUtil`增加`apply`方法
+* 【core 】 `ReferenceUtil`增加`get`方法
+* 【db 】 `Condition`增加构造方法支持BETWEEN(issue#4041@Github)
+* 【core 】 `IoUtil.writeObjects`判空避免空指针(issue#4049@Github)
+* 【extra 】 `OsInfo`增加`isWindows11`方法(pr#4054@Github)
+* 【extra 】 `RedisDS`增加`getPool`和`getSetting`方法(issue#ICVWDI@Gitee)
+* 【core 】 `NumberUtil.pow`增加重载,支持指数自定义保留位数(pr#4052@Github)
+* 【core 】 `NumberUtil.isPrimes`优化判断(pr#4058@Github)
+* 【extra 】 `Mail.buildContent`改进,正文部分总在最前(issue#4072@Github)
+* 【core 】 `DataSizeUtil`改进,兼容`GiB`等单位名称(issue#ICXXVF@Github)
+* 【ai 】 `Message`增加setter和构造方法(issue#ICXTP2@Gitee)
+* 【extra 】 `PinyinUtil`增加判空(pr#4081@Github)
+* 【core 】 `LocalDateTimeUtil.parseDate`注释修正(pr#4085@Github)
+* 【core 】 `StrUtil`增加null检查处理(pr#4086@Github)
+* 【json 】 增加Record支持(pr#4096@Github)
+* 【crypto 】 增加`SpecUtil`,`KeyUtil`增加`generateRSAPrivateKey`重载,(issue#ID1EIK@Gitee)
+* 【core 】 `RandomUtil`增加`randomStringLower`方法
+
+### 🐞Bug修复
+* 【core 】 修复`ReflectUtil`中因class和Method关联导致的缓存无法回收问题(issue#4039@Github)
+* 【db 】 修复`Condition`的`Condition("discount_end_time", "!=", (String) null)`方法生成SQL时,生成SQL不符合预期要求的错误(pr#4042@Github)
+* 【core 】 修复`IoUtil`的`closeIfPosible`拼写错误,新建一个`closeIfPossible`方法,原方法标记deprecated(issue#4047@Github)
+* 【http 】 修复`HttpRequest.sendRedirectIfPossible`未对308做判断问题。(issue#4053@Github)
+* 【cron 】 修复`CronPatternUtil.nextDateAfter`当日为L时计算错误问题。(issue#4056@Github)
+* 【db 】 修复`NamedSql.replaceVar`关键字处理问题(issue#4062@Github)
+* 【db 】 修复`DialectRunner.count`方法中,去除包含多字段order by子句的SQL语句时错误问题(issue#4066@Github)
+* 【extra 】 修复`JschSessionPool`并发问题(pr#4079@Github)
+* 【extra 】 修复`Sftp`递归删除目录时使用相对路径可能导致死循环的问题(pr#1380@Gitee)
+* 【db 】 修复`SqlUtil.removeOuterOrderBy`处理没有order by的语句导致异常问题(pr#4089@Github)
+* 【extra 】 修复`Sftp.upload`目标路径为null时空指针问题(issue#ID14WX@Gitee)
+* 【ai 】 修复`AIConfigBuilder`中方法名拼写错误(pr#1382@Gitee)
+* 【core 】 修复`StrBuilder`charAt越界判断错误(pr#4094@Github)
+* 【dfa 】 修复`WordTree.addWord`末尾为特殊字符导致的无法匹配问题(pr#4092@Github)
+* 【core 】 修复`ServiceLoaderUtil.loadFirstAvailable`在JDK24+后未捕获异常导致的报错问题(pr#4098@Github)
+* 【cron 】 修复`CronTimer`在任务非常多时,追赶系统时间导致遗漏任务的问题(issue#IB49EF@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.40(2025-08-26)
+
+### 🐣新特性
+* 【captcha】 `MathGenerator`四则运算方式支持不生成负数结果(pr#1363@Gitee)
+* 【core 】 增加`MapValueProvider`和`RecordConverter`并支持Record转换(issue#3985@Github)
+* 【core 】 `CalendarUtil`增加`isSameYear`和`calendar`方法(issue#3995@Github)
+* 【core 】 `DateUtil`增加`yyyy-MM-dd'T'HH:mmXXX`格式支持(pr#1367@Gitee)
+* 【core 】 `MapUtil`增加flatten方法(pr#1368@Gitee)
+* 【extra 】 `getClientIP`优先获取传入的请求头信息(pr#1373@Gitee)
+* 【db 】 增加`Gbase8s`驱动支持(issue#ICSFAM@Gitee)
+* 【db 】 增加TDSQL PostgreSQL版本、TDSQL-H LibraDB、Snowflake、Teradata 的驱动支持(pr#4024@Github)
+* 【core 】 `EnumUtil`增加缓存支持(pr#1376@Gitee)
+
+### 🐞Bug修复
+* 【extra 】 `Sftp``reconnectIfTimeout`方法改为捕获所有异常(issue#3989@Github)
+* 【core 】 修复`ChineseDate `闰年闰月节日获取问题(issue#ICL1BT@Gitee)
+* 【core 】 修复`TreeBuilder`append重复向idTreeMap中put问题(pr#3992@Github)
+* 【extra 】 修复`QLExpressEngine`allowClassSet无效问题(issue#3994@Github)
+* 【core 】 修复`StrBuilder`insert插入计算错误问题(issue#ICTSRZ@Gitee)
+* 【cron 】 修复`CronPatternUtil.nextDateAfter`计算下一个匹配表达式的日期时,计算错误问题(issue#4006@Github)
+* 【cache 】 `ReentrantCache`修改get逻辑key锁改为全局锁,保证安全(issue#4022@Github)
+* 【core 】 修复`NumberWordFormatter`formatSimple输出错误问题(pr#4034@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.39(2025-06-20)
+
+### 🐣新特性
+* 【ai 】 增加SSE流式返回函数参数callback,增加超时时间配置,豆包、grok新增文生图接口,豆包生成视频支持使用model,新增HutoolAI平台
+* 【core 】 DesensitizedUtil新增护照号码脱敏功能(pr#1347@Gitee)
+* 【core 】 优化XXXToMapCopier的部分性能(pr#1345@Gitee)
+* 【http 】 `HttpConfig`增加参数`setIgnoreContentLength`可选忽略读取响应contentLength头(issue#ICB1B8@Gitee)
+* 【core 】 `Assert`新增断言给定集合为空的方法以及单元测试用例(pr#3952@Github)
+* 【db 】 Db添加FetchSize的全局设置(pr#3978@Github)
+* 【core 】 增加可召回批处理线程池执行器`RecyclableBatchThreadPoolExecutor`(pr#1343@Gitee)
+*
+### 🐞Bug修复
+* 【core 】 修复`NumberUtil`isNumber方法以L结尾没有小数点判断问题(issue#3938@Github)
+* 【core 】 修复`CharsequenceUtil`toLowerCase方法拼写错误(issue#3941@Github)
+* 【core 】 修复`UUID`equals的问题,改为final类(issue#3948@Github)
+* 【core 】 修复`Money`中金额分配的问题bug(issue#IC9Y35@Gitee)
+* 【poi 】 修复`ExcelPicUtil`中可能的空指针异常
+* 【core 】 修复`LunarFestival`中重复节日问题(issue#ICC8X3@Gitee)
+* 【core 】 修复`ThreadUtil`中中断异常处理丢失中断信息的问题,解决ConcurrencyTester资源未释放的问题(pr#1358@Gitee)
+* 【core 】 修复`TEL_400_800`正则规则太窄问题(issue#3967@Github)
+* 【core 】 修复`ClassUti`isNormalClass判断未排除String问题(issue#3965@Github)
+* 【core 】 修复`ZipUtil`中zlib和unZlib调用后资源未释放问题(issue#3976@Github)
+* 【core 】 修复`Money`类的setAmount方法没有获取当前币种的小数位数而是使用的默认小数位和在遇到非2小数位的币种(如日元使用 0 位)会导致金额设置错误问题(pr#3970@Github)
+* 【cache 】 修复`AbstractCache`putWithoutLock方法可能导致的外部资源泄露问题(pr#3958@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.38(2025-05-13)
+
+### 🐣新特性
+* 【core 】 `PathUtil#del`增加null检查(pr#1331@Gitee)
+* 【db 】 增加SAP HANA识别及方言(pr#3914@Github)
+* 【crypto 】 增加`Argon2`类,实现Argon2算法(issue#3890@Github)
+* 【core 】 `CharSequenceUtil`增加toLoweCase和toUpperCase方法(issue#IC0H2B@Gitee)
+* 【core 】 增加分段锁实现`SegmentLock`(pr#1330@Gitee)
+* 【core 】 重载subtractToList方法,提供isLinked选项(pr#3923@Github)
+* 【extra 】 `TemplateConfig`增加`setUseCache`方法(issue#IC3JRY@Gitee)
+* 【extra 】 `AbstractFtp`增加`rename`方法(issue#IC3PMI@Gitee)
+* 【core 】 优化`PropDesc`缓存注解判断,提升性能(pr#1335@Gitee)
+* 【core 】 添加`RecordUtil`支持record类(issue#3931@Github)
+* 【core 】 `Dict`的customKey方法访问权限修改为protected(pr#1340@Gitee)
+* 【ai 】 增加hutool-ai模块,对AI大模型的封装实现(pr#3937@Github)
+
+### 🐞Bug修复
+* 【setting】 修复`Setting`autoLoad可能的加载为空的问题(issue#3919@Github)
+* 【db 】 修复某些数据库的getParameterMetaData会返回NULL,导致空指针的问题。(pr#3936@Github)
+* 【extra 】 修正`SshjSftp`在SftpSubsystem服务时报错问题(pr#1338@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.37(2025-03-31)
+
+### 🐣新特性
+* 【json 】 ObjectMapper删除重复trim(pr#3859@Github)
+* 【core 】 `FileWriter`增加方法,可选是否追加换行符(issue#3858@Github)
+* 【core 】 `IdcardUtil`验证10位身份证兼容中英文括号(issue#IBP6T1@Gitee)
+* 【extra 】 `PinyinUtil`增加重载可选是否返回声调(pr#3875@Github)
+* 【extra 】 `PinyinEngine`增加重载可选是否返回声调(pr#3883@Github)
+* 【core 】 增加`VersionUtil`版本比较工具(pr#3876@Github)
+* 【db 】 增加GoldenDB识别(pr#3886@Github)
+* 【http 】 改进`UrlQuery`对无参URL增加判断识别(issue#IBRVE4@Gitee)
+* 【core 】 改进`PropDesc`中去除Transient引用避免NoClassDefFoundError(issue#3901@Github)
+* 【core 】 `StrUtil.isBlank`增加`\u200c`判断(issue#3903@Github)
+* 【core 】 优化`CombinationAnnotationElement`注解数组性能(pr#1323@Gitee)
+* 【core 】 完善季度相关 API(pr#1324@Gitee)
+
+### 🐞Bug修复
+* 【setting】 修复`SettingLoader`load未抛出异常导致配置文件无法正常遍历的问题(pr#3868@Github)
+* 【cache 】 修复`ReentrantCache#getOrRemoveExpired`方法丢失onRemove触发问题(pr#1315@Gitee)
+* 【json 】 修复`JsonUtil.toBean`泛型数组类型丢失问题(pr#3876@Github)
+* 【http 】 修复`HttpUtil.normalizeParams`规则问题(issue#IBQIYQ@Gitee)
+* 【http 】 修复`NumberChineseFormatter.format`中自定义单位在0时错误问题(issue#3888@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.36(2025-02-18)
+
+### 🐣新特性
+* 【crypto 】 增加BCUtil.decodeECPrivateKey方法(issue#3829@Github)
+* 【core 】 增加HtmlUtil.cleanEmptyTag方法(pr#3838@Github)
+* 【db 】 GlobalDbSetting优化默认配置读取规则,优先读取文件而非jar中的文件(issue#900@Github)
+* 【dfa 】 删除StopChar类中存在重复字符(pr#3841@Github)
+* 【http 】 支持鸿蒙设备 UA 解析(pr#1301@Gitee)
+
+### 🐞Bug修复
+* 【aop 】 修复ProxyUtil可能的空指针问题(issue#IBF20Z@Gitee)
+* 【core 】 修复XmlUtil转义调用方法错误问题,修复XmlEscape未转义单引号问题(pr#3837@Github)
+* 【core 】 修复FileUtil.isAbsolutePath没有判断smb路径问题(pr#1299@Gitee)
+* 【core 】 修复AbstractFilter没有检查参数长度问题(issue#3854@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.35(2024-12-25)
+
+### 🐣新特性
+* 【poi 】 优化ExcelWriter中使用比较器writer的方法,只对第一条数据进行排序(pr#3807@Github)
+* 【extra 】 优化Ftp.download,返回false抛出异常(issue#3805@Github)
+* 【core 】 优化MAC地址正则(issue#IB95X4@Gitee)
+* 【json 】 JSON的getByPath方法新增更为通用的指定出参类型重载(pr#3814@Github)
+* 【core 】 DateUtil.parseUTC方法标记废弃,改名为parseISO8601(issue#IBB6I5@Gitee)
+* 【core 】 添加EnumUtil#getBy(Class, Func1, Object)方法(pr#1283@Gitee)
+* 【db 】 添加Entity.addCondition方法(issue#IBCDL2@Gitee)
+* 【poi 】 添加StopReadException,定义sax读取时用户可手动终止(issue#3820@Github)
+
+### 🐞Bug修复
+* 【crypto 】 修复JWTSignerUtil.createSigner中algorithmId未转换问题(issue#3806@Github)
+* 【core 】 修复DateUtil.rangeContains未重置问题(issue#IB8OFS@Gitee)
+* 【cache 】 修复StampedCache类get方法并发问题(issue#IBCIQG@Gitee)
+* 【cache 】 修复FIFOCache类使用StampedCache导致并发读的并发问题(issue#IBCIQG@Gitee)
+* 【cache 】 废弃StampedCache,可能造成Map循环调用导致死锁(issue#IBDGBZ@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.34(2024-11-25)
+
+### 🐣新特性
+* 【http 】 增加Windows微信浏览器识别(issue#IB3SJF@Gitee)
+* 【core 】 ZipUtil.unzip增加编码容错(issue#I3UZ28@Gitee)
+* 【core 】 Calculator兼容`x`字符作为乘号(issue#3787@Github)
+* 【poi 】 Excel07SaxReader中,对于小数类型,增加精度判断(issue#IB0EJ9@Gitee)
+* 【extra 】 SpringUtil增加getBean重载(issue#3779@Github)
+* 【core 】 DataSizeUtil 新增format方法(issue#IB6UUX@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复DateUtil.rangeToList中step小于等于0时无限循环问题(issue#3783@Github)
+* 【cron 】 修复cron模块依赖log模块问题
+* 【extra 】 修复MailUtil发送html格式邮件无法正常展示图片问题(pr#1279@Gitee)
+* 【core 】 【可能的向下兼容问题】修复双引号转义符转义错误问题,修改规则后,对非闭合双引号字段的策略变更,如"aa,则被识别为aa(issue#IB5UQ8@Gitee)
+* 【extra 】 修复Sftp中传入Session重连时逻辑错误问题(issue#IB69U8@Gitee)
+* 【json 】 修复JSONUtil.toBean()中将JSON数组字符串转Map对象返回错误问题(issue#3795@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.33(2024-11-05)
+
+### 🐣新特性
+* 【core 】 SyncFinisher增加setExecutorService方法(issue#IANKQ1@Gitee)
+* 【http 】 HttpConfig增加`setUseDefaultContentTypeIfNull`方法(issue#3719@Github)
+* 【core 】 用ArrayList重新实现权重随机类:WeightListRandom(pr#3720@Github)
+* 【crypto 】 SM2解密时,兼容GmSSL非压缩省略的04头的密文(issue#IAP1QJ@Gitee)
+* 【core 】 兼容NumberUtil.add方法传入整型自动类型转换为浮点类型的精度丢失问题(pr#3721@Github)
+* 【core 】 ModifierUtil明确注释,并增加hasAllModifiers方法(issue#IAQ2U0@Gitee)
+* 【http 】 HttpRequest增加setFixedContentLength方法(issue#3462@Github)
+* 【db 】 AbstractDb增加getDs方法(issue#IARKZL@Gitee)
+* 【db 】 QrCodeUtil添加二维码logo支持配置圆角(pr#3747@Github)
+* 【core 】 TreeUtil.buildSingle指定rootId节点存在时,作为根节点(issue#IAUSHR@Gitee)
+* 【core 】 EscapeUtil.escapeHtml4增加空处理(issue#IAZMYU@Gitee)
+* 【core 】 PropDesc.isTransientForGet使用className,避免Android下类找不到问题(issue#IB0JP5@Gitee)
+* 【core 】 优化NumberUtil.count(pr#3772@Github)
+* 【crypto 】 SM2.signHex改名为signHexFromHex,原名标记废弃,避免歧义(issue#IB0NVY@Gitee)
+* 【all 】 优化所调用的ObjectUtil#defaultIfNull避免重复创建(pr#1274@Gitee)
+* 【core 】 NetUtil.bigIntegerToIPv6增加长度修正(issue#IB27HV@Gitee)
+
+### 🐞Bug修复
+* 【json 】 修复JSONConfig.setDateFormat设置后toBean无效问题(issue#3713@Github)
+* 【core 】 修复RegexPool.CHINESE_NAME范围太大的问题(issue#IAOGDR@Gitee)
+* 【http 】 修复重定向没有按照RFC7231规范跳转的问题,修改为除了307外重定向使用GET方式(issue#3722@Github)
+* 【core 】 修复ArrayUtil.lastIndexOfSub死循环问题(issue#IAQ16E@Gitee)
+* 【core 】 修复ImgUtil.write写出临时文件未清理问题(issue#IAPZG7@Gitee)
+* 【json 】 修复ignoreNullValue在JSONArray中无效问题(issue#3759@Github)
+
+-------------------------------------------------------------------------------------------------------------
+**# 5.8.32(2024-08-30)
+
+### 🐣新特性
+* 【core 】 FileUtil.getTotalLines()支持CR换行符(issue#IAMZYR@Gitee)
+* 【json 】 GlobalSerializeMapping增加null检查(issue#IANH1Y@Gitee)
+
+### 🐞Bug修复
+* 【http 】 修复getFileNameFromDisposition不符合规范问题(issue#IAKBPD@Gitee)
+* 【crypto 】 修复SymmetricCrypto.setParams和setRandom没有加锁问题(issue#IAJIY3@Gitee)
+* 【crypto 】 修复ZipUtil压缩成流的方法检查文件时报错问题(issue#3697@Github)
+* 【core 】 修复CopyOptions.setFieldValueEditor后生成null值setIgnoreNullValue无效问题(issue#3702@Github)
+* 【json 】 修复JSONConfig.setDateFormat设置后setWriteLongAsString失效问题(issue#IALQ0N@Gitee)
+* 【core 】 修复Tree.cloneTree的Parent节点引用错误问题(issue#IANJTC@Gitee)
+
+-------------------------------------------------------------------------------------------------------------**
+# 5.8.31(2024-08-12)
+
+### 🐣新特性
+* 【core 】 TreeUtil增加build方法,可以构建Bean的树结构(pr#3692@Github)
+
+### 🐞Bug修复
+* 【extra 】 修复JakartaMailUtil引用javax的问题
+* 【core 】 修复GraphicsUtil.drawString方法签名变化导致的问题(issue#3694@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.30(2024-08-09)
+
+### 🐣新特性
+* 【core 】 Converter转换规则变更,空对象、空值转为Bean时,创建默认对象,而非null(issue#3649@Github)
+* 【core 】 UrlQuery增加remove方法
+* 【extra 】 增加JakartaMailUtil,支持新包名的mail
+* 【core 】 CharSequenceUtil增加removeAllPrefix和removeAllSuffix方法(pr#3655@Github)
+* 【core 】 CharSequenceUtil增加stripAll方法(pr#3659@Github)
+* 【crypto 】 支持"RSA/ECB/OAEPWithSHA-1AndMGF1Padding"的RSA加解密(pr#3675@Github)
+* 【core 】 Opt增加ifFail(pr#1239@Gitee)
+* 【poi 】 增加GlobalPoiConfig(issue#IAEHJH@Gitee)
+* 【core 】 优化IndexedComparator性能(pr#1240@Gitee)
+* 【http 】 改进ContentType.get忽略空格(pr#3664@Github)
+* 【http 】 CompressUtil.createExtractor支持tgz自动识别(pr#3674@Github)
+* 【poi 】 ExcelWriter.autoSizeColumn增加可选widthRatio参数,可配置中文字符宽度倍数(pr#3689@Github)
+* 【mail 】 MailAccount增加自定义参数支持(issue#3687@Github)
+* 【mail 】 增加文字颜色与背景颜色色差设置(pr#1252@gitee)
+* 【mail 】 XmlUtil增加xmlToBean重载,支持CopyOptions参数(issue#IAISBB@gitee)
+* 【core 】 增加默认色差方法(pr#1257@gitee)
+* 【all 】 单元测试由Junit4变更为Junit5
+
+### 🐞Bug修复
+* 【core 】 修复因RFC3986理解有误导致的UrlPath处理冒号转义问题(issue#IAAE88@Gitee)
+* 【core 】 修复FileUtil.cleanEmpty无法正确清空递归空目录问题(pr#1233@Gitee)
+* 【core 】 修复BeanUtil.copyProperties中mapToMap时key被转为String问题(issue#3645@Github)
+* 【core 】 修复FileUtil.file末尾换行符导致路径判断错误的问题(issue#IAB65V@Gitee)
+* 【core 】 修复FileTypeUtil.getType空指针问题(issue#IAD5JM@Gitee)
+* 【core 】 修复IdcardUtil.isValidHKCard校验问题(issue#IAFOLI@Gitee)
+* 【core 】 修复Convert.digitToChinese(0)输出金额无`元整问题`(issue#3662@Github)
+* 【core 】 修复CsvParser中对正文中双引号处理逻辑问题(pr#1244@Gitee)
+* 【core 】 修复ZipUtil.zip压缩到本目录时可能造成的死循环问题(issue#IAGYDG@Gitee)
+* 【cache 】 修复AbstractCache.get中锁不一致导致的并发问题(issue#3686@Github)
+* 【cron 】 修复CronPatternUtil.nextDateAfter栈溢出问题(issue#3685@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.29(2024-07-03)
+
+### 🐣新特性
+* 【core 】 DateUtil增加offsetYear方法
+* 【core 】 ListUtil增加move方法(issue#3603@Github)
+* 【core 】 CollUtil.subtract增加空判定(issue#3605@Github)
+* 【core 】 优化DateUtil.format(Date date, String format)接口效率(pr#1226@Gitee)
+* 【csv 】 CsvWriter.writeBeans增加重载,可选是否写出表头(issue#IA57W2@Gitee)
+* 【core 】 BetweenFormatter支持自定义设置单位(pr#1228@Gitee)
+* 【cache 】 Cache.put变更策略,对于替换的键值对,不清理队列(issue#3618@Github)
+* 【core 】 添加 Windows 资源管理器风格字符串比较器(pr#3620@Github)
+* 【core 】 Week.of支持中文名称(issue#3637@Github)
+* 【core 】 ThreadUtil.newExecutor等方法变更方法签名,返回值变更为ThreadPoolExecutor(pr#1230@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复AnnotationUtil可能的空指针错误
+* 【core 】 修复BeanUtil.isBean判断Dict错误问题(issue#I9VTZG@Gitee)
+* 【core 】 修复VersionComparator传入空字符串报错问题(pr#3614@Github)
+* 【core 】 修复CaseInsensitiveLinkedMap顺序错误问题(issue#IA4K4F@Gitee)
+* 【core 】 修复DateUtil.offset空指针问题(issue#3617@Github)
+* 【core 】 修复PathMover.moveContent问题(issue#IA5Q8D@Gitee)
+* 【db 】 修复PooledConnection可能的数据库驱动未找到问题(issue#IA6EUQ@Gitee)
+* 【http 】 修复Mac下的微信浏览器被识别为移动端问题(issue#IA74K2@Gitee)
+* 【core 】 修复Tailer指定初始读取行数的计算错误问题(issue#IA77ML@Gitee)
+* 【http 】 修复getFileNameFromDisposition获取头错误问题(issue#3632@Github)
+* 【core 】 修复\n#出现在双引号中解析错误问题(issue#IA8WE0@Gitee)
+* 【core 】 修复FastDatePrinter处理YY错误问题(issue#3641@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.28(2024-05-29)
+
+### 🐣新特性
+* 【core 】 修正XmlUtil的omitXmlDeclaration描述注释(issue#I9CPC7@Gitee)
+* 【core 】 StrUtil增加toStringOrEmpty方法(issue#I9CPC7@Gitee)
+* 【extra 】 设置jsch登录认证方式,跳过Kerberos身份验证(pr#3530@Github)
+* 【extra 】 增加设置验证码大小和针对alias注释(pr#3533@Github)
+* 【json 】 JSONConfig增加setWriteLongAsString可选是否将Long写出为String类型(issue#3541@Github)
+* 【cache 】 CacheUtil.newTimedCache增加有schedulePruneDelay参数的重载方法(issue#I9HO25@Gitee)
+* 【core 】 NumberChineseFormatter提供阿拉伯转中文支持多位小数的方法(pr#3552@Github)
+* 【captcha】 Captcha.setBackground为null时背景透明(issue#3558@Github)
+* 【captcha】 HttpDownloader.downloadBytes增加超时参数重载(issue#3556@Github)
+* 【http 】 增加ExceptionFilter和DefaultExceptionFilter支持异常处理(issue#3568@Github)
+* 【poi 】 增加ExcelWriter.addIgnoredErrors,支持忽略警告小标
+* 【core 】 PropertyComparator增加compareSelf构造重载(issue#3569@Github)
+* 【db 】 增加OceanBase的driver推断(pr#1217@Gitee)
+* 【http 】 HttpRequest#get不再尝试File路径(issue#I9O6DA@Gitee)
+* 【core 】 增加IdConstants,提高Snowflake初始化性能(issue#3581@Github)
+* 【core 】 优化 CharSequenceUtil工具类 startWithAny()、startWithAnyIgnoreCase() 参数命名错误问题(pr#1219@Gitee)
+* 【core 】 ListUtil.setOrPadding增加重载,可选限制index大小(issue#3586@Github)
+* 【http 】 getFileNameFromDisposition更加规范,从多个头的值中获取,且`filename*`优先级更高(pr#3590@Gitee)
+* 【core 】 CsvWriter增加重载writeBeans方法,支持可选bean字段(pr#1222@Gitee)
+* 【core 】 LocalDateTimeUtil增加beginOfDay和endOfDay重载(issue#3594@Github)
+* 【core 】 NumberUtil.pow支持负数(issue#3598@Github)
+
+### 🐞Bug修复
+* 【http 】 修复HttpUtil.urlWithFormUrlEncoded方法重复编码问题(issue#3536@Github)
+* 【core 】 修复FileMagicNumber.getMagicNumber空指针问题(issue#I9FE8B@Gitee)
+* 【extra 】 修复CompressUtil工具多出\问题(issue#I71K5V@Gitee)
+* 【db 】 解决oracle情况下setObject(inputStream)报错问题,java.sql.SQLException: 无效的列类型问题(pr#1207@Gitee)
+* 【core 】 解决CalendarUtil.isSameDay时区不同导致结果错误问题(pr#3548@Github)
+* 【core 】 修复RandomUtil.randomStringWithoutStr方法问题(pr#1209@Gitee)
+* 【http 】 修复HttpRequest.header相同key被覆盖问题(issue#I9I61C@Gitee)
+* 【core 】 修复TemporalAccessorConverter自定义格式转换问题(issue#I9HQQE@Gitee)
+* 【cron 】 修复CronPattern.nextMatchAfter匹配初始值问题(issue#I9FQUA@Gitee)
+* 【core 】 修复FileUtil.copyFile没有创建父目录导致的问题(issue#3557@Github)
+* 【http 】 修复HttpDownloader全局超时无效问题(issue#3556@Github)
+* 【core 】 修复ZipReader.checkZipBomb遇到空目录报错问题(issue#I9K494@Gitee)
+* 【db 】 修复Oracle下特殊表名导致meta信息获取不到问题(issue#I9BANE@Gitee)
+* 【db 】 修复FuncComparator.thenComparing不生效问题(issue#3569@Github)
+* 【core 】 修复EnumUtil空指针问题(issue#I9NSZ4@Gitee)
+* 【core 】 修复NumberWordFormatter.format小数问题(issue#3579@Github)
+* 【db 】 修复JndiDSFactory空指针问题
+* 【core 】 修复BiMap.put错误的返回值(pr#1218@Gitee)
+* 【core 】 修复BooleanUtil.andOfWrap针对null错误问题(issue#3587@Github)
+* 【core 】 修复FileUtil#getTotalLines在JDK9+结果错误问题(issue#3591@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.27(2024-03-29)
+
+### 🐣新特性
+* 【extra 】 FreemarkerEngine修改默认版本参数
+* 【db 】 增加达梦数据库方言(pr#1178@Gitee)
+* 【core 】 HexUtil#format方法增加prefix参数(issue#I93PU9@Gitee)
+* 【core 】 StrUtil.replace歧义,修改为replaceByCodePoint(issue#I96LWH@Gitee)
+* 【core 】 FileUtil和PathUtil增加Resource重载(issue#I97FJT@Gitee)
+* 【core 】 优化ThreadUtil.safeSleep,使用System.nanoTime()(issue#I9BMGK@Gitee)
+* 【db 】 新增数据库Wrapper支持反解(pr#1192@Gitee)
+* 【core 】 新增RFC2822日期格式解析支持(issue#I9C2D4@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复PathMover对目标已存在且只读文件报错错误问题(issue#I95CLT@Gitee)
+* 【json 】 修复JSONUtil序列化和反序列化预期的结果不一致问题(pr#3507@Github)
+* 【http 】 修复CVE-2022-22885,HttpGlobalConfig可选关闭信任host(issue#2042@Github)
+* 【core 】 修复DateUtil.betweenYear闰年2月问题(issue#I97U3J@Gitee)
+* 【captcha】 修复Graphics2D的资源没释放问题(issue#I98PYN@Gitee)
+* 【core 】 修复ClassUtil.getTypeArgument() 获取泛型存在null问题(issue#3516@Github)
+* 【core 】 修复图片操作未调用flush导致资源未释放问题(issue#I9C7NA@Gitee)
+* 【cron 】 修复cron中在小月时使用“L”的计算问题(pr#1189@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.26(2024-02-10)
+
+### 🐣新特性
+* 【db 】 RedisDS增加user支持(issue#I8XEQ4@Gitee)
+* 【core 】 MapUtil增加partition方法(pr#1170@Gitee)
+* 【core 】 增加Version类(issue#I8Z3VE@Gitee)
+
+### 🐞Bug修复
+* 【crypto】 修复BouncyCastleProvider导致graalvm应用报错UnsupportedFeatureError(pr#3464@Github)
+* 【http 】 修复UserAgentUtil对QQ浏览器识别问题(issue#I8X5XQ@Gitee)
+* 【core 】 修复BeanToMapCopier获取类型数组越界问题(issue#3468@Github)
+* 【extra 】 修复SshjSftpSession关闭导致的问题(issue#3472@Github)
+* 【http 】 修复HtmlUtil.removeHtmlAttr处理空格问题(issue#I8YV0K@Gitee)
+* 【core 】 修复CollUtil.containsAll在coll2长度大于coll1时逻辑歧义问题(issue#I8Z2Q4@Gitee)
+* 【poi 】 修复当sheetName 不存在时,ExcelUtil.getReader方法不会释放文件问题(issue#I8ZIQC@Gitee)
+* 【crypto】 通过添加系统属性hutool.crypto.decodeHex强制关闭hex识别以解决hex和Base64歧义问题(issue#I90M9D@Gitee)
+* 【core 】 修复VersionComparator违反传递问题(issue#I8Z3VE@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.25(2024-01-11)
+
+### 🐣新特性
+* 【core 】 WatchServer新增通过Path获取WatchKey方法(pr#1145@Gitee)
+* 【core 】 CopyOptions中增加setAutoTransCamelCase方法(issue#3452@Github)
+* 【captcha】 验证码生成器增加构造方法,可自定义随机数字符集(pr#1147@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复StrJoin当append内容后调用length()会出现空指针问题(issue#3444@Github)
+* 【core 】 修复PostgreSQL、H2使用upsert字段大小写问题(issue#I8PB4X@Gitee)
+* 【core 】 修复RandomUtil.randomInt,RandomUtil.randomLong边界问题(pr#3450@Github)
+* 【db 】 修复Druid连接池无法设置部分属性问题(issue#I8STFC@Gitee)
+* 【core 】 修复金额转换为英文时缺少 trillion 单位问题(pr#3454@Github)
+* 【json 】 增加ParseConfig,通过增加maxNestingDepth参数避免StackOverflowError问题,修复CVE-2022-45688漏洞(issue#2748@Github)
+* 【system】 修复UserInfo中用户名加/问题(pr#3458@Github)
+* 【core 】 修复NumberUtil.toBigDecimal方法报StackOverflowError(CVE-2023-51080)(issue#3423@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.24(2023-12-23)
+
+### 🐣新特性
+* 【cache 】 Cache增加get重载,可自定义超时时间(issue#I8G0DL@Gitee)
+* 【cache 】 JWT#sign增加重载,可选是否增加默认的typ参数(issue#3386@Github)
+* 【db 】 增加识别OpenGauss的驱动类(issue#I8K6C0@Gitee)
+* 【core 】 修复CharSequenceUtil注释和引用,避免循环引用
+* 【extra 】 SpringUtil增加getProperty重载(pr#1122@Gitee)
+* 【core 】 FileTypeUtil增加null判断(issue#3419@Github)
+* 【core 】 DateUtil.parse支持毫秒时间戳(issue#I8NMP7@Gitee)
+* 【extra 】 优化TokenizerEngine使用IK分词器支持并发(pr#3427@Github)
+* 【core 】 Opt.ofEmptyAble支持更多类型(issue#I8OOSY@Gitee)
+* 【http 】 HTMLFilter保留p标签(issue#3433@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复LocalDateTime#parseDate未判断空问题(issue#I8FN7F@Gitee)
+* 【http 】 修复RootAction send404 抛异常问题(pr#1107@Gitee)
+* 【extra 】 修复Archiver 最后一个 Entry 为空文件夹时未关闭 Entry问题(pr#1123@Gitee)
+* 【core 】 修复ImgUtil.convert png转jpg在jdk9+中失败问题(issue#I8L8UA@Gitee)
+* 【cache 】 修复StampedCache的get方法非原子问题(issue#I8MEIX@Gitee)
+* 【core 】 修复StrSplitter.splitByRegex使用空参数导致的OOM问题(issue#3421@Github)
+* 【db 】 修复嵌套SQL中order by子句错误截断问题(issue#I89RXV@Gitee)
+* 【http 】 修复graalvm编译后,未读取Content-Length可能导致的读取时间过长问题(issue#I6Q30X@Gitee)
+* 【core 】 修复JavaSourceCompiler.addSource目录处理错误问题(issue#3425@Github)
+* 【core 】 修复时间戳转Bean时异常问题(issue#I8NMP7@Gitee)
+* 【core 】 修复PostgreSQL使用upsert字段大小写问题(issue#I8PB4X@Gitee)
+* 【extra 】 修复TinyPinyinEngine可能的空指针问题(issue#3437@Github)
+* 【core 】 修复graalvm原生打包使用http工具被转为file协议问题(issue#I8PY3Y@Gitee)
+* 【poi 】 修复cloneSheet参数错误导致非XSSFWorkbook错误命名问题(issue#I8QIBB@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.23(2023-11-12)
+
+### 🐣新特性
+* 【json 】 改进TemporalAccessorSerializer支持dayOfMonth和month枚举名(issue#I82AM8@Gitee)
+* 【core 】 新增ProxySocketFactory
+* 【http 】 UserAgent增加百度浏览器识别(issue#I847JY@Gitee)
+* 【core 】 ReflectUtil.getFieldsValue增加Filter重载(pr#1090@Gitee)
+* 【core 】 Snowflake增加方法:根据传入时间戳,计算ID起终点(pr#1096@Gitee)
+* 【core 】 PathUtil增加loopFiles重载,可选是否追踪软链(issue#3353@Github)
+
+### 🐞Bug修复
+* 【cron 】 修复Cron表达式range解析错误问题(issue#I82CSH@Gitee)
+* 【core 】 修复VersionComparator在极端数据排序时候违反了自反性问题(issue#I81N3H@Gitee)
+* 【json 】 修复JSONStrFormatter:format函数对于转义符号处理逻辑错误问题(issue#I84V6I@Gitee)
+* 【core 】 修复特定情况下BiMap覆盖Value后,仍能通过旧Value查询到Key问题(issue#I88R5M@Gitee)
+* 【core 】 修复aop的afterException无法生效问题(issue#3329@Github)
+* 【core 】 修复TypeUtil.getClass方法强转报错问题(pr#1092@Github)
+* 【core 】 修复DataSize.parse(size)不支持空格问题(issue#I88Z4Z@Gitee)
+* 【http 】 修复SimpleServer在添加的HttpFilter中有获取请求参数时报错问题(issue#3343@Github)
+* 【http 】 修复options请求无响应体问题
+* 【core 】 ImgUtil的sliceByRowsAndCols背景无法透明问题(issue#3347@Github)
+* 【core 】 修复ClassUtil#scanJar未正确关闭文件问题(issue#3361@Github)
+* 【db 】 修复Column.getDigit返回值错误问题(issue#3370@Github)
+* 【core 】 修复合成注解在并发环境无法保证正确缓存属性值的问题(pr#1097@Gitee)
+* 【core 】 修复CollectorUtil.reduceListMap与collectors.groupby一起使用时出现与预期不符问题(pr#1102@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.22(2023-09-13)
+
+### 🐣新特性
+* 【core 】 NumberUtil.nullToZero增加重载(issue#I7PPD2@Gitee)
+* 【core 】 DesensitizedUtil增加清空策略(issue#I7PUJ2@Gitee)
+* 【all 】 修改异常包装策略:运行时异常不包装,只包装非运行时异常(issue#I7RJZT@Gitee)
+* 【core 】 增加IJSONTypeConverter,避免反射调用(pr#1051@Gitee)
+* 【http 】 优化HttpUtil.urlWithForm方法(pr#1052@Gitee)
+* 【http 】 优化HttpUtil.urlWithForm方法(pr#1052@Gitee)
+* 【cron 】 优化PatternParser支持年的步进(issue#I7SMP7@Gitee)
+* 【core 】 TreeUtil增加getParentsId方法(issue#I7TDCF@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复NumberUtil.toBigDecimal转换科学计数法问题(issue#3241@Github)
+* 【core 】 修复PathUtil.moveContent当target不存在时会报错问题(issue#3238@Github)
+* 【db 】 修复SqlUtil.formatSql 格式化的sql换行异常(pr#3247@Github)
+* 【core 】 修复DateUtil.parse 给定一个时间解析错误问题(issue#I7QI6R@Gitee)
+* 【core 】 去除默认的ACCEPT_LANGUAGE(issue#3258@Github)
+* 【core 】 修复FieldsComparator比较结果不正确问题(issue#3259@Github)
+* 【core 】 修复Db.findAll全局忽略大小写无效问题(issue#I7T30Y@Gitee)
+* 【core 】 修复Ipv4Util.getEndIpLong 取反符号导致数据越界(issue#I7U1OQ@Gitee)
+* 【http 】 修复302重定向时,Location中的问号被转义问题(issue#3265@Github)
+* 【core 】 修复CombinationAnnotationElement判断循环问题(pr#3267@Github)
+* 【core 】 修复StrUtil#containsAny NPE问题(pr#1063@Gitee)
+* 【all 】 修复SONArray的add()方法抛出OutOfMemory异常问题(issue#3286@Github)
+* 【core 】 修复fillColumns空指针问题(issue#3284@Github)
+* 【core 】 修复Convert不能转换Optional和Opt问题(issue#I7WJHH@Gitee)
+* 【core 】 修复DateUtil.age年龄计算问题(issue#I7XMYW@Gitee)
+* 【core 】 修复JSONUtil.parse()溢出问题(issue#3289@Github)
+* 【core 】 修复Tailer stop NPE问题(pr#1067@Gitee)
+* 【json 】 修复toJSONString导致CPU使用率高的问题(issue#3297@Github)
+* 【core 】 修复NumberUtil.parseInt 16进制解析错误的问题(pr#1071@Gitee)
+* 【core 】 修复CopyOptions.setIgnoreCase和setIgnoreProperties冲突问题(issue#I80FP4@Gitee)
+* 【core 】 修复LocalDateTimeUtil.of 某些特殊TemporalAccessor无法返回正确结果的问题(issue#3301@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.21(2023-07-29)
+
+### 🐣新特性
+* 【core 】 list 为空时,CollUtil.max等返回null而非异常(pr#1027@Gitee)
+* 【poi 】 ExcelReader.getWriter逻辑变更,当从非文件读取时,获取sheet,而非空表格。
+* 【core 】 Ipv4Util 新增方法:检测指定 IP 地址是否匹配通配符(pr#3171@Github)
+* 【core 】 DateUtil.parse适配6位毫秒格式(issue#I7H34N@Gitee)
+* 【core 】 RandomUtil增加可选是否包含边界的重载(issue#3182@Github)
+* 【core 】 StrUtil增加truncateByByteLength方法(pr#3176@Github)
+* 【core 】 身份证工具类isValidCard18、isValidCard15入参null直接返回null(pr#1034@Gitee)
+* 【http 】 使用multiparty方式支持body参数(issue#3158@Github)
+* 【core 】 ZipReader增加setMaxSizeDiff方法,自定义或关闭ZipBomb(issue#3018@Github)
+* 【db 】 Query.of(entity)构建时传入fields(issue#I7M5JU@Gitee)
+* 【db 】 clickhouse驱动名称变更为com.clickhouse.jdbc.ClickHouseDriver(issue#3224@Github)
+* 【core 】 UrlResource增加size方法(issue#3226@Github)
+
+### 🐞Bug修复
+* 【core 】 修复MapUtil工具使用filter方法构造传入参数结果问题(issue#3162@Github)
+* 【core 】 修复序列化和反序列化Class问题(issue#I7FQ29@Gitee)
+* 【setting】 修复utf8-bom的setting文件读取问题(issue#I7G34E@Gitee)
+* 【core 】 修复PathUtil.getMimeType可能造成的异常(issue#3179@Github)
+* 【core 】 修复Pair序列化转换无效问题(issue#I7GPGX@Github)
+* 【core 】 修复TypeUtil.getTypeArgument对实现接口获取不全面问题(issue#I7CRIW@Gitee)
+* 【core 】 修复BeanUtil.isCommonFieldsEqual判空导致的问题
+* 【extra 】 修复CompressUtil.createArchiver 将文件压缩为tgz时文件名规则无效问题(issue#I7LLL7@Gitee)
+* 【core 】 修复脱敏银行卡号长度bug(pr#3210@Github)
+* 【jwt 】 修复JWTSignerUtil中ES256签名不符合规范问题(issue#3205@Github)
+* 【core 】 修复UserInfo获取country问题(issue#I7MCKW@Gitee)
+* 【extra 】 修复MVEL加载错误问题(issue#3214@Github)
+* 【json 】 修复JSONBeanParser在遇到List时没有被正确递归问题(issue#I7M2GZ@Gitee)
+* 【core 】 修复VersionComparator对1.0.3及1.0.2a比较有误的问题(pr#1043@Gitee)
+* 【core 】 修复IOS系统下,chrome 浏览器的解析规则有误(pr#1044@Gitee)
+* 【extra 】 修复多线程下Sftp中Channel关闭的问题(issue#I7OHIB@Gitee)
+* 【extra 】 修复CVE-2023-24163漏洞(issue#I6AJWJ@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.20(2023-06-16)
+
+### 🐣新特性
+* 【core 】 UrlQuery增加setStrict方法,区分是否严格模式(issue#I78PB1@Gitee)
+* 【poi 】 添加系列方法writeCol,以支持按列输出(pr#1003@Gitee)
+* 【core 】 CollUtil新增anyMatch和allMatch方法(pr#1008@Gitee)
+* 【core 】 CsvWriter如果开启了append=true,默认自动开启endingLineBreak=true(pr#1010@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复TreeUtil.getParentsName()获取到的路径集合中存在值为null的路径名称问题(issue#I795IN@Gitee)
+* 【core 】 修复umberUtil.parseNumber对+解析问题(issue#I79VS7@Gitee)
+* 【core 】 修复IdcardUtil.getGenderByIdCard存在潜在的异常(pr#1007@Gitee)
+* 【core 】 修复Table#contains空指针问题(issue#3135@Gitee)
+* 【core 】 修复FileUtil.checkSlip方法缺陷(issue#3140@Github)
+* 【extra 】 修复Sftp中exists方法父目录不存在时报错(issue#I7CSQ9@Gitee)
+* 【extra 】 修复xml转json再转bean失败问题(issue#3139@Github)
+* 【poi 】 修复RowUtil传入参数错误问题(issue#3139@Github)
+* 【core 】 修复XmlUtil.xmlToBean空节点转换失败问题(issue#3136@Github)
+* 【core 】 修复CVE-2023-3276漏洞,XmlUtil.readBySax问题(issue#I7DX8W@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.19(2023-05-27)
+
+### 🐣新特性
+* 【db 】 优化HttpRequest.toString()内容打印(issue#3072@Github)
+* 【poi 】 优化Sax方式读取时空白行返回0,修改为返回-1(issue#I6WYF6@Gitee)
+* 【db 】 优化count查询兼容informix(issue#I713XQ@Gitee)
+* 【core 】 去除Opt头部的GPL协议头(pr#995@Gitee)
+* 【core 】 邮箱校验添加对中文的支持(pr#997@Gitee)
+* 【core 】 FileUtil.getMimeType增加webp识别(pr#997@Gitee)
+* 【core 】 SyncFinisher增加setExceptionHandler方法(issue#I716SX@Gitee)
+* 【core 】 FileTypeUtil.getType增加文件判断(pr#3112@Github)
+* 【core 】 增加CsvWriteConfig.setEndingLineBreak配置项(issue#I75K5G@Gitee)
+* 【core 】 增加Tailer追踪文件时文件被删除的处理情况(pr#3115@Github)
+* 【core 】 DelegatedExecutorService构造方法设置成public(issue#I77LUE@Gitee)
+* 【core 】 切面代理工具中的cglib支持多参数构造生成(issue#I74EX7@Gitee)
+* 【poi 】 添加writeCellValue的重载,以支持isHeader(pr#1002@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复URLUtil.decode无法解码UTF-16问题(issue#3063@Github)
+* 【db 】 修复insertOrUpdate更新中条件字段没有移除问题(issue#I6W91Z@Gitee)
+* 【core 】 修复VIN(车架号)正则问题(pr#3078@Github)
+* 【core 】 修复HtmlUtil的removeHtmlAttr方法匹配问题(issue#I6YNTF@Gitee)
+* 【core 】 修复JSONUtil.toBean目标存在Map字段无序问题(issue#I6YN2A@Gitee)
+* 【http 】 修复HttpDownloader.downloadFile 方法缺少static问题(issue#I6Z8VU@Gitee)
+* 【core 】 修复NumberUtil mul 传入null的string入参报错问题(issue#I70JB3@Gitee)
+* 【core 】 修复ZipReader.get调用reset异常问题(issue#3099@Github)
+* 【core 】 修复FileUtil.createTempFile可能导致的漏洞(issue#3103@Github)
+* 【cron 】 修复SystemTimer无法结束进程问题(issue#3090@Github)
+* 【core 】 修复BeanUtil.copyToList复制Long等类型错误问题(issue#3091@Github)
+* 【poi 】 修复MapRowHandler结果Map无序问题(issue#I71SE8@Github)
+* 【db 】 修复SqlExecutor.execute执行ORACLE insert into select报ORA-00933问题(issue#I778U7@Gitee)
+* 【db 】 修复AbstractDb#page分页查询异常问题(issue#I73770@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.18 (2023-04-27)
+
+### 🐣新特性
+* 【extra 】 JschUtil新增一个重载方法以支持私钥以byte数组形式载入(pr#3057@Github)
+* 【crypto】 优化MD5性能(issue#I6ZIQH@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复CollUtil.reverseNew针对非可变列表异常(issue#3056@Github)
+* 【all 】 修复junit被关联引入的bug(issue#3062@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.17 (2023-04-12)
+
+### 🐣新特性
+* 【core 】 SerializeUtil.deserialize增加白名单类,避免RCE vulnerability(issue#3021@Github)
+* 【poi 】 ExcelWriter在关闭后不清空currentRow,以便复用(issue#3025@Github)
+* 【core 】 完善HttpStatus,参考相关规范,补全缺失的状态码(pr#968@Gitee)
+* 【core 】 NumberUtil增加(pr#968@Gitee)
+* 【core 】 Number128增加hash和equals方法(pr#968@Gitee)
+* 【core 】 NamingCase.toCamelCase新增重载,可选是否转换其他字符为小写(issue#3031@ithub)
+* 【core 】 新增JdkUtil
+* 【core 】 DateUtil.getZodiac增加越界检查(issue#3036@Github)
+* 【core 】 CsvReader修改策略,添加可选是否关闭Reader重载,默认不关闭Reader(issue#I6UAX1@Gitee)
+* 【core 】 isNotEmpty修改规则,避开IDEA错误提示(pr#974@Gitee)
+
+### 🐞Bug修复
+* 【core 】 CollUtil.split优化切割列表参数判断,避免OOM(pr#3026@Github)
+* 【core 】 修复FileUtil.move传入相同目录或子目录丢失源目录的问题(pr#3032@Github)
+* 【core 】 修复SafeConcurrentHashMap.computeIfAbsent可能存在的结果为null的情况(issue#I6RVMY@Gitee)
+* 【json 】 修复Pair反序列化报错问题(issue#I6SZYB@Gitee)
+* 【core 】 修复使用AnnotationUtil.getAnnotationAlias获取注解时可能会出现空指针的问题(pr#975@Gitee)
+* 【json 】 修复没有属性的对象转json字符串抛异常问题(issue#3051@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.16 (2023-03-26)
+
+### 🐣新特性
+* 【core 】 改进Calculator.conversion,兼容乘法符号省略写法(issue#2964@Github)
+* 【core 】 改进XmlUtil.xmlToBean,支持xml转bean时父节点忽略大小写
+* 【core 】 优化ArrayUtil的空判断(pr#2969@Github)
+* 【extra 】 优化SpringUtil在非Spring环境下的异常(issue#2835@Github)
+* 【core 】 StrUtil增加commonPrefix和commonSuffix方法(pr#3007@Github)
+* 【core 】 NumberUtil增加重载parseXXX方法, 解析失败返回默认值(pr#3007@Github)
+* 【core 】 FileUtil增加readLines重载,支持filter(pr#3006@Github)
+* 【json 】 当用户选择ignoreError时,错误对象转JSON也忽略
+
+### 🐞Bug修复
+* 【crypto】 修复NoSuchMethodError未捕获问题(issue#2966@Github)
+* 【poi 】 修复SXSSFWorkbook调用setComment时错位的问题(issue#I6MBS5@Gitee)
+* 【core 】 修复BeanUtil.hasGetter没有跳过getClass方法的问题(issue#I6MBS5@Gitee)
+* 【core 】 修复FileMagicNumber长度判断问题导致的越界异常(issue#I6MACI@Gitee)
+* 【core 】 修复DateUtil针对ISO8601时间格式部分场景下的解析存在问题(issue#2981@Github)
+* 【core 】 修复JSONUtil.toBean可能的空指针问题(issue#2987@Github)
+* 【core 】 修复CalendarUtil.isSameMonth没有判断公元前导致不一致的问题(issue#3011@Github)
+* 【core 】 修复WatchUtil createModify maxDepth传递后没有使用问题(issue#3005@Github)
+* 【core 】 修复NullComparator反转无效问题(pr#964@Gitee)
+* 【setting】 修复props.toBean 数组字段未赋值问题(issue#3008@Github)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.15 (2023-03-09)
+
+### 🐣新特性
+* 【http 】 新增followRedirectsCookie配置,支持开启自动重定向携带cookie(pr#2961@Github)
+
+### 🐞Bug修复
+* 【all 】 修复Automatic-Module-Name错误问题(issue#2952@Github)
+* 【core 】 修复NumberWithFormat导致转换Long异常问题(issue#I6L2LO@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+# 5.8.14 (2023-03-05)
+
+### 🐣新特性
+* 【core 】 增加PathMover(issue#I666HB@Github)
+
+### 🐞Bug修复
+* 【core 】 修复FileUtil.moveContent会删除源目录的问题(issue#I666HB@Github)
+* 【http 】 修复HttpBase.body导致的空指针问题
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.13 (2023-03-03)
+
+### 🐣新特性
+* 【core 】 PhoneUtil.isTel400800支持400-XXX-XXXX格式(issue#2929@Github)
+* 【core 】 build(pom): 添加 Automatic-Module-Name属性(pr#2926@Github)
+* 【core 】 根据JDK-8080225修改了部分新建文件输入流和文件输出流的创建方式(pr#2930@Github)
+* 【http 】 HttpRequest#body增加支持Resource重载(issue#2901@Github)
+* 【core 】 JavaSourceCompiler#compile增加自定义options重载(issue#I6IVZK@Gitee)
+
+### 🐞Bug修复
+* 【db 】 修复识别JDBC驱动时重复问题(pr#940@Gitee)
+* 【core 】 修复法定年龄计算的BUG(pr#935@Gitee)
+* 【core 】 修复FileUtil.rename报NoSuchFileException问题(pr#2894@Github)
+* 【core 】 修复StrUtil.split切分长度为0时的bug(pr#944@Gitee)
+* 【core 】 修复ReUtil.delAll方法当 content 仅为空格时的问题(issue#I6GIMT@Gitee)
+* 【core 】 修复ReUtil.delAll方法当 content 仅为空格时的问题(issue#I6GIMT@Gitee)
+* 【core 】 修复文件内容跟随在调用stop后,文件依旧被占用问题(issue#I6GFD2@Gitee)
+* 【core 】 修复ReflectUtil.invokeRaw方法中参数类型转换动作未生效的问题(pr#2912@Github)
+* 【core 】 修复isXXX转换时的匹配问题(issue#I6H0XF@Gitee)
+* 【core 】 修复MutableObj.equals空指针问题
+* 【core 】 修复JavaSourceFileObject在编译错误时抛出IOException异常而非CompilerException问题(pr#2942@Github)
+* 【jwt 】 修复JWT自定义时间格式后的时间戳转换问题(issue#I6IS5B@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.12 (2023-02-09)
+
+### 🐣新特性
+* 【http 】 HttpGlobalConfig.allowPatch()调用时忽略错误(issue#2832@Github)
+* 【core 】 重构根据file magic number判断文件类型(pr#2834@Github)
+* 【core 】 增加WGS84 坐标与墨卡托投影互转(pr#2811@Github)
+* 【extra 】 ServletUtil遵循rfc 3986优化(issue#I6ALAO@Gitee)
+* 【http 】 HttpUtil.decodeParams增加isFormUrlEncoded重载(pr#918@Gitee)
+* 【db 】 AbstractDb添加返回类型为PageResult的page重载方法(pr#916@Gitee)
+* 【core 】 DesensitizedUtil增加对IPv4和IPv6支持(issue#I6ABCS@Gitee)
+* 【core 】 针对CollUtil.subtract coll1 为只读集合的补偿(pr#2865@Github)
+* 【core 】 DateUtil.date方法统一修改规则,传入null返回null(pr#2877@Github)
+* 【core 】 DateUtil.parseUTC统一规范,舍弃3位毫秒数后的数字(pr#2889@Github)
+
+### 🐞Bug修复
+* 【core 】 修复HexUtil.isHexNumber()对"-"的判断问题(issue#2857@Github)
+* 【core 】 修复FileTypeUtil判断wav后缀的录音文件类型不能匹配问题(pr#2834@Github)
+* 【core 】 修复FileUtil的rename在newName与原文件夹名称一样时,文件夹会被删除问题(issue#2845@Github)
+* 【core 】 修复IoUtil.readBytes使用SocketInputStream读取不完整问题(issue#I6AT49@Gitee)
+* 【core 】 修复ClassScanner自定义classload无效问题(issue#I68TV2@Gitee)
+* 【core 】 【重要】删除XmlUtil.readObjectFromXml方法,避免漏洞(issue#2855@Github)
+* 【core 】 修复Ipv4Util.list()方法的bug(pr#929@Gitee)
+* 【poi 】 修复“sax方式读取excel2003版本,会调用两次doAfterAllAnalysed方法”问题。(pr#919@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.11 (2022-12-27)
+
+### 🐣新特性
+* 【core 】 CharUtil.isBlankChar增加\u180e(pr#2738@Github)
+* 【core 】 SyncFinisher线程同步结束器添加立即结束方法(pr#879@Gitee)
+* 【core 】 HtmlUtil中escape方法,增加不断开空格(nbsp)转译,防止xss攻击(pr#2755@Github)
+* 【extra 】 修正sftp.cd方法 方法注释和实际效果不符(issue#2758@Github)
+* 【core 】 修改PhoneUtil容易歧义的注释(issue#I63GWK@Gitee)
+* 【crypto】 KeyUtil中的读取KeyStore文件的方法增加全局Provider(issue#I6796G@Gitee)
+* 【extra 】 CompressUtil 新增 stripComponents 参数(pr#904@Gitee)
+* 【extra 】 ServletUtil和JakartaServletUtil新增获取所有响应头的方法(pr#2828@Github)
+* 【core 】 BooleanUtil增加toString重载(pr#2816@Github)
+
+### 🐞Bug修复
+* 【json 】 修复普通byte数组转JSONArray时的异常(pr#875@Gitee)
+* 【core 】 修复ArrayUtil.insert()不支持原始类型数组的问题(pr#874@Gitee)
+* 【core 】 修复HexUtil.isHexNumber()判断逻辑超出long的精度问题(issue#I62H7K@Gitee)
+* 【core 】 修复BiMap中未重写computeIfAbsent和putIfAbsent导致双向查找出问题(issue#I62X8O@Gitee)
+* 【json 】 修复JSON解析栈溢出部分问题(issue#2746@Github)
+* 【json 】 修复getMultistageReverseProxyIp未去除空格问题(issue#I64P9J@Gitee)
+* 【db 】 修复NamedSql中in没有判断大小写问题(issue#2792@Github)
+* 【core 】 修复ZIP bomb漏洞(issue#2797@Github)
+* 【core 】 修复JSONXMLSerializer将Json转为XML时,遇到嵌套需要递归情况时会丢失contentKeys问题(pr#903@Gitee)
+* 【db 】 修复使用mariadb通过jdbcurl创建SimpleDataSource报NullPointException(pr#900@Gitee)
+* 【core 】 修复UrlBuilder中参数中包括"://"判断错误问题(pr#898@Gitee)
+* 【core 】 修复IndexedComparator导致的数据错乱问题(ExcelWriter使用部分别名导致字段丢失)(issue#I66Z6B@Gitee)
+* 【crypto】 修复sm2构造方法NullPointerException(pr#2820@Github)
+* 【core 】 修复ConverterRegistry中无效加载导致的问题(issue#2812@Github)
+* 【core 】 修复CoordinateUtil坐标转换参数错误(pr#895@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.10 (2022-11-17)
+
+### 🐣新特性
+* 【http 】 HttpResponse增加getFileNameFromDisposition方法(pr#2676@Github)
+* 【core 】 FileUtil.copy,当来源为文件时,返回文件而非目录(issue#I5YCVL@Gitee)
+* 【db 】 DialectFactory增加identifyDriver重载(issue#I5YWI6@Gitee)
+* 【core 】 去除ClassloaderUtil的Cache(issue#I5YWI6@Gitee)
+* 【core 】 ClassScanner 增加忽略加载错误类的扫描方法(pr#855@Gitee)
+* 【core 】 DateUtil和LocalDateTimeUtil添加区间退化为点,点与区间,点与点之间关系判断。(pr#2725@Github)
+* 【http 】 UserAgentUtil增加对钉钉PC端的支持(issue#I60UOP@Gitee)
+* 【extra 】 兼容ZipArchiveInputStream多参数情况(issue#2736@Github)
+
+### 🐞Bug修复
+* 【db 】 修复分页时order by截断问题(issue#I5X6FM@Gitee)
+* 【core 】 修复Partition计算size除数为0报错问题(pr#2677@Github)
+* 【core 】 由于对于ASCII的编码解码有缺陷,且这种BCD实现并不规范,因此BCD标记为弃用(issue#I5XEC6@Gitee)
+* 【core 】 修复IoUtil.copyByNIO方法写出时没有flush的问题
+* 【core 】 修复TreeBuilder中使用HashMap导致默认乱序问题(issue#I5Z8C5@Gitee)
+* 【core 】 修复StrUtil.subWithLength负数问题(issue#I5YN49@Gitee)
+* 【core 】 修复DefaultTrustManager空指针问题(issue#2716@Github)
+* 【core 】 修复时间轮添加任务线程安全问题(pr#2712@Github)
+* 【core 】 修复 BeanUtil#copyProperties 源对象与目标对象都是 Map 时设置忽略属性无效问题(pr#2698@Github)
+* 【core 】 修复ChineseDate传入农历日期非闰月时获取公历错误问题(issue#I5YB1A@Gitee)
+* 【core 】 修复key为弱引用 value为强引用 会导致key无法被回收 弱引用失效问题(pr#2723@Github)
+* 【core 】 修复BeanUtil.copyProperties 包含EnumSet ,类型转换异常问题(pr#2684@Github)
+* 【extra 】 修复Ftp.uploadFileOrDirectory上传目录错误调用错误问题(issue#I5R2DE@Gitee)
+* 【extra 】 修复字节数组转float 返回类型却是double的bug(pr#867@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.9 (2022-10-22)
+
+### 🐣新特性
+* 【core 】 DateUtil增加isLastDayOfMonth、getLastDayOfMonth方法(pr#824@Gitee)
+* 【core 】 AnnotationUtil类支持Lambda获取某注解属性值(pr#827@Gitee)
+* 【core 】 CharUtil.isBlank添加Hangul Filler字符(issue#I5UGSQ@Gitee)
+* 【poi 】 优化合并单元格读取(issue#I5UJZ1@Gitee)
+* 【extra 】 增加QLExpress支持(issue#2653@Github)
+* 【core 】 UrlBuilder增加getPortWithDefault方法(pr#835@Gitee)
+* 【core 】 FuncKeyMap的子类,传入可被序列化的keyFunc(pr#838@Gitee)
+* 【extra 】 SpringUtil支持SpringBoot3自动配置(pr#839@Gitee)
+* 【core 】 CollectorUtil添加支持对值集合进行映射的分组方法(pr#844@Gitee)
+* 【core 】 FileTypeUtil增加ppt识别(issue#2663@Github)
+
+### 🐞Bug修复
+* 【poi 】 修复ExcelReader读取只有标题行报错问题(issue#I5U1JA@Gitee)
+* 【http 】 修复Http重定向时相对路径导致的问题(issue#I5TPSY@Gitee)
+* 【http 】 修复Http重定全局设置无效问题(pr#2639@Github)
+* 【core 】 修复ReUtil.replaceAll替换变量错误问题(pr#2639@Github)
+* 【core 】 修复FileNameUtil.mainName二级扩展名获取错误问题(issue#2642@Github)
+* 【cache 】 修复LRUCache移除事件监听失效问题(issue#2647@Github)
+* 【core 】 修复MapToMap中ignoreNullValue无效问题(issue#2647@Github)
+* 【core 】 修复ReflectUtil.invokeRaw方法转换失败抛出异常问题(pr#837@Gitee)
+* 【core 】 修复TableMap没有default方法导致的问题(issue#I5WMST@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.8 (2022-09-26)
+
+### 🐣新特性
+* 【core 】 StreamUtil.of方法新增对 Iterator 支持;StreamUtil.of(Iterable) 方法优化(pr#807@Gitee)
+* 【core 】 增加.wgt格式的MimeType(pr#2617@Github)
+* 【core 】 EnumUtil.getBy增加带默认值重载(issue#I5RZU6@Gitee)
+* 【core 】 ModifierUtil和ReflectUtil增加removeFinalModify(pr#810@Gitee)
+* 【core 】 AbsCollValueMap添加removeValue和removeValues方法,用于list value值移除(pr#813@Gitee)
+* 【extra 】 hutool-extra ftp 支持上传文件或目录(pr#821@Gitee)
+* 【core 】 CharsetDetector增加默认识别的长度(issue#2547@Github)
+
+### 🐞Bug修复
+* 【core 】 修复FileNameUtil.cleanInvalid无法去除换行符问题(issue#I5RMZV@Gitee)
+* 【core 】 修复murmur3_32实现错误(pr#2616@Github)
+* 【core 】 修复PunyCode处理域名的问题(pr#2620@Github)
+* 【core 】 修复ObjectUtil.defaultIfNull去掉误加的deprecated(issue#I5SIZT@Gitee)
+* 【core 】 修复ReflectUtil 反射方法中桥接判断问题(issue#2625@Github)
+* 【poi 】 修复ExcelWriter导出List引起的个数混乱问题(issue#2627@Github)
+* 【poi 】 修复ExcelReader读取时间变成12小时形式问题(issue#I5Q1TW@Gitee)
+* 【db 】 修复DB工具分页查询的时候oracle数据库会把ROWNUM_也带出来问题(issue#2618@Github)
+* 【crypto 】 修复部分环境下使用 Bouncy Castle可能的JCE cannot authenticate the provider BC问题(issue#2631@Github)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.7 (2022-09-15)
+
+### 🐣新特性
+* 【core 】 BooleanUtil的andOfWrap和orOfWrap()忽略null(issue#2599@Github)
+* 【jwt 】 优化JWT自动识别header中的算法,并可自定义header中key的顺序(issue#I5QRUO@Gitee)
+* 【core 】 IdcardUtil增加convert18To15方法(issue#I5QYCP@Gitee)
+* 【core 】 新增AnsiColors(改自Spring Boot)、AnsiColorWrapper,优化QrCodeUtil(pr#778@Gitee)
+* 【core 】 TemplateUtil的实现类增加getRawEngine方法(issues#2530@Github)
+* 【core 】 ImgUtil中颜色相关方法剥离到ColorUtil中
+* 【core 】 增加SafeConcurrentHashMap
+
+### 🐞Bug修复
+* 【core 】 修复ObjectUtil.defaultIfXXX中NPE问题(pr#2603@Github)
+* 【db 】 修复Hive2驱动无法识别问题(issue#2606@Github)
+* 【core 】 修复computeIfAbsent问题(issue#I5PTN3@Gitee)
+* 【extra 】 修复Ftp中路径问题(issue#I5R2DE@Gitee)
+* 【core 】 修复ConcurrentHashMap.computeIfAbsent缺陷导致的问题
+* 【core 】 修复DateUtil.parseUTC时对-的处理问题(issue#2612@Github)
+* 【core 】 修复Convert.chineseMoneyToNumber角分丢失问题(issue#2611@Github)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.6 (2022-09-05)
+
+### ❌不兼容特性
+* 【json 】 由于设计缺陷,导致JSONObject#write方法中Filter中key的泛型不得已变动为Object,以解决无法递归的bug(issue#I5OMSC@Gitee)
+
+### 🐣新特性
+* 【core 】 CollUtil新增addIfAbsent方法(pr#750@Gitee)
+* 【core 】 DateUtil.parseUTC支持只有时分的格式(issue#I5M6DP@Gitee)
+* 【core 】 NumberUtil.parseInt忽略科学计数法(issue#I5M55F@Gitee)
+* 【core 】 IterUtil.getFirst优化(pr#753@Gitee)
+* 【core 】 增加Tree add 类型校验(pr#2542@Github)
+* 【core 】 增加PunyCode处理完整域名(pr#2543@Github)
+* 【core 】 增加替换字符串中第一个指定字符串和最后一个指定字符串方法(pr#2533@Github)
+* 【jwt 】 JWT补充部分算法(pr#2546@Github)
+* 【core 】 NumberUtil.roundStr() 修改为使用toPlainString(pr#775@Gitee)
+* 【extra 】 QrCodeUtil新增SVG格式、Ascii Art字符画格式(pr#763@Gitee)
+* 【jwt 】 JWTUtil的parseToken增加空值异常抛出(issue#I5OCQB@Gitee)
+* 【extra 】 resource.loader等过期参数替换(issue#2571@Github)
+* 【core 】 添加ObjectUtil的别名工具类ObjUtil
+* 【core 】 扩展LocalDateTimeUtil.isIn方法使用场景(pr#2589@Github)
+* 【core 】 MapUtil增加根据entry分组(pr#2591@Github)
+* 【core 】 优化 getProcessorCount 潜在的获取不到的问题(pr#792@Gitee)
+* 【core 】 ImgUtil增加sliceByRowsAndCols重载方法支持自定义图片格式(pr#793@Gitee)
+*
+### 🐞Bug修复
+* 【http 】 修复https下可能的Patch、Get请求失效问题(issue#I3Z3DH@Gitee)
+* 【core 】 修复RandomUtil#randomString 入参length为负数时报错问题(issue#2515@Github)
+* 【core 】 修复SecureUtil传入null的key抛出异常问题(pr#2521@Github)
+* 【core 】 修复UrlBuilder的toURI方法将url重复编码(issue#2503@Github)
+* 【core 】 修复CollUtil.lastIndexOf序号错误问题
+* 【core 】 修复zip被识别成jar和apk被识别成jar或zip的问题(pr#2548@Github)
+* 【core 】 修复UrlBuilder.addPath 方法传入非有效路径字符串时,会出现空指针异常的问题(issue#I5O4ML@Gitee)
+* 【core 】 修复FilterIter当参数filter为空时存在问题(issue#I5OG7U@Gitee)
+* 【poi 】 修复Excel读取提示信息错误(issue#I5OSFC@Gitee)
+* 【json 】 解决JSONObject#write无法递归的bug(issue#I5OMSC@Gitee)
+* 【json 】 修复DayOfWeek转json异常问题(issue#2572@Github)
+* 【extra 】 Ftp方法isDir和exist修复及改进(pr#2574@Github)
+* 【json 】 修复JSON反序列化时,引用字段类型的自定义JsonDeserializer无效(issue#2555@Github)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.5 (2022-07-29)
+
+### ❌不兼容特性
+* 【core 】 合成注解相关功能重构,增加@Link及其子注解(pr#702@Gitee)
+
+### 🐣新特性
+* 【core 】 NumberUtil新增isIn方法(pr#669@Gitee)
+* 【core 】 修复注解工具类getAnnotations的NPE问题,注解扫描器添新功能(pr#671@Gitee)
+* 【core 】 合成注解SyntheticAnnotation提取为接口,并为实现类添加注解选择器和属性处理器(pr#678@Gitee)
+* 【core 】 增加BeanValueProvider(issue#I5FBHV@Gitee)
+* 【core 】 Convert工具类中,新增中文大写数字金额转换为数字工具方法(pr#674@Gitee)
+* 【core 】 新增CollectorUtil.reduceListMap()(pr#676@Gitee)
+* 【core 】 CollStreamUtil为空返回空的集合变为可编辑(pr#681@Gitee)
+* 【core 】 增加StrUtil.containsAll(pr#2437@Github)
+* 【core 】 ForestMap添加getNodeValue方法(pr#699@Gitee)
+* 【http 】 优化HttpUtil.isHttp判断,避免NPE(pr#698@Gitee)
+* 【core 】 修复Dict#containsKey方法没区分大小写问题(pr#697@Gitee)
+* 【core 】 增加比较两个LocalDateTime是否为同一天(pr#693@Gitee)
+* 【core 】 增加TemporalAccessorUtil.isIn、LocalDateTimeUtil.isIn(issue#I5HBL0@Gitee)
+* 【core 】 ReUtil增加getAllGroups重载(pr#2455@Github)
+* 【core 】 PageUtil#totalPage增加totalCount为long类型的重载方法(pr#2442@Github)
+* 【crypto 】 PemUtil.readPemPrivateKey支持pkcs#1格式,增加OpensslKeyUtil(pr#2456@Github)
+* 【core 】 添加了通用的注解扫描器 `GenericAnnotationScanner`,并在 `AnnotationScanner` 接口中统一提供了提前配置好的扫描器静态实例(pr#715@Github)
+* 【json 】 JSONConfig增加允许重复key配置,解决不规整json序列化的问题(pr#720@Github)
+* 【core 】 完善了codec包下一些方法的入参空校验(pr#719@Gitee)
+* 【extra 】 完善QrCodeUtil对于DATA_MATRIX生成的形状随机不可指定的功能(pr#722@Gitee)
+* 【core 】 修改NetUtil.ipv6ToBigInteger,原方法标记为过期(pr#2485@Github)
+* 【core 】 ZipUtil新增zip文件解压大小限制,防止zip炸弹(pr#726@Gitee)
+* 【core 】 CompressUtil增加压缩和解压tgz(.tar.gz)文件(issue#I5J33E@Gitee)
+*
+### 🐞Bug修复
+* 【core 】 修复CollUtil里面关于可变参数传null造成的crash问题(pr#2428@Github)
+* 【socket 】 修复异常socket没有关闭问题(pr#690@Gitee)
+* 【core 】 修复当时间戳为Integer时时间转换问题(pr#2449@Github)
+* 【core 】 修复bmp文件判断问题(issue#I5H93G@Gitee)
+* 【core 】 修复CombinationAnnotationElement造成递归循环(issue#I5FQGW@Gitee)
+* 【core 】 修复Dict缺少putIfAbsent、computeIfAbsent问题(issue#I5FQGW@Gitee)
+* 【core 】 修复Console.log应该把异常信息输出位置错误问题(pr#716@Gitee)
+* 【core 】 修复UrlBuilder无法配置末尾追加“/”问题(issue#2459@Github)
+* 【core 】 修复SystemPropsUtil.getBoolean方法应该只有值为true时才返回true,其他情况都应该返回false(pr#717@Gitee)
+* 【core 】 修复isBase64判断不准确的问题(pr#727@Gitee)
+* 【core 】 修复Convert#toMap默认转成HashMap的问题(pr#729@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.4 (2022-06-27)
+
+### 🐣新特性
+* 【extra 】 Sftp增加构造重载,支持超时(pr#653@Gitee)
+* 【core 】 BeanUtil增加isCommonFieldsEqual(pr#653@Gitee)
+* 【json 】 修改byte[]统一转换为数组形式(issue#2377@Github)
+* 【http 】 HttpResponse增加body方法,支持自定义返回内容(pr#655@Gitee)
+* 【core 】 修改ObjectUtil.isNull逻辑(issue#I5COJF@Gitee)
+* 【core 】 BlockPolicy增加线程池关闭后的逻辑(pr#660@Gitee)
+* 【core 】 Ipv4Util增加ipv4ToLong重载(pr#661@Gitee)
+* 【core 】 LocalDateTimeUtil.parse改为blank检查(issue#I5CZJ9@Gitee)
+* 【core 】 BeanPath在空元素时默认加入map,修改根据下标类型赋值List or map(issue#2362@Github)
+* 【core 】 localAddressList 添加重构方法(pr#665@Gitee)
+* 【cron 】 从配置文件加载任务时,自定义ID避免重复从配置文件加载(issue#I5E7BM@Gitee)
+* 【core 】 新增注解扫描器和合成注解(pr#654@Gitee)
+*
+### 🐞Bug修复
+* 【extra 】 修复createExtractor中抛出异常后流未关闭问题(pr#2384@Github)
+* 【core 】 修复CsvData.getHeader没有判空导致空指针问题(issue#I5CK7Q@Gitee)
+* 【core 】 修复单字母转换为数字的问题(issue#I5C4K1@Gitee)
+* 【core 】 修复IterUtil.filter无效问题
+* 【core 】 修复NumberUtil传入null,返回了true(issue#I5DTSL@Gitee)
+* 【core 】 修复NumberUtil.isDouble问题(pr#2400@Github)
+* 【core 】 修复ZipUtil使用append替换文件时,父目录存在报错问题(issue#I5DRU0@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.3 (2022-06-10)
+
+### 🐣新特性
+* 【extra 】 mail增加writeTimeout参数支持(issue#2355@Github)
+* 【core 】 FileTypeUtil增加pptx扩展名支持(issue#I5A0GO@Gitee)
+* 【core 】 IterUtil.get增加判空(issue#I5B12A@Gitee)
+* 【core 】 FileTypeUtil增加webp类型判断(issue#I5BGTF@Gitee)
+### 🐞Bug修复
+* 【core 】 修复NumberUtil.isXXX空判断错误(issue#2356@Github)
+* 【core 】 修复Convert.toSBC空指针问题(issue#I5APKK@Gitee)
+* 【json 】 修复Bean中存在bytes,无法转换问题(issue#2365@Github)
+* 【core 】 ArrayUtil.setOrAppend()传入空数组时,抛出异常(issue#I5APJE@Gitee)
+* 【extra 】 JschSessionPool修复空指针检查问题(issue#I5BK4D@Gitee)
+* 【core 】 修复使用ValueProvider中setFieldMapping无效问题(issue#I5B4R7@Gitee)
+* 【json 】 修复byte[]作为JSONArray构造问题(issue#2369@Github)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.2 (2022-05-27)
+
+### 🐣新特性
+* 【core 】 BeanUtil拷贝对象增加空检查(issue#I58CJ3@Gitee)
+* 【db 】 Column#size改为long
+* 【core 】 ClassUtil增加isInterface等方法(pr#623@Gitee)
+* 【socket 】 增加ChannelUtil
+
+### 🐞Bug修复
+* 【extra 】 修复SshjSftp初始化未能代入端口配置问题(issue#2333@Github)
+* 【core 】 修复Convert.numberToSimple转换问题(issue#2334@Github)
+* 【core 】 修复TemporalAccessorConverter导致的转换问题(issue#2341@Github)
+* 【core 】 修复NumberUtil除法空指针问题(issue#I58XKE@Gitee)
+* 【core 】 修复CAR_VIN正则(pr#624@Gitee)
+* 【db 】 修复count查询别名问题(issue#I590YB@Gitee)
+* 【json 】 修复json中byte[]无法转换问题(issue#I59LW4@Gitee)
+* 【core 】 修复NumberUtil.isXXX未判空问题(issue#2350@Github)
+* 【core 】 修复Singleton中ConcurrentHashMap在JDK8下的bug引起的可能的死循环问题(issue#2349@Github)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.1 (2022-05-16)
+
+### 🐣新特性
+* 【core 】 BooleanUtil增加toBooleanObject方法(issue#I56AG3@Gitee)
+* 【core 】 CharSequenceUtil增加startWithAnyIgnoreCase方法(issue#2312@Github)
+* 【system 】 JavaInfo增加版本(issue#2310@Github)
+* 【core 】 新增CastUtil(pr#2313@Github)
+* 【core 】 ByteUtil新增bytesToShort重载(issue#I57FA7@Gitee)
+* 【core 】 ReflectUtil.invoke方法抛出运行时异常增加InvocationTargetRuntimeException(issue#I57GI2@Gitee)
+* 【core 】 NumberUtil.parseNumber支持16进制(issue#2328@Github)
+
+### 🐞Bug修复
+* 【core 】 MapUtil.map对null友好,且修复了测试用例中分组问题(pr#614@Gitee)
+* 【core 】 修复BeanUtil.beanToMap中properties为null的空指针问题(issue#2303@Github)
+* 【db 】 DialectName中修正为POSTGRESQL(issue#2308@Github)
+* 【core 】 修复BeanPath无法识别引号内的内容问题(issue#I56DE0@Gitee)
+* 【core 】 修复Map.entry方法返回可变不可变相反问题
+* 【jwt 】 修复jwt的过期容忍时间问题(issue#2329@Gitee)
+
+-------------------------------------------------------------------------------------------------------------
+
+# 5.8.0 (2022-05-06)
### ❌不兼容特性
* 【extra 】 升级jakarta.validation-api到3.x,包名变更导致不能向下兼容
+* 【core 】 BeanUtil删除了beanToMap(Object)方法,因为有可变参数的方法,这个删除可能导致直接升级找不到方法,重新编译项目即可。
### 🐣新特性
+* 【core 】 Singleton增加部分方法(pr#609@Gitee)
+* 【core 】 BeanUtil增加beanToMap重载(pr#2292@Github)
+* 【core 】 Assert增加对应的equals及notEquals方法(pr#612@Gitee)
+* 【core 】 Assert增加对应的equals及notEquals方法(pr#612@Gitee)
+* 【core 】 DigestUtil增加sha512方法(issue#2298@Github)
### 🐞Bug修复
* 【db 】 修复RedisDS无法设置maxWaitMillis问题(issue#I54TZ9@Gitee)
@@ -43,7 +1091,7 @@
* 【core 】 修复SimpleCache线程安全问题
* 【core 】 修复ClassLoaderUtil中可能的关联ClassLoader错位问题
* 【extra 】 修复Sftp错误内容解析大小写问题(issue#I53GPI@Gitee)
-* 【core 】 修复Tailer当文件内容为空时,会报异常问题(pr#602@Gitee)
+* 【core 】 修复当文件内容为空时,会报异常问题(pr#602@Gitee)
-------------------------------------------------------------------------------------------------------------
@@ -168,4 +1216,7 @@
* 【core 】 FileUtil.getMimeType增加rar、7z支持(issue#I4ZBN0@Gitee)
* 【json 】 JSON修复transient设置无效问题(issue#2212@Github)
* 【core 】 修复IterUtil.getElementType获取结果为null的问题(issue#2222@Github)
-* 【core 】 修复农历转公历在闰月时错误(issue#I4ZSGJ@Gitee)
\ No newline at end of file
+* 【core 】 修复农历转公历在闰月时错误(issue#I4ZSGJ@Gitee)
+
+# 5.7.x 或更早版本
+* [https://gitee.com/chinabugotech/hutool/blob/v5-master/CHANGELOG_5.0-5.7.md](https://gitee.com/chinabugotech/hutool/blob/v5-master/CHANGELOG_5.0-5.7.md)
\ No newline at end of file
diff --git a/CHANGELOG_5.0-5.7.md b/CHANGELOG_5.0-5.7.md
index 04fc0e922d3b9bc0733735e0c1b1a5751859738b..3072d65378d1e197a633008fd5cba0dc46f42824 100755
--- a/CHANGELOG_5.0-5.7.md
+++ b/CHANGELOG_5.0-5.7.md
@@ -1564,7 +1564,7 @@
### 🐞Bug修复
* 【core 】 修复NumberWordFormatter拼写错误(issue#799@Github)
* 【poi 】 修复xls文件下拉列表无效问题(issue#I1C79P@Gitee)
-* 【poi 】 修复使用Cglib代理问题(issue#I1C79P@Gitee)
+* 【poi 】 修复使用Cglib代理问题(issue#806@Github)
* 【core 】 修复DateUtil.weekCount跨年计算问题
-------------------------------------------------------------------------------------------------------------
diff --git a/README-EN.md b/README-EN.md
index 1f79bbb656ac57d3997657a2f3dfb1584e3980a8..6c7d66ceca58726e5c1780f92153cabf9b5261cf 100755
--- a/README-EN.md
+++ b/README-EN.md
@@ -12,46 +12,51 @@
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
-
-
+
+
-------------------------------------------------------------------------------
+
+
[**🌎中文说明**](README.md)
-------------------------------------------------------------------------------
## 📚Introduction
-**Hutool** is a small but comprehensive library of Java tools, encapsulation by static methods, reduce the cost of learning related APIs, increase productivity, and make Java as elegant as a functional programming language,let the Java be "sweet" too.
+**Hutool** is a small but comprehensive library of Java tools, achieved by encapsulation through static methods, reduce the cost of learning related APIs, increase productivity, and make Java as elegant as a functional programming language,let the Java be "sweet" too.
**Hutool** tools and methods from each user's crafted, it covers all aspects of the underlying code of Java development, it is a powerful tool for large project development to solve small problems, but also the efficiency of small projects;
@@ -81,8 +86,8 @@ Hutool exists to reduce code search costs and avoid bugs caused by imperfect cod
## 🛠️Module
A Java-based tool class for files, streams, encryption and decryption, transcoding, regular, thread, XML and other JDK methods for encapsulation,composing various Util tool classes, as well as providing the following modules:
-| module | description |
-| -------------------|-------------------------------------------------------------------------------------------------------------------------|
+| module | description |
+|--------------------|-------------------------------------------------------------------------------------------------------------------------|
| hutool-aop | JDK dynamic proxy encapsulation to provide non-IOC faceting support |
| hutool-bloomFilter | Bloom filtering to provide some Hash algorithm Bloom filtering |
| hutool-cache | Simple cache |
@@ -102,6 +107,7 @@ A Java-based tool class for files, streams, encryption and decryption, transcodi
| hutool-poi | Tools for working with Excel and Word in POI |
| hutool-socket | Java-based tool classes for NIO and AIO sockets |
| hutool-jwt | JSON Web Token (JWT) implement |
+| hutool-ai | AI implement |
Each module can be introduced individually, or all modules can be introduced by introducing `hutool-all` as required.
@@ -109,33 +115,17 @@ Each module can be introduced individually, or all modules can be introduced by
## 📝Doc
-[📘Chinese documentation](https://www.hutool.cn/docs/)
+[📘Chinese documentation](https://doc.hutool.cn/pages/index/)
-[📘Chinese back-up documentation](https://plus.hutool.cn/docs/#/)
+[📘Chinese back-up documentation](https://plus.hutool.cn/)
-[📙API](https://apidoc.gitee.com/dromara/hutool/)
+[📙API](https://plus.hutool.cn/apidocs/)
[🎬Video](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2)
-------------------------------------------------------------------------------
-## 🪙Support Hutool
-
-### 💳Donate
-
-If you think Hutool is good, you can donate to buy the author a pack of chili~, thanks in advance ^_^.
-
-[Gitee donate](https://gitee.com/dromara/hutool)
-
-[Dromara donate](https://dromara.gitee.io/donate.html)
-
-### 👕Shop about Hutool
-We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
-
-👉 [Hutool Shop](https://market.m.taobao.com/apps/market/content/index.html?wh_weex=true&contentId=331724720170) 👈
-
--------------------------------------------------------------------------------
## 📦Install
@@ -144,18 +134,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
cn.hutool
hutool-all
- 5.8.0.M5
+ 5.8.41
```
### 🍐Gradle
```
-implementation 'cn.hutool:hutool-all:5.8.0.M5'
+implementation 'cn.hutool:hutool-all:5.8.41'
```
## 📥Download
-- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M5/)
+- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.41/)
> 🔔️note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
@@ -165,9 +155,9 @@ implementation 'cn.hutool:hutool-all:5.8.0.M5'
Download the entire project source code
-gitee:[https://gitee.com/dromara/hutool](https://gitee.com/dromara/hutool)
+gitee:[https://gitee.com/chinabugotech/hutool](https://gitee.com/chinabugotech/hutool)
-github:[https://github.com/dromara/hutool](https://github.com/dromara/hutool)
+github:[https://github.com/chinabugotech/hutool](https://github.com/chinabugotech/hutool)
```sh
cd ${hutool}
@@ -191,8 +181,9 @@ Hutool's source code is divided into two branches:
When submitting feedback, please indicate which JDK version, Hutool version, and related dependency library version you are using.
-- [Gitee issue](https://gitee.com/dromara/hutool/issues)
-- [Github issue](https://github.com/dromara/hutool/issues)
+- [Gitee issue](https://gitee.com/chinabugotech/hutool/issues)
+- [Github issue](https://github.com/chinabugotech/hutool/issues)
+- [Gitcode issue](https://gitcode.com/chinabugotech/hutool/issues)
### 🧬Principles of PR(pull request)
@@ -203,15 +194,12 @@ Hutool welcomes anyone to contribute code to Hutool, but the author suffers from
3. Newly added methods do not use third-party library methods,Unless the method tool is add to the '**extra module**'.
4. Please pull request to the `v5-dev` branch. Hutool uses a new branch after 5.x: `v5-master` is the master branch, which indicates the version of the central library that has been released, and this branch does not allow pr or modifications.
--------------------------------------------------------------------------------
+### 📖 Documentation source code
-## ⭐Star Hutool
+[Documentation source code](https://gitee.com/loolly_admin/hutool-doc-handy)
-[](https://starchart.cc/dromara/hutool)
+-------------------------------------------------------------------------------
-## 📌WeChat Official Account
+## ⭐Star Hutool
-
-
-
-
\ No newline at end of file
+[](https://starchart.cc/chinabugotech/hutool)
diff --git a/README.md b/README.md
index 601bf892d66f9aa9c4290197555e95d67e2a247a..061b727bc1e2054ee225a2d7abfc35993a65c16f 100755
--- a/README.md
+++ b/README.md
@@ -12,73 +12,70 @@
-
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-------------------------------------------------------------------------------
+=======
+
[**🌎English Documentation**](README-EN.md)
-------------------------------------------------------------------------------
## 📚简介
-Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
-
-Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;
-Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。
+`Hutool`是一个功能丰富且易用的**Java工具库**,通过诸多实用工具类的使用,旨在帮助开发者快速、便捷地完成各类开发任务。
+这些封装的工具涵盖了字符串、数字、集合、编码、日期、文件、IO、加密、数据库JDBC、JSON、HTTP客户端等一系列操作,
+可以满足各种不同的开发需求。
### 🎁Hutool名称的由来
Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu”是公司名称的表示,tool表示工具。Hutool谐音“糊涂”,一方面简洁易懂,一方面寓意“难得糊涂”。
-### 🍺Hutool如何改变我们的coding方式
-
-Hutool的目标是使用一个工具方法代替一段复杂代码,从而最大限度的避免“复制粘贴”代码的问题,彻底改变我们写代码的方式。
+### 🍺Hutool理念
-以计算MD5为例:
+`Hutool`既是一个工具集,也是一个知识库,我们从不自诩代码原创,大多数工具类都是**搬运**而来,因此:
-- 👴【以前】打开搜索引擎 -> 搜“Java MD5加密” -> 打开某篇博客-> 复制粘贴 -> 改改好用
-- 👦【现在】引入Hutool -> SecureUtil.md5()
-
-Hutool的存在就是为了减少代码搜索成本,避免网络上参差不齐的代码出现导致的bug。
+- 你可以引入使用,也可以**拷贝**和修改使用,而**不必标注任何信息**,只是希望能把bug及时反馈回来。
+- 我们努力健全**中文**注释,为源码学习者提供良好地学习环境,争取做到人人都能看得懂。
-------------------------------------------------------------------------------
## 🛠️包含组件
一个Java基础工具类,对文件、流、加密解密、转码、正则、线程、XML等JDK方法进行封装,组成各种Util工具类,同时提供以下组件:
-| 模块 | 介绍 |
-| -------------------|---------------------------------------------------------------------------------- |
+| 模块 | 介绍 |
+|--------------------|---------------------------------------------------------------------------------- |
| hutool-aop | JDK动态代理封装,提供非IOC下的切面支持 |
| hutool-bloomFilter | 布隆过滤,提供一些Hash算法的布隆过滤 |
| hutool-cache | 简单缓存实现 |
@@ -98,6 +95,7 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
| hutool-poi | 针对POI中Excel和Word的封装 |
| hutool-socket | 基于Java的NIO和AIO的Socket封装 |
| hutool-jwt | JSON Web Token (JWT)封装实现 |
+| hutool-ai | AI大模型封装实现 |
可以根据需求对每个模块单独引入,也可以通过引入`hutool-all`方式引入所有模块。
@@ -105,35 +103,17 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
## 📝文档
-[📘中文文档](https://www.hutool.cn/docs/)
+[📘中文文档](https://doc.hutool.cn/pages/index/)
-[📘中文备用文档](https://plus.hutool.cn/docs/#/)
+[📘中文备用文档](https://plus.hutool.cn/)
-[📙参考API](https://apidoc.gitee.com/dromara/hutool/)
+[📙参考API](https://plus.hutool.cn/apidocs/)
[🎬视频介绍](https://www.bilibili.com/video/BV1bQ4y1M7d9?p=2)
-------------------------------------------------------------------------------
-## 🪙支持Hutool
-
-### 💳捐赠
-
-如果你觉得Hutool不错,可以捐赠请维护者吃包辣条~,在此表示感谢^_^。
-
-[Gitee上捐赠](https://gitee.com/dromara/hutool)
-
-[捐赠给Dromara组织](https://dromara.gitee.io/donate.html)
-
-### 👕周边商店
-你也可以通过购买Hutool的周边商品来支持Hutool维护哦!
-
-我们提供了印有Hutool Logo的周边商品,欢迎点击购买支持:
-
-👉 [Hutool 周边商店](https://market.m.taobao.com/apps/market/content/index.html?wh_weex=true&contentId=331724720170) 👈
-
--------------------------------------------------------------------------------
## 📦安装
@@ -144,20 +124,20 @@ Hutool的存在就是为了减少代码搜索成本,避免网络上参差不
cn.hutool
hutool-all
- 5.8.0.M5
+ 5.8.41
```
### 🍐Gradle
```
-implementation 'cn.hutool:hutool-all:5.8.0.M5'
+implementation 'cn.hutool:hutool-all:5.8.41'
```
### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
-- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.0.M5/)
+- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.41/)
> 🔔️注意
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
@@ -165,7 +145,7 @@ implementation 'cn.hutool:hutool-all:5.8.0.M5'
### 🚽编译安装
-访问Hutool的Gitee主页:[https://gitee.com/dromara/hutool](https://gitee.com/dromara/hutool) 下载整个项目源码(v5-master或v5-dev分支都可)然后进入Hutool项目目录执行:
+访问Hutool的Gitee主页:[https://gitee.com/chinabugotech/hutool](https://gitee.com/chinabugotech/hutool) 下载整个项目源码(v5-master或v5-dev分支都可)然后进入Hutool项目目录执行:
```sh
./hutool.sh install
@@ -190,17 +170,18 @@ Hutool的源码分为两个分支,功能如下:
提交问题反馈请说明正在使用的JDK版本呢、Hutool版本和相关依赖库版本。
-- [Gitee issue](https://gitee.com/dromara/hutool/issues)
-- [Github issue](https://github.com/dromara/hutool/issues)
+- [Gitee issue](https://gitee.com/chinabugotech/hutool/issues)
+- [Github issue](https://github.com/chinabugotech/hutool/issues)
+- [Gitcode issue](https://gitcode.com/chinabugotech/hutool/issues)
### 🧬贡献代码的步骤
-1. 在Gitee或者Github上fork项目到自己的repo
+1. 在Gitee或者Github/Gitcode上fork项目到自己的repo
2. 把fork过去的项目也就是你的项目clone到你的本地
3. 修改代码(记得一定要修改v5-dev分支)
4. commit后push到自己的库(v5-dev分支)
-5. 登录Gitee或Github在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
+5. 登录Gitee或Github/Gitcode在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。
6. 等待维护者合并
### 📐PR遵照的原则
@@ -213,15 +194,12 @@ Hutool欢迎任何人为Hutool添砖加瓦,贡献代码,不过维护者是
4. 请pull request到`v5-dev`分支。Hutool在5.x版本后使用了新的分支:`v5-master`是主分支,表示已经发布中央库的版本,这个分支不允许pr,也不允许修改。
5. 我们如果关闭了你的issue或pr,请不要诧异,这是我们保持问题处理整洁的一种方式,你依旧可以继续讨论,当有讨论结果时我们会重新打开。
--------------------------------------------------------------------------------
+### 📖文档源码地址
-## ⭐Star Hutool
+[文档源码地址](https://gitee.com/loolly_admin/hutool-doc-handy) 点击前往添砖加瓦
-[](https://starchart.cc/dromara/hutool)
+-------------------------------------------------------------------------------
-## 📌公众号
+## ⭐Star Hutool
-
-
-
-
\ No newline at end of file
+[](https://starchart.cc/chinabugotech/hutool)
diff --git a/SECURITY.md b/SECURITY.md
index d0c2c55899035458a94663daf97100c9e12a47fa..e5b59f02c6aed476e5e18ceb54720bbaf05f7534 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -10,6 +10,6 @@
## Reporting a Vulnerability(报告漏洞)
-如果你发现有安全问题或漏洞,请发送邮件到`loolly@aliyun.com`。
+如果你发现有安全问题或漏洞,请发送邮件到`bugo@bugotech.cn`。
-To report any found security issues or vulnerabilities, please send a mail to `loolly@aliyun.com`.
\ No newline at end of file
+To report any found security issues or vulnerabilities, please send a mail to `bugo@bugotech.cn`.
\ No newline at end of file
diff --git a/bin/javadoc.sh b/bin/javadoc.sh
index b9aaa3914757b117124ed1bf637479edc2678f14..0341e1d221d9087ac580c13a1813c8e0bc15c420 100755
--- a/bin/javadoc.sh
+++ b/bin/javadoc.sh
@@ -1,3 +1,11 @@
#!/bin/bash
-exec mvn javadoc:javadoc
+#exec mvn javadoc:javadoc
+
+# 多模块聚合文档,生成在target/site/apidocs
+exec mvn javadoc:aggregate
+
+bin_home="$(dirname ${BASH_SOURCE[0]})"
+
+# 拷贝自定义的index.html到聚合文档目录
+cp -vf $bin_home/../docs/apidocs/index.html $bin_home/../target/reports/apidocs/
diff --git a/bin/push_dev.sh b/bin/push_dev.sh
index 2e7f969360d024adfdf95ae1f190dca5ba6f17b3..5bca1c384cb1dfb562044dd014f6e57b97c3c5ef 100755
--- a/bin/push_dev.sh
+++ b/bin/push_dev.sh
@@ -3,7 +3,11 @@
echo -e "\033[32mCheckout to v5-dev\033[0m"
git checkout v5-dev
-echo -e "\033[32mPush to origin v5-dev\033[0m"
+echo -e "\033[32mPush to Github(origin) v5-dev\033[0m"
git push origin v5-dev
-echo -e "\033[32mPush to osc v5-dev\033[0m"
+
+echo -e "\033[32mPush to Gitee v5-dev\033[0m"
git push osc v5-dev
+
+echo -e "\033[32mPush to Gitcode v5-dev\033[0m"
+git push gitcode v5-dev
diff --git a/bin/push_master.sh b/bin/push_master.sh
index 729541c981bc8cc106936fda74ce66f9f71956df..683910d49b5aeb868413ae14e021b58c7c1d4899 100755
--- a/bin/push_master.sh
+++ b/bin/push_master.sh
@@ -6,7 +6,11 @@ git checkout v5-master
echo -e "\033[32mMerge v5-dev branch\033[0m"
git merge v5-dev -m 'Prepare release'
-echo -e "\033[32mPush to origin v5-master\033[0m"
+echo -e "\033[32mPush to Github(origin) v5-master\033[0m"
git push origin v5-master
-echo -e "\033[32mPush to osc v5-master\033[0m"
+
+echo -e "\033[32mPush to Gitee v5-master\033[0m"
git push osc v5-master
+
+echo -e "\033[32mPush to Gitcode v5-master\033[0m"
+git push gitcode v5-master
diff --git a/bin/sync.sh b/bin/sync.sh
new file mode 100644
index 0000000000000000000000000000000000000000..393c032470d59c840117ef33b2f554d521f76cc7
--- /dev/null
+++ b/bin/sync.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+git checkout v5-dev
+git pull osc v5-dev
+git pull origin v5-dev
+git pull gitcode v5-dev
diff --git a/bin/version.txt b/bin/version.txt
index 0185a6be924e63de532889825043f0e0d02e997a..027269ea75577984022a9af9ae2f2976fda33986 100755
--- a/bin/version.txt
+++ b/bin/version.txt
@@ -1 +1 @@
-5.8.0.M5
+5.8.41
diff --git a/docs/apidocs/index.html b/docs/apidocs/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..cf1759990e18826fd07e6a23172b383a2a705345
--- /dev/null
+++ b/docs/apidocs/index.html
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/js/version.js b/docs/js/version.js
index 2ce39747beddc7e6d2185569373690c188b67de8..81406b3bb1c01022f5caad0e1d45a0b79adedde7 100755
--- a/docs/js/version.js
+++ b/docs/js/version.js
@@ -1 +1 @@
-var version = '5.8.0.M5'
\ No newline at end of file
+var version = '5.8.41'
\ No newline at end of file
diff --git a/hutool-ai/pom.xml b/hutool-ai/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fc6192417065860a13c2248170f3da06d9c64dec
--- /dev/null
+++ b/hutool-ai/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+
+ cn.hutool
+ hutool-parent
+ 5.8.41
+
+
+ hutool-ai
+ ${project.artifactId}
+ Hutool AI大模型封装
+
+
+ cn.hutool.ai
+
+
+
+
+ cn.hutool
+ hutool-core
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-http
+ ${project.parent.version}
+
+
+ cn.hutool
+ hutool-log
+ ${project.parent.version}
+ compile
+
+
+ cn.hutool
+ hutool-json
+ ${project.parent.version}
+ compile
+
+
+
+
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/AIException.java b/hutool-ai/src/main/java/cn/hutool/ai/AIException.java
new file mode 100644
index 0000000000000000000000000000000000000000..57bad86ad1cf7e9b5844a09640c2c44caff41f84
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/AIException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai;
+
+import cn.hutool.core.util.StrUtil;
+
+/**
+ * 异常处理类
+ */
+public class AIException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 构造
+ *
+ * @param e 异常
+ */
+ public AIException(final Throwable e) {
+ super(e);
+ }
+
+ /**
+ * 构造
+ *
+ * @param message 消息
+ */
+ public AIException(final String message) {
+ super(message);
+ }
+
+ /**
+ * 构造
+ *
+ * @param messageTemplate 消息模板
+ * @param params 参数
+ */
+ public AIException(String messageTemplate, Object... params) {
+ super(StrUtil.format(messageTemplate, params));
+ }
+
+ /**
+ * 构造
+ *
+ * @param message 消息
+ * @param cause 被包装的子异常
+ */
+ public AIException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * 构造
+ *
+ * @param message 消息
+ * @param cause 被包装的子异常
+ * @param enableSuppression 是否启用抑制
+ * @param writableStackTrace 堆栈跟踪是否应该是可写的
+ */
+ public AIException(final String message, final Throwable cause, final boolean enableSuppression, final boolean writableStackTrace) {
+ super(message, cause, enableSuppression, writableStackTrace);
+ }
+
+ /**
+ * 构造
+ *
+ * @param throwable 被包装的子异常
+ * @param messageTemplate 消息模板
+ * @param params 参数
+ */
+ public AIException(Throwable throwable, String messageTemplate, Object... params) {
+ super(StrUtil.format(messageTemplate, params), throwable);
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/AIServiceFactory.java b/hutool-ai/src/main/java/cn/hutool/ai/AIServiceFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..23cb87403742a2ae5085a732bb4521e18095626e
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/AIServiceFactory.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.AIService;
+import cn.hutool.ai.core.AIServiceProvider;
+import cn.hutool.core.util.ServiceLoaderUtil;
+
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 创建AIModelService的工厂类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class AIServiceFactory {
+
+ private static final Map providers = new ConcurrentHashMap<>();
+
+ // 加载所有 AIModelProvider 实现类
+ static {
+ final ServiceLoader loader = ServiceLoaderUtil.load(AIServiceProvider.class);
+ for (final AIServiceProvider provider : loader) {
+ providers.put(provider.getServiceName().toLowerCase(), provider);
+ }
+ }
+
+ /**
+ * 获取AI服务
+ *
+ * @param config AIConfig配置
+ * @return AI服务实例
+ * @since 5.8.38
+ */
+ public static AIService getAIService(final AIConfig config) {
+ return getAIService(config, AIService.class);
+ }
+
+ /**
+ * 获取AI服务
+ *
+ * @param config AIConfig配置
+ * @param clazz AI服务类
+ * @return clazz对应的AI服务类实例
+ * @since 5.8.38
+ * @param AI服务类
+ */
+ @SuppressWarnings("unchecked")
+ public static T getAIService(final AIConfig config, final Class clazz) {
+ final AIServiceProvider provider = providers.get(config.getModelName().toLowerCase());
+ if (provider == null) {
+ throw new IllegalArgumentException("Unsupported model: " + config.getModelName());
+ }
+
+ final AIService service = provider.create(config);
+ if (!clazz.isInstance(service)) {
+ throw new AIException("Model service is not of type: " + clazz.getSimpleName());
+ }
+
+ return (T) service;
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/AIUtil.java b/hutool-ai/src/main/java/cn/hutool/ai/AIUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..7f4659d629d71272d1eaeef12ba81619dd606942
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/AIUtil.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.AIService;
+import cn.hutool.ai.core.Message;
+import cn.hutool.ai.model.deepseek.DeepSeekService;
+import cn.hutool.ai.model.doubao.DoubaoService;
+import cn.hutool.ai.model.grok.GrokService;
+import cn.hutool.ai.model.hutool.HutoolService;
+import cn.hutool.ai.model.openai.OpenaiService;
+
+import java.util.List;
+
+/**
+ * AI工具类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class AIUtil {
+
+ /**
+ * 获取AI模型服务,每个大模型提供的功能会不一样,可以调用此方法指定不同AI服务类,调用不同的功能
+ *
+ * @param config 创建的AI服务模型的配置
+ * @param clazz AI模型服务类
+ * @return AIModelService的实现类实例
+ * @since 5.8.38
+ * @param AIService实现类
+ */
+ public static T getAIService(final AIConfig config, final Class clazz) {
+ return AIServiceFactory.getAIService(config, clazz);
+ }
+
+ /**
+ * 获取AI模型服务
+ *
+ * @param config 创建的AI服务模型的配置
+ * @return AIModelService 其中只有公共方法
+ * @since 5.8.38
+ */
+ public static AIService getAIService(final AIConfig config) {
+ return getAIService(config, AIService.class);
+ }
+
+ /**
+ * 获取Hutool-AI服务
+ *
+ * @param config 创建的AI服务模型的配置
+ * @return HutoolService
+ * @since 5.8.39
+ */
+ public static HutoolService getHutoolService(final AIConfig config) {
+ return getAIService(config, HutoolService.class);
+ }
+
+ /**
+ * 获取DeepSeek模型服务
+ *
+ * @param config 创建的AI服务模型的配置
+ * @return DeepSeekService
+ * @since 5.8.38
+ */
+ public static DeepSeekService getDeepSeekService(final AIConfig config) {
+ return getAIService(config, DeepSeekService.class);
+ }
+
+ /**
+ * 获取Doubao模型服务
+ *
+ * @param config 创建的AI服务模型的配置
+ * @return DoubaoService
+ * @since 5.8.38
+ */
+ public static DoubaoService getDoubaoService(final AIConfig config) {
+ return getAIService(config, DoubaoService.class);
+ }
+
+ /**
+ * 获取Grok模型服务
+ *
+ * @param config 创建的AI服务模型的配置
+ * @return GrokService
+ * @since 5.8.38
+ */
+ public static GrokService getGrokService(final AIConfig config) {
+ return getAIService(config, GrokService.class);
+ }
+
+ /**
+ * 获取Openai模型服务
+ *
+ * @param config 创建的AI服务模型的配置
+ * @return OpenAIService
+ * @since 5.8.38
+ */
+ public static OpenaiService getOpenAIService(final AIConfig config) {
+ return getAIService(config, OpenaiService.class);
+ }
+
+ /**
+ * AI大模型对话功能
+ *
+ * @param config 创建的AI服务模型的配置
+ * @param prompt 需要对话的内容
+ * @return AI模型返回的Response响应字符串
+ * @since 5.8.38
+ */
+ public static String chat(final AIConfig config, final String prompt) {
+ return getAIService(config).chat(prompt);
+ }
+
+ /**
+ * AI大模型对话功能
+ *
+ * @param config 创建的AI服务模型的配置
+ * @param messages 由目前为止的对话组成的消息列表,可以设置role,content。详细参考官方文档
+ * @return AI模型返回的Response响应字符串
+ * @since 5.8.38
+ */
+ public static String chat(final AIConfig config, final List messages) {
+ return getAIService(config).chat(messages);
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/ModelName.java b/hutool-ai/src/main/java/cn/hutool/ai/ModelName.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e680fe76deacb9f4452b2af59de7935a36f8017
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/ModelName.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai;
+
+/**
+ * 模型厂商的名称(不指具体的模型)
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public enum ModelName {
+
+ /**
+ * hutool
+ */
+ HUTOOL("hutool"),
+ /**
+ * deepSeek
+ */
+ DEEPSEEK("deepSeek"),
+ /**
+ * openai
+ */
+ OPENAI("openai"),
+ /**
+ * doubao
+ */
+ DOUBAO("doubao"),
+ /**
+ * grok
+ */
+ GROK("grok"),
+ /**
+ * ollama
+ */
+ OLLAMA("ollama");
+
+ private final String value;
+
+ ModelName(final String value) {
+ this.value = value;
+ }
+
+ /**
+ * 获取值
+ *
+ * @return 值
+ */
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/Models.java b/hutool-ai/src/main/java/cn/hutool/ai/Models.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1dd6266d1f6c91ddf0ef74551df924eda23f538
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/Models.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai;
+
+/**
+ * 各模型厂商包含的model(指具体的模型)
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class Models {
+
+
+ // Hutool的模型
+ public enum Hutool {
+ HUTOOL("hutool");
+
+ private final String model;
+
+ Hutool(String model) {
+ this.model = model;
+ }
+
+ public String getModel() {
+ return model;
+ }
+ }
+
+ // DeepSeek的模型
+ public enum DeepSeek {
+ DEEPSEEK_CHAT("deepseek-chat"),
+ DEEPSEEK_REASONER("deepseek-reasoner");
+
+ private final String model;
+
+ DeepSeek(String model) {
+ this.model = model;
+ }
+
+ public String getModel() {
+ return model;
+ }
+ }
+
+ // Openai的模型
+ public enum Openai {
+ GPT_4_5_PREVIEW("gpt-4.5-preview"),
+ GPT_4O("gpt-4o"),
+ CHATGPT_4O_LATEST("chatgpt-4o-latest"),
+ GPT_4O_MINI("gpt-4o-mini"),
+ O1("o1"),
+ O1_MINI("o1-mini"),
+ O1_PREVIEW("o1-preview"),
+ O3_MINI("o3-mini"),
+ GPT_4O_REALTIME_PREVIEW("gpt-4o-realtime-preview"),
+ GPT_4O_MINI_REALTIME_PREVIEW("gpt-4o-mini-realtime-preview"),
+ GPT_4O_AUDIO_PREVIEW("gpt-4o-audio-preview"),
+ GPT_4O_MINI_AUDIO_PREVIEW("gpt-4o-mini-audio-preview"),
+ GPT_4_TURBO("gpt-4-turbo"),
+ GPT_4_TURBO_PREVIEW("gpt-4-turbo-preview"),
+ GPT_4("gpt-4"),
+ GPT_3_5_TURBO_0125("gpt-3.5-turbo-0125"),
+ GPT_3_5_TURBO("gpt-3.5-turbo"),
+ GPT_3_5_TURBO_1106("gpt-3.5-turbo-1106"),
+ GPT_3_5_TURBO_INSTRUCT("gpt-3.5-turbo-instruct"),
+ DALL_E_3("dall-e-3"),
+ DALL_E_2("dall-e-2"),
+ TTS_1("tts-1"),
+ TTS_1_HD("tts-1-hd"),
+ WHISPER_1("whisper-1"),
+ TEXT_EMBEDDING_3_LARGE("text-embedding-3-large"),
+ TEXT_EMBEDDING_3_SMALL("text-embedding-3-small"),
+ TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"),
+ OMNI_MODERATION_LATEST("omni-moderation-latest"),
+ OMNI_MODERATION_2024_09_26("omni-moderation-2024-09-26"),
+ TEXT_MODERATION_LATEST("text-moderation-latest"),
+ TEXT_MODERATION_STABLE("text-moderation-stable"),
+ TEXT_MODERATION_007("text-moderation-007"),
+ BABBAGE_002("babbage-002"),
+ DAVINCI_002("davinci-002");
+
+ private final String model;
+
+ Openai(String model) {
+ this.model = model;
+ }
+
+ public String getModel() {
+ return model;
+ }
+ }
+
+ // Doubao的模型
+ public enum Doubao {
+ DOUBAO_1_5_PRO_32K("doubao-1.5-pro-32k-250115"),
+ DOUBAO_1_5_PRO_256K("doubao-1.5-pro-256k-250115"),
+ DOUBAO_1_5_LITE_32K("doubao-1.5-lite-32k-250115"),
+ DEEPSEEK_R1("deepseek-r1-250120"),
+ DEEPSEEK_R1_DISTILL_QWEN_32B("deepseek-r1-distill-qwen-32b-250120"),
+ DEEPSEEK_R1_DISTILL_QWEN_7B("deepseek-r1-distill-qwen-7b-250120"),
+ DEEPSEEK_V3("deepseek-v3-241226"),
+ DOUBAO_PRO_4K_240515("doubao-pro-4k-240515"),
+ DOUBAO_PRO_4K_CHARACTER_240728("doubao-pro-4k-character-240728"),
+ DOUBAO_PRO_4K_FUNCTIONCALL_240615("doubao-pro-4k-functioncall-240615"),
+ DOUBAO_PRO_4K_BROWSING_240524("doubao-pro-4k-browsing-240524"),
+ DOUBAO_PRO_32K_241215("doubao-pro-32k-241215"),
+ DOUBAO_PRO_32K_FUNCTIONCALL_241028("doubao-pro-32k-functioncall-241028"),
+ DOUBAO_PRO_32K_BROWSING_241115("doubao-pro-32k-browsing-241115"),
+ DOUBAO_PRO_32K_CHARACTER_241215("doubao-pro-32k-character-241215"),
+ DOUBAO_PRO_128K_240628("doubao-pro-128k-240628"),
+ DOUBAO_PRO_256K_240828("doubao-pro-256k-240828"),
+ DOUBAO_LITE_4K_240328("doubao-lite-4k-240328"),
+ DOUBAO_LITE_4K_PRETRAIN_CHARACTER_240516("doubao-lite-4k-pretrain-character-240516"),
+ DOUBAO_LITE_32K_240828("doubao-lite-32k-240828"),
+ DOUBAO_LITE_32K_CHARACTER_241015("doubao-lite-32k-character-241015"),
+ DOUBAO_LITE_128K_240828("240828"),
+ MOONSHOT_V1_8K("moonshot-v1-8k"),
+ MOONSHOT_V1_32K("moonshot-v1-32k"),
+ MOONSHOT_V1_128K("moonshot-v1-128k"),
+ CHATGLM3_130B_FC("chatglm3-130b-fc-v1.0"),
+ CHATGLM3_130_FIN("chatglm3-130-fin-v1.0-update"),
+ MISTRAL_7B("mistral-7b-instruct-v0.2"),
+ DOUBAO_1_5_VISION_PRO_32K("doubao-1.5-vision-pro-32k-250115"),
+ DOUBAO_VISION_PRO_32K("doubao-vision-pro-32k-241008"),
+ DOUBAO_VISION_LITE_32K("doubao-vision-lite-32k-241015"),
+ DOUBAO_EMBEDDING_LARGE("doubao-embedding-large-text-240915"),
+ DOUBAO_EMBEDDING_TEXT_240715("doubao-embedding-text-240715"),
+ DOUBAO_EMBEDDING_VISION("doubao-embedding-vision-241215"),
+ DOUBAO_SEEDREAM_3_0_T2I("doubao-seedream-3-0-t2i-250415"),
+ Doubao_Seedance_1_0_lite_t2v("doubao-seedance-1-0-lite-t2v-250428"),
+ Doubao_Seedance_1_0_lite_i2v("doubao-seedance-1-0-lite-i2v-250428"),
+ Wan2_1_14B_t2v("wan2-1-14b-t2v-250225"),
+ Wan2_1_14B_i2v("wan2-1-14b-i2v-250225");
+
+ private final String model;
+
+ Doubao(String model) {
+ this.model = model;
+ }
+
+ public String getModel() {
+ return model;
+ }
+ }
+
+ // Grok的模型
+ public enum Grok {
+ GROK_3_BETA_LATEST("grok-3-beta"),
+ GROK_3_BETA("grok-3-beta"),
+ GROK_3("grok-3-beta"),
+ GROK_3_MINI_FAST_LATEST("grok-3-mini-fast-beta"),
+ GROK_3_MINI_FAST_BETA("grok-3-mini-fast-beta"),
+ GROK_3_MINI_FAST("grok-3-mini-fast-beta"),
+ GROK_3_FAST_LATEST("grok-3-fast-beta"),
+ GROK_3_FAST_BETA("grok-3-fast-beta"),
+ GROK_3_FAST("grok-3-fast-beta"),
+ GROK_3_MINI_LATEST("grok-3-mini-beta"),
+ GROK_3_MINI_BETA("grok-3-mini-beta"),
+ GROK_3_MINI("grok-3-mini-beta"),
+ GROK_2_IMAGE_LATEST("grok-2-image-1212"),
+ GROK_2_IMAGE("grok-2-image-1212"),
+ GROK_2_IMAGE_1212("grok-2-image-1212"),
+ grok_2_latest("grok-2-1212"),
+ GROK_2("grok-2-1212"),
+ GROK_2_1212("grok-2-1212"),
+ GROK_2_VISION_1212("grok-2-vision-1212"),
+ GROK_BETA("grok-beta"),
+ GROK_VISION_BETA("grok-vision-beta");
+
+ private final String model;
+
+ Grok(String model) {
+ this.model = model;
+ }
+
+ public String getModel() {
+ return model;
+ }
+ }
+
+ // Ollama的模型
+ public enum Ollama {
+ QWEN3_32B("qwen3:32b");
+
+ private final String model;
+
+ Ollama(String model) {
+ this.model = model;
+ }
+
+ public String getModel() {
+ return model;
+ }
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..8efbbca591b875991e2b07e6e3e763f8e6d1035a
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfig.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.core;
+
+import java.util.Map;
+
+/**
+ * AI配置类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public interface AIConfig {
+
+ /**
+ * 获取模型(厂商)名称
+ *
+ * @return 模型(厂商)名称
+ * @since 5.8.38
+ */
+ default String getModelName() {
+ return this.getClass().getSimpleName();
+ }
+
+ /**
+ * 设置apiKey
+ *
+ * @param apiKey apiKey
+ * @since 5.8.38
+ */
+ void setApiKey(String apiKey);
+
+ /**
+ * 获取apiKey
+ *
+ * @return apiKey
+ * @since 5.8.38
+ */
+ String getApiKey();
+
+ /**
+ * 设置apiUrl
+ *
+ * @param apiUrl api请求地址
+ * @since 5.8.38
+ */
+ void setApiUrl(String apiUrl);
+
+ /**
+ * 获取apiUrl
+ *
+ * @return apiUrl
+ * @since 5.8.38
+ */
+ String getApiUrl();
+
+ /**
+ * 设置model
+ *
+ * @param model model
+ * @since 5.8.38
+ */
+ void setModel(String model);
+
+ /**
+ * 返回model
+ *
+ * @return model
+ * @since 5.8.38
+ */
+ String getModel();
+
+ /**
+ * 设置动态参数
+ *
+ * @param key 参数字段
+ * @param value 参数值
+ * @since 5.8.38
+ */
+ void putAdditionalConfigByKey(String key, Object value);
+
+ /**
+ * 获取动态参数
+ *
+ * @param key 参数字段
+ * @return 参数值
+ * @since 5.8.38
+ */
+ Object getAdditionalConfigByKey(String key);
+
+ /**
+ * 获取动态参数列表
+ *
+ * @return 参数列表Map
+ * @since 5.8.38
+ */
+ Map getAdditionalConfigMap();
+
+ /**
+ * 设置连接超时时间
+ *
+ * @param timeout 连接超时时间
+ * @since 5.8.39
+ */
+ void setTimeout(int timeout);
+
+ /**
+ * 获取连接超时时间
+ *
+ * @return timeout
+ * @since 5.8.39
+ */
+ int getTimeout();
+
+ /**
+ * 设置读取超时时间
+ *
+ * @param readTimeout 连接超时时间
+ * @since 5.8.39
+ */
+ void setReadTimeout(int readTimeout);
+
+ /**
+ * 获取读取超时时间
+ *
+ * @return readTimeout
+ * @since 5.8.39
+ */
+ int getReadTimeout();
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigBuilder.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..bedd79071ae92b6ac26ef28c18403efcaa39ee1c
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigBuilder.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.core;
+
+import java.lang.reflect.Constructor;
+
+/**
+ * 用于AIConfig的创建,创建同时支持链式设置参数
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class AIConfigBuilder {
+
+ private final AIConfig config;
+
+ /**
+ * 构造
+ *
+ * @param modelName 模型厂商的名称(注意不是指具体的模型)
+ */
+ public AIConfigBuilder(final String modelName) {
+ try {
+ // 获取配置类
+ final Class extends AIConfig> configClass = AIConfigRegistry.getConfigClass(modelName);
+ if (configClass == null) {
+ throw new IllegalArgumentException("Unsupported model: " + modelName);
+ }
+
+ // 使用反射创建实例
+ final Constructor extends AIConfig> constructor = configClass.getDeclaredConstructor();
+ config = constructor.newInstance();
+ } catch (final Exception e) {
+ throw new RuntimeException("Failed to create AIConfig instance", e);
+ }
+ }
+
+ /**
+ * 设置apiKey
+ *
+ * @param apiKey apiKey
+ * @return config
+ * @since 5.8.38
+ */
+ public synchronized AIConfigBuilder setApiKey(final String apiKey) {
+ if (apiKey != null) {
+ config.setApiKey(apiKey);
+ }
+ return this;
+ }
+
+ /**
+ * 设置AI模型请求API接口的地址,不设置为默认值
+ *
+ * @param apiUrl API接口地址
+ * @return config
+ * @since 5.8.38
+ */
+ public synchronized AIConfigBuilder setApiUrl(final String apiUrl) {
+ if (apiUrl != null) {
+ config.setApiUrl(apiUrl);
+ }
+ return this;
+ }
+
+ /**
+ * 设置具体的model,不设置为默认值
+ *
+ * @param model 具体model的名称
+ * @return config
+ * @since 5.8.38
+ */
+ public synchronized AIConfigBuilder setModel(final String model) {
+ if (model != null) {
+ config.setModel(model);
+ }
+ return this;
+ }
+
+ /**
+ * 动态设置Request请求体中的属性字段,每个模型功能支持的字段请参照对应的官方文档
+ *
+ * @param key Request中的支持的属性名
+ * @param value 设置的属性值
+ * @return config
+ * @since 5.8.38
+ */
+ public AIConfigBuilder putAdditionalConfig(final String key, final Object value) {
+ if (value != null) {
+ config.putAdditionalConfigByKey(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * 设置连接超时时间,不设置为默认值
+ *
+ * @param timeout 超时时间
+ * @return config
+ * @since 5.8.39
+ * @deprecated 请使用 {@link #setTimeout(int)}
+ */
+ @Deprecated
+ public AIConfigBuilder setTimout(final int timeout) {
+ return setTimeout(timeout);
+ }
+
+ /**
+ * 设置连接超时时间,不设置为默认值
+ *
+ * @param timeout 超时时间
+ * @return config
+ * @since 5.8.41
+ */
+ public synchronized AIConfigBuilder setTimeout(final int timeout) {
+ if (timeout > 0) {
+ config.setTimeout(timeout);
+ }
+ return this;
+ }
+
+ /**
+ * 设置读取超时时间,不设置为默认值
+ *
+ * @param readTimout 取超时时间
+ * @return config
+ * @since 5.8.39
+ * @deprecated 请使用 {@link #setReadTimeout(int)}
+ */
+ @Deprecated
+ public AIConfigBuilder setReadTimout(final int readTimout) {
+ return setReadTimeout(readTimout);
+ }
+
+ /**
+ * 设置读取超时时间,不设置为默认值
+ *
+ * @param readTimeout 取超时时间
+ * @return config
+ * @since 5.8.41
+ */
+ public synchronized AIConfigBuilder setReadTimeout(final int readTimeout) {
+ if (readTimeout > 0) {
+ config.setReadTimeout(readTimeout);
+ }
+ return this;
+ }
+
+ /**
+ * 返回config实例
+ *
+ * @return config
+ * @since 5.8.38
+ */
+ public AIConfig build() {
+ return config;
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigRegistry.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigRegistry.java
new file mode 100644
index 0000000000000000000000000000000000000000..61664404950c343dd0976f67a5e0378e737ad0e1
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIConfigRegistry.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.core;
+
+import cn.hutool.core.util.ServiceLoaderUtil;
+
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * AIConfig实现类的加载器
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class AIConfigRegistry {
+
+ private static final Map> configClasses = new ConcurrentHashMap<>();
+
+ // 加载所有 AIConfig 实现类
+ static {
+ final ServiceLoader loader = ServiceLoaderUtil.load(AIConfig.class);
+ for (final AIConfig config : loader) {
+ configClasses.put(config.getModelName().toLowerCase(), config.getClass());
+ }
+ }
+
+ /**
+ * 根据模型名称获取AIConfig实现类
+ *
+ * @param modelName 模型名称
+ * @return AIConfig实现类
+ */
+ public static Class extends AIConfig> getConfigClass(final String modelName) {
+ return configClasses.get(modelName.toLowerCase());
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIService.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIService.java
new file mode 100644
index 0000000000000000000000000000000000000000..3805f0e4a53de1392c2f25148b28cefedf54ff7d
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.core;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * 模型公共的API功能,特有的功能在model.xx.XXService下定义
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public interface AIService {
+
+ /**
+ * 对话
+ *
+ * @param prompt user题词
+ * @return AI回答
+ * @since 5.8.38
+ */
+ default String chat(String prompt){
+ final List messages = new ArrayList<>();
+ messages.add(new Message("system", "You are a helpful assistant"));
+ messages.add(new Message("user", prompt));
+ return chat(messages);
+ }
+
+ /**
+ * 对话-SSE流式输出
+ * @param prompt user题词
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ default void chat(String prompt, final Consumer callback){
+ final List messages = new ArrayList<>();
+ messages.add(new Message("system", "You are a helpful assistant"));
+ messages.add(new Message("user", prompt));
+ chat(messages, callback);
+ }
+
+ /**
+ * 对话
+ *
+ * @param messages 由目前为止的对话组成的消息列表,可以设置role,content。详细参考官方文档
+ * @return AI回答
+ * @since 5.8.38
+ */
+ String chat(final List messages);
+
+
+ /**
+ * 对话-SSE流式输出
+ * @param messages 由目前为止的对话组成的消息列表,可以设置role,content。详细参考官方文档
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ void chat(final List messages, final Consumer callback);
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/AIServiceProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/core/AIServiceProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..341f43a7f38d234ddc84e43b4e8d6859770cddee
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/AIServiceProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.core;
+
+/**
+ * 用于加载AI服务,每一个通过SPI创建的AI服务都要实现此接口
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public interface AIServiceProvider {
+
+ /**
+ * 获取AI服务名称
+ *
+ * @return AI服务名称
+ * @since 5.8.38
+ */
+ String getServiceName();
+
+ /**
+ * 创建AI服务实例
+ *
+ * @param config AIConfig配置
+ * @param AIService实现类
+ * @return AI服务实例
+ * @since 5.8.38
+ */
+ T create(final AIConfig config);
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java b/hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1106e8bf7561f22f4d702cf5b06c2b29fac40847
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/BaseAIService.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.core;
+
+import cn.hutool.ai.AIException;
+import cn.hutool.http.*;
+import cn.hutool.json.JSONUtil;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * 基础AIService,包含基公共参数和公共方法
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class BaseAIService {
+
+ protected final AIConfig config;
+
+ /**
+ * 构造方法
+ *
+ * @param config AI配置
+ */
+ public BaseAIService(final AIConfig config) {
+ this.config = config;
+ }
+
+ /**
+ * 发送Get请求
+ * @param endpoint 请求节点
+ * @return 请求响应
+ */
+ protected HttpResponse sendGet(final String endpoint) {
+ //链式构建请求
+ try {
+ //设置超时3分钟
+ return HttpRequest.get(config.getApiUrl() + endpoint)
+ .header(Header.ACCEPT, "application/json")
+ .header(Header.AUTHORIZATION, "Bearer " + config.getApiKey())
+ .timeout(config.getTimeout())
+ .execute();
+ } catch (final AIException e) {
+ throw new AIException("Failed to send GET request: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 发送Post请求
+ * @param endpoint 请求节点
+ * @param paramJson 请求参数json
+ * @return 请求响应
+ */
+ protected HttpResponse sendPost(final String endpoint, final String paramJson) {
+ //链式构建请求
+ try {
+ return HttpRequest.post(config.getApiUrl() + endpoint)
+ .header(Header.CONTENT_TYPE, "application/json")
+ .header(Header.ACCEPT, "application/json")
+ .header(Header.AUTHORIZATION, "Bearer " + config.getApiKey())
+ .body(paramJson)
+ .timeout(config.getTimeout())
+ .execute();
+ } catch (final AIException e) {
+ throw new AIException("Failed to send POST request:" + e.getMessage(), e);
+ }
+
+ }
+
+ /**
+ * 发送表单请求
+ * @param endpoint 请求节点
+ * @param paramMap 请求参数map
+ * @return 请求响应
+ */
+ protected HttpResponse sendFormData(final String endpoint, final Map paramMap) {
+ //链式构建请求
+ try {
+ //设置超时3分钟
+ return HttpRequest.post(config.getApiUrl() + endpoint)
+ .header(Header.CONTENT_TYPE, "multipart/form-data")
+ .header(Header.ACCEPT, "application/json")
+ .header(Header.AUTHORIZATION, "Bearer " + config.getApiKey())
+ .form(paramMap)
+ .timeout(config.getTimeout())
+ .execute();
+ } catch (final AIException e) {
+ throw new AIException("Failed to send POST request:" + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 支持流式返回的 POST 请求
+ *
+ * @param endpoint 请求地址
+ * @param paramMap 请求参数
+ * @param callback 流式数据回调函数
+ */
+ protected void sendPostStream(final String endpoint, final Map paramMap, Consumer callback) {
+ HttpURLConnection connection = null;
+ try {
+ // 创建连接
+ URL apiUrl = new URL(config.getApiUrl() + endpoint);
+ connection = (HttpURLConnection) apiUrl.openConnection();
+ connection.setRequestMethod(Method.POST.name());
+ connection.setRequestProperty(Header.CONTENT_TYPE.getValue(), "application/json");
+ connection.setRequestProperty(Header.AUTHORIZATION.getValue(), "Bearer " + config.getApiKey());
+ connection.setDoOutput(true);
+ //5分钟
+ connection.setReadTimeout(config.getReadTimeout());
+ //3分钟
+ connection.setConnectTimeout(config.getTimeout());
+ // 发送请求体
+ try (OutputStream os = connection.getOutputStream()) {
+ String jsonInputString = JSONUtil.toJsonStr(paramMap);
+ os.write(jsonInputString.getBytes());
+ os.flush();
+ }
+
+ // 读取流式响应
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ // 调用回调函数处理每一行数据
+ callback.accept(line);
+ }
+ }
+ } catch (Exception e) {
+ callback.accept("{\"error\": \"" + e.getMessage() + "\"}");
+ } finally {
+ // 关闭连接
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/BaseConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/core/BaseConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..3b99e9059c1695730c33dda5df2f03b277ff8666
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/BaseConfig.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.core;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Config基础类,定义模型配置的基本属性
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class BaseConfig implements AIConfig {
+
+ //apiKey
+ protected volatile String apiKey;
+ //API请求地址
+ protected volatile String apiUrl;
+ //具体模型
+ protected volatile String model;
+ //动态扩展字段
+ protected Map additionalConfig = new ConcurrentHashMap<>();
+ //连接超时时间
+ protected volatile int timeout = 180000;
+ //读取超时时间
+ protected volatile int readTimeout = 300000;
+
+ @Override
+ public void setApiKey(final String apiKey) {
+ this.apiKey = apiKey;
+ }
+
+ @Override
+ public String getApiKey() {
+ return apiKey;
+ }
+
+ @Override
+ public void setApiUrl(final String apiUrl) {
+ this.apiUrl = apiUrl;
+ }
+
+ @Override
+ public String getApiUrl() {
+ return apiUrl;
+ }
+
+ @Override
+ public void setModel(final String model) {
+ this.model = model;
+ }
+
+ @Override
+ public String getModel() {
+ return model;
+ }
+
+ @Override
+ public void putAdditionalConfigByKey(final String key, final Object value) {
+ this.additionalConfig.put(key, value);
+ }
+
+ @Override
+ public Object getAdditionalConfigByKey(final String key) {
+ return additionalConfig.get(key);
+ }
+
+ @Override
+ public Map getAdditionalConfigMap() {
+ return new ConcurrentHashMap<>(additionalConfig);
+ }
+
+ @Override
+ public int getTimeout() {
+ return timeout;
+ }
+
+ @Override
+ public void setTimeout(final int timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public int getReadTimeout() {
+ return readTimeout;
+ }
+
+ @Override
+ public void setReadTimeout(final int readTimeout) {
+ this.readTimeout = readTimeout;
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/Message.java b/hutool-ai/src/main/java/cn/hutool/ai/core/Message.java
new file mode 100644
index 0000000000000000000000000000000000000000..2c60e6f72690e9341c62371541f41daa3c2338ea
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/Message.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.core;
+
+/**
+ * 公共Message类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class Message {
+ //角色 注意:如果设置系统消息,请放在messages列表的第一位
+ private String role;
+ //内容
+ private Object content;
+
+ /**
+ * 构造
+ */
+ public Message() {
+ }
+
+ /**
+ * 构造
+ *
+ * @param role 角色
+ * @param content 内容
+ */
+ public Message(final String role, final Object content) {
+ this.role = role;
+ this.content = content;
+ }
+
+ /**
+ * 设置角色
+ *
+ * @param role 角色
+ */
+ public void setRole(final String role) {
+ this.role = role;
+ }
+
+ /**
+ * 获取角色
+ *
+ * @return 角色
+ */
+ public String getRole() {
+ return role;
+ }
+
+ /**
+ * 获取内容
+ *
+ * @return 内容
+ */
+ public Object getContent() {
+ return content;
+ }
+
+ /**
+ * 设置内容
+ *
+ * @param content 内容
+ */
+ public void setContent(final Object content) {
+ this.content = content;
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/core/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/core/package-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..855a610343951820eb252250eedbd35f7688f4fb
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/core/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * AI相关基础类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+
+package cn.hutool.ai.core;
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekCommon.java
new file mode 100644
index 0000000000000000000000000000000000000000..c712b488ae1490ccb0f3c2ce37526ba81101c7da
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekCommon.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.deepseek;
+
+/**
+ * deepSeek公共类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class DeepSeekCommon {
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..8497d6fd62b7efc5eaf3738834d4d87cadd6b822
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekConfig.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.deepseek;
+
+import cn.hutool.ai.Models;
+import cn.hutool.ai.core.BaseConfig;
+
+/**
+ * DeepSeek配置类,初始化API接口地址,设置默认的模型
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class DeepSeekConfig extends BaseConfig {
+
+ private final String API_URL = "https://api.deepseek.com";
+
+ private final String DEFAULT_MODEL = Models.DeepSeek.DEEPSEEK_CHAT.getModel();
+
+ public DeepSeekConfig() {
+ setApiUrl(API_URL);
+ setModel(DEFAULT_MODEL);
+ }
+
+ public DeepSeekConfig(String apiKey) {
+ this();
+ setApiKey(apiKey);
+ }
+
+ @Override
+ public String getModelName() {
+ return "deepSeek";
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..6fe344d928138e6015caf846ddd861eef5e318d4
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.deepseek;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.AIServiceProvider;
+
+/**
+ * 创建DeepSeek服务实现类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class DeepSeekProvider implements AIServiceProvider {
+
+ @Override
+ public String getServiceName() {
+ return "deepSeek";
+ }
+
+ @Override
+ public DeepSeekService create(final AIConfig config) {
+ return new DeepSeekServiceImpl(config);
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekService.java
new file mode 100644
index 0000000000000000000000000000000000000000..d537a078d46427556a24a9c741e71a07516b4ec4
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekService.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.deepseek;
+
+import cn.hutool.ai.core.AIService;
+import java.util.function.Consumer;
+
+/**
+ * deepSeek支持的扩展接口
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public interface DeepSeekService extends AIService {
+
+ /**
+ * 模型beta功能
+ *
+ * @param prompt 题词
+ * @return AI的回答
+ * @since 5.8.38
+ */
+ String beta(String prompt);
+
+ /**
+ * 模型beta功能-SSE流式输出
+ * @param prompt 题词
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ void beta(String prompt, final Consumer callback);
+
+ /**
+ * 列出所有模型列表
+ *
+ * @return model列表
+ * @since 5.8.38
+ */
+ String models();
+
+ /**
+ * 查询余额
+ *
+ * @return 余额
+ * @since 5.8.38
+ */
+ String balance();
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..624b97aabf584f78e262163531f8b3dad5b659d2
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/DeepSeekServiceImpl.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.deepseek;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.BaseAIService;
+import cn.hutool.ai.core.Message;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * DeepSeek服务,AI具体功能的实现
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class DeepSeekServiceImpl extends BaseAIService implements DeepSeekService {
+
+ //对话补全
+ private final String CHAT_ENDPOINT = "/chat/completions";
+ //FIM补全(beta)
+ private final String BETA_ENDPOINT = "/beta/completions";
+ //列出模型
+ private final String MODELS_ENDPOINT = "/models";
+ //余额查询
+ private final String BALANCE_ENDPOINT = "/user/balance";
+
+ /**
+ * 构造函数
+ *
+ * @param config AI配置
+ */
+ public DeepSeekServiceImpl(final AIConfig config) {
+ //初始化DeepSeek客户端
+ super(config);
+ }
+
+ @Override
+ public String chat(final List messages) {
+ final String paramJson = buildChatRequestBody(messages);
+ final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void chat(final List messages, final Consumer callback) {
+ Map paramMap = buildChatStreamRequestBody(messages);
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "deepseek-chat-sse").start();
+ }
+
+ @Override
+ public String beta(final String prompt) {
+ final String paramJson = buildBetaRequestBody(prompt);
+ final HttpResponse response = sendPost(BETA_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void beta(final String prompt, final Consumer callback) {
+ Map paramMap = buildBetaStreamRequestBody(prompt);
+ ThreadUtil.newThread(() -> sendPostStream(BETA_ENDPOINT, paramMap, callback::accept), "deepseek-beta-sse").start();
+ }
+
+ @Override
+ public String models() {
+ final HttpResponse response = sendGet(MODELS_ENDPOINT);
+ return response.body();
+ }
+
+ @Override
+ public String balance() {
+ final HttpResponse response = sendGet(BALANCE_ENDPOINT);
+ return response.body();
+ }
+
+ // 构建chat请求体
+ private String buildChatRequestBody(final List messages) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ // 构建chatStream请求体
+ private Map buildChatStreamRequestBody(final List messages) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ // 构建beta请求体
+ private String buildBetaRequestBody(final String prompt) {
+ // 定义消息结构
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("prompt", prompt);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ // 构建betaStream请求体
+ private Map buildBetaStreamRequestBody(final String prompt) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("prompt", prompt);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/package-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..99b1ca41469f7b4a204c771d8b646814f41b0e66
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/deepseek/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * 对deepSeek的封装实现
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+
+package cn.hutool.ai.model.deepseek;
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoCommon.java
new file mode 100644
index 0000000000000000000000000000000000000000..494f2c66823b19e74a3359b52a445029f5d11653
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoCommon.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.doubao;
+
+/**
+ * doubao公共类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class DoubaoCommon {
+
+ //doubao上下文缓存参数
+ public enum DoubaoContext {
+
+ SESSION("session"),
+ COMMON_PREFIX("common_prefix");
+
+ private final String mode;
+
+ DoubaoContext(String mode) {
+ this.mode = mode;
+ }
+
+ public String getMode() {
+ return mode;
+ }
+ }
+
+ //doubao视觉参数
+ public enum DoubaoVision {
+
+ AUTO("auto"),
+ LOW("low"),
+ HIGH("high");
+
+ private final String detail;
+
+ DoubaoVision(String detail) {
+ this.detail = detail;
+ }
+
+ public String getDetail() {
+ return detail;
+ }
+ }
+
+ //doubao视频生成参数
+ public enum DoubaoVideo {
+
+ //宽高比例
+ RATIO_16_9("--rt", "16:9"),//[1280, 720]
+ RATIO_4_3("--rt", "4:3"),//[960, 720]
+ RATIO_1_1("--rt", "1:1"),//[720, 720]
+ RATIO_3_4("--rt", "3:4"),//[720, 960]
+ RATIO_9_16("--rt", "9:16"),//[720, 1280]
+ RATIO_21_9("--rt", "21:9"),//[1280, 544]
+
+ //生成视频时长
+ DURATION_5("--dur", 5),//文生视频,图生视频
+ DURATION_10("--dur", 10),//文生视频
+
+ //帧率,即一秒时间内视频画面数量
+ FPS_5("--fps", 24),
+
+ //视频分辨率
+ RESOLUTION_5("--rs", "720p"),
+
+ //生成视频是否包含水印
+ WATERMARK_TRUE("--wm", true),
+ WATERMARK_FALSE("--wm", false);
+
+ private final String type;
+ private final Object value;
+
+ DoubaoVideo(String type, Object value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Object getValue() {
+ if (value instanceof String) {
+ return (String) value;
+ } else if (value instanceof Integer) {
+ return (Integer) value;
+ } else if (value instanceof Boolean) {
+ return (Boolean) value;
+ }
+ return value;
+ }
+
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa7b1c8a52f02a77c02b3b62efe4d210102a3fb1
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoConfig.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.doubao;
+
+import cn.hutool.ai.Models;
+import cn.hutool.ai.core.BaseConfig;
+
+/**
+ * Doubao配置类,初始化API接口地址,设置默认的模型
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class DoubaoConfig extends BaseConfig {
+
+ private final String API_URL = "https://ark.cn-beijing.volces.com/api/v3";
+
+ private final String DEFAULT_MODEL = Models.Doubao.DOUBAO_1_5_LITE_32K.getModel();
+
+ public DoubaoConfig() {
+ setApiUrl(API_URL);
+ setModel(DEFAULT_MODEL);
+ }
+
+ public DoubaoConfig(String apiKey) {
+ this();
+ setApiKey(apiKey);
+ }
+
+ @Override
+ public String getModelName() {
+ return "doubao";
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..46d5be8d2d568c204ffa41414879982765510b8b
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.doubao;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.AIServiceProvider;
+
+/**
+ * 创建Doubap服务实现类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class DoubaoProvider implements AIServiceProvider {
+
+ @Override
+ public String getServiceName() {
+ return "doubao";
+ }
+
+ @Override
+ public DoubaoService create(final AIConfig config) {
+ return new DoubaoServiceImpl(config);
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e94ae258f5a403f586263e2aca5ce95dc37d4bd6
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoService.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.doubao;
+
+import cn.hutool.ai.core.AIService;
+import cn.hutool.ai.core.Message;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * doubao支持的扩展接口
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public interface DoubaoService extends AIService {
+
+ /**
+ * 图像理解:模型会依据传入的图片信息以及问题,给出回复。
+ *
+ * @param prompt 提问
+ * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
+ * @return AI回答
+ * @since 5.8.38
+ */
+ default String chatVision(String prompt, final List images) {
+ return chatVision(prompt, images, DoubaoCommon.DoubaoVision.AUTO.getDetail());
+ }
+
+ /**
+ * 图像理解-SSE流式输出
+ *
+ * @param prompt 提问
+ * @param images 图片列表/或者图片Base64编码图片列表(URI形式)
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ default void chatVision(String prompt, final List images, final Consumer callback) {
+ chatVision(prompt, images, DoubaoCommon.DoubaoVision.AUTO.getDetail(), callback);
+ }
+
+ /**
+ * 图像理解:模型会依据传入的图片信息以及问题,给出回复。
+ *
+ * @param prompt 提问
+ * @param images 图片列表/或者图片Base64编码图片列表(URI形式)
+ * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto
+ * @return AI回答
+ * @since 5.8.38
+ */
+ String chatVision(String prompt, final List images, String detail);
+
+ /**
+ * 图像理解-SSE流式输出
+ *
+ * @param prompt 提问
+ * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
+ * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ void chatVision(String prompt, final List images, String detail, final Consumer callback);
+
+ /**
+ * 创建视频生成任务
+ * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID。详细参考官方文档
+ *
+ * @param text 文本提示词
+ * @param image 图片/或者图片Base64编码图片(URI形式)
+ * @param videoParams 视频参数列表
+ * @return 生成任务id
+ * @since 5.8.38
+ */
+ String videoTasks(String text, String image, final List videoParams);
+
+ /**
+ * 创建视频生成任务
+ * 注意:调用该方法时,配置config中的model为生成视频的模型或者您创建的推理接入点(Endpoint)ID。详细参考官方文档
+ *
+ * @param text 文本提示词
+ * @param image 图片/或者图片Base64编码图片(URI形式)
+ * @return 生成任务id
+ * @since 5.8.38
+ */
+ default String videoTasks(String text, String image) {
+ return videoTasks(text, image, null);
+ }
+
+ /**
+ * 查询视频生成任务信息
+ *
+ * @param taskId 通过创建生成视频任务返回的生成任务id
+ * @return 生成任务信息
+ * @since 5.8.38
+ */
+ String getVideoTasksInfo(String taskId);
+
+ /**
+ * 文本向量化
+ *
+ * @param input 需要向量化的内容列表,支持中文、英文
+ * @return 处理后的向量信息
+ * @since 5.8.38
+ */
+ String embeddingText(String[] input);
+
+ /**
+ * 图文向量化:仅支持单一文本、单张图片或文本与图片的组合输入(即一段文本 + 一张图片),暂不支持批量文本 / 图片的同时处理
+ *
+ * @param text 需要向量化的内容
+ * @param image 需要向量化的图片地址/或者图片Base64编码图片(URI形式)
+ * @return 处理后的向量信息
+ * @since 5.8.38
+ */
+ String embeddingVision(String text, String image);
+
+ /**
+ * 应用(Bot) config中model设置为您创建的应用ID
+ *
+ * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
+ * @return AI回答
+ * @since 5.8.38
+ */
+ String botsChat(final List messages);
+
+ /**
+ * 应用(Bot)-SSE流式输出 config中model设置为您创建的应用ID
+ *
+ * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ void botsChat(final List messages, final Consumer callback);
+
+ /**
+ * 分词:可以将文本转换为模型可理解的 token id,并返回文本的 tokens 数量、token id、 token 在原始文本中的偏移量等信息
+ *
+ * @param text 需要分词的内容列表
+ * @return 分词结果
+ * @since 5.8.38
+ */
+ String tokenization(String[] text);
+
+ /**
+ * 批量推理 Chat
+ * 注意:调用该方法时,配置config中的model为您创建的批量推理接入点(Endpoint)ID。详细参考官方文档
+ * 该方法不支持流式
+ *
+ * @param prompt chat内容
+ * @return AI回答
+ * @since 5.8.38
+ */
+ default String batchChat(String prompt){
+ final List messages = new ArrayList<>();
+ messages.add(new Message("system", "You are a helpful assistant"));
+ messages.add(new Message("user", prompt));
+ return batchChat(messages);
+ }
+
+ /**
+ * 批量推理 Chat
+ * 注意:调用该方法时,配置config中的model为您创建的批量推理接入点(Endpoint)ID。详细参考官方文档
+ * 该方法不支持流式
+ *
+ * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
+ * @return AI回答
+ * @since 5.8.38
+ */
+ String batchChat(final List messages);
+
+ /**
+ * 创建上下文缓存: 创建上下文缓存,获得缓存 id字段后,在上下文缓存对话 API中使用。
+ * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID,
+ * 推理接入点中使用的模型需要在模型管理中开启缓存功能。详细参考官方文档
+ *
+ * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
+ * @param mode 上下文缓存的类型,详细参考官方文档 默认为session
+ * @return 返回的缓存id
+ * @since 5.8.38
+ */
+ String createContext(final List messages, String mode);
+
+ /**
+ * 创建上下文缓存: 创建上下文缓存,获得缓存 id字段后,在上下文缓存对话 API中使用。
+ * 注意:调用该方法时,配置config中的model为您创建的推理接入点(Endpoint)ID,
+ * 推理接入点中使用的模型需要在模型管理中开启缓存功能。详细参考官方文档
+ *
+ * @param messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
+ * @return 返回的缓存id
+ * @since 5.8.38
+ */
+ default String createContext(final List messages) {
+ return createContext(messages, DoubaoCommon.DoubaoContext.SESSION.getMode());
+ }
+
+ /**
+ * 上下文缓存对话: 向大模型发起带上下文缓存的请求
+ * 注意:配置config中的model可以为您创建的推理接入点(Endpoint)ID,也可以是支持chat的model
+ *
+ * @param prompt 对话的内容题词
+ * @param contextId 创建上下文缓存后获取的缓存id
+ * @return AI的回答
+ * @since 5.8.38
+ */
+ default String chatContext(String prompt, String contextId){
+ final List messages = new ArrayList<>();
+ messages.add(new Message("user", prompt));
+ return chatContext(messages, contextId);
+ }
+
+ /**
+ * 上下文缓存对话-SSE流式输出
+ * 注意:配置config中的model可以为您创建的推理接入点(Endpoint)ID,也可以是支持chat的model
+ *
+ * @param prompt 对话的内容题词
+ * @param contextId 创建上下文缓存后获取的缓存id
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ default void chatContext(String prompt, String contextId, final Consumer callback){
+ final List messages = new ArrayList<>();
+ messages.add(new Message("user", prompt));
+ chatContext(messages, contextId, callback);
+ }
+
+ /**
+ * 上下文缓存对话: 向大模型发起带上下文缓存的请求
+ * 注意:配置config中的model可以为您创建的推理接入点(Endpoint)ID,也可以是支持chat的model
+ *
+ * @param messages 对话的信息 不支持最后一个元素的role设置为assistant。如使用session 缓存(mode设置为session)传入最新一轮对话的信息,无需传入历史信息
+ * @param contextId 创建上下文缓存后获取的缓存id
+ * @return AI的回答
+ * @since 5.8.38
+ */
+ String chatContext(final List messages, String contextId);
+
+ /**
+ * 上下文缓存对话-SSE流式输出
+ * 注意:配置config中的model可以为您创建的推理接入点(Endpoint)ID,也可以是支持chat的model
+ *
+ * @param messages 对话的信息 不支持最后一个元素的role设置为assistant。如使用session 缓存(mode设置为session)传入最新一轮对话的信息,无需传入历史信息
+ * @param contextId 创建上下文缓存后获取的缓存id
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ void chatContext(final List messages, String contextId, final Consumer callback);
+
+ /**
+ * 文生图
+ * 请设置config中model为支持图片功能的模型,目前支持Doubao-Seedream-3.0-t2i
+ *
+ * @param prompt 题词
+ * @return 包含生成图片的url
+ * @since 5.8.39
+ */
+ String imagesGenerations(String prompt);
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c1836fec44a6f611df8866634fd258899f863da5
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/DoubaoServiceImpl.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.doubao;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.BaseAIService;
+import cn.hutool.ai.core.Message;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Doubao服务,AI具体功能的实现
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class DoubaoServiceImpl extends BaseAIService implements DoubaoService {
+
+ //对话
+ private final String CHAT_ENDPOINT = "/chat/completions";
+ //文本向量化
+ private final String EMBEDDING_TEXT = "/embeddings";
+ //图文向量化
+ private final String EMBEDDING_VISION = "/embeddings/multimodal";
+ //应用bots
+ private final String BOTS_CHAT = "/bots/chat/completions";
+ //分词
+ private final String TOKENIZATION = "/tokenization";
+ //批量推理chat
+ private final String BATCH_CHAT = "/batch/chat/completions";
+ //创建上下文缓存
+ private final String CREATE_CONTEXT = "/context/create";
+ //上下文缓存对话
+ private final String CHAT_CONTEXT = "/context/chat/completions";
+ //创建视频生成任务
+ private final String CREATE_VIDEO = "/contents/generations/tasks";
+ //文生图
+ private final String IMAGES_GENERATIONS = "/images/generations";
+
+ public DoubaoServiceImpl(final AIConfig config) {
+ //初始化doubao客户端
+ super(config);
+ }
+
+ @Override
+ public String chat(final List messages) {
+ String paramJson = buildChatRequestBody(messages);
+ final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void chat(final List messages, final Consumer callback) {
+ Map paramMap = buildChatStreamRequestBody(messages);
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "doubao-chat-sse").start();
+ }
+
+ @Override
+ public String chatVision(String prompt, final List images, String detail) {
+ String paramJson = buildChatVisionRequestBody(prompt, images, detail);
+ final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void chatVision(String prompt, List images, String detail, Consumer callback) {
+ Map paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "doubao-chatVision-sse").start();
+ }
+
+ @Override
+ public String videoTasks(String text, String image, final List videoParams) {
+ String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams);
+ final HttpResponse response = sendPost(CREATE_VIDEO, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String getVideoTasksInfo(String taskId) {
+ final HttpResponse response = sendGet(CREATE_VIDEO + "/" + taskId);
+ return response.body();
+ }
+
+
+ @Override
+ public String embeddingText(String[] input) {
+ String paramJson = buildEmbeddingTextRequestBody(input);
+ final HttpResponse response = sendPost(EMBEDDING_TEXT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String embeddingVision(String text, String image) {
+ String paramJson = buildEmbeddingVisionRequestBody(text, image);
+ final HttpResponse response = sendPost(EMBEDDING_VISION, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String botsChat(final List messages) {
+ String paramJson = buildBotsChatRequestBody(messages);
+ final HttpResponse response = sendPost(BOTS_CHAT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void botsChat(List messages, Consumer callback) {
+ Map paramMap = buildBotsChatStreamRequestBody(messages);
+ ThreadUtil.newThread(() -> sendPostStream(BOTS_CHAT, paramMap, callback::accept), "doubao-botsChat-sse").start();
+ }
+
+ @Override
+ public String tokenization(String[] text) {
+ String paramJson = buildTokenizationRequestBody(text);
+ final HttpResponse response = sendPost(TOKENIZATION, paramJson);
+ return response.body();
+ }
+
+
+ @Override
+ public String batchChat(final List messages) {
+ String paramJson = buildBatchChatRequestBody(messages);
+ final HttpResponse response = sendPost(BATCH_CHAT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String createContext(final List messages, String mode) {
+ String paramJson = buildCreateContextRequest(messages, mode);
+ final HttpResponse response = sendPost(CREATE_CONTEXT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String chatContext(final List messages, String contextId) {
+ String paramJson = buildChatContentRequestBody(messages, contextId);
+ final HttpResponse response = sendPost(CHAT_CONTEXT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void chatContext(final List messages, String contextId, final Consumer callback) {
+ Map paramMap = buildChatContentStreamRequestBody(messages, contextId);
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_CONTEXT, paramMap, callback::accept), "doubao-chatContext-sse").start();
+ }
+
+ @Override
+ public String imagesGenerations(String prompt) {
+ String paramJson = buildImagesGenerationsRequestBody(prompt);
+ final HttpResponse response = sendPost(IMAGES_GENERATIONS, paramJson);
+ return response.body();
+ }
+
+ // 构建chat请求体
+ private String buildChatRequestBody(final List messages) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ // 构建chatStream请求体
+ private Map buildChatStreamRequestBody(final List messages) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ //构建chatVision请求体
+ private String buildChatVisionRequestBody(String prompt, final List images, String detail) {
+ // 定义消息结构
+ final List messages = new ArrayList<>();
+ final List content = new ArrayList<>();
+
+ final Map contentMap = new HashMap<>();
+ contentMap.put("type", "text");
+ contentMap.put("text", prompt);
+ content.add(contentMap);
+ for (String img : images) {
+ HashMap imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ HashMap urlMap = new HashMap<>();
+ urlMap.put("url", img);
+ urlMap.put("detail", detail);
+ imgUrlMap.put("image_url", urlMap);
+ content.add(imgUrlMap);
+ }
+
+ messages.add(new Message("user", content));
+
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ private Map buildChatVisionStreamRequestBody(String prompt, final List images, String detail) {
+ // 定义消息结构
+ final List messages = new ArrayList<>();
+ final List content = new ArrayList<>();
+
+ final Map contentMap = new HashMap<>();
+ contentMap.put("type", "text");
+ contentMap.put("text", prompt);
+ content.add(contentMap);
+ for (String img : images) {
+ HashMap imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ HashMap urlMap = new HashMap<>();
+ urlMap.put("url", img);
+ urlMap.put("detail", detail);
+ imgUrlMap.put("image_url", urlMap);
+ content.add(imgUrlMap);
+ }
+
+ messages.add(new Message("user", content));
+
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ return paramMap;
+ }
+
+ //构建文本向量化请求体
+ private String buildEmbeddingTextRequestBody(String[] input) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("input", input);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建图文向量化请求体
+ private String buildEmbeddingVisionRequestBody(String text, String image) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+
+ final List input = new ArrayList<>();
+ //添加文本参数
+ if (!StrUtil.isBlank(text)) {
+ final Map textMap = new HashMap<>();
+ textMap.put("type", "text");
+ textMap.put("text", text);
+ input.add(textMap);
+ }
+ //添加图片参数
+ if (!StrUtil.isBlank(image)) {
+ final Map imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ final Map urlMap = new HashMap<>();
+ urlMap.put("url", image);
+ imgUrlMap.put("image_url", urlMap);
+ input.add(imgUrlMap);
+ }
+
+ paramMap.put("input", input);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建应用chat请求体
+ private String buildBotsChatRequestBody(final List messages) {
+ return buildChatRequestBody(messages);
+ }
+
+ private Map buildBotsChatStreamRequestBody(final List messages) {
+ return buildChatStreamRequestBody(messages);
+ }
+
+ //构建分词请求体
+ private String buildTokenizationRequestBody(String[] text) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("text", text);
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建批量推理chat请求体
+ private String buildBatchChatRequestBody(final List messages) {
+ return buildChatRequestBody(messages);
+ }
+
+ private Map buildBatchChatStreamRequestBody(final List messages) {
+ return buildChatStreamRequestBody(messages);
+ }
+
+ //构建创建上下文缓存请求体
+ private String buildCreateContextRequest(final List messages, String mode) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("messages", messages);
+ paramMap.put("model", config.getModel());
+ paramMap.put("mode", mode);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建上下文缓存对话请求体
+ private String buildChatContentRequestBody(final List messages, String contextId) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ paramMap.put("context_id", contextId);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ private Map buildChatContentStreamRequestBody(final List messages, String contextId) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ paramMap.put("context_id", contextId);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ //构建创建视频任务请求体
+ private String buildGenerationsTasksRequestBody(String text, String image, final List videoParams) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+
+ final List content = new ArrayList<>();
+ //添加文本参数
+ final Map textMap = new HashMap<>();
+ if (!StrUtil.isBlank(text)) {
+ textMap.put("type", "text");
+ textMap.put("text", text);
+ content.add(textMap);
+ }
+ //添加图片参数
+ if (!StrUtil.isBlank(image)) {
+ final Map imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ final Map urlMap = new HashMap<>();
+ urlMap.put("url", image);
+ imgUrlMap.put("image_url", urlMap);
+ content.add(imgUrlMap);
+ }
+
+ //添加视频参数
+ if (videoParams != null && !videoParams.isEmpty()) {
+ //如果有文本参数就加在后面
+ if (textMap != null && !textMap.isEmpty()) {
+ int textIndex = content.indexOf(textMap);
+ StringBuilder textBuilder = new StringBuilder(text);
+ for (DoubaoCommon.DoubaoVideo videoParam : videoParams) {
+ textBuilder.append(" ").append(videoParam.getType()).append(" ").append(videoParam.getValue());
+ }
+ textMap.put("type", "text");
+ textMap.put("text", textBuilder.toString());
+
+ if (textIndex != -1) {
+ content.set(textIndex, textMap);
+ } else {
+ content.add(textMap);
+ }
+ } else {
+ //如果没有文本参数就重新增加
+ StringBuilder textBuilder = new StringBuilder();
+ for (DoubaoCommon.DoubaoVideo videoParam : videoParams) {
+ textBuilder.append(videoParam.getType()).append(videoParam.getValue()).append(" ");
+ }
+ textMap.put("type", "text");
+ textMap.put("text", textBuilder.toString());
+ content.add(textMap);
+ }
+ }
+
+ paramMap.put("content", content);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建文生图请求体
+ private String buildImagesGenerationsRequestBody(String prompt) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("prompt", prompt);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/package-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd1e84371a84e9274a58e9c313135df86aebfc29
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/doubao/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * 对doubao的封装实现
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+
+package cn.hutool.ai.model.doubao;
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokCommon.java
new file mode 100644
index 0000000000000000000000000000000000000000..2044e58e9ff218dc20869acda6b19ec3ed800b0b
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokCommon.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.grok;
+
+/**
+ * grok公共类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class GrokCommon {
+
+ //grok视觉参数
+ public enum GrokVision {
+
+ AUTO("auto"),
+ LOW("low"),
+ HIGH("high");
+
+ private final String detail;
+
+ GrokVision(String detail) {
+ this.detail = detail;
+ }
+
+ public String getDetail() {
+ return detail;
+ }
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a4727b95850f397acda67e575ef3a8bb8cfd329
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokConfig.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.grok;
+
+import cn.hutool.ai.Models;
+import cn.hutool.ai.core.BaseConfig;
+
+/**
+ * Grok配置类,初始化API接口地址,设置默认的模型
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class GrokConfig extends BaseConfig {
+
+ private final String API_URL = "https://api.x.ai/v1";
+
+ private final String DEFAULT_MODEL = Models.Grok.GROK_2_1212.getModel();
+
+
+ public GrokConfig() {
+ setApiUrl(API_URL);
+ setModel(DEFAULT_MODEL);
+ }
+
+ public GrokConfig(String apiKey) {
+ this();
+ setApiKey(apiKey);
+ }
+
+ @Override
+ public String getModelName() {
+ return "grok";
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..af62fd2195dd60a0ead3ed38c9c94454f7bf7920
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.grok;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.AIServiceProvider;
+
+/**r
+ * 创建Grok服务实现类
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class GrokProvider implements AIServiceProvider {
+
+ @Override
+ public String getServiceName() {
+ return "grok";
+ }
+
+ @Override
+ public GrokService create(final AIConfig config) {
+ return new GrokServiceImpl(config);
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokService.java
new file mode 100644
index 0000000000000000000000000000000000000000..4658f766b025ce7d4fa0a577d108328b2598356a
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokService.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.grok;
+
+import cn.hutool.ai.core.AIService;
+import cn.hutool.ai.core.Message;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * grok支持的扩展接口
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public interface GrokService extends AIService {
+
+ /**
+ * 创建消息回复
+ *
+ * @param prompt 题词
+ * @param maxToken 最大token
+ * @return AI回答
+ * @since 5.8.38
+ */
+ default String message(String prompt, int maxToken){
+ // 定义消息结构
+ final List messages = new ArrayList<>();
+ messages.add(new Message("system", "You are a helpful assistant"));
+ messages.add(new Message("user", prompt));
+ return message(messages, maxToken);
+ }
+
+ /**
+ * 创建消息回复-SSE流式输出
+ *
+ * @param prompt 题词
+ * @param maxToken 最大token
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ default void message(String prompt, int maxToken, final Consumer callback){
+ final List messages = new ArrayList<>();
+ messages.add(new Message("system", "You are a helpful assistant"));
+ messages.add(new Message("user", prompt));
+ message(messages, maxToken, callback);
+ }
+
+ /**
+ * 创建消息回复
+ *
+ * @param messages messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
+ * @param maxToken 最大token
+ * @return AI回答
+ * @since 5.8.39
+ */
+ String message(List messages, int maxToken);
+
+ /**
+ * 创建消息回复-SSE流式输出
+ *
+ * @param messages messages 由对话组成的消息列表。如系统人设,背景信息等,用户自定义的信息
+ * @param maxToken 最大token
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ void message(List messages, int maxToken, final Consumer callback);
+
+ /**
+ * 图像理解:模型会依据传入的图片信息以及问题,给出回复。
+ *
+ * @param prompt 题词
+ * @param images 图片列表/或者图片Base64编码图片列表(URI形式)
+ * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto
+ * @return AI回答
+ * @since 5.8.38
+ */
+ String chatVision(String prompt, final List images, String detail);
+
+ /**
+ * 图像理解-SSE流式输出
+ *
+ * @param prompt 题词
+ * @param images 图片列表/或者图片Base64编码图片列表(URI形式)
+ * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ void chatVision(String prompt, final List images, String detail,final Consumer callback);
+
+ /**
+ * 图像理解:模型会依据传入的图片信息以及问题,给出回复。
+ *
+ * @param prompt 题词
+ * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
+ * @return AI回答
+ * @since 5.8.38
+ */
+ default String chatVision(String prompt, final List images) {
+ return chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail());
+ }
+
+ /**
+ * 图像理解:模型会依据传入的图片信息以及问题,给出回复。
+ *
+ * @param prompt 题词
+ * @param images 传入|的图片列表地址/或者图片Base64编码图片列表(URI形式)
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ default void chatVision(String prompt, final List images, final Consumer callback){
+ chatVision(prompt, images, GrokCommon.GrokVision.AUTO.getDetail(), callback);
+ }
+
+ /**
+ * 列出所有model列表
+ *
+ * @return model列表
+ * @since 5.8.38
+ */
+ String models();
+
+ /**
+ * 获取模型信息
+ *
+ * @param modelId model ID
+ * @return model信息
+ * @since 5.8.38
+ */
+ String getModel(String modelId);
+
+ /**
+ * 列出所有语言model
+ *
+ * @return languageModel列表
+ * @since 5.8.38
+ */
+ String languageModels();
+
+ /**
+ * 获取语言模型信息
+ *
+ * @param modelId model ID
+ * @return model信息
+ * @since 5.8.38
+ */
+ String getLanguageModel(String modelId);
+
+ /**
+ * 分词:可以将文本转换为模型可理解的 token 信息
+ *
+ * @param text 需要分词的内容
+ * @return 分词结果
+ * @since 5.8.38
+ */
+ String tokenizeText(String text);
+
+ /**
+ * 从延迟对话中获取结果
+ *
+ * @param requestId 延迟对话中的延迟请求ID
+ * @return AI回答
+ * @since 5.8.38
+ */
+ String deferredCompletion(String requestId);
+
+ /**
+ * 文生图
+ * 请设置config中model为支持图片功能的模型,目前支持GROK_2_IMAGE
+ *
+ * @param prompt 题词
+ * @return 包含生成图片的url
+ * @since 5.8.39
+ */
+ String imagesGenerations(String prompt);
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..1c53932e527711db2e35d7a00b38a21921e8353d
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/GrokServiceImpl.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.grok;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.BaseAIService;
+import cn.hutool.ai.core.Message;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Grok服务,AI具体功能的实现
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+public class GrokServiceImpl extends BaseAIService implements GrokService {
+
+ //对话补全
+ private final String CHAT_ENDPOINT = "/chat/completions";
+ //创建消息回复
+ private final String MESSAGES = "/messages";
+ //列出模型
+ private final String MODELS_ENDPOINT = "/models";
+ //列出语言模型
+ private final String LANGUAGE_MODELS = "/language-models";
+ //分词
+ private final String TOKENIZE_TEXT = "/tokenize-text";
+ //获取延迟对话
+ private final String DEFERRED_COMPLETION = "/chat/deferred-completion";
+ //文生图
+ private final String IMAGES_GENERATIONS = "/images/generations";
+
+ public GrokServiceImpl(final AIConfig config) {
+ //初始化grok客户端
+ super(config);
+ }
+
+ @Override
+ public String chat(final List messages) {
+ String paramJson = buildChatRequestBody(messages);
+ final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void chat(List messages,Consumer callback) {
+ Map paramMap = buildChatStreamRequestBody(messages);
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "grok-chat-sse").start();
+ }
+
+ @Override
+ public String message(final List messages, int maxToken) {
+ String paramJson = buildMessageRequestBody(messages, maxToken);
+ final HttpResponse response = sendPost(MESSAGES, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void message(List messages, int maxToken, final Consumer callback) {
+ Map paramMap = buildMessageStreamRequestBody(messages, maxToken);
+ ThreadUtil.newThread(() -> sendPostStream(MESSAGES, paramMap, callback::accept), "grok-message-sse").start();
+ }
+
+ @Override
+ public String chatVision(String prompt, final List images, String detail) {
+ String paramJson = buildChatVisionRequestBody(prompt, images, detail);
+ final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void chatVision(String prompt, List images, String detail, Consumer callback) {
+ Map paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "grok-chatVision-sse").start();
+ }
+
+ @Override
+ public String models() {
+ final HttpResponse response = sendGet(MODELS_ENDPOINT);
+ return response.body();
+ }
+
+ @Override
+ public String getModel(String modelId) {
+ final HttpResponse response = sendGet(MODELS_ENDPOINT + "/" + modelId);
+ return response.body();
+ }
+
+ @Override
+ public String languageModels() {
+ final HttpResponse response = sendGet(LANGUAGE_MODELS);
+ return response.body();
+ }
+
+ @Override
+ public String getLanguageModel(String modelId) {
+ final HttpResponse response = sendGet(LANGUAGE_MODELS + "/" + modelId);
+ return response.body();
+ }
+
+ @Override
+ public String tokenizeText(String text) {
+ String paramJson = buildTokenizeRequestBody(text);
+ final HttpResponse response = sendPost(TOKENIZE_TEXT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String deferredCompletion(String requestId) {
+ final HttpResponse response = sendGet(DEFERRED_COMPLETION + "/" + requestId);
+ return response.body();
+ }
+
+ @Override
+ public String imagesGenerations(String prompt) {
+ String paramJson = buildImagesGenerationsRequestBody(prompt);
+ final HttpResponse response = sendPost(IMAGES_GENERATIONS, paramJson);
+ return response.body();
+ }
+
+ // 构建chat请求体
+ private String buildChatRequestBody(final List messages) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ private Map buildChatStreamRequestBody(final List messages) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ //构建chatVision请求体
+ private String buildChatVisionRequestBody(String prompt, final List images, String detail) {
+ // 定义消息结构
+ final List messages = new ArrayList<>();
+ final List content = new ArrayList<>();
+
+ final Map contentMap = new HashMap<>();
+ contentMap.put("type", "text");
+ contentMap.put("text", prompt);
+ content.add(contentMap);
+ for (String img : images) {
+ HashMap imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ HashMap urlMap = new HashMap<>();
+ urlMap.put("url", img);
+ urlMap.put("detail", detail);
+ imgUrlMap.put("image_url", urlMap);
+ content.add(imgUrlMap);
+ }
+
+ messages.add(new Message("user", content));
+
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ private Map buildChatVisionStreamRequestBody(String prompt, final List images, String detail) {
+ // 定义消息结构
+ final List messages = new ArrayList<>();
+ final List content = new ArrayList<>();
+
+ final Map contentMap = new HashMap<>();
+ contentMap.put("type", "text");
+ contentMap.put("text", prompt);
+ content.add(contentMap);
+ for (String img : images) {
+ HashMap imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ HashMap urlMap = new HashMap<>();
+ urlMap.put("url", img);
+ urlMap.put("detail", detail);
+ imgUrlMap.put("image_url", urlMap);
+ content.add(imgUrlMap);
+ }
+
+ messages.add(new Message("user", content));
+
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ return paramMap;
+ }
+
+ //构建消息回复请求体
+ private String buildMessageRequestBody(final List messages, int maxToken) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ paramMap.put("max_tokens", maxToken);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ private Map buildMessageStreamRequestBody(final List messages, int maxToken) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ paramMap.put("max_tokens", maxToken);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ //构建分词请求体
+ private String buildTokenizeRequestBody(String text) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("text", text);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建文生图请求体
+ private String buildImagesGenerationsRequestBody(String prompt) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("prompt", prompt);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/grok/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/package-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..6541d40fd0bd818a2394e9c047b0aeb60a4fc2af
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/grok/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * 对grok的封装实现
+ *
+ * @author elichow
+ * @since 5.8.38
+ */
+
+package cn.hutool.ai.model.grok;
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolCommon.java
new file mode 100644
index 0000000000000000000000000000000000000000..4db506a64084e59e76ad2c8d24deea285f26a0ea
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolCommon.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.hutool;
+
+/**
+ * hutool公共类
+ *
+ * @author elichow
+ * @since 5.8.39
+ */
+public class HutoolCommon {
+
+ //hutool视觉参数
+ public enum HutoolVision {
+
+ AUTO("auto"),
+ LOW("low"),
+ HIGH("high");
+
+ private final String detail;
+
+ HutoolVision(String detail) {
+ this.detail = detail;
+ }
+
+ public String getDetail() {
+ return detail;
+ }
+ }
+
+ //hutool音频参数
+ public enum HutoolSpeech {
+
+ ALLOY("alloy"),
+ ASH("ash"),
+ CORAL("coral"),
+ ECHO("echo"),
+ FABLE("fable"),
+ ONYX("onyx"),
+ NOVA("nova"),
+ SAGE("sage"),
+ SHIMMER("shimmer");
+
+ private final String voice;
+
+ HutoolSpeech(String voice) {
+ this.voice = voice;
+ }
+
+ public String getVoice() {
+ return voice;
+ }
+ }
+
+ //hutool视频生成参数
+ public enum HutoolVideo {
+
+ //宽高比例
+ RATIO_16_9("--rt", "16:9"),//[1280, 720]
+ RATIO_4_3("--rt", "4:3"),//[960, 720]
+ RATIO_1_1("--rt", "1:1"),//[720, 720]
+ RATIO_3_4("--rt", "3:4"),//[720, 960]
+ RATIO_9_16("--rt", "9:16"),//[720, 1280]
+ RATIO_21_9("--rt", "21:9"),//[1280, 544]
+
+ //生成视频时长
+ DURATION_5("--dur", 5),//文生视频,图生视频
+ DURATION_10("--dur", 10),//文生视频
+
+ //帧率,即一秒时间内视频画面数量
+ FPS_5("--fps", 24),
+
+ //视频分辨率
+ RESOLUTION_5("--rs", "720p"),
+
+ //生成视频是否包含水印
+ WATERMARK_TRUE("--wm", true),
+ WATERMARK_FALSE("--wm", false);
+
+ private final String type;
+ private final Object value;
+
+ HutoolVideo(String type, Object value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Object getValue() {
+ if (value instanceof String) {
+ return (String) value;
+ } else if (value instanceof Integer) {
+ return (Integer) value;
+ } else if (value instanceof Boolean) {
+ return (Boolean) value;
+ }
+ return value;
+ }
+
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..a4f120b4c71852e8464d764a6d52eef78fdbe2bb
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolConfig.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.hutool;
+
+import cn.hutool.ai.Models;
+import cn.hutool.ai.core.BaseConfig;
+
+/**
+ * Hutool配置类,初始化API接口地址,设置默认的模型
+ *
+ * @author elichow
+ * @since 5.8.39
+ */
+public class HutoolConfig extends BaseConfig {
+
+ private final String API_URL = "https://api.hutool.cn/ai/api";
+
+ private final String DEFAULT_MODEL = Models.Hutool.HUTOOL.getModel();
+
+ public HutoolConfig() {
+ setApiUrl(API_URL);
+ setModel(DEFAULT_MODEL);
+ }
+
+ public HutoolConfig(String apiKey) {
+ this();
+ setApiKey(apiKey);
+ }
+
+ @Override
+ public String getModelName() {
+ return "hutool";
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..c983ced9f2927edd8fc860ba9b13598e2dbcfd4f
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.hutool;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.AIServiceProvider;
+
+/**r
+ * 创建Hutool服务实现类
+ *
+ * @author elichow
+ * @since 5.8.39
+ */
+public class HutoolProvider implements AIServiceProvider {
+
+ @Override
+ public String getServiceName() {
+ return "hutool";
+ }
+
+ @Override
+ public HutoolService create(final AIConfig config) {
+ return new HutoolServiceImpl(config);
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolService.java
new file mode 100644
index 0000000000000000000000000000000000000000..7293d5d0c12fc9ef67c6fa2e35a2835fc297ef21
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolService.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.hutool;
+
+import cn.hutool.ai.core.AIService;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * hutool支持的扩展接口
+ *
+ * @author elichow
+ * @since 5.8.39
+ */
+public interface HutoolService extends AIService {
+
+ /**
+ * 图像理解:模型会依据传入的图片信息以及问题,给出回复。
+ *
+ * @param prompt 题词
+ * @param images 图片列表/或者图片Base64编码图片列表(URI形式)
+ * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto
+ * @return AI回答
+ * @since 5.8.39
+ */
+ String chatVision(String prompt, final List images, String detail);
+
+ /**
+ * 图像理解-SSE流式输出
+ *
+ * @param prompt 题词
+ * @param images 图片列表/或者图片Base64编码图片列表(URI形式)
+ * @param detail 手动设置图片的质量,取值范围high、low、auto,默认为auto
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ void chatVision(String prompt, final List images, String detail,final Consumer callback);
+
+ /**
+ * 图像理解:模型会依据传入的图片信息以及问题,给出回复。
+ *
+ * @param prompt 题词
+ * @param images 传入的图片列表地址/或者图片Base64编码图片列表(URI形式)
+ * @return AI回答
+ * @since 5.8.39
+ */
+ default String chatVision(String prompt, final List images) {
+ return chatVision(prompt, images, HutoolCommon.HutoolVision.AUTO.getDetail());
+ }
+
+ /**
+ * 图像理解:模型会依据传入的图片信息以及问题,给出回复。
+ *
+ * @param prompt 题词
+ * @param images 传入|的图片列表地址/或者图片Base64编码图片列表(URI形式)
+ * @param callback 流式数据回调函数
+ * @since 5.8.39
+ */
+ default void chatVision(String prompt, final List images, final Consumer callback){
+ chatVision(prompt, images, HutoolCommon.HutoolVision.AUTO.getDetail(), callback);
+ }
+
+ /**
+ * 分词:可以将文本转换为模型可理解的 token 信息
+ *
+ * @param text 需要分词的内容
+ * @return 分词结果
+ * @since 5.8.39
+ */
+ String tokenizeText(String text);
+
+ /**
+ * 文生图
+ *
+ * @param prompt 题词
+ * @return 包含生成图片的url
+ * @since 5.8.39
+ */
+ String imagesGenerations(String prompt);
+
+ /**
+ * 图文向量化:仅支持单一文本、单张图片或文本与图片的组合输入(即一段文本 + 一张图片),暂不支持批量文本 / 图片的同时处理
+ *
+ * @param text 需要向量化的内容
+ * @param image 需要向量化的图片地址/或者图片Base64编码图片(URI形式)
+ * @return 处理后的向量信息
+ * @since 5.8.39
+ */
+ String embeddingVision(String text, String image);
+
+ /**
+ * TTS文本转语音
+ *
+ * @param input 需要转成语音的文本
+ * @param voice AI的音色
+ * @return 返回的音频mp3文件流
+ * @since 5.8.39
+ */
+ InputStream tts(String input, final HutoolCommon.HutoolSpeech voice);
+
+ /**
+ * TTS文本转语音
+ *
+ * @param input 需要转成语音的文本
+ * @return 返回的音频mp3文件流
+ * @since 5.8.39
+ */
+ default InputStream tts(String input) {
+ return tts(input, HutoolCommon.HutoolSpeech.ALLOY);
+ }
+
+ /**
+ * STT音频转文本
+ *
+ * @param file 需要转成文本的音频文件
+ * @return 返回的文本内容
+ * @since 5.8.39
+ */
+ String stt(final File file);
+
+ /**
+ * 创建视频生成任务
+ *
+ * @param text 文本提示词
+ * @param image 图片/或者图片Base64编码图片(URI形式)
+ * @param videoParams 视频参数列表
+ * @return 生成任务id
+ * @since 5.8.39
+ */
+ String videoTasks(String text, String image, final List videoParams);
+
+ /**
+ * 创建视频生成任务
+ *
+ * @param text 文本提示词
+ * @param image 图片/或者图片Base64编码图片(URI形式)
+ * @return 生成任务id
+ * @since 5.8.39
+ */
+ default String videoTasks(String text, String image) {
+ return videoTasks(text, image, null);
+ }
+
+ /**
+ * 查询视频生成任务信息
+ *
+ * @param taskId 通过创建生成视频任务返回的生成任务id
+ * @return 生成任务信息
+ * @since 5.8.39
+ */
+ String getVideoTasksInfo(String taskId);
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..27b90e75c445debc5f650eb7b82741526b898409
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/HutoolServiceImpl.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.hutool;
+
+import cn.hutool.ai.AIException;
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.BaseAIService;
+import cn.hutool.ai.core.Message;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONUtil;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Hutool服务,AI具体功能的实现
+ *
+ * @author elichow
+ * @since 5.8.39
+ */
+public class HutoolServiceImpl extends BaseAIService implements HutoolService {
+
+ //对话补全
+ private final String CHAT_ENDPOINT = "/chat/completions";
+ //分词
+ private final String TOKENIZE_TEXT = "/tokenize/text";
+ //文生图
+ private final String IMAGES_GENERATIONS = "/images/generations";
+ //图文向量化
+ private final String EMBEDDING_VISION = "/embeddings/multimodal";
+ //文本转语音
+ private final String TTS = "/audio/tts";
+ //语音转文本
+ private final String STT = "/audio/stt";
+ //创建视频生成任务
+ private final String CREATE_VIDEO = "/video/generations";
+
+ public HutoolServiceImpl(final AIConfig config) {
+ //初始化hutool客户端
+ super(config);
+ }
+
+ @Override
+ public String chat(final List messages) {
+ String paramJson = buildChatRequestBody(messages);
+ final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void chat(List messages,Consumer callback) {
+ Map paramMap = buildChatStreamRequestBody(messages);
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "hutool-chat-sse").start();
+ }
+
+ @Override
+ public String chatVision(String prompt, final List images, String detail) {
+ String paramJson = buildChatVisionRequestBody(prompt, images, detail);
+ final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void chatVision(String prompt, List images, String detail, Consumer callback) {
+ Map paramMap = buildChatVisionStreamRequestBody(prompt, images, detail);
+ System.out.println(JSONUtil.toJsonStr(paramMap));
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "hutool-chatVision-sse").start();
+ }
+
+ @Override
+ public String tokenizeText(String text) {
+ String paramJson = buildTokenizeRequestBody(text);
+ final HttpResponse response = sendPost(TOKENIZE_TEXT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String imagesGenerations(String prompt) {
+ String paramJson = buildImagesGenerationsRequestBody(prompt);
+ final HttpResponse response = sendPost(IMAGES_GENERATIONS, paramJson);
+ return response.body();
+ }
+
+
+ @Override
+ public String embeddingVision(String text, String image) {
+ String paramJson = buildEmbeddingVisionRequestBody(text, image);
+ final HttpResponse response = sendPost(EMBEDDING_VISION, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public InputStream tts(String input, final HutoolCommon.HutoolSpeech voice) {
+ try {
+ String paramJson = buildTTSRequestBody(input, voice.getVoice());
+ final HttpResponse response = sendPost(TTS, paramJson);
+
+ // 检查响应内容类型
+ String contentType = response.header("Content-Type");
+ if (contentType != null && contentType.startsWith("application/json")) {
+ // 如果是JSON响应,说明有错误
+ String errorBody = response.body();
+ throw new AIException("TTS请求失败: " + errorBody);
+ }
+ // 默认返回音频流
+ return response.bodyStream();
+ } catch (Exception e) {
+ throw new AIException("TTS处理失败: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String stt(final File file) {
+ final Map paramMap = buildSTTRequestBody(file);
+ final HttpResponse response = sendFormData(STT, paramMap);
+ return response.body();
+ }
+
+
+ @Override
+ public String videoTasks(String text, String image, final List videoParams) {
+ String paramJson = buildGenerationsTasksRequestBody(text, image, videoParams);
+ final HttpResponse response = sendPost(CREATE_VIDEO, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String getVideoTasksInfo(String taskId) {
+ final HttpResponse response = sendGet(CREATE_VIDEO + "/" + taskId);
+ return response.body();
+ }
+
+
+ // 构建chat请求体
+ private String buildChatRequestBody(final List messages) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ private Map buildChatStreamRequestBody(final List messages) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ //构建chatVision请求体
+ private String buildChatVisionRequestBody(String prompt, final List images, String detail) {
+ // 定义消息结构
+ final List messages = new ArrayList<>();
+ final List content = new ArrayList<>();
+
+ final Map contentMap = new HashMap<>();
+ contentMap.put("type", "text");
+ contentMap.put("text", prompt);
+ content.add(contentMap);
+ for (String img : images) {
+ HashMap imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ HashMap urlMap = new HashMap<>();
+ urlMap.put("url", img);
+ urlMap.put("detail", detail);
+ imgUrlMap.put("image_url", urlMap);
+ content.add(imgUrlMap);
+ }
+
+ messages.add(new Message("user", content));
+
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ private Map buildChatVisionStreamRequestBody(String prompt, final List images, String detail) {
+ // 定义消息结构
+ final List messages = new ArrayList<>();
+ final List content = new ArrayList<>();
+
+ final Map contentMap = new HashMap<>();
+ contentMap.put("type", "text");
+ contentMap.put("text", prompt);
+ content.add(contentMap);
+ for (String img : images) {
+ HashMap imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ HashMap urlMap = new HashMap<>();
+ urlMap.put("url", img);
+ urlMap.put("detail", detail);
+ imgUrlMap.put("image_url", urlMap);
+ content.add(imgUrlMap);
+ }
+
+ messages.add(new Message("user", content));
+
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ return paramMap;
+ }
+
+
+ //构建分词请求体
+ private String buildTokenizeRequestBody(String text) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("text", text);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建文生图请求体
+ private String buildImagesGenerationsRequestBody(String prompt) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("prompt", prompt);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建图文向量化请求体
+ private String buildEmbeddingVisionRequestBody(String text, String image) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+
+ final List input = new ArrayList<>();
+ //添加文本参数
+ if (!StrUtil.isBlank(text)) {
+ final Map textMap = new HashMap<>();
+ textMap.put("type", "text");
+ textMap.put("text", text);
+ input.add(textMap);
+ }
+ //添加图片参数
+ if (!StrUtil.isBlank(image)) {
+ final Map imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ final Map urlMap = new HashMap<>();
+ urlMap.put("url", image);
+ imgUrlMap.put("image_url", urlMap);
+ input.add(imgUrlMap);
+ }
+
+ paramMap.put("input", input);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ System.out.println(JSONUtil.toJsonStr(paramMap));
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+
+ //构建TTS请求体
+ private String buildTTSRequestBody(String input, String voice) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("input", input);
+ paramMap.put("voice", voice);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ //构建STT请求体
+ private Map buildSTTRequestBody(final File file) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("file", file);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ //构建创建视频任务请求体
+ private String buildGenerationsTasksRequestBody(String text, String image, final List videoParams) {
+ //使用JSON工具
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+
+ final List content = new ArrayList<>();
+ //添加文本参数
+ final Map textMap = new HashMap<>();
+ if (!StrUtil.isBlank(text)) {
+ textMap.put("type", "text");
+ textMap.put("text", text);
+ content.add(textMap);
+ }
+ //添加图片参数
+ if (!StrUtil.isBlank(image)) {
+ final Map imgUrlMap = new HashMap<>();
+ imgUrlMap.put("type", "image_url");
+ final Map urlMap = new HashMap<>();
+ urlMap.put("url", image);
+ imgUrlMap.put("image_url", urlMap);
+ content.add(imgUrlMap);
+ }
+
+ //添加视频参数
+ if (videoParams != null && !videoParams.isEmpty()) {
+ //如果有文本参数就加在后面
+ if (textMap != null && !textMap.isEmpty()) {
+ int textIndex = content.indexOf(textMap);
+ StringBuilder textBuilder = new StringBuilder(text);
+ for (HutoolCommon.HutoolVideo videoParam : videoParams) {
+ textBuilder.append(" ").append(videoParam.getType()).append(" ").append(videoParam.getValue());
+ }
+ textMap.put("type", "text");
+ textMap.put("text", textBuilder.toString());
+
+ if (textIndex != -1) {
+ content.set(textIndex, textMap);
+ } else {
+ content.add(textMap);
+ }
+ } else {
+ //如果没有文本参数就重新增加
+ StringBuilder textBuilder = new StringBuilder();
+ for (HutoolCommon.HutoolVideo videoParam : videoParams) {
+ textBuilder.append(videoParam.getType()).append(videoParam.getValue()).append(" ");
+ }
+ textMap.put("type", "text");
+ textMap.put("text", textBuilder.toString());
+ content.add(textMap);
+ }
+ }
+
+ paramMap.put("content", content);
+ //合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+ System.out.println(JSONUtil.toJsonStr(paramMap));
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/package-info.java b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/package-info.java
new file mode 100644
index 0000000000000000000000000000000000000000..f89797d27a77b67c557b04be47ce27e92f3d9799
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/hutool/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * 对hutool的封装实现
+ *
+ * @author elichow
+ * @since 5.8.39
+ */
+
+package cn.hutool.ai.model.hutool;
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaCommon.java b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaCommon.java
new file mode 100644
index 0000000000000000000000000000000000000000..e504c61c17bcc853bba25bddca4f7a0a57630cbb
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaCommon.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.ollama;
+
+/**
+ * Ollama公共类
+ *
+ * @author yangruoyu-yumeisoft
+ * @since 5.8.40
+ */
+public class OllamaCommon {
+
+ /**
+ * Ollama模型格式枚举
+ */
+ public enum OllamaFormat {
+ /**
+ * JSON格式
+ */
+ JSON("json"),
+ /**
+ * 无格式
+ */
+ NONE("");
+
+ private final String format;
+
+ OllamaFormat(String format) {
+ this.format = format;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+ }
+
+ /**
+ * Ollama选项常量
+ */
+ public static class Options {
+ /**
+ * 温度参数
+ */
+ public static final String TEMPERATURE = "temperature";
+ /**
+ * top_p参数
+ */
+ public static final String TOP_P = "top_p";
+ /**
+ * top_k参数
+ */
+ public static final String TOP_K = "top_k";
+ /**
+ * 最大token数
+ */
+ public static final String NUM_PREDICT = "num_predict";
+ /**
+ * 随机种子
+ */
+ public static final String SEED = "seed";
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaConfig.java b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..761c1f1b0f553ad6ed60f4e4db5f66188a20c209
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaConfig.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.ollama;
+
+import cn.hutool.ai.Models;
+import cn.hutool.ai.core.BaseConfig;
+
+/**
+ * Ollama配置类,初始化API接口地址,设置默认的模型
+ *
+ * @author yangruoyu-yumeisoft
+ * @since 5.8.40
+ */
+public class OllamaConfig extends BaseConfig {
+
+ private final String API_URL = "http://localhost:11434";
+
+ private final String DEFAULT_MODEL = Models.Ollama.QWEN3_32B.getModel();
+
+ public OllamaConfig() {
+ setApiUrl(API_URL);
+ setModel(DEFAULT_MODEL);
+ }
+
+ public OllamaConfig(String apiUrl) {
+ this();
+ setApiUrl(apiUrl);
+ }
+
+ public OllamaConfig(String apiUrl, String model) {
+ this();
+ setApiUrl(apiUrl);
+ setModel(model);
+ }
+
+ @Override
+ public String getModelName() {
+ return "ollama";
+ }
+
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaProvider.java b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..ec2639b3340b3a32ad699ee7a5bea9c57afd12b0
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.ollama;
+
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.AIServiceProvider;
+
+/**
+ * 创建Ollama服务实现类
+ *
+ * @author yangruoyu-yumeisoft
+ * @since 5.8.40
+ */
+public class OllamaProvider implements AIServiceProvider {
+
+ @Override
+ public String getServiceName() {
+ return "ollama";
+ }
+
+ @Override
+ public OllamaService create(final AIConfig config) {
+ return new OllamaServiceImpl(config);
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaService.java b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1a947e68e436744e4da4a8dab6a7b17e4508877
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaService.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.ollama;
+
+import cn.hutool.ai.core.AIService;
+import cn.hutool.ai.core.Message;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Ollama特有的功能
+ *
+ * @author yangruoyu-yumeisoft
+ * @since 5.8.40
+ */
+public interface OllamaService extends AIService {
+
+ /**
+ * 生成文本补全
+ *
+ * @param prompt 输入提示
+ * @return AI回答
+ * @since 5.8.40
+ */
+ String generate(String prompt);
+
+ /**
+ * 生成文本补全-SSE流式输出
+ *
+ * @param prompt 输入提示
+ * @param callback 流式数据回调函数
+ * @since 5.8.40
+ */
+ void generate(String prompt, Consumer callback);
+
+ /**
+ * 生成文本补全(带选项)
+ *
+ * @param prompt 输入提示
+ * @param format 响应格式
+ * @return AI回答
+ * @since 5.8.40
+ */
+ String generate(String prompt, String format);
+
+ /**
+ * 生成文本补全(带选项)-SSE流式输出
+ *
+ * @param prompt 输入提示
+ * @param format 响应格式
+ * @param callback 流式数据回调函数
+ * @since 5.8.40
+ */
+ void generate(String prompt, String format, Consumer callback);
+
+ /**
+ * 生成文本嵌入向量
+ *
+ * @param prompt 输入文本
+ * @return 嵌入向量结果
+ * @since 5.8.40
+ */
+ String embeddings(String prompt);
+
+ /**
+ * 列出本地可用的模型
+ *
+ * @return 模型列表
+ * @since 5.8.40
+ */
+ String listModels();
+
+ /**
+ * 显示模型信息
+ *
+ * @param modelName 模型名称
+ * @return 模型信息
+ * @since 5.8.40
+ */
+ String showModel(String modelName);
+
+ /**
+ * 拉取模型
+ *
+ * @param modelName 模型名称
+ * @return 拉取结果
+ * @since 5.8.40
+ */
+ String pullModel(String modelName);
+
+ /**
+ * 删除模型
+ *
+ * @param modelName 模型名称
+ * @return 删除结果
+ * @since 5.8.40
+ */
+ String deleteModel(String modelName);
+
+ /**
+ * 复制模型
+ *
+ * @param source 源模型名称
+ * @param destination 目标模型名称
+ * @return 复制结果
+ * @since 5.8.40
+ */
+ String copyModel(String source, String destination);
+
+ /**
+ * 简化的对话方法
+ *
+ * @param prompt 对话题词
+ * @return AI回答
+ * @since 5.8.40
+ */
+ default String chat(String prompt) {
+ final List messages = new ArrayList<>();
+ messages.add(new Message("user", prompt));
+ return chat(messages);
+ }
+
+ /**
+ * 简化的对话方法-SSE流式输出
+ *
+ * @param prompt 对话题词
+ * @param callback 流式数据回调函数
+ * @since 5.8.40
+ */
+ default void chat(String prompt, Consumer callback) {
+ final List messages = new ArrayList<>();
+ messages.add(new Message("user", prompt));
+ chat(messages, callback);
+ }
+}
diff --git a/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaServiceImpl.java b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..503a795c9f598d6de98144d445faa5633453ffc4
--- /dev/null
+++ b/hutool-ai/src/main/java/cn/hutool/ai/model/ollama/OllamaServiceImpl.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (c) 2025 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.ai.model.ollama;
+
+import cn.hutool.ai.AIException;
+import cn.hutool.ai.core.AIConfig;
+import cn.hutool.ai.core.BaseAIService;
+import cn.hutool.ai.core.Message;
+import cn.hutool.core.bean.BeanPath;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.Header;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * Ollama服务,AI具体功能的实现
+ *
+ * @author yangruoyu-yumeisoft
+ * @since 5.8.40
+ */
+public class OllamaServiceImpl extends BaseAIService implements OllamaService {
+
+ // 对话补全
+ private static final String CHAT_ENDPOINT = "/api/chat";
+ // 文本生成
+ private static final String GENERATE_ENDPOINT = "/api/generate";
+ // 文本嵌入
+ private static final String EMBEDDINGS_ENDPOINT = "/api/embeddings";
+ // 列出模型
+ private static final String LIST_MODELS_ENDPOINT = "/api/tags";
+ // 显示模型信息
+ private static final String SHOW_MODEL_ENDPOINT = "/api/show";
+ // 拉取模型
+ private static final String PULL_MODEL_ENDPOINT = "/api/pull";
+ // 删除模型
+ private static final String DELETE_MODEL_ENDPOINT = "/api/delete";
+ // 复制模型
+ private static final String COPY_MODEL_ENDPOINT = "/api/copy";
+
+ /**
+ * 构造函数
+ *
+ * @param config AI配置
+ */
+ public OllamaServiceImpl(final AIConfig config) {
+ super(config);
+ }
+
+ @Override
+ public String chat(final List messages) {
+ final String paramJson = buildChatRequestBody(messages);
+ final HttpResponse response = sendPost(CHAT_ENDPOINT, paramJson);
+ JSONObject responseJson = JSONUtil.parseObj(response.body());
+ Object errorMessage = BeanPath.create("error").get(responseJson);
+ if(errorMessage!=null){
+ throw new RuntimeException(errorMessage.toString());
+ }
+ return BeanPath.create("message.content").get(responseJson).toString();
+ }
+
+ @Override
+ public void chat(final List messages, final Consumer callback) {
+ Map paramMap = buildChatStreamRequestBody(messages);
+ ThreadUtil.newThread(() -> sendPostStream(CHAT_ENDPOINT, paramMap, callback::accept), "ollama-chat-sse").start();
+ }
+
+ @Override
+ public String generate(String prompt) {
+ final String paramJson = buildGenerateRequestBody(prompt, null);
+ final HttpResponse response = sendPost(GENERATE_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void generate(String prompt, Consumer callback) {
+ Map paramMap = buildGenerateStreamRequestBody(prompt, null);
+ ThreadUtil.newThread(() -> sendPostStream(GENERATE_ENDPOINT, paramMap, callback::accept), "ollama-generate-sse").start();
+ }
+
+ @Override
+ public String generate(String prompt, String format) {
+ final String paramJson = buildGenerateRequestBody(prompt, format);
+ final HttpResponse response = sendPost(GENERATE_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public void generate(String prompt, String format, Consumer callback) {
+ Map paramMap = buildGenerateStreamRequestBody(prompt, format);
+ ThreadUtil.newThread(() -> sendPostStream(GENERATE_ENDPOINT, paramMap, callback::accept), "ollama-generate-sse").start();
+ }
+
+ @Override
+ public String embeddings(String prompt) {
+ final String paramJson = buildEmbeddingsRequestBody(prompt);
+ final HttpResponse response = sendPost(EMBEDDINGS_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String listModels() {
+ final HttpResponse response = sendGet(LIST_MODELS_ENDPOINT);
+ return response.body();
+ }
+
+ @Override
+ public String showModel(String modelName) {
+ final String paramJson = buildShowModelRequestBody(modelName);
+ final HttpResponse response = sendPost(SHOW_MODEL_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String pullModel(String modelName) {
+ final String paramJson = buildPullModelRequestBody(modelName);
+ final HttpResponse response = sendPost(PULL_MODEL_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String deleteModel(String modelName) {
+ final String paramJson = buildDeleteModelRequestBody(modelName);
+ final HttpResponse response = sendDeleteRequest(DELETE_MODEL_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ @Override
+ public String copyModel(String source, String destination) {
+ final String paramJson = buildCopyModelRequestBody(source, destination);
+ final HttpResponse response = sendPost(COPY_MODEL_ENDPOINT, paramJson);
+ return response.body();
+ }
+
+ // 构建chat请求体
+ private String buildChatRequestBody(final List messages) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream",false);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ // 合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ // 构建chatStream请求体
+ private Map buildChatStreamRequestBody(final List messages) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("messages", messages);
+ // 合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ // 构建generate请求体
+ private String buildGenerateRequestBody(final String prompt, final String format) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("prompt", prompt);
+ if (StrUtil.isNotBlank(format)) {
+ paramMap.put("format", format);
+ }
+ // 合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ // 构建generateStream请求体
+ private Map buildGenerateStreamRequestBody(final String prompt, final String format) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("stream", true);
+ paramMap.put("model", config.getModel());
+ paramMap.put("prompt", prompt);
+ if (StrUtil.isNotBlank(format)) {
+ paramMap.put("format", format);
+ }
+ // 合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return paramMap;
+ }
+
+ // 构建embeddings请求体
+ private String buildEmbeddingsRequestBody(final String prompt) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("model", config.getModel());
+ paramMap.put("prompt", prompt);
+ // 合并其他参数
+ paramMap.putAll(config.getAdditionalConfigMap());
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ // 构建showModel请求体
+ private String buildShowModelRequestBody(final String modelName) {
+ final Map paramMap = new HashMap<>();
+ paramMap.put("name", modelName);
+
+ return JSONUtil.toJsonStr(paramMap);
+ }
+
+ // 构建pullModel请求体
+ private String buildPullModelRequestBody(final String modelName) {
+ final Map