日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > vue >内容正文

vue

基于Vue+Java实现的在线聊天APP系统设计与实现

發(fā)布時(shí)間:2023/12/8 vue 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于Vue+Java实现的在线聊天APP系统设计与实现 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

全套資料下載地址:https://download.csdn.net/download/sheziqiong/85595798

一、需求分析

1.核心用戶分析

在線聊天系統(tǒng)主要針對(duì)一些年輕用戶群體以及因?yàn)楣ぷ餍枨蠖鴮?duì)于實(shí)時(shí)交流以及非實(shí)時(shí)交流有較大需求的群里。就青年群體而言,這一用戶群體特征比較鮮明,其主要需求為基礎(chǔ)聊天需求以及一些能夠凸顯個(gè)性的功能需求。在線聊天對(duì)于青年人來(lái)說也逐漸成為一種主流的設(shè)計(jì)方式。年輕人們通過在線交流和好友印象的可以了解到對(duì)方的性格,而且可以通過相互添加好友保持關(guān)系。而對(duì)于有工作需求的人來(lái)說能夠?qū)崟r(shí)交流以及處理未讀消息就顯得十分重要。

2. 系統(tǒng)的主要功能的概述

首先未注冊(cè)的用戶可以注冊(cè)賬號(hào),已經(jīng)注冊(cè)的用戶可以使用賬號(hào)密碼進(jìn)行登錄。

用戶可以搜索好友,搜索之后可以進(jìn)行添加好友

主界面分為兩個(gè)部分,一個(gè)部分為消息盒子,一個(gè)部分為好友盒子

消息盒子主要存放未讀消息,如果有一個(gè)好友向你發(fā)送消息你沒有點(diǎn)到聊天框里查看的話就會(huì)在消息盒子界面顯示

好友盒子顯示如下幾個(gè)部分,好友列表,添加好友的入口,個(gè)人信息的入口,朋友驗(yàn)證的入口

所有的好友會(huì)在好友列表中展示,一開始所有的好友的在默認(rèn)分組。點(diǎn)擊好友之后可以進(jìn)入好友的資料卡頁(yè)面

可以在好友資料卡中可以查看好友的基本消息,以及會(huì)顯示好友的印象,當(dāng)點(diǎn)擊某個(gè)印象標(biāo)簽的時(shí)候會(huì)提示你可以進(jìn)行刪除。還可以在好友資料卡頁(yè)面點(diǎn)擊發(fā)送消息進(jìn)入聊天窗口。除此之外右上角點(diǎn)擊之后可以有刪除好友,移動(dòng)好友,添加標(biāo)簽的選項(xiàng)

刪除好友:點(diǎn)擊之后好友將被刪除,你可以通過再次發(fā)送驗(yàn)證消息進(jìn)行添加

移動(dòng)好友:可以將好友移動(dòng)到指定的分組,如果分組不存在則創(chuàng)建分組,若移動(dòng)后分組內(nèi)沒有成員則刪除分組。

添加標(biāo)簽:可以為好友添加一個(gè)標(biāo)簽。

當(dāng)進(jìn)入聊天框之后發(fā)送消息對(duì)方就可以發(fā)收到,點(diǎn)擊下載聊天記錄的按鈕就可以下載所有的聊天記錄,點(diǎn)擊刪除聊天記錄可以刪除和當(dāng)前用戶所有的云端記錄。

個(gè)人信息,在這里可以修改個(gè)人信息包括修改頭像,以及刪除別人給自己的標(biāo)簽,并且可以在此處退出登錄

3. 項(xiàng)目操作流程圖

4. 功能詳解

  • 登錄
  • 使用賬號(hào)密碼進(jìn)行登錄,登錄成功之后跳轉(zhuǎn)到主頁(yè)面中的消息盒子的頁(yè)面

  • 注冊(cè)
  • 賬號(hào)采用郵箱格式,密碼要求大于八位

  • 消息盒子
  • 消息盒子顯示你的所有的未讀消息,一旦消息已讀就會(huì)從消息盒子中去除

  • 好友盒子
  • 好友盒子有如下這些部分組成:新的朋友,我的賬號(hào),朋友驗(yàn)證,好友列表

  • 好友列表
  • 按照分組展示所有的好友,點(diǎn)擊好友可以進(jìn)入好友資料卡頁(yè)面

  • 朋友驗(yàn)證
  • 當(dāng)你發(fā)送的請(qǐng)求別人已經(jīng)處理完了或者別人向你發(fā)送了請(qǐng)求的話此處會(huì)有一個(gè)紅點(diǎn)表示消息數(shù)量。點(diǎn)擊進(jìn)入之后進(jìn)入驗(yàn)證消息模塊

  • 我的賬號(hào)
  • 點(diǎn)擊之后進(jìn)入個(gè)人資料卡,在這里可以修改姓名,頭像,性別,頭像要求小于 30kb,年齡要求不能為負(fù)數(shù),性別要求只能是男或者女,還可以在此處刪除自己的標(biāo)簽,也可以退出登錄。

  • 新的朋友
  • 可以進(jìn)行全局搜索,即不進(jìn)行任何輸入直接回車可以顯示所有的好友,并且可以進(jìn)行模糊搜索,只輸入名字的部分也可搜索到。并且可以添加年齡和性別的限制條件。點(diǎn)擊搜索結(jié)果可以進(jìn)入好友資料卡。在這里可以填寫驗(yàn)證消息,并且發(fā)送好友驗(yàn)證,自己不能添加自己,不能添加以及添加的好友,如果已經(jīng)發(fā)送過依次請(qǐng)求對(duì)方為響應(yīng)也不能發(fā)送。當(dāng)這里發(fā)送之后對(duì)方的朋友驗(yàn)證會(huì)出現(xiàn)紅點(diǎn)。

  • 驗(yàn)證消息
  • 當(dāng)我們點(diǎn)擊朋友驗(yàn)證之后,進(jìn)入驗(yàn)證消息頁(yè)面,如果我們發(fā)送的消息被處理了,則會(huì)有一個(gè)紅點(diǎn)標(biāo)記,別人發(fā)送的請(qǐng)求我們可以選擇拒絕和接受。如果我們進(jìn)入了此頁(yè)面的話,如果存在我們發(fā)送的消息被處理了且我們自己之前未讀的,則會(huì)被設(shè)置為已讀。對(duì)于別人發(fā)給自己的請(qǐng)求,則必須在處理完之后才會(huì)被設(shè)置為已讀。

  • 好友資料卡
  • 顯示好友的基本信息,好友的標(biāo)簽,點(diǎn)擊標(biāo)簽可以進(jìn)行刪除,并且可以在此頁(yè)面點(diǎn)擊發(fā)送消息進(jìn)入聊天框進(jìn)行聊天,此頁(yè)面中點(diǎn)擊右上角還可以進(jìn)行刪除好友,移動(dòng)好友,添加標(biāo)簽。

  • 刪除好友:將好友從列表中刪除,刪除后可以再次發(fā)送驗(yàn)證消息
  • 移動(dòng)好友
  • 輸入要移動(dòng)的分組如果不存在則創(chuàng)建分組,若某個(gè)分組內(nèi)沒有了用戶則刪除分組,所有用戶默認(rèn)在默認(rèn)分組中

  • 添加標(biāo)簽
  • 可以對(duì)一個(gè)用戶添加一個(gè)標(biāo)簽,添加重復(fù)標(biāo)簽沒有用

  • 聊天界面
  • 聊天界面可以雙方可以實(shí)時(shí)發(fā)送消息,顯示的時(shí)候自己的消息在右側(cè),對(duì)方的消息在左側(cè),且按時(shí)間排序,點(diǎn)擊下載按鈕可以進(jìn)行聊天記錄下載,點(diǎn)擊刪除按鈕可以刪除云端數(shù)據(jù)

    5. 系統(tǒng)的頂級(jí)用例圖

    6. 系統(tǒng)的原型圖設(shè)計(jì)

    原型圖主要是用圖片的形式站輸出之前的功能模塊,并且也是后面前端 UI 的主要依據(jù)

    登陸界面,注冊(cè)界面類似消息盒子界面

    聊天界面 好友列表界面

    搜索界面 好友申請(qǐng)界面

    個(gè)人資料頁(yè)面 驗(yàn)證消息界面

    項(xiàng)目的頁(yè)面和原型圖過多這里就不一一展示,詳情可見壓縮文件

    二、數(shù)據(jù)庫(kù)設(shè)計(jì)

    因?yàn)榱奶煜到y(tǒng)的所有功能基本上都是圍繞著用戶進(jìn)行的。聊天是用戶和用戶聊天,添加好友也是用戶添加用戶,印象管理也是用戶給用戶添加印象。所以主要的聯(lián)表操作都和 user 表有關(guān)。這里就先給出整個(gè)項(xiàng)目的 ER 圖

    根據(jù) ER 圖可以構(gòu)建數(shù)據(jù)庫(kù)的物理設(shè)計(jì)如下

  • 好友關(guān)系表 friendship
  • 好友印象表 impression
  • 聊天記錄表 record
  • 好友驗(yàn)證表 validation
  • 用戶表 user
  • Redis 中的存儲(chǔ)結(jié)構(gòu)的說明,因?yàn)?Redis 的 Nosql 的性質(zhì)很適合用于存儲(chǔ)未讀的聊天記錄,我是用 Redis 中的哈希結(jié)構(gòu)進(jìn)行存儲(chǔ)未讀消息。每一條記錄規(guī)則如下鍵為 unread+userId,值為一個(gè) Java 中的 Map<String, Map<String, String>> 的類型。Map<String, Map<String, String>> 的類型數(shù)據(jù)格式如下:

    senderId: {nickname:xxx,content:xxx,pic:xxx }

    除此之外在 Redis 中單獨(dú)存儲(chǔ)一個(gè)哈希結(jié)構(gòu),鍵為 unreadNum+userId,值為未讀消息數(shù)量。

    三、架構(gòu)設(shè)計(jì)

    1.技術(shù)棧

    (1)前端

    ①Vue 作為前端框架

    ②vue-router 進(jìn)行前端路由管理

    ③webpack 開發(fā) SPA(單頁(yè)面應(yīng)用)

    ④mint-UI 作為 UI 框架

    ⑤STOMP 實(shí)現(xiàn) Socket 通信的框架

    ⑥axios 發(fā)送請(qǐng)求

    ⑦sass(css 預(yù)處理器,進(jìn)行 CSS 代碼的編寫)

    前端架構(gòu)說明:

    Webpack 搭建項(xiàng)目前端框架,打包生成 SPA(單頁(yè)面)的移動(dòng)端應(yīng)用。

    1、 Webpack 配置文件

    webpack.base.conf.js 為基礎(chǔ)配置 一些最基本的 loader 與 plugin 都在這里面 webpack.dev.conf.js 為開發(fā)環(huán)境配置,配置了適合開發(fā)環(huán)境的 sourceMap,能快速的定位開發(fā)環(huán)境代碼報(bào)錯(cuò)位置 webpack.prod.conf.js 為生產(chǎn)環(huán)境下配置,關(guān)閉了 sourceMap,極大的減小了線上環(huán)境代碼包的大小,啟用了 UglifyJsPlugin 進(jìn)行代碼壓縮,使首屏加載速度低于 500ms。

    2、 Src 目錄下為主要代碼, assets 文件夾下存儲(chǔ)著圖片,iconfont 等靜態(tài)資源。Router 文件夾下為 vue 的路由,控制著頁(yè)面的跳轉(zhuǎn)。 Views 文件夾下為視圖組件,我們開發(fā)的代碼主要在這里。每個(gè)人負(fù)責(zé)不同的模塊,采用 gitflow 工作流,幾乎沒有產(chǎn)生沖突。

    (2)后端

    ①Spring Boot 作為后端框架

    ②Shiro 作為安全驗(yàn)證框架

    ③STOMP 協(xié)議作為通信協(xié)議

    ④Redis 存儲(chǔ)未讀消息

    ⑤MySQL 作為用戶信息等的存儲(chǔ)

    (3)部署

    ①Docker 部署

    (4)測(cè)試

    ①Selemiun

    ②textNG

    (5)項(xiàng)目管理平臺(tái)

    ①github+git

    后端架構(gòu)說明:

    (1) 后端 package 說明如下:

    ① config 表示配置文件,里面存放諸如 Shiro,STOMP 的配置

    ② controller 放置 API 接口

    ③ dao 放置數(shù)據(jù)庫(kù)操作類和接口,其下的 impl 這個(gè) package 表示 JAP 擴(kuò)展實(shí)現(xiàn)類

    ④ dto 存放數(shù)據(jù)庫(kù)表的交互數(shù)據(jù)結(jié)構(gòu)

    ⑤ entity 存放數(shù)據(jù)庫(kù)表中的映射

    ⑥ pojo:存放普通 Java 類,一般是存放工具類

    ⑦ processor:存放過濾器,攔截器和監(jiān)聽器

    ⑧ service:供其他模塊調(diào)用的服務(wù)類,采用接口和實(shí)現(xiàn)的方式。impl 為其實(shí)現(xiàn)類

    ⑨ vo 和前端交互的數(shù)據(jù)類(主要用于將數(shù)據(jù)格式轉(zhuǎn)化為和前端約定的格式)

    (2) 使用如此結(jié)構(gòu)的原因

    ① 這些 package 中需要實(shí)現(xiàn) config 中的文件和 entity 中的文件,只要這兩個(gè) package 的文件配置了,整個(gè)項(xiàng)目就可以運(yùn)行。之后的編寫只需要在其他包中添加自己的代碼即可

    ② 在完成了 config 和 entity 的文件之后,其他功能的編寫只需要注重代碼邏輯的實(shí)現(xiàn)即可

    ③ 當(dāng)多個(gè)人同時(shí)寫后端的時(shí)候,在寫自己的功能的時(shí)候只需要在對(duì)應(yīng)的 package 中編寫對(duì)應(yīng)的代碼即可互不干擾。

    四、功能實(shí)現(xiàn)

    后端實(shí)現(xiàn):

    1) 基礎(chǔ)配置:

    1. RedisConfig

    主要是注入 RedisTemplate<Object, Object> 這個(gè) bean

    2. Shiro

    首先建立自己的匹配器 TokenCredentialsMatcher 用于驗(yàn)證比較,此比較器實(shí)現(xiàn)了 CredentialsMatcher 接口,實(shí)現(xiàn)類 doCredentialsMatch 方法。我們通過 token 比較進(jìn)行驗(yàn)證用戶登錄狀態(tài)是否合法。

    String token = (String) authenticationToken.getCredentials(); return TokenManager.verify(token); ```http://www.biyezuopin.vip然后再建立自己的 Reanlm:TokenRealm,自己配置的 TokenRealm 繼承自 AuthorizingRealm,并且再構(gòu)造函數(shù)的時(shí)候就將我們自己的 TokenCredentialsMatcher 作為此類的匹配器加入。這樣的話進(jìn)行比較的時(shí)候就不會(huì)調(diào)用默認(rèn)的比較匹配器而是調(diào)用我們自定義的比較器。我們重寫 supports 方法,只有當(dāng) support 方法返回 true 的時(shí)候才會(huì)之后的操作。

    return token instanceof MyToken

    然后再重寫 doGetAuthenticationInfo 方法和 doGetAuthorizationInfo 方法,但是 doGetAuthorizationInfo 為授權(quán)方法,在我們本系統(tǒng)中并沒有使用,所以只給出前者核心代碼

    String token = (String)authenticationToken.getCredentials();
    Integer userId = TokenManager.getId(token);
    if(userId==null)
    userId=0;
    return new SimpleAuthenticationInfo(userId, token, getName());

    MyToken 是我自己建立的 Token 最主要的方式是重寫 getPrincipal 和 getCredentials,前者表示標(biāo)識(shí),后者表示驗(yàn)證的憑證

    @Override
    public Object getPrincipal() {
    Integer userId = -1;
    if(TokenManager.verify(this.token)){
    userId = TokenManager.getId(token);
    }
    return userId;
    }

    @Override
    public Object getCredentials() {
    return token;
    }

    做好基礎(chǔ)文件之后開始編寫 ShiroConfig,主要完成兩個(gè)函數(shù)一個(gè)是注冊(cè) ShiroFilterFactoryBean 的函數(shù),另一個(gè)是注冊(cè) SessionsSecurityManager 的函數(shù),在前中我們配置我們的過濾器,以及過濾器攔截的 url,在后這種我們進(jìn)行的工作主要是關(guān)閉 Shiro 自帶的 session 功能。

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

    shiroFilterFactoryBean.setSecurityManager(securityManager);

    Map<String, Filter> filterMap = new HashMap<>();
    filterMap.put(“verification”, new AuthFilter());
    shiroFilterFactoryBean.setFilters(filterMap);
    Map<String, String> filterRuleMap = new LinkedHashMap<>();
    filterRuleMap.put(“/verification/”, “verification”);
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
    return shiroFilterFactoryBean;
    }

    @Bean
    public SessionsSecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

    securityManager.setRealm(tokenRealm);
    //關(guān)閉Shiro自帶的Session。
    DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
    DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
    defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
    subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
    securityManager.setSubjectDAO(subjectDAO);
    return securityManager;
    }

    #### 3. 然后再來(lái)看過濾器的編寫,這里主要是做登錄驗(yàn)證的其主要思路如下:編寫的過濾器繼承自 BasicHttpAuthenticationFilter 類,然后重寫了 isLoginAttempt 方法用于判斷是否是進(jìn)行需要登錄驗(yàn)證的操作,如果判斷返回 true 則才會(huì)進(jìn)行之后的操作。然后重寫 executeLogin 方法即這個(gè)方法進(jìn)行 token 的驗(yàn)證,通過我們自定義的 TokenReanlm 進(jìn)行驗(yàn)證。如果驗(yàn)證成功則返回 true 即繼續(xù)執(zhí)行原來(lái)的請(qǐng)求,如果驗(yàn)證失敗則進(jìn)入 onAccessDenied 方法。我們通過沖洗 onAccessDenied 方法將降入此方法后就返回 401 異常說明,token 驗(yàn)證失敗。

    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
    return ((HttpServletRequest) request).getHeader(“Authorization”) != null;
    }

    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response){
    HttpServletRequest httpServletRequest = (HttpServletRequest) request;
    String authorization = httpServletRequest.getHeader(“Authorization”);
    MyToken token = new MyToken(authorization.substring(7));
    getSubject(request, response).login(token);
    return true;
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
    try {
    if (isLoginAttempt(request, response)) {
    executeLogin(request, response);
    } else {
    throw new Exception();
    }
    } catch (Exception e){
    return false;
    }
    return true;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response){
    HttpServletResponse httpServletResponse = (HttpServletResponse)response;
    AuthMessage authMessage = AuthMessage.getAuthMessage(
    “請(qǐng)先登錄”, “”, “l(fā)ogin /auth/login”,
    “/auth/login”, “l(fā)ogin”, “application/json”);
    httpServletResponse.setCharacterEncoding(“UTF-8”);
    httpServletResponse.setHeader(“Content-Type”, “application/json;charset=UTF-8”);
    String ret = JSON.toJSONString(authMessage);
    httpServletResponse.setStatus(401);
    try {
    response.getWriter().write(ret);
    response.getWriter().close();
    } catch (IOException ex) {
    ex.printStackTrace();
    }
    return false;
    }

    #### 4. WebSocket 配置文件:通過重寫 registerStompEndpoints 方法規(guī)定 socket 建立連接時(shí)候的接口。然后再 configureMessageBroker 方法中設(shè)置訂閱 url 和發(fā)送消息的前綴

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint(“/chat”).setAllowedOrigins(“*”).withSockJS(); //設(shè)置連接url并且設(shè)置跨域
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker(“/subscription”); //設(shè)置訂閱的url
    config.setApplicationDestinationPrefixes(“/socket”); //設(shè)置訪問url前綴
    }

    2) 數(shù)據(jù)庫(kù)實(shí)體 entity 的類在這里就不展示,詳情可見代碼壓縮包,因?yàn)檫@個(gè) package 中的文件和數(shù)據(jù)庫(kù)中的表緊密聯(lián)系,而數(shù)據(jù)庫(kù)中的表在之前已經(jīng)詳細(xì)說明過了 3) dto 作為 entity 中的對(duì)外交互類這里也不給出具體代碼,具體代碼可以見代碼壓縮包#### 5.controller1. ###### 登錄首先通過前端傳送上來(lái)的數(shù)據(jù)去數(shù)據(jù)庫(kù)中尋找數(shù)據(jù)庫(kù)中的 User,這里調(diào)用了 UserService 的 setUser 方法,此方法將 user 放置到 UserSerivce 對(duì)象中的時(shí)候同時(shí)會(huì)去數(shù)據(jù)庫(kù)查找賬號(hào)對(duì)應(yīng)的 user,且放入到此類的 userInDataBase 中,調(diào)用 UserService 的 encode 方法將當(dāng)前收到的密碼進(jìn)行加密,然后進(jìn)行判斷,如果 userInDataBase 為空也就是 UserService 方法中的 isRegistered()返回了 false,則表示賬號(hào)還沒被注冊(cè),如果 checkPassword 方法返回 false 也就是 encode 之后的密碼和 userInDataBase 中的密碼不一樣,說明密碼錯(cuò)誤。如果二者都不滿足則登錄成功,這里如果發(fā)送錯(cuò)誤的話就會(huì)拋出異常然后有一個(gè)全局的異常捕捉函數(shù)進(jìn)行處理

    userService.setUser(user);

    userService.encode();

    if(!userService.isRegistered()){
    throw new HttpException(HttpStatus.UNAUTHORIZED, AuthMessage.getAuthMessage(
    “賬號(hào)未注冊(cè)”,“”, “registration /auth/registration”,
    “/auth/registration”, “registration”, “application/json”));

    } else if(!userService.checkPassword()){
    throw new HttpException(HttpStatus.UNAUTHORIZED, AuthMessage.getAuthMessage(
    “賬號(hào)密碼不匹配”,“”, “l(fā)ogin /auth/login”,
    “/auth/login”, “l(fā)ogin”, “application/json”));

    }else{
    response.setStatus(200);
    return AuthMessage.getAuthMessage(
    “登錄成功”,TokenManager.sign(userService.getUserInDataBase().getId()), “messages /management/messages”,
    “/management/messages”, “messages”, “application/json”);
    }

    ###### 2.注冊(cè)和登錄邏輯類似,通過 UserService 的 setUser 方法將注冊(cè)的用戶放置到 UserService 類中,然后進(jìn)行是否注冊(cè)過賬號(hào)密碼是否小于 8 的判斷,如果都沒有的話,隨機(jī)分配一個(gè)頭像,年齡默認(rèn)為 1,性別默認(rèn)為女,然后進(jìn)行注冊(cè)

    userService.setUser(user);
    if(userService.isRegistered()){
    throw new HttpException(HttpStatus.FORBIDDEN, AuthMessage.getAuthMessage(
    “賬號(hào)已注冊(cè)”,“”, “registration /auth/registration”,
    “/auth/registration”, “registration”, “application/json”));
    }else if(user.getPassword().length()<8){
    throw new HttpException(HttpStatus.FORBIDDEN, AuthMessage.getAuthMessage(
    “密碼長(zhǎng)度小于八位”,“”, “registration /auth/registration”,
    “/auth/registration”, “registration”, “application/json”));
    }else{
    userService.encode();
    userService.initPic();
    userService.setAge(1);
    userService.setGender(“男”);
    userService.save();
    response.setStatus(201);
    return AuthMessage.getAuthMessage(
    “注冊(cè)成功”, TokenManager.sign(userService.getUser().getId()), “messages /management/messages”,
    “/management/messages”, “messages”, “application/json”);
    }

    ###### 3. Socket 實(shí)現(xiàn)實(shí)時(shí)聊天1. 規(guī)定如下,一個(gè)用戶之間里一個(gè) socket 連接,當(dāng)點(diǎn)擊進(jìn)入聊天框的時(shí)候建立連接,離開聊天框的時(shí)候斷開連接。每個(gè)用戶訂閱的評(píng)到就是/subscription/userId。然后每一個(gè)用戶要發(fā)送消息的時(shí)候消息格式如下:

    {
    senderId:xxx,
    recipientId:xxx,
    content:{“me”:”xxx”}
    }

    后端接收到消息之后將 content 轉(zhuǎn)化為{“he”,”xxx”}這是因?yàn)榍岸虽秩拘枰?#xff0c;然后通過 recipientId 可以知道要發(fā)送得了路徑,可以通過 MessageingTemplate.converAndSend 來(lái)主動(dòng)推送消息。除此之外還要將消息送入 Reids 和 MySQL。

    @MessageMapping(“/chat”)
    public void sayHello(ChatMessage message){
    User sender = userService.findById(message.getSenderId());
    User recipient = userService.findById(message.getRecipientId());
    if(sendernull||recipientnull)
    return;
    Map<String, String> content = message.getContent();;
    String destination = “/subscription/” + recipient.getId();
    String origin = “/subscription/” + sender.getId();
    if(relationshipService.isFriend(recipient, sender)){
    Record record = new Record(content.get(“my”), new Date(), sender, recipient);
    userMessage.save(record);
    userMessage.addMessage(recipient.getId(), sender.getId(), sender.getNickname(), content.get(“my”), sender.getPic());
    messagingTemplate.convertAndSend(destination, new HashMap<String, Object>(){{
    put(“senderId”, String.valueOf(sender.getId()));
    put(“senderPic”, sender.getPic());
    put(“senderName”, sender.getNickname());
    put(“content”, new HashMap<String, String>(){{
    put(“he”, content.get(“my”));
    }}
    );
    }});
    messagingTemplate.convertAndSend(origin, new HashMap<String, String>(){{
    put(“content”,“發(fā)送成功”);
    }});http://www.biyezuopin.vip
    }else{
    messagingTemplate.convertAndSend(origin, new HashMap<String, String>(){{
    put(“content”,“對(duì)方不是你好友”);
    }});
    }
    }

    ###### 4. 消息盒子的消息獲取因?yàn)橄⒑凶邮遣捎幂喸兎绞将@取,我們通過 UserService 的無(wú)參數(shù)的 setUser 方法可以將 Shiro 框架中通過驗(yàn)證的用戶放入到 UserService 的 user 中去,然后我們獲取到 Redis 的所有存儲(chǔ)消息并且返回給前端,返回給前端的數(shù)據(jù)包括 Redis 中的未讀消息(發(fā)送方頭像,姓名,Id,內(nèi)容),以及對(duì)于每一個(gè)發(fā)送方的未讀消息數(shù)量,以及自己的頭像,和基礎(chǔ)信息。

    @RequestMapping(value = “/index”, method = {RequestMethod.GET})
    public InfoMessage getIndex(@RequestParam Integer customer){
    User user = userService.setUser();
    if(!(customernull||customer-1)){
    userMessage.readMessage(user.getId(),customer);
    }
    Map<Object, Object> unreadMessages = userMessage.getAllUnread(user.getId());
    Map<Object, Object> unreadNum = userMessage.getAllUnreadNum(user.getId());
    LinkMessage linkMessage = new LinkMessage(
    “”,
    “”,
    “”,
    “”);
    return new InfoMessage(“獲取成功”, unreadMessages, unreadNum, new UserInformation(
    user.getId(), user.getNickname(), user.getPic(), user.getGender(), user.getAge()
    ), linkMessage);
    }

    ###### 5. 獲取所有的歷史記錄這個(gè)邏輯也比較簡(jiǎn)單,也是獲取到當(dāng)前登錄的用戶之后,去查找當(dāng)前登錄的用戶和對(duì)方的所有聊天記錄,然后將消息轉(zhuǎn)化為前端要求的格式返回即可。這里不給出代碼,具體可見代碼壓縮包

    @RequestMapping(value = “/history/download”, method = {RequestMethod.GET})
    public ResponseEntity download(@RequestParam Integer customer){
    User user = userService.setUser();
    List records = recordRepository.getChatRecord(user.getId(), customer, -1);
    HttpHeaders headers = new HttpHeaders();
    headers.add(“Cache-Control”, “no-cache, no-store, must-revalidate”);
    headers.add(“Content-Disposition”, “attachment; filename=” + System.currentTimeMillis() + “.xls”);
    headers.add(“Pragma”, “no-cache”);
    headers.add(“Expires”, “0”);
    headers.add(“Last-Modified”, new Date().toString());
    headers.add(“ETag”, String.valueOf(System.currentTimeMillis()));
    try {
    File file = new File(“history/”+user.getId()+“with”+customer+“.txt”); // 相對(duì)路徑,如果沒有則要建立一個(gè)新的output.txt文件
    if(!file.exists()) {
    file.createNewFile(); // 創(chuàng)建新文件,有同名的文件的話直接覆蓋
    }
    FileWriter writer = new FileWriter(file);
    BufferedWriter out = new BufferedWriter(writer);
    for(RecordDto record: records){
    out.write(“at “+ record.getTime()+”|”+record.getSenderId()+“–>”+record.getRecipientId()+“=”+record.getContent()+“rn”);
    }
    out.flush(); // 把緩存區(qū)內(nèi)容壓入文件
    out.close();
    return ResponseEntity.ok().headers(headers) .contentLength(file.length()).contentType(MediaType.parseMediaType(“application/octet-stream”)) .body(new FileSystemResource(file));
    } catch (IOException e) {
    e.printStackTrace();
    }
    File file = new File(“history/”+user.getId()+“with”+customer+“.txt”);
    return ResponseEntity.ok().headers(headers) .contentLength(file.length()).contentType(MediaType.parseMediaType(“application/octet-stream”)) .body(new FileSystemResource(file));
    }

    ###### 6. 歷史記錄下載下載分為兩部分,第一步為查詢聊天記錄,查詢的流程在上面已經(jīng)說過了,這里不再贅述,主要是第二部。我們更改 httpheader,加入文件流的控制。然后將我們查詢到的聊天記錄根據(jù)規(guī)定格式寫入到 Java 的 File 類中,然后將這個(gè) File 類返回給前端即可。###### 7. 刪除歷史記錄刪除歷史記錄的邏輯很簡(jiǎn)單,知道知道獲取到對(duì)方 Id 和自己的 Id 然后將數(shù)據(jù)庫(kù)中的所有這兩個(gè)人的記錄都刪除即可(sender=a,customer=b||sender=b,customer=a)這里就不給出代碼,具體見壓縮包### 前端實(shí)現(xiàn):#### Home 主界面的編寫使用 mint-ui 的 mt-tabbar 標(biāo)簽來(lái)實(shí)現(xiàn)消息盒子組件與好友列表組件之間的切換,同時(shí)添加了圖片實(shí)現(xiàn)點(diǎn)擊可以切換狀態(tài)。 消息 好友列表 ```

    好友列表的渲染

    根據(jù)后端傳來(lái)的雙層數(shù)組進(jìn)行列表渲染,使用 Vue 中 value 和 key 的列表渲染方式,將不同分組的人員分開渲染,同時(shí)使用 URL 的傳值方式,使點(diǎn)擊不同的用戶,好友展示中展示不同的好友信息

    <div v-for="(value,key) in friends" :key="key"><mt-cell :title="key"></mt-cell><mt-cellv-for="(value2,key2) in value":key="key2":to="'/home/showfriend?group='+key+'&id='+key2"><span>{{value2.nickname}}</span><img slot="icon" v-bind:src="value2.pic" width="28" height="28" /></mt-cell></div>

    添加好友模塊

    使用 mint-ui 的搜索框組件進(jìn)行昵稱的輸入,同時(shí)使用兩個(gè)下拉框?qū)崿F(xiàn)年齡和性別的篩選,使用 Vue 的原生回車事件進(jìn)行提交

    <mt-searchv-model="searchContent"cancel-text="取消"placeholder="輸入昵稱搜索"@keyup.enter.native="search"></mt-search>

    發(fā)送好友請(qǐng)求模塊

    模塊初始化時(shí),使用 LocalStorage 取出存儲(chǔ)的用戶信息,使用 axios 的錯(cuò)誤捕獲來(lái)判斷是否成功發(fā)送好友請(qǐng)求

    .catch(err => {MessageBox.alert("不能重復(fù)添加,或者添加自己").then(action => {});});

    個(gè)人信息修改模塊

    個(gè)人修改的信息通過表單獲取,同時(shí)使用正則表達(dá)式判斷是否符合要求。圖片文件則進(jìn)行后綴以及大小的合法性判斷,不符合要求則不進(jìn)行修改

    var regPos = /^\d+$/;if (this.info.nickname == "" ||this.info.account == "" ||this.info.pic == "" ||this.info.gender == "") {MessageBox.alert("所填項(xiàng)不能為空").then(action => {});return false;}else if(this.info.gender!="男" && this.info.gender !="女"){MessageBox.alert("性別只能為男或女").then(action => {});return false;}else if(!regPos.test(this.info.age)){MessageBox.alert("年齡只能為非負(fù)整數(shù)").then(action => {});return false;}var img = e.target.files[0];if (img.type !== "image/jpeg" &&img.type !== "image/png" &&img.type !== "image/gif") {MessageBox.alert("請(qǐng)選擇圖片文件").then(action => {});return false;} else if (img.size > 1024 * 30) {console.log(img.size);MessageBox.alert("選擇小于30kb的圖片").then(action => {});return false;http://www.biyezuopin.vip}

    好友展示模塊

    通過 mint-ui 的上拉框進(jìn)行功能的選擇,可以選擇刪除好友,添加標(biāo)簽,移動(dòng)好友的功能,添加標(biāo)簽時(shí)與以往的標(biāo)簽進(jìn)行比較,若有相同的則進(jìn)行去重操作

    <mt-button icon="more" slot="right" @click.native="actionSheet"></mt-button>var add = {};add.targetId = this.info.id;add.contents = this.info.impressions;const impressions = new Set(add.contents);if(!impressions.has(value)){this.info.impressions.push(value);}console.log(impressions);add.contents = [...impressions.values()];

    好友驗(yàn)證模塊

    使用兩個(gè)列表渲染,展示自己發(fā)送的請(qǐng)求,以及別人發(fā)送給你的請(qǐng)求,同時(shí)組件被創(chuàng)建時(shí),自動(dòng)請(qǐng)求后端,將所有的未讀消息置為已讀消息

    this.axios //標(biāo)記好友請(qǐng)求已讀.post("/verification/user/validation-reading").then(response => {}).catch(err => {console.log(err); //異常});

    聊天模塊

    Connection方法,建立stomp連接,并且為了防止websocket連接中斷,我采用了心跳保活技術(shù),每60s發(fā)送一條無(wú)用消息,確保連接正常。 并且推出頁(yè)面會(huì)斷開socket連接,以減小服務(wù)器壓力。

    發(fā)送消息,進(jìn)行了內(nèi)容判斷 發(fā)送內(nèi)容不能為空或者內(nèi)容長(zhǎng)度不能超過 200

    登錄注冊(cè)模塊

    登錄后 將 token 與個(gè)人信息相關(guān)的數(shù)據(jù)存入 localstorage 內(nèi),退出登錄就直接清除 localstorage 內(nèi)全部數(shù)據(jù)。

    注冊(cè)后,直接跳轉(zhuǎn)到主頁(yè)面,有良好的錯(cuò)誤處理機(jī)制。

    測(cè)試模塊

    TESTNG 文件配置

    使用 XML 配置要測(cè)試的模塊,包括注冊(cè)功能,登錄功能,添加好友功能,聊天功能等

    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Link聊天自動(dòng)化測(cè)試"><test name="測(cè)試"><classes><class name="register.Register"/><class name="login.Login" /><class name="addfriend.Addfriend"/><class name="impression.Impression"/><class name="chat.Chat"/><class name="deletefriend.Deletefriend"/></classes></test> </suite>

    瀏覽器驅(qū)動(dòng)公共代碼

    公共代碼中實(shí)現(xiàn)公共操作,包括啟動(dòng)瀏覽器驅(qū)動(dòng),瀏覽器驅(qū)動(dòng)關(guān)閉,共享變量 DRIVER,供所有模塊使用

    @BeforeClass public static void setUp() throws Exception {System.out.println("啟動(dòng)瀏覽器");System.setProperty("webdriver.chrome.driver","./src/chromedriver.exe");driver = new ChromeDriver();driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); }

    注冊(cè)模塊自動(dòng)化測(cè)試代碼

    使用 selenium 進(jìn)行瀏覽器的操作,輸入待注冊(cè)的賬號(hào),注冊(cè)完成后跳轉(zhuǎn)到主頁(yè)面進(jìn)行登錄,判斷是否注冊(cè)成功

    Main.setUp(); Main.driver.get(RegisterURL); Main.driver.findElement(By.name("user_register")).sendKeys(this.email); Main.driver.findElement(By.name("user_name")).sendKeys(this.name); Main.driver.findElement(By.name("user_pass")).sendKeys(this.psw); Main.driver.findElement(By.cssSelector(".btn_register")).click(); Thread.sleep(3000); String current_url = Main.driver.getCurrentUrl(); try {Assert.assertEquals(current_url,this.SuccessURL,"注冊(cè)驗(yàn)證失敗!!"); }catch (Exception e){Main.tearDown(); } Main.tearDown();

    登錄模塊自動(dòng)化代碼

    在輸入框中輸入賬號(hào)與密碼,點(diǎn)擊登錄,頁(yè)面加載完成后判斷是否轉(zhuǎn)跳到主頁(yè)面

    Main.setUp(); Main.driver.get(Main.BaseURL); WebElement email = Main.driver.findElement(By.name("user_login")); WebElement psw = Main.driver.findElement(By.name("user_pass")); WebElement button = Main.driver.findElement(By.cssSelector(".btn_login")); email.sendKeys(Main.email); psw.sendKeys(Main.psw); button.click();

    印象模塊自動(dòng)化測(cè)試代碼

    根據(jù)提供的賬號(hào)登錄到主頁(yè)面,點(diǎn)擊好友框,添加印象,然后匹配數(shù)據(jù)庫(kù)中的印象表,查看是否添加成功

    Main.driver.findElement(By.xpath("//*[text()='康王']")).click(); Main.driver.findElement(By.className("mintui-more")).click(); Thread.sleep(2000); Main.driver.findElement(By.xpath("//*[text()='添加標(biāo)簽']")).click(); Main.driver.findElement(By.cssSelector("input")).sendKeys(this.impre); Main.driver.findElement(By.xpath("//*[text()='確定']")).click();

    好友刪除自動(dòng)化測(cè)試代碼

    點(diǎn)擊頁(yè)面刪除好友按鈕,退回到好友列表界面,查看是否刪除該好友

    Main.driver.findElement(By.className("mintui-more")).click(); Main.driver.findElement(By.xpath("//*[text()='刪除好友']")).click(); Main.driver.findElement(By.xpath("//*[text()='確定']")).click(); Main.driver.findElement(By.xpath("//*[text()='確定']")).click();try{WebElement a = Main.driver.findElement(By.xpath("//*[text()='康王']"));Thread.sleep(4000); }catch (Exception e){Main.tearDown(); } Main.tearDown();

    聊天界面自動(dòng)化測(cè)試代碼

    點(diǎn)擊預(yù)訂的好友,點(diǎn)擊發(fā)送消息,跳轉(zhuǎn)到消息界面,輸入框中輸入消息,點(diǎn)擊發(fā)送,查看頁(yè)面中是否渲染出該條消息,并登陸另一賬號(hào)查看是否接受到該消息

    Main.driver.findElement(By.className("mint-button--large")).click(); Main.driver.findElement(By.className("footer_inpuit")).sendKeys("呆呆呆呆"); Main.driver.findElement(By.className("footer_button")).click(); try{WebElement a = Main.driver.findElement(By.xpath("//*[text()='確定']")); }catch(Exception e){Assert.fail("發(fā)送消息失敗!!");Main.tearDown(); }

    五、成果展示

    登錄頁(yè)面 注冊(cè)頁(yè)面

    消息盒子頁(yè)面(無(wú)消息) 消息盒子(有消息)

    好友盒子(無(wú)朋友驗(yàn)證) 好友盒子(有朋友驗(yàn)證)

    在好友列表中點(diǎn)擊好友之后顯示的頁(yè)面 點(diǎn)擊右上角的點(diǎn)之后的頁(yè)面

    個(gè)人信息 搜索結(jié)果界面

    搜索點(diǎn)擊好友后的界面 可以發(fā)送驗(yàn)證消息(帶信息)

    聊天界面

    驗(yàn)證消息

    全套資料下載地址:https://download.csdn.net/download/sheziqiong/85595798

    總結(jié)

    以上是生活随笔為你收集整理的基于Vue+Java实现的在线聊天APP系统设计与实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。