commit feat: 新增和改进HTTP请求处理及文件下载功能

- 新增文件下载功能:
  - 新增 `FileBody.java` 类,定义文件下载的数据结构。
  - 新增 `FileCallback.java` 类,处理文件下载的回调逻辑,包括线程池管理和下载进度报告。
  - 新增 `HttpDownloadUtils.java` 工具类,提供异步和同步的文件下载方法,并支持下载接口回调。

- 改进HTTP请求处理:
  - 修改 `NapCatQQ.java` 的 `onResponse` 方法,增加对响应头(`Headers`)的处理。
  - 修改 `GetRequestParams.java` 和 `PostRequestParams.java` 的 `getRequest` 方法,支持在请求中添加自定义Header。
  - 修改 `HttpCallback.java` 的 `onResponse` 方法,增加对响应头(`Headers`)的处理。

- 优化HTTP日志记录:
  - 修改 `HttpLoggingInterceptor.java`,调整日志级别为默认输出响应体,修复日志输出格式和异常处理问题。

- 改进BaseApi类:
  - 修改 `BaseApi.java`,增加对请求头的支持,并优化错误处理逻辑。
  - 在拦截器中处理非成功的响应码,返回统一格式的错误信息,避免业务逻辑中重复处理。

- 更新QQBotManager API回调:
  - 修改 `QQBotManager.java`,更新API回调方法以处理响应头(`Headers`),确保所有回调方法一致。

- 新增GPT API交互功能:
  - 新增 `OpenAiBean.java` 类,定义与OpenAI交互的数据结构,包含响应中的各个字段。
  - 新增 `SiliconGPTManager.java` 类,实现新的GPT管理器,处理与Silicon GPT API的交互。
  - 新增 `GPTApi.java` 类,提供与GPT API交互的基础配置和API调用方法。
  - 新增 `GPTBuilder.java` 类,用于构建发送给GPT API的请求对象。

- 改进GPT管理器:
  - 修改 `AbsGPTManager.java`,添加锁机制,防止同一用户同时发起多个请求。
  - 提供获取消息列表的方法,限制历史消息的最大数量。
  - 支持设置模型版本。
  - 修改 `BaiduGPTManager.java`,移除重复的清除方法,继承自父类。
  - 使用父类提供的方法获取消息列表,简化代码逻辑。

- 优化和重构:
  - 进一步优化 `HttpLoggingInterceptor.java` 的日志记录逻辑,提高性能和可读性。
  - 重构 `BaseApi.java` 初始化方法,简化代码逻辑,提高可读性。

这些改动增强了HTTP请求处理能力,增加了文件下载功能,并为与GPT API的交互提供了支持。
This commit is contained in:
Yutou 2025-02-08 18:27:00 +08:00
parent 94890f001c
commit 864d5960a7
25 changed files with 863 additions and 104 deletions

View File

@ -10,6 +10,7 @@ import com.yutou.napcat.http.NapCatApi;
import com.yutou.qqbot.QQBotManager;
import com.yutou.qqbot.utlis.Base64Tools;
import lombok.val;
import okhttp3.Headers;
import java.io.File;
import java.util.ArrayList;
@ -42,7 +43,7 @@ public class NapCatQQ {
.build()
).enqueue(new HttpCallback<SendMessageResponse>() {
@Override
public void onResponse(int code, String status, SendMessageResponse response, String rawResponse) {
public void onResponse(Headers headers, int code, String status, SendMessageResponse response, String rawResponse) {
System.out.println("code = " + code + ", status = " + status + ", response = " + response + ", rawResponse = " + rawResponse);
}

View File

@ -0,0 +1,11 @@
package com.yutou.okhttp;
import lombok.Data;
import java.io.InputStream;
@Data
public class FileBody<T> {
InputStream inputStream;
T t;
}

View File

@ -0,0 +1,112 @@
package com.yutou.okhttp;
import com.yutou.qqbot.utlis.Log;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public abstract class FileCallback<T> implements Callback<FileBody<T>> {
private static ThreadPoolExecutor executor;
private final T bean;
private String savePath;
public FileCallback(T bean, String savePath) {
this.bean = bean;
this.savePath = savePath;
if (executor == null) {
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
}
}
private class DownloadTask implements Runnable {
private T bean;
private Headers headers;
private HttpUrl url;
private InputStream inputStream;
public DownloadTask(T bean, Headers headers, HttpUrl url, InputStream inputStream) {
this.bean = bean;
this.headers = headers;
this.url = url;
this.inputStream = inputStream;
}
@Override
public void run() {
try {
Log.i("开始下载");
File file = new File(savePath);
onStart(bean);
if (!file.exists()) {
boolean mkdirs = file.getParentFile().mkdirs();
Log.i("mkdirs = " + mkdirs);
}
FileOutputStream outputStream = new FileOutputStream(file);
int len;
long total = 0;
byte[] bytes = new byte[4096];
boolean isDownload = true;
long available = inputStream.available();
while ((len = inputStream.read(bytes)) != -1 && isDownload) {
total += len;
outputStream.write(bytes, 0, len);
outputStream.flush();
isDownload = onDownload(headers, bean, total, available);
}
Log.i("下载完成");
outputStream.close();
} catch (Exception e) {
Log.e(e);
onFailure(bean,e);
} finally {
onFinish(bean);
try {
inputStream.close();
} catch (IOException e) {
Log.e(e);
}
}
}
}
public abstract void onStart(T bean);
public abstract boolean onDownload(Headers headers, T bean, long len, long total);
public abstract void onFinish(T bean);
public abstract void onFailure(T bean, Throwable throwable);
@Override
public void onResponse(Call<FileBody<T>> call, Response<FileBody<T>> response) {
try {
executor.execute(new DownloadTask(bean, response.headers(), call.request().url(), response.body().getInputStream()));
} catch (Exception e) {
Log.e(e);
onFailure(bean,e);
call.cancel();
}
}
@Override
public void onFailure(Call<FileBody<T>> call, Throwable throwable) {
onFailure(bean, throwable);
call.cancel();
}
}

View File

@ -1,6 +1,7 @@
package com.yutou.okhttp;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Request;
@ -14,12 +15,18 @@ public class GetRequestParams implements IRequestParam {
* @return
*/
@Override
public Request getRequest(HashMap<String, String> map, Request request) {
public Request getRequest(HashMap<String, String> headerMap, HashMap<String, String> map, Request request) {
Headers.Builder headerBuild = request.headers().newBuilder();
if (!headerMap.isEmpty()) {
for (String key : headerMap.keySet()) {
headerBuild.add(key, headerMap.get(key));
}
}
//添加公共参数
HttpUrl.Builder builder = request.url().newBuilder();
for (String key : map.keySet()) {
builder.addQueryParameter(key, String.valueOf(map.get(key)));
}
return request.newBuilder().url(builder.build()).build();
return request.newBuilder().url(builder.build()).headers(headerBuild.build()).build();
}
}

View File

@ -1,12 +1,13 @@
package com.yutou.okhttp;
import okhttp3.Headers;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public abstract class HttpCallback<T> implements Callback<HttpBody<T>> {
public abstract void onResponse(int code, String status, T response, String rawResponse);
public abstract void onResponse(Headers headers,int code, String status, T response, String rawResponse);
public abstract void onFailure(Throwable throwable);
@ -14,6 +15,7 @@ public abstract class HttpCallback<T> implements Callback<HttpBody<T>> {
public void onResponse(Call<HttpBody<T>> call, Response<HttpBody<T>> response) {
if (response.body() != null) {
onResponse(
response.headers(),
response.body().getRetcode(),
response.body().getStatus(),
response.body().getData(),
@ -22,10 +24,12 @@ public abstract class HttpCallback<T> implements Callback<HttpBody<T>> {
} else {
onFailure(new NullPointerException("response body is null"));
}
call.cancel();
}
@Override
public void onFailure(Call<HttpBody<T>> call, Throwable throwable) {
onFailure(throwable);
call.cancel();
}
}

View File

@ -0,0 +1,145 @@
package com.yutou.okhttp;
import com.yutou.qqbot.interfaces.DownloadInterface;
import com.yutou.qqbot.utlis.ConfigTools;
import com.yutou.qqbot.utlis.Log;
import lombok.Data;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
public class HttpDownloadUtils {
public static void download(Builder builder) {
createHttpClient(builder).enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
if (builder.downloadInterface != null) {
builder.downloadInterface.onError(e);
}
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
InputStream inputStream = Objects.requireNonNull(response.body()).byteStream();
File target;
if (StringUtils.hasText(builder.fileName)) {
target = new File(builder.path, builder.fileName);
} else {
target = new File(builder.path);
}
FileOutputStream fileOutputStream = new FileOutputStream(target);
if (builder.downloadInterface != null) {
builder.downloadInterface.onDownloadStart();
}
try {
byte[] buffer = new byte[2048];
int len;
long soFarBytes = 0;
long totalBytes = inputStream.available();
while ((len = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
soFarBytes += len;
if (builder.downloadInterface != null) {
if (!builder.downloadInterface.onDownloading(soFarBytes, totalBytes)) {
break;
}
}
}
fileOutputStream.flush();
} catch (IOException e) {
Log.e(e,"download error:", builder.url);
} finally {
if (builder.downloadInterface != null) {
builder.downloadInterface.onDownload(target);
}
}
}
});
}
public static File downloadSync(Builder builder) {
try {
Response response = createHttpClient(builder).execute();
InputStream inputStream = Objects.requireNonNull(response.body()).byteStream();
File target;
if (StringUtils.hasText(builder.fileName)) {
target = new File(builder.path, builder.fileName);
} else {
target = new File(builder.path);
}
try (FileOutputStream fileOutputStream = new FileOutputStream(target)) {
byte[] buffer = new byte[2048];
int len;
while ((len = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, len);
}
fileOutputStream.flush();
return target;
} catch (IOException e) {
Log.e(e,"download error:" , builder.url);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return null;
}
private static Call createHttpClient(Builder builder) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(2, TimeUnit.MINUTES)
.readTimeout(2, TimeUnit.MINUTES)
.build();
Request.Builder rb = new Request.Builder()
.get()
.addHeader("User-Agent", ConfigTools.getUserAgent())
.url(builder.url);
if (StringUtils.hasText(builder.cookie)) {
rb.addHeader("Set-Cookie", builder.cookie);
rb.addHeader("Cookie", builder.cookie);
}
Request request = rb.build();
return okHttpClient.newCall(request);
}
@Data
public static class Builder {
String url;
String path;
String fileName;
DownloadInterface downloadInterface;
String cookie;
public Builder setUrl(String url) {
this.url = url;
return this;
}
public Builder setPath(String path) {
this.path = path;
return this;
}
public Builder setFileName(String fileName) {
this.fileName = fileName;
return this;
}
public Builder setDownloadInterface(DownloadInterface downloadInterface) {
this.downloadInterface = downloadInterface;
return this;
}
public Builder setCookie(String cookie) {
this.cookie = cookie;
return this;
}
}
}

View File

@ -1,34 +1,25 @@
package com.yutou.okhttp;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.yutou.qqbot.utlis.Log;
import okhttp3.Connection;
import okhttp3.Headers;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import lombok.val;
import okhttp3.*;
import okhttp3.internal.http.HttpHeaders;
import okio.Buffer;
import okio.BufferedSource;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
public class HttpLoggingInterceptor implements Interceptor {
private static final String TAG = "HttpLogging";
private static final Charset UTF8 = Charset.forName("UTF-8");
private static final Charset UTF8 = StandardCharsets.UTF_8;
private volatile Level printLevel = Level.NONE;
private volatile Level printLevel = Level.BODY;
private java.util.logging.Level colorLevel;
private Logger logger;
@ -63,7 +54,7 @@ public class HttpLoggingInterceptor implements Interceptor {
private void log(String message) {
//logger.log(colorLevel, message);
if (prLog) {
Log.i(TAG, message);
Log.getDynamicLogger(TAG).info(message);
}
//Log.e(TAG,message);
}
@ -71,8 +62,11 @@ public class HttpLoggingInterceptor implements Interceptor {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (request.body().contentLength() == 0) {
request = chain.call().request();
if (request.body() != null && request.body().contentLength() == 0) {
val headers = request.headers();
request = chain.call().request().newBuilder()
.headers(headers)
.build();
}
if (printLevel == Level.NONE) {
return chain.proceed(request);
@ -109,8 +103,6 @@ public class HttpLoggingInterceptor implements Interceptor {
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
log("\tContent-Type: " + requestBody.contentType());
}
@ -121,7 +113,6 @@ public class HttpLoggingInterceptor implements Interceptor {
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
log("\t" + name + ": " + headers.value(i));
}
@ -151,7 +142,7 @@ public class HttpLoggingInterceptor implements Interceptor {
boolean logHeaders = (printLevel == Level.BODY || printLevel == Level.HEADERS);
try {
log("<-- " + clone.code() + ' ' + clone.message() + ' ' + clone.request().url() + " (" + tookMs + "ms");
log("<-- " + clone.code() + ' ' + clone.message() + ' ' + clone.request().url() + " (" + tookMs + "ms)");
if (logHeaders) {
Headers headers = clone.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
@ -162,12 +153,13 @@ public class HttpLoggingInterceptor implements Interceptor {
if (responseBody == null) return response;
if (isPlaintext(responseBody.contentType())) {
byte[] bytes = responseBody.byteStream().readAllBytes();
MediaType contentType = responseBody.contentType();
String body = new String(bytes, getCharset(contentType));
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // 请求整个流
Buffer buffer = source.buffer();
Charset charset = getCharset(responseBody.contentType());
String body = buffer.clone().readString(charset);
log("\tbody:" + body);
responseBody = ResponseBody.create(responseBody.contentType(), bytes);
return response.newBuilder().body(responseBody).build();
} else {
log("\tbody: maybe [binary body], omitted!");
}
@ -214,6 +206,8 @@ public class HttpLoggingInterceptor implements Interceptor {
body.writeTo(buffer);
Charset charset = getCharset(body.contentType());
log("\tbody:" + buffer.readString(charset));
// 重置请求体以确保后续处理不受影响
buffer.clear();
} catch (Exception e) {
logger.log(java.util.logging.Level.WARNING, e.getMessage(), e);
}

View File

@ -5,5 +5,5 @@ import okhttp3.Request;
import java.util.HashMap;
public interface IRequestParam {
Request getRequest(HashMap<String,String> map, Request request);
Request getRequest(HashMap<String,String> header, HashMap<String,String> map, Request request);
}

View File

@ -1,5 +1,6 @@
package com.yutou.okhttp;
import com.yutou.qqbot.utlis.ConfigTools;
import okhttp3.Request;
import java.util.HashMap;
@ -8,6 +9,7 @@ public class ParamsContext {
private IRequestParam iRequestParam;
private Request request;
private HashMap<String, String> map;
private HashMap<String, String> headerMap;
public ParamsContext(HashMap<String, String> map, Request request) {
if (map == null) {
@ -15,6 +17,16 @@ public class ParamsContext {
}
this.map = map;
this.request = request;
this.headerMap = new HashMap<>();
}
public ParamsContext(HashMap<String, String> headerMap, HashMap<String, String> map, Request request) {
if (map == null) {
map = new HashMap<>();
}
this.map = map;
this.request = request;
this.headerMap = headerMap;
}
public Request getInRequest() {
@ -26,6 +38,7 @@ public class ParamsContext {
iRequestParam = new PostRequestParams();
break;
}
return iRequestParam.getRequest(map,request);
headerMap.put("User-Agent", ConfigTools.getUserAgent());
return iRequestParam.getRequest(headerMap, map, request);
}
}

View File

@ -2,22 +2,26 @@ package com.yutou.okhttp;
import com.alibaba.fastjson2.JSONObject;
import com.yutou.qqbot.utlis.Log;
import okhttp3.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class PostRequestParams implements IRequestParam {
@Override
public Request getRequest(HashMap<String, String> map, Request request) {
public Request getRequest(HashMap<String, String> headerMap, HashMap<String, String> map, Request request) {
Headers.Builder headerBuilder = request.headers().newBuilder();
if (!headerMap.isEmpty()) {
for (String key : headerMap.keySet()) {
headerBuilder.add(key, headerMap.get(key));
}
}
if (request.body() instanceof FormBody) {
FormBody.Builder bodyBuilder = new FormBody.Builder();
FormBody formBody = (FormBody) request.body();
for (int i = 0; i < formBody.size(); i++) {
bodyBuilder.addEncoded(formBody.encodedName(i), formBody.encodedValue(i));
}
@ -25,10 +29,12 @@ public class PostRequestParams implements IRequestParam {
bodyBuilder.addEncoded(key, String.valueOf(map.get(key)));
}
formBody = bodyBuilder.build();
request = request.newBuilder().post(formBody).build();
request = request.newBuilder().headers(headerBuilder.build()).post(formBody).build();
} else if (request.body() != null) {
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), toUrlParams(map));
request = request.newBuilder().post(request.body())
request = request.newBuilder()
.headers(headerBuilder.build())
.post(request.body())
.post(requestBody).build();
}
return request;
@ -41,7 +47,7 @@ public class PostRequestParams implements IRequestParam {
try {
string.append("&").append(key).append("=").append(URLEncoder.encode(json.getString(key), "UTF-8"));
} catch (Exception e) {
e.printStackTrace();
Log.e(e);
try {
string.append("&").append(URLEncoder.encode(key, "UTF-8")).append("=");
// string += "&" + key + "=";

View File

@ -1,16 +1,16 @@
package com.yutou.okhttp.api;
import com.alibaba.fastjson2.JSONObject;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.internal.bind.DateTypeAdapter;
import com.yutou.okhttp.HttpBody;
import com.yutou.okhttp.HttpLoggingInterceptor;
import com.yutou.okhttp.ParamsContext;
import com.yutou.okhttp.converter.JsonCallAdapter;
import com.yutou.okhttp.converter.JsonConverterFactory;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import lombok.val;
import okhttp3.*;
import retrofit2.CallAdapter;
import retrofit2.Converter;
import retrofit2.Retrofit;
@ -22,18 +22,41 @@ import java.util.logging.Level;
public class BaseApi {
private String URL;
private HashMap<String, String> params;
private HashMap<String, String> params = new HashMap<>();
private HashMap<String, String> headers = new HashMap<>();
public BaseApi setURL(String URL) {
this.URL = URL;
return this;
}
public BaseApi setHeaders(HashMap<String, String> headers) {
this.headers = headers;
return this;
}
public void addHeader(HashMap<String, String> headers) {
this.headers.putAll(headers);
}
public BaseApi setParams(HashMap<String, String> params) {
this.params = params;
return this;
}
public void useCookie(JSONObject json) {
StringBuilder ck = new StringBuilder();
json.remove("sql_time");
json.remove("gourl");
for (String key : json.keySet()) {
ck.append(key).append("=").append(json.getString(key)).append(";");
}
headers.put("Cookie", ck.toString());
setHeaders(headers);
}
/**
* 创建一个接口方法
*
@ -74,7 +97,6 @@ public class BaseApi {
loggingInterceptor.setPrintLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient.Builder builder = new OkHttpClient()
.newBuilder()
.addInterceptor(initQuery())
.addInterceptor(loggingInterceptor);
return create(builder.build(),
@ -83,14 +105,30 @@ public class BaseApi {
URL,
apiClass);
}
public Interceptor initQuery() {
Interceptor addQueryParameterInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//配置公共参数
request = new ParamsContext(params,request).getInRequest();
return chain.proceed(request);
request = new ParamsContext(headers, params, request).getInRequest();
val proceed = chain.proceed(request);
if (!proceed.isSuccessful()) {
HttpBody<?> httpBody = new HttpBody<>();
httpBody.setCode(200);
val parse = JSONObject.parse(proceed.body().string());
httpBody.setRetcode(parse.getInteger("code"));
httpBody.setMsg(parse.getString("message"));
ResponseBody errorResponseBody = ResponseBody.create(
JSONObject.toJSONString(httpBody),
MediaType.get("application/json; charset=utf-8"));
val newResponse=proceed.newBuilder()
.code(200)
.body(errorResponseBody).build();
return newResponse;
}
return proceed;
}
};
return addQueryParameterInterceptor;

View File

@ -31,12 +31,26 @@ public class JsonResponseBodyConverter<T> implements Converter<ResponseBody, T>
HttpBody<T> body;
try {
body = JSONObject.parseObject(string, type);
if(body.getData()==null){
JSONObject jt=JSONObject.parseObject(JSONObject.toJSONString(new HttpBody<>()));
jt.put("data",JSONObject.parseObject(string));
HttpBody<T> bt=JSONObject.parseObject(jt.toJSONString(),type);
body.setData(bt.getData());
}
body.setSrc(string);
return (T) body;
} catch (Exception e) {
e.printStackTrace();
body = new HttpBody();
try {
body = new HttpBody<>();
body.setData(JSONObject.parseObject(string, type));
body.setSrc(string);
} catch (Exception e2) {
e2.printStackTrace();
body = new HttpBody<>();
body.setSrc(string);
}
}
return (T) body;

View File

@ -15,6 +15,7 @@ import com.yutou.okhttp.HttpCallback;
import com.yutou.qqbot.data.MessageChainBuilder;
import com.yutou.qqbot.interfaces.ObjectInterface;
import com.yutou.qqbot.utlis.*;
import okhttp3.Headers;
import retrofit2.Response;
@ -47,7 +48,7 @@ public class QQBotManager {
sendMessage(true, 583819556L, "姬妻酱上线拉~☆Daze~ 当前版本:" + QQBotApplication.version);
NapCatApi.getGroupApi().getGroupList().enqueue(new HttpCallback<List<GroupBean>>() {
@Override
public void onResponse(int code, String status, List<GroupBean> response, String rawResponse) {
public void onResponse(Headers headers, int code, String status, List<GroupBean> response, String rawResponse) {
for (GroupBean groupBean : response) {
QQDatabase.addGroup(groupBean.getGroupId(), groupBean);
QQNumberManager.getManager().addNumber(groupBean.getGroupId(), true);
@ -61,7 +62,7 @@ public class QQBotManager {
});
NapCatApi.getFriendApi().getFriendList().enqueue(new HttpCallback<List<FriendBean>>() {
@Override
public void onResponse(int code, String status, List<FriendBean> response, String rawResponse) {
public void onResponse(Headers headers, int code, String status, List<FriendBean> response, String rawResponse) {
for (FriendBean friendBean : response) {
QQDatabase.addUser(friendBean.getUserId(), friendBean);
QQNumberManager.getManager().addNumber(friendBean.getUserId(), false);
@ -75,7 +76,7 @@ public class QQBotManager {
});
NapCatApi.getUtilsApi().getLoginInfo().enqueue(new HttpCallback<FriendBean>() {
@Override
public void onResponse(int code, String status, FriendBean response, String rawResponse) {
public void onResponse(Headers headers,int code, String status, FriendBean response, String rawResponse) {
QQDatabase.setMe(response);
}
@ -214,7 +215,7 @@ public class QQBotManager {
public void groupBan(long qqGroup, long user, int timer, ObjectInterface objectInterface) {
NapCatApi.getGroupApi().groupBan(qqGroup, user, timer).enqueue(new HttpCallback<BaseBean>() {
@Override
public void onResponse(int code, String status, BaseBean response, String rawResponse) {
public void onResponse(Headers headers,int code, String status, BaseBean response, String rawResponse) {
if (objectInterface != null) {
objectInterface.out("1");
}
@ -240,7 +241,7 @@ public class QQBotManager {
NapCatApi.getGroupApi().setGroupSpecialTitle(group, user, title, duration).enqueue(new HttpCallback<BaseBean>() {
@Override
public void onResponse(int code, String status, BaseBean response, String rawResponse) {
public void onResponse(Headers headers,int code, String status, BaseBean response, String rawResponse) {
}

View File

@ -0,0 +1,90 @@
package com.yutou.qqbot.data.gpt;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import java.util.List;
@Data
public class OpenAiBean {
@JSONField(name = "id")
private String id;
@JSONField(name = "choices")
private List<Choice> choices;
@JSONField(name = "tool_calls")
private List<ToolCall> toolCalls;
@JSONField(name = "usage")
private Usage usage;
@JSONField(name = "created")
private long created;
@JSONField(name = "model")
private String model;
@JSONField(name = "object")
private String object;
@JSONField(name = "system_fingerprint")
private String systemFingerprint;
@Data
public static class Choice {
@JSONField(name = "index")
private int index;
@JSONField(name = "message")
private Message message;
@JSONField(name = "finish_reason")
private String finishReason;
}
@Data
public static class Message {
@JSONField(name = "role")
private String role;
@JSONField(name = "content")
private String content;
@JSONField(name = "reasoning_content")
private String reasoningContent;
}
@Data
public static class ToolCall {
@JSONField(name = "id")
private String id;
@JSONField(name = "type")
private String type;
@JSONField(name = "function")
private Function function;
}
@Data
public static class Function {
@JSONField(name = "name")
private String name;
@JSONField(name = "arguments")
private String arguments;
}
@Data
public static class Usage {
@JSONField(name = "prompt_tokens")
private int promptTokens;
@JSONField(name = "completion_tokens")
private int completionTokens;
@JSONField(name = "total_tokens")
private int totalTokens;
}
}

View File

@ -3,14 +3,35 @@ package com.yutou.qqbot.gpt;
import com.yutou.qqbot.data.baidu.Message;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbsGPTManager {
protected static final AtomicInteger MAX_MESSAGE = new AtomicInteger(20);
protected final ConcurrentHashMap<String, List<Message>> msgMap= new ConcurrentHashMap<>();
// 新增锁映射表
protected final ConcurrentHashMap<String, AtomicBoolean> userLocks = new ConcurrentHashMap<>();
protected String model ;
/**
* 清除与GPT管理器相关的所有缓存或状态信息
*/
public abstract void clear();
/**
* 这里确实是需要清空所有数据
*/
public synchronized void clear() { // 添加同步
msgMap.clear();
for (AtomicBoolean value : userLocks.values()) {
value.set(false);
}
userLocks.forEachValue(1, atomicBoolean -> atomicBoolean.set(false));
userLocks.clear();
}
/**
* 发送消息到指定用户
@ -54,6 +75,17 @@ public abstract class AbsGPTManager {
*/
public abstract int setMaxMessageCount(int count);
public List<Message> getMessageList(String user){
List<Message> list = msgMap.computeIfAbsent(user, k -> Collections.synchronizedList(new ArrayList<>()));
// 限制历史消息的最大数量
synchronized (list) {
if (list.size() >= MAX_MESSAGE.get()) {
int removeCount = list.size() - MAX_MESSAGE.get() + 1; // 腾出空间给新消息
list.subList(0, removeCount).clear();
}
}
return list;
}
/**
* 根据指定的类获取相应的GPT管理器实例
*
@ -63,6 +95,8 @@ public abstract class AbsGPTManager {
public static <T extends AbsGPTManager> AbsGPTManager getManager(Class<T> tClass) {
if (tClass == BaiduGPTManager.class) {
return BaiduGPTManager.getManager();
}else if(tClass== SiliconGPTManager.class){
return SiliconGPTManager.getInstance();
}
return new AbsGPTManager() {
@Override
@ -97,4 +131,8 @@ public abstract class AbsGPTManager {
};
}
protected AbsGPTManager setModel(String model) {
this.model=model;
return this;
}
}

View File

@ -29,16 +29,12 @@ public class BaiduGPTManager extends AbsGPTManager {
//ConfigTools.load操作可以确保获取到相关参数所以无需关心
private static final String AccessKey = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_ACCESS_KEY, String.class);
private static final String SecretKey = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_SECRET_KEY, String.class);
private final ConcurrentHashMap<String, List<Message>> msgMap;
private final static String modelFor40 = "ERNIE-4.0-8K";
private final static String modelFor35 = "ERNIE-Speed-128K";
private String model = modelFor35;
// 新增锁映射表
private final ConcurrentHashMap<String, AtomicBoolean> userLocks = new ConcurrentHashMap<>();
private final Qianfan qianfan;
private BaiduGPTManager() {
msgMap = new ConcurrentHashMap<>();
qianfan = new Qianfan(AccessKey, SecretKey);
String savedVersion = ConfigTools.load(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_VERSION, String.class);
if (StringUtils.isEmpty(savedVersion) || (!"3.5".equals(savedVersion) && !"4.0".equals(savedVersion))) {
@ -77,18 +73,6 @@ public class BaiduGPTManager extends AbsGPTManager {
ConfigTools.save(ConfigTools.CONFIG, ConfigTools.BAIDU_GPT_VERSION, "3.5");
}
/**
* 这里确实是需要清空所有数据
*/
@Override
public synchronized void clear() { // 添加同步
msgMap.clear();
for (AtomicBoolean value : userLocks.values()) {
value.set(false);
}
userLocks.forEachValue(1, atomicBoolean -> atomicBoolean.set(false));
userLocks.clear();
}
// 这个是官方的示例代码表示连续对话
@ -115,15 +99,8 @@ public class BaiduGPTManager extends AbsGPTManager {
return Message.create("您有请求正在处理中,请稍后再试", true);
}
try {
List<Message> list = msgMap.computeIfAbsent(user, k -> Collections.synchronizedList(new ArrayList<>()));
// 限制历史消息的最大数量
synchronized (list) {
if (list.size() >= MAX_MESSAGE.get()) {
int removeCount = list.size() - MAX_MESSAGE.get() + 1; // 腾出空间给新消息
list.subList(0, removeCount).clear();
}
List<Message> list = getMessageList(user);
list.add(Message.create(message));
}
val builder = qianfan.chatCompletion()
.model(model);
for (Message msg : list) {

View File

@ -0,0 +1,117 @@
package com.yutou.qqbot.gpt;
import com.yutou.okhttp.HttpLoggingInterceptor;
import com.yutou.qqbot.data.baidu.Message;
import com.yutou.qqbot.data.gpt.OpenAiBean;
import com.yutou.qqbot.http.GPTApi;
import com.yutou.qqbot.http.GPTBuilder;
import lombok.val;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public class SiliconGPTManager extends AbsGPTManager {
//生成单例模式
private volatile static SiliconGPTManager instance = new SiliconGPTManager();
private SiliconGPTManager() {
}
public static SiliconGPTManager getInstance() {
if (instance == null) {
synchronized (SiliconGPTManager.class) {
if (instance == null) {
instance = new SiliconGPTManager();
}
}
}
return instance;
}
@Override
public synchronized Message sendMessage(String user, String message) {
// 获取或创建用户锁
AtomicBoolean lock = userLocks.computeIfAbsent(user, k -> new AtomicBoolean(false));
try {
GPTApi.setLog(false);
// 尝试加锁如果已被锁定则立即返回提示
if (!lock.compareAndSet(false, true)) {
return Message.create("您有请求正在处理中,请稍后再试", true);
}
val builder = GPTBuilder.create(model);
List<Message> list=getMessageList(user);
list.add(Message.create(message));
for (Message msg : list) {
builder.addMessage(msg.getRole(),msg.getContent());
}
val response = GPTApi.getApi().completions(builder.build()).execute();
if (!response.isSuccessful()) {
return Message.create("API请求失败", true);
}
val body = response.body();
if (body == null || body.getData() == null) {
return Message.create("API请求为空", true);
}
if (body.getRetcode() != 0) {
return Message.create(body.getMsg(), true);
}
val choices = body.getData().getChoices();
if (choices == null || choices.isEmpty()) {
return Message.create("没有对话信息", true);
}
val choice = choices.get(choices.size() - 1);
val bot = Message.create(choice.getMessage().getContent(), true);
list.add(bot);
return bot;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
lock.set(false);
userLocks.remove(user, lock);
}
}
@Override
public File textToImage(String user, String text) {
return null;
}
@Override
public String imageToText(String user, File file) {
return "";
}
@Override
public String getGPTVersion() {
return model;
}
@Override
public int setMaxMessageCount(int count) {
MAX_MESSAGE.set(count);
return count;
}
public static void main(String[] args) {
String model="THUDM/glm-4-9b-chat";
val message = AbsGPTManager.getManager(SiliconGPTManager.class)
.setModel(model)
.sendMessage("user", "宫廷玉液酒的下一句是什么?");
System.out.println(message);
System.out.println(AbsGPTManager.getManager(SiliconGPTManager.class)
.setModel(model)
.sendMessage("user", "宫廷玉液酒减去大锤等于多少")
);
}
}

View File

@ -0,0 +1,26 @@
package com.yutou.qqbot.http;
import com.yutou.okhttp.HttpLoggingInterceptor;
import com.yutou.okhttp.api.BaseApi;
import com.yutou.qqbot.utlis.ConfigTools;
import lombok.val;
import java.util.HashMap;
public class GPTApi extends BaseApi {
public static void setLog(boolean log) {
HttpLoggingInterceptor.setLog(log);
}
public static SiliconGPTApi getApi() {
val api = new GPTApi();
api.setURL("https://api.siliconflow.cn/v1/");
// api.setURL("http://127.0.0.1:8080/");
HashMap<String, String> header = new HashMap<>();
header.put("Authorization", "Bearer sk-dcmhlbhyitcdnjbjfgflhwimahdmygfrcaopzjjcpgsfzmzo");
header.put("Content-Type", "application/json");
api.addHeader(header);
return api.createApi(SiliconGPTApi.class);
}
}

View File

@ -0,0 +1,127 @@
package com.yutou.qqbot.http;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import lombok.val;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@Data
class Message {
@JSONField(name = "role")
String role;
@JSONField(name = "content")
String content;
public Message(String role, String content) {
this.role = role;
this.content = content;
}
}
@Data
class GPTRequest {
@JSONField(name = "model")
String model;
@JSONField(name = "messages")
List<Message> messages = new ArrayList<>();
@JSONField(name = "stream")
boolean stream = false;
@JSONField(name = "max_tokens")
int maxTokens;
@JSONField(name = "stop")
List<String> stop = new ArrayList<>(List.of("null"));
@JSONField(name = "temperature")
float temperature ;
@JSONField(name = "top_p")
float topP ;
@JSONField(name = "top_k")
int topK ;
@JSONField(name = "frequency_penalty")
float frequencyPenalty;
}
public class GPTBuilder {
private final GPTRequest request;
private GPTBuilder(String model) {
request = new GPTRequest();
request.model = model;
}
public static GPTBuilder create(String model) {
return new GPTBuilder(model);
}
public GPTBuilder addMessage(String content,boolean isGPT) {
request.messages.add(new Message(isGPT ? "assistant" : "user", content));
return this;
}
public GPTBuilder addMessage(String role, String content) {
request.messages.add(new Message(role, content));
return this;
}
public GPTBuilder setMaxTokens(int maxTokens) {
request.maxTokens = maxTokens;
return this;
}
public GPTBuilder setStream(boolean stream) {
request.stream = stream;
return this;
}
public GPTBuilder setTemperature(float temperature) {
request.temperature = temperature;
return this;
}
public GPTBuilder setTopP(float topP) {
request.topP = topP;
return this;
}
public GPTBuilder setTopK(int topK) {
request.topK = topK;
return this;
}
public GPTBuilder setFrequencyPenalty(float frequencyPenalty) {
request.frequencyPenalty = frequencyPenalty;
return this;
}
// 可以根据需要添加更多配置方法
public JSONObject build() {
val json = JSONObject.parse(JSONObject.toJSONString(request));
// 创建一个迭代器来遍历JSON对象的键
Iterator<String> keys = json.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
Object value = json.get(key);
// 检查值是否为0或为空字符串
if (value == null || value.equals(0) || (value instanceof String && ((String) value).isEmpty())) {
keys.remove(); // 移除键值对
}
}
return json;
}
}

View File

@ -0,0 +1,17 @@
package com.yutou.qqbot.http;
import com.alibaba.fastjson2.JSONObject;
import com.yutou.napcat.model.MessageBean;
import com.yutou.okhttp.HttpBody;
import com.yutou.qqbot.data.gpt.OpenAiBean;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
public interface SiliconGPTApi {
@POST("/chat/completions")
Call<HttpBody<OpenAiBean>> completions(
@Body
JSONObject message
);
}

View File

@ -3,7 +3,9 @@ package com.yutou.qqbot.interfaces;
import java.io.File;
public abstract class DownloadInterface {
public void onDownloading(double soFarBytes, double totalBytes){};
public void onDownload(File file){};
public void onError(Exception e){};
public void onDownloadStart(){}
public boolean onDownloading(double soFarBytes, double totalBytes){return true;}
}

View File

@ -12,6 +12,7 @@ import com.yutou.qqbot.QQBotManager;
import com.yutou.qqbot.interfaces.ObjectInterface;
import com.yutou.qqbot.models.Model;
import com.yutou.qqbot.utlis.RedisTools;
import okhttp3.Headers;
import java.text.SimpleDateFormat;
import java.util.*;
@ -103,7 +104,7 @@ public class QQBean extends Model {
void releaseAll(long qq, boolean isRelease) {
QQBotManager.getInstance().getShutUpList(qq, new HttpCallback<List<GroupUserBean>>() {
@Override
public void onResponse(int code, String status, List<GroupUserBean> response, String rawResponse) {
public void onResponse(Headers headers, int code, String status, List<GroupUserBean> response, String rawResponse) {
List<GroupUserBean> shutList = new ArrayList<>();
for (GroupUserBean bean : response) {
if (bean.getShutUpTimestamp() > 60) {
@ -124,7 +125,7 @@ public class QQBean extends Model {
for (GroupUserBean bean : shutList) {
NapCatApi.getGroupApi().groupBan(qq, bean.getUserId(), 0).enqueue(new HttpCallback<BaseBean>() {
@Override
public void onResponse(int code, String status, BaseBean response, String rawResponse) {
public void onResponse(Headers headers, int code, String status, BaseBean response, String rawResponse) {
}

View File

@ -142,4 +142,11 @@ public class ConfigTools {
public static String getServerUrl() {
return ConfigTools.load(CONFIG, SERVER_URL, String.class);
}
public static String getUserAgent() {
String ua=load(CONFIG,"userAgent",String.class);
if(!org.springframework.util.StringUtils.hasText(ua)){
ua="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36";
}
return ua;
}
}

View File

@ -8,6 +8,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
@ -80,8 +81,17 @@ public class DynamicLogFile {
.withPolicy(triggeringPolicy)
.build();
// 创建控制台Appender
Appender consoleAppender = ConsoleAppender.newBuilder()
.setName(loggerName + "-console")
.setLayout(layout)
.setTarget(ConsoleAppender.Target.SYSTEM_OUT)
.build();
appender.start();
consoleAppender.start();
config.addAppender(appender);
config.addAppender(consoleAppender);
// 获取Logger对象
org.apache.logging.log4j.core.Logger coreLogger = context.getLogger(loggerName);
@ -91,6 +101,7 @@ public class DynamicLogFile {
// 将Appender添加到Logger对象中
coreLogger.addAppender(appender);
coreLogger.addAppender(consoleAppender);
coreLogger.setLevel(Level.ALL);
coreLogger.setAdditive(false);

View File

@ -28,7 +28,7 @@
<!-- 屏蔽 org.apache.hc.client5 包下的所有日志 -->
<Logger name="org.apache.hc.client5" level="OFF"/>
<!-- 根日志记录器 -->
<Root level="info">
<Root level="debug">
<AppenderRef ref="ConsoleAppender"/>
<AppenderRef ref="RoutingAppender"/>
</Root>