Docker部署私有仓库(registry&Harbor)-CSDN博客

mikel阅读(306)

来源: Docker部署私有仓库(registry&Harbor)-CSDN博客

简介
Docker Hub 官方仓库
在 Docker 中,当我们执行 docker pull xxx 的时候 ,它实际上是从 registry.hub.docker.com 这个地址去查找,这就是Docker公司为我们提供的公共仓库。在工作中,我们不可能把企业项目push到公有仓库进行管理。所以为了更好的管理镜像,Docker不仅提供了一个中央仓库,同时也允许我们搭建本地私有仓库。

仓库(Repository)
一个容易混淆的概念是注册服务器(registry)。实际上注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址docker.sina.com.cn/centos:centos7来说,docekr.sian.com.cn是注册服务器地址,centos是仓库名,centos7是仓库的tag(标签)。

部署registry
(1) 拉取registry私有库镜像
Docker 官方提供了一个搭建私有仓库的镜像 registry ,只需把镜像下载下来,运行容器并暴露5000端口,就可以使用了。

docker pull registry:2
1
docker run -d -v /opt/registry:/var/lib/registry -p 5000:5000 –name myregistry registry:2

1
2
参数解析

-d:后台运行
-p:将容器的5000端口映射到宿主机的5000端口
–restart:docker服务重启后总是重启此容器
–name:容器的名称
-v = –volume:数据卷,进行一个挂载宿主机:容器内。将容器的/var/lib/registry映射到宿主机的 /opt/registry目录

Registry服务默认会将上传的镜像保存在容器的/var/lib/registry,我们将主机的/opt/registry目录挂载到该目录,即可实现将镜像保存到主机的/opt/registry目录了。

执行命令 curl 172.16.1.30:5000/v2/_catalog,收到的响应如下,是个json对象,其中repositories对应的值是空的json数组,表示目前仓库里还没有镜像:

curl 172.16.1.30:5000/v2/_catalog
{“repositories”:[]}
1
2
(2) 支持http协议推送
正常情况下,应用服务器推送镜像到仓库用的是https,此处我们通过命令行来测试推送用的是普通的http,所以需要在客户端配置一下私有仓库的可信任设置让我们可以通过HTTP直接访问:

两种方法

修改/usr/lib/systemd/system/docker.service文件

或在/etc/docker/daemon.json 文件中添加以下内容

~]# cat /etc/docker/daemon.json
{ “insecure-registries”:[“192.168.50.11”] }
1
2
PS:如果不设置可信任源,又没有配置HTTPS证书,那么会遇到这个错误:error: Get https://ip:port/v1/_ping: http: server gave HTTP response to HTTPS client.

重新加载进程并重启docker服务:

systemctl daemon-reload
systemctl restart docker

(3)将本地镜像push到私有仓库中
如果不对私有仓库命名的话,默认走的是公共仓库(docker hub),所以需要命名镜像。

私有仓库镜像的命名规则:宿主机ip地址:端口号/xxxx(需要更改的名称)

以nignx镜像为例,下载nginx镜像:

docker pull nginx
docker tag nginx:latest 172.16.1.30:5000/nginx:latest
1
2
注意:当你对源镜像(nginx:latest)进行命名后,命名后的镜像名称也视为一个标签,因为id号是相同的。如果当源镜像(nginx:latest)删除,命名后的镜像依然会存在,因为删除的是一个标签。

docker push 172.16.1.30:5000/nginx:latest
1
查看私有仓库中的镜像

curl 172.16.1.30:5000/v2/_catalog
{“repositories”:[“nginx”]}
1
2
查看仓库中镜像的标签

curl 172.16.1.30:5000/v2/nginx/tags/list
{“name”:”nginx”,”tags”:[“latest”]}
1
2
如果需要删除私有仓库中的镜像,有一个简单的方法在运行registry时挂载在本地的目录[/opt/registry]中,找到指定的镜像存放的目录来进行删除。

cd /opt/registry
ls
blobs repositories
cd repositories/
ls
nginx
rm -rf nginx/
1
2
3
4
5
6
7
删除后再次查看私有仓库中的镜像是否还存在:

curl http://172.16.1.30:5000/v2/_catalog
{“repositories”:[]}
1
2
(4) 从私有仓库中拉取镜像
docker pull 172.16.1.30:5000/nginx #使用pull命令进行拉取
1
如果要在其它机器拉取镜像的话,记得修改/usr/lib/systemd/system/docker.service文件。

部署Harbor
docker 官方提供的私有仓库 registry,用起来虽然简单 ,但在管理的功能上存在不足。 Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,harbor使用的是官方的docker registry(v2命名是distribution)服务去完成。harbor在docker distribution的基础上增加了一些安全、访问控制、管理的功能以满足企业对于镜像仓库的需求。

(1)安装compose
yum install docker-compose

部署harbor依赖这个东西

(2)下载harbor安装包并进行解压
下载地址:https://github.com/goharbor/harbor/releases

wget https://github.com/goharbor/harbor/releases/download/v1.10.2/harbor-offline-installer-v1.10.2.tgz
tar zxf harbor-offline-installer-v1.10.2.tgz -C /usr/local/
cd /usr/local/harbor/
1
2
3
(3) 编写harbor配置文件
配置参数位于安装目录的 harbor.yml 文件中。

必须参数

hostname: 目标主机的主机名。它应该是目标计算机的 IP 地址或完全限定的域名(FQDN),例如:172.16.1.30 或 reg.yourdomain.com。不要使用 localhost 或 127.0.0.1 作为主机名。
data_volume: Harbor 数据的存储位置
harbor_admin_password: 管理员的初始密码。此密码仅在 Harbor 首次启动时生效。请注意,默认用户 名/密码为 admin / Harbor12345
database: 与本地数据库相关的配置。
password: 默认数据库密码为 root123,应该改为一个安全的生产环境密码。
可选参数

http:
port: 你的 http 的端口号。

https: 启用 https 协议。如果启用了秘钥,则必须设置为 https。
port: https 的端口号。
certificate: SSL 证书的路径,仅在协议设置为 https 时应用。
private_key: SSL 密钥的路径,仅在协议设置为 https 时应用。

(4)启动Harbor
./install.sh
1
由于 Harbor 也是由一组容器构成,包括 redis、nginx等,安装过程会下载很多镜像,并启动很多容器,等脚本执行完后,看到提示这面这样就说明安装完毕了。

✔ —-Harbor has been installed and started successfully.—-

Now you should be able to visit the admin portal at http://172.16.1.30.
For more details, please visit https://github.com/goharbor/harbor .
1
2
3
4
(5) 注册和创建仓库
访问 http://172.16.1.30 就看到登录界面了。(用户admin,密码:Harbor12345(在harbor配置文件中可以查看到)

新建项目

在项目中创建一个新的公开镜像仓库 testproject。

点击推送镜像,可以看到推送到此仓库的格式。

 

(6)推送镜像
我们使用此格式,从另一台机器推送镜像。

docker tag SOURCE_IMAGE[:TAG] 172.16.1.30/testproject/IMAGE[:TAG]
docker push 172.16.1.30/testproject/IMAGE[:TAG]

在本地终端上连接harbor:
docker login 172.16.1.30:80
1
因为默认走https所以不指定端口将默认443

将需要上传到harbor私有仓库的镜像进行push:
docker tag nginx:latest 172.16.1.30:80/testproject/nginx:latest #更改标签
docker push 172.16.1.30:80/testproject/nginx:latest #push到刚才在网页上创建的项目
1
2
push推送的时候,也要记得加上端口。

然后在 Harbor web 控制台中已经可以看到刚才上传的镜像了,私有本地仓库就创建好了,后期我们可以使用 admin 账户来管理。

并且在生产环境中,建议加入证书,使用 https 协议通信,为了防止单点故障,可以使用分布式存储如 Ceph 作为后端存储。

迁移Docker仓库
采用registry部署的迁移方法

参考文档:https://www.jianshu.com/p/0c4b55675b99

大概流程就是导出registry镜像,在新的机器导入,然后把镜像文件放到相同的目录下,启动就可以了。

采用Harbor部署的迁移方法

编写脚本把镜像重新上传到Harbor

切换镜像仓库

不管以上如何实现,但切换镜像仓库的方法大致相同。在测试没问题的前提下,执行以下流程:

1、如果选择registry,则只需要把原来registry仓库的镜像文件,直接复制到新的registry仓库的目录下。如过选用Harbor,则需要编写脚本把原来镜像仓库的镜像同步过去。

2、编写shell脚本通过ansible更新所有要与Registry交互的Docker主机。如果选择Insecure Registry需要更改所有 Docker主机的Docker Daemon。如果选择Secure Registry则是颁发证书。

3、通知新镜像仓库的地址,避免push、pull失败。

sudo docker run -d -p 5000:5000 –restart=always –name registry -v /app_data/registry/:/var/lib/registry registry:2

参考文档:
https://juejin.im/post/5cecd8f3f265da1bae38daa7
https://blog.51cto.com/13972012/2446357
https://monster0303.github.io/posts/3abcceaa/#%E4%BA%8C%E3%80%81-Harbor

支持HTTPS的私有DOCKER Registry :
https://blog.csdn.net/xcjing/article/details/70238273
https://www.cnblogs.com/xcloudbiz/articles/5526262.html
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/cyt0906/article/details/105536119

Linux下OpenJDK与OracleJDK1.8共存与切换(安装JDK1.8)_jdk1.8对应openjdk版本-CSDN博客

mikel阅读(274)

来源: Linux下OpenJDK与OracleJDK1.8共存与切换(安装JDK1.8)_jdk1.8对应openjdk版本-CSDN博客

首先我的系统自带了OpenJDK 11.0.12;我使用Java -version命令查看的

……PC:~/桌面$ java -version
openjdk version “11.0.12” 2021-07-20
OpenJDK Runtime Environment (build 11.0.12+7-post-Debian-2)
OpenJDK 64-Bit Server VM (build 11.0.12+7-post-Debian-2, mixed mode, sharing)
现在我们需要安装OracleJDK1.8,

安装前需要先确认我们的机器架构;使用命令:uname -m

……PC:~/桌面$ uname -m

x86_64
从上面的回复我们可以看到我们的架构为64位的。所以我们需要到官网上下载相关的软件包。

官网地址:Java Downloads | Oracle 中国

我选择的是:x64 Compressed Archive(请根据实际情况选择)

然后使用解压缩命令解压到:/usr/java/ 下(当然,每个人的安装路径不一样,视情况而选择),如果 /usr/ 下没有java目录,请使用命令:mkdir 来创建

解压命令:

tar -zxvf ……(省略号是你下载的包名)
解压到/usr/java/下

接下来需要配置环境变量了,使用一下命令打开文件:

sudo vi /etc/profile
在文件末尾插入命令:

export JAVA_HOME=/usr/java/jdk1.8.0_431
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib:$CLASSPATH
export JAVA_PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin
export PATH=$PATH:${JAVA_PATH}
当然配置完成后需要使用命令:source /etc/profile 来重新加载配置。你以为就完了,哈哈,当你使用 java -version 的时候会发现系统默认的还是OpenJDK。所以下面是配置的关键。

在使用这之前请允许我啰嗦一下命令的说明:update-alternatives 是一个在 Linux 系统(尤其是 Debian 系列,包括 Ubuntu)上用于管理多个相同功能的软件版本的命令。它让你可以在系统中安装多个版本的同一个程序(例如多个版本的JAVA 或者GCC),并通过 优先级 和 符号链接 来控制默认使用哪个版本。

所以我们需要使用次命令做一下配置:

sudo update-alternatives –install /usr/bin/java java /usr/java/jdk1.8.0_431/bin/java 100

sudo update-alternatives –install /usr/bin/javac javac /usr/java/jdk1.8.0_431/bin/javac 100

配置完成后就到了切换版本的时候了,输入以下命令:

sudo update-alternatives –config java
你会看到:

……-PC:/usr/lib/jvm$ sudo update-alternatives –config java
有 2 个候选项可用于替换 java (提供 /usr/bin/java)。

选择 路径 优先级 状态
————————————————————
* 0 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1111 自动模式
1 /usr/java/jdk1.8.0_431/bin/java 100 手动模式
2 /usr/lib/jvm/java-11-openjdk-amd64/bin/java 1111 手动模式

要维持当前值[*]请按<回车键>,或者键入选择的编号:1
根据需要去选择相应选择下的数据即可切换。

注意:你目前只切换了java的,还需要再切换一次javaC的

sudo update-alternatives –config javac
至此配置完成,请记住以后需要切换的时候请使用命令:sudo update-alternatives –config *** 来进行版本切换。

切换完成后可以使用:java -version 与 javac -version 来看看是否切换过来。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/wangyuan052/article/details/144268777

Exception in thread “main“ java.lang.NoClassDefFoundError: com/google/common/collect/ImmutableSet_exception in thread "main" noclassdeffounderror: i-CSDN博客

mikel阅读(192)

来源: Exception in thread “main“ java.lang.NoClassDefFoundError: com/google/common/collect/ImmutableSet_exception in thread “main” noclassdeffounderror: i-CSDN博客

项目除了jdk,无法引入其他依赖,
报这个错误,

Exception in thread “main” java.lang.NoClassDefFoundError: com/google/common/collect/ImmutableSet
at org.apache.maven.extension.internal.CoreExtensionEntry.<init>(CoreExtensionEntr
y.java:53)
at org.apache.maven.extension.internal.CoreExtensionEntry.discoverFrom(CoreExtensi
onEntry.java:107)

尝试找下这个pom,然后引入,就好了。。。。
删除了 发现项目依然是正常的。不知道为什么。。。。。。
猜测可能这个包丢失或者错误

<dependency>
<groupId>com.google.collect</groupId>
<artifactId>com.springsource.com.google.common.collect</artifactId>
<version>0.8.0.20080820</version>
</depe
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_43094085/article/details/115083661

这是我见过最全的支付系统!一套适合互联网企业使用的开源支付系统 - Java陈序员 - 博客园

mikel阅读(532)

来源: 这是我见过最全的支付系统!一套适合互联网企业使用的开源支付系统 – Java陈序员 – 博客园

项目介绍

Jeepay —— 计全支付,一套适合互联网企业使用的开源支付系统,支持多渠道服务商和普通商户模式。

已对接微信支付,支付宝,云闪付官方接口,支持聚合码支付

系统架构:

Jeepay 使用 Spring Boot 和 Ant Design Vue 开发,集成 Spring Security 实现权限管理功能,是一套非常实用的 web 开发框架!

系统截图

运营平台

主页

商户管理

服务商管理

订单管理

支付配置

系统管理

商户系统

主页

商户中心

订单中心

分账管理

快速上手

环境准备:

  • Java 8
  • Redis
  • MySQL
  • MQ:ActiveMQ 或 RabbitMQ 或 RocketMQ
  • Node.js

后端服务

1、拉取代码

git clone https://github.com/jeequan/jeepay.git

代码目录结构:

jeepay
├── conf -- 存放系统部署使用的.yml文件
├── docker -- 存放docker相关文件
└── docs -- 存放项目相关文档说明
     ├── intsll -- 项目部署shell脚本
     ├── script -- 项目启动shell脚本
     └── sql -- 初始化sql文件
└── jeepay-components -- 公共组件目录
     ├── jeepay-components-mq -- mq组件
     └── jeepay-components-oss -- oss组件
├── jeepay-core -- 核心依赖包
├── jeepay-manager -- 运营平台服务端[9217]
├── jeepay-merchant -- 商户系统服务端[9218]
├── jeepay-payment -- 支付网关[9216]
├── jeepay-service -- 业务层代码
└── jeepay-z-codegen -- mybatis代码生成

2、初始化数据库

在 MySQL 5.7 下创建数据库 jeepaydb 并执行项目下 docs/sql/init.sql,确保所有语句执行成功。

3、将代码以 Maven 的形式导入到 IDEA

4、修改配置文件中的 MySQL、Redis、MQ 等中间件的连接地址、用户名、密码等配置

5、进入到不同的业务模块代码中,执行主启动类,启动后端服务

前端服务

1、拉取代码

git clone https://github.com/jeequan/jeepay-ui.git

代码目录结构:

jeepay-ui
├── jeepay-ui-cashier -- 聚合收银台项目
├── jeepay-ui-manager -- 运营平台web管理端
└── jeepay-ui-merchant -- 商户系统web管理端

2、以 jeepay-ui-manager 为例,进入到目录 jeepay-ui-manager 下,安装依赖

npm install

3、将文件 .env 中的 VUE_APP_API_BASE_URL 配置改为后端服务地址

4、启动项目

npm run serve

5、浏览器访问

http://localhost:8000

jeepay-ui-cashier、jeepay-ui-merchant 重复执行上述操作即可,只是启动后的访问端口不一样。

最后

推荐的开源项目已经收录到 GitHub 项目,欢迎 Star

https://github.com/chenyl8848/great-open-source-project

或者访问网站,进行在线浏览:

https://chencoding.top:8090/#/

大家的点赞、收藏和评论都是对作者的支持,如文章对你有帮助还请点赞转发支持下,谢谢!

Java对接JeePay支付、转账实现以及回调函数-CSDN博客

mikel阅读(338)

来源: Java对接JeePay支付、转账实现以及回调函数-CSDN博客

最近公司对接了第三方支付平台JeePay,看到网上文章比较少,给大家发一篇对接微信支付的吧,支付宝也一样,更换里面的参数即可,官方文档地址:系统介绍 – 计全文档,具体的服务需要大家去搭建,并创建里面的应用,我这里只给大家展示出了代码,具体的服务搭建和创建应用大家去看下官网,下面开始实现我们的代码。

首先我们引入两个pom依赖。

<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>

<dependency>
<groupId>com.jeequan</groupId>
<artifactId>jeepay-sdk-java</artifactId>
<version>1.5.0</version>
</dependency>
下面我们需要在配置文件里面加上相关配置,我们可以新建一个 application-jee-pay.properties。

#这个是回调的地址,一定要能访问到我们回调的IP上面,自定义
domain-name=http://120.29.172.100:8500
#这个固定写死
api-base=https://pay.vichel.com.cn/
#商户私钥
api-key=商户的私钥,如何获取看下面截图
#商户号,看下面截图
mch-no=M1670111111
#应用ID
app-id=1111b3f0e4b05e7111111111
#转账回调地址
wx-withdrawal=${domain-name}/wxPay/result/withdrawalCallback
#支付回调地址
wx-recharge=${domain-name}/wxPay/result/wxRechargeCallback
打开jeePay运营平台,点击我们要对接的应用,点击修改,里面有应用ID和商户号,我们直接复制就可以了。随机生成出一个私钥,点击保存就可以了

 

 

基础配置类

package com.mart.web.pay;

import com.jeequan.jeepay.Jeepay;
import com.jeequan.jeepay.JeepayClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

/**
* JeePay配置相关
*/
@Configuration
//这里指定我们要读取的配置文件
@PropertySource(“classpath:application-jee-pay.properties”)
public class JeePayClientConfig {

@Autowired
private Environment config;

@Bean
public JeepayClient jeePayConfig(){
//地址
Jeepay.setApiBase(config.getProperty(“api-base”));
//私钥
Jeepay.apiKey = config.getProperty(“api-key”);
//商户号
Jeepay.mchNo = config.getProperty(“mch-no”);
//应用ID
Jeepay.appId = config.getProperty(“app-id”);
JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, Jeepay.getApiBase());
return jeepayClient;
}
}
下面是一些支付转账操作的模块,我给大家出了一个示例,里面的参数需要结合业务需求做相应的调整,我下面只要使用的是微信小程序支付和微信零钱的转账功能。商户可以转账到用户微信的零钱。

package com.mart.web.pay;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.jeequan.jeepay.Jeepay;
import com.jeequan.jeepay.JeepayClient;
import com.jeequan.jeepay.exception.JeepayException;
import com.jeequan.jeepay.model.PayOrderCreateReqModel;
import com.jeequan.jeepay.model.TransferOrderCreateReqModel;
import com.jeequan.jeepay.model.TransferOrderCreateResModel;
import com.jeequan.jeepay.request.PayOrderCreateRequest;
import com.jeequan.jeepay.request.TransferOrderCreateRequest;
import com.jeequan.jeepay.response.PayOrderCreateResponse;
import com.jeequan.jeepay.response.TransferOrderCreateResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

/**
*
* 支付转账核心操作功能模块
*/
@Slf4j
@Service
public class JeePayService {

@Autowired
private JeepayClient jeepayClient;

@Autowired
private Environment config;

/**
* 转账操作,转账到微信零钱(特别注意,下面转账的时候Jeepay一定要使用微信的主商户进行转账,如果使用的是子商户就会出现 {“code”:9999,”msg”:”微信子商户暂不支持转账业务”})
* @param openId 用户的openId
* @param amount 转账金额
* @param numberOn 转账订单号
* @return
*/
public Boolean withdrawal(String openId, BigDecimal amount,String numberOn) {
// 构建请求数据
TransferOrderCreateRequest request = new TransferOrderCreateRequest();
TransferOrderCreateReqModel model = new TransferOrderCreateReqModel();
// 商户号
model.setMchNo(Jeepay.mchNo);
// 应用ID
model.setAppId(Jeepay.appId);
// 商户订单号
model.setMchOrderNo(numberOn);
// 支付方式
model.setIfCode(“wxpay”);
// 入账方式
model.setEntryType(“WX_CASH”);
// 我们传入的是元,这里需要吧金额转成单位分
amount = amount.multiply(new BigDecimal(“100”));
model.setAmount(amount.longValue());
// 币种,目前只支持cny
model.setCurrency(“CNY”);
model.setAccountNo(openId);
// 转账备注
model.setTransferDesc(“测试转账操作”);
// 异步通知地址
model.setNotifyUrl(config.getProperty(“wx-withdrawal”));
// 商户扩展参数,回调时原样返回
model.setExtParam(numberOn);
request.setBizModel(model);
log.info(“jeepay下单参数处理完毕,参数:[{}]”, JSON.toJSONString(request));
try {
TransferOrderCreateResponse response = jeepayClient.execute(request);
// 下单成功
if (response.isSuccess(Jeepay.apiKey)) {
//转账成功
log.warn(“转账成功:{}”);
return true;
}
} catch (JeepayException e) {
log.error(e.getMessage());
}
log.warn(“转账失败:{}”);
return false;
}

/**
* 支付操作,我下面使用的是微信支付的
* @param Subject 商品标题
* @param body 描述
* @param openId 微信的OpenId或者是支付宝的用户ID
* @param amount 支付的金额 CNY
* @param numberOn 平台自己生成的随机订单号
* @return
*/
public String scanPay(String Subject,String body,String openId, BigDecimal amount,String numberOn) {
// 构建请求数据
PayOrderCreateRequest request = new PayOrderCreateRequest();
PayOrderCreateReqModel model = new PayOrderCreateReqModel();
// 商户号
model.setMchNo(Jeepay.mchNo);
// 应用ID
model.setAppId(Jeepay.appId);
// 商户订单号
model.setMchOrderNo(numberOn);
// 支付方式
model.setWayCode(“WX_JSAPI”);
amount = amount.multiply(new BigDecimal(“100”));
// 金额,单位分
model.setAmount(amount.longValue());
// 币种,目前只支持cny
model.setCurrency(“CNY”);
// 发起支付请求客户端的IP地址
model.setClientIp(config.getProperty(“ip-address”));
// 商品标题
model.setSubject(Subject);
// 商品描述
model.setBody(body);
// 异步通知地址
model.setNotifyUrl(config.getProperty(“wx-recharge”));
// 渠道扩展参数 传OpenId
model.setChannelExtra(“{\”openid\”: \””+openId+”\”}”);
// 商户扩展参数,回调时原样返回
model.setExtParam(numberOn);
request.setBizModel(model);
log.info(“jeepay下单参数处理完毕,参数:[{}]”, JSON.toJSONString(request));
try {
PayOrderCreateResponse response = jeepayClient.execute(request);
// 下单成功
if (response.isSuccess(Jeepay.apiKey)) {
String result = response.getData().getString(“payData”);
return result;
}
} catch (JeepayException e) {
log.error(e.getMessage());
}
return null;
}

}
回调函数

package com.mart.web.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.jeequan.jeepay.util.JeepayKit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;

/**
* 回调接收
* @author HayDen
* @date 2022-11-15
*/
@RestController
@RequestMapping(“/wxPay/result”)
@SentinelResource(value = “CallbackController”)
public class CallbackController
{

@Autowired
private Environment config;

/**
* 转账回调
* @return
*/
@PostMapping(“/withdrawalCallback”)
public String withdrawalCallback(HttpServletRequest req) throws Exception
{
String result = “failure”;
try {
Map<String, Object> map = getParamsMap(req);
//获取私钥
String apikey = config.getProperty(“api-key”);
//验签
if (chackSgin(map, apikey)) {
return result;
}
//提现成功
//获取订单号
String orderNumber = map.get(“mchOrderNo”).toString();
//提现金额
BigDecimal amount = new BigDecimal(map.get(“amount”).toString()).divide(new BigDecimal(“100”),4,BigDecimal.ROUND_HALF_UP);

}
result = “success”;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}

/**
* 微信支付回调
* @return
*/
@PostMapping(“/wxRechargeCallback”)
public String wxRechargeCallback(HttpServletRequest req) throws Exception
{
String result = “failure”;
try {
Map<String, Object> map = getParamsMap(req);
//获取私钥
String apikey = config.getProperty(“api-key”);
//验签
if (chackSgin(map, apikey)) {
return result;
}
//订单号
String orderNumber = map.get(“mchOrderNo”).toString();
//支付金额
BigDecimal amount = new BigDecimal(map.get(“amount”).toString()).divide(new BigDecimal(“100”),4,BigDecimal.ROUND_HALF_UP);
//返回成功
result = “success”;
} catch (Exception e) {
e.printStackTrace();
}
return result;
}

/**
* 回调验签
* @param map
* @param apikey
* @return
*/
private Boolean chackSgin(Map<String, Object> map, String apikey) {
Object sign = map.remove(“sign”);
String reSign = JeepayKit.getSign(map, apikey);

if (!Objects.equals(reSign, sign)) {
return true;
}
return false;
}

private Map<String, Object> getParamsMap(HttpServletRequest req) {
Map<String, String[]> requestMap = req.getParameterMap();
Map<String, Object> paramsMap = new HashMap<>();
requestMap.forEach((key, values) -> {
String strs = “”;
for (String value : values) {
strs = strs + value;
}
paramsMap.put(key, strs);
});
return paramsMap;
}

}
好了,到这里基本就结束了,如果大家有什么疑问可以给我留言,看到后一定会第一时间回复的,有建议那是最好的,欢迎大家提出来,如果合理我一定第一时间优化代码。

如果这篇文章在你一筹莫展的时候帮助到了你,可以请作者吃个棒棒糖🙂,如果有啥疑问或者需要完善的地方欢迎大家在下面留言或者私信作者优化改进。

————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_38935605/article/details/128639513

【Docker 模块】使用手册 - Linux面板 - 宝塔面板论坛

mikel阅读(276)

来源: 【Docker 模块】使用手册 – Linux面板 – 宝塔面板论坛

什么是 Docker?
Docker 是一个用于开发、发布和运行应用程序的开放平台。Docker 使您能够将应用程序与基础架构分离,以便您可以快速交付软件。使用 Docker,您可以像管理应用程序一样管理基础设施。通过利用 Docker 快速交付、测试和部署代码的方法,您可以显着减少编写代码和在生产环境中运行之间的延迟。

什么是 Dockerfile?
Docker 可以通过读取 Dockerfile 中的指令自动构建镜像,Dockerfile 是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。

什么是Docker Compose?
Docker Compose 是一种用于帮助定义和共享多容器应用程序的工具。使用 Compose,我们可以创建一个 YAML 文件来定义服务,并且使用一个命令或操作,可以启动所有内容或将其全部关闭。

安装:
测试版本的面板:在左侧菜单 “Docker” 界面安装
正式版本的面板:需要先在”面板设置”中的 面板菜单栏隐藏 将 “Docker” 打开,按F5刷新界面,在左侧菜单 “Docker” 界面安装
安装后界面如下:点击图片可看大图

产品功能列表与详解:
一、镜像
Docker 镜像是容器的基础。镜像没有状态,它永远不会改变。
二、容器
容器是Docker 镜像的运行实例。
三、Compose
Compose是一个使用 Docker 定义和运行复杂应用程序的工具。
四、Compose模板
Compose模板 定义构成Compose 项目的yaml文件。
五、网络
容器网络是由 Docker 为容器创造的虚拟环境的一部分,能让容器从宿主机的网络环境中独立出来。
六、存储卷
存储卷是一个或多个容器中的一个特别指定的目录,存储卷旨在数据持久化,独立于容器的生命周期。因此删除容器时,Docker 永远不会自动删除卷。
七、仓库
仓库是用来存放Docker镜像。
八、设置
Docker 服务的状态。

一、镜像
镜像界面预览:点击图片可看大图

  • 从仓库拉取:从Docker官方库、第三方库拉取镜像
    例:
    从docker官方仓库拉取镜像:mySQL:5.7
    mySQL:5.7 解释; mySQL是镜像的名称,5.7是标签,采用英文“:”分隔
    从第三方仓库拉取镜像:registry.cn-shenzhen.aliyuncs.com/star7th/showdoc
    解释:第三方仓库/用户/镜像的名称,没有标签默认为:latest
  • 导入镜像:从本服务器导入镜像
  • 构建镜像:通过Dockerfile文件构建镜像
    查看构建镜像例子:点我直达 Dockerfile 构建镜像例子
  • 推送:将镜像推送到指定的仓库
    注意:需要先设置你自己仓库,才能进行推送
    标签填写格式:镜像名:标签,镜像名要与仓库名相同才能正常推送。如图:
  • 导出:导出镜像到本服务器
  • 删除:删除镜像,删除镜像后无法恢复请注意相关的备份
  • ID:镜像的sha256指纹信息,也是镜像的ID
  • 镜像名:镜像的名称和标签,Docker镜像唯一的标识,如果”构建/导入镜像”有相同”镜像名称:标签”将会覆盖旧的镜像
  • 大小:镜像的大小
  • 创建时间:镜像创建的时间

二、容器
容器界面预览:点击图片可看大图

  • 添加容器:通过镜像创建容器、通过Compose模板创建容器编排
    例:我们以“mysql:5.7”镜像为例创建容器
    在命令行中一般这样创建容器:

    1. docker run -d –name mysql_test -p 3361:3306 -v /docker/mysql_data/:/var/lib/mysql/ -e MYSQL_ROOT_PASSWORD=my-passwd mysql:5.7

    复制代码

    命令解释:

    run:使用mysql:5.7镜像创建容器
    -d:容器后台运行
    –name:设置容器名为:mysql_test
    -p 3361:3306:映射宿主机3361端口到容器3306端口
    -v /docker/mysql_data/:/var/lib/mysql/:映射宿主机/docker/mysql_data/ 目录到容器/var/lib/mysql/ 目录,用于数据的持久化(宿主机目录如果不存在会自动创建)
    -e:MYSQL_ROOT_PASSWORD=my-passwd:指定环境变量,并设置MySQL root用户密码为 my-passwd,在mysql镜像中如果不指定这条环境变量将无法运行容器

    在面板中可以这样创建容器:

    界面解释:

    容器:对应 –name,填写容器名称
    镜像:选择mysql:5.7 创建容器
    暴露端口:对应 -p 3361:3306 ,注意:填写完成后要点+号图标,才会生效
    启动命令:可留空
    容器停止后自动删除容器:勾上后容器停止将自动删除这个容器,请注意数据的备份删除后无法恢复
    限制CPU、内存:限制容器使用的资源
    挂载卷:对应 -v /docker/mysql_data/:/var/lib/mysql/ ,也可以在 “存储卷界面” 添加并且选择存储卷名做数据的持久化,注意:填写完成后要点+号图标,才会生效
    标签:用于标识容器的用处,可留空
    环境变量:对应 -e MYSQL_ROOT_PASSWORD=my-passwd
    重启规则:
            不重启:重启服务器/重启Docker服务不会启动容器
            关闭时重启:重启服务器/重启Docker服务自动启动容器
            错误时重启(默认5次):容器出错时将重启,超过5次后不再重启,请查看容器日志进行排查错误
    面板默认已经使用 run -d 后台运行
    公共镜像的容器如何使用,请查看其官方的资料
  • 实时监控:实时监控容器的 CPU、内存、硬盘IO、网络IO 资源的使用
  • 终端:终端模式进入容器
    注意:需要容器是启动状态才能进入容器,否则将退出到宿主机
  • 目录:通过文件管理器进入容器目录
  • 日志:查看容器的运行日志
  • 删除:删除容器,删除容器后数据无法恢复请注意数据的备份,如果容器有做数据持久化,存储卷是不会被删除
  • 容器名:可以点击进去使用“容器配置”,“生成镜像”功能
    (1) 容器配置:点击“容器名”查看容器配置,可点击“…”可以查看更多配置

    (2) 生成镜像:将当前容器生成镜像、生成镜像后导出压缩包
  • 状态:可以选择 启动、停止、暂停、取消暂停、重启、重载容器
  • 镜像:容器使用的镜像
  • IP:容器的IP地址
  • CPU使用率:容器的CPU使用率
  • 端口 (主机–>容器):宿主机映射到容器的端口,左边为宿主机
  • 启动时间:第一次启动容器的时间
  • 批量操作:选择1个及以上对容器进行批量操作:启动、停止、暂停、取消暂停、重启、重载、删除容器

三、Compose
Compose界面预览:点击图片可看大图

  • 添加Compose项目:从Compose模板中建立容器
    查看Docker Compose例子:点我直达 Docker Compose例子
  • 容器列表:
    (1) Compose操作:可选择 启动、停止、暂停、取消暂停、重启整个Compose项目
    (2) 状态:可以选择 启动、停止、暂停、取消暂停、重启、重载容器
    (3) 终端:终端模式进入容器
    (4) 目录:通过文件管理器进入容器目录
    (5) 日志:查看容器的运行日志
    (6) 删除:删除单个容器,删除后数据无法恢复请注意数据的备份,如果容器有做数据持久化,存储卷是不会被删除
  • 删除:删除整个Compose项目的容器,将删除项目内所有的容器,删除后数据无法恢复请注意数据的备份,如果容器有做数据持久化,存储卷是不会被删除
  • Compose项目名称:项目名称
  • 容器数量:项目内的容器数量
  • 启动时间:第一次启动容器的时间
  • 描述:用于描述此项目的用处
  • 批量操作:选择1个及以上对项目进行批量操作:删除Comose项目

四、Compose模板
Compose模板界面预览:点击图片可看大图

  • 添加:添加Compose模板、搜索本地模板
  • 编辑:编辑Compose模板
  • 拉取镜像:根据Compose模板的配置拉取镜像
  • 删除:删除Compose模板,删除后无法恢复请注意模板内容的备份
  • 模板名:模板的名称
  • 路径:模板的存储路径
  • 描述:用于描述此模板的用处
  • 批量操作:选择1个及以上对模板进行批量操作:删除Compose模板

五、网络
网络界面预览:点击图片可看大图

  • 添加网络:添加新的网络到Docker
  • 删除:删除容器网络
  • 网络名:网络的名称
  • 显示:网络驱动类型
    (1) none:none模式不使用网络
    (2) host:host模式直接使用宿主机的网络
    (3) bridge:bridge模式与宿主机桥接,容器默认使用的网络
  • 网络号:容器网络的IP范围
  • 网关:容器网络子网的网关IP地址
  • 标签:用于标识容器网络的用处
  • 创建时间:创建容器网络的时间
  • 批量操作:选择1个及以上对网络进行批量操作:删除网络

六、存储卷
存储卷界面预览:点击图片可看大图

  • 添加存储卷:添加新的存储卷
    可先添加存储卷,再到 添加容器界面 选择存储卷
  • 删除:删除存储卷,删除存储卷后数据无法恢复请注意数据的备份
  • 存储卷:容器数据持久化的存储卷名称
  • 挂载点:存储在宿主机的路径
  • 所属容器:目前那个容器在使用这个存储卷
  • 设备:存储卷所存储的设备
  • 创建时间:创建存储卷的时间
  • 标签:用于标识存储卷的用处
  • 批量操作:选择1个及以上对存储卷进行批量操作:删除存储卷,删除存储卷后数据无法恢复请注意数据的备份

七、仓库
仓库界面预览:点击图片可看大图

  • 添加仓库:添加docker官方库、第三方仓库
    docker官方库添加如下:

    添加仓库可参考:https://www.bt.cn/bbs/thread-80965-1-1.html
  • 编辑:编辑仓库
  • 删除:删除仓库
  • URL:镜像仓库链接
  • 用户:登录仓库用户
  • 仓库名:仓库名称/镜像名称
  • 描述:用于标识仓库的用处
  • 批量操作:选择1个及以上对仓库进行批量操作:删除仓库

八、设置
设置界面预览:点击图片可看大图

  • Docker服务:当前Docker服务状态,可开启、重启、停止。启动Docker服务后,容器是否启动根据重启规则来启动
  • 容器监控:开启、关闭。关闭后CPU使用率将不再监控
  • 监控天数:设置容器页面监控保存天数。默认为30天
  • 加速URL:设置加速URL,设置加速后需要手动重启Docker。Docker默认仓库在国内下载较慢,建议使用加速URL
    常用的国内镜像仓库:
    科大:

    1. https://docker.mirrors.ustc.edu.cn/

    复制代码

    网易:

    1. https://hub-mirror.c.163.com/

    复制代码

    阿里云: (需要注册登录获取专属镜像加速器地址) https://cr.console.aliyun.com/

【Docker 模块】使用手册—2022/7/5 第一版
本使用手册编写时使用的环境:
操作系统:CentOS7.9.2009 x86_64,内核3.10.0
面板版本:7.9.2(当前最新正式版)
面板Python环境:3.7.9

如何优雅的一键升级宝塔面板多个Docker容器。 | 老张博客

mikel阅读(303)

来源: 如何优雅的一键升级宝塔面板多个Docker容器。 | 老张博客

目前老张在自己的宝塔面板Docker里部署了好几个项目,Alist、ChatGPT-web、Trilium、思源笔记这四个常用的项目。对于这几个项目里,更新频率最高的就是思源笔记了。我在《宝塔面板下利用Docker部署思源笔记!》等几篇关于宝塔面板Docker的文章里也有说法,如果官方项目版本更新之后,需要将本地的容器和镜像删除之后,重新拉取最新的镜像,然后再新建容器进行重新配置,这样真的很麻烦。

后来经过向度娘请教之后,发现很多人在群晖里升级Docker容器用了watchtower这个项目来自动升级Docker里的项目容器,我便把他搬到了宝塔里来了。

watchtower本身也是Docker里一个项目,但是我们这次使用他是在宝塔面板的”计划任务”里。打开宝塔的“计划任务”新建一个“shell脚本”,执行周期可以设置成每周执行一次。而脚本内容可以按需复制以下代码:

1.运行一次,更新所有的容器,并清除旧的容器 。

docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --cleanup --run-once

2.运行一次,更新所有的容器,并清除旧的容器,并删除watchtower容器。

docker run --rm --name watchtower -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --cleanup --run-once

3.指定容器更新,如无需自动更新所有稳定运行的容器,可以配置仅更新指定容器,只需要在命令后加上容器名.例如只更新nginx和redis。

docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower --cleanup --run-once nginx redis

有了这个神项目,就不再需要手动更新docker容器了。

修改『Visual Studio Code(VS Code)』插件默认安装路径的方法_vscode修改插件安装路径-CSDN博客

mikel阅读(915)

来源: 修改『Visual Studio Code(VS Code)』插件默认安装路径的方法_vscode修改插件安装路径-CSDN博客

修改『Visual Studio Code(VS Code)』插件默认安装路径的方法
前言
方法一:修改快捷方式目标路径(★★☆)
1. 确保『code』快捷命令的可用
2. 移动插件文件到自定义目录
3. 将自定义文件夹加入VS Code扩展目录
4. 修改快捷方式目标路径
方法二:默认路径下插件文件变为快捷方式(★☆☆)
总结
前言
作者希望将『Visual Studio Code(以下简称为“VS Code”)』的插件安装在数据盘(D盘),用于统一管理,因此需要修改VS Code插件安装路径。
VS Code插件默认的安装位置为:C:\Users\{个人用户名}\.vscode\extensions。
本文通过2种方式解决问题,作者用(★/☆)标注操作的难易程度,具体如下。

方法一:修改快捷方式目标路径(★★☆)
1. 确保『code』快捷命令的可用
按键 Win + R 进入运行。

输入『cmd』后,按确定进入命令提示符(CMD)。
在命令提示符中输入『code -v』确定『code』快捷命令的可用性。

『code』快捷命令的如不可用,可选择如下操作:
a. 重新安装,并在安装时勾选『添加到PATH(重启后生效)』。
b. VS Code中执行 『Ctrl + Shift + P』,打开命令面板,键入『shell』,选择『Shell命令: 在PATH中安装”code”命令』。
c. 若不想安装,则可选方法二。
2. 移动插件文件到自定义目录
插件默认安装路径在C:\Users\{个人用户名}\.vscode\extensions目录下,找到『extensions』文件夹,右键→剪切。

作者自定义的路径是D:\Buffer\VSCode\extensions,找到D:\Buffer\VSCode,右键→粘贴,将插件文件夹『extensions』移动到此处。

3. 将自定义文件夹加入VS Code扩展目录
按照第1步方式,打开命令提示符(CMD)。
输入『code –extensions-dir “{自定义路径}”』→回车。
注:英文双引号里面是你自己定义的文件夹路径,根据自身情况修改。
code –extensions-dir “D:\Buffer\VSCode\extensions”
1
确认是否修改成功,打开VS Code,点击左侧边栏→扩展,会显示出已安装插件;若未出现已安装插件,则重新执行第2步。
4. 修改快捷方式目标路径
找到VS Code桌面快捷方式,右键→属性→快捷方式→目标,在目标的原目录后面添加『 –extensions-dir “D:\Buffer\VSCode\extensions”』。
注:英文双引号里面是你自己定义的文件夹路径,根据自身情况修改;可以直接复制『』中的内容,(空格)–extensions-dir(空格)”{自定义路径}”。

继按确定,完成修改。点击VS Code桌面快捷方式,点击左侧边栏→扩展,会显示出已安装插件;若未出现已安装插件,则重新执行第2步。
方法二:默认路径下插件文件变为快捷方式(★☆☆)
本方法将C盘插件默认安装路径下的extensions文件夹移动(剪切)到自定义路径,然后将C盘下的extensions文件变为快捷方式。

移动(剪切)插件文件到自定义目录。插件默认安装路径在C:\Users\{个人用户名}\.vscode\extensions目录下,找到『extensions』文件夹,右键→剪切。
注:对文件处理时,必须是剪切。

作者自定义的路径是D:\Buffer\VSCode\extensions,找到D:\Buffer\VSCode,右键→粘贴,将插件文件夹『extensions』移动到此处。

在管理员权限下的命令提示符(CMD)输入以下命令:mklink /D “C:\Users\{个人用户名}\.vscode\extensions” “{自定义路径}”;运行成功后,会提示创建的符号链接。
注:必须在管理员权限下进行;必须使用命令提示符(CMD),不能使用Powershell。
mklink /D “C:\Users\zhang3\.vscode\extensions” “D:\Buffer\VSCode\extensions”
1

3. 打开C:\Users\{个人用户名}\.vscode,extensions变成快捷方式,则修改成功。

 

总结
本文所采用的2种方法,任意选一种都可以解决修改『Visual Studio Code(VS Code)』插件默认安装路径的方法。
请读者根据自身需求,选择所需方式。作者推荐方法二。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_41679061/article/details/132448137

Visual Studio 修改NuGet 包缓存路径 - Earen - 博客园

mikel阅读(393)

来源: Visual Studio 修改NuGet 包缓存路径 – Earen – 博客园

Visual Studio 下载的NuGet包默认会缓存到 C:\Users{Windows用户名}.nuget\packages 下,时间一长就会导致 C盘空间严重不足。

那么怎样去设置,让包缓存文件保存到其他盘呢?

首先我们要找到 Microsoft.VisualStudio.Offline.config 这个文件,它在哪呢? 在 C:\Program Files (x86)\NuGet\Config。

再到文件并用记事本打开 如下:

我们在中间增加如下配置内容:

  <config>
      <add key="globalPackagesFolder" value="D:\Nuget\.nuget\packages" />
  </config>

最终效果如下图:

这样就配置好了,重新打开 Visual Studio 下载 NuGet 包文件后,查看包的引用地址就会发现地址为修改后的地址。

温馨提示:在配置好地址后,可将原来已经缓存的包文件全部拷贝到新的目录中。

Spring AI与DeepSeek实战四:系统API调用 - zlt2000 - 博客园

mikel阅读(383)

来源: Spring AI与DeepSeek实战四:系统API调用 – zlt2000 – 博客园

一、概述

在 AI 应用开发中,工具调用 Tool Calling 是增强大模型能力的核心技术。通过让模型与外部 API 或工具交互,可实现 实时信息检索(如天气查询、新闻获取)、系统操作(如创建任务、发送邮件)等功能。

Spring AI 作为企业级 AI 开发框架,在 1.0.0.M6 版本中进行了重要升级:废弃 Function Calling 引入 Tool Calling 以更贴合行业术语;本文结合 Spring AI 与大模型,演示如何通过 Tool Calling 实现系统 API 调用,同时处理多轮对话中的会话记忆。

关于 Spring AI 与 DeepSeek 的集成,以及 API-KEY 的申请等内容,可参考文章《Spring AI与DeepSeek实战一:快速打造智能对话应用

 

二、函数调用原理

大模型仅负责 决定是否调用工具 和 提供参数,实际执行逻辑由客户端(Spring 应用)实现,确保工具调用的可控性与安全性。

2.1. 工具元数据注入

在发起Chat Request时,将工具描述(name/description)、参数结构(input schema)等元数据封装至请求体,建立大模型的工具调用能力基线。

2.2. 模型决策响应

大模型根据上下文推理生成工具调用指令(tool_calls字段),返回包含选定工具名称及结构化参数的中间响应。

2.3. 服务端路由执行

Spring AI模块解析工具调用指令,通过服务发现机制定位目标工具实例,注入参数并触发同步/异步执行。

2.4. 执行结果标准化

工具返回原始执行结果后,系统进行数据类型校验、异常捕获和JSON序列化处理,生成模型可解析的标准化数据结构。

2.5. 上下文增强推理

将标准化结果作为新增上下文(tool_outputs)回传大模型,触发基于增强上下文的二次推理流程。

2.6. 终端响应生成

模型综合初始请求与工具执行结果,生成最终自然语言响应,完成工具增强的对话闭环。

 

三、核心代码

3.1. 定义工具

创建类 TestTools 并用 @Tool 注解定义 tool 的描述

public static class TestTools {
    @Tool(description = "获取今天日期")
    String getCurrentDateTime() {
        System.out.println("======getCurrentDateTime");
        return LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
    }

    @Tool(description = "获取当前温度")
    String getCurrentTemperature(MyToolReques  toolReques) {
        System.out.println("======getCurrentTemperature: " + toolReques.localName + "__" + toolReques.date);
        return toolReques.date + toolReques.localName + "温度为20摄氏度";
    }

    public record MyToolReques(String localName, String date) {}
}

这里定义了两个方法,并通过注解的 description 参数告诉大模型方法的用途。

record 类型是 Java17 的新特性,可用于替代传统的 POJO 类。

3.2. 创建对话接口

private ChatMemory chatMemory = new InMemoryChatMemory();
private MessageChatMemoryAdvisor messageChatMemoryAdvisor = new MessageChatMemoryAdvisor(chatMemory);

@GetMapping(value = "/chat")
public String chat(@RequestParam String input, String sessionId, HttpServletResponse response) {
    response.setCharacterEncoding("UTF-8");

    return chatClient.prompt().user(input)
            .tools(new TestTools())
            .advisors(messageChatMemoryAdvisor)
            .advisors(spec -> spec
                    .param(MessageChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId))
            .call()
            .content();
}
  • tools 给大模型注册可以调用的方法。
  • MessageChatMemoryAdvisor 实现聊天记忆,InMemoryChatMemory 为 SpringAI 自带的实现(基于内存)。
  • 可以使用 CHAT_MEMORY_CONVERSATION_ID_KEY 参数指定对话ID,不同的会话ID用于隔离记忆。

3.3. 测试

通过后台打印信息可以看到大模型会自动识别,先调用 getCurrentDate 方法获取今天日期,再调用 getCurrentTemperature 方法获取天气。

======getCurrentDate

======getCurrentTemperature: 广州__2025年04月13日

 

加一个聊天框,测试多轮对话效果:

第二次会话直接输入 广州 大模型就知道我要问什么了,说明聊天记忆功能已生效。

 

四、总结

本文以问天气为场景,通过 Tool Calling 实现系统 API 调用,同时实现多轮对话中的会话记忆。需要注意的是 InMemoryChatMemory 只能作为测试使用,在企业应用中需要使用其他实现方式,把聊天记录存储在 Redis 或者 数据库中,并且需要考虑消息的保存时间、容量、如何清除等问题。

 

五、完整代码

  • Gitee地址:

https://gitee.com/zlt2000/zlt-spring-ai-app

  • Github地址:

https://github.com/zlt2000/zlt-spring-ai-app