Bladeren bron

教育雲學生Login流程API

jeff 1 jaar geleden
bovenliggende
commit
a42f80ec25

+ 4 - 0
TEAMModelOS.SDK/Models/Cosmos/Student/Student.cs

@@ -65,6 +65,10 @@ namespace TEAMModelOS.SDK.Models
         /// 学生的专业id
         /// </summary>
         public string majorId { get; set; }
+        /// <summary>
+        /// 學生的OpenID (TW教育雲綁定ID)
+        /// </summary>
+        public string openId { get; set; }
     }
     public class Guardian : CosmosEntity
     {

+ 107 - 0
TEAMModelOS.SDK/Models/Service/LoginService.cs

@@ -4,7 +4,9 @@ using Microsoft.Extensions.Logging;
 using StackExchange.Redis;
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
+using System.Security.Cryptography;
 using System.Text;
 using System.Threading.Tasks;
 using TEAMModelOS.Models;
@@ -382,5 +384,110 @@ namespace TEAMModelOS.SDK.Models.Service
                 _logger.LogError(msg);
             }
         }
+
+        /// <summary>
+        /// AES加密
+        /// </summary>
+        /// <param name="content">原始字串</param>
+        /// <param name="key">自訂金鑰</param>
+        /// <param name="iv">自訂向量</param>
+        /// <returns></returns>
+        public static string AesEncrypt(string content, string key, string iv)
+        {
+            string encrypt = "";
+            try
+            {
+                MD5 md5 = MD5.Create();
+                SHA256 sha256 = SHA256.Create();
+                byte[] encrypted;
+                byte[] keyData = sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
+                byte[] ivData = md5.ComputeHash(Encoding.UTF8.GetBytes(iv));
+                byte[] dataByteArray = Encoding.UTF8.GetBytes(content);
+                using (System.Security.Cryptography.Aes aesAlg = System.Security.Cryptography.Aes.Create())
+                {
+                    aesAlg.Key = keyData;
+                    aesAlg.IV = ivData;
+
+                    // Create an encryptor to perform the stream transform.
+                    ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
+
+                    // Create the streams used for encryption.
+                    using (MemoryStream msEncrypt = new MemoryStream())
+                    {
+                        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
+                        {
+                            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
+                            {
+                                //Write all data to the stream.
+                                swEncrypt.Write(content);
+                            }
+                            encrypted = msEncrypt.ToArray();
+                        }
+                    }
+                }
+
+                // Return the encrypted bytes from the memory stream.
+                encrypt = Convert.ToBase64String(encrypted);
+                return encrypt;
+            }
+            catch (Exception ex)
+            {
+                //todo...
+            }
+
+            return encrypt;
+        }
+
+        /// <summary>
+        /// AES 解密
+        /// </summary>
+        /// <param name="hexString">已加密字串</param>
+        /// <param name="key">自訂金鑰</param>
+        /// <param name="iv">自訂向量</param>
+        /// <returns></returns>
+        public static string AesDecrypt(string hexString, string key, string iv)
+        {
+
+            string decrypt = hexString;
+            try
+            {
+                MD5 md5 = MD5.Create();
+                SHA256 sha256 = SHA256.Create();
+                byte[] keyData = sha256.ComputeHash(Encoding.UTF8.GetBytes(key));
+                byte[] ivData = md5.ComputeHash(Encoding.UTF8.GetBytes(iv));
+                byte[] dataByteArray = Convert.FromBase64String(hexString);
+                string plaintext = null;
+                using (System.Security.Cryptography.Aes aesAlg = System.Security.Cryptography.Aes.Create())
+                {
+                    aesAlg.Key = keyData;
+                    aesAlg.IV = ivData;
+
+                    // Create a decryptor to perform the stream transform.
+                    ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
+
+                    // Create the streams used for decryption.
+                    using (MemoryStream msDecrypt = new MemoryStream(dataByteArray))
+                    {
+                        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
+                        {
+                            using (StreamReader srDecrypt = new StreamReader(csDecrypt))
+                            {
+
+                                // Read the decrypted bytes from the decrypting stream
+                                // and place them in a string.
+                                plaintext = srDecrypt.ReadToEnd();
+                            }
+                        }
+                    }
+                }
+                return plaintext;
+            }
+            catch (Exception ex)
+            {
+                //todo...
+            }
+
+            return decrypt;
+        }
     }
 }

+ 188 - 142
TEAMModelOS/Controllers/Student/StudentController.cs

@@ -24,6 +24,7 @@ using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Hosting;
 using Microsoft.Extensions.Options;
 using Microsoft.Identity.Client;
+using Org.BouncyCastle.Asn1.Tsp;
 using StackExchange.Redis;
 using TEAMModelOS.Filter;
 using TEAMModelOS.Models;
@@ -485,65 +486,7 @@ namespace TEAMModelOS.Controllers
                     if (HashedPW.Equals(dbpw.GetString()))
                     {
                         (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck(school,$"{id}", $"{classId}", $"{school_code}", $"{picture}", $"{name}", schoolClient, teacherClient,  school.areaId,ip, client, student);
-                        //授权规模数量
-                        DateTimeOffset dateTime = DateTimeOffset.UtcNow;
-                        var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
-                        string key = $"Login:School:{school_code}:student-day:{dateDay}";
-                        SortedSetEntry[] countStudent = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores(key);
-                        int countAuthorized = 0;
-                        if (countStudent != null && countStudent.Length > 0)
-                        {
-                            bool notify=false;
-                            countAuthorized = countStudent.Length;
-                            if (school.scale > 0 && school.scale - countAuthorized <= 0) {
-                                //登录人数已达授权规模数上限
-                                if (!string.IsNullOrWhiteSpace(school.areaId))
-                                {
-                                    AreaSetting areaSetting = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemAsync<AreaSetting>(school.areaId, new PartitionKey("AreaSetting"));
-                                    if (areaSetting.ignoreScaleExpire > dateTime.ToUnixTimeMilliseconds())
-                                    {
-                                        //将人数控制在最大规模数以下。
-                                        countAuthorized = school.scale - 1;
-                                    }
-                                    else {
-                                        notify=true;
-                                    }
-                                }
-                                else {
-                                    notify = true;
-                                }
-                                if (notify) {
-                                    //通知key 一天只通知一次
-                                    string scaleNotifykey = $"Login:School:{school.id}:student-scale-notify:{dateDay}";
-                                    bool Exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync(scaleNotifykey);
-                                    if (!Exists) {
-                                        //获取学校管理员
-                                        List<IdNameCode> ids = new List<IdNameCode>();
-                                        string sql = $"select   value c from c    where c.code='Teacher-{school.id}' and c.status='join'  and  array_contains(c.roles,'admin') ";
-                                        List<SchoolTeacher> adminTeachers = new List<SchoolTeacher>();
-                                        await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
-                                            .GetItemQueryIterator<SchoolTeacher>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Teacher-{school.id}") }))
-                                        {
-                                            adminTeachers.Add(item);
-                                        }
-                                        if (adminTeachers.IsNotEmpty())
-                                        {
-
-                                            string sqlAdmin = $"select c.id,c.lang  as code ,c.name from c where c.id in ({string.Join(",", adminTeachers.Select(z => $"'{z.id}'"))}) ";
-                                            await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
-                                                .GetItemQueryIterator<IdNameCode>(queryText: sqlAdmin, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
-                                            {
-                                                ids.Add(item);
-                                            }
-                                            foreach (var uds in ids) {
-                                                _coreAPIHttpService.PushNotify(new List<IdNameCode> { uds}, $"school-scale-notify", Constant.NotifyType_IES5_Management, new Dictionary<string, object> { { "tmdname", uds.name }, { "countAuthorized", $"{countAuthorized}" }, { "scale", $"{school.scale}" }, { "schoolName", school.name }, { "schoolId", $"{school.id}" }, },_option.Location, _configuration, _dingDing,   _environment.ContentRootPath);
-                                            }
-                                            await _azureRedis.GetRedisClient(8).StringSetAsync(scaleNotifykey, scaleNotifykey, new TimeSpan(hours: 24, minutes: 0, seconds: 0));
-                                        }
-                                    }
-                                }
-                            }
-                        }
+                        int countAuthorized = await GetStudentAuthNumByScale($"{school_code}", school);
                         return Ok(new {school.scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
                     }
                     else
@@ -728,7 +671,7 @@ namespace TEAMModelOS.Controllers
                 string grant_type = "educloudtw";
                 string client_id = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
                 string redirect_uri = _configuration.GetValue<string>("HaBookAuth:CoreAccountAPI");
-                string nounce = RandomString(16);
+                string nonce = RandomString(16);
                 string lang = "zh-tw";
                 string open_code = _open_code.GetString();
                 if(!open_code.Contains("EduCloudTWL")) return BadRequest();
@@ -741,22 +684,13 @@ namespace TEAMModelOS.Controllers
                     {   "grant_type", grant_type },
                     {   "client_id", client_id },
                     {   "redirect_uri", $"{redirect_uri}/" },
-                    {   "nounce", nounce },
+                    {   "nonce", nonce },
                     {   "lang", lang },
                     {   "open_code", open_code },
                     {   "is_extrnal_id", is_extrnal_id }
                 };
-                var clientID = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
-                var clientSecret = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
-                var csToken = await CoreTokenExtensions.CreateAccessToken(clientID, clientSecret, location);
-
+                
                 var httpClient = _httpClient.CreateClient();
-                if (httpClient.DefaultRequestHeaders.Contains("Authorization"))
-                {
-                    httpClient.DefaultRequestHeaders.Remove("Authorization");
-                }
-                httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {csToken.AccessToken}");
-                string test = dict.ToJsonString();
                 HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
                 HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
                 if (httpResponse.StatusCode == HttpStatusCode.OK)
@@ -785,7 +719,7 @@ namespace TEAMModelOS.Controllers
                 }
                 //用OpenData取得學生資訊
                 Student stuinfo = new Student();
-                var queryLogin = $"SELECT * FROM c WHERE IS_DEFINED(c.openid) AND c.openid = '{openData.open_id}'";
+                var queryLogin = $"SELECT * FROM c WHERE IS_DEFINED(c.openid) AND c.openId = '{openData.open_id}'";
                 await foreach (var item in studentClient.GetItemQueryStreamIterator(queryText: queryLogin, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Base-{openData.schoolCode}") }))
                 {
                     using var json = await JsonDocument.ParseAsync(item.ContentStream);
@@ -805,71 +739,7 @@ namespace TEAMModelOS.Controllers
                         return Ok(new { error = 3, message = "Graduate already!" });
                     }
                     (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck(school, $"{stuinfo.id}", $"{stuinfo.classId}", $"{openData.schoolCode}", $"{stuinfo.picture}", $"{stuinfo.name}", schoolClient, teacherClient, school.areaId, ip, client, stuinfo);
-                    //授权规模数量
-                    DateTimeOffset dateTime = DateTimeOffset.UtcNow;
-                    var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
-                    string key = $"Login:School:{openData.schoolCode}:student-day:{dateDay}";
-                    SortedSetEntry[] countStudent = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores(key);
-                    int countAuthorized = 0;
-                    if (countStudent != null && countStudent.Length > 0)
-                    {
-                        bool notify = false;
-                        countAuthorized = countStudent.Length;
-                        if (school.scale > 0 && school.scale - countAuthorized <= 0)
-                        {
-                            //登录人数已达授权规模数上限
-                            if (!string.IsNullOrWhiteSpace(school.areaId))
-                            {
-                                AreaSetting areaSetting = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemAsync<AreaSetting>(school.areaId, new PartitionKey("AreaSetting"));
-                                if (areaSetting.ignoreScaleExpire > dateTime.ToUnixTimeMilliseconds())
-                                {
-                                    //将人数控制在最大规模数以下。
-                                    countAuthorized = school.scale - 1;
-                                }
-                                else
-                                {
-                                    notify = true;
-                                }
-                            }
-                            else
-                            {
-                                notify = true;
-                            }
-                            if (notify)
-                            {
-                                //通知key 一天只通知一次
-                                string scaleNotifykey = $"Login:School:{school.id}:student-scale-notify:{dateDay}";
-                                bool Exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync(scaleNotifykey);
-                                if (!Exists)
-                                {
-                                    //获取学校管理员
-                                    List<IdNameCode> ids = new List<IdNameCode>();
-                                    string sql = $"select   value c from c    where c.code='Teacher-{school.id}' and c.status='join' and array_contains(c.roles,'admin') ";
-                                    List<SchoolTeacher> adminTeachers = new List<SchoolTeacher>();
-                                    await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
-                                        .GetItemQueryIterator<SchoolTeacher>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Teacher-{school.id}") }))
-                                    {
-                                        adminTeachers.Add(item);
-                                    }
-                                    if (adminTeachers.IsNotEmpty())
-                                    {
-
-                                        string sqlAdmin = $"select c.id,c.lang  as code ,c.name from c where c.id in ({string.Join(",", adminTeachers.Select(z => $"'{z.id}'"))}) ";
-                                        await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
-                                            .GetItemQueryIterator<IdNameCode>(queryText: sqlAdmin, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
-                                        {
-                                            ids.Add(item);
-                                        }
-                                        foreach (var uds in ids)
-                                        {
-                                            _coreAPIHttpService.PushNotify(new List<IdNameCode> { uds }, $"school-scale-notify", Constant.NotifyType_IES5_Management, new Dictionary<string, object> { { "tmdname", uds.name }, { "countAuthorized", $"{countAuthorized}" }, { "scale", $"{school.scale}" }, { "schoolName", school.name }, { "schoolId", $"{school.id}" }, }, _option.Location, _configuration, _dingDing, _environment.ContentRootPath);
-                                        }
-                                        await _azureRedis.GetRedisClient(8).StringSetAsync(scaleNotifykey, scaleNotifykey, new TimeSpan(hours: 24, minutes: 0, seconds: 0));
-                                    }
-                                }
-                            }
-                        }
-                    }
+                    int countAuthorized = await GetStudentAuthNumByScale($"{school.id}", school);
                     return Ok(new { school.scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
                 }
                 //分歧2 無此學生 => 取得該校同名學生資訊
@@ -917,6 +787,11 @@ namespace TEAMModelOS.Controllers
                             }
                         }
                     }
+                    //OpenID AES加密
+                    string aeskey = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+                    string aesiv = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+                    string encOpenId = LoginService.AesEncrypt(openData.open_id, aeskey, aesiv);
+                    //string decOpenId = LoginService.AesDecrypt(encOpenId, aeskey, aesiv);
                     //回傳值
                     List<stuOpenDataOrientation> result = new List<stuOpenDataOrientation>();
                     foreach (Student studata in stuList)
@@ -931,7 +806,6 @@ namespace TEAMModelOS.Controllers
                         stuResultRow.periodId = studata.periodId;
                         stuResultRow.periodName = (!string.IsNullOrWhiteSpace(studata.periodId) && periodDic.ContainsKey(studata.periodId)) ? periodDic[studata.periodId] : string.Empty;
                         stuResultRow.year = studata.year;
-                        stuResultRow.gender = studata.gender;
                         Period curPeriod = new Period();
                         if (!string.IsNullOrWhiteSpace(studata.periodId))
                         {
@@ -947,12 +821,108 @@ namespace TEAMModelOS.Controllers
                         result.Add(stuResultRow);
                     }
                     //回傳值
-                    return Ok(result);
+                    return Ok(new { openToken = encOpenId, students = result } );
                 }
             }
             catch (Exception ex)
             {
-                await _dingDing.SendBotMsg($"OS,{_option.Location},student/openlogin()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
+                //await _dingDing.SendBotMsg($"OS,{_option.Location},student/openlogin()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
+                return BadRequest();
+            }
+        }
+
+        /// <summary>
+        /// 學生追加教育雲ID
+        /// </summary>
+        /// <param name = "request" ></ param >
+        [AllowAnonymous]
+        [HttpPost("add-open-stu")]
+        public async Task<IActionResult> addOpenidToStudent(JsonElement request)
+        {
+            try
+            {
+                //參數取得
+                string location = _option.Location;
+                string openToken = (request.TryGetProperty("open_token", out JsonElement _open_token)) ? _open_token.GetString() : string.Empty;
+                if(string.IsNullOrWhiteSpace(openToken)) return BadRequest();
+                if (!location.Contains("Global")) return BadRequest();
+                string schoolCode = (request.TryGetProperty("school_code", out JsonElement _school_code)) ? _school_code.GetString() : string.Empty;
+                if (string.IsNullOrWhiteSpace(schoolCode)) return BadRequest();
+                string stuid = (request.TryGetProperty("stuid", out JsonElement _stuid)) ? _stuid.GetString() : string.Empty;
+                if (string.IsNullOrWhiteSpace(stuid)) return BadRequest();
+                //OpenID AES解密
+                string aeskey = _configuration.GetValue<string>("HaBookAuth:CoreService:clientID");
+                string aesiv = _configuration.GetValue<string>("HaBookAuth:CoreService:clientSecret");
+                string openId = LoginService.AesDecrypt(openToken, aeskey, aesiv);
+                if(string.IsNullOrWhiteSpace(openId)) return BadRequest();
+
+                var client = _azureCosmos.GetCosmosClient();
+                var schoolClient = client.GetContainer(Constant.TEAMModelOS, "School");
+                var teacherClient = client.GetContainer(Constant.TEAMModelOS, "Teacher");
+                var studentClient = client.GetContainer(Constant.TEAMModelOS, "Student");
+                //取得學校基本資料
+                School school = new School();
+                try
+                {
+                    school = await schoolClient.ReadItemAsync<School>($"{schoolCode}", new PartitionKey("Base"));
+                }
+                catch (CosmosException ex)
+                {
+                    return Ok(new { error = 1, message = "Can not find school data." });
+                }
+                //取得學生基本資料
+                Student student = new Student();
+                try
+                {
+                    student = await studentClient.ReadItemAsync<Student>($"{stuid}", new PartitionKey($"Base-{schoolCode}"));
+                }
+                catch (CosmosException ex)
+                {
+                    return Ok(new { error = 2, message = "Can not find student data." });
+                }
+                if (student.graduate.Equals(1))
+                {
+                    return Ok(new { error = 3, message = "Graduate already!" });
+                }
+                //1.向CS詢問是否已綁定
+                stuOpenData openData = new stuOpenData();
+                string grant_type = "bind";
+                string csv2Domain = _configuration.GetValue<string>("HaBookAuth:CoreAPI");
+                string csv2Url = $"{csv2Domain}/oauth2/EduCloudTWBingManage";
+                Dictionary<string, object> dict = new() {
+                        {   "grant_type", grant_type },
+                        {   "open_id", openId },
+                        {   "id", $"{school.id},{student.id}" }
+                };
+
+                var httpClient = _httpClient.CreateClient();
+                HttpContent content = new StringContent(dict.ToJsonString(), Encoding.UTF8, "application/json");
+                HttpResponseMessage httpResponse = await httpClient.PostAsync(csv2Url, content);
+                if (httpResponse.StatusCode == HttpStatusCode.OK)
+                {
+                    string responseContent = await httpResponse.Content.ReadAsStringAsync();
+                    if(!string.IsNullOrWhiteSpace(responseContent))
+                    {
+                        csApiResponse csResult = responseContent.ToObject<csApiResponse>();
+                        return Ok(csResult.message);
+                    }
+                }
+                else
+                {
+                    return Ok(new { error = 1, message = "Can not get opendata from CS." });
+                }
+                //2.將OpenID放入學生Base
+                student.openId = openId;
+                await studentClient.ReplaceItemAsync(student, student.id);
+                //3.學生登入流程
+                (string ip, string region) = await LoginService.LoginIp(HttpContext, _searcher);
+                (string auth_token, string blob_uri, string blob_sas, object classinfo, List<object> courses, AuthenticationResult token) = await StudentCheck(school, $"{student.id}", $"{student.classId}", $"{schoolCode}", $"{student.picture}", $"{student.name}", schoolClient, teacherClient, school.areaId, ip, client, student);
+                int countAuthorized = await GetStudentAuthNumByScale($"{schoolCode}", school);
+                return Ok(new { school.scale, countAuthorized, location = _option.Location, error = 0, auth_token, blob_uri, blob_sas, classinfo, courses, token = new { access_token = token.AccessToken, expires_in = token.ExpiresOn, id_token = auth_token, token_type = token.TokenType } });
+            }
+            catch (Exception ex)
+            {
+                //await _dingDing.SendBotMsg($"OS,{_option.Location},student/addOpenidToStudent()\n{ex.Message}\n{ex.StackTrace}\n", GroupNames.醍摩豆服務運維群組);
                 return BadRequest();
             }
         }
@@ -960,7 +930,7 @@ namespace TEAMModelOS.Controllers
         //查询学生名单详情
         [ProducesDefaultResponseType]
         //[AuthToken(Roles = "teacher")]
-        [HttpPost("get-summary-student")]
+        [HttpPost("add-openid-student")]
         [AuthToken(Roles = "teacher,admin,student")]
 
 #if !DEBUG
@@ -1227,6 +1197,77 @@ namespace TEAMModelOS.Controllers
             return result;
         }
 
+        //學生登入後根據學校規模取得授權數
+        private async Task<int> GetStudentAuthNumByScale(string school_code, School school)
+        {
+            //授权规模数量
+            DateTimeOffset dateTime = DateTimeOffset.UtcNow;
+            var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
+            string key = $"Login:School:{school_code}:student-day:{dateDay}";
+            SortedSetEntry[] countStudent = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores(key);
+            int countAuthorized = 0;
+            if (countStudent != null && countStudent.Length > 0)
+            {
+                bool notify = false;
+                countAuthorized = countStudent.Length;
+                if (school.scale > 0 && school.scale - countAuthorized <= 0)
+                {
+                    //登录人数已达授权规模数上限
+                    if (!string.IsNullOrWhiteSpace(school.areaId))
+                    {
+                        AreaSetting areaSetting = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemAsync<AreaSetting>(school.areaId, new PartitionKey("AreaSetting"));
+                        if (areaSetting.ignoreScaleExpire > dateTime.ToUnixTimeMilliseconds())
+                        {
+                            //将人数控制在最大规模数以下。
+                            countAuthorized = school.scale - 1;
+                        }
+                        else
+                        {
+                            notify = true;
+                        }
+                    }
+                    else
+                    {
+                        notify = true;
+                    }
+                    if (notify)
+                    {
+                        //通知key 一天只通知一次
+                        string scaleNotifykey = $"Login:School:{school.id}:student-scale-notify:{dateDay}";
+                        bool Exists = await _azureRedis.GetRedisClient(8).KeyExistsAsync(scaleNotifykey);
+                        if (!Exists)
+                        {
+                            //获取学校管理员
+                            List<IdNameCode> ids = new List<IdNameCode>();
+                            string sql = $"select   value c from c    where c.code='Teacher-{school.id}' and c.status='join'  and  array_contains(c.roles,'admin') ";
+                            List<SchoolTeacher> adminTeachers = new List<SchoolTeacher>();
+                            await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
+                                .GetItemQueryIterator<SchoolTeacher>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Teacher-{school.id}") }))
+                            {
+                                adminTeachers.Add(item);
+                            }
+                            if (adminTeachers.IsNotEmpty())
+                            {
+
+                                string sqlAdmin = $"select c.id,c.lang  as code ,c.name from c where c.id in ({string.Join(",", adminTeachers.Select(z => $"'{z.id}'"))}) ";
+                                await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher)
+                                    .GetItemQueryIterator<IdNameCode>(queryText: sqlAdmin, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
+                                {
+                                    ids.Add(item);
+                                }
+                                foreach (var uds in ids)
+                                {
+                                    _coreAPIHttpService.PushNotify(new List<IdNameCode> { uds }, $"school-scale-notify", Constant.NotifyType_IES5_Management, new Dictionary<string, object> { { "tmdname", uds.name }, { "countAuthorized", $"{countAuthorized}" }, { "scale", $"{school.scale}" }, { "schoolName", school.name }, { "schoolId", $"{school.id}" }, }, _option.Location, _configuration, _dingDing, _environment.ContentRootPath);
+                                }
+                                await _azureRedis.GetRedisClient(8).StringSetAsync(scaleNotifykey, scaleNotifykey, new TimeSpan(hours: 24, minutes: 0, seconds: 0));
+                            }
+                        }
+                    }
+                }
+            }
+            return countAuthorized;
+        }
+
         public static string RandomString(int length)
         {
             Random random = new Random();
@@ -1243,6 +1284,12 @@ namespace TEAMModelOS.Controllers
             public string open_mail { get; set; }
             public string schoolCode { get; set; }
         }
+        //CS API 返回架構
+        private class csApiResponse
+        {
+            public int error { get; set; }
+            public string message { get; set; }
+        }
         //無法取得OpenID搜尋學生姓名回傳的學生資料
         private class stuOpenDataOrientation
         {
@@ -1255,7 +1302,6 @@ namespace TEAMModelOS.Controllers
             public string periodId { get; set; }
             public string periodName { get; set; }
             public int year { get; set; }
-            public string gender { get; set; }
             public string gradeIndex { get; set; }
             public string gradeName { get; set; }
         }