Commit 38869e0a by renyizhao

获取模型列表接口

parent 8cbaffa1
package com.luhu.computility.module.apihub.controller.app.aimodel.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
/**
* APP端 - 模型定价信息 VO(聚合后)
*/
@Data
@Schema(description = "APP端 - 模型定价信息")
public class AppModelPricingVO {
@Schema(description = "模型名")
private String modelName;
@Schema(description = "图标名(lobehub 风格,如 Qwen.Color)")
private String icon;
@Schema(description = "供应商名")
private String vendorName;
@Schema(description = "支持的端点列表")
private List<String> supportedEndpoints;
@Schema(description = "支持的分组列表")
private List<String> enableGroups;
@Schema(description = "计价方式:0-按倍率,1-按固定价")
private Integer quotaType;
@Schema(description = "模型倍率")
private BigDecimal modelRatio;
@Schema(description = "固定价(元/1k tokens)")
private BigDecimal modelPrice;
@Schema(description = "补全价格倍率")
private BigDecimal completionRatio;
@Schema(description = "当前用户分组倍率")
private BigDecimal groupRatio;
@Schema(description = "输入价格(元/1k tokens),已乘 groupRatio")
private BigDecimal inputPrice;
@Schema(description = "输出价格(元/1k tokens),已乘 groupRatio")
private BigDecimal outputPrice;
@Schema(description = "状态:1-启用,0-禁用")
private Integer status;
@Schema(description = "模型描述(未设置则为空)")
private String description;
}
package com.luhu.computility.module.apihub.controller.app.aimodel.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* APP端 - 模型信息 VO
*/
@Data
@Schema(description = "APP端 - 模型信息")
public class AppModelVO {
@Schema(description = "模型ID")
private Long id;
@Schema(description = "模型名")
private String modelName;
@Schema(description = "图标名")
private String icon;
@Schema(description = "供应商名(自动匹配)")
private String vendorName;
@Schema(description = "支持的端点")
private List<String> endpoints;
@Schema(description = "支持的分组")
private List<String> enableGroups;
@Schema(description = "状态:1-启用,0-禁用")
private Integer status;
@Schema(description = "模型描述(未设置则为空)")
private String description;
}
package com.luhu.computility.module.apihub.controller.app.aimodel.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
import java.util.Map;
/**
* APP端 - 定价信息 VO
*/
@Data
@Schema(description = "APP端 - 定价信息")
public class AppPricingVO {
@Schema(description = "当前用户所属分组名")
private String group;
@Schema(description = "当前分组倍率")
private java.math.BigDecimal groupRatio;
@Schema(description = "所有可用分组及倍率")
private Map<String, java.math.BigDecimal> groupRatios;
@Schema(description = "支持的端点")
private Map<String, Object> supportedEndpoint;
@Schema(description = "供应商列表(用于图标 fallback)")
private List<Map<String, Object>> vendors;
@Schema(description = "模型定价列表(原始数据)")
private List<Map<String, Object>> modelPricing;
}
......@@ -5,6 +5,8 @@ import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.luhu.computility.module.apihub.config.NewApiProperties;
import com.luhu.computility.module.apihub.controller.admin.newapi.*;
import lombok.extern.slf4j.Slf4j;
......@@ -21,6 +23,9 @@ public class NewApiClient {
@Resource
private NewApiProperties newApiProperties;
@Resource
private ObjectMapper objectMapper;
private static final String HEADER_USER = "New-Api-User";
private static final String HEADER_AUTH = "Authorization";
private static final String USER_ANONYMOUS = "-1";
......@@ -283,6 +288,122 @@ public class NewApiClient {
}
}
/**
* 获取定价信息(含模型定价、分组倍率、支持端点)
* GET /api/pricing
*
* @param accessToken 用户访问令牌(可空)。传入后使用对应账号权限请求,group_ratio.default 即为该用户实际倍率;不传则匿名请求。
* @param userId 用户在 New API 的 userId(与 accessToken 配套传入,用于 New-Api-User 头)。可空。
*/
public NewApiResponse<Map<String, Object>> getPricing(String accessToken, String userId) {
String url = newApiProperties.getBaseUrl() + "/api/pricing";
log.info("[NewApiClient.getPricing] 请求URL: {}, 带token={}, 带userId={}", url,
StrUtil.isNotBlank(accessToken), StrUtil.isNotBlank(userId));
try {
HttpRequest request = HttpRequest.get(url).timeout(30000);
if (StrUtil.isBlank(accessToken)) {
// 匿名请求
request.header(HEADER_USER, USER_ANONYMOUS);
} else {
// 带 token 时同时传 New-Api-User 头
if (StrUtil.isNotBlank(userId)) {
request.header(HEADER_USER, userId);
}
request.header(HEADER_AUTH, "Bearer " + accessToken);
}
HttpResponse response = request.execute();
log.info("[NewApiClient.getPricing] 响应状态: {}", response.getStatus());
return parseMapResponse(response);
} catch (Exception e) {
log.error("[NewApiClient.getPricing] 请求异常", e);
return NewApiResponse.<Map<String, Object>>builder()
.success(false)
.message("请求异常: " + e.getMessage())
.httpResponse(null)
.build();
}
}
/**
* 获取模型列表(管理员权限)
* GET /api/models/?p=0&page_size=100
*/
public NewApiResponse<Map<String, Object>> getModels(Integer page, Integer pageSize) {
String url = newApiProperties.getBaseUrl() + "/api/models/";
if (page != null || pageSize != null) {
StringBuilder sb = new StringBuilder(url).append("?");
if (page != null) sb.append("p=").append(page);
if (page != null && pageSize != null) sb.append("&");
if (pageSize != null) sb.append("page_size=").append(pageSize);
url = sb.toString();
}
log.info("[NewApiClient.getModels] 请求URL: {}", url);
try {
HttpResponse response = HttpRequest.get(url)
.header(HEADER_USER, newApiProperties.getAdminUserId())
.header(HEADER_AUTH, "Bearer " + newApiProperties.getAdminToken())
.timeout(30000)
.execute();
log.info("[NewApiClient.getModels] 响应状态: {}", response.getStatus());
return parseMapResponse(response);
} catch (Exception e) {
log.error("[NewApiClient.getModels] 请求异常", e);
return NewApiResponse.<Map<String, Object>>builder()
.success(false)
.message("请求异常: " + e.getMessage())
.httpResponse(null)
.build();
}
}
@SuppressWarnings("unchecked")
private NewApiResponse<Map<String, Object>> parseMapResponse(HttpResponse response) {
try {
String body = response.body();
if (StrUtil.isBlank(body)) {
log.warn("[NewApiClient.parseMapResponse] 响应为空,status={}", response.getStatus());
return NewApiResponse.<Map<String, Object>>builder()
.success(false)
.message("Empty response")
.httpResponse(response)
.build();
}
// 调试日志:记录原始响应体(截断避免过长)
String bodyPreview = body.length() > 500 ? body.substring(0, 500) + "..." : body;
log.info("[NewApiClient.parseMapResponse] status={}, body={}", response.getStatus(), bodyPreview);
// 使用 Jackson 解析(兼容 New API 返回的含特殊字符 JSON)
Map<String, Object> root = objectMapper.readValue(body, new TypeReference<Map<String, Object>>() {});
NewApiResponse<Map<String, Object>> result = new NewApiResponse<>();
result.setSuccess(root.get("success") == null ? null : Boolean.valueOf(root.get("success").toString()));
result.setMessage(root.get("message") == null ? null : root.get("message").toString());
result.setHttpResponse(response);
if (root.containsKey("data") && root.get("data") != null) {
Object data = root.get("data");
if (data instanceof Map) {
result.setData((Map<String, Object>) data);
} else {
// data 是其他类型(List 等),原样返回,调用方自行处理
Map<String, Object> wrapper = new HashMap<>();
wrapper.put("data", data);
result.setData(wrapper);
}
}
return result;
} catch (Exception e) {
log.error("[NewApiClient.parseMapResponse] 解析响应异常,body={}", response.body(), e);
return NewApiResponse.<Map<String, Object>>builder()
.success(false)
.message("解析响应失败: " + e.getMessage())
.build();
}
}
@SuppressWarnings("unchecked")
private <T> NewApiResponse<T> parseResponse(HttpResponse response, Class<T> dataClass) {
try {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment