记录一下使用JavaFX + Netty开发仿微信聊天程序 --- 米虫IM。

功能需求

米虫IM已经完成的功能有:

  • 用户注册功能
  • 用户登录功能
  • 搜索好友功能
  • 添加好友功能
  • 文本聊天功能
  • 离线消息功能

架构设计

米虫IM,主要划分为界面层,通讯层和基础层,界面采用JavaFX开发,可独立运行调试,通讯层采用Netty开发,采用C/S架构,分为服务端和客户端。

单机演示

单机版不需要运行服务端,单纯为JavaFX程序,可以独立运行,调试:

单机调试只需要实现通讯接口即可进行测试、调试:

public interface Caller {

    void register(String nickname, String username, String password);

    void login(String username, String password);

    void fetchChatList();

    void fetchFriendList();

    void readMessage(long friendId);

    void chat(long friendId, String message);

    void queryFriend(String nickname);

    void addFriend(long friendId);

    void applyAddFriend(long friendId);
}

一个实现案例:

public class DebugCaller implements Caller {
    @Override
    public void register(String nickname, String username, String password) {
        UserRet ret = new UserRet();
        ret.setId(1);
        ret.setNickname(nickname);
        ret.setUsername(username);
        FXListenerContext.onRegister(0, ret, null);
    }

    @Override
    public void login(String username, String password) {
        UserRet ret = new UserRet();
        ret.setId(1);
        ret.setNickname("米虫2022");
        ret.setUsername(username);
        FXListenerContext.onLogin(0, ret, null);
    }

    @Override
    public void fetchChatList() {
        String[] users = new String[]{"阿德莱德", "东南墨尔本凤凰", "独行侠", "南安普顿", "步行者"/*, "JavaScript"*/};
        String[] messages = new String[]{"F1意大利大奖赛第二次练习赛", "全运会-羽毛球男团决赛", "2021ECEA东亚电竞锦标赛英雄联盟G3",
                "英超第4轮 曼联", "世界职业拳王争霸赛"/*, "全运会-跳水男子三米板决赛 全运会"*/};
        List<ChatRet> chatRetList = new ArrayList<>();
        for (int i = 0; i < users.length; i++) {
            ChatRet chatRet = new ChatRet();
            chatRet.setType(ChatRet.TEXT);
            chatRet.setFriendId(i);
            chatRet.setNickname(users[i]);
            chatRet.setMessage(messages[i]);
            chatRet.setTimestamp(System.currentTimeMillis() - new Random().nextInt(10) * 24 * 60 * 60 * 1000);
            chatRet.setMessageCount(new Random().nextInt(150));
            if (i == users.length - 1) {
                chatRet.setMessageCount(0);
            }
            chatRetList.add(chatRet);
        }
        ChatRet ret = new ChatRet();
        ret.setFriendId(1001);
        ret.setTimestamp(System.currentTimeMillis());
        ret.setType(ChatRet.ADD_FRIEND);
        ret.setNickname("米虫2022");
        ret.setMessageCount(1);
        ret.setMessage("1001");
        chatRetList.add(ret);
        FXListenerContext.onFetchChatList(0, chatRetList, null);
    }

    @Override
    public void fetchFriendList() {
        String[] users = new String[]{"阿德莱德", "东南墨尔本凤凰", "独行侠", "南安普顿", "步行者", "珀斯野猫"};
        List<UserRet> userRetList = new ArrayList<>();
        for (int i = 0; i < users.length; i++) {
            UserRet userRet = new UserRet();
            userRet.setId(i);
            userRet.setNickname(users[i]);
            userRetList.add(userRet);
        }
        FXListenerContext.onFetchFriendList(0, userRetList, null);
    }

    @Override
    public void readMessage(long friendId) {
    }

    @Override
    public void chat(long friendId, String message) {
        ChatRet chatRet = new ChatRet();
        chatRet.setFriendId(friendId);
        chatRet.setType(ChatRet.TEXT);
        chatRet.setNickname("机器人自动回复");
        chatRet.setMessage("我是复读机:" + message);
        chatRet.setTimestamp(System.currentTimeMillis());
        FXListenerContext.onChat(chatRet);
    }

    @Override
    public void queryFriend(String nickname) {
        String[] users = new String[]{"Java", "C", "C++", "Python", "Go", "JavaScript"};
        List<UserRet> userRetList = new ArrayList<>();
        for (int i = 0; i < users.length; i++) {
            UserRet userRet = new UserRet();
            userRet.setId(i);
            userRet.setNickname(users[i]);
            userRetList.add(userRet);
        }
        FXListenerContext.onQueryFriend(0, userRetList, null);
    }

    @Override
    public void addFriend(long friendId) {
    }

    @Override
    public void applyAddFriend(long friendId) {
        new Thread(() -> {
            try {
                TimeUnit.MICROSECONDS.sleep(30);
            } catch (InterruptedException e) {
            }
            ChatRet ret = new ChatRet();
            ret.setFriendId(1001);
            ret.setTimestamp(System.currentTimeMillis());
            ret.setType(ChatRet.ADD_FRIEND_RET);
            ret.setNickname("米虫2022");
            ret.setMessageCount(1);
            ret.setMessage("我们已经是好友了,开始聊天吧!");
            FXListenerContext.onChat(ret);
        }).start();
    }
}

通讯演示

通讯版需要先运行Netty开发的服务端,然后运行多个客户端进行通讯:

Netty通讯版只要引入JavaFX包实现上面的Caller即可。

public class ImCaller implements Caller {

    @Override
    public void register(String nickname, String username, String password) {
        RegisterBO bo = new RegisterBO();
        bo.setName(nickname);
        bo.setUsername(username);
        bo.setPassword(password);
        ImContext.sendBO(Type.register, bo);
    }

    @Override
    public void login(String username, String password) {
        LoginBO bo = new LoginBO();
        bo.setUsername(username);
        bo.setPassword(password);
        ImContext.sendBO(Type.login, bo);
    }

    @Override
    public void fetchChatList() {
        ImContext.sendBO(Type.fetch_chat, new EmptyBO());
    }

    @Override
    public void fetchFriendList() {
        ImContext.sendBO(Type.fetch_friend, new EmptyBO());
    }

    @Override
    public void readMessage(long friendId) {
        ImContext.sendBO(Type.rend_message, new StringBO(String.valueOf(friendId)));
    }

    @Override
    public void chat(long friendId, String message) {
        ChatBO bo = new ChatBO();
        bo.setContent(message);
        bo.setToUserId(friendId);
        bo.setType(ChatBO.Type.text);
        ImContext.sendBO(Type.chat, bo);
    }
    @Override
    public void queryFriend(String nickname) {
        ImContext.sendBO(Type.query_friend, new StringBO(nickname));
    }

    @Override
    public void addFriend(long friendId) {
        StringBO bo = new StringBO();
        bo.setContent(String.valueOf(friendId));
        ImContext.sendBO(Type.add_friend, bo);
    }

    @Override
    public void applyAddFriend(long friendId) {
        ImContext.sendBO(Type.apply_add_friend, new StringBO(String.valueOf(friendId)));
    }
}

关于源码

需要源码的扫描左侧头像微信小程序或私信联系,单机版源码结构如下:

.
├── App.java
├── AppStarter.java
├── caller
│   ├── Caller.java
│   └── DebugCaller.java
├── component
│   ├── FX.java
│   ├── FXContext.java
│   ├── FXImage.java
│   └── FXUI.java
├── javascript
│   ├── HTMLBuilder.java
│   └── JSMember.java
├── listener
│   ├── ChatListener.java
│   ├── ErrorListener.java
│   ├── FXListenerContext.java
│   ├── FetchChatListListener.java
│   ├── FetchFriendListListener.java
│   ├── LoginListener.java
│   ├── QueryFriendListener.java
│   ├── RegisterListener.java
│   └── ret
│       ├── ChatRet.java
│       └── UserRet.java
├── model
│   ├── AppMO.java
│   ├── ChatMO.java
│   ├── FriendMO.java
│   ├── LoginMO.java
│   └── RegisterMO.java
└── ui
    ├── UserDataController.java
    ├── chat
    │   ├── ChatAddFriendDialog.java
    │   ├── ChatItemController.css
    │   ├── ChatItemController.fxml
    │   ├── ChatItemController.java
    │   ├── ChatListController.css
    │   ├── ChatListController.fxml
    │   ├── ChatListController.java
    │   ├── ChatMainController.css
    │   ├── ChatMainController.fxml
    │   └── ChatMainController.java
    ├── friend
    │   ├── FriendAddController.fxml
    │   ├── FriendAddController.java
    │   ├── FriendAddItemController.fxml
    │   ├── FriendAddItemController.java
    │   ├── FriendAddStage.java
    │   ├── FriendItemController.css
    │   ├── FriendItemController.fxml
    │   ├── FriendItemController.java
    │   ├── FriendListController.css
    │   ├── FriendListController.fxml
    │   ├── FriendListController.java
    │   ├── FriendProfileController.css
    │   ├── FriendProfileController.fxml
    │   └── FriendProfileController.java
    ├── login
    │   ├── LoginController.fxml
    │   ├── LoginController.java
    │   ├── LoginForm.java
    │   └── LoginStage.java
    ├── main
    │   ├── MainController.css
    │   ├── MainController.fxml
    │   └── MainController.java
    └── register
        ├── RegisterController.fxml
        ├── RegisterController.java
        └── RegisterForm.java

12 directories, 60 files
posted on 2022-12-24 16:22  $$X$$  阅读(969)  评论(0)    收藏  举报