CrazyIter_Bin 6 miesięcy temu
rodzic
commit
3b7f682d91

+ 80 - 0
HTEX.DataETL/Controllers/LessonRecordController.cs

@@ -52,7 +52,87 @@ namespace HTEX.DataETL.Controllers
             _azureRedis = azureRedis;
             _azureRedis = azureRedis;
 
 
         }
         }
+        [HttpPost("schools")]
+        public async Task<IActionResult> Schools(JsonElement json) 
+        {
 
 
+            List<string> ids = new List<string> { "cdscxx",
+  "sdsyxq",
+  "cdsxxx",
+  "cdssz",
+  "cdsshx",
+  "ptszx",
+  "cdwwsz",
+  "csdswz",
+  "cdfczx",
+  "sdsy",
+  "qyszgh",
+  "sdsydq",
+  "sslzxq",
+  "cd37z",
+  "cdqysz",
+  "cd11z",
+  "xhlxx",
+  "cdpxjj",
+  "xcfxqy",
+  "csdswx",
+  "thgjxx",
+  "cdwwsx",
+  "cdsjy",
+  "cdsxwy",
+  "cdsxzq",
+  "cdsxmd",
+  "cdsxcf",
+  "cdsxqh",
+  "cdsyxx",
+  "cdsxx",
+  "sxgkxx",
+  "cdqbxx",
+  "qyszfx",
+  "cdpxxq",
+  "cdpxlz",
+  "cdpx",
+  "cdkhxx",
+  "cdhmxx",
+  "cdjsxx",
+  "cdhhxx",
+  "cdhygj",
+  "hygjqb",
+  "cdglxx",
+  "cddpxx",
+  "cddcg",
+  "cdctzm",
+  "cdctxx",
+  "cdchxx",
+  "cdctxq",
+  "sdsyqb",
+  "sslzsx",
+  "sslzjs",
+  "cdjsxc",
+  "cdjsxb",
+  "yfsxwy",
+  "sdyzhy",
+  "yxmdfs",
+  "cdcgxc",
+  "cdcgxa" };
+            foreach (string id in ids) {
+                var res = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student).GetList<Student>("select value c  from c ", $"Base-{id}");
+                if (res.list.IsNotEmpty()) 
+                {
+                   var change=  res.list.FindAll(x => x.graduate==1);
+                    if (change.IsNotEmpty()) 
+                    {
+                        foreach (var item in change)
+                        {
+                            item.graduate=0;
+                            await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student).UpsertItemAsync(item, new PartitionKey(item.code));
+                        }
+                    }
+                    
+                }
+            }
+            return Ok(new {  });
+        }
         [HttpPost("process-lesson")]
         [HttpPost("process-lesson")]
         public async Task<IActionResult> ProcessLesson(JsonElement json)
         public async Task<IActionResult> ProcessLesson(JsonElement json)
         {
         {

+ 33 - 0
IESHybridCloud/Controllers/WeatherForecastController.cs

@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace IESHybridCloud.Controllers
+{
+    [ApiController]
+    [Route("[controller]")]
+    public class WeatherForecastController : ControllerBase
+    {
+        private static readonly string[] Summaries = new[]
+        {
+            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
+        };
+
+        private readonly ILogger<WeatherForecastController> _logger;
+
+        public WeatherForecastController(ILogger<WeatherForecastController> logger)
+        {
+            _logger = logger;
+        }
+
+        [HttpGet]
+        public IEnumerable<WeatherForecast> Get()
+        {
+            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
+            {
+                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
+                TemperatureC = Random.Shared.Next(-20, 55),
+                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
+            })
+            .ToArray();
+        }
+    }
+}

+ 25 - 0
IESHybridCloud/IESHybridCloud.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Hangfire" Version="1.8.17" />
+    <PackageReference Include="Hangfire.Dashboard.BasicAuthorization" Version="1.0.2" />
+    <PackageReference Include="Hangfire.Redis.StackExchange" Version="1.9.4" />
+    <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.8" />
+    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
+    <PackageReference Include="MQTTnet.AspNetCore" Version="4.3.7.1207" />
+    <PackageReference Include="MQTTnet.AspNetCore.Routing" Version="0.4.37" />
+    <PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
+    <PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\TEAMModelOS.SDK\TEAMModelOS.SDK.csproj" />
+  </ItemGroup>
+
+</Project>

+ 6 - 0
IESHybridCloud/IESHybridCloud.http

@@ -0,0 +1,6 @@
+@IESHybridCloud_HostAddress = http://localhost:5251
+
+GET {{IESHybridCloud_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###

+ 83 - 0
IESHybridCloud/Program.cs

@@ -0,0 +1,83 @@
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Serilog;
+using System.Text.Json;
+using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK.DI.Device;
+
+namespace IESHybridCloud
+{
+    public class Program
+    {
+        public static void Main(string[] args)
+        {
+            var builder = WebApplication.CreateBuilder(args);
+            //防止编译后的appsettings.json 文件内容,在重新部署的时候,因为不同的环境导致被覆盖的问题,
+            //所以在正式环境中指定appsettings-prod.json一个本地开发环境不存在的文件,以达到不会被覆盖的问题,
+            //即使在生产环境中未配置appsettings-prod.json 也不影响启动,因为会按照appsettings.json的配置启动
+#if !DEBUG
+            builder.Host.ConfigureAppConfiguration((context, config) => {
+                config.SetBasePath(Directory.GetCurrentDirectory());
+                config.AddJsonFile("appsettings-prod.json", optional: true, reloadOnChange: true);
+            });
+#endif
+            Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Console(outputTemplate: "{Timestamp:HH:mm:ss.fff zzz} [{Level:u3}] ({ThreadId}) {Message}{NewLine}{Exception}")
+           .WriteTo.File("logs/log-.log", rollingInterval: RollingInterval.Day).CreateLogger();
+            builder.Host.UseSerilog();
+            // Add services to the container.
+
+            builder.Services.AddControllers();
+            builder.Services.AddHttpClient();
+
+            string? StorageConnectionString = builder.Configuration.GetValue<string>("Azure:Storage:ConnectionString");
+            string? RedisConnectionString = builder.Configuration.GetValue<string>("Azure:Redis:ConnectionString");
+            //Storage
+            builder.Services.AddAzureStorage(StorageConnectionString, "Default");
+            //Redis
+            builder.Services.AddAzureRedis(RedisConnectionString, "Default");
+            builder.Services.AddSignalR();
+            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.AddSingleton<CoreDevice>();
+            builder.Services.AddCors(options =>
+            {
+                options.AddDefaultPolicy(
+                builder =>
+                {
+
+                    builder.AllowAnyOrigin()
+                            .AllowAnyHeader()
+                            .AllowAnyMethod();
+                });
+            });
+            builder.Services.AddControllersWithViews();
+            ////MQTT  服务端API 发送消息到MQTT客户端 https://www.cnblogs.com/weskynet/p/16441219.html
+            //#region MQTT配置
+            //builder.Services.AddSingleton<MQTTEvents>();
+            //builder.Services.AddSingleton(new JsonSerializerOptions(JsonSerializerDefaults.Web));
+            //builder.Services.AddHostedMqttServerWithServices(x =>
+            //{
+            //    x.WithDefaultEndpoint()
+            //    .WithConnectionBacklog(1000)
+            //    .WithPersistentSessions(true).WithKeepAlive()
+            //    .WithDefaultCommunicationTimeout(TimeSpan.FromMilliseconds(30));
+            //}).AddMqttConnectionHandler().AddConnections().AddMqttControllers();
+            //#endregion
+            var app = builder.Build();
+
+            // Configure the HTTP request pipeline.
+
+            app.UseHttpsRedirection();
+
+            app.UseAuthorization();
+
+
+            app.MapControllers();
+
+            app.Run();
+        }
+    }
+}

+ 41 - 0
IESHybridCloud/Properties/launchSettings.json

@@ -0,0 +1,41 @@
+{
+  "$schema": "http://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:37858",
+      "sslPort": 44395
+    }
+  },
+  "profiles": {
+    "http": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "weatherforecast",
+      "applicationUrl": "http://localhost:5251",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "https": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "weatherforecast",
+      "applicationUrl": "https://localhost:7222;http://localhost:5251",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "weatherforecast",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 139 - 0
IESHybridCloud/Services/SignalRIESHybridClientHub.cs

@@ -0,0 +1,139 @@
+using Microsoft.AspNetCore.SignalR.Client;
+using System.Text;
+using System.Text.Json;
+using System.Web;
+using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.DI.Device;
+using TEAMModelOS.SDK.Extension;
+
+namespace IESHybridCloud.Services
+{
+    public class SignalRIESHybridClientHub : BackgroundService, IDisposable
+    {
+        private readonly IConfiguration _configuration;
+        private readonly ILogger<SignalRIESHybridClientHub> _logger;
+        private TEAMModelOS.SDK.ScreenClient? device;
+        private readonly CoreDevice _device;
+        private readonly IHttpClientFactory _httpClientFactory;
+        public SignalRIESHybridClientHub(IConfiguration configuration, ILogger<SignalRIESHybridClientHub> logger, IHttpClientFactory httpClientFactory, CoreDevice device)
+        {
+
+            _configuration=configuration;
+            _logger=logger;
+            _httpClientFactory=httpClientFactory;
+            _device = device;
+        }
+        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            var coreDevice = await _device.GetCoreDevice();
+            device=coreDevice.ToJsonString().ToObject<TEAMModelOS.SDK.ScreenClient>();
+            string clientid = device.deviceId!;
+            string? CenterUrl = _configuration.GetSection("ScreenClient:CenterUrl").Value;
+            string? ScreenUrl = _configuration.GetSection("ScreenClient:ScreenUrl").Value;
+            long Timeout = _configuration.GetValue<long>("ScreenClient:Timeout");
+            long Delay = _configuration.GetValue<long>("ScreenClient:Delay");
+            device.timeout = Timeout;
+            device.delay = Delay;
+            device.screenUrl = ScreenUrl;
+            await StartHubConnectionAsync(clientid, CenterUrl);
+        }
+        private async Task StartHubConnectionAsync(string clientid, string? CenterUrl)
+        { 
+            
+            //重写重连策略,防止服务端更新,断线后,客户端达到最大连接次数,依然连线不上服务端。
+            var reconnectPolicy = new ExponentialBackoffReconnectPolicy(TimeSpan.FromSeconds(10), _logger); // 尝试重连的最大次数,这里使用 int.MaxValue 表示无限次
+            reconnectPolicy.MaxRetryCount = int.MaxValue;
+            HubConnection hubConnection = new HubConnectionBuilder()
+               .WithUrl($"{CenterUrl}/signalr/screen?grant_type={HybridConstant.grant_type}&clientid={clientid}&device={HttpUtility.UrlEncode(device.ToJsonString(), Encoding.Unicode)}") //only one slash
+               .WithAutomaticReconnect(reconnectPolicy)
+               .ConfigureLogging(logging =>
+               {
+                   logging.SetMinimumLevel(LogLevel.Information);
+                   logging.AddConsole();
+               })
+               .Build();
+            try
+            {
+                hubConnection.On<ConnectionMessage>("ReceiveConnection", (message) =>
+                {
+                    _logger.LogInformation($"连接成功:{message.ToJsonString()}");
+                    //重置重连次数。
+                    reconnectPolicy.Reset();
+                });
+                hubConnection.On<JsonElement>("ReceiveMessage", async (message) =>
+                {
+                    {
+                        _logger.LogInformation($"获得推送数据,{message.ToJsonString()}");
+                    }
+                });
+                await hubConnection.StartAsync();
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError("初次启动连接SignalR失败,等待重连......");
+                int retryCount = 0;
+                const int maxRetries = 360;
+                const int retryDelaySeconds = 10;
+                while (retryCount < maxRetries)
+                {
+                    try
+                    {
+                        await Task.Delay(retryDelaySeconds * 1000); // 等待一段时间后重试  
+                        await hubConnection.StartAsync();
+                        _logger.LogInformation("SignalR连接成功(重试后)!");
+                        break; // 连接成功,退出循环  
+                    }
+                    catch (Exception retryEx)
+                    {
+                        retryCount++;
+                        _logger.LogInformation($"SignalR连接重试失败: {retryEx.Message}。重试次数: {retryCount}/{maxRetries}");
+                        // 可以在这里决定是否因为某种原因停止重试  
+                        if (retryCount == maxRetries)
+                        {
+                            _logger.LogInformation("达到最大重试次数,停止重试。");
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        public async Task<(int status, string msg, JsonElement? task)> ReceiveMessage(JsonElement message) 
+        {
+            int status = 0;
+            string msg = string.Empty;
+            return (status, msg, message);
+
+        }
+    }
+    public class ExponentialBackoffReconnectPolicy : IRetryPolicy
+    {
+        private readonly TimeSpan _retryInterval;
+
+        private int _retryCount;
+        public int MaxRetryCount { get; set; } = int.MaxValue;
+        public readonly ILogger<SignalRIESHybridClientHub> _logger;
+        public ExponentialBackoffReconnectPolicy(TimeSpan retryInterval, ILogger<SignalRIESHybridClientHub> logger)
+        {
+            _retryInterval = retryInterval;
+            _retryCount = 0;
+            _logger = logger;
+        }
+
+        public TimeSpan? NextRetryDelay(RetryContext retryContext)
+        {
+            _logger.LogInformation($"重连次数: {_retryCount}");
+            if (_retryCount < MaxRetryCount)
+            {
+                _retryCount++;
+                // 计算下一次重连的延迟时间
+                return _retryInterval;
+            }
+            return null; // 达到最大重连次数后不再重连
+        }
+
+        public void Reset()
+        {
+            _retryCount = 0;
+        }
+    }
+}

+ 13 - 0
IESHybridCloud/WeatherForecast.cs

@@ -0,0 +1,13 @@
+namespace IESHybridCloud
+{
+    public class WeatherForecast
+    {
+        public DateOnly Date { get; set; }
+
+        public int TemperatureC { get; set; }
+
+        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+
+        public string? Summary { get; set; }
+    }
+}

+ 8 - 0
IESHybridCloud/appsettings.Development.json

@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}

+ 9 - 0
IESHybridCloud/appsettings.json

@@ -0,0 +1,9 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}

+ 1 - 1
TEAMModelOS.Extension/HTEX.Complex/Services/SignalRScreenServerHub.cs

@@ -105,7 +105,7 @@ namespace HTEX.Complex.Services
                             screenClient.cpu = device.cpu;
                             screenClient.cpu = device.cpu;
                             screenClient.ram = device.ram;
                             screenClient.ram = device.ram;
                             screenClient.last_time= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                             screenClient.last_time= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
-                            screenClient.deviceId = serverDevice.deviceId;
+                            screenClient.deviceId = device.deviceId;
                             //连接成功,发送消息给客户端。
                             //连接成功,发送消息给客户端。
                             await SendConnection(connid, new ConnectionMessage
                             await SendConnection(connid, new ConnectionMessage
                             {
                             {

+ 1 - 1
TEAMModelOS.Extension/HTEX.ScreenClient/Services/SignalRScreenClientHub.cs

@@ -59,7 +59,7 @@ namespace HTEX.ScreenClient.Services
             var reconnectPolicy = new ExponentialBackoffReconnectPolicy(TimeSpan.FromSeconds(10), _logger); // 尝试重连的最大次数,这里使用 int.MaxValue 表示无限次
             var reconnectPolicy = new ExponentialBackoffReconnectPolicy(TimeSpan.FromSeconds(10), _logger); // 尝试重连的最大次数,这里使用 int.MaxValue 表示无限次
             reconnectPolicy.MaxRetryCount = int.MaxValue;
             reconnectPolicy.MaxRetryCount = int.MaxValue;
             HubConnection hubConnection = new HubConnectionBuilder()
             HubConnection hubConnection = new HubConnectionBuilder()
-               .WithUrl($"{CenterUrl}/signalr/screen?grant_type=bookjs_api&clientid={clientid}&device={HttpUtility.UrlEncode(device.ToJsonString(), Encoding.Unicode)}") //only one slash
+               .WithUrl($"{CenterUrl}/signalr/screen?grant_type={ScreenConstant.grant_type}&clientid={clientid}&device={HttpUtility.UrlEncode(device.ToJsonString(), Encoding.Unicode)}") //only one slash
                .WithAutomaticReconnect(reconnectPolicy)
                .WithAutomaticReconnect(reconnectPolicy)
                .ConfigureLogging(logging =>
                .ConfigureLogging(logging =>
                {
                {

+ 10 - 10
TEAMModelOS.Function/IESHttpTrigger.cs

@@ -448,16 +448,16 @@ namespace TEAMModelOS.Function
                             item.graduate = 1;
                             item.graduate = 1;
                             students.Add(item);
                             students.Add(item);
                         }
                         }
-                        foreach (var item in students)
-                        {
-                            item.graduate = 1;
-                            await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student).ReplaceItemAsync<Student>(item, item.id, new PartitionKey($"Base-{schoolId}"));
-                        }
-                        foreach (var item in graduate_classes)
-                        {
-                            item.graduate = 1;
-                            await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReplaceItemAsync<Class>(item, item.id, new PartitionKey($"Class-{schoolId}"));
-                        }
+                        //foreach (var item in students)
+                        //{
+                        //    item.graduate = 1;
+                        //    await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student).ReplaceItemAsync<Student>(item, item.id, new PartitionKey($"Base-{schoolId}"));
+                        //}
+                        //foreach (var item in graduate_classes)
+                        //{
+                        //    item.graduate = 1;
+                        //    await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReplaceItemAsync<Class>(item, item.id, new PartitionKey($"Class-{schoolId}"));
+                        //}
                     }
                     }
                 }
                 }
                 //未毕业的
                 //未毕业的

+ 15 - 0
TEAMModelOS.Function/IESServiceBusTrigger.cs

@@ -48,6 +48,7 @@ using static TEAMModelOS.SDK.Models.Service.SystemService;
 using System.Xml;
 using System.Xml;
 using System.Reflection;
 using System.Reflection;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
+using Microsoft.Azure.Cosmos.Core;
 
 
 namespace TEAMModelOS.Function
 namespace TEAMModelOS.Function
 {
 {
@@ -1280,6 +1281,20 @@ namespace TEAMModelOS.Function
                 prodSum.service = servicesProductSumOrg;
                 prodSum.service = servicesProductSumOrg;
                 prodSum.hard = hardsProductSumOrg;
                 prodSum.hard = hardsProductSumOrg;
                 await client.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<SchoolProductSum>(prodSum, prodSum.id, new PartitionKey($"{prodSum.code}"));
                 await client.GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync<SchoolProductSum>(prodSum, prodSum.id, new PartitionKey($"{prodSum.code}"));
+                ///更新学生端学校列表
+                var res = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).GetList<dynamic>("select  c.id,c.city,c.name,c.province,c.picture,c.region from c ", "Base");
+                var schools = new { schools = res.list };
+                var blockBlob = _azureStorage.GetBlobContainerClient("0-public").GetBlobClient("schools.json");
+                string content_type = "application/octet-stream";
+                ContentTypeDict.dict.TryGetValue(".json", out string contenttype);
+                if (!string.IsNullOrEmpty(contenttype))
+                {
+                    content_type = contenttype;
+                }
+                byte[] bytes = System.Text.Encoding.Default.GetBytes(schools.ToJsonString());
+                Stream streamBlob = new MemoryStream(bytes);
+                await blockBlob.UploadAsync(streamBlob, true);
+                blockBlob.SetHttpHeaders(new BlobHttpHeaders { ContentType = content_type });
             }
             }
             catch (CosmosException ex)
             catch (CosmosException ex)
             {
             {

+ 49 - 9
TEAMModelOS.SDK/Models/Service/GenPDFService.cs

@@ -778,9 +778,52 @@ namespace TEAMModelOS.SDK
         /// </summary>
         /// </summary>
         public string blobFullUrl { get; set; }
         public string blobFullUrl { get; set; }
     }
     }
+    public static class HybridConstant
+    {
+        public static readonly string busy = "busy";
+        public static readonly string idle = "idle";
+        public static readonly string error = "error";
+        public static readonly string offline = "offline";
+        public static readonly string grant_type = "hybrid-cloud";
+        public static readonly string env_release = "release";
+        public static readonly string env_develop = "develop";
+        /// <summary>
+        /// 冗余时间
+        /// </summary>
+        public static readonly long time_excess = 5000;
+    }
 
 
-
-
+    public class HybridClient : ClientDevice 
+    {
+        /// <summary>
+        /// 授权类型,hybrid-cloud 
+        /// </summary>
+        public string? grant_type { get; set; }
+        /// <summary>
+        /// 客户端id
+        /// </summary>
+        public string? clientid { get; set; }
+        /// <summary>
+        /// SignalR的连接ID 不建议暴露。
+        /// </summary>
+        public string? connid { get; set; }
+        /// <summary>
+        /// 状态  busy 忙碌,free 空闲,down 离线,error 错误
+        /// </summary>
+        public string? status { get; set; }
+        /// <summary>
+        /// 最后更新时间
+        /// </summary>
+        public long last_time { get; set; }
+        /// <summary>
+        /// 超时时间,单位毫秒
+        /// </summary>
+        public long timeout { get; set; } = 30000;
+        /// <summary>
+        /// 延迟时间,单位毫秒
+        /// </summary>
+        public long delay { get; set; } = 3000;
+    }
 
 
     public class ScreenClient  : ClientDevice
     public class ScreenClient  : ClientDevice
     {
     {
@@ -911,15 +954,12 @@ namespace TEAMModelOS.SDK
         public static readonly string error = "error";
         public static readonly string error = "error";
         public static readonly string offline = "offline";
         public static readonly string offline = "offline";
         public static readonly string grant_type = "bookjs_api";
         public static readonly string grant_type = "bookjs_api";
-
-
         public static readonly string env_release = "release";
         public static readonly string env_release = "release";
         public static readonly string env_develop = "develop";
         public static readonly string env_develop = "develop";
-                
-    /// <summary>
-    /// 冗余时间
-    /// </summary>
-    public static readonly long time_excess = 5000;
+        /// <summary>
+        /// 冗余时间
+        /// </summary>
+        public static readonly long time_excess = 5000;
     }
     }
     public enum MessageType {
     public enum MessageType {
         conn_success,//连接成功
         conn_success,//连接成功

+ 8 - 8
TEAMModelOS.SDK/Models/Service/SchoolService.cs

@@ -1158,14 +1158,14 @@ namespace TEAMModelOS.SDK
                 }
                 }
                 if (graduate_classes.Any() || cancel_graduate_classes.Any())
                 if (graduate_classes.Any() || cancel_graduate_classes.Any())
                 {
                 {
-                    if (waite == 0)
-                    {
-                        _ = _httpTrigger.RequestHttpTrigger(new { graduate_classes = graduate_classes, cancel_graduate_classes = cancel_graduate_classes, schoolId = $"{school_base.id}" }, _option.Location, "graduate-change");
-                    }
-                    else
-                    {
-                        await _httpTrigger.RequestHttpTrigger(new { graduate_classes = graduate_classes, cancel_graduate_classes = cancel_graduate_classes, schoolId = $"{school_base.id}" }, _option.Location, "graduate-change");
-                    }
+                    //if (waite == 0)
+                    //{
+                    //    _ = _httpTrigger.RequestHttpTrigger(new { graduate_classes = graduate_classes, cancel_graduate_classes = cancel_graduate_classes, schoolId = $"{school_base.id}" }, _option.Location, "graduate-change");
+                    //}
+                    //else
+                    //{
+                    //    await _httpTrigger.RequestHttpTrigger(new { graduate_classes = graduate_classes, cancel_graduate_classes = cancel_graduate_classes, schoolId = $"{school_base.id}" }, _option.Location, "graduate-change");
+                    //}
                 }
                 }
             } catch (Exception ex) {
             } catch (Exception ex) {
                await _dingDing.SendBotMsg($"{_option.Location},{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);
                await _dingDing.SendBotMsg($"{_option.Location},{ex.Message}\n{ex.StackTrace}", GroupNames.醍摩豆服務運維群組);

+ 235 - 197
TEAMModelOS.SDK/Models/Service/StudentService.cs

@@ -1757,6 +1757,7 @@ namespace TEAMModelOS.SDK
                 writer.WriteString("style", "smart");
                 writer.WriteString("style", "smart");
                 writer.WriteString("openType", "1");
                 writer.WriteString("openType", "1");
                 writer.WriteString("scope", "school");
                 writer.WriteString("scope", "school");
+                writer.WriteNumber("graduate", 0);
                 writer.WriteEndObject();
                 writer.WriteEndObject();
                 writer.Flush();
                 writer.Flush();
 
 
@@ -1950,16 +1951,59 @@ namespace TEAMModelOS.SDK
                 }
                 }
                 List<Student> graduate_students = new List<Student>();
                 List<Student> graduate_students = new List<Student>();
                 List<Class> school_classes = new List<Class>();
                 List<Class> school_classes = new List<Class>();
+                List<object> ret = new List<object>();
                 string queryText = "";
                 string queryText = "";
                 if (graduate == 0)
                 if (graduate == 0)
                 {
                 {
-                    queryText = $"SELECT  *  FROM c WHERE c.code = 'Base-{schoolId}' and (c.graduate = 0 or IS_DEFINED(c.graduate) = false )";
+                    ///查询当年的毕业班
+                    string sql = sql = $"SELECT value c FROM c where  c.graduate =0"; ;
+
+                    var client = _azureCosmos.GetCosmosClient();
+                    await foreach (var item in client.GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIteratorSql<Class>
+                    (queryText: sql,
+                       requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Class-{schoolId}") }))
+                    {
+                        school_classes.Add(item);
+                    }
+
+                    queryText = $"SELECT  value c  FROM c WHERE c.code = 'Base-{schoolId}' and  (c.classId =null or IS_DEFINED(c.classId) = false or c.classId in ( {string.Join(",", school_classes.Select(x => $"'{x.id}'"))}))";
+                    string continuationToken = string.Empty;
+                    var container = _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student");
+                    var studentsRes = await container.GetList<Student>(queryText, $"Base-{schoolId}");
+                    if (studentsRes.list.IsNotEmpty())
+                    {
+                        foreach (var student in studentsRes.list)
+                        {
+                            var imeiObj = imeis.Find(x => x.stuid.Equals(student.id));
+                            Class stuClass = school_classes.Find(x =>!string.IsNullOrWhiteSpace(student.classId) &&  x.id.Equals(student.classId));
+                            ret.Add(new
+                            {
+                                student.id,
+                                student.name,
+                                student.picture,
+                                student.year,
+                                student.no,
+                                classId = stuClass?.id,
+                                classNo = stuClass?.no,
+                                className = stuClass?.name,
+                                gradeId = stuClass?.gradeId,
+                                student.periodId,
+                                classYear = stuClass?.year,
+                                irs = student.irs,
+                                imei = imeiObj?.id,
+                                gender = student.gender,
+                                hasEduOpenId = (!string.IsNullOrWhiteSpace(student.openId)) ? true : false,
+                                guardians = student.guardians,
+                                graduate = !string.IsNullOrWhiteSpace(student.classId) ? 0:student.graduate
+                            });
+
+                        }
+                    }
                 }
                 }
                 else
                 else
                 {
                 {
                     queryText = $"SELECT  *  FROM c WHERE c.code = 'Base-{schoolId}' and  c.graduate = 1 and c.year ={inyear} ";
                     queryText = $"SELECT  *  FROM c WHERE c.code = 'Base-{schoolId}' and  c.graduate = 1 and c.year ={inyear} ";
 
 
-
                     ///查询当年的毕业班
                     ///查询当年的毕业班
                     string sql = sql = $"SELECT value c FROM c where  c.graduate =1 and c.year ={inyear}"; ;
                     string sql = sql = $"SELECT value c FROM c where  c.graduate =1 and c.year ={inyear}"; ;
 
 
@@ -1973,215 +2017,209 @@ namespace TEAMModelOS.SDK
                     //如果已经毕业的班级不为空。
                     //如果已经毕业的班级不为空。
                     if (school_classes.IsNotEmpty())
                     if (school_classes.IsNotEmpty())
                     {
                     {
-                        string classSql = $"SELECT  value c    FROM c WHERE c.code = 'Base-{schoolId}' and  c.graduate = 1  and c.classId in(   {string.Join(",", school_classes.Select(x => $"'{x.id}'"))}  )";
+                        string classSql = $"SELECT  value c    FROM c WHERE c.code = 'Base-{schoolId}' and    c.classId in(   {string.Join(",", school_classes.Select(x => $"'{x.id}'"))}  )";
                         await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryIteratorSql<Student>(queryText: classSql,
                         await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student").GetItemQueryIteratorSql<Student>(queryText: classSql,
                         requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{schoolId}") }))
                         requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{schoolId}") }))
                         {
                         {
                             graduate_students.Add(item);
                             graduate_students.Add(item);
                         }
                         }
                     }
                     }
-                }
-
-
-                //回傳用ContinuationToken
-                string continuationToken = string.Empty;
-                var container = _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student");
-
-                //進行學生資料的查詢 TEAMModelOS-Student
-                await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
-                   .GetItemQueryStreamIteratorSql(
-                       queryText: queryText,
-                       requestOptions: new QueryRequestOptions()
-                       { PartitionKey = new PartitionKey($"Base-{schoolId}"), MaxItemCount = -1 }))
-                {
-                    continuationToken = item.ContinuationToken;
-                    using var json = await JsonDocument.ParseAsync(item.Content);
-                    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                    graduate_students.ForEach(o =>
                     {
                     {
-                        List<(string id, string name, string picture, int year, string no, List<StudentGuardian> guardians)> students = new List<(string id, string name, string picture, int year, string no, List<StudentGuardian> guardians)>();
-
-                        var accounts = json.RootElement.GetProperty("Documents").EnumerateArray();
-                        while (accounts.MoveNext())
+                        var imeiObj = imeis.Find(x => x.stuid.Equals(o.id));
+                        Class stuClass = school_classes.Find(x => x.id.Equals(o.classId));
+                        ret.Add(
+                        new
                         {
                         {
-                            JsonElement acc = accounts.Current;
-
-                            string classId = acc.GetProperty("classId").GetString();
-                            acc.TryGetProperty("irs", out JsonElement irs);
-                            acc.TryGetProperty("guardians", out JsonElement _guardians);
-                            List<StudentGuardian> guardians = new List<StudentGuardian>();
-
-                            if (_guardians.ValueKind.Equals(JsonValueKind.Array))
-                            {
-                                guardians = _guardians.Deserialize<List<StudentGuardian>>();
-                                if (guardians.Any())
-                                {
-                                    guardians = guardians.FindAll(x => !string.IsNullOrWhiteSpace(x.mobile));
-                                }
-                            }
-
-                            var imeiObj = imeis.Find(x => x.stuid.Equals(acc.GetProperty("id").GetString()));
-                            if (string.IsNullOrWhiteSpace(classId))
-                            {
-                                notJoinClassStuds.Add(
-                                        (
-                                            acc.GetProperty("id").GetString(),
-                                            acc.GetProperty("name").GetString(),
-                                            acc.GetProperty("picture").GetString(),
-                                            acc.GetProperty("year").GetInt32(),
-                                            acc.GetProperty("no").GetString(),
-                                            acc.TryGetProperty("periodId", out JsonElement _periodId) && _periodId.ValueKind.Equals(JsonValueKind.String) ? _periodId.GetString() : null,
-                                            $"{irs}",
-                                            imeiObj?.id,//imei
-                                            acc.TryGetProperty("gender", out JsonElement _gender) && _gender.ValueKind.Equals(JsonValueKind.String) ? _gender.GetString() : null,
-                                            acc.TryGetProperty("graduate", out JsonElement _graduate) && _graduate.ValueKind.Equals(JsonValueKind.Number) ? int.Parse($"{_graduate}") : 0,
-                                            acc.TryGetProperty("openId", out JsonElement _openId) && !string.IsNullOrWhiteSpace(_openId.GetString()) ? true : false,
-                                            guardians
-                                        )
-                                    );
-                            }
-                            else
-                            {
-                                if (dicClassStuds.ContainsKey(classId))
-                                {
-                                    dicClassStuds[classId].Add(
-                                            (
-                                                acc.GetProperty("id").GetString(),
-                                                acc.GetProperty("name").GetString(),
-                                                acc.GetProperty("picture").GetString(),
-                                                acc.GetProperty("year").GetInt32(),
-                                                acc.GetProperty("no").GetString(), acc.TryGetProperty("periodId", out JsonElement _periodId) && _periodId.ValueKind.Equals(JsonValueKind.String) ? _periodId.GetString() : null,
-                                                $"{irs}",
-                                                imeiObj?.id,//imei
-                                                acc.TryGetProperty("gender", out JsonElement _gender) && _gender.ValueKind.Equals(JsonValueKind.String) ? _gender.GetString() : null,
-                                                acc.TryGetProperty("graduate", out JsonElement _graduate) && _graduate.ValueKind.Equals(JsonValueKind.Number) ? int.Parse($"{_graduate}") : 0,
-                                                acc.TryGetProperty("openId", out JsonElement _openId) && !string.IsNullOrWhiteSpace(_openId.GetString()) ? true : false,
-                                                guardians
-                                            )
-                                        );
-                                }
-                                else
-                                {
-                                    dicClassStuds.Add(classId,
-                                            new List<(string id, string name, string picture, int year, string no, string periodId, string irs, string imei, string gender, int graduate, bool hasEduOpenId, List<StudentGuardian> guardians)>()
-                                            {
-                                                (
-                                                    acc.GetProperty("id").GetString(),
-                                                    acc.GetProperty("name").GetString(),
-                                                    acc.GetProperty("picture").GetString(),
-                                                    acc.GetProperty("year").GetInt32(),
-                                                    acc.GetProperty("no").GetString(),  acc.TryGetProperty("periodId",out JsonElement _periodId)&&  _periodId.ValueKind.Equals(JsonValueKind.String) ? _periodId.GetString() : null,
-                                                    $"{irs}"  ,
-                                                    imeiObj?.id,//imei
-                                                    acc.TryGetProperty("gender", out JsonElement _gender) && _gender.ValueKind.Equals(JsonValueKind.String) ? _gender.GetString() : null,
-                                                    acc.TryGetProperty("graduate", out JsonElement _graduate) && _graduate.ValueKind.Equals(JsonValueKind.Number) ? int.Parse($"{_graduate}"): 0,
-                                                    acc.TryGetProperty("openId", out JsonElement _openId) && !string.IsNullOrWhiteSpace(_openId.GetString()) ? true : false,
-                                                    guardians
-                                                )
-                                            }
-                                        );
-                                }
-                            }
-                        }
-                    }
+                            o.id,
+                            o.name,
+                            o.picture,
+                            o.year,
+                            o.no,
+                            classId = stuClass?.id,
+                            classNo = stuClass?.no,
+                            className = stuClass?.name,
+                            gradeId = stuClass?.gradeId,
+                            o.periodId,
+                            classYear = stuClass?.year,
+                            irs = o.irs,
+                            imei = imeiObj?.id,
+                            gender = o.gender,
+                            hasEduOpenId = (!string.IsNullOrWhiteSpace(o.openId)) ? true : false,
+                            guardians = o.guardians,
+                            graduate=1
+                        });
+                    });
                 }
                 }
 
 
-                //查學生所屬的教室及座號
-                List<object> ret = new List<object>();
+                //await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "Student")
+                //   .GetItemQueryStreamIteratorSql(
+                //       queryText: queryText,
+                //       requestOptions: new QueryRequestOptions()
+                //       { PartitionKey = new PartitionKey($"Base-{schoolId}"), MaxItemCount = -1 }))
+                //{
+                //    continuationToken = item.ContinuationToken;
+                //    using var json = await JsonDocument.ParseAsync(item.Content);
+                //    if (json.RootElement.TryGetProperty("_count", out JsonElement count) && count.GetUInt16() > 0)
+                //    {
+
+                //        var accounts = json.RootElement.GetProperty("Documents").EnumerateArray();
+                //        while (accounts.MoveNext())
+                //        {
+                //            JsonElement acc = accounts.Current;
+
+                //            string classId = acc.GetProperty("classId").GetString();
+                //            acc.TryGetProperty("irs", out JsonElement irs);
+                //            acc.TryGetProperty("guardians", out JsonElement _guardians);
+                //            List<StudentGuardian> guardians = new List<StudentGuardian>();
+
+                //            if (_guardians.ValueKind.Equals(JsonValueKind.Array))
+                //            {
+                //                guardians = _guardians.Deserialize<List<StudentGuardian>>();
+                //                if (guardians.Any())
+                //                {
+                //                    guardians = guardians.FindAll(x => !string.IsNullOrWhiteSpace(x.mobile));
+                //                }
+                //            }
+
+                //            var imeiObj = imeis.Find(x => x.stuid.Equals(acc.GetProperty("id").GetString()));
+                //            if (string.IsNullOrWhiteSpace(classId))
+                //            {
+                //                notJoinClassStuds.Add(
+                //                        (
+                //                            acc.GetProperty("id").GetString(),
+                //                            acc.GetProperty("name").GetString(),
+                //                            acc.GetProperty("picture").GetString(),
+                //                            acc.GetProperty("year").GetInt32(),
+                //                            acc.GetProperty("no").GetString(),
+                //                            acc.TryGetProperty("periodId", out JsonElement _periodId) && _periodId.ValueKind.Equals(JsonValueKind.String) ? _periodId.GetString() : null,
+                //                            $"{irs}",
+                //                            imeiObj?.id,//imei
+                //                            acc.TryGetProperty("gender", out JsonElement _gender) && _gender.ValueKind.Equals(JsonValueKind.String) ? _gender.GetString() : null,
+                //                            acc.TryGetProperty("graduate", out JsonElement _graduate) && _graduate.ValueKind.Equals(JsonValueKind.Number) ? int.Parse($"{_graduate}") : 0,
+                //                            acc.TryGetProperty("openId", out JsonElement _openId) && !string.IsNullOrWhiteSpace(_openId.GetString()) ? true : false,
+                //                            guardians
+                //                        )
+                //                    );
+                //            }
+                //            else
+                //            {
+                //                if (dicClassStuds.ContainsKey(classId))
+                //                {
+                //                    dicClassStuds[classId].Add(
+                //                            (
+                //                                acc.GetProperty("id").GetString(),
+                //                                acc.GetProperty("name").GetString(),
+                //                                acc.GetProperty("picture").GetString(),
+                //                                acc.GetProperty("year").GetInt32(),
+                //                                acc.GetProperty("no").GetString(), acc.TryGetProperty("periodId", out JsonElement _periodId) && _periodId.ValueKind.Equals(JsonValueKind.String) ? _periodId.GetString() : null,
+                //                                $"{irs}",
+                //                                imeiObj?.id,//imei
+                //                                acc.TryGetProperty("gender", out JsonElement _gender) && _gender.ValueKind.Equals(JsonValueKind.String) ? _gender.GetString() : null,
+                //                                acc.TryGetProperty("graduate", out JsonElement _graduate) && _graduate.ValueKind.Equals(JsonValueKind.Number) ? int.Parse($"{_graduate}") : 0,
+                //                                acc.TryGetProperty("openId", out JsonElement _openId) && !string.IsNullOrWhiteSpace(_openId.GetString()) ? true : false,
+                //                                guardians
+                //                            )
+                //                        );
+                //                }
+                //                else
+                //                {
+                //                    dicClassStuds.Add(classId,
+                //                            new List<(string id, string name, string picture, int year, string no, string periodId, string irs, string imei, string gender, int graduate, bool hasEduOpenId, List<StudentGuardian> guardians)>()
+                //                            {
+                //                                (
+                //                                    acc.GetProperty("id").GetString(),
+                //                                    acc.GetProperty("name").GetString(),
+                //                                    acc.GetProperty("picture").GetString(),
+                //                                    acc.GetProperty("year").GetInt32(),
+                //                                    acc.GetProperty("no").GetString(),  acc.TryGetProperty("periodId",out JsonElement _periodId)&&  _periodId.ValueKind.Equals(JsonValueKind.String) ? _periodId.GetString() : null,
+                //                                    $"{irs}"  ,
+                //                                    imeiObj?.id,//imei
+                //                                    acc.TryGetProperty("gender", out JsonElement _gender) && _gender.ValueKind.Equals(JsonValueKind.String) ? _gender.GetString() : null,
+                //                                    acc.TryGetProperty("graduate", out JsonElement _graduate) && _graduate.ValueKind.Equals(JsonValueKind.Number) ? int.Parse($"{_graduate}"): 0,
+                //                                    acc.TryGetProperty("openId", out JsonElement _openId) && !string.IsNullOrWhiteSpace(_openId.GetString()) ? true : false,
+                //                                    guardians
+                //                                )
+                //                            }
+                //                        );
+                //                }
+                //            }
+                //        }
+                //    }
+                //}
+
+              
 
 
                 //查教室的資訊,用以取得gradeId,periodId資訊。
                 //查教室的資訊,用以取得gradeId,periodId資訊。
-                var classInfos = await getClassInfoUseId(_azureCosmos, _dingDing, _option, schoolId, dicClassStuds.Keys.ToList());
+              //  var classInfos = await getClassInfoUseId(_azureCosmos, _dingDing, _option, schoolId, dicClassStuds.Keys.ToList());
 
 
                 //輪循所有教室學生的資料
                 //輪循所有教室學生的資料
-                foreach (var classStud in dicClassStuds)
-                {
-                    string classId = null, classNo = null, className = null, gradeId = null, periodId = null;
-                    int classYear = -1;
-                    if (classInfos.ContainsKey(classStud.Key))
-                    {
-                        classId = classInfos[classStud.Key].GetProperty("id").GetString();
-                        classNo = classInfos[classStud.Key].GetProperty("no").GetString();
-                        className = classInfos[classStud.Key].GetProperty("name").GetString();
-                        periodId = classInfos[classStud.Key].TryGetProperty("periodId", out JsonElement _periodId) && _periodId.ValueKind.Equals(JsonValueKind.String) ? _periodId.GetString() : null;
-                        if (classInfos[classStud.Key].TryGetProperty("year", out JsonElement year))
-                        {
-                            if (year.ValueKind.Equals(JsonValueKind.Number))
-                            {
-                                classYear = classInfos[classStud.Key].GetProperty("year").GetInt32();
-                            }
-                        }
-                    }
-
-                    var tmp = classStud.Value.Select(o =>
-                                     new
-                                     {
-                                         o.id,
-                                         o.name,
-                                         o.picture,
-                                         o.year,
-                                         o.no,
-                                         classId,
-                                         classNo,
-                                         className,
-                                         gradeId,
-                                         periodId = string.IsNullOrEmpty(periodId) ? o.periodId : periodId,
-                                         classYear,
-                                         irs = o.irs,
-                                         imei = o.imei,
-                                         gender = o.gender,
-                                         graduate = o.graduate,
-                                         hasEduOpenId = o.hasEduOpenId,
-                                         guardians = o.guardians,
-                                     });
-                    ret.AddRange(tmp);
-                }
-
-                //彙整沒有加入教室的學生
-                notJoinClassStuds.ForEach(o => ret.Add(
-                    new
-                    {
-                        o.id,
-                        o.name,
-                        o.picture,
-                        o.year,
-                        o.no,
-                        classId = (string)null,
-                        classNo = (string)null,
-                        className = (string)null,
-                        gradeId = (string)null,
-                        o.periodId,
-                        classYear = -1,
-                        irs = o.irs,
-                        imei = o.imei,
-                        gender = o.gender,
-                        hasEduOpenId = o.hasEduOpenId,
-                        guardians = o.guardians,
-                    }));
-
-                graduate_students.ForEach(o =>
-                {
-                    var imeiObj = imeis.Find(x => x.stuid.Equals(o.id));
-                    Class stuClass = school_classes.Find(x => x.id.Equals(o.classId));
-                    ret.Add(
-                    new
-                    {
-                        o.id,
-                        o.name,
-                        o.picture,
-                        o.year,
-                        o.no,
-                        classId = stuClass?.id,
-                        classNo = stuClass?.no,
-                        className = stuClass?.name,
-                        gradeId = stuClass?.gradeId,
-                        o.periodId,
-                        classYear = stuClass?.year,
-                        irs = o.irs,
-                        imei = imeiObj?.id,
-                        gender = o.gender,
-                        hasEduOpenId = (!string.IsNullOrWhiteSpace(o.openId)) ? true : false,
-                        guardians = o.guardians,
-                    });
-                });
+                //foreach (var classStud in dicClassStuds)
+                //{
+                //    string classId = null, classNo = null, className = null, gradeId = null, periodId = null;
+                //    int classYear = -1;
+                //    if (classInfos.ContainsKey(classStud.Key))
+                //    {
+                //        classId = classInfos[classStud.Key].GetProperty("id").GetString();
+                //        classNo = classInfos[classStud.Key].GetProperty("no").GetString();
+                //        className = classInfos[classStud.Key].GetProperty("name").GetString();
+                //        periodId = classInfos[classStud.Key].TryGetProperty("periodId", out JsonElement _periodId) && _periodId.ValueKind.Equals(JsonValueKind.String) ? _periodId.GetString() : null;
+                //        if (classInfos[classStud.Key].TryGetProperty("year", out JsonElement year))
+                //        {
+                //            if (year.ValueKind.Equals(JsonValueKind.Number))
+                //            {
+                //                classYear = classInfos[classStud.Key].GetProperty("year").GetInt32();
+                //            }
+                //        }
+                //    }
+
+                //    var tmp = classStud.Value.Select(o =>
+                //                     new
+                //                     {
+                //                         o.id,
+                //                         o.name,
+                //                         o.picture,
+                //                         o.year,
+                //                         o.no,
+                //                         classId,
+                //                         classNo,
+                //                         className,
+                //                         gradeId,
+                //                         periodId = string.IsNullOrEmpty(periodId) ? o.periodId : periodId,
+                //                         classYear,
+                //                         irs = o.irs,
+                //                         imei = o.imei,
+                //                         gender = o.gender,
+                //                         graduate = o.graduate,
+                //                         hasEduOpenId = o.hasEduOpenId,
+                //                         guardians = o.guardians,
+                //                     });
+                //    ret.AddRange(tmp);
+                //}
+
+                ////彙整沒有加入教室的學生
+                //notJoinClassStuds.ForEach(o => ret.Add(
+                //    new
+                //    {
+                //        o.id,
+                //        o.name,
+                //        o.picture,
+                //        o.year,
+                //        o.no,
+                //        classId = (string)null,
+                //        classNo = (string)null,
+                //        className = (string)null,
+                //        gradeId = (string)null,
+                //        o.periodId,
+                //        classYear = -1,
+                //        irs = o.irs,
+                //        imei = o.imei,
+                //        gender = o.gender,
+                //        hasEduOpenId = o.hasEduOpenId,
+                //        guardians = o.guardians,
+                //    }));
+
+                
                 return ret;
                 return ret;
             }
             }
             catch (Exception ex)
             catch (Exception ex)

+ 8 - 1
TEAMModelOS.sln

@@ -27,7 +27,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTEX.ScreenClient", "TEAMMo
 EndProject
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTEX.Test", "TEAMModelOS.Extension\HTEX.Test\HTEX.Test.csproj", "{0AB3F94D-9698-4DB1-B532-5E6C8E56F770}"
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTEX.Test", "TEAMModelOS.Extension\HTEX.Test\HTEX.Test.csproj", "{0AB3F94D-9698-4DB1-B532-5E6C8E56F770}"
 EndProject
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HTEX.DataETL", "HTEX.DataETL\HTEX.DataETL.csproj", "{1F30D9C2-3E52-4E7C-AE46-8C13916153D2}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HTEX.DataETL", "HTEX.DataETL\HTEX.DataETL.csproj", "{1F30D9C2-3E52-4E7C-AE46-8C13916153D2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IESHybridCloud", "IESHybridCloud\IESHybridCloud.csproj", "{A76FCB84-C9FF-46C6-8B82-FC7538779A98}"
 EndProject
 EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -85,6 +87,10 @@ Global
 		{1F30D9C2-3E52-4E7C-AE46-8C13916153D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1F30D9C2-3E52-4E7C-AE46-8C13916153D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{1F30D9C2-3E52-4E7C-AE46-8C13916153D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1F30D9C2-3E52-4E7C-AE46-8C13916153D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{1F30D9C2-3E52-4E7C-AE46-8C13916153D2}.Release|Any CPU.Build.0 = Release|Any CPU
 		{1F30D9C2-3E52-4E7C-AE46-8C13916153D2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A76FCB84-C9FF-46C6-8B82-FC7538779A98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A76FCB84-C9FF-46C6-8B82-FC7538779A98}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A76FCB84-C9FF-46C6-8B82-FC7538779A98}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A76FCB84-C9FF-46C6-8B82-FC7538779A98}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -98,6 +104,7 @@ Global
 		{BB3DD2CC-CAFA-4DE9-97FA-866465A274F1} = {9B74B53F-20E8-46CC-903B-62AEB1583DD7}
 		{BB3DD2CC-CAFA-4DE9-97FA-866465A274F1} = {9B74B53F-20E8-46CC-903B-62AEB1583DD7}
 		{0AB3F94D-9698-4DB1-B532-5E6C8E56F770} = {9B74B53F-20E8-46CC-903B-62AEB1583DD7}
 		{0AB3F94D-9698-4DB1-B532-5E6C8E56F770} = {9B74B53F-20E8-46CC-903B-62AEB1583DD7}
 		{1F30D9C2-3E52-4E7C-AE46-8C13916153D2} = {9B74B53F-20E8-46CC-903B-62AEB1583DD7}
 		{1F30D9C2-3E52-4E7C-AE46-8C13916153D2} = {9B74B53F-20E8-46CC-903B-62AEB1583DD7}
+		{A76FCB84-C9FF-46C6-8B82-FC7538779A98} = {9B74B53F-20E8-46CC-903B-62AEB1583DD7}
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {76440725-5E50-4288-851F-BA5C0BC8E8C6}
 		SolutionGuid = {76440725-5E50-4288-851F-BA5C0BC8E8C6}

+ 3 - 4
TEAMModelOS/Controllers/Client/AClassONEController.cs

@@ -196,7 +196,7 @@ namespace TEAMModelOS.Controllers
         [ProducesDefaultResponseType]
         [ProducesDefaultResponseType]
         [HttpPost("get-school-info")]
         [HttpPost("get-school-info")]
 #if !DEBUG
 #if !DEBUG
-        [Authorize(Roles = "AClassONE")]
+        [Authorize(Roles = "AClassONE,IES")]
 #endif
 #endif
         [AuthToken(Roles = "teacher,admin")]
         [AuthToken(Roles = "teacher,admin")]
         public async Task<IActionResult> GetSchoolInfo(JsonElement json) {
         public async Task<IActionResult> GetSchoolInfo(JsonElement json) {
@@ -411,9 +411,8 @@ namespace TEAMModelOS.Controllers
         /// <returns></returns>
         /// <returns></returns>
         [ProducesDefaultResponseType]
         [ProducesDefaultResponseType]
         [HttpPost("get-teach-info")]
         [HttpPost("get-teach-info")]
-#if !DEBUG
-        [Authorize(Roles = "AClassONE")]
-#endif
+
+        [Authorize(Roles = "AClassONE,IES")]
         [AuthToken(Roles = "teacher,admin")]
         [AuthToken(Roles = "teacher,admin")]
         public async Task<IActionResult> GetTeachInfo(JsonElement json) {
         public async Task<IActionResult> GetTeachInfo(JsonElement json) {
             (string tmdid, _, _, string school) = HttpContext.GetAuthTokenInfo();
             (string tmdid, _, _, string school) = HttpContext.GetAuthTokenInfo();

+ 186 - 0
TEAMModelOS/Filter/IESHybridCloudHub.cs

@@ -0,0 +1,186 @@
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Logging;
+using System.Threading.Tasks;
+using TEAMModelOS.SDK;
+using TEAMModelOS.SDK.DI.Device;
+using TEAMModelOS.SDK.DI;
+using Microsoft.Extensions.Primitives;
+using System.Text;
+using System.Web;
+using TEAMModelOS.SDK.Extension;
+using System;
+using System.Text.Json;
+using TEAMModelOS.SDK.Models;
+
+namespace TEAMModelOS.Filter
+{
+    public class IESHybridCloudHub: Hub<IClient>
+    {
+       
+        private readonly ILogger<IESHybridCloudHub> _logger;
+        private readonly AzureRedisFactory _azureRedis;
+        private readonly AzureStorageFactory _azureStorage;
+        private readonly DingDing _dingDing;
+        private readonly CoreDevice _device;
+        public IESHybridCloudHub(AzureRedisFactory azureRedis, ILogger<IESHybridCloudHub> logger, AzureStorageFactory azureStorage, DingDing dingDing, CoreDevice device)
+        {
+            _logger = logger;
+            _azureRedis = azureRedis;
+            _dingDing = dingDing;
+            _device=device;
+        }
+        public override async Task OnConnectedAsync() 
+        {
+            var serverDevice = await _device.GetCoreDevice();
+
+            var connid = Context.ConnectionId;
+            var httpContext = Context.GetHttpContext();
+            if (httpContext != null) 
+            {
+                //wss://www.winteach.cn/signalr/notify?grant_type=wechat_qrcode&scene=0a75aca57536490ba00fe62e27bb8f6c&id=U2MNiCFNPPuVcw2gUI_gRA
+                //wss://www.winteach.cn/signalr/notify?grant_type=bookjs_api&clientid={clientid}&id=客户端自动生成的
+                httpContext.Request.Query.TryGetValue("grant_type", out StringValues grant_type);
+                httpContext.Request.Query.TryGetValue("clientid", out StringValues clientid);
+                httpContext.Request.Query.TryGetValue("device", out StringValues _device);
+                await Groups.AddToGroupAsync(connid, grant_type!);
+                if (!clientid.Equals(StringValues.Empty) && !grant_type.Equals(StringValues.Empty)) 
+                {
+                    ///连接配置,并且使用钉钉 通知。
+                    var client = new SignalRClient
+                    {
+                        connid = connid,
+                        grant_type = grant_type,
+                        clientid= clientid,
+                        serverid=serverDevice.deviceId,
+                    };
+                    await _azureRedis.GetRedisClient(8).HashSetAsync($"SignalRClient:connects:{serverDevice.deviceId}", connid, client.ToJsonString());
+                    HybridClient device = HttpUtility.UrlDecode(_device, Encoding.Unicode).ToObject<HybridClient>();
+                    switch (true) 
+                    {
+                        case bool when grant_type.Equals(HybridConstant.grant_type):
+                            HybridClient hybridClient;
+                            var value = await _azureRedis.GetRedisClient(8).HashGetAsync($"HybridApi:clients:{serverDevice.deviceId}", client.clientid);
+                            if (value!=default  && value.HasValue)
+                            {
+                                hybridClient = value.ToString().ToObject<HybridClient>();
+
+                                // 这里不强制设置free ,因为如果是重连,可能正在执行任务,需要等待执行完成
+
+
+                                //先检查状态是否是在忙碌,在时间戳范围里,如果不在时间戳范围,强制free。
+                                if (!hybridClient.status!.Equals(ScreenConstant.idle) && hybridClient.last_time  + hybridClient.timeout+ hybridClient.delay + ScreenConstant.time_excess < DateTimeOffset.UtcNow.ToUnixTimeMilliseconds())
+                                {
+                                    hybridClient.status = ScreenConstant.idle;
+                                }
+                            }
+                            else
+                            {
+                                hybridClient = new HybridClient
+                                {
+                                    status = ScreenConstant.idle,
+                                };
+                            }
+                            hybridClient.connid=connid;
+                            hybridClient.grant_type = grant_type;
+                            hybridClient.clientid = clientid;
+                            hybridClient.os = device.os;
+                            hybridClient.port = device.port;
+                            hybridClient.name = device.name;
+                            hybridClient.region = device.region;
+                            hybridClient.remote = device.remote;
+                            hybridClient.networks = device.networks;
+                            hybridClient.delay = device.delay;
+                            hybridClient.timeout = device.timeout;
+                            hybridClient.cpu = device.cpu;
+                            hybridClient.ram = device.ram;
+                            hybridClient.last_time= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                            hybridClient.deviceId = device.deviceId;
+                            //连接成功,发送消息给客户端。
+                            await SendConnection(connid, new ConnectionMessage
+                            {
+                                connid=connid,
+                                clientid = clientid,
+                                status = hybridClient.status,
+                                grant_type = grant_type,
+                                message_type= MessageType.conn_success,
+                                content = $"连接成功"
+                            });
+                            _logger.LogInformation($"客户端连接成功=>{hybridClient.name},{hybridClient.region},{clientid}:\n{hybridClient.ToJsonString()}");
+                            await _azureRedis.GetRedisClient(8).HashSetAsync($"HybridApi:clients:{serverDevice.deviceId}", client.clientid, hybridClient.ToJsonString());
+                            break;
+                    }
+                }
+            }
+        }
+        public override async Task OnDisconnectedAsync(Exception? exception) 
+        {
+
+            var serverDevice = await _device.GetCoreDevice();
+            var connid = Context.ConnectionId;
+            var redisData = await _azureRedis.GetRedisClient(8).HashGetAsync($"SignalRClient:connects:{serverDevice.deviceId}", connid);
+            _logger.LogInformation($"客户端断开连接=>{connid} ");
+            ///连接配置,并且使用钉钉 离线通知。
+            if (!redisData.IsNullOrEmpty)
+            {
+                var client = redisData.ToString().ToObject<SignalRClient>();
+                await _azureRedis.GetRedisClient(8).HashDeleteAsync($"SignalRClient:connects:{serverDevice.deviceId}", connid);
+                if (client != null)
+                {
+                    await Groups.RemoveFromGroupAsync(connid, client.grant_type!);
+                    var value = await _azureRedis.GetRedisClient(8).HashGetAsync($"HybridApi:clients:{serverDevice.deviceId}", client.clientid);
+                    if (value!=default  && value.HasValue)
+                    {
+                        HybridClient hybridClient = value.ToString().ToObject<HybridClient>();
+                        _logger.LogInformation($"客户端断开连接=>{connid},{hybridClient.name},{hybridClient.region},{hybridClient.clientid} ");
+                        long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+                        // 判断是否过期
+                        if (hybridClient.status!.Equals(HybridConstant.busy))
+                        {
+                            //超时了
+                            if (hybridClient.last_time+hybridClient.timeout+hybridClient.delay+ ScreenConstant.time_excess <=now)
+                            {
+                                hybridClient.status=ScreenConstant.offline;
+                                hybridClient.connid= string.Empty;
+                            }
+                        }
+                        else
+                        {
+                            hybridClient.status=ScreenConstant.offline;
+                            hybridClient.connid= string.Empty;
+                        }
+                        await _azureRedis.GetRedisClient(8).HashSetAsync($"HybridApi:clients:{serverDevice.deviceId}", client.clientid, hybridClient.ToJsonString());
+                    }
+                }
+            }
+        }
+        public async Task ReceiveMessage(JsonElement message) 
+        {
+            ////接收消息
+            //如果是超时,放回队列。
+            ///分发新任务。
+            var serverDevice = await _device.GetCoreDevice();
+            long nowNew = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            var connid = Context.ConnectionId;
+            _logger.LogInformation($"接收消息:{message.ToJsonString()}");
+
+        }
+        public async Task SendConnection(string connectionId, MessageBody msg)
+        {
+            await Clients.Client(connectionId).ReceiveConnection(msg);
+        }
+        public async Task SendMessage(string connectionId, MessageBody msg)
+        {
+            await Clients.Client(connectionId).ReceiveMessage(msg);
+        }
+        public async Task SendDisConnection(string connectionId, MessageBody msg)
+        {
+            await Clients.Client(connectionId).ReceiveDisConnection(msg);
+        }
+    }
+    public interface IClient
+    {
+        Task ReceiveMessage(MessageBody message);
+        Task ReceiveConnection(MessageBody message);
+        Task ReceiveDisConnection(MessageBody message);
+    }
+}

+ 2 - 1
TEAMModelOS/Startup.cs

@@ -204,6 +204,7 @@ namespace TEAMModelOS
            // services.AddSingleton<ILoggerProvider, BlobLoggerProvider>();
            // services.AddSingleton<ILoggerProvider, BlobLoggerProvider>();
             services.AddMvcFilter<RequestAuditFilter>();
             services.AddMvcFilter<RequestAuditFilter>();
             services.AddNetMail();
             services.AddNetMail();
+            services.AddSignalR().AddAzureSignalR(Configuration.GetValue<string>("Azure:SignalR:ConnectionString"));
 #if !DEBUG
 #if !DEBUG
  //第一步: 配置gzip与br的压缩等级为最优
  //第一步: 配置gzip与br的压缩等级为最优
             //builder.Services.AddMyResponseCompression();
             //builder.Services.AddMyResponseCompression();
@@ -278,9 +279,9 @@ namespace TEAMModelOS
             //调用放在之后、UseRouting 和 UseCors,但在 UseEndpoints之前
             //调用放在之后、UseRouting 和 UseCors,但在 UseEndpoints之前
             app.UseAuthentication();
             app.UseAuthentication();
             app.UseAuthorization();
             app.UseAuthorization();
-            
             app.UseEndpoints(endpoints =>
             app.UseEndpoints(endpoints =>
             {
             {
+                endpoints.MapHub<IESHybridCloudHub>("/hybrid-cloud").RequireCors("any");
                 endpoints.MapControllers();
                 endpoints.MapControllers();
                 endpoints.MapServerSentEvents("/service/sse", new ServerSentEventsOptions
                 endpoints.MapServerSentEvents("/service/sse", new ServerSentEventsOptions
                 {
                 {