wanshenmean
昨天 0b2869539598059704e1d208e2bcb18603b0fe0f
feat(出库时效): 添加出库时效配置功能

- 新增独立的 outbound_time_config.json 配置文件管理出库时效参数
- 实现前端配置页面和后端API接口
- 优化机器人任务处理逻辑,添加任务编号缓存
- 分离电芯条码为累积批次和当前批次
- 更新相关状态管理和任务处理逻辑
已添加4个文件
已修改12个文件
349 ■■■■■ 文件已修改
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/RobotState/RobotSocketState.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/Task/RobotTaskController.cs 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/router/viewGird.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSClient/src/views/system/outboundTimeConfig.vue 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/OutboundTimeConfigOptions.cs 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/System/OutboundTimeConfigController.cs 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/WIDESEA_WMSServer.csproj 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/outbound_time_config.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Model/Models/RobotState/RobotSocketState.cs
@@ -127,13 +127,23 @@
        public int[]? LastPutPositions { get; set; }
        /// <summary>
        /// ç”µæ± /货位条码列表
        /// ç”µæ± /货位条码列表(累积所有批次,不重复)
        /// </summary>
        /// <remarks>
        /// åœ¨ç»„盘操作时用于记录生成的托盘条码。
        /// åœ¨ç»„盘操作时用于累积记录所有电芯条码,每次读取新条码时追加(去重)。
        /// ä»…在收到 allputfinished(全部放货完成)时清空。
        /// æ¯ä¸ªæ¡ç æ ¼å¼ä¸º "TRAY" + æ—¥æœŸ + æ—¶é—´ + éšæœºæ•°ã€‚
        /// </remarks>
        public List<string> CellBarcode { get; set; } = new List<string>();
        /// <summary>
        /// å½“前批次的电芯条码列表
        /// </summary>
        /// <remarks>
        /// æ¯æ¬¡è¯»å–新条码时设置为本批次的条码,仅用于 WMS æäº¤æ—¶æŒ‰æ‰¹æ¬¡æäº¤ã€‚
        /// æ¯æ¬¡æ–°æ‰¹æ¬¡è¯»å–时覆盖,在 allputfinished æ—¶æ¸…空。
        /// </remarks>
        public List<string> CurrentBatchBarcodes { get; set; } = new List<string>();
        /// <summary>
        /// æœºæ¢°æ‰‹å½“前正在执行的任务
@@ -217,5 +227,14 @@
        /// æ‹‰å¸¦çº¿ä¸Šç”µèŠ¯æ˜¯å¦åˆ°ä½ã€‚
        /// </remarks>
        public bool BatteryArrived { get; set; } = false;
        /// <summary>
        /// å½“前执行中的机器人任务编号
        /// </summary>
        /// <remarks>
        /// ä¸‹å‘任务时缓存任务编号,用于 RobotJob å¿«é€ŸæŸ¥æ‰¾æ‰§è¡Œä¸­çš„任务,
        /// é¿å…æ¯æ¬¡è½®è¯¢å…¨è¡¨æ‰«æã€‚任务完成时清空为 null。
        /// </remarks>
        public int? CurrentTaskNum { get; set; }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Server/Controllers/Task/RobotTaskController.cs
@@ -1,9 +1,8 @@
using Autofac.Core;
using Masuit.Tools;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.BaseController;
using WIDESEAWCS_DTO.TaskInfo;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
@@ -27,12 +26,12 @@
            return WebResponseContent.Instance.Error();
        }
        [HttpGet, HttpPost, Route("GetRobotTaskTotalNum"), AllowAnonymous]
        public int GetRobotTaskTotalNum( int taskType, string? palletCode)
        {
            return Service.GetRobotTaskTotalNum(taskType, palletCode);
        }
        // æ‰‹åŠ¨æœºæ¢°æ‰‹ä»»åŠ¡
        [HttpGet, HttpPost, Route("CreateRobotTaskManually"), AllowAnonymous]
        public WebResponseContent CreateRobotTaskManually([FromBody] ManualRobotTaskDto request) 
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -209,7 +209,15 @@
                }
                // è½®è¯¢èŽ·å–è¯¥è®¾å¤‡çš„å¾…å¤„ç†ä»»åŠ¡
                var task = _taskProcessor.GetTask(robotCrane);
                // ä¼˜å…ˆé€šè¿‡çŠ¶æ€ä¸­ç¼“å­˜çš„ä»»åŠ¡ç¼–å·æŸ¥æ‰¾æ‰§è¡Œä¸­çš„ä»»åŠ¡
                Dt_RobotTask? task = null;
                if (state.CurrentTaskNum.HasValue)
                {
                    task = _taskProcessor.GetTaskByNum(state.CurrentTaskNum.Value);
                }
                // ç¼“存的任务号未找到对应任务时,按设备编码获取新任务
                task ??= _taskProcessor.GetTask(robotCrane);
                // å¦‚果没有获取到待处理任务,且RobotArmObject为1(有物料),则获取该设备执行中的任务
                //if (task == null && state.RobotArmObject == 1)
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -124,6 +124,20 @@
        }
        /// <summary>
        /// æŒ‰ä»»åŠ¡ç¼–å·èŽ·å–æœºå™¨äººä»»åŠ¡
        /// </summary>
        /// <remarks>
        /// ç”¨äºŽ RobotJob å¿«é€ŸæŸ¥æ‰¾æ‰§è¡Œä¸­çš„任务,避免全表扫描。
        /// ä¼˜å…ˆé€šè¿‡çŠ¶æ€ä¸­ç¼“å­˜çš„ CurrentTaskNum å®šä½ä»»åŠ¡ã€‚
        /// </remarks>
        /// <param name="taskNum">机器人任务编号</param>
        /// <returns>匹配的任务对象,如果没有则返回 null</returns>
        public Dt_RobotTask? GetTaskByNum(int taskNum)
        {
            return _robotTaskService.Repository.QueryFirst(x => x.RobotTaskNum == taskNum);
        }
        /// <summary>
        /// æŒ‰è®¾å¤‡ç¼–码获取当前机器人的执行中任务
        /// </summary>
        /// <remarks>
@@ -186,6 +200,7 @@
            // å°†ä»»åŠ¡å…³è”åˆ°çŠ¶æ€å¯¹è±¡
            state.CurrentTask = task;
            state.CurrentTaskNum = task.RobotTaskNum;
            if (isScanNG)
            {
@@ -276,6 +291,7 @@
            // å°†ä»»åŠ¡å…³è”åˆ°çŠ¶æ€å¯¹è±¡
            state.CurrentTask = task;
            state.CurrentTaskNum = task.RobotTaskNum;
            if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
            {
@@ -346,6 +362,7 @@
            task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
            state.CurrentTask = task;
            state.CurrentTaskNum = task.RobotTaskNum;
            if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
            {
@@ -389,6 +406,7 @@
        {
            task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
            state.CurrentTask = task;
            state.CurrentTaskNum = task.RobotTaskNum;
            if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
            {
@@ -434,6 +452,7 @@
        {
            task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
            state.CurrentTask = task;
            state.CurrentTaskNum = task.RobotTaskNum;
            if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
            {
@@ -688,9 +707,9 @@
                        // é€šé“/位置编号
                        Channel = x,
                        // ç”µæ± æ¡ç ï¼šå¦‚果状态中有条码列表,取对应位置的条码;否则为空
                        // ç”µæ± æ¡ç ï¼šä½¿ç”¨å½“前批次条码列表,取对应位置的条码;否则为空
                        //CellBarcode = state.CellBarcode?.Count > 0 ? state.CellBarcode[x - 1] : ""
                        CellBarcode = !state.CellBarcode.IsNullOrEmpty() ? state.CellBarcode[idx].ToString() ?? string.Empty : string.Empty
                        CellBarcode = !state.CurrentBatchBarcodes.IsNullOrEmpty() ? state.CurrentBatchBarcodes[idx].ToString() ?? string.Empty : string.Empty
                    })
                    .ToList()
            };
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
@@ -221,8 +221,10 @@
                                QuartzLogHelper.LogInfo(_logger, $"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
                                state.CurrentTask = null;
                                state.CurrentTaskNum = null;
                                state.RobotTaskTotalNum = 0;
                                state.CellBarcode = new List<string>();
                                state.CurrentBatchBarcodes = new List<string>();
                                state.ChangePalletPhase = 0;
                                state.CurrentBatchIndex = 1;
                                state.IsInFakeBatteryMode = false;
@@ -335,8 +337,10 @@
                                }
                                state.CurrentTask = null;
                                state.CurrentTaskNum = null;
                                state.RobotTaskTotalNum = 0;
                                state.CellBarcode = new List<string>();
                                state.CurrentBatchBarcodes = new List<string>();
                                await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
                                QuartzLogHelper.LogInfo(_logger, $"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
@@ -371,8 +375,10 @@
                            }
                            state.CurrentTask = null;
                            state.CurrentTaskNum = null;
                            state.RobotTaskTotalNum = 0;
                            state.CellBarcode = new List<string>();
                            state.CurrentBatchBarcodes = new List<string>();
                            await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
                            QuartzLogHelper.LogInfo(_logger, $"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
@@ -411,8 +417,10 @@
                            // æ¸…理状态,为下一个任务做准备
                            state.CurrentTask = null;           // æ¸…除当前任务
                            state.CurrentTaskNum = null;        // æ¸…除当前任务编号
                            state.RobotTaskTotalNum = 0;        // é‡ç½®ä»»åŠ¡è®¡æ•°
                            state.CellBarcode = new List<string>();  // æ¸…空条码列表
                            state.CurrentBatchBarcodes = new List<string>();  // æ¸…空当前批次条码
                            await _socketClientGateway.SendToClientAsync(state.IPAddress, $"Group,diskFinished");
                            QuartzLogHelper.LogInfo(_logger, $"发送消息:【Group,diskFinished】", state.RobotCrane.DeviceName);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -271,6 +271,7 @@
                if (stateToUpdate != null)
                {
                    stateToUpdate.CurrentTask = task;
                    stateToUpdate.CurrentTaskNum = task.RobotTaskNum;
                    if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
                    {
@@ -356,8 +357,14 @@
                        QuartzLogHelper.LogInfo(_logger, $"HandlePutFinishedStateAsync:读取的电芯条码唯一,继续执行,任务号: {task.RobotTaskNum}", stateForUpdate?.RobotCrane?.DeviceName ?? ipAddress);
                        // å°†æ¡ç æ·»åŠ åˆ°çŠ¶æ€ä¸­ï¼Œä¾›åŽç»­æ”¾è´§æ—¶ä½¿ç”¨
                        stateForUpdate.CellBarcode = new List<string>()
                        // å°†æ¡ç ç´¯ç§¯åˆ° CellBarcode(去重),并设置当前批次条码
                        if (!stateForUpdate.CellBarcode.Contains(trayBarcode1))
                            stateForUpdate.CellBarcode.Add(trayBarcode1);
                        if (!stateForUpdate.CellBarcode.Contains(trayBarcode2))
                            stateForUpdate.CellBarcode.Add(trayBarcode2);
                        // è®¾ç½®å½“前批次条码,用于 WMS æäº¤
                        stateForUpdate.CurrentBatchBarcodes = new List<string>()
                        {
                            trayBarcode1,trayBarcode2
                        };
Code/WMS/WIDESEA_WMSClient/src/router/viewGird.js
@@ -236,6 +236,11 @@
    name: 'PDA',
    component: () => import('@/views/system/PDA.vue')
  }
  , {
    path: '/outboundTimeConfig',
    name: 'outboundTimeConfig',
    component: () => import('@/views/system/outboundTimeConfig.vue')
  }
]
export default viewgird
Code/WMS/WIDESEA_WMSClient/src/views/system/outboundTimeConfig.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,109 @@
<template>
  <div class="config-container">
    <el-card class="config-card">
      <template #header>
        <span>出库时效配置</span>
      </template>
      <el-form :model="form" label-width="180px" v-loading="loading">
        <el-form-item label="GW首放入库时效(小时)">
          <el-input-number
            v-model="form.gw1FirstHours"
            :min="0.01"
            :max="999"
            :precision="2"
            :step="1"
          />
        </el-form-item>
        <el-form-item label="GW二放入库时效(小时)">
          <el-input-number
            v-model="form.gw1SecondHours"
            :min="0.01"
            :max="999"
            :precision="2"
            :step="0.01"
          />
        </el-form-item>
        <el-form-item label="CW出库时效(小时)">
          <el-input-number
            v-model="form.cw1Hours"
            :min="0.01"
            :max="999"
            :precision="2"
            :step="1"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSave" :loading="saving">
            ä¿å­˜
          </el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>
<script>
import { ref, onMounted } from "vue";
import { ElMessage } from "element-plus";
import http from "@/api/http";
export default {
  name: "outboundTimeConfig",
  setup() {
    const loading = ref(false);
    const saving = ref(false);
    const form = ref({
      gw1FirstHours: 24,
      gw1SecondHours: 0.05,
      cw1Hours: 3,
    });
    /// åŠ è½½å½“å‰é…ç½®
    const loadConfig = async () => {
      loading.value = true;
      try {
        const res = await http.post("/api/OutboundTimeConfig/get", {}, false);
        if (res.status) {
          form.value = {
            gw1FirstHours: res.data.gw1FirstHours,
            gw1SecondHours: res.data.gw1SecondHours,
            cw1Hours: res.data.cw1Hours,
          };
        }
      } finally {
        loading.value = false;
      }
    };
    /// ä¿å­˜é…ç½®
    const handleSave = async () => {
      saving.value = true;
      try {
        const res = await http.post("/api/OutboundTimeConfig/update", form.value, false);
        if (res.status) {
          ElMessage.success("保存成功");
        } else {
          ElMessage.error(res.message || "保存失败");
        }
      } finally {
        saving.value = false;
      }
    };
    onMounted(() => {
      loadConfig();
    });
    return { form, loading, saving, handleSave };
  },
};
</script>
<style scoped>
.config-container {
  padding: 20px;
}
.config-card {
  max-width: 600px;
}
</style>
Code/WMS/WIDESEA_WMSServer/WIDESEA_Core/Core/OutboundTimeConfigOptions.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
namespace WIDESEA_Core.Core
{
    /// <summary>
    /// å‡ºåº“时效配置选项,绑定 appsettings.json ä¸­çš„ OutboundTimeConfig èŠ‚
    /// </summary>
    public class OutboundTimeConfigOptions
    {
        /// <summary>
        /// é…ç½®èŠ‚åç§°
        /// </summary>
        public const string SectionName = "OutboundTimeConfig";
        /// <summary>
        /// GW首放入库时效(小时),默认24小时
        /// </summary>
        public double Gw1FirstHours { get; set; } = 24;
        /// <summary>
        /// GW二放入库时效(小时),默认0.05小时(约3分钟)
        /// </summary>
        public double Gw1SecondHours { get; set; } = 0.05;
        /// <summary>
        /// CW出库时效(小时),默认3小时
        /// </summary>
        public double Cw1Hours { get; set; } = 3;
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/TaskService.cs
@@ -9,6 +9,7 @@
using WIDESEA_Common.StockEnum;
using WIDESEA_Common.TaskEnum;
using WIDESEA_Common.WareHouseEnum;
using Microsoft.Extensions.Options;
using WIDESEA_Core;
using WIDESEA_Core.BaseRepository;
using WIDESEA_Core.BaseServices;
@@ -43,6 +44,7 @@
        private readonly IMesLogService _mesLogService;
        private readonly IMesUploadHelper _mesUploadHelper;
        private readonly ISqlSugarClient _sqlSugarClient;
        private readonly IOptionsMonitor<OutboundTimeConfigOptions> _outboundTimeOptions;
        public IRepository<Dt_Task> Repository => BaseDal;
@@ -71,7 +73,8 @@
            IMESDeviceConfigService mesDeviceConfigService,
            IMesLogService mesLogService,
            IMesUploadHelper mesUploadHelper,
            ISqlSugarClient sqlSugarClient) : base(BaseDal)
            ISqlSugarClient sqlSugarClient,
            IOptionsMonitor<OutboundTimeConfigOptions> outboundTimeOptions) : base(BaseDal)
        {
            _mapper = mapper;
            _stockInfoService = stockInfoService;
@@ -88,6 +91,7 @@
            _mesLogService = mesLogService;
            _mesUploadHelper = mesUploadHelper;
            _sqlSugarClient = sqlSugarClient;
            _outboundTimeOptions = outboundTimeOptions;
        }
        /// <summary>
Code/WMS/WIDESEA_WMSServer/WIDESEA_TaskInfoService/WCS/TaskService_Inbound.cs
@@ -233,14 +233,15 @@
        /// <param name="stockInfo">库存信息</param>
        private void SetOutboundDateByRoadway(Dt_Task task, Dt_StockInfo stockInfo)
        {
            var config = _outboundTimeOptions.CurrentValue;
            var now = DateTime.Now;
            if (task.Roadway.Contains("GW"))
            {
                stockInfo.OutboundDate = string.IsNullOrEmpty(stockInfo.Remark)
                    ? now.AddHours(OutboundTimeConstants.OUTBOUND_HOURS_GW1_FIRST)
                    ? now.AddHours(config.Gw1FirstHours)
                    : stockInfo.Remark == StockRemarkConstants.GW1
                        ? now.AddHours(OutboundTimeConstants.OUTBOUND_HOURS_GW1_SECOND)
                        : now.AddHours(OutboundTimeConstants.OUTBOUND_HOURS_GW1_FIRST);
                        ? now.AddHours(config.Gw1SecondHours)
                        : now.AddHours(config.Gw1FirstHours);
                stockInfo.Remark = string.IsNullOrEmpty(stockInfo.Remark)
                    ? StockRemarkConstants.GW1
@@ -250,7 +251,7 @@
            }
            else if (task.Roadway.Contains("CW"))
            {
                stockInfo.OutboundDate = now.AddHours(OutboundTimeConstants.OUTBOUND_HOURS_CW1);
                stockInfo.OutboundDate = now.AddHours(config.Cw1Hours);
                if (stockInfo.Remark == StockRemarkConstants.GW2)
                    stockInfo.Remark = StockRemarkConstants.CW1;
            }
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Controllers/System/OutboundTimeConfigController.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,94 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using WIDESEA_Core;
using WIDESEA_Core.Core;
namespace WIDESEA_WMSServer.Controllers
{
    /// <summary>
    /// å‡ºåº“时效配置控制器,读写独立的 outbound_time_config.json é…ç½®æ–‡ä»¶
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    [Authorize]
    public class OutboundTimeConfigController : ControllerBase
    {
        private readonly IOptionsMonitor<OutboundTimeConfigOptions> _optionsMonitor;
        private readonly IWebHostEnvironment _env;
        /// <summary>
        /// é…ç½®æ–‡ä»¶å
        /// </summary>
        private const string ConfigFileName = "outbound_time_config.json";
        /// <summary>
        /// æž„造函数
        /// </summary>
        /// <param name="optionsMonitor">出库时效配置 Options ç›‘控器</param>
        /// <param name="env">Web å®¿ä¸»çŽ¯å¢ƒï¼Œç”¨äºŽå®šä½é…ç½®æ–‡ä»¶è·¯å¾„</param>
        public OutboundTimeConfigController(IOptionsMonitor<OutboundTimeConfigOptions> optionsMonitor, IWebHostEnvironment env)
        {
            _optionsMonitor = optionsMonitor;
            _env = env;
        }
        /// <summary>
        /// èŽ·å–å½“å‰å‡ºåº“æ—¶æ•ˆé…ç½®
        /// </summary>
        /// <returns>当前配置值</returns>
        [HttpPost("get")]
        public IActionResult Get()
        {
            var config = _optionsMonitor.CurrentValue;
            return Ok(WebResponseContent.Instance.OK("获取成功", new
            {
                config.Gw1FirstHours,
                config.Gw1SecondHours,
                config.Cw1Hours
            }));
        }
        /// <summary>
        /// æ›´æ–°å‡ºåº“时效配置,写入独立的 outbound_time_config.json æ–‡ä»¶
        /// </summary>
        /// <param name="config">新的配置值</param>
        /// <returns>操作结果</returns>
        [HttpPost("update")]
        public IActionResult Update([FromBody] OutboundTimeConfigOptions config)
        {
            if (config.Gw1FirstHours <= 0 || config.Gw1SecondHours <= 0 || config.Cw1Hours <= 0)
            {
                return Ok(WebResponseContent.Instance.Error("配置值必须大于0"));
            }
            try
            {
                var filePath = Path.Combine(_env.ContentRootPath, ConfigFileName);
                // æž„建配置 JSON,包裹在 OutboundTimeConfig èŠ‚ä¸‹ä»¥åŒ¹é… Options ç»‘定
                var configSection = new JObject
                {
                    ["Gw1FirstHours"] = config.Gw1FirstHours,
                    ["Gw1SecondHours"] = config.Gw1SecondHours,
                    ["Cw1Hours"] = config.Cw1Hours
                };
                var jsonObj = new JObject
                {
                    [OutboundTimeConfigOptions.SectionName] = configSection
                };
                // å†™å…¥æ–‡ä»¶ï¼Œæ ¼å¼åŒ–输出
                System.IO.File.WriteAllText(filePath, jsonObj.ToString(Formatting.Indented));
                return Ok(WebResponseContent.Instance.OK("配置更新成功"));
            }
            catch (Exception ex)
            {
                return Ok(WebResponseContent.Instance.Error($"配置更新失败: {ex.Message}"));
            }
        }
    }
}
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/Program.cs
@@ -36,6 +36,7 @@
    hostingContext.Configuration.ConfigureApplication();
    config.Sources.Clear();
    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
    config.AddJsonFile("outbound_time_config.json", optional: true, reloadOnChange: true);
});
builder.Host.UseSerilog((context, services, loggerConfiguration) =>
@@ -75,6 +76,8 @@
builder.Services.AddSingleton<RoundRobinService>();
builder.Services.Configure<AutoOutboundTaskOptions>(
    builder.Configuration.GetSection("AutoOutboundTask"));
builder.Services.Configure<OutboundTimeConfigOptions>(
    builder.Configuration.GetSection(OutboundTimeConfigOptions.SectionName));
builder.Services.AddMemoryCacheSetup(); // ç¼“存服务
builder.Services.AddWebSocketSetup();
builder.Services.AddSqlsugarSetup(); // SqlSugar æ•°æ®åº“配置
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/WIDESEA_WMSServer.csproj
@@ -24,6 +24,12 @@
  </ItemGroup>
  <ItemGroup>
    <Content Update="outbound_time_config.json">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>
  </ItemGroup>
  <ItemGroup>
      <Content Update="wwwroot\swg-login.html">
          <CopyToOutputDirectory>Never</CopyToOutputDirectory>
      </Content>
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/appsettings.json
@@ -34,7 +34,7 @@
  "MainDB": "DB_WIDESEA", //当前项目的主库,所对应的连接字符串的Enabled必须为true
  //连接字符串
  //"ConnectionString": "HTI6FB1H05Krd07mNm9yBCNhofW6edA5zLs9TY~MNthRYW3kn0qKbMIsGp~3yyPDF1YZUCPBQx8U0Jfk4PH~ajNFXVIwlH85M3F~v_qKYQ3CeAz3q1mLVDn8O5uWt1~3Ut2V3KRkEwYHvW2oMDN~QIDXPxDgXN0R2oTIhc9dNu7QNaLEknblqmHhjaNSSpERdDVZIgHnMKejU_SL49tralBkZmDNi0hmkbL~837j1NWe37u9fJKmv91QPb~16JsuI9uu0EvNZ06g6PuZfOSAeFH9GMMIZiketdcJG3tHelo=",
  "ConnectionString": "Data Source=192.168.60.30;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  "ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=P@ssw0rd;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=.;Initial Catalog=WIDESEAWMS_ShanMei;User ID=sa;Password=123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //"ConnectionString": "Data Source=10.30.4.92;Initial Catalog=WMS_TC;User ID=sa;Password=duo123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False",
  //旧WMS数据库连接
Code/WMS/WIDESEA_WMSServer/WIDESEA_WMSServer/outbound_time_config.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
{
  "OutboundTimeConfig": {
    "Gw1FirstHours": 24.0,
    "Gw1SecondHours": 0.05,
    "Cw1Hours": 3.0
  }
}