1.车间综合看板接口调整
2.报工记录子表和不良记录表写入报工人员,班组编码
3.修改调整定时任务写入到数据库,并实现消息推送
已添加3个文件
已删除1个文件
已修改15个文件
1936 ■■■■■ 文件已修改
VueWebCoreApi/Controllers/TaskBackGroundController.cs 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/DLL/DAL/KanBanManagerentDAL.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/DLL/DAL/WorkOrderDAL.cs 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/HttpManager.cs 189 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/HttpResultfulJob.cs 150 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/IOCJobFactory.cs 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/JobAction.cs 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/JobActionLog.cs 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/QuartzDapperHelper.cs 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/QuartzFileInfo.cs 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/QuartzNETExtension.cs 646 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/QuartzRepository.cs 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/QuartzSearchData.cs 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/TaskLog.cs 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Quartz/TaskOptions.cs 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/SignalR/ChatHub.cs 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/SignalR/UserIdsStore.cs 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Startup.cs 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/appsettings.json 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
VueWebCoreApi/Controllers/TaskBackGroundController.cs
@@ -16,122 +16,161 @@
    [Route("api/[controller]")]
    public class TaskBackGroundController : Controller
    {
        //定义全局信息返回变量
        ToMessage mes = new ToMessage();
        private readonly ISchedulerFactory _schedulerFactory;
        private readonly IJobFactory _jobFactory;
        public TaskBackGroundController(ISchedulerFactory schedulerFactory, IJobFactory jobFactory)
        private readonly QuartzRepository _quartzRepo;
        public TaskBackGroundController(ISchedulerFactory schedulerFactory,
                                       IJobFactory jobFactory,
                                       QuartzRepository quartzRepo)
        {
            this._jobFactory = jobFactory;
            this._schedulerFactory = schedulerFactory;
            _schedulerFactory = schedulerFactory;
            _jobFactory = jobFactory;
            _quartzRepo = quartzRepo;
        }
        /// <summary>
        /// èŽ·å–æ‰€æœ‰çš„ä½œä¸š
        /// </summary>
        /// <returns></returns>
        [Route(template: "GetJobs")]
        [Route("GetJobs")]
        [HttpGet]
        public async Task<IActionResult> GetJobs()
        {
            mes.code = "200";
            mes.message = "查询成功!";
            mes.data = await _schedulerFactory.GetJobs();
            try
            {
                mes.code = "200";
                mes.message = "查询成功!";
                mes.data = await _schedulerFactory.GetJobs(_quartzRepo);
            }
            catch (Exception ex)
            {
                mes.code = "300";
                mes.message = "查询失败:" + ex.Message;
                mes.data = null;
            }
            return Json(mes);
        }
        /// <summary>
        /// èŽ·å–ä½œä¸šè¿è¡Œæ—¥å¿—
        /// </summary>
        /// <param name="taskName"></param>
        /// <param name="groupName"></param>
        /// <param name="page"></param>
        /// <returns></returns>
        [Route(template: "GetRunLog")]
        [Route("GetRunLog")]
        [HttpGet]
        public IActionResult GetRunLog(string taskName, string groupName, int page = 1)
        public async Task<IActionResult> GetRunLog(string taskName, string groupName, int page = 1)
        {
            mes.code = "200";
            mes.data = FileQuartz.GetJobRunLog(taskName, groupName, page);
            // å…¥å‚校验
            if (string.IsNullOrEmpty(taskName) || string.IsNullOrEmpty(groupName))
            {
                mes.code = "300";
                mes.message = "任务名和分组名不能为空!";
                mes.data = null;
                return Json(mes);
            }
            try
            {
                mes.code = "200";
                mes.message = "查询成功!";
                mes.data = await _quartzRepo.GetJobRunLogAsync(taskName, groupName, page);
            }
            catch (Exception ex)
            {
                mes.code = "300";
                mes.message = "查询失败:" + ex.Message;
                mes.data = null;
            }
            return Json(mes);
        }
        /// <summary>
        /// æ·»åŠ ä»»åŠ¡
        /// </summary>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        [Route(template: "Add")]
        [Route("Add")]
        [HttpPost]
        //[TaskAuthor]
        public async Task<IActionResult> Add(TaskOptions taskOptions)
        {
            return Json(await taskOptions.AddJob(_schedulerFactory, jobFactory: _jobFactory));
            if (string.IsNullOrEmpty(taskOptions.TaskName) || string.IsNullOrEmpty(taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务名和分组名不能为空!";
                return Json(mes);
            }
            return Json(await taskOptions.AddJob(_schedulerFactory, false, _jobFactory, _quartzRepo));
        }
        /// <summary>
        /// åˆ é™¤ä»»åŠ¡
        /// </summary>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        [Route(template: "Remove")]
        [Route("Remove")]
        [HttpPost]
        //[TaskAuthor]
        public async Task<IActionResult> Remove(TaskOptions taskOptions)
        {
            return Json(await _schedulerFactory.Remove(taskOptions));
            if (string.IsNullOrEmpty(taskOptions.TaskName) || string.IsNullOrEmpty(taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务名和分组名不能为空!";
                return Json(mes);
            }
            return Json(await _schedulerFactory.Remove(taskOptions, _quartzRepo));
        }
        /// <summary>
        /// ä¿®æ”¹ä»»åŠ¡
        /// </summary>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        [Route(template: "Update")]
        [Route("Update")]
        [HttpPost]
        //[TaskAuthor]
        public async Task<IActionResult> Update(TaskOptions taskOptions)
        {
            return Json(await _schedulerFactory.Update(taskOptions));
            if (string.IsNullOrEmpty(taskOptions.TaskName) || string.IsNullOrEmpty(taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务名和分组名不能为空!";
                return Json(mes);
            }
            return Json(await _schedulerFactory.Update(taskOptions, _quartzRepo));
        }
        /// <summary>
        /// æš‚停任务
        /// </summary>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        [Route(template: "Pause")]
        [Route("Pause")]
        [HttpPost]
        //[TaskAuthor]
        public async Task<IActionResult> Pause(TaskOptions taskOptions)
        {
            return Json(await _schedulerFactory.Pause(taskOptions));
            if (string.IsNullOrEmpty(taskOptions.TaskName) || string.IsNullOrEmpty(taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务名和分组名不能为空!";
                return Json(mes);
            }
            // æ ¡éªŒä»»åŠ¡æ˜¯å¦å­˜åœ¨
            if (!await _quartzRepo.TaskExistsAsync(taskOptions.TaskName, taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务不存在!";
                return Json(mes);
            }
            return Json(await _schedulerFactory.Pause(taskOptions, _quartzRepo));
        }
        /// <summary>
        /// å¼€å¯ä»»åŠ¡
        /// </summary>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        [Route(template: "Start")]
        [Route("Start")]
        [HttpPost]
        //[TaskAuthor]
        public async Task<IActionResult> Start(TaskOptions taskOptions)
        {
            return Json(await _schedulerFactory.Start(taskOptions));
            if (string.IsNullOrEmpty(taskOptions.TaskName) || string.IsNullOrEmpty(taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务名和分组名不能为空!";
                return Json(mes);
            }
            if (!await _quartzRepo.TaskExistsAsync(taskOptions.TaskName, taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务不存在!";
                return Json(mes);
            }
            return Json(await _schedulerFactory.Start(taskOptions, _quartzRepo));
        }
        /// <summary>
        /// ç«‹å³æ‰§è¡Œ
        /// </summary>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        [Route(template: "Run")]
        [Route("Run")]
        [HttpPost]
        //[TaskAuthor]
        public async Task<IActionResult> Run(TaskOptions taskOptions)
        {
            return Json(await _schedulerFactory.Run(taskOptions));
            if (string.IsNullOrEmpty(taskOptions.TaskName) || string.IsNullOrEmpty(taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务名和分组名不能为空!";
                return Json(mes);
            }
            if (!await _quartzRepo.TaskExistsAsync(taskOptions.TaskName, taskOptions.GroupName))
            {
                mes.code = "300";
                mes.message = "任务不存在!";
                return Json(mes);
            }
            return Json(await _schedulerFactory.Run(taskOptions, _quartzRepo));
        }
    }
}
VueWebCoreApi/DLL/DAL/KanBanManagerentDAL.cs
@@ -214,17 +214,13 @@
        public static ToMessage WorkShopCompreRightBottom()
        {
            var sql = "";
            Dictionary<object, object> dir = new Dictionary<object, object>();
            try
            {
                sql = @"select * from h_v_JLKanBan_WorkShopCompreRightBottom";
                var data = DapperHelper.selecttable(sql);
                dir.Add("Y", data.Rows[0]["BaseQuantity"].ToString());
                dir.Add("B", data.Rows[1]["BaseQuantity"].ToString());
                dir.Add("C", data.Rows[2]["BaseQuantity"].ToString());
                mes.code = "200";
                mes.message = "查询成功!";
                mes.data = dir;
                mes.data = data;
            }
            catch (Exception e)
            {
VueWebCoreApi/DLL/DAL/WorkOrderDAL.cs
@@ -133,7 +133,7 @@
        #endregion
        #region[ERP订单下达]
        public static ToMessage MarkSaveErpOrder(string erporderid, string sbid, string erpordercode, string saleordercode, string partcode,string deptcode, string wkshopcode, string warehousecode, string erpqty, string markqty, string ordernum, string relse_qty, string idTopInventory, string TopInventoryCode, string TopInventoryName, string saleOrderDeliveryDate, string paystartdate, string payenddate, string clerkuser, User us)
        public static ToMessage MarkSaveErpOrder(string erporderid, string sbid, string erpordercode, string saleordercode, string partcode, string deptcode, string wkshopcode, string warehousecode, string erpqty, string markqty, string ordernum, string relse_qty, string idTopInventory, string TopInventoryCode, string TopInventoryName, string saleOrderDeliveryDate, string paystartdate, string payenddate, string clerkuser, User us)
        {
            var sql = "";
            string orderstatus = "", isstep = ""; //工单状态、是否绑定工艺
@@ -230,7 +230,7 @@
                                wo_code = wo,
                                wotype = "PO",
                                status = orderstatus,  //"NEW"
                                dept_code= deptcode,
                                dept_code = deptcode,
                                wkshp_code = wkshopcode,
                                plan_qty = cdqty + (decimal.Parse(markqty) - sumqty),  //末单下单数量=切分数量+(下单数量-累计切分下单数量)
                                stck_code = warehousecode,
@@ -298,7 +298,7 @@
                                wo_code = wo,
                                wotype = "PO",
                                status = orderstatus, //"NEW"
                                dept_code=deptcode,
                                dept_code = deptcode,
                                wkshp_code = wkshopcode,
                                plan_qty = cdqty,
                                stck_code = warehousecode,
@@ -500,13 +500,13 @@
                                wo_code = wo,
                                wotype = "PO",
                                status = orderstatus, //"NEW"
                                dept_code=model.deptcode,
                                dept_code = model.deptcode,
                                wkshp_code = model.wkshopcode,
                                plan_qty = decimal.Parse(model.erpqty),  //订单数量
                                stck_code = model.warehousecode,
                                sbid = model.sbid,
                                materiel_code = model.partcode,
                                route_code=data0.Rows.Count<=0?"": data0.Rows[0]["default_route"].ToString(),
                                route_code = data0.Rows.Count <= 0 ? "" : data0.Rows[0]["default_route"].ToString(),
                                sourceid = model.erporderid,
                                m_po = model.erpordercode,
                                username = us.usercode,
@@ -518,7 +518,7 @@
                                data_sources = "ERP",
                                isstep = isstep,  //是否关联工序 "N"
                                clerkuser = model.clerkuser, //销售订单业务员
                                idTopInventory =model.idTopInventory,
                                idTopInventory = model.idTopInventory,
                                TopInventoryCode = model.TopInventoryCode,
                                TopInventoryName = model.TopInventoryName
                            }
@@ -540,7 +540,7 @@
                                        seq = data0.Rows[i]["seq"].ToString(),
                                        step_code = data0.Rows[i]["step_code"].ToString(),
                                        route_code = data0.Rows[i]["default_route"].ToString(),
                                        stepprice = decimal.Parse(data0.Rows[i]["unprice"].ToString()==""|| data0.Rows[i]["unprice"].ToString() ==null? "0":data0.Rows[i]["unprice"].ToString()),
                                        stepprice = decimal.Parse(data0.Rows[i]["unprice"].ToString() == "" || data0.Rows[i]["unprice"].ToString() == null ? "0" : data0.Rows[i]["unprice"].ToString()),
                                        plan_quantity = decimal.Parse(model.erpqty),  //订单数量
                                        plan_qty = decimal.Parse(model.erpqty),  //订单数量
                                        ratio = 0,
@@ -931,7 +931,7 @@
                var total = 0; //总条数
                var sql = @"select A.id, A.status,A.wotype,A.wo_code,A.materiel_code as partcode,B.partname,B.partspec,A.idTopInventory,A.TopInventoryCode,A.TopInventoryName,A.route_code,B.default_route,R.name as route_name,A.plan_qty,A.wkshp_code,C.torg_name as wkshp_name,
                            A.stck_code,D.name as stck_name,A.plan_startdate,A.plan_enddate,A.piroque,A.sourceid,A.m_po,A.saleOrderDeliveryDate,A.saleOrderCode,U.username as lm_user,A.lm_date,A.data_sources,A.isstep,A.clerkuser,
                            B.priuserdefnvc1,B.priuserdefnvc2,B.priuserdefnvc3,B.priuserdefnvc4,B.priuserdefnvc5,B.priuserdefnvc6,A.printcount
                            B.priuserdefnvc1,B.priuserdefnvc2,B.priuserdefnvc3,B.priuserdefnvc4,B.priuserdefnvc5,B.priuserdefnvc6,A.printcount,W.memo,W.bomsubspec
                            from TK_Wrk_Man A
                            left join TKimp_Ewo W on A.m_po=W.wo and A.materiel_code=W.materiel_code and A.sbid=W.sbid
                            left join TMateriel_Info B on A.materiel_code=B.partcode
@@ -1019,7 +1019,7 @@
                var total = 0; //总条数
                var sql = @"select A.id, A.status,A.wotype,A.wo_code,A.materiel_code as partcode,B.partname,B.partspec,A.idTopInventory,A.TopInventoryCode,A.TopInventoryName,A.plan_qty,A.wkshp_code,C.torg_name as wkshp_name,
                            A.stck_code,D.name as stck_name,A.plan_startdate,A.plan_enddate,A.piroque,A.sourceid,A.m_po,A.saleOrderDeliveryDate,W.saleOrderCode,U.username as lm_user,A.lm_date,S.laborbad_qty,S.materielbad_qty,
                            B.priuserdefnvc1,B.priuserdefnvc2,B.priuserdefnvc3,B.priuserdefnvc4,B.priuserdefnvc5,B.priuserdefnvc6
                            B.priuserdefnvc1,B.priuserdefnvc2,B.priuserdefnvc3,B.priuserdefnvc4,B.priuserdefnvc5,B.priuserdefnvc6,W.memo,W.bomsubspec
                            from TK_Wrk_Man A
                            left join TKimp_Ewo W on A.m_po=W.wo and A.materiel_code=W.materiel_code and A.sbid=W.sbid
                            left join (select wo_code,isnull(sum(laborbad_qty),0) as laborbad_qty,isnull(sum(materielbad_qty),0) as materielbad_qty from  TK_Wrk_Step where (laborbad_qty+materielbad_qty)>0 group by wo_code) S on A.wo_code=S.wo_code
@@ -1178,7 +1178,7 @@
                            materiel_code = json.partcode,
                            route_code = route_code,
                            sourceid = json.sourceid == "" ? null : json.sourceid, //无源单时赋值NULL
                            saleOrderCode=json.saleordercode,
                            saleOrderCode = json.saleordercode,
                            m_po = json.sourcewo,
                            saleOrderDeliveryDate = json.deliverydate,
                            plan_startdate = json.paystartdate,
@@ -1206,7 +1206,7 @@
                                stepprice = json.WorkListSub[i].stepprice,
                                plan_quantity = json.woqty,
                                plan_qty = json.WorkListSub[i].sumqty,
                                ratio=json.WorkListSub[i].ratio,
                                ratio = json.WorkListSub[i].ratio,
                                status = json.wostatus,
                                isbott = json.WorkListSub[i].isbott,
                                isend = json.WorkListSub[i].isend,
@@ -1288,7 +1288,7 @@
                                stepprice = json.WorkListSub[i].stepprice,
                                plan_quantity = json.woqty,
                                plan_qty = json.WorkListSub[i].sumqty,
                                ratio=json.WorkListSub[i].ratio,
                                ratio = json.WorkListSub[i].ratio,
                                status = json.wostatus,
                                isbott = json.WorkListSub[i].isbott,
                                isend = json.WorkListSub[i].isend,
@@ -1382,7 +1382,7 @@
                    if (worksteplist[i].data_sources == "ERP")  //数据来源ERP
                    {
                        //查询当前工单可修改数量=订单总数-已下达工单总数
                        sql = @"select isnull(plan_qty,0) as plan_qty   from TK_Wrk_Man 
                            where sourceid=@sourceid and m_po=@sourcewo and wo_code=@wocode";
@@ -1421,7 +1421,7 @@
                    // å‘字典中添加数据
                    dict.Add("canupdate_qty", canupdate_qty);
                    dict.Add("stepdata", data1);
                    dict.Add("stepdata", data1);
                    // å°†å­—典添加到列表中
                    dir.Add(dict);
                    mes.code = "200";
@@ -1478,7 +1478,7 @@
                                materiel_code = json[i].partcode,
                                route_code = route_code,
                                sourceid = json[i].sourceid == "" ? null : json[i].sourceid, //无源单时赋值NULL
                                saleOrderCode=json[i].saleordercode,
                                saleOrderCode = json[i].saleordercode,
                                m_po = json[i].sourcewo,
                                saleOrderDeliveryDate = json[i].deliverydate,
                                plan_startdate = json[i].paystartdate,
@@ -1506,7 +1506,7 @@
                                    stepprice = json[i].WorkListSub[j].stepprice,
                                    plan_quantity = json[i].woqty,
                                    plan_qty = json[i].WorkListSub[j].sumqty,
                                    ratio=json[i].WorkListSub[j].ratio,
                                    ratio = json[i].WorkListSub[j].ratio,
                                    status = json[i].wostatus,
                                    isbott = json[i].WorkListSub[j].isbott,
                                    isend = json[i].WorkListSub[j].isend,
@@ -1555,7 +1555,7 @@
                                materiel_code = json[i].partcode,
                                route_code = route_code,
                                sourceid = json[i].sourceid == "" ? null : json[i].sourceid, //无源单时赋值NULL
                                saleOrderCode=json[i].saleordercode,
                                saleOrderCode = json[i].saleordercode,
                                m_po = json[i].sourcewo,
                                saleOrderDeliveryDate = json[i].deliverydate,
                                plan_startdate = json[i].paystartdate,
@@ -1592,7 +1592,7 @@
                                    stepprice = json[i].WorkListSub[j].stepprice,
                                    plan_quantity = json[i].woqty,
                                    plan_qty = json[i].WorkListSub[j].sumqty,
                                    ratio=json[i].WorkListSub[j].ratio,
                                    ratio = json[i].WorkListSub[j].ratio,
                                    status = json[i].wostatus,
                                    isbott = json[i].WorkListSub[j].isbott,
                                    isend = json[i].WorkListSub[j].isend,
@@ -2034,8 +2034,8 @@
                    {
                        wocode = wocodelist,
                        status = "ALLO",
                        distributionuser=us.usercode,
                        distributiontime= DateTime.Now.ToString()
                        distributionuser = us.usercode,
                        distributiontime = DateTime.Now.ToString()
                    }
                });
                //更新工序任务表状态
@@ -2276,7 +2276,7 @@
        #region[生产开报工扫码获取工单对应工序任务(自制)]
        public static ToMessage MesOrderStepSearch(string wkshopcode, string wocode,string orderno,string saorderno, string partcode, string partname, string partspec, int startNum, int endNum, string prop, string order)
        public static ToMessage MesOrderStepSearch(string wkshopcode, string wocode, string orderno, string saorderno, string partcode, string partname, string partspec, int startNum, int endNum, string prop, string order)
        {
            var sql = "";
            string search = "";
@@ -2389,7 +2389,7 @@
        #endregion
        #region[生产开报工扫码获取工单对应工序任务(外协)]
        public static ToMessage MesOrderWxStepSearch(string wkshopcode, string wocode, string orderno,string saorderno, string partcode, string partname, string partspec, int startNum, int endNum, string prop, string order)
        public static ToMessage MesOrderWxStepSearch(string wkshopcode, string wocode, string orderno, string saorderno, string partcode, string partname, string partspec, int startNum, int endNum, string prop, string order)
        {
            var sql = "";
            string search = "";
@@ -2505,7 +2505,7 @@
        #endregion
        #region[生产开报工扫码获取工单对应工序任务(不良)]
        public static ToMessage MesOrderNgStepSearch(string wkshopcode, string wocode, string orderno,string saorderno, string partcode, string partname, string partspec, int startNum, int endNum, string prop, string order)
        public static ToMessage MesOrderNgStepSearch(string wkshopcode, string wocode, string orderno, string saorderno, string partcode, string partname, string partspec, int startNum, int endNum, string prop, string order)
        {
            var sql = "";
            string search = "";
@@ -2647,7 +2647,7 @@
                    rt.stepname = data.Rows[0]["stepname"].ToString(); //工序名称
                    rt.stepdesc = data.Rows[0]["descr"].ToString(); //工序描述
                    rt.planqty = decimal.Parse(data.Rows[0]["plan_qty"].ToString()); //任务超产总数量
                    rt.planquantity= decimal.Parse(data.Rows[0]["plan_quantity"].ToString()); //任务数量
                    rt.planquantity = decimal.Parse(data.Rows[0]["plan_quantity"].ToString()); //任务数量
                    rt.noreportqty = decimal.Parse(data.Rows[0]["good_qty"].ToString()); //报工数量
                    rt.noputqty = decimal.Parse(data.Rows[0]["ng_qty"].ToString()); //不良数量
                    string isend = data.Rows[0]["isend"].ToString();//末道工序
@@ -2741,7 +2741,7 @@
                        else //不按序收发料
                        {
                            mes = ScanStartReport.NoWXEncodingSeach(SelectType, wocode, stepcode);
                        }
                        }
                        break;
                    default:
                        break;
@@ -2759,7 +2759,7 @@
        #endregion
        #region[生产开报工,报工提交]
        public static ToMessage SavaMesOrderStepReport(string mesordercode, string partcode, string stepseq, string stepcode, string stepprice, string eqpcode, string inbarcode, string reckway, string usergroupcode, string reportuser,string payrate, string taskqty, string startqty, string reportqty, List<ReportDefectList> defectobjs, string remarks, User us)
        public static ToMessage SavaMesOrderStepReport(string mesordercode, string partcode, string stepseq, string stepcode, string stepprice, string eqpcode, string inbarcode, string reckway, string usergroupcode, string reportuser, string payrate, string taskqty, string startqty, string reportqty, List<ReportDefectList> defectobjs, string remarks, User us)
        {
            var sql = "";
            decimal ngqty = 0;
@@ -2865,7 +2865,7 @@
                        {
                            m_id = int.Parse(dt.Rows[0]["ID"].ToString()),
                            eqp_code = eqpcode,
                            payrate= payrate,
                            payrate = payrate,
                            report_person = reportuser,
                            report_date = date,
                            report_qty = reportqty,
@@ -2883,9 +2883,9 @@
                        //写入缺陷记录表
                        for (int i = 0; i < groupedItems.Count; i++)
                        {
                            sql = @"insert into  CSR_WorkRecord_Defect(record_id,wo_code,partnumber,step_seq,step_code,defect_qty,defect_pendqty,defect_code,remarks,style,lm_user,lm_date)
                                values(@record_id,@wo_code,@partcode,@stepseq,@stepcode,@ngqty,@defect_pendqty,@defect_code,@remarks,@style,@lm_user,@lm_date)";
                            list.Add(new { str = sql, parm = new { record_id = int.Parse(dt.Rows[0]["ID"].ToString()), wo_code = mesordercode, partcode = partcode, stepseq = stepseq, stepcode = stepcode, ngqty = groupedItems[i].badqty, defect_pendqty = groupedItems[i].badqty, defect_code = groupedItems[i].defect_code, remarks = remarks, style = "B", lm_user = us.usercode, lm_date = date } });
                            sql = @"insert into  CSR_WorkRecord_Defect(record_id,wo_code,partnumber,step_seq,step_code,defect_qty,defect_pendqty,defect_code,report_person,usergroup_code,remarks,style,lm_user,lm_date)
                                values(@record_id,@wo_code,@partcode,@stepseq,@stepcode,@ngqty,@defect_pendqty,@defect_code,@report_person,@usergroup_code,@remarks,@style,@lm_user,@lm_date)";
                            list.Add(new { str = sql, parm = new { record_id = int.Parse(dt.Rows[0]["ID"].ToString()), wo_code = mesordercode, partcode = partcode, stepseq = stepseq, stepcode = stepcode, ngqty = groupedItems[i].badqty, defect_pendqty = groupedItems[i].badqty, defect_code = groupedItems[i].defect_code,report_person=reportuser,usergroup_code= usergroupcode, remarks = remarks, style = "B", lm_user = us.usercode, lm_date = date } });
                        }
                    }
@@ -2910,7 +2910,7 @@
                        {
                            m_id = int.Parse(dt.Rows[0]["ID"].ToString()),
                            eqp_code = eqpcode,
                            payrate= payrate,
                            payrate = payrate,
                            report_person = reportuser,
                            report_date = date,
                            report_qty = reportqty,
@@ -2928,9 +2928,9 @@
                        //写入缺陷记录表
                        for (int i = 0; i < groupedItems.Count; i++)
                        {
                            sql = @"insert into  CSR_WorkRecord_Defect(record_id,wo_code,partnumber,step_seq,step_code,defect_qty,defect_pendqty,defect_code,remarks,style,lm_user,lm_date)
                                values(@record_id,@wo_code,@partcode,@stepseq,@stepcode,@ngqty,@defect_pendqty,@defect_code,@remarks,@style,@lm_user,@lm_date)";
                            list.Add(new { str = sql, parm = new { record_id = int.Parse(dt.Rows[0]["ID"].ToString()), wo_code = mesordercode, partcode = partcode, stepseq = stepseq, stepcode = stepcode, ngqty = groupedItems[i].badqty, defect_pendqty = groupedItems[i].badqty, defect_code = groupedItems[i].defect_code, remarks = remarks, style = "B", lm_user = us.usercode, lm_date = date } });
                            sql = @"insert into  CSR_WorkRecord_Defect(record_id,wo_code,partnumber,step_seq,step_code,defect_qty,defect_pendqty,defect_code,report_person,usergroup_code,remarks,style,lm_user,lm_date)
                                values(@record_id,@wo_code,@partcode,@stepseq,@stepcode,@ngqty,@defect_pendqty,@defect_code,@report_person,@usergroup_code,@remarks,@style,@lm_user,@lm_date)";
                            list.Add(new { str = sql, parm = new { record_id = int.Parse(dt.Rows[0]["ID"].ToString()), wo_code = mesordercode, partcode = partcode, stepseq = stepseq, stepcode = stepcode, ngqty = groupedItems[i].badqty, defect_pendqty = groupedItems[i].badqty, defect_code = groupedItems[i].defect_code, report_person=reportuser, usergroup_code =usergroupcode, remarks = remarks, style = "B", lm_user = us.usercode, lm_date = date } });
                        }
                    }
@@ -3160,9 +3160,9 @@
                        //写入缺陷记录表
                        for (int i = 0; i < groupedItems.Count; i++)
                        {
                            sql = @"insert into  CSR_WorkRecord_Defect(record_id,wo_code,partnumber,step_seq,step_code,defect_qty,defect_pendqty,defect_code,remarks,style,lm_user,lm_date)
                                values(@record_id,@wo_code,@partcode,@stepseq,@stepcode,@ngqty,@defect_pendqty,@defect_code,@remarks,@style,@lm_user,@lm_date)";
                            list.Add(new { str = sql, parm = new { record_id = int.Parse(dt.Rows[0]["ID"].ToString()), wo_code = mesordercode, partcode = partcode, stepseq = stepseq, stepcode = stepcode, ngqty = groupedItems[i].badqty, defect_pendqty = groupedItems[i].badqty, defect_code = groupedItems[i].defect_code, remarks = remarks, style = "S", lm_user = us.usercode, lm_date = date } });
                            sql = @"insert into  CSR_WorkRecord_Defect(record_id,wo_code,partnumber,step_seq,step_code,defect_qty,defect_pendqty,defect_code,report_person,remarks,style,lm_user,lm_date)
                                values(@record_id,@wo_code,@partcode,@stepseq,@stepcode,@ngqty,@defect_pendqty,@defect_code,@report_person,@remarks,@style,@lm_user,@lm_date)";
                            list.Add(new { str = sql, parm = new { record_id = int.Parse(dt.Rows[0]["ID"].ToString()), wo_code = mesordercode, partcode = partcode, stepseq = stepseq, stepcode = stepcode, ngqty = groupedItems[i].badqty, defect_pendqty = groupedItems[i].badqty, defect_code = groupedItems[i].defect_code, report_person=inuser, remarks = remarks, style = "S", lm_user = us.usercode, lm_date = date } });
                        }
                    }
@@ -3187,9 +3187,9 @@
                        //写入缺陷记录表
                        for (int i = 0; i < groupedItems.Count; i++)
                        {
                            sql = @"insert into  CSR_WorkRecord_Defect(record_id,wo_code,partnumber,step_seq,step_code,defect_qty,defect_pendqty,defect_code,remarks,style,lm_user,lm_date)
                                values(@record_id,@wo_code,@partcode,@stepseq,@stepcode,@ngqty,@defect_pendqty,@defect_code,@remarks,@style,@lm_user,@lm_date)";
                            list.Add(new { str = sql, parm = new { record_id = int.Parse(dt.Rows[0]["ID"].ToString()), wo_code = mesordercode, partcode = partcode, stepseq = stepseq, stepcode = stepcode, ngqty = groupedItems[i].badqty, defect_pendqty = groupedItems[i].badqty, defect_code = groupedItems[i].defect_code, remarks = remarks, style = "S", lm_user = us.usercode, lm_date = date } });
                            sql = @"insert into  CSR_WorkRecord_Defect(record_id,wo_code,partnumber,step_seq,step_code,defect_qty,defect_pendqty,defect_code,report_person,remarks,style,lm_user,lm_date)
                                values(@record_id,@wo_code,@partcode,@stepseq,@stepcode,@ngqty,@defect_pendqty,@defect_code,@report_person,@remarks,@style,@lm_user,@lm_date)";
                            list.Add(new { str = sql, parm = new { record_id = int.Parse(dt.Rows[0]["ID"].ToString()), wo_code = mesordercode, partcode = partcode, stepseq = stepseq, stepcode = stepcode, ngqty = groupedItems[i].badqty, defect_pendqty = groupedItems[i].badqty, defect_code = groupedItems[i].defect_code, report_person=inuser, remarks = remarks, style = "S", lm_user = us.usercode, lm_date = date } });
                        }
                    }
@@ -3509,7 +3509,7 @@
        #region[生产执行,报工调整数据查询接口]
        public static ToMessage MesOrderStepVerifySearch(string wkshopcode, string wo_code,string orderno,string saorderno, string partnumber, string partname, string partspec,string stepcode, string reportuser, string reportdateopendate, string reportdateclosedate, int startNum, int endNum, string prop, string order)
        public static ToMessage MesOrderStepVerifySearch(string wkshopcode, string wo_code, string orderno, string saorderno, string partnumber, string partname, string partspec, string stepcode, string reportuser, string reportdateopendate, string reportdateclosedate, int startNum, int endNum, string prop, string order)
        {
            var dynamicParams = new DynamicParameters();
            string search = "";
@@ -4217,7 +4217,7 @@
                                    }
                                }
                            }
                        }
                        }
                    }
                }
                else //不按序
@@ -4225,10 +4225,10 @@
                    //控制逻辑:当前工序报工调整-> (本道工序当前调整合格数+本道工序非当前报工合格总数)<下道工序报工总数(合格+不良+报废)   ==不能小于下道报工总数
                    list.Clear();
                    //判断当前工序是自制工序还是外协工序
                    if (json[0].flw_type.ToString() == "Z")
                    if (json[0].flw_type.ToString() == "Z")
                    {
                       //查询当前报工工序非此次报工:总报工数量、总不良数量、总工废数量、总料废数量
                        //查询当前报工工序非此次报工:总报工数量、总不良数量、总工废数量、总料废数量
                        sql = @"select isnull(sum(good_qty),0) as good_qty,isnull(sum(ng_qty),0) as ng_qty,isnull(sum(laborbad_qty),0) as laborbad_qty,isnull(sum(materielbad_qty),0) as materielbad_qty   
                                from TK_Wrk_Record where wo_code=@wo_code and style='B' and id<>@id and step_code=@step_code";
                        dynamicParams.Add("@wo_code", json[0].wo_code);
@@ -4250,7 +4250,7 @@
                            return mes;
                        }
                    }
                    if (json[0].flw_type.ToString() == "W")
                    if (json[0].flw_type.ToString() == "W")
                    {
                        //获取当前工序、供应商对应的总发料数量
                        sql = @"select isnull(sum(fqty),0) as fqty
@@ -4272,7 +4272,7 @@
                        decimal notthis_ngqty = decimal.Parse(dt.Rows[0]["ng_qty"].ToString());  //当前末道工序非本次报工总数
                        decimal notthis_laborbad_qty = decimal.Parse(dt.Rows[0]["laborbad_qty"].ToString());  //当前末道工序非本次报工工废总数
                        decimal notthis_materielbad_qty = decimal.Parse(dt.Rows[0]["materielbad_qty"].ToString());  //当前末道工序非本次报工料废总数
                       //获取当前末道工序收料总数量:本次修改收料数量+本次修改不良数量+本次修改工废数量+本次修改报工料废数量+当前工序非本次收料总数+当前工序非本次不良总数+当前工序非本次工废总数+当前工序非本次料废总数
                                                                                                                    //获取当前末道工序收料总数量:本次修改收料数量+本次修改不良数量+本次修改工废数量+本次修改报工料废数量+当前工序非本次收料总数+当前工序非本次不良总数+当前工序非本次工废总数+当前工序非本次料废总数
                        decimal updatereportsumqty = this_reportqty + this_ngqty + this_laborbadqty + this_materielbadqty + notthis_reportqty + notthis_ngqty + notthis_laborbad_qty + notthis_materielbad_qty;
                        //判断当前工序供应商收料总数>当前工序供应商对应发料数量
                        if (updatereportsumqty > decimal.Parse(dt_0.Rows[0]["fqty"].ToString()))
@@ -4293,7 +4293,7 @@
                        //回写对应的报工记录子表合格数量、不良数量、报废数量
                        sql = @"update TK_Wrk_RecordSub set report_qty=report_qty+@repair_qty,ng_qty=ng_qty+@ng_qty,laborbad_qty=laborbad_qty+@laborbad_qty,materielbad_qty=materielbad_qty+@materielbad_qty,
                            updatereportuser=@updatereportuser,updatereportdate=@updatereportdate
                            report_person=@report_person,usergroup_code=@usergroup_code,updatereportuser=@updatereportuser,updatereportdate=@updatereportdate
                            where  m_id=@m_id and id=@id and style='B'";
                        list.Add(new
                        {
@@ -4307,6 +4307,8 @@
                                laborbad_qty = this_laborbad_dvalue,
                                materielbad_qty = this_materielbad_dvalue,
                                //bad_money = decimal.Parse(json[i].badmoney_dvalue),
                                report_person = json[0].usercode,
                                usergroup_code = json[0].groupcode,
                                updatereportuser = us.usercode,
                                updatereportdate = date
                            }
@@ -4320,7 +4322,7 @@
                            str = sql,
                            parm = new
                            {
                                step_price=decimal.Parse(json[0].unprice),
                                step_price = decimal.Parse(json[0].unprice),
                                good_qty = decimal.Parse(json[0].report_dvalue),
                                ng_qty = this_ng_dvalue,
                                laborbad_qty = this_laborbad_dvalue,
@@ -4353,7 +4355,7 @@
                        {
                            //回写不良
                            sql = @"update CSR_WorkRecord_Defect set defect_qty=defect_qty+@ng_qty,defect_pendqty=defect_pendqty+@ng_qty,laborbad_qty=laborbad_qty+@laborbad_qty,materielbad_qty=materielbad_qty+@materielbad_qty,
                                updatereportuser=@updatereportuser,updatereportdate=@updatereportdate
                                report_person=@report_person,usergroup_code=@usergroup_code,updatereportuser=@updatereportuser,updatereportdate=@updatereportdate
                                where wo_code=@wo_code and step_code=@step_code and id=@ng_id and record_id=@record_id and style='B'";
                            list.Add(new
                            {
@@ -4367,6 +4369,8 @@
                                    step_code = json[0].step_code,
                                    ng_id = int.Parse(json[0].children[i].ng_id),
                                    record_id = json[0].id,
                                    report_person = json[0].usercode,
                                    usergroup_code = json[0].groupcode,
                                    updatereportuser = us.usercode,
                                    updatereportdate = date
                                }
@@ -4395,7 +4399,7 @@
                        //回写对应的外协记录子表收料数量、不良数量、报废数量
                        sql = @"update TK_Wrk_OutRecordSub set sqty=sqty+@repair_qty,ng_qty=ng_qty+@ng_qty,laborbad_qty=laborbad_qty+@laborbad_qty,materielbad_qty=materielbad_qty+@materielbad_qty,
                            updatereportuser=@updatereportuser,updatereportdate=@updatereportdate
                            out_person=@out_person,updatereportuser=@updatereportuser,updatereportdate=@updatereportdate
                            where  m_id=@m_id and id=@id and style='S' and wx_code=@wx_code";
                        list.Add(new
                        {
@@ -4410,6 +4414,7 @@
                                materielbad_qty = this_materielbad_dvalue,
                                wx_code = json[0].wxcode,
                                //bad_money = decimal.Parse(json[i].badmoney_dvalue),
                                out_person = json[0].usercode,
                                updatereportuser = us.usercode,
                                updatereportdate = date
                            }
@@ -4423,7 +4428,7 @@
                            str = sql,
                            parm = new
                            {
                                step_price=decimal.Parse(json[0].unprice),
                                step_price = decimal.Parse(json[0].unprice),
                                good_qty = decimal.Parse(json[0].report_dvalue),
                                ng_qty = this_ng_dvalue,
                                laborbad_qty = this_laborbad_dvalue,
@@ -4457,7 +4462,7 @@
                        {
                            //回写不良
                            sql = @"update CSR_WorkRecord_Defect set defect_qty=defect_qty+@ng_qty,defect_pendqty=defect_pendqty+@ng_qty,laborbad_qty=laborbad_qty+@laborbad_qty,materielbad_qty=materielbad_qty+@materielbad_qty,
                                updatereportuser=@updatereportuser,updatereportdate=@updatereportdate
                                report_person=@report_person,usergroup_code=@usergroup_code,updatereportuser=@updatereportuser,updatereportdate=@updatereportdate
                                where wo_code=@wo_code and step_code=@step_code and id=@ng_id and record_id=@record_id and style='S'";
                            list.Add(new
                            {
@@ -4471,6 +4476,8 @@
                                    step_code = json[0].step_code,
                                    ng_id = int.Parse(json[0].children[i].ng_id),
                                    record_id = json[0].id,
                                    report_person = json[0].usercode,
                                    usergroup_code = json[0].groupcode,
                                    updatereportuser = us.usercode,
                                    updatereportdate = date
                                }
@@ -4606,7 +4613,7 @@
                    if (aa)
                    {
                        //写入操作记录表
                        LogHelper.DbOperateLog(us.usercode, "批量修改工价", "报工ID:" + string.Join(",",json.Select(bp => bp.id).ToArray())+",操作类型:"+ string.Join(",", json.Select(bp => bp.type).ToArray()), us.usertype);
                        LogHelper.DbOperateLog(us.usercode, "批量修改工价", "报工ID:" + string.Join(",", json.Select(bp => bp.id).ToArray()) + ",操作类型:" + string.Join(",", json.Select(bp => bp.type).ToArray()), us.usertype);
                        mes.code = "200";
                        mes.count = 0;
                        mes.message = "操作成功!";
@@ -4620,7 +4627,7 @@
                        mes.data = null;
                    }
                }
                else
                else
                {
                    mes.code = "300";
                    mes.count = 0;
@@ -4641,7 +4648,7 @@
        #region[生产执行,报工审核列表数据查询接口]
        public static ToMessage MesOrderStepReportVerifySearch(string reviewstatus, string wkshopcode, string wo_code, string orderno,string saorderno, string partnumber, string partname, string partspec, string stepname, string reportuser, string reportdateopendate, string reportdateclosedate, int startNum, int endNum, string prop, string order)
        public static ToMessage MesOrderStepReportVerifySearch(string reviewstatus, string wkshopcode, string wo_code, string orderno, string saorderno, string partnumber, string partname, string partspec, string stepname, string reportuser, string reportdateopendate, string reportdateclosedate, int startNum, int endNum, string prop, string order)
        {
            var dynamicParams = new DynamicParameters();
            string search = "";
VueWebCoreApi/Quartz/HttpManager.cs
@@ -4,6 +4,7 @@
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace VueWebCoreApi.Quartz
@@ -12,62 +13,182 @@
    {
        public static string GetUserIP(IHttpContextAccessor httpContextAccessor)
        {
            var Request = httpContextAccessor.HttpContext.Request;
            string realIP = null;
            string forwarded = null;
            string remoteIpAddress = httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString();
            if (Request.Headers.ContainsKey("X-Real-IP"))
            if (httpContextAccessor?.HttpContext == null)
                return string.Empty;
            var request = httpContextAccessor.HttpContext.Request;
            string remoteIpAddress = httpContextAccessor.HttpContext.Connection.RemoteIpAddress?.ToString() ?? string.Empty;
            if (request.Headers.ContainsKey("X-Real-IP"))
            {
                realIP = Request.Headers["X-Real-IP"].ToString();
                if (realIP != remoteIpAddress)
                var realIp = request.Headers["X-Real-IP"].ToString();
                if (!string.IsNullOrEmpty(realIp) && realIp != remoteIpAddress)
                {
                    remoteIpAddress = realIP;
                    remoteIpAddress = realIp;
                }
            }
            if (Request.Headers.ContainsKey("X-Forwarded-For"))
            if (request.Headers.ContainsKey("X-Forwarded-For"))
            {
                forwarded = Request.Headers["X-Forwarded-For"].ToString();
                if (forwarded != remoteIpAddress)
                var forwarded = request.Headers["X-Forwarded-For"].ToString();
                if (!string.IsNullOrEmpty(forwarded) && forwarded != remoteIpAddress)
                {
                    remoteIpAddress = forwarded;
                }
            }
            return remoteIpAddress;
        }
        public static async Task<string> HttpSendAsync(this IHttpClientFactory httpClientFactory, HttpMethod method, string url, Dictionary<string, string> headers = null)
        public static async Task<string> HttpSendAsyncString(this IHttpClientFactory httpClientFactory, HttpMethod method, string url, string parameters = null, Dictionary<string, string> headers = null)
        {
            if (httpClientFactory == null)
                throw new ArgumentNullException(nameof(httpClientFactory));
            if (method == null)
                throw new ArgumentNullException(nameof(method));
            if (string.IsNullOrEmpty(url))
                throw new ArgumentNullException(nameof(url));
            var client = httpClientFactory.CreateClient();
            var content = new StringContent("");
            //content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            //content.Headers.ContentType.CharSet = "utf-8";
            var request = new HttpRequestMessage(method, url)
            {
                Content = content
            };
            if (headers != null)
            {
                foreach (var header in headers)
                {
                    request.Headers.Add(header.Key, header.Value);
                }
            }
            string finalUrl = url;
            HttpContent content = new StringContent("");
            try
            {
                HttpResponseMessage httpResponseMessage = await client.SendAsync(request);
                // ====================== GET è¯·æ±‚:拼接参数到 URL ======================
                if (method == HttpMethod.Get && !string.IsNullOrWhiteSpace(parameters))
                {
                    finalUrl = url.Contains("?")
                        ? $"{url}&{parameters}"
                        : $"{url}?{parameters}";
                }
                var result = await httpResponseMessage.Content
                    .ReadAsStringAsync();
                return result;
                // ====================== POST è¯·æ±‚:参数放入 Body ======================
                if (method == HttpMethod.Post && !string.IsNullOrWhiteSpace(parameters))
                {
                    content = new StringContent(
                        parameters,
                        System.Text.Encoding.UTF8,
                        "application/x-www-form-urlencoded");
                }
                var request = new HttpRequestMessage(method, finalUrl) { Content = content };
                // è¿½åŠ è¯·æ±‚å¤´
                if (headers != null)
                {
                    foreach (var header in headers)
                    {
                        if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value))
                        {
                            content.Headers.TryAddWithoutValidation(header.Key, header.Value);
                        }
                    }
                }
                using (var response = await client.SendAsync(request))
                {
                    response.EnsureSuccessStatusCode();
                    return await response.Content.ReadAsStringAsync();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                return ex.Message;
                return $"请求失败:{ex.Message}";
            }
        }
        public static async Task<HttpResult> HttpSendAsync(this IHttpClientFactory httpClientFactory,HttpMethod method,string url,string parameters = null,Dictionary<string, string> headers = null)
        {
            if (httpClientFactory == null)
                throw new ArgumentNullException(nameof(httpClientFactory));
            if (method == null)
                throw new ArgumentNullException(nameof(method));
            if (string.IsNullOrEmpty(url))
                throw new ArgumentNullException(nameof(url));
            var client = httpClientFactory.CreateClient();
            string finalUrl = url;
            HttpContent content = new StringContent("");
            try
            {
                // GET æ‹¼æŽ¥å‚æ•°
                if (method == HttpMethod.Get && !string.IsNullOrWhiteSpace(parameters))
                {
                    finalUrl = url.Contains("?") ? $"{url}&{parameters}" : $"{url}?{parameters}";
                }
                // POST å¤„理参数
                if (method == HttpMethod.Post && !string.IsNullOrWhiteSpace(parameters))
                {
                    content = new StringContent(parameters, Encoding.UTF8, "application/x-www-form-urlencoded");
                }
                var request = new HttpRequestMessage(method, finalUrl) { Content = content };
                // è¯·æ±‚头
                if (headers != null)
                {
                    foreach (var header in headers)
                    {
                        if (!request.Headers.TryAddWithoutValidation(header.Key, header.Value))
                        {
                            content.Headers.TryAddWithoutValidation(header.Key, header.Value);
                        }
                    }
                }
                using (var response = await client.SendAsync(request))
                {
                    // è¯»å–返回内容
                    string responseContent = await response.Content.ReadAsStringAsync();
                    return new HttpResult
                    {
                        IsSuccess = response.IsSuccessStatusCode, // æ˜¯å¦æˆåŠŸ
                        StatusCode = (int)response.StatusCode,    // çŠ¶æ€ç  200/404/500
                        Content = responseContent,                // è¿”回内容
                        ErrorMsg = response.IsSuccessStatusCode ? "" : $"HTTP请求失败,状态码:{(int)response.StatusCode}"
                    };
                }
            }
            catch (Exception ex)
            {
                // ç½‘络异常、超时等
                return new HttpResult
                {
                    IsSuccess = false,
                    StatusCode = 0, // ç½‘络异常状态码设为0
                    Content = "",
                    ErrorMsg = ex.Message
                };
            }
        }
        /// <summary>
        /// HTTP è¯·æ±‚统一返回模型
        /// </summary>
        public class HttpResult
        {
            /// <summary>
            /// æ˜¯å¦è¯·æ±‚成功(200-299)
            /// </summary>
            public bool IsSuccess { get; set; }
            /// <summary>
            /// HTTP çŠ¶æ€ç ï¼ˆæ•°å­—ï¼š200,404,500)
            /// </summary>
            public int StatusCode { get; set; }
            /// <summary>
            /// è¿”回内容
            /// </summary>
            public string Content { get; set; }
            /// <summary>
            /// é”™è¯¯ä¿¡æ¯
            /// </summary>
            public string ErrorMsg { get; set; }
        }
    }
}
VueWebCoreApi/Quartz/HttpResultfulJob.cs
@@ -12,83 +12,157 @@
using VueWebCoreApi.Extensions;
using VueWebCoreApi.SignalR;
using VueWebCoreApi.Tools;
using static VueWebCoreApi.Quartz.HttpManager;
namespace VueWebCoreApi.Quartz
{
    /// <summary>
    /// å®šæ—¶ä»»åŠ¡æ‰§è¡Œç±»ï¼šQuartz到时间后,自动运行这个类的Execute方法
    /// ä½œç”¨ï¼šå‘送HTTP请求、记录任务日志、推送执行结果
    /// </summary>
    public class HttpResultfulJob : IJob
    {
        private ILog log = LogManager.GetLogger(Startup.repository.Name, typeof(ChatHub));
        // æ—¥å¿—记录工具(log4net)
        private readonly ILog _log = LogManager.GetLogger(Startup.repository.Name, typeof(ChatHub));
        // SignalR å®žæ—¶æŽ¨é€å·¥å…·ï¼ˆç»™å‰ç«¯æŽ¨é€æ¶ˆæ¯ï¼‰
        private readonly IHubContext<ChatHub, IChatClient> _hubContext;
        // HttpClient å·¥åŽ‚ï¼šç”¨äºŽå‘é€ HTTP è¯·æ±‚
        private readonly IHttpClientFactory _httpClientFactory;
        // æ•°æ®åº“仓储:用于记录任务运行日志、更新最后执行时间
        private readonly QuartzRepository _quartzRepo;
        readonly IHttpClientFactory httpClientFactory;
        /// <summary>
        /// 2020.05.31增加构造方法
        /// æž„造函数:依赖注入(自动赋值所有需要的服务)
        /// </summary>
        /// <param name="serviceProvider"></param>
        /// <param name="httpClientFactory"></param>
        public HttpResultfulJob(IServiceProvider serviceProvider, IHttpClientFactory httpClientFactory, IHubContext<ChatHub, IChatClient> hubContext)
        public HttpResultfulJob(IServiceProvider serviceProvider,
                                IHttpClientFactory httpClientFactory,
                                IHubContext<ChatHub, IChatClient> hubContext,
                                QuartzRepository quartzRepo)
        {
            this.httpClientFactory = httpClientFactory;
            _httpClientFactory = httpClientFactory;
            _hubContext = hubContext;
            //serviceProvider.GetService()
            _quartzRepo = quartzRepo;
        }
        /// <summary>
        /// ã€æ ¸å¿ƒæ–¹æ³•】
        /// Quartz è°ƒåº¦å™¨æ ¹æ® Cron è¡¨è¾¾å¼ï¼Œåˆ°æ—¶é—´åŽ**自动调用**这个方法
        /// ä½œç”¨ï¼šæ‰§è¡Œå…·ä½“的任务逻辑(发送HTTP请求)
        /// </summary>
        public async Task Execute(IJobExecutionContext context)
        {
            DateTime dateTime = DateTime.Now;
            TaskOptions taskOptions = context.GetTaskOptions();
            string httpMessage = "";
            AbstractTrigger trigger = (context as JobExecutionContextImpl).Trigger as AbstractTrigger;
            // è®°å½•任务开始执行时间
            var beginTime = DateTime.Now;
            // ä»Ž Quartz ä¸Šä¸‹æ–‡èŽ·å–å½“å‰è¦æ‰§è¡Œçš„ä»»åŠ¡é…ç½®ï¼ˆåç§°ã€åˆ†ç»„ã€URL、Cron等)
            var taskOptions = context.GetTaskOptions();
            // å­˜å‚¨ HTTP è¯·æ±‚返回的结果信息
            string httpMessage = string.Empty;
            HttpResult result = new HttpResult();
            // ==========================================
            // ç¬¬ä¸€æ­¥ï¼šæ ¡éªŒä»»åŠ¡æ˜¯å¦å­˜åœ¨
            // ==========================================
            if (taskOptions == null)
            {
                FileHelper.WriteFile(FileQuartz.LogPath + trigger.Group, $"{trigger.Name}.txt", "未到找作业或可能被移除", true);
                // å¦‚果任务为空,记录错误日志到数据库
                var trigger = context.Trigger;
                await _quartzRepo.AddJobRunLogAsync(trigger.Key.Name, trigger.Key.Group, beginTime, DateTime.Now, "未找到作业或可能被移除", taskOptions.MessagePush, taskOptions.PushUserCode);
                return;
            }
            Console.WriteLine($"作业[{taskOptions.TaskName}]开始:{ DateTime.Now.ToString("yyyy-MM-dd HH:mm:sss")}");
            // ç³»ç»Ÿæ—¥å¿—输出:任务开始执行
            _log.Info($"作业[{taskOptions.TaskName}]开始:{beginTime:yyyy-MM-dd HH:mm:ss}");
            // ==========================================
            // ç¬¬äºŒæ­¥ï¼šæ ¡éªŒä»»åŠ¡æ˜¯å¦é…ç½®äº†è¯·æ±‚åœ°å€
            // ==========================================
            if (string.IsNullOrEmpty(taskOptions.ApiUrl) || taskOptions.ApiUrl == "/")
            {
                FileHelper.WriteFile(FileQuartz.LogPath + trigger.Group, $"{trigger.Name}.txt", $"{ DateTime.Now.ToString("yyyy-MM-dd HH:mm:sss")}未配置url,", true);
                // æœªé…ç½®API地址,记录日志
                await _quartzRepo.AddJobRunLogAsync(taskOptions.TaskName, taskOptions.GroupName, beginTime, DateTime.Now, "未配置URL", taskOptions.MessagePush, taskOptions.PushUserCode);
                return;
            }
            // ==========================================
            // ç¬¬ä¸‰æ­¥ï¼šå‘送 HTTP è¯·æ±‚(核心业务)
            // ==========================================
            try
            {
                Dictionary<string, string> header = new Dictionary<string, string>();
                // å­˜å‚¨è¯·æ±‚头(身份验证信息)
                var headers = new Dictionary<string, string>();
                // å¦‚果配置了 AuthKey å’Œ AuthValue,添加到请求头
                if (!string.IsNullOrEmpty(taskOptions.AuthKey) && !string.IsNullOrEmpty(taskOptions.AuthValue))
                {
                    header.Add(taskOptions.AuthKey.Trim(), taskOptions.AuthValue.Trim());
                    headers.Add(taskOptions.AuthKey.Trim(), taskOptions.AuthValue.Trim());
                }
                httpMessage = await httpClientFactory.HttpSendAsync(taskOptions.RequestType?.ToLower() == "get" ? HttpMethod.Get : HttpMethod.Post, taskOptions.ApiUrl, header);
                // åˆ¤æ–­è¯·æ±‚方式:GET æˆ– POST(默认POST)
                var method = taskOptions.RequestType?.ToLower() == "get" ? HttpMethod.Get : HttpMethod.Post;
                // è°ƒç”¨å·¥å…·ç±»ï¼Œå‘送 HTTP è¯·æ±‚,并获取返回结果
                result = await _httpClientFactory.HttpSendAsync(method, taskOptions.ApiUrl, taskOptions.RequestParameters, headers);
            }
            catch (Exception ex)
            {
                httpMessage = ex.Message;
                // å¦‚果请求异常(网络错误、接口报错),记录异常信息
                result.ErrorMsg = ex.Message;
                _log.Error($"作业[{taskOptions.TaskName}]执行异常:{ex.Message}", ex);
            }
            try
            {
                string logContent = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}_{dateTime.ToString("yyyy-MM-dd HH:mm:ss")}_{(string.IsNullOrEmpty(httpMessage) ? "OK" : httpMessage)}\r\n";
                FileHelper.WriteFile(FileQuartz.LogPath + taskOptions.GroupName + "\\", $"{taskOptions.TaskName}.txt", logContent, true);
                //await _hubContext.Clients.All.SendAsync("SendMessage系统通知:"+$"最新消息{DateTime.Now}");
                //await _hubContext.Clients.All.SendAll(logContent);
                //查找系统用户
                var sql = @"select usercode as code,username as name
                            from TUser
                            where  is_delete='0' and enable='Y' and password='123'";
                var data = DapperHelper.selecttable(sql);
                var departmentIDs = data.AsEnumerable().Select(x => x.Field<string>("code")).ToList();//获取推送人员编码
                var FindPublicBaseDic = UserIdsStore.Ids.Where(d => departmentIDs.Contains(d.Value)).Select(x => x.Key).ToList(); //匹配已经登录的推送人员connectionIds
                if (FindPublicBaseDic.Count > 0)
                // 1. æŠŠæœ¬æ¬¡æ‰§è¡Œè®°å½•写入 SQL Server æ•°æ®åº“(任务运行日志表)
                await _quartzRepo.AddJobRunLogAsync(taskOptions.TaskName, taskOptions.GroupName, beginTime, DateTime.Now,result.StatusCode==200?result.Content.ToString():result.ErrorMsg, taskOptions.MessagePush, taskOptions.PushUserCode);
                // 2. æ›´æ–°ä»»åŠ¡è¡¨ä¸­çš„ã€æœ€åŽæ‰§è¡Œæ—¶é—´ã€‘
                await _quartzRepo.UpdateTaskLastRunTimeAsync(taskOptions.TaskName, taskOptions.GroupName, beginTime);
                // ==========================================
                // ä»¥ä¸‹æ˜¯ SignalR å®žæ—¶æŽ¨é€ï¼ˆç»™å‰ç«¯é¡µé¢å‘消息)
                // ==========================================
                //判断请求是否成功
                if (result.IsSuccess)
                {
                    //推送指定用户
                    await _hubContext.Clients.Clients(FindPublicBaseDic).SendCustomUserMessage(logContent);
                    //写入任务子表
                }
                if (taskOptions.MessagePush == "N"||taskOptions.PushUserCode==null) //是否需要推送
                {
                    _log.Info($"作业[{taskOptions.TaskName}]无需要推送的用户,跳过SignalR推送");
                    return;
                }
                //步骤2:提取用户编码(去重 + ç©ºå€¼è¿‡æ»¤ï¼‰
                var departmentIDs = taskOptions.PushUserCode.Split(',').ToList();
                if (departmentIDs.Count == 0)
                {
                    _log.Info($"作业[{taskOptions.TaskName}]查询到的用户编码为空,跳过SignalR推送");
                    return;
                }
                // æ­¥éª¤3:获取在线用户的连接ID(使用线程安全方法)
                var targetConnIds = UserIdsStore.GetConnectionIdsByUserCodes(departmentIDs);
                if (targetConnIds.Count == 0)
                {
                    _log.Info($"作业[{taskOptions.TaskName}]目标用户均不在线,跳过SignalR推送");
                    return;
                }
                // æ­¥éª¤4:构造推送消息(标准化格式)
                var logContent = $"【{taskOptions.TaskName}】执行结果:{beginTime:yyyy-MM-dd HH:mm:ss} è‡³ {DateTime.Now:yyyy-MM-dd HH:mm:ss} | ç»“果:{(string.IsNullOrEmpty(httpMessage) ? "执行成功" : httpMessage)}";
                // æ­¥éª¤5:批量推送(增加超时+异常捕获)
                _log.Info($"作业[{taskOptions.TaskName}]开始向[{targetConnIds.Count}]个在线连接推送消息:{logContent}");
                // è®¾ç½®æŽ¨é€è¶…时时间(避免长时间阻塞)
                var pushTask = _hubContext.Clients.Clients(targetConnIds).SendCustomUserMessage(logContent);
                if (await Task.WhenAny(pushTask, Task.Delay(5000)) == pushTask)
                {
                    await pushTask; // æŽ¨é€æˆåŠŸ
                    _log.Info($"作业[{taskOptions.TaskName}]SignalR推送完成,目标连接数:{targetConnIds.Count}");
                }
                else
                {
                    _log.Warn($"作业[{taskOptions.TaskName}]SignalR推送超时(5秒),目标连接数:{targetConnIds.Count}");
                }
            }
            catch (Exception)
            catch (Exception ex)
            {
                // è®°å½•日志写入或推送失败的异常
                _log.Error($"作业[{taskOptions.TaskName}]日志写入/SignalR推送异常:{ex.Message}", ex);
            }
            Console.WriteLine(trigger.FullName + " " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:sss") + " " + httpMessage);
            return;
            // ç³»ç»Ÿæ—¥å¿—输出:任务执行结束
            _log.Info($"作业[{taskOptions.TaskName}]结束:{DateTime.Now:yyyy-MM-dd HH:mm:ss} ç»“æžœ:{result.ErrorMsg}");
        }
    }
}
VueWebCoreApi/Quartz/IOCJobFactory.cs
@@ -16,12 +16,14 @@
        }
        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            // ä»ŽDI容器获取Job实例(支持构造函数注入)
            return _serviceProvider.GetService(bundle.JobDetail.JobType) as IJob;
        }
        public void ReturnJob(IJob job)
        {
            // é‡Šæ”¾èµ„源(若需要)
            (job as IDisposable)?.Dispose();
        }
    }
VueWebCoreApi/Quartz/JobAction.cs
@@ -11,8 +11,8 @@
        åˆ é™¤ = 2,
        ä¿®æ”¹ = 3,
        æš‚停 = 4,
        åœæ­¢,
        å¼€å¯,
        ç«‹å³æ‰§è¡Œ
        åœæ­¢ = 5,
        å¼€å¯ = 6,
        ç«‹å³æ‰§è¡Œ = 7
    }
}
VueWebCoreApi/Quartz/JobActionLog.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace VueWebCoreApi.Quartz
{
    public class JobActionLog
    {
        public long Id { get; set; }
        public string ActionType { get; set; }
        public string TaskName { get; set; }
        public string GroupName { get; set; }
        public string Content { get; set; }
        public DateTime CreateTime { get; set; }
    }
}
VueWebCoreApi/Quartz/QuartzDapperHelper.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,135 @@
using Dapper;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Threading.Tasks;
using VueWebCoreApi.Tools;
namespace VueWebCoreApi.Quartz
{
    public static class DapperHelper
    {
        // é…ç½®æ–‡ä»¶è¯»å–,建议放到appsettings.json
        private static readonly string _connectionString = AppSetting.GetAppSetting("DBServer");
        /// <summary>
        /// èŽ·å–æ•°æ®åº“è¿žæŽ¥
        /// </summary>
        public static IDbConnection GetConnection()
        {
            var conn = new SqlConnection(_connectionString);
            if (conn.State != ConnectionState.Open)
                conn.Open();
            return conn;
        }
        /// <summary>
        /// æ‰§è¡ŒæŸ¥è¯¢è¿”回DataTable
        /// </summary>
        public static DataTable SelectTable(string sql, object param = null)
        {
            using (var conn = GetConnection())
            {
                var reader = conn.ExecuteReader(sql, param);
                var dt = new DataTable();
                dt.Load(reader);
                return dt;
            }
        }
        /// <summary>
        /// æ‰§è¡Œå¢žåˆ æ”¹
        /// </summary>
        public static int Execute(string sql, object param = null)
        {
            using (var conn = GetConnection())
            {
                return conn.Execute(sql, param);
            }
        }
        /// <summary>
        /// å¼‚步执行增删改
        /// </summary>
        public static async Task<int> ExecuteAsync(string sql, object param = null)
        {
            using (var conn = GetConnection())
            {
                return await conn.ExecuteAsync(sql, param);
            }
        }
        /// <summary>
        /// æ‰§è¡Œäº‹åŠ¡
        /// </summary>
        public static bool DoTransaction(List<(string Sql, object Param)> list)
        {
            using (var conn = GetConnection())
            {
                using (var tran = conn.BeginTransaction())
                {
                    try
                    {
                        foreach (var item in list)
                        {
                            conn.Execute(item.Sql, item.Param, tran);
                        }
                        tran.Commit();
                        return true;
                    }
                    catch
                    {
                        tran.Rollback();
                        return false;
                    }
                }
            }
        }
        /// <summary>
        /// æŸ¥è¯¢åˆ—表
        /// </summary>
        public static List<T> Query<T>(string sql, object param = null)
        {
            using (var conn = GetConnection())
            {
                return conn.Query<T>(sql, param).ToList();
            }
        }
        /// <summary>
        /// å¼‚步查询列表
        /// </summary>
        public static async Task<List<T>> QueryAsync<T>(string sql, object param = null)
        {
            using (var conn = GetConnection())
            {
                var result = await conn.QueryAsync<T>(sql, param);
                return result.ToList();
            }
        }
        /// <summary>
        /// æŸ¥è¯¢å•个对象
        /// </summary>
        public static T QueryFirstOrDefault<T>(string sql, object param = null)
        {
            using (var conn = GetConnection())
            {
                return conn.QueryFirstOrDefault<T>(sql, param);
            }
        }
        /// <summary>
        /// å¼‚步查询单个对象
        /// </summary>
        public static async Task<T> QueryFirstOrDefaultAsync<T>(string sql, object param = null)
        {
            using (var conn = GetConnection())
            {
                return await conn.QueryFirstOrDefaultAsync<T>(sql, param);
            }
        }
    }
}
VueWebCoreApi/Quartz/QuartzFileInfo.cs
@@ -5,6 +5,9 @@
namespace VueWebCoreApi.Quartz
{
    /// <summary>
    /// Quartz文件存储常量配置
    /// </summary>
    public class QuartzFileInfo
    {
        /// <summary>
VueWebCoreApi/Quartz/QuartzNETExtension.cs
@@ -17,110 +17,142 @@
namespace VueWebCoreApi.Extensions
{
    /// <summary>
    /// Quartz.NET æ‰©å±•方法类
    /// å°è£…Quartz任务的初始化、增删改查、启停、立即执行等核心操作
    /// è§£å†³äº†Scheduler启动、内存与数据库状态同步、Cron表达式兼容、Key匹配等核心问题
    /// </summary>
    public static class QuartzNETExtension
    {
        public static ToMessage mes = new ToMessage(); //定义全局返回信息对象
        /// <summary>
        /// å…¨å±€è¿”回信息对象(统一接口返回格式)
        /// åŒ…含code(状态码)、count(数量)、message(提示信息)、data(数据)
        /// </summary>
        public static ToMessage mes = new ToMessage();
        /// <summary>
        /// å†…存级任务列表缓存
        /// ç”¨äºŽå‡å°‘数据库查询,同时保证内存与数据库状态一致
        /// </summary>
        private static List<TaskOptions> _taskList = new List<TaskOptions>();
        /// <summary>
        /// åˆå§‹åŒ–作业
        /// æ‰©å±•IApplicationBuilder,初始化Quartz任务调度器并加载所有任务
        /// ã€æ ¸å¿ƒä¿®å¤ã€‘解决Quartz默认不启动Scheduler的问题
        /// </summary>
        /// <param name="applicationBuilder"></param>
        /// <param name="env"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseQuartz(this IApplicationBuilder applicationBuilder, IWebHostEnvironment env)
        /// <param name="app">ASP.NET Core应用构建器</param>
        /// <param name="env">Web主机环境</param>
        /// <returns>应用构建器(链式调用)</returns>
        public static IApplicationBuilder UseQuartz(this IApplicationBuilder app, IWebHostEnvironment env)
        {
            IServiceProvider services = applicationBuilder.ApplicationServices;
            // ä»ŽDI容器获取依赖服务
            var services = app.ApplicationServices;
            // Quartz调度器工厂
            var schedulerFactory = services.GetService<ISchedulerFactory>();
            // è‡ªå®šä¹‰Quartz仓储(数据库操作)
            var quartzRepo = services.GetService<QuartzRepository>();
            ISchedulerFactory _schedulerFactory = services.GetService<ISchedulerFactory>();
            string path = FileQuartz.CreateQuartzRootPath(env);
            //查找任务列表
            var data = QuartzSearchData.QuartzSearch();
            if (data.Rows.Count <= 0)
            // 1. æ ¸å¿ƒä¿®å¤ï¼šQuartz默认不会自动启动Scheduler,必须显式启动
            var scheduler = schedulerFactory.GetScheduler().GetAwaiter().GetResult();
            if (!scheduler.IsStarted)
            {
                FileHelper.WriteFile(FileQuartz.LogPath, "start.txt", $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},没有默认配置任务\r\n", true);
                return applicationBuilder;
                scheduler.Start().GetAwaiter().GetResult();
            }
            string jobConfig = JsonConvert.SerializeObject(data);
            //string jobConfig = FileHelper.ReadFile(path + QuartzFileInfo.JobConfigFileName);
            //if (string.IsNullOrEmpty(jobConfig))
            //{
            //    FileHelper.WriteFile(FileQuartz.LogPath, "start.txt", $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")},没有默认配置任务\r\n", true);
            //    return applicationBuilder;
            //}
            // 2. ä»Žæ•°æ®åº“加载所有配置的任务
            var taskList = quartzRepo.GetAllTasksAsync().GetAwaiter().GetResult();
            if (taskList.Count == 0)
            {
                //无任务时记录启动日志
                quartzRepo.WriteStartLogAsync($"{DateTime.Now:yyyy-MM-dd HH:mm:ss},没有默认配置任务").GetAwaiter().GetResult();
                return app;
            }
            // 3. éåŽ†ä»»åŠ¡å¹¶åˆå§‹åŒ–ï¼ˆè®°å½•å¤±è´¥æ•°å’Œå¼‚å¸¸ä¿¡æ¯ï¼‰
            int errorCount = 0;
            string errorMsg = "";
            TaskOptions options = null;
            try
            string errorMsg = string.Empty;
            // åˆå§‹åŒ–内存任务列表
            _taskList = taskList;
            foreach (var task in taskList)
            {
                _taskList = JsonConvert.DeserializeObject<List<TaskOptions>>(jobConfig);
                _taskList.ForEach(x =>
                try
                {
                    options = x;
                    var result = x.AddJob(_schedulerFactory, true, jobFactory: services.GetService<IJobFactory>()).GetAwaiter().GetResult();
                });
                    //var result = task.AddJob(schedulerFactory, true, services.GetService<IJobFactory>()).GetAwaiter().GetResult();
                    // è°ƒç”¨TaskOptions的AddJob方法添加任务到调度器
                    // ä¼ å…¥ä»“储用于数据库操作,传入JobFactory用于自定义Job实例化
                    var result = task.AddJob(schedulerFactory, true, services.GetService<IJobFactory>(), quartzRepo).GetAwaiter().GetResult();
                }
                catch (Exception ex)
                {
                    // è®°å½•单个任务初始化失败信息
                    errorCount++;
                    errorMsg += $"作业:{task.TaskName},异常:{ex.Message};";
                }
            }
            catch (Exception ex)
            {
                errorCount = +1;
                errorMsg += $"作业:{options?.TaskName},异常:{ex.Message}";
            }
            string content = $"成功:{   _taskList.Count - errorCount}个,失败{errorCount}个,异常:{errorMsg}\r\n";
            FileQuartz.WriteStartLog(content);
            // 4. è®°å½•整体初始化结果日志
            var content = $"成功:{taskList.Count - errorCount}个,失败{errorCount}个,异常:{errorMsg}";
            quartzRepo.WriteStartLogAsync($"{DateTime.Now:yyyy-MM-dd HH:mm:ss},{content}").GetAwaiter().GetResult();
            return applicationBuilder;
            return app;
        }
        /// <summary>
        /// èŽ·å–æ‰€æœ‰çš„ä½œä¸š
        /// èŽ·å–è°ƒåº¦å™¨ä¸­æ‰€æœ‰ä»»åŠ¡ï¼Œå¹¶åŒæ­¥å†…å­˜/数据库状态(核心修复:解决内存列表与数据库不一致问题)
        /// </summary>
        /// <param name="schedulerFactory"></param>
        /// <returns></returns>
        public static async Task<List<TaskOptions>> GetJobs(this ISchedulerFactory schedulerFactory)
        /// <param name="schedulerFactory">调度器工厂</param>
        /// <param name="quartzRepo">Quartz仓储</param>
        /// <returns>最新的任务列表</returns>
        public static async Task<List<TaskOptions>> GetJobs(this ISchedulerFactory schedulerFactory, QuartzRepository quartzRepo)
        {
            List<TaskOptions> list = new List<TaskOptions>();
            var list = new List<TaskOptions>();
            try
            {
                IScheduler _scheduler = await schedulerFactory.GetScheduler();
                var groups = await _scheduler.GetJobGroupNames();
                var scheduler = await schedulerFactory.GetScheduler();
                // ç¡®ä¿Scheduler处于启动状态(防御性编程)
                if (!scheduler.IsStarted) await scheduler.Start();
                // 1. èŽ·å–æ‰€æœ‰ä»»åŠ¡åˆ†ç»„ï¼ˆQuartz任务按分组管理)
                var groups = await scheduler.GetJobGroupNames();
                foreach (var groupName in groups)
                {
                    foreach (var jobKey in await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)))
                    // 2. èŽ·å–åˆ†ç»„ä¸‹æ‰€æœ‰JobKey(任务唯一标识:名称+分组)
                    var jobKeys = await scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName));
                    foreach (var jobKey in jobKeys)
                    {
                        TaskOptions taskOptions = _taskList.Where(x => x.GroupName == jobKey.Group && x.TaskName == jobKey.Name)
                            .FirstOrDefault();
                        if (taskOptions == null)
                            continue;
                        var triggers = await _scheduler.GetTriggersOfJob(jobKey);
                        foreach (ITrigger trigger in triggers)
                        // 3. æ ¸å¿ƒä¿®å¤ï¼šä¼˜å…ˆä»Žæ•°æ®åº“获取最新任务(避免内存数据过期)
                        var dbTask = await quartzRepo.TaskExists(jobKey.Name, jobKey.Group);
                        // æ•°æ®åº“无数据时,降级从内存列表获取
                        var task = dbTask.FirstOrDefault() ?? _taskList.FirstOrDefault(x => x.GroupName == jobKey.Group && x.TaskName == jobKey.Name);
                        // è¿‡æ»¤æ— æ•ˆä»»åŠ¡
                        if (task == null) continue;
                        // 4. èŽ·å–ä»»åŠ¡å…³è”çš„è§¦å‘å™¨ï¼ˆQuartz任务通过触发器触发执行)
                        var triggers = await scheduler.GetTriggersOfJob(jobKey);
                        foreach (var trigger in triggers)
                        {
                            DateTimeOffset? dateTimeOffset = trigger.GetPreviousFireTimeUtc();
                            if (dateTimeOffset != null)
                            // 5. åŒæ­¥è§¦å‘器实际状态到任务对象,并更新到数据库
                            task.Status = (int)await scheduler.GetTriggerState(trigger.Key);
                            await quartzRepo.UpdateTaskAsync(task); // çŠ¶æ€åŒæ­¥åˆ°åº“
                            // 6. åŒæ­¥ä»»åŠ¡æœ€åŽæ‰§è¡Œæ—¶é—´
                            if (trigger.GetPreviousFireTimeUtc() != null)
                            {
                                taskOptions.LastRunTime = Convert.ToDateTime(dateTimeOffset.ToString());
                                //更改最后执行时间
                                bool aa = QuartzSearchData.QuartzUpdate(taskOptions);
                                // ä¼˜å…ˆä»Žè§¦å‘器获取最后执行时间(UTC转本地时间)
                                task.LastRunTime = trigger.GetPreviousFireTimeUtc()?.LocalDateTime;
                                await quartzRepo.UpdateTaskLastRunTimeAsync(task.TaskName, task.GroupName, task.LastRunTime.Value);
                            }
                            else
                            {
                                var runlog = FileQuartz.GetJobRunLog(taskOptions.TaskName, taskOptions.GroupName, 1, 2);
                                if (runlog.Count > 0)
                                // è§¦å‘器无记录时,从执行日志中获取最后执行时间
                                var logs = await quartzRepo.GetJobRunLogAsync(task.TaskName, task.GroupName, 1, 1);
                                if (logs.Count > 0 && DateTime.TryParse(logs[0].BeginDate, out var lastRunTime))
                                {
                                    DateTime.TryParse(runlog[0].BeginDate, out DateTime lastRunTime);
                                    taskOptions.LastRunTime = lastRunTime;
                                    //更改最后执行时间
                                    bool aa = QuartzSearchData.QuartzUpdate(taskOptions);
                                    task.LastRunTime = lastRunTime;
                                    await quartzRepo.UpdateTaskLastRunTimeAsync(task.TaskName, task.GroupName, lastRunTime);
                                }
                            }
                        }
                        list.Add(taskOptions);
                        list.Add(task);
                    }
                }
                // 7. åŒæ­¥å†…存列表为最新数据(保证后续操作使用最新状态)
                _taskList = list;
            }
            catch (Exception ex)
            {
@@ -128,314 +160,246 @@
                mes.count = 0;
                mes.message = ex.Message + ex.StackTrace;
                mes.data = null;
                FileQuartz.WriteStartLog("获取作业异常:" + ex.Message + ex.StackTrace);
                await quartzRepo.WriteStartLogAsync($"获取作业异常:{ex.Message}{ex.StackTrace}");
                return new List<TaskOptions>();
            }
            return list;
        }
        /// <summary>
        /// æ·»åŠ ä½œä¸š
        /// æ–°å¢žQuartz任务(初始化/手动添加通用)
        /// </summary>
        /// <param name="taskOptions"></param>
        /// <param name="schedulerFactory"></param>
        /// <param name="init">是否初始化,否=需要重新生成配置文件,是=不重新生成配置文件</param>
        /// <returns></returns>
        public static async Task<object> AddJob(this TaskOptions taskOptions, ISchedulerFactory schedulerFactory, bool init = false, IJobFactory jobFactory = null)
        /// <param name="task">任务配置项</param>
        /// <param name="schedulerFactory">调度器工厂</param>
        /// <param name="init">是否为初始化阶段(初始化时不重复入库)</param>
        /// <param name="jobFactory">Job工厂(自定义Job实例化逻辑)</param>
        /// <param name="quartzRepo">Quartz仓储</param>
        /// <returns>统一返回结果(ToMessage)</returns>
        public static async Task<object> AddJob(this TaskOptions task, ISchedulerFactory schedulerFactory, bool init = false, IJobFactory jobFactory = null, QuartzRepository quartzRepo = null)
        {
            try
            {
                //处理前端Corn表达式最后一个月的星期天表达式缺陷问题
                string resultString = "";
                string[] str = taskOptions.Interval.Split(" ");
                if ((str[3].Equals("1L") || str[3].Equals("2L") || str[3].Equals("3L") || str[3].Equals("4L") || str[3].Equals("5L") || str[3].Equals("6L") || str[3].Equals("7L")) && str[5].Equals("?"))
                // 1. ä¿®å¤å‰ç«¯ä¼ å…¥çš„Cron表达式兼容性问题
                task.Interval = FixCronExpression(task.Interval);
                // 2. éªŒè¯Cron表达式有效性
                var (isValid, msg) = task.Interval.IsValidExpression();
                if (!isValid)
                    return new ToMessage { code = "300", count = 0, message = msg, data = null };
                // 3. éžåˆå§‹åŒ–阶段:校验任务是否已存在(避免重复添加)
                if (!init && await quartzRepo.TaskExistsAsync(task.TaskName, task.GroupName))
                {
                    str[5] = str[3];
                    str[3] = "?";
                    for (int i = 0; i < str.Length; i++)
                    {
                        resultString += str[i] + " ";
                    }
                    taskOptions.Interval = resultString;
                    return new ToMessage { code = "300", count = 0, message = $"作业:{task.TaskName},分组:{task.GroupName}已经存在", data = null };
                }
                (bool, string) validExpression = taskOptions.Interval.IsValidExpression();
                if (!validExpression.Item1)
                    return new { code = 300,count=0,Message=validExpression.Item2,data="null" };
                (bool, object) result = taskOptions.Exists(init);
                if (!result.Item1)
                    return result.Item2;
                // 4. éžåˆå§‹åŒ–阶段:新增任务到内存+数据库+记录操作日志
                if (!init)
                {
                    //将历史任务及新增加任务天加到List集合
                    _taskList.Add(taskOptions);
                    //将集合数据添加到json文件(覆盖式)
                    FileQuartz.WriteJobConfig(_taskList);
                    //将当前新任务添加到数据表
                    bool aa = QuartzSearchData.QuartzCreate(taskOptions);
                    _taskList.Add(task);// å†…存添加
                    await quartzRepo.CreateTaskAsync(task);// æ•°æ®åº“添加
                    await quartzRepo.AddJobActionLogAsync(JobAction.新增.ToString(), task.TaskName, task.GroupName, "新增任务成功");// æ“ä½œæ—¥å¿—
                }
                // 5. æž„建Quartz Job(任务执行体)
                var job = JobBuilder.Create<HttpResultfulJob>()// HttpResultfulJob:自定义的HTTP请求型Job
                    .WithIdentity(task.TaskName, task.GroupName)// è®¾ç½®Job唯一标识(名称+分组)
                    .Build();
                IJobDetail job = JobBuilder.Create<HttpResultfulJob>().WithIdentity(taskOptions.TaskName, taskOptions.GroupName).Build();
                ITrigger trigger = TriggerBuilder.Create()
                   .WithIdentity(taskOptions.TaskName, taskOptions.GroupName)
                   .StartNow()
                   .WithDescription(taskOptions.Describe)
                   .WithCronSchedule(taskOptions.Interval)
                   .Build();
                // 6. æž„建Cron触发器(按Cron表达式触发)
                var trigger = TriggerBuilder.Create()
                    .WithIdentity(task.TaskName, task.GroupName)// è§¦å‘器唯一标识(与Job保持一致)
                    .StartNow()// ç«‹å³å¯åЍ
                    .WithDescription(task.Describe)// ä»»åŠ¡æè¿°
                    .WithCronSchedule(task.Interval)// ç»‘定Cron表达式
                    .Build();
                IScheduler scheduler = await schedulerFactory.GetScheduler();
                if (jobFactory == null)
                {
                    try
                    {
                        jobFactory = HttpContext.Current.RequestServices.GetService<IJobFactory>();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"创建任务[{taskOptions.TaskName}]异常,{ex.Message}");
                    }
                }
                // 7. èŽ·å–è°ƒåº¦å™¨å¹¶ç¡®ä¿å¯åŠ¨
                var scheduler = await schedulerFactory.GetScheduler();
                if (!scheduler.IsStarted) await scheduler.Start(); // ç¡®ä¿å¯åЍ
                // 8. è®¾ç½®è‡ªå®šä¹‰JobFactory(如需控制Job的依赖注入/实例化)
                if (jobFactory != null)
                {
                    scheduler.JobFactory = jobFactory;
                }
                // 9. å°†Job和触发器绑定到调度器
                await scheduler.ScheduleJob(job, trigger);
                if (taskOptions.Status == (int)TriggerState.Normal)
                // 10. æ ¹æ®ä»»åŠ¡çŠ¶æ€è®¾ç½®è§¦å‘å™¨å¯åœ
                if (task.Status == (int)TriggerState.Normal)
                {
                    await scheduler.Start();
                    await scheduler.ResumeTrigger(trigger.Key);// æ˜¾å¼æ¢å¤ï¼ˆç¡®ä¿è§¦å‘器正常运行)
                }
                else
                {
                    await scheduler.PauseJob(job.Key);
                    //  await schedulerFactory.Pause(taskOptions);
                    FileQuartz.WriteStartLog($"作业:{taskOptions.TaskName},分组:{taskOptions.GroupName},新建时未启动原因,状态为:{taskOptions.Status}");
                    await scheduler.PauseJob(job.Key);// æš‚停任务
                    await quartzRepo.WriteStartLogAsync($"作业:{task.TaskName},分组:{task.GroupName},新建时未启动原因,状态为:{task.Status}");
                }
                if (!init)
                    FileQuartz.WriteJobAction(JobAction.新增, taskOptions.TaskName, taskOptions.GroupName);
                mes.code = "200";
                mes.count = 0;
                mes.message ="执行成功!";
                mes.message = "执行成功!";
                mes.data = null;
            }
            catch (Exception ex)
            {
                mes.code = "300";
                mes.count = 0;
                mes.message = ex.Message;
                mes.data = null;
                return new { status = false, msg = ex.Message };
                return new ToMessage { code = "300", count = 0, message = ex.Message, data = null };
            }
            return mes;
        }
        /// <summary>
        /// ç§»é™¤ä½œä¸š
        /// ç§»é™¤ï¼ˆåˆ é™¤ï¼‰ä»»åŠ¡
        /// </summary>
        /// <param name="schedulerFactory"></param>
        /// <param name="taskName"></param>
        /// <param name="groupName"></param>
        /// <returns></returns>
        public static Task<object> Remove(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions)
        /// <param name="schedulerFactory">调度器工厂</param>
        /// <param name="task">任务配置</param>
        /// <param name="quartzRepo">Quartz仓储</param>
        /// <returns>操作结果</returns>
        public static Task<object> Remove(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo)
        {
            return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.删除, taskOptions);
            return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.删除, task, quartzRepo);
        }
        /// <summary>
        /// æ›´æ–°ä½œä¸š
        /// ä¿®æ”¹ä»»åŠ¡é…ç½®
        /// </summary>
        /// <param name="schedulerFactory"></param>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        public static Task<object> Update(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions)
        /// <param name="schedulerFactory">调度器工厂</param>
        /// <param name="task">新的任务配置</param>
        /// <param name="quartzRepo">Quartz仓储</param>
        /// <returns>操作结果</returns>
        public static Task<object> Update(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo)
        {
            return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.修改, taskOptions);
            return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.修改, task, quartzRepo);
        }
        /// <summary>
        /// æš‚停作业
        /// æš‚停任务
        /// </summary>
        /// <param name="schedulerFactory"></param>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        public static Task<object> Pause(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions)
        /// <param name="schedulerFactory">调度器工厂</param>
        /// <param name="task">任务配置</param>
        /// <param name="quartzRepo">Quartz仓储</param>
        /// <returns>操作结果</returns>
        public static Task<object> Pause(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo)
        {
            return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.暂停, taskOptions);
            return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.暂停, task, quartzRepo);
        }
        /// <summary>
        /// å¯åŠ¨ä½œä¸š
        /// å¯åŠ¨ï¼ˆæ¢å¤ï¼‰ä»»åŠ¡
        /// </summary>
        /// <param name="schedulerFactory"></param>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        public static Task<object> Start(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions)
        /// <param name="schedulerFactory">调度器工厂</param>
        /// <param name="task">任务配置</param>
        /// <param name="quartzRepo">Quartz仓储</param>
        /// <returns>操作结果</returns>
        public static Task<object> Start(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo)
        {
            return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.开启, taskOptions);
            return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.开启, task, quartzRepo);
        }
        /// <summary>
        /// ç«‹å³æ‰§è¡Œä¸€æ¬¡ä½œä¸š
        /// ç«‹å³æ‰§è¡Œä»»åŠ¡ï¼ˆæ— è§†Cron表达式)
        /// </summary>
        /// <param name="schedulerFactory"></param>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        public static Task<object> Run(this ISchedulerFactory schedulerFactory, TaskOptions taskOptions)
        /// <param name="schedulerFactory">调度器工厂</param>
        /// <param name="task">任务配置</param>
        /// <param name="quartzRepo">Quartz仓储</param>
        /// <returns>操作结果</returns>
        public static Task<object> Run(this ISchedulerFactory schedulerFactory, TaskOptions task, QuartzRepository quartzRepo)
        {
            return schedulerFactory.TriggerAction(taskOptions.TaskName, taskOptions.GroupName, JobAction.立即执行, taskOptions);
        }
        public static object ModifyTaskEntity(this TaskOptions taskOptions, ISchedulerFactory schedulerFactory, JobAction action)
        {
            TaskOptions options = null;
            object result = null;
            switch (action)
            {
                case JobAction.删除:
                    for (int i = 0; i < _taskList.Count; i++)
                    {
                        options = _taskList[i];
                        if (options.TaskName == taskOptions.TaskName && options.GroupName == taskOptions.GroupName)
                        {
                            _taskList.RemoveAt(i);
                            //删除任务
                            bool aa = QuartzSearchData.QuartzDelete(options);
                        }
                    }
                    break;
                case JobAction.修改:
                    options = _taskList.Where(x => x.TaskName == taskOptions.TaskName && x.GroupName == taskOptions.GroupName).FirstOrDefault();
                    //移除以前的配置
                    if (options != null)
                    {
                        _taskList.Remove(options);
                        //删除任务
                        bool aa = QuartzSearchData.QuartzDelete(options);
                    }
                    //生成任务并添加新配置
                    result = taskOptions.AddJob(schedulerFactory, false).GetAwaiter().GetResult();
                    break;
                case JobAction.暂停:
                case JobAction.开启:
                case JobAction.停止:
                case JobAction.立即执行:
                    options = _taskList.Where(x => x.TaskName == taskOptions.TaskName && x.GroupName == taskOptions.GroupName).FirstOrDefault();
                    if (action == JobAction.暂停)
                    {
                        options.Status = (int)TriggerState.Paused;
                        //更新任务状态
                        bool aa= QuartzSearchData.QuartzUpdate(options);
                    }
                    else if (action == JobAction.停止)
                    {
                        options.Status = (int)action;
                        //更新任务状态
                        bool aa = QuartzSearchData.QuartzUpdate(options);
                    }
                    else
                    {
                        options.Status = (int)TriggerState.Normal;
                        //更新任务状态
                        bool aa = QuartzSearchData.QuartzUpdate(options);
                    }
                    break;
            }
            //生成配置文件
            FileQuartz.WriteJobConfig(_taskList);
            FileQuartz.WriteJobAction(action, taskOptions.TaskName, taskOptions.GroupName, "操作对象:" + JsonConvert.SerializeObject(taskOptions));
            return result;
            return TriggerAction(schedulerFactory, task.TaskName, task.GroupName, JobAction.立即执行, task, quartzRepo);
        }
        /// <summary>
        /// è§¦å‘新增、删除、修改、暂停、启用、立即执行事件
        /// ä»»åŠ¡æ“ä½œæ ¸å¿ƒé€»è¾‘ï¼ˆæ ¸å¿ƒä¿®å¤ï¼šKey匹配+状态同步+Scheduler启动校验)
        /// å°è£…删除/修改/暂停/开启/立即执行的通用逻辑,避免代码冗余
        /// </summary>
        /// <param name="schedulerFactory"></param>
        /// <param name="taskName"></param>
        /// <param name="groupName"></param>
        /// <param name="action"></param>
        /// <param name="taskOptions"></param>
        /// <returns></returns>
        public static async Task<object> TriggerAction(this ISchedulerFactory schedulerFactory, string taskName, string groupName, JobAction action, TaskOptions taskOptions = null)
        /// <param name="schedulerFactory">调度器工厂</param>
        /// <param name="taskName">任务名称</param>
        /// <param name="groupName">分组名称</param>
        /// <param name="action">操作类型(删除/修改/暂停/开启/立即执行)</param>
        /// <param name="task">任务配置</param>
        /// <param name="quartzRepo">Quartz仓储</param>
        /// <returns>操作结果</returns>
        private static async Task<object> TriggerAction(this ISchedulerFactory schedulerFactory, string taskName, string groupName, JobAction action, TaskOptions task, QuartzRepository quartzRepo)
        {
            string errorMsg = "";
            try
            {
                //处理前端Corn表达式最后一个月的星期天表达式缺陷问题
                string resultString = "";
                string[] str = taskOptions.Interval.Split(" ");
                if ((str[3].Equals("1L") || str[3].Equals("2L") || str[3].Equals("3L") || str[3].Equals("4L") || str[3].Equals("5L") || str[3].Equals("6L") || str[3].Equals("7L")) && str[5].Equals("?"))
                {
                    str[5] = str[3];
                    str[3] = "?";
                    for (int i = 0; i < str.Length; i++)
                    {
                        resultString += str[i] + " ";
                    }
                    taskOptions.Interval = resultString;
                }
                IScheduler scheduler = await schedulerFactory.GetScheduler();
                List<JobKey> jobKeys = scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals(groupName)).Result.ToList();
                if (jobKeys == null || jobKeys.Count() == 0)
                {
                    errorMsg = $"未找到分组[{groupName}]";
                    return new { code = 300, count = 0, Message = errorMsg, data = "null" };
                    //return new { status = false, msg = errorMsg };
                }
                JobKey jobKey = jobKeys.Where(s => scheduler.GetTriggersOfJob(s).Result.Any(x => (x as CronTriggerImpl).Name == taskName)).FirstOrDefault();
                if (jobKey == null)
                {
                    errorMsg = $"未找到触发器[{taskName}]";
                    return new { code = 300, count = 0, Message = errorMsg, data = "null" };
                }
                var triggers = await scheduler.GetTriggersOfJob(jobKey);
                ITrigger trigger = triggers?.Where(x => (x as CronTriggerImpl).Name == taskName).FirstOrDefault();
                // 1. é¢„处理Cron表达式(修复前端传入的表达式)
                if (task != null) task.Interval = FixCronExpression(task.Interval);
                if (trigger == null)
                // 2. èŽ·å–è°ƒåº¦å™¨å¹¶ç¡®ä¿å¯åŠ¨ï¼ˆæ ¸å¿ƒä¿®å¤1:全局Scheduler启动校验)
                var scheduler = await schedulerFactory.GetScheduler();
                // æ ¸å¿ƒä¿®å¤1:确保Scheduler全局启动
                if (!scheduler.IsStarted) await scheduler.Start();
                // 3. æ ¸å¿ƒä¿®å¤2:正确构建JobKey(任务名+分组),解决Key匹配失败问题
                var jobKey = new JobKey(taskName, groupName);
                if (!await scheduler.CheckExists(jobKey))
                {
                    errorMsg = $"未找到触发器[{taskName}]";
                    return new { code = 300, count = 0, Message = errorMsg, data = "null" };
                    return new ToMessage { code = "300", count = 0, message = $"未找到任务[{taskName}-{groupName}]", data = null };
                }
                // 4. èŽ·å–ä»»åŠ¡å…³è”çš„è§¦å‘å™¨ï¼ˆæ— è§¦å‘å™¨åˆ™ä»»åŠ¡æ— æ³•æ‰§è¡Œï¼‰
                var triggers = await scheduler.GetTriggersOfJob(jobKey);
                if (triggers == null || !triggers.Any())
                {
                    return new ToMessage { code = "300", count = 0, message = $"任务[{taskName}-{groupName}]无触发器", data = null };
                }
                // å–第一个触发器(单任务默认绑定一个触发器)
                var trigger = triggers.First();
                object result = null;
                // 5. æ ¹æ®æ“ä½œç±»åž‹æ‰§è¡Œä¸åŒé€»è¾‘
                switch (action)
                {
                    case JobAction.删除:
                    case JobAction.修改:
                        // æš‚停触发器 -> è§£ç»‘触发器 -> åˆ é™¤Job -> åŒæ­¥å†…å­˜+数据库 -> è®°å½•日志
                        await scheduler.PauseTrigger(trigger.Key);
                        await scheduler.UnscheduleJob(trigger.Key);// ç§»é™¤è§¦å‘器
                        await scheduler.DeleteJob(trigger.JobKey);
                        result = taskOptions.ModifyTaskEntity(schedulerFactory, action);
                        await scheduler.UnscheduleJob(trigger.Key);
                        await scheduler.DeleteJob(jobKey);
                        _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName);
                        await quartzRepo.DeleteTaskAsync(taskName, groupName);
                        await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "删除任务成功");
                        break;
                    case JobAction.修改:
                        // ä¿®æ”¹é€»è¾‘:先删除旧任务 -> é‡æ–°æ·»åŠ æ–°é…ç½® -> è®°å½•日志
                        await scheduler.PauseTrigger(trigger.Key);
                        await scheduler.UnscheduleJob(trigger.Key);
                        await scheduler.DeleteJob(jobKey);
                        _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName);
                        await quartzRepo.DeleteTaskAsync(taskName, groupName);
                        result = await task.AddJob(schedulerFactory, false, null, quartzRepo);// é‡æ–°æ·»åŠ ä»»åŠ¡
                        await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, JsonConvert.SerializeObject(task));
                        break;
                    case JobAction.暂停:
                    case JobAction.停止:
                    case JobAction.开启:
                        result = taskOptions.ModifyTaskEntity(schedulerFactory, action);
                        if (action == JobAction.暂停)
                        {
                            await scheduler.PauseTrigger(trigger.Key);
                        }
                        else if (action == JobAction.开启)
                        {
                            await scheduler.ResumeTrigger(trigger.Key);
                            //   await scheduler.RescheduleJob(trigger.Key, trigger);
                        }
                        else
                        {
                            await scheduler.Shutdown();
                        }
                        // æš‚停触发器 -> æ›´æ–°ä»»åŠ¡çŠ¶æ€ -> åŒæ­¥å†…å­˜+数据库 -> è®°å½•日志
                        await scheduler.PauseTrigger(trigger.Key);
                        task.Status = (int)TriggerState.Paused;
                        await quartzRepo.UpdateTaskAsync(task);
                        _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName);
                        _taskList.Add(task); // åŒæ­¥å†…存状态
                        await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "暂停任务成功");
                        break;
                    case JobAction.开启:
                        // æ¢å¤è§¦å‘器 -> æ›´æ–°ä»»åŠ¡çŠ¶æ€ -> åŒæ­¥å†…å­˜+数据库 -> è®°å½•日志
                        await scheduler.ResumeTrigger(trigger.Key);
                        task.Status = (int)TriggerState.Normal;
                        await quartzRepo.UpdateTaskAsync(task);
                        _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName);
                        _taskList.Add(task); // åŒæ­¥å†…存状态
                        await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "开启任务成功");
                        break;
                    case JobAction.立即执行:
                        if (taskOptions != null && taskOptions.Status != (int)TriggerState.Normal)
                        // ç«‹å³æ‰§è¡Œå‰ç¡®ä¿ä»»åŠ¡å¤„äºŽå¯åŠ¨çŠ¶æ€ -> è§¦å‘Job立即执行 -> è®°å½•日志
                        if (task.Status != (int)TriggerState.Normal)
                        {
                            result = taskOptions.ModifyTaskEntity(schedulerFactory, JobAction.开启);
                             await scheduler.ResumeTrigger(trigger.Key);
                            task.Status = (int)TriggerState.Normal;
                            await quartzRepo.UpdateTaskAsync(task);
                            _taskList.RemoveAll(x => x.TaskName == taskName && x.GroupName == groupName);
                            _taskList.Add(task);
                            await scheduler.ResumeTrigger(trigger.Key);
                        }
                        else {
                            await scheduler.TriggerJob(jobKey);
                        }
                        await scheduler.TriggerJob(jobKey); // ç«‹å³è§¦å‘任务执行
                        await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, "立即执行任务成功");
                        break;
                }
                mes.code = "200";
@@ -443,70 +407,78 @@
                mes.message = $"作业{action.ToString()}成功";
                mes.data = null;
                return result ?? mes;
                //return result ?? new { status = true, msg = $"作业{action.ToString()}成功" };
            }
            catch (Exception ex)
            {
                errorMsg = ex.Message;
                mes.code = "300";
                mes.count = 0;
                mes.message = errorMsg;
                mes.data = null;
                return mes;
                //return new { status = false, msg = ex.Message };
            }
            finally
            {
                FileQuartz.WriteJobAction(action, taskName, groupName, errorMsg);
                await quartzRepo.AddJobActionLogAsync(action.ToString(), taskName, groupName, $"操作失败:{ex.Message}");
                return new ToMessage { code = "300", count = 0, message = ex.Message, data = null };
            }
        }
        /// <summary>
        ///
        /// ä¿®å¤å‰ç«¯ä¼ å…¥çš„Cron表达式缺陷
        /// å…¼å®¹ASP.NET Core 3.1中Cron表达式的Day/Week字段写法问题
        /// </summary>
        /// <param name="context"></param>通过作业上下文获取作业对应的配置参数
        /// <returns></returns>
        public static TaskOptions GetTaskOptions(this IJobExecutionContext context)
        /// <param name="cron">原始Cron表达式</param>
        /// <returns>修复后的Cron表达式</returns>
        private static string FixCronExpression(string cron)
        {
            AbstractTrigger trigger = (context as JobExecutionContextImpl).Trigger as AbstractTrigger;
            TaskOptions taskOptions = _taskList.Where(x => x.TaskName == trigger.Name && x.GroupName == trigger.Group).FirstOrDefault();
            return taskOptions ?? _taskList.Where(x => x.TaskName == trigger.JobName && x.GroupName == trigger.JobGroup).FirstOrDefault();
            if (string.IsNullOrEmpty(cron))
                return cron;
            // æ‹†åˆ†Cron表达式(标准Cron:秒 åˆ† æ—¶ æ—¥ æœˆ å‘¨ å¹´ï¼ˆå¯é€‰ï¼‰ï¼‰
            var str = cron.Split(" ", StringSplitOptions.RemoveEmptyEntries);
            if (str.Length < 6)
                return cron;
            // å…¼å®¹ ASP.NET Core 3.1 çš„写法(替换 or è¯­æ³•)
            // ä¿®å¤åœºæ™¯ï¼šDay字段为1L/2L...7L ä¸” å‘¨å­—段为? æ—¶ï¼Œäº¤æ¢Day和周字段
            // åŽŸå› ï¼šå‰ç«¯å¯èƒ½æ··æ·†äº†Day/周字段的写法,Quartz对L(最后一天)的解析有特定规则
            var dayValue = str[3];// ç¬¬4个字段:Day
            if ((dayValue == "1L" || dayValue == "2L" || dayValue == "3L" ||
                 dayValue == "4L" || dayValue == "5L" || dayValue == "6L" || dayValue == "7L") && str[5] == "?")
            {
                str[5] = str[3]; // å‘¨å­—段 = åŽŸDay字段
                str[3] = "?";    // Day字段 = ?
                return string.Join(" ", str);
            }
            return cron;
        }
        /// <summary>
        /// ä½œä¸šæ˜¯å¦å­˜åœ¨
        /// éªŒè¯Cron表达式是否有效
        /// </summary>
        /// <param name="taskOptions"></param>
        /// <param name="init">初始化的不需要判断</param>
        /// <returns></returns>
        public static (bool, object) Exists(this TaskOptions taskOptions, bool init)
        {
            if (!init && _taskList.Any(x => x.TaskName == taskOptions.TaskName && x.GroupName == taskOptions.GroupName))
            {
                return (false,
                    new
                    {
                        status = false,
                        msg = $"作业:{taskOptions.TaskName},分组:{taskOptions.GroupName}已经存在"
                    });
            }
            return (true, null);
        }
        /// <param name="cronExpression">Cron表达式</param>
        /// <returns>是否有效 + é”™è¯¯ä¿¡æ¯</returns>
        public static (bool, string) IsValidExpression(this string cronExpression)
        {
            try
            {
                CronTriggerImpl trigger = new CronTriggerImpl();
                // é€šè¿‡Quartz内置的CronTriggerImpl验证表达式
                var trigger = new CronTriggerImpl();
                trigger.CronExpressionString = cronExpression;
                DateTimeOffset? date = trigger.ComputeFirstFireTimeUtc(null);
                return (date != null, date == null ? $"请确认表达式{cronExpression}是否正确!" : "");
                // è®¡ç®—第一个触发时间:null表示表达式无效
                var date = trigger.ComputeFirstFireTimeUtc(null);
                return (date != null, date == null ? $"表达式{cronExpression}无效!" : string.Empty);
            }
            catch (Exception e)
            {
                return (false, $"请确认表达式{cronExpression}是否正确!{e.Message}");
                return (false, $"表达式{cronExpression}无效!{e.Message}");
            }
        }
    }
        /// <summary>
        /// ä»ŽJob执行上下文获取任务配置
        /// ç”¨äºŽJob执行时获取最新的任务参数
        /// </summary>
        /// <param name="context">Job执行上下文</param>
        /// <returns>任务配置项</returns>
        public static TaskOptions GetTaskOptions(this IJobExecutionContext context)
        {
            var jobKey = context.JobDetail.Key;
            // ä»Žå†…存列表中获取匹配的任务(Job执行时优先用内存,减少数据库查询)
            return _taskList.FirstOrDefault(x => x.TaskName == jobKey.Name && x.GroupName == jobKey.Group);
        }
    }
}
VueWebCoreApi/Quartz/QuartzRepository.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace VueWebCoreApi.Quartz
{
    public class QuartzRepository
    {
        #region ä»»åŠ¡åŸºç¡€æ“ä½œ
        public async Task<List<TaskOptions>> GetAllTasksAsync()
        {
            var sql = @"SELECT TaskName,GroupName,Interval,ApiUrl,AuthKey,AuthValue,Describe,MessagePush,PushUserCode,RequestType,LastRunTime,Status,CreateAuthor,CreateTime,UpdateTime
                        FROM ScheduleInfo";
            return await DapperHelper.QueryAsync<TaskOptions>(sql);
        }
        public async Task<bool> CreateTaskAsync(TaskOptions task)
        {
            var sql = @"INSERT INTO ScheduleInfo(TaskName,GroupName,Interval,ApiUrl,RequestParameters,AuthKey,AuthValue,Describe,MessagePush,PushUserCode,RequestType,LastRunTime,Status,CreateAuthor,CreateTime,UpdateTime)
                        VALUES(@TaskName,@GroupName,@Interval,@ApiUrl,@RequestParameters,@AuthKey,@AuthValue,@Describe,@MessagePush,@PushUserCode,@RequestType,@LastRunTime,@Status,'9999',GETDATE(),GETDATE())";
            var rows = await DapperHelper.ExecuteAsync(sql, task);
            return rows > 0;
        }
        /// <summary>
        /// ä¿®å¤ï¼šå¢žåŠ æ›´æ–°ç»“æžœæ ¡éªŒï¼Œè¿”å›žå…·ä½“ä¿¡æ¯
        /// </summary>
        public async Task<(bool Success, string Msg)> UpdateTaskWithCheckAsync(TaskOptions task)
        {
            if (string.IsNullOrEmpty(task.TaskName) || string.IsNullOrEmpty(task.GroupName))
            {
                return (false, "任务名/分组名不能为空");
            }
            // å…ˆæ ¡éªŒä»»åŠ¡æ˜¯å¦å­˜åœ¨
            if (!await TaskExistsAsync(task.TaskName, task.GroupName))
            {
                return (false, $"任务[{task.TaskName}-{task.GroupName}]不存在");
            }
            var sql = @"UPDATE ScheduleInfo
                        SET Interval=@Interval,ApiUrl=@ApiUrl,RequestParameters=@RequestParameters,AuthKey=@AuthKey,AuthValue=@AuthValue,Describe=@Describe,
                        MessagePush=@MessagePush,PushUserCode=@PushUserCode,RequestType=@RequestType,LastRunTime=@LastRunTime,Status=@Status,UpdateTime=GETDATE()
                        WHERE TaskName=@TaskName AND GroupName=@GroupName";
            var rows = await DapperHelper.ExecuteAsync(sql, task);
            return rows > 0 ? (true, "更新成功") : (false, "更新失败(无数据行更新)");
        }
        // å…¼å®¹åŽŸæœ‰æ–¹æ³•
        public async Task<bool> UpdateTaskAsync(TaskOptions task)
        {
            var (success, _) = await UpdateTaskWithCheckAsync(task);
            return success;
        }
        public async Task<bool> DeleteTaskAsync(string taskName, string groupName)
        {
            var sql = "DELETE FROM ScheduleInfo WHERE TaskName=@TaskName AND GroupName=@GroupName";
            var rows = await DapperHelper.ExecuteAsync(sql, new { TaskName = taskName, GroupName = groupName });
            return rows > 0;
        }
        public async Task<List<TaskOptions>> TaskExists(string taskName, string groupName)
        {
            var sql = "SELECT * FROM ScheduleInfo WHERE TaskName=@TaskName AND GroupName=@GroupName";
            return await DapperHelper.QueryAsync<TaskOptions>(sql, new { TaskName = taskName, GroupName = groupName });
        }
        public async Task<bool> TaskExistsAsync(string taskName, string groupName)
        {
            var sql = "SELECT 1 FROM ScheduleInfo WHERE TaskName=@TaskName AND GroupName=@GroupName";
            var result = await DapperHelper.QueryFirstOrDefaultAsync<int?>(sql, new { TaskName = taskName, GroupName = groupName });
            return result.HasValue;
        }
        public async Task<bool> UpdateTaskLastRunTimeAsync(string taskName, string groupName, DateTime lastRunTime)
        {
            var sql = "UPDATE ScheduleInfo SET LastRunTime=@LastRunTime,UpdateTime=GETDATE() WHERE TaskName=@TaskName AND GroupName=@GroupName";
            var rows = await DapperHelper.ExecuteAsync(sql, new { TaskName = taskName, GroupName = groupName, LastRunTime = lastRunTime });
            return rows > 0;
        }
        #endregion
        #region ä»»åŠ¡è¿è¡Œæ—¥å¿—æ“ä½œ
        public async Task<bool> AddJobRunLogAsync(string taskName, string groupName, DateTime beginDate, DateTime endDate, string msg,string MessagePush,string PushUserCode)
        {
            var sql = @"INSERT INTO JobRunLog(TaskName,GroupName,BeginDate,EndDate,Msg,CreateTime,MessagePush,PushUserCode)
                        VALUES(@TaskName,@GroupName,@BeginDate,@EndDate,@Msg,GETDATE(),@MessagePush,@PushUserCode)";
            var param = new
            {
                TaskName = taskName,
                GroupName = groupName,
                BeginDate = beginDate,
                EndDate = endDate,
                Msg = msg,
                MessagePush= MessagePush,
                PushUserCode= PushUserCode
            };
            var rows = await DapperHelper.ExecuteAsync(sql, param);
            return rows > 0;
        }
        /// <summary>
        /// ä¿®å¤ï¼šåˆ†é¡µå‚数可配置,避免写死100
        /// </summary>
        public async Task<List<TaskLog>> GetJobRunLogAsync(string taskName, string groupName, int page, int pageSize = 20)
        {
            var sql = $@"SELECT Id,TaskName,GroupName,CONVERT(VARCHAR(20),BeginDate,120) AS BeginDate,
                             CONVERT(VARCHAR(20),EndDate,120) AS EndDate,Msg,CreateTime,MessagePush,PushUserCode
                        FROM JobRunLog
                        WHERE TaskName=@TaskName AND GroupName=@GroupName
                        ORDER BY CreateTime DESC
                        OFFSET {(page - 1) * pageSize} ROWS FETCH NEXT {pageSize} ROWS ONLY";
            return await DapperHelper.QueryAsync<TaskLog>(sql, new { TaskName = taskName, GroupName = groupName });
        }
        #endregion
        #region ä»»åŠ¡æ“ä½œæ—¥å¿—æ“ä½œ
        public async Task<bool> AddJobActionLogAsync(string actionType, string taskName, string groupName, string content)
        {
            var sql = @"INSERT INTO JobActionLog(ActionType,TaskName,GroupName,Content,CreateTime)
                        VALUES(@ActionType,@TaskName,@GroupName,@Content,GETDATE())";
            var param = new
            {
                ActionType = actionType,
                TaskName = taskName,
                GroupName = groupName,
                Content = content
            };
            var rows = await DapperHelper.ExecuteAsync(sql, param);
            return rows > 0;
        }
        #endregion
        #region ç³»ç»Ÿå¯åŠ¨æ—¥å¿—æ“ä½œ
        public async Task<bool> WriteStartLogAsync(string content)
        {
            var sql = "INSERT INTO SystemStartLog(Content,CreateTime) VALUES(@Content,GETDATE())";
            var rows = await DapperHelper.ExecuteAsync(sql, new { Content = content });
            return rows > 0;
        }
        #endregion
    }
}
VueWebCoreApi/Quartz/QuartzSearchData.cs
ÎļþÒÑɾ³ý
VueWebCoreApi/Quartz/TaskLog.cs
@@ -7,8 +7,12 @@
{
    public class TaskLog
    {
        public long Id { get; set; }
        public string TaskName { get; set; }
        public string GroupName { get; set; }
        public string BeginDate { get; set; }
        public string EndDate { get; set; }
        public string Msg { get; set; }
        public DateTime CreateTime { get; set; }
    }
}
VueWebCoreApi/Quartz/TaskOptions.cs
@@ -7,15 +7,57 @@
{
    public class TaskOptions
    {
        /// <summary>
        /// ä»»åŠ¡åç§°
        /// </summary>
        public string TaskName { get; set; }
        /// <summary>
        /// ä»»åŠ¡ç»„åç§°
        /// </summary>
        public string GroupName { get; set; }
        /// <summary>
        /// Cron表达式(任务执行时间规则)
        /// </summary>
        public string Interval { get; set; }
        /// <summary>
        /// ä»»åŠ¡è§¦å‘çš„æŽ¥å£åœ°å€ï¼ˆGET/POST请求地址)
        /// </summary>
        public string ApiUrl { get; set; }
        /// <summary>
        /// è¯·æ±‚参数
        /// </summary>
        public string RequestParameters { get; set; }
        /// <summary>
        /// æŽ¥å£è¯·æ±‚授权Key(请求头参数)
        /// </summary>
        public string AuthKey { get; set; }
        /// <summary>
        /// æŽ¥å£è¯·æ±‚授权Value(请求头参数)
        /// </summary>
        public string AuthValue { get; set; }
        /// <summary>
        /// ä»»åŠ¡æè¿°/备注(说明任务用途)
        /// </summary>
        public string Describe { get; set; }
        /// <summary>
        /// è¯·æ±‚方式:GET/POST
        /// </summary>
        public string RequestType { get; set; }
        public DateTime? LastRunTime { get; set; }
        /// <summary>
        /// æ˜¯å¦æ¶ˆæ¯æŽ¨é€ï¼šY/N
        /// </summary>
        public string MessagePush { get; set; }
        /// <summary>
        /// æ¶ˆæ¯æŽ¨é€ç”¨æˆ·ç¼–号集合
        /// </summary>
        public string PushUserCode { get; set; }
        /// <summary>
        /// ä»»åŠ¡çŠ¶æ€
        /// </summary>
        public int Status { get; set; }
        /// <summary>
        /// æœ€åŽæ‰§è¡Œæ—¶é—´
        /// </summary>
        public DateTime? LastRunTime { get; set; }
    }
}
VueWebCoreApi/SignalR/ChatHub.cs
@@ -19,11 +19,13 @@
        private readonly IHttpContextAccessor _httpContextAccessor;
        private ILog log = LogManager.GetLogger(Startup.repository.Name, typeof(ChatHub));
        ILogger<ChatHub> _logger;
        public ChatHub(ILogger<ChatHub> logger, IHttpContextAccessor httpContextAccessor)
        {
            _logger = logger;
            _httpContextAccessor = httpContextAccessor;
        }
        /// <summary>
        /// å®¢æˆ·ç«¯è¿žæŽ¥æœåŠ¡ç«¯
        /// </summary>
@@ -35,6 +37,7 @@
            _logger.LogInformation($"Client ConnectionId=> [[{id}]] Already Connection Server!");
            return base.OnConnectedAsync();
        }
        /// <summary>
        /// å®¢æˆ·ç«¯æ–­å¼€è¿žæŽ¥
        /// </summary>
@@ -43,14 +46,12 @@
        public override Task OnDisconnectedAsync(Exception exception)
        {
            var id = Context.ConnectionId;
            // åˆ é™¤ç”¨æˆ·ID
            UserIdsStore.Ids.Remove(id);
            // ä½¿ç”¨çº¿ç¨‹å®‰å…¨æ–¹æ³•移除连接
            UserIdsStore.RemoveUser(id);
            _logger.LogInformation($"Client ConnectionId=> [[{id}]] Already Close Connection Server!");
            return base.OnDisconnectedAsync(exception);
        }
        /**
         * æµ‹è¯•
         * */
        /// <summary>
        /// ç»™æ‰€æœ‰å®¢æˆ·ç«¯å‘送消息
        /// </summary>
@@ -61,27 +62,31 @@
        }
        /// <summary>
        /// æ·»åŠ åˆ°åœ¨çº¿ç”¨æˆ·åˆ—è¡¨
        /// æ·»åŠ åˆ°åœ¨çº¿ç”¨æˆ·åˆ—è¡¨ï¼ˆçº¿ç¨‹å®‰å…¨ç‰ˆï¼‰
        /// </summary>
        /// <param name="usercode"></param>
        /// <param name="usercode">用户编码</param>
        /// <returns></returns>
        public async Task AddUser(string usercode)
        {
            if (string.IsNullOrEmpty(usercode))
            {
                _logger.LogWarning("AddUser方法接收的usercode为空,忽略操作");
                return;
            }
            string cid = Context.ConnectionId;
            if (!UserIdsStore.Ids.ContainsValue(usercode))
            try
            {
                await Task.Run(() => UserIdsStore.Ids.Add(cid, usercode));
                // è°ƒç”¨çº¿ç¨‹å®‰å…¨çš„æ·»åŠ /更新方法
                UserIdsStore.AddOrUpdateUser(cid, usercode);
                _logger.LogInformation($"用户[{usercode}]的连接[{cid}]已注册到在线列表");
            }
            else
            catch (Exception ex)
            {
                //lambada表达式:根据值去键名Key
                string key = UserIdsStore.Ids.FirstOrDefault(d => d.Value == usercode).Key.ToString();
                // åˆ›å»ºä¸€ä¸ªæ–°çš„键值对
                UserIdsStore.Ids.Add(cid, usercode);
                // ç§»é™¤æ—§çš„键值对
                UserIdsStore.Ids.Remove(key);
                _logger.LogError(ex, $"添加用户[{usercode}]连接[{cid}]失败");
                throw; // æŠ›å‡ºå¼‚常让客户端感知错误
            }
            await Task.CompletedTask; // æ— å¼‚步操作时返回已完成任务
        }
    }
}
VueWebCoreApi/SignalR/UserIdsStore.cs
@@ -6,10 +6,96 @@
namespace VueWebCoreApi.SignalR
{
    /// <summary>
    /// ç”¨æˆ·id集合
    /// ç”¨æˆ·è¿žæŽ¥ID与用户编码映射存储(线程安全版)
    /// è§£å†³å¤šçº¿ç¨‹å¹¶å‘读写导致的字典数据错乱问题
    /// </summary>
    public static class UserIdsStore
    {
        public static Dictionary<string, string> Ids = new Dictionary<string, string>();
        // æ›¿æ¢ä¸ºçº¿ç¨‹å®‰å…¨çš„并发字典
        private static readonly Dictionary<string, string> _ids = new Dictionary<string, string>();
        // åŠ é”ä¿è¯å­—å…¸æ“ä½œçš„åŽŸå­æ€§ï¼ˆæ¯”ConcurrentDictionary更灵活控制批量操作)
        private static readonly object _lockObj = new object();
        /// <summary>
        /// å¯¹å¤–暴露的连接映射(只读,避免外部直接修改)
        /// </summary>
        public static IReadOnlyDictionary<string, string> Ids
        {
            get
            {
                lock (_lockObj)
                {
                    // è¿”回新字典,避免外部拿到引用后修改
                    return new Dictionary<string, string>(_ids);
                }
            }
        }
        /// <summary>
        /// æ·»åŠ /更新用户连接(原子操作)
        /// </summary>
        /// <param name="connectionId">SignalR连接ID</param>
        /// <param name="userCode">用户编码</param>
        public static void AddOrUpdateUser(string connectionId, string userCode)
        {
            if (string.IsNullOrEmpty(connectionId) || string.IsNullOrEmpty(userCode))
                throw new ArgumentNullException("连接ID和用户编码不能为空");
            lock (_lockObj)
            {
                // å…ˆç§»é™¤è¯¥ç”¨æˆ·çš„æ—§è¿žæŽ¥ï¼ˆä¸€ä¸ªç”¨æˆ·åªä¿ç•™æœ€æ–°çš„连接)
                var oldConnIds = _ids.Where(kv => kv.Value == userCode).Select(kv => kv.Key).ToList();
                foreach (var oldConnId in oldConnIds)
                {
                    _ids.Remove(oldConnId);
                }
                // æ·»åŠ æ–°è¿žæŽ¥ï¼ˆè¦†ç›–åŒåconnectionId,避免重复)
                if (_ids.ContainsKey(connectionId))
                {
                    _ids[connectionId] = userCode;
                }
                else
                {
                    _ids.Add(connectionId, userCode);
                }
            }
        }
        /// <summary>
        /// ç§»é™¤æŒ‡å®šè¿žæŽ¥ID的映射
        /// </summary>
        /// <param name="connectionId">SignalR连接ID</param>
        public static void RemoveUser(string connectionId)
        {
            if (string.IsNullOrEmpty(connectionId))
                return;
            lock (_lockObj)
            {
                if (_ids.ContainsKey(connectionId))
                {
                    _ids.Remove(connectionId);
                }
            }
        }
        /// <summary>
        /// æ ¹æ®ç”¨æˆ·ç¼–码获取所有在线连接ID(一个用户可能多端登录时返回多个)
        /// </summary>
        /// <param name="userCodes">用户编码列表</param>
        /// <returns>连接ID列表</returns>
        public static List<string> GetConnectionIdsByUserCodes(List<string> userCodes)
        {
            if (userCodes == null || userCodes.Count == 0)
                return new List<string>();
            lock (_lockObj)
            {
                return _ids.Where(kv => userCodes.Contains(kv.Value))
                           .Select(kv => kv.Key)
                           .Distinct() // åŽ»é‡ï¼Œé¿å…é‡å¤æŽ¨é€
                           .ToList();
            }
        }
    }
}
VueWebCoreApi/Startup.cs
@@ -93,23 +93,38 @@
            XmlConfigurator.Configure(repository, new FileInfo("Config/log4net.config"));
            BasicConfigurator.Configure(repository);
            services.AddControllersWithViews();
            //Quartz
            services.AddHttpClient();
            // æ³¨å†ŒHttpContextAccessor
            services.AddHttpContextAccessor();
            services.AddSingleton<IPathProvider, PathProvider>();
            services.AddTransient<HttpResultfulJob>();
            services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
            services.AddSingleton<IJobFactory, IOCJobFactory>();
            // æ³¨å†ŒHttpClient
            services.AddHttpClient();
            // æ³¨å†ŒQuartz(补充IOCJobFactory)
            services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
            services.AddSingleton<IJobFactory>(sp => new IOCJobFactory(sp)); // å…³é”®ï¼šæ³¨å†Œè‡ªå®šä¹‰JobFactory
            services.AddSingleton<HttpResultfulJob>();
            services.AddSingleton<QuartzRepository>();
            // æ³¨å†Œè·¯å¾„提供器
            services.AddScoped<IPathProvider, PathProvider>();
            //跨域设置
            //services.AddCors(options =>
            //{
            //    options.AddPolicy(
            //        "cors",
            //        set =>
            //        {
            //            set.SetIsOriginAllowed(origin => true).AllowAnyHeader().AllowAnyMethod().AllowCredentials();
            //        });
            //});
            services.AddCors(options =>
            {
                options.AddPolicy(
                    "cors",
                    set =>
                    {
                        set.SetIsOriginAllowed(origin => true).AllowAnyHeader().AllowAnyMethod().AllowCredentials();
                        set.AllowAnyOrigin()
                           .AllowAnyHeader()
                           .AllowAnyMethod();
                    });
            });
            //设置SignalR
@@ -164,11 +179,9 @@
            });
            #endregion
            // å…ˆé‡å®šå‘ HTTPS(如果需要)
            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            //添加静态文件中间件
            app.UseDefaultFiles();
            //添加授权文件
@@ -185,6 +198,11 @@
            app.UseStaticFiles();
            //跨域
            app.UseCors("cors");
            //路由
            app.UseRouting();
            //授权
            app.UseAuthorization();
            //用户session服务
            app.UseSession();
            //cookies
VueWebCoreApi/appsettings.json
@@ -9,13 +9,13 @@
  "AllowedHosts": "*",
  //服务器环境
  "ConnectionStrings": {
    "DBServer": "Data Source=121.196.36.24,1533;Initial Catalog=vmes_yy;User ID=sa;Password=xkd@20230101;pooling=false",
    "DBServer": "Data Source=121.196.36.24,1533;Initial Catalog=vmes_alkv2;User ID=sa;Password=xkd@20230101;pooling=false",
    //企业编码
    "CompanyCode": "000",
    //企业名称
    "CompanyName": "浙江寅寅科技有限公司",
    "CompanyName": "浙江阿鲁克健身器材有限公司",
    //redis企业配置
    "Enterprise": "NewYYMES",
    "Enterprise": "NewAlkV2MES",
    //redis数据库链接配置ip
    "RedisConnIp": "127.0.0.1",
    //redis数据库链接配置端口号