From 17e5dbd7bd0364e27a33f1a7dab91cf33d5dcabc Mon Sep 17 00:00:00 2001
From: wanshenmean <cathay_xy@163.com>
Date: 星期三, 04 三月 2026 11:52:12 +0800
Subject: [PATCH] 增强Redis缓存服务与设备通信优化
---
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs | 20
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Http/HTTP/HttpClientHelper.cs | 46
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/RedisCacheService.cs | 226 +++++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/UnitTest1.cs | 10
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskExecuteDetailService.cs | 6
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Const/CommunicationConst.cs | 55 +
Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs | 6
Code/WCS/WIDESEAWCS_Server/CLAUDE.md | 172 +++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Options/RedisOptions.cs | 20
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs | 11
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/StackerCrane/Spec/SpeTemperatureStackerCrane.cs | 36
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/LogHelper/Logger.cs | 5
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Connection/RedisConnectionManager.cs | 16
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/BaseRepository/UnitOfWorks/UnitOfWorkManage.cs | 10
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json | 4
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs | 11
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/HybridCacheService.cs | 554 ++++++++++++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/CacheSyncBackgroundService.cs | 211 ++++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Omron/OmronEtherNetCommunicator.cs | 11
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/HttpHelper.cs | 24
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/ConfigKey.cs | 48
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs | 6
Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs | 58
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs | 151 ++-
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/WIDESEAWCS_QuartzJob.csproj | 1
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs | 11
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/WIDESEAWCS_Core.csproj | 4
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs | 3
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/CommunicationConstTests.cs | 84 +
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ShuttleCarJob/ShuttleCarJob.cs | 9
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/SerialPortComm/SerialPortCommunicator.cs | 2
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/ICacheService.cs | 153 +++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/MemoryCacheService.cs | 202 ++++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/QuartzNetExtension.cs | 10
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln | 18
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Redis使用案例.md | 270 +++++
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/BaseAPI.cs | 31
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/Redis/RedisPrefix.cs | 29
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/WIDESEAWCS_Tests.csproj | 29
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/Redis/RedisName.cs | 31
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/AppSettings.cs | 10
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/Service/RouterService.cs | 8
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Extensions/RedisServiceSetup.cs | 6
43 files changed, 2,430 insertions(+), 198 deletions(-)
diff --git a/Code/WCS/WIDESEAWCS_Server/CLAUDE.md b/Code/WCS/WIDESEAWCS_Server/CLAUDE.md
new file mode 100644
index 0000000..e2e08a3
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/CLAUDE.md
@@ -0,0 +1,172 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Build Commands
+
+```bash
+# Build entire solution
+dotnet build WIDESEAWCS_Server.sln
+
+# Build and run server
+cd WIDESEAWCS_Server
+dotnet run
+
+# Run tests
+cd WIDESEAWCS_Tests
+dotnet test
+```
+
+## Architecture Overview
+
+This is a **WCS (Warehouse Control System)** built with ASP.NET Core 6.0, using:
+- **Autofac** for DI with automatic service discovery via `IDependency` marker interface
+- **Quartz.NET** for scheduled job execution (device communication loops)
+- **SqlSugar ORM** for database access
+- **Redis** (via `WIDESEAWCS_RedisService`) for distributed caching with L1+L2 hybrid pattern
+- **StackExchange.Redis** for Redis operations
+- **TCP Socket Server** for real-time device communication
+- **HslCommunication** library for PLC/hardware communication
+
+## Project Structure
+
+```
+WIDESEAWCS_Server/ # Main ASP.NET Core API server
+WIDESEAWCS_Core/ # Core infrastructure: base classes, DI, extensions, middleware
+WIDESEAWCS_Model/ # Data models and DTOs
+WIDESEAWCS_Communicator/ # Hardware communication drivers (Siemens, Omron, Modbus, etc.)
+WIDESEAWCS_QuartzJob/ # Job scheduling infrastructure and device abstractions
+WIDESEAWCS_Tasks/ # Quartz job implementations (device communication loops)
+WIDESEAWCS_RedisService/ # Redis services: Cache, Lock, Counter, PubSub, etc.
+WIDESEAWCS_*Repository/ # Data access layer implementations
+WIDESEAWCS_*Service/ # Business service layer
+WIDESEAWCS_Tests/ # Unit tests
+```
+
+## Dependency Injection - IDependency Pattern
+
+Services are **automatically registered** with Autofac by implementing the empty `IDependency` marker interface:
+
+```csharp
+// In WIDESEAWCS_Core/IDependency.cs
+public interface IDependency { }
+
+// Your service gets auto-registered
+public class MyService : IDependency // Automatically registered as scoped
+{
+ // ...
+}
+```
+
+Registration happens in `AutofacModuleRegister` which scans all project assemblies for `IDependency` implementations.
+
+**Important**: When adding services to `IServiceCollection` (e.g., in `Program.cs`), they can be overridden by Autofac's registrations. Use `Remove()` to replace existing registrations:
+
+```csharp
+// In RedisServiceSetup.cs - removes MemoryCacheService before adding HybridCacheService
+var existing = services.FirstOrDefault(d => d.ServiceType == typeof(ICacheService));
+if (existing != null) services.Remove(existing);
+```
+
+## Caching - ICacheService
+
+The system uses a **hybrid L1 (Memory) + L2 (Redis)** cache pattern via `ICacheService`. Three implementations exist:
+- `MemoryCacheService` - Memory only
+- `RedisCacheService` - Redis only
+- `HybridCacheService` - L1+L2 with fallback (default when Redis enabled)
+
+**Common methods**:
+- `Add/AddObject` - Add cache
+- `Get/Get<T>` - Retrieve cached values
+- `Remove` - Delete single key
+- `RemoveByPrefix/RemoveByPattern` - Bulk delete by pattern
+- `GetOrAdd<T>` - Retrieve or add with factory
+- `TryAdd/TryUpdate/TryUpdateIfChanged` - ConcurrentDictionary-style operations
+
+**Configuration** in `appsettings.json`:
+```json
+"RedisConfig": {
+ "Enabled": true,
+ "ConnectionString": "127.0.0.1:6379,password=P@ssw0rd,...",
+ "KeyPrefix": "wcs:"
+}
+```
+
+## Quartz Jobs - Device Communication
+
+Jobs inherit from `JobBase` and implement Quartz's `IJob`:
+
+```csharp
+public class MyDeviceJob : JobBase, IJob
+{
+ public async Task Execute(IJobExecutionContext context)
+ {
+ ExecuteJob(context, async () => {
+ // Job logic here
+ WriteDebug("MyDevice", "Debug message");
+ WriteInfo("MyDevice", "Info message");
+ });
+ }
+}
+```
+
+Jobs are registered dynamically via `SchedulerCenterServer` using device info from `Dt_DeviceInfo` table.
+
+**Device types**:
+- `IStackerCrane` - Stacker cranes
+- `IConveyorLine` - Conveyor lines
+- `IShuttleCar` - Shuttle cars
+- `IRobot` - Robot cranes
+
+## Hardware Communication
+
+Communicator classes wrap the `HslCommunication` library:
+- `SiemensS7Communicator` / `SiemensS7200SmartCommunicator` - Siemens PLCs
+- `OmronEtherNetCommunicator` - Omron PLCs
+- `ModbusTcpCommunicator` - Modbus TCP
+- `SerialPortCommunicator` - Serial port devices
+
+## TCP Socket Server
+
+The `TcpSocketServer` (port 2000) handles real-time device communication:
+- Managed as a Singleton with `SocketServerHostedService`
+- Client connections stored in `ConcurrentDictionary<string, TcpClient>`
+- Messages handled via `OnDataReceived` event
+
+## Configuration Settings
+
+Key settings in `appsettings.json`:
+- `"urls": "http://*:9292"` - Server port
+- `"QuartzJobAutoStart": true` - Auto-start scheduled jobs
+- `"SocketServer:Enabled": true` - Enable TCP server
+- `"RedisConfig:Enabled": true` - Enable Redis caching
+- `"LogAOPEnable": false` - Enable AOP logging
+- `"DBType": "SqlServer"` - Database type
+
+## Service Layer Pattern
+
+Services follow a layered pattern:
+- **Interface** in `WIDESEAWCS_IService/` (e.g., `ITaskInfoService`)
+- **Implementation** in `WIDESEAWCS_Service/` (e.g., `TaskInfoService`)
+- Both implement `IDependency` for auto-registration
+
+## Base Classes
+
+- `ServiceBase<T, TKey>` - Base service with CRUD operations
+- `RepositoryBase<TEntity>` - Base repository with SqlSugar ORM
+- `ApiBaseController` - Base API controller with common functionality
+- `JobBase` - Base Quartz job with logging helpers
+
+## Adding New Features
+
+1. **New Service**: Create interface in `I*Service/` and class in `*Service/`, implement `IDependency`
+2. **New Job**: Inherit from `JobBase` and `IJob` in `WIDESEAWCS_Tasks/`
+3. **New Device Type**: Add interface in `WIDESEAWCS_QuartzJob/Device/` and implement
+
+## Important Notes
+
+- The application uses **CamelCase** JSON serialization
+- All services use **scoped** lifetime by default via Autofac
+- Redis connection uses **Lazy initialization** - first access triggers connection
+- Use `ConsoleHelper.WriteSuccessLine()` / `WriteErrorLine()` for console output in jobs
+- TCP Socket server runs independently of the HTTP API
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/BaseAPI.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/BaseAPI.cs
new file mode 100644
index 0000000..08a0f08
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/BaseAPI.cs
@@ -0,0 +1,31 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WIDESEAWCS_Common
+{
+ public class BaseAPI
+ {
+ /// <summary>
+ /// WMS鎺ュ彛鍩虹URL
+ /// </summary>
+ public const string WMSBaseUrl = "http://localhost:9291/api/";
+
+ /// <summary>
+ /// WCS鎺ュ彛鍩虹URL
+ /// </summary>
+ public const string WCSBaseUrl = "http://localhost:9292/api/";
+
+ /// <summary>
+ /// MES鎺ュ彛鍩虹URL
+ /// </summary>
+ public const string MESBaseUrl = "http://localhost:9293/api/";
+
+ /// <summary>
+ /// ERP鎺ュ彛鍩虹URL
+ /// </summary>
+ public const string ERPBaseUrl = "http://localhost:9294/api/";
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/ConfigKey.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/ConfigKey.cs
index 498a87c..c02cdf0 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/ConfigKey.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/HttpEnum/ConfigKey.cs
@@ -1,27 +1,9 @@
-锘縰sing System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace WIDESEAWCS_Common.HttpEnum
+锘縩amespace WIDESEAWCS_Common.HttpEnum
{
public enum ConfigKey
{
- ERP_API_Base,
- ERP_API_Url,
- ERP_API_Timeout,
-
-
- WMS_API_Base,
- WMS_API_Url,
- WMS_API_Timeout,
-
- MES_API_Base,
- MES_API_Url,
- MES_API_Timeout,
-
#region WMS鎺ュ彛
+
/// <summary>
/// 缁勭洏
/// </summary>
@@ -45,8 +27,28 @@
/// <summary>
/// 鑾峰彇浠诲姟鍙叆璐т綅
/// </summary>
- GetTasksLocation
+ GetTasksLocation,
- #endregion
+ /// <summary>
+ /// 鍑哄簱浠诲姟瀹屾垚
+ /// </summary>
+ OutboundFinishTaskAsync,
+
+ /// <summary>
+ /// 鍏ュ簱浠诲姟瀹屾垚
+ /// </summary>
+ InboundFinishTaskAsync,
+
+ /// <summary>
+ /// 鍒涘缓绌烘墭鐩樺嚭搴撲换鍔�
+ /// </summary>
+ GetOutBoundTrayTaskAsync,
+
+ /// <summary>
+ /// 鍒涘缓绌烘墭鐩樺叆搴撲换鍔�
+ /// </summary>
+ CreateTaskInboundTrayAsync,
+
+ #endregion WMS鎺ュ彛
}
-}
+}
\ No newline at end of file
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/Redis/RedisName.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/Redis/RedisName.cs
new file mode 100644
index 0000000..81764e6
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/Redis/RedisName.cs
@@ -0,0 +1,31 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WIDESEAWCS_Common
+{
+ public class RedisName
+ {
+ /// <summary>
+ /// Socket璁惧鍒楄〃锛屾竻闄ょ紦瀛樻椂浼氱敤鍒帮紝娓呴櫎浠ocketDevices寮�澶寸殑缂撳瓨
+ /// </summary>
+ public const string SocketDevices = "SocketDevices";
+
+ /// <summary>
+ /// 璁惧鍒楄〃锛屾竻闄ょ紦瀛樻椂浼氱敤鍒帮紝娓呴櫎浠Device寮�澶寸殑缂撳瓨
+ /// </summary>
+ public const string IDevice = "IDevice";
+
+ /// <summary>
+ /// 璁惧浜や簰浣嶇疆鍒楄〃锛屾竻闄ょ紦瀛樻椂浼氱敤鍒帮紝娓呴櫎浠evicePositions寮�澶寸殑缂撳瓨
+ /// </summary>
+ public const string DevicePositions = "DevicePositions";
+
+ /// <summary>
+ /// API鍒楄〃锛屾竻闄ょ紦瀛樻椂浼氱敤鍒帮紝娓呴櫎浠evicePositions寮�澶寸殑缂撳瓨
+ /// </summary>
+ public const string API = "API";
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/Redis/RedisPrefix.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/Redis/RedisPrefix.cs
new file mode 100644
index 0000000..d1d1e7a
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Common/Redis/RedisPrefix.cs
@@ -0,0 +1,29 @@
+锘縰sing System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WIDESEAWCS_Common
+{
+ /// <summary>
+ /// Redis鍓嶇紑绫伙紝瀹氫箟浜嗙郴缁熴�佺敤鎴枫�佷唬鐮佺瓑鍓嶇紑锛屾竻闄ょ紦瀛樻椂浼氱敤鍒帮紝娓呴櫎浠ヨ繖浜涘墠缂�寮�澶寸殑缂撳瓨
+ /// </summary>
+ public class RedisPrefix
+ {
+ /// <summary>
+ /// 绯荤粺鍓嶇紑锛屾竻闄ょ紦瀛樻椂浼氱敤鍒帮紝娓呴櫎浠ystem寮�澶寸殑缂撳瓨
+ /// </summary>
+ public const string System = "System";
+
+ /// <summary>
+ /// 鐢ㄦ埛鍓嶇紑锛屾竻闄ょ紦瀛樻椂浼氱敤鍒帮紝娓呴櫎浠ser寮�澶寸殑缂撳瓨
+ /// </summary>
+ public const string User = "User";
+
+ /// <summary>
+ /// 浠g爜鍓嶇紑锛屾竻闄ょ紦瀛樻椂浼氱敤鍒帮紝娓呴櫎浠ode寮�澶寸殑缂撳瓨
+ /// </summary>
+ public const string Code = "Code";
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Omron/OmronEtherNetCommunicator.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Omron/OmronEtherNetCommunicator.cs
index a3ee114..bfe0b83 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Omron/OmronEtherNetCommunicator.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/Omron/OmronEtherNetCommunicator.cs
@@ -24,6 +24,13 @@
[Description("娆у榫橢therNet/IP(CIP)")]
public class OmronEtherNetCommunicator : BaseCommunicator
{
+ #region Constants
+ /// <summary>
+ /// Ping 妫�娴嬮棿闅旀椂闂达紙姣锛�
+ /// </summary>
+ private const int PingIntervalMs = 100;
+ #endregion Constants
+
#region Private Member
/// <summary>
/// HSLCommunication鐨勮タ闂ㄥ瓙鐨凷7鍗忚鐨勯�氳绫�
@@ -328,7 +335,7 @@
private void Ping()
{
- Task.Run(() =>
+ Task.Run(async () =>
{
while (_isPing)
{
@@ -342,7 +349,7 @@
}
finally
{
- Thread.Sleep(100);
+ await Task.Delay(PingIntervalMs);
}
}
});
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/SerialPortComm/SerialPortCommunicator.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/SerialPortComm/SerialPortCommunicator.cs
index 99ef5e6..0e27f6e 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/SerialPortComm/SerialPortCommunicator.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Communicator/SerialPortComm/SerialPortCommunicator.cs
@@ -120,7 +120,7 @@
}
catch (Exception ex)
{
-
+ LogNet.WriteException(Name, $"涓插彛鏁版嵁鎺ユ敹寮傚父锛屼覆鍙e彿:{_serialPortName}", ex);
}
}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/BaseRepository/UnitOfWorks/UnitOfWorkManage.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/BaseRepository/UnitOfWorks/UnitOfWorkManage.cs
index e046f58..2310264 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/BaseRepository/UnitOfWorks/UnitOfWorkManage.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/BaseRepository/UnitOfWorks/UnitOfWorkManage.cs
@@ -6,6 +6,7 @@
using System.Linq;
using System.Reflection;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using WIDESEAWCS_Core.Helper;
@@ -135,9 +136,10 @@
lock (this)
{
string result = "";
+ var spinner = new SpinWait();
while (!TranStack.IsEmpty && !TranStack.TryPeek(out result))
{
- Thread.Sleep(1);
+ spinner.SpinOnce();
}
@@ -158,9 +160,10 @@
}
finally
{
+ var spinner2 = new SpinWait();
while (!TranStack.TryPop(out _))
{
- Thread.Sleep(1);
+ spinner2.SpinOnce();
}
_tranCount = TranStack.Count;
@@ -185,9 +188,10 @@
lock (this)
{
string result = "";
+ var spinner = new SpinWait();
while (!TranStack.IsEmpty && !TranStack.TryPeek(out result))
{
- Thread.Sleep(1);
+ spinner.SpinOnce();
}
if (result == method.GetFullName())
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/ICacheService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/ICacheService.cs
index d733532..85ef381 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/ICacheService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/ICacheService.cs
@@ -45,6 +45,52 @@
/// <returns></returns>
void Remove(IEnumerable<string> keys);
+ #region 鍒犻櫎鎵╁睍鏂规硶
+
+ /// <summary>
+ /// 鍒犻櫎骞惰幏鍙栧�硷細鍒犻櫎鎸囧畾Key骞惰繑鍥炲叾鍊�
+ /// </summary>
+ /// <param name="key">缂撳瓨Key</param>
+ /// <returns>琚垹闄ょ殑鍊硷紝涓嶅瓨鍦ㄨ繑鍥瀗ull</returns>
+ string? RemoveAndGet(string key);
+
+ /// <summary>
+ /// 鍒犻櫎骞惰幏鍙栧璞★細鍒犻櫎鎸囧畾Key骞惰繑鍥炲叾瀵硅薄鍊�
+ /// </summary>
+ /// <param name="key">缂撳瓨Key</param>
+ /// <returns>琚垹闄ょ殑瀵硅薄锛屼笉瀛樺湪杩斿洖null</returns>
+ T? RemoveAndGet<T>(string key) where T : class;
+
+ /// <summary>
+ /// 鎸夊墠缂�鍒犻櫎锛氬垹闄ゆ墍鏈変互鎸囧畾鍓嶇紑寮�澶寸殑Key
+ /// </summary>
+ /// <param name="prefix">Key鍓嶇紑</param>
+ /// <returns>鍒犻櫎鐨勬暟閲�</returns>
+ int RemoveByPrefix(string prefix);
+
+ /// <summary>
+ /// 鎸夋ā寮忓垹闄わ細鍒犻櫎鍖归厤鎸囧畾妯″紡鐨勬墍鏈塊ey锛堟敮鎸�*閫氶厤绗︼級
+ /// </summary>
+ /// <param name="pattern">鍖归厤妯″紡锛屽 "user:*", "session:123:*"</param>
+ /// <returns>鍒犻櫎鐨勬暟閲�</returns>
+ int RemoveByPattern(string pattern);
+
+ /// <summary>
+ /// 鎵归噺鍒犻櫎骞惰繑鍥炴垚鍔熸暟閲�
+ /// </summary>
+ /// <param name="keys">缂撳瓨Key闆嗗悎</param>
+ /// <returns>鎴愬姛鍒犻櫎鐨勬暟閲�</returns>
+ int RemoveAll(IEnumerable<string> keys);
+
+ /// <summary>
+ /// 鏉′欢鍒犻櫎锛氬垹闄ゆ弧瓒虫寚瀹氭潯浠剁殑鎵�鏈塊ey
+ /// </summary>
+ /// <param name="predicate">鏉′欢璋撹瘝</param>
+ /// <returns>鍒犻櫎鐨勬暟閲�</returns>
+ int RemoveWhere(Func<string, bool> predicate);
+
+ #endregion
+
/// <summary>
/// 鑾峰彇缂撳瓨
/// </summary>
@@ -60,6 +106,95 @@
/// <param name="key">缂撳瓨Key</param>
/// <returns></returns>
string? Get(string key);
+
+ #region 娣诲姞鍜屼慨鏀规墿灞曟柟娉�
+
+ /// <summary>
+ /// 鎵归噺娣诲姞缂撳瓨
+ /// </summary>
+ void AddAll(IDictionary<string, string> items, int expireSeconds = -1);
+
+ /// <summary>
+ /// 鎵归噺娣诲姞瀵硅薄缂撳瓨
+ /// </summary>
+ void AddAllObjects(IDictionary<string, object> items, int expireSeconds = -1);
+
+ /// <summary>
+ /// 鏇挎崲锛氫粎褰揔ey瀛樺湪鏃舵浛鎹㈠叾鍊�
+ /// </summary>
+ bool Replace(string key, string newValue, int expireSeconds = -1);
+
+ /// <summary>
+ /// 鏇挎崲瀵硅薄锛氫粎褰揔ey瀛樺湪鏃舵浛鎹㈠叾鍊�
+ /// </summary>
+ bool Replace<T>(string key, T newValue, int expireSeconds = -1) where T : class;
+
+ /// <summary>
+ /// 鑾峰彇骞跺埛鏂帮細鑾峰彇鍊煎苟鍒锋柊鍏惰繃鏈熸椂闂�
+ /// </summary>
+ string? GetAndRefresh(string key, int expireSeconds);
+
+ /// <summary>
+ /// 鑾峰彇骞跺埛鏂板璞★細鑾峰彇瀵硅薄骞跺埛鏂板叾杩囨湡鏃堕棿
+ /// </summary>
+ T? GetAndRefresh<T>(string key, int expireSeconds) where T : class;
+
+ /// <summary>
+ /// 鍒锋柊杩囨湡鏃堕棿锛氭洿鏂版寚瀹欿ey鐨勮繃鏈熸椂闂�
+ /// </summary>
+ bool RefreshExpire(string key, int expireSeconds);
+
+ /// <summary>
+ /// 璁剧疆杩囨湡鏃堕棿锛氬湪鎸囧畾绉掓暟鍚庤繃鏈�
+ /// </summary>
+ bool ExpireIn(string key, int seconds);
+
+ /// <summary>
+ /// 璁剧疆杩囨湡鏃堕棿锛氬湪鎸囧畾鏃堕棿鐐硅繃鏈�
+ /// </summary>
+ bool ExpireAt(string key, DateTime expireTime);
+
+ /// <summary>
+ /// 鑾峰彇鍓╀綑杩囨湡鏃堕棿锛堢锛�
+ /// </summary>
+ long? GetExpire(string key);
+
+ /// <summary>
+ /// 鍘熷瓙鎿嶄綔锛氫粎褰揔ey涓嶅瓨鍦ㄦ椂娣诲姞锛堝師瀛愭搷浣滐級
+ /// </summary>
+ bool AddIfNotExists(string key, string value, int expireSeconds = -1);
+
+ /// <summary>
+ /// 鍘熷瓙鎿嶄綔锛氫粎褰揔ey涓嶅瓨鍦ㄦ椂娣诲姞瀵硅薄锛堝師瀛愭搷浣滐級
+ /// </summary>
+ bool AddIfNotExists<T>(string key, T value, int expireSeconds = -1) where T : class;
+
+ /// <summary>
+ /// 鍘熷瓙鎿嶄綔锛氳幏鍙栨棫鍊煎苟璁剧疆鏂板��
+ /// </summary>
+ string? GetAndSet(string key, string newValue, int expireSeconds = -1);
+
+ /// <summary>
+ /// 鍘熷瓙鎿嶄綔锛氳幏鍙栨棫瀵硅薄骞惰缃柊瀵硅薄
+ /// </summary>
+ T? GetAndSet<T>(string key, T newValue, int expireSeconds = -1) where T : class;
+
+ /// <summary>
+ /// 鑷锛氬皢Key涓殑鏁板�艰嚜澧烇紝杩斿洖鑷鍚庣殑鍊�
+ /// </summary>
+ long Increment(string key, long value = 1);
+
+ /// <summary>
+ /// 鑷噺锛氬皢Key涓殑鏁板�艰嚜鍑忥紝杩斿洖鑷噺鍚庣殑鍊�
+ /// </summary>
+ long Decrement(string key, long value = 1);
+
+ /// <summary>
+ /// 杩藉姞锛氬悜鐜版湁鍊艰拷鍔犲唴瀹�
+ /// </summary>
+ long Append(string key, string value);
+
+ #endregion
#region ConcurrentDictionary椋庢牸鏂规硶
@@ -94,6 +229,24 @@
bool TryUpdate(string key, string newValue, int expireSeconds = -1);
/// <summary>
+ /// 鍊煎彂鐢熸敼鍙樻椂鏇存柊锛氫粎褰揔ey瀛樺湪涓旀柊鍊间笌鏃у�间笉鍚屾椂鎵嶆洿鏂�
+ /// </summary>
+ /// <param name="key">缂撳瓨Key</param>
+ /// <param name="newValue">鏂板��</param>
+ /// <param name="expireSeconds">杩囨湡鏃堕棿锛堢锛�</param>
+ /// <returns>鍊兼槸鍚﹀彂鐢熶簡鏀瑰彉骞舵洿鏂版垚鍔�</returns>
+ bool TryUpdateIfChanged(string key, string newValue, int expireSeconds = -1);
+
+ /// <summary>
+ /// 鍊煎彂鐢熸敼鍙樻椂鏇存柊瀵硅薄锛氫粎褰揔ey瀛樺湪涓旀柊鍊间笌鏃у�间笉鍚屾椂鎵嶆洿鏂�
+ /// </summary>
+ /// <param name="key">缂撳瓨Key</param>
+ /// <param name="newValue">鏂板��</param>
+ /// <param name="expireSeconds">杩囨湡鏃堕棿锛堢锛�</param>
+ /// <returns>鍊兼槸鍚﹀彂鐢熶簡鏀瑰彉骞舵洿鏂版垚鍔�</returns>
+ bool TryUpdateIfChanged<T>(string key, T newValue, int expireSeconds = -1) where T : class;
+
+ /// <summary>
/// 鑾峰彇鎴栨坊鍔狅細Key瀛樺湪鍒欒繑鍥炵幇鏈夊�硷紝涓嶅瓨鍦ㄥ垯娣诲姞骞惰繑鍥炴柊鍊�
/// </summary>
string GetOrAdd(string key, string value, int expireSeconds = -1);
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/MemoryCacheService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/MemoryCacheService.cs
index 5f6d4ef..a1c7c18 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/MemoryCacheService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Caches/MemoryCacheService.cs
@@ -145,6 +145,183 @@
keys.ToList().ForEach(item => _cache.Remove(item));
}
+ #region 鍒犻櫎鎵╁睍鏂规硶
+
+ public string? RemoveAndGet(string key)
+ {
+ var value = Get(key);
+ if (value != null) _cache.Remove(key);
+ return value;
+ }
+
+ public T? RemoveAndGet<T>(string key) where T : class
+ {
+ var value = Get<T>(key);
+ if (value != null) _cache.Remove(key);
+ return value;
+ }
+
+ public int RemoveByPrefix(string prefix)
+ {
+ if (string.IsNullOrEmpty(prefix)) return 0;
+ // MemoryCache涓嶆敮鎸佹灇涓撅紝杩斿洖0
+ return 0;
+ }
+
+ public int RemoveByPattern(string pattern)
+ {
+ // MemoryCache涓嶆敮鎸佹ā寮忓尮閰�
+ return 0;
+ }
+
+ public int RemoveAll(IEnumerable<string> keys)
+ {
+ if (keys == null) return 0;
+ int count = 0;
+ foreach (var key in keys)
+ {
+ if (Remove(key)) count++;
+ }
+ return count;
+ }
+
+ public int RemoveWhere(Func<string, bool> predicate)
+ {
+ // MemoryCache涓嶆敮鎸佹灇涓炬墍鏈塊ey
+ return 0;
+ }
+
+ #endregion
+
+ #region 娣诲姞鍜屼慨鏀规墿灞曟柟娉�
+
+ public void AddAll(IDictionary<string, string> items, int expireSeconds = -1)
+ {
+ if (items == null) return;
+ foreach (var item in items)
+ {
+ Add(item.Key, item.Value, expireSeconds);
+ }
+ }
+
+ public void AddAllObjects(IDictionary<string, object> items, int expireSeconds = -1)
+ {
+ if (items == null) return;
+ foreach (var item in items)
+ {
+ AddObject(item.Key, item.Value, expireSeconds);
+ }
+ }
+
+ public bool Replace(string key, string newValue, int expireSeconds = -1)
+ {
+ return TryUpdate(key, newValue, expireSeconds);
+ }
+
+ public bool Replace<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ if (!Exists(key)) return false;
+ AddObject(key, newValue, expireSeconds);
+ return true;
+ }
+
+ public string? GetAndRefresh(string key, int expireSeconds)
+ {
+ var value = Get(key);
+ if (value != null)
+ {
+ Add(key, value, expireSeconds);
+ return value;
+ }
+ return null;
+ }
+
+ public T? GetAndRefresh<T>(string key, int expireSeconds) where T : class
+ {
+ var value = Get<T>(key);
+ if (value != null)
+ {
+ AddObject(key, value, expireSeconds);
+ return value;
+ }
+ return default;
+ }
+
+ public bool RefreshExpire(string key, int expireSeconds)
+ {
+ var value = Get(key);
+ if (value != null)
+ {
+ Add(key, value, expireSeconds);
+ return true;
+ }
+ return false;
+ }
+
+ public bool ExpireIn(string key, int seconds)
+ {
+ return RefreshExpire(key, seconds);
+ }
+
+ public bool ExpireAt(string key, DateTime expireTime)
+ {
+ var seconds = (int)(expireTime - DateTime.Now).TotalSeconds;
+ if (seconds <= 0) return Remove(key);
+ return RefreshExpire(key, seconds);
+ }
+
+ public long? GetExpire(string key)
+ {
+ return null; // MemoryCache涓嶆敮鎸乀TL鏌ヨ
+ }
+
+ public bool AddIfNotExists(string key, string value, int expireSeconds = -1)
+ {
+ return TryAdd(key, value, expireSeconds);
+ }
+
+ public bool AddIfNotExists<T>(string key, T value, int expireSeconds = -1) where T : class
+ {
+ return TryAdd(key, value, expireSeconds);
+ }
+
+ public string? GetAndSet(string key, string newValue, int expireSeconds = -1)
+ {
+ var oldValue = Get(key);
+ Add(key, newValue, expireSeconds);
+ return oldValue;
+ }
+
+ public T? GetAndSet<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ var oldValue = Get<T>(key);
+ AddObject(key, newValue, expireSeconds);
+ return oldValue;
+ }
+
+ public long Increment(string key, long value = 1)
+ {
+ var current = long.TryParse(Get(key), out var v) ? v : 0;
+ var newValue = current + value;
+ Add(key, newValue.ToString());
+ return newValue;
+ }
+
+ public long Decrement(string key, long value = 1)
+ {
+ return Increment(key, -value);
+ }
+
+ public long Append(string key, string value)
+ {
+ var current = Get(key) ?? "";
+ var newValue = current + value;
+ Add(key, newValue);
+ return newValue.Length;
+ }
+
+ #endregion
+
public bool TryAdd(string key, string value, int expireSeconds = -1)
{
if (Exists(key)) return false;
@@ -190,6 +367,31 @@
return true;
}
+ public bool TryUpdateIfChanged(string key, string newValue, int expireSeconds = -1)
+ {
+ var existing = Get(key);
+ if (existing == null) return false;
+ if (existing == newValue) return false; // 鍊肩浉鍚岋紝涓嶆洿鏂�
+ Remove(key);
+ Add(key, newValue, expireSeconds);
+ return true;
+ }
+
+ public bool TryUpdateIfChanged<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ var existing = Get<T>(key);
+ if (existing == null) return false;
+
+ // 浣跨敤JSON搴忓垪鍖栨瘮杈冨唴瀹癸紝鑰屼笉鏄紩鐢ㄦ瘮杈�
+ var existingJson = JsonConvert.SerializeObject(existing);
+ var newJson = JsonConvert.SerializeObject(newValue);
+ if (existingJson == newJson) return false; // JSON瀛楃涓茬浉鍚岋紝涓嶆洿鏂�
+
+ Remove(key);
+ AddObject(key, newValue, expireSeconds);
+ return true;
+ }
+
public string GetOrAdd(string key, string value, int expireSeconds = -1)
{
var existing = _cache.Get(key)?.ToString();
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Const/CommunicationConst.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Const/CommunicationConst.cs
new file mode 100644
index 0000000..a729346
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Const/CommunicationConst.cs
@@ -0,0 +1,55 @@
+using System;
+
+namespace WIDESEAWCS_Core.Const
+{
+ /// <summary>
+ /// 璁惧閫氳鐩稿叧甯搁噺
+ /// </summary>
+ public static class CommunicationConst
+ {
+ /// <summary>
+ /// 璁惧绛夊緟闂撮殧鏃堕棿锛堟绉掞級
+ /// </summary>
+ public const int WaitIntervalMs = 500;
+
+ /// <summary>
+ /// 璁惧绛夊緟瓒呮椂鏃堕棿鍩烘暟锛堟绉掞級
+ /// </summary>
+ public const int WaitTimeoutBaseMs = 6000;
+
+ /// <summary>
+ /// 璁惧绛夊緟瓒呮椂鏃堕棿鍊嶆暟
+ /// </summary>
+ public const int WaitTimeoutMultiplier = 10;
+
+ /// <summary>
+ /// 璁惧绛夊緟鎬昏秴鏃舵椂闂达紙姣锛�= WaitTimeoutMultiplier * WaitTimeoutBaseMs
+ /// </summary>
+ public static readonly int WaitTotalTimeoutMs = WaitTimeoutMultiplier * WaitTimeoutBaseMs;
+
+ /// <summary>
+ /// Ping 妫�娴嬮棿闅旀椂闂达紙姣锛�
+ /// </summary>
+ public const int PingIntervalMs = 100;
+
+ /// <summary>
+ /// 鏃ュ織鍐欏叆闂撮殧鏃堕棿锛堟绉掞級
+ /// </summary>
+ public const int LogWriteIntervalMs = 5000;
+
+ /// <summary>
+ /// HTTP 榛樿瓒呮椂鏃堕棿锛堢锛�
+ /// </summary>
+ public const int HttpDefaultTimeoutSeconds = 60;
+
+ /// <summary>
+ /// HTTP 杩炴帴瓒呮椂鏃堕棿锛堢锛�
+ /// </summary>
+ public const int HttpConnectTimeoutSeconds = 30;
+
+ /// <summary>
+ /// 鏁版嵁搴撹繛鎺ヨ秴鏃舵椂闂达紙绉掞級
+ /// </summary>
+ public const int DbConnectTimeoutSeconds = 30;
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/AppSettings.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/AppSettings.cs
index 642febb..3202c5a 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/AppSettings.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/AppSettings.cs
@@ -52,7 +52,10 @@
return Configuration[string.Join(":", sections)];
}
}
- catch (Exception) { }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"AppSettings璇诲彇閰嶇疆澶辫触: {ex.Message}");
+ }
return "";
}
@@ -83,7 +86,10 @@
{
return Configuration[sectionsPath];
}
- catch (Exception) { }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"AppSettings璇诲彇閰嶇疆澶辫触: {ex.Message}");
+ }
return "";
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/HttpHelper.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/HttpHelper.cs
index 03706c4..122c2ed 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/HttpHelper.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Helper/HttpHelper.cs
@@ -6,6 +6,7 @@
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
+using WIDESEAWCS_Core.Const;
using WIDESEAWCS_Core.LogHelper;
namespace WIDESEAWCS_Core.Helper
@@ -24,7 +25,7 @@
/// 鏂规硶浼氳嚜鍔ㄨ褰曡姹傛棩蹇楋紝鍖呭惈璇锋眰鍦板潃銆佸搷搴斿唴瀹瑰拰鑰楁椂绛変俊鎭�
/// 榛樿璇锋眰瓒呮椂鏃堕棿涓�60绉�
/// </remarks>
- public static async Task<string> GetAsync(string serviceAddress, string contentType = "application/json", int timeOut = 60, Dictionary<string, string>? headers = null)
+ public static async Task<string> GetAsync(string serviceAddress, string contentType = "application/json", int timeOut = CommunicationConst.HttpDefaultTimeoutSeconds, Dictionary<string, string>? headers = null)
{
string result = string.Empty;
DateTime beginDate = DateTime.Now;
@@ -39,7 +40,8 @@
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
- result = await httpClient.GetAsync(serviceAddress).Result.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await httpClient.GetAsync(serviceAddress);
+ result = await response.Content.ReadAsStringAsync();
return result;
}
catch (Exception e)
@@ -63,7 +65,7 @@
/// <remarks>
/// 鑷姩璁板綍璇锋眰鏃ュ織锛屽寘鍚姹傚湴鍧�銆佽姹傛暟鎹�佸搷搴旀暟鎹拰鑰楁椂
/// </remarks>
- public static async Task<string?> PostAsync(string serviceAddress, string requestJson = null, string contentType = "application/json", int timeOut = 60, Dictionary<string, string>? headers = null)
+ public static async Task<string?> PostAsync(string serviceAddress, string requestJson = null, string contentType = "application/json", int timeOut = CommunicationConst.HttpDefaultTimeoutSeconds, Dictionary<string, string>? headers = null)
{
string result = string.Empty;
DateTime beginDate = DateTime.Now;
@@ -86,7 +88,8 @@
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
- result = await httpClient.PostAsync(serviceAddress, httpContent).Result.Content.ReadAsStringAsync();
+ HttpResponseMessage response = await httpClient.PostAsync(serviceAddress, httpContent);
+ result = await response.Content.ReadAsStringAsync();
}
return result;
}
@@ -111,7 +114,7 @@
/// /// <remarks>
/// 璇ユ柟娉曚細鑷姩璁板綍璇锋眰鏃ュ織锛屽寘鍚姹傚湴鍧�銆佽姹傛暟鎹�佸搷搴旀暟鎹拰鑰楁椂
/// </remarks>
- public static string Get(string serviceAddress, string contentType = "application/json", int timeOut = 60, Dictionary<string, string>? headers = null)
+ public static string Get(string serviceAddress, string contentType = "application/json", int timeOut = CommunicationConst.HttpDefaultTimeoutSeconds, Dictionary<string, string>? headers = null)
{
string result = string.Empty;
DateTime beginDate = DateTime.Now;
@@ -126,7 +129,8 @@
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
- result = httpClient.GetStringAsync(serviceAddress).Result;
+ Task<string> task = httpClient.GetStringAsync(serviceAddress);
+ result = task.GetAwaiter().GetResult();
return result;
}
catch (Exception e)
@@ -151,7 +155,7 @@
/// <remarks>
/// 璇ユ柟娉曚細鑷姩璁板綍璇锋眰鏃ュ織锛屽寘鍚姹傚湴鍧�銆佽姹傛暟鎹�佸搷搴旀暟鎹拰鑰楁椂
/// </remarks>
- public static string Post(string serviceAddress, string requestJson = null, string contentType = "application/json", int timeOut = 60, Dictionary<string, string>? headers = null)
+ public static string Post(string serviceAddress, string requestJson = null, string contentType = "application/json", int timeOut = CommunicationConst.HttpDefaultTimeoutSeconds, Dictionary<string, string>? headers = null)
{
string result = string.Empty;
DateTime beginDate = DateTime.Now;
@@ -174,10 +178,12 @@
httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
}
- HttpResponseMessage message = httpClient.PostAsync(serviceAddress, httpContent).Result;
+ Task<HttpResponseMessage> postTask = httpClient.PostAsync(serviceAddress, httpContent);
+ HttpResponseMessage message = postTask.GetAwaiter().GetResult();
if (message.StatusCode == HttpStatusCode.OK)
{
- result = message.Content.ReadAsStringAsync().Result;
+ Task<string> readTask = message.Content.ReadAsStringAsync();
+ result = readTask.GetAwaiter().GetResult();
}
else
{
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Http/HTTP/HttpClientHelper.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Http/HTTP/HttpClientHelper.cs
index 4b08204..635d07d 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Http/HTTP/HttpClientHelper.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/Http/HTTP/HttpClientHelper.cs
@@ -10,6 +10,8 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
+using WIDESEAWCS_Common;
+using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Core.Caches;
namespace WIDESEA_Core
@@ -23,6 +25,19 @@
{
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
+
+
+
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.CreateTaskInboundAsync)}", $"Task/CreateTaskInbound");
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.GetTasksLocation)}", $"Task/GetTasksLocation");
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.OutboundFinishTaskAsync)}", $"Task/OutboundFinishTask");
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.InboundFinishTaskAsync)}", $"Task/InboundFinishTask");
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.GetOutBoundTrayTaskAsync)}", $"Task/GetOutBoundTrayTask");
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.CreateTaskInboundTrayAsync)}", $"Task/CreateTaskInboundTray");
+
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.GroupPalletAsync)}", $"Stock/GroupPalletAsync");
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.ChangePalletAsync)}", $"Stock/ChangePalletAsync");
+ _cache.TryAdd($"{RedisPrefix.Code}:{RedisName.API}:{nameof(ConfigKey.SplitPalletAsync)}", $"Stock/SplitPalletAsync");
}
/// <summary>
@@ -35,26 +50,31 @@
/// <returns></returns>
public HttpResponseResult Post(string url, string content, string contentType = "application/json", HttpRequestConfig? config = null)
{
- url = _cache.Get(url);
- HttpResponseResult httpResponseResult = ExecuteAsync(async (client) =>
+ HttpResponseResult httpResponseResult = Task.Run(async () =>
{
- var request = new HttpRequestMessage(HttpMethod.Post, url);
- request.Content = new StringContent(content ?? string.Empty, Encoding.UTF8, contentType);
- SetRequestHeaders(request, config?.Headers);
- return await client.SendAsync(request);
- }, config, $"POST {url}").Result;
+ return await ExecuteAsync(async (client) =>
+ {
+ var request = new HttpRequestMessage(HttpMethod.Post, url);
+ request.Content = new StringContent(content ?? string.Empty, Encoding.UTF8, contentType);
+ SetRequestHeaders(request, config?.Headers);
+ return await client.SendAsync(request);
+ }, config, $"POST {url}");
+ }).GetAwaiter().GetResult();
httpResponseResult.ApiUrl = url;
return httpResponseResult;
}
public HttpResponseResult Get(string url, HttpRequestConfig? config = null)
{
- HttpResponseResult httpResponseResult = ExecuteAsync(async (client) =>
+ HttpResponseResult httpResponseResult = Task.Run(async () =>
{
- var request = new HttpRequestMessage(HttpMethod.Get, url);
- SetRequestHeaders(request, config?.Headers);
- return await client.SendAsync(request);
- }, config, $"GET {url}").Result;
+ return await ExecuteAsync(async (client) =>
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ SetRequestHeaders(request, config?.Headers);
+ return await client.SendAsync(request);
+ }, config, $"GET {url}");
+ }).GetAwaiter().GetResult();
httpResponseResult.ApiUrl = url;
return httpResponseResult;
@@ -72,6 +92,7 @@
public HttpResponseResult<TResponse> Post<TResponse>(string url, string content, string contentType = "application/json", HttpRequestConfig? config = null)
{
+ url = BaseAPI.WMSBaseUrl + _cache.Get($"{RedisPrefix.Code}:{RedisName.API}:{url}");
HttpResponseResult httpResponseResult = Post(url, content, contentType, config);
HttpResponseResult<TResponse> result = new HttpResponseResult<TResponse>
@@ -113,6 +134,7 @@
public HttpResponseResult<TResponse> Get<TResponse>(string url, HttpRequestConfig? config = null)
{
+ url = BaseAPI.WMSBaseUrl + _cache.Get(url);
HttpResponseResult httpResponseResult = Get(url, config);
HttpResponseResult<TResponse> result = new HttpResponseResult<TResponse>
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/LogHelper/Logger.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/LogHelper/Logger.cs
index 70a7b03..b9387a0 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/LogHelper/Logger.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/LogHelper/Logger.cs
@@ -7,6 +7,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using WIDESEAWCS_Core.Const;
using WIDESEAWCS_Core.DB;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.HttpContextUser;
@@ -35,7 +36,7 @@
/// 2. 褰撶紦瀛樿〃鏈夋暟鎹椂锛屼娇鐢⊿qlSugar杩涜鎵归噺鎻掑叆
/// 3. 鍙戠敓寮傚父鏃朵細杈撳嚭閿欒淇℃伅浣嗕笉浼氱粓姝㈢嚎绋�
/// </remarks>
- static void StartWriteLog()
+ static async void StartWriteLog()
{
DataTable queueTable = CreateEmptyTable();
while (true)
@@ -48,7 +49,7 @@
DequeueToTable(queueTable); continue;
}
//姣�5绉掑啓涓�娆℃暟鎹�
- Thread.Sleep(5000);
+ await Task.Delay(CommunicationConst.LogWriteIntervalMs);
//濡傛灉闃熷垪琛ㄤ腑鐨勮鏁颁负0锛屽垯璺宠繃鏈寰幆
if (queueTable.Rows.Count == 0) { continue; }
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/WIDESEAWCS_Core.csproj b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/WIDESEAWCS_Core.csproj
index f3bb1e7..37f3c9c 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/WIDESEAWCS_Core.csproj
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Core/WIDESEAWCS_Core.csproj
@@ -76,4 +76,8 @@
<Folder Include="ServiceExtensions\" />
</ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\WIDESEAWCS_Common\WIDESEAWCS_Common.csproj" />
+ </ItemGroup>
+
</Project>
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/QuartzNetExtension.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/QuartzNetExtension.cs
index b52c966..50b4e4e 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/QuartzNetExtension.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/QuartzNetExtension.cs
@@ -1,5 +1,7 @@
锘縰sing System.Reflection;
+using WIDESEAWCS_Common;
using WIDESEAWCS_Core;
+using WIDESEAWCS_Core.Caches;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_QuartzJob.DTO;
using WIDESEAWCS_QuartzJob.Service;
@@ -15,19 +17,21 @@
private readonly IDeviceInfoService _deviceInfoService;
private readonly IDispatchInfoService _dispatchInfoService;
private readonly IDeviceProtocolDetailService _deviceProtocolDetailService;
+ private readonly ICacheService _cacheService;
private readonly Storage _storage;
/// <summary>
/// 鍚姩绋嬪簭鑷姩寮�鍚皟搴︽湇鍔�
/// </summary>
/// <returns></returns>
- public QuartzNetExtension(IDeviceInfoService deviceInfoService, IDispatchInfoService dispatchInfoService, ISchedulerCenter schedulerCenter, IDeviceProtocolDetailService deviceProtocolDetailService, Storage storage)
+ public QuartzNetExtension(IDeviceInfoService deviceInfoService, IDispatchInfoService dispatchInfoService, ISchedulerCenter schedulerCenter, IDeviceProtocolDetailService deviceProtocolDetailService, Storage storage, ICacheService cacheService)
{
_deviceInfoService = deviceInfoService;
_dispatchInfoService = dispatchInfoService;
_schedulerCenter = schedulerCenter;
_deviceProtocolDetailService = deviceProtocolDetailService;
_storage = storage;
+ _cacheService = cacheService;
}
/// <summary>
@@ -40,6 +44,7 @@
{
List<DispatchInfoDTO> dispatches = _dispatchInfoService.QueryDispatchInfos();
List<DeviceInfoDTO> deviceInfos = await _deviceInfoService.QueryDeviceProInfos();
+ _cacheService.RemoveByPrefix($"{RedisPrefix.System}");
deviceInfos.ForEach(x =>
{
@@ -107,6 +112,8 @@
x.Device = (IDevice)deviceInstance;
Storage.Devices.Add((IDevice)deviceInstance);
+
+ _cacheService.AddObject($"{RedisPrefix.System}:{RedisName.IDevice}:{x.DeviceName}", (IDevice)deviceInstance);
}
}
catch (Exception ex)
@@ -125,6 +132,7 @@
if (targetDevice is null) continue;
+
// 浣跨敤妯″紡鍖归厤
dispatches[i].JobParams = targetDevice switch
{
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs
index 3b8288c..1556608 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/QuartzNet/SchedulerCenterServer.cs
@@ -49,9 +49,10 @@
public SchedulerCenterServer(IJobFactory jobFactory)
{
_iocjobFactory = jobFactory;
- _scheduler = GetSchedulerAsync();
+ // 浣跨敤鍚屾涓婁笅鏂囪繍琛屽紓姝ユ柟娉�
+ _scheduler = Task.Run(async () => await GetSchedulerAsync()).GetAwaiter().GetResult();
}
- private IScheduler GetSchedulerAsync()
+ private async Task<IScheduler> GetSchedulerAsync()
{
if (_scheduler != null)
return this._scheduler;
@@ -66,7 +67,7 @@
};
//StdSchedulerFactory factory = new StdSchedulerFactory(collection);
StdSchedulerFactory factory = new StdSchedulerFactory();
- return _scheduler = factory.GetScheduler().Result;
+ return _scheduler = await factory.GetScheduler();
}
catch (Exception ex)
{
@@ -93,7 +94,7 @@
};
//StdSchedulerFactory factory = new StdSchedulerFactory(collection);
StdSchedulerFactory factory = new StdSchedulerFactory();
- _scheduler = factory.GetScheduler().Result;
+ _scheduler = await factory.GetScheduler();
}
this._scheduler.JobFactory = this._iocjobFactory;
@@ -176,7 +177,7 @@
};
//StdSchedulerFactory factory = new StdSchedulerFactory(collection);
StdSchedulerFactory factory = new StdSchedulerFactory();
- _scheduler = factory.GetScheduler().Result;
+ _scheduler = await factory.GetScheduler();
}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/Service/RouterService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/Service/RouterService.cs
index c63f06e..994ed38 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/Service/RouterService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/Service/RouterService.cs
@@ -11,6 +11,7 @@
using WIDESEAWCS_DTO.BasicInfo;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Repository;
+using WIDESEAWCS_Common;
using ICacheService = WIDESEAWCS_Core.Caches.ICacheService;
namespace WIDESEAWCS_QuartzJob.Service
@@ -330,10 +331,9 @@
{
// 鍒涘缓涓�涓瓧绗︿覆鍒楄〃锛岀敤浜庡瓨鍌ㄦ墍鏈変綅缃�
List<string> positions = new List<string>();
- var device = _cacheService.Get<List<string>>($"DevicePositions:{deviceCode}");
+ var device = _cacheService.Get<List<string>>($"{RedisPrefix.System}:{RedisName.DevicePositions}:{deviceCode}");
if (device.IsNullOrEmpty())
{
-
try
{
// 鏌ヨ鎵�鏈夎繘鍏ヨ矾鐢卞櫒鐨勪綅缃�
@@ -352,6 +352,10 @@
{
}
+ finally
+ {
+ _cacheService.TryAdd($"{RedisPrefix.System}:{RedisName.DevicePositions}:{deviceCode}", positions);
+ }
}
else
positions = device;
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/StackerCrane/Spec/SpeTemperatureStackerCrane.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/StackerCrane/Spec/SpeTemperatureStackerCrane.cs
index cc063d3..89d18be 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/StackerCrane/Spec/SpeTemperatureStackerCrane.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/StackerCrane/Spec/SpeTemperatureStackerCrane.cs
@@ -16,6 +16,30 @@
[Description("楂樺父娓╁爢鍨涙満")]
public class SpeTemperatureStackerCrane : StackerCraneBase, IStackerCrane
{
+ #region Constants
+
+ /// <summary>
+ /// 璁惧绛夊緟闂撮殧鏃堕棿锛堟绉掞級
+ /// </summary>
+ private const int WaitIntervalMs = 500;
+
+ /// <summary>
+ /// 璁惧绛夊緟瓒呮椂鏃堕棿鍩烘暟锛堟绉掞級
+ /// </summary>
+ private const int WaitTimeoutBaseMs = 6000;
+
+ /// <summary>
+ /// 璁惧绛夊緟瓒呮椂鏃堕棿鍊嶆暟
+ /// </summary>
+ private const int WaitTimeoutMultiplier = 10;
+
+ /// <summary>
+ /// 璁惧绛夊緟鎬昏秴鏃舵椂闂达紙姣锛�
+ /// </summary>
+ private static readonly int WaitTotalTimeoutMs = WaitTimeoutMultiplier * WaitTimeoutBaseMs;
+
+ #endregion Constants
+
#region Private Member
/// <summary>
@@ -410,22 +434,22 @@
return typeCode switch
{
- TypeCode.Boolean => Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000,
+ TypeCode.Boolean => Communicator.Wait(devicePro.DeviceProAddress, WaitIntervalMs, WaitTotalTimeoutMs,
Convert.ToBoolean(deviceProtocolDetail.ProtocalDetailValue)),
- TypeCode.Byte => Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000,
+ TypeCode.Byte => Communicator.Wait(devicePro.DeviceProAddress, WaitIntervalMs, WaitTotalTimeoutMs,
Convert.ToByte(deviceProtocolDetail.ProtocalDetailValue)),
- TypeCode.Int16 => Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000,
+ TypeCode.Int16 => Communicator.Wait(devicePro.DeviceProAddress, WaitIntervalMs, WaitTotalTimeoutMs,
Convert.ToInt16(deviceProtocolDetail.ProtocalDetailValue)),
- TypeCode.Int32 => Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000,
+ TypeCode.Int32 => Communicator.Wait(devicePro.DeviceProAddress, WaitIntervalMs, WaitTotalTimeoutMs,
Convert.ToInt32(deviceProtocolDetail.ProtocalDetailValue)),
- TypeCode.UInt16 => Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000,
+ TypeCode.UInt16 => Communicator.Wait(devicePro.DeviceProAddress, WaitIntervalMs, WaitTotalTimeoutMs,
Convert.ToUInt16(deviceProtocolDetail.ProtocalDetailValue)),
- TypeCode.UInt32 => Communicator.Wait(devicePro.DeviceProAddress, 500, 10 * 6000,
+ TypeCode.UInt32 => Communicator.Wait(devicePro.DeviceProAddress, WaitIntervalMs, WaitTotalTimeoutMs,
Convert.ToUInt32(deviceProtocolDetail.ProtocalDetailValue)),
_ => new OperateResult<TimeSpan>()
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/WIDESEAWCS_QuartzJob.csproj b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/WIDESEAWCS_QuartzJob.csproj
index ce6ef49..aced664 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/WIDESEAWCS_QuartzJob.csproj
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_QuartzJob/WIDESEAWCS_QuartzJob.csproj
@@ -40,6 +40,7 @@
</ItemGroup>
<ItemGroup>
+ <ProjectReference Include="..\WIDESEAWCS_Common\WIDESEAWCS_Common.csproj" />
<ProjectReference Include="..\WIDESEAWCS_Communicator\WIDESEAWCS_Communicator.csproj" />
<ProjectReference Include="..\WIDESEAWCS_Core\WIDESEAWCS_Core.csproj" />
</ItemGroup>
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/CacheSyncBackgroundService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/CacheSyncBackgroundService.cs
new file mode 100644
index 0000000..c366493
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/CacheSyncBackgroundService.cs
@@ -0,0 +1,211 @@
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using StackExchange.Redis;
+using System.Collections.Concurrent;
+using WIDESEAWCS_RedisService.Connection;
+using WIDESEAWCS_RedisService.Options;
+using WIDESEAWCS_RedisService.Serialization;
+
+namespace WIDESEAWCS_RedisService.Cache
+{
+ /// <summary>
+ /// Redis鍒板唴瀛樼紦瀛樼殑鑷姩鍚屾鍚庡彴鏈嶅姟
+ /// 瀹氭湡浠嶳edis鎻愬彇鏁版嵁骞惰鐩栧唴瀛樼紦瀛橈紝纭繚鏁版嵁涓�鑷存��
+ /// </summary>
+ public class CacheSyncBackgroundService : BackgroundService
+ {
+ private readonly IRedisConnectionManager _connectionManager;
+ private readonly IMemoryCache _memoryCache;
+ private readonly IRedisSerializer _serializer;
+ private readonly RedisOptions _options;
+ private readonly ILogger<CacheSyncBackgroundService> _logger;
+ private readonly ConcurrentDictionary<string, bool> _trackedKeys;
+
+ public CacheSyncBackgroundService(
+ IRedisConnectionManager connectionManager,
+ IMemoryCache memoryCache,
+ IRedisSerializer serializer,
+ IOptions<RedisOptions> options,
+ ILogger<CacheSyncBackgroundService> logger)
+ {
+ _connectionManager = connectionManager;
+ _memoryCache = memoryCache;
+ _serializer = serializer;
+ _options = options.Value;
+ _logger = logger;
+ _trackedKeys = new ConcurrentDictionary<string, bool>();
+ }
+
+ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ {
+ // 濡傛灉鏈惎鐢↙1缂撳瓨锛屽垯鏃犻渶鍚屾
+ if (!_options.EnableL1Cache)
+ {
+ _logger.LogInformation("L1缂撳瓨鏈惎鐢紝鍚庡彴鍚屾鏈嶅姟涓嶄細杩愯");
+ return;
+ }
+
+ // 濡傛灉鏈惎鐢ㄨ嚜鍔ㄥ悓姝ワ紝鍒欎笉杩愯
+ if (!_options.EnableAutoSync)
+ {
+ _logger.LogInformation("Redis鑷姩鍚屾鏈惎鐢紝鍚庡彴鍚屾鏈嶅姟涓嶄細杩愯");
+ return;
+ }
+
+ _logger.LogInformation("Redis缂撳瓨鍚屾鏈嶅姟宸插惎鍔紝鍚屾闂撮殧: {Interval}绉�", _options.SyncIntervalSeconds);
+
+ // 绛夊緟Redis杩炴帴灏辩华
+ while (!_connectionManager.IsConnected && !stoppingToken.IsCancellationRequested)
+ {
+ _logger.LogWarning("绛夊緟Redis杩炴帴灏辩华...");
+ await Task.Delay(5000, stoppingToken);
+ }
+
+ if (stoppingToken.IsCancellationRequested)
+ return;
+
+ // 鍚姩鏃跺厛杩涜涓�娆″叏閲忓悓姝�
+ await SyncCacheAsync(stoppingToken);
+
+ // 瀹氭湡鍚屾
+ while (!stoppingToken.IsCancellationRequested)
+ {
+ try
+ {
+ await Task.Delay(TimeSpan.FromSeconds(_options.SyncIntervalSeconds), stoppingToken);
+
+ if (_connectionManager.IsConnected)
+ {
+ await SyncCacheAsync(stoppingToken);
+ }
+ else
+ {
+ _logger.LogWarning("Redis鏈繛鎺ワ紝璺宠繃鏈鍚屾");
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ // 姝e父閫�鍑�
+ break;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "缂撳瓨鍚屾杩囩▼涓彂鐢熼敊璇�");
+ }
+ }
+
+ _logger.LogInformation("Redis缂撳瓨鍚屾鏈嶅姟宸插仠姝�");
+ }
+
+ /// <summary>
+ /// 鎵ц缂撳瓨鍚屾
+ /// </summary>
+ private async Task SyncCacheAsync(CancellationToken stoppingToken)
+ {
+ try
+ {
+ var db = _connectionManager.GetDatabase();
+ var server = _connectionManager.GetServer();
+ var keyPrefix = _options.KeyPrefix;
+
+ // 鑾峰彇鎵�鏈夊尮閰嶅墠缂�鐨凴edis key
+ var redisKeys = server.Keys(pattern: $"{keyPrefix}*", pageSize: _options.SyncBatchSize).ToList();
+
+ if (redisKeys.Count == 0)
+ {
+ _logger.LogDebug("Redis涓病鏈夋壘鍒板尮閰嶅墠缂� {Prefix} 鐨刱ey", keyPrefix);
+ return;
+ }
+
+ int syncedCount = 0;
+ int skippedCount = 0;
+ int removedCount = 0;
+
+ // 1. 鍚屾Redis涓殑鏁版嵁鍒板唴瀛樼紦瀛�
+ foreach (var redisKey in redisKeys)
+ {
+ if (stoppingToken.IsCancellationRequested)
+ break;
+
+ try
+ {
+ var keyStr = redisKey.ToString();
+ _trackedKeys.AddOrUpdate(keyStr, true, (_, _) => true);
+
+ // 鑾峰彇Redis涓殑鍊�
+ var value = db.StringGet(redisKey);
+ if (!value.IsNullOrEmpty)
+ {
+ var valueStr = value.ToString();
+ var ttl = db.KeyTimeToLive(redisKey);
+
+ // 鑾峰彇杩囨湡鏃堕棿锛屽鏋滄病鏈夎缃甌TL鍒欎娇鐢ㄩ粯璁�5鍒嗛挓
+ var expireSeconds = ttl.HasValue && ttl.Value.TotalSeconds > 0
+ ? (int)ttl.Value.TotalSeconds
+ : 300;
+
+ // 鏇存柊鍐呭瓨缂撳瓨
+ var entryOptions = new MemoryCacheEntryOptions
+ {
+ AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(expireSeconds)
+ };
+
+ _memoryCache.Set(keyStr, valueStr, entryOptions);
+ syncedCount++;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "鍚屾key {Key} 鏃跺彂鐢熼敊璇�", redisKey);
+ }
+ }
+
+ // 2. 娓呯悊鍐呭瓨缂撳瓨涓笉瀛樺湪浜嶳edis鐨刱ey
+ // 娉ㄦ剰锛欼MemoryCache涓嶆敮鎸佹灇涓撅紝杩欓噷鍙兘娓呯悊宸茶窡韪殑key
+ var keysToRemove = _trackedKeys.Where(k => !redisKeys.Contains(k.Key)).Select(k => k.Key).ToList();
+
+ foreach (var keyToRemove in keysToRemove)
+ {
+ _memoryCache.Remove(keyToRemove);
+ _trackedKeys.TryRemove(keyToRemove, out _);
+ removedCount++;
+ }
+
+ _logger.LogInformation(
+ "缂撳瓨鍚屾瀹屾垚: 鍚屾 {SyncedCount} 涓�, 璺宠繃 {SkippedCount} 涓�, 娓呯悊 {RemovedCount} 涓�, 鎬昏 {TotalCount} 涓猭ey",
+ syncedCount, skippedCount, removedCount, redisKeys.Count);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "缂撳瓨鍚屾鏃跺彂鐢熼敊璇�");
+ }
+ }
+
+ /// <summary>
+ /// 鑾峰彇鍚屾缁熻淇℃伅
+ /// </summary>
+ public CacheSyncStatistics GetStatistics()
+ {
+ return new CacheSyncStatistics
+ {
+ IsEnabled = _options.EnableAutoSync && _options.EnableL1Cache,
+ SyncIntervalSeconds = _options.SyncIntervalSeconds,
+ TrackedKeysCount = _trackedKeys.Count,
+ IsRedisConnected = _connectionManager.IsConnected
+ };
+ }
+ }
+
+ /// <summary>
+ /// 缂撳瓨鍚屾缁熻淇℃伅
+ /// </summary>
+ public class CacheSyncStatistics
+ {
+ public bool IsEnabled { get; set; }
+ public int SyncIntervalSeconds { get; set; }
+ public int TrackedKeysCount { get; set; }
+ public bool IsRedisConnected { get; set; }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/HybridCacheService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/HybridCacheService.cs
index 55c3c22..c991362 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/HybridCacheService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/HybridCacheService.cs
@@ -53,9 +53,20 @@
public bool Add(string key, string value, int expireSeconds = -1, bool isSliding = false)
{
var fullKey = BuildKey(key);
- SetMemoryCache(fullKey, value, expireSeconds, isSliding);
+
+ // 鍙湁鍚敤L1缂撳瓨鏃舵墠鍐欏叆鍐呭瓨缂撳瓨
+ if (_options.EnableL1Cache)
+ {
+ SetMemoryCache(fullKey, value, expireSeconds, isSliding);
+ }
+
if (!RedisAvailable)
{
+ if (!_options.EnableL1Cache)
+ {
+ _logger.LogWarning("Redis涓嶅彲鐢ㄤ笖L1缂撳瓨宸茬鐢�, key={Key}", key);
+ return false;
+ }
_logger.LogWarning("Redis涓嶅彲鐢紝浠呬娇鐢ㄥ唴瀛樼紦瀛�, key={Key}", key);
return true;
}
@@ -69,7 +80,7 @@
catch (Exception ex)
{
_logger.LogWarning(ex, "Redis Add澶辫触, key={Key}", key);
- return _options.FallbackToMemory;
+ return _options.FallbackToMemory && _options.EnableL1Cache;
}
}
@@ -91,8 +102,14 @@
public bool Remove(string key)
{
var fullKey = BuildKey(key);
- _memoryCache.Remove(fullKey);
- if (!RedisAvailable) return true;
+
+ // 鍙湁鍚敤L1缂撳瓨鏃舵墠浠庡唴瀛樼紦瀛樹腑绉婚櫎
+ if (_options.EnableL1Cache)
+ {
+ _memoryCache.Remove(fullKey);
+ }
+
+ if (!RedisAvailable) return _options.EnableL1Cache;
try
{
return _connectionManager.GetDatabase().KeyDelete(fullKey);
@@ -109,9 +126,434 @@
foreach (var key in keys) Remove(key);
}
+ #region 鍒犻櫎鎵╁睍鏂规硶
+
+ public string? RemoveAndGet(string key)
+ {
+ var value = Get(key);
+ if (value != null) Remove(key);
+ return value;
+ }
+
+ public T? RemoveAndGet<T>(string key) where T : class
+ {
+ var value = Get<T>(key);
+ if (value != null) Remove(key);
+ return value;
+ }
+
+ public int RemoveByPrefix(string prefix)
+ {
+ if (string.IsNullOrEmpty(prefix)) return 0;
+ var fullPrefix = BuildKey(prefix);
+ int count = 0;
+
+ // 鍒犻櫎鍐呭瓨缂撳瓨涓殑鍖归厤椤�
+ // MemoryCache鏃犳硶鏋氫妇锛岃烦杩�
+
+ if (RedisAvailable)
+ {
+ try
+ {
+ var server = _connectionManager.GetServer();
+ var keys = server.Keys(pattern: $"{fullPrefix}*").ToArray();
+ if (keys.Length > 0)
+ {
+ count = (int)_connectionManager.GetDatabase().KeyDelete(keys);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis RemoveByPrefix澶辫触, prefix={Prefix}", prefix);
+ }
+ }
+
+ return count;
+ }
+
+ public int RemoveByPattern(string pattern)
+ {
+ if (string.IsNullOrEmpty(pattern)) return 0;
+ int count = 0;
+
+ if (RedisAvailable)
+ {
+ try
+ {
+ var server = _connectionManager.GetServer();
+ var keys = server.Keys(pattern: $"{_options.KeyPrefix}{pattern}").ToArray();
+ if (keys.Length > 0)
+ {
+ count = (int)_connectionManager.GetDatabase().KeyDelete(keys);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis RemoveByPattern澶辫触, pattern={Pattern}", pattern);
+ }
+ }
+
+ return count;
+ }
+
+ public int RemoveAll(IEnumerable<string> keys)
+ {
+ if (keys == null) return 0;
+ int count = 0;
+ foreach (var key in keys)
+ {
+ if (Remove(key)) count++;
+ }
+ return count;
+ }
+
+ public int RemoveWhere(Func<string, bool> predicate)
+ {
+ if (predicate == null) return 0;
+ int count = 0;
+
+ if (RedisAvailable)
+ {
+ try
+ {
+ var server = _connectionManager.GetServer();
+ var keys = server.Keys(pattern: $"{_options.KeyPrefix}*").ToArray();
+ var keysToDelete = keys.Where(k =>
+ {
+ var originalKey = k.ToString().Replace(_options.KeyPrefix, "");
+ return predicate(originalKey);
+ }).ToArray();
+
+ if (keysToDelete.Length > 0)
+ {
+ count = (int)_connectionManager.GetDatabase().KeyDelete(keysToDelete);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis RemoveWhere澶辫触");
+ }
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region 娣诲姞鍜屼慨鏀规墿灞曟柟娉�
+
+ public void AddAll(IDictionary<string, string> items, int expireSeconds = -1)
+ {
+ if (items == null) return;
+ foreach (var item in items)
+ {
+ Add(item.Key, item.Value, expireSeconds);
+ }
+ }
+
+ public void AddAllObjects(IDictionary<string, object> items, int expireSeconds = -1)
+ {
+ if (items == null) return;
+ foreach (var item in items)
+ {
+ AddObject(item.Key, item.Value, expireSeconds);
+ }
+ }
+
+ public bool Replace(string key, string newValue, int expireSeconds = -1)
+ {
+ return TryUpdate(key, newValue, expireSeconds);
+ }
+
+ public bool Replace<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ if (!Exists(key)) return false;
+ AddObject(key, newValue, expireSeconds);
+ return true;
+ }
+
+ public string? GetAndRefresh(string key, int expireSeconds)
+ {
+ var fullKey = BuildKey(key);
+ string? value = null;
+
+ // 浠嶳edis鑾峰彇鍊�
+ if (RedisAvailable)
+ {
+ try
+ {
+ var redisValue = _connectionManager.GetDatabase().StringGet(fullKey);
+ if (!redisValue.IsNullOrEmpty)
+ {
+ value = redisValue.ToString();
+ // 鍒锋柊Redis杩囨湡鏃堕棿
+ _connectionManager.GetDatabase().KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis GetAndRefresh澶辫触, key={Key}", key);
+ }
+ }
+
+ // 濡傛灉Redis涓嶅彲鐢紝灏濊瘯浠庡唴瀛樼紦瀛樿幏鍙�
+ if (value == null && _options.EnableL1Cache)
+ {
+ if (_memoryCache.TryGetValue(fullKey, out string? cached))
+ value = cached;
+ }
+
+ // 鏇存柊鍐呭瓨缂撳瓨锛堝鏋滄湁鍊硷級
+ if (value != null && _options.EnableL1Cache)
+ {
+ SetMemoryCache(fullKey, value, expireSeconds, false);
+ }
+
+ return value;
+ }
+
+ public T? GetAndRefresh<T>(string key, int expireSeconds) where T : class
+ {
+ var fullKey = BuildKey(key);
+ T? value = default;
+
+ // 浠嶳edis鑾峰彇鍊�
+ if (RedisAvailable)
+ {
+ try
+ {
+ var redisValue = _connectionManager.GetDatabase().StringGet(fullKey);
+ if (!redisValue.IsNullOrEmpty)
+ {
+ var json = redisValue.ToString();
+ value = _serializer.Deserialize<T>(json);
+ // 鍒锋柊Redis杩囨湡鏃堕棿
+ _connectionManager.GetDatabase().KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis GetAndRefresh<T>澶辫触, key={Key}", key);
+ }
+ }
+
+ // 濡傛灉Redis涓嶅彲鐢紝灏濊瘯浠庡唴瀛樼紦瀛樿幏鍙�
+ if (value == null && _options.EnableL1Cache)
+ {
+ if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
+ value = _serializer.Deserialize<T>(cached);
+ }
+
+ // 鏇存柊鍐呭瓨缂撳瓨锛堝鏋滄湁鍊硷級
+ if (value != null && _options.EnableL1Cache)
+ {
+ SetMemoryCache(fullKey, _serializer.Serialize(value), expireSeconds, false);
+ }
+
+ return value;
+ }
+
+ public bool RefreshExpire(string key, int expireSeconds)
+ {
+ var fullKey = BuildKey(key);
+ bool result = false;
+
+ // 鍒锋柊Redis杩囨湡鏃堕棿
+ if (RedisAvailable)
+ {
+ try
+ {
+ result = _connectionManager.GetDatabase().KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
+ }
+ catch { }
+ }
+
+ // 鏇存柊鍐呭瓨缂撳瓨杩囨湡鏃堕棿锛堥渶瑕侀噸鏂拌缃�兼潵鍒锋柊杩囨湡鏃堕棿锛�
+ if (_options.EnableL1Cache && _memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
+ {
+ SetMemoryCache(fullKey, cached, expireSeconds, false);
+ return true;
+ }
+
+ return result;
+ }
+
+ public bool ExpireIn(string key, int seconds)
+ {
+ return RefreshExpire(key, seconds);
+ }
+
+ public bool ExpireAt(string key, DateTime expireTime)
+ {
+ var seconds = (long)(expireTime - DateTime.Now).TotalSeconds;
+ if (seconds <= 0) return Remove(key);
+ return RefreshExpire(key, (int)seconds);
+ }
+
+ public long? GetExpire(string key)
+ {
+ if (RedisAvailable)
+ {
+ try
+ {
+ var ttl = _connectionManager.GetDatabase().KeyTimeToLive(BuildKey(key));
+ return ttl.HasValue ? (long)ttl.Value.TotalSeconds : null;
+ }
+ catch { }
+ }
+ return null; // MemoryCache涓嶆敮鎸乀TL鏌ヨ
+ }
+
+ public bool AddIfNotExists(string key, string value, int expireSeconds = -1)
+ {
+ return TryAdd(key, value, expireSeconds);
+ }
+
+ public bool AddIfNotExists<T>(string key, T value, int expireSeconds = -1) where T : class
+ {
+ return TryAdd(key, value, expireSeconds);
+ }
+
+ public string? GetAndSet(string key, string newValue, int expireSeconds = -1)
+ {
+ var fullKey = BuildKey(key);
+ string? oldValue = null;
+
+ // 浠嶳edis鑾峰彇鏃у��
+ if (RedisAvailable)
+ {
+ try
+ {
+ var value = _connectionManager.GetDatabase().StringGet(fullKey);
+ oldValue = value.IsNullOrEmpty ? null : value.ToString();
+ }
+ catch { }
+ }
+
+ // 濡傛灉Redis涓嶅彲鐢紝浠庡唴瀛樼紦瀛樿幏鍙�
+ if (oldValue == null && _options.EnableL1Cache)
+ {
+ _memoryCache.TryGetValue(fullKey, out oldValue);
+ }
+
+ // 鍐欏叆Redis
+ if (RedisAvailable)
+ {
+ try
+ {
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ _connectionManager.GetDatabase().StringSet(fullKey, newValue, expiry);
+ }
+ catch { }
+ }
+
+ // 鏇存柊鍐呭瓨缂撳瓨
+ if (_options.EnableL1Cache)
+ {
+ SetMemoryCache(fullKey, newValue, expireSeconds, false);
+ }
+
+ return oldValue;
+ }
+
+ public T? GetAndSet<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ var fullKey = BuildKey(key);
+ T? oldValue = default;
+ string? oldJson = null;
+
+ // 浠嶳edis鑾峰彇鏃у��
+ if (RedisAvailable)
+ {
+ try
+ {
+ var value = _connectionManager.GetDatabase().StringGet(fullKey);
+ if (!value.IsNullOrEmpty)
+ {
+ oldJson = value.ToString();
+ oldValue = _serializer.Deserialize<T>(oldJson);
+ }
+ }
+ catch { }
+ }
+
+ var newJson = _serializer.Serialize(newValue);
+
+ // 鍐欏叆Redis
+ if (RedisAvailable)
+ {
+ try
+ {
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ _connectionManager.GetDatabase().StringSet(fullKey, newJson, expiry);
+ }
+ catch { }
+ }
+
+ // 鏇存柊鍐呭瓨缂撳瓨
+ if (_options.EnableL1Cache)
+ {
+ SetMemoryCache(fullKey, newJson, expireSeconds, false);
+ }
+
+ return oldValue;
+ }
+
+ public long Increment(string key, long value = 1)
+ {
+ if (RedisAvailable)
+ {
+ try
+ {
+ return _connectionManager.GetDatabase().StringIncrement(BuildKey(key), value);
+ }
+ catch { }
+ }
+
+ // Fallback to memory
+ var current = long.TryParse(Get(key), out var v) ? v : 0;
+ var newValue = current + value;
+ Add(key, newValue.ToString());
+ return newValue;
+ }
+
+ public long Decrement(string key, long value = 1)
+ {
+ return Increment(key, -value);
+ }
+
+ public long Append(string key, string value)
+ {
+ var current = Get(key) ?? "";
+ var newValue = current + value;
+ Add(key, newValue);
+ return newValue.Length;
+ }
+
+ #endregion
+
public T? Get<T>(string key) where T : class
{
var fullKey = BuildKey(key);
+
+ // 濡傛灉绂佺敤浜哃1缂撳瓨锛岀洿鎺ユ煡Redis
+ if (!_options.EnableL1Cache)
+ {
+ if (!RedisAvailable) return default;
+ try
+ {
+ var value = _connectionManager.GetDatabase().StringGet(fullKey);
+ if (value.IsNullOrEmpty) return default;
+ return _serializer.Deserialize<T>(value!);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis Get<T>澶辫触, key={Key}", key);
+ return default;
+ }
+ }
+
+ // 姝e父鐨凩1+L2閫昏緫
if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
return _serializer.Deserialize<T>(cached);
@@ -134,6 +576,25 @@
public object? Get(Type type, string key)
{
var fullKey = BuildKey(key);
+
+ // 濡傛灉绂佺敤浜哃1缂撳瓨锛岀洿鎺ユ煡Redis
+ if (!_options.EnableL1Cache)
+ {
+ if (!RedisAvailable) return null;
+ try
+ {
+ var value = _connectionManager.GetDatabase().StringGet(fullKey);
+ if (value.IsNullOrEmpty) return null;
+ return _serializer.Deserialize(value!, type);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis Get(Type)澶辫触, key={Key}", key);
+ return null;
+ }
+ }
+
+ // 姝e父鐨凩1+L2閫昏緫
if (_memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
return _serializer.Deserialize(cached, type);
@@ -156,6 +617,24 @@
public string? Get(string key)
{
var fullKey = BuildKey(key);
+
+ // 濡傛灉绂佺敤浜哃1缂撳瓨锛岀洿鎺ユ煡Redis
+ if (!_options.EnableL1Cache)
+ {
+ if (!RedisAvailable) return null;
+ try
+ {
+ var value = _connectionManager.GetDatabase().StringGet(fullKey);
+ return value.IsNullOrEmpty ? null : value.ToString();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis Get澶辫触, key={Key}", key);
+ return null;
+ }
+ }
+
+ // 姝e父鐨凩1+L2閫昏緫
if (_memoryCache.TryGetValue(fullKey, out string? cached))
return cached;
@@ -229,6 +708,73 @@
return true;
}
+ public bool TryUpdateIfChanged(string key, string newValue, int expireSeconds = -1)
+ {
+ var existing = Get(key);
+ if (existing == null) return false;
+ if (existing == newValue) return false; // 鍊肩浉鍚岋紝涓嶆洿鏂�
+ Add(key, newValue, expireSeconds);
+ return true;
+ }
+
+ public bool TryUpdateIfChanged<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ var fullKey = BuildKey(key);
+
+ // 鎬绘槸浠嶳edis鑾峰彇褰撳墠瀹為檯鍊艰繘琛屾瘮杈冿紝纭繚鏁版嵁涓�鑷存��
+ string? existingJson = null;
+ if (RedisAvailable)
+ {
+ try
+ {
+ var value = _connectionManager.GetDatabase().StringGet(fullKey);
+ if (!value.IsNullOrEmpty)
+ existingJson = value.ToString();
+ }
+ catch { }
+ }
+
+ if (existingJson == null)
+ {
+ // Redis涓嶅彲鐢紝妫�鏌ュ唴瀛樼紦瀛�
+ if (_options.EnableL1Cache && _memoryCache.TryGetValue(fullKey, out string? cached) && cached != null)
+ existingJson = cached;
+ else
+ return false;
+ }
+
+ var newJson = _serializer.Serialize(newValue);
+ if (existingJson == newJson) return false; // JSON瀛楃涓茬浉鍚岋紝涓嶆洿鏂�
+
+ // 鍏堝啓鍏edis锛屾垚鍔熷悗鍐嶆洿鏂板唴瀛樼紦瀛�
+ if (RedisAvailable)
+ {
+ try
+ {
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ if (!_connectionManager.GetDatabase().StringSet(fullKey, newJson, expiry))
+ {
+ // Redis鍐欏叆澶辫触
+ _logger.LogWarning("Redis TryUpdateIfChanged鍐欏叆澶辫触, key={Key}", key);
+ return _options.FallbackToMemory && _options.EnableL1Cache;
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Redis TryUpdateIfChanged鍐欏叆澶辫触, key={Key}", key);
+ return _options.FallbackToMemory && _options.EnableL1Cache;
+ }
+ }
+
+ // Redis鍐欏叆鎴愬姛锛堟垨Redis涓嶅彲鐢ㄦ椂锛夛紝鏇存柊鍐呭瓨缂撳瓨
+ if (_options.EnableL1Cache)
+ {
+ SetMemoryCache(fullKey, newJson, expireSeconds, false);
+ }
+
+ return true;
+ }
+
public string GetOrAdd(string key, string value, int expireSeconds = -1)
{
var existing = Get(key);
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/RedisCacheService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/RedisCacheService.cs
index fe19c5f..f52aa42 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/RedisCacheService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Cache/RedisCacheService.cs
@@ -70,6 +70,209 @@
Db.KeyDelete(redisKeys);
}
+ #region 鍒犻櫎鎵╁睍鏂规硶
+
+ public string? RemoveAndGet(string key)
+ {
+ var fullKey = BuildKey(key);
+ var value = Db.StringGet(fullKey);
+ if (!value.IsNullOrEmpty)
+ {
+ Db.KeyDelete(fullKey);
+ return value.ToString();
+ }
+ return null;
+ }
+
+ public T? RemoveAndGet<T>(string key) where T : class
+ {
+ var fullKey = BuildKey(key);
+ var value = Db.StringGet(fullKey);
+ if (!value.IsNullOrEmpty)
+ {
+ Db.KeyDelete(fullKey);
+ return _serializer.Deserialize<T>(value!);
+ }
+ return default;
+ }
+
+ public int RemoveByPrefix(string prefix)
+ {
+ var fullPrefix = BuildKey(prefix);
+ var server = Db.Multiplexer.GetServer(Db.Multiplexer.GetEndPoints().First());
+ var keys = server.Keys(pattern: $"{fullPrefix}*").ToArray();
+ if (keys.Length == 0) return 0;
+ return (int)Db.KeyDelete(keys);
+ }
+
+ public int RemoveByPattern(string pattern)
+ {
+ var fullPattern = BuildKey(pattern).Replace("*", ""); // 淇濈暀鐢ㄦ埛浼犲叆鐨勯�氶厤绗�
+ var server = Db.Multiplexer.GetServer(Db.Multiplexer.GetEndPoints().First());
+ var keys = server.Keys(pattern: $"{_options.KeyPrefix}{pattern}").ToArray();
+ if (keys.Length == 0) return 0;
+ return (int)Db.KeyDelete(keys);
+ }
+
+ public int RemoveAll(IEnumerable<string> keys)
+ {
+ if (keys == null) return 0;
+ var redisKeys = keys.Select(k => (RedisKey)BuildKey(k)).ToArray();
+ return (int)Db.KeyDelete(redisKeys);
+ }
+
+ public int RemoveWhere(Func<string, bool> predicate)
+ {
+ if (predicate == null) return 0;
+ var server = Db.Multiplexer.GetServer(Db.Multiplexer.GetEndPoints().First());
+ var keys = server.Keys(pattern: $"{_options.KeyPrefix}*")
+ .Where(k => predicate(k.ToString().Replace(_options.KeyPrefix, "")))
+ .ToArray();
+ if (keys.Length == 0) return 0;
+ return (int)Db.KeyDelete(keys);
+ }
+
+ #endregion
+
+ #region 娣诲姞鍜屼慨鏀规墿灞曟柟娉�
+
+ public void AddAll(IDictionary<string, string> items, int expireSeconds = -1)
+ {
+ if (items == null) return;
+ var batch = Db.CreateBatch();
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ foreach (var item in items)
+ {
+ batch.StringSetAsync(BuildKey(item.Key), item.Value, expiry);
+ }
+ batch.Execute();
+ }
+
+ public void AddAllObjects(IDictionary<string, object> items, int expireSeconds = -1)
+ {
+ if (items == null) return;
+ var batch = Db.CreateBatch();
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ foreach (var item in items)
+ {
+ batch.StringSetAsync(BuildKey(item.Key), _serializer.Serialize(item.Value), expiry);
+ }
+ batch.Execute();
+ }
+
+ public bool Replace(string key, string newValue, int expireSeconds = -1)
+ {
+ return TryUpdate(key, newValue, expireSeconds);
+ }
+
+ public bool Replace<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ var fullKey = BuildKey(key);
+ if (!Db.KeyExists(fullKey)) return false;
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ return Db.StringSet(fullKey, _serializer.Serialize(newValue), expiry, When.Exists);
+ }
+
+ public string? GetAndRefresh(string key, int expireSeconds)
+ {
+ var fullKey = BuildKey(key);
+ var value = Db.StringGet(fullKey);
+ if (!value.IsNullOrEmpty)
+ {
+ Db.KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
+ return value.ToString();
+ }
+ return null;
+ }
+
+ public T? GetAndRefresh<T>(string key, int expireSeconds) where T : class
+ {
+ var fullKey = BuildKey(key);
+ var value = Db.StringGet(fullKey);
+ if (!value.IsNullOrEmpty)
+ {
+ Db.KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
+ return _serializer.Deserialize<T>(value!);
+ }
+ return default;
+ }
+
+ public bool RefreshExpire(string key, int expireSeconds)
+ {
+ return Db.KeyExpire(BuildKey(key), TimeSpan.FromSeconds(expireSeconds));
+ }
+
+ public bool ExpireIn(string key, int seconds)
+ {
+ return Db.KeyExpire(BuildKey(key), TimeSpan.FromSeconds(seconds));
+ }
+
+ public bool ExpireAt(string key, DateTime expireTime)
+ {
+ return Db.KeyExpire(BuildKey(key), expireTime);
+ }
+
+ public long? GetExpire(string key)
+ {
+ var ttl = Db.KeyTimeToLive(BuildKey(key));
+ return ttl.HasValue ? (long)ttl.Value.TotalSeconds : null;
+ }
+
+ public bool AddIfNotExists(string key, string value, int expireSeconds = -1)
+ {
+ var fullKey = BuildKey(key);
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ return Db.StringSet(fullKey, value, expiry, When.NotExists);
+ }
+
+ public bool AddIfNotExists<T>(string key, T value, int expireSeconds = -1) where T : class
+ {
+ var fullKey = BuildKey(key);
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ return Db.StringSet(fullKey, _serializer.Serialize(value), expiry, When.NotExists);
+ }
+
+ public string? GetAndSet(string key, string newValue, int expireSeconds = -1)
+ {
+ var fullKey = BuildKey(key);
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ var oldValue = Db.StringGetSet(fullKey, newValue);
+ if (expireSeconds > 0)
+ {
+ Db.KeyExpire(fullKey, expiry);
+ }
+ return oldValue.IsNullOrEmpty ? null : oldValue.ToString();
+ }
+
+ public T? GetAndSet<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ var fullKey = BuildKey(key);
+ var serialized = _serializer.Serialize(newValue);
+ var oldValue = Db.StringGetSet(fullKey, serialized);
+ if (expireSeconds > 0)
+ {
+ Db.KeyExpire(fullKey, TimeSpan.FromSeconds(expireSeconds));
+ }
+ return oldValue.IsNullOrEmpty ? default : _serializer.Deserialize<T>(oldValue!);
+ }
+
+ public long Increment(string key, long value = 1)
+ {
+ return Db.StringIncrement(BuildKey(key), value);
+ }
+
+ public long Decrement(string key, long value = 1)
+ {
+ return Db.StringDecrement(BuildKey(key), value);
+ }
+
+ public long Append(string key, string value)
+ {
+ return Db.StringAppend(BuildKey(key), value);
+ }
+
+ #endregion
+
public T? Get<T>(string key) where T : class
{
var value = Db.StringGet(BuildKey(key));
@@ -139,6 +342,29 @@
return Db.StringSet(fullKey, newValue, expiry, When.Exists);
}
+ public bool TryUpdateIfChanged(string key, string newValue, int expireSeconds = -1)
+ {
+ var fullKey = BuildKey(key);
+ var existing = Db.StringGet(fullKey);
+ if (existing.IsNullOrEmpty) return false;
+ if (existing.ToString() == newValue) return false; // 鍊肩浉鍚岋紝涓嶆洿鏂�
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ return Db.StringSet(fullKey, newValue, expiry, When.Exists);
+ }
+
+ public bool TryUpdateIfChanged<T>(string key, T newValue, int expireSeconds = -1) where T : class
+ {
+ var fullKey = BuildKey(key);
+ var existing = Db.StringGet(fullKey);
+ if (existing.IsNullOrEmpty) return false;
+
+ var newJson = _serializer.Serialize(newValue);
+ if (existing.ToString() == newJson) return false; // JSON瀛楃涓茬浉鍚岋紝涓嶆洿鏂�
+
+ var expiry = expireSeconds > 0 ? TimeSpan.FromSeconds(expireSeconds) : (TimeSpan?)null;
+ return Db.StringSet(fullKey, newJson, expiry, When.Exists);
+ }
+
public string GetOrAdd(string key, string value, int expireSeconds = -1)
{
var fullKey = BuildKey(key);
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Connection/RedisConnectionManager.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Connection/RedisConnectionManager.cs
index bc37709..a5e772f 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Connection/RedisConnectionManager.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Connection/RedisConnectionManager.cs
@@ -2,6 +2,7 @@
using Microsoft.Extensions.Options;
using StackExchange.Redis;
using System.Linq;
+using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_RedisService.Options;
namespace WIDESEAWCS_RedisService.Connection
@@ -21,8 +22,6 @@
{
// 寮哄埗璁块棶Value鏉ヨЕ鍙戣繛鎺ュ垱寤�
var connected = _connection.Value.IsConnected;
- _logger.LogDebug("IsConnected妫�鏌�: IsValueCreated={IsValueCreated}, IsConnected={Connected}",
- _connection.IsValueCreated, connected);
return connected;
}
catch (Exception ex)
@@ -37,22 +36,17 @@
{
_options = options.Value;
_logger = logger;
- _logger.LogInformation("RedisConnectionManager鏋勯�犲紑濮�, ConnectionString={ConnectionString}, Enabled={Enabled}",
- _options.ConnectionString, _options.Enabled);
_connection = new Lazy<ConnectionMultiplexer>(CreateConnection);
- _logger.LogInformation("RedisConnectionManager鏋勯�犲畬鎴�, Lazy宸插垱寤�");
}
private ConnectionMultiplexer CreateConnection()
{
try
{
- _logger.LogInformation("寮�濮嬪垱寤篟edis杩炴帴, ConnectionString={ConnectionString}", _options.ConnectionString);
var configOptions = ConfigurationOptions.Parse(_options.ConnectionString);
configOptions.AbortOnConnectFail = false;
configOptions.ConnectRetry = _options.ConnectRetry;
configOptions.DefaultDatabase = _options.DefaultDatabase;
- _logger.LogInformation("ConfigurationOptions瑙f瀽瀹屾垚, EndPoints={EndPoints}", string.Join(",", configOptions.EndPoints.Select(e => e.ToString())));
if (_options.EnableSentinel && _options.SentinelEndpoints.Count > 0)
{
@@ -65,16 +59,16 @@
var connection = ConnectionMultiplexer.Connect(configOptions);
connection.ConnectionFailed += (_, e) =>
- _logger.LogError("Redis杩炴帴澶辫触: {FailureType}", e.FailureType);
+ ConsoleHelper.WriteErrorLine($"Redis杩炴帴澶辫触: {e.FailureType}");
connection.ConnectionRestored += (_, e) =>
- _logger.LogInformation("Redis杩炴帴鎭㈠: {EndPoint}", e.EndPoint);
+ ConsoleHelper.WriteSuccessLine($"Redis杩炴帴鎭㈠: {e.EndPoint}");
- _logger.LogInformation("Redis杩炴帴鎴愬姛: {EndPoints}", string.Join(",", configOptions.EndPoints));
+ ConsoleHelper.WriteSuccessLine($"Redis杩炴帴鎴愬姛: {string.Join(",", configOptions.EndPoints)}");
return connection;
}
catch (Exception ex)
{
- _logger.LogError(ex, "Redis杩炴帴鍒涘缓澶辫触");
+ ConsoleHelper.WriteErrorLine($"Redis杩炴帴鍒涘缓澶辫触:{ex.Message}");
throw;
}
}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Extensions/RedisServiceSetup.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Extensions/RedisServiceSetup.cs
index bd5d40a..ac3d377 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Extensions/RedisServiceSetup.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Extensions/RedisServiceSetup.cs
@@ -71,6 +71,12 @@
services.AddSingleton<ISessionStorage, RedisSessionStorage>();
services.AddSingleton<IRedisMonitorService, RedisMonitorService>();
+ // 鍚庡彴鏈嶅姟锛堜粎鍦ㄥ惎鐢↙1缂撳瓨鏃舵敞鍐岋級
+ if (options.EnableL1Cache)
+ {
+ services.AddHostedService<CacheSyncBackgroundService>();
+ }
+
return services;
}
}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Options/RedisOptions.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Options/RedisOptions.cs
index 8b8a824..0bfb5a8 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Options/RedisOptions.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_RedisService/Options/RedisOptions.cs
@@ -26,6 +26,26 @@
public string KeyPrefix { get; set; } = "wcs:";
+ /// <summary>
+ /// 鏄惁鍚敤L1鍐呭瓨缂撳瓨灞傘�傜鐢ㄥ悗鍙娇鐢≧edis锛岄�傜敤浜庨渶瑕佸閮ㄤ慨鏀筊edis鏁版嵁鐨勫満鏅�
+ /// </summary>
+ public bool EnableL1Cache { get; set; } = true;
+
+ /// <summary>
+ /// 鏄惁鍚敤Redis鍒板唴瀛樼紦瀛樼殑鑷姩鍚屾
+ /// </summary>
+ public bool EnableAutoSync { get; set; } = true;
+
+ /// <summary>
+ /// 鑷姩鍚屾闂撮殧鏃堕棿锛堢锛夛紝榛樿30绉�
+ /// </summary>
+ public int SyncIntervalSeconds { get; set; } = 30;
+
+ /// <summary>
+ /// 鍚屾鏃跺崟娆℃壒閲忚幏鍙栫殑Redis key鏁伴噺涓婇檺锛岄槻姝竴娆℃壂鎻忚繃澶歬ey
+ /// </summary>
+ public int SyncBatchSize { get; set; } = 1000;
+
public MonitoringOptions Monitoring { get; set; } = new();
public EvictionOptions Eviction { get; set; } = new();
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
index 795321d..acb6e73 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server.sln
@@ -1,7 +1,7 @@
锘�
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.9.34728.123
+# Visual Studio Version 18
+VisualStudioVersion = 18.2.11415.280 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WIDESEAWCS_Server", "WIDESEAWCS_Server\WIDESEAWCS_Server.csproj", "{487FA45B-EA1A-4ACA-BB5B-0F6708F462C0}"
EndProject
@@ -65,6 +65,8 @@
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WIDESEAWCS_BasicInfoService", "WIDESEAWCS_BasicInfoService\WIDESEAWCS_BasicInfoService.csproj", "{FFAB2C76-1C9E-4006-95C8-A0B2AA53139D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WIDESEAWCS_RedisService", "WIDESEAWCS_RedisService\WIDESEAWCS_RedisService.csproj", "{F9886971-C3B2-4334-B014-D5109F2041DE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WIDESEAWCS_Tests", "WIDESEAWCS_Tests\WIDESEAWCS_Tests.csproj", "{D4D17AAD-CB14-AF78-5BD1-F16380EBE911}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -328,6 +330,18 @@
{F9886971-C3B2-4334-B014-D5109F2041DE}.Release|x64.Build.0 = Release|Any CPU
{F9886971-C3B2-4334-B014-D5109F2041DE}.Release|x86.ActiveCfg = Release|Any CPU
{F9886971-C3B2-4334-B014-D5109F2041DE}.Release|x86.Build.0 = Release|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Debug|x64.Build.0 = Debug|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Debug|x86.Build.0 = Debug|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Release|x64.ActiveCfg = Release|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Release|x64.Build.0 = Release|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Release|x86.ActiveCfg = Release|Any CPU
+ {D4D17AAD-CB14-AF78-5BD1-F16380EBE911}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git "a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Redis\344\275\277\347\224\250\346\241\210\344\276\213.md" "b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Redis\344\275\277\347\224\250\346\241\210\344\276\213.md"
index 8f86a1b..9ebca13 100644
--- "a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Redis\344\275\277\347\224\250\346\241\210\344\276\213.md"
+++ "b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Redis\344\275\277\347\224\250\346\241\210\344\276\213.md"
@@ -1,6 +1,6 @@
# Redis 鏈嶅姟浣跨敤妗堜緥
-## 1. 缂撳瓨锛圛CacheService锛�
+## 1. 缂撳瓨锛圛CacheService锛�- 鍩虹鎿嶄綔
閫氳繃鏋勯�犲嚱鏁版敞鍏� `ICacheService`锛孒ybridCacheService 鑷姩瀹炵幇 L1(鍐呭瓨) + L2(Redis) 鍙屽眰缂撳瓨銆�
@@ -38,7 +38,211 @@
}
```
-## 2. 鍒嗗竷寮忛攣锛圛DistributedLockService锛�
+## 2. 缂撳瓨锛圛CacheService锛�- 鎵╁睍鍒犻櫎鏂规硶
+
+```csharp
+public class CacheDeleteDemo
+{
+ private readonly ICacheService _cache;
+
+ // 鍒犻櫎骞惰幏鍙栧��
+ public string? RemoveAndGet(string key)
+ {
+ return _cache.RemoveAndGet(key); // 杩斿洖琚垹闄ょ殑鍊�
+ }
+
+ // 鎸夊墠缂�鍒犻櫎鎵�鏈夊尮閰嶇殑key
+ public int ClearUserCache()
+ {
+ return _cache.RemoveByPrefix("user:"); // 鍒犻櫎鎵�鏈� user: 寮�澶寸殑key
+ }
+
+ // 鎸夋ā寮忓垹闄わ紙鏀寔閫氶厤绗︼級
+ public int ClearSessionCache()
+ {
+ return _cache.RemoveByPattern("session:123:*"); // 鍒犻櫎 session:123: 寮�澶寸殑鎵�鏈塳ey
+ }
+
+ // 鎵归噺鍒犻櫎骞惰繑鍥炴垚鍔熸暟閲�
+ public int RemoveMultiple()
+ {
+ var keys = new[] { "key1", "key2", "key3" };
+ return _cache.RemoveAll(keys); // 杩斿洖瀹為檯鍒犻櫎鐨勬暟閲�
+ }
+
+ // 鏉′欢鍒犻櫎
+ public int RemoveTempCache()
+ {
+ return _cache.RemoveWhere(key => key.Contains("temp")); // 鍒犻櫎鍖呭惈"temp"鐨刱ey
+ }
+}
+```
+
+## 3. 缂撳瓨锛圛CacheService锛�- 娣诲姞鍜屼慨鏀规墿灞曟柟娉�
+
+```csharp
+public class CacheAdvancedDemo
+{
+ private readonly ICacheService _cache;
+
+ // 鎵归噺娣诲姞
+ public void AddMultiple()
+ {
+ var items = new Dictionary<string, string>
+ {
+ { "user:1", "寮犱笁" },
+ { "user:2", "鏉庡洓" },
+ { "user:3", "鐜嬩簲" }
+ };
+ _cache.AddAll(items, 300); // 鎵归噺娣诲姞锛�300绉掕繃鏈�
+ }
+
+ // 鎵归噺娣诲姞瀵硅薄
+ public void AddMultipleObjects()
+ {
+ var items = new Dictionary<string, object>
+ {
+ { "order:1", new { Id = 1, Amount = 100 } },
+ { "order:2", new { Id = 2, Amount = 200 } }
+ };
+ _cache.AddAllObjects(items, 600);
+ }
+
+ // 鏇挎崲锛堜粎瀛樺湪鏃舵浛鎹級
+ public bool ReplaceExisting()
+ {
+ return _cache.Replace("user:1", "鏂扮敤鎴峰悕"); // key涓嶅瓨鍦ㄨ繑鍥瀎alse
+ }
+
+ // 鑾峰彇骞跺埛鏂拌繃鏈熸椂闂�
+ public string? GetAndRefresh(string key)
+ {
+ return _cache.GetAndRefresh(key, 1800); // 鑾峰彇鍊煎苟寤堕暱30鍒嗛挓
+ }
+
+ // 鍒锋柊杩囨湡鏃堕棿
+ public bool RefreshExpire(string key)
+ {
+ return _cache.RefreshExpire(key, 3600); // 鍒锋柊涓�1灏忔椂鍚庤繃鏈�
+ }
+
+ // 璁剧疆N绉掑悗杩囨湡
+ public bool SetExpireIn(string key, int seconds)
+ {
+ return _cache.ExpireIn(key, seconds);
+ }
+
+ // 璁剧疆鍦ㄦ寚瀹氭椂闂寸偣杩囨湡
+ public bool SetExpireAt(string key, DateTime expireTime)
+ {
+ return _cache.ExpireAt(key, expireTime);
+ }
+
+ // 鑾峰彇鍓╀綑杩囨湡鏃堕棿
+ public long? GetTTL(string key)
+ {
+ return _cache.GetExpire(key); // 杩斿洖鍓╀綑绉掓暟锛宯ull琛ㄧず姘镐笉杩囨湡鎴杒ey涓嶅瓨鍦�
+ }
+}
+```
+
+## 4. 缂撳瓨锛圛CacheService锛�- 鍘熷瓙鎿嶄綔鏂规硶
+
+```csharp
+public class AtomicOperationDemo
+{
+ private readonly ICacheService _cache;
+
+ // 鍘熷瓙娣诲姞锛堜粎涓嶅瓨鍦ㄦ椂娣诲姞锛�- 鍒嗗竷寮忛攣鍦烘櫙
+ public bool AcquireLock(string lockKey, string lockValue)
+ {
+ return _cache.AddIfNotExists(lockKey, lockValue, 30); // 30绉掕嚜鍔ㄨ繃鏈�
+ }
+
+ // 鑾峰彇鏃у�煎苟璁剧疆鏂板��
+ public string? GetAndSet(string key, string newValue)
+ {
+ return _cache.GetAndSet(key, newValue); // 杩斿洖鏃у�硷紝璁剧疆鏂板��
+ }
+
+ // 鑷璁℃暟鍣�
+ public long IncrementCounter(string key)
+ {
+ return _cache.Increment(key); // 鑷1锛岃繑鍥炴柊鍊�
+ }
+
+ // 鑷鎸囧畾鍊�
+ public long IncrementBy(string key, long value)
+ {
+ return _cache.Increment(key, value); // 鑷value
+ }
+
+ // 鑷噺璁℃暟鍣�
+ public long DecrementCounter(string key)
+ {
+ return _cache.Decrement(key); // 鑷噺1
+ }
+
+ // 杩藉姞鍐呭
+ public long AppendContent(string key, string content)
+ {
+ return _cache.Append(key, content); // 杩斿洖杩藉姞鍚庣殑瀛楃涓查暱搴�
+ }
+}
+```
+
+## 5. 缂撳瓨锛圛CacheService锛�- ConcurrentDictionary椋庢牸鏂规硶
+
+```csharp
+public class ConcurrentStyleDemo
+{
+ private readonly ICacheService _cache;
+
+ // 灏濊瘯娣诲姞锛堜粎涓嶅瓨鍦ㄦ椂娣诲姞锛�
+ public bool TryAdd(string key, string value)
+ {
+ return _cache.TryAdd(key, value, 60); // key瀛樺湪杩斿洖false
+ }
+
+ // 灏濊瘯鑾峰彇
+ public bool TryGet(string key, out string? value)
+ {
+ return _cache.TryGetValue(key, out value);
+ }
+
+ // 灏濊瘯绉婚櫎骞惰繑鍥炲��
+ public bool TryRemove(string key, out string? value)
+ {
+ return _cache.TryRemove(key, out value);
+ }
+
+ // 灏濊瘯鏇存柊锛堜粎瀛樺湪鏃舵洿鏂帮級
+ public bool TryUpdate(string key, string newValue)
+ {
+ return _cache.TryUpdate(key, newValue, 60);
+ }
+
+ // 鍊兼敼鍙樻椂鏇存柊锛堥伩鍏嶆棤鏁堝啓鍏ワ級
+ public bool TryUpdateIfChanged(string key, string newValue)
+ {
+ return _cache.TryUpdateIfChanged(key, newValue, 60); // 鍊肩浉鍚岃繑鍥瀎alse
+ }
+
+ // 鑾峰彇鎴栨坊鍔�
+ public string GetOrAdd(string key)
+ {
+ return _cache.GetOrAdd(key, "榛樿鍊�", 60);
+ }
+
+ // 鑾峰彇鎴栨坊鍔狅紙宸ュ巶鏂规硶锛�
+ public T GetOrAdd<T>(string key, Func<string, T> factory) where T : class
+ {
+ return _cache.GetOrAdd(key, factory, 60);
+ }
+}
+```
+
+## 6. 鍒嗗竷寮忛攣锛圛DistributedLockService锛�
```csharp
public class OrderService
@@ -80,7 +284,7 @@
}
```
-## 3. 璁℃暟鍣紙ICounterService锛�
+## 7. 璁℃暟鍣紙ICounterService锛�
```csharp
public class StatisticsService
@@ -107,7 +311,7 @@
}
```
-## 4. 鍙戝竷/璁㈤槄锛圛MessageQueueService锛�
+## 8. 鍙戝竷/璁㈤槄锛圛MessageQueueService锛�
```csharp
public class NotificationService
@@ -143,7 +347,7 @@
}
```
-## 5. 闄愭祦锛圛RateLimitingService锛�
+## 9. 闄愭祦锛圛RateLimitingService锛�
```csharp
public class ApiController
@@ -175,7 +379,7 @@
}
```
-## 6. 鍒嗗竷寮廔D鐢熸垚鍣紙IDistributedIdGenerator锛�
+## 10. 鍒嗗竷寮廔D鐢熸垚鍣紙IDistributedIdGenerator锛�
```csharp
public class TaskService
@@ -203,7 +407,7 @@
}
```
-## 7. 鎺掕姒滐紙ILeaderboardService锛�
+## 11. 鎺掕姒滐紙ILeaderboardService锛�
```csharp
public class LeaderboardDemo
@@ -231,7 +435,7 @@
}
```
-## 8. 瀵硅薄瀛樺偍锛圛ObjectStorageService锛�
+## 12. 瀵硅薄瀛樺偍锛圛ObjectStorageService锛�
```csharp
public class DeviceService
@@ -259,7 +463,7 @@
}
```
-## 9. 閰嶇疆涓績锛圛ConfigurationCenterService锛�
+## 13. 閰嶇疆涓績锛圛ConfigurationCenterService锛�
```csharp
public class ConfigDemo
@@ -290,7 +494,7 @@
}
```
-## 10. 鐩戞帶锛圛RedisMonitorService锛�
+## 14. 鐩戞帶锛圛RedisMonitorService锛�
```csharp
public class MonitorDemo
@@ -318,7 +522,7 @@
}
```
-## 11. Session瀛樺偍锛圛SessionStorage锛�
+## 15. Session瀛樺偍锛圛SessionStorage锛�
```csharp
public class SessionDemo
@@ -350,7 +554,7 @@
}
```
-## 12. 甯冮殕杩囨护鍣紙IBloomFilterService锛�
+## 16. 甯冮殕杩囨护鍣紙IBloomFilterService锛�
```csharp
public class BloomFilterDemo
@@ -378,3 +582,45 @@
}
}
```
+
+## 17. 缂撳瓨鑷姩鍚屾锛圕acheSyncBackgroundService锛�
+
+Redis鍒板唴瀛樼紦瀛樼殑鑷姩鍚屾鍚庡彴鏈嶅姟锛岃В鍐矻1+L2娣峰悎缂撳瓨涓閮ㄤ慨鏀筊edis鏁版嵁瀵艰嚧鍐呭瓨缂撳瓨涓嶄竴鑷寸殑闂銆�
+
+### 閰嶇疆璇存槑
+
+鍦� `appsettings.json` 涓厤缃細
+
+```json
+{
+ "RedisConfig": {
+ "EnableL1Cache": true, // 鍚敤L1鍐呭瓨缂撳瓨
+ "EnableAutoSync": true, // 鍚敤鑷姩鍚屾
+ "SyncIntervalSeconds": 30, // 鍚屾闂撮殧锛�30绉�
+ "SyncBatchSize": 1000 // 鍗曟鎵归噺鑾峰彇key鏁伴噺涓婇檺
+ }
+}
+```
+
+### 宸ヤ綔鍘熺悊
+
+1. **鍚姩鏃跺叏閲忓悓姝�**锛氭湇鍔″惎鍔ㄥ悗绔嬪嵆鎵ц涓�娆″叏閲忓悓姝�
+2. **瀹氭湡澧為噺鍚屾**锛氭寜鐓ч厤缃殑闂撮殧锛堥粯璁�30绉掞級瀹氭湡鎵ц鍚屾
+3. **鍙屽悜鍚屾**锛�
+ - 灏哛edis涓殑鏁版嵁鏇存柊鍒板唴瀛樼紦瀛�
+ - 娓呯悊鍐呭瓨缂撳瓨涓笉瀛樺湪浜嶳edis鐨刱ey
+4. **鏅鸿兘TTL鍚屾**锛氬悓姝ユ椂浼氫繚鐣橰edis涓缃殑杩囨湡鏃堕棿
+
+### 浣跨敤鍦烘櫙
+
+- **澶氱郴缁熷叡浜玆edis**锛氬綋澶氫釜WCS瀹炰緥鍏变韩鍚屼竴涓猂edis锛屼竴涓疄渚嬩慨鏀规暟鎹悗锛屽叾浠栧疄渚嬭兘鑷姩鍚屾
+- **澶栭儴淇敼Redis**锛氶�氳繃Redis CLI鎴栧叾浠栧伐鍏蜂慨鏀规暟鎹悗锛屽簲鐢ㄨ兘鑷姩鑾峰彇鏈�鏂板��
+- **缂撳瓨涓�鑷存�т繚闅�**锛氶伩鍏嶅惎鐢↙1缂撳瓨鍚庯紝鍐呭瓨缂撳瓨鍜孯edis鏁版嵁涓嶄竴鑷寸殑闂
+
+### 娉ㄦ剰浜嬮」
+
+- 浠呭湪 `EnableL1Cache = true` 涓� `EnableAutoSync = true` 鏃惰繍琛�
+- IMemoryCache涓嶆敮鎸佹灇涓撅紝鍥犳鍙兘娓呯悊宸茶窡韪殑key
+- 鍚屾闂撮殧寤鸿璁剧疆涓�30-60绉掞紝杩囩煭浼氬奖鍝嶆�ц兘
+- 瀵逛簬瑕佹眰寮轰竴鑷存�х殑鍦烘櫙锛屽缓璁洿鎺ョ鐢↙1缂撳瓨锛坄EnableL1Cache: false`锛�
+```
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
index d3da09b..7f04fdd 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/appsettings.json
@@ -75,6 +75,10 @@
"SerializerType": "Newtonsoft", //搴忓垪鍖栨柟寮忥細Newtonsoft
"FallbackToMemory": true, //Redis涓嶅彲鐢ㄦ椂鏄惁闄嶇骇鍒板唴瀛樼紦瀛�
"KeyPrefix": "wcs:", //鍏ㄥ眬Key鍓嶇紑锛岀敤浜庨殧绂讳笉鍚岀郴缁熺殑鏁版嵁
+ "EnableL1Cache": true, //鏄惁鍚敤L1鍐呭瓨缂撳瓨灞傘�傜鐢ㄥ悗鍙娇鐢≧edis锛岄�傜敤浜庨渶瑕佸閮ㄤ慨鏀筊edis鏁版嵁鐨勫満鏅�
+ "EnableAutoSync": true, //鏄惁鍚敤Redis鍒板唴瀛樼紦瀛樼殑鑷姩鍚屾
+ "SyncIntervalSeconds": 30, //鑷姩鍚屾闂撮殧鏃堕棿锛堢锛夛紝寤鸿30-60绉�
+ "SyncBatchSize": 1000, //鍚屾鏃跺崟娆℃壒閲忚幏鍙栫殑Redis key鏁伴噺涓婇檺
"Monitoring": {
"Enabled": false, //鏄惁鍚敤鐩戞帶
"SlowLogThresholdMs": 100, //鎱㈡煡璇㈤槇鍊硷紙姣锛�
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskExecuteDetailService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskExecuteDetailService.cs
index 35ef6b0..75feddd 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskExecuteDetailService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskExecuteDetailService.cs
@@ -99,7 +99,7 @@
}
catch (Exception ex)
{
-
+ Console.WriteLine($"AddTaskExecuteDetail 娣诲姞浠诲姟鎵ц璇︽儏澶辫触: {ex.Message}");
}
}
@@ -220,7 +220,7 @@
}
catch (Exception ex)
{
-
+ content = WebResponseContent.Instance.Error($"鑾峰彇浠诲姟璇︽儏澶辫触: {ex.Message}");
}
return content;
}
@@ -244,7 +244,7 @@
}
catch (Exception ex)
{
-
+ content = WebResponseContent.Instance.Error($"鑾峰彇浠诲姟璇︽儏澶辫触: {ex.Message}");
}
return content;
}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
index 7ae869a..288f717 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_TaskInfoService/TaskService.cs
@@ -179,6 +179,9 @@
/// <returns></returns>
public Dt_Task QueryExecutingConveyorLineTask(int taskNum, string nextAddress)
{
+ if (string.IsNullOrEmpty(nextAddress))
+ throw new ArgumentNullException(nameof(nextAddress), "涓嬩竴鍦板潃涓嶈兘涓虹┖");
+
return BaseDal.QueryFirst(x => x.TaskNum == taskNum && x.NextAddress == nextAddress && (x.TaskState == (int)TaskInStatusEnum.Line_InExecuting || x.TaskState == (int)TaskOutStatusEnum.Line_OutExecuting), TaskOrderBy);
}
@@ -190,6 +193,9 @@
/// <returns></returns>
public Dt_Task QueryCompletedConveyorLineTask(int taskNum, string currentAddress)
{
+ if (string.IsNullOrEmpty(currentAddress))
+ throw new ArgumentNullException(nameof(currentAddress), "褰撳墠鍦板潃涓嶈兘涓虹┖");
+
return BaseDal.QueryFirst(x => x.TaskNum == taskNum && x.CurrentAddress == currentAddress && (x.TaskState == (int)TaskInStatusEnum.Line_InFinish || x.TaskState == (int)TaskOutStatusEnum.Line_OutFinish), TaskOrderBy);
}
@@ -273,6 +279,9 @@
WebResponseContent content = new WebResponseContent();
try
{
+ if (string.IsNullOrEmpty(message))
+ throw new ArgumentNullException(nameof(message), "寮傚父淇℃伅涓嶈兘涓虹┖");
+
Dt_Task task = BaseDal.QueryFirst(x => x.TaskNum == taskNum);
if (task == null) return WebResponseContent.Instance.Error($"鏈壘鍒拌浠诲姟淇℃伅,浠诲姟鍙�:銆恵taskNum}銆�");
if (task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
@@ -371,7 +380,8 @@
{
PalletCode = task.PalletCode,
};
- var result = _httpClientHelper.Post<WebResponseContent>("WMS", taskDto.ToJson(), nameof(ConfigKey.GetTasksLocation));
+
+ var result = _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.GetTasksLocation), taskDto.ToJson(), nameof(ConfigKey.GetTasksLocation));
if (!result.IsSuccess && !result.Data.Status)
{
return WebResponseContent.Instance.Error($"璋冪敤WMS鎺ュ彛鑾峰彇浠诲姟鐩爣鍦板潃澶辫触,浠诲姟鍙�:銆恵task.TaskNum}銆�,閿欒淇℃伅:銆恵content.Message}銆�");
@@ -434,6 +444,9 @@
{
try
{
+ if (string.IsNullOrEmpty(currentAddress))
+ throw new ArgumentNullException(nameof(currentAddress), "褰撳墠鍦板潃涓嶈兘涓虹┖");
+
Dt_Task task = BaseDal.QueryFirst(x => x.TaskNum == taskNum && x.CurrentAddress == currentAddress);
if (task == null) throw new Exception($"鏈壘鍒拌浠诲姟淇℃伅,浠诲姟鍙�:銆恵taskNum}銆�");
@@ -455,6 +468,7 @@
}
catch (Exception ex)
{
+ Console.WriteLine($"UpdatePosition 鏇存柊浠诲姟浣嶇疆澶辫触,浠诲姟鍙�:銆恵taskNum}銆�,閿欒淇℃伅:銆恵ex.Message}銆�");
}
return null;
}
@@ -487,7 +501,7 @@
_taskExecuteDetailService.AddTaskExecuteDetail(task.TaskId, $"鍫嗗灈鏈哄嚭搴撳畬鎴�");
- var result = _httpClientHelper.Post<WebResponseContent>("WMS", (new StockInfoDTO() { PalletCode = task.PalletCode, TaskNum = task.TaskNum }).ToJson());
+ var result = _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.OutboundFinishTaskAsync), (new StockInfoDTO() { PalletCode = task.PalletCode, TaskNum = task.TaskNum }).ToJson());
if (result.IsSuccess && result.Data.Status)
{
return content.Error($"閫氱煡WMS绯荤粺鍫嗗灈鏈哄嚭搴撳畬鎴愭垚鍔�,浠诲姟鍙�:銆恵task.TaskNum}銆�,鎵樼洏鍙�:銆恵task.PalletCode}銆�");
@@ -508,7 +522,7 @@
_taskExecuteDetailService.AddTaskExecuteDetail(task.TaskId, $"鍫嗗灈鏈哄叆搴撳畬鎴�");
- var result = _httpClientHelper.Post<WebResponseContent>("WMS", (new CreateTaskDto()
+ var result = _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.InboundFinishTaskAsync), (new CreateTaskDto()
{
PalletCode = task.PalletCode,
}).ToJson());
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
index f8219c9..729e360 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
@@ -81,6 +81,12 @@
{
ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer<ConveyorLineTaskCommandNew>(childDeviceCode);
+
+ if (command == null)
+ {
+ return;
+ }
+
#region 妫�鏌ョ壒瀹氫綅缃槸鍚︽湁鎵樼洏
var checkPalletPositions = App.Configuration.GetSection("CheckPalletPositions")
@@ -112,10 +118,7 @@
#endregion
- if (command == null || command.PLC_STB != 1)
- {
- return;
- }
+ if (command.PLC_STB != 1) return;//PLC_STB=1鏃舵墠澶勭悊浠诲姟
if (command.Barcode.IsNullOrEmpty())
{
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
index 497a7ed..bd3d436 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -1,22 +1,22 @@
锘縰sing HslCommunication;
using Newtonsoft.Json;
+using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup;
using Quartz;
-using System.Collections.Concurrent;
+using System.Net;
using System.Net.Sockets;
-using System.Text.Json;
+using System.Threading.Tasks;
+using WIDESEA_Core;
+using WIDESEAWCS_Common;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Caches;
using WIDESEAWCS_Core.Helper;
-using WIDESEAWCS_Core.Http;
using WIDESEAWCS_DTO.Stock;
using WIDESEAWCS_DTO.TaskInfo;
-using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
-using WIDESEAWCS_QuartzJob.Service;
using WIDESEAWCS_Tasks.SocketServer;
namespace WIDESEAWCS_Tasks
@@ -27,22 +27,25 @@
private const int MaxTaskTotalNum = 48;
private readonly TcpSocketServer _TcpSocket;
+
//private static readonly ConcurrentDictionary<string, RobotSocketState> _socketStates = new();
private static int _eventSubscribedFlag;
private readonly ITaskService _taskService;
private readonly IRobotTaskService _robotTaskService;
private readonly ICacheService _cache;
+ private readonly HttpClientHelper _httpClientHelper;
private static IRobotTaskService _latestRobotTaskService = null!;
private static ITaskService _latestTaskService = null!;
- public RobotJob(TcpSocketServer TcpSocket, IRobotTaskService RobottaskService, ITaskService TaskService, ICacheService cache)
+ public RobotJob(TcpSocketServer TcpSocket, IRobotTaskService RobottaskService, ITaskService TaskService, ICacheService cache, HttpClientHelper httpClientHelper)
{
_TcpSocket = TcpSocket;
_robotTaskService = RobottaskService;
_taskService = TaskService;
_cache = cache;
+ _httpClientHelper = httpClientHelper;
_latestRobotTaskService = RobottaskService;
_latestTaskService = TaskService;
@@ -60,7 +63,7 @@
string ipAddress = robotCrane.IPAddress;
// 鑾峰彇鎴栧垱寤虹姸鎬�
- RobotSocketState state = _cache.GetOrAdd(ipAddress, _ => new RobotSocketState
+ RobotSocketState state = _cache.GetOrAdd($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress}", _ => new RobotSocketState
{
IPAddress = ipAddress,
RobotCrane = robotCrane
@@ -71,7 +74,6 @@
try
{
-
// 妫�鏌ユ槸鍚︽湁璇ュ鎴风杩炴帴
var clientIds = _TcpSocket.GetClientIds();
if (!clientIds.Contains(ipAddress))
@@ -97,6 +99,9 @@
Console.WriteLine($"HandleClientAsync error: {t.Exception?.GetBaseException().Message}");
}, TaskContinuationOptions.OnlyOnFaulted);
state.IsEventSubscribed = true;
+
+ // 鏇存柊缂撳瓨涓殑鐘舵��
+ _cache.TryUpdateIfChanged($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress}", state);
}
}
@@ -104,16 +109,12 @@
Dt_RobotTask? task = GetTask(robotCrane);
if (task != null)
{
- state.IsSplitPallet = task.RobotTaskType == RobotTaskTypeEnum.SplitPallet.GetHashCode();
- state.IsGroupPallet = task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode() || task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
- state.CurrentTask = task;
if (task.RobotTaskTotalNum <= MaxTaskTotalNum)
{
// 澶勭悊姝e湪鎵ц鐨勪换鍔�
if (state.RobotRunMode == 2 && state.RobotControlMode == 1 && state.OperStatus != "Running")
{
- await Task.Delay(1000);
- if (state.CurrentAction == "PickFinished" && state.RobotArmObject == 1 && task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode())
+ if (state.CurrentAction == "PickFinished" && state.RobotArmObject == 1 && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
{
string taskString = $"Putbattery,{task.RobotTargetAddress}";
bool result = await _TcpSocket.SendToClientAsync(ipAddress, taskString);
@@ -123,33 +124,42 @@
await _robotTaskService.UpdateRobotTaskAsync(task);
}
}
- else if (state.CurrentAction == "PutFinished" && state.RobotArmObject == 0 && task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode())
+ else if (state.CurrentAction == "PutFinished" && state.RobotArmObject == 0 && task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode())
{
task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
await _robotTaskService.UpdateRobotTaskAsync(task);
}
else if (state.OperStatus == "Homed" && state.RobotArmObject == 0 && task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode())
{
- // TODO 璇诲彇绾夸綋鐢垫睜鏉$爜锛屽彂閫佸彇鐢垫睜鎸囦护
// 闅忔満鐢熸垚涓ゅぉ鎵樼洏鏉$爜瀛樻斁鍒颁袱涓彉閲忛噷闈�
// 瀹氫箟鍓嶇紑锛堜緥濡傦細TRAY浠h〃鎵樼洏锛�
- string prefix = "TRAY";
-
- // 鐢熸垚涓や釜鎵樼洏鏉$爜
- string trayBarcode1 = GenerateTrayBarcode(state, prefix);
- string trayBarcode2 = GenerateTrayBarcode(state, prefix);
- if (!trayBarcode1.IsNullOrEmpty() && !trayBarcode2.IsNullOrEmpty())
+ // 缁勭洏璇诲彇绾夸綋鏉$爜
+ if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
{
- string taskString = $"Pickbattery,{task.RobotSourceAddress}";
- // 鍙戦�佷换鍔℃寚浠�
- bool result = await _TcpSocket.SendToClientAsync(ipAddress, taskString);
- if (result)
+ string prefix = "TRAY";
+
+ // 鐢熸垚涓や釜鎵樼洏鏉$爜
+ string trayBarcode1 = GenerateTrayBarcode(state, prefix);
+ string trayBarcode2 = GenerateTrayBarcode(state, prefix);
+ if (!trayBarcode1.IsNullOrEmpty() && !trayBarcode2.IsNullOrEmpty())
{
- // TODO 澶勭悊鎴愬姛鍙戦�佷换鍔℃寚浠ゅ悗鐨勯�昏緫
- task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
- result = await _robotTaskService.UpdateRobotTaskAsync(task);
+
+ await SendSocketRobotPickAsync(task, state);
}
}
+ else // 鎹㈢洏鐩存帴鍙戦�佸彇璐у湴鍧�
+ {
+ await SendSocketRobotPickAsync(task, state);
+ }
+ }
+
+ if (state.CurrentTask.IsNullOrEmpty() && state.ToJson() != task.ToJson())
+ {
+ state.IsSplitPallet = task.RobotTaskType == RobotTaskTypeEnum.SplitPallet.GetHashCode();
+ state.IsGroupPallet = task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode() || task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
+ state.CurrentTask = task;
+ // 鏇存柊缂撳瓨涓殑鐘舵��
+ _cache.TryUpdateIfChanged($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{ipAddress}", state);
}
}
}
@@ -157,13 +167,10 @@
}
catch (Exception)
{
-
}
finally
{
// 鍙�夛細鍦ㄨ繖閲屽鐞嗕换浣曢渶瑕佸湪浠诲姟瀹屾垚鍚庢墽琛岀殑娓呯悊宸ヤ綔
- // 鏇存柊缂撳瓨涓殑鐘舵��
- _cache.AddOrUpdate(ipAddress, state);
}
}
@@ -181,7 +188,7 @@
// 缁勫悎锛氬墠缂� + 鏃ユ湡 + 鏃堕棿 + 闅忔満鏁�
var barCode = prefix + datePart + timePart + randomPart;
- state.CellBarcode.Add(randomPart);
+ state.CellBarcode.Add(barCode);
return barCode;
}
@@ -193,7 +200,14 @@
/// <returns></returns>
private Task<string?> _TcpSocket_RobotReceived(string clientId)
{
- _cache.TryRemove(clientId, out _);
+ var robotSocketState = _cache.Get<RobotSocketState>($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{clientId}");
+ robotSocketState.IsEventSubscribed = false;
+ robotSocketState.CurrentAction = "";
+ robotSocketState.OperStatus = "";
+ robotSocketState.RobotArmObject = 0;
+ robotSocketState.RobotControlMode = 0;
+ robotSocketState.RobotRunMode = 0;
+ _cache.TryUpdateIfChanged($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{clientId}", robotSocketState);
return Task.FromResult<string?>(null);
}
@@ -207,15 +221,16 @@
/// <returns></returns>
private async Task<string?> _TcpSocket_MessageReceived(string message, bool isJson, TcpClient client, RobotSocketState state)
{
+ if (!(bool)(_cache?.TryGetValue($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{client.Client.RemoteEndPoint}", out state)))
+ return null;
+
string messageLower = message.ToLowerInvariant();
if (await IsSimpleCommandAsync(messageLower, state))
{
await _TcpSocket.SendMessageAsync(client, message);
- return null;
}
-
- if (IsPrefixCommand(messageLower))
+ else if (IsPrefixCommand(messageLower))
{
try
{
@@ -238,9 +253,9 @@
var stockDTO = BuildStockDTO(state, positions);
state.LastPickPositions = positions;
- var result = await HttpRequestHelper.HTTPPostAsync(nameof(Category.WMS), stockDTO.ToJsonString(), nameof(ConfigKey.SplitPalletAsync));
+ var result = _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.SplitPalletAsync), stockDTO.ToJson());
- if (result.Status)
+ if (result.Data.Status && result.IsSuccess)
{
state.CurrentAction = "PickFinished";
}
@@ -250,6 +265,7 @@
state.CurrentAction = "PickFinished";
}
+ state.LastPickPositions = positions;
task.RobotTaskState = TaskRobotStatusEnum.RobotPickFinish.GetHashCode();
await _latestRobotTaskService.Repository.UpdateDataAsync(task);
}
@@ -262,8 +278,9 @@
var stockDTO = BuildStockDTO(state, positions);
var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
? nameof(ConfigKey.ChangePalletAsync) : nameof(ConfigKey.GroupPalletAsync);
- var result = await HttpRequestHelper.HTTPPostAsync(nameof(Category.WMS), stockDTO.ToJsonString(), configKey);
- putSuccess = result.Status;
+
+ var result = _httpClientHelper.Post<WebResponseContent>(configKey, stockDTO.ToJson());
+ putSuccess = result.Data.Status && result.IsSuccess;
}
if (putSuccess)
@@ -276,17 +293,18 @@
task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode();
await _latestRobotTaskService.Repository.UpdateDataAsync(task);
}
+
+ await _TcpSocket.SendMessageAsync(client, message);
}
}
catch (Exception ex)
{
Console.WriteLine($"RobotJob MessageReceived Error: {ex.Message}");
}
-
- await _TcpSocket.SendMessageAsync(client, message);
-
- return null;
}
+
+ // 鏇存柊缂撳瓨涓殑鐘舵��
+ _cache.TryUpdateIfChanged($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{state.IPAddress}", state);
return null;
}
@@ -297,7 +315,7 @@
/// <param name="message"></param>
/// <param name="state"></param>
/// <returns></returns>
- private static async Task<bool> IsSimpleCommandAsync(string message, RobotSocketState state)
+ private async Task<bool> IsSimpleCommandAsync(string message, RobotSocketState state)
{
switch (message)
{
@@ -378,7 +396,7 @@
}
}
- private static async Task HandleInboundTaskAsync(RobotSocketState state, bool useSourceAddress)
+ private async Task HandleInboundTaskAsync(RobotSocketState state, bool useSourceAddress)
{
var currentTask = state.CurrentTask;
if (currentTask == null)
@@ -399,9 +417,8 @@
PalletType = 1,
TaskType = 4
};
-
- var result = await HttpRequestHelper.HTTPPostAsync(nameof(Category.WMS), taskDto.ToJsonString(), nameof(ConfigKey.CreateTaskInboundAsync));
- if (!result.Status)
+ var result = _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.CreateTaskInboundAsync), taskDto.ToJson());
+ if (!result.Data.Status && result.IsSuccess)
{
return;
}
@@ -433,20 +450,21 @@
private static StockDTO BuildStockDTO(RobotSocketState state, int[] positions)
{
+ string sss = state.ToJson();
return new StockDTO
{
- SourceLineNo = state.CurrentTask?.RobotSourceAddressLineCode,
- SourcePalletNo = state.CurrentTask?.RobotSourceAddressPalletCode,
- TargetPalletNo = state.CurrentTask?.RobotTargetAddressPalletCode,
- TargetLineNo = state.CurrentTask?.RobotTargetAddressLineCode,
+ SourceLineNo = state.CurrentTask.RobotSourceAddressLineCode,
+ SourcePalletNo = state.CurrentTask.RobotSourceAddressPalletCode,
+ TargetPalletNo = state.CurrentTask.RobotTargetAddressPalletCode,
+ TargetLineNo = state.CurrentTask.RobotTargetAddressLineCode,
Details = positions
.Where(x => x > 0)
.OrderBy(x => x)
.Select((x, idx) => new StockDetailDTO
{
- Quantity = state.CurrentTask?.RobotTaskTotalNum ?? 1,
+ Quantity = state.RobotTaskTotalNum > 0 ? state.RobotTaskTotalNum + positions.Length : positions.Length,
Channel = x,
- CellBarcode = !state.CellBarcode.IsNullOrEmpty() ? state.CellBarcode[idx] : ""
+ CellBarcode = state.CellBarcode?.Count > 0 ? state.CellBarcode[x - 1] : ""
})
.ToList()
};
@@ -455,6 +473,27 @@
private Dt_RobotTask? GetTask(RobotCraneDevice robotCrane)
{
return _robotTaskService.QueryRobotCraneTask(robotCrane.DeviceCode);
+ }
+
+ /// <summary>
+ /// 鍙戦�佹満姊版墜鍙栬揣鍛戒护
+ /// </summary>
+ /// <param name="task"></param>
+ /// <param name="state"></param>
+ /// <returns></returns>
+ private async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state)
+ {
+ string taskString = $"Pickbattery,{task.RobotSourceAddress}";
+ // 鍙戦�佷换鍔℃寚浠�
+ bool result = await _TcpSocket.SendToClientAsync(state.IPAddress, taskString);
+ if (result)
+ {
+ // TODO 澶勭悊鎴愬姛鍙戦�佷换鍔℃寚浠ゅ悗鐨勯�昏緫
+ task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
+ result = await _robotTaskService.UpdateRobotTaskAsync(task);
+ // 鏇存柊缂撳瓨涓殑鐘舵��
+ _cache.TryUpdateIfChanged($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{state.IPAddress}", state);
+ }
}
}
@@ -510,7 +549,7 @@
/// <summary>
/// 鎶撳彇浣嶇疆鏉$爜
/// </summary>
- public List<string> CellBarcode { get; set; } = new();
+ public List<string> CellBarcode { get; set; }
/// <summary>
/// 褰撳墠鎶撳彇浠诲姟
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ShuttleCarJob/ShuttleCarJob.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ShuttleCarJob/ShuttleCarJob.cs
index 6f98935..2324fa8 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ShuttleCarJob/ShuttleCarJob.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ShuttleCarJob/ShuttleCarJob.cs
@@ -8,6 +8,7 @@
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.Service;
+using Microsoft.Extensions.Logging;
namespace WIDESEAWCS_Tasks
{
@@ -18,24 +19,26 @@
private readonly ITaskExecuteDetailService _taskExecuteDetailService;
private readonly IRouterService _routerService;
private readonly IMapper _mapper;
+ private readonly ILogger<ShuttleCarJob> _logger;
- public ShuttleCarJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper)
+ public ShuttleCarJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, ILogger<ShuttleCarJob> logger)
{
_taskService = taskService;
_taskExecuteDetailService = taskExecuteDetailService;
_routerService = routerService;
_mapper = mapper;
+ _logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
try
{
-
+
}
catch (Exception ex)
{
-
+ _logger.LogError(ex, "ShuttleCarJob 鎵ц澶辫触");
}
finally
{
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs
index d3c401b..fcd5764 100644
--- a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/TcpSocketServer.Server.cs
@@ -3,8 +3,10 @@
using System.Linq;
using System.Net;
using System.Net.Sockets;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using WIDESEAWCS_Core.Helper;
namespace WIDESEAWCS_Tasks.SocketServer
{
@@ -87,6 +89,7 @@
try
{
client = await _listener!.AcceptTcpClientAsync().WaitAsync(cancellationToken);
+ ConsoleHelper.WriteSuccessLine($"客户端上线:{client.Client.RemoteEndPoint.ToString()}");
}
catch (OperationCanceledException)
{
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/CommunicationConstTests.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/CommunicationConstTests.cs
new file mode 100644
index 0000000..df04f31
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/CommunicationConstTests.cs
@@ -0,0 +1,84 @@
+using Xunit;
+using WIDESEAWCS_Core.Const;
+
+namespace WIDESEAWCS_Tests
+{
+ /// <summary>
+ /// 閫氳甯搁噺绫诲崟鍏冩祴璇�
+ /// </summary>
+ public class CommunicationConstTests
+ {
+ [Fact]
+ public void WaitIntervalMs_ShouldBe500()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(500, CommunicationConst.WaitIntervalMs);
+ }
+
+ [Fact]
+ public void WaitTimeoutBaseMs_ShouldBe6000()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(6000, CommunicationConst.WaitTimeoutBaseMs);
+ }
+
+ [Fact]
+ public void WaitTimeoutMultiplier_ShouldBe10()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(10, CommunicationConst.WaitTimeoutMultiplier);
+ }
+
+ [Fact]
+ public void WaitTotalTimeoutMs_ShouldBe60000()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(60000, CommunicationConst.WaitTotalTimeoutMs);
+ }
+
+ [Fact]
+ public void WaitTotalTimeoutMs_ShouldEqualMultiplierTimesBase()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(
+ CommunicationConst.WaitTimeoutMultiplier * CommunicationConst.WaitTimeoutBaseMs,
+ CommunicationConst.WaitTotalTimeoutMs
+ );
+ }
+
+ [Fact]
+ public void PingIntervalMs_ShouldBe100()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(100, CommunicationConst.PingIntervalMs);
+ }
+
+ [Fact]
+ public void LogWriteIntervalMs_ShouldBe5000()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(5000, CommunicationConst.LogWriteIntervalMs);
+ }
+
+ [Fact]
+ public void HttpDefaultTimeoutSeconds_ShouldBe60()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(60, CommunicationConst.HttpDefaultTimeoutSeconds);
+ }
+
+ [Fact]
+ public void HttpConnectTimeoutSeconds_ShouldBe30()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(30, CommunicationConst.HttpConnectTimeoutSeconds);
+ }
+
+ [Fact]
+ public void DbConnectTimeoutSeconds_ShouldBe30()
+ {
+ // Arrange & Act & Assert
+ Assert.Equal(30, CommunicationConst.DbConnectTimeoutSeconds);
+ }
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/UnitTest1.cs b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/UnitTest1.cs
new file mode 100644
index 0000000..60b7383
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/UnitTest1.cs
@@ -0,0 +1,10 @@
+锘縩amespace WIDESEAWCS_Tests;
+
+public class UnitTest1
+{
+ [Fact]
+ public void Test1()
+ {
+
+ }
+}
diff --git a/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/WIDESEAWCS_Tests.csproj b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/WIDESEAWCS_Tests.csproj
new file mode 100644
index 0000000..eaaa744
--- /dev/null
+++ b/Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/WIDESEAWCS_Tests.csproj
@@ -0,0 +1,29 @@
+锘�<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <IsPackable>false</IsPackable>
+ <NoWarn>$(NoWarn);NU1605</NoWarn>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="coverlet.collector" Version="6.0.4" />
+ <PackageReference Include="FluentAssertions" Version="8.8.0" />
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
+ <PackageReference Include="Moq" Version="4.20.72" />
+ <PackageReference Include="xunit" Version="2.9.3" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <Using Include="Xunit" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\WIDESEAWCS_Core\WIDESEAWCS_Core.csproj" />
+ <ProjectReference Include="..\WIDESEAWCS_TaskInfoService\WIDESEAWCS_TaskInfoService.csproj" />
+ </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs
index 3bfd085..d28570d 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_IStockService/IStockService.cs
@@ -19,11 +19,11 @@
IStockInfo_HtyService StockInfo_HtyService { get; }
- Task<bool> GroupPalletAsync(StockDTO stock);
+ Task<WebResponseContent> GroupPalletAsync(StockDTO stock);
- Task<bool> ChangePalletAsync(StockDTO stock);
+ Task<WebResponseContent> ChangePalletAsync(StockDTO stock);
- Task<bool> SplitPalletAsync(StockDTO stock);
+ Task<WebResponseContent> SplitPalletAsync(StockDTO stock);
/// <summary>
/// 鍫嗗灈鏈烘崲鐩樺悗鏇存柊搴撳瓨淇℃伅锛堟竻绌哄簱浣嶄俊鎭級
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
index c8cf69a..c7ad96a 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_StockService/StockSerivce.cs
@@ -1,7 +1,4 @@
-锘縰sing Autofac.Core;
-using System.Net;
-using System.Threading.Channels;
-using WIDESEA_Common.StockEnum;
+锘縰sing WIDESEA_Common.StockEnum;
using WIDESEA_Core;
using WIDESEA_DTO.Stock;
using WIDESEA_IStockService;
@@ -19,8 +16,6 @@
public IStockInfoDetail_HtyService StockInfoDetail_HtyService { get; }
public IStockInfo_HtyService StockInfo_HtyService { get; }
-
-
public StockSerivce(
IStockInfoDetailService stockInfoDetailService,
IStockInfoService stockInfoService,
@@ -36,8 +31,9 @@
/// <summary>
/// 缁勭洏
/// </summary>
- public async Task<bool> GroupPalletAsync(StockDTO stock)
+ public async Task<WebResponseContent> GroupPalletAsync(StockDTO stock)
{
+ WebResponseContent content = new WebResponseContent();
var now = DateTime.Now;
var details = stock.Details.Select(item => new Dt_StockInfoDetail
{
@@ -55,10 +51,13 @@
}).ToList();
var existingStock = StockInfoService.Repository.QueryFirst(s => s.PalletCode == stock.TargetPalletNo);
+ var result = false;
if (existingStock != null)
{
details.ForEach(d => d.StockId = existingStock.Id);
- return await StockInfoDetailService.Repository.AddDataAsync(details) > 0;
+ result = await StockInfoDetailService.Repository.AddDataAsync(details) > 0;
+ if (result) return content.OK("缁勭洏鎴愬姛");
+ return content.Error("缁勭洏澶辫触");
}
var entity = new Dt_StockInfo
@@ -70,24 +69,27 @@
Details = details
};
- return StockInfoService.Repository.AddData(entity, x => x.Details);
+ result = StockInfoService.Repository.AddData(entity, x => x.Details);
+ if (result) return content.OK("缁勭洏鎴愬姛");
+ return content.Error("缁勭洏澶辫触");
}
/// <summary>
/// 鎹㈢洏
/// </summary>
- public async Task<bool> ChangePalletAsync(StockDTO stock)
+ public async Task<WebResponseContent> ChangePalletAsync(StockDTO stock)
{
+ WebResponseContent content = new WebResponseContent();
if (stock == null ||
string.IsNullOrWhiteSpace(stock.TargetPalletNo) ||
string.IsNullOrWhiteSpace(stock.SourcePalletNo) ||
string.Equals(stock.SourcePalletNo, stock.TargetPalletNo, StringComparison.OrdinalIgnoreCase))
{
- return false;
+ return content.Error("婧愭墭鐩樺彿涓庣洰鏍囨墭鐩樺彿鐩稿悓");
}
var sourceStock = StockInfoService.Repository.QueryFirst(s => s.PalletCode == stock.SourcePalletNo);
- if (sourceStock == null) return false;
+ if (sourceStock == null) return content.Error("婧愭墭鐩樹笉瀛樺湪");
var targetStock = StockInfoService.Repository.QueryFirst(s => s.PalletCode == stock.TargetPalletNo);
if (targetStock == null)
@@ -101,39 +103,42 @@
};
var newId = StockInfoService.Repository.AddData(newStock);
- if (newId <= 0) return false;
+ if (newId <= 0) return content.Error("鎹㈢洏澶辫触");
targetStock = newStock;
targetStock.Id = newId;
}
var serialNumbers = stock.Details.Select(d => d.CellBarcode).Distinct().ToList();
- if (!serialNumbers.Any()) return false;
+ if (!serialNumbers.Any()) return content.Error("鏈壘鍒版湁鏁堢殑搴忓垪鍙�");
var detailEntities = StockInfoDetailService.Repository.QueryData(
d => d.StockId == sourceStock.Id && serialNumbers.Contains(d.SerialNumber));
- if (!detailEntities.Any()) return false;
+ if (!detailEntities.Any()) return content.Error("鏈壘鍒版湁鏁堢殑搴撳瓨鏄庣粏");
if (await StockInfoDetail_HtyService.Repository.AddDataAsync(CreateDetailHistory(detailEntities, "鎹㈢洏")) <= 0)
- return false;
+ return content.Error("鎹㈢洏鍘嗗彶璁板綍淇濆瓨澶辫触");
if (await StockInfo_HtyService.Repository.AddDataAsync(CreateStockHistory(new[] { sourceStock, targetStock }, "鎹㈢洏")) <= 0)
- return false;
+ return content.Error("鎹㈢洏鍘嗗彶璁板綍淇濆瓨澶辫触");
detailEntities.ForEach(d => d.StockId = targetStock.Id);
- return await StockInfoDetailService.Repository.UpdateDataAsync(detailEntities);
+ var result = await StockInfoDetailService.Repository.UpdateDataAsync(detailEntities);
+ if (!result) return content.Error("鎹㈢洏澶辫触");
+ return content.OK("鎹㈢洏鎴愬姛");
}
/// <summary>
/// 鎷嗙洏
/// </summary>
- public async Task<bool> SplitPalletAsync(StockDTO stock)
+ public async Task<WebResponseContent> SplitPalletAsync(StockDTO stock)
{
+ WebResponseContent content = new WebResponseContent();
if (stock == null || string.IsNullOrWhiteSpace(stock.SourcePalletNo))
- return false;
+ return content.Error("婧愭墭鐩樺彿涓嶈兘涓虹┖");
var sourceStock = StockInfoService.Repository.QueryFirst(s => s.PalletCode == stock.SourcePalletNo);
- if (sourceStock == null) return false;
+ if (sourceStock == null) return content.Error("婧愭墭鐩樹笉瀛樺湪");
var serialNumbers = stock.Details.Select(d => d.CellBarcode).Distinct().ToList();
if (!serialNumbers.Any())
@@ -146,15 +151,17 @@
var detailEntities = StockInfoDetailService.Repository.QueryData(
d => d.StockId == sourceStock.Id && serialNumbers.Contains(d.SerialNumber));
- if (!detailEntities.Any()) return false;
+ if (!detailEntities.Any()) return content.Error("鏈壘鍒版湁鏁堢殑搴撳瓨鏄庣粏");
if (await StockInfoDetail_HtyService.Repository.AddDataAsync(CreateDetailHistory(detailEntities, "鎷嗙洏")) <= 0)
- return false;
+ return content.Error("鎷嗙洏鍘嗗彶璁板綍淇濆瓨澶辫触");
if (await StockInfo_HtyService.Repository.AddDataAsync(CreateStockHistory(new[] { sourceStock }, "鎷嗙洏")) <= 0)
- return false;
+ return content.Error("鎷嗙洏鍘嗗彶璁板綍淇濆瓨澶辫触");
- return await StockInfoDetailService.Repository.DeleteDataAsync(detailEntities);
+ var result = await StockInfoDetailService.Repository.DeleteDataAsync(detailEntities);
+ if (!result) return content.Error("鎷嗙洏澶辫触");
+ return content.OK("鎷嗙洏鎴愬姛");
}
/// <summary>
@@ -227,6 +234,5 @@
ModifyDate = s.ModifyDate
}).ToList();
}
-
}
}
\ No newline at end of file
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs
index cb2276f..30c6dd7 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/Stock/StockController.cs
@@ -26,7 +26,7 @@
/// <param name="stock"></param>
/// <returns></returns>
[HttpGet,HttpPost,Route("GroupPalletAsync"), AllowAnonymous]
- public async Task<bool> GroupPallet([FromBody]StockDTO stock)
+ public async Task<WebResponseContent> GroupPallet([FromBody]StockDTO stock)
{
return await Service.GroupPalletAsync(stock);
}
@@ -37,7 +37,7 @@
/// <param name="stock"></param>
/// <returns></returns>
[HttpGet, HttpPost, Route("ChangePalletAsync"),AllowAnonymous]
- public async Task<bool> ChangePalletAsync([FromBody] StockDTO stock)
+ public async Task<WebResponseContent> ChangePalletAsync([FromBody] StockDTO stock)
{
return await Service.ChangePalletAsync(stock);
}
@@ -48,7 +48,7 @@
/// <param name="stock"></param>
/// <returns></returns>
[HttpGet, HttpPost, Route("SplitPalletAsync"), AllowAnonymous]
- public async Task<bool> SplitPalletAsync([FromBody] StockDTO stock)
+ public async Task<WebResponseContent> SplitPalletAsync([FromBody] StockDTO stock)
{
return await Service.SplitPalletAsync(stock);
}
diff --git a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
index 5f0f297..b743efb 100644
--- a/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
+++ b/Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/TaskInfo/TaskController.cs
@@ -90,6 +90,17 @@
}
/// <summary>
+ /// 鍒涘缓绌烘墭鐩樺嚭搴撲换鍔�
+ /// </summary>
+ /// <param name="taskDto"></param>
+ /// <returns></returns>
+ [HttpGet, HttpPost, Route("CreateTaskInboundTray"), AllowAnonymous]
+ public async Task<WebResponseContent?> CreateTaskInboundTrayAsync([FromBody] CreateTaskDto taskDto)
+ {
+ return await Service.CreateTaskInboundTrayAsync(taskDto);
+ }
+
+ /// <summary>
/// 鍫嗗灈鏈哄彇鏀捐揣瀹屾垚鍚庣墿娴侀�氱煡鍖栨垚鍒嗗鏌滃畬鎴愪俊鍙�
/// </summary>
/// <param name="input"></param>
--
Gitblit v1.9.3