From 3edc6956b30df3fc11025e0b719f320fcb1ec9c5 Mon Sep 17 00:00:00 2001
From: wangxinhui <wangxinhui@hnkhzn.com>
Date: 星期一, 19 一月 2026 16:31:15 +0800
Subject: [PATCH] 更新出库线体配置,PP、干膜质检单独判断

---
 代码管理/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs |  499 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 462 insertions(+), 37 deletions(-)

diff --git "a/\344\273\243\347\240\201\347\256\241\347\220\206/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs" "b/\344\273\243\347\240\201\347\256\241\347\220\206/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs"
index 2df00a5..2fe284f 100644
--- "a/\344\273\243\347\240\201\347\256\241\347\220\206/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs"
+++ "b/\344\273\243\347\240\201\347\256\241\347\220\206/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockInfoService.cs"
@@ -1,10 +1,13 @@
 锘縰sing AutoMapper;
 using MailKit.Search;
+using Newtonsoft.Json;
 using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup;
 using SqlSugar;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
+using System.Net.Http;
 using System.Text;
 using System.Threading.Tasks;
 using WIDESEA_Common.StockEnum;
@@ -236,11 +239,19 @@
                 while (needQuantity > 0)
                 {
                     Dt_StockInfo stockInfo = stockInfos[index];
-                    float useableStockQuantity = stockInfo.Details.Where(x => x.MaterielCode == materielCode).Sum(x => x.StockQuantity - x.OutboundQuantity);
-                    if (useableStockQuantity < needQuantity)
+                    // 璁$畻鍙敤搴撳瓨鏃惰浆鎹负decimal
+                    decimal useableStockQuantity = stockInfo.Details
+                        .Where(x => x.MaterielCode == materielCode)
+                        .Sum(x => (decimal)x.StockQuantity - (decimal)x.OutboundQuantity);
+
+                    // 灏唍eedQuantity杞崲涓篸ecimal杩涜姣旇緝
+                    if (useableStockQuantity < (decimal)needQuantity && useableStockQuantity > 0)
                     {
-                        stockInfo.Details.ForEach(x => x.OutboundQuantity = x.StockQuantity);
-                        needQuantity -= useableStockQuantity;
+                        stockInfo.Details.ForEach(x =>
+                            x.OutboundQuantity = x.StockQuantity);
+
+                        // 浣跨敤decimal杩涜璁$畻鍚庡啀杞洖float
+                        needQuantity = (float)((decimal)needQuantity - useableStockQuantity);
                     }
                     else
                     {
@@ -248,14 +259,20 @@
                         {
                             if (x.StockQuantity > x.OutboundQuantity && x.MaterielCode == materielCode)
                             {
-                                if (x.StockQuantity - x.OutboundQuantity >= needQuantity)
+                                // 灏嗙浉鍏冲�艰浆鎹负decimal杩涜绮剧‘璁$畻
+                                decimal currentStock = (decimal)x.StockQuantity;
+                                decimal currentOutbound = (decimal)x.OutboundQuantity;
+                                decimal currentNeed = (decimal)needQuantity;
+                                decimal available = currentStock - currentOutbound;
+
+                                if (available >= currentNeed)
                                 {
-                                    x.OutboundQuantity += needQuantity;
+                                    x.OutboundQuantity = (float)(currentOutbound + currentNeed);
                                     needQuantity = 0;
                                 }
                                 else
                                 {
-                                    needQuantity -= (x.StockQuantity - x.OutboundQuantity);
+                                    needQuantity = (float)(currentNeed - available);
                                     x.OutboundQuantity = x.StockQuantity;
                                 }
                             }
@@ -264,39 +281,11 @@
                     outStocks.Add(stockInfo);
                     index++;
                 }
+
             }
             else
             {
-                for (int i = 0; i < stockInfos.Count; i++)
-                {
-                    Dt_StockInfo stockInfo = stockInfos[i];
-                    float useableStockQuantity = stockInfo.Details.Where(x => x.MaterielCode == materielCode).Sum(x => x.StockQuantity - x.OutboundQuantity);
-                    if (useableStockQuantity < needQuantity)
-                    {
-                        stockInfo.Details.ForEach(x => x.OutboundQuantity = x.StockQuantity);
-                        needQuantity -= useableStockQuantity;
-                    }
-                    else
-                    {
-                        stockInfo.Details.ForEach(x =>
-                        {
-                            if (x.StockQuantity > x.OutboundQuantity && x.MaterielCode == materielCode)
-                            {
-                                if (x.StockQuantity - x.OutboundQuantity >= needQuantity)
-                                {
-                                    x.OutboundQuantity += needQuantity;
-                                    needQuantity = 0;
-                                }
-                                else
-                                {
-                                    needQuantity -= (x.StockQuantity - x.OutboundQuantity);
-                                    x.OutboundQuantity = x.StockQuantity;
-                                }
-                            }
-                        });
-                    }
-                    outStocks.Add(stockInfo);
-                }
+                throw new Exception("搴撳瓨涓嶈冻");
             }
             residueQuantity = needQuantity;
             return outStocks;
@@ -304,6 +293,10 @@
 
         public List<Dt_StockInfo> GetUseableStocks(string materielCode, string batchNo, int warehoseId)
         {
+            if ((materielCode.Equals("405000585")|| materielCode.Equals("405000831") || materielCode.Equals("405005565") || materielCode.Equals("405405097") || materielCode.Equals("405005461")) && warehoseId == 5)
+            {
+                warehoseId = 3;
+            }
             List<string> locationCodes = _basicRepository.LocationInfoRepository.GetCanOutLocationCodes(warehoseId);
 
             return BaseDal.GetStockInfos(materielCode, batchNo, locationCodes);
@@ -318,5 +311,437 @@
             return BaseDal.GetStockInfos(materielCode, batchNo, locationCodes);
         }
 
+        public WebResponseContent UpdateExpirationlabel()
+        {
+            try
+            {
+                var today = DateTime.Today;
+                int batchSize = 1000;
+                int totalUpdated = 0;
+                int skipCount = 0;
+
+                // 鍙煡璇㈤渶瑕佺殑瀛楁锛屽噺灏戞暟鎹紶杈撳拰鍐呭瓨鍗犵敤
+                var query = BaseDal.Db.Queryable<Dt_StockInfoDetail>()
+                    .InnerJoin<Dt_StockInfo>((detail, master) => detail.StockId == master.Id)
+                    .Select((detail, master) => new
+                    {
+                        MasterId = master.Id,
+                        master.WarehouseId,
+                        detail.EffectiveDate,
+                        CurrentExpirationlabel = master.Expirationlabel
+                    });
+
+                while (true)
+                {
+                    var batchData = query.Skip(skipCount).Take(batchSize).ToList();
+                    if (!batchData.Any()) break;
+                    var groupedData = batchData.GroupBy(item => item.MasterId)
+                        .Select(g => new
+                        {
+                            MasterId = g.Key,
+                            WarehouseId = g.First().WarehouseId,
+                            // 鍙栨渶鏃╃殑鏈夋晥鏃ユ湡
+                            EarliestEffectiveDate = g.Min(item =>
+                            {
+                                DateTime.TryParse(item.EffectiveDate, out DateTime date);
+                                return date;
+                            }),
+                            CurrentExpirationlabel = g.First().CurrentExpirationlabel
+                        })
+                        .ToList();
+
+                    var updateDic = new Dictionary<long, int>();
+
+                    foreach (var group in groupedData)
+                    {
+                        DateTime effectiveDate = group.EarliestEffectiveDate;
+                        if (effectiveDate == default(DateTime)) // 澶勭悊瑙f瀽澶辫触鐨勬儏鍐�
+                        {
+                            Console.WriteLine($"涓昏〃ID {group.MasterId} 涓嬫棤鏈夋晥鏃ユ湡锛岃烦杩�");
+                            continue;
+                        }
+
+                        int newLabel;
+                        if (effectiveDate < today)
+                        {
+                            newLabel = ExpirationlabelEnum.杩囨湡.ObjToInt();
+                        }
+                        else if (group.WarehouseId == 3)
+                        {
+                            int daysDiff = (effectiveDate - today).Days;
+                            newLabel = daysDiff < 60
+                                ? ExpirationlabelEnum.涓存湡棰勮.ObjToInt()
+                                : ExpirationlabelEnum.鏈复鏈�.ObjToInt();
+                        }
+                        else
+                        {
+                            int daysDiff = (effectiveDate - today).Days;
+                            newLabel = daysDiff < 30
+                                ? ExpirationlabelEnum.涓存湡棰勮.ObjToInt()
+                                : ExpirationlabelEnum.鏈复鏈�.ObjToInt();
+                        }
+
+                        if (newLabel != group.CurrentExpirationlabel && !updateDic.ContainsKey(group.MasterId))
+                        {
+                            updateDic[group.MasterId] = newLabel;
+                        }
+                    }
+
+                    if (updateDic.Any())
+                    {
+                        var updateBuilder = BaseDal.Db.Updateable<Dt_StockInfo>();
+                        var idsToUpdate = updateDic.Keys.ToList();
+                        int updateValue = updateDic.First().Value;
+                        updateBuilder.SetColumns(m => m.Expirationlabel == updateValue)
+                                     .Where(m => idsToUpdate.Contains(m.Id));
+
+                        int batchUpdated = updateBuilder.ExecuteCommand();
+                        totalUpdated += batchUpdated;
+
+                        Console.WriteLine($"鎵规鏇存柊锛歿batchUpdated} 鏉★紝绱鏇存柊锛歿totalUpdated} 鏉★紝鏇存柊鏉′欢锛歿JsonConvert.SerializeObject(idsToUpdate)}");
+                    }
+
+                    skipCount += batchSize;
+                }
+
+                return WebResponseContent.Instance.OK($"鏇存柊鎴愬姛锛屽叡鏇存柊 {totalUpdated} 鏉¤褰�");
+            }
+            catch (Exception ex)
+            {
+                return WebResponseContent.Instance.Error("鏇存柊澶辫触锛岃鑱旂郴绠$悊鍛�");
+            }
+        }
+        /// <summary>
+        /// 閽夐拤鏈哄櫒浜烘秷鎭帹閫佹祴璇�
+        /// </summary>
+        /// <returns></returns>
+        public async Task T0DingTalkText(string webhookUrl, string secret)
+        {
+            try
+            {
+                if (webhookUrl == null || secret == null)
+                {
+                    webhookUrl = "https://oapi.dingtalk.com/robot/send?access_token=c3e05f2c6bcd595383ee02e713446174b9201bad91db216590620fe0acd4e75e";
+                    secret = "SEC617f06140fc7cbd8b91d3e203f270826320637af41e7423d756e62df40f62282";
+                }
+
+                // 1. 瀹氫箟浠撳簱ID涓庝粨搴撳悕绉扮殑鏄犲皠鍏崇郴锛堝鍔犳垚鍝佷粨锛�
+                var warehouseIdToName = new Dictionary<int, string>
+        {
+            { 2, "娌瑰ⅷ浠�" },
+            { 3, "鏉挎潗浠�" },
+            { 4, "PP浠�" },
+            { 6, "娴嬭瘯鏋朵粨" },
+            { 7, "鎴愬搧浠�" }, // 鏂板鎴愬搧浠�
+            { 11, "骞茶啘浠�" },
+            { 12, "闃荤剨浠�" }
+        };
+
+                // 2. 鏃堕棿鐩稿叧閰嶇疆锛岀敤浜庣瓫閫夎秴杩�90澶╃殑鏁版嵁
+                var currentTime = DateTime.Now;
+                var ninetyDaysAgo = currentTime.AddDays(-90);
+
+                // 3. 澶勭悊鍘熸潗鏂欎粨锛堜粨搴揑D 鈮� 7锛�
+                // 3.1 鏌ヨ鏍囪瘑鐮佷负3锛堣繃鏈燂級鐨勪富琛ㄦ暟鎹紙鎺掗櫎浠撳簱ID=5鍜�7锛�
+                var expirationLabel3Stocks = BaseDal.Db.Queryable<Dt_StockInfo>()
+                    .Where(s => s.Expirationlabel == 3 && s.WarehouseId != 5 && s.WarehouseId != 7 && s.LocationCode != "" && s.LocationCode != null)
+                    .ToList();
+
+                // 3.2 鏌ヨ瓒呰繃90澶╂湭淇敼锛堟湭浣跨敤锛夌殑涓昏〃鏁版嵁锛堟帓闄や粨搴揑D=5鍜�7锛�
+                var over90DaysStocks = BaseDal.Db.Queryable<Dt_StockInfo>()
+                    .Where(s => s.ModifyDate <= ninetyDaysAgo && s.Expirationlabel != 3 && s.WarehouseId != 5 && s.WarehouseId != 7 && s.LocationCode != "" && s.LocationCode != null)
+                    .ToList();
+
+                // 4. 澶勭悊鎴愬搧浠擄紙浠撳簱ID = 7锛�
+                var proOver90DaysStocks = new List<Dt_ProStockInfo>();
+                if (warehouseIdToName.ContainsKey(7))
+                {
+                    proOver90DaysStocks = BaseDal.Db.Queryable<Dt_ProStockInfo>()
+                        .Where(s => s.WarehouseId == 7 && s.ModifyDate <= ninetyDaysAgo && s.LocationCode != "" && s.LocationCode != null)
+                        .ToList();
+                }
+
+                // 鏃犵鍚堟潯浠舵暟鎹椂鐩存帴杩斿洖
+                if (!expirationLabel3Stocks.Any() && !over90DaysStocks.Any() && !proOver90DaysStocks.Any())
+                {
+                    return;
+                }
+
+                // 5. 鎻愬彇鍘熸潗鏂欎富琛ㄦ暟鎹殑ID
+                var expirationLabel3StockIds = expirationLabel3Stocks.Select(s => s.Id).ToList();
+                var over90DaysStockIds = over90DaysStocks.Select(s => s.Id).ToList();
+
+                // 6. 鍏宠仈鏌ヨ鍘熸潗鏂欐槑缁嗚〃鏁版嵁
+                var expirationLabel3Details = expirationLabel3StockIds.Any()
+                    ? BaseDal.Db.Queryable<Dt_StockInfoDetail>()
+                        .Where(d => expirationLabel3StockIds.Contains(d.StockId))
+                        .ToList()
+                    : new List<Dt_StockInfoDetail>();
+
+                var over90DaysDetails = over90DaysStockIds.Any()
+                    ? BaseDal.Db.Queryable<Dt_StockInfoDetail>()
+                        .Where(d => over90DaysStockIds.Contains(d.StockId))
+                        .ToList()
+                    : new List<Dt_StockInfoDetail>();
+
+                // 7. 澶勭悊鎴愬搧浠撴槑缁嗘暟鎹�
+                var proOver90DaysDetails = new List<Dt_ProStockInfoDetail>();
+                if (proOver90DaysStocks.Any())
+                {
+                    var proStockIds = proOver90DaysStocks.Select(s => s.Id).ToList();
+                    proOver90DaysDetails = BaseDal.Db.Queryable<Dt_ProStockInfoDetail>()
+                        .Where(d => proStockIds.Contains(d.ProStockId))
+                        .ToList();
+                }
+
+                // 8. 寤虹珛涓昏〃ID鍒颁粨搴揑D鐨勬槧灏勶紙鍖呮嫭鎴愬搧浠擄級
+                var stockIdToWarehouseId = new Dictionary<int, int>();
+
+                // 鍘熸潗鏂欎粨鏄犲皠
+                foreach (var stock in expirationLabel3Stocks.Concat(over90DaysStocks))
+                {
+                    if (!stockIdToWarehouseId.ContainsKey(stock.Id))
+                    {
+                        stockIdToWarehouseId[stock.Id] = stock.WarehouseId;
+                    }
+                }
+
+                // 鎴愬搧浠撴槧灏�
+                foreach (var proStock in proOver90DaysStocks)
+                {
+                    if (!stockIdToWarehouseId.ContainsKey(proStock.Id))
+                    {
+                        stockIdToWarehouseId[proStock.Id] = proStock.WarehouseId;
+                    }
+                }
+
+                // 9. 鎸変粨搴撳垎缁勫鐞嗙墿鏂欐暟鎹�
+                // 9.1 鍘熸潗鏂欒繃鏈熺墿鏂�
+                var expirationLabel3Groups = expirationLabel3Details
+                    .GroupBy(d => stockIdToWarehouseId[d.StockId])
+                    .ToDictionary(g => g.Key, g => g.Select(d => new
+                    {
+                        MaterielCode = d.MaterielCode,
+                        BatchNo = d.BatchNo,
+                        IsProStock = false // 鏍囪涓洪潪鎴愬搧浠�
+                    }).Distinct().ToList());
+
+                // 9.2 鍘熸潗鏂欒秴杩�90澶╂湭浣跨敤鐗╂枡
+                var over90DaysGroups = over90DaysDetails
+                    .GroupBy(d => stockIdToWarehouseId[d.StockId])
+                    .ToDictionary(g => g.Key, g => g.Select(d => new
+                    {
+                        MaterielCode = d.MaterielCode,
+                        BatchNo = d.BatchNo,
+                        IsProStock = false // 鏍囪涓洪潪鎴愬搧浠�
+                    }).Distinct().ToList());
+
+                // 9.3 鎴愬搧浠撹秴杩�90澶╂湭浣跨敤鐗╂枡
+                if (proOver90DaysDetails.Any())
+                {
+                    var proWarehouseId = 7;
+                    var proGroup = proOver90DaysDetails
+                        .GroupBy(d => stockIdToWarehouseId[d.ProStockId])
+                        .Select(g => new
+                        {
+                            WarehouseId = g.Key,
+                            Materials = g.Select(d => new
+                            {
+                                MaterielCode = d.ProductCode, // 鎴愬搧浠撲娇鐢≒roductCode瀛楁
+                                BatchNo = d.LotNumber,        // 鎴愬搧浠撲娇鐢↙otNumber瀛楁
+                                IsProStock = true             // 鏍囪涓烘垚鍝佷粨
+                            }).Distinct().ToList()
+                        })
+                        .FirstOrDefault();
+
+                    if (proGroup != null)
+                    {
+                        // 娣诲姞鍒皁ver90DaysGroups涓�
+                        if (over90DaysGroups.ContainsKey(proWarehouseId))
+                        {
+                            over90DaysGroups[proWarehouseId].AddRange(proGroup.Materials);
+                        }
+                        else
+                        {
+                            over90DaysGroups[proWarehouseId] = proGroup.Materials;
+                        }
+                    }
+                }
+
+                // 10. 鑾峰彇鎵�鏈夋秹鍙婄殑浠撳簱ID
+                var allWarehouseIds = expirationLabel3Groups.Keys
+                    .Union(over90DaysGroups.Keys)
+                    .ToList();
+
+                // 11. 鍙戦�侀拤閽夋秷鎭�
+                using (HttpClient httpClient = new HttpClient())
+                {
+                    foreach (var warehouseId in allWarehouseIds)
+                    {
+                        var warehouseName = warehouseIdToName.TryGetValue(warehouseId, out var name)
+                            ? name
+                            : $"浠撳簱{warehouseId}";
+
+                        // 11.1 鏋勫缓markdown鏍煎紡娑堟伅
+                        var markdownContent = new StringBuilder();
+                        markdownContent.AppendLine($"## {warehouseName}鐗╂枡鎻愰啋閫氱煡\n");
+
+                        // 缁熻淇℃伅
+                        var expiredCount = expirationLabel3Groups.TryGetValue(warehouseId, out var expList) ?
+                            expList.Count : 0;
+
+                        var over90Count = over90DaysGroups.TryGetValue(warehouseId, out var over90List) ?
+                            over90List.Count : 0;
+
+                        // 濡傛灉鏄垚鍝佷粨锛岄渶瑕佽繃婊ゅ嚭鎴愬搧浠撶殑鏁版嵁
+                        if (warehouseId == 7)
+                        {
+                            over90Count = over90List?.Count(m => m.IsProStock) ?? 0;
+                        }
+
+                        markdownContent.AppendLine($"**缁熻姒傝锛�**");
+                        if (warehouseId == 7)
+                        {
+                            markdownContent.AppendLine($"- 瓒呰繃90澶╂湭浣跨敤鎴愬搧锛歿over90Count}鏉�");
+                        }
+                        else
+                        {
+                            markdownContent.AppendLine($"- 杩囨湡鐗╂枡锛歿expiredCount}鏉�");
+                            markdownContent.AppendLine($"- 瓒呰繃90澶╂湭浣跨敤鐗╂枡锛歿over90Count}鏉�");
+                        }
+                        markdownContent.AppendLine();
+
+                        // 11.2 娣诲姞杩囨湡鐗╂枡琛ㄦ牸锛堝鏋滄湁鏁版嵁涓斾笉鏄垚鍝佷粨锛�
+                        if (expiredCount > 0 && warehouseId != 7)
+                        {
+                            markdownContent.AppendLine("### 涓�銆佽繃鏈熺墿鏂�");
+                            markdownContent.AppendLine("| 搴忓彿 | 鐗╂枡缂栫爜 | 鎵规鍙� |");
+                            markdownContent.AppendLine("| :--- | :--- | :--- |");
+
+                            int index = 1;
+                            var expiredToShow = expList.Take(200);
+                            foreach (var material in expiredToShow)
+                            {
+                                markdownContent.AppendLine($"| {index} | {material.MaterielCode} | {material.BatchNo} |");
+                                index++;
+                            }
+
+                            if (expiredCount > 200)
+                            {
+                                markdownContent.AppendLine($"| ... | 鍏眥expiredCount}鏉★紝浠呮樉绀哄墠200鏉� | ... |");
+                            }
+                            markdownContent.AppendLine();
+                        }
+
+                        // 11.3 娣诲姞瓒呰繃90澶╂湭浣跨敤鐗╂枡琛ㄦ牸锛堝鏋滄湁鏁版嵁锛�
+                        if (over90Count > 0)
+                        {
+                            if (warehouseId == 7)
+                            {
+                                markdownContent.AppendLine("### 瓒呰繃90澶╂湭浣跨敤鎴愬搧");
+                            }
+                            else
+                            {
+                                markdownContent.AppendLine("### 浜屻�佽秴杩�90澶╂湭浣跨敤鐗╂枡");
+                            }
+
+                            markdownContent.AppendLine("| 搴忓彿 | 鐗╂枡缂栫爜 | 鎵规鍙� |");
+                            markdownContent.AppendLine("| :--- | :--- | :--- |");
+
+                            int index = 1;
+                            var over90ToShow = warehouseId == 7 ?
+                                over90List?.Where(m => m.IsProStock).Take(500) :
+                                over90List?.Take(500);
+
+                            if (over90ToShow != null)
+                            {
+                                foreach (var material in over90ToShow)
+                                {
+                                    markdownContent.AppendLine($"| {index} | {material.MaterielCode} | {material.BatchNo} |");
+                                    index++;
+                                }
+
+                                if (over90Count > (warehouseId == 7 ? 500 : 200))
+                                {
+                                    markdownContent.AppendLine($"| ... | 鍏眥over90Count}鏉★紝浠呮樉绀哄墠{(warehouseId == 7 ? 500 : 200)}鏉� | ... |");
+                                }
+                            }
+                            markdownContent.AppendLine();
+                        }
+
+                        // 11.4 娣诲姞鏃堕棿鎴冲拰鎻愮ず淇℃伅
+                        markdownContent.AppendLine($"**鎶ュ憡鏃堕棿锛�** {currentTime:yyyy-MM-dd HH:mm:ss}");
+                        if (warehouseId == 7)
+                        {
+                            markdownContent.AppendLine("**澶囨敞锛�** 璇锋垚鍝佷粨绠$悊浜哄憳鍙婃椂澶勭悊瓒呰繃90澶╂湭浣跨敤鐨勬垚鍝併��");
+                        }
+                        else
+                        {
+                            markdownContent.AppendLine("**澶囨敞锛�** 璇风浉鍏充粨搴撶鐞嗕汉鍛樺強鏃跺鐞嗕互涓婄墿鏂欍��");
+                        }
+
+                        var messageContent = markdownContent.ToString();
+
+                        // 11.5 鐢熸垚閽夐拤娑堟伅鎵�闇�鐨勬椂闂存埑鍜岀鍚�
+                        var timestamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
+                        var sign = GenerateSign(timestamp, secret);
+
+                        // 11.6 鏋勫缓閽夐拤璇锋眰URL
+                        var uri = new Uri(webhookUrl);
+                        var token = System.Web.HttpUtility.ParseQueryString(uri.Query)["access_token"];
+                        var baseUrl = uri.GetLeftPart(UriPartial.Path);
+                        var url = $"{baseUrl}?access_token={token}&timestamp={timestamp}&sign={sign}";
+
+                        // 11.7 鏋勫缓璇锋眰浣擄紙浣跨敤markdown鏍煎紡锛�
+                        var requestBody = new
+                        {
+                            msgtype = "markdown",
+                            markdown = new
+                            {
+                                title = $"{warehouseName}鐗╂枡鎻愰啋",
+                                text = messageContent
+                            },
+                            at = new
+                            {
+                                // 鍙互鎸囧畾@鏌愪簺浜猴紝濡傛灉涓嶉渶瑕佸彲浠ュ垹闄よ繖閮ㄥ垎
+                                // atMobiles = new[] { "138xxxx8888" },
+                                // isAtAll = false
+                            }
+                        };
+
+                        var jsonBody = JsonConvert.SerializeObject(requestBody);
+                        var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
+
+                        // 11.8 鍙戦�丳OST璇锋眰骞跺鐞嗗搷搴�
+                        var response = await httpClient.PostAsync(url, content);
+                        if (!response.IsSuccessStatusCode)
+                        {
+                            var errorContent = await response.Content.ReadAsStringAsync();
+                            throw new Exception($"銆恵warehouseName}銆戞秷鎭彂閫佸け璐ワ紝鐘舵�佺爜锛歿response.StatusCode}锛岄敊璇俊鎭細{errorContent}");
+                        }
+
+                        // 閬垮厤鍙戦�侀鐜囪繃蹇�
+                        await Task.Delay(1000);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                // 鎹曡幏寮傚父骞惰ˉ鍏呬笂涓嬫枃锛屼究浜庨棶棰樺畾浣�
+                throw new Exception($"閽夐拤娑堟伅鎺ㄩ�佹暣浣撳け璐ワ紝閿欒璇︽儏锛歿ex.Message}", ex);
+            }
+        }
+
+        // 閽夐拤绛惧悕鐢熸垚鏂规硶
+        private string GenerateSign(long timestamp, string secret)
+        {
+            var stringToSign = $"{timestamp}\n{secret}";
+            using (var hmac = new System.Security.Cryptography.HMACSHA256(Encoding.UTF8.GetBytes(secret)))
+            {
+                var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));
+                return Convert.ToBase64String(hash);
+            }
+        }
+
     }
 }

--
Gitblit v1.9.3