Java學習-第一部分-第三階段-項目實戰:多用戶即時通訊系統

来源:https://www.cnblogs.com/wenjie2000/archive/2022/09/11/16683379.html
-Advertisement-
Play Games

多用戶即時通訊系統 包含推消息 私聊 發文件 等功能 筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html) 為什麼選擇這個項目 有趣 涉及到java各個方面的技術 ✔項目框架設計 ✔java面向對象編程 ✔網路編程 ✔多線程 ✔IO流 ✔ ...


多用戶即時通訊系統

包含推消息 私聊 發文件 等功能

筆記目錄:(https://www.cnblogs.com/wenjie2000/p/16378441.html)

為什麼選擇這個項目

  1. 有趣

  2. 涉及到java各個方面的技術

    ✔項目框架設計

    ✔java面向對象編程

    ✔網路編程

    ✔多線程

    ✔IO流

    ✔Mysql/ 學習前使用集合充當記憶體資料庫

  3. 鞏固舊知識,學習新知識

項目開發流程

需求分析-->設計階段--->編碼實現-->測試階段-->實施階段

需求分析

  1. 用戶登錄
  2. 拉取線上用戶列表
  3. 無異常退出(客戶端、伺服器端)
  4. 私聊
  5. 群聊
  6. 發文件
  7. 伺服器推送新聞

界面設計

  1. 用戶登錄

    image

  2. 拉取線上用戶列表

    image

  3. 私聊

    image

  4. 群聊

    image

  5. 發文件

    image

  6. 文件伺服器推送新聞

    image

功能實現-用戶登錄

  1. 功能說明

    因為還沒有學習資料庫,我們人為規定用戶名/id = 100,密碼123456就可以登錄,其它用戶不能登錄

    後面使用HashMap模擬資料庫,可以多個用戶登錄。

    image

  2. 思路分析+程式框架圖

    image

  3. 代碼實現(每部分功能代碼實現較為分散,推薦看視頻)

    P688~P695

功能實現-拉取線上用戶列表

  1. 功能說明

    image

  2. 代碼實現

    P696~P698

功能實現-無異常退出

  1. 功能說明

    某一端直接強行退出會造成另一端socket報錯,因為使用了catch會導致死迴圈報錯

  2. 思路分析+程式框架圖

    image

  3. 代碼實現

    P699~P700

功能實現-私聊

  1. 功能說明

    image

  2. 代碼實現

    P701~P703

功能實現-群聊

  1. 功能說明

    image

  2. 代碼實現

    P704

功能實現-發文件

  1. 功能說明

    image

  2. 思路分析+程式框架圖

    image

  3. 代碼實現

    P705~P707

功能實現-文件伺服器推送新聞

  1. 功能說明

    image

  2. 思路分析+程式框架圖

    image

  3. 代碼實現

    P708

擴展功能-自己獨立完成

  1. 功能說明

    提示:核心技術已經講過了,小伙伴認真想想,是完全可以完成的,並不難.在實際工作中,獨立解決問題的能力非常重要,必須有意識培養。

    1. 實現離線留言,如果某個用戶沒有線上,當登錄後,可以接受離線的消息

    2. 實現離線發文件,如果某個用戶沒有線上,當登錄後,可以接受離線的文件

  2. 實現思路+程式框架圖

    image

  3. 代碼實現

    這部分沒寫(以後可能會補上),實現並不難。

完整代碼

伺服器端

程式文件結構

image

代碼

com.hspedu

qqcommon

Message

package com.hspedu.qqcommon;

import java.io.Serializable;

/**
 * 表示客戶端和服務端通信時的消息對象
 */
public class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    private String sender;//發送者
    private String getter;//接收者
    private String content;//消息內容
    private String sendTime;//發送時間
    private String mesType;//消息類型[可以在介面定義消息類型]

    //進行擴展 和文件相關的成員
    private byte[] fileBytes;
    private int fileLen = 0;
    private String dest; //將文件傳輸到哪裡
    private String src; //源文件路徑

    public byte[] getFileBytes() {
        return fileBytes;
    }

    public void setFileBytes(byte[] fileBytes) {
        this.fileBytes = fileBytes;
    }

    public int getFileLen() {
        return fileLen;
    }

    public void setFileLen(int fileLen) {
        this.fileLen = fileLen;
    }

    public String getDest() {
        return dest;
    }

    public void setDest(String dest) {
        this.dest = dest;
    }

    public String getSrc() {
        return src;
    }

    public void setSrc(String src) {
        this.src = src;
    }

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getGetter() {
        return getter;
    }

    public void setGetter(String getter) {
        this.getter = getter;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }
}

MessageType

package com.hspedu.qqcommon;

public interface MessageType {
    //解讀
    //1. 在介面中定義了一些常量
    //2. 不同的常量的值,表示不同的消息類型.
    String MESSAGE_LOGIN_SUCCEED = "1"; //表示登錄成功
    String MESSAGE_LOGIN_FAIL = "2"; // 表示登錄失敗
    String MESSAGE_COMM_MES = "3"; //普通信息包(用戶之間的文字消息)
    String MESSAGE_GET_ONLINE_FRIEND = "4"; //要求返回線上用戶列表
    String MESSAGE_RET_ONLINE_FRIEND = "5"; //返回線上用戶列表
    String MESSAGE_CLIENT_EXIT = "6"; //客戶端請求退出
    String MESSAGE_TO_ALL_MES = "7"; //群發消息報
    String MESSAGE_FILE_MES = "8"; //文件消息(發送文件)
}

User

package com.hspedu.qqcommon;

import java.io.Serializable;

/**
 * 表示一個用戶信息
 */
public class User implements Serializable {

    private static final long serialVersionUID = 1L;
    private String userId;//用戶Id/用戶名
    private String passwd;//用戶密碼

    public User(String userId, String passwd) {
        this.userId = userId;
        this.passwd = passwd;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
}

qqframe

QQFrame

package com.hspedu.qqframe;

import com.hspedu.qqserver.service.QQServer;

/**
 * 該類創建QQServer ,啟動後臺的服務
 */
public class QQFrame {
    public static void main(String[] args)  {
        new QQServer();
    }
}

qqserver.service

ManageClientThreads

package com.hspedu.qqserver.service;

import java.util.HashMap;
import java.util.Iterator;

/**
* 該類用於管理和客戶端通信的線程
*/
public class ManageClientThreads {
   private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();


   //返回 hm
   public static HashMap<String, ServerConnectClientThread> getHm() {
       return hm;
   }

   //添加線程對象到 hm 集合
   public static void addClientThread(String userId, ServerConnectClientThread serverConnectClientThread) {

       hm.put(userId, serverConnectClientThread);

   }

   //根據userId 返回ServerConnectClientThread線程
   public static ServerConnectClientThread getServerConnectClientThread(String userId) {
       return hm.get(userId);
   }

   //增加一個方法,從集合中,移除某個線程對象
   public static void removeServerConnectClientThread(String userId) {
       hm.remove(userId);
   }

   //這裡編寫方法,可以返回線上用戶列表
   public static String getOnlineUser() {
       //集合遍歷 ,遍歷 hashmap的key
       Iterator<String> iterator = hm.keySet().iterator();
       String onlineUserList = "";
       while (iterator.hasNext()) {
           onlineUserList += iterator.next() + " ";
       }
       return  onlineUserList;
   }
}

QQServer

package com.hspedu.qqserver.service;

import com.hspedu.qqcommon.Message;
import com.hspedu.qqcommon.MessageType;
import com.hspedu.qqcommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;

/**
* 這是伺服器, 在監聽9999,等待客戶端的連接,並保持通信
*/
public class QQServer {

   private ServerSocket ss = null;
   //創建一個集合,存放多個用戶,如果是這些用戶登錄,就認為是合法
   //這裡我們也可以使用 ConcurrentHashMap, 可以處理併發的集合,沒有線程安全
   //HashMap 沒有處理線程安全,因此在多線程情況下是不安全
   //ConcurrentHashMap 處理的線程安全,即線程同步處理, 在多線程情況下是安全
   private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();
   //private static ConcurrentHashMap<String, ArrayList<Message>> offLineDb = new ConcurrentHashMap<>();

   static { //在靜態代碼塊,初始化 validUsers

       validUsers.put("100", new User("100", "123456"));
       validUsers.put("200", new User("200", "123456"));
       validUsers.put("300", new User("300", "123456"));
       validUsers.put("至尊寶", new User("至尊寶", "123456"));
       validUsers.put("紫霞仙子", new User("紫霞仙子", "123456"));
       validUsers.put("菩提老祖", new User("菩提老祖", "123456"));

   }

   //驗證用戶是否有效的方法
   private boolean checkUser(String userId, String passwd) {

       User user = validUsers.get(userId);
       //過關的驗證方式
       if(user == null) {//說明userId沒有存在validUsers 的key中
           return  false;
       }
       if(!user.getPasswd().equals(passwd)) {//userId正確,但是密碼錯誤
           return false;
       }
       return true;
   }

   public QQServer() {
       //註意:埠可以寫在配置文件.
       try {
           System.out.println("服務端在9999埠監聽...");
           //啟動推送新聞的線程
           new Thread(new SendNewsToAllService()).start();
           ss = new ServerSocket(9999);

           while (true) { //當和某個客戶端連接後,會繼續監聽, 因此while
               Socket socket = ss.accept();//如果沒有客戶端連接,就會阻塞在這裡
               //得到socket關聯的對象輸入流
               ObjectInputStream ois =
                       new ObjectInputStream(socket.getInputStream());

               //得到socket關聯的對象輸出流
               ObjectOutputStream oos =
                       new ObjectOutputStream(socket.getOutputStream());
               User u = (User) ois.readObject();//讀取客戶端發送的User對象
               //創建一個Message對象,準備回覆客戶端
               Message message = new Message();
               //驗證用戶 方法
               if (checkUser(u.getUserId(), u.getPasswd())) {//登錄通過
                  message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);
                  //將message對象回覆客戶端
                   oos.writeObject(message);
                   //創建一個線程,和客戶端保持通信, 該線程需要持有socket對象
                   ServerConnectClientThread serverConnectClientThread =
                           new ServerConnectClientThread(socket, u.getUserId());
                   //啟動該線程
                   serverConnectClientThread.start();
                   //把該線程對象,放入到一個集合中,進行管理.
                   ManageClientThreads.addClientThread(u.getUserId(), serverConnectClientThread);

               } else { // 登錄失敗
                   System.out.println("用戶 id=" + u.getUserId() + " pwd=" + u.getPasswd() + " 驗證失敗");
                   message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                   oos.writeObject(message);
                   //關閉socket
                   socket.close();
               }
           }

       } catch (Exception e) {
           e.printStackTrace();
       } finally {

           //如果伺服器退出了while,說明伺服器端不在監聽,因此關閉ServerSocket
           try {
               ss.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
   }
}

SendNewsToAllService

package com.hspedu.qqserver.service;

import com.hspedu.qqcommon.Message;
import com.hspedu.qqcommon.MessageType;
import com.hspedu.utils.Utility;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.*;

/**
* 發送新聞到所用用戶
*/
public class SendNewsToAllService implements Runnable {


   @Override
   public void run() {

       //為了可以推送多次新聞,使用while
       while (true) {
           System.out.println("請輸入伺服器要推送的新聞/消息[輸入exit表示退出推送服務線程]");
           String news = Utility.readString(100);
           if("exit".equals(news)) {
               break;
           }
           //構建一個消息 , 群發消息
           Message message = new Message();
           message.setSender("伺服器");
           message.setMesType(MessageType.MESSAGE_TO_ALL_MES);
           message.setContent(news);
           message.setSendTime(new Date().toString());
           System.out.println("伺服器推送消息給所有人 說: " + news);

           //遍歷當前所有的通信線程,得到socket,併發送message

           HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();

           Iterator<String> iterator = hm.keySet().iterator();
           while (iterator.hasNext()) {
               String onLineUserId = iterator.next().toString();
               try {
                   ObjectOutputStream oos =
                           new ObjectOutputStream(hm.get(onLineUserId).getSocket().getOutputStream());
                   oos.writeObject(message);
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }

   }
}

ServerConnectClientThread

package com.hspedu.qqserver.service;

import com.hspedu.qqcommon.Message;
import com.hspedu.qqcommon.MessageType;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;

/**
* 該類的一個對象和某個客戶端保持通信, 根據用戶發來的數據,進行對應操作
*/
public class ServerConnectClientThread extends Thread {

   private Socket socket;
   private String userId;//連接到服務端的用戶id

   public ServerConnectClientThread(Socket socket, String userId) {
       this.socket = socket;
       this.userId = userId;
   }

   public Socket getSocket() {
       return socket;
   }

   @Override
   public void run() { //這裡線程處於run的狀態,可以發送/接收消息

       while (true) {
           try {
               System.out.println("服務端和客戶端" + userId + " 保持通信,讀取數據...");
               ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
               Message message = (Message) ois.readObject();
               //後面會使用message, 根據message的類型,做相應的業務處理
               if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {//返回線上用戶列表
                   //客戶端要線上用戶列表
                   /*
                   線上用戶列表形式 100  200  紫霞仙子
                    */
                   System.out.println(message.getSender() + " 要線上用戶列表");
                   String onlineUser = ManageClientThreads.getOnlineUser();
                   //返回message
                   //構建一個Message 對象,返回給客戶端
                   Message message2 = new Message();
                   message2.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);
                   message2.setContent(onlineUser);
                   message2.setGetter(message.getSender());
                   //返回給客戶端
                   ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                   oos.writeObject(message2);

               } else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {//普通信息包(用戶之間的文字消息)
                   //根據message獲取getter id, 然後在得到對應先線程
                   ServerConnectClientThread serverConnectClientThread =
                           ManageClientThreads.getServerConnectClientThread(message.getGetter());
                   //得到對應socket的對象輸出流,將message對象轉發給指定的客戶端
                   ObjectOutputStream oos =
                           new ObjectOutputStream(serverConnectClientThread.getSocket().getOutputStream());
                   oos.writeObject(message);//轉發,提示如果客戶不線上,可以保存到資料庫,這樣就可以實現離線留言

               } else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {//群發消息
                   //需要遍歷 管理線程的集合,把所有的線程的socket得到,然後把message進行轉發即可
                   HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();

                   Iterator<String> iterator = hm.keySet().iterator();
                   while (iterator.hasNext()) {

                       //取出線上用戶id
                       String onLineUserId = iterator.next();
                       if (!onLineUserId.equals(message.getSender())) {//排除群發消息的這個用戶

                           //進行轉發message
                           ObjectOutputStream oos =
                                   new ObjectOutputStream(hm.get(onLineUserId).getSocket().getOutputStream());
                           oos.writeObject(message);
                       }

                   }

               } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {//發送文件
                   //根據getter id 獲取到對應的線程,將message對象轉發
                   ObjectOutputStream oos =
                           new ObjectOutputStream(ManageClientThreads.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());
                   //轉發
                   oos.writeObject(message);
               } else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {//客戶端退出

                   System.out.println(message.getSender() + " 退出");
                   //將這個客戶端對應線程,從集合刪除.
                   ManageClientThreads.removeServerConnectClientThread(message.getSender());
                   socket.close();//關閉連接
                   //退出線程
                   break;

               } else {
                   System.out.println("其他類型的message , 暫時不處理");
               }
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
   }
}

utils

Utility//此處為工具類,可直接複製使用,沒必要自己寫

package com.hspedu.utils;


/**
  工具類的作用:
  處理各種情況的用戶輸入,並且能夠按照程式員的需求,得到用戶的控制台輸入。
*/

import java.util.Scanner;

/**

  
*/
public class Utility {
  //靜態屬性。。。
   private static Scanner scanner = new Scanner(System.in);

   
   /**
    * 功能:讀取鍵盤輸入的一個菜單選項,值:1——5的範圍
    * @return 1——5
    */
  public static char readMenuSelection() {
       char c;
       for (; ; ) {
           String str = readKeyBoard(1, false);//包含一個字元的字元串
           c = str.charAt(0);//將字元串轉換成字元char類型
           if (c != '1' && c != '2' && 
               c != '3' && c != '4' && c != '5') {
               System.out.print("選擇錯誤,請重新輸入:");
           } else break;
       }
       return c;
   }

  /**
   * 功能:讀取鍵盤輸入的一個字元
   * @return 一個字元
   */
   public static char readChar() {
       String str = readKeyBoard(1, false);//就是一個字元
       return str.charAt(0);
   }
   /**
    * 功能:讀取鍵盤輸入的一個字元,如果直接按回車,則返回指定的預設值;否則返回輸入的那個字元
    * @param defaultValue 指定的預設值
    * @return 預設值或輸入的字元
    */
   
   public static char readChar(char defaultValue) {
       String str = readKeyBoard(1, true);//要麼是空字元串,要麼是一個字元
       return (str.length() == 0) ? defaultValue : str.charAt(0);
   }
  
   /**
    * 功能:讀取鍵盤輸入的整型,長度小於2位
    * @return 整數
    */
   public static int readInt() {
       int n;
       for (; ; ) {
           String str = readKeyBoard(10, false);//一個整數,長度<=10位
           try {
               n = Integer.parseInt(str);//將字元串轉換成整數
               break;
           } catch (NumberFormatException e) {
               System.out.print("數字輸入錯誤,請重新輸入:");
           }
       }
       return n;
   }
   /**
    * 功能:讀取鍵盤輸入的 整數或預設值,如果直接回車,則返回預設值,否則返回輸入的整數
    * @param defaultValue 指定的預設值
    * @return 整數或預設值
    */
   public static int readInt(int defaultValue) {
       int n;
       for (; ; ) {
           String str = readKeyBoard(10, true);
           if (str.equals("")) {
               return defaultValue;
           }
        
        //異常處理...
           try {
               n = Integer.parseInt(str);
               break;
           } catch (NumberFormatException e) {
               System.out.print("數字輸入錯誤,請重新輸入:");
           }
       }
       return n;
   }

   /**
    * 功能:讀取鍵盤輸入的指定長度的字元串
    * @param limit 限制的長度
    * @return 指定長度的字元串
    */

   public static String readString(int limit) {
       return readKeyBoard(limit, false);
   }

   /**
    * 功能:讀取鍵盤輸入的指定長度的字元串或預設值,如果直接回車,返回預設值,否則返回字元串
    * @param limit 限制的長度
    * @param defaultValue 指定的預設值
    * @return 指定長度的字元串
    */
  
   public static String readString(int limit, String defaultValue) {
       String str = readKeyBoard(limit, true);
       return str.equals("")? defaultValue : str;
   }


  /**
   * 功能:讀取鍵盤輸入的確認選項,Y或N
   * 將小的功能,封裝到一個方法中.
   * @return Y或N
   */
   public static char readConfirmSelection() {
       System.out.println("請輸入你的選擇(Y/N): 請小心選擇");
       char c;
       for (; ; ) {//無限迴圈
          //在這裡,將接受到字元,轉成了大寫字母
          //y => Y n=>N
           String str = readKeyBoard(1, false).toUpperCase();
           c = str.charAt(0);
           if (c == 'Y' || c == 'N') {
               break;
           } else {
               System.out.print("選擇錯誤,請重新輸入:");
           }
       }
       return c;
   }

   /**
    * 功能: 讀取一個字元串
    * @param limit 讀取的長度
    * @param blankReturn 如果為true ,表示 可以讀空字元串。 
    *                   如果為false表示 不能讀空字元串。
    *           
   * 如果輸入為空,或者輸入大於limit的長度,就會提示重新輸入。
    * @return
    */
   private static String readKeyBoard(int limit, boolean blankReturn) {
       
     //定義了字元串
     String line = "";

     //scanner.hasNextLine() 判斷有沒有下一行
       while (scanner.hasNextLine()) {
           line = scanner.nextLine();//讀取這一行
          
        //如果line.length=0, 即用戶沒有輸入任何內容,直接回車
        if (line.length() == 0) {
               if (blankReturn) return line;//如果blankReturn=true,可以返回空串
               else continue; //如果blankReturn=false,不接受空串,必須輸入內容
           }

        //如果用戶輸入的內容大於了 limit,就提示重寫輸入  
        //如果用戶如的內容 >0 <= limit ,我就接受
           if (line.length() < 1 || line.length() > limit) {
               System.out.print("輸入長度(不能大於" + limit + ")錯誤,請重新輸入:");
               continue;
           }
           break;
       }

       return line;
   }
}

客戶端

程式文件結構

image

代碼

com.hspedu

qqclient

service

ClientConnectServerThread

package com.hspedu.qqclient.service;

import com.hspedu.qqcommon.Message;
import com.hspedu.qqcommon.MessageType;

import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;

/**
*該類為登錄後創建的線程,和伺服器保持通訊,根據伺服器傳回的數據進行對應操作
*/
public class ClientConnectServerThread extends Thread {
   //該線程需要持有Socket
   private Socket socket;

   //構造器可以接受一個Socket對象
   public ClientConnectServerThread(Socket socket) {
       this.socket = socket;
   }

   //
   @Override
   public void run() {
       //因為Thread需要在後臺和伺服器通信,因此我們while迴圈
       while (true) {

           try {
               System.out.println("客戶端線程,等待從讀取從伺服器端發送的消息");
               ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
               //如果伺服器沒有發送Message對象,線程會阻塞在這裡
               Message message = (Message) ois.readObject();
               //註意,後面我們需要去使用message
               //判斷這個message類型,然後做相應的業務處理
               //如果是讀取到的是 服務端返回的線上用戶列表
               if (message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {
                   //取出線上列表信息,並顯示
                   //規定
                   String[] onlineUsers = message.getContent().split(" ");
                   System.out.println("\n=======當前線上用戶列表========");
                   for (int i = 0; i < onlineUsers.length; i++) {
                       System.out.println("用戶: " + onlineUsers[i]);
                   }

               } else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {//普通的聊天消息
                   //把從伺服器轉發的消息,顯示到控制台即可
                   System.out.println("\n" + message.getSender()
                           + " 對 " + message.getGetter() + " 說: " + message.getContent());
               } else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {
                   //顯示在客戶端的控制台
                   System.out.println("\n" + message.getSender() + " 對大家說: " + message.getContent());
               } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {//如果是文件消息
                   //讓用戶指定保存路徑。。。
                   System.out.println("\n" + message.getSender() + " 給 " + message.getGetter()
                           + " 發文件: " + message.getSrc() + " 到我的電腦的目錄 " + message.getDest());

                   //取出message的文件位元組數組,通過文件輸出流寫出到磁碟
                   FileOutputStream fileOutputStream = new FileOutputStream(message.getDest(), true);
                   fileOutputStream.write(message.getFileBytes());
                   fileOutputStream.close();
                   System.out.println("\n 保存文件成功~");

               } else {
                   System.out.println("是其他類型的message, 暫時不處理....");
               }

           } catch (Exception e) {
               e.printStackTrace();
           }
       }
   }

   //為了更方便的得到Socket
   public Socket getSocket() {
       return socket;
   }
}

FileClientService

package com.hspedu.qqclient.service;

import com.hspedu.qqcommon.Message;
import com.hspedu.qqcommon.MessageType;

import java.io.*;

/**
* 該類/對象完成 文件傳輸服務
*/
public class FileClientService {
   /**
    *
    * @param src 源文件
    * @param dest 把該文件傳輸到對方的哪個目錄
    * @param senderId 發送用戶id
    * @param getterId 接收用戶id
    */
   public void sendFileToOne(String src, String dest, String senderId, String getterId) {

       //讀取src文件  -->  message
       Message message = new Message();
       message.setMesType(MessageType.MESSAGE_FILE_MES);
       message.setSender(senderId);
       message.setGetter(getterId);
       message.setSrc(src);
       message.setDest(dest);

       //需要將文件讀取
       FileInputStream fileInputStream = null;
       byte[] fileBytes = new byte[(int)new File(src).length()];

       try {
           fileInputStream = new FileInputStream(src);
           fileInputStream.read(fileBytes);//將src文件讀入到程式的位元組數組
           //將文件對應的位元組數組設置message
           message.setFileBytes(fileBytes);
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           //關閉
           if(fileInputStream != null) {
               try {
                   fileInputStream.close();
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
       }
       //提示信息
       System.out.println("\n" + senderId + " 給 " + getterId + " 發送文件: " + src
               + " 到對方的電腦的目錄 " + dest);
       //發送
       try {
           ObjectOutputStream oos =
                   new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
           oos.writeObject(message);
       } catch (IOException e) {
           e.printStackTrace();
       }


   }
}

ManageClientConnectServerThread

package com.hspedu.qqclient.service;

import java.util.HashMap;

/**
* 該類管理客戶端連接到伺服器端的線程的類
*/
public class ManageClientConnectServerThread {
   //我們把多個線程放入一個HashMap集合,key 就是用戶id, value 就是線程
   private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();

   //將某個線程加入到集合
   public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {
       hm.put(userId, clientConnectServerThread);
   }
   //通過userId 可以得到對應線程
   public static ClientConnectServerThread getClientConnectServerThread(String userId) {
       return hm.get(userId);
   }

}

MessageClientService

package com.hspedu.qqclient.service;

import com.hspedu.qqcommon.Message;
import com.hspedu.qqcommon.MessageType;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Date;

/**
* 該類/對象,提供和消息相關的服務方法 發送信息給伺服器
*/
public class MessageClientService {
   /**
    * @param content  內容
    * @param senderId 發送者
    */
   public void sendMessageToAll(String content, String senderId) {
       //構建message
       Message message = new Message();
       message.setMesType(MessageType.MESSAGE_TO_ALL_MES);//群發消息這種類型
       message.setSender(senderId);
       message.setContent(content);
       message.setSendTime(new Date().toString());//發送時間設置到message對象
       System.out.println(senderId + " 對大家說 " + content);
       //發送給服務端

       try {
           ObjectOutputStream oos =
                   new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
           oos.writeObject(message);
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

   /**
    * @param content  內容
    * @param senderId 發送用戶id
    * @param getterId 接收用戶id
    */
   public void sendMessageToOne(String content, String senderId, String getterId) {
       //構建message
       Message message = new Message();
       message.setMesType(MessageType.MESSAGE_COMM_MES);//普通的聊天消息這種類型
       message.setSender(senderId);
       message.setGetter(getterId);
       message.setContent(content);
       message.setSendTime(new Date().toString());//發送時間設置到message對象
       System.out.println(senderId + " 對 " + getterId + " 說 " + content);
       //發送給服務端

       try {
           ObjectOutputStream oos =
                   new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());
           oos.writeObject(message);
       } catch (IOException e) {
           e.printStackTrace();
       }

   }
}

UserClientService

package com.hspedu.qqclient.service;

import com.hspedu.qqcommon.Message;
import com.hspedu.qqcommon.MessageType;
import com.hspedu.qqcommon.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
* 該類向伺服器發送信息 完成用戶登錄驗證、退出登錄和用戶註冊等功能.
*/
public class UserClientService {
   //因為我們可能在其他地方用使用user信息, 因此作出成員屬性
   private User u = new User();
   //因為Socket在其它地方也可能使用,因此作出屬性
   private Socket socket;

   //根據userId 和 pwd 到伺服器驗證該用戶是否合法
   public boolean checkUser(String userId, String pwd) {
       boolean b = false;
       //創建User對象
       u.setUserId(userId);
       u.setPasswd(pwd);

       try {
           //連接到服務端,發送u對象
           socket = new Socket(InetAddress.getByName("127.0.0.1"), 9999);
           //得到ObjectOutputStream對象
           ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
           oos.writeObject(u);//發送User對象

           //讀取從伺服器回覆的Message對象
           ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
           Message ms = (Message) ois.readObject();

           if (ms.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {//登錄OK


               //創建一個和伺服器端保持通信的線程-> 創建一個類 ClientConnectServerThread
               ClientConnectServerThread clientConnectServerThread =
                       new ClientConnectServerThread(socket);
               //啟動客戶端的線程
               clientConnectServerThread.start();
               //這裡為了後面客戶端的擴展,我們將線程放入到集合管理
               ManageClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);
               b = true;
           } else {
               //如果登錄失敗, 我們就不能啟動和伺服器通信的線程, 關閉socket
               socket.close();
           }

       } catch (Exception e) {
           e.printStackTrace();
       }

       return b;

   }

   //向伺服器端請求線上用戶列表
   public void onlineFriendList() {

       //發送一個Message , 類型MESSAGE_GET_ONLINE_FRIEND
       Message message = new Message();
       message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);
       message.setSender(u.getUserId());

       //發送給伺服器

       try {
           //從管理線程的集合中,通過userId, 得到這個線程對象
           ClientConnectServerThread clientConnectServerThread =
                   ManageClientConnectServerThread.getClientConnectServerThread(u.getUserId());
           //通過這個線程得到關聯的socket
           Socket socket = clientConnectServerThread.getSocket();
           //得到當前線程的Socket 對應的 ObjectOutputStream對象
           ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
           oos.writeObject(message); //發送一個Message對象,向服務端要求線上用戶列表
       } catch (IOException e) {
           e.printStackTrace();
       }

   }

   //編寫方法,退出客戶端,並給服務端發送一個退出系統的message對象
   public void logout() {
       Message message = new Message();
       message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
       message.setSender(u.getUserId());//一定要指定我是哪個客戶端id

       //發送message
       try {
           //ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
           ObjectOutputStream oos =
                   new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(u.getUserId()).getSocket().getOutputStream());
           oos.writeObject(message);
           System.out.println(u.getUserId() + " 退出系統 ");
           System.exit(0);//結束進程
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
}

utils

Utility

package com.hspedu.qqclient.utils;


/**
  工具類的作用:
  處理各種情況的用戶輸入,並且能夠按照程式員的需求,得到用戶的控制台輸入。
*/

import java.util.Scanner;

/**

  
*/
public class Utility {
  //靜態屬性。。。
   private static Scanner scanner = new Scanner(System.in);

   
   /**
    * 功能:讀取鍵盤輸入的一個菜單選項,值:1——5的範圍
    * @return 1——5
    */
  public static char readMenuSelection() {
       char c;
       for (; ; ) {
           String str = readKeyBoard(1, false);//包含一個字元的字元串
           c = str.charAt(0);//將字元串轉換成字元char類型
           if (c != '1' && c != '2' && 
               c != '3' && c != '4' && c != '5') {
               System.out.print("選擇錯誤,請重新輸入:");
           } else break;
       }
       return c;
   }

  /**
   * 功能:讀取鍵盤輸入的一個字元
   * @return 一個字元
   */
   public static char readChar() {
       String str = readKeyBoard(1, false);//就是一個字元
       return str.charAt(0);
   }
   /**
    * 功能:讀取鍵盤輸入的一個字元,如果直接按回車,則返回指定的預設值;否則返回輸入的那個字元
    * @param defaultValue 指定的預設值
    * @return 預設值或輸入的字元
    */
   
   public static char readChar(char defaultValue) {
       String str = readKeyBoard(1, true);//要麼是空字元串,要麼是一個字元
       return (str.length() == 0) ? defaultValue : str.charAt(0);
   }
  
   /**
    * 功能:讀取鍵盤輸入的整型,長度小於2位
    * @return 整數
    */
   public static int readInt() {
       int n;
       for (; ; ) {
           String str = readKeyBoard(10, false);//一個整數,長度<=10位
           try {
               n = Integer.parseInt(str);//將字元串轉換成整數
               break;
           } catch (NumberFormatException e) {
               System.out.print("數字輸入錯誤,請重新輸入:");
           }
       }
       return n;
   }
   /**
    * 功能:讀取鍵盤輸入的 整數或預設值,如果直接回車,則返回預設值,否則返回輸入的整數
    * @param defaultValue 指定的預設值
    * @return 整數或預設值
    */
   public static int readInt(int defaultValue) {
       int n;
       for (; ; ) {
           String str = readKeyBoard(10, true);
           if (str.equals("")) {
               return defaultValue;
           }
        
        //異常處理...
           try {
               n = Integer.parseInt(str);
               break;
           } catch (NumberFormatException e) {
               System.out.print("數字輸入錯誤,請重新輸入:");
           }
       }
       return n;
   }

   /**
    * 功能:讀取鍵盤輸入的指定長度的字元串
    * @param limit 限制的長度
    * @return 指定長度的字元串
    */

   public static String readString(int limit) {
       return readKeyBoard(limit, false);
   }

   /**
    * 功能:讀取鍵盤輸入的指定長度的字元串或預設值,如果直接回車,返回預設值,否則返回字元串
    * @param limit 限制的長度
    * @param defaultValue 指定的預設值
    * @return 指定長度的字元串
    */
  
   public static String readString(int limit, String defaultValue) {
       String str = readKeyBoard(limit, true);
       return str.equals("")? defaultValue : str;
   }


  /**
   * 功能:讀取鍵盤輸入的確認選項,Y或N
   * 將小的功能,封裝到一個方法中.
   * @return Y或N
   */
   public static char readConfirmSelection() {
       System.out.println("請輸入你的選擇(Y/N): 請小心選擇");
       char c;
       for (; ; ) {//無限迴圈
          //在這裡,將接受到字元,轉成了大寫字母
          //y => Y n=>N
           String str = readKeyBoard(1, false).toUpperCase();
           c = str.charAt(0);
           if (c == 'Y' || c == 'N') {
               break;
           } else {
               System.out.print("選擇錯誤,請重新輸入:");
           }
       }
       return c;
   }

   /**
    * 功能: 讀取一個字元串
    * @param limit 讀取的長度
    * @param blankReturn 如果為true ,表示 可以讀空字元串。 
    *                   如果為false表示 不能讀空字元串。
    *           
   * 如果輸入為空,或者輸入大於limit的長度,就會提示重新輸入。
    * @return
    */
   private static String readKeyBoard(int limit, boolean blankReturn) {
       
     //定義了字元串
     String line = "";

     //scanner.hasNextLine() 判斷有沒有下一行
       while (scanner.hasNextLine()) {
           line = scanner.nextLine();//讀取這一行
          
        //如果line.length=0, 即用戶沒有輸入任何內容,直接回車
        if (line.length() == 0) {
               if (blankReturn) return line;//如果blankReturn=true,可以返回空串
               else continue; //如果blankReturn=false,不接受空串,必須輸入內容
           }

        //如果用戶輸入的內容大於了 limit,就提示重寫輸入  
        //如果用戶如的內容 >0 <= limit ,我就接受
           if (line.length() < 1 || line.length() > limit) {
               System.out.print("輸入長度(不能大於" + limit + ")錯誤,請重新輸入:");
               continue;
           }
           break;
       }

       return line;
   }
}

view

QQView

package com.hspedu.qqclient.view;

import com.hspedu.qqclient.service.FileClientService;
import com.hspedu.qqclient.service.MessageClientService;
import com.hspedu.qqclient.service.UserClientService;
import com.hspedu.qqclient.utils.Utility;

/**
* 登錄界面和登錄後的界面
*/
public class QQView {

   private boolean loop = true; //控制是否顯示菜單
   private String key = ""; // 接收用戶的鍵盤輸入
   private UserClientService userClientService = new UserClientService();//對象是用於登錄服務/註冊用戶
   private MessageClientService messageClientService = new MessageClientService();//對象用戶私聊/群聊.
   private FileClientService fileClientService = new FileClientService();//該對象用戶傳輸文件

   public static void main(String[] args) {
       new QQView().mainMenu();
       System.out.println("客戶端退出系統.....");
   }

   //顯示主菜單
   private void mainMenu() {

       while (loop) {

           System.out.println("===========歡迎登錄網路通信系統===========");
           System.out.println("\t\t 1 登錄系統");
           System.out.println("\t\t 9 退出系統");
           System.out.print("請輸入你的選擇: ");
           key = Utility.readString(1);

           //根據用戶的輸入,來處理不同的邏輯
           switch (key) {
               case "1":
                   System.out.print("請輸入用戶號: ");
                   String userId = Utility.readString(50);
                   System.out.print("請輸入密  碼: ");
                   String pwd = Utility.readString(50);
                   //這裡就比較麻煩了, 需要到服務端去驗證該用戶是否合法
                   //這裡有很多代碼, 我們這裡編寫一個類 UserClientService[用戶登錄/註冊]
                   if (userClientService.checkUser(userId, pwd)) { //還沒有寫完, 先把整個邏輯打通....
                       System.out.println("===========歡迎 (用戶 " + userId + " 登錄成功) ===========");
                       //進入到二級菜單
                       while (loop) {
                           System.out.println("\n=========網路通信系統二級菜單(用戶 " + userId + " )=======");
                           System.out.println("\t\t 1 顯示線上用戶列表");
                           System.out.println("\t\t 2 群發消息");
                           System.out.println("\t\t 3 私聊消息");
                           System.out.println("\t\t 4 發送文件");
                           System.out.println("\t\t 9 退出系統");
                           System.out.print("請輸入你的選擇: ");
                           key = Utility.readString(1);
                           switch (key) {
                               case "1":
                                   //這裡老師準備寫一個方法,來獲取線上用戶列表
                                   userClientService.onlineFriendList();
                                   break;
                               case "2"://群發消息
                                   System.out.println("請輸入想對大家說的話: ");
                                   String s = Utility.readString(100);
                                   messageClientService.sendMessageToAll(s, userId);
                                   break;
                               case "3"://私發消息
                                   System.out.print("請輸入想聊天的用戶號(線上): ");
                                   String getterId = Utility.readString(50);
                                   System.out.print("請輸入想說的話: ");
                                   String content = Utility.readString(100);
                                   //編寫一個方法,將消息發送給伺服器端
                                   messageClientService.sendMessageToOne(content, userId, getterId);
                                   break;
                               case "4"://發文件
                                   System.out.print("請輸入你想把文件發送給的用戶(線上用戶): ");
                                   getterId = Utility.readString(50);
                                   System.out.print("請輸入發送文件的路徑(形式 d:\\xx.jpg)");
                                   String src = Utility.readString(100);
                                   System.out.print("請輸入把文件發送到對應的路徑(形式 d:\\yy.jpg)");
                                   String dest = Utility.readString(100);
                                   fileClientService.sendFileToOne(src, dest, userId, getterId);
                                   break;
                               case "9":
                                   //調用方法,給伺服器發送一個退出系統的message
                                   userClientService.logout();
                                   loop = false;
                                   break;
                           }

                       }
                   } else { //登錄伺服器失敗
                       System.out.println("=========登錄失敗=========");
                   }
                   break;
               case "9":
                   loop = false;
                   break;
           }

       }

   }
}

qqcommon

Message

package com.hspedu.qqcommon;

import java.io.Serializable;

/**
* 表示客戶端和服務端通信時的消息對象
*/
public class Message implements Serializable {
   private static final long serialVersionUID = 1L;
   private String sender;//發送者
   private String getter;//接收者
   private String content;//消息內容
   private String sendTime;//發送時間
   private String mesType;//消息類型[可以在介面定義消息類型]

   //進行擴展 和文件相關的成員
   private byte[] fileBytes;
   private int fileLen = 0;
   private String dest; //將文件傳輸到哪裡
   private String src; //源文件路徑

   public byte[] getFileBytes() {
       return fileBytes;
   }

   public void setFileBytes(byte[] fileBytes) {
       this.fileBytes = fileBytes;
   }

   public int getFileLen() {
       return fileLen;
   }

   public void setFileLen(int fileLen) {
       this.fileLen = fileLen;
   }

   public String getDest() {
       return dest;
   }

   public void setDest(String dest) {
       this.dest = dest;
   }

   public String getSrc() {
       return src;
   }

   public void setSrc(String src) {
       this.src = src;
   }

   public String getMesType() {
       return mesType;
   }

   public void setMesType(String mesType) {
       this.mesType = mesType;
   }

   public String getSender() {
       return sender;
   }

   public void setSender(String sender) {
       this.sender = sender;
   }

   public String getGetter() {
       return getter;
   }

   public void setGetter(String getter) {
       this.getter = getter;
   }

   public String getContent() {
       return content;
   }

   public void setContent(String content) {
       this.content = content;
   }

   public String getSendTime() {
       return sendTime;
   }

   public void setSendTime(String sendTime) {
       this.sendTime = sendTime;
   }
}

MessageType

package com.hspedu.qqcommon;

public interface MessageType {
   //解讀
   //1. 在介面中定義了一些常量
   //2. 不同的常量的值,表示不同的消息類型.
   String MESSAGE_LOGIN_SUCCEED = "1"; //表示登錄成功
   String MESSAGE_LOGIN_FAIL = "2"; // 表示登錄失敗
   String MESSAGE_COMM_MES = "3"; //普通信息包
   String MESSAGE_GET_ONLINE_FRIEND = "4"; //要求返回線上用戶列表
   String MESSAGE_RET_ONLINE_FRIEND = "5"; //返回線上用戶列表
   String MESSAGE_CLIENT_EXIT = "6"; //客戶端請求退出
   String MESSAGE_TO_ALL_MES = "7"; //群發消息報
   String MESSAGE_FILE_MES = "8"; //文件消息(發送文件)
}

User

package com.hspedu.qqcommon;

import java.io.Serializable;

/**
* 表示一個用戶信息
*/
public class User implements Serializable {

   private static final long serialVersionUID = 1L;
   private String userId;//用戶Id/用戶名
   private String passwd;//用戶密碼

   public User() {
   }

   public User(String userId, String passwd) {
       this.userId = userId;
       this.passwd = passwd;
   }

   public String getUserId() {
       return userId;
   }

   public void setUserId(String userId) {
       this.userId = userId;
   }

   public String getPasswd() {
       return passwd;
   }

   public void setPasswd(String passwd) {
       this.passwd = passwd;
   }
}
```1.

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.前言 往往一些剛接觸C#編程的初學者,對於泛型的認識就是直接跳到對泛型集合的使用上,雖然微軟為我們提供了很多內置的泛型類型,但是如果我們只是片面的瞭解調用方式,這會導致我們對泛型盲目的使用。至於為什麼要使用泛型,什麼情況下定義屬於自己的泛型,定義泛型又能為程式帶來哪些好處。要理清這些問題,我們就 ...
  • 使用asp.net core 開發應用系統過程中,基本上都會涉及到用戶身份的認證,及授權訪問控制,因此瞭解認證和授權流程也相當重要,下麵通過分析asp.net core 框架中的認證和授權的源碼來分析認證、授權的原理及認證和授權的關係。 認證是什麼? 認證是應用系統識別當前訪問者的身份的一個過程,當 ...
  • 題目 中文 實現一個以 T 作為泛型參數的 IsNever類型. 如果 T 是never, 返回 true, 否則返回 false. 示例: type A = IsNever<never>; // expected to be true type B = IsNever<undefined>; // ...
  • 拉取一個centos鏡像 docker pull centos:centos7 運行一個容器 docker run -i -t -d --restart=always --name baota -p 1870:8888 -p 1871:3306 -p 1872:22 -p 1873:443 -p 1 ...
  • 最近工作需要,研究了一些Liunx性能工具,發現sar這個工具非常優秀。sar是一個非常全面的一個分析工具,對文件的讀寫,系統調用的使用情況,磁碟IO,CPU相關使用情況,記憶體使用情況,進程活動等都可以進行有效的分析。sar工具將對系統當前的狀態進行取樣,然後通過計算數據和比例來表達系統的當前運行狀 ...
  • 2022-09-10 MySQL中的自連接 何謂自連接? 自連接,即為自己查自己,本表查詢本表。 自連接一般使用於何種地方? 例如:如果在設計一張表中,表中的欄位名包含id(省份/市的郵政編碼),title(省份名/市級名),cid(如果前面title是省份名,那麼此處為空;如果前面title是市級 ...
  • 《Redis 7.x 入門和開發實戰》技術專欄通過基礎知識介紹入門-環境搭建-項目開發實踐,讓初學者快速掌握Redis。內部包括分散式緩存組件Redis 7.x的安裝配置部署、基本數據類型、常用命令、操作實踐、HyperLoglog數據結構、事務、慢日誌分析、Redis 集成 Spring Boot... ...
  • 1.視圖:view 視圖就是一張虛擬的表。表是真正存數據的,視圖只是顯示查詢結果。 視圖的作用:隱藏表的結構、簡化sql嵌套查詢操作 註意:視圖就是你要查詢數據的一個中間結果集,我們一般只用來做數據查詢的 創建視圖:create view view_name as 查詢語句 例如: mysql> c ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...