using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms; using HslCommunication; using HslCommunication.ModBus; using System.Threading; using System.Collections; using HslCommunication.Controls; using VzClientSDKDemo; using System.Runtime.InteropServices; using System.IO; using System.Net; using parkMonitor.entity; using System.Diagnostics; using System.Threading.Tasks; using parkMonitor.model; using parkMonitor.viewModel.objectTree; using parkMonitor.server.uiLogServer; using System.Configuration; using parkMonitor.LOG; using parkMonitor.Database2; namespace parkMonitor.server { /// /// 号牌机通信类 /// public partial class NumMachineLinker : Form, IEquipments { /// /// 刷新时间间隔与个数 /// public const int REFRESHINGTIME = 200, FILTERINGNUMBER = 20; ///通过设备句柄访问pic;链接时add,系统关闭时remove private static Dictionary devPicMap = new Dictionary(); ///通过名字获取pic对象;创建pic时add,系统关闭remove private static Dictionary namePicMap = new Dictionary(); ///通过ip获取设备id;产生ip时创建 private static Dictionary ipIdMap = new Dictionary(); ///通过ip获取设备句柄;产生句柄时创建 private static Dictionary ipHandleMap = new Dictionary(); /// 句柄到号牌回调映射 private static Dictionary handleCallbackMap = new Dictionary(); ///号牌队列 private static Queue LicBuffer = new Queue(); ///计数Map private static Dictionary> filterMap = new Dictionary>(); /// 号牌机编号与激活计数 private Dictionary idCountMap = new Dictionary(); /// 筛选计数 private static int filterCount = 0; private static double filterRatio = 0.7; /// 系统关闭 private static bool isClosing = false; /// 开启拍照的设备 private static int snapshotDevHandle = -1; /// 允许无号牌时拍照 private static bool enableEmptySnapshot = true; private static NumberMachineMessage nmMsg = new NumberMachineMessage(); private static VzClientSDK.VZLPRC_FIND_DEVICE_CALLBACK_EX find_DeviceCB = null; private const int MSG_PLATE_INFO = 0x901; private const int MSG_DEVICE_INFO = 0x902; /// /// 用于消息传递机制 /// public static IntPtr hwndMain; /// /// 号牌机类构造函数 /// public NumMachineLinker() { Control.CheckForIllegalCrossThreadCalls = false; InitializeComponent(); Thread thread = new Thread(new ThreadStart(DateTimeInfo)); thread.IsBackground = true; thread.Start(); //显示当前时间 VzClientSDK.VzLPRClient_Setup(); hwndMain = this.Handle; try { filterRatio = Double.Parse(ConfigurationManager.AppSettings.Get("filterRatio")); } catch (Exception) { UILogServer.ins.error("号牌机配置文件异常"); Log.WriteLog(LogType.NOT_DATABASE, LogFile.ERROR, "号牌机配置文件异常"); } //m_sAppPath = System.IO.Directory.GetCurrentDirectory(); } /// /// 定义时钟委托 /// public delegate void SetDateTime(); /// /// 实时采集 /// public delegate void Begin_Read(); private void FormSiemens_Load(object sender, EventArgs e) { Thread thread = new Thread(new ThreadStart(DateTimeInfo)); thread.IsBackground = true; thread.Start(); //显示当前时间 VzClientSDK.VzLPRClient_Setup(); hwndMain = this.Handle; //m_sAppPath = System.IO.Directory.GetCurrentDirectory(); //this.ShowInTaskbar = false; //this.Opacity = 0; } private void FormSiemens_FormClosing(object sender, FormClosingEventArgs e) { Stop(); } private void DateTimeInfo() { try { while (!isClosing) { SetDateTime setDate = new SetDateTime( delegate { toolStripStatusLabel2.Text = DateTime.Now.ToString(); }); setDate(); Thread.Sleep(1000); } // ReSharper disable once FunctionNeverReturns } catch (Exception e) { Debug.Print(e.StackTrace); } } //****************************************************************************************************************** private void UpdateStatus(NumberMachineNode nmn) { //提示次数计数 int count = 0; while (!isClosing) { if (GetStatus(nmn.ip) == 1) { nmn.status = EnumNumberMachineStatus.Normal; count = 0; } else { count++; //号牌机断线写日志 nmn.status = EnumNumberMachineStatus.Offline; if (count == 1) { Log.WriteLog(LogType.NOT_DATABASE, LogFile.ERROR, "正在与ip为 " + nmn.ip + " 的号牌机 进行连接。"); UILogServer.ins.info("正在与ip为 " + nmn.ip + " 的号牌机 进行连接。"); } else if (count == 30) { nmMsg.data.Remove(nmn); } Thread.Sleep(10000); } Thread.Sleep(REFRESHINGTIME); } } private void Run() { try { while (!isClosing) { Thread.Sleep(REFRESHINGTIME); //定时更新号牌 lock (devPicMap) { Dictionary.Enumerator devEnumer = devPicMap.GetEnumerator(); while (devEnumer.MoveNext()) { ActivateSnap(devEnumer.Current.Key); //Debug.WriteLine(lv.data.Count.ToString()+":\n"+ lv.data[lv.data.Count-1].LicenseNum+ lv.data[lv.data.Count - 1].TimeRecord); } } ////根据指令返回号牌 //for (int i = 1; i <= idCountMap.Count; i++) //{ // int count = 0; // if (idCountMap.TryGetValue(i, out count) && count <= FILTERINGNUMBER) // { // idCountMap[i] += 1; // Dictionary.Enumerator ipEnumer = ipIdMap.GetEnumerator(); // while (ipEnumer.MoveNext()) // { // int handle = 0; // if (ipEnumer.Current.Value == i && ipHandleMap.TryGetValue(ipEnumer.Current.Key, out handle)) // { // ActivateSnap(handle); // } // } // } //} //读取设备ip与id映射关系 lock (ipHandleMap) { Dictionary.Enumerator ipEnumer = ipHandleMap.GetEnumerator(); while (ipEnumer.MoveNext()) { //映射关系不存在则读取配置文件 lock (ipIdMap) { if (ipEnumer.Current.Key != null && !ipIdMap.ContainsKey(ipEnumer.Current.Key)) { try { int id = Int32.Parse(ConfigurationManager.AppSettings.Get(ipEnumer.Current.Key)); ipIdMap.Add(ipEnumer.Current.Key, id); idCountMap.Add(id, FILTERINGNUMBER); } catch (Exception) { UILogServer.ins.log("读取号牌机编号映射失败,配置文件填写有误"); Log.WriteLog(LogType.NOT_DATABASE, LogFile.ERROR, "读取号牌机编号映射失败"); } } } } } lock (LicBuffer) { //删除已停好车的号牌 for (int i = 0; i < LicBuffer.Count; i++) { NumberMachineNode n = (NumberMachineNode)LicBuffer.Dequeue().Clone(); if (n.ip != "") { LicBuffer.Enqueue((NumberMachineNode)n.Clone()); } } } } } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } } ///寻找设备回调函数 private void FIND_DEVICE_CALLBACK_EX(string pStrDevName, string pStrIPAddr, ushort usPort1, ushort usPort2, uint SL, uint SH, string netmask, string gateway, IntPtr pUserData) { string pStrDev = pStrIPAddr.ToString() + ":" + usPort1.ToString(); string serialNO = SL.ToString() + ":" + SH.ToString() + ":" + netmask + ":" + gateway; VzClientSDK.VZ_LPR_DEVICE_INFO device_info = new VzClientSDK.VZ_LPR_DEVICE_INFO(); device_info.device_ip = pStrDev; device_info.serial_no = serialNO; DeviceLink(pStrDev, serialNO); } ///与设备连接,启动更新设备状态线程,输出视频 private void DeviceLink(string pStrDev, string serialNO) { IPEndPoint ipe; GetIpEndPoint(pStrDev, out ipe); string ip = ipe.Address.ToString(); if (ipHandleMap.ContainsKey(ip)) { //MessageBox.Show("设备已分配句柄"); return; } int handle = 0; //找到设备,加入list NumberMachineNode node = new NumberMachineNode(ip, 0, "", "", 1); nmMsg.data.Add(node); handle = VzClientSDK.VzLPRClient_Open(ip, (ushort)80, "admin", "admin"); Task.Factory.StartNew(() => { UpdateStatus(node); }); if (handle == 0) { return; } VzClientSDK.VzLPRClient_SetVideoEncodeType(handle, 0); //将句柄加入 lock (ipHandleMap) { ipHandleMap.Add(ip, handle); } //MessageBox.Show("摄像头打开成功"); lock (handleCallbackMap) { handleCallbackMap.Add(handle, null); } //链接句柄到新PictureBox VideoOutput(handle); } ///视频输出 private void VideoOutput(int handle) { try { lock (devPicMap) { lock (handleCallbackMap) { //存在设备则复位并在原pic上输出 if (devPicMap.ContainsKey(handle)) { VzClientSDK.VzLPRClient_SetPlateInfoCallBack(handle, null, IntPtr.Zero, 0); PictureBox pic; if (devPicMap.TryGetValue(handle, out pic) && handleCallbackMap.ContainsKey(handle)) { int playHandle = VzClientSDK.VzLPRClient_StartRealPlay(handle, pic.Handle); // 设置车牌识别结果回调 handleCallbackMap[handle] = new VzClientSDK.VZLPRC_PLATE_INFO_CALLBACK(OnPlateResult); VzClientSDK.VzLPRClient_SetPlateInfoCallBack(handle, handleCallbackMap[handle], IntPtr.Zero, 1); //m_PlateResultCB = new VzClientSDK.VZLPRC_PLATE_INFO_CALLBACK(OnPlateResult); //VzClientSDK.VzLPRClient_SetPlateInfoCallBack(handle, m_PlateResultCB, IntPtr.Zero, 1); //VzClientSDK.VzLPRClient_StopRealPlay(handle); } } else//否则找一个空位加pic { for (int i = 0; i < 20; i++) { string str = "PictureBox" + Convert.ToString(i); //该名称对应控件不存在,则创建并链接pic lock (namePicMap) { if (!namePicMap.ContainsKey(str) && handleCallbackMap.ContainsKey(handle)) { PictureBox pic; if (CreatePic(i, out pic)) { devPicMap.Add(handle, pic); //ipIdMap.Add(Get_IP(handle), i); VzClientSDK.VzLPRClient_SetPlateInfoCallBack(handle, null, IntPtr.Zero, 0); int playHandle = VzClientSDK.VzLPRClient_StartRealPlay(handle, pic.Handle); // 设置车牌识别结果回调 handleCallbackMap[handle] = new VzClientSDK.VZLPRC_PLATE_INFO_CALLBACK(OnPlateResult); VzClientSDK.VzLPRClient_SetPlateInfoCallBack(handle, handleCallbackMap[handle], IntPtr.Zero, 1); //m_PlateResultCB = new VzClientSDK.VZLPRC_PLATE_INFO_CALLBACK(OnPlateResult); //VzClientSDK.VzLPRClient_SetPlateInfoCallBack(handle, m_PlateResultCB, IntPtr.Zero, 1); //VzClientSDK.VzLPRClient_StopRealPlay(handle); break; } } } } } } } } catch (Exception) { Debug.WriteLine("jumped out"); } } ///号牌信息回调 private int OnPlateResult(int handle, IntPtr pUserData, IntPtr pResult, uint uNumPlates, VzClientSDK.VZ_LPRC_RESULT_TYPE eResultType, IntPtr pImgFull, IntPtr pImgPlateClip) { if (eResultType != VzClientSDK.VZ_LPRC_RESULT_TYPE.VZ_LPRC_RESULT_REALTIME) { VzClientSDK.TH_PlateResult result = (VzClientSDK.TH_PlateResult)Marshal.PtrToStructure(pResult, typeof(VzClientSDK.TH_PlateResult)); string strLicense = (new string(result.license)).Split('\0')[0]; VzClientSDK.VZ_LPR_MSG_PLATE_INFO plateInfo = new VzClientSDK.VZ_LPR_MSG_PLATE_INFO(); plateInfo.plate = strLicense; SetDetail(plateInfo, Get_IP(handle)); //根据setMessage中通过id信息找到的handle保存图片 if (handle == snapshotDevHandle) { if (enableEmptySnapshot || !strLicense.Contains("_无_")) { string strFilePath = ConfigurationManager.AppSettings["LogPath"] + DateTime.Now.ToString("yyyyMMdd") + "\\"; if (!Directory.Exists(strFilePath)) { Directory.CreateDirectory(strFilePath); } string ip = Get_IP(handle); string path = strFilePath + ip + "-" + DateTime.Now.ToString("hh_mm_ss") + ".jpg"; int temp = VzClientSDK.VzLPRClient_ImageSaveToJpeg(pImgFull, path, 50); if (temp != -1) { Log.WriteLog(LogType.NOT_DATABASE, LogFile.LOG, "号牌机" + ip + "已拍照,图片保存于 " + strFilePath); UILogServer.ins.info("号牌机" + ip + "已拍照,图片保存于 " + strFilePath); } else { UILogServer.ins.info("图片保存失败"); Log.WriteLog(LogType.NOT_DATABASE, LogFile.ERROR, "图片保存失败"); } } snapshotDevHandle = -1; } } return 0; } ///记录车牌,时间,状态信息;刷新本地entity,并且入buffer private void SetDetail(VzClientSDK.VZ_LPR_MSG_PLATE_INFO plateInformation, string strIP) { int stat = GetStatus(strIP); bool found = false; int id = 0; lock (ipIdMap) { if (!ipIdMap.ContainsKey(strIP)) { try { id = Int32.Parse(ConfigurationManager.AppSettings.Get(strIP)); ipIdMap.Add(strIP, id); idCountMap.Add(id, FILTERINGNUMBER); } catch (Exception) { UILogServer.ins.error("读取号牌机编号映射失败,配置文件填写有误"); Log.WriteLog(LogType.NOT_DATABASE, LogFile.ERROR, "读取号牌机编号映射失败"); } } } //检查设备是否存在 foreach (NumberMachineNode nmn in nmMsg.data) { //相同设备 if (nmn.ip == strIP) { found = true; //号牌不为空 if (!(plateInformation.plate.Contains("_无_"))) { nmn.SetLic(strIP, nmn.id, plateInformation.plate, DateTime.Now.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"), GetStatus(strIP)); FilterLic(strIP, (NumberMachineNode)nmn.Clone()); } //号牌为空 else { nmn.SetLic(strIP, nmn.id, "", "", GetStatus(strIP)); FilterLic(strIP, null); } } } //新设备 if (!found) { lock (ipIdMap) { if (!(plateInformation.plate.Contains("_无_")) && ipIdMap.TryGetValue(strIP, out id)) { NumberMachineNode nmn = new NumberMachineNode(strIP, id, plateInformation.plate, DateTime.Now.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss"), GetStatus(strIP)); NumberMachineNode nmnc = (NumberMachineNode)nmn.Clone(); nmMsg.data.Add(nmnc); FilterLic(strIP, nmnc); } else { nmMsg.data.Add(new NumberMachineNode(strIP, id, "", "", GetStatus(strIP))); FilterLic(strIP, null); } } } } ///筛选号牌 private void FilterLic(string ip, NumberMachineNode nmn) { int id = 0, activeCount = 0; if (ipIdMap.TryGetValue(ip, out id) && idCountMap.TryGetValue(id, out activeCount) && activeCount < FILTERINGNUMBER) { Dictionary filter; //该filter不存在则创建 if (!filterMap.ContainsKey(ip)) { filter = new Dictionary(); filterMap.Add(ip, filter); } else if (!filterMap.TryGetValue(ip, out filter))//计数器异常 { return; } else if (activeCount == 0) //刚接到拍摄命令 { filter.Clear(); } //激活次数统计 idCountMap[id] = activeCount + 1; //filter计数 int count = 0; if (nmn == null) { if (activeCount >= FILTERINGNUMBER - 1) { UILogServer.ins.error("本轮未扫描到号牌,请重新点击按钮停车"); string context = System.DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + ":" + "号牌未扫描到,请重发指令"; string sql = "insert into messagequeue(userID,context,messageType) values (1,'" + context + "',1)"; List strs = new List(); strs.Add(sql); DBOperation.InsertMessageQueue(EntityForCore.remoteBQ, strs); } return; } else if (filter.ContainsKey(nmn) && filter.TryGetValue(nmn, out count))//存在则数量+1 { filter[nmn] = count + 1; } else//不存在则计数1 { filter.Add(nmn, 1); } //计算总数 filterCount = 0; Dictionary.Enumerator countEnumer = filter.GetEnumerator(); while (countEnumer.MoveNext()) { filterCount += countEnumer.Current.Value; } //达到计数限制,计算众数是否达标,达标则入队 if (filterCount >= FILTERINGNUMBER) { lock (LicBuffer) { Dictionary.Enumerator enumer = filter.GetEnumerator(); int maxCount = 0; NumberMachineNode node = null; while (enumer.MoveNext()) { ////遍历,计数达标且队列中无此号牌 //if (enumer.Current.Value >= (int)(filterCount * filterRatio) && enumer.Current.Key != null && !LicBuffer.Contains(enumer.Current.Key)) //找到最大计数及相应号牌信息 if (enumer.Current.Value >= maxCount && enumer.Current.Key != null && !LicBuffer.Contains(enumer.Current.Key)) { //输出节点为空或与该号牌不同 if (nmMsg.aNode == null || nmMsg.aNode.LicenseNum == null || nmMsg.aNode.LicenseNum != enumer.Current.Key.LicenseNum) { maxCount = enumer.Current.Value; node = (NumberMachineNode)enumer.Current.Key.Clone(); } } }; //将相应号牌信息存入队列 if (node != null && ipIdMap.TryGetValue(node.ip, out node.id)) { LicBuffer.Enqueue((NumberMachineNode)node.Clone()); } filterCount = 0; filter.Clear(); } } } } ///停止播放与设备句柄关联的视频 private void StopPlay(int handleInput) { if (handleInput != 0) { int ret = VzClientSDK.VzLPRClient_StopRealPlay(handleInput); } } ///强制获取号牌 private void ActivateSnap(int handle) { if (handle > 0) { VzClientSDK.VzLPRClient_ForceTrigger(handle); } } ///ip+port字符串转ipe private void GetIpEndPoint(string ipp, out IPEndPoint ipe) { IPAddress myIP = IPAddress.Parse(ipp.Remove(ipp.LastIndexOf(':')) + ""); string myPort = ipp.Substring(ipp.IndexOf(':') + 1); ipe = new IPEndPoint(myIP, int.Parse(myPort)); } ///创建新pic并记录在picNameMap private bool CreatePic(int index, out PictureBox pb) { pb = new PictureBox { Width = 230, Height = 180, AutoSize = false, Name = "PictureBox" + Convert.ToString(index) }; lock (namePicMap) { if (!namePicMap.ContainsKey(pb.Name)) { flowLayoutPanel1.Controls.Add(pb); namePicMap.Add(pb.Name, pb); return true; } else return false; } } ///更新设备状态 private int GetStatus(string devIP) { int myHandle; byte stat = 0; if (ipHandleMap.TryGetValue(devIP, out myHandle)) { VzClientSDK.VzLPRClient_IsConnected(myHandle, ref stat); return stat; } return -1; } private string Get_IP(int lprHandle) { byte[] strDecIP = new byte[32]; int max_count = 32; int ret = VzClientSDK.VzLPRClient_GetDeviceIP(lprHandle, ref strDecIP[0], max_count); string strIP = System.Text.Encoding.Default.GetString(strDecIP); strIP = strIP.TrimEnd('\0'); return strIP; } private bool NodeValidation(NumberMachineNode node) { return (node != null && node.ip != null && node.ip != "" && node.ip != "used" && node.LicenseNum != null && node.LicenseNum != "") ? true : false; } /// /// 系统启动 /// public void Start() { Task.Factory.StartNew(() => { isClosing = false; try { VzClientSDK.VZLPRClient_StopFindDevice(); find_DeviceCB = new VzClientSDK.VZLPRC_FIND_DEVICE_CALLBACK_EX(FIND_DEVICE_CALLBACK_EX); int ret = VzClientSDK.VZLPRClient_StartFindDeviceEx(find_DeviceCB, IntPtr.Zero); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } }); Task.Factory.StartNew(() => { Run(); }); //Task imgTest = Task.Factory.StartNew(() => //{ // while (!isClosing) // { // Command cmd = new Command // { // id = 1 // }; // SetMessage(cmd); // Thread.Sleep(30000); // Command cmd2 = new Command // { // id = 2 // }; // SetMessage(cmd2); // Thread.Sleep(30000); // } //}); } /// /// 系统关闭 /// public void Stop() { VzClientSDK.VZLPRClient_StopFindDevice(); lock (devPicMap) { Dictionary.Enumerator enumer = devPicMap.GetEnumerator(); while (enumer.MoveNext()) { if (enumer.Current.Key != 0) { StopPlay(enumer.Current.Key); VzClientSDK.VzLPRClient_Close(enumer.Current.Key); flowLayoutPanel1.Controls.Remove(enumer.Current.Value); } } isClosing = true; devPicMap.Clear(); } lock (namePicMap) { namePicMap.Clear(); } lock (ipIdMap) { ipIdMap.Clear(); } lock (ipHandleMap) { ipHandleMap.Clear(); } //this.Close(); } /// /// 监控线程获取号牌机信息,核心线程获取号牌信息 /// public AbstractMessage GetMessage() { lock (LicBuffer) { //准备输出的数据中存在非法Node,且LicBuffer可出队产生一个合法Node,则替换该非法Node if (!NodeValidation(nmMsg.aNode)) { for (int i = 0; i < LicBuffer.Count; i++) { NumberMachineNode n = LicBuffer.Dequeue(); if (NodeValidation(n)) { if (nmMsg.aNode != null && nmMsg.aNode.ip != null) { LicBuffer.Enqueue((NumberMachineNode)nmMsg.aNode.Clone()); } nmMsg.aNode = n; break; } else { LicBuffer.Enqueue(n); } } //遍历licBuffer后仍不合法,则丢入队列,将node置为null if (!NodeValidation(nmMsg.aNode)) { if (nmMsg.aNode != null && nmMsg.aNode.ip != null) { LicBuffer.Enqueue((NumberMachineNode)nmMsg.aNode.Clone()); } nmMsg.aNode = null; } } } return nmMsg; } /// /// 一次停车流程完成时调用该方法,发送已完成车辆号牌信息 /// /// 已完成车辆的号牌相关信息存于message的aNode中,用于标记需清空的号牌 public void SetMessage(AbstractMessage message) { if (message.GetType().Equals(typeof(NumberMachineMessage))) { NumberMachineNode n = ((NumberMachineMessage)message).aNode; lock (LicBuffer) { //输入号牌格式无误 if (n != null && n.ip != null && n.ip == "") { //与类成员变量中aNode号牌相同,将其ip复位表示已使用,重新入队等待清除 if (nmMsg != null && nmMsg.aNode != null && nmMsg.aNode.LicenseNum == n.LicenseNum) { nmMsg.aNode.ip = ""; LicBuffer.Enqueue((NumberMachineNode)nmMsg.aNode.Clone()); nmMsg.aNode = null; } //搜索号牌队列,将相应号牌置空,准备清除 else { for (int i = 0; i < LicBuffer.Count; i++) { NumberMachineNode temp = LicBuffer.Dequeue(); //已匹配上,ip置空 if (temp.LicenseNum == n.LicenseNum) { temp.ip = ""; LicBuffer.Enqueue(temp); break; } LicBuffer.Enqueue(temp); } } } } } //传入指令,根据ip找到handle,改变snapshotDevHandle的值在回调函数中截图 if (message.GetType().Equals(typeof(Command))) { Command cmd = (Command)message; if (cmd != null && cmd.id != 0) { //按动按钮启动号牌机扫描 if (cmd.manual) { lock (idCountMap) { if (idCountMap.ContainsKey(cmd.id)) { idCountMap[cmd.id] = 0; } } } else { lock (ipIdMap) { Dictionary.Enumerator enumerator = ipIdMap.GetEnumerator(); while (enumerator.MoveNext()) { // if (enumerator.Current.Value == cmd.id && ipHandleMap.TryGetValue(enumerator.Current.Key, out int handle)) int handle = 0; if (enumerator.Current.Value == cmd.id && ipHandleMap.TryGetValue(enumerator.Current.Key, out handle)) { snapshotDevHandle = handle; break; } } } } } else { UILogServer.ins.error("参数错误,图片未保存"); Log.WriteLog(LogType.NOT_DATABASE, LogFile.ERROR, "参数错误,图片未保存"); } } } } }