zoukankan      html  css  js  c++  java
  • 聊天室(C++客户端+Pyhton服务器)2.基本功能添加

    根据之前的框架添加新的功能

    登录

    点击相关按钮

    // 登录按钮的响应
    void CMainDialog::OnBnClickedLogin()
    {
    // 1. 获取用户输入的数据
    UpdateData(TRUE);

    // 2. 判断输入的数据是否为空
    if (m_UserName.IsEmpty() || m_PassWord.IsEmpty())
    {
    MessageBox(L"请输入用户名和密码");
    return;
    }

    // 3. 填充需要发送的数据内容
    SEND_INFO SendInfo = { TYPE::LOGIN, GetSafeHwnd() };
    memcpy(SendInfo.LoginInfo.UserName, m_UserName.GetBuffer(), m_UserName.GetLength() * 2);
    memcpy(SendInfo.LoginInfo.PassWord, m_PassWord.GetBuffer(), m_PassWord.GetLength() * 2);

    // 4. 将数据通过自定义消息发送到服务器
    SendMessage(UM_SEND_MSG, (WPARAM)& SendInfo, sizeof(SEND_INFO));
    }

    发送的结构体

    // 1. 登录使用的的结构体
    using LOGIN_INFO = _REGISTER_INFO;
    using PLOGIN_INFO = _REGISTER_INFO*;

     

    python接收到后处理的方法

    # 实例方式:用于处理登录消息
       def on_login(self, client, message):
           # 解包获取发送来的数据
           hwnd, username, password = struct.unpack("i64s64s", message[:132])
           # 将用户名和密码进行解码
           username = username.decode("UTF16").strip("x00")
           password = password.decode("UTF16").strip("x00")
           # 判断用户和密码是否存在于数据库中
           if self.mysql.select("SELECT * FROM user_table WHERE user='%s' AND pswd=MD5('%s');" %(username, password)):
               # 如果用户名和密码匹配,返回结果 => 类型 + 句柄 + 是否成功 + 服务器返回的信息
               client.send(struct.pack("iii40s", Type.LOGIN.value, hwnd, 1, "登录成功".encode("UTF16")))
               # 将登录成功的用户添加到【在线用户】字典中
               print(username, "登录了聊天室")
               self.dict_client[username] = client
           else:
               # 失败的信息
               client.send(struct.pack("iii40s", Type.LOGIN.value, hwnd, 0, "用户名或密码不匹配".encode("UTF16")))

     

    客户端接接受到服务端发来的消息做的反应

    主窗口响应,转发

    // 响应登录
    case TYPE::LOGIN:
    {
    // 对于注册是否成功的消息,wParam 没有用,lParam 指向 RECV_STATE
    ::SendMessage(RecvInfo->hWnd, UM_RECV_LOGIN, NULL, (LPARAM)& RecvInfo->RecvState);
    break;
    }

    转发到的地方响应

    // 是否登录成功
    afx_msg LRESULT CMainDialog::OnUmRecvLogin(WPARAM wParam, LPARAM lParam)
    {
    // 将 lParam 转换成对应的的结构
    PRECV_STATE RecvState = (PRECV_STATE)lParam;

    // 判断是否登录成功
    if (RecvState->IsSuccess)
    {
    // 弹出用户话框(好友、群组)
    CUserDialog* dialog = new CUserDialog;
    dialog->Create(IDD_USERDIALOG, this);
    // [ 对话框必须是非模态的,否则会导致消息的阻塞 ]
    // [ 对话框必须是保存在堆空间中,否则离开作用域会销毁 ]
    return dialog->ShowWindow(SW_SHOWNORMAL);
    }

    // 不成功执行这里
    MessageBox(RecvState->MsgInfo);
    }

     

     

    登录之后进到一个新的界面

    获取父窗口句柄

    //构造函数内设置主窗口
    CUserDialog::CUserDialog(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_USERDIALOG, pParent)
    , m_AddEdit(_T(""))
    {
    // 保存父窗口
    MainDialog = (CMainDialog*)AfxGetMainWnd();
    }

     

    设置标题

    初始化树控件

    // 初始化对话框(获取用户和群组)
    BOOL CUserDialog::OnInitDialog()
    {
    CDialogEx::OnInitDialog();

    // 隐藏父窗口
    MainDialog->ShowWindow(SW_HIDE);
    SetWindowText(MainDialog->m_UserName + L"主窗口");

    // 添加好友分组和群组的节点
    m_FrdNode = m_TreeCtrl.InsertItem(L"好友列表");
    m_GrpNode = m_TreeCtrl.InsertItem(L"群组列表");

    return TRUE;
    }

     

     

     

    C++客户端添加好友功能

    客户端向服务器发送要添加的消息

    获取要添加的名字

    打包发送给服务器

    / 添加好友按钮的响应
    void CUserDialog::OnBnClickedAddfrd()
    {
    // 1. 获取自己的名字和目标名称
    UpdateData(TRUE);
    CString MyName = MainDialog->m_UserName;

    // 2. 自己不能添加自己为好友
    if (MyName == m_AddEdit)
    {
    MessageBox(L"无法添加自己为好友!");
    return;
    }

    // 3. 组合消息
    SEND_INFO SendInfo = { TYPE::ADDFRD, GetSafeHwnd() };
    memcpy(SendInfo.AddFrdInfo.User1, MyName.GetBuffer(), MyName.GetLength() * 2);
    memcpy(SendInfo.AddFrdInfo.User2, m_AddEdit.GetBuffer(), m_AddEdit.GetLength() * 2);

    // 4. 通过主窗口发送消息      
    ::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)& SendInfo, sizeof(SendInfo));
    }

     

    打包前得先弄信息结构体

    // 2. 添加好友的消息
    typedef struct _ADDFRD_INFO
    {
    WCHAR User1[32];
    WCHAR User2[32];
    } ADDFRD_INFO, * PADDFRD_INFO;

     

     

    服务端接收到消息之后的处理

    添加类型

    添加方法

    定义方法

    添加新的表单friend_table

    user1

    user2

    发送回客户端

     

    # 添加好友的消息
       def on_addfrd(self, client, message):
           # 解包获取发送来的数据
           hwnd, user1, user2 = struct.unpack("i64s64s", message[:132])
           # 获取双方的用户名
           user1 = user1.decode("UTF16").strip("x00")
           user2 = user2.decode("UTF16").strip("x00")
           # 是否有这样一个用户
           if not self.mysql.select("SELECT * FROM user_table WHERE user='%s';" % user2):
               client.send(struct.pack("iii40s", Type.ADDFRD.value, hwnd, 0, "用户不存在".encode("UTF16")))
           # 是否已经是我的好友
           elif self.mysql.select("SELECT * FROM friend_table WHERE user1='%s' AND user2='%s';" % (user1, user2)):
               client.send(struct.pack("iii40s", Type.ADDFRD.value, hwnd, 0, "不能重复添加好友".encode("UTF16")))
           # 添加成功的情况
           else:
               # 向数据库中添加一组信息 (我,好友)
               self.mysql.exec("INSERT INTO friend_table VALUE('%s', '%s');" % (user1, user2))
               # 在返回的消息处填写添加的好友的名称
               client.send(struct.pack("iii40s", Type.ADDFRD.value, hwnd, 1, user2.encode("UTF16")))

     
    客户端接收到服务器的反馈的反应

    要用非模态的对话框,否则信息会堵塞

     

    主窗口响应,转发

    // 响应加好友
    case TYPE::ADDFRD:
    {
    // 对于注册是否成功的消息,wParam 没有用,lParam 指向 RECV_STATE
    ::SendMessage(RecvInfo->hWnd, UM_RECV_ADDFRD, NULL, (LPARAM)& RecvInfo->RecvState);
    break;
    }

    转发到的地方

    // 查看是否成功
    afx_msg LRESULT CUserDialog::OnUmRecvAddfrd(WPARAM wParam, LPARAM lParam)
    {
    // 将 lParam 转换成对应的的结构
    PRECV_STATE RecvState = (PRECV_STATE)lParam;

    // 判断是否添加成功
    if (RecvState->IsSuccess)
    {
    // 如果成功执行直接添加到列表
    m_TreeCtrl.InsertItem(RecvState->MsgInfo, m_FrdNode);
    return 0;
    }

    // 不成功执行这里
    MessageBox(RecvState->MsgInfo);
    return 0;
    }

     

    更新好友列表

    打开好友的时候会自动更新好友列表

    初始化的时候发送请求

    // 发消息给服务器更新好友列表
    SEND_INFO SendInfo = { TYPE::UPDATEFRD, GetSafeHwnd() };
    memcpy(SendInfo.UpdateFrdInfo.User, MainDialog->m_UserName.GetBuffer(),
    MainDialog->m_UserName.GetLength() * 2);

    // 通过主窗口向服务器发送消息
    ::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)& SendInfo, sizeof(SendInfo));

    相关的结构体

    // 3. 更新好友列表
    typedef struct _UPDATEFRD_INFO
    {
    WCHAR User[32];
    } UPDATEFRD_INFO, * PUPDATEFRD_INFO;

     

    服务器接收到处理之后发回来

    # 更新好友的消息
       def on_updatefrd(self, client, message):
           # 解包获取发送来的数据
           hwnd, user = struct.unpack("i64s", message[:68])
           # 获取目标用户名
           user = user.decode("UTF16").strip("x00")
           # 查询目标用户的所有好友名称
           friends = self.mysql.select("SELECT user2 FROM friend_table WHERE user1='%s';" % user)
           # 通过一个循环,将每进一个用户名发送到客户端
           for name in friends:   # (("1"),("2"),("3"))
               client.send(struct.pack("ii64s", Type.UPDATEFRD.value, hwnd, name[0].encode("UTF16")))
               time.sleep(0.1)

     

     

    客户端接受到消息之后处理

    主窗口响应,转发

    // 响应更新好友
    case TYPE::UPDATEFRD:
    {
    // 对于注册是否成功的消息,wParam 没有用,lParam 指向 RECV_STATE
    ::SendMessage(RecvInfo->hWnd, UM_RECV_UPDATEFRD, NULL, (LPARAM)& RecvInfo->RecvState);
    break;
    }

    转发到的地方

    // 响应更新好友的消息
    afx_msg LRESULT CUserDialog::OnUmRecvUpdatefrd(WPARAM wParam, LPARAM lParam)
    {
    // 转换成对应的结构体
    PUPDATEFRD_INFO UserInfo = (PUPDATEFRD_INFO)lParam;

    // 【添加到树控件的好友节点中,收到的消息前两个字节会是 ff fe】
    m_TreeCtrl.InsertItem(&UserInfo->User[1], m_FrdNode);

    return 0;
    }

     

     

     

    好友私聊

    私聊窗口的界面

    客户端的话,双击树控件的好友的时候要能弹出聊天窗口,

    所以需要响应树控件按钮

     

    字典#include<map>

    using namespace std:

    判断是不是好友窗口,群窗口同理

    窗口可以双击打开,

    // 响应窗口聊天
    void CUserDialog::OnDblclkTree1(NMHDR* pNMHDR, LRESULT* pResult)
    {
    // 1. 获取当前点击的这一个子节点的内容
    HTREEITEM CurrentNode = m_TreeCtrl.GetSelectedItem();
    CString Name = m_TreeCtrl.GetItemText(CurrentNode);

    // 2. 判断节点是不是[好友列表]或者[群组列表]
    if (Name.Compare(L"好友列表") && Name.Compare(L"群组列表"))
    {
    // 3.1 判断窗口是否已经存在
    if (MainDialog->WindowsMap.find(Name) != MainDialog->WindowsMap.end())
    {
    // 存在则显示,确保一个窗口只会被打开一次
    MainDialog->WindowsMap[Name]->ShowWindow(SW_SHOWNORMAL);
    }
    // 3.2 判断是不是好友聊天窗口
    else if (m_TreeCtrl.GetParentItem(CurrentNode) == m_FrdNode)
    {
    // 3.2.1 创建用户聊天窗口
    CFrdDialog* dialog = new CFrdDialog(Name);
    dialog->Create(IDD_DIALOG2, this);
    // 3.2.2 设置用户聊天窗口的窗口名为点击的文字
    dialog->SetWindowText(Name);
    dialog->ShowWindow(SW_SHOWNORMAL);
    // 3.2.3 将新创建的窗口添加到字典中
    MainDialog->WindowsMap[Name] = dialog;
    }
    // 3.3 判断是不是群组聊天窗口
    else if (m_TreeCtrl.GetParentItem(CurrentNode) == m_GrpNode)
    {
    // 3.3.1 创建群组聊天窗口
    CGrpDialog* dialog = new CGrpDialog(Name);
    dialog->Create(IDD_DIALOG3, this);
    // 3.3.2 设置群组聊天窗口的窗口名为点击的文字
    dialog->SetWindowText(Name);
    dialog->ShowWindow(SW_SHOWNORMAL);
    // 3.3.3 将新创建的窗口添加到字典中
    MainDialog->WindowsMap[Name] = dialog;
    }
    }

    *pResult = 0;
    }

    初始化的时候绑定参数(私聊对象跟自己)

    CFrdDialog::CFrdDialog(CString TargetName, CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_DIALOG2, pParent)
    , m_ShowEdit(_T(""))
    {
    m_TargetName = TargetName;
    MainDialog = (CMainDialog*)AfxGetMainWnd();
    }

     

    接下来是发送信息,先能显示在窗口上

    往服务器发消息

    // 主动的发送一个消息
    void CFrdDialog::OnBnClickedSendmsg()
    {
    // 1. 获取编辑框(聊天记录 + 输入框)的内容
    UpdateData(TRUE);

    // 2. 将用户的输入拼接到聊天框的结尾
    SYSTEMTIME SystemTime = { 0 };
    GetSystemTime(&SystemTime);
    CString TimeString;
    TimeString.Format(L"%d:%d:%d %d:%d:%d", SystemTime.wYear,
    SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour,
    SystemTime.wMinute, SystemTime.wSecond);
    m_ShowEdit += L"你 说(" + TimeString + L"):   "
    + m_InputEdit + L" ";

    // 3. 将输入的数据进行组合,句柄是无效的
    CString MyName = MainDialog->m_UserName;
    SEND_INFO SendInfo = { TYPE::FRDMSG, NULL };
    memcpy(SendInfo.FrdMsg.From, MyName.GetBuffer(), MyName.GetLength() * 2);
    memcpy(SendInfo.FrdMsg.To, m_TargetName.GetBuffer(), m_TargetName.GetLength() * 2);
    memcpy(SendInfo.FrdMsg.Msg, m_InputEdit.GetBuffer(), m_InputEdit.GetLength() * 2);

    // 4. 通过主窗口发送到服务器
    ::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)& SendInfo, sizeof(SEND_INFO));

    // 5. 将组合的数据显示到输出框
    m_InputEdit = "";
    UpdateData(FALSE);
    }

    所需结构体

    // 4. 聊天消息
    typedef struct _FRDMSG_INFO
    {
    WCHAR From[32];
    WCHAR To[32];
    WCHAR Msg[100];
    } FRDMSG_INFO, * PFRDMSG_INFO;

     

    服务器处理消息发送回来

    # 发送好友的消息
       def on_frdmsg(self, client, message):
           # 解包获取发送来的数据
           hwnd, fromuser, to, msg = struct.unpack("i64s64s200s", message[:332])
           # 获取发送给的用户的名字
           to = to.decode("UTF16").strip("x00")
           # 查看目标用户是否在线
           if to in self.dict_client:
               # 自己提供消息类型+原有的所有数据
               self.dict_client[to].send(b'x04x00x00x00' + message)

     

     

    客户端处理消息显示好友发来的信息。

    // 响应好友发过来的消息
    afx_msg LRESULT CFrdDialog::OnUmRecvFrdmsg(WPARAM wParam, LPARAM lParam)
    {
    // 0. 转换成对应的结构体
    PFRDMSG_INFO Msg = (PFRDMSG_INFO)lParam;

    // 1. 获取编辑框(聊天记录)的内容
    UpdateData(TRUE);

    // 2. 将用户的输入拼接到聊天框的结尾
    SYSTEMTIME SystemTime = { 0 };
    GetSystemTime(&SystemTime);
    CString TimeString;
    TimeString.Format(L"%d:%d:%d %d:%d:%d", SystemTime.wYear,
    SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour,
    SystemTime.wMinute, SystemTime.wSecond);
    m_ShowEdit += CString(Msg->From) + L" 说(" + TimeString + L"):   "
    + Msg->Msg + L" ";

    // 3. 将组合的数据显示到输出框
    UpdateData(FALSE);
    return 0;
    }

     

    私聊发送文件

    点击传送文件

    //文件传送
    void CPrivateChat::OnBnClickedTransferfiles()
    {

    //设置过滤器
    TCHAR szFilter[] = _T("文本文件(*.txt)|*.txt|所有文件(*.*)|*.*||");
    // 构造打开文件对话框
    CFileDialog fileDlg(TRUE, _T("txt"), NULL, 0, szFilter, this);
    CString strFilePath, strText;

    // 显示打开文件对话框
    if (IDOK == fileDlg.DoModal())
    {
    CString MyName = MainDialog->m_UserName;
    SEND_INFO SendInfo = { TYPE::TRSFERFILE,NULL };
    memcpy(SendInfo.TrsferFile.From, MyName.GetBuffer(), MyName.GetLength() * 2);
    memcpy(SendInfo.TrsferFile.To, m_TargetName.GetBuffer(), m_TargetName.GetLength() * 2);
    strFilePath = fileDlg.GetPathName(); //获取文件路径
    CStdioFile file2;
    if (!file2.Open(strFilePath, CFile::modeRead)) //打开文件
    return;
    while (true)//按行循环读取文件内容存入strText
    {
    if (!file2.ReadString(strText))
    {
    SetDlgItemText(IDC_EDIT2, strFilePath + " 发送完毕... ");
    break;
    }
    MessageBox(strText);
    memcpy(SendInfo.TrsferFile.Msg, strText.GetBuffer(),strText.GetLength() * 2);
    ::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)&SendInfo, sizeof(SEND_INFO));
    }

    }



    }

    所需结构体

    using TRSFERFILE_INFO = FRDMSG_INFO;
    using PTRSFERFILE_INFO = PFRDMSG_INFO;

    服务器

        def on_trsferfile(self,client,message):
           hwnd, fromuser, to, msg = struct.unpack("i64s64s200s", message[:332])
           fromuser = fromuser.decode("UTF16").strip("x00")
           to = to.decode("UTF16").strip("x00")
           msg = msg.decode("UTF16").strip("x00")
           if to in self.dict_client:
               self.dict_client[to].send(b'x0bx00x00x00' + message)

     

    客户端

    case TYPE::TRSFERFILE:
    {
    //获取到是谁发来的,判断是否有窗口。
    CString Name = RecvInfo->TrsferFileInfo.From;
    if (WindowsMap.find(Name) == WindowsMap.end())
    {
    CPrivateChat* dialog = new CPrivateChat(Name);
    dialog->Create(IDD_PRIVATE, this);
    dialog->SetWindowTextW(Name);
    dialog->ShowWindow(SW_SHOWNORMAL);
    WindowsMap[Name] = dialog;
    }
    ::SendMessage(WindowsMap[Name]->GetSafeHwnd(), UM_RECV_TRSFERFILE, NULL, (LPARAM)&RecvInfo->TrsferFileInfo);
    break;
    }
    //接收文件传送
    afx_msg LRESULT CPrivateChat::OnUmRecvTrsferfile(WPARAM wParam, LPARAM lParam)
    {
    PTRSFERFILE_INFO Msg = (PTRSFERFILE_INFO)lParam;
    // 1. 创建打开文件,返回文件句柄,失败返回 -1
    HANDLE FileHandle = CreateFile(
    L"E:\2.txt",               // 文件的路径
    GENERIC_ALL,                // 表示可以对文件执行任何操作
    NULL,                       // 共享方式,我正在操作的时候,允许其他进程执行的操作
    NULL,                       // 安全属性
    OPEN_ALWAYS,              // 始终创建这个文件
    FILE_ATTRIBUTE_NORMAL,      // 普通的文件
    NULL);

    DWORD RealWrite = 0;
    WriteFile(FileHandle,Msg->Msg , 100, &RealWrite, NULL);

    MessageBox(L"接受到了 ");
    CloseHandle(FileHandle);
    return 0;
    }

    聊天记录

    数据库msg_log(fromuser,touser,msg,datetime)

    点击按钮

    //获取聊天记录
    void CPrivateChat::OnBnClickedLogmsg()

    {

    CString MyName = MainDialog->m_UserName;
    SEND_INFO SendInfo = { TYPE::LOGMSG,NULL };
    memcpy(SendInfo.LogMsg.User1, MyName.GetBuffer(), MyName.GetLength() * 2);
    memcpy(SendInfo.LogMsg.User2, m_TargetName.GetBuffer(), m_TargetName.GetLength() * 2);

    ::SendMessage(MainDialog->GetSafeHwnd(), UM_SEND_MSG, (WPARAM)&SendInfo, sizeof(SEND_INFO));

    }

    所需结构体

    using LOGMSG_INFO = ADDFRD_INFO;
    using PLOGMSG_INFO = PADDFRD_INFO;

     

    服务器

        def on_logmsg(self,client,message):
           hwnd,user1,user2=struct.unpack("i64s64s",message[:132])
           user1 =user1.decode("UTF16").strip("x00")
           user2 =user2.decode("UTF16").strip("x00")
           len, result = self.mysql.getresult("SELECT * FROM msg_log WHERE fromuser='%s' AND touser = '%s' "
                               " OR fromuser='%s' AND touser='%s';" % (user1, user2, user2, user1))
           for index in range(len):

               fromuser =result[index][0].encode("UTF16")
               touser =result[index][1].encode("UTF16")
               LogMsg =result[index][2].encode("UTF16")
               times = result[index][3].strftime("%Y-%m-%d %H:%M:%S")

               client.send(struct.pack("ii64s64s64s200s60s",Type.LOGMSG.value,hwnd,user2.encode("UTF16"),fromuser,touser
                                      ,LogMsg,times.encode("UTF16")))
               print(111)
               if(index + 1)% 4==0:
                   time.sleep(0.1)

     

    客户端

    case TYPE::LOGMSG:
    {
    //获取到跟谁的聊天记录,判断是否有窗口
    CString Name = &RecvInfo->LogMsg.From[1];
    int  a = 10;
    if (WindowsMap.find(Name) == WindowsMap.end())
    {
    CPrivateChat* dialog = new CPrivateChat(Name);
    dialog->Create(IDD_PRIVATE, this);
    dialog->SetWindowTextW(Name);
    dialog->ShowWindow(SW_SHOWNORMAL);
    WindowsMap[Name] = dialog;
    }
    ::SendMessage(WindowsMap[Name]->GetSafeHwnd(), UM_RECV_LOGMSG, NULL, (LPARAM)&RecvInfo->FrdMsg);
    break;
    }
    //接受获取到的聊天记录
    afx_msg LRESULT CPrivateChat::OnUmRecvLogmsg(WPARAM wParam, LPARAM lParam)
    {
    //解包
    PRECV_LOGMSG Msg = (PRECV_LOGMSG)lParam;
    CString User1 = Msg->User1;
    CString msg = &Msg->Msg[1];
    //解密
    CString receive("");
    for (int i = 0; i < msg.GetLength(); i++)
    {
    receive += WCHAR(msg[i] ^ 1);
    }

    //更新窗口信息

    if (User1== MainDialog->m_UserName)
    {
    m_ShowEdit += CString(Msg->User1) + L"说(" + Msg->DateTime + L"):   "
    + receive + L" ";
    }
    else
    {
    m_ShowEdit += CString(Msg->User2) + L"说(" + Msg->DateTime + L"):   "
    + receive + L" ";
    }


    UpdateData(FALSE);
    return 0;
    }

    接受聊天记录所需要结构体

    typedef struct _RECV_LOGMSG
    {
    WCHAR From[32];
    WCHAR User1[32];
    WCHAR User2[32];
    WCHAR Msg[100];
    WCHAR DateTime[30];


    }RECV_LOGMSG,*PRECV_LOGMSG;

     

     

  • 相关阅读:
    机器学习log
    shiro教程
    开源litemall学习
    开源mall学习
    elasticsearch 踩坑
    C#工具代码
    Java开发环境搭建——Tomcat配置
    Resource leak: 'context' is never closed
    JavaScript备忘录
    Java开发环境搭建——IntelliJ Idea开发环境
  • 原文地址:https://www.cnblogs.com/ltyandy/p/11031511.html
Copyright © 2011-2022 走看看