Commit 93afe3d7 刘韬

修正服务器偶发连接失败的问题

1 个父辈 88aa209c
......@@ -222,6 +222,7 @@ namespace DeviceLibrary
if (retrytime > 2)
return;
retrytime++;
CloseCamera(cameraName);
LogUtil.info($"bitmap为空重试第{retrytime}次");
Task.Delay(1500).Wait();
goto retry;
......@@ -311,14 +312,13 @@ namespace DeviceLibrary
}
return codeList;
}
static bool lastHasReel = false;
[HandleProcessCorruptedStateExceptions]
public static bool TestHasReel(string cameraName)
{
int retrytime = 0;
retry:
LogUtil.error($"【" + cameraName + "】开始取图片测试是否有料盘");
string logtxt = $"【" + cameraName + "】开始取图片测试是否有料盘"+"\r\n";
DateTime startTime = DateTime.Now;
Bitmap bmp = null;
try
......@@ -329,11 +329,12 @@ namespace DeviceLibrary
if (retrytime > 2)
return false;
retrytime++;
CloseCamera(cameraName);
LogUtil.info($"bitmap为空重试第{retrytime}次");
Task.Delay(1500).Wait();
Thread.Sleep(500);
goto retry;
}
LogUtil.error($"【" + cameraName + "】获取到图像");
logtxt +=$"【" + cameraName + "】获取到图像"+"\r\n";
int totalcover = ConfigHelper.Config.Get("CamTestReel_totalcover", 69577);
int hl = ConfigHelper.Config.Get("CamTestReel_HL", 18);
int hh = ConfigHelper.Config.Get("CamTestReel_HH", 47);
......@@ -382,8 +383,13 @@ namespace DeviceLibrary
SaveImageToFile("test2", cameraName, b);
b.Dispose();
double calc = maskcout / (double)totalcover;
LogUtil.error($" 检测到覆盖面积计数:maskcout:{maskcout}/{totalcover}={calc}<{threshold},result:{calc <= threshold}");
return calc <= threshold;
logtxt+=$" 检测到覆盖面积计数:maskcout:{maskcout}/{totalcover}={calc}<{threshold},result:{calc <= threshold}";
bool hasReel = calc <= threshold;
if (hasReel != lastHasReel) {
lastHasReel = hasReel;
}else
logtxt = "";
return hasReel;
}
catch (AccessViolationException e)
{
......@@ -399,6 +405,8 @@ namespace DeviceLibrary
{
if (bmp != null)
bmp.Dispose();
if (!string.IsNullOrEmpty(logtxt))
LogUtil.error(logtxt);
}
return true;
}
......
......@@ -13,7 +13,15 @@ namespace DeviceLibrary
{
class ServerCommunication
{
public StoreStatus storeStatus = StoreStatus.Debugging;
volatile StoreStatus _storeStatus = StoreStatus.Debugging;
public StoreStatus storeStatus {
get => _storeStatus;
set {
if (_storeStatus!= value)
LogUtil.info($"set storeStatus to {value}");
_storeStatus = value;
}
}
static string server = ConfigHelper.Config.Get("http_server");
static string CID = ConfigHelper.Config.Get("CID");
int StoreID = 1;
......@@ -27,7 +35,7 @@ namespace DeviceLibrary
serverConnectTimer.Interval = 1000;
serverConnectTimer.AutoReset = true;
serverConnectTimer.Enabled = true;
serverConnectTimer.Elapsed += ServerConnectTimer_Elapsed; ;
serverConnectTimer.Elapsed += ServerConnectTimer_Elapsed;
}
private void ServerConnectTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
......@@ -95,21 +103,29 @@ namespace DeviceLibrary
ResultProcess(resultOperation);
}
}
public void SendStoreState(string posid, StoreStatus storeStatus)
public bool SendStoreState(string posid, StoreStatus storeStatus)
{
lock (serverclock)
{
retry:
Operation operation = getLineBoxStatus();
if (!string.IsNullOrEmpty(posid))
operation.boxStatus[StoreID].data.Add(ParamDefine.posId, posid);
LogUtil.info($"SendStoreState,posid:{posid}, storeStatus:{storeStatus}");
operation.boxStatus[StoreID].status = (int)storeStatus;
LogUtil.info(JsonHelper.SerializeObject(operation));
if (RobotManage.InoutDebugMode)
return;
return true;
Operation resultOperation = HttpHelper.Post(GetPostApi(), operation, false);
if (resultOperation == null)
{
LogUtil.info($"SendStoreState error,posid:{posid}, storeStatus:{storeStatus}");
//goto retry;
return false;
}
LogUtil.info($"SendStoreState success,posid:{posid}, storeStatus:{storeStatus}");
ResultProcess(resultOperation);
if (storeStatus == StoreStatus.OutStoreEnd ||
......@@ -118,7 +134,9 @@ namespace DeviceLibrary
{
this.storeStatus = StoreStatus.StoreOnline;
}
}
return true;
}
public string spiltStr = "##";
private string ProcessCode(string[] codeList,int reelw,int reelh)
......@@ -220,12 +238,15 @@ namespace DeviceLibrary
return host + api_communication;
}
int getthtime = 0;
int laststatus = 0;
public void SendLineStatus()
{
if (RobotManage.InoutDebugMode)
return;
lock (serverclock)
{
bool printlog = false;
DateTime time = DateTime.Now;
//构建发送给服务器的对象
Operation lineOperation = getLineBoxStatus();
......@@ -240,7 +261,13 @@ namespace DeviceLibrary
}
}
Operation resultOperation = HttpHelper.Post(GetPostApi(), lineOperation, false);
if (lineOperation.status != laststatus) {
laststatus = lineOperation.status;
printlog = true;
}
Operation resultOperation = HttpHelper.Post(GetPostApi(), lineOperation, printlog);
if (resultOperation != null)
getthtime = 0;
//LogUtil.info(JsonHelper.SerializeObject(resultOperation.data));
......@@ -433,7 +460,7 @@ namespace DeviceLibrary
SendStoreState("", StoreStatus.InStoreError);
//RobotManage.mainMachine.ClampMoveInfo.NextMoveStep(MoveStep.NGOUT_01);
RobotManage.mainMachine.NGPuted(msg);
LogUtil.info("服务器没有正确返回库位.");
LogUtil.info("服务器没有正确返回库位. msg="+ msg);
}
else {
......@@ -592,6 +619,7 @@ namespace DeviceLibrary
/// </summary>
public enum StoreStatus
{
None = 0,
/// <summary>
/// 1=设备联机(正常就绪)(入库后,BOX恢复原始状态)(出库后,移载装置恢复原始状态),
/// </summary>
......@@ -648,5 +676,6 @@ namespace DeviceLibrary
/// 扫码入库失败
/// </summary>
InStoreError = 14,
}
}
......@@ -44,14 +44,22 @@ namespace DeviceLibrary
{
isOk = false;
}
MoveInfo.WaitList.ForEach((w) => {
if (w.WaitType.Equals(WaitEnum.W014_Msg)) {
w.IsEnd = true;
Msg.add(w.ActionMsg, w.Data);
}
});
foreach (WaitResultInfo wait in MoveInfo.WaitList)
{
if (wait.IsEnd)
{
if (!wait.WaitType.Equals(WaitEnum.W002_IOValue))
if(!wait.WaitType.Equals(WaitEnum.W002_IOValue))
{
continue;
}
}
NotOkMsg = wait.ToStr();
if (wait.WaitType.Equals(WaitEnum.W001_AxisMove))
......@@ -77,16 +85,16 @@ namespace DeviceLibrary
else if (wait.WaitType.Equals(WaitEnum.W002_IOValue))
{
ConfigIO io = RobotManage.Config.getWaitIO(wait.IoType);
NotOkMsg = MoveInfo.Name+$" {crc.GetString(L.wait, "等待")}【" + io.DisplayStr + "】=【" + wait.IoValue + "】";
NotOkMsg = MoveInfo.Name + $" {crc.GetString(L.wait, "等待")}【" + io.DisplayStr + "】=【" + wait.IoValue + "】";
wait.IsEnd = IOManager.IOValue(wait.IoType).Equals(wait.IoValue);
if (!wait.IsEnd)
{
int timeOutMs = RobotManage.Config.IOSingle_TimerOut*1000;
int timeOutMs = RobotManage.Config.IOSingle_TimerOut * 1000;
if (span.TotalMilliseconds > timeOutMs && NoAlarm())
{
WarnMsg = MoveInfo.Name + "[" + MoveInfo.MoveStep + $"] {crc.GetString(L.wait, "等待")}(" + io.DisplayStr + "=" + wait.IoValue + $") {crc.GetString(L.timeout, "超时")}";
Msg.add(WarnMsg, MsgLevel.alarm);
if (NoAlarm())
{
......@@ -113,7 +121,7 @@ namespace DeviceLibrary
}
else if (wait.WaitType.Equals(WaitEnum.W008_BatchAxis))
{
AxisBean axisBean=Batch_Axis;
AxisBean axisBean = Batch_Axis;
//等待信号亮或者走到绝对位置才停止
if (IOValue(axisBean.TargetIoType).Equals(axisBean.TargetIoValue))
......
......@@ -42,7 +42,7 @@ namespace DeviceLibrary
//出入库 绿闪 黄闪
if (ClampMoveInfo.MoveStep > MoveStep.Wait
|| StoreMoveInfo.MoveStep > MoveStep.Wait
|| (StringMoveInfo.MoveStep > MoveStep.Wait && StringMoveInfo.MoveStep != MoveStep.StringReadyPut))
|| (StringMoveInfo.MoveStep > MoveStep.Wait && StringMoveInfo.MoveStep != MoveStep.StringReadyPut && StringMoveInfo.MoveStep <= MoveStep.StringOut_01))
{
RunningLed.LedState = LedState.blink;
StandbyLed.LedState = LedState.blink;
......@@ -60,7 +60,7 @@ namespace DeviceLibrary
StandbyLed.LedState = LedState.blink;
AlarmLed.LedState = LedState.blink;
}
//系统暂停,说明书未定义, 绿闪,
//系统暂停,说明书未定义, 绿闪,
if (!canRunning || UserPause)
{
RunningLed.LedState = LedState.blink;
......
......@@ -225,25 +225,32 @@ namespace DeviceLibrary
var m = Msg.get();
ProcessMsgEvent?.Invoke(m);
ServerCM.ProcessMsg(m);
StoreStatus currnetstoreStatus= StoreStatus.None;
if (m.Find((aa) => aa.msgLevel == MsgLevel.alarm) == null)
{
hasAlarm = false;
AlarmBuzzer.OFF();
if (ServerCM.storeStatus != StoreStatus.InStoreExecute
&& ServerCM.storeStatus != StoreStatus.OutStoreExecute
&& ServerCM.storeStatus != StoreStatus.ResetMove )
ServerCM.storeStatus = StoreStatus.StoreOnline;
&& ServerCM.storeStatus != StoreStatus.OutStoreExecute
&& ServerCM.storeStatus != StoreStatus.ResetMove && ServerCM.storeStatus != StoreStatus.StoreOnline)
{
LogUtil.info($"test storeStatus is {ServerCM.storeStatus} change to StoreOnline");
currnetstoreStatus = StoreStatus.StoreOnline;
}
}
else {
hasAlarm = true;
AlarmBuzzer.ON();
ServerCM.storeStatus = isInSuddenDown ? StoreStatus.SuddenStop : StoreStatus.Warning;
currnetstoreStatus = isInSuddenDown ? StoreStatus.SuddenStop : StoreStatus.Warning;
}
//ProcessMoveinfoEvent?.Invoke(MoveInfo.List);
if (!UserPause)
Msg.clear();
else
ServerCM.storeStatus = StoreStatus.Debugging;
currnetstoreStatus = StoreStatus.Debugging;
if (currnetstoreStatus!=StoreStatus.None)
ServerCM.storeStatus = currnetstoreStatus;
}
}
LogUtil.info("主线程已退出.");
......@@ -289,8 +296,10 @@ namespace DeviceLibrary
case MoveStep.H01_HomeReset:
ResetMoveInfo.NextMoveStep(MoveStep.H02_HomeReset_01);
ServerCM.storeStatus = StoreStatus.ResetMove;
if (IOValue(IO_Type.StringBack_Check).Equals(IO_VALUE.LOW) && IOValue(IO_Type.StringFront_Check).Equals(IO_VALUE.LOW)
|| IOValue(IO_Type.StringFront_Check).Equals(IO_VALUE.HIGH)) {
if (IOValue(IO_Type.StringBack_Check).Equals(IO_VALUE.LOW) && IOValue(IO_Type.StringFront_Check).Equals(IO_VALUE.LOW)){
ResetMoveInfo.log("复位时没有料串");
}
else if (IOValue(IO_Type.StringFront_Check).Equals(IO_VALUE.HIGH)) {
Msg.add(crc.GetString(L.string_not_onposition, "回原时X09,X10信号异常,料串可能不在正确位置,请检查."), MsgLevel.alarm);//0429
RobotManage.UserPause("回原时X09,X10信号异常,料串可能不在正确位置,请检查");
return;
......@@ -397,8 +406,8 @@ namespace DeviceLibrary
Msg.add(crc.GetString(L.x29_higt_has_reel, "系统启动X29检测到有无信息料盘,等待取走单料口料盘"), MsgLevel.alarm);
if (ConfigHelper.Config.Get("CamTestReel_Ability", false))
{
ResetMoveInfo.NextMoveStep(MoveStep.H14_HomeReset);
ResetMoveInfo.WaitList.Add(WaitResultInfo.WaitTime(1000));
//ResetMoveInfo.NextMoveStep(MoveStep.H14_HomeReset);
//ResetMoveInfo.WaitList.Add(WaitResultInfo.WaitTime(1000));
}
else
{
......
......@@ -162,6 +162,7 @@ namespace DeviceLibrary
else
{
ClampMoveInfo.log($"拍照扫码");
scantrytimes = 0;
ScanCode();
}
break;
......@@ -188,12 +189,23 @@ namespace DeviceLibrary
ServerCM.SendInStoreRequest(cc.ToArray(), ClampMoveInfo.MoveParam,true);
}
}
else if (ClampMoveInfo.IsTimeOut(20))
else if (ClampMoveInfo.IsTimeOut(15))
{
ClampMoveInfo.NextMoveStep(MoveStep.NGOUT_01);
ClampMoveInfo.MoveParam.IsNg = false;
ClampMoveInfo.MoveParam.NgMsg = crc.GetString(L.scan_code_timeout, "扫码超时");
ClampMoveInfo.log($"等待扫码超时,NG口送出");
scantrytimes++;
if (scantrytimes < 3)
{
ClampMoveInfo.NextMoveStep(MoveStep.ReelClamp_10);
ClampMoveInfo.log($"扫码超时,第{scantrytimes}次重试,拍照扫码");
ScanTask = null;
ScanCode();
}
else
{
ClampMoveInfo.NextMoveStep(MoveStep.NGOUT_01);
ClampMoveInfo.MoveParam.IsNg = false;
ClampMoveInfo.MoveParam.NgMsg = crc.GetString(L.scan_code_timeout, "扫码超时");
ClampMoveInfo.log($"等待扫码超时,NG口送出");
}
}
break;
case MoveStep.InWaitServerCallback:
......@@ -247,8 +259,8 @@ namespace DeviceLibrary
Msg.add(crc.GetString(L.please_take_ngdoor_reel, "等待取走单口料盘"), MsgLevel.alarm);
if (ConfigHelper.Config.Get("CamTestReel_Ability", false))
{
ClampMoveInfo.NextMoveStep(MoveStep.NGOUT_03);
ClampMoveInfo.WaitList.Add(WaitResultInfo.WaitTime(1000));
//ClampMoveInfo.LastSetpTime = DateTime.Now;
//ClampMoveInfo.WaitList.Add(WaitResultInfo.WaitTime(1000));
}
else {
RobotManage.UserPause("等待取走单口料盘");
......@@ -366,6 +378,7 @@ namespace DeviceLibrary
return !ScanTask.IsCompleted;
}
int scantrytimes = 0;
/// <summary>
/// 扫码线程
/// </summary>
......
......@@ -86,11 +86,15 @@ namespace DeviceLibrary
}
break;
case MoveStep.StoreIn01:
if (!ServerCM.SendStoreState(StoreMoveInfo.MoveParam.PosID, StoreStatus.InStoreExecute))
{
Msg.add("服务器连接异常",MsgLevel.warning);
return;
}
StoreMoveInfo.NextMoveStep(MoveStep.StoreIn03);
var ac = CSVPositionReader<ACStorePosition>.GetPositon(StoreMoveInfo.MoveParam.PosID);
boxTransport.Start(new BoxStorePosition(Config,StoreSide.NGDoor, StoreMoveInfo.MoveParam), new BoxStorePosition(Config, ac, StoreMoveInfo.MoveParam),StoreMoveType.InStore,true);
StoreMoveInfo.log($"开始转运料盘");
ServerCM.SendStoreState(StoreMoveInfo.MoveParam.PosID, StoreStatus.InStoreExecute);
var ac = CSVPositionReader<ACStorePosition>.GetPositon(StoreMoveInfo.MoveParam.PosID);
boxTransport.Start(new BoxStorePosition(Config, StoreSide.NGDoor, StoreMoveInfo.MoveParam), new BoxStorePosition(Config, ac, StoreMoveInfo.MoveParam), StoreMoveType.InStore, true);
StoreMoveInfo.log($"开始转运料盘");
break;
case MoveStep.StoreIn02:
break;
......@@ -109,30 +113,44 @@ namespace DeviceLibrary
case MoveStep.StoreIn04:
if (boxTransport.IsComplateOrFree)
{
if (!ServerCM.SendStoreState(StoreMoveInfo.MoveParam.PosID, StoreStatus.InStoreEnd))
{
Msg.add("服务器连接异常", MsgLevel.warning);
return;
}
StoreMoveInfo.log($"料盘已到达目的地");
ServerCM.SendStoreState(StoreMoveInfo.MoveParam.PosID, StoreStatus.InStoreEnd);
StoreMoveInfo.EndMove();
StoreMoveInfo.EndMove();
}
break;
case MoveStep.StoreOut_NGPre:
StoreMoveInfo.NextMoveStep(MoveStep.StoreOut10);
break;
case MoveStep.StoreOut10:
if (!ServerCM.SendStoreState(StoreMoveInfo.MoveParam.PosID, StoreStatus.OutStoreExecute))
{
Msg.add("服务器连接异常", MsgLevel.warning);
return;
}
StoreMoveInfo.NextMoveStep(MoveStep.StoreOut11);
var outFrom = CSVPositionReader<ACStorePosition>.GetPositon(StoreMoveInfo.MoveParam.PosID);
BoxStorePosition outTo;
if (StoreMoveInfo.MoveParam.IsNg)
outTo = new BoxStorePosition(Config, StoreSide.NGDoor, StoreMoveInfo.MoveParam);
else
outTo = new BoxStorePosition(Config, StoreSide.String, StoreMoveInfo.MoveParam);
boxTransport.Start(outFrom==null?null:new BoxStorePosition(Config, outFrom, StoreMoveInfo.MoveParam), outTo, StoreMoveType.OutStore);
StoreMoveInfo.log($"开始转运料盘{(StoreMoveInfo.MoveParam.IsNg?"单料口":"料串")}");
ServerCM.SendStoreState(StoreMoveInfo.MoveParam.PosID, StoreStatus.OutStoreExecute);
var outFrom = CSVPositionReader<ACStorePosition>.GetPositon(StoreMoveInfo.MoveParam.PosID);
BoxStorePosition outTo;
if (StoreMoveInfo.MoveParam.IsNg)
outTo = new BoxStorePosition(Config, StoreSide.NGDoor, StoreMoveInfo.MoveParam);
else
outTo = new BoxStorePosition(Config, StoreSide.String, StoreMoveInfo.MoveParam);
boxTransport.Start(outFrom == null ? null : new BoxStorePosition(Config, outFrom, StoreMoveInfo.MoveParam), outTo, StoreMoveType.OutStore);
StoreMoveInfo.log($"开始转运料盘{(StoreMoveInfo.MoveParam.IsNg ? "单料口" : "料串")}");
break;
case MoveStep.StoreOut11:
if (boxTransport.IsTakedReel)
{
ServerCM.SendStoreState(StoreMoveInfo.MoveParam.PosID, StoreStatus.OutStoreBoxEnd);
if (!ServerCM.SendStoreState(StoreMoveInfo.MoveParam.PosID, StoreStatus.OutStoreBoxEnd))
{
Msg.add("服务器连接异常", MsgLevel.warning);
return;
}
StoreMoveInfo.NextMoveStep(MoveStep.StoreOut12);
StoreMoveInfo.log($"料盘已取走");
}
......
......@@ -66,6 +66,7 @@ namespace DeviceLibrary
StringMoveInfo.log($"升起待机料串");
StringMoveInfo.NextMoveStep(MoveStep.StringLoad_03);
CylinderMove(StringMoveInfo, IO_Type.StringPosChecker_Home, IO_Type.StringPosChecker_Work, IO_VALUE.HIGH);
newreel = true;
return true;
}
......@@ -145,6 +146,7 @@ namespace DeviceLibrary
StringMoveInfo.log($"批量轴到顶部检测点");
//CylinderMove(StringMoveInfo, IO_Type.StringDoor_Close, IO_Type.StringDoor_Open, IO_VALUE.LOW);
BatchAxisToP2(StringMoveInfo);
StringMoveInfo.WaitList.Add(WaitResultInfo.WaitMsg("料串正在上升",MsgLevel.warning));
}
else if (StringMoveInfo.IsTimeOut(10))
{
......@@ -159,11 +161,12 @@ namespace DeviceLibrary
StringMoveInfo.log($"恢复上次料串信息为出库料串");
StringState = StringStateE.OutStore;
}
else if (Batch_Axis.IsInPosition(Config.Batch_P2) && IOValue(IO_Type.TrayCheck).Equals(IO_VALUE.LOW))
if (Batch_Axis.IsInPosition(Config.Batch_P2) && IOValue(IO_Type.TrayCheck).Equals(IO_VALUE.LOW))
{
StringMoveInfo.log($"料串检测为空");
StringState = StringStateE.OutStore;
if (OutStoreJobList.Count == 0) {
if (ConfigHelper.Config.Get("Device_String_StandbyAtBottom",true) && OutStoreJobList.Count == 0) {
StringMoveInfo.log($"当钱空料串, 并且没有出库任务, 料串下降待机");
StringMoveInfo.NextMoveStep(MoveStep.StringOut_01);
}
......@@ -298,7 +301,7 @@ namespace DeviceLibrary
StringMoveInfo.log($"料串下降到P1点");
Batch_Axis.AbsMove(StringMoveInfo, Config.Batch_P1, Config.Batch_P1_speed);
CylinderMove(StringMoveInfo, IO_Type.StringPosChecker_Home, IO_Type.StringPosChecker_Work, IO_VALUE.LOW);
StringMoveInfo.WaitList.Add(WaitResultInfo.WaitMsg("料串正在下降", MsgLevel.warning));
break;
case MoveStep.StringOut_02:
StringMoveInfo.NextMoveStep(MoveStep.StringOut_03);
......
......@@ -135,6 +135,10 @@ namespace DeviceLibrary
LogUtil.error(msg);
}
}
public void Msg(string msg, MsgLevel msgLevel)
{
WaitList.Add(WaitResultInfo.WaitMsg(msg, msgLevel));
}
}
public class WaitResultInfo
......@@ -217,7 +221,15 @@ namespace DeviceLibrary
wait.ActionMsg = msg;
return wait;
}
public static WaitResultInfo WaitMsg(string msg, MsgLevel msgLevel)
{
WaitResultInfo wait = new WaitResultInfo();
wait.WaitType = WaitEnum.W014_Msg;
wait.ActionMsg = msg;
wait.IsEnd = true;
wait.Data = msgLevel;
return wait;
}
public string ToStr()
{
if (WaitType.Equals(WaitEnum.W001_AxisMove))
......@@ -315,6 +327,7 @@ namespace DeviceLibrary
/// </summary>
public Func<WaitResultInfo, bool> Action { get; set; }
public string ActionMsg { get; set; }
public dynamic Data { get; set; }
}
internal class WaitEnum
......@@ -355,6 +368,10 @@ namespace DeviceLibrary
/// 通用代理等待方法
/// </summary>
internal static int W013_Action = 13;
/// <summary>
/// 等待时反馈消息
/// </summary>
internal static int W014_Msg = 14;
}
......
......@@ -466,8 +466,13 @@ namespace TheMachine
frm.CurrLanguage = Crc_GetLanguageEvent();
frm.chbZxing.Checked = false;
frm.ShowDialog();
frm.Dispose();
try
{
frm.ShowDialog();
frm.Dispose();
}
catch { }
//IOManager.IOMove(IO_Type.Camera_Led, IO_VALUE.LOW);
}
......
支持 Markdown 格式
你添加了 0 到此讨论。请谨慎行事。
Finish editing this message first!