2026-01-28 20:42:36 +08:00
|
|
|
|
using Infrastructure.Attribute;
|
|
|
|
|
|
using Infrastructure.Extensions;
|
|
|
|
|
|
using Infrastructure.Model;
|
|
|
|
|
|
using MDM.Model.Plant;
|
|
|
|
|
|
using MDM.Model.Process;
|
|
|
|
|
|
using Microsoft.AspNetCore.JsonPatch.Operations;
|
|
|
|
|
|
using Microsoft.Extensions.Hosting;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Microsoft.Extensions.Options;
|
|
|
|
|
|
using NLog;
|
|
|
|
|
|
using RIZO.Model.Mes;
|
|
|
|
|
|
using RIZO.Model.MES.product_trace;
|
|
|
|
|
|
using RIZO.Repository;
|
|
|
|
|
|
using RIZO.Service.MES.product.IService;
|
|
|
|
|
|
using RIZO.Service.PLC;
|
|
|
|
|
|
using S7.Net;
|
|
|
|
|
|
using SqlSugar.IOC;
|
|
|
|
|
|
using System;
|
2026-01-31 17:12:58 +08:00
|
|
|
|
using System.Runtime.Intrinsics.X86;
|
|
|
|
|
|
using System.Text;
|
2026-01-28 20:42:36 +08:00
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
|
|
namespace RIZO.Service.PLCBackground.Stations.Into
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// OP点散热胶GF1500
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
//[AppService(ServiceType = typeof(PlcIntoStationService_Common), ServiceLifetime = LifeTime.Singleton)]
|
|
|
|
|
|
public class PlcIntoStationService_Common : BackgroundService
|
|
|
|
|
|
{
|
|
|
|
|
|
protected static Logger _logger = LogManager.GetCurrentClassLogger();
|
|
|
|
|
|
protected PlcConntectHepler _plcService;
|
|
|
|
|
|
protected readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(5);
|
|
|
|
|
|
// private readonly string _ipAddress = "192.168.10.222";
|
|
|
|
|
|
// private readonly CpuType _cpuType = CpuType.S71500;
|
|
|
|
|
|
protected string WorkstationCode;
|
|
|
|
|
|
protected readonly SqlSugarClient Context;
|
|
|
|
|
|
protected readonly OptionsSetting _optionsSetting;
|
|
|
|
|
|
protected PlcSettings plcSetting;
|
|
|
|
|
|
public PlcIntoStationService_Common(IOptions<OptionsSetting> options)
|
|
|
|
|
|
{
|
|
|
|
|
|
Context = DbScoped.SugarScope.CopyNew();
|
|
|
|
|
|
_optionsSetting= options.Value;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.Info("PLC Polling Service started");
|
|
|
|
|
|
|
|
|
|
|
|
// 使用工厂方法创建服务实例
|
2026-01-31 17:12:58 +08:00
|
|
|
|
|
2026-01-28 20:42:36 +08:00
|
|
|
|
// 获取当前的工序/工站
|
|
|
|
|
|
//WorkstationCode = await Context.Queryable<PlantWorkstation>().Where(it => it.PlcIP == plcSetting.IpAddress)
|
|
|
|
|
|
// .Select(it => it.WorkstationCode).FirstAsync();
|
|
|
|
|
|
plcSetting = _optionsSetting.PlcSettings.Where(it => it.WorkStationCode == WorkstationCode).First();
|
2026-01-31 17:12:58 +08:00
|
|
|
|
Enum.TryParse<CpuType>(plcSetting.PlcType, out CpuType cpuType);
|
|
|
|
|
|
|
|
|
|
|
|
if (!plcSetting.isEnble)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-28 20:42:36 +08:00
|
|
|
|
using (_plcService = new PlcConntectHepler(plcSetting.IpAddress, cpuType))
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
while (!stoppingToken.IsCancellationRequested)
|
2026-01-31 17:12:58 +08:00
|
|
|
|
|
2026-01-28 20:42:36 +08:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
//心跳检测 "DB1010.DBW0"
|
2026-01-31 17:12:58 +08:00
|
|
|
|
// await _plcService.WriteAsync(plcSetting.intoStation.Heartbeat, (short)1);
|
|
|
|
|
|
await _plcService.WriteAsync2(plcSetting.intoStation.Heartbeat, 1);
|
2026-01-28 20:42:36 +08:00
|
|
|
|
// 轮询进站请求信号/工位开始查询请求 "DB1001.DBW2000"
|
2026-01-28 21:04:23 +08:00
|
|
|
|
short intoStationAsk = await _plcService.ReadAsync<short>(plcSetting.intoStation.IntoStationAsk);
|
2026-01-28 20:42:36 +08:00
|
|
|
|
if (intoStationAsk == 1)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 处理进站请求
|
|
|
|
|
|
_logger.Info("Processing into station request...");
|
|
|
|
|
|
|
|
|
|
|
|
//获取产品SN码 "DB1001.DBB1000"
|
2026-01-31 17:12:58 +08:00
|
|
|
|
// string productModel = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductModel);
|
|
|
|
|
|
string productModel = ReadPlcStringAsync(_plcService._plc, plcSetting.intoStation.ProductModel, 48).Result;
|
2026-01-28 20:42:36 +08:00
|
|
|
|
//"DB1001.DBB1054"
|
2026-01-31 17:12:58 +08:00
|
|
|
|
//string productSN = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductSN);
|
|
|
|
|
|
string productSN = ReadPlcStringAsync(_plcService._plc, plcSetting.intoStation.ProductSN, 48).Result;
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取工单
|
|
|
|
|
|
|
|
|
|
|
|
//获取工艺路线工序
|
|
|
|
|
|
List<ProcessOperation> processOperations = await Context.Queryable<ProcessRouting>().LeftJoin<ProcessOperation>((r, o) => r.RoutingCode == o.FkRoutingCode)
|
|
|
|
|
|
.Where((r, o) => r.FkProductMaterialCode == productModel && r.Status == 1)
|
|
|
|
|
|
.Select((r, o) => o).ToListAsync();
|
|
|
|
|
|
//判断改产品是正常件还是返工件
|
|
|
|
|
|
|
2026-01-31 17:12:58 +08:00
|
|
|
|
string Routingcode = processOperations?.First()?.FkRoutingCode ?? "";
|
2026-01-28 20:42:36 +08:00
|
|
|
|
//插入入站请求ASK过站记录
|
|
|
|
|
|
ProductPassStationRecord ASKintoStation = new ProductPassStationRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
ProductSN = productSN,
|
|
|
|
|
|
WorkstationCode = WorkstationCode,
|
2026-01-31 17:12:58 +08:00
|
|
|
|
Routingcode = Routingcode,
|
2026-01-28 20:42:36 +08:00
|
|
|
|
OperationCode = WorkstationCode,
|
|
|
|
|
|
OperationName= plcSetting.WorkStationName,
|
|
|
|
|
|
ProductionLifeStage = 1, // 1表示生产中
|
|
|
|
|
|
PasstationType = 0, // 0表示入站请求
|
|
|
|
|
|
PasstationDescription = "入站请求ASK",
|
|
|
|
|
|
EffectTime = DateTime.Now,
|
|
|
|
|
|
CreatedTime = DateTime.Now,
|
|
|
|
|
|
};
|
|
|
|
|
|
await Context.Insertable(ASKintoStation).ExecuteCommandAsync();
|
|
|
|
|
|
//判断该产品是否允许进站
|
|
|
|
|
|
EntryPermissionResult result = await checkEntryPermission(productModel, productSN, WorkstationCode, processOperations);
|
|
|
|
|
|
// "DB1010.DBW2000"
|
2026-01-31 17:12:58 +08:00
|
|
|
|
// await _plcService.WriteAsync(plcSetting.intoStation.IntoStationResp, (short)result);
|
2026-02-01 14:10:08 +08:00
|
|
|
|
await _plcService.WriteAsync2(plcSetting.intoStation.IntoStationResp,(int) result);
|
2026-01-28 20:42:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
await Task.Delay(_pollingInterval, stoppingToken);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2026-02-01 14:10:08 +08:00
|
|
|
|
catch (OperationCanceledException ex)
|
2026-01-28 20:42:36 +08:00
|
|
|
|
{
|
|
|
|
|
|
_logger.Info("PLC Polling Service is stopping");
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.Error(ex, "PLC polling error");
|
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
_logger.Info("PLC Polling Service stopped");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-31 17:12:58 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按业务规则解析PLC字符串(支持中文GBK编码)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task<string> ReadPlcStringAsync(Plc plc, string addr, int maxLen)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 解析地址:DB1010.DBB50 → DB编号1010 + 起始字节50
|
|
|
|
|
|
var parts = addr.Split('.', StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
|
|
if (parts.Length != 2)
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
if (!int.TryParse(parts[0].Replace("DB", ""), out int dbNum) || dbNum <= 0)
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
if (!int.TryParse(parts[1].Replace("DBB", ""), out int startByte) || startByte < 0)
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
// 读取字节数组
|
|
|
|
|
|
var bytes = await Task.Run(() => plc.ReadBytes(DataType.DataBlock, dbNum, startByte, maxLen));
|
|
|
|
|
|
if (bytes == null || bytes.Length < 3)
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
// 严格按规则解析:bytes[0]=总长度(备用)、bytes[1]=有效长度、bytes[2+]=内容
|
|
|
|
|
|
int validLen = Math.Clamp(bytes[1], 0, bytes.Length - 2);
|
|
|
|
|
|
if (validLen <= 0)
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
// 提取有效内容并转字符串(GBK编码支持中文)
|
|
|
|
|
|
var contentBytes = new byte[validLen];
|
|
|
|
|
|
Array.Copy(bytes, 2, contentBytes, 0, validLen);
|
|
|
|
|
|
return Encoding.GetEncoding("GBK").GetString(contentBytes).Trim('\0', ' ', '\t');
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-28 20:42:36 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 校验是否可以进站
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="productModel">产品型号</param>
|
|
|
|
|
|
/// <param name="productSN">产品SN</param>
|
|
|
|
|
|
/// <returns></returns>
|
2026-01-31 17:12:58 +08:00
|
|
|
|
protected virtual async Task<EntryPermissionResult> checkEntryPermission(string productModel, string productSN, string workstationCode, List<ProcessOperation> processOperations)
|
2026-01-28 20:42:36 +08:00
|
|
|
|
{
|
2026-02-01 14:10:08 +08:00
|
|
|
|
workstationCode = workstationCode.Trim();
|
2026-01-28 20:42:36 +08:00
|
|
|
|
EntryPermissionResult result = EntryPermissionResult.UnkownException;
|
|
|
|
|
|
|
|
|
|
|
|
//2上工位无记录
|
|
|
|
|
|
|
2026-02-01 14:10:08 +08:00
|
|
|
|
var LastOperationSeqObject = processOperations?.Where(operation =>
|
2026-02-02 14:29:40 +08:00
|
|
|
|
string.Equals(CleanString(operation.OperationCode), CleanString(workstationCode), StringComparison.OrdinalIgnoreCase))
|
2026-02-01 14:10:08 +08:00
|
|
|
|
.FirstOrDefault();
|
2026-02-02 14:08:46 +08:00
|
|
|
|
if (LastOperationSeqObject.LastOperationSeq != 0)
|
2026-01-31 17:12:58 +08:00
|
|
|
|
{
|
2026-02-02 14:08:46 +08:00
|
|
|
|
ProcessOperation LastOperation = processOperations.Where(it => it.OperationSeq == LastOperationSeqObject.LastOperationSeq).First();
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 清理 OperationCode:去除首尾空格 + 移除 \r 等不可见控制字符(兼容你之前的问题)
|
|
|
|
|
|
string cleanOperationCode = LastOperation.OperationCode
|
|
|
|
|
|
.Replace("\r", "") // 移除回车符
|
|
|
|
|
|
.Replace("\n", "") // 可选:移除换行符
|
|
|
|
|
|
.Trim(); // 移除首尾空格
|
|
|
|
|
|
productSN = productSN.Replace("\r", "").Replace("\n", "").Trim();
|
|
|
|
|
|
// 3. 构建并执行真正的异步查询(补充执行方法,使 await 生效)
|
|
|
|
|
|
var ExistLastOperationRecord = await DbScoped.SugarScope.CopyNew()
|
|
|
|
|
|
.Queryable<ProductPassStationRecord>()
|
2026-02-02 14:29:40 +08:00
|
|
|
|
.Where(it => CleanString(it.ProductSN) == productSN)
|
|
|
|
|
|
.Where(it => CleanString(it.OperationCode) == cleanOperationCode) // 使用清理后的字符串匹配
|
2026-02-02 14:08:46 +08:00
|
|
|
|
// 补充:根据需求选择合适的执行方法(二选一或其他)
|
|
|
|
|
|
.ToListAsync(); // 推荐:返回符合条件的所有数据(列表)
|
|
|
|
|
|
// .FirstOrDefaultAsync(); // 若只需返回第一条数据,用这个(无匹配返回 null)
|
|
|
|
|
|
// bool isExistLastOperationRecord = false;
|
|
|
|
|
|
if (ExistLastOperationRecord != null && ExistLastOperationRecord.Count() == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
return EntryPermissionResult.NoRecordAtPreviousStation;
|
|
|
|
|
|
}
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
2026-02-02 14:08:46 +08:00
|
|
|
|
// 3 上工位NG 入站或者出站结果 NG
|
|
|
|
|
|
var ExistLastOperationNG = Context.Queryable<ProductPassStationRecord>()
|
2026-02-02 14:29:40 +08:00
|
|
|
|
.Where(it => CleanString(it.ProductSN) == productSN)
|
|
|
|
|
|
.Where(it => CleanString(it.OperationCode) == cleanOperationCode)
|
2026-02-02 14:08:46 +08:00
|
|
|
|
.Where(it => it.PasstationType == 3)
|
|
|
|
|
|
.Where(it => it.ResultCode == 1).ToList();
|
|
|
|
|
|
|
|
|
|
|
|
if (ExistLastOperationNG.Count() == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
result = EntryPermissionResult.PreviousStationNG;
|
|
|
|
|
|
goto InsertPassrecord;
|
|
|
|
|
|
}
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 14:08:46 +08:00
|
|
|
|
// 4 扫码产品型号错误
|
|
|
|
|
|
//bool isExistproductSN = await Context.Queryable<ProductLifecycle>()
|
|
|
|
|
|
// .Where(it => it.ProductSN == productSN).AnyAsync();
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 14:08:46 +08:00
|
|
|
|
//bool isExistproductSNProducting = await Context.Queryable<ProductLifecycle>()
|
|
|
|
|
|
// .Where(it => it.ProductSN == productSN && (it.ProductCurrentStatus != 1 && it.ProductCurrentStatus != 3)).AnyAsync();
|
|
|
|
|
|
//if (!isExistproductSN || !isExistproductSNProducting)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// result = EntryPermissionResult.ProductModelError;
|
|
|
|
|
|
// goto InsertPassrecord;
|
|
|
|
|
|
//}
|
|
|
|
|
|
//6时间超出规定无法生产
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 14:08:46 +08:00
|
|
|
|
//7固化时间未到规定时间
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
2026-02-02 14:08:46 +08:00
|
|
|
|
int LastOperationStandardTime = processOperations.Where(operation => operation.OperationCode == LastOperation.OperationCode)
|
|
|
|
|
|
.Select(operation => operation.StandardTime ?? 0).First();
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-02-02 14:08:46 +08:00
|
|
|
|
if (LastOperationStandardTime > 0)
|
2026-01-28 20:42:36 +08:00
|
|
|
|
{
|
2026-02-02 14:08:46 +08:00
|
|
|
|
// 上一站的出站时间 和本站的请求时间差值
|
|
|
|
|
|
DateTime LastInStationTime = await Context.Queryable<ProductPassStationRecord>()
|
|
|
|
|
|
.Where(it => it.ProductSN == productSN)
|
2026-02-02 14:29:40 +08:00
|
|
|
|
.Where(it => CleanString(it.OperationCode) == cleanOperationCode)
|
2026-02-02 14:08:46 +08:00
|
|
|
|
.Where(it => it.PasstationType == 3)
|
|
|
|
|
|
.MaxAsync(it => it.OutStationTime ?? DateTime.MinValue);
|
|
|
|
|
|
TimeSpan timeDiff = DateTime.Now - LastInStationTime;
|
|
|
|
|
|
double totalSeconds = timeDiff.TotalSeconds;
|
|
|
|
|
|
if (totalSeconds < LastOperationStandardTime)
|
|
|
|
|
|
{
|
|
|
|
|
|
result = EntryPermissionResult.CuringTimeNotReached;
|
|
|
|
|
|
goto InsertPassrecord;
|
|
|
|
|
|
}
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
2026-02-02 14:08:46 +08:00
|
|
|
|
}
|
2026-01-28 20:42:36 +08:00
|
|
|
|
}
|
2026-02-02 14:08:46 +08:00
|
|
|
|
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
|
|
|
|
|
//12 最大重复入站次数
|
|
|
|
|
|
int MaxRepeatEntries = processOperations.Where(operation => operation.OperationCode == workstationCode).Select(operation => operation.MaxStationCount??1).First();
|
|
|
|
|
|
if(MaxRepeatEntries>1)
|
|
|
|
|
|
{
|
|
|
|
|
|
int currentStationEntryCount = await Context.Queryable<ProductPassStationRecord>()
|
|
|
|
|
|
.Where(it => it.ProductSN == productSN)
|
|
|
|
|
|
.Where(it => it.OperationCode == workstationCode)
|
|
|
|
|
|
.Where(it => it.PasstationType == 1)
|
|
|
|
|
|
.CountAsync();
|
2026-01-31 17:12:58 +08:00
|
|
|
|
//if(currentStationEntryCount >= MaxRepeatEntries)
|
|
|
|
|
|
//{
|
|
|
|
|
|
// result = EntryPermissionResult.MaximumRepeatedEntries;
|
|
|
|
|
|
// goto InsertPassrecord;
|
|
|
|
|
|
//}
|
2026-01-28 20:42:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//OK 准许进站
|
|
|
|
|
|
result = EntryPermissionResult.AllowIntoStation;
|
|
|
|
|
|
goto InsertPassrecord;
|
|
|
|
|
|
InsertPassrecord:
|
|
|
|
|
|
//插入入站请求Resp过站记录
|
|
|
|
|
|
ProductPassStationRecord RespintoStation = new ProductPassStationRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
ProductSN = productSN,
|
|
|
|
|
|
WorkstationCode = WorkstationCode,
|
2026-01-28 21:12:18 +08:00
|
|
|
|
Routingcode = processOperations?.First()?.FkRoutingCode??"",
|
2026-01-28 20:42:36 +08:00
|
|
|
|
OperationCode = WorkstationCode,
|
|
|
|
|
|
OperationName = plcSetting.WorkStationName,
|
|
|
|
|
|
ProductionLifeStage = 1, // 1表示生产中
|
|
|
|
|
|
PasstationType = 1, // 入站完毕
|
|
|
|
|
|
PasstationDescription = "入站完毕Resp",
|
|
|
|
|
|
EffectTime = DateTime.Now,
|
|
|
|
|
|
InStationTime = DateTime.Now,
|
|
|
|
|
|
ResultCode = (int)result,
|
|
|
|
|
|
ResultDescription = result.ToString(),
|
|
|
|
|
|
CreatedTime = DateTime.Now,
|
|
|
|
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
await Context.Insertable(RespintoStation).ExecuteCommandAsync();
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-02 14:29:40 +08:00
|
|
|
|
private string CleanString(string input)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(input))
|
|
|
|
|
|
{
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 清洗逻辑:
|
|
|
|
|
|
// 1. Trim():去除前后普通半角空格
|
|
|
|
|
|
// 2. 替换全角空格、制表符、换行符、回车符(常见隐藏空白字符)
|
|
|
|
|
|
return input.Trim()
|
|
|
|
|
|
.Replace(" ", "") // 替换全角空格(中文输入法空格)
|
|
|
|
|
|
.Replace("\t", "") // 替换制表符
|
|
|
|
|
|
.Replace("\n", "") // 替换换行符
|
|
|
|
|
|
.Replace("\r", ""); // 替换回车符
|
|
|
|
|
|
}
|
2026-01-28 20:42:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|