├── .babelrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .postcssrc.js
├── LICENSE
├── README.md
├── build
├── build.js
├── check-versions.js
├── logo.png
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
└── prod.env.js
├── index.html
├── package-lock.json
├── package.json
├── server
├── config.js
├── controllers
│ ├── groupChat.js
│ ├── groupInfo.js
│ ├── login.js
│ ├── message.js
│ ├── newFriends.js
│ ├── privateChat.js
│ ├── register.js
│ ├── robot.js
│ └── userInfo.js
├── gulpfile.js
├── index.js
├── init
│ ├── index.js
│ ├── sql
│ │ └── airchat.sql
│ └── util
│ │ ├── get-sql-content-map.js
│ │ ├── get-sql-map.js
│ │ └── walk-file.js
├── middlewares
│ └── verify.js
├── models
│ ├── groupChat.js
│ ├── groupInfo.js
│ ├── message.js
│ ├── newFriends.js
│ ├── privateChat.js
│ ├── soketHander.js
│ └── user_info.js
├── package-lock.json
├── package.json
├── routes
│ └── index.js
└── utils
│ └── db.js
├── src
├── App.vue
├── assets
│ ├── base.scss
│ ├── chat.scss
│ ├── icon.svg
│ ├── loginregister.scss
│ └── logo.png
├── components
│ ├── ChatItem.vue
│ ├── Footer.vue
│ ├── Header.vue
│ ├── Message
│ │ ├── index.js
│ │ └── main.vue
│ ├── MessageBox
│ │ ├── index.js
│ │ └── main.vue
│ ├── index.js
│ └── template.vue
├── main.js
├── pages
│ ├── Add.vue
│ ├── AddSeach.vue
│ ├── ContactList.vue
│ ├── CreatEditorGroup.vue
│ ├── GroupChat.vue
│ ├── GroupInfo.vue
│ ├── Login.vue
│ ├── Me.vue
│ ├── Message.vue
│ ├── NewFriends.vue
│ ├── Register.vue
│ ├── Robot.vue
│ ├── UserInfo.vue
│ ├── VerifyReq.vue
│ └── privateChat.vue
├── router
│ └── index.js
├── store
│ └── index.js
└── utils
│ └── transformTime.js
└── static
└── .gitkeep
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", {
4 | "modules": false,
5 | "targets": {
6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7 | }
8 | }],
9 | "stage-2"
10 | ],
11 | "plugins": ["transform-vue-jsx", "transform-runtime"]
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | # Editor directories and files
8 | .idea
9 | .vscode
10 | *.suo
11 | *.ntvs*
12 | *.njsproj
13 | *.sln
14 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | "plugins": {
5 | "postcss-import": {},
6 | "postcss-url": {},
7 | // to edit target browsers: use "browserslist" field in package.json
8 | "autoprefixer": {}
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Hxvin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-chat
2 |
3 | ## 目前将一直重点更新维护react版本的,此版本注重性能和代码质量,完成度更高,更用心地去写,有兴趣的同学请移步 -> [react版本](https://github.com/aermin/react-chat)
4 |
5 |
6 | ## 线上地址
7 |
8 | [点击加入线上聊天](https://im.aermin.top)
9 |
10 | 欢迎加入 "ghChat项目交流群" 这个群交流呀,可搜索群名(不用全打)加入,也可点击机器人的邀请加入(如下图)
11 |
12 | 
13 |
14 |
15 | ### 介绍
16 |
17 | 这是我的毕设项目,产品功能和页面参照qq,微信,TIM,不完全一样,有些是自己的想法。前后端都自己写。
18 | 感觉是一个挺不错的全栈入门项目,各种交互各种业务逻辑,不花哨,但实用。
19 |
20 | 对node(koa)和vue学习会挺有帮助,现在开源出来,接下去将继续不断完善😄欢迎star
21 |
22 | ### 技术栈:
23 |
24 | 前端vue,vue-router,vuex ,vue-cli和axios,scss,用rem做了移动端适配,没有用第三方组件。
25 | 后端用koa2,用gulp构建工具实现自动刷新后端代码运行。
26 | 数据库用mysql,基于Token的jwt鉴权机制,用socket.io做双向通信;
27 |
28 |
29 | 
30 |
31 | ### 项目展示
32 |
33 | - 系统组成
34 |
35 | 
36 |
37 |
38 | 
39 | 
40 | 
41 |
42 | 
43 |
44 |
45 | 
46 |
47 | ### todo
48 |
49 | > 2018.01.13 开始
50 |
51 | - [x] 登录
52 | - [x] 注册
53 | - [x] 登出
54 | - [x] 弹窗,提示等组件
55 | - [x] 机器人智能聊天回复
56 | - [x] 私聊
57 | - [x] 群聊
58 |
59 | > 2018.02.01
60 |
61 | - [x] 用户资料卡
62 | - [x] 加好友及验证好友请求
63 | - [x] 好友请求通知
64 | - [x] 删除好友
65 | - [x] 未读消息提示
66 |
67 | > 2018.02.10 吐槽一下,不得不佩服qq,微信的用户体验,功能细节挺多。。。。。
68 |
69 | - [x] 搜索用户,群组
70 | - [x] 创建群
71 | - [x] 群资料卡
72 | - [x] 加群
73 | - [x] 退群
74 | - [x] 修改备注
75 |
76 | > 2018.02.11
77 |
78 | - [x] 发布到线上
79 | - [x] 修改个人信息
80 |
81 | > 2018.03.02
82 |
83 | - [x] 收到添加好友请求底部tab红点提醒
84 |
85 | > 2018.03.20 收拾掉不少bug :-)
86 |
87 | - [x] 迁移成Electron桌面版本的,支持mac和win环境。 ~~(Electron版本将于答辩完开源)~~
88 |
89 | 已开源:[Electron桌面版本项目地址](https://github.com/aermin/electron-vue-chat)
90 |
91 | > 2018.03.25
92 |
93 | - [ ] 通讯录展示
94 | - [ ] 聊天发表情
95 | - [ ] 聊天发图片
96 | - [ ] 支持聊天代码美化,md语法
97 | - [ ] 用户上传头像
98 | - [ ] 性能优化,redis做缓存
99 | - [x] 实现react版本的
100 |
101 | ### 下载到本地开发环境跑
102 |
103 | 本次开发我用了三个git分支,分别是主分支master ,开发分支dev , 线上分支online,如果你要fork到你的本地跑,请fork master分支 。
104 |
105 | (注意下到本地后如果要体验soket.io通信互聊,用两个浏览器各打开一个账号,不能用同一个浏览器,因为我用localstorage缓存账户信息)
106 |
107 | Fork 或者 下载本项目
108 |
109 | 然后进入本项目的文件夹,把vue-chat/server/init/sql 的 airchat.sql文件 拉到你的msyql客户端 run一下生成数据库(我使用的是mac下的 `Sequel Pro` 挺好用的,当然你用其他方式也可生成数据库也可)
110 |
111 | ```js
112 | npm i
113 | ```
114 | ```js
115 | npm run dev
116 | ```
117 | ```js
118 | cd server
119 | ```
120 | ```js
121 | npm i
122 | ```
123 | 接着下面两条命令执行一条就行,看着选;(如果想要修改后端代码即时保存刷新,用第一条;如果像想用chrome进行debug调试,用第二条)
124 |
125 | ```js
126 | npm run start
127 | ```
128 | ```js
129 | npm run dev
130 | ```
131 |
132 | ### 打包上线,让所有人都能用到你的产品(非必须)
133 |
134 | 打包上线前需要对master分支的代码做一些修改。具体怎么修改以及后续如何打包上线,请看我单独写的一篇文章[vue-chat 打包上线小记](https://github.com/aermin/blog/issues/28),希望对你有帮助。
135 |
136 | > 老习惯,代码注释比较详细,需要注释而没有注释的我也尽快补上;
137 | 后面也会写几篇博客来详细介绍本项目,希望更好的帮助到入门的小伙伴(大神请略过,或者给些指导建议😄)
138 |
139 | ##### 如果对您有帮助,希望给个star,鼓励我继续更新^ ^
140 |
141 | ### 开发目录
142 |
143 | ```
144 | ├── LICENSE
145 | ├── README.md
146 | ├── build
147 | │ ├── build.js
148 | │ ├── check-versions.js
149 | │ ├── logo.png
150 | │ ├── utils.js
151 | │ ├── vue-loader.conf.js
152 | │ ├── webpack.base.conf.js
153 | │ ├── webpack.dev.conf.js
154 | │ └── webpack.prod.conf.js
155 | ├── config
156 | │ ├── dev.env.js
157 | │ ├── index.js
158 | │ └── prod.env.js
159 | ├── dist //打包后的静态资源
160 | │ ├── index.html
161 | │ └── static
162 | ├── index.html
163 | ├── package-lock.json
164 | ├── package.json
165 | ├── server // 后端代码
166 | │ ├── config.js
167 | │ ├── controllers
168 | │ ├── gulpfile.js
169 | │ ├── index.js
170 | │ ├── init //数据库初始化(sql文件也在这)
171 | │ ├── middlewares
172 | │ ├── models
173 | │ ├── package-lock.json
174 | │ ├── package.json
175 | │ ├── routes
176 | │ └── utils
177 | ├── src //前端代码
178 | │ ├── App.vue
179 | │ ├── assets
180 | │ ├── components
181 | │ ├── main.js
182 | │ ├── pages
183 | │ ├── router
184 | │ ├── store //vuex在这
185 | │ └── utils
186 | └── static
187 | ```
188 |
189 | ### 材料
190 |
191 | #### 自己总结的
192 |
193 | [web移动端适配方案](https://github.com/aermin/blog/issues/8)
194 |
195 | [vue-chat 打包上线小记](https://github.com/aermin/blog/issues/28)
196 |
197 | [token,Json web token(jwt)](https://github.com/aermin/blog/issues/24)
198 |
199 | [web移动端页面怎么调试](https://github.com/aermin/blog/issues/9)
200 |
201 | [本地mysql客户端连接centos的数据库](https://github.com/aermin/blog/issues/7)
202 |
203 | 文章都在[我的博客](https://github.com/aermin/blog)上,欢迎star我的博客😄
204 |
205 | #### 第三方的(在此感谢)
206 |
207 | [socket.io英文文档](https://socket.io/docs/server-api/)
208 |
209 | [socket.io中文文档](https://zhuanlan.zhihu.com/p/29148869)
210 |
211 | [socket.io in github](https://github.com/socketio/socket.io/)
212 |
213 | [socket.io-client in github](https://github.com/socketio/socket.io-client)
214 |
215 | [聊天机器人api](http://www.tuling123.com/)
216 |
217 | ### License
218 |
219 | [MIT](http://opensource.org/licenses/MIT)
220 |
221 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | require('./check-versions')()
3 |
4 | process.env.NODE_ENV = 'production'
5 |
6 | const ora = require('ora')
7 | const rm = require('rimraf')
8 | const path = require('path')
9 | const chalk = require('chalk')
10 | const webpack = require('webpack')
11 | const config = require('../config')
12 | const webpackConfig = require('./webpack.prod.conf')
13 |
14 | const spinner = ora('building for production...')
15 | spinner.start()
16 |
17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
18 | if (err) throw err
19 | webpack(webpackConfig, (err, stats) => {
20 | spinner.stop()
21 | if (err) throw err
22 | process.stdout.write(stats.toString({
23 | colors: true,
24 | modules: false,
25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
26 | chunks: false,
27 | chunkModules: false
28 | }) + '\n\n')
29 |
30 | if (stats.hasErrors()) {
31 | console.log(chalk.red(' Build failed with errors.\n'))
32 | process.exit(1)
33 | }
34 |
35 | console.log(chalk.cyan(' Build complete.\n'))
36 | console.log(chalk.yellow(
37 | ' Tip: built files are meant to be served over an HTTP server.\n' +
38 | ' Opening index.html over file:// won\'t work.\n'
39 | ))
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const chalk = require('chalk')
3 | const semver = require('semver')
4 | const packageConfig = require('../package.json')
5 | const shell = require('shelljs')
6 |
7 | function exec (cmd) {
8 | return require('child_process').execSync(cmd).toString().trim()
9 | }
10 |
11 | const versionRequirements = [
12 | {
13 | name: 'node',
14 | currentVersion: semver.clean(process.version),
15 | versionRequirement: packageConfig.engines.node
16 | }
17 | ]
18 |
19 | if (shell.which('npm')) {
20 | versionRequirements.push({
21 | name: 'npm',
22 | currentVersion: exec('npm --version'),
23 | versionRequirement: packageConfig.engines.npm
24 | })
25 | }
26 |
27 | module.exports = function () {
28 | const warnings = []
29 |
30 | for (let i = 0; i < versionRequirements.length; i++) {
31 | const mod = versionRequirements[i]
32 |
33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
34 | warnings.push(mod.name + ': ' +
35 | chalk.red(mod.currentVersion) + ' should be ' +
36 | chalk.green(mod.versionRequirement)
37 | )
38 | }
39 | }
40 |
41 | if (warnings.length) {
42 | console.log('')
43 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
44 | console.log()
45 |
46 | for (let i = 0; i < warnings.length; i++) {
47 | const warning = warnings[i]
48 | console.log(' ' + warning)
49 | }
50 |
51 | console.log()
52 | process.exit(1)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/build/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aermin/vue-chat/1f447896b54c9dc908c8fe0b0f718cd4c4a3e133/build/logo.png
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const config = require('../config')
4 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
5 | const packageConfig = require('../package.json')
6 |
7 | exports.assetsPath = function (_path) {
8 | const assetsSubDirectory = process.env.NODE_ENV === 'production'
9 | ? config.build.assetsSubDirectory
10 | : config.dev.assetsSubDirectory
11 |
12 | return path.posix.join(assetsSubDirectory, _path)
13 | }
14 |
15 | exports.cssLoaders = function (options) {
16 | options = options || {}
17 |
18 | const cssLoader = {
19 | loader: 'css-loader',
20 | options: {
21 | sourceMap: options.sourceMap
22 | }
23 | }
24 |
25 | const postcssLoader = {
26 | loader: 'postcss-loader',
27 | options: {
28 | sourceMap: options.sourceMap
29 | }
30 | }
31 |
32 | // generate loader string to be used with extract text plugin
33 | function generateLoaders (loader, loaderOptions) {
34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
35 |
36 | if (loader) {
37 | loaders.push({
38 | loader: loader + '-loader',
39 | options: Object.assign({}, loaderOptions, {
40 | sourceMap: options.sourceMap
41 | })
42 | })
43 | }
44 |
45 | // Extract CSS when that option is specified
46 | // (which is the case during production build)
47 | if (options.extract) {
48 | return ExtractTextPlugin.extract({
49 | use: loaders,
50 | fallback: 'vue-style-loader'
51 | })
52 | } else {
53 | return ['vue-style-loader'].concat(loaders)
54 | }
55 | }
56 |
57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html
58 | return {
59 | css: generateLoaders(),
60 | postcss: generateLoaders(),
61 | less: generateLoaders('less'),
62 | sass: generateLoaders('sass', { indentedSyntax: true }),
63 | scss: generateLoaders('sass'),
64 | stylus: generateLoaders('stylus'),
65 | styl: generateLoaders('stylus')
66 | }
67 | }
68 |
69 | // Generate loaders for standalone style files (outside of .vue)
70 | exports.styleLoaders = function (options) {
71 | const output = []
72 | const loaders = exports.cssLoaders(options)
73 |
74 | for (const extension in loaders) {
75 | const loader = loaders[extension]
76 | output.push({
77 | test: new RegExp('\\.' + extension + '$'),
78 | use: loader
79 | })
80 | }
81 |
82 | return output
83 | }
84 |
85 | exports.createNotifierCallback = () => {
86 | const notifier = require('node-notifier')
87 |
88 | return (severity, errors) => {
89 | if (severity !== 'error') return
90 |
91 | const error = errors[0]
92 | const filename = error.file && error.file.split('!').pop()
93 |
94 | notifier.notify({
95 | title: packageConfig.name,
96 | message: severity + ': ' + error.name,
97 | subtitle: filename || '',
98 | icon: path.join(__dirname, 'logo.png')
99 | })
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const isProduction = process.env.NODE_ENV === 'production'
5 | const sourceMapEnabled = isProduction
6 | ? config.build.productionSourceMap
7 | : config.dev.cssSourceMap
8 |
9 | module.exports = {
10 | loaders: utils.cssLoaders({
11 | sourceMap: sourceMapEnabled,
12 | extract: isProduction
13 | }),
14 | cssSourceMap: sourceMapEnabled,
15 | cacheBusting: config.dev.cacheBusting,
16 | transformToRequire: {
17 | video: ['src', 'poster'],
18 | source: 'src',
19 | img: 'src',
20 | image: 'xlink:href'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const config = require('../config')
5 | const vueLoaderConfig = require('./vue-loader.conf')
6 |
7 | function resolve (dir) {
8 | return path.join(__dirname, '..', dir)
9 | }
10 |
11 |
12 |
13 | module.exports = {
14 | context: path.resolve(__dirname, '../'),
15 | entry: {
16 | app: './src/main.js'
17 | },
18 | output: {
19 | path: config.build.assetsRoot,
20 | filename: '[name].js',
21 | publicPath: process.env.NODE_ENV === 'production'
22 | ? config.build.assetsPublicPath
23 | : config.dev.assetsPublicPath
24 | },
25 | resolve: {
26 | extensions: ['.js', '.vue', '.json'],
27 | alias: {
28 | 'vue$': 'vue/dist/vue.esm.js',
29 | '@': resolve('src'),
30 | }
31 | },
32 | module: {
33 | rules: [
34 | {
35 | test: /\.vue$/,
36 | loader: 'vue-loader',
37 | options: vueLoaderConfig
38 | },
39 | {
40 | test: /\.js$/,
41 | loader: 'babel-loader',
42 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
43 | },
44 | {
45 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
46 | loader: 'url-loader',
47 | options: {
48 | limit: 10000,
49 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
50 | }
51 | },
52 | {
53 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
54 | loader: 'url-loader',
55 | options: {
56 | limit: 10000,
57 | name: utils.assetsPath('media/[name].[hash:7].[ext]')
58 | }
59 | },
60 | {
61 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
62 | loader: 'url-loader',
63 | options: {
64 | limit: 10000,
65 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
66 | }
67 | }
68 | ]
69 | },
70 | node: {
71 | // prevent webpack from injecting useless setImmediate polyfill because Vue
72 | // source contains it (although only uses it if it's native).
73 | setImmediate: false,
74 | // prevent webpack from injecting mocks to Node native modules
75 | // that does not make sense for the client
76 | dgram: 'empty',
77 | fs: 'empty',
78 | net: 'empty',
79 | tls: 'empty',
80 | child_process: 'empty'
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const utils = require('./utils')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const path = require('path')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
11 | const portfinder = require('portfinder')
12 |
13 | const HOST = process.env.HOST
14 | const PORT = process.env.PORT && Number(process.env.PORT)
15 |
16 | const devWebpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
19 | },
20 | // cheap-module-eval-source-map is faster for development
21 | devtool: config.dev.devtool,
22 |
23 | // these devServer options should be customized in /config/index.js
24 | devServer: {
25 | clientLogLevel: 'warning',
26 | historyApiFallback: {
27 | rewrites: [
28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
29 | ],
30 | },
31 | hot: true,
32 | contentBase: false, // since we use CopyWebpackPlugin.
33 | compress: true,
34 | host: HOST || config.dev.host,
35 | port: PORT || config.dev.port,
36 | open: config.dev.autoOpenBrowser,
37 | overlay: config.dev.errorOverlay
38 | ? { warnings: false, errors: true }
39 | : false,
40 | publicPath: config.dev.assetsPublicPath,
41 | proxy: config.dev.proxyTable,
42 | quiet: true, // necessary for FriendlyErrorsPlugin
43 | watchOptions: {
44 | poll: config.dev.poll,
45 | }
46 | },
47 | plugins: [
48 | new webpack.DefinePlugin({
49 | 'process.env': require('../config/dev.env')
50 | }),
51 | new webpack.HotModuleReplacementPlugin(),
52 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
53 | new webpack.NoEmitOnErrorsPlugin(),
54 | // https://github.com/ampedandwired/html-webpack-plugin
55 | new HtmlWebpackPlugin({
56 | filename: 'index.html',
57 | template: 'index.html',
58 | inject: true
59 | }),
60 | // copy custom static assets
61 | new CopyWebpackPlugin([
62 | {
63 | from: path.resolve(__dirname, '../static'),
64 | to: config.dev.assetsSubDirectory,
65 | ignore: ['.*']
66 | }
67 | ])
68 | ]
69 | })
70 |
71 | module.exports = new Promise((resolve, reject) => {
72 | portfinder.basePort = process.env.PORT || config.dev.port
73 | portfinder.getPort((err, port) => {
74 | if (err) {
75 | reject(err)
76 | } else {
77 | // publish the new Port, necessary for e2e tests
78 | process.env.PORT = port
79 | // add port to devServer config
80 | devWebpackConfig.devServer.port = port
81 |
82 | // Add FriendlyErrorsPlugin
83 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
84 | compilationSuccessInfo: {
85 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
86 | },
87 | onErrors: config.dev.notifyOnErrors
88 | ? utils.createNotifierCallback()
89 | : undefined
90 | }))
91 |
92 | resolve(devWebpackConfig)
93 | }
94 | })
95 | })
96 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const path = require('path')
3 | const utils = require('./utils')
4 | const webpack = require('webpack')
5 | const config = require('../config')
6 | const merge = require('webpack-merge')
7 | const baseWebpackConfig = require('./webpack.base.conf')
8 | const CopyWebpackPlugin = require('copy-webpack-plugin')
9 | const HtmlWebpackPlugin = require('html-webpack-plugin')
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
13 |
14 | const env = require('../config/prod.env')
15 |
16 | const webpackConfig = merge(baseWebpackConfig, {
17 | module: {
18 | rules: utils.styleLoaders({
19 | sourceMap: config.build.productionSourceMap,
20 | extract: true,
21 | usePostCSS: true
22 | })
23 | },
24 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
25 | output: {
26 | path: config.build.assetsRoot,
27 | filename: utils.assetsPath('js/[name].[chunkhash].js'),
28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
29 | },
30 | plugins: [
31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html
32 | new webpack.DefinePlugin({
33 | 'process.env': env
34 | }),
35 | new UglifyJsPlugin({
36 | uglifyOptions: {
37 | compress: {
38 | warnings: false
39 | }
40 | },
41 | sourceMap: config.build.productionSourceMap,
42 | parallel: true
43 | }),
44 | // extract css into its own file
45 | new ExtractTextPlugin({
46 | filename: utils.assetsPath('css/[name].[contenthash].css'),
47 | // Setting the following option to `false` will not extract CSS from codesplit chunks.
48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack.
49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`,
50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110
51 | allChunks: true,
52 | }),
53 | // Compress extracted CSS. We are using this plugin so that possible
54 | // duplicated CSS from different components can be deduped.
55 | new OptimizeCSSPlugin({
56 | cssProcessorOptions: config.build.productionSourceMap ?
57 | {
58 | safe: true,
59 | map: {
60 | inline: false
61 | }
62 | } :
63 | {
64 | safe: true
65 | }
66 | }),
67 | // generate dist index.html with correct asset hash for caching.
68 | // you can customize output by editing /index.html
69 | // see https://github.com/ampedandwired/html-webpack-plugin
70 | new HtmlWebpackPlugin({
71 | filename: config.build.index,
72 | template: 'index.html',
73 | inject: true,
74 | minify: {
75 | removeComments: true,
76 | collapseWhitespace: true,
77 | removeAttributeQuotes: false
78 | // more options:
79 | // https://github.com/kangax/html-minifier#options-quick-reference
80 | },
81 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin
82 | chunksSortMode: 'dependency'
83 | }),
84 | // keep module.id stable when vendor modules does not change
85 | new webpack.HashedModuleIdsPlugin(),
86 | // enable scope hoisting
87 | new webpack.optimize.ModuleConcatenationPlugin(),
88 | // split vendor js into its own file
89 | new webpack.optimize.CommonsChunkPlugin({
90 | name: 'vendor',
91 | minChunks(module) {
92 | // any required modules inside node_modules are extracted to vendor
93 | return (
94 | module.resource &&
95 | /\.js$/.test(module.resource) &&
96 | module.resource.indexOf(
97 | path.join(__dirname, '../node_modules')
98 | ) === 0
99 | )
100 | }
101 | }),
102 | // extract webpack runtime and module manifest to its own file in order to
103 | // prevent vendor hash from being updated whenever app bundle is updated
104 | new webpack.optimize.CommonsChunkPlugin({
105 | name: 'manifest',
106 | minChunks: Infinity
107 | }),
108 | // This instance extracts shared chunks from code splitted chunks and bundles them
109 | // in a separate chunk, similar to the vendor chunk
110 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
111 | new webpack.optimize.CommonsChunkPlugin({
112 | name: 'app',
113 | async: 'vendor-async',
114 | children: true,
115 | minChunks: 3
116 | }),
117 |
118 | // copy custom static assets
119 | new CopyWebpackPlugin([{
120 | from: path.resolve(__dirname, '../static'),
121 | to: config.build.assetsSubDirectory,
122 | ignore: ['.*']
123 | }])
124 | ]
125 | })
126 |
127 | if (config.build.productionGzip) {
128 | const CompressionWebpackPlugin = require('compression-webpack-plugin')
129 |
130 | webpackConfig.plugins.push(
131 | new CompressionWebpackPlugin({
132 | asset: '[path].gz[query]',
133 | algorithm: 'gzip',
134 | test: new RegExp(
135 | '\\.(' +
136 | config.build.productionGzipExtensions.join('|') +
137 | ')$'
138 | ),
139 | threshold: 10240,
140 | minRatio: 0.8
141 | })
142 | )
143 | }
144 |
145 | if (config.build.bundleAnalyzerReport) {
146 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
147 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
148 | }
149 |
150 | module.exports = webpackConfig
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const merge = require('webpack-merge')
3 | const prodEnv = require('./prod.env')
4 |
5 | module.exports = merge(prodEnv, {
6 | NODE_ENV: '"development"'
7 | })
8 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | // Template version: 1.3.1
3 | // see http://vuejs-templates.github.io/webpack for documentation.
4 |
5 | const path = require('path')
6 |
7 | module.exports = {
8 | dev: {
9 |
10 | // Paths
11 | assetsSubDirectory: 'static',
12 | assetsPublicPath: '/',
13 | proxyTable: {},
14 |
15 | // Various Dev Server settings
16 | host: 'localhost', // can be overwritten by process.env.HOST
17 | port: 8081, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
18 | autoOpenBrowser: false,
19 | errorOverlay: true,
20 | notifyOnErrors: true,
21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
22 |
23 |
24 | /**
25 | * Source Maps
26 | */
27 |
28 | // https://webpack.js.org/configuration/devtool/#development
29 | devtool: 'cheap-module-eval-source-map',
30 |
31 | // If you have problems debugging vue-files in devtools,
32 | // set this to false - it *may* help
33 | // https://vue-loader.vuejs.org/en/options.html#cachebusting
34 | cacheBusting: true,
35 |
36 | cssSourceMap: true
37 | },
38 |
39 | build: {
40 | // Template for index.html
41 | index: path.resolve(__dirname, '../dist/index.html'),
42 |
43 | // Paths
44 | assetsRoot: path.resolve(__dirname, '../dist'),
45 | assetsSubDirectory: 'static',
46 | assetsPublicPath: '/airchat/dist',
47 |
48 | /**
49 | * Source Maps
50 | */
51 |
52 | productionSourceMap: true,
53 | // https://webpack.js.org/configuration/devtool/#production
54 | devtool: '#source-map',
55 |
56 | // Gzip off by default as many popular static hosts such as
57 | // Surge or Netlify already gzip all static assets for you.
58 | // Before setting to `true`, make sure to:
59 | // npm install --save-dev compression-webpack-plugin
60 | productionGzip: false,
61 | productionGzipExtensions: ['js', 'css'],
62 |
63 | // Run the build command with an extra argument to
64 | // View the bundle analyzer report after build finishes:
65 | // `npm run build --report`
66 | // Set to `true` or `false` to always turn it on or off
67 | bundleAnalyzerReport: process.env.npm_config_report
68 | }
69 | }
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | module.exports = {
3 | NODE_ENV: '"production"'
4 | }
5 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | airchat
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "airchat",
3 | "version": "1.0.0",
4 | "description": "easy to chat",
5 | "author": "hxvin ",
6 | "private": true,
7 | "scripts": {
8 | "init_sql": "node ./server/init/index.js",
9 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
10 | "start": "npm run dev",
11 | "build": "node build/build.js",
12 | "server-dev": "node server/index.js"
13 | },
14 | "dependencies": {
15 | "axios": "^0.19.0",
16 | "iscroll": "^5.2.0",
17 | "md5": "^2.2.1",
18 | "moment": "^2.18.1",
19 | "socket.io": "^2.0.4",
20 | "vue": "^2.4.2",
21 | "vue-router": "^2.7.0",
22 | "vuex": "^3.0.1"
23 | },
24 | "devDependencies": {
25 | "autoprefixer": "^7.1.2",
26 | "babel-core": "^6.22.1",
27 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
28 | "babel-loader": "^7.1.1",
29 | "babel-plugin-syntax-jsx": "^6.18.0",
30 | "babel-plugin-transform-runtime": "^6.22.0",
31 | "babel-plugin-transform-vue-jsx": "^3.5.0",
32 | "babel-preset-env": "^1.3.2",
33 | "babel-preset-stage-2": "^6.22.0",
34 | "chalk": "^2.0.1",
35 | "copy-webpack-plugin": "^4.0.1",
36 | "css-loader": "^0.28.0",
37 | "extract-text-webpack-plugin": "^3.0.0",
38 | "file-loader": "^1.1.4",
39 | "friendly-errors-webpack-plugin": "^1.6.1",
40 | "html-webpack-plugin": "^2.30.1",
41 | "node-notifier": "^5.1.2",
42 | "node-sass": "^4.12.0",
43 | "optimize-css-assets-webpack-plugin": "^3.2.0",
44 | "ora": "^1.2.0",
45 | "portfinder": "^1.0.13",
46 | "postcss-import": "^11.0.0",
47 | "postcss-loader": "^2.0.8",
48 | "postcss-url": "^7.2.1",
49 | "rimraf": "^2.6.0",
50 | "sass-loader": "^6.0.6",
51 | "semver": "^5.3.0",
52 | "shelljs": "^0.7.6",
53 | "uglifyjs-webpack-plugin": "^1.1.1",
54 | "url-loader": "^0.5.8",
55 | "vue-loader": "^13.3.0",
56 | "vue-style-loader": "^3.0.1",
57 | "vue-template-compiler": "^2.5.2",
58 | "webpack": "^3.6.0",
59 | "webpack-bundle-analyzer": "^2.9.0",
60 | "webpack-dev-server": "^2.11.5",
61 | "webpack-merge": "^4.1.0"
62 | },
63 | "engines": {
64 | "node": ">= 6.0.0",
65 | "npm": ">= 3.0.0"
66 | },
67 | "browserslist": [
68 | "> 1%",
69 | "last 2 versions",
70 | "not ie <= 8"
71 | ]
72 | }
73 |
--------------------------------------------------------------------------------
/server/config.js:
--------------------------------------------------------------------------------
1 | const db = {
2 | host: '127.0.0.1', // 数据库IP
3 | port: 3306, // 数据库端口
4 | database: 'airchat', // 数据库名称
5 | user: 'root', // 数据库用户名
6 | password: '123456', // 数据库密码
7 | }
8 | const baseApi = 'api/v1'
9 |
10 | const secret = 'airchat-sec'
11 |
12 | module.exports = {
13 | db,
14 | baseApi,
15 | secret
16 | }
--------------------------------------------------------------------------------
/server/controllers/groupChat.js:
--------------------------------------------------------------------------------
1 | const groupChatModel = require("../models/groupChat");
2 |
3 | /**
4 | * 获取群资料
5 | * @param groupMsg 群聊信息
6 | * @param groupInfo 群资料
7 | * @param message 消息
8 | * @param name 用户名
9 | * @param time 时间
10 | * @return
11 | */
12 |
13 | let getGroupInfo = async (ctx, next) => {
14 | try {
15 | const RowDataPacket = await groupChatModel.getGroupInfo([ctx.query.groupId, ctx.query.groupName]),
16 | groupInfo = JSON.parse(JSON.stringify(RowDataPacket));
17 | ctx.body = {
18 | success: true,
19 | data: {
20 | groupInfo: groupInfo
21 | }
22 | };
23 | } catch (error) {
24 | console.log(error);
25 | }
26 | };
27 |
28 |
29 | /**
30 | * 获取群聊相关内容
31 | * @param groupMsg 群聊信息
32 | * @param groupInfo 群资料
33 | * @param message 消息
34 | * @param name 用户名
35 | * @param time 时间
36 | * @return
37 | */
38 |
39 | let getGroupDetail = async (ctx, next) => {
40 | try {
41 | const groupId = ctx.query.groupId,
42 | RowDataPacket1 = await groupChatModel.getGroupMsg(groupId),
43 | RowDataPacket2 = await groupChatModel.getGroupInfo([groupId, null]),
44 | RowDataPacket3 = await groupChatModel.getGroupMember(groupId),
45 | groupMsg = JSON.parse(JSON.stringify(RowDataPacket1)),
46 | groupInfo = JSON.parse(JSON.stringify(RowDataPacket2)),
47 | groupMember = JSON.parse(JSON.stringify(RowDataPacket3));
48 | let newGroupMember = [];
49 | groupMember.forEach(element => {
50 | newGroupMember.push(element.group_member_id);
51 | });
52 | // console.log('newGroupMember',newGroupMember)
53 | ctx.body = {
54 | success: true,
55 | data: {
56 | groupMsg: groupMsg,
57 | groupInfo: groupInfo,
58 | groupMember: newGroupMember
59 | }
60 | };
61 | } catch (error) {
62 | console.log(error);
63 | }
64 | };
65 | /**
66 | * 存储群聊信息
67 | * @param userId 用户id
68 | * @param groupId 群id
69 | * @param message 消息
70 | * @param name 用户名
71 | * @param time 时间
72 | * @return
73 | */
74 | let saveGroupMsg = async (ctx, next) => {
75 | const userId = ctx.user_id,
76 | groupId = ctx.request.body.groupId,
77 | message = ctx.request.body.message,
78 | name = ctx.request.body.name,
79 | time = ctx.request.body.time;
80 | // console.log(userId,groupId,message,name,time)
81 | await groupChatModel.saveGroupMsg(userId, groupId, message, name, time)
82 | .then(result => {
83 | console.log("saveGroupMsg11", result);
84 | if (result) {
85 | ctx.body = {
86 | success: true
87 | };
88 | console.log("保存群消息成功");
89 | }
90 | })
91 | .catch(err => {
92 | console.log(err);
93 | });
94 | };
95 | /**
96 | * 群添加成员并返回群成员
97 | * @param userId 用户id
98 | * @param groupId 群id
99 | * @return 群成员
100 | */
101 | let addGroupUserRelation = async (ctx, next) => {
102 | const userId = ctx.user_id,
103 | groupId = ctx.request.body.groupId;
104 | await groupChatModel.addGroupUserRelation(userId, groupId);
105 | const RowDataPacket = await groupChatModel.getGroupMember(groupId),
106 | groupMember = JSON.parse(JSON.stringify(RowDataPacket));
107 | let newGroupMember = [];
108 | groupMember.forEach(element => {
109 | newGroupMember.push(element.group_member_id);
110 | });
111 |
112 | ctx.body = {
113 | success: true,
114 | data: {
115 | groupMember: newGroupMember
116 | }
117 | };
118 | console.log("添加群成员成功");
119 | };
120 |
121 | module.exports = {
122 | getGroupInfo,
123 | getGroupDetail,
124 | saveGroupMsg,
125 | addGroupUserRelation
126 | };
--------------------------------------------------------------------------------
/server/controllers/groupInfo.js:
--------------------------------------------------------------------------------
1 | const groupInfo = require("../models/groupInfo");
2 | const uuidv1 = require('uuid/v1');
3 |
4 | /**
5 | * 加入群
6 | * @param user_id 用户id
7 | * @param group_id 群id
8 | * @return
9 | */
10 |
11 | let joinGroup = async (ctx, next) => {
12 | await groupInfo.joinGroup(ctx.user_id, ctx.request.body.group_id)
13 | .then((res) => {
14 | ctx.body = {
15 | success: true
16 | };
17 | console.log("加入群成功");
18 | });
19 | };
20 |
21 | /**
22 | * 查看某个用户是否在某个群中(根据返回的数组长度是不是为零就知道)
23 | * @param user_id 用户id
24 | * @param group_id 群id
25 | * @return
26 | */
27 | let isInGroup = async (ctx, next) => {
28 | const RowDataPacket = await groupInfo.isInGroup(
29 | ctx.user_id,
30 | ctx.query.group_id
31 | ),
32 | group_user = JSON.parse(JSON.stringify(RowDataPacket));
33 | ctx.body = {
34 | success: true,
35 | data: {
36 | group_user: group_user
37 | }
38 | };
39 | };
40 |
41 | /**
42 | * [createGroup 建群]
43 | * @param {[type]} ctx [群名,群公告,群头像,创建人,创建时间]
44 | * @param {Function} next [description]
45 | * @return {Promise} [description]
46 | */
47 | let createGroup = async (ctx, next) => {
48 | const uuid = uuidv1();
49 | console.log('uuid', uuid)
50 | const arr = [uuid, ctx.request.body.group_name, ctx.request.body.group_notice, ctx.request.body.group_avator, ctx.name, ctx.request.body.creater_time];
51 | await groupInfo.createGroup(arr);
52 | ctx.body = {
53 | success: true,
54 | data: {
55 | group_id: uuid
56 | }
57 | };
58 | };
59 |
60 | /**
61 | * [delGroup 退出群]
62 | * @param {[type]} ctx [用户id,群id]
63 | * @param {Function} next [description]
64 | * @return {Promise} [success: true]
65 | */
66 | let exitGroup = async (ctx, next) => {
67 | await groupInfo.exitGroup(ctx.user_id, ctx.query.group_id);
68 | ctx.body = {
69 | success: true
70 | };
71 | console.log('退群成功')
72 | };
73 |
74 | module.exports = {
75 | joinGroup,
76 | isInGroup,
77 | createGroup,
78 | exitGroup
79 | };
--------------------------------------------------------------------------------
/server/controllers/login.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 | const secret = require("../config").secret;
3 | const userModel = require("../models/user_info");
4 | const md5 = require("md5");
5 | module.exports = async (ctx, next) => {
6 | let name = ctx.request.body.name || "",
7 | password = ctx.request.body.password || "";
8 | if (name === "" || password === "") {
9 | ctx.body = {
10 | success: false,
11 | message: "用户名或密码不能为空"
12 | };
13 | return;
14 | }
15 | const RowDataPacket = await userModel.findDataByName(name);
16 | const res = JSON.parse(JSON.stringify(RowDataPacket));
17 | if (res.length > 0) {
18 | // 验证成功后,服务端会签发一个 Token,再把这个 Token 发送给客户端
19 | if (md5(password) === res[0]["password"]) {
20 | const payload = { //payload
21 | name: name,
22 | id: res[0]["id"]
23 | };
24 | const token = jwt.sign(payload, secret, {
25 | expiresIn: Math.floor(Date.now() / 1000) + 24 * 60 * 60 // 一天
26 | });
27 | ctx.body = {
28 | success: true,
29 | message: "登录成功",
30 | token: token,
31 | userInfo: {
32 | name: res[0]["name"],
33 | user_id: res[0]["id"],
34 | sex: res[0]["sex"],
35 | website: res[0]["website"],
36 | github: res[0]["github"],
37 | intro: res[0]["intro"],
38 | avator: res[0]["avator"],
39 | place: res[0]["place"],
40 | socketId: res[0]["socketid"]
41 | }
42 | };
43 | } else {
44 | ctx.body = {
45 | success: false,
46 | message: "密码错误"
47 | };
48 | }
49 | } else {
50 | ctx.body = {
51 | success: false,
52 | message: "用户名错误"
53 | };
54 | }
55 | };
--------------------------------------------------------------------------------
/server/controllers/message.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 | const secret = require("../config").secret;
3 | const msgModel = require("../models/message");
4 |
5 | module.exports = async (ctx, next) => {
6 | try {
7 | const res1 = await msgModel.getPrivateList(ctx.user_id);
8 | const privateList = JSON.parse(JSON.stringify(res1));
9 | const res2 = await msgModel.getGroupList(ctx.user_id);
10 | const groupList = JSON.parse(JSON.stringify(res2));
11 | ctx.body = {
12 | success: true,
13 | data: {
14 | privateList: privateList,
15 | groupList: groupList
16 | }
17 | };
18 | } catch (error) {
19 | console.log(error);
20 | }
21 | };
--------------------------------------------------------------------------------
/server/controllers/newFriends.js:
--------------------------------------------------------------------------------
1 | const newFriendsModel = require("../models/newFriends");
2 |
3 | /**
4 | * 获取我的新好友通知
5 | * @param user_id 我的id
6 | * @return
7 | */
8 |
9 | let getnewFriends = async (ctx, next) => {
10 | const RowDataPacket = await newFriendsModel.getnewFriends(ctx.user_id),
11 | newFriends = JSON.parse(JSON.stringify(RowDataPacket));
12 | ctx.body = {
13 | success: true,
14 | data: {
15 | newFriends: newFriends
16 | }
17 | };
18 | };
19 |
20 | /**
21 | * 添加我的新好友通知
22 | * @param
23 | * @return
24 | */
25 |
26 | let insertNewFriends = async (ctx, next) => {
27 | const arr = [ctx.user_id, ctx.request.body.to_user, ctx.request.body.content, ctx.request.body.time, ctx.request.body.status];
28 | await newFriendsModel.insertNewFriends(arr).then(result => {
29 | ctx.body = {
30 | success: true
31 | };
32 | }).catch(err => {
33 | console.log(err);
34 | });
35 | };
36 |
37 | /**
38 | * 更新我的新好友通知状态
39 | * @param
40 | * @return
41 | */
42 |
43 | let updateNewFriends = async (ctx, next) => {
44 | await newFriendsModel.updateNewFriends(ctx.request.body.from_user, ctx.user_id).then(result => {
45 | console.log('updateNewFriends更新我的新好友通知状态成功')
46 | ctx.body = {
47 | success: true
48 | };
49 | }).catch(err => {
50 | console.log(err);
51 | });
52 | };
53 |
54 | module.exports = {
55 | getnewFriends,
56 | insertNewFriends,
57 | updateNewFriends
58 | };
--------------------------------------------------------------------------------
/server/controllers/privateChat.js:
--------------------------------------------------------------------------------
1 | const privateChatModel = require("../models/privateChat");
2 |
3 | /**
4 | * 获取私聊相关内容
5 | * @param to_user 信息发送者的id
6 | * @param from_user 信息接收者的id
7 | * @return from_user 此条信息的发送者
8 | * message 私聊信息
9 | * time 时间
10 | * avator 发送者的头像
11 | * sex 发送者的性别
12 | * place 发送者居住地
13 | * status 发送者的是否在线
14 | */
15 |
16 | let getprivateDetail = async (ctx, next) => {
17 | const to_user = ctx.query.to_user,
18 | from_user = ctx.user_id,
19 | RowDataPacket = await privateChatModel.getPrivateDetail(from_user, to_user),
20 | privateDetail = JSON.parse(JSON.stringify(RowDataPacket));
21 | ctx.body = {
22 | success: true,
23 | data: {
24 | privateDetail: privateDetail
25 | }
26 | };
27 | }
28 |
29 |
30 | /**
31 | * 存储私聊聊信息
32 | * @param to_user 信息发送者的id
33 | * @param from_user 信息接收者的id
34 | * @param message 消息
35 | * @param name 用户名
36 | * @param time 时间
37 | * @return
38 | */
39 | let savePrivateMsg = async (ctx, next) => {
40 | const from_user = ctx.user_id,
41 | to_user = ctx.request.body.to_user,
42 | message = ctx.request.body.message,
43 | name = ctx.request.body.name,
44 | time = ctx.request.body.time;
45 | await privateChatModel.savePrivateMsg(from_user, to_user, message, name, time)
46 | .then(result => {
47 | console.log("privateChatModel11", result);
48 | if (result) {
49 | ctx.body = {
50 | success: true
51 | };
52 | console.log("保存私聊消息成功");
53 | }
54 | })
55 | .catch(err => {
56 | console.log(err);
57 | });
58 | };
59 |
60 | module.exports = {
61 | getprivateDetail,
62 | savePrivateMsg
63 | };
--------------------------------------------------------------------------------
/server/controllers/register.js:
--------------------------------------------------------------------------------
1 | let userModel = require("../models/user_info");
2 | let md5 = require("md5");
3 |
4 | module.exports = async (ctx, next) => {
5 | console.log("register");
6 | var user = {
7 | name: ctx.request.body.name,
8 | password: ctx.request.body.password
9 | };
10 |
11 | await userModel.findDataByName(user.name).then(result => {
12 | console.log(result);
13 | if (result.length) {
14 | ctx.body = {
15 | success: false,
16 | message: "用户名已存在"
17 | };
18 | } else {
19 | ctx.body = {
20 | success: true,
21 | message: "注册成功!"
22 | };
23 | console.log("注册成功");
24 | userModel.insertData([
25 | ctx.request.body.name,
26 | md5(ctx.request.body.password)
27 | ]);
28 | }
29 | });
30 | };
31 |
--------------------------------------------------------------------------------
/server/controllers/robot.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 | const secret = require("../config").secret;
3 | const request = require("request-promise");
4 |
5 | module.exports = async (ctx, next) => {
6 | const auth = ctx.get("Authorization");
7 | const token = auth.split(" ")[1];
8 | const payload = jwt.verify(token, secret);
9 | const date = {
10 | key: "92febb91673740c2814911a6c16dbcc5",
11 | info: "" + ctx.query.message,
12 | userid: "" + payload.id
13 | };
14 |
15 | const options = {
16 | method: "POST",
17 | uri: "http://www.tuling123.com/openapi/api",
18 | body: date,
19 | json: true // Automatically stringifies the body to JSON
20 | };
21 |
22 | const response = await request(options);
23 | console.log(response);
24 | ctx.body = {
25 | data: response
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/server/controllers/userInfo.js:
--------------------------------------------------------------------------------
1 | const userModel = require("../models/user_info");
2 |
3 | /**
4 | * 通过user_id获取用户信息 (不包括密码)
5 | * @param
6 | * @return 用户名,性别,头像,最后登录时间,状态等
7 | */
8 |
9 | let getUserInfo = async (ctx, next) => {
10 | const RowDataPacket = await userModel.getUserInfo(ctx.query.user_id),
11 | userInfo = JSON.parse(JSON.stringify(RowDataPacket));
12 | ctx.body = {
13 | success: true,
14 | data: {
15 | userInfo: userInfo
16 | }
17 | };
18 | };
19 |
20 | /**
21 | * 通过用户名获取用户信息 (不包括密码)
22 | * @param
23 | * @return id,用户名,性别,头像,地方,github
24 | */
25 |
26 | let findUIByName = async (ctx, next) => {
27 | const RowDataPacket = await userModel.findUIByName(ctx.query.name),
28 | userInfo = JSON.parse(JSON.stringify(RowDataPacket));
29 | ctx.body = {
30 | success: true,
31 | data: {
32 | userInfo: userInfo
33 | }
34 | };
35 | };
36 |
37 | /**
38 | * 通过要查看的用户id 查询是否是本机用户的好友
39 | * @param user_id other_user_id
40 | * @return 如果是 返回 user_id other_user_id 和 remark 备注
41 | * 否则返回空
42 | */
43 |
44 | let isFriend = async (ctx, next) => {
45 | const RowDataPacket1 = await userModel.isFriend(
46 | ctx.user_id,
47 | ctx.query.other_user_id
48 | ),
49 | RowDataPacket2 = await userModel.isFriend(
50 | ctx.query.other_user_id,
51 | ctx.user_id
52 | ),
53 | isMyFriend = JSON.parse(JSON.stringify(RowDataPacket1)),
54 | isHisFriend = JSON.parse(JSON.stringify(RowDataPacket2));
55 | ctx.body = {
56 | success: true,
57 | data: {
58 | isMyFriend: isMyFriend,
59 | isHisFriend: isHisFriend
60 | }
61 | };
62 | };
63 |
64 | /**
65 | * 加为好友
66 | * @param user_id 本机用户
67 | * other_user_id 本机用户的朋友(对方)
68 | * @return
69 | *
70 | */
71 | let agreeBeFriend = async (ctx, next) => {
72 | const RowDataPacket1 = await userModel.isFriend(
73 | ctx.user_id,
74 | ctx.request.body.other_user_id
75 | ),
76 | RowDataPacket2 = await userModel.isFriend(
77 | ctx.request.body.other_user_id,
78 | ctx.user_id
79 | ),
80 | isMyFriend = JSON.parse(JSON.stringify(RowDataPacket1)),
81 | isHisFriend = JSON.parse(JSON.stringify(RowDataPacket2));
82 | console.log("isMyFriend", isMyFriend);
83 | console.log("isHisFriend", isHisFriend);
84 | //变成本机用户的朋友
85 | if (isMyFriend.length === 0) {
86 | await userModel.addAsFriend(
87 | ctx.user_id,
88 | ctx.request.body.other_user_id,
89 | ctx.request.body.time
90 | );
91 | }
92 | //本机用户变成ta的朋友
93 | if (isHisFriend.length === 0) {
94 | await userModel.addAsFriend(
95 | ctx.request.body.other_user_id,
96 | ctx.user_id,
97 | ctx.request.body.time
98 | );
99 | }
100 | ctx.body = {
101 | success: true
102 | };
103 | console.log("添加好友成功");
104 | };
105 |
106 | /**
107 | * 删除好友
108 | * @param user_id 本机用户id
109 | * other_user_id 对方id
110 | * @return
111 | */
112 | let delFriend = async (ctx, next) => {
113 | await userModel.delFriend(ctx.user_id, ctx.query.other_user_id)
114 | .then(result => {
115 | if (result) {
116 | ctx.body = {
117 | success: true
118 | };
119 | console.log("删除好友成功");
120 | }
121 | })
122 | .catch(err => {
123 | console.log(err);
124 | });
125 | };
126 |
127 | /**
128 | * 屏蔽好友
129 | * @param status 0为不屏蔽 1为屏蔽
130 | * user_id 本机用户id
131 | * other_user_id 对方id
132 | * @return
133 | */
134 | let shieldFriend = async (ctx, next) => {
135 | await userModel.delFriend(
136 | ctx.request.body.status,
137 | ctx.request.body.user_id,
138 | ctx.request.body.other_user_id
139 | ).then(result => {
140 | console.log("shieldFriend", result);
141 | if (result) {
142 | ctx.body = {
143 | success: true
144 | };
145 | console.log("(取消)屏蔽好友成功");
146 | }
147 | })
148 | .catch(err => {
149 | console.log(err);
150 | });
151 | };
152 |
153 | /**
154 | * 修改备注
155 | * @param remark 备注
156 | * user_id 本机用户id
157 | * other_user_id 对方id
158 | * @return
159 | */
160 | let editorRemark = async (ctx, next) => {
161 | await userModel.editorRemark(
162 | ctx.request.body.remark,
163 | ctx.user_id,
164 | ctx.request.body.other_user_id
165 | ).then(result => {
166 | console.log("editorRemark", result);
167 | if (result) {
168 | ctx.body = {
169 | success: true
170 | };
171 | console.log("修改备注成功");
172 | }
173 | })
174 | .catch(err => {
175 | console.log(err);
176 | });
177 | };
178 |
179 | /**
180 | * 修改备注
181 | * @param github github
182 | * website website
183 | * sex 性别
184 | * place 来自哪里
185 | * user_id 本机用户id
186 | * @return
187 | */
188 | let editorInfo = async (ctx, next) => {
189 | const data = [ctx.request.body.github, ctx.request.body.website, ctx.request.body.sex, ctx.request.body.place, ctx.user_id]
190 | console.log('editorInfo', data)
191 | await userModel.editorInfo(data).then(result => {
192 | console.log("editorInfo", result);
193 | if (result) {
194 | ctx.body = {
195 | success: true
196 | };
197 | console.log("修改个人信息成功");
198 | }
199 | })
200 | .catch(err => {
201 | console.log(err);
202 | });
203 | };
204 |
205 | module.exports = {
206 | getUserInfo,
207 | findUIByName,
208 | isFriend,
209 | agreeBeFriend,
210 | delFriend,
211 | shieldFriend,
212 | editorRemark,
213 | editorInfo
214 | };
--------------------------------------------------------------------------------
/server/gulpfile.js:
--------------------------------------------------------------------------------
1 | let gulp = require('gulp')
2 | let nodemon = require('gulp-nodemon')
3 |
4 | // nodemon 修改服务端代码自动重启
5 | gulp.task('start', function () {
6 | nodemon({
7 | script: 'index.js'
8 | , ext: 'js html'
9 | , env: { 'NODE_ENV': 'development' }
10 | })
11 | })
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const Koa = require("koa");
2 | const bodyParser = require("koa-bodyparser");
3 | const cors = require("@koa/cors");
4 | const router = require("./routes/index");
5 | const { query } = require("./utils/db");
6 | const socketModel = require("./models/soketHander");
7 | const app = new Koa();
8 |
9 | app.use(cors());
10 |
11 | const server = require("http").createServer(app.callback());
12 | const io = require("socket.io")(server);
13 |
14 | app.use(bodyParser());
15 |
16 | app.use(router.routes()).use(router.allowedMethods());
17 |
18 | global.query = query;
19 |
20 | io.on("connection", socket => {
21 | const socketId = socket.id;
22 | //登录
23 | socket.on("login", async userId => {
24 | await socketModel.saveUserSocketId(userId, socketId);
25 | });
26 | // 更新soketId
27 | socket.on("update", async userId => {
28 | await socketModel.saveUserSocketId(userId, socketId);
29 | });
30 | //私聊
31 | socket.on("sendPrivateMsg", async data => {
32 | const arr = await socketModel.getUserSocketId(data.to_user);
33 | const RowDataPacket = arr[0];
34 | const socketid = JSON.parse(JSON.stringify(RowDataPacket)).socketid;
35 | io.to(socketid).emit("getPrivateMsg", data);
36 | });
37 | // 群聊
38 | socket.on("sendGroupMsg", async data => {
39 | io.sockets.emit("getGroupMsg", data);
40 | });
41 |
42 | //加好友请求
43 | socket.on("sendRequest", async data => {
44 | console.log("sendRequest", data);
45 | const arr = await socketModel.getUserSocketId(data.to_user);
46 | const RowDataPacket = arr[0];
47 | const socketid = JSON.parse(JSON.stringify(RowDataPacket)).socketid;
48 | console.log("给谁的socketid", socketid);
49 | io.to(socketid).emit("getresponse", data);
50 | });
51 |
52 | socket.on("disconnect", data => {
53 | console.log("disconnect", data);
54 | });
55 | });
56 |
57 | server.listen(3000);
58 | console.log("服务器已启动,端口3000");
59 |
--------------------------------------------------------------------------------
/server/init/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const getSqlContentMap = require('./util/get-sql-content-map');
3 | const { query } = require('../utils/db');
4 |
5 |
6 | // 打印脚本执行日志
7 | const eventLog = function( err , sqlFile, index ) {
8 | if( err ) {
9 | console.log(`[ERROR] sql脚本文件: ${sqlFile} 第${index + 1}条脚本 执行失败 o(╯□╰)o !`)
10 | } else {
11 | console.log(`[SUCCESS] sql脚本文件: ${sqlFile} 第${index + 1}条脚本 执行成功 O(∩_∩)O !`)
12 | }
13 | }
14 |
15 | // 获取所有sql脚本内容
16 | let sqlContentMap = getSqlContentMap()
17 |
18 | // 执行建表sql脚本
19 | const createAllTables = async () => {
20 | for( let key in sqlContentMap ) {
21 | let sqlShell = sqlContentMap[key]
22 | let sqlShellList = sqlShell.split(';')
23 |
24 | for ( let [ i, shell ] of sqlShellList.entries() ) {
25 | if ( shell.trim() ) {
26 | let result = await query( shell )
27 | if ( result.serverStatus * 1 === 2 ) {
28 | eventLog( null, key, i)
29 | } else {
30 | eventLog( true, key, i)
31 | }
32 | }
33 | }
34 | }
35 | console.log('sql脚本执行结束!')
36 | console.log('请按 ctrl + c 键退出!')
37 |
38 | }
39 |
40 | createAllTables();
--------------------------------------------------------------------------------
/server/init/sql/airchat.sql:
--------------------------------------------------------------------------------
1 | # ************************************************************
2 | # Sequel Pro SQL dump
3 | # Version 4541
4 | #
5 | # http://www.sequelpro.com/
6 | # https://github.com/sequelpro/sequelpro
7 | #
8 | # Host: localhost (MySQL 5.6.35)
9 | # Database: airchat
10 | # Generation Time: 2018-02-23 10:14:21 +0000
11 | # ************************************************************
12 |
13 |
14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
17 | /*!40101 SET NAMES utf8 */;
18 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
19 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
20 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
21 |
22 |
23 | # Dump of table group_info
24 | # ------------------------------------------------------------
25 |
26 | DROP TABLE IF EXISTS `group_info`;
27 |
28 | CREATE TABLE `group_info` (
29 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '群id',
30 | `group_id` char(100) NOT NULL,
31 | `group_name` varchar(20) NOT NULL DEFAULT '交流群' COMMENT '群名称',
32 | `group_notice` varchar(100) NOT NULL DEFAULT '欢迎大家入群交流~' COMMENT '群公告',
33 | `group_avator` varchar(50) NOT NULL DEFAULT 'https://avatars2.githubusercontent.com/u/24861316?s=460&v=4' COMMENT '群头像',
34 | `group_creater` varchar(10) NOT NULL DEFAULT '' COMMENT '群创建人',
35 | `creater_time` int(11) NOT NULL COMMENT '群创建时间',
36 | PRIMARY KEY (`id`)
37 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
38 |
39 | LOCK TABLES `group_info` WRITE;
40 | /*!40000 ALTER TABLE `group_info` DISABLE KEYS */;
41 |
42 | INSERT INTO `group_info` (`id`, `group_id`, `group_name`, `group_notice`, `group_avator`, `group_creater`, `creater_time`)
43 | VALUES
44 | (1,'8eeccfc0-0f1e-11e8-892e-5ba8fc68dc36','交流群','交流群','https://avatars2.githubusercontent.com/u/24861316?s=460&v=4','罗宾',1518348455);
45 |
46 | /*!40000 ALTER TABLE `group_info` ENABLE KEYS */;
47 | UNLOCK TABLES;
48 |
49 |
50 | # Dump of table group_msg
51 | # ------------------------------------------------------------
52 |
53 | DROP TABLE IF EXISTS `group_msg`;
54 |
55 | CREATE TABLE `group_msg` (
56 | `id` int(11) NOT NULL AUTO_INCREMENT,
57 | `from_user` int(11) NOT NULL COMMENT '谁发的',
58 | `to_group` char(100) NOT NULL DEFAULT '' COMMENT '群id',
59 | `message` text NOT NULL COMMENT '聊天信息',
60 | `time` int(11) NOT NULL COMMENT '发送时间',
61 | PRIMARY KEY (`id`),
62 | KEY `to_group` (`to_group`)
63 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
64 |
65 | LOCK TABLES `group_msg` WRITE;
66 | /*!40000 ALTER TABLE `group_msg` DISABLE KEYS */;
67 |
68 | INSERT INTO `group_msg` (`id`, `from_user`, `to_group`, `message`, `time`)
69 | VALUES
70 | (1,1,'8eeccfc0-0f1e-11e8-892e-5ba8fc68dc36','路飞 : 有人不?',1518348482),
71 | (2,14,'8eeccfc0-0f1e-11e8-892e-5ba8fc68dc36','罗宾 : 有呀 我呀',1518348493);
72 |
73 | /*!40000 ALTER TABLE `group_msg` ENABLE KEYS */;
74 | UNLOCK TABLES;
75 |
76 |
77 | # Dump of table group_user_relation
78 | # ------------------------------------------------------------
79 |
80 | DROP TABLE IF EXISTS `group_user_relation`;
81 |
82 | CREATE TABLE `group_user_relation` (
83 | `id` int(11) NOT NULL AUTO_INCREMENT,
84 | `group_id` char(100) NOT NULL DEFAULT '',
85 | `user_id` int(11) NOT NULL,
86 | PRIMARY KEY (`id`)
87 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
88 |
89 | LOCK TABLES `group_user_relation` WRITE;
90 | /*!40000 ALTER TABLE `group_user_relation` DISABLE KEYS */;
91 |
92 | INSERT INTO `group_user_relation` (`id`, `group_id`, `user_id`)
93 | VALUES
94 | (2,'8eeccfc0-0f1e-11e8-892e-5ba8fc68dc36',1),
95 | (3,'8eeccfc0-0f1e-11e8-892e-5ba8fc68dc36',14);
96 |
97 | /*!40000 ALTER TABLE `group_user_relation` ENABLE KEYS */;
98 | UNLOCK TABLES;
99 |
100 |
101 | # Dump of table new_friends
102 | # ------------------------------------------------------------
103 |
104 | DROP TABLE IF EXISTS `new_friends`;
105 |
106 | CREATE TABLE `new_friends` (
107 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
108 | `from_user` int(11) NOT NULL COMMENT '主动加方',
109 | `to_user` int(11) NOT NULL COMMENT '被加方',
110 | `content` varchar(50) NOT NULL DEFAULT '' COMMENT '加好友验证内容',
111 | `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '1同意,0未同意',
112 | `time` int(11) NOT NULL,
113 | PRIMARY KEY (`id`)
114 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
115 |
116 | LOCK TABLES `new_friends` WRITE;
117 | /*!40000 ALTER TABLE `new_friends` DISABLE KEYS */;
118 |
119 | INSERT INTO `new_friends` (`id`, `from_user`, `to_user`, `content`, `status`, `time`)
120 | VALUES
121 | (1,14,1,'您好,我想加您为好友',1,1518348301),
122 | (2,1,14,'咋把我删了呢',1,1518349582),
123 | (3,1,14,'您好,我想加您为好友',1,1518350494),
124 | (4,1,14,'您好,我想加您为好友',1,1518350921),
125 | (5,1,14,'咋把我删了呢,重新加一下',1,1518351200),
126 | (6,14,1,'您好,我想加您为好友',1,1518485362),
127 | (7,14,1,'您好,我想加您为好友',1,1518745791);
128 |
129 | /*!40000 ALTER TABLE `new_friends` ENABLE KEYS */;
130 | UNLOCK TABLES;
131 |
132 |
133 | # Dump of table private__msg
134 | # ------------------------------------------------------------
135 |
136 | DROP TABLE IF EXISTS `private__msg`;
137 |
138 | CREATE TABLE `private__msg` (
139 | `id` int(11) NOT NULL AUTO_INCREMENT,
140 | `from_user` int(11) NOT NULL COMMENT '谁发的',
141 | `to_user` int(11) NOT NULL COMMENT '发给谁',
142 | `message` text NOT NULL COMMENT '聊天信息',
143 | `time` int(11) NOT NULL COMMENT '发送时间',
144 | PRIMARY KEY (`id`),
145 | KEY `from_user` (`from_user`),
146 | KEY `to_user` (`to_user`)
147 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
148 |
149 | LOCK TABLES `private__msg` WRITE;
150 | /*!40000 ALTER TABLE `private__msg` DISABLE KEYS */;
151 |
152 | INSERT INTO `private__msg` (`id`, `from_user`, `to_user`, `message`, `time`)
153 | VALUES
154 | (1,1,14,'路飞 : 你好罗宾',1518348322),
155 | (2,14,1,'罗宾 : 你好呀路飞',1518348331),
156 | (3,14,1,'罗宾 : 我建个群去 ,你待会加哈 叫 交流群',1518348364),
157 | (6,1,14,'路飞 : 咋把我删了呢,重新加一下',1518351218),
158 | (7,14,1,'罗宾 : 额 误删.',1518351226),
159 | (8,14,1,'罗宾 : 在么',1519377863);
160 |
161 | /*!40000 ALTER TABLE `private__msg` ENABLE KEYS */;
162 | UNLOCK TABLES;
163 |
164 |
165 | # Dump of table user_info
166 | # ------------------------------------------------------------
167 |
168 | DROP TABLE IF EXISTS `user_info`;
169 |
170 | CREATE TABLE `user_info` (
171 | `id` int(11) NOT NULL AUTO_INCREMENT,
172 | `name` varchar(20) NOT NULL DEFAULT 'NOT NULL' COMMENT '用户名',
173 | `password` varchar(40) NOT NULL DEFAULT 'NOT NULL' COMMENT '密码',
174 | `sex` varchar(2) NOT NULL DEFAULT '0' COMMENT '性别',
175 | `avator` varchar(100) NOT NULL DEFAULT 'https://avatars2.githubusercontent.com/u/24861316?s=460&v=4' COMMENT '头像',
176 | `place` varchar(50) DEFAULT NULL COMMENT '来自哪里',
177 | `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '在线状态,0离线,1在线',
178 | `socketid` varchar(20) NOT NULL DEFAULT '' COMMENT '登陆时的socketid',
179 | `website` varchar(50) DEFAULT NULL COMMENT '个人网站',
180 | `github` varchar(50) DEFAULT NULL,
181 | `intro` varchar(20) DEFAULT NULL,
182 | PRIMARY KEY (`id`)
183 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
184 |
185 | LOCK TABLES `user_info` WRITE;
186 | /*!40000 ALTER TABLE `user_info` DISABLE KEYS */;
187 |
188 | INSERT INTO `user_info` (`id`, `name`, `password`, `sex`, `avator`, `place`, `status`, `socketid`, `website`, `github`, `intro`)
189 | VALUES
190 | (1,'路飞','6512bd43d9caa6e02c990b0a82652dca',0,'https://avatars2.githubusercontent.com/u/24861316?s=460&v=4','厦门',0,'tIckUQsrpFm_Wki0AAAF','','https://github.com/Hxvin',NULL),
191 | (2,'索隆','b6d767d2f8ed5d21a44b0e5886680cb9',1,'https://avatars2.githubusercontent.com/u/24861316?s=460&v=4','深圳',0,'l12EoQ8PbnmvupNQAAAP',NULL,'',NULL),
192 | (3,'乔治','182be0c5cdcd5072bb1864cdee4d3d6e',1,'https://avatars2.githubusercontent.com/u/24861316?s=460&v=4','杭州',0,'ue0dCyN0zAyJurW-AABQ',NULL,NULL,NULL),
193 | (4,'罗','f7177163c833dff4b38fc8d2872f1ec6',1,'https://avatars2.githubusercontent.com/u/24861316?s=460&v=4','',0,'67kamGg8ibMLEjpZAAAD',NULL,NULL,NULL),
194 | (14,'罗宾','b6d767d2f8ed5d21a44b0e5886680cb9',1,'https://avatars2.githubusercontent.com/u/24861316?s=460&v=4',NULL,0,'9sictmm25dBk8tj2AAAC',NULL,NULL,NULL);
195 |
196 | /*!40000 ALTER TABLE `user_info` ENABLE KEYS */;
197 | UNLOCK TABLES;
198 |
199 |
200 | # Dump of table user_user_relation
201 | # ------------------------------------------------------------
202 |
203 | DROP TABLE IF EXISTS `user_user_relation`;
204 |
205 | CREATE TABLE `user_user_relation` (
206 | `id` int(11) NOT NULL AUTO_INCREMENT,
207 | `user_id` int(11) NOT NULL COMMENT '用户',
208 | `other_user_id` int(11) NOT NULL COMMENT '用户的朋友',
209 | `remark` varchar(10) DEFAULT '' COMMENT '备注',
210 | `shield` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0不屏蔽 1屏蔽',
211 | `time` int(11) NOT NULL,
212 | PRIMARY KEY (`id`)
213 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
214 |
215 | LOCK TABLES `user_user_relation` WRITE;
216 | /*!40000 ALTER TABLE `user_user_relation` DISABLE KEYS */;
217 |
218 | INSERT INTO `user_user_relation` (`id`, `user_id`, `other_user_id`, `remark`, `shield`, `time`)
219 | VALUES
220 | (1,1,14,'网友',0,1518348308),
221 | (2,14,1,'23333',0,1518745801);
222 |
223 | /*!40000 ALTER TABLE `user_user_relation` ENABLE KEYS */;
224 | UNLOCK TABLES;
225 |
226 |
227 |
228 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
229 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
230 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
231 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
232 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
233 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
234 |
--------------------------------------------------------------------------------
/server/init/util/get-sql-content-map.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const getSqlMap = require('./get-sql-map')
3 |
4 | let sqlContentMap = {}
5 |
6 | /**
7 | * 读取sql文件内容
8 | * @param {string} fileName 文件名称
9 | * @param {string} path 文件所在的路径
10 | * @return {string} 脚本文件内容
11 | */
12 | function getSqlContent( fileName, path ) {
13 | let content = fs.readFileSync( path, 'binary' )
14 | sqlContentMap[ fileName ] = content
15 | }
16 |
17 | /**
18 | * 封装所有sql文件脚本内容
19 | * @return {object}
20 | */
21 | function getSqlContentMap () {
22 | let sqlMap = getSqlMap()
23 | for( let key in sqlMap ) {
24 | getSqlContent( key, sqlMap[key] )
25 | }
26 |
27 | return sqlContentMap
28 | }
29 |
30 | module.exports = getSqlContentMap
--------------------------------------------------------------------------------
/server/init/util/get-sql-map.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const walkFile = require('./walk-file')
3 |
4 | /**
5 | * 获取sql目录下的文件目录数据
6 | * @return {object}
7 | */
8 | function getSqlMap () {
9 | let basePath = __dirname
10 | basePath = basePath.replace(/\\/g, '\/')
11 |
12 | let pathArr = basePath.split('\/')
13 | pathArr = pathArr.splice( 0, pathArr.length - 1 )
14 | basePath = pathArr.join('/') + '/sql/'
15 |
16 | let fileList = walkFile( basePath, 'sql' )
17 | return fileList
18 | }
19 |
20 | module.exports = getSqlMap
--------------------------------------------------------------------------------
/server/init/util/walk-file.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 |
3 | /**
4 | * 遍历目录下的文件目录
5 | * @param {string} pathResolve 需进行遍历的目录路径
6 | * @param {string} mime 遍历文件的后缀名
7 | * @return {object} 返回遍历后的目录结果
8 | */
9 | const walkFile = function( pathResolve , mime ){
10 |
11 | let files = fs.readdirSync( pathResolve )
12 |
13 | let fileList = {}
14 |
15 | for( let [ i, item] of files.entries() ) {
16 | let itemArr = item.split('\.')
17 |
18 | let itemMime = ( itemArr.length > 1 ) ? itemArr[ itemArr.length - 1 ] : 'undefined'
19 | let keyName = item + ''
20 | if( mime === itemMime ) {
21 | fileList[ item ] = pathResolve + item
22 | }
23 | }
24 |
25 | return fileList
26 | }
27 |
28 | module.exports = walkFile
--------------------------------------------------------------------------------
/server/middlewares/verify.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file 处理验证的中间件
3 | */
4 |
5 | const jwt = require("jsonwebtoken");
6 | const secret = require("../config").secret;
7 |
8 | module.exports = async function(ctx, next) {
9 | // 同步验证
10 | const auth = ctx.get('Authorization')
11 | const token = auth.split(' ')[1];
12 | try {
13 | //解码取出之前存在payload的user_id 和 name
14 | const payload = jwt.verify(token, secret)
15 | ctx.user_id = payload.id;
16 | ctx.name = payload.name;
17 | await next()
18 | } catch (err) {
19 | ctx.throw(401, err)
20 | }
21 | }
--------------------------------------------------------------------------------
/server/models/groupChat.js:
--------------------------------------------------------------------------------
1 | const {
2 | query
3 | } = require("../utils/db");
4 | /**
5 | * 获取群消息
6 | * @param 群id
7 | * @return message 群消息
8 | * @return time 时间
9 | * @return from_user 发送人id
10 | * @return avator 发送人头像
11 | */
12 | let getGroupMsg = function(groupId) {
13 | let _sql =
14 | "SELECT g.message , g.time , g.from_user ,i.avator ,i.name FROM group_msg As g inner join user_info AS i ON g.from_user = i.id WHERE to_group = ? order by time ";
15 | return query(_sql, groupId);
16 | };
17 | /**
18 | * 获取群成员
19 | * @param 群id
20 | * @return group_member_id 群成员id
21 | */
22 | let getGroupMember = function(groupId) {
23 | let _sql =
24 | " SELECT user_id AS group_member_id FROM group_user_relation WHERE group_id = ? ";
25 | return query(_sql, groupId);
26 | };
27 | /**
28 | * 获取群资料
29 | * @param arr 包括 groupId goupName 至少一个
30 | * @return
31 | */
32 | let getGroupInfo = function(arr) {
33 | let _sql =
34 | " SELECT group_id , group_name , group_notice ,group_avator ,group_creater ,creater_time FROM group_info WHERE group_id = ? OR group_name = ? ;";
35 | return query(_sql, arr);
36 | };
37 |
38 | /**
39 | * 存聊天记录
40 | * @param userId 用户id
41 | * @param groupId 群id
42 | * @param message 消息
43 | * @param name 用户名
44 | * @param time 时间
45 | * @return
46 | */
47 |
48 | let saveGroupMsg = function(userId, groupId, message, name, time) {
49 | const data = [userId, groupId, `${name} : ${message}`, time];
50 | let _sql =
51 | " INSERT INTO group_msg(from_user,to_group,message ,time) VALUES(?,?,?,?); ";
52 | return query(_sql, data);
53 | };
54 | /**
55 | * 群添加成员并返回群成员
56 | * @param userId 用户id
57 | * @param groupId 群id
58 | * @return
59 | */
60 | let addGroupUserRelation = function(userId, groupId) {
61 | const data = [groupId, userId];
62 | let _sql =
63 | " INSERT INTO group_user_relation(group_id,user_id) VALUES(?,?); ";
64 | return query(_sql, data);
65 | };
66 | module.exports = {
67 | getGroupMsg,
68 | getGroupMember,
69 | getGroupInfo,
70 | saveGroupMsg,
71 | addGroupUserRelation
72 | };
--------------------------------------------------------------------------------
/server/models/groupInfo.js:
--------------------------------------------------------------------------------
1 | const {
2 | query
3 | } = require('../utils/db');
4 |
5 |
6 | // 加入群
7 | let joinGroup = (user_id, group_id) => {
8 | let _sql = "INSERT INTO group_user_relation(user_id,group_id) VALUES(?,?);"
9 | return query(_sql, [user_id, group_id])
10 | }
11 |
12 | // 查看某个用户是否在某个群中
13 | let isInGroup = (user_id, group_id) => {
14 | let _sql = "SELECT * FROM group_user_relation WHERE user_id = ? AND group_id = ?;"
15 | return query(_sql, [user_id, group_id])
16 | }
17 |
18 | // 建群
19 | let createGroup = (arr) => {
20 | let _sql = "INSERT INTO group_info (group_id,group_name,group_notice,group_avator,group_creater,creater_time) VALUES (?,?,?,?,?,?)"
21 | return query(_sql, arr)
22 | }
23 |
24 | // 删除群
25 | let exitGroup = (user_id, group_id) => {
26 | let _sql = "DELETE FROM group_user_relation WHERE user_id = ? AND group_id = ? ;"
27 | return query(_sql, [user_id, group_id])
28 | }
29 |
30 |
31 | module.exports = {
32 | joinGroup,
33 | isInGroup,
34 | createGroup,
35 | exitGroup
36 | };
--------------------------------------------------------------------------------
/server/models/message.js:
--------------------------------------------------------------------------------
1 | const {
2 | query
3 | } = require('../utils/db');
4 |
5 | // 通过user_id查找首页群列表
6 | let getGroupList = function(user_id) {
7 | let _sql = `SELECT r.group_id ,i.group_name , i.creater_time, i.group_avator ,
8 | (SELECT g.message FROM group_msg AS g WHERE g.to_group = r.group_id ORDER BY TIME DESC LIMIT 1 ) AS message ,
9 | (SELECT g.time FROM group_msg AS g WHERE g.to_group = r.group_id ORDER BY TIME DESC LIMIT 1 ) AS time
10 | FROM group_user_relation AS r inner join group_info AS i on r.group_id = i.group_id WHERE r.user_id = ? `
11 | return query(_sql, user_id)
12 | }
13 |
14 | // 通过user_id查找首页私聊列表
15 | let getPrivateList = function(user_id) {
16 | let _sql = ` SELECT r.other_user_id ,i.name , i.avator , r.time as be_friend_time,
17 | (SELECT p.message FROM private__msg AS p
18 | WHERE (p.to_user = r.other_user_id and p.from_user = r.user_id) or (p.from_user = r.other_user_id and p.to_user = r.user_id) ORDER BY p.time DESC LIMIT 1 ) AS message ,
19 | (SELECT p.time FROM private__msg AS p WHERE (p.to_user = r.other_user_id and p.from_user = r.user_id) or (p.from_user = r.other_user_id and p.to_user = r.user_id) ORDER BY p.time DESC LIMIT 1 ) AS time
20 | FROM user_user_relation AS r inner join user_info AS i on r.other_user_id = i.id WHERE r.user_id = ? `
21 | return query(_sql, user_id)
22 | }
23 |
24 | module.exports = {
25 | getGroupList,
26 | getPrivateList
27 | }
--------------------------------------------------------------------------------
/server/models/newFriends.js:
--------------------------------------------------------------------------------
1 | const { query } = require("../utils/db");
2 |
3 | // 获取我的新好友通知
4 | let getnewFriends = function(to_user) {
5 | const _sql =
6 | "SELECT n.from_user , n.to_user , n.content, n.status , n.time , u.avator ,u.sex ,u.name FROM (select * from new_friends order by time desc) as n inner join user_info as u on n.from_user = u.id WHERE n.to_user = ? group by n.from_user";
7 | return query(_sql, [to_user]);
8 | };
9 |
10 | //添加我的新好友通知
11 | let insertNewFriends = function(arr) {
12 | console.log('insertNewFriendsmol22222')
13 | const _sql =
14 | "insert into new_friends(from_user,to_user,content,time,status) values(?,?,?,?,?);";
15 | return query(_sql, arr);
16 | };
17 |
18 | //更新我的新好友通知状态
19 | let updateNewFriends = (from_user, to_user) => {
20 | const _sql =
21 | "UPDATE new_friends SET status = 1 WHERE from_user = ? AND to_user = ?";
22 | return query(_sql, [from_user, to_user]);
23 | };
24 |
25 | module.exports = {
26 | getnewFriends,
27 | insertNewFriends,
28 | updateNewFriends
29 | };
30 |
--------------------------------------------------------------------------------
/server/models/privateChat.js:
--------------------------------------------------------------------------------
1 | const { query } = require("../utils/db");
2 |
3 |
4 | /**
5 | * 获取私聊相关内容
6 | * @param to_user 私聊对象的id
7 | * @param from_user 私聊者自己的id
8 | * @return from_user 此条信息的发送者
9 | * to_user 此条信息的接收者
10 | * message 私聊信息
11 | * time 时间
12 | * avator 发送者的头像
13 | // * sex 发送者的性别
14 | // * place 发送者居住地
15 | * status 发送者的是否在线
16 | */
17 | let getPrivateDetail = (from_user,to_user)=>{
18 | const data = [from_user,to_user,to_user,from_user]
19 | const _sql =
20 | 'select p.from_user,p.to_user, p.message ,p.time ,i.avator , i.name ,i.status from private__msg as p inner join user_info as i on p.from_user = i.id where (p.from_user = ? AND p.to_user = ? ) or (p.from_user = ? AND p.to_user = ? ) order by time '
21 | return query(_sql, data);
22 | }
23 |
24 | /**
25 | * 存聊天记录
26 | * @param from_user 发送者id
27 | * @param to_user 接收者id
28 | * @param message 消息
29 | * @param name 用户名
30 | * @param time 时间
31 | * @return
32 | */
33 |
34 | let savePrivateMsg = function(from_user, to_user, message, name, time) {
35 | const data = [from_user, to_user, `${name} : ${message}`, time];
36 | let _sql =
37 | " INSERT INTO private__msg(from_user,to_user,message ,time) VALUES(?,?,?,?); ";
38 | return query(_sql, data);
39 | };
40 |
41 | module.exports = {
42 | getPrivateDetail,
43 | savePrivateMsg
44 | };
--------------------------------------------------------------------------------
/server/models/soketHander.js:
--------------------------------------------------------------------------------
1 |
2 | let saveUserSocketId = function(userId, socketId){
3 | const data = [socketId,userId]
4 | let _sql = ' UPDATE user_info SET socketid = ? WHERE id= ? limit 1 ; '
5 | return query( _sql,data)
6 | }
7 |
8 |
9 | let getUserSocketId = function(toUserId){
10 | let _sql = ' SELECT socketid FROM user_info WHERE id=? limit 1 ;'
11 | return query( _sql,[toUserId])
12 | }
13 |
14 | module.exports = {
15 | saveUserSocketId,
16 | getUserSocketId
17 | }
18 |
--------------------------------------------------------------------------------
/server/models/user_info.js:
--------------------------------------------------------------------------------
1 | const {
2 | query
3 | } = require('../utils/db');
4 |
5 | // 注册用户
6 | let insertData = function(value) {
7 | let _sql = "insert into user_info(name,password) values(?,?);"
8 | return query(_sql, value)
9 | }
10 |
11 | // 通过用户名查找用户信息 user_info
12 | let findDataByName = function(name) {
13 | let _sql = 'SELECT * FROM user_info WHERE name= ? '
14 | return query(_sql, name)
15 | }
16 |
17 | // 通过用户名查找用户信息 user_info 不包括密码
18 | let findUIByName = function(name) {
19 | let _sql = 'SELECT id ,name ,sex,avator,place,github FROM user_info WHERE name = ? '
20 | return query(_sql, name)
21 | }
22 |
23 | //修改我的信息
24 | let editorInfo = function(data) {
25 | let _sql = ' UPDATE user_info SET github = ?,website = ?,sex = ?,place = ? WHERE id = ? ; '
26 | return query(_sql, data)
27 | }
28 |
29 | // 通过用户id查找用户信息 user_info 包括密码
30 | let findDataByUserid = function(userid) {
31 | let _sql = 'SELECT * FROM user_info WHERE id= ? '
32 | return query(_sql, [userid])
33 | }
34 |
35 | // 通过用户id查找用户信息 user_info 包括用户名,性别,头像,最后登录时间,状态等,不包括密码
36 | let getUserInfo = (user_id) => {
37 | const _sql =
38 | 'SELECT id AS user_id, name ,sex ,avator,place ,website,github,intro,status FROM user_info WHERE user_info.id =? '
39 | return query(_sql, [user_id]);
40 | }
41 |
42 | // 通过要查看的用户id 查询是否是本机用户的好友 如果是 返回user_id 和 remark 备注
43 | let isFriend = (user_id, other_user_id) => {
44 | const _sql =
45 | 'SELECT * FROM user_user_relation AS u WHERE u.user_id = ? AND u.other_user_id = ? '
46 | return query(_sql, [user_id, other_user_id]);
47 | }
48 |
49 | // 加为好友 单方面
50 | let addAsFriend = (user_id, other_user_id, time) => {
51 | const _sql =
52 | 'INSERT INTO user_user_relation(user_id,other_user_id,time) VALUES (?,?,?)'
53 | return query(_sql, [user_id, other_user_id, time]);
54 | }
55 |
56 | //两边都互加为好友
57 | // let addFriendEachOther = (user_id,other_user_id)=>{
58 | // const _sql =
59 | // 'INSERT INTO user_user_relation(user_id,other_user_id) VALUES (?,?)'
60 | // return query(_sql, [user_id,other_user_id]);
61 | // }
62 |
63 | // 删除好友
64 | let delFriend = (user_id, other_user_id) => {
65 | const _sql =
66 | 'DELETE FROM user_user_relation WHERE user_id = ? AND other_user_id = ?'
67 | return query(_sql, [user_id, other_user_id]);
68 | }
69 |
70 | //屏蔽好友
71 | let shieldFriend = (status, user_id, other_user_id) => {
72 | const _sql =
73 | 'UPDATE user_user_relation SET shield = ? WHERE user_id = ? AND other_user_id = ? '
74 | return query(_sql, [status, user_id, other_user_id]);
75 | }
76 |
77 | //修改备注
78 | let editorRemark = (remark, user_id, other_user_id) => {
79 | const _sql =
80 | 'UPDATE user_user_relation SET remark = ? WHERE user_id = ? AND other_user_id = ? '
81 | return query(_sql, [remark, user_id, other_user_id]);
82 | }
83 |
84 |
85 | module.exports = {
86 | insertData,
87 | findDataByName,
88 | findUIByName,
89 | getUserInfo,
90 | isFriend,
91 | addAsFriend,
92 | delFriend,
93 | shieldFriend,
94 | editorRemark,
95 | editorInfo
96 | }
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "airchat",
3 | "version": "1.0.0",
4 | "description": "server of airchat",
5 | "author": "hxvin ",
6 | "private": true,
7 | "scripts": {
8 | "init_sql": "node ./init/index.js",
9 | "start": "gulp start",
10 | "dev": "node --inspect ../server"
11 | },
12 | "engines": {
13 | "node": ">= 0.10.0"
14 | },
15 | "dependencies": {
16 | "@koa/cors": "^2.2.3",
17 | "async": "0.2.9",
18 | "jsonwebtoken": "^8.1.0",
19 | "koa": "^2.3.0",
20 | "koa-bodyparser": "^4.2.0",
21 | "koa-router": "^7.2.1",
22 | "koa-static": "^4.0.0",
23 | "md5": "^2.2.1",
24 | "mysql": "2.x",
25 | "request": "^2.83.0",
26 | "request-promise": "^4.2.2",
27 | "socket.io": "^2.0.3",
28 | "uuid": "^3.2.1"
29 | },
30 | "devDependencies": {
31 | "gulp": "^3.9.1",
32 | "gulp-nodemon": "^2.2.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/server/routes/index.js:
--------------------------------------------------------------------------------
1 | const router = require('koa-router')(),
2 | baseApi = require('../config').baseApi,
3 | register = require('../controllers/register'),
4 | login = require('../controllers/login'),
5 | verify = require('../middlewares/verify'),
6 | robot = require('../controllers/robot'),
7 | message = require('../controllers/message'),
8 | groupChat = require('../controllers/groupChat.js'),
9 | privateChat = require('../controllers/privateChat.js'),
10 | userInfo = require('../controllers/userInfo.js'),
11 | newFriends = require('../controllers/newFriends.js'),
12 | groupInfo = require('../controllers/groupInfo.js');
13 | router.prefix(`/${baseApi}`)
14 | router.post('/register', register) //注册
15 | .post('/login', login) //登录
16 | .get('/robot', verify, robot) //机器人交流
17 | .get('/message', verify, message) // 获取首页列表信息
18 | .get('/get_group_info', verify, groupChat.getGroupInfo) //获取群资料
19 | .get('/group_chat', verify, groupChat.getGroupDetail) //获取群相关内容
20 | .post('/group_chat_msg', verify, groupChat.saveGroupMsg) // 保存群信息
21 | .post('/group_chat_relation', verify, groupChat.addGroupUserRelation) //群添加成员并返回群成员
22 | .post('/create_group', verify, groupInfo.createGroup) // 建群
23 | .post('/join_group', verify, groupInfo.joinGroup) // 加入群
24 | .get('/is_in_group', verify, groupInfo.isInGroup) // 看某个用户是否在某个群中(根据返回的数组长度是不是为零就知道)
25 | .delete('/exit_group', verify, groupInfo.exitGroup) // 退群
26 | .get('/private_detail', verify, privateChat.getprivateDetail) // 获取私聊相关内容
27 | .post('/private_save_msg', verify, privateChat.savePrivateMsg) //保存私聊信息
28 | .get('/user_info', verify, userInfo.getUserInfo) // 获取用户资料
29 | .get('/is_friend', verify, userInfo.isFriend) // 是否是好友
30 | .post('/be_friend', verify, userInfo.agreeBeFriend) // 加为好友
31 | .delete('/del_friend', verify, userInfo.delFriend) // 删除好友
32 | .put('/shield_friend', verify, userInfo.shieldFriend) // 屏蔽好友
33 | .put('/editor_remark', verify, userInfo.editorRemark) // 修改备注
34 | .put('/editor_info', verify, userInfo.editorInfo) // 修改我的信息
35 | .get('/find_people', verify, userInfo.findUIByName) //通过用户名搜索加人,此接口返回用户信息
36 | .get('/get_newfriends', verify, newFriends.getnewFriends) // 获取新朋友通知
37 | .post('/insert_newfriends', verify, newFriends.insertNewFriends) // 添加我的新好友通知
38 | .put('/update_newfriends', verify, newFriends.updateNewFriends) // 更新 新好友状态 是否已被同意加好友
39 |
40 |
41 |
42 |
43 |
44 |
45 | console.log("router");
46 |
47 | module.exports = router
--------------------------------------------------------------------------------
/server/utils/db.js:
--------------------------------------------------------------------------------
1 | const mysql = require('mysql')
2 | const dbConfig = require('../config').db;
3 | const pool = mysql.createPool({
4 | user: dbConfig.user,
5 | password: dbConfig.password,
6 | database: dbConfig.database,
7 | host: dbConfig.host,
8 | })
9 |
10 |
11 | let query = function( sql, values ) {
12 |
13 | return new Promise(( resolve, reject ) => {
14 | pool.getConnection(function(err, connection) {
15 | if (err) {
16 | resolve( err )
17 | } else {
18 | connection.query(sql, values, ( err, rows) => {
19 |
20 | if ( err ) {
21 | reject( err )
22 | } else {
23 | resolve( rows )
24 | }
25 | connection.release()
26 | })
27 | }
28 | })
29 | })
30 |
31 | }
32 |
33 |
34 | module.exports = {
35 | query
36 | }
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
55 |
56 |
112 |
--------------------------------------------------------------------------------
/src/assets/base.scss:
--------------------------------------------------------------------------------
1 | $bule:#1E90FF;
2 | $gray:#555;
--------------------------------------------------------------------------------
/src/assets/chat.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | // height: 100vh;
3 | padding-top: 0.6rem;
4 | z-index: 1;
5 |
6 | ul {
7 | width: 100%;
8 | padding-bottom: 1.6rem;
9 | display: flex;
10 | display: -webkit-flex;
11 | flex-direction: column;
12 |
13 | li {
14 | list-style-type: none;
15 | }
16 | }
17 |
18 | .input-msg {
19 | height: 0.46rem;
20 | position: fixed;
21 | bottom: 0.03rem;
22 | display: flex;
23 | display: -webkit-flex;
24 | /* Safari */
25 | width: 100%;
26 | z-index: 999;
27 |
28 | textarea {
29 | width: 87.8%;
30 | margin: 0 0.06rem;
31 | padding-top: 0.07rem;
32 | padding-left: 0.06rem;
33 | border-radius: 0.02rem;
34 | outline: none;
35 | resize: none;
36 | border: none;
37 | overflow-y: hidden;
38 | font: 0.16rem/0.18rem 'Microsoft Yahei';
39 | }
40 |
41 | p.btn {
42 | font-size: 0.2rem;
43 | display: flex;
44 | display: -webkit-flex;
45 | /* Safari */
46 | align-items: center;
47 | justify-content: center;
48 | text-align: center;
49 | margin-right: 0.06rem;
50 | height: 100%;
51 | width: 11%;
52 | background: #ccc;
53 | color: white;
54 | border-radius: 0.02rem;
55 | cursor: not-allowed;
56 | font-family: 'Microsoft Yahei';
57 |
58 | &.enable {
59 | background: #1E90FF;
60 | cursor: pointer;
61 | }
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/src/assets/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
58 |
--------------------------------------------------------------------------------
/src/assets/loginregister.scss:
--------------------------------------------------------------------------------
1 | .login {
2 | // background-color: #39ace7;
3 | font-family: "Poppins", sans-serif;
4 | height: 100vh;
5 |
6 | .wrapper {
7 | display: flex;
8 | align-items: center;
9 | flex-direction: column;
10 | justify-content: center;
11 | width: 100%;
12 | min-height: 100%;
13 | // padding: 0.4rem;
14 | #formContent {
15 | -webkit-border-radius: 0.2rem 0.2rem 0.2rem 0.2rem;
16 | border-radius: 0.2rem 0.2rem 0.2rem 0.2rem;
17 | background: #fff;
18 | padding: 0.6rem;
19 | width: 80%;
20 | max-width: 5rem;
21 | position: relative;
22 | padding: 0;
23 | -webkit-box-shadow: 0 0.6rem 1.2rem 0 rgba(0, 0, 0, 0.3);
24 | box-shadow: 0 0.6rem 1.2rem 0 rgba(0, 0, 0, 0.3);
25 | text-align: center;
26 |
27 | h2 {
28 | text-align: center;
29 | font-size: 0.26rem;
30 | font-weight: 600;
31 | text-transform: uppercase;
32 | display: inline-block;
33 | margin: 0.4rem 0.08rem 0.1rem;
34 | color: #cccccc;
35 | }
36 |
37 | .active {
38 | color: #39ace7;
39 | }
40 | // .underlineHover:after {
41 | // display: block;
42 | // left: 0;
43 | // bottom: -0.1rem;
44 | // width: 0;
45 | // height: 0.02rem;
46 | // background-color: #39ace7;
47 | // content: "";
48 | // transition: width 0.2s;
49 | // }
50 | // // .underlineHover:hover {
51 | // // color: #0d0d0d;
52 | // // }
53 | // .underlineHover:hover:after {
54 | // width: 100%;
55 | // }
56 | form {
57 | input[type=button],
58 | input[type=reset],
59 | input[type=submit] {
60 | background-color: #39ace7;
61 | border: none;
62 | color: white;
63 | padding: 0.15rem 0.8rem;
64 | text-align: center;
65 | text-decoration: none;
66 | display: inline-block;
67 | text-transform: uppercase;
68 | font-size: 0.13rem;
69 | -webkit-box-shadow: 0 0.1rem 0.3rem 0 rgba(95, 186, 233, 0.4);
70 | box-shadow: 0 0.1rem 0.3rem 0 rgba(95, 186, 233, 0.4);
71 | -webkit-border-radius: 0.05rem 0.05rem 0.05rem 0.05rem;
72 | border-radius: 0.05rem 0.05rem 0.05rem 0.05rem;
73 | margin: 0.05rem 0.2rem 0.4rem;
74 | -webkit-transition: all 0.3s ease-in-out;
75 | -moz-transition: all 0.3s ease-in-out;
76 | -ms-transition: all 0.3s ease-in-out;
77 | -o-transition: all 0.3s ease-in-out;
78 | transition: all 0.3s ease-in-out;
79 | }
80 |
81 | input[type=button]:hover,
82 | input[type=reset]:hover,
83 | input[type=submit]:hover {
84 | background-color: #39ace7;
85 | }
86 |
87 | input[type=button]:active,
88 | input[type=reset]:active,
89 | input[type=submit]:active {
90 | -moz-transform: scale(0.95);
91 | -webkit-transform: scale(0.95);
92 | -o-transform: scale(0.95);
93 | -ms-transform: scale(0.95);
94 | transform: scale(0.95);
95 | }
96 |
97 | input[type=password],
98 | input[type=text] {
99 | background-color: #f6f6f6;
100 | border: none;
101 | color: #0d0d0d;
102 | padding: 0.15rem 0.32rem;
103 | text-align: center;
104 | text-decoration: none;
105 | display: inline-block;
106 | font-size: 0.16rem;
107 | margin: 0.05rem;
108 | width: 85%;
109 | border: 0.02rem solid #f6f6f6;
110 | -webkit-transition: all 0.5s ease-in-out;
111 | -moz-transition: all 0.5s ease-in-out;
112 | -ms-transition: all 0.5s ease-in-out;
113 | -o-transition: all 0.5s ease-in-out;
114 | transition: all 0.5s ease-in-out;
115 | -webkit-border-radius: 0.05rem 0.05rem 0.05rem 0.05rem;
116 | border-radius: 0.05rem 0.05rem 0.05rem 0.05rem;
117 | }
118 |
119 | input[type=text]:focus {
120 | background-color: #fff;
121 | border-bottom: 0.02rem solid #5fbae9;
122 | }
123 |
124 | input[type=password]:focus {
125 | background-color: #fff;
126 | border-bottom: 0.02rem solid #5fbae9;
127 | }
128 |
129 | input[type=text]:placeholder {
130 | color: #cccccc;
131 | }
132 |
133 | input[type=password]:placeholder {
134 | color: #cccccc;
135 | }
136 | }
137 | }
138 |
139 | .fadeInDown {
140 | -webkit-animation-name: fadeInDown;
141 | animation-name: fadeInDown;
142 | -webkit-animation-duration: 1s;
143 | animation-duration: 1s;
144 | -webkit-animation-fill-mode: both;
145 | animation-fill-mode: both;
146 | }
147 | @-webkit-keyframes fadeInDown {
148 | 0% {
149 | opacity: 0;
150 | -webkit-transform: translate3d(0, -100%, 0);
151 | transform: translate3d(0, -100%, 0);
152 | }
153 |
154 | 100% {
155 | opacity: 1;
156 | -webkit-transform: none;
157 | transform: none;
158 | }
159 | }
160 | @keyframes fadeInDown {
161 | 0% {
162 | opacity: 0;
163 | -webkit-transform: translate3d(0, -100%, 0);
164 | transform: translate3d(0, -100%, 0);
165 | }
166 |
167 | 100% {
168 | opacity: 1;
169 | -webkit-transform: none;
170 | transform: none;
171 | }
172 | }
173 | }
174 | }
175 |
176 | *:focus {
177 | outline: none;
178 | }
179 |
180 | #icon {
181 | width: 60%;
182 | }
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aermin/vue-chat/1f447896b54c9dc908c8fe0b0f718cd4c4a3e133/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/ChatItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
![]()
6 |
{{name}}{{time}}
7 |
{{msg}}
8 |
9 |
10 |
11 |
12 |
![]()
13 |
{{time}}{{name}}
14 |
{{msg}}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
30 |
31 |
32 |
51 |
52 |
80 |
--------------------------------------------------------------------------------
/src/components/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
59 |
60 |
61 |
91 |
--------------------------------------------------------------------------------
/src/components/Message/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Message from './main.vue'
3 |
4 | Message.installMessage = function(options) {
5 | if (options === undefined || options === null) {
6 | options = {
7 | message: ''
8 | }
9 | } else if (typeof options === 'string' || typeof options === 'number') {
10 | options = {
11 | message: options
12 | }
13 | }
14 | var message = Vue.extend(Message)
15 |
16 | var component = new message({
17 | data: options
18 | }).$mount()
19 | document.querySelector('body').appendChild(component.$el)
20 | }
21 |
22 | export default Message
--------------------------------------------------------------------------------
/src/components/Message/main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{message}}
7 |
8 |
9 | {{message}}
12 |
13 |
14 | {{message}}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
47 |
48 |
68 |
--------------------------------------------------------------------------------
/src/components/MessageBox/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import MessageBox from './main.vue'
3 |
4 | export default MessageBox
--------------------------------------------------------------------------------
/src/components/MessageBox/main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
{{title}}
7 |
8 |
27 |
28 |
29 |
30 |
34 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
115 |
116 |
267 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import Message from './Message/index'
2 | import MessageBox from './MessageBox/index'
3 |
4 |
5 | const install = function(Vue) {
6 | Vue.component(Message.name, Message)
7 | Vue.component(MessageBox.name, MessageBox)
8 |
9 | Vue.prototype.$message = Message.installMessage
10 | }
11 | export default install
--------------------------------------------------------------------------------
/src/components/template.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
29 |
30 |
33 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | // The Vue build version to load with the `import` command
2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
3 | import Vue from 'vue'
4 | import App from './App'
5 | import router from './router'
6 | import axios from 'axios'
7 | import store from './store'
8 |
9 |
10 | import Components from './components/index'
11 | Vue.use(Components);
12 |
13 | Vue.config.productionTip = false
14 |
15 | axios.defaults.baseURL = 'http://localhost:3000'
16 | axios.interceptors.request.use(
17 | config => {
18 | const token = localStorage.getItem('userToken');
19 | if (token) {
20 | // Bearer是JWT的认证头部信息
21 | config.headers.common['Authorization'] = 'Bearer ' + token;
22 | }
23 | return config;
24 | },
25 | error => {
26 | return Promise.reject(error);
27 | }
28 | );
29 |
30 |
31 | /* eslint-disable no-new */
32 | new Vue({
33 | el: '#app',
34 | router,
35 | store,
36 | components: {
37 | App
38 | },
39 | template: ''
40 | })
--------------------------------------------------------------------------------
/src/pages/Add.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 建一个群
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 加人:  {{this.inputContent}}
16 |
17 |
进群:  {{this.inputContent}}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
61 |
62 |
152 |
--------------------------------------------------------------------------------
/src/pages/AddSeach.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
![]()
8 |
9 |
{{data.name}}
10 |
11 |
12 |
13 |
14 | {{data.place}}
15 | {{data.github}}
16 |
17 |
18 |
19 |
20 | -
21 |
22 |
![]()
23 |
24 |
{{data.group_name}}
25 | {{data.group_creater}}
26 |
27 |
28 | 群公告:{{data.group_notice}}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
104 |
105 |
163 |
--------------------------------------------------------------------------------
/src/pages/ContactList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 新朋友
6 |
7 |
8 |
朋友群组
9 |
12 |
13 |
14 |
15 |
16 |
55 |
56 |
100 |
--------------------------------------------------------------------------------
/src/pages/CreatEditorGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{this.messageBox.message}}
7 |
8 |
9 |
10 |
![]()
11 |
12 |
群名:
13 |
群公告:
14 |
15 |
16 |
17 | 确定建群
18 |
19 |
20 |
21 |
22 |
23 |
98 |
99 |
201 |
--------------------------------------------------------------------------------
/src/pages/GroupChat.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 |
193 |
194 |
197 |
--------------------------------------------------------------------------------
/src/pages/GroupInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
![]()
7 |
8 | 群名:{{this.groupInfo.group_name}}
9 |
10 |
11 | 群创建者:{{this.groupInfo.group_creater}}
12 |
13 |
14 | 群创建时间:{{this.groupInfo.creater_time}}
15 |
16 |
17 | 群公告:{{this.groupInfo.group_notice}}
18 |
19 |
20 |
21 | 退出群聊
22 | {{isMyGroup ? '进入群聊' : '加入群聊' }}
23 |
24 |
27 |
28 |
29 |
30 |
101 |
102 |
152 |
--------------------------------------------------------------------------------
/src/pages/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{this.messageBox.message}}
6 |
7 |
8 |
9 |
登录
10 |
11 | 注册
12 |
13 |
14 |

15 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
97 |
98 |
101 |
--------------------------------------------------------------------------------
/src/pages/Me.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{this.messageBox.message}}
6 |
7 |
8 |
9 | -
10 |
11 | {{userInfo.name}}
12 |
13 |
14 |
18 |
19 |
20 | 退出登录
21 |
22 |
23 |
24 |
25 |
26 |
80 |
81 |
161 |
--------------------------------------------------------------------------------
/src/pages/Message.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | -
7 |
{{data.unread}}
8 |
{{data.unread}}
9 |
10 |
{{data.group_name}}{{data.time}}
11 |
{{data.name}}{{data.time}}
12 |
{{data.message}}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
73 |
74 |
158 |
--------------------------------------------------------------------------------
/src/pages/NewFriends.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
7 |
![]()
8 |
9 |
{{data.name}}
10 |
{{data.content}}
11 |
12 |
13 |
14 |
15 | 同意
16 | 已通过验证
17 |
18 |
19 |
20 |
21 |
22 |
23 |
108 |
109 |
157 |
--------------------------------------------------------------------------------
/src/pages/Register.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{this.messageBox.message}}
6 |
7 |
8 |
9 |
10 | 登录
11 |
12 |
注册
13 |
14 |
15 |

16 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
90 |
91 |
94 |
--------------------------------------------------------------------------------
/src/pages/Robot.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
89 |
90 |
147 |
--------------------------------------------------------------------------------
/src/pages/UserInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{this.messageBox.message}}
7 |
8 |
9 |
11 | {{this.messageBox.message}}
12 |
13 |
14 |
15 |
![]()
16 |
17 |
18 |
19 |
20 |
21 | 备注:{{this.remark}}
22 |
23 |
24 | 用户名:{{userInfo.name}}
25 |
26 |
27 | 性别:{{userInfo.sex === 0 ? '男' : '女' }}
28 |
29 |
30 | 来自:{{userInfo.place}}
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 |
324 |
325 |
395 |
--------------------------------------------------------------------------------
/src/pages/VerifyReq.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{this.messageBox.message}}
5 |
6 |
7 |
你需要发送验证申请,等对方通过
8 |
9 |
发送
10 |
11 |
12 |
13 |
88 |
89 |
119 |
--------------------------------------------------------------------------------
/src/pages/privateChat.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 |
217 |
218 |
221 |
--------------------------------------------------------------------------------
/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Router from "vue-router";
3 | import Message from "@/pages/Message";
4 | import Robot from "@/pages/Robot";
5 | import Me from "@/pages/Me";
6 | import Register from "@/pages/Register";
7 | import Login from "@/pages/Login";
8 | import GroupChat from "@/pages/GroupChat";
9 | import PrivateChat from "@/pages/PrivateChat";
10 | import UserInfo from "@/pages/UserInfo";
11 | import VerifyReq from "@/pages/VerifyReq";
12 | import ContactList from "@/pages/ContactList";
13 | import NewFriends from "@/pages/NewFriends";
14 | import Add from "@/pages/Add";
15 | import AddSeach from "@/pages/AddSeach";
16 | import GroupInfo from "@/pages/GroupInfo";
17 | import CreatEditorGroup from "@/pages/CreatEditorGroup";
18 |
19 | import axios from "axios";
20 |
21 | Vue.use(Router);
22 |
23 | const router = new Router({
24 | routes: [{ // 消息首页
25 | path: "/message",
26 | component: Message
27 | },
28 | { //群聊
29 | path: "/group_chat/:group_id",
30 | component: GroupChat
31 | },
32 | { //私聊
33 | path: "/private_chat/:user_id",
34 | component: PrivateChat
35 | },
36 | { //机器人聊天
37 | path: "/robot",
38 | component: Robot
39 | },
40 | { //通讯录
41 | path: "/contact_list",
42 | component: ContactList
43 | },
44 | { //加好友通知
45 | path: "/contact_list/new_friends",
46 | component: NewFriends
47 | },
48 | { //个人中心
49 | path: "/me",
50 | component: Me
51 | },
52 | { //登录
53 | path: "/login",
54 | component: Login
55 | },
56 | { //注册
57 | path: "/register",
58 | component: Register
59 | },
60 | { //用户信息卡
61 | path: "/user_info/:user_id",
62 | component: UserInfo
63 | },
64 | { //群信息卡
65 | path: "/group_info/:group_id",
66 | component: GroupInfo
67 | },
68 | { //加好友请求验证
69 | path: "/user_info/verify/:user_id",
70 | component: VerifyReq
71 | },
72 | { //加人或进群
73 | path: "/add",
74 | component: Add
75 | },
76 | { //搜人
77 | path: "/add_seach/user/:username",
78 | component: AddSeach
79 | },
80 | { //搜群
81 | path: "/add_seach/group/:groupname",
82 | component: AddSeach
83 | },
84 | { //建群
85 | path: "/creat_group",
86 | component: CreatEditorGroup
87 | },
88 | { //编辑群
89 | path: "/editor_group",
90 | component: CreatEditorGroup
91 | },
92 | {
93 | path: "/",
94 | redirect: "/login"
95 | }
96 | ]
97 | });
98 |
99 | //路由守卫
100 | router.beforeEach((to, from, next) => {
101 | if (!localStorage.userToken) {
102 | if (to.path === "/login" || to.path === "/register") {
103 | next();
104 | } else {
105 | next("/login");
106 | }
107 | } else {
108 | if (to.path === "/login" || to.path === "/register") {
109 | next("/message");
110 | } else {
111 | next();
112 | }
113 | }
114 | });
115 |
116 | export default router;
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 | import axios from "axios";
4 | import {
5 | toNomalTime
6 | } from "../utils/transformTime";
7 |
8 | Vue.use(Vuex);
9 |
10 | const store = new Vuex.Store({
11 | state: {
12 | firstLoad: true, //是否是第一次加载首页消息页面
13 | robotmsg: [
14 | // 机器人首语
15 | {
16 | message: "hi , 欢迎与我聊天,问我问题哦!",
17 | user: "robot"
18 | }
19 | ],
20 | msgList: [], // 消息首页列表
21 | groupInfo: [], //群资料
22 | groupMember: [], //群成员
23 | toUserInfo: [], //私聊对方的资料
24 | someOneInfo: {}, //某个用户的用户资料
25 | addAsFriend: {
26 | //加为好友
27 | user_id: "", //请求方
28 | other_user_id: "" //被请求方
29 | },
30 | newFriend: [], //新朋友列表
31 | myInfo: {}, //我的信息
32 | tabTips: { //底部tab的未读提示 暂时只做好友添加请求的提示
33 | addFriendReq: "" //是否有好友添加请求 0没有 1有
34 | }
35 | },
36 | getters: {
37 | robotMsgGetter: state => state.robotmsg,
38 | msgListGetter: state => state.msgList,
39 | updateListGetter: state => state.msgList,
40 | groupInfoGetter: state => state.groupInfo,
41 | groupMemberGetter: state => state.groupMember,
42 | toUserInfoGetter: state => state.toUserInfo,
43 | someOneInfoGetter: state => state.someOneInfo,
44 | addAsFriendGetter: state => state.addAsFriend,
45 | newFriendGetter: state => state.newFriend,
46 | myInfoGetter: state => state.myInfo,
47 | tabTipsGetter: state => state.tabTips
48 | },
49 | mutations: {
50 | //是否是第一次加载首页消息页面
51 | firstLoadMutation(state, data) {
52 | state.firstLoad = data;
53 | },
54 | //机器人消息
55 | robotMsgMutation(state, data) {
56 | state.robotmsg.push(data);
57 | },
58 | //我的信息
59 | myInfoMutation(state, data) {
60 | state.myInfo = data;
61 | },
62 | //首页消息列表
63 | msgListMutation(state, data) {
64 | state.msgList = data;
65 | },
66 | //未读信息归零
67 | resetUnredMutation(state, id) {
68 | state.msgList.forEach(ele => {
69 | if (ele.id == id) {
70 | ele.unread = null
71 | }
72 | })
73 | },
74 | //是否有好友添加请求
75 | friendReqTipsMutation(state, data) {
76 | if (data) {
77 | state.tabTips.addFriendReq = "tips";
78 | } else {
79 | state.tabTips.addFriendReq = "";
80 | }
81 |
82 | },
83 | //更新首页消息列表
84 | updateListMutation(state, data) {
85 | let unread = 0;
86 | data.time = toNomalTime(data.time);
87 | //添加
88 | if (data.action === "push") {
89 | data.unread = unread + 1;
90 | state.msgList.push(data);
91 | return
92 | }
93 | //删除
94 | if (data.action === "delete") {
95 | for (var i = 0; i < state.msgList.length; i++) {
96 | if (state.msgList[i].id == data.id) {
97 | state.msgList.splice(i, 1);
98 | };
99 | }
100 | return
101 | }
102 | //替换更新
103 | if (data.type === "private") {
104 | //在请求添加好友的情况下
105 | let haveThisEle = state.msgList.filter(ele => ele.other_user_id == data.from_user);
106 | if (haveThisEle.length === 0 && data.action === "request") {
107 | data.unread = unread + 1;
108 | data.other_user_id = data.from_user;
109 | data.id = data.from_user;
110 | delete data.from_user;
111 | delete data.to_user;
112 | state.msgList.push(data);
113 | return
114 | }
115 | //正常私聊情况下
116 | state.msgList.forEach(ele => {
117 | //判断是哪个人 对方发的
118 | if (ele.other_user_id == data.from_user) {
119 | ele.message = data.name + ' : ' + data.message;
120 | ele.time = data.time;
121 | ele.name = data.name;
122 | ele.avator = data.avator;
123 | //如果是当前的聊天,没必要加未读标识了
124 | if (data.chatOfNow) return
125 | //增加未读消息数
126 | if (!ele.unread) {
127 | ele.unread = unread + 1;
128 | } else {
129 | ele.unread += 1;
130 | }
131 | } else if (ele.other_user_id == data.to_user) { //自己发的
132 | ele.message = data.name + ' : ' + data.message;
133 | ele.time = data.time;
134 | }
135 | });
136 | } else if (data.type === "group") {
137 | state.msgList.forEach(ele => {
138 | //判断是哪个群
139 | if (ele.group_id == data.groupId) {
140 | ele.message = data.name + ' : ' + data.message;
141 | ele.time = data.time;
142 | ele.group_name = data.group_name;
143 | ele.group_avator = data.group_avator;
144 | ele.id = data.groupId;
145 | //增加未读消息数
146 | if (data.chatOfNow) {
147 | ele.unread = null;
148 | } else {
149 | if (!ele.unread) {
150 | ele.unread = unread + 1;
151 | } else {
152 | ele.unread += 1;
153 | }
154 | }
155 | } else {
156 |
157 | }
158 | });
159 | }
160 | // }
161 | },
162 | //群资料
163 | groupInfoMutation(state, data) {
164 | state.groupInfo = data;
165 | },
166 | //群成员
167 | groupMemberMutation(state, data) {
168 | state.groupMember = data;
169 | },
170 | //私聊对方的用户资料
171 | toUserInfoMutation(state, data) {
172 | state.toUserInfo = data;
173 | },
174 | //用户资料
175 | someOneInfoMutation(state, data) {
176 | state.someOneInfo = data;
177 | },
178 | //添加好友
179 | addAsFriendMutation(state, data) {
180 | state.addAsFriend = data;
181 | },
182 | //新朋友列表
183 | newFriendMutation(state, data) {
184 | state.newFriend = data;
185 | }
186 | },
187 | actions: {
188 | //机器人
189 | robatMsgAction({
190 | commit
191 | }, data) {
192 | // console.log(data + " robatMsgAction");
193 | axios.get("/api/v1/robot", {
194 | params: data
195 | }).then(res => {
196 | if (res) {
197 | if (res.data.data.code === 100000) {
198 | commit("robotMsgMutation", {
199 | message: res.data.data.text,
200 | user: "robot"
201 | });
202 | } else if (res.data.data.code === 200000) {
203 | let data = res.data.data.text + res.data.data.url;
204 | commit("robotMsgMutation", {
205 | message: data,
206 | user: "robot"
207 | });
208 | } else if (res.data.data.code === 302000) {
209 | commit("robotMsgMutation", {
210 | message: "暂不支持此类对话",
211 | user: "robot"
212 | });
213 | } else {
214 | commit("robotMsgMutation", {
215 | message: "暂不支持此类对话",
216 | user: "robot"
217 | });
218 | }
219 | }
220 | })
221 | .catch(err => {
222 | console.log(err);
223 | });
224 | },
225 | // 消息首页列表
226 | async msgListAction({
227 | commit
228 | }) {
229 | // console.log('msgListAction')
230 | const res = await axios.get("/api/v1/message");
231 | console.log("res", res);
232 | if (res.data.success) {
233 | const groupList = res.data.data.groupList;
234 | const privateList = res.data.data.privateList;
235 | groupList.forEach(element => {
236 | element.type = "group";
237 | element.time = element.time ? toNomalTime(element.time) : toNomalTime(element.creater_time);
238 | element.id = element.group_id;
239 | });
240 | privateList.forEach(element => {
241 | element.type = "private";
242 | element.time = element.time ? toNomalTime(element.time) : toNomalTime(element.be_friend_time);
243 | element.id = element.other_user_id;
244 | // element.unread = 0;
245 | });
246 | const allMsgList = groupList.concat(privateList);
247 | allMsgList.sort((a, b) => {
248 | return b.time - a.time;
249 | });
250 | commit("msgListMutation", allMsgList);
251 | }
252 | },
253 | //某个用户的用户资料
254 | async someOneInfoAction({
255 | commit
256 | }, user_id) {
257 | // console.log("user_id666", user_id);
258 | const res = await axios.get("/api/v1/user_info", {
259 | params: {
260 | user_id: user_id
261 | }
262 | });
263 | commit("someOneInfoMutation", res.data.data.userInfo[0]);
264 | },
265 | //获取新朋友列表
266 | async newFriendAction({
267 | commit
268 | }, user_id) {
269 | // console.log("user_id666", user_id);
270 | const res = await axios.get("/api/v1/get_newfriends");
271 | // console.log('newFriendAction', res)
272 | commit("newFriendMutation", res.data.data.newFriends);
273 | }
274 | }
275 | });
276 | export default store;
--------------------------------------------------------------------------------
/src/utils/transformTime.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | *
4 | * @param 时间戳
5 | * @return yyyy-MM-dd hh:mm 格式的时间
6 | */
7 | export const toNomalTime = (timestamp)=> {
8 | const date = new Date(timestamp*1000) ,
9 | Y = date.getFullYear() + '-',
10 | M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-',
11 | D = date.getDate() + ' ',
12 | h = date.getHours() + ':',
13 | m = date.getMinutes();
14 | return Y+M+D+h+m
15 | }
16 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aermin/vue-chat/1f447896b54c9dc908c8fe0b0f718cd4c4a3e133/static/.gitkeep
--------------------------------------------------------------------------------