Let’s chatting on web

项目萌芽

干这行的都知道,程序员开发一定少不了浏览器,或许现在还少不了ChatGPT?

总之我是尽量用各种软件的web端,除非web端没有或者烂到不能用的。

我的电脑上没有安装微信,现在没有,以后也不想有。(QQ倒是装了一个,至少Windows上的QQ还是蛮好用的

但是现在我事实上用的最多的两款社交软件(QQ、微信)都没有Web端。

另外我也经常使用QQ作为跨端文件传输工具,但是手机QQ本地文件存储的位置我一直没能找到,并且其自带的文件过期机制,使得明明是自己传输的文件却做不到收放自如。

基于以上两个痛点,我开始萌发做一款Web聊天软件的想法。

开始动手

最初的动手是在2022年9月-10月。促使动手的原因很简单,需要做一个项目应付软件实训。当时看了市面上大部分的Web聊天系统,基本上都是基于 Vue + Express + socket.io。而我的技术栈是Java后端,前端属于原生级别。不过所幸还是找到了一款相当合适的。

这个就是当时看中的一款:https://github.com/Kanarienvogels/Chatroom

clone到本地,花了好几天将前后端代码都看完了,然后在原来的基础上做了一些规范化操作,并提交了一条PR,奈何原作者已经不玩GitHub了,故最终还是将这条pr关了,如果当时原作者能够响应,或许又是另一个故事了。

于是我索性基于这个项目单开一个项目,取名为 chatting 。然后接下来就是不断地重构、重构。第一次比较大的重构主要是从Spring到SpringBoot,后面的重构主要包括前后端各种地方的代码重写,总之就是在没有添加明显功能的前提下,不断地提高代码的质量和可扩展性。

当代码终于稳定下来后,我发现如果我需要添加新功能居然寸步难行。其中最大的问题在于前端,原因是因为最初的前端就是原生js + jQuery + Bootstrap样式。虽然我改动了不少js代码,但是对于Bootstrap样式我并不熟悉,而且由于大量使用了jQuery,所以js代码和样式以及结构耦合十分严重,经典的改一行出现五行新的报错。

而且当时项目检查的时间也快到了,我就索性开摆拿另一个功能更多的项目顶了上去,此后,项目被搁置了几个月。

前端重写,后端重构

转眼间,毕设就要求选题了,于是我又再次拾起了这个项目。但是这时我是知道这个项目的问题在哪的,于是我花了一周时间学习了Vue框架(说实话中文项目上手还是很快的)。最初使用vue并不十分顺手,因为它和曾经前端的直来直去编写方式有所差异,很多时候完成一项功能需要绕个弯。但是当你写了几千行代码后,这些问题就渐渐不是问题而是习惯了。

前端页面编写的难点倒是不多,但是需要考虑功能的全面性,防止以后又要再加功能改结构和样式,那将是代码灾难。整个页面的设计和编码花的时间不算多,大概一周就写完了,最终的成品和现在的也差不了多少。

接下来主要就是改后端,其实此时完全没必要用上以前的后端,因为除了Netty相关其他部分基本上都被重写了,不过为了情怀嘛,整个项目的后端还是在以前的基础上进行的开发。

中间经历了不少联调适配,反正前后端代码都是在不断地修修补补,最终形成了现在的效果。

demo

更多的话

整个项目开发过程大概就是这样,不过接下来我还想说一些我认为值得一提的过程与结果。

  • 前端的编码设计一直在重构,真真正正的使用到了Vue的各种特性,如store、plugin、mixin、router、services等,也真正的体会到了为什么Vue有这些特性,为什么Vue需要这些特性。
  • 后端的代码也一直在重构,组件也尽量在升级,最近的升级主要是从Mybatis到Mybatis-plus,规范程度向jar包靠拢。
  • 需求和bug采用issue管理,pr分情况使用rebase或者merge。
  • 欢迎合作

项目总结

  • 前端架构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    ├─.github 
    │ ├─dependabot.yml
    │ └─workflows
    │  ├─ci.yml
    │  └─tagged-release.yml
    ├─.gitignore
    ├─babel.config.js
    ├─jenkins
    │ └─Jenkinsfile
    ├─jsconfig.json
    ├─package-lock.json
    ├─package.json
    ├─public
    │ ├─avatar
    │ │ ├─default_group_avatar.jpg
    │ │ ├─default_user_avatar.jpg
    │ │ └─openai.png
    │ ├─favicon.ico
    │ ├─img
    │ │ ├─loading.gif
    │ │ ├─login_bg.jpg
    │ │ └─sign-weixin.png
    │ ├─index.html
    │ └─upload
    ├─README.md
    ├─src
    │ ├─App.vue
    │ ├─assets
    │ │ └─emoji.json
    │ ├─components
    │ │ ├─MessageArea
    │ │ │ ├─MessageArea.vue
    │ │ │ ├─MessageBottomInputArea.vue
    │ │ │ └─MessageTopTitle.vue
    │ │ ├─NavigationZone
    │ │ │ ├─ApplicationList.vue
    │ │ │ ├─NavigationApplication.vue
    │ │ │ ├─NavigationContact.vue
    │ │ │ ├─NavigationTopSearchBar.vue
    │ │ │ ├─NavigationZone.vue
    │ │ │ └─RelationList.vue
    │ │ ├─SearchArea
    │ │ │ ├─SearchArea.vue
    │ │ │ └─SearchAreaBody.vue
    │ │ └─ToolBar
    │ │  ├─ToolBar.vue
    │ │  ├─ToolBarBottom.vue
    │ │  └─ToolBarTop.vue
    │ ├─config
    │ │ ├─api.js
    │ │ └─openai.js
    │ ├─main.js
    │ ├─mixins
    │ │ └─socketMixin.js
    │ ├─pages
    │ │ ├─ChattingPage.vue
    │ │ └─LoginPage.vue
    │ ├─plugins
    │ │ └─apiPlugin.js
    │ ├─router
    │ │ └─index.js
    │ ├─services
    │ │ ├─api
    │ │ │ ├─api.js
    │ │ │ └─index.js
    │ │ └─request.js
    │ ├─store
    │ │ ├─actions.js
    │ │ ├─index.js
    │ │ └─mutations.js
    │ ├─styles
    │ │ └─common.css
    │ └─utils
    │  ├─common.js
    │  ├─date.js
    │  └─socket.js
    └─vue.config.js
  • 后端架构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    ├─.github 
    │ └─workflows
    │  └─ci.yml
    ├─.gitignore
    ├─docker-compose.yml
    ├─Dockerfile
    ├─Jenkinsfile
    ├─Makefile
    ├─pom.xml
    ├─README.md
    ├─scripts
    │ └─local-deploy.bat
    └─src
    ├─main
    │ ├─java
    │ │ └─com
    │ │  └─sxrekord
    │ │  └─chatting
    │ │  ├─ChattingApplication.java
    │ │  ├─common
    │ │  │ ├─Constant.java
    │ │  │ ├─MessageType.java
    │ │  │ └─WSType.java
    │ │  ├─config
    │ │  │ ├─UriConfig.java
    │ │  │ └─WebMvcConfig.java
    │ │  ├─context
    │ │  │ └─AppContext.java
    │ │  ├─controller
    │ │  │ ├─AdminController.java
    │ │  │ ├─FileController.java
    │ │  │ ├─GroupController.java
    │ │  │ ├─MessageController.java
    │ │  │ ├─RelationController.java
    │ │  │ └─UserController.java
    │ │  ├─dao
    │ │  │ ├─FileContentDao.java
    │ │  │ ├─FileDao.java
    │ │  │ ├─GroupDao.java
    │ │  │ ├─ImageContentDao.java
    │ │  │ ├─MessageDao.java
    │ │  │ ├─RelationDao.java
    │ │  │ ├─TextContentDao.java
    │ │  │ └─UserDao.java
    │ │  ├─exception
    │ │  │ └─GlobalExceptionHandler.java
    │ │  ├─interceptor
    │ │  │ └─UserAuthInterceptor.java
    │ │  ├─model
    │ │  │ ├─po
    │ │  │ │ ├─File.java
    │ │  │ │ ├─FileContent.java
    │ │  │ │ ├─Group.java
    │ │  │ │ ├─ImageContent.java
    │ │  │ │ ├─Message.java
    │ │  │ │ ├─Relation.java
    │ │  │ │ ├─TextContent.java
    │ │  │ │ └─User.java
    │ │  │ └─vo
    │ │  │  └─ResponseJson.java
    │ │  ├─service
    │ │  │ ├─ChatService.java
    │ │  │ ├─FileService.java
    │ │  │ ├─GroupService.java
    │ │  │ ├─impl
    │ │  │ │ ├─ChatServiceImpl.java
    │ │  │ │ ├─FileServiceImpl.java
    │ │  │ │ ├─GroupServiceImpl.java
    │ │  │ │ ├─MessageServiceImpl.java
    │ │  │ │ ├─RelationServiceImpl.java
    │ │  │ │ └─UserServiceImpl.java
    │ │  │ ├─MessageService.java
    │ │  │ ├─RelationService.java
    │ │  │ └─UserService.java
    │ │  ├─task
    │ │  │ └─FileTask.java
    │ │  ├─util
    │ │  │ ├─FileUtils.java
    │ │  │ ├─HeaderUtils.java
    │ │  │ ├─JsonMsgHelper.java
    │ │  │ ├─JwtTokenUtils.java
    │ │  │ ├─RedisUtils.java
    │ │  │ └─WrapEntity.java
    │ │  └─websocket
    │ │  ├─HttpRequestHandler.java
    │ │  ├─WebSocketChildChannelHandler.java
    │ │  ├─WebSocketServer.java
    │ │  └─WebSocketServerHandler.java
    │ └─resources
    │  ├─application.yml
    │  ├─banner.txt
    │  ├─mapper
    │  │ ├─file-mapper.xml
    │  │ ├─filecontent-mapper.xml
    │  │ ├─group-mapper.xml
    │  │ ├─imagecontent-mapper.xml
    │  │ ├─message_mapper.xml
    │  │ ├─relation-mapper.xml
    │  │ ├─textcontent-mapper.xml
    │  │ └─user-mapper.xml
    │  └─sql
    │  ├─data.sql
    │  └─schema.sql
    └─test
    └─java
    └─com
    └─sxrekord
    └─chatting
    ├─config
    │ ├─DruidTest.java
    │ └─FastAutoGeneratorTest.java
    ├─dao
    │ ├─GroupDaoTest.java
    │ ├─MessageDaoTest.java
    │ └─RelationDaoTest.java
    ├─model
    │ └─vo
    │  └─ResponseJsonTest.java
    ├─service
    │ ├─FileServiceTest.java
    │ ├─GroupServiceTest.java
    │ ├─MessageServiceTest.java
    │ ├─RelationServiceTest.java
    │ └─UserServiceTest.java
    └─util
    ├─FileUtilsTest.java
    ├─JwtTokenUtilTest.java
    └─RedisUtilsTest.java
  • 功能特色

    • 实现群组和好友的完全管理
    • 支持多种消息格式
    • 消息记录持久化与懒加载
    • 使用JWT作为token,安全性更高
    • 前端会在用户无感知的前提下自动鉴权
    • 文件管理十分精巧,不误删、不滥存

最后

希望自己不忘初心,能坚持更新维护吧!