CrazyIter_Bin 11 months ago
parent
commit
d5264e8ff8

+ 967 - 5
TEAMModelOS.Function/IESHttpTrigger.cs

@@ -1,24 +1,986 @@
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Http;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc;
 using Microsoft.Azure.Functions.Worker;
 using Microsoft.Azure.Functions.Worker;
+using Microsoft.Extensions.Configuration;
 using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Logging;
+using TEAMModelOS.Function.DI;
+using TEAMModelOS.SDK.DI;
+using TEAMModelOS.SDK;
+using TEAMModelOS.Models;
+using Microsoft.Extensions.Options;
+using System.Net;
+using System.Text.Json;
+using System.Reflection;
+using HTEXLib.COMM.Helpers;
+using StackExchange.Redis;
+using static TEAMModelOS.SDK.Services.BlobService;
+using TEAMModelOS.SDK.Models;
+using Azure.Cosmos;
+using TEAMModelOS.SDK.Extension;
+using System.Dynamic;
+using Azure.Storage.Blobs.Models;
+using TEAMModelOS.SDK.Models.Table;
 
 
 namespace TEAMModelOS.Function
 namespace TEAMModelOS.Function
 {
 {
     public class IESHttpTrigger
     public class IESHttpTrigger
     {
     {
         private readonly ILogger<IESHttpTrigger> _logger;
         private readonly ILogger<IESHttpTrigger> _logger;
-
-        public IESHttpTrigger(ILogger<IESHttpTrigger> logger)
+        private readonly AzureCosmosFactory _azureCosmos;
+        private readonly DingDing _dingDing;
+        private readonly AzureStorageFactory _azureStorage;
+        private readonly AzureRedisFactory _azureRedis;
+        private readonly IHttpClientFactory _httpClient;
+        private readonly Option? _option;
+        private readonly CoreAPIHttpService _coreAPIHttpService;
+        private readonly IConfiguration _configuration;
+        private readonly BackgroundWorkerQueue _backgroundWorkerQueue;
+        public IESHttpTrigger(ILogger<IESHttpTrigger> logger, AzureCosmosFactory azureCosmos, DingDing dingDing, CoreAPIHttpService coreAPIHttpService, AzureStorageFactory azureStorage , AzureRedisFactory azureRedis, IHttpClientFactory httpClient, IOptionsSnapshot<Option> option,
+             IConfiguration configuration, BackgroundWorkerQueue backgroundWorkerQueue)
         {
         {
             _logger = logger;
             _logger = logger;
+            _azureCosmos = azureCosmos;
+            _dingDing = dingDing;
+            _azureStorage = azureStorage;
+            _azureRedis = azureRedis;
+            _httpClient = httpClient;
+            _coreAPIHttpService = coreAPIHttpService;
+            _option = option?.Value;
+            _configuration = configuration;
+            _backgroundWorkerQueue = backgroundWorkerQueue;
+        }
+
+        [Function("upsert-student-portrait")]
+        public async  Task<IActionResult> UpsertStudentPortrait([HttpTrigger(AuthorizationLevel.Anonymous,  "post",Route =null )] HttpRequest req)
+        {
+            _logger.LogInformation("C# HTTP trigger function processed a request.");
+
+            string data = await new StreamReader(req.Body).ReadToEndAsync();
+            var json = JsonDocument.Parse(data).RootElement;
+            //var response = req.CreateResponse(HttpStatusCode.OK);
+            var responseData = await OpenApiService.UpsertStudentPortrait(_azureCosmos, _dingDing, _azureRedis, json);
+            //  await response.WriteAsJsonAsync(new { data = responseData });
+            // return new OkObjectResult("Welcome to Azure Functions!");
+            return new OkObjectResult(new { data = responseData });
         }
         }
 
 
-        [Function("IESHttpTrigger")]
-        public IActionResult Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
+
+        [Function("system-info-function")]
+        public async Task<IActionResult> SystemInfo([HttpTrigger(AuthorizationLevel.Anonymous,  "post", Route = null)] HttpRequest req)
         {
         {
             _logger.LogInformation("C# HTTP trigger function processed a request.");
             _logger.LogInformation("C# HTTP trigger function processed a request.");
-            return new OkObjectResult("Welcome to Azure Functions!");
+
+            Type attr = this.GetType();
+            string currentDirectory = Path.GetDirectoryName(attr.Assembly.Location);
+            Assembly assembly = Assembly.LoadFrom($"{currentDirectory}\\TEAMModelOS.FunctionV4.dll");
+            var description = assembly.GetCustomAttribute<AssemblyDescriptionAttribute>().Description;
+            //var v1 = Assembly.GetEntryAssembly().GetName().Version;
+            //var v2 = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
+            // var description = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyDescriptionAttribute>().Description;
+            var version = Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>().Version;
+            long nowtime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
+            //Console.WriteLine($"Assembly.GetEntryAssembly().GetName().Version: " +
+
+            //                  $"{Assembly.GetEntryAssembly().GetName().Version}");5.2107.12.1
+
+            //Console.WriteLine($"Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>().Version:" +
+            //                  $"{Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>().Version}");5.2107.12.1
+
+            //Console.WriteLine($"Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion:" +
+
+            //                  $"{Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion}");5.2107.12
+            return new OkObjectResult(new { version, description, nowtime });
+        }
+        [Function("surplus-space-notify")]
+        public   IActionResult SurplusSpaceNotify([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
+        {
+            string msg = "";
+            _backgroundWorkerQueue.QueueBackgroundWorkItem(async token =>
+            {
+                try
+                {
+                    string data = await new StreamReader(req.Body).ReadToEndAsync();
+                    var json = JsonDocument.Parse(data).RootElement;
+                    json.TryGetProperty("name", out JsonElement _name);
+                    json.TryGetProperty("scope", out JsonElement _scope);
+                    json.TryGetProperty("percent", out JsonElement _percent);
+                    double percent = _percent.GetDouble();
+                    string name = _name.ToString();
+                    string scope = _scope.ToString();
+
+                    int tag = 11;
+                    if (percent <= 10 && percent > 5)
+                    {
+                        tag = 10;
+                    }
+                    else if (percent <= 5 && percent > 0)
+                    {
+                        tag = 5;
+                    }
+                    else if (percent <= 0)
+                    {
+                        tag = 0;
+                    }
+
+                    List<IdNameCode> ids = new List<IdNameCode>();
+                    Teacher teacher = null;
+                    School school = null;
+
+                    if (scope.Equals("school", StringComparison.OrdinalIgnoreCase))
+                    {
+                        school = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReadItemAsync<School>(name, new PartitionKey("Base"));
+                        string sql = $"select   value c from c    where c.code='Teacher-{name}' 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-{name}") }))
+                        {
+                            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);
+                            }
+
+                        }
+                    }
+                    else
+                    {
+                        teacher=  await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Teacher).ReadItemAsync<Teacher>(name, new PartitionKey("Base"));
+                        ids.Add(new IdNameCode
+                        {
+                            id = teacher.id,
+                            name = teacher.name,
+                            code = teacher.lang
+                        });
+                    }
+                    //如果已经扩容请忽略此通知!
+                    string key = scope.Equals("school", StringComparison.OrdinalIgnoreCase) ? $"Blob:Space:School:Notify:{name}" : $"Blob:Space:Private:Notify:{name}";
+                    foreach (var idnamecode in ids)
+                    {
+                        string filed = $"{idnamecode.id}-{tag}";
+                        BlobSpaceNotify? blobSpaceNotify = null;
+                        RedisValue value = await _azureRedis.GetRedisClient(8).HashGetAsync(key, filed);
+                        if (value != default && !value.IsNullOrEmpty)
+                        {
+                            blobSpaceNotify = value.ToString().ToObject<BlobSpaceNotify>();
+                        }
+                        if (tag < 11)
+                        {
+
+                            if (blobSpaceNotify == null)
+                            {
+                                if ("school".Equals(scope, StringComparison.OrdinalIgnoreCase))
+                                {
+                                    _coreAPIHttpService.PushNotify(new List<IdNameCode> { idnamecode }, $"blob-space-school-notify", Constant.NotifyType_IES5_Management,
+                                    new Dictionary<string, object> { { "tmdname", idnamecode.name }, { "schoolName", school.name }, { "percent", $"{tag}" }, { "schoolId", $"{school.id}" }, { "tmdid", idnamecode.id } },
+                                    $"{Environment.GetEnvironmentVariable("Option:Location")}", _configuration, _dingDing, "");
+
+                                }
+                                else
+                                {
+                                    _coreAPIHttpService.PushNotify(new List<IdNameCode> { idnamecode }, $"blob-space-private-notify", Constant.NotifyType_IES5_Management,
+                                        new Dictionary<string, object> { { "tmdname", idnamecode.name }, { "percent", $"{tag}" }, { "tmdid", idnamecode.id } },
+                                        $"{Environment.GetEnvironmentVariable("Option:Location")}", _configuration, _dingDing, "");
+                                }
+                                blobSpaceNotify = new BlobSpaceNotify { id = idnamecode.id, tag = tag, containerName = name, scope = scope, notifyIndex = Guid.NewGuid().ToString() };
+                                await _azureRedis.GetRedisClient(8).HashSetAsync(key, filed, blobSpaceNotify.ToJsonString());
+                                await _azureRedis.GetRedisClient(8).KeyExpireAsync(key, new TimeSpan(hours: 7*24, minutes: 0, seconds: 0));
+                            }
+                            else
+                            {
+                                //已经发送过的不在提交
+                            }
+                        }
+                        else
+                        {
+                            if (blobSpaceNotify != null)
+                            {
+                                //撤销
+                                var index = blobSpaceNotify.notifyIndex;
+                                await _azureRedis.GetRedisClient(8).HashDeleteAsync(key, filed);
+                                _coreAPIHttpService.CancelNotify(index, $"{Environment.GetEnvironmentVariable("Option:Location")}", _configuration);
+                            }
+
+                        }
+                    }
+
+                }
+                catch (Exception ex)
+                {
+                    await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")},空间不足,通知发送处理异常{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+                }
+
+
+            });
+            return new OkObjectResult(new { msg });
+        }
+
+        [Function("area-artsetting-change")]
+        public async Task<IActionResult> AreaArtSettingChange([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
+        {
+            string msg = "";
+            try
+            {
+                string data = await new StreamReader(req.Body).ReadToEndAsync();
+                var json = JsonDocument.Parse(data).RootElement;
+                json.TryGetProperty("areaId", out JsonElement _areaId);
+                string schoolSQL = $"select value c from c where c.areaId='{_areaId}'";
+                List<School> schools = new List<School>();
+                await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School)
+                               .GetItemQueryIterator<School>(queryText: schoolSQL, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base") }))
+                {
+                    schools.Add(item);
+                }
+                ArtSetting artSetting = await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Normal).ReadItemAsync<ArtSetting>($"{_areaId}", new PartitionKey("ArtSetting"));
+                foreach (var school in schools)
+                {
+                    msg = school.ToJsonString();
+                    List<Period> periods = new List<Period>();
+                    var hastype_period = school.period.Where(p => !string.IsNullOrWhiteSpace(p.periodType));
+                    if (hastype_period.Any())
+                    {
+                        periods.AddRange(hastype_period);
+                    }
+                    var nottype_period = school.period.Where(p => string.IsNullOrWhiteSpace(p.periodType));
+                    if (nottype_period!=null && nottype_period.Count()>0)
+                    {
+                        foreach (var period in nottype_period)
+                        {
+                            if (period.name.Contains("小学"))
+                            {
+                                period.periodType= "primary";
+                            }
+                            if (period.name.Contains("初中"))
+                            {
+                                period.periodType = "junior";
+                            }
+                            if (period.name.Contains("高中"))
+                            {
+                                period.periodType = "senior";
+                            }
+                            if (string.IsNullOrWhiteSpace(period.periodType) && school.period.Count == 1)
+                            {
+                                if (school.name.Contains("小学"))
+                                {
+                                    period.periodType = "primary";
+
+                                }
+                                if (school.name.Contains("初中"))
+                                {
+                                    period.periodType = "junior";
+                                }
+                                if (school.name.Contains("高中"))
+                                {
+                                    period.periodType = "senior";
+                                }
+                            }
+                            if (!string.IsNullOrWhiteSpace(period.periodType))
+                            {
+                                periods.Add(period);
+                            }
+                        }
+                    }
+                    foreach (var period in periods)
+                    {
+                        var dimension = artSetting.dimensions.FindAll(x => x.type.Intersect(new List<string> { period.periodType }).Any());
+                        var bindIds = period.subjects.Where(s => !string.IsNullOrWhiteSpace(s.bindId)).Select(x => x.bindId);
+                        //该学段未同步学科的。
+                        var unBindIds = dimension.Where(z => !string.IsNullOrWhiteSpace(z.subjectBind)).Select(x => x.subjectBind).ToHashSet().Except(bindIds);
+                        if (unBindIds.Any()  && unBindIds.Count()>0)
+                        {
+                            //尝试寻找同名学科且没有设置bindId的
+                            foreach (var unBindId in unBindIds)
+                            {
+                                var subjects = artSetting.dimensions.FindAll(d => !string.IsNullOrWhiteSpace(d.subjectBind) && !string.IsNullOrWhiteSpace(d.subject) && d.subjectBind.Equals(unBindId))?.Select(m => m.subject);
+                                if (subjects != null)
+                                {
+                                    foreach (var subject in subjects)
+                                    {
+                                        //获取同名学科,且没绑定的
+                                        var sub = period.subjects.FindAll(sub => sub.name.Contains(subject) && string.IsNullOrWhiteSpace(sub.bindId));
+                                        if (sub.IsNotEmpty())
+                                        {
+                                            sub[0].bindId = unBindId;
+                                        }
+                                        else
+                                        {
+                                            period.subjects.Add(new Subject { id = Guid.NewGuid().ToString(), name = subject, bindId = unBindId, type = 1 });
+
+                                        }
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                        var period_subjects = period.subjects.Where(s => !string.IsNullOrWhiteSpace(s.bindId));
+                        foreach (var subject in period_subjects)
+                        {
+                            var dim = dimension.Where(x => x.subjectBind.Equals(subject.bindId));
+                            if (dim.Any())
+                            {
+                                Knowledge old = null;
+                                string sql = $"select value(c) from c where c.periodId = '{period.id}'";
+                                string pk = $"Knowledge-{school.id}-{subject.id}";
+                                await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").
+                                    GetItemQueryIterator<Knowledge>(queryText: sql, requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey(pk) }))
+                                {
+                                    old = item;
+                                    break;
+                                }
+
+                                //同步知识块。
+
+                                if (old != null)
+                                {
+                                    bool change = false;
+                                    //如果之前的是1 来源于区级,后面因区级删除,应该还原为0。
+                                    var oldBlocks = old.blocks.Select(x => x.name).ToHashSet();
+                                    var dimBlocks = dim.SelectMany(d => d.blocks);
+                                    //增加的
+                                    var addBlocks = dimBlocks.Except(oldBlocks);
+                                    //减少的
+                                    var cutBlocks = oldBlocks.Except(dimBlocks);
+                                    foreach (var add in addBlocks)
+                                    {
+                                        old.blocks.Add(new Block { name = add, source = 1 });
+                                        change = true;
+                                    }
+                                    //减少的还原为0
+                                    if (cutBlocks.Any())
+                                    {
+                                        old.blocks.ForEach(ob => {
+                                            if (cutBlocks.Contains(ob.name))
+                                            {
+                                                ob.source = 0;
+                                                change = true;
+                                            }
+                                        });
+                                    }
+                                    foreach (var db in dimBlocks)
+                                    {
+                                        old.blocks.ForEach(ob => {
+                                            if (db.Equals(ob.name))
+                                            {
+                                                ob.source = 1;
+                                                change = true;
+                                            }
+                                        });
+                                    }
+                                    if (change)
+                                    {
+                                        await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReplaceItemAsync(old, old.id, new PartitionKey(old.code));
+                                    }
+                                    var count = new { pcount = old.points != null ? old.points.Count : 0, bcount = old.blocks != null ? old.blocks.Count : 0 };
+                                    //处理知识点,知识块计数问题
+                                    await _azureRedis.GetRedisClient(8).HashSetAsync($"Knowledge:Count:{old.owner}-{old.subjectId}", old.periodId, count.ToJsonString());
+                                }
+                                else
+                                {
+                                    var blocks = dim.SelectMany(x => x.blocks).Select(bs => new Block { name = bs, source = 1 });
+                                    if (blocks.Any())
+                                    {
+                                        var _new = new Knowledge
+                                        {
+                                            id = Guid.NewGuid().ToString(),
+                                            pk = "Knowledge",
+                                            code = pk,
+                                            owner = school.id,
+                                            periodId = period.id,
+                                            subjectId = subject.id,
+                                            blocks = blocks.ToList()
+                                        };
+                                        await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).CreateItemAsync(_new, new PartitionKey(_new.code));
+                                        var count = new { pcount = _new.points != null ? _new.points.Count : 0, bcount = _new.blocks != null ? _new.blocks.Count : 0 };
+                                        //处理知识点,知识块计数问题
+                                        await _azureRedis.GetRedisClient(8).HashSetAsync($"Knowledge:Count:{_new.owner}-{_new.subjectId}", _new.periodId, count.ToJsonString());
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReplaceItemAsync(school, school.id, new PartitionKey(school.code));
+                }
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")},area-artsetting-change,{ex.Message}\n{ex.StackTrace}\n{msg}", GroupNames.醍摩豆服務運維群組);
+            }
+            return new OkObjectResult(new { });
+        }
+        [Function("graduate-change")]
+        public async Task<IActionResult> GraduateChange([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
+        {
+            dynamic jsondata = new ExpandoObject();
+            try
+            {
+                string data = await new StreamReader(req.Body).ReadToEndAsync();
+                var json = JsonDocument.Parse(data).RootElement;
+                jsondata = json;
+                //await  _dingDing.SendBotMsg( "毕业状态变更:"+json.ToJsonString(), GroupNames.成都开发測試群組);
+                string schoolId = null;
+                if (json.TryGetProperty("schoolId", out JsonElement _schoolId))
+                {
+                    schoolId = $"{_schoolId}";
+                }
+                if (string.IsNullOrEmpty(schoolId))
+                {
+                    return new OkObjectResult(new { });
+                }
+                //计算毕业的
+                if (json.TryGetProperty("graduate_classes", out JsonElement _graduate_classes))
+                {
+                    List<Class> graduate_classes = _graduate_classes.ToObject<List<Class>>();
+                    if (graduate_classes.IsNotEmpty())
+                    {
+                        var ids = graduate_classes.Where(x => !string.IsNullOrWhiteSpace(x.id)).Select(x => $"'{x.id}'");
+                        List<Student> students = new List<Student>();
+                        string sql = $"select value c from c where  (c.graduate = 0 or  IS_DEFINED(c.graduate) = false)  and  c.classId in ({string.Join(",", ids)})";
+                        await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student)
+                            .GetItemQueryIterator<Student>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base-{schoolId}") }))
+                        {
+                            item.graduate = 1;
+                            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}"));
+                        }
+                    }
+                }
+                //未毕业的
+                if (json.TryGetProperty("cancel_graduate_classes", out JsonElement _cancel_graduate_classes))
+                {
+                    List<Class> cancel_graduate_classes = _cancel_graduate_classes.ToObject<List<Class>>();
+                    if (cancel_graduate_classes.IsNotEmpty())
+                    {
+                        var ids = cancel_graduate_classes.Where(x => !string.IsNullOrWhiteSpace(x.id)).Select(x => $"'{x.id}'");
+                        List<Student> students = new List<Student>();
+                        string sql = $"select value c from c where  c.graduate =1   and  c.classId in ({string.Join(",", ids)})";
+                        await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student)
+                            .GetItemQueryIterator<Student>(queryText: sql, requestOptions: new QueryRequestOptions { PartitionKey = new PartitionKey($"Base-{schoolId}") }))
+                        {
+                            item.graduate = 0;
+                            students.Add(item);
+                        }
+                        foreach (var item in students)
+                        {
+                            item.graduate = 0;
+                            await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.Student).ReplaceItemAsync<Student>(item, item.id, new PartitionKey($"Base-{schoolId}"));
+                        }
+                        foreach (var item in cancel_graduate_classes)
+                        {
+                            item.graduate = 0;
+                            await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, Constant.School).ReplaceItemAsync<Class>(item, item.id, new PartitionKey($"Class-{schoolId}"));
+                        }
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"graduate-change,{ex.Message}\n{ex.StackTrace}\n{jsondata.ToJsonString()}", GroupNames.醍摩豆服務運維群組);
+            }
+            return new OkObjectResult(new {   });
+        }
+
+        [Function("lesson-tag-change")]
+        public async Task<IActionResult> LessonTagChange([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
+        {
+            string data = await new StreamReader(req.Body).ReadToEndAsync();
+            var json = JsonDocument.Parse(data).RootElement;
+            List<TagOldNew> old_new = null;
+            string school = null;
+            if (json.TryGetProperty("school", out JsonElement _school))
+            {
+                school = _school.GetString();
+            }
+            if (json.TryGetProperty("old_new", out JsonElement _old_new))
+            {
+                old_new = _old_new.ToObject<List<TagOldNew>>();
+            }
+            if (old_new.IsNotEmpty() && !string.IsNullOrWhiteSpace(school))
+            {
+                foreach (var on in old_new)
+                {
+                    List<LessonRecord> lessonRecords = new List<LessonRecord>();
+                    string sql = $"select value(c) from c    where array_contains(c.category,'{on._old}') ";
+                    await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<LessonRecord>
+                        (queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"LessonRecord-{_school}") }))
+                    {
+                        lessonRecords.Add(item);
+                    }
+
+                    lessonRecords.ForEach(item =>
+                    {
+                        //修改标签
+                        if (!string.IsNullOrWhiteSpace(on._new))
+                        {
+                            for (int i = 0; i < item.category.Count; i++)
+                            {
+                                if (item.category[i].Equals(on._old))
+                                {
+                                    item.category[i] = on._new;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            //表示删除标签
+                            item.category.RemoveAll(x => x.Equals(on._old));
+                        }
+                    });
+                    foreach (var item in lessonRecords)
+                    {
+                        await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync(item, item.id, new PartitionKey(item.code));
+                    }
+                }
+            }
+            return new OkObjectResult(new { data= json});
+        }
+
+        [Function("knowledge-change")]
+        public async Task<IActionResult> KnowledgeChange([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
+        {
+            string data = await new StreamReader(req.Body).ReadToEndAsync();
+            var json = JsonDocument.Parse(data).RootElement;
+            List<TagOldNew> old_new = null;
+            string school = null;
+            if (json.TryGetProperty("school", out JsonElement _school))
+            {
+                school = _school.GetString();
+            }
+            if (json.TryGetProperty("old_new", out JsonElement _old_new))
+            {
+                old_new = _old_new.ToObject<List<TagOldNew>>();
+            }
+            if (old_new.IsNotEmpty() && !string.IsNullOrWhiteSpace(school))
+            {
+                foreach (var on in old_new)
+                {
+                    List<ItemInfo> items = new List<ItemInfo>();
+                    string sql = $"select value(c) from c    where array_contains(c.knowledge,'{on._old}') ";
+                    await foreach (var item in _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").GetItemQueryIterator<ItemInfo>
+                        (queryText: sql.ToString(), requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey($"Item-{_school}") }))
+                    {
+                        items.Add(item);
+                    }
+
+                    items.ForEach(item =>
+                    {
+                        //修改知识点
+                        if (!string.IsNullOrEmpty(on._new))
+                        {
+                            for (int i = 0; i < item.knowledge.Count; i++)
+                            {
+                                if (item.knowledge[i].Equals(on._old))
+                                {
+                                    item.knowledge[i] = on._new;
+                                }
+                            }
+                        }
+                        else
+                        {
+                            //表示删除知识点
+                            item.knowledge.RemoveAll(x => x.Equals(on._old));
+                        }
+                    });
+                    foreach (var item in items)
+                    {
+                        ItemBlob itemBlob = null;
+                        try
+                        {
+                            BlobDownloadInfo blobDownloadResult = await _azureStorage.GetBlobContainerClient($"{school}").GetBlobClient($"/item/{item.id}/{item.id}.json").DownloadAsync();
+                            if (blobDownloadResult != null)
+                            {
+                                var blob = JsonDocument.Parse(blobDownloadResult.Content);
+                                itemBlob = blob.RootElement.ToObject<ItemBlob>();
+                                itemBlob.exercise.knowledge = item.knowledge;
+                                await _azureStorage.GetBlobContainerClient($"{school}").UploadFileByContainer(itemBlob.ToJsonString(), "item", $"{item.id}/{item.id}.json", true);
+                            }
+                        }
+                        catch (Exception ex)
+                        {
+                            itemBlob = null;
+                        }
+                        await _azureCosmos.GetCosmosClient().GetContainer(Constant.TEAMModelOS, "School").ReplaceItemAsync(item, item.id, new PartitionKey(item.code));
+                    }
+                }
+            }
+            return new OkObjectResult(new {data=json });
+        }
+        [Function("online-record")]
+        public async Task<IActionResult> OnlineRecord([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req)
+        {
+           
+           // return new OkObjectResult(new { });
+            string data = await new StreamReader(req.Body).ReadToEndAsync();
+            var json = JsonDocument.Parse(data).RootElement;
+            try
+            {
+                string school = null;
+                string scope = null;
+                string id = null;
+                string ip = null;
+                int expire = 1;
+
+                if (json.TryGetProperty("school", out JsonElement _school))
+                    school = _school.GetString();
+
+                if (json.TryGetProperty("scope", out JsonElement _scope))
+                    scope = _scope.GetString();
+
+                if (json.TryGetProperty("id", out JsonElement _id))
+                    id = _id.GetString();
+
+                if (json.TryGetProperty("ip", out JsonElement _ip))
+                    ip = _ip.GetString();
+                if (json.TryGetProperty("expire", out JsonElement _expire))
+                    expire = _expire.GetInt32();
+
+                var table = _azureStorage.GetCloudTableClient().GetTableReference("IESLogin");
+                var cosmosClient = _azureCosmos.GetCosmosClient();
+
+                DateTimeOffset dateTime = DateTimeOffset.UtcNow;
+                var dateHour = dateTime.ToString("yyyyMMddHH"); //获取当天的小时
+                var dateDay = dateTime.ToString("yyyyMMdd"); //获取当天的日期
+                var dateMonth = dateTime.ToString("yyyyMM");//获取当月的日期
+                var currentHour = dateTime.Hour;   //当前小时
+                var currentDay = dateTime.Day;   //当前天
+                long Expire = dateTime.AddHours(expire).ToUnixTimeMilliseconds();  //token到期时间
+                long now = dateTime.ToUnixTimeMilliseconds();   //当前时间戳
+                DateTime hour = DateTime.UtcNow.AddHours(25);   //25小时到期
+                DateTime month = DateTime.UtcNow.AddDays(32);   //一个月到期
+                var delTbHour = dateTime.AddHours(-168).ToString("yyyyMMddHH");   //168小时前
+                var delTbDay = dateTime.AddDays(-180).ToString("yyyyMMdd");   //180天前            
+                switch (scope)
+                {
+                    case "teacher":
+                        try
+                        {
+                            //Teacher teacher = await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReadItemAsync<Teacher>(id, new PartitionKey("Base"));
+                            //teacher.loginInfos = new List<LoginInfo>() { new LoginInfo { expire = Expire, ip = ip, time = now } };
+                            //await cosmosClient.GetContainer("TEAMModelOS", "Teacher").ReplaceItemAsync<Teacher>(teacher, teacher.id, new PartitionKey("Base"));
+                        }
+                        catch { }
+                        break;
+                    case "student":
+                        try
+                        {
+                            // Student student = await cosmosClient.GetContainer("TEAMModelOS", Constant.Student).ReadItemAsync<Student>(id, new PartitionKey($"Base-{school}"));
+                            //student.loginInfos = new List<LoginInfo>() { new LoginInfo { expire = Expire, ip = ip, time = now } };
+                            //await cosmosClient.GetContainer("TEAMModelOS", Constant.Student).ReplaceItemAsync<Student>(student, student.id, new PartitionKey($"Base-{school}"));
+                            string key = $"Login:School:{school}:student-day:{dateDay}";
+                            //记录一个学校每天每个学生登录的次数
+                            await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync(key, id, 1);
+                            //获取key到期的时间
+                            await _azureRedis.GetRedisClient(8).KeyExpireAsync(key, hour);  //设置到期时间25小时
+                        }
+                        catch (Exception ex) { await _dingDing.SendBotMsg($"{ex.Message}{ex.StackTrace}", GroupNames.成都开发測試群組); }
+                        break;
+                    case "tmduser":
+                        try
+                        {
+                            //TmdUser tmdUser = await cosmosClient.GetContainer("TEAMModelOS", Constant.Student).ReadItemAsync<TmdUser>(id, new PartitionKey("Base"));
+                            //tmdUser.loginInfos = new List<LoginInfo>() { new LoginInfo { expire = Expire, ip = ip, time = now } };
+                            //await cosmosClient.GetContainer("TEAMModelOS", Constant.Student).ReplaceItemAsync<TmdUser>(tmdUser, tmdUser.id, new PartitionKey("Base"));
+                        }
+                        catch { }
+                        break;
+                }
+
+                //天
+                SortedSetEntry[] dayCnt = null;
+                //月
+                SortedSetEntry[] monthCnt = null;
+                try
+                {
+                    await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:IES:{scope}:{dateDay}", $"{currentHour}", 1);//一天24小时  小时为单位
+                    await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:IES:{scope}:{dateMonth}", $"{currentDay}", 1); //一天的累计   天为单位
+
+                    var resDay = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"Login:IES:{scope}:{dateDay}");
+                    if (resDay == null)
+                        await _azureRedis.GetRedisClient(8).KeyExpireAsync($"Login:IES:{scope}:{dateDay}", hour);  //设置到期时间
+                    var rspMonth = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"Login:IES:{scope}:{dateMonth}");
+                    if (rspMonth == null)
+                        await _azureRedis.GetRedisClient(8).KeyExpireAsync($"Login:IES:{scope}:{dateMonth}", month);  //设置到期时间
+
+                    //保存当前小时统计
+                    dayCnt = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:{scope}:{dateDay}");
+                    //保存当前的统计数据
+                    monthCnt = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:IES:{scope}:{dateMonth}");
+                }
+                catch { }
+                if (dayCnt != null && dayCnt.Length > 0)
+                {
+                    List<HourLogin> hourLogins = new();
+                    foreach (var dCnt in dayCnt)
+                    {
+                        if (((int)dCnt.Element) == currentHour)
+                        {
+                            var tphourLogins = await table.QueryWhereString<HourLogin>($"PartitionKey eq 'HourLogin' and RowKey eq '{dateHour}'");
+                            if (tphourLogins.Count > 0)
+                            {
+                                foreach (var hourLogin in tphourLogins)
+                                {
+                                    if (scope.Equals("teacher"))
+                                        hourLogin.Teacher = (int)dCnt.Score;
+                                    else if (scope.Equals("student"))
+                                        hourLogin.Student = (int)dCnt.Score;
+                                    else
+                                        hourLogin.TmdUser = (int)dCnt.Score;
+                                    hourLogins.Add(hourLogin);
+                                }
+                            }
+                            else
+                            {
+                                HourLogin hourLogin = new() { PartitionKey = $"HourLogin", RowKey = dateHour, Hour = currentHour };
+                                if (scope.Equals("teacher"))
+                                {
+                                    hourLogin.Teacher = 1;
+                                    hourLogin.Student = 0;
+                                    hourLogin.TmdUser = 0;
+                                }
+                                else if (scope.Equals("student"))
+                                {
+                                    hourLogin.Teacher = 0;
+                                    hourLogin.Student = 1;
+                                    hourLogin.TmdUser = 0;
+                                }
+                                else
+                                {
+                                    hourLogin.Teacher = 0;
+                                    hourLogin.Student = 0;
+                                    hourLogin.TmdUser = 1;
+                                }
+                                hourLogins.Add(hourLogin);
+                            }
+                        }
+                    }
+                    await table.SaveOrUpdateAll(hourLogins);  //保存和更新保存当前小时登录次数
+                }
+                if (monthCnt != null && monthCnt.Length > 0)
+                {
+                    List<DayLogin> dayLogins = new();
+                    foreach (var mCnt in monthCnt)
+                    {
+                        if (((int)mCnt.Element) == currentDay)
+                        {
+                            //保存当天的峰值
+                            var tbDays = await table.QueryWhereString<DayLogin>($"PartitionKey eq 'DayLogin' and RowKey eq '{dateDay}'");
+                            if (tbDays.Count > 0)
+                            {
+                                foreach (var dayLogin in tbDays)
+                                {
+                                    if (scope.Equals("teacher"))
+                                        dayLogin.Teacher = (int)mCnt.Score;
+                                    else if (scope.Equals("student"))
+                                        dayLogin.Student = (int)mCnt.Score;
+                                    else
+                                        dayLogin.TmdUser = (int)mCnt.Score;
+                                    dayLogins.Add(dayLogin);
+                                }
+                            }
+                            else
+                            {
+                                //保存当月每天的峰值
+                                DayLogin dayLogin = new() { PartitionKey = $"DayLogin", RowKey = dateDay, Day = currentDay };
+                                if (scope.Equals("teacher"))
+                                {
+                                    dayLogin.Teacher = 1;
+                                    dayLogin.Student = 0;
+                                    dayLogin.TmdUser = 0;
+                                }
+                                else if (scope.Equals("student"))
+                                {
+                                    dayLogin.Teacher = 0;
+                                    dayLogin.Student = 1;
+                                    dayLogin.TmdUser = 0;
+                                }
+                                else
+                                {
+                                    dayLogin.Teacher = 0;
+                                    dayLogin.Student = 0;
+                                    dayLogin.TmdUser = 1;
+                                }
+                                dayLogins.Add(dayLogin);
+                            }
+                        }
+                    }
+                    await table.SaveOrUpdateAll(dayLogins);// 保存当月每天在线数据
+                }
+
+                string tbHourSql = $"PartitionKey eq 'HourLogin' and RowKey le '{delTbHour}'";
+
+                string tbDaySql = $"PartitionKey eq 'DayLogin' and RowKey le '{delTbDay}'";
+                try
+                {
+                    await table.DeleteStringWhere<HourLogin>(rowKey: tbHourSql);  //删除168小时前的数据
+                    await table.DeleteStringWhere<DayLogin>(rowKey: tbDaySql);   //删除180天前的数据
+                }
+                catch { }
+
+                if (!string.IsNullOrWhiteSpace(school))
+                {
+                    //天
+                    SortedSetEntry[] scDayCnt = null;
+                    //月
+                    SortedSetEntry[] scMonthCnt = null;
+                    try
+                    {
+                        await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:School:{school}:{scope}:{dateDay}", $"{currentHour}", 1);//当天当前小时在线人加1
+                        await _azureRedis.GetRedisClient(8).SortedSetIncrementAsync($"Login:School:{school}:{scope}:{dateMonth}", $"{currentDay}", 1); //当天的在线加1
+
+                        var reScDay = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"Login:School:{school}:{scope}:{dateDay}");
+                        if (reScDay == null)
+                            await _azureRedis.GetRedisClient(8).KeyExpireAsync($"Login:School:{school}:{scope}:{dateDay}", hour);  //设置到期时间
+                        var reScMonth = await _azureRedis.GetRedisClient(8).KeyTimeToLiveAsync($"Login:School:{school}:{scope}:{dateMonth}");
+                        if (reScMonth == null)
+                            await _azureRedis.GetRedisClient(8).KeyExpireAsync($"Login:School:{school}:{scope}:{dateMonth}", month);  //设置到期时间
+
+                        //保存学校当天每小时的
+                        scDayCnt = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:School:{school}:{scope}:{dateDay}");
+                        //学校天峰值
+                        scMonthCnt = _azureRedis.GetRedisClient(8).SortedSetRangeByScoreWithScores($"Login:School:{school}:{scope}:{dateMonth}");
+                    }
+                    catch { }
+
+                    if (scDayCnt != null && scDayCnt.Length > 0)
+                    {
+                        List<HourLoginSchool> hourLoginSchools = new();
+                        foreach (var scDCnt in scDayCnt)
+                        {
+                            if (((int)scDCnt.Element) == currentHour)
+                            {
+                                var tmpHour = await table.QueryWhereString<HourLoginSchool>($"PartitionKey eq 'HourLogin-{school}' and RowKey eq '{dateHour}'");
+                                if (tmpHour.Count > 0)
+                                {
+                                    foreach (var hLoginSc in tmpHour)
+                                    {
+                                        if (scope.Equals("teacher"))
+                                            hLoginSc.Teacher = (int)scDCnt.Score;
+                                        else if (scope.Equals("student"))
+                                            hLoginSc.Student = (int)scDCnt.Score;
+                                        else
+                                            hLoginSc.TmdUser = (int)scDCnt.Score;
+                                        hourLoginSchools.Add(hLoginSc);
+                                    }
+                                }
+                                else
+                                {
+                                    //学校小时峰值
+                                    HourLoginSchool hourLoginSc = new() { PartitionKey = $"HourLogin-{school}", RowKey = dateHour, Hour = currentHour, School = school };
+                                    if (scope.Equals("teacher"))
+                                    {
+                                        hourLoginSc.Teacher = 1;
+                                        hourLoginSc.Student = 0;
+                                        hourLoginSc.TmdUser = 0;
+                                    }
+                                    else if (scope.Equals("student"))
+                                    {
+                                        hourLoginSc.Teacher = 0;
+                                        hourLoginSc.Student = 1;
+                                        hourLoginSc.TmdUser = 0;
+                                    }
+                                    else
+                                    {
+                                        hourLoginSc.Teacher = 0;
+                                        hourLoginSc.Student = 0;
+                                        hourLoginSc.TmdUser = 1;
+                                    }
+                                    hourLoginSchools.Add(hourLoginSc);
+                                }
+                            }
+                        }
+                        await table.SaveOrUpdateAll(hourLoginSchools);
+                    }
+                    if (scMonthCnt != null && scMonthCnt.Length > 0)
+                    {
+                        List<DayLoginSchool> DayLoginSchools = new();
+                        foreach (var scMCnt in scMonthCnt)
+                        {
+                            if (((int)scMCnt.Element) == currentDay)
+                            {
+                                var tempDays = await table.QueryWhereString<DayLoginSchool>($"PartitionKey eq 'DayLogin-{school}' and RowKey eq '{dateDay}'");
+                                if (tempDays.Count > 0)
+                                {
+                                    foreach (var dLoginSc in tempDays)
+                                    {
+                                        if (scope.Equals("teacher"))
+                                            dLoginSc.Teacher = (int)scMCnt.Score;
+                                        else if (scope.Equals("student"))
+                                            dLoginSc.Student = (int)scMCnt.Score;
+                                        else
+                                            dLoginSc.TmdUser = (int)scMCnt.Score;
+                                        DayLoginSchools.Add(dLoginSc);
+                                    }
+                                }
+                                else
+                                {
+                                    //学校天峰值
+                                    DayLoginSchool dayLoginSc = new() { PartitionKey = $"DayLogin-{school}", RowKey = dateDay, Day = currentDay, School = school };
+                                    if (scope.Equals("teacher"))
+                                    {
+                                        dayLoginSc.Teacher = 1;
+                                        dayLoginSc.Student = 0;
+                                        dayLoginSc.TmdUser = 0;
+                                    }
+                                    else if (scope.Equals("student"))
+                                    {
+                                        dayLoginSc.Teacher = 0;
+                                        dayLoginSc.Student = 1;
+                                        dayLoginSc.TmdUser = 0;
+                                    }
+                                    else
+                                    {
+                                        dayLoginSc.Teacher = 0;
+                                        dayLoginSc.Student = 0;
+                                        dayLoginSc.TmdUser = 1;
+                                    }
+
+                                    DayLoginSchools.Add(dayLoginSc);
+                                }
+                            }
+                        }
+                        await table.SaveOrUpdateAll(DayLoginSchools);//保存学校当月在线数据
+                    }
+
+                    string tbScHourSql = $"PartitionKey eq 'HourLogin-{school}' and RowKey le '{delTbHour}'";
+                    List<HourLogin> scHourLog = await table.QueryWhereString<HourLogin>(tbScHourSql);
+                    if (scHourLog.Count > 0)
+                        try
+                        {
+                            //await table.DeleteStringWhere<HourLogin>(tbScHourSql); //删除学校168小时前的数据
+                            await table.DeleteAll(scHourLog);
+                        }
+                        catch { }
+                    string tbScDaySql = $"PartitionKey eq 'DayLogin-{school}' and RowKey le '{delTbDay}'";
+                    List<DayLogin> scDayLog = await table.QueryWhereString<DayLogin>(tbScDaySql);
+                    if (scDayLog.Count > 0)
+                        try
+                        {
+                            //await table.DeleteStringWhere<DayLogin>(tbScDaySql); //删除学校180天前的数据
+                            await table.DeleteAll(scDayLog);
+                        }
+                        catch { }
+                }
+
+                return new OkObjectResult(new {  data=json});
+            }
+            catch (Exception ex)
+            {
+                await _dingDing.SendBotMsg($"{Environment.GetEnvironmentVariable("Option:Location")}-online-record 人数记录异常{ex.Message}\n{ex.StackTrace}", GroupNames.成都开发測試群組);
+                return new OkObjectResult(new { data = json });
+            }
         }
         }
     }
     }
 }
 }

+ 31 - 0
TEAMModelOS.Function/Lang/en-us.json

@@ -0,0 +1,31 @@
+{
+  "request_school": [ "Apply to join school", "{tmdname}({tmdid}) is applying to join {schoolName}.", "Agree", "Refuse" ],
+  "invite_school": [ "Invite to join school", "{schoolName} has invited you to join.", "Accept", "Decline" ],
+  "remove_school": [ "Remove from school", "{schoolName} removed you from the school's teacher list." ],
+  "request-join_school": [ "Agree to join school", "{schoolName} has agreed you to join the school." ],
+  "invite-join_school": [ "Accepting join school invitation", "{tmdname} has accepted an invitation to join {schoolName}." ],
+  "coedit_syllabus": [ "Invite to co-edit syllabus", "{tmdname} of {schoolName} has invited you to co-edit school-based syllabus, Volume name: {volumeName}, Node name: {syllabusName}." ],
+  "coedit_volume": [ "Invite to co-edit volume", "{tmdname} of {schoolName} has invited  you to co-edit school-based volume,Volume name:{volumeName}." ],
+  "share_syllabus": [ "Receive shared syllabus", "{tmdname} shared personal syllabus with you, Volume name: {volumeName}, Node name: {syllabusName}." ],
+  "transfer-admin_school": [ "Transfer administrator", "{tmdname} will transfer the administrator of {schoolName} to you." ],
+  "scoring-arb_school": [ "Assign arbitration exam scoring task", "{tmdname} of {schoolName} has assign you an arbitration exam scoring task." ],
+  "scoring-err_school": [ "Assign abnormal exam paper grading task", "{tmdname} of {schoolName} has assign you an abnormal exam paper grading task." ],
+  "scoring-mark_school": [ "Assign exam paper grading task", "{tmdname} of {schoolName} has assign you an exam paper grading task." ],
+  "scan-join_groupList": [ "Join course notice", "{tmdname} join the {groupListName} course via QRcode scanning" ],
+  "scan-join_school": [ "Join school notice", "{tmdname} join school, {schoolName}, via QRcode scanning" ],
+  "submitanswer-school_homework": [ "Homework submission notice", "{tmdname} of {schoolName} has submitted a homework:{homeworkName}" ],
+  "submitanswer-private_homework": [ "Homework submission notice", "{tmdname} has submitted a homework({homeworkName})" ],
+  "expire-school_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName}, on {schoolName} will expire at {expireTime}" ],
+  "expire-private_lessonRecord": [ "Lesson record expiration notice", "Your lesson record, {lessonName} will expire at {expireTime}" ],
+  "create-school": [ "Create schools in batches", "{tmdname}You successfully created schools in batch with Bi" ],
+  "copy-file_area": [ "Batch copy file start", "{tmdname}您用BI创区成功开始复制区域文件" ],
+  "art-template-comment1": "{studentName}同学,你在本次艺术评测活动中整体{level}。",
+  "art-template-comment2": "你在本次考核中{quotasHigh}等指标取得很好的成绩,希望继续保持。",
+  "art-template-comment3": "你在本次考核中{quotasLow}等指标有待提高,希望进一步加强。",
+  "art-template-comment4": "你在本次考核中{pointHigh}等知识点取得很好的成绩,希望继续保持。",
+  "art-template-comment5": "你在本次考核中{pointLow}等知识有待提高,希望进一步加强。",
+  "art-template-subject_music": "其实音乐殿堂的门槛并没有你想的那么高,对吧?希望你继续努力能够在这里欣赏到更美丽的风景!跳动的音符充满魅力,每个人的生活都离不开音乐,加油吧,相信你在音乐中会有所收获的。",
+  "art-template-subject_painting": "你敢于探素,乐于欣赏,在五彩的画笔中快乐成长,如果你再多一点耐心、仔细刻画,一定会更棒!你积极热爱美术,总是充满快乐,希望你能够坚持美术学习,永不放弃,在美术的道路上快乐地成长!",
+  "blob-space-school-notify": [ "There is not enough space left", "Dear {schoolName} administrator {tmdname}, remind you that the remaining space of your school is less than {percent}%, please contact the sales to purchase space, for details, please log in to IES (teammodel.net ) to view. Please ignore this notification if the capacity has already been expanded." ],
+  "blob-space-private-notify": [ "There is not enough space left", "Hello, {tmdname}, remind you that the remaining space belong to your account {tmdid} is less than {percent}%. Please contact the school administrator to configure or purchase your own space. Please ignore this notification if the capacity has already been expanded." ]
+}

+ 31 - 0
TEAMModelOS.Function/Lang/zh-cn.json

@@ -0,0 +1,31 @@
+{
+  "request_school": [ "申请加入学校通知", "{tmdname}({tmdid})申请加入{schoolName}。", "同意", "拒绝" ],
+  "invite_school": [ "邀请加入学校通知", "{schoolName}邀请您加入学校。", "接受", "婉拒" ],
+  "remove_school": [ "从学校移除通知", "{schoolName}将您从学校教师名单中移除。" ],
+  "request-join_school": [ "同意申请加入学校通知", "{schoolName}已同意您申请加入学校。" ],
+  "invite-join_school": [ "同意邀请加入学校通知", "{tmdname}已接受加入{schoolName}的邀请。" ],
+  "coedit_syllabus": [ "邀请共编课纲通知", "{schoolName}的{tmdname}邀请你参与共编校本课纲,册别名称:{volumeName},课纲节点名称:{syllabusName}。" ],
+  "coedit_volume": [ "邀请共编册别通知", "{schoolName}的{tmdname}邀请你参与共编校本册别,册别名称:{volumeName}。" ],
+  "share_syllabus": [ "课纲分享接收通知", "{tmdname}向您分享了个人课纲,册别名称:{volumeName},课纲节点名称:{syllabusName}。" ],
+  "transfer-admin_school": [ "管理员移交通知", "{tmdname}将{schoolName}的管理员移交给您。" ],
+  "scoring-arb_school": [ "仲裁卷阅卷任务通知", "{schoolName}的{tmdname}向您发送了仲裁卷阅卷任务。" ],
+  "scoring-err_school": [ "异常卷阅卷任务通知", "{schoolName}的{tmdname}向您发送了异常卷阅卷任务。" ],
+  "scoring-mark_school": [ "普通阅卷任务通知", "{schoolName}的{tmdname}向您发送了普通卷阅卷任务。" ],
+  "scan-join_groupList": [ "扫码加入名单通知", "{tmdname}扫码加入名单,名单:{groupListName}" ],
+  "scan-join_school": [ "扫码加入学校通知", "{tmdname}扫码加入学校,学校名称:{schoolName}" ],
+  "submitanswer-school_homework": [ "作业提交通知", "{schoolName}的{tmdname}已提交作业,作业名称:{homeworkName}" ],
+  "submitanswer-private_homework": [ "作业提交通知", "{tmdname}已提交作业,作业名称({homeworkName})" ],
+  "expire-school_lessonRecord": [ "课例到期通知", "您在{schoolName}的课例将在{expireTime}到期,课例名称:{lessonName}" ],
+  "expire-private_lessonRecord": [ "课例到期通知", "您的课例将在{expireTime}到期,课例名称:{lessonName}" ],
+  "create-school": [ "批量创建学校", "{tmdname}您用BI批量创建学校成功" ],
+  "copy-file_area": [ "批复制文件开始", "{tmdname}您用BI创区成功开始复制区域文件" ],
+  "art-template-comment1": "{studentName}同学,你在本次艺术评测活动中整体{level}。",
+  "art-template-comment2": "你在本次考核中{quotasHigh}等指标取得很好的成绩,希望继续保持。",
+  "art-template-comment3": "你在本次考核中{quotasLow}等指标有待提高,希望进一步加强。",
+  "art-template-comment4": "你在本次考核中{pointHigh}等知识点取得很好的成绩,希望继续保持。",
+  "art-template-comment5": "你在本次考核中{pointLow}等知识有待提高,希望进一步加强。",
+  "art-template-subject_music": "其实音乐殿堂的门槛并没有你想的那么高,对吧?希望你继续努力能够在这里欣赏到更美丽的风景!跳动的音符充满魅力,每个人的生活都离不开音乐,加油吧,相信你在音乐中会有所收获的。",
+  "art-template-subject_painting": "你敢于探素,乐于欣赏,在五彩的画笔中快乐成长,如果你再多一点耐心、仔细刻画,一定会更棒!你积极热爱美术,总是充满快乐,希望你能够坚持美术学习,永不放弃,在美术的道路上快乐地成长!",
+  "blob-space-school-notify": [ "剩余空间不足", "亲爱的{schoolName}管理员{tmdname}您好,提醒您贵校剩余空间不足{percent}%,请联系官方客服购买空间,详情请登入IES(teammodel.net)查看,如果已经扩容请忽略此通知。" ],
+  "blob-space-private-notify": [ "剩余空间不足", "亲爱的{tmdname}您好,提醒您的帐号{tmdid}授权的剩余空间不足{percent}%,请联系学校管理员配置或是购买个人空间,如果已经扩容请忽略此通知。" ]
+}

+ 31 - 0
TEAMModelOS.Function/Lang/zh-tw.json

@@ -0,0 +1,31 @@
+{
+  "request_school": [ "申請加入學校通知", "{tmdname}({tmdid})申請加入{schoolName}。", "同意", "拒絕" ],
+  "invite_school": [ "邀請加入學校通知", "{schoolName}邀請您加入學校。", "接受", "婉拒" ],
+  "remove_school": [ "從學校移除通知", "{schoolName}將您從學校教師名單中移除。" ],
+  "request-join_school": [ "同意申請加入學校通知", "{schoolName}已同意您申請加入學校。" ],
+  "invite-join_school": [ "同意邀請加入學校通知", "{tmdname}已接受加入{schoolName}的邀請。" ],
+  "coedit_syllabus": [ "邀請共編課綱通知", "{schoolName}的{tmdname}邀請你參與共編校本課綱,冊別名稱:{volumeName},課綱節點名稱:{syllabusName}。" ],
+  "coedit_volume": [ "邀請共編冊別通知", "{schoolName}的{tmdname}邀請你參與共編校本冊別,冊別名稱:{volumeName}。" ],
+  "share_syllabus": [ "課綱分享接收通知", "{tmdname}向您分享了個人課綱,冊別名稱:{volumeName},課綱節點名稱:{syllabusName}。" ],
+  "transfer-admin_school": [ "管理員移交通知", "{tmdname}將{schoolName}的管理員移交給您。" ],
+  "scoring-arb_school": [ "仲裁卷閱卷任務通知", "{schoolName}的{tmdname}向您發送了仲裁卷閱卷任務。" ],
+  "scoring-err_school": [ "異常卷閱卷任務通知", "{schoolName}的{tmdname}向您發送了異常卷閱卷任務。" ],
+  "scoring-mark_school": [ "普通閱卷任務通知", "{schoolName}的{tmdname}向您發送了普通卷閱卷任務。" ],
+  "scan-join_groupList": [ "掃碼加入名單通知", "{tmdname}掃碼加入名單,名單:{groupListName}" ],
+  "scan-join_school": [ "掃碼加入學校通知", "{tmdname}掃碼加入學校,學校名稱:{schoolName}" ],
+  "submitanswer-school_homework": [ "作業提交通知", "{schoolName}的{tmdname}已提交作業,作業名稱:{homeworkName}" ],
+  "submitanswer-private_homework": [ "作業提交通知", "{tmdname}已提交作業,作業名稱({homeworkName})" ],
+  "expire-school_lessonRecord": [ "課例到期通知", "您在{schoolName}的課例將在{expireTime}到期,課例名稱:{lessonName}" ],
+  "expire-private_lessonRecord": [ "課例到期通知", "您的課例將在{expireTime}到期,課例名稱:{lessonName}" ],
+  "create-school": [ "批量創建學校", "{tmdname}您用BI批量創建學校成功" ],
+  "copy-file_area": [ "批復制文件開始", "{tmdname}您用BI创区成功开始复制区域文件" ],
+  "art-template-comment1": "{studentName}同学,你在本次艺术评测活动中整体{level}。",
+  "art-template-comment2": "你在本次考核中{quotasHigh}等指标取得很好的成绩,希望继续保持。",
+  "art-template-comment3": "你在本次考核中{quotasLow}等指标有待提高,希望进一步加强。",
+  "art-template-comment4": "你在本次考核中{pointHigh}等知识点取得很好的成绩,希望继续保持。",
+  "art-template-comment5": "你在本次考核中{pointLow}等知识有待提高,希望进一步加强。",
+  "art-template-subject_music": "其实音乐殿堂的门槛并没有你想的那么高,对吧?希望你继续努力能够在这里欣赏到更美丽的风景!跳动的音符充满魅力,每个人的生活都离不开音乐,加油吧,相信你在音乐中会有所收获的。",
+  "art-template-subject_painting": "你敢于探素,乐于欣赏,在五彩的画笔中快乐成长,如果你再多一点耐心、仔细刻画,一定会更棒!你积极热爱美术,总是充满快乐,希望你能够坚持美术学习,永不放弃,在美术的道路上快乐地成长!",
+  "blob-space-school-notify": [ "剩餘空間不足", "親愛的{schoolName}管理員{tmdname}您好,提醒您貴校剩餘空間不足{percent}%,請聯繫官方客服購買空間,詳情請登入IES(teammodel.net)查看,如果已經擴容請忽略此通知。" ],
+  "blob-space-private-notify": [ "剩餘空間不足", "親愛的{tmdname}您好,提醒您的帳號{tmdid}授權的剩餘空間不足{percent}%,請聯繫學校管理員配置或是購買個人空間,如果已經擴容請忽略此通知。" ]
+}

+ 22 - 0
TEAMModelOS.Function/TEAMModelOS.Function.csproj

@@ -6,6 +6,28 @@
     <ImplicitUsings>enable</ImplicitUsings>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
     <Nullable>enable</Nullable>
   </PropertyGroup>
   </PropertyGroup>
+  <ItemGroup>
+    <None Remove="Lang\en-us.json" />
+    <None Remove="Lang\zh-cn.json" />
+    <None Remove="Lang\zh-tw.json" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Lang\en-us.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+    </Content>
+    <Content Include="Lang\zh-cn.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+    </Content>
+    <Content Include="Lang\zh-tw.json">
+      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+      <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
+      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
+    </Content>
+  </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <FrameworkReference Include="Microsoft.AspNetCore.App" />
     <FrameworkReference Include="Microsoft.AspNetCore.App" />
     <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.22.0" />
     <PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.22.0" />

+ 0 - 1
TEAMModelOS.FunctionV4/TEAMModelOS.FunctionV4.csproj

@@ -57,7 +57,6 @@
 		<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.15.0" />
 		<PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.15.0" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.1.0" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Timer" Version="4.1.0" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.7.0" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.7.0" />
-
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.2.0" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.0.1" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="5.0.1" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.7.0" />
 		<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.7.0" />