CrazyIter_Bin 10 月之前
父節點
當前提交
e8812711e6
共有 24 個文件被更改,包括 44754 次插入224 次删除
  1. 5 7
      TEAMModelOS.Extension/HTEX.Screen/Controllers/ChatController.cs
  2. 0 207
      TEAMModelOS.Extension/HTEX.Screen/Controllers/IndexController.cs
  3. 294 2
      TEAMModelOS.Extension/HTEX.Screen/Controllers/ScreenController.cs
  4. 2 2
      TEAMModelOS.Extension/HTEX.Screen/Dockerfile
  5. 6 1
      TEAMModelOS.Extension/HTEX.Screen/HTEX.Screen.csproj
  6. 二進制
      TEAMModelOS.Extension/HTEX.Screen/JsonFiles/ip2region.db
  7. 42708 0
      TEAMModelOS.Extension/HTEX.Screen/JsonFiles/latlng.json
  8. 80 0
      TEAMModelOS.Extension/HTEX.Screen/Models/ChatRequest.cs
  9. 11 0
      TEAMModelOS.Extension/HTEX.Screen/Models/ChatResponseVO.cs
  10. 9 0
      TEAMModelOS.Extension/HTEX.Screen/Models/ErrorViewModel.cs
  11. 16 0
      TEAMModelOS.Extension/HTEX.Screen/Models/ModelTypeEnum.cs
  12. 151 5
      TEAMModelOS.Extension/HTEX.Screen/Program.cs
  13. 146 0
      TEAMModelOS.Extension/HTEX.Screen/Service/MLService.cs
  14. 53 0
      TEAMModelOS.Extension/HTEX.Screen/Service/ScreenPDFSub.cs
  15. 87 0
      TEAMModelOS.Extension/HTEX.Screen/Service/ScreenPDFSubTest.cs
  16. 265 0
      TEAMModelOS.Extension/HTEX.Screen/Service/ScreenService.cs
  17. 86 0
      TEAMModelOS.Extension/HTEX.Screen/Service/VisitSettleJob.cs
  18. 86 0
      TEAMModelOS.Extension/HTEX.Screen/Services/AiAppServiceImpl.cs
  19. 153 0
      TEAMModelOS.Extension/HTEX.Screen/Services/ChatGlmServiceImpl.cs
  20. 154 0
      TEAMModelOS.Extension/HTEX.Screen/Services/ErnieBotServiceImpl.cs
  21. 15 0
      TEAMModelOS.Extension/HTEX.Screen/Services/IAiAppService.cs
  22. 14 0
      TEAMModelOS.Extension/HTEX.Screen/Services/IModelService.cs
  23. 137 0
      TEAMModelOS.Extension/HTEX.Screen/Services/QianWenServiceImpl.cs
  24. 276 0
      TEAMModelOS.Extension/HTEX.Screen/Services/SparkDeskServiceImpl.cs

+ 5 - 7
TEAMModelOS.Extension/HTEX.Screen/Controllers/ChatController.cs

@@ -4,18 +4,17 @@ using Microsoft.AspNetCore.Mvc;
 using System.Diagnostics.Tracing;
 using System.Net;
 
-namespace HTEX.Screen.Controllers
+namespace HTEXGpt.Controllers
 {
     [ProducesResponseType(StatusCodes.Status200OK)]
     [ProducesResponseType(StatusCodes.Status400BadRequest)]
     [Route("chat")]
     [ApiController]
-    public class ChatController : ControllerBase
+    public class ChatController:ControllerBase
     {
         private static int _eventCounter = 0;
         private readonly IAiAppService _aiAppService;
-        public ChatController(IAiAppService aiAppService)
-        {
+        public ChatController(IAiAppService aiAppService) {
             _aiAppService=aiAppService;
         }
         /// <summary>
@@ -26,9 +25,8 @@ namespace HTEX.Screen.Controllers
         /// <param name="dto"></param>
         /// <returns></returns>
         [HttpPost("message")]
-        public async Task<IActionResult> Message(ChatRequest dto)
-        {
-            var chatResponse = await _aiAppService.ChatMessage(dto.modelType, dto, HttpContext, Response);
+        public async Task<IActionResult > Message(ChatRequest dto) {
+        var chatResponse =   await  _aiAppService.ChatMessage(dto.modelType, dto, HttpContext, Response);
             return Ok(new { response = chatResponse });
         }
     }

+ 0 - 207
TEAMModelOS.Extension/HTEX.Screen/Controllers/IndexController.cs

@@ -1,207 +0,0 @@
-using Azure.Storage.Blobs.Models;
-using Azure.Storage.Blobs.Specialized;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Cors;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Options;
-using System.Collections.Concurrent;
-using System.Configuration;
-using System.Drawing;
-using System.IO;
-using System.Net;
-using System.Runtime.InteropServices;
-using System.ServiceModel.Channels;
-using System.Text;
-using System.Text.Json;
-using System.Text.RegularExpressions;
-using System.Web;
-using TEAMModelOS.SDK;
-using TEAMModelOS.SDK.DI;
-using static TEAMModelOS.SDK.Models.Service.SystemService;
-using TEAMModelOS.SDK.Extension;
-using TEAMModelOS.SDK.Models.Service;
-namespace HTEX.Screen.Controllers
-{
-    [ApiController]
-    [Route("api")]
-    public class IndexController : ControllerBase
-    {
-        private readonly DingDing _dingDing;
-        private readonly IHttpClientFactory _httpClient;
-        private readonly IConfiguration _configuration;
-        private readonly AzureStorageFactory _azureStorage;
-        private readonly AzureRedisFactory _azureRedis;
-        private readonly IPSearcher _ipSearcher;
-        private readonly Region2LongitudeLatitudeTranslator _longitudeLatitudeTranslator;
-        public IndexController(AzureRedisFactory azureRedis, Region2LongitudeLatitudeTranslator longitudeLatitudeTranslator, IHttpClientFactory httpClient, IConfiguration configuration, AzureStorageFactory azureStorage, IPSearcher searcher, DingDing dingDing)
-        {
-            _httpClient = httpClient;
-            _configuration = configuration;
-            _azureStorage = azureStorage;
-            _ipSearcher = searcher;
-            _dingDing = dingDing;
-            _azureRedis=azureRedis;
-            _longitudeLatitudeTranslator = longitudeLatitudeTranslator;
-        }
-        /// <summary>
-        ///  上传到blob
-        /// </summary>
-        /// <param name="request"></param>
-        /// <returns></returns>
-        [HttpPost("http-log")]
-
-        [RequestSizeLimit(102_400_000_00)] //最大10000m左右
-        public async Task<IActionResult> HttpLog(JsonElement json)
-        {
-            try
-            {
-                string data = json.ToJsonString();
-                var gmt8Time = DateTimeOffset.Now.GetGMTTime(8);
-                var appendBlob = _azureStorage.GetBlobContainerClient("0-service-log").GetAppendBlobClient($"http-log/{gmt8Time:yyyy}/{gmt8Time:MM}/{gmt8Time:dd}/{gmt8Time:HH}.log");
-                if (!await appendBlob.ExistsAsync())
-                {
-                    await appendBlob.CreateAsync();
-                }
-                using (var stream = new MemoryStream(Encoding.UTF8.GetBytes($"{data},\n")))
-                {
-                    await appendBlob.AppendBlockAsync(stream);
-                }
-                return Ok(new { code = 1 });
-            }
-            catch (Exception ex)
-            {
-                return Ok(new { code = 1 });
-            }
-        }
-
-        [HttpPost("report-api-settle")]
-
-        [AllowAnonymous]
-        [RequestSizeLimit(102_400_000_00)] //最大10000m左右
-        public async Task<IActionResult> ReportApiSettle(JsonElement json)
-        {
-
-            List<string> times = json.GetProperty("times").ToObject<List<string>>();
-
-            return Ok(new { });
-        }
-        [HttpPost("report-api-reload")]
-
-        [AllowAnonymous]
-        [RequestSizeLimit(102_400_000_00)] //最大10000m左右
-        public async Task<IActionResult> ReportApiTest(JsonElement json)
-        {
-            try
-            {
-                List<string> times = json.GetProperty("times").ToObject<List<string>>();
-                foreach (var timeDate in times)
-                {
-                    if (DateTimeOffset.TryParse(timeDate, out DateTimeOffset date))
-                    {
-                        var gmt8Time = date.GetGMTTime(8);
-                        var nowGmt8Time = DateTimeOffset.Now.GetGMTTime(8);
-                        List<string> logs = await _azureStorage.GetBlobContainerClient("0-service-log").List($"http-log/{date:yyyy}/{date:MM}/{date:dd}");
-                        List<HttpLog> httpLogs = new List<HttpLog>();
-                        foreach (var log in logs)
-                        {
-                            string nowPath = $"{nowGmt8Time:yyyy/MM/dd/HH}.log";
-                            if (!log.Contains("index.log")  && !log.Contains(".json") &&!log.Contains(nowPath))
-                            {
-                                BlobDownloadResult result = await _azureStorage.GetBlobContainerClient("0-service-log").GetBlobBaseClient(log).DownloadContentAsync();
-                                var content = result.Content.ToString();
-                                content= content.Substring(0, content.Length-2);
-                                if (content.EndsWith("}"))
-                                {
-                                    content=$"[{content}]";
-                                }
-                                else
-                                {
-                                    content=$"[{content}}}]";
-                                }
-                                httpLogs.AddRange(content.ToObject<List<HttpLog>>());
-                            }
-                        }
-                        (ConcurrentBag<ApiVisit> visits, ConcurrentBag<(string uuid, HttpLog httpLog, List<string> tmdid, List<string> school)> uuidInfo)   =
-                            await SystemService.ConvertHttpLog(httpLogs, _azureRedis, _ipSearcher, _longitudeLatitudeTranslator, gmt8Time, true);
-                        if (visits!=null  && visits.Count>0)
-                        {
-                            var appendDayBlob = _azureStorage.GetBlobContainerClient("0-service-log").GetAppendBlobClient($"http-log/{gmt8Time:yyyy}/{gmt8Time:MM}/{gmt8Time:dd}/index.log");
-                            if (!await appendDayBlob.ExistsAsync())
-                            {
-
-                                await appendDayBlob.CreateAsync();
-                            }
-                            else
-                            {
-                                await appendDayBlob.DeleteAsync();
-                                await appendDayBlob.CreateAsync();
-                            }
-                            int maxSize = 4*1024 * 1024; // 1M
-                            List<string> parts = new List<string>();
-                            StringBuilder sb = new StringBuilder();
-
-                            foreach (var item in visits)
-                            {
-                                string jsonString = $"{item.ToJsonString()},\n";
-                                int currentSize = Encoding.UTF8.GetByteCount(sb.ToString());
-
-                                if (currentSize + Encoding.UTF8.GetByteCount(jsonString) > maxSize)
-                                {
-                                    parts.Add(sb.ToString());
-                                    sb.Clear();
-                                }
-
-                                sb.Append(jsonString);
-                            }
-
-                            if (sb.Length > 0)
-                            {
-                                parts.Add(sb.ToString());
-                            }
-
-                            foreach (string part in parts)
-                            {
-                                using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(part)))
-                                {
-                                    await appendDayBlob.AppendBlockAsync(stream);
-                                }
-                            }
-
-
-                            //using (var stream = new MemoryStream(Encoding.UTF8.GetBytes($"{sb}")))
-                            //{
-                            //    await appendDayBlob.AppendBlockAsync(stream);
-                            //}
-                        }
-                    }
-                }
-
-                //全网:用户访问数,学校访问数,学生访问数,不同业务访问量,
-                return Ok(new { });
-            }
-            catch (Exception ex)
-            {
-                await _dingDing.SendBotMsg($"{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
-            }
-            return Ok();
-        }
-
-        /// <summary>
-        /// 使用时长,一天内累计
-        /// </summary>
-        /// <param name="json"></param>
-        /// <returns></returns>
-        [HttpPost("report-api")]
-
-        [AllowAnonymous]
-        [RequestSizeLimit(102_400_000_00)] //最大10000m左右
-        public async Task<IActionResult> ReportApi(JsonElement json)
-        {
-            var force = json.GetProperty("force").GetString();
-            List<string> times = json.GetProperty("times").ToObject<List<string>>();
-            await SystemService.VisitSettle(times, _azureStorage, _azureRedis, _longitudeLatitudeTranslator, _ipSearcher, $"{force}");
-            return Ok(new { code = 200 });
-        }
-
-    }
-}

文件差異過大導致無法顯示
+ 294 - 2
TEAMModelOS.Extension/HTEX.Screen/Controllers/ScreenController.cs


+ 2 - 2
TEAMModelOS.Extension/HTEX.Screen/Dockerfile

@@ -3,8 +3,8 @@
 FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
 USER app
 WORKDIR /app
-EXPOSE 8080
-EXPOSE 8081
+EXPOSE 80
+EXPOSE 443
 
 FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
 ARG BUILD_CONFIGURATION=Release

+ 6 - 1
TEAMModelOS.Extension/HTEX.Screen/HTEX.Screen.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk.Web">
+<Project Sdk="Microsoft.NET.Sdk.Web">
 
   <PropertyGroup>
     <TargetFramework>net8.0</TargetFramework>
@@ -11,6 +11,11 @@
 
   <ItemGroup>
     <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
+	<PackageReference Include="Hangfire" Version="1.8.11" />
+	<PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
+	<PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.3" />
+	<PackageReference Include="PuppeteerSharp" Version="7.1.0" />
+	<PackageReference Include="Microsoft.ML" Version="3.0.1" />
   </ItemGroup>
 
   <ItemGroup>

二進制
TEAMModelOS.Extension/HTEX.Screen/JsonFiles/ip2region.db


文件差異過大導致無法顯示
+ 42708 - 0
TEAMModelOS.Extension/HTEX.Screen/JsonFiles/latlng.json


+ 80 - 0
TEAMModelOS.Extension/HTEX.Screen/Models/ChatRequest.cs

@@ -0,0 +1,80 @@
+using System.Net;
+using System.Text.Json;
+
+namespace HTEXGpt.Models
+{
+    public class ChatRequest
+    {
+        /// <summary>
+        /// @ApiModelProperty(value = "聊天上下文信息", notes = "
+        /// (1)最后一个message为当前请求的信息,前面的message为历史对话信息\n 
+        /// (2)成员数目必须为奇数\n 
+        /// (3)示例中message中的role值分别为user、assistant;
+        /// 奇数位message中的role值为user;
+        /// 偶数位值为assistant  example = "[{\"role\":\"user\",\"content\":\"你好\"},{\"role\":\"assistant\",\"content\":\"需要什么帮助\"},{\"role\":\"user\",\"content\":\"自我介绍下\"}]")
+        /// @NotNull(message = "聊天上下文信息不能为空")
+        /// </summary>
+        public List<MessageDTO> messages{  get; set; }= new List<MessageDTO>();
+
+        /// <summary>
+        ///  @ApiModelProperty(value = "模型人设", notes = "主要用于人设设定,例如,你是xxx公司制作的AI助手,最大20000字符", example = "你是一名天气助手,需要提供天气查询服务")
+        /// </summary>
+        public string? system { get; set; }
+
+        /// <summary>
+        ///   @ApiModelProperty(value = "请求参数", notes = "请求参数", example = "{\"key\":\"value\"}")
+        /// </summary>
+        public ChatParam @params { get; set; }
+        /// <summary>
+        /// 模型类型
+        /// </summary>
+        public string? modelType { get; set; }
+        public string? uid { get; set; }
+    }
+    public class MessageDTO
+    {
+        /// <summary>
+        ///  @ApiModelProperty(value = "角色", notes = "说明: user-用户, assistant-助手", example = "user")
+        /// </summary>
+        public string? role { get; set; }
+        /// <summary>
+        /// @ApiModelProperty(value = "消息内容", notes = "说明: 消息内容", example = "你好")
+        /// </summary>
+        public string? content { get; set; }
+    }
+    public class ChatParam 
+    {
+        //  public int max_tokens { get; set; }
+
+        /// <summary>
+        /// 通义千问0-2 ,讯飞星火0-1,默认0.5  ,文心一言 0-1,默认0.95
+        /// </summary>
+        public double temperature { get; set; }
+        /// <summary>
+        /// 通义千问0-2 ,讯飞星火0-1,文心一言 0-1,默认0.7
+        /// </summary>
+        public double top_p { get; set; }
+       // public double presencePenalty { get; set; }
+      //  public double frequencyPenalty { get; set; }
+    }
+
+    public class ChatResponse
+    {
+        public string? result{ get; set; }
+        /// <summary>
+        /// 总花费
+        /// </summary>
+        public double total_tokens { get; set; }
+        /// <summary>
+        /// 输出花费
+        /// </summary>
+        public double completion_tokens { get; set; }
+        /// <summary>
+        /// 输入花费
+        /// </summary>
+        public double prompt_tokens { get; set; }
+        public long time { get; set; }
+        public string? error {  get; set; }
+        public HttpStatusCode statusCode { get; set; }
+    }
+}

+ 11 - 0
TEAMModelOS.Extension/HTEX.Screen/Models/ChatResponseVO.cs

@@ -0,0 +1,11 @@
+namespace HTEXGpt.Models
+{
+    public class ChatResponseVO
+    {
+        /// <summary>
+        ///  @ApiModelProperty(value = "结果", notes = "结果")
+        /// </summary>
+        public string? result {  get; set; }
+        public int token {  get; set; }
+    }
+}

+ 9 - 0
TEAMModelOS.Extension/HTEX.Screen/Models/ErrorViewModel.cs

@@ -0,0 +1,9 @@
+namespace HTEXGpt.Models
+{
+    public class ErrorViewModel
+    {
+        public string? RequestId { get; set; }
+
+        public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
+    }
+}

+ 16 - 0
TEAMModelOS.Extension/HTEX.Screen/Models/ModelTypeEnum.cs

@@ -0,0 +1,16 @@
+using System.ComponentModel;
+
+namespace HTEXGpt.Models
+{
+    public enum ModelTypeEnum
+    {
+        [Description("文心一言")]
+        ErnieBot,
+        [Description("讯飞星火")]
+        SparkDesk,
+        [Description("智谱清言")]
+        ChatGlm,
+        [Description("通义千问")]
+        QianWen
+    }
+}

+ 151 - 5
TEAMModelOS.Extension/HTEX.Screen/Program.cs

@@ -1,3 +1,16 @@
+using HTEXGpt.Services;
+using Microsoft.AspNetCore.Authentication.JwtBearer;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.IdentityModel.Tokens;
+using System.IdentityModel.Tokens.Jwt;
+using System.Net.WebSockets;
+using TEAMModelOS.SDK.DI;
+using Hangfire;
+using Hangfire.Dashboard.BasicAuthorization;
+using Hangfire.Redis.StackExchange;
+using HTEXScreen.Service.CoreHangfire;
+using HTEXScreen.Service;
+using TEAMModelOS.SDK;
 namespace HTEX.Screen
 {
     public class Program
@@ -6,21 +19,154 @@ namespace HTEX.Screen
         {
             var builder = WebApplication.CreateBuilder(args);
 
-            // Add services to the container.
 
+            // Add services to the container.
+            JwtSecurityTokenHandler.DefaultMapInboundClaims = false;
+            builder.Services.AddAuthentication(options => options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme)
+                .AddJwtBearer(options => //AzureADJwtBearer
+                {
+                    //options.SaveToken = true; //驗證令牌由服務器生成才有效,不適用於服務重啟或分布式架構
+                    options.Authority ="https://login.chinacloudapi.cn/4807e9cf-87b8-4174-aa5b-e76497d7392b/v2.0";// builder.Configuration["Option:Authority"];
+                    options.Audience = "72643704-b2e7-4b26-b881-bd5865e7a7a5";//builder.Configuration["Option:Audience"];
+                    options.RequireHttpsMetadata = true;
+                    options.TokenValidationParameters = new TokenValidationParameters
+                    {
+                        RoleClaimType = "roles",
+                        //ValidAudiences = new string[] { builder.Configuration["Option:Audience"], $"api://{builder.Configuration["Option:Audience"]}" }
+                        ValidAudiences = new string[] { "72643704-b2e7-4b26-b881-bd5865e7a7a5", $"api://72643704-b2e7-4b26-b881-bd5865e7a7a5" }
+                    };
+                    options.Events = new JwtBearerEvents();
+                    //下列事件有需要紀錄則打開
+                    //options.Events.OnMessageReceived = async context => { await Task.FromResult(0); };
+                    //options.Events.OnForbidden = async context => { await Task.FromResult(0); };
+                    //options.Events.OnChallenge = async context => { await Task.FromResult(0); };
+                    //options.Events.OnAuthenticationFailed = async context => { await Task.FromResult(0); };
+                    options.Events.OnTokenValidated = async context =>
+                    {
+                        if (!context.Principal.Claims.Any(x => x.Type.Equals("http://schemas.microsoft.com/identity/claims/scope")) //ClaimConstants.Scope
+                        && !context.Principal.Claims.Any(y => y.Type.Equals("roles"))) //ClaimConstants.Roles //http://schemas.microsoft.com/ws/2008/06/identity/claims/role
+                        {
+                            //TODO 需處理額外授權非角色及範圍的訪問異常紀錄
+                            throw new UnauthorizedAccessException("Neither scope or roles claim was found in the bearer token.");
+                        }
+                        await Task.FromResult(0);
+                    };
+                });
             builder.Services.AddControllers();
+#if DEBUG
+            builder.WebHost.UseUrls(new[] { "https://*:7298" });
+#endif
+            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+            builder.Services.AddEndpointsApiExplorer();
+            //builder.Services.AddSwaggerGen();
+            builder.Services.AddHttpClient();
+            string StorageConnectionString = builder.Configuration.GetValue<string>("Azure:Storage:ConnectionString");
+            string StorageConnectionStringTest = builder.Configuration.GetValue<string>("Azure:Storage:ConnectionString-Test");
 
-            var app = builder.Build();
+            string ServiceBusConnectionString = builder.Configuration.GetValue<string>("Azure:ServiceBus:ConnectionString");
+            string ServiceBusConnectionStringTest = builder.Configuration.GetValue<string>("Azure:ServiceBus:ConnectionString-Test");
 
-            // Configure the HTTP request pipeline.
+            string RedisConnectionString = builder.Configuration.GetValue<string>("Azure:Redis:ConnectionString");
+            string RedisConnectionStringTest = builder.Configuration.GetValue<string>("Azure:Redis:ConnectionString-Test");
 
-            app.UseHttpsRedirection();
+            string CosmosConnectionString = builder.Configuration.GetValue<string>("Azure:Cosmos:ConnectionString");
+            string CosmosConnectionStringTest = builder.Configuration.GetValue<string>("Azure:Cosmos:ConnectionString-Test");
 
-            app.UseAuthorization();
+            //Storage
+            builder.Services.AddAzureStorage(StorageConnectionString, "Default");
+            builder.Services.AddAzureStorage(StorageConnectionStringTest, "Test");
+            //ServiceBus
+            builder.Services.AddAzureServiceBus(ServiceBusConnectionString, "Default");
+            builder.Services.AddAzureServiceBus(ServiceBusConnectionStringTest, "Test");
+            //Redis
+            builder.Services.AddAzureRedis(RedisConnectionString, "Default");
+            builder.Services.AddAzureRedis(RedisConnectionStringTest, "Test");
+            //Cosmos
+            builder.Services.AddAzureCosmos(CosmosConnectionString, "Default");
+            builder.Services.AddAzureCosmos(CosmosConnectionStringTest, "Test");
 
+            builder.Services.AddHostedService<ScreenPDFSub>();
+            builder.Services.AddHostedService<ScreenPDFSubTest>();
+            builder.Services.AddHttpContextAccessor();
+            builder.Services.AddHttpClient<DingDing>();
+            string path = $"{builder.Environment.ContentRootPath}/JsonFiles";
+            builder.Services.TryAddSingleton(new Region2LongitudeLatitudeTranslator(path));
+            builder.Services.AddIPSearcher(path);
+            builder.Services.AddScoped<IAiAppService, AiAppServiceImpl>();
+            builder.Services.AddScoped<SparkDeskServiceImpl>();
+            builder.Services.AddScoped<QianWenServiceImpl>();
+            builder.Services.AddScoped<ErnieBotServiceImpl>();
+            builder.Services.AddScoped<ClientWebSocket>();
+            builder.Services.AddScoped<ChatGlmServiceImpl>();
+            builder.Services.AddHttpClient();
+            builder.Services.AddCors(options =>
+            {
+                options.AddPolicy("MyAllowSpecificOrigins",
+                builder =>
+                {
+                    builder.WithOrigins("http://teammodelos-test.chinacloudsites.cn",
+                                        "https://www.teammodel.cn", "https://localhost:5001",
+                                        "http://localhost:5000", "http://localhost:64524",
+                                        "https://localhost:44341", "https://localhost:8888", "http://localhost:8888", "https://localhost:8081", "http://localhost:8081")
+                    .AllowAnyHeader()
+                    .AllowAnyMethod();
+                });
+            });
+            // 添加CORS服务
+            builder.Services.AddCors(options =>
+            {
+                options.AddPolicy("AllowSpecificOrigin", builder =>
+                {
+#if DEBUG
+                    builder.AllowAnyOrigin() // 添加允许的源
+                           .AllowAnyHeader() // 允许任何请求标头
+                                             //  .AllowCredentials()
+                           ; // 允许包含凭据
+#else
+                    //builder.WithOrigins("https://teammodeltest.blob.core.chinacloudapi.cn",  "https://teammodelos.blob.core.chinacloudapi.cn") // 添加允许的源       .AllowAnyMethod() // 允许任何请求方法
+                    //       .AllowAnyHeader() // 允许任何请求标头
+                    //       .AllowCredentials(); // 允许包含凭据
+#endif
+                });
+            });
+            builder.Services.AddHangfire(config => {
+                config.UseRedisStorage(builder.Configuration.GetValue<string>("Azure:Redis:ConnectionString"), new RedisStorageOptions { Db=1 });
+            });
+            builder.Services.AddHangfireServer();
+            var app = builder.Build();
+            if (app.Environment.IsDevelopment())
+            {
+                //app.UseSwagger();
+                //app.UseSwaggerUI();
+            }
 
+
+            app.UseRouting();
+
+            app.UseCors("MyAllowSpecificOrigins"); //使用跨域設定
+            app.UseHttpsRedirection(); //開發中暫時關掉
+            app.UseAuthentication();
+            app.UseAuthorization();
             app.MapControllers();
+            app.UseHangfireDashboard("/cdhabook-hangfire", new DashboardOptions
+            {
+                Authorization = new[] { new BasicAuthAuthorizationFilter(new BasicAuthAuthorizationFilterOptions
+        {
+            RequireSsl = false,
+            SslRedirect = false,
+            LoginCaseSensitive = true,
+            Users = new []
+            {
+                new BasicAuthAuthorizationUser
+                {
+                    Login = "cdhabook",
+                    PasswordClear =  "cdhabook_abc123"
+                }
+            }
 
+        }) }
+            });
+            RecurringJob.AddOrUpdate<VisitSettleJob>("1.访问日志记录统计VisitSettleJob,每小时的第2分钟,统计上个小时的", job => job.Run(), Cron.Hourly(2), options: new RecurringJobOptions { TimeZone=TimeZoneInfo.Local });
             app.Run();
         }
     }

+ 146 - 0
TEAMModelOS.Extension/HTEX.Screen/Service/MLService.cs

@@ -0,0 +1,146 @@
+
+using Microsoft.ML;
+using Microsoft.ML.Data;
+
+namespace HTEX.Complex.Service
+{
+
+    public static class MLService
+    {
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="datas">数据需要去掉0</param>
+        /// <param name="numberOfClusters"></param>
+        /// <returns></returns>
+        public static List<ClusterData> KMeans(double[] datas, int numberOfClusters = 5)
+        {
+            List<DataPoint> data = new List<DataPoint>();
+            foreach (var d in datas)
+            {
+                data.Add(new DataPoint { Feature = d });
+            }
+
+            // 定义数据视图  
+            var mlContext = new MLContext();
+            var dataView = mlContext.Data.LoadFromEnumerable(data);
+            // 定义聚类管道  
+            var pipeline = mlContext.Transforms.Concatenate("Features", new[] { "Feature" })
+                                .Append(mlContext.Clustering.Trainers.KMeans(numberOfClusters: numberOfClusters)); // 假设我们想要将数据分成3个集群  
+                                                                                                                   // 训练模型  
+            var model = pipeline.Fit(dataView);
+
+            // 转换数据以获取聚类结果  
+            var predictions = model.Transform(dataView);
+
+            // 提取聚类结果  
+            var inMemoryCollection = mlContext.Data.CreateEnumerable<ClusterPrediction>(predictions, reuseRowObject: false);
+
+
+
+            // 打印聚类结果  
+            //var clusterSizes = new int[3]; // 假设有3个聚类  
+            int index = 0;
+            List<ClusterData> clusterDatas = new List<ClusterData>();
+            foreach (var prediction in inMemoryCollection)
+            {
+                //Console.WriteLine($"Data point: {data[index].Feature}, Cluster: {prediction.ClusterId}");
+                var clusterData = clusterDatas.Find(x => x.ClusterId.Equals(prediction.ClusterId));
+                if (clusterData!=null)
+                {
+                    clusterData.count +=1;
+                    clusterData.datas.Add(data[index].Feature);
+                    clusterData.avg=clusterData.datas.Sum() / clusterData.datas.Count();
+                }
+                else
+                {
+                    clusterDatas.Add(new ClusterData { avg=data[index].Feature, count=1, ClusterId=prediction.ClusterId, datas=new List<double> { data[index].Feature } });
+                }
+                index++;
+                //计算每个聚类的数据点数量
+                //clusterSizes[prediction.ClusterId-1]++;
+            }
+            // 预测聚类
+
+            // 确定最密集的部分  
+            // 这通常需要对聚类结果进行分析,比如计算每个聚类的平均距离、大小等  
+            // 在这里,你可以通过比较不同聚类的数据点数量或计算聚类中心周围的密度来估计哪个是最密集的  
+
+
+
+
+            // 找出最大的聚类  
+            //  var maxClusterIndex = clusterSizes.ToList().IndexOf(clusterSizes.Max());
+            //Console.WriteLine($"The densest cluster is cluster {maxClusterIndex} with {clusterSizes[maxClusterIndex]} data points.");
+
+            // 你还可以进一步分析聚类的特性,比如找出聚类中心、计算聚类内的方差等  
+
+            return clusterDatas;
+        }
+       /// <summary>
+       /// 
+       /// </summary>
+       /// <param name="datas"></param>
+       /// <param name="numberOfClusters"></param>
+       /// <param name="dropPercent">最大平均数的聚类与数量最多的聚类数量的落差小于30% 则以更高的为准</param>
+       /// <returns></returns>
+        public static ClusterData GetNormalCluster (double[] datas, int numberOfClusters = 5,double dropPercent=0.3)
+        {
+            List<ClusterData> clusterDatas = KMeans(datas, numberOfClusters);
+            clusterDatas=clusterDatas.OrderByDescending(dr => dr.count).ToList();
+            ClusterData clusterData = FindSatisfactoryRecord(clusterDatas, 0, dropPercent);
+            return clusterData;
+        }
+
+        static ClusterData FindSatisfactoryRecord(List<ClusterData> data, int currentIndex,double dropPercent)
+        {        // 如果当前索引小于0,说明已经到达列表开头,返回null
+            if (currentIndex < 0) { return null; }
+
+            // 获取当前数据
+            ClusterData current = data.ElementAt(currentIndex);
+            if (currentIndex+1>=data.Count())
+            {
+                return current;
+            }
+            else
+            {
+                ClusterData next = data.ElementAt(currentIndex +1);        // 检查平均值和人数差是否满足条件
+                if (current.avg > next.avg)
+                {
+                    return current;
+                }
+                else
+                {
+                    if ((current.count- next.count)/current.count>=dropPercent)
+                    {
+                        return current;
+                    }
+                    else
+                    { // 递归调用,继续向前比较
+                        return FindSatisfactoryRecord(data, currentIndex + 1, dropPercent);
+                    }
+                }
+            }
+        }
+    }
+    // 定义数据模型  
+    public class DataPoint
+    {
+        public double Feature { get; set; }
+    }
+    // 聚类预测类  
+    public class ClusterPrediction
+    {
+        [ColumnName("PredictedLabel")]
+        public uint ClusterId;
+
+        // 你可以添加其他预测列,比如距离聚类中心的距离等  
+    }
+    public class ClusterData
+    {
+        public List<double> datas = new List<double>();
+        public uint ClusterId { get; set; }
+        public int count { get; set; }
+        public double avg { get; set; }
+    }
+}

+ 53 - 0
TEAMModelOS.Extension/HTEX.Screen/Service/ScreenPDFSub.cs

@@ -0,0 +1,53 @@
+using Azure.Messaging.ServiceBus;
+using System.Text.Json;
+using TEAMModelOS.SDK.DI;
+
+namespace HTEXScreen.Service
+{
+    public class ScreenPDFSub : BackgroundService, IDisposable
+    {
+        private readonly AzureStorageFactory _azureStorage;
+        private readonly HttpClient _httpClient;
+        private readonly AzureRedisFactory _azureRedisFactory;
+        private readonly AzureCosmosFactory _azureCosmosFactory;
+        //消息订阅
+        private const string Channel = "screenpdf";
+        private readonly    ServiceBusReceiver _receiver;
+        public static SpinWait spinWait = new SpinWait(); // 构造SpinWait实例
+        public ScreenPDFSub(AzureRedisFactory azureRedisFactory, AzureCosmosFactory azureCosmosFactory, HttpClient httpClient, AzureStorageFactory azureStorage,AzureServiceBusFactory azureService)
+        { 
+            _receiver = azureService.GetServiceBusClient("Default").CreateReceiver(Channel, new ServiceBusReceiverOptions { ReceiveMode= ServiceBusReceiveMode.PeekLock});
+            _httpClient = httpClient;
+            _azureStorage = azureStorage;
+            _azureRedisFactory = azureRedisFactory;
+            _azureCosmosFactory = azureCosmosFactory;
+        }
+
+        protected async  override Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            while (true) 
+            {
+                IReadOnlyList<ServiceBusReceivedMessage> receivedMessages = await _receiver.ReceiveMessagesAsync(maxMessages: 1);
+                //单条数据 5 分钟未释放。进入死信
+                foreach (ServiceBusReceivedMessage receivedMessage in receivedMessages)
+                {
+                    try
+                    {
+                        string body = receivedMessage.Body.ToString();
+                        ScreenshotDto? screenshot = JsonSerializer.Deserialize<ScreenshotDto>(body);
+                        await _receiver.CompleteMessageAsync(receivedMessage);
+                        List<(string name ,string url )> urls=  await ScreenService.ScreenshotPdf(screenshot, _azureStorage);
+                        await ScreenService.UpdateStuArtPDF(urls.Select(z => z.url), screenshot, _azureRedisFactory, _azureCosmosFactory);
+                       
+                    } catch
+                    {
+                        //失败则放回队列死信中 
+                        await _receiver.DeadLetterMessageAsync(receivedMessage);
+                    }
+                }
+                //cpu空转一次
+                spinWait.SpinOnce();
+            } 
+        }
+    }
+}

文件差異過大導致無法顯示
+ 87 - 0
TEAMModelOS.Extension/HTEX.Screen/Service/ScreenPDFSubTest.cs


文件差異過大導致無法顯示
+ 265 - 0
TEAMModelOS.Extension/HTEX.Screen/Service/ScreenService.cs


+ 86 - 0
TEAMModelOS.Extension/HTEX.Screen/Service/VisitSettleJob.cs

@@ -0,0 +1,86 @@
+using Azure.Storage.Blobs.Models;
+using Azure.Storage.Blobs.Specialized;
+using Microsoft.Azure.Cosmos.Linq;
+using System.Collections.Concurrent;
+using System.Text;
+using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.Models.Service;
+using static TEAMModelOS.SDK.Models.Service.SystemService;
+using TEAMModelOS.SDK.Extension;
+namespace HTEXScreen.Service.CoreHangfire
+{
+    public class VisitSettleJob
+    {
+        private readonly IConfiguration _configuration;
+        private readonly AzureRedisFactory _azureRedis;
+        private readonly AzureCosmosFactory _azureCosmos;
+       // private readonly AzureServiceBusFactory _serviceBus;
+        private readonly AzureStorageFactory _azureStorage;
+        private readonly DingDing _dingDing;
+        private readonly  IPSearcher _ipSearcher;
+        private readonly Region2LongitudeLatitudeTranslator _longitudeLatitudeTranslator;
+        public VisitSettleJob(Region2LongitudeLatitudeTranslator longitudeLatitudeTranslator,IPSearcher ipSearcher, DingDing dingDing,IConfiguration configuration, AzureRedisFactory azureRedis, AzureCosmosFactory azureCosmos,  AzureStorageFactory azureStorage)
+        {
+            _azureCosmos = azureCosmos;
+            _azureRedis = azureRedis;
+            _configuration = configuration;
+          //  _serviceBus = azureServiceBus;
+            _azureStorage = azureStorage;
+            _dingDing=dingDing;
+            _ipSearcher=ipSearcher;
+            _longitudeLatitudeTranslator=longitudeLatitudeTranslator;
+        }
+        public async Task Run()
+        {
+            var gmt8Time = DateTimeOffset.Now.GetGMTTime(8).AddHours(-1);
+            await _dingDing.SendBotMsg($"{DateTimeOffset.Now.GetGMTTime(8):yyyy-MM-dd HH:mm:ss}Http日志访问统计开始,统计时间:{gmt8Time:yyyy-MM-dd HH}", GroupNames.成都开发測試群組);
+            try
+            {
+              //  string location = _configuration.GetValue<string>("Option:Location");
+                //获取上一个小时的数据
+                var appendBlob = _azureStorage.GetBlobContainerClient("0-service-log").GetAppendBlobClient($"http-log/{gmt8Time:yyyy}/{gmt8Time:MM}/{gmt8Time:dd}/{gmt8Time:HH}.log");
+                if (await appendBlob.ExistsAsync())
+                {
+                    BlobDownloadResult result = await appendBlob.DownloadContentAsync();
+                    var content = result.Content.ToString();
+                    content= content.Substring(0, content.Length-2);
+                    if (content.EndsWith("}"))
+                    {
+                        content=$"[{content}]";
+                    }
+                    else
+                    {
+                        content=$"[{content}}}]";
+                    }
+                    var httpLogList = content.ToObject<List<HttpLog>>();
+                    (ConcurrentBag<ApiVisit> vists, ConcurrentBag<(string uuid, HttpLog httpLog, List<string> tmdid, List<string> school)> uuidInfo)   =
+                        await SystemService.ConvertHttpLog(httpLogList, _azureRedis, _ipSearcher, _longitudeLatitudeTranslator, gmt8Time);
+                    if (vists!=null  && vists.Count>0)
+                    {
+                        var appendDayBlob = _azureStorage.GetBlobContainerClient("0-service-log").GetAppendBlobClient($"http-log/{gmt8Time:yyyy}/{gmt8Time:MM}/{gmt8Time:dd}/index.log");
+                        if (!await appendDayBlob.ExistsAsync())
+                        {
+                            await appendDayBlob.CreateAsync();
+                        }
+                        StringBuilder sb = new StringBuilder();
+                        foreach (var item in vists)
+                        {
+                            sb.Append($"{item.ToJsonString()},\n");
+
+                        }
+                        using (var stream = new MemoryStream(Encoding.UTF8.GetBytes($"{sb}")))
+                        {
+                            await appendDayBlob.AppendBlockAsync(stream);
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"日志访问统计异常{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+            }
+            await _dingDing.SendBotMsg($"{DateTimeOffset.Now.GetGMTTime(8):yyyy-MM-dd HH:mm:ss}Http日志访问统计结束,统计时间:{gmt8Time:yyyy-MM-dd HH}", GroupNames.成都开发測試群組);
+        }
+    }
+}

+ 86 - 0
TEAMModelOS.Extension/HTEX.Screen/Services/AiAppServiceImpl.cs

@@ -0,0 +1,86 @@
+using HTEXGpt.Models;
+
+namespace HTEXGpt.Services
+{
+    public class AiAppServiceImpl : IAiAppService
+    {
+
+        private readonly IServiceProvider _serviceProvider;
+
+        public AiAppServiceImpl(IServiceProvider  serviceProvider) 
+        {
+            _serviceProvider = serviceProvider;
+        }
+        public async Task<ChatResponse> ChatMessage(string? modelType, ChatRequest dto, HttpContext httpContext,HttpResponse response)
+        {
+            this.CheckMessages(dto.messages);
+            IModelService? modelService = GetModelService(modelType!);
+            return   await   modelService!.ChatMessage(dto,httpContext, response);
+        }
+
+        /// <summary>
+        /// 根据模型类型获取对应的模型服务
+        /// </summary>
+        /// <param name="modelType"></param>
+        /// <returns></returns>
+        /// <exception cref="InvalidOperationException"></exception>
+        private IModelService? GetModelService(string  modelType)
+        {
+            try
+            {
+                // 将字符串转换为枚举值
+                ModelTypeEnum modelTypeEnum = (ModelTypeEnum)Enum.Parse(typeof(ModelTypeEnum), modelType!);
+                // 根据枚举值获取对应的服务类型
+                Type? serviceType = typeof(IModelService).Assembly.GetTypes()
+                    .FirstOrDefault(t => t.IsClass && !t.IsAbstract && t.GetInterfaces().Contains(typeof(IModelService)) && t.Name.StartsWith(modelTypeEnum.ToString()));
+
+                if (serviceType == null)
+                {
+                    throw new InvalidOperationException($"未找到与模型类型 {modelType} 对应的服务。");
+                }
+                // 获取服务实例
+                var s=  _serviceProvider.GetRequiredService(serviceType);
+                var s1 = _serviceProvider.GetService(serviceType);
+                return (IModelService?)_serviceProvider.GetService(serviceType);
+            }
+            catch (ArgumentException e)
+            {
+                throw new InvalidOperationException("模型类型错误", e);
+            }
+        }
+
+        /// <summary>
+        /// 检查消息参数是否符合规范@param messages 消息参数
+        /// </summary>
+        /// <param name="messages"></param>
+        /// <exception cref="RuntimeException"></exception>
+        private void CheckMessages(List<MessageDTO> messages)
+        {
+            if (messages!=null   && messages.Count>0)
+            {
+                // messages参数个数必须为奇数并且奇数个数的消息role必须为user,偶数个数的消息role必须为assistant
+                if (messages.Count() % 2 == 0)
+                {
+                    throw new Exception("messages参数个数必须为奇数");
+                }
+                for (int i = 0; i < messages.Count(); i++)
+                {
+                    if (i % 2 == 0)
+                    {
+                        if (!"user".Equals(messages[i].role))
+                        {
+                            throw new Exception("messages奇数参数的role必须为user");
+                        }
+                    }
+                    else
+                    {
+                        if (!"assistant".Equals(messages[i].role))
+                        {
+                            throw new Exception("messages偶数参数的role必须为assistant");
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 153 - 0
TEAMModelOS.Extension/HTEX.Screen/Services/ChatGlmServiceImpl.cs

@@ -0,0 +1,153 @@
+using HTEXGpt.Models;
+using Microsoft.IdentityModel.Tokens;
+using System.Diagnostics;
+using System.IdentityModel.Tokens.Jwt;
+using System.Net;
+using System.Security.Claims;
+using System.Security.Cryptography;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+using System.Xml.Linq;
+namespace HTEXGpt.Services
+{
+    public class ChatGlmServiceImpl : IModelService
+    {
+        private string apiKey = "2c012f8a5f430c93bff19b24a3428dc2.882B3AzRmv1UZiKt";
+        private string HOST_URL = "https://open.bigmodel.cn/api/paas/v4/chat/completions";
+        private readonly IHttpClientFactory _httpClientFactory;
+        public ChatGlmServiceImpl(IHttpClientFactory httpClientFactory)
+        {
+            _httpClientFactory=httpClientFactory;
+        }
+
+        public async Task<ChatResponse> ChatMessage(ChatRequest dto, HttpContext httpContext, HttpResponse response)
+        {
+            ChatResponse chatResponse = new ChatResponse();
+            Stopwatch stopwatch = Stopwatch.StartNew();
+            try
+            {
+                ChatGlmDTO chatGlmDTO = new ChatGlmDTO();
+                chatGlmDTO.messages = dto.messages;
+                if (!string.IsNullOrWhiteSpace(dto.system))
+                {
+                    chatGlmDTO.messages.Insert(0, new MessageDTO { role="system", content= dto.system });
+                }
+                chatGlmDTO.model="glm-4";
+                //chatGlmDTO.temperature=dto.@params.temperature;
+                //chatGlmDTO.top_p=dto.@params.top_p;
+                chatGlmDTO.user_id= dto.uid;
+                var token = GenerateToken(apiKey);
+                HttpClient httpClient = _httpClientFactory.CreateClient();
+                if (httpClient.DefaultRequestHeaders.Contains("Authorization"))
+                {
+                    httpClient.DefaultRequestHeaders.Remove("Authorization");
+                }
+                httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
+                HttpResponseMessage httpResponse = await httpClient.PostAsJsonAsync(HOST_URL, chatGlmDTO);
+                if (httpResponse.StatusCode.Equals(HttpStatusCode.OK))
+                {
+                    string content = await httpResponse.Content.ReadAsStringAsync();
+                    JsonNode? jsonNode = JsonSerializer.Deserialize<JsonNode>(content);
+                    content= $"{jsonNode?["choices"]?[0]?["message"]?["content"]}";
+                    chatResponse.result = content;
+
+                    var total_tokens = jsonNode?["usage"]?["total_tokens"];
+                    if (total_tokens!= null)
+                    {
+                        chatResponse.total_tokens=int.Parse($"{total_tokens}");
+                    }
+                    var completion_tokens = jsonNode?["usage"]?["completion_tokens"];
+                    if (completion_tokens!= null)
+                    {
+                        chatResponse.completion_tokens=int.Parse($"{completion_tokens}");
+                    }
+
+                    var prompt_tokens = jsonNode?["usage"]?["prompt_tokens"];
+                    if (prompt_tokens!= null)
+                    {
+                        chatResponse.prompt_tokens=int.Parse($"{prompt_tokens}");
+                    }
+                    chatResponse.statusCode= httpResponse.StatusCode;
+                }
+                else { 
+                    chatResponse.statusCode = httpResponse.StatusCode;
+                    string content = await httpResponse.Content.ReadAsStringAsync();
+                    chatResponse.error = content;
+                }
+
+            } catch (Exception ex) {
+                chatResponse.error=$"{ex.Message},{ex.StackTrace}";
+            } finally { 
+                stopwatch.Stop();
+                chatResponse.time= stopwatch.ElapsedMilliseconds;
+            }
+            return chatResponse;
+        }
+
+        public static String GenerateToken(string apikey, int expSeconds= 3600)
+        {
+            string[]parts= apikey.Split('.');
+            try
+            {
+                var payload = new JwtPayload {
+                    { JwtRegisteredClaimNames.Exp,DateTimeOffset.UtcNow.AddHours(expSeconds).ToUnixTimeSeconds()},
+                    { "api_key",parts[0]},
+                    { "timestamp",DateTimeOffset.UtcNow.ToUnixTimeSeconds()},
+                    //{ "api_key",parts[0]},
+                    //{ JwtRegisteredClaimNames.Exp,1718696388},
+                    //{ "timestamp",1718692788},
+                };
+                //var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(parts[1]));
+                //var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
+                //var header = new JwtHeader(signingCredentials);
+                //header.Add("sign_type", "SIGN");
+                string? header_base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"typ\":\"JWT\",\"alg\":\"HS256\",\"sign_type\":\"SIGN\"}")) .Replace("=","");
+                string? payload_base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(payload))).Replace("=", "");
+                var signature_base64 =  SignToken(header_base64, payload_base64, parts[1]).Replace("=","");
+                string? token  = $"{header_base64}.{payload_base64}.{signature_base64}";
+                //Java生成的Token 
+                //string s = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsInNpZ25fdHlwZSI6IlNJR04ifQ.eyJhcGlfa2V5IjoiMmMwMTJmOGE1ZjQzMGM5M2JmZjE5YjI0YTM0MjhkYzIiLCJleHAiOjE3MTg2OTYzODgsInRpbWVzdGFtcCI6MTcxODY5Mjc4OH0.ZCgsRMqFkbpx0eig1ouItkdrZ9HjRNWgKQVuqFU2JZ4";
+                return token;
+            }
+            catch (Exception e)
+            {
+                throw new Exception("Error generating token", e);
+            }
+        }
+
+        /// <summary>
+        /// 报错IDX10720: Unable to create KeyedHashAlgorithm for algorithmHS256'. the key size must be greater than: '256' bits, key has '128' bits. Arg ParamName Name
+        /// 由于.net8 HMACSHA256算法强制验证secret必须至少是256位,所以用这种方式
+        /// </summary>
+        /// <param name="header"></param>
+        /// <param name="payload"></param>
+        /// <param name="secret"></param>
+        /// <returns></returns>
+        public static  string SignToken(string header, string payload, string secret)
+        {
+            header= header.Replace("=", "");
+            var encoding = new UTF8Encoding();
+            byte[] keyByte = encoding.GetBytes(secret);
+            byte[] messageBytes = encoding.GetBytes(header + "." + payload);
+            using (var hmacsha256 = new HMACSHA256(keyByte))
+            {
+                byte[] hash = hmacsha256.ComputeHash(messageBytes);
+                return Convert.ToBase64String(hash);
+            }
+        }
+    }
+
+    public class ChatGlmDTO
+    {
+        public string? model {  get; set; }
+        public List<MessageDTO>? messages { get; set; }= new List<MessageDTO>();
+      //  public string? request_id {  get; set; }
+        //public bool do_sample { get; set; }
+        public bool stream {  get; set; }
+        public double temperature { get; set; } = 0.95;
+        // public double top_p { get; set; }
+        public int max_tokens { get; set; } = 8192;
+        public string? user_id {  get; set; }
+    }
+}

+ 154 - 0
TEAMModelOS.Extension/HTEX.Screen/Services/ErnieBotServiceImpl.cs

@@ -0,0 +1,154 @@
+using HTEXGpt.Models;
+using Microsoft.AspNetCore.Http;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace HTEXGpt.Services
+{
+    public class ErnieBotServiceImpl : IModelService
+    {
+        private readonly string appSecret = "7E0C6SzWE7kb1lSp1Dkb7k6Eg2xFkJoR";
+
+        private readonly string apiKey = "vnxaIH9aJLrsiwMI8OchFKEf";
+
+        private readonly static string TOKEN_URL_TEMPLATE = "https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id={0}&client_secret={1}";
+        //最大输出Token  max_output_tokens=4096
+        // private readonly static String CHAT_URL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie-speed-128k?access_token={0}";
+
+        //最大输出Token  max_output_tokens=2048
+        private readonly static String CHAT_URL = "https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/ernie_speed?access_token={0}";
+        private readonly string appId = "71670766";
+
+        private readonly  IHttpClientFactory _httpClientFactory;
+        public ErnieBotServiceImpl(IHttpClientFactory httpClientFactory) 
+        {
+            _httpClientFactory=httpClientFactory; 
+        }
+
+        public async Task<ChatResponse> ChatMessage(ChatRequest dto, HttpContext httpContext, HttpResponse response)
+        {
+            // response.Headers.ContentType="text/event-stream";
+            //response.Headers.CacheControl="no-cache";
+            //response.Headers.Connection="keep-alive";
+            Stopwatch stopwatch = Stopwatch.StartNew(); // 开始计时
+            ChatResponse chatResponse= new ChatResponse();
+            try {
+                var token = await getAccessToken();
+                JsonElement json = JsonSerializer.Deserialize<JsonElement>(token);
+                var access_token= json.GetProperty("access_token").GetString();
+                ErnieBotDTO ernieBotDTO = new ErnieBotDTO
+                {
+                    messages= dto.messages,
+                    system=dto.system,
+                    stream=false,
+                  //  temperature=dto.@params.temperature,
+                   // top_p=dto.@params.top_p,
+                  //  max_output_tokens=4096
+                    user_id =dto.uid
+                };
+                var httpClient = _httpClientFactory.CreateClient();
+                string url = string.Format(CHAT_URL, access_token);
+                var data = JsonSerializer.Serialize(ernieBotDTO);
+                HttpResponseMessage httpResponse = await httpClient.PostAsJsonAsync(url, ernieBotDTO);
+                if (httpResponse.IsSuccessStatusCode)
+                {
+
+                    var content = await httpResponse.Content.ReadAsStringAsync();
+                    //using (var responseStream = await httpResponse.Content.ReadAsStreamAsync())
+                    //{
+                    //    StringBuilder sb = new StringBuilder();
+                    //    var buffer = new byte[1024];
+                    //    int bytesRead;
+                    //    while ((bytesRead =   responseStream.Read(buffer, 0, buffer.Length)) > 0)
+                    //    {
+                    //        string contentData = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
+                    //        Console.WriteLine(contentData);
+                    //        await response.WriteAsync($"event:result\n");
+                    //        await response.WriteAsync(@$"{contentData}");
+                    //        await response.Body.FlushAsync();
+                    //        await Task.Delay(500);
+                    //    }
+                    //}
+                    StringBuilder sb = new StringBuilder();
+                    var datas = content.Split("data:");
+                    foreach (var jsonData in datas)
+                    {
+                        try
+                        {
+                            JsonNode j = JsonSerializer.Deserialize<JsonNode>(jsonData);
+                            sb.Append($"{j["result"]}");
+                            var total_tokens = j?["usage"]?["total_tokens"];
+                            if (total_tokens!= null)
+                            {
+                                chatResponse.total_tokens=int.Parse($"{total_tokens}");
+                            }
+                            var completion_tokens = j?["usage"]?["completion_tokens"];
+                            if (completion_tokens!= null)
+                            {
+                                chatResponse.completion_tokens=int.Parse($"{completion_tokens}");
+                            }
+
+                            var prompt_tokens = j?["usage"]?["prompt_tokens"];
+                            if (prompt_tokens!= null)
+                            {
+                                chatResponse.prompt_tokens=int.Parse($"{prompt_tokens}");
+                            }
+                        }
+                        catch { }
+                    }
+                    chatResponse.result= sb.ToString();
+                    chatResponse.statusCode= System.Net.HttpStatusCode.OK;
+                    // await response.WriteAsync(@$"{content}");
+                    // await response.Body.FlushAsync();
+                }
+                else {
+                    chatResponse.statusCode=httpResponse.StatusCode;
+                    string content = await httpResponse.Content.ReadAsStringAsync();
+                    chatResponse.error=$"{content}";
+                }
+            } catch (Exception ex) {
+                chatResponse.statusCode=System.Net.HttpStatusCode.InternalServerError;
+                chatResponse.error=$"{ex.Message},{ex.StackTrace}";
+            }
+            finally
+            {
+                //   response.Body.Close();
+                stopwatch.Stop(); // 停止计时
+                chatResponse.time= stopwatch.ElapsedMilliseconds;
+            }
+            return chatResponse;
+        }
+
+        private async Task<string> getAccessToken() 
+        {
+
+            var token_url=  string.Format(TOKEN_URL_TEMPLATE, apiKey, appSecret);
+            var httpClient = _httpClientFactory.CreateClient();
+            httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
+            var response =  await httpClient.PostAsJsonAsync(token_url,new { });
+            if (response.StatusCode==System.Net.HttpStatusCode.OK) {
+
+                var content = await  response.Content.ReadAsStringAsync();
+                return content;
+            }
+            return null; 
+
+        }
+    }
+
+    public class ErnieBotDTO 
+    {
+        public List<MessageDTO> messages { get; set; } = new List<MessageDTO>();
+        public string? system { get; set; }
+        public double temperature { get; set; } = 0.95;
+        public bool stream { get; set; } = true;
+        public double top_p { get; set; } = 0.7;
+        public string response_format { get; set; } = "text";
+       // public double penalty_score { get; set; }
+       // public bool enable_citation { get; set; }
+       // public bool disable_search { get; set; }
+        public string? user_id { get; set; }
+    }
+}

+ 15 - 0
TEAMModelOS.Extension/HTEX.Screen/Services/IAiAppService.cs

@@ -0,0 +1,15 @@
+using HTEXGpt.Models;
+
+namespace HTEXGpt.Services
+{
+    public interface IAiAppService
+    {
+        /// <summary>
+        /// 向大模型发起对话请求-根据模型编码、用户ID
+        /// </summary>
+        /// <param name="modelType">模型类型</param>
+        /// <param name="dto">消息参数</param>
+        /// <returns></returns>
+        public    Task<ChatResponse> ChatMessage(string? modelType, ChatRequest dto, HttpContext httpContext,HttpResponse response);
+    }
+}

+ 14 - 0
TEAMModelOS.Extension/HTEX.Screen/Services/IModelService.cs

@@ -0,0 +1,14 @@
+using HTEXGpt.Models;
+
+namespace HTEXGpt.Services
+{
+    public interface IModelService
+    {
+        /// <summary>
+        /// 发起请求
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        public Task<ChatResponse> ChatMessage(ChatRequest dto, HttpContext httpContext, HttpResponse response );
+    }
+}

+ 137 - 0
TEAMModelOS.Extension/HTEX.Screen/Services/QianWenServiceImpl.cs

@@ -0,0 +1,137 @@
+using HTEXGpt.Models;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace HTEXGpt.Services
+{
+    public class QianWenServiceImpl : IModelService
+    {
+        private string HOST_URL= "https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation";
+        private string apiKey = "sk-b8354384e92d466ea4876410c53eb4ce";
+        private string model = "qwen-turbo";
+        //qwen-turbo最大值和默认值为1500 tokens。
+        //qwen-max、qwen-max-1201、qwen-max-longcontext和qwen-plus模型,最大值和默认值均为2000 tokens。
+        private readonly IHttpClientFactory _httpClientFactory;
+        public QianWenServiceImpl(IHttpClientFactory httpClientFactory) {
+            _httpClientFactory = httpClientFactory;
+        }
+        public async Task<ChatResponse> ChatMessage(ChatRequest dto, HttpContext httpContext, HttpResponse response)
+        {
+             Stopwatch stopwatch = Stopwatch.StartNew();
+            ChatResponse chatResponse = new ChatResponse();
+           // response.Headers.ContentType="text/event-stream";
+            try {
+                QianWenDTO qianWenDTO = new QianWenDTO();
+                qianWenDTO.model=model;
+                QianWenInputDTO input = new QianWenInputDTO();
+                string? system = dto.system;
+                if (!string.IsNullOrWhiteSpace(system))
+                {
+                    MessageDTO messageDTO = new MessageDTO { role="system", content=system };
+                    dto.messages.Insert(0, messageDTO);
+                }
+                input.messages.AddRange(dto.messages);
+                qianWenDTO.input=input;
+              //  qianWenDTO.parameters= JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(dto.@params));
+                var httpClient = _httpClientFactory.CreateClient();
+                httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
+                // httpClient.DefaultRequestHeaders.Add("Content-Type", $"application/json");
+              // httpClient.DefaultRequestHeaders.Add("X-DashScope-SSE", "enable");
+
+                JsonSerializerOptions option = new System.Text.Json.JsonSerializerOptions
+                {
+                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
+                };
+
+                var json = JsonSerializer.Serialize(qianWenDTO, option);
+
+                var content = new StringContent(json, Encoding.UTF8, "application/json");
+                var httpRequest = new HttpRequestMessage(HttpMethod.Post, HOST_URL) { Content =content };
+                HttpResponseMessage httpResponse = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead);
+                if (httpResponse.IsSuccessStatusCode)
+                {
+                    // 读取响应内容的异步流
+                    StringBuilder data = new StringBuilder();
+                    var contentData = await httpResponse.Content.ReadAsStringAsync();
+                    JsonNode? jsonNode = JsonSerializer.Deserialize<JsonNode>(contentData);
+                    contentData= $"{jsonNode?["output"]?["choices"]?[0]?["message"]?["content"]}";
+
+                    var total_tokens = jsonNode?["usage"]?["total_tokens"];
+                    if (total_tokens!= null)
+                    {
+                        chatResponse.total_tokens=int.Parse($"{total_tokens}");
+                    }
+                    var output_tokens = jsonNode?["usage"]?["output_tokens"];
+                    if (output_tokens!= null)
+                    {
+                        chatResponse.completion_tokens=int.Parse($"{output_tokens}");
+                    }
+
+                    var input_tokens = jsonNode?["usage"]?["input_tokens"];
+                    if (input_tokens!= null)
+                    {
+                        chatResponse.prompt_tokens=int.Parse($"{input_tokens}");
+                    }
+                    //using (var responseStream = await httpResponse.Content.ReadAsStreamAsync())
+                    //{
+                    //    //Debug.Print("============start response use minseconds=" + (DateTime.Now - d).TotalMilliseconds + "  =================\r\n");
+                    //    // 逐块读取并处理响应内容
+                    //    var buffer = new byte[1024];
+                    //    int bytesRead;
+                    //    while ((bytesRead =   responseStream.Read(buffer, 0, buffer.Length)) > 0)
+                    //    {
+                    //        // 处理响应内容
+                    //        string contentData = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
+                    //        Console.WriteLine(contentData);
+                    //        await response.WriteAsync(@$"{contentData}");
+                    //        await response.Body.FlushAsync();
+                    //        await Task.Delay(500);
+                    //    }
+                    //}
+
+                    chatResponse.result= contentData;
+                    chatResponse.statusCode= httpResponse.StatusCode;
+                }
+                else {
+                    var contentData = await httpResponse.Content.ReadAsStringAsync();
+                    chatResponse.error=$"{contentData}";
+                    chatResponse.statusCode= httpResponse.StatusCode;
+                }
+            } catch (Exception ex) 
+            {
+                chatResponse.error=$"{ex.Message},{ex.StackTrace}";
+                chatResponse.statusCode= System.Net.HttpStatusCode.InternalServerError;
+
+            }finally {
+                //  response.Body.Close(); 
+                stopwatch.Stop(); // 停止计时
+                chatResponse.time= stopwatch.ElapsedMilliseconds;
+            }
+            return chatResponse;
+        }
+    }
+
+    public class  QianWenDTO
+    {
+        public string? model { get; set; }
+
+        public QianWenInputDTO? input { get; set; }
+
+        public QianWenParameters parameters { get; set; }= new QianWenParameters();
+    }
+
+    public class QianWenParameters 
+    {
+        public string result_format { get; set; } = "message";
+        public double temperature { get; set; } = 0.85;
+        public double top_p { get; set; } = 0.8;
+    }
+    public class QianWenInputDTO 
+    {
+        public List<MessageDTO> messages { get; set; } = new List<MessageDTO>();
+    }
+}

+ 276 - 0
TEAMModelOS.Extension/HTEX.Screen/Services/SparkDeskServiceImpl.cs

@@ -0,0 +1,276 @@
+using HTEXGpt.Models;
+using Newtonsoft.Json.Linq;
+using System.Diagnostics;
+using System.Net.WebSockets;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Nodes;
+
+namespace HTEXGpt.Services
+{
+    public class SparkDeskServiceImpl : IModelService
+    {
+        static ClientWebSocket? webSocket0;
+        static CancellationToken cancellation;
+        private string appId = "33cc7806";
+        private string appSecret = "YmVmNWI4YTRmNzFmZDg4MzUyMGM1MzYx";
+
+        private string appKey = "7f6165ce496c92a7e231e80a87fe62e9";
+        private static string HOST_URL = "https://spark-api.xf-yun.com/v3.5/chat";
+        private static string domain = "generalv3.5";
+
+        public SparkDeskServiceImpl(ClientWebSocket webSocket) {
+            webSocket0=webSocket;
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        public async Task<ChatResponse> ChatMessage(ChatRequest dto,HttpContext httpContext ,HttpResponse? response)
+        {
+            //测试,debug到这里的时候你会发现,协议使用的是HTTP/2. APS.NET Core 2.1以上就默认支持HTTP/2,无需额外的配置。再Windows Server2016/Windows10+会自动提供支持。
+            //string requestProtocol = httpContext.Request.Protocol;
+            //响应头部添加text/event-stream,这是HTTP/2协议的一部分。
+            //  response!.Headers.Add("Content-Type", "text/event-stream");
+
+            ChatResponse chatResponse = new ChatResponse();
+            Stopwatch stopwatch = Stopwatch.StartNew();
+          //  response!.Headers.ContentType="text/event-stream";
+            //for (int i = 0; i<100; i++)
+            //{
+            //    // event:ping event是事件字段名,冒号后面是事件名称,不要忘了\n换行符。
+            //    await HttpContext.Response.WriteAsync($"event:ping\n");
+
+            //    // data: 是数据字段名称,冒号后面是数据字段内容。注意数据内容仅仅支持UTF-8,不支持二进制格式。
+            //    // data后面的数据当然可以传递JSON String的。
+            //    await HttpContext.Response.WriteAsync($"data:Controller {i} at {DateTime.Now}\r\r");
+
+            //    // 写入数据到响应后不要忘记 FlushAsync(),因为该api方法是异步的,所以要全程异步,调用同步方法会报错。
+            //    await HttpContext.Response.Body.FlushAsync();
+
+            //    //模拟一个1秒的延迟。
+            //    //await Task.Delay(1000);
+            //}
+            string authUrl = GetAuthUrl();
+
+            StringBuilder sb = new StringBuilder();
+            string url = authUrl.Replace("http://", "ws://").Replace("https://", "wss://");
+            //if (webSocket0!=null) {
+            //    Console.WriteLine();
+            //}
+            //using (webSocket0 = new ClientWebSocket())
+            {
+                try
+                {
+                    await webSocket0!.ConnectAsync(new Uri(url), cancellation);
+
+                    SparkDeskRequest request = new SparkDeskRequest();
+                    request.header = new SparkDeskHeader()
+                    {
+                        app_id = appId,
+                        uid = dto.uid
+                    };
+                    request.parameter = new SparkDeskParameter()
+                    {
+                        chat = new SparkDeskChat()
+                        {
+                            domain = domain,//模型领域,默认为星火通用大模型
+                            temperature = dto.@params.temperature,//温度采样阈值,用于控制生成内容的随机性和多样性,值越大多样性越高;范围(0,1)
+                            //generalv3.5  max_tokens=8192
+                            max_tokens = 8192,//生成内容的最大长度,范围(0,4096)
+                        }
+                    };
+                    request.payload = new SparkDeskPayload()
+                    {
+                        //  new Content() { role = "user", content = "你是谁" },
+                        // new Content() { role = "assistant", content = "....." }, // AI的历史回答结果,这里省略了具体内容,可以根据需要添加更多历史对话信息和最新问题的内容。
+                        message = new SparkDeskMessage()
+                        {
+                            text=dto.messages
+                        }
+                    };
+                    if (null == request.payload.message.text.Find(x=>x.role!.Equals("system"))) 
+                    {
+                        request.payload.message.text.Insert(0, new MessageDTO { role="system", content= dto.system });
+                    }
+                    string jsonString = JsonSerializer.Serialize(request);
+                    //连接成功,开始发送数据
+
+
+                    var frameData2 = System.Text.Encoding.UTF8.GetBytes(jsonString.ToString());
+
+
+                    await webSocket0.SendAsync(new ArraySegment<byte>(frameData2), WebSocketMessageType.Text, true, cancellation);
+
+                    // 接收流式返回结果进行解析
+                    byte[] receiveBuffer = new byte[1024];
+                    WebSocketReceiveResult result = await webSocket0.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), cancellation);
+                   // string resp = "";
+                    while (!result.CloseStatus.HasValue)
+                    {
+                        if (result.MessageType == WebSocketMessageType.Text)
+                        {
+                            string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, result.Count);
+                            //将结果构造为json
+                            var node = JsonNode.Parse(receivedMessage);
+                            int? code = (int?)node?["header"]?["code"];
+                            //JObject jsonObj = JObject.Parse(receivedMessage);
+                            // int code = (int)jsonObj["header"]["code"];
+
+
+                            if (0==code)
+                            {
+                                // int status = (int)jsonObj["payload"]["choices"]["status"];
+                                int? status = (int?)node?["payload"]?["choices"]?["status"];
+
+                                //JArray textArray = (JArray)jsonObj["payload"]["choices"]["text"];
+                                JsonArray? textArray = (JsonArray?)node?["payload"]?["choices"]?["text"];
+                                string? content = (string?)textArray?[0]?["content"];
+                                // resp += content;
+                                sb.Append(content);
+                                if (status != 2)
+                                {
+
+                                   // await httpContext.Response.WriteAsync($"event:ping\n");
+                                    //await httpContext.Response.WriteAsync($"data:Controller {receivedMessage} at {DateTime.Now}\r\r");
+                                   // await httpContext.Response.Body.FlushAsync();
+
+                                   // Console.WriteLine($"已接收到数据: {receivedMessage}");
+                                }
+                                else
+                                {
+                                    Console.WriteLine($"最后一帧: {receivedMessage}");
+                                    //int totalTokens = (int)jsonObj["payload"]["usage"]["text"]["total_tokens"];
+                                    int? total_tokens = (int?)node?["payload"]?["usage"]?["text"]?["total_tokens"];
+                                    int? prompt_tokens = (int?)node?["payload"]?["usage"]?["text"]?["prompt_tokens"];
+                                    int? completion_tokens = (int?)node?["payload"]?["usage"]?["text"]?["completion_tokens"];
+                                    if (total_tokens.HasValue) { 
+                                        chatResponse.total_tokens = total_tokens.Value;
+                                    }
+                                    if (prompt_tokens.HasValue)
+                                    {
+                                        chatResponse.prompt_tokens = prompt_tokens.Value;
+                                    }
+                                    if (completion_tokens.HasValue)
+                                    {
+                                        chatResponse.completion_tokens = completion_tokens.Value;
+                                    }
+                                    // Console.WriteLine($"整体返回结果: {resp}");
+                                    //Console.WriteLine($"本次消耗token数: {totalTokens}");
+                                    // await httpContext.Response.WriteAsync($"event:ping\n");
+                                    //await httpContext.Response.WriteAsync($"data: {resp} at {DateTime.Now}\r\r");
+                                    // await httpContext.Response.Body.FlushAsync();
+                                    break;
+                                }
+                            }
+                            else
+                            {
+                               // Console.WriteLine($"请求报错: {receivedMessage}");
+                            }
+                        }
+                        else if (result.MessageType == WebSocketMessageType.Close)
+                        {
+                           // Console.WriteLine("已关闭WebSocket连接");
+                            //数据发送完毕后关闭连接。
+                            break;
+                        }
+                        result = await webSocket0.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), cancellation);
+                    }
+                    chatResponse.result= sb.ToString();
+                    chatResponse.statusCode=System.Net.HttpStatusCode.OK;
+                }
+                catch (Exception e)
+                {
+                    chatResponse.error=$"{e.Message},{e.StackTrace}";
+                    chatResponse.statusCode=System.Net.HttpStatusCode.InternalServerError;
+                    //Console.WriteLine($"{e.Message}\n{e.StackTrace}");
+                }
+                finally {
+                    //response.Body.Close();
+                    await webSocket0!.CloseAsync(WebSocketCloseStatus.NormalClosure, "正常关闭", cancellation);
+                    webSocket0.Dispose();
+                    stopwatch.Stop();
+                    chatResponse.time= stopwatch.ElapsedMilliseconds;
+                }
+            }
+            // response.Body.Close();
+            return chatResponse; 
+        }
+        // 返回code为错误码时,请查询https://www.xfyun.cn/document/error-code解决方案
+        public  string GetAuthUrl()
+        {
+            string date = DateTime.UtcNow.ToString("r");
+
+            Uri uri = new Uri(HOST_URL);
+            StringBuilder builder = new StringBuilder("host: ").Append(uri.Host).Append("\n").//
+                                    Append("date: ").Append(date).Append("\n").//
+                                    Append("GET ").Append(uri.LocalPath).Append(" HTTP/1.1");
+
+            string sha = HMACsha256(appSecret, builder.ToString());
+            string authorization = string.Format("api_key=\"{0}\", algorithm=\"{1}\", headers=\"{2}\", signature=\"{3}\"", appKey, "hmac-sha256", "host date request-line", sha);
+            //System.Web.HttpUtility.UrlEncode
+
+            string NewUrl = "https://" + uri.Host + uri.LocalPath;
+
+            string path1 = "authorization" + "=" + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(authorization));
+            date = date.Replace(" ", "%20").Replace(":", "%3A").Replace(",", "%2C");
+            string path2 = "date" + "=" + date;
+            string path3 = "host" + "=" + uri.Host;
+
+            NewUrl = NewUrl + "?" + path1 + "&" + path2 + "&" + path3;
+            return NewUrl;
+        }
+        public static string HMACsha256(string apiSecretIsKey, string buider)
+        {
+            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(apiSecretIsKey);
+            System.Security.Cryptography.HMACSHA256 hMACSHA256 = new System.Security.Cryptography.HMACSHA256(bytes);
+            byte[] date = System.Text.Encoding.UTF8.GetBytes(buider);
+            date = hMACSHA256.ComputeHash(date);
+            hMACSHA256.Clear();
+
+            return Convert.ToBase64String(date);
+
+        }
+    }
+
+
+    //构造请求体
+    public class SparkDeskRequest
+    {
+        public SparkDeskHeader header { get; set; }
+        public SparkDeskParameter parameter { get; set; }
+        public SparkDeskPayload payload { get; set; }
+    }
+
+    public class SparkDeskHeader
+    {
+        public string? app_id { get; set; }
+        public string? uid { get; set; }
+    }
+
+    public class SparkDeskParameter
+    {
+        public SparkDeskChat chat { get; set; }
+    }
+
+    public class SparkDeskChat
+    {
+        public string domain { get; set; }
+        public double temperature { get; set; } = 0.5;
+        public int max_tokens { get; set; }
+    }
+
+    public class SparkDeskPayload
+    {
+        public SparkDeskMessage message { get; set; }
+    }
+
+    public class SparkDeskMessage
+    {
+        public List<MessageDTO> text { get; set; } = new List<MessageDTO>();
+    }
+
+    
+}