初步版本251204
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FATrace.Com
|
||||
{
|
||||
@@ -17,7 +18,7 @@ namespace FATrace.Com
|
||||
/// </summary>
|
||||
/// <param name="configKey"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetVideoName(string basePath,string Code)
|
||||
public static string GetVideoPathName(string basePath, string Code)
|
||||
{
|
||||
// 清洗非法文件名字符,避免保存失败
|
||||
string safeCode = Code;
|
||||
@@ -25,11 +26,156 @@ namespace FATrace.Com
|
||||
{
|
||||
var invalid = System.IO.Path.GetInvalidFileNameChars();
|
||||
safeCode = new string(Code.Where(c => !invalid.Contains(c)).ToArray());
|
||||
if (string.IsNullOrWhiteSpace(safeCode)) safeCode = "CODE";
|
||||
if (string.IsNullOrWhiteSpace(safeCode)) safeCode = DateTime.Now.ToString("yyyy-MM-dd HHmmss");
|
||||
|
||||
}
|
||||
catch { }
|
||||
|
||||
return $"{basePath}\\{DateTime.Now.ToString("yyyy-MM-dd HHmmss")} {safeCode}.mp4";
|
||||
return $"{basePath}\\{safeCode}.mp4";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取视频名称
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static string GetVideoName(string Code)
|
||||
{
|
||||
string safeCode = Code;
|
||||
try
|
||||
{
|
||||
var invalid = System.IO.Path.GetInvalidFileNameChars();
|
||||
safeCode = new string(Code.Where(c => !invalid.Contains(c)).ToArray());
|
||||
if (string.IsNullOrWhiteSpace(safeCode)) safeCode = DateTime.Now.ToString("yyyy-MM-dd HHmmss");
|
||||
|
||||
}
|
||||
catch { }
|
||||
return $"{safeCode}.mp4";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析条码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static (string RawCode, string Batch, string Weight) ParseCode(string Code)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Code))
|
||||
{
|
||||
return (string.Empty, string.Empty, string.Empty);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 标准化分隔符并切分:支持 英文逗号/中文逗号/分号/空格
|
||||
var normalized = Code.Trim()
|
||||
.Replace(',', ',')
|
||||
.Replace(';', ',')
|
||||
.Replace(';', ',');
|
||||
var parts = normalized
|
||||
.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(p => p.Trim())
|
||||
.ToArray();
|
||||
|
||||
string rawCode = parts.Length > 0 ? parts[0] : string.Empty;
|
||||
|
||||
// 批号:通常为 8 位日期 yyyyMMdd;保留前 8 位数字
|
||||
string batch = string.Empty;
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
var digits = new string(parts[1].Where(char.IsDigit).ToArray());
|
||||
if (digits.Length >= 8) batch = digits.Substring(0, 8);
|
||||
else batch = digits; // 若不足 8 位,原样返回可供上层判定
|
||||
}
|
||||
|
||||
// 重量:3/4 位数字,最后一位为小数位(例:802 => 80.2g)。
|
||||
string weight = string.Empty;
|
||||
if (parts.Length > 2)
|
||||
{
|
||||
var digits = new string(parts[2].Where(char.IsDigit).ToArray());
|
||||
if (digits.Length >= 2)
|
||||
{
|
||||
// 在最后一位前插入小数点,如 802 => 80.2,1234 => 123.4
|
||||
weight = string.Concat(digits.AsSpan(0, digits.Length - 1), ".", digits.AsSpan(digits.Length - 1));
|
||||
}
|
||||
else if (digits.Length == 1)
|
||||
{
|
||||
weight = "0." + digits; // 兜底:1 位数字视为 0.x
|
||||
}
|
||||
}
|
||||
|
||||
return (rawCode, batch, weight);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (string.Empty, string.Empty, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public static ParsedCodeInfo ParseCodeFull(string code)
|
||||
{
|
||||
var result = new ParsedCodeInfo();
|
||||
if (string.IsNullOrWhiteSpace(code)) return result;
|
||||
result.Code = code;
|
||||
|
||||
try
|
||||
{
|
||||
var normalized = code.Trim()
|
||||
.Replace(',', ',')
|
||||
.Replace(';', ',')
|
||||
.Replace(';', ',');
|
||||
var parts = normalized
|
||||
.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(p => p.Trim())
|
||||
.ToArray();
|
||||
|
||||
result.RawCode = parts.Length > 0 ? parts[0] : string.Empty;
|
||||
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
var digits = new string(parts[1].Where(char.IsDigit).ToArray());
|
||||
result.Batch = digits.Length >= 8 ? digits.Substring(0, 8) : digits;
|
||||
}
|
||||
|
||||
if (parts.Length > 2)
|
||||
{
|
||||
var digits = new string(parts[2].Where(char.IsDigit).ToArray());
|
||||
string weightStr = string.Empty;
|
||||
if (digits.Length >= 2)
|
||||
{
|
||||
weightStr = string.Concat(digits.AsSpan(0, digits.Length - 1), ".", digits.AsSpan(digits.Length - 1));
|
||||
}
|
||||
else if (digits.Length == 1)
|
||||
{
|
||||
weightStr = "0." + digits;
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(weightStr))
|
||||
{
|
||||
if (decimal.TryParse(weightStr, NumberStyles.Any, CultureInfo.InvariantCulture, out var w))
|
||||
result.Weight = w;
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.Length > 3)
|
||||
{
|
||||
var digits = new string(parts[3].Where(char.IsDigit).ToArray());
|
||||
if (int.TryParse(digits, out var m)) result.ShelfLifeMonths = m;
|
||||
}
|
||||
|
||||
if (parts.Length > 4)
|
||||
{
|
||||
var digits = new string(parts[4].Where(char.IsDigit).ToArray());
|
||||
result.RegionCode = digits;
|
||||
result.RegionName = digits == "01" ? "国内" : (digits == "02" ? "日本" : "未知");
|
||||
}
|
||||
|
||||
if (parts.Length > 5)
|
||||
{
|
||||
var digits = new string(parts[5].Where(char.IsDigit).ToArray());
|
||||
if (int.TryParse(digits, out var c)) result.Count = c;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
26
FATrace.Com/ParsedCodeInfo.cs
Normal file
26
FATrace.Com/ParsedCodeInfo.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FATrace.Com
|
||||
{
|
||||
public class ParsedCodeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 直接的条码
|
||||
/// 从这个条码中解析出其他信息
|
||||
/// </summary>
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
public string RawCode { get; set; } = string.Empty;
|
||||
public string RawName { get; set; } = string.Empty;
|
||||
public string Batch { get; set; } = string.Empty;
|
||||
public decimal Weight { get; set; }
|
||||
public int ShelfLifeMonths { get; set; }
|
||||
public string RegionCode { get; set; } = string.Empty;
|
||||
public string RegionName { get; set; } = string.Empty;
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,8 @@ using FreeSql.DataAnnotations;
|
||||
namespace FATrace.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// 下载任务实体(持久化队列项)。
|
||||
/// 表示一条从海康 NVR 下载视频的计划与执行状态。
|
||||
/// 下载任务实体(持久化队列项)
|
||||
/// 表示一条从海康 NVR 下载视频的计划与执行状态
|
||||
/// </summary>
|
||||
[Table(Name = "DownloadTask")]
|
||||
public class DownloadTask
|
||||
@@ -25,8 +25,8 @@ namespace FATrace.Model
|
||||
/// <summary>
|
||||
/// 原料名称
|
||||
/// </summary>
|
||||
[Column(StringLength = 100, IsNullable = false)]
|
||||
public string? RawName { get; set; }
|
||||
[Column(StringLength = 100, IsNullable = true)]
|
||||
public string? RawName { get; set; }="";
|
||||
|
||||
/// <summary>
|
||||
/// 原料条码
|
||||
@@ -62,7 +62,7 @@ namespace FATrace.Model
|
||||
/// 失败时的错误信息
|
||||
/// </summary>
|
||||
[Column(StringLength = 500)]
|
||||
public string? Error { get; set; }
|
||||
public string? Error { get; set; }="";
|
||||
|
||||
/// <summary>
|
||||
/// 已尝试次数(每次 Running 前加一)
|
||||
|
||||
@@ -33,10 +33,34 @@ namespace FATrace.Model
|
||||
public string? RawCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 视频链接
|
||||
/// Video 开始时间(默认当前时间 - 30秒)
|
||||
/// </summary>
|
||||
[Column(Name = "VideoUrl", IsNullable = false, StringLength = 500)]
|
||||
public string? VideoUrl{ get; set; }
|
||||
[Column(IsNullable = false)]
|
||||
public DateTime VideoStartTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Video 结束时间(默认当前时间)
|
||||
/// </summary>
|
||||
[Column(IsNullable = false)]
|
||||
public DateTime VideoEndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 视频本地保存路径
|
||||
/// </summary>
|
||||
[Column(Name = "VideoFilePath", IsNullable = false, StringLength = 500)]
|
||||
public string? VideoFilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 视频名称
|
||||
/// </summary>
|
||||
[Column(Name = "VideoName", IsNullable = false, StringLength = 100)]
|
||||
public string? VideoName { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// 视频链接
|
||||
///// </summary>
|
||||
//[Column(Name = "VideoUrl", IsNullable = false, StringLength = 500)]
|
||||
//public string? VideoUrl{ get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
@@ -44,11 +68,11 @@ namespace FATrace.Model
|
||||
[Column(Name = "User", IsNullable = false, StringLength = 100)]
|
||||
public string? User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// URl状态
|
||||
/// </summary>
|
||||
[Column(Name = "UrlState", IsNullable = false)]
|
||||
public bool UrlState { get; set; }
|
||||
///// <summary>
|
||||
///// URl状态
|
||||
///// </summary>
|
||||
//[Column(Name = "UrlState", IsNullable = false)]
|
||||
//public bool UrlState { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// 视频信息
|
||||
@@ -62,15 +86,15 @@ namespace FATrace.Model
|
||||
[Column(ServerTime = DateTimeKind.Local, CanUpdate = true)]
|
||||
public DateTime CreateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ///////////////////////////////////////////导航属性 LIN 一对一///////////////////////////////////////////////////////
|
||||
/// </summary>
|
||||
public long VideoActionId { get; set; } // 外键字段,必要
|
||||
///// <summary>
|
||||
///// ///////////////////////////////////////////导航属性 LIN 一对一///////////////////////////////////////////////////////
|
||||
///// </summary>
|
||||
//public long VideoActionId { get; set; } // 外键字段,必要
|
||||
|
||||
/// <summary>
|
||||
/// 视频信息
|
||||
/// </summary>
|
||||
public VideoAction? VideoAction { get; set; }
|
||||
///// <summary>
|
||||
///// 视频信息
|
||||
///// </summary>
|
||||
//public VideoAction? VideoAction { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,17 @@
|
||||
<add key="PLCScan" value="600" />
|
||||
<add key="PDAScanCode" value="D1000" />
|
||||
<add key="DownloadTaskMaxRetry" value="3" />
|
||||
<add key="VideoTime" value="30" />
|
||||
<add key="VideoFileSaveDay" value="365" />
|
||||
<add key="DbSaveDay" value="180" />
|
||||
<add key="NVRIP" value="192.168.0.15" />
|
||||
<add key="NVRPort" value="8000" />
|
||||
<add key="NVRUserName" value="admin" />
|
||||
<add key="NVRPw" value="glico@2025" />
|
||||
<add key="NVRVideoSavePath" value="Y:\" />
|
||||
<add key="NVRVideoSavePath" value="Y:" />
|
||||
<add key="InsCheckPort" value="COM10" />
|
||||
<add key="InsCheckRate" value="9600" />
|
||||
<add key="RawUseCsvPath" value="D:\TestData" />
|
||||
<!-- Jellyfin 配置 -->
|
||||
<add key="JellyfinBaseUrl" value="http://192.168.0.30:8096" />
|
||||
<add key="JellyfinApiKey" value="f4285acda9f94efca46a6e4d0a0273c0" />
|
||||
|
||||
@@ -422,6 +422,7 @@
|
||||
<PackageReference Include="ReaLTaiizor" Version="3.8.1.3" />
|
||||
<PackageReference Include="VideoLAN.LibVLC.Windows" Version="3.0.21" />
|
||||
<PackageReference Include="RestSharp" Version="110.2.0" />
|
||||
<PackageReference Include="CsvHelper" Version="33.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
370
FATrace.OEMApp/MainApp.Designer.cs
generated
370
FATrace.OEMApp/MainApp.Designer.cs
generated
@@ -31,34 +31,29 @@ namespace FATrace.OEMApp
|
||||
components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainApp));
|
||||
statusStrip1 = new StatusStrip();
|
||||
tslPlcConnection = new ToolStripStatusLabel();
|
||||
tslSqlConnection = new ToolStripStatusLabel();
|
||||
tslNasConnection = new ToolStripStatusLabel();
|
||||
tslNVRConnection = new ToolStripStatusLabel();
|
||||
imageList1 = new ImageList(components);
|
||||
materialTabControl1 = new ReaLTaiizor.Controls.MaterialTabControl();
|
||||
tabPage1 = new TabPage();
|
||||
materialCard4 = new ReaLTaiizor.Controls.MaterialCard();
|
||||
label18 = new Label();
|
||||
txtIOOutputTime = new TextBox();
|
||||
label17 = new Label();
|
||||
txtIOInputTime = new TextBox();
|
||||
label16 = new Label();
|
||||
txtIOInBagCode = new TextBox();
|
||||
txtRemainBoxCount = new TextBox();
|
||||
label15 = new Label();
|
||||
txtOutBoxCount = new TextBox();
|
||||
label14 = new Label();
|
||||
txtInBoxCount = new TextBox();
|
||||
label13 = new Label();
|
||||
txtCsvSaveState = new TextBox();
|
||||
label12 = new Label();
|
||||
label9 = new Label();
|
||||
txtIORawName = new TextBox();
|
||||
DownloadFileName = new TextBox();
|
||||
label10 = new Label();
|
||||
txtIOOutBagCode = new TextBox();
|
||||
label9 = new Label();
|
||||
label11 = new Label();
|
||||
DownloadProgressBarMain = new ProgressBar();
|
||||
materialCard3 = new ReaLTaiizor.Controls.MaterialCard();
|
||||
LvLog = new ListView();
|
||||
label8 = new Label();
|
||||
materialCard2 = new ReaLTaiizor.Controls.MaterialCard();
|
||||
label19 = new Label();
|
||||
txtRURawCode = new TextBox();
|
||||
gridRULog = new DataGridView();
|
||||
btnRawStopLoadVideo = new Button();
|
||||
DownloadProgressBarMain = new ProgressBar();
|
||||
btnTestAction = new Button();
|
||||
label7 = new Label();
|
||||
txtRURawName = new TextBox();
|
||||
@@ -83,9 +78,8 @@ namespace FATrace.OEMApp
|
||||
metroProgressBar1 = new ReaLTaiizor.Controls.MetroProgressBar();
|
||||
DownloadProgressBar = new ProgressBar();
|
||||
btnStopLoadVideo = new Button();
|
||||
btnLoadVideo = new Button();
|
||||
btnNVRLogin = new Button();
|
||||
LvLog = new ListView();
|
||||
statusStrip1.SuspendLayout();
|
||||
materialTabControl1.SuspendLayout();
|
||||
tabPage1.SuspendLayout();
|
||||
materialCard4.SuspendLayout();
|
||||
@@ -101,12 +95,41 @@ namespace FATrace.OEMApp
|
||||
//
|
||||
// statusStrip1
|
||||
//
|
||||
statusStrip1.Items.AddRange(new ToolStripItem[] { tslPlcConnection, tslSqlConnection, tslNasConnection, tslNVRConnection });
|
||||
statusStrip1.Location = new Point(3, 1005);
|
||||
statusStrip1.Name = "statusStrip1";
|
||||
statusStrip1.Size = new Size(1914, 22);
|
||||
statusStrip1.TabIndex = 1;
|
||||
statusStrip1.Text = "statusStrip1";
|
||||
//
|
||||
// tslPlcConnection
|
||||
//
|
||||
tslPlcConnection.Name = "tslPlcConnection";
|
||||
tslPlcConnection.Size = new Size(474, 17);
|
||||
tslPlcConnection.Spring = true;
|
||||
tslPlcConnection.Text = "Plc连接状态";
|
||||
//
|
||||
// tslSqlConnection
|
||||
//
|
||||
tslSqlConnection.Name = "tslSqlConnection";
|
||||
tslSqlConnection.Size = new Size(474, 17);
|
||||
tslSqlConnection.Spring = true;
|
||||
tslSqlConnection.Text = "服务连接状态";
|
||||
//
|
||||
// tslNasConnection
|
||||
//
|
||||
tslNasConnection.Name = "tslNasConnection";
|
||||
tslNasConnection.Size = new Size(474, 17);
|
||||
tslNasConnection.Spring = true;
|
||||
tslNasConnection.Text = "NAS连接状态";
|
||||
//
|
||||
// tslNVRConnection
|
||||
//
|
||||
tslNVRConnection.Name = "tslNVRConnection";
|
||||
tslNVRConnection.Size = new Size(474, 17);
|
||||
tslNVRConnection.Spring = true;
|
||||
tslNVRConnection.Text = "NVR连接状态";
|
||||
//
|
||||
// imageList1
|
||||
//
|
||||
imageList1.ColorDepth = ColorDepth.Depth32Bit;
|
||||
@@ -155,24 +178,13 @@ namespace FATrace.OEMApp
|
||||
// materialCard4
|
||||
//
|
||||
materialCard4.BackColor = Color.FromArgb(255, 255, 255);
|
||||
materialCard4.Controls.Add(label18);
|
||||
materialCard4.Controls.Add(txtIOOutputTime);
|
||||
materialCard4.Controls.Add(label17);
|
||||
materialCard4.Controls.Add(txtIOInputTime);
|
||||
materialCard4.Controls.Add(label16);
|
||||
materialCard4.Controls.Add(txtIOInBagCode);
|
||||
materialCard4.Controls.Add(txtRemainBoxCount);
|
||||
materialCard4.Controls.Add(label15);
|
||||
materialCard4.Controls.Add(txtOutBoxCount);
|
||||
materialCard4.Controls.Add(label14);
|
||||
materialCard4.Controls.Add(txtInBoxCount);
|
||||
materialCard4.Controls.Add(label13);
|
||||
materialCard4.Controls.Add(txtCsvSaveState);
|
||||
materialCard4.Controls.Add(label12);
|
||||
materialCard4.Controls.Add(label9);
|
||||
materialCard4.Controls.Add(txtIORawName);
|
||||
materialCard4.Controls.Add(DownloadFileName);
|
||||
materialCard4.Controls.Add(label10);
|
||||
materialCard4.Controls.Add(txtIOOutBagCode);
|
||||
materialCard4.Controls.Add(label9);
|
||||
materialCard4.Controls.Add(label11);
|
||||
materialCard4.Controls.Add(DownloadProgressBarMain);
|
||||
materialCard4.Depth = 0;
|
||||
materialCard4.ForeColor = Color.FromArgb(222, 0, 0, 0);
|
||||
materialCard4.Location = new Point(14, 373);
|
||||
@@ -180,187 +192,72 @@ namespace FATrace.OEMApp
|
||||
materialCard4.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
|
||||
materialCard4.Name = "materialCard4";
|
||||
materialCard4.Padding = new Padding(14);
|
||||
materialCard4.Size = new Size(864, 535);
|
||||
materialCard4.Size = new Size(561, 535);
|
||||
materialCard4.TabIndex = 5;
|
||||
//
|
||||
// label18
|
||||
// txtCsvSaveState
|
||||
//
|
||||
label18.AutoSize = true;
|
||||
label18.ForeColor = Color.DimGray;
|
||||
label18.Location = new Point(41, 260);
|
||||
label18.Name = "label18";
|
||||
label18.Size = new Size(88, 26);
|
||||
label18.TabIndex = 17;
|
||||
label18.Text = "出库时间";
|
||||
//
|
||||
// txtIOOutputTime
|
||||
//
|
||||
txtIOOutputTime.Location = new Point(154, 257);
|
||||
txtIOOutputTime.Name = "txtIOOutputTime";
|
||||
txtIOOutputTime.Size = new Size(446, 32);
|
||||
txtIOOutputTime.TabIndex = 16;
|
||||
//
|
||||
// label17
|
||||
//
|
||||
label17.AutoSize = true;
|
||||
label17.ForeColor = Color.DimGray;
|
||||
label17.Location = new Point(41, 211);
|
||||
label17.Name = "label17";
|
||||
label17.Size = new Size(88, 26);
|
||||
label17.TabIndex = 15;
|
||||
label17.Text = "入库时间";
|
||||
//
|
||||
// txtIOInputTime
|
||||
//
|
||||
txtIOInputTime.Location = new Point(154, 208);
|
||||
txtIOInputTime.Name = "txtIOInputTime";
|
||||
txtIOInputTime.Size = new Size(446, 32);
|
||||
txtIOInputTime.TabIndex = 14;
|
||||
//
|
||||
// label16
|
||||
//
|
||||
label16.AutoSize = true;
|
||||
label16.ForeColor = Color.DimGray;
|
||||
label16.Location = new Point(41, 109);
|
||||
label16.Name = "label16";
|
||||
label16.Size = new Size(107, 26);
|
||||
label16.TabIndex = 13;
|
||||
label16.Text = "内袋二维码";
|
||||
//
|
||||
// txtIOInBagCode
|
||||
//
|
||||
txtIOInBagCode.Location = new Point(154, 106);
|
||||
txtIOInBagCode.Name = "txtIOInBagCode";
|
||||
txtIOInBagCode.Size = new Size(446, 32);
|
||||
txtIOInBagCode.TabIndex = 12;
|
||||
//
|
||||
// txtRemainBoxCount
|
||||
//
|
||||
txtRemainBoxCount.BackColor = SystemColors.ButtonHighlight;
|
||||
txtRemainBoxCount.BorderStyle = BorderStyle.None;
|
||||
txtRemainBoxCount.Font = new Font("Microsoft YaHei UI", 21.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
|
||||
txtRemainBoxCount.Location = new Point(507, 417);
|
||||
txtRemainBoxCount.Name = "txtRemainBoxCount";
|
||||
txtRemainBoxCount.ReadOnly = true;
|
||||
txtRemainBoxCount.Size = new Size(100, 37);
|
||||
txtRemainBoxCount.TabIndex = 11;
|
||||
txtRemainBoxCount.Text = "100";
|
||||
txtRemainBoxCount.TextAlign = HorizontalAlignment.Center;
|
||||
//
|
||||
// label15
|
||||
//
|
||||
label15.AutoSize = true;
|
||||
label15.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
|
||||
label15.ForeColor = Color.DimGray;
|
||||
label15.Location = new Point(496, 475);
|
||||
label15.Name = "label15";
|
||||
label15.Size = new Size(117, 28);
|
||||
label15.TabIndex = 10;
|
||||
label15.Text = "剩余总箱数";
|
||||
//
|
||||
// txtOutBoxCount
|
||||
//
|
||||
txtOutBoxCount.BackColor = SystemColors.ButtonHighlight;
|
||||
txtOutBoxCount.BorderStyle = BorderStyle.None;
|
||||
txtOutBoxCount.Font = new Font("Microsoft YaHei UI", 21.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
|
||||
txtOutBoxCount.Location = new Point(317, 417);
|
||||
txtOutBoxCount.Name = "txtOutBoxCount";
|
||||
txtOutBoxCount.ReadOnly = true;
|
||||
txtOutBoxCount.Size = new Size(100, 37);
|
||||
txtOutBoxCount.TabIndex = 9;
|
||||
txtOutBoxCount.Text = "100";
|
||||
txtOutBoxCount.TextAlign = HorizontalAlignment.Center;
|
||||
//
|
||||
// label14
|
||||
//
|
||||
label14.AutoSize = true;
|
||||
label14.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
|
||||
label14.ForeColor = Color.DimGray;
|
||||
label14.Location = new Point(306, 475);
|
||||
label14.Name = "label14";
|
||||
label14.Size = new Size(117, 28);
|
||||
label14.TabIndex = 8;
|
||||
label14.Text = "出库总箱数";
|
||||
//
|
||||
// txtInBoxCount
|
||||
//
|
||||
txtInBoxCount.BackColor = SystemColors.ButtonHighlight;
|
||||
txtInBoxCount.BorderStyle = BorderStyle.None;
|
||||
txtInBoxCount.Font = new Font("Microsoft YaHei UI", 21.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
|
||||
txtInBoxCount.Location = new Point(103, 417);
|
||||
txtInBoxCount.Name = "txtInBoxCount";
|
||||
txtInBoxCount.ReadOnly = true;
|
||||
txtInBoxCount.Size = new Size(100, 37);
|
||||
txtInBoxCount.TabIndex = 7;
|
||||
txtInBoxCount.Text = "100";
|
||||
txtInBoxCount.TextAlign = HorizontalAlignment.Center;
|
||||
//
|
||||
// label13
|
||||
//
|
||||
label13.AutoSize = true;
|
||||
label13.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
|
||||
label13.ForeColor = Color.DimGray;
|
||||
label13.Location = new Point(92, 475);
|
||||
label13.Name = "label13";
|
||||
label13.Size = new Size(117, 28);
|
||||
label13.TabIndex = 6;
|
||||
label13.Text = "入库总箱数";
|
||||
txtCsvSaveState.Location = new Point(27, 264);
|
||||
txtCsvSaveState.Name = "txtCsvSaveState";
|
||||
txtCsvSaveState.ReadOnly = true;
|
||||
txtCsvSaveState.Size = new Size(493, 32);
|
||||
txtCsvSaveState.TabIndex = 15;
|
||||
//
|
||||
// label12
|
||||
//
|
||||
label12.AutoSize = true;
|
||||
label12.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
|
||||
label12.ForeColor = Color.FromArgb(64, 64, 64);
|
||||
label12.Location = new Point(10, 319);
|
||||
label12.ForeColor = Color.DimGray;
|
||||
label12.Location = new Point(26, 229);
|
||||
label12.Name = "label12";
|
||||
label12.Size = new Size(159, 28);
|
||||
label12.TabIndex = 5;
|
||||
label12.Text = "成品出入库统计";
|
||||
label12.Size = new Size(164, 26);
|
||||
label12.TabIndex = 14;
|
||||
label12.Text = "CSV文件生成状态";
|
||||
//
|
||||
// label9
|
||||
// DownloadFileName
|
||||
//
|
||||
label9.AutoSize = true;
|
||||
label9.ForeColor = Color.DimGray;
|
||||
label9.Location = new Point(41, 160);
|
||||
label9.Name = "label9";
|
||||
label9.Size = new Size(88, 26);
|
||||
label9.TabIndex = 4;
|
||||
label9.Text = "原料名称";
|
||||
//
|
||||
// txtIORawName
|
||||
//
|
||||
txtIORawName.Location = new Point(154, 157);
|
||||
txtIORawName.Name = "txtIORawName";
|
||||
txtIORawName.Size = new Size(446, 32);
|
||||
txtIORawName.TabIndex = 3;
|
||||
DownloadFileName.Location = new Point(27, 96);
|
||||
DownloadFileName.Name = "DownloadFileName";
|
||||
DownloadFileName.ReadOnly = true;
|
||||
DownloadFileName.Size = new Size(493, 32);
|
||||
DownloadFileName.TabIndex = 11;
|
||||
//
|
||||
// label10
|
||||
//
|
||||
label10.AutoSize = true;
|
||||
label10.ForeColor = Color.DimGray;
|
||||
label10.Location = new Point(41, 61);
|
||||
label10.Location = new Point(27, 58);
|
||||
label10.Name = "label10";
|
||||
label10.Size = new Size(107, 26);
|
||||
label10.TabIndex = 2;
|
||||
label10.Text = "外箱二维码";
|
||||
label10.Size = new Size(126, 26);
|
||||
label10.TabIndex = 12;
|
||||
label10.Text = "视频文件名称";
|
||||
//
|
||||
// txtIOOutBagCode
|
||||
// label9
|
||||
//
|
||||
txtIOOutBagCode.Location = new Point(154, 58);
|
||||
txtIOOutBagCode.Name = "txtIOOutBagCode";
|
||||
txtIOOutBagCode.Size = new Size(446, 32);
|
||||
txtIOOutBagCode.TabIndex = 1;
|
||||
label9.AutoSize = true;
|
||||
label9.ForeColor = Color.DimGray;
|
||||
label9.Location = new Point(27, 144);
|
||||
label9.Name = "label9";
|
||||
label9.Size = new Size(88, 26);
|
||||
label9.TabIndex = 11;
|
||||
label9.Text = "下载进度";
|
||||
//
|
||||
// label11
|
||||
//
|
||||
label11.AutoSize = true;
|
||||
label11.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134);
|
||||
label11.Font = new Font("Microsoft YaHei UI", 16F, FontStyle.Bold);
|
||||
label11.ForeColor = Color.FromArgb(64, 64, 64);
|
||||
label11.Location = new Point(10, 9);
|
||||
label11.Name = "label11";
|
||||
label11.Size = new Size(159, 28);
|
||||
label11.Size = new Size(145, 30);
|
||||
label11.TabIndex = 0;
|
||||
label11.Text = "成品出入库信息";
|
||||
label11.Text = "视频下载状态";
|
||||
//
|
||||
// DownloadProgressBarMain
|
||||
//
|
||||
DownloadProgressBarMain.Location = new Point(27, 178);
|
||||
DownloadProgressBarMain.Name = "DownloadProgressBarMain";
|
||||
DownloadProgressBarMain.Size = new Size(493, 32);
|
||||
DownloadProgressBarMain.TabIndex = 6;
|
||||
//
|
||||
// materialCard3
|
||||
//
|
||||
@@ -369,14 +266,22 @@ namespace FATrace.OEMApp
|
||||
materialCard3.Controls.Add(label8);
|
||||
materialCard3.Depth = 0;
|
||||
materialCard3.ForeColor = Color.FromArgb(222, 0, 0, 0);
|
||||
materialCard3.Location = new Point(897, 373);
|
||||
materialCard3.Location = new Point(590, 373);
|
||||
materialCard3.Margin = new Padding(14);
|
||||
materialCard3.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER;
|
||||
materialCard3.Name = "materialCard3";
|
||||
materialCard3.Padding = new Padding(14);
|
||||
materialCard3.Size = new Size(1000, 535);
|
||||
materialCard3.Size = new Size(1307, 535);
|
||||
materialCard3.TabIndex = 1;
|
||||
//
|
||||
// LvLog
|
||||
//
|
||||
LvLog.Location = new Point(17, 58);
|
||||
LvLog.Name = "LvLog";
|
||||
LvLog.Size = new Size(1281, 460);
|
||||
LvLog.TabIndex = 6;
|
||||
LvLog.UseCompatibleStateImageBehavior = false;
|
||||
//
|
||||
// label8
|
||||
//
|
||||
label8.AutoSize = true;
|
||||
@@ -391,9 +296,10 @@ namespace FATrace.OEMApp
|
||||
// materialCard2
|
||||
//
|
||||
materialCard2.BackColor = Color.FromArgb(255, 255, 255);
|
||||
materialCard2.Controls.Add(label19);
|
||||
materialCard2.Controls.Add(txtRURawCode);
|
||||
materialCard2.Controls.Add(gridRULog);
|
||||
materialCard2.Controls.Add(btnRawStopLoadVideo);
|
||||
materialCard2.Controls.Add(DownloadProgressBarMain);
|
||||
materialCard2.Controls.Add(btnTestAction);
|
||||
materialCard2.Controls.Add(label7);
|
||||
materialCard2.Controls.Add(txtRURawName);
|
||||
@@ -410,18 +316,35 @@ namespace FATrace.OEMApp
|
||||
materialCard2.Size = new Size(1883, 348);
|
||||
materialCard2.TabIndex = 0;
|
||||
//
|
||||
// label19
|
||||
//
|
||||
label19.AutoSize = true;
|
||||
label19.ForeColor = Color.DimGray;
|
||||
label19.Location = new Point(41, 180);
|
||||
label19.Name = "label19";
|
||||
label19.Size = new Size(88, 26);
|
||||
label19.TabIndex = 10;
|
||||
label19.Text = "原料条码";
|
||||
//
|
||||
// txtRURawCode
|
||||
//
|
||||
txtRURawCode.Location = new Point(154, 177);
|
||||
txtRURawCode.Name = "txtRURawCode";
|
||||
txtRURawCode.Size = new Size(387, 32);
|
||||
txtRURawCode.TabIndex = 9;
|
||||
//
|
||||
// gridRULog
|
||||
//
|
||||
gridRULog.AllowUserToAddRows = false;
|
||||
gridRULog.AllowUserToDeleteRows = false;
|
||||
gridRULog.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
gridRULog.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
gridRULog.Location = new Point(557, 9);
|
||||
gridRULog.Location = new Point(585, 7);
|
||||
gridRULog.Name = "gridRULog";
|
||||
gridRULog.ReadOnly = true;
|
||||
gridRULog.RowHeadersVisible = false;
|
||||
gridRULog.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
gridRULog.Size = new Size(1309, 338);
|
||||
gridRULog.Size = new Size(1281, 338);
|
||||
gridRULog.TabIndex = 8;
|
||||
//
|
||||
// btnRawStopLoadVideo
|
||||
@@ -434,13 +357,6 @@ namespace FATrace.OEMApp
|
||||
btnRawStopLoadVideo.UseVisualStyleBackColor = true;
|
||||
btnRawStopLoadVideo.Click += btnRawStopLoadVideo_Click;
|
||||
//
|
||||
// DownloadProgressBarMain
|
||||
//
|
||||
DownloadProgressBarMain.Location = new Point(7, 321);
|
||||
DownloadProgressBarMain.Name = "DownloadProgressBarMain";
|
||||
DownloadProgressBarMain.Size = new Size(540, 23);
|
||||
DownloadProgressBarMain.TabIndex = 6;
|
||||
//
|
||||
// btnTestAction
|
||||
//
|
||||
btnTestAction.Location = new Point(144, 271);
|
||||
@@ -492,9 +408,9 @@ namespace FATrace.OEMApp
|
||||
label5.ForeColor = Color.FromArgb(64, 64, 64);
|
||||
label5.Location = new Point(10, 9);
|
||||
label5.Name = "label5";
|
||||
label5.Size = new Size(138, 28);
|
||||
label5.Size = new Size(180, 28);
|
||||
label5.TabIndex = 0;
|
||||
label5.Text = "原料使用信息";
|
||||
label5.Text = "当前原料使用信息";
|
||||
//
|
||||
// tabPage2
|
||||
//
|
||||
@@ -654,7 +570,6 @@ namespace FATrace.OEMApp
|
||||
tabPage3.Controls.Add(metroProgressBar1);
|
||||
tabPage3.Controls.Add(DownloadProgressBar);
|
||||
tabPage3.Controls.Add(btnStopLoadVideo);
|
||||
tabPage3.Controls.Add(btnLoadVideo);
|
||||
tabPage3.Controls.Add(btnNVRLogin);
|
||||
tabPage3.ImageKey = "set3.png";
|
||||
tabPage3.Location = new Point(4, 26);
|
||||
@@ -704,16 +619,6 @@ namespace FATrace.OEMApp
|
||||
btnStopLoadVideo.UseVisualStyleBackColor = true;
|
||||
btnStopLoadVideo.Click += btnStopLoadVideo_Click;
|
||||
//
|
||||
// btnLoadVideo
|
||||
//
|
||||
btnLoadVideo.Location = new Point(247, 54);
|
||||
btnLoadVideo.Name = "btnLoadVideo";
|
||||
btnLoadVideo.Size = new Size(130, 58);
|
||||
btnLoadVideo.TabIndex = 1;
|
||||
btnLoadVideo.Text = "下载视频";
|
||||
btnLoadVideo.UseVisualStyleBackColor = true;
|
||||
btnLoadVideo.Click += btnLoadVideo_Click;
|
||||
//
|
||||
// btnNVRLogin
|
||||
//
|
||||
btnNVRLogin.Location = new Point(84, 54);
|
||||
@@ -724,14 +629,6 @@ namespace FATrace.OEMApp
|
||||
btnNVRLogin.UseVisualStyleBackColor = true;
|
||||
btnNVRLogin.Click += btnNVRLogin_Click;
|
||||
//
|
||||
// LvLog
|
||||
//
|
||||
LvLog.Location = new Point(17, 58);
|
||||
LvLog.Name = "LvLog";
|
||||
LvLog.Size = new Size(966, 460);
|
||||
LvLog.TabIndex = 6;
|
||||
LvLog.UseCompatibleStateImageBehavior = false;
|
||||
//
|
||||
// MainApp
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(8F, 17F);
|
||||
@@ -747,6 +644,8 @@ namespace FATrace.OEMApp
|
||||
Text = "添加剂追溯系统";
|
||||
WindowState = FormWindowState.Maximized;
|
||||
Load += MainApp_Load;
|
||||
statusStrip1.ResumeLayout(false);
|
||||
statusStrip1.PerformLayout();
|
||||
materialTabControl1.ResumeLayout(false);
|
||||
tabPage1.ResumeLayout(false);
|
||||
materialCard4.ResumeLayout(false);
|
||||
@@ -778,7 +677,6 @@ namespace FATrace.OEMApp
|
||||
private TabPage tabPage2;
|
||||
private TabPage tabPage3;
|
||||
private Button btnNVRLogin;
|
||||
private Button btnLoadVideo;
|
||||
private Button btnStopLoadVideo;
|
||||
private ProgressBar DownloadProgressBar;
|
||||
private ReaLTaiizor.Controls.MetroProgressBar metroProgressBar1;
|
||||
@@ -804,28 +702,22 @@ namespace FATrace.OEMApp
|
||||
private ReaLTaiizor.Controls.MaterialCard materialCard3;
|
||||
private Label label8;
|
||||
private ReaLTaiizor.Controls.MaterialCard materialCard4;
|
||||
private Label label9;
|
||||
private TextBox txtIORawName;
|
||||
private Label label10;
|
||||
private TextBox txtIOOutBagCode;
|
||||
private Label label11;
|
||||
private Label label12;
|
||||
private Label label13;
|
||||
private TextBox txtInBoxCount;
|
||||
private TextBox txtOutBoxCount;
|
||||
private Label label14;
|
||||
private TextBox txtRemainBoxCount;
|
||||
private Label label15;
|
||||
private Label label16;
|
||||
private TextBox txtIOInBagCode;
|
||||
private Label label18;
|
||||
private TextBox txtIOOutputTime;
|
||||
private Label label17;
|
||||
private TextBox txtIOInputTime;
|
||||
private Button btnTestAction;
|
||||
private ProgressBar DownloadProgressBarMain;
|
||||
private Button btnRawStopLoadVideo;
|
||||
private DataGridView gridRULog;
|
||||
private ListView LvLog;
|
||||
private ToolStripStatusLabel tslPlcConnection;
|
||||
private ToolStripStatusLabel tslSqlConnection;
|
||||
private ToolStripStatusLabel tslNasConnection;
|
||||
private ToolStripStatusLabel tslNVRConnection;
|
||||
private Label label19;
|
||||
private TextBox txtRURawCode;
|
||||
private TextBox DownloadFileName;
|
||||
private Label label10;
|
||||
private Label label9;
|
||||
private Label label12;
|
||||
private TextBox txtCsvSaveState;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ using FATrace.HKNetLib.Hardware;
|
||||
using FATrace.HKNetLib.Wrapper;
|
||||
using FATrace.Model;
|
||||
using FATrace.OEMApp.Services;
|
||||
using FATrace.OEMApp.Model;
|
||||
using System.Threading.Tasks;
|
||||
using LibVLCSharp.Shared;
|
||||
using NLog;
|
||||
using ReaLTaiizor.Forms;
|
||||
@@ -51,8 +53,8 @@ namespace FATrace.OEMApp
|
||||
LvLog.GridLines = true;
|
||||
LvLog.HeaderStyle = ColumnHeaderStyle.Nonclickable;
|
||||
LvLog.Columns.Add("时间", 150);
|
||||
LvLog.Columns.Add("级别", 60);
|
||||
LvLog.Columns.Add("消息", 720);
|
||||
LvLog.Columns.Add("级别", 100);
|
||||
LvLog.Columns.Add("消息", 800);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -98,6 +100,7 @@ namespace FATrace.OEMApp
|
||||
/// </summary>
|
||||
private PLCDataService PLCDataService { get; set; }
|
||||
private TimeClearDataService TimeClearService { get; set; }
|
||||
private System.Windows.Forms.Timer _statusTimer;
|
||||
|
||||
/// <summary>
|
||||
/// 主窗体加载:
|
||||
@@ -143,8 +146,13 @@ namespace FATrace.OEMApp
|
||||
{
|
||||
DownloadTaskWorker.Instance.Start(HkCameraClient);
|
||||
LogInfo("下载队列服务已启动");
|
||||
JellyfinMonitorQueueService.Instance.Start();
|
||||
LogInfo("Jellyfin 监控服务已启动");
|
||||
try { DownloadTaskWorker.Instance.DownloadFileNameChanged += OnDownloadFileNameChanged; } catch { }
|
||||
try { DownloadTaskWorker.Instance.TaskStarted += OnTaskStatusChanged; } catch { }
|
||||
try { DownloadTaskWorker.Instance.TaskCompleted += OnTaskStatusChanged; } catch { }
|
||||
try { DownloadTaskWorker.Instance.TaskFailed += OnTaskFailed; } catch { }
|
||||
// JellyfinMonitorQueueService.Instance.Start();
|
||||
// LogInfo("Jellyfin 监控服务已启动");
|
||||
LogInfo("Jellyfin 监控服务已停用");
|
||||
TimeClearService = new TimeClearDataService();
|
||||
TimeClearService.Info += (m) => LogInfo($"[清理]{m}");
|
||||
TimeClearService.Start();
|
||||
@@ -158,9 +166,15 @@ namespace FATrace.OEMApp
|
||||
|
||||
// 初始化 gridRULog 并启动 UI 定时刷新
|
||||
InitRuLogGrid();
|
||||
// 立即刷新一次,避免首次 1s 空白
|
||||
// 仅初始化一次,当天数据
|
||||
RefreshRuLogGrid();
|
||||
StartTaskUiTimer();
|
||||
|
||||
// 初始化底部连接状态(立即检测一次 + 定时轻量检测)
|
||||
SafeSetStatus(tslPlcConnection, PLCDataService?.PlcConnected == true, "Plc连接状态");
|
||||
_ = UpdateDbStatusAsync();
|
||||
_ = UpdateNasStatusAsync();
|
||||
_ = UpdateNvrStatusAsync();
|
||||
StartStatusTimer();
|
||||
|
||||
//materialListView1.DataBindings
|
||||
try
|
||||
@@ -194,17 +208,30 @@ namespace FATrace.OEMApp
|
||||
/// <param name="Code"></param>
|
||||
private void PLCDataService_ScanCodeEventHandler(object? sender, string Code)
|
||||
{
|
||||
//解析Code条码数据,内包条码数据
|
||||
CurParsedCodeInfo = NVRCom.ParseCodeFull(Code);
|
||||
|
||||
var taskId = DownloadTaskWorker.Instance.Enqueue(
|
||||
CurParsedCodeInfo,
|
||||
user: CurUserName,
|
||||
start: DateTime.Now,
|
||||
end: DateTime.Now.AddSeconds(DownloadTaskWorker.VideoTime+2)
|
||||
);
|
||||
//MessageBox.Show($"[Test] 已入队下载任务,Id={taskId}");
|
||||
|
||||
LogInfo($"扫码: {Code}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PLC数据服务:PLC连接
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <summary>
|
||||
/// PLC数据服务:PLC连接
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void PLCDataService_PlcConnectedEventHandler(object? sender, string e)
|
||||
{
|
||||
LogInfo($"PLC连接: {e}");
|
||||
var ok = string.Equals(e, "OK", StringComparison.OrdinalIgnoreCase);
|
||||
SafeSetStatus(tslPlcConnection, ok, "Plc连接状态");
|
||||
}
|
||||
|
||||
private void NVRLogin()
|
||||
@@ -220,10 +247,12 @@ namespace FATrace.OEMApp
|
||||
{
|
||||
MessageBox.Show("登录成功");
|
||||
LogInfo("NVR 登录成功");
|
||||
_ = UpdateNvrStatusAsync();
|
||||
return;
|
||||
}
|
||||
MessageBox.Show($"登录失败:{HkCameraClient.LastMsgErr}");
|
||||
LogError($"NVR 登录失败: {HkCameraClient.LastMsgErr}");
|
||||
SafeSetStatus(tslNVRConnection, false, "NVR连接状态");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -250,15 +279,24 @@ namespace FATrace.OEMApp
|
||||
|
||||
#region 内包信息
|
||||
|
||||
/// <summary>
|
||||
/// 内包条码
|
||||
/// </summary>
|
||||
public string CurInBagCode { get; set; } = "AAAAAA";
|
||||
///// <summary>
|
||||
///// 内包条码
|
||||
///// </summary>
|
||||
//public string CurInBagCode { get; set; } = "AAAAAA";
|
||||
|
||||
///// <summary>
|
||||
///// 内包条码 原料名称
|
||||
///// </summary>
|
||||
//public string CurRawName { get; set; } = "添加剂Test";
|
||||
///// <summary>
|
||||
///// 内包条码 原料代码
|
||||
///// </summary>
|
||||
//public string CurRawCode { get; set; } = "ASASASS";
|
||||
|
||||
/// <summary>
|
||||
/// 内包条码 原料名称
|
||||
/// 内包条码 解析结果
|
||||
/// </summary>
|
||||
public string CurInBagRawName { get; set; } = "添加剂Test";
|
||||
public ParsedCodeInfo CurParsedCodeInfo { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -282,25 +320,6 @@ namespace FATrace.OEMApp
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 下载按钮:仅将请求入队为 DownloadTask,由后台 DownloadTaskWorker 顺序处理。
|
||||
/// 注意:不会在此处直接调用 SDK 下载,也不会订阅 SDK 事件,避免并发与重复。
|
||||
/// </summary>
|
||||
private void btnLoadVideo_Click(object sender, EventArgs e)
|
||||
{
|
||||
// 仅入队下载任务,由后台 DownloadTaskWorker 顺序处理
|
||||
var taskId = DownloadTaskWorker.Instance.Enqueue(
|
||||
code: CurInBagCode,
|
||||
rawName: CurInBagRawName,
|
||||
user: CurUserName,
|
||||
start: DateTime.Now.AddSeconds(-30),
|
||||
end: DateTime.Now
|
||||
);
|
||||
MessageBox.Show($"已入队下载任务,Id={taskId}");
|
||||
LogInfo($"下载任务入队 Id={taskId} Code={CurInBagCode}");
|
||||
}
|
||||
|
||||
private void HkCameraClient_NVRLoadVideoProcessEventHandler(object? sender, short value)
|
||||
{
|
||||
this.BeginInvoke(new Action(() =>
|
||||
@@ -310,7 +329,7 @@ namespace FATrace.OEMApp
|
||||
if (value >= 100 && _lastProgressLogged != 100)
|
||||
{
|
||||
_lastProgressLogged = 100;
|
||||
LogInfo("下载进度 100%");
|
||||
LogInfo($"{DownloadTaskWorker.Instance.CurDownloadTask.Code} - 下载进度 100%");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -592,13 +611,12 @@ namespace FATrace.OEMApp
|
||||
private void btnTestAction_Click(object sender, EventArgs e)
|
||||
{
|
||||
var taskId = DownloadTaskWorker.Instance.Enqueue(
|
||||
code: CurInBagCode,
|
||||
rawName: CurInBagRawName,
|
||||
CurParsedCodeInfo,
|
||||
user: CurUserName,
|
||||
start: DateTime.Now.AddSeconds(-100),
|
||||
end: DateTime.Now
|
||||
start: DateTime.Now,
|
||||
end: DateTime.Now.AddSeconds(DownloadTaskWorker.VideoTime)
|
||||
);
|
||||
MessageBox.Show($"[Test] 已入队下载任务,Id={taskId}");
|
||||
//MessageBox.Show($"[Test] 已入队下载任务,Id={taskId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -628,59 +646,12 @@ namespace FATrace.OEMApp
|
||||
}
|
||||
|
||||
LogInfo($"下载完成: {localNameOrPath}");
|
||||
|
||||
// 先保存当前的信息,记录主键 Id
|
||||
var rawUse = new OEMRawUse()
|
||||
{
|
||||
InBagCode = CurInBagCode,
|
||||
RawName = CurInBagRawName,
|
||||
User = CurUserName,
|
||||
UrlState = false,
|
||||
VideoUrl = string.Empty
|
||||
};
|
||||
long rawUseId = 0;
|
||||
try
|
||||
{
|
||||
rawUseId = FSqlContext.FDb.Insert<OEMRawUse>(rawUse).ExecuteIdentity();
|
||||
_downloadProcessingKeys[key] = rawUseId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_downloadProcessingKeys.TryRemove(key, out _);
|
||||
System.Diagnostics.Debug.WriteLine($"[NVRLoadVideoComplete] 插入 OEMRawUse 失败: {ex.Message}");
|
||||
return;
|
||||
}
|
||||
|
||||
// 后台执行,避免阻塞 UI 线程
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var monitor = new JellyfinMonitorService();
|
||||
await monitor.MonitorAndUpdateAfterDownloadAsync(
|
||||
oemRawUseId: rawUseId,
|
||||
videoLocalPathOrName: localNameOrPath,
|
||||
code: CurInBagCode,
|
||||
rawName: CurInBagRawName,
|
||||
userName: CurUserName,
|
||||
cancellationToken: CancellationToken.None
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex1)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[JellyfinMonitor] 异常: {ex1.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 移除去重 key,允许后续相同文件再次处理(如需重试)
|
||||
_downloadProcessingKeys.TryRemove(key, out _);
|
||||
}
|
||||
});
|
||||
// Jellyfin 监控已停用:仅移除去重 key
|
||||
_downloadProcessingKeys.TryRemove(key, out _);
|
||||
}
|
||||
|
||||
#region 任务监控(gridRULog / DataGridView)
|
||||
|
||||
private System.Windows.Forms.Timer _taskUiTimer;
|
||||
private BindingSource ruLogBindingSource;
|
||||
private BindingList<RuLogRow> ruLogBindingList;
|
||||
|
||||
@@ -691,6 +662,7 @@ namespace FATrace.OEMApp
|
||||
{
|
||||
public string TimeText { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty; // 下载/监听
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public long TaskId { get; set; }
|
||||
public string StatusText { get; set; } = string.Empty;
|
||||
public string? ProgressText { get; set; }
|
||||
@@ -726,6 +698,14 @@ namespace FATrace.OEMApp
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.None
|
||||
});
|
||||
gridRULog.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colCode",
|
||||
HeaderText = "条码",
|
||||
DataPropertyName = nameof(RuLogRow.Code),
|
||||
Width = 400,
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.None
|
||||
});
|
||||
gridRULog.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
Name = "colTaskId",
|
||||
HeaderText = "任务Id",
|
||||
@@ -772,7 +752,6 @@ namespace FATrace.OEMApp
|
||||
gridRULog.ColumnHeadersDefaultCellStyle.ForeColor = Color.Black;
|
||||
gridRULog.RowHeadersVisible = false;
|
||||
// 使用 Designer 中的 Location/Size 与 Anchor,不额外 Dock
|
||||
gridRULog.BringToFront();
|
||||
|
||||
// 跟随父容器尺寸变化进行定位与大小调整
|
||||
materialCard2.Resize -= MaterialCard2_Resize;
|
||||
@@ -810,7 +789,6 @@ namespace FATrace.OEMApp
|
||||
var height = Math.Max(120, client.Height - top - 9);
|
||||
gridRULog.Location = new Point(left, top);
|
||||
gridRULog.Size = new Size(width, height);
|
||||
gridRULog.BringToFront();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -820,40 +798,22 @@ namespace FATrace.OEMApp
|
||||
LayoutRuLogGrid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动 UI 定时器,定时刷新下载与 Jellyfin 监听任务状态
|
||||
/// </summary>
|
||||
private void StartTaskUiTimer()
|
||||
{
|
||||
_taskUiTimer = new System.Windows.Forms.Timer();
|
||||
_taskUiTimer.Interval = 1000; // 1s 刷新
|
||||
_taskUiTimer.Tick += TaskUiTimer_Tick;
|
||||
_taskUiTimer.Start();
|
||||
}
|
||||
|
||||
private void TaskUiTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
RefreshRuLogGrid();
|
||||
}
|
||||
// 定时刷新已移除:仅在下载开始/完成/失败时按需更新
|
||||
|
||||
/// <summary>
|
||||
/// 刷新 gridRULog(下载 + 监听)
|
||||
/// 刷新 gridRULog(仅当天下载)
|
||||
/// </summary>
|
||||
private void RefreshRuLogGrid()
|
||||
{
|
||||
try
|
||||
{
|
||||
var db = FSqlContext.FDb;
|
||||
var today = DateTime.Today;
|
||||
var downloads = db.Select<DownloadTask>()
|
||||
.Where(a => a.UpdateTime >= today)
|
||||
.OrderByDescending(a => a.UpdateTime)
|
||||
.Limit(50)
|
||||
.ToList();
|
||||
var monitors = db.Select<JellyfinMonitorTask>()
|
||||
.OrderByDescending(a => a.UpdateTime)
|
||||
.Limit(50)
|
||||
.ToList();
|
||||
|
||||
var rows = new List<RuLogRow>(downloads.Count + monitors.Count);
|
||||
var rows = new List<RuLogRow>(downloads.Count);
|
||||
|
||||
// 运行中优先
|
||||
foreach (var t in downloads.Where(x => x.Status == TaskStatus.Running))
|
||||
@@ -862,24 +822,14 @@ namespace FATrace.OEMApp
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "下载",
|
||||
Code = t.Code ?? string.Empty,
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = t.Progress.ToString(),
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.VideoFilePath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
foreach (var t in monitors.Where(x => x.Status == TaskStatus.Running))
|
||||
{
|
||||
rows.Add(new RuLogRow
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "监听",
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = string.Empty,
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.FoundItemId ?? t.LocalFileNameOrPath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
// 监听任务展示已移除
|
||||
|
||||
// 其余
|
||||
foreach (var t in downloads.Where(x => x.Status != TaskStatus.Running))
|
||||
@@ -888,26 +838,16 @@ namespace FATrace.OEMApp
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "下载",
|
||||
Code = t.Code ?? string.Empty,
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = t.Progress.ToString(),
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.VideoFilePath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
foreach (var t in monitors.Where(x => x.Status != TaskStatus.Running))
|
||||
{
|
||||
rows.Add(new RuLogRow
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "监听",
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = string.Empty,
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.FoundItemId ?? t.LocalFileNameOrPath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
// 监听任务展示已移除
|
||||
|
||||
// 批量更新绑定列表,尽量减少闪烁
|
||||
// 批量更新绑定列表
|
||||
ruLogBindingList.RaiseListChangedEvents = false;
|
||||
ruLogBindingList.Clear();
|
||||
foreach (var r in rows)
|
||||
@@ -923,6 +863,138 @@ namespace FATrace.OEMApp
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTaskStatusChanged(DownloadTask t)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (t.UpdateTime.Date != DateTime.Today) return;
|
||||
if (InvokeRequired)
|
||||
{
|
||||
BeginInvoke(new Action(() => UpsertRuLogRowFromTask(t)));
|
||||
}
|
||||
else
|
||||
{
|
||||
UpsertRuLogRowFromTask(t);
|
||||
}
|
||||
|
||||
if (t.Status == TaskStatus.Completed || t.Status == TaskStatus.Failed)
|
||||
{
|
||||
ResetProgressBar();
|
||||
}
|
||||
if (t.Status == TaskStatus.Completed)
|
||||
{
|
||||
_ = SaveCsvForTaskAsync(t);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void OnTaskFailed(DownloadTask t, string? error)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (t.UpdateTime.Date != DateTime.Today) return; // 仅当天
|
||||
if (InvokeRequired)
|
||||
{
|
||||
BeginInvoke(new Action(() => UpsertRuLogRowFromTask(t)));
|
||||
}
|
||||
else
|
||||
{
|
||||
UpsertRuLogRowFromTask(t);
|
||||
}
|
||||
// 失败同样清零进度
|
||||
ResetProgressBar();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void UpsertRuLogRowFromTask(DownloadTask t)
|
||||
{
|
||||
if (ruLogBindingList == null) return;
|
||||
var existing = ruLogBindingList.FirstOrDefault(x => x.TaskId == t.Id);
|
||||
if (existing == null)
|
||||
{
|
||||
ruLogBindingList.Insert(0, new RuLogRow
|
||||
{
|
||||
TimeText = t.UpdateTime.ToString("HH:mm:ss"),
|
||||
Type = "下载",
|
||||
Code = t.Code ?? string.Empty,
|
||||
TaskId = t.Id,
|
||||
StatusText = t.Status.ToString(),
|
||||
ProgressText = t.Progress.ToString(),
|
||||
Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.VideoFilePath ?? string.Empty) : t.Error!
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.TimeText = t.UpdateTime.ToString("HH:mm:ss");
|
||||
existing.StatusText = t.Status.ToString();
|
||||
existing.ProgressText = t.Progress.ToString();
|
||||
existing.Code = t.Code ?? string.Empty;
|
||||
existing.Remark = string.IsNullOrWhiteSpace(t.Error) ? (t.VideoFilePath ?? string.Empty) : t.Error!;
|
||||
}
|
||||
ruLogBindingList.ResetBindings();
|
||||
}
|
||||
|
||||
private void ResetProgressBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (DownloadProgressBarMain == null || DownloadProgressBarMain.IsDisposed) return;
|
||||
if (InvokeRequired)
|
||||
{
|
||||
BeginInvoke(new Action(() => DownloadProgressBarMain.Value = 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
DownloadProgressBarMain.Value = 0;
|
||||
}
|
||||
_lastProgressLogged = -1;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task SaveCsvForTaskAsync(DownloadTask t)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dto = new RawUseCsvDto
|
||||
{
|
||||
RawCode = t.RawCode,
|
||||
RawName = t.RawName,
|
||||
InBagCode = t.Code,
|
||||
OpUser = t.User,
|
||||
VideoSavePath = t.VideoFilePath,
|
||||
UseTime = t.UpdateTime
|
||||
};
|
||||
var svc = new CsvService();
|
||||
var path = await Task.Run(() => svc.ExportSingle(dto));
|
||||
SafeSetCsvState($"CSV保存成功: {path}", true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SafeSetCsvState($"CSV保存失败: {ex.Message}", false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SafeSetCsvState(string text, bool ok)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (txtCsvSaveState == null || txtCsvSaveState.IsDisposed) return;
|
||||
if (InvokeRequired)
|
||||
{
|
||||
BeginInvoke(new Action(() => { txtCsvSaveState.Text = text ?? string.Empty; txtCsvSaveState.ForeColor = ok ? Color.ForestGreen : Color.DarkRed; }));
|
||||
}
|
||||
else
|
||||
{
|
||||
txtCsvSaveState.Text = text ?? string.Empty;
|
||||
txtCsvSaveState.ForeColor = ok ? Color.ForestGreen : Color.DarkRed;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static void SetDataGridViewDoubleBuffered(DataGridView dgv)
|
||||
{
|
||||
try
|
||||
@@ -947,8 +1019,112 @@ namespace FATrace.OEMApp
|
||||
|
||||
protected override void OnFormClosing(FormClosingEventArgs e)
|
||||
{
|
||||
try { DownloadTaskWorker.Instance.DownloadFileNameChanged -= OnDownloadFileNameChanged; } catch { }
|
||||
try { DownloadTaskWorker.Instance.TaskStarted -= OnTaskStatusChanged; } catch { }
|
||||
try { DownloadTaskWorker.Instance.TaskCompleted -= OnTaskStatusChanged; } catch { }
|
||||
try { DownloadTaskWorker.Instance.TaskFailed -= OnTaskFailed; } catch { }
|
||||
try { _statusTimer?.Stop(); _statusTimer?.Dispose(); _statusTimer = null; } catch { }
|
||||
try { TimeClearService?.Stop(); } catch { }
|
||||
base.OnFormClosing(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前下载文件的名称改变
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
private void OnDownloadFileNameChanged(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (DownloadFileName == null || DownloadFileName.IsDisposed) return;
|
||||
if (InvokeRequired)
|
||||
{
|
||||
try { BeginInvoke(new Action(() => DownloadFileName.Text = name ?? string.Empty)); } catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
try { BeginInvoke(new Action(() => DownloadFileName.Text = name ?? string.Empty)); } catch { }
|
||||
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
#region 底部状态栏(PLC/DB/NAS/NVR)
|
||||
private void StartStatusTimer()
|
||||
{
|
||||
try
|
||||
{
|
||||
_statusTimer = new System.Windows.Forms.Timer();
|
||||
_statusTimer.Interval = 30000; // 30s 轻量检测
|
||||
_statusTimer.Tick += async (s, e) =>
|
||||
{
|
||||
await UpdateDbStatusAsync();
|
||||
await UpdateNasStatusAsync();
|
||||
await UpdateNvrStatusAsync();
|
||||
};
|
||||
_statusTimer.Start();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void SafeSetStatus(ToolStripStatusLabel lbl, bool ok, string title)
|
||||
{
|
||||
try
|
||||
{
|
||||
string text = ok ? $"{title}: 正常" : $"{title}: 异常";
|
||||
var fore = ok ? Color.ForestGreen : Color.DarkRed;
|
||||
if (InvokeRequired)
|
||||
{
|
||||
BeginInvoke(new Action(() => { lbl.Text = text; lbl.ForeColor = fore; }));
|
||||
}
|
||||
else
|
||||
{
|
||||
lbl.Text = text; lbl.ForeColor = fore;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task UpdateDbStatusAsync()
|
||||
{
|
||||
bool ok = false;
|
||||
try
|
||||
{
|
||||
var db = FSqlContext.FDb;
|
||||
var ret = await db.Ado.ExecuteScalarAsync("SELECT 1");
|
||||
ok = Convert.ToString(ret) == "1";
|
||||
}
|
||||
catch { ok = false; }
|
||||
SafeSetStatus(tslSqlConnection, ok, "服务连接状态");
|
||||
}
|
||||
|
||||
private async Task UpdateNasStatusAsync()
|
||||
{
|
||||
bool ok = false;
|
||||
try
|
||||
{
|
||||
var path = HkCameraClient?.NVRVideoSavePath;
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
ok = await Task.Run(() => System.IO.Directory.Exists(path));
|
||||
}
|
||||
}
|
||||
catch { ok = false; }
|
||||
SafeSetStatus(tslNasConnection, ok, "NAS连接状态");
|
||||
}
|
||||
|
||||
private async Task UpdateNvrStatusAsync()
|
||||
{
|
||||
bool ok = false;
|
||||
try
|
||||
{
|
||||
ok = HkCameraClient != null && HkCameraClient.NVRLoginState && HkCameraClient.IsOnline();
|
||||
}
|
||||
catch { ok = false; }
|
||||
await Task.Yield();
|
||||
SafeSetStatus(tslNVRConnection, ok, "NVR连接状态");
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs
|
||||
LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu
|
||||
SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAaCEAAAJNU0Z0AUkBTAIBAQgB
|
||||
AAFYAQEBWAEBARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAATADAAEBAQABIAYAATD/
|
||||
AAGIAQEBiAEBARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAATADAAEBAQABIAYAATD/
|
||||
AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AFIAAdsBlgESAf8B2wGWARIB/wQAAxIBGANJAYgB
|
||||
2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsB
|
||||
lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAHbAZYB
|
||||
@@ -217,7 +217,7 @@
|
||||
2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wMqAUAY
|
||||
AAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B
|
||||
2wGWARIB/xgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB
|
||||
lgESAf8B2wGWARIB/wJ8AVsB+BAAAm8BUQH3AdsBlgESAf8MAAMzAVAB2wGWARIB/wQAAdsBlgESAf8Q
|
||||
lgESAf8B2wGWARIB/wJ8AVwB+BAAAm8BUQH3AdsBlgESAf8MAAMzAVAB2wGWARIB/wQAAdsBlgESAf8Q
|
||||
AAHbAZYBEgH/EAACWwFZAcAgAAJHAUYBgAMqAUAUAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB
|
||||
EgH/DAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/CAAB2wGWARIB/wHbAZYB
|
||||
EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B
|
||||
|
||||
49
FATrace.OEMApp/Model/RawUseCsvDto.cs
Normal file
49
FATrace.OEMApp/Model/RawUseCsvDto.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FATrace.OEMApp.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// 原材料使用CSV保存信息
|
||||
/// </summary>
|
||||
public class RawUseCsvDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 原料编号
|
||||
/// </summary>
|
||||
public string? RawCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原料名称
|
||||
/// </summary>
|
||||
public string? RawName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 内袋二维码
|
||||
/// </summary>
|
||||
public string? InBagCode { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// 外箱二维码
|
||||
///// </summary>
|
||||
//public string? BoxCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作者
|
||||
/// </summary>
|
||||
public string? OpUser { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 视频保存路径
|
||||
/// </summary>
|
||||
public string? VideoSavePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原料使用时间
|
||||
/// </summary>
|
||||
public DateTime UseTime { get; set; }
|
||||
}
|
||||
}
|
||||
23
FATrace.OEMApp/Model/RawUseCsvDtoMap.cs
Normal file
23
FATrace.OEMApp/Model/RawUseCsvDtoMap.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using CsvHelper.Configuration;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FATrace.OEMApp.Model
|
||||
{
|
||||
public class RawUseCsvDtoMap : ClassMap<RawUseCsvDto>
|
||||
{
|
||||
public RawUseCsvDtoMap()
|
||||
{
|
||||
Map(x => x.RawCode).Index(0).Name("原料编号");
|
||||
Map(x => x.RawName).Index(1).Name("原料名称");
|
||||
Map(x => x.InBagCode).Index(2).Name("内袋二维码");
|
||||
Map(x => x.OpUser).Index(3).Name("操作者");
|
||||
Map(x => x.VideoSavePath).Index(4).Name("视频保存路径");
|
||||
Map(x => x.UseTime).Index(5).Name("使用时间").TypeConverterOption.Format("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
92
FATrace.OEMApp/Services/CsvService.cs
Normal file
92
FATrace.OEMApp/Services/CsvService.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using FATrace.Com;
|
||||
using FATrace.OEMApp.Model;
|
||||
using NLog;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace FATrace.OEMApp.Services
|
||||
{
|
||||
public class CsvService
|
||||
{
|
||||
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
|
||||
public CsvService()
|
||||
{
|
||||
RawUseCsvPath = ConfigHelper.GetValue("RawUseCsvPath");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 原料使用信息CSV文件路径
|
||||
/// </summary>
|
||||
public string RawUseCsvPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 将一条原料使用记录导出为单独CSV文件(包含表头)
|
||||
/// 文件保存目录来自 RawUseCsvPath;文件名包含时间戳与内袋二维码(若有)
|
||||
/// </summary>
|
||||
/// <param name="data">原料使用记录</param>
|
||||
/// <returns>生成的CSV完整路径</returns>
|
||||
/// <exception cref="ArgumentNullException">当 data 为空时抛出</exception>
|
||||
/// <exception cref="InvalidOperationException">当 RawUseCsvPath 未配置时抛出</exception>
|
||||
/// <exception cref="IOException">当写入文件失败时抛出</exception>
|
||||
public string ExportSingle(RawUseCsvDto data)
|
||||
{
|
||||
if (data == null) throw new ArgumentNullException(nameof(data));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(RawUseCsvPath))
|
||||
{
|
||||
const string msg = "RawUseCsvPath 未配置,无法导出CSV。";
|
||||
_logger.Error(msg);
|
||||
throw new InvalidOperationException(msg);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 确保目录存在
|
||||
Directory.CreateDirectory(RawUseCsvPath);
|
||||
|
||||
// 构建安全文件名:时间戳_内袋二维码.csv(若二维码为空则用RawUse代替)
|
||||
var safeCode = SanitizeFileName(string.IsNullOrWhiteSpace(data.InBagCode) ? "RawUse" : data.InBagCode!.Trim());
|
||||
var fileName = $"{safeCode}.csv";
|
||||
var fullPath = Path.Combine(RawUseCsvPath, fileName);
|
||||
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
HasHeaderRecord = true,
|
||||
};
|
||||
|
||||
using (var writer = new StreamWriter(fullPath, false, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)))
|
||||
using (var csv = new CsvWriter(writer, config))
|
||||
{
|
||||
csv.Context.RegisterClassMap<RawUseCsvDtoMap>();
|
||||
|
||||
csv.WriteHeader<RawUseCsvDto>();
|
||||
csv.NextRecord();
|
||||
csv.WriteRecord(data);
|
||||
csv.NextRecord();
|
||||
}
|
||||
|
||||
_logger.Info($"CSV 导出成功: {fullPath}");
|
||||
return fullPath;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "导出原料使用CSV失败。");
|
||||
throw new IOException("导出原料使用CSV失败。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string name)
|
||||
{
|
||||
var invalid = Path.GetInvalidFileNameChars();
|
||||
var sb = new StringBuilder(name.Length);
|
||||
foreach (var ch in name)
|
||||
{
|
||||
sb.Append(invalid.Contains(ch) ? '_' : ch);
|
||||
}
|
||||
var result = sb.ToString();
|
||||
return string.IsNullOrWhiteSpace(result) ? "RawUse" : result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,30 @@ namespace FATrace.OEMApp.Services
|
||||
|
||||
private HkCamera? _hk; // 供下载与事件回调使用的海康客户端(由 UI 注入)
|
||||
|
||||
private DownloadTaskWorker() { }
|
||||
// 当前下载文件名变更事件(用于 UI 显示)
|
||||
public event Action<string>? DownloadFileNameChanged;
|
||||
private void RaiseDownloadFileName(string name)
|
||||
{
|
||||
try { DownloadFileNameChanged?.Invoke(name); } catch { }
|
||||
}
|
||||
|
||||
// 任务状态事件(用于按需刷新 gridRULog)
|
||||
public event Action<DownloadTask>? TaskStarted;
|
||||
public event Action<DownloadTask>? TaskCompleted;
|
||||
public event Action<DownloadTask, string?>? TaskFailed;
|
||||
private void RaiseTaskStarted(DownloadTask t) { try { TaskStarted?.Invoke(t); } catch { } }
|
||||
private void RaiseTaskCompleted(DownloadTask t) { try { TaskCompleted?.Invoke(t); } catch { } }
|
||||
private void RaiseTaskFailed(DownloadTask t, string? err) { try { TaskFailed?.Invoke(t, err); } catch { } }
|
||||
|
||||
/// <summary>
|
||||
/// 下载的视频时间
|
||||
/// </summary>
|
||||
public static int VideoTime { get; set; }
|
||||
|
||||
private DownloadTaskWorker()
|
||||
{
|
||||
VideoTime = ConfigHelper.GetIntOrDefault("VideoTime", 30);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动下载队列后台循环。
|
||||
@@ -105,10 +128,10 @@ namespace FATrace.OEMApp.Services
|
||||
/// <param name="end">NVR 下载结束时间(默认当前时间)。</param>
|
||||
/// <param name="rawCode">原料条码(可选)。未提供时默认等于 code。</param>
|
||||
/// <returns>新增 DownloadTask 的自增 Id。</returns>
|
||||
public long Enqueue(string code, string rawName, string user, DateTime? start = null, DateTime? end = null, string? rawCode = null)
|
||||
public long Enqueue(ParsedCodeInfo parsedCodeInfo, string user, DateTime? start = null, DateTime? end = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(code)) throw new ArgumentException("code 不能为空");
|
||||
if (string.IsNullOrWhiteSpace(rawName)) throw new ArgumentException("rawName 不能为空");
|
||||
if (parsedCodeInfo == null) throw new ArgumentException("code 不能为空");
|
||||
if (string.IsNullOrWhiteSpace(parsedCodeInfo.Code)) throw new ArgumentException("code 不能为空");
|
||||
if (string.IsNullOrWhiteSpace(user)) throw new ArgumentException("user 不能为空");
|
||||
|
||||
var now = DateTime.Now;
|
||||
@@ -117,13 +140,13 @@ namespace FATrace.OEMApp.Services
|
||||
// - rawCode 未提供时回落到 code,避免非空列插入失败
|
||||
var task = new DownloadTask
|
||||
{
|
||||
Code = code,
|
||||
RawName = rawName,
|
||||
RawCode = rawCode ?? code,
|
||||
Code = parsedCodeInfo.Code,
|
||||
RawName = parsedCodeInfo.RawName,
|
||||
RawCode = parsedCodeInfo.RawCode,
|
||||
User = user,
|
||||
Status = TaskStatus.Pending,
|
||||
Progress = 0,
|
||||
NvrStartTime = start ?? now.AddSeconds(-30),
|
||||
NvrStartTime = start ?? now.AddSeconds(-VideoTime),
|
||||
NvrEndTime = end ?? now,
|
||||
CreateTime = now,
|
||||
UpdateTime = now
|
||||
@@ -131,7 +154,7 @@ namespace FATrace.OEMApp.Services
|
||||
|
||||
// 入库返回自增 Id,便于 UI 提示与后续跟踪
|
||||
var id = FSqlContext.FDb.Insert<DownloadTask>(task).ExecuteIdentity();
|
||||
_logger.Info("[DownloadTaskWorker] 入队 DownloadTask: Id={Id}, Code={Code}", id, code);
|
||||
_logger.Info("[DownloadTaskWorker] 入队 DownloadTask: Id={Id}, Code={Code}", id, parsedCodeInfo.Code);
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -143,26 +166,27 @@ namespace FATrace.OEMApp.Services
|
||||
{
|
||||
while (!token.IsCancellationRequested)
|
||||
{
|
||||
DownloadTask? next = null;
|
||||
try
|
||||
{
|
||||
// 查询最早入队但未处理的任务
|
||||
var db = FSqlContext.FDb;
|
||||
var next = await db.Select<DownloadTask>()
|
||||
.Where(t => t.Status == TaskStatus.Pending)
|
||||
.OrderBy(t => t.Id)
|
||||
.FirstAsync();
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
// 暂无任务,稍候再查
|
||||
await Task.Delay(5000, token);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 使用信号量确保同一时间仅有一个任务进入下载处理
|
||||
await _singleRunner.WaitAsync(token);
|
||||
// 通过 ContinueWith 在任务结束时释放信号量,避免阻塞主循环
|
||||
_ = ProcessTaskAsync(next, token).ContinueWith(_ => _singleRunner.Release());
|
||||
try
|
||||
{
|
||||
var db = FSqlContext.FDb;
|
||||
next = await db.Select<DownloadTask>()
|
||||
.Where(t => t.Status == TaskStatus.Pending && t.NvrEndTime < DateTime.Now.AddSeconds(5))
|
||||
.OrderBy(t => t.Id)
|
||||
.FirstAsync();
|
||||
|
||||
if (next != null)
|
||||
{
|
||||
await ProcessTaskAsync(next, token);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_singleRunner.Release();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -171,12 +195,21 @@ namespace FATrace.OEMApp.Services
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "[DownloadTaskWorker] 主循环异常");
|
||||
try { await Task.Delay(2000, token); } catch { }
|
||||
}
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
try { await Task.Delay(5000, token); } catch { }
|
||||
}
|
||||
}
|
||||
_logger.Info("[DownloadTaskWorker] 已停止");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前处理中的 DownloadTask
|
||||
/// </summary>
|
||||
public DownloadTask CurDownloadTask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实际执行单个下载任务:
|
||||
/// 1) 标记 Running,生成保存路径
|
||||
@@ -186,39 +219,46 @@ namespace FATrace.OEMApp.Services
|
||||
/// </summary>
|
||||
/// <param name="t">待处理的 DownloadTask。</param>
|
||||
/// <param name="token">取消令牌。</param>
|
||||
private async Task ProcessTaskAsync(DownloadTask t, CancellationToken token)
|
||||
private async Task ProcessTaskAsync(DownloadTask downloadTask, CancellationToken token)
|
||||
{
|
||||
if (_hk == null)
|
||||
{
|
||||
_logger.Warn("[DownloadTaskWorker] HkCamera 未初始化,跳过任务 Id={Id}", t.Id);
|
||||
_logger.Warn("[DownloadTaskWorker] HkCamera 未初始化,跳过任务 Id={Id}", downloadTask.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
CurDownloadTask = downloadTask;
|
||||
|
||||
var db = FSqlContext.FDb;
|
||||
|
||||
// 步骤1:状态入库(Running/TryCount/Progress/UpdateTime)
|
||||
// 标记运行中
|
||||
t.Status = TaskStatus.Running;
|
||||
t.TryCount += 1;
|
||||
t.Progress = 0;
|
||||
t.UpdateTime = DateTime.Now;
|
||||
downloadTask.Status = TaskStatus.Running;
|
||||
downloadTask.TryCount += 1;
|
||||
downloadTask.Progress = 0;
|
||||
downloadTask.UpdateTime = DateTime.Now;
|
||||
await db.Update<DownloadTask>()
|
||||
.SetSource(t)
|
||||
.SetSource(downloadTask)
|
||||
.UpdateColumns(a => new { a.Status, a.TryCount, a.Progress, a.UpdateTime })
|
||||
.Where(a => a.Id == t.Id)
|
||||
.Where(a => a.Id == downloadTask.Id)
|
||||
.ExecuteAffrowsAsync();
|
||||
|
||||
// 通知任务开始
|
||||
RaiseTaskStarted(downloadTask);
|
||||
|
||||
// 步骤2:生成保存路径(含安全文件名),并确保保存目录存在
|
||||
// 生成本地文件名/路径
|
||||
var saveBase = _hk.NVRVideoSavePath;
|
||||
if (string.IsNullOrWhiteSpace(saveBase))
|
||||
{
|
||||
_logger.Error("[DownloadTaskWorker] NVRVideoSavePath 为空,无法下载。任务 Id={Id}", t.Id);
|
||||
await MarkFailedAsync(t, "NVRVideoSavePath 未配置");
|
||||
_logger.Error("[DownloadTaskWorker] NVRVideoSavePath 为空,无法下载。任务 Id={Id}", downloadTask.Id);
|
||||
await MarkFailedAsync(downloadTask, "NVRVideoSavePath 未配置");
|
||||
return;
|
||||
}
|
||||
var filePath = NVRCom.GetVideoName(saveBase, t.Code ?? "CODE");
|
||||
var filePath = NVRCom.GetVideoPathName(saveBase, downloadTask.Code!);
|
||||
// 确保保存目录存在,避免 SDK 写文件失败
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
var dir = Path.GetDirectoryName(filePath);
|
||||
@@ -231,9 +271,15 @@ namespace FATrace.OEMApp.Services
|
||||
await db.Update<DownloadTask>()
|
||||
.Set(a => a.VideoFilePath, filePath)
|
||||
.Set(a => a.UpdateTime, DateTime.Now)
|
||||
.Where(a => a.Id == t.Id)
|
||||
.Where(a => a.Id == downloadTask.Id)
|
||||
.ExecuteAffrowsAsync();
|
||||
|
||||
//当前的下载路径
|
||||
downloadTask.VideoFilePath = filePath;
|
||||
|
||||
// 通知 UI 当前下载文件名
|
||||
RaiseDownloadFileName(Path.GetFileName(filePath) ?? string.Empty);
|
||||
|
||||
// 步骤3:订阅 SDK 事件 -> TCS 转换
|
||||
// 事件 -> TCS
|
||||
// 订阅两个事件:进度与完成。进度事件写回数据库;完成事件用于唤醒等待
|
||||
@@ -243,6 +289,8 @@ namespace FATrace.OEMApp.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
//实时更新下载进度
|
||||
|
||||
//db.Update<DownloadTask>()
|
||||
// .Set(a => a.Progress, Math.Max((short)0, Math.Min((short)100, p)))
|
||||
// .Set(a => a.UpdateTime, DateTime.Now)
|
||||
@@ -268,114 +316,136 @@ namespace FATrace.OEMApp.Services
|
||||
try
|
||||
{
|
||||
// 发起下载(按时间范围)
|
||||
var res = _hk.Sdk_NET_DVR_GetFileByTime_V40(t.NvrStartTime, t.NvrEndTime, filePath);
|
||||
var res = _hk.Sdk_NET_DVR_GetFileByTime_V40(downloadTask.NvrStartTime, downloadTask.NvrEndTime, filePath);
|
||||
if (!res.Result)
|
||||
{
|
||||
await MarkFailedAsync(t, res.Msg);
|
||||
await MarkFailedAsync(downloadTask, res.Msg);
|
||||
return;
|
||||
}
|
||||
|
||||
// HkCamera 内部会启动进度监控:此处等待完成事件触发(带超时)
|
||||
var timeWindowSec = Math.Max(1, (int)(t.NvrEndTime - t.NvrStartTime).TotalSeconds);
|
||||
var timeoutSec = ConfigHelper.GetIntOrDefault("DownloadTaskTimeoutSeconds", Math.Max(120, Math.Min(1800, timeWindowSec * 3)));
|
||||
//下载过程中的监控事件
|
||||
// 计算这次下载的时间窗口长度(秒),用于推导合理的超时
|
||||
var timeWindowSec = Math.Max(1, (int)(downloadTask.NvrEndTime - downloadTask.NvrStartTime).TotalSeconds);
|
||||
|
||||
// 计算总超时秒数:优先读配置 DownloadTaskTimeoutSeconds;
|
||||
// 如果未配置,用时间窗口 * 3 的经验值,并限制在 [120, 1800] 区间,防止过短/过长。
|
||||
var timeoutSec = ConfigHelper.GetIntOrDefault(
|
||||
"DownloadTaskTimeoutSeconds",
|
||||
Math.Max(120, Math.Min(1800, timeWindowSec * 3))
|
||||
);
|
||||
|
||||
// completed 代表“下载完成”这个事件(由 tcs.TrySetResult 在回调里触发)
|
||||
Task completed = tcs.Task;
|
||||
|
||||
// timeoutTask 是“超时定时器”,超时时间到会完成;同时受 token 取消影响
|
||||
Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSec), token);
|
||||
|
||||
// 当外部取消(Stop/退出)时,主动让 tcs 进入取消态,避免一直等待 completed
|
||||
using (token.Register(() =>
|
||||
{
|
||||
try { tcs.TrySetCanceled(); } catch { }
|
||||
}))
|
||||
{
|
||||
// 等待“完成事件”或“超时”二者之一先发生
|
||||
var finished = await Task.WhenAny(completed, timeoutTask);
|
||||
|
||||
// 如果先等到了 timeoutTask,说明超时发生
|
||||
if (finished == timeoutTask)
|
||||
{
|
||||
// 尽力停止 SDK 的下载(防止后台还在跑)
|
||||
try { _hk.Sdk_NET_DVR_StopGetFile(); } catch { }
|
||||
await MarkFailedAsync(t, $"下载超时({timeoutSec}s)");
|
||||
return;
|
||||
// 将任务标记为失败(原因是超时),写入数据库状态
|
||||
await MarkFailedAsync(downloadTask, $"下载超时({timeoutSec}s)");
|
||||
return; // 结束本次任务处理
|
||||
}
|
||||
}
|
||||
|
||||
// 能走到这里,表示 completed 先发生(下载完成事件被触发)
|
||||
// 读取回调携带的完成消息(有些 SDK 会返回提示文本,例如“下载完成”)
|
||||
var completeMsg = await ((Task<string>)tcs.Task);
|
||||
// 依据完成事件消息判断成功/失败(包含“完成”视为成功)
|
||||
|
||||
// 简单的成功判断:消息非空,并包含“完成”字样就算成功
|
||||
var succeed = !string.IsNullOrWhiteSpace(completeMsg) && completeMsg.Contains("完成");
|
||||
|
||||
// 若判断不通过,视为失败,尽力停止下载并入库失败原因
|
||||
if (!succeed)
|
||||
{
|
||||
try { _hk.Sdk_NET_DVR_StopGetFile(); } catch { }
|
||||
await MarkFailedAsync(t, string.IsNullOrWhiteSpace(completeMsg) ? "下载失败" : completeMsg);
|
||||
await MarkFailedAsync(downloadTask, string.IsNullOrWhiteSpace(completeMsg) ? "下载失败" : completeMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// 走到这里即认为下载成功,后续会继续执行文件校验与入库逻辑
|
||||
|
||||
// 步骤5:文件有效性检查(存在且大小>0)
|
||||
// 文件有效性检查
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath) || new FileInfo(filePath).Length <= 0)
|
||||
{
|
||||
await MarkFailedAsync(t, "下载文件不存在或为空");
|
||||
await MarkFailedAsync(downloadTask, "下载文件不存在或为空");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 文件系统异常
|
||||
await MarkFailedAsync(t, "下载文件验证异常");
|
||||
await MarkFailedAsync(downloadTask, "下载文件验证异常");
|
||||
return;
|
||||
}
|
||||
|
||||
// 步骤6:入库——写 OEMRawUse 并创建 Jellyfin 监听任务
|
||||
// 下载完成:写 OEMRawUse 并创建 JellyfinMonitorTask
|
||||
// 1) 插入 OEMRawUse(UrlState=false, VideoUrl 空)
|
||||
//// 步骤6:入库(不再使用 Jellyfin 监听):
|
||||
//// 1) 插入 VideoAction(保存本地视频元数据)
|
||||
//var va = new VideoAction
|
||||
//{
|
||||
// Code = downloadTask.Code,
|
||||
// User = downloadTask.User,
|
||||
// VideoFilePath = filePath,
|
||||
// VideoName = Path.GetFileName(filePath) ?? string.Empty,
|
||||
// StartTime = downloadTask.NvrStartTime,
|
||||
// EndTime = downloadTask.NvrEndTime,
|
||||
// CreateTime = DateTime.Now
|
||||
//};
|
||||
//var videoActionId = db.Insert<VideoAction>(va).ExecuteIdentity();
|
||||
|
||||
// 2) 插入 OEMRawUse(直接填充本地文件路径作为 VideoUrl,并置 UrlState=true)
|
||||
var rawUse = new OEMRawUse
|
||||
{
|
||||
InBagCode = t.Code,
|
||||
RawName = t.RawName,
|
||||
RawCode = t.RawCode,
|
||||
User = t.User,
|
||||
UrlState = false,
|
||||
VideoUrl = string.Empty,
|
||||
CreateTime = DateTime.Now,
|
||||
VideoActionId = 0
|
||||
InBagCode = downloadTask.Code,
|
||||
RawName = downloadTask.RawName,
|
||||
RawCode = downloadTask.RawCode,
|
||||
User = downloadTask.User,
|
||||
VideoStartTime = downloadTask.NvrStartTime,
|
||||
VideoEndTime = downloadTask.NvrEndTime,
|
||||
VideoFilePath = downloadTask.VideoFilePath,
|
||||
VideoName = NVRCom.GetVideoName(downloadTask.Code!),
|
||||
};
|
||||
var rawUseId = db.Insert<OEMRawUse>(rawUse).ExecuteIdentity();
|
||||
|
||||
// 2) 创建 Jellyfin 监听任务,交由 JellyfinMonitorQueueService 批量匹配
|
||||
var jfTask = new JellyfinMonitorTask
|
||||
{
|
||||
OemRawUseId = rawUseId,
|
||||
LocalFileNameOrPath = filePath,
|
||||
Code = t.Code,
|
||||
RawName = t.RawName,
|
||||
User = t.User,
|
||||
NvrStartTime = t.NvrStartTime,
|
||||
NvrEndTime = t.NvrEndTime,
|
||||
Status = TaskStatus.Pending,
|
||||
TryCount = 0,
|
||||
CreateTime = DateTime.Now,
|
||||
UpdateTime = DateTime.Now
|
||||
};
|
||||
db.Insert<JellyfinMonitorTask>(jfTask).ExecuteAffrows();
|
||||
|
||||
// 步骤7:收尾——标记下载完成并记录日志
|
||||
// 标记下载完成
|
||||
t.Status = TaskStatus.Completed;
|
||||
t.Progress = 100;
|
||||
t.UpdateTime = DateTime.Now;
|
||||
downloadTask.Status = TaskStatus.Completed;
|
||||
downloadTask.Progress = 100;
|
||||
downloadTask.UpdateTime = DateTime.Now;
|
||||
await db.Update<DownloadTask>()
|
||||
.SetSource(t)
|
||||
.SetSource(downloadTask)
|
||||
.UpdateColumns(a => new { a.Status, a.Progress, a.UpdateTime })
|
||||
.Where(a => a.Id == t.Id)
|
||||
.Where(a => a.Id == downloadTask.Id)
|
||||
.ExecuteAffrowsAsync();
|
||||
|
||||
_logger.Info("[DownloadTaskWorker] 下载完成,已创建 Jellyfin 监控任务 DownloadTaskId={Id}, OEMRawUseId={RawUseId}", t.Id, rawUseId);
|
||||
_logger.Info("[DownloadTaskWorker] 下载完成并入库:DownloadTaskId={Id}, OEMRawUseId={RawUseId}", downloadTask.Id, rawUseId);
|
||||
// 通知任务完成
|
||||
RaiseTaskCompleted(downloadTask);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// 取消:停止当前 SDK 下载并标记失败
|
||||
try { _hk.Sdk_NET_DVR_StopGetFile(); } catch { }
|
||||
await MarkFailedAsync(t, "任务被取消");
|
||||
await MarkFailedAsync(downloadTask, "任务被取消");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await MarkFailedAsync(t, ex.Message);
|
||||
await MarkFailedAsync(downloadTask, ex.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -383,6 +453,8 @@ namespace FATrace.OEMApp.Services
|
||||
try { _hk.NVRLoadVideoProcessEventHandler -= OnProgress; } catch { }
|
||||
try { _hk.NVRLoadVideoCompleteEventHandler -= OnComplete; } catch { }
|
||||
//_singleRunner.Release();
|
||||
// 清空 UI 显示(可选)
|
||||
RaiseDownloadFileName(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,11 +472,13 @@ namespace FATrace.OEMApp.Services
|
||||
t.Error = error;
|
||||
t.UpdateTime = DateTime.Now;
|
||||
_logger.Warn("[DownloadTaskWorker] 任务失败 Id={Id}, 错误={Err}", t.Id, error);
|
||||
return db.Update<DownloadTask>()
|
||||
var task = db.Update<DownloadTask>()
|
||||
.SetSource(t)
|
||||
.UpdateColumns(a => new { a.Status, a.Error, a.UpdateTime })
|
||||
.Where(a => a.Id == t.Id)
|
||||
.ExecuteAffrowsAsync();
|
||||
try { RaiseTaskFailed(t, error); } catch { }
|
||||
return task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,9 +211,9 @@ namespace FATrace.OEMApp.Services
|
||||
db.Update<OEMRawUse>()
|
||||
.Set(a => new OEMRawUse
|
||||
{
|
||||
UrlState = true,
|
||||
VideoUrl = playUrl,
|
||||
VideoActionId = actionId,
|
||||
//UrlState = true,
|
||||
//VideoUrl = playUrl,
|
||||
//VideoActionId = actionId,
|
||||
RawName = t.RawName
|
||||
})
|
||||
.Where(a => a.Id == t.OemRawUseId)
|
||||
|
||||
@@ -150,9 +150,9 @@ namespace FATrace.OEMApp.Services
|
||||
db.Update<OEMRawUse>()
|
||||
.Set(a => new OEMRawUse
|
||||
{
|
||||
UrlState = true,
|
||||
VideoUrl = playUrl,
|
||||
VideoActionId = actionId,
|
||||
//UrlState = true,
|
||||
//VideoUrl = playUrl,
|
||||
//VideoActionId = actionId,
|
||||
RawName = rawName
|
||||
})
|
||||
.Where(a => a.Id == oemRawUseId)
|
||||
|
||||
@@ -215,7 +215,13 @@ namespace FATrace.OEMApp.Services
|
||||
OperateResultPDAScanCode = KeyencePlcMcNet!.ReadString(PadCodeAddress, 40);
|
||||
if (OperateResultPDAScanCode.IsSuccess)
|
||||
{
|
||||
ScanCode = RevData(OperateResultPDAScanCode.Content).Replace("\r", "").Replace("\n", "").Trim();
|
||||
//ScanCode = RevData(OperateResultPDAScanCode.Content).Replace("\r", "").Replace("\n", "").Replace("\0", "").Trim();
|
||||
ScanCode = (OperateResultPDAScanCode.Content).Replace("\r", "").Replace("\n", "").Replace("\0", "").Trim();
|
||||
PlcConnected = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlcConnected = false;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FATrace.Com;
|
||||
using FATrace.Com;
|
||||
using FATrace.Model;
|
||||
using NLog;
|
||||
|
||||
@@ -13,13 +10,23 @@ namespace FATrace.OEMApp.Services
|
||||
private CancellationTokenSource? _cts;
|
||||
private Task? _loopTask;
|
||||
private TimeSpan _runAt = new TimeSpan(2, 0, 0);
|
||||
private int _retentionDays = 365;
|
||||
|
||||
/// <summary>
|
||||
/// 文件暂存保存的天数
|
||||
/// </summary>
|
||||
private int FileRetentionDays = 365;
|
||||
private bool _enabled = true;
|
||||
|
||||
/// <summary>
|
||||
/// 数据库保存的信息天数
|
||||
/// </summary>
|
||||
private int DbRetentionDays = 180;
|
||||
public event Action<string>? Info;
|
||||
|
||||
public void Start()
|
||||
{
|
||||
FileRetentionDays=ConfigHelper.GetIntOrDefault("VideoFileSaveDay", 365);
|
||||
DbRetentionDays = ConfigHelper.GetIntOrDefault("DbSaveDay", 180);
|
||||
if (_cts != null) return;
|
||||
|
||||
try
|
||||
@@ -30,7 +37,6 @@ namespace FATrace.OEMApp.Services
|
||||
{
|
||||
_runAt = new TimeSpan(2, 0, 0);
|
||||
}
|
||||
_retentionDays = Math.Max(1, ConfigHelper.GetIntOrDefault("DataRetentionDays", 365));
|
||||
}
|
||||
catch { }
|
||||
|
||||
@@ -42,8 +48,8 @@ namespace FATrace.OEMApp.Services
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
_loopTask = Task.Run(() => RunAsync(_cts.Token));
|
||||
_logger.Info("[TimeClear] 服务已启动,时间点={RunAt}, 保留天数={Days}", _runAt, _retentionDays);
|
||||
SafeInfo($"定时清理服务启动,时间点={_runAt}, 保留天数={_retentionDays}");
|
||||
_logger.Info("[TimeClear] 服务已启动,时间点={RunAt}, 文件保留天数={FileDays}, 数据库保留天数={DbDays}", _runAt, FileRetentionDays, DbRetentionDays);
|
||||
SafeInfo($"定时清理服务启动,时间点={_runAt}, 文件保留天数={FileRetentionDays}, 数据库保留天数={DbRetentionDays}");
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
@@ -87,23 +93,62 @@ namespace FATrace.OEMApp.Services
|
||||
|
||||
private async Task CleanupOnceAsync(CancellationToken token)
|
||||
{
|
||||
var cutoff = DateTime.Now.AddDays(-_retentionDays);
|
||||
_logger.Info("[TimeClear] 开始清理,截止时间: {Cutoff}", cutoff);
|
||||
SafeInfo($"开始清理,截止时间: {cutoff:yyyy-MM-dd HH:mm:ss}");
|
||||
var fileCutoff = DateTime.Now.AddDays(-FileRetentionDays);
|
||||
var dbCutoff = DateTime.Now.AddDays(-DbRetentionDays);
|
||||
_logger.Info("[TimeClear] 开始清理,文件截止: {FileCutoff}, 数据库截止: {DbCutoff}", fileCutoff, dbCutoff);
|
||||
SafeInfo($"开始清理,文件截止: {fileCutoff:yyyy-MM-dd HH:mm:ss}, 数据库截止: {dbCutoff:yyyy-MM-dd HH:mm:ss}");
|
||||
|
||||
long delJf = 0, delDl = 0, delRaw = 0, delAct = 0, delDlFiles = 0;
|
||||
|
||||
long delJf = 0, delDl = 0, delRaw = 0, delAct = 0;
|
||||
try
|
||||
{
|
||||
delJf = FSqlContext.FDb.Delete<JellyfinMonitorTask>().Where(a => a.CreateTime < cutoff).ExecuteAffrows();
|
||||
var toDelFile = FSqlContext.FDb.Select<DownloadTask>()
|
||||
.Where(a => a.CreateTime < fileCutoff && a.VideoFilePath != null && a.VideoFilePath != "")
|
||||
.ToList();
|
||||
foreach (var t in toDelFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(t.VideoFilePath) && File.Exists(t.VideoFilePath))
|
||||
{
|
||||
File.Delete(t.VideoFilePath);
|
||||
delDlFiles++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "[TimeClear] 删除 DownloadTask 文件失败: {Path}", t.VideoFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "[TimeClear] 清理 JellyfinMonitorTask 失败");
|
||||
_logger.Warn(ex, "[TimeClear] 扫描 DownloadTask 待删文件失败");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
delDl = FSqlContext.FDb.Delete<DownloadTask>().Where(a => a.CreateTime < cutoff).ExecuteAffrows();
|
||||
var dbOld = FSqlContext.FDb.Select<DownloadTask>()
|
||||
.Where(a => a.CreateTime < dbCutoff)
|
||||
.ToList();
|
||||
foreach (var t in dbOld)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(t.VideoFilePath) && File.Exists(t.VideoFilePath))
|
||||
{
|
||||
File.Delete(t.VideoFilePath);
|
||||
delDlFiles++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "[TimeClear] 删除 DownloadTask 文件失败(删库前): {Path}", t.VideoFilePath);
|
||||
}
|
||||
}
|
||||
delDl = FSqlContext.FDb.Delete<DownloadTask>()
|
||||
.Where(a => a.CreateTime < dbCutoff)
|
||||
.ExecuteAffrows();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -112,7 +157,16 @@ namespace FATrace.OEMApp.Services
|
||||
|
||||
try
|
||||
{
|
||||
delRaw = FSqlContext.FDb.Delete<OEMRawUse>().Where(a => a.CreateTime < cutoff).ExecuteAffrows();
|
||||
delJf = FSqlContext.FDb.Delete<JellyfinMonitorTask>().Where(a => a.CreateTime < dbCutoff).ExecuteAffrows();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "[TimeClear] 清理 JellyfinMonitorTask 失败");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
delRaw = FSqlContext.FDb.Delete<OEMRawUse>().Where(a => a.CreateTime < dbCutoff).ExecuteAffrows();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -121,15 +175,15 @@ namespace FATrace.OEMApp.Services
|
||||
|
||||
try
|
||||
{
|
||||
delAct = FSqlContext.FDb.Delete<VideoAction>().Where(a => a.CreateTime < cutoff).ExecuteAffrows();
|
||||
delAct = FSqlContext.FDb.Delete<VideoAction>().Where(a => a.CreateTime < dbCutoff).ExecuteAffrows();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warn(ex, "[TimeClear] 清理 VideoAction 失败");
|
||||
}
|
||||
|
||||
_logger.Info("[TimeClear] 清理完成: JellyfinMonitorTask={Jf}, DownloadTask={Dl}, OEMRawUse={Raw}, VideoAction={Act}", delJf, delDl, delRaw, delAct);
|
||||
SafeInfo($"清理完成: Jf={delJf}, Dl={delDl}, Raw={delRaw}, Act={delAct}");
|
||||
_logger.Info("[TimeClear] 清理完成: JellyfinMonitorTask={Jf}, DownloadTask(删库)={Dl}, DownloadTask(删文件)={DlFiles}, OEMRawUse={Raw}, VideoAction={Act}", delJf, delDl, delDlFiles, delRaw, delAct);
|
||||
SafeInfo($"清理完成: Jf={delJf}, Dl(库)={delDl}, Dl(文件)={delDlFiles}, Raw={delRaw}, Act={delAct}");
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user