修改机器码的验证,抽象通用方法
添加 OpenAI 格式的 流式转发接口 保持返回的数据格式
This commit is contained in:
parent
6d41f52de5
commit
5875ffe671
@ -38,7 +38,7 @@ public class ForwardController(ForwardWordService forwardWordService, ILogger<Fo
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region 流式转发接口,需要系统数据
|
#region 流式转发接口(只返回数据),需要系统数据
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 流式转发
|
/// 流式转发
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -83,6 +83,75 @@ public class ForwardController(ForwardWordService forwardWordService, ILogger<Fo
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region OpenAI 格式流式返回接口(保留接口),需要系统数据
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("/lms/Forward/forward-stream-struct")]
|
||||||
|
public async Task<IActionResult> ForwardStreamStruct([FromBody] ForwardModel req)
|
||||||
|
{
|
||||||
|
HttpResponseMessage? upstreamResponse;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
upstreamResponse = await _forwardWordService.ForwardWordStreamRaw(req);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return BadRequest(e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理上游响应与转发
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// A. 上游 OpenAI 报错 (如 401 key 错误, 429 限流, 500 等)
|
||||||
|
// 我们需要透传这个错误给前端,而不是报 400
|
||||||
|
if (!upstreamResponse.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Response.StatusCode = (int)upstreamResponse.StatusCode;
|
||||||
|
|
||||||
|
if (upstreamResponse.Content.Headers.ContentType != null)
|
||||||
|
{
|
||||||
|
Response.ContentType = upstreamResponse.Content.Headers.ContentType.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接读取错误内容并写入
|
||||||
|
var errorContent = await upstreamResponse.Content.ReadAsStringAsync();
|
||||||
|
await Response.WriteAsync(errorContent);
|
||||||
|
|
||||||
|
// 显式返回空结果,告诉框架处理结束
|
||||||
|
return new EmptyResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
// B. 上游成功 (200 OK) -> 建立 SSE 管道
|
||||||
|
HttpContext.Response.ContentType = "text/event-stream";
|
||||||
|
HttpContext.Response.Headers.Add("Cache-Control", "no-cache");
|
||||||
|
HttpContext.Response.Headers.Add("Connection", "keep-alive");
|
||||||
|
|
||||||
|
// 获取上游流
|
||||||
|
await using var stream = await upstreamResponse.Content.ReadAsStreamAsync();
|
||||||
|
|
||||||
|
// 开始流式复制
|
||||||
|
// CopyToAsync 会自动处理缓冲,直到上游流结束
|
||||||
|
await stream.CopyToAsync(Response.Body);
|
||||||
|
await Response.Body.FlushAsync();
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
if (!Response.HasStarted)
|
||||||
|
{
|
||||||
|
return StatusCode(502, "Upstream connection failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
upstreamResponse?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兜底返回,确保所有路径都有返回值
|
||||||
|
return new EmptyResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region Post 直接转发接口
|
#region Post 直接转发接口
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,30 @@
|
|||||||
using LMS.DAO;
|
using Betalgo.Ranul.OpenAI;
|
||||||
|
using Betalgo.Ranul.OpenAI.Managers;
|
||||||
|
using Betalgo.Ranul.OpenAI.ObjectModels.RequestModels;
|
||||||
|
using LMS.Common.Extensions;
|
||||||
|
using LMS.DAO;
|
||||||
using LMS.Repository.DB;
|
using LMS.Repository.DB;
|
||||||
using LMS.Repository.Forward;
|
using LMS.Repository.Forward;
|
||||||
|
using LMS.Repository.Model;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using static LMS.Common.Enums.ResponseCodeEnum;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using LMS.Repository.Model;
|
using static LMS.Common.Enums.ResponseCodeEnum;
|
||||||
using Betalgo.Ranul.OpenAI.Managers;
|
using static LMS.service.Controllers.ForwardController;
|
||||||
using Betalgo.Ranul.OpenAI;
|
|
||||||
using Betalgo.Ranul.OpenAI.ObjectModels.RequestModels;
|
|
||||||
using LMS.Common.Extensions;
|
|
||||||
|
|
||||||
namespace LMS.service.Service;
|
namespace LMS.service.Service;
|
||||||
|
|
||||||
public class ForwardWordService(ApplicationDbContext context)
|
public class ForwardWordService(ApplicationDbContext context, IHttpClientFactory httpClientFactory, MachineService machineService)
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContext _context = context;
|
private readonly ApplicationDbContext _context = context;
|
||||||
|
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory = httpClientFactory;
|
||||||
|
|
||||||
|
private readonly MachineService _machineService = machineService;
|
||||||
|
|
||||||
#region 非流转发接口,需要系统数据
|
#region 非流转发接口,需要系统数据
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 转发OpenAi格式的请求 非流
|
/// 转发OpenAi格式的请求 非流
|
||||||
@ -228,7 +234,84 @@ public class ForwardWordService(ApplicationDbContext context)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
#endregion
|
||||||
|
#region OpenAI 格式流式返回接口(保留接口),需要系统数据
|
||||||
|
|
||||||
|
public async Task<HttpResponseMessage> ForwardWordStreamRaw(ForwardModel request)
|
||||||
|
{
|
||||||
|
// --- 1. 基础校验 (保持原有逻辑) ---
|
||||||
|
if (string.IsNullOrEmpty(request.Word)) throw new Exception("参数错误");
|
||||||
|
if (string.IsNullOrEmpty(request.GptUrl)) throw new Exception("请求的url为空");
|
||||||
|
|
||||||
|
var allowedUrls = new[] {
|
||||||
|
"https://ark.cn-beijing.volces.com",
|
||||||
|
"https://api.moonshot.cn",
|
||||||
|
"https://laitool.net",
|
||||||
|
"https://api.laitool.cc",
|
||||||
|
"https://laitool.cc",
|
||||||
|
"https://zhiluoai.net"
|
||||||
|
};
|
||||||
|
|
||||||
|
// 简单的校验逻辑优化
|
||||||
|
if (!allowedUrls.Any(url => request.GptUrl.StartsWith(url)))
|
||||||
|
{
|
||||||
|
throw new Exception("请求的url不合法");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验机器码
|
||||||
|
Machine? machine = await _machineService.GetActiveMachineByMachineId(request.MachineId);
|
||||||
|
if (machine == null)
|
||||||
|
{
|
||||||
|
throw new Exception("机器码不存在或已过期");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. 获取提示词预设 (保持原有逻辑) ---
|
||||||
|
Prompt? prompt = await _context.Prompt.FirstOrDefaultAsync(x => x.PromptTypeId == request.PromptTypeId && x.Id == request.PromptId);
|
||||||
|
if (prompt == null)
|
||||||
|
{
|
||||||
|
throw new Exception("FindPromptStringFail"); // 建议使用具体错误码
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. 手动构建 OpenAI 格式的请求体 ---
|
||||||
|
// SDK 帮你做了这一步,现在我们要自己做,以便获得原始流
|
||||||
|
var payload = new
|
||||||
|
{
|
||||||
|
model = request.Model,
|
||||||
|
messages = new List<object>
|
||||||
|
{
|
||||||
|
new { role = "system", content = prompt.PromptString },
|
||||||
|
new { role = "user", content = request.Word }
|
||||||
|
},
|
||||||
|
stream = true, // 必须开启流式
|
||||||
|
// max_tokens = 2000, // 如果有需要可以加其他参数
|
||||||
|
// temperature = 0.7
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonContent = JsonConvert.SerializeObject(payload, new JsonSerializerSettings { ContractResolver = new LowercaseContractResolver() });
|
||||||
|
var httpContent = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
// --- 4. 发起 HTTP 请求 ---
|
||||||
|
var client = _httpClientFactory.CreateClient(); // 或者直接 new HttpClient();
|
||||||
|
|
||||||
|
// 拼接完整的 API 地址,通常 OpenAI 兼容接口的路径是 /v1/chat/completions
|
||||||
|
// 注意处理 request.GptUrl 结尾是否有 / 的情况
|
||||||
|
var baseUrl = request.GptUrl.TrimEnd('/');
|
||||||
|
var targetUrl = $"{baseUrl}/v1/chat/completions";
|
||||||
|
|
||||||
|
var requestMessage = new HttpRequestMessage(HttpMethod.Post, targetUrl);
|
||||||
|
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", request.ApiKey);
|
||||||
|
requestMessage.Content = httpContent;
|
||||||
|
|
||||||
|
// *** 关键点:使用 HttpCompletionOption.ResponseHeadersRead ***
|
||||||
|
// 这表示一旦读到响应头就返回,不要等待整个 Body 下载完成,这样才能实现流式转发
|
||||||
|
var response = await client.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region Get直接转发接口
|
#region Get直接转发接口
|
||||||
internal async Task<ActionResult<APIResponseModel<object>>> GetTransfer(GetTransferModel getTransferModel)
|
internal async Task<ActionResult<APIResponseModel<object>>> GetTransfer(GetTransferModel getTransferModel)
|
||||||
|
|||||||
@ -216,6 +216,19 @@ namespace LMS.service.Service
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 查询当前的机器码状态,返回机器码
|
||||||
|
|
||||||
|
public async Task<Machine?> GetActiveMachineByMachineId(string machineId)
|
||||||
|
{
|
||||||
|
Machine? machine = await _context.Machine.FirstOrDefaultAsync(
|
||||||
|
x => x.MachineId == machineId
|
||||||
|
&& x.Status == MachineStatus.Active
|
||||||
|
&& (x.DeactivationTime == null || x.DeactivationTime > BeijingTimeExtension.GetBeijingTime()));
|
||||||
|
return machine;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region 获取机器码状态
|
#region 获取机器码状态
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -224,12 +237,12 @@ namespace LMS.service.Service
|
|||||||
/// <param name="machineId"></param>
|
/// <param name="machineId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="NotImplementedException"></exception>
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
internal async Task<ActionResult<APIResponseModel<MachineStatusResponse>>> GetMachineStatus(string machineId)
|
public async Task<ActionResult<APIResponseModel<MachineStatusResponse>>> GetMachineStatus(string machineId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 获取对应的machine
|
// 获取对应的machine
|
||||||
Machine? machine = await _context.Machine.FirstOrDefaultAsync(x => x.MachineId == machineId && x.Status == MachineStatus.Active && x.DeactivationTime > BeijingTimeExtension.GetBeijingTime());
|
Machine? machine = await GetActiveMachineByMachineId(machineId);
|
||||||
if (machine == null)
|
if (machine == null)
|
||||||
{
|
{
|
||||||
return APIResponseModel<MachineStatusResponse>.CreateErrorResponseModel(ResponseCode.MachineNotFound);
|
return APIResponseModel<MachineStatusResponse>.CreateErrorResponseModel(ResponseCode.MachineNotFound);
|
||||||
|
|||||||
@ -412,6 +412,7 @@ namespace LMS.service.Service.Other
|
|||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 验证对应的程序和机器码是不是有效
|
#region 验证对应的程序和机器码是不是有效
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 验证对应的程序和机器码是不是有效
|
/// 验证对应的程序和机器码是不是有效
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user