├── html-web ├── .eslintignore ├── static │ ├── bg.jpg │ ├── test.jpg │ └── favicon.ico ├── config │ ├── prod.env.js │ ├── dev.env.js │ └── index.js ├── src │ ├── assets │ │ ├── images │ │ │ └── test.jpg │ │ ├── styles │ │ │ ├── iconfont │ │ │ │ ├── iconfont.eot │ │ │ │ ├── iconfont.ttf │ │ │ │ └── iconfont.woff │ │ │ ├── reset.css │ │ │ └── iconfont.css │ │ └── scripts │ │ │ ├── urlList.js │ │ │ ├── ws │ │ │ ├── apiRequest.js │ │ │ ├── msgSender.js │ │ │ └── noticeEvent.js │ │ │ └── common.js │ ├── App.vue │ ├── router │ │ └── index.js │ ├── store │ │ ├── index.js │ │ ├── mutation-types.js │ │ ├── mutations.js │ │ ├── getters.js │ │ └── state.js │ ├── main.js │ └── components │ │ ├── common │ │ ├── FriendList.vue │ │ ├── GroupInfoCard.vue │ │ └── InfoCard.vue │ │ └── indexItem │ │ ├── Info.vue │ │ └── TextArea.vue ├── .editorconfig ├── .babelrc ├── .postcssrc.js ├── build │ ├── vue-loader.conf.js │ ├── build.js │ ├── check-versions.js │ ├── utils.js │ ├── webpack.base.conf.js │ └── webpack.dev.conf.js ├── .eslintrc.js ├── index.html └── package.json ├── node-http ├── config │ ├── upload.json │ ├── redis.json │ ├── mongo.json │ └── basic.json ├── extends │ ├── date.js │ └── index.js ├── const │ ├── whitePathname.js │ ├── cookiesName.js │ ├── allowedMimeType.js │ ├── reg.js │ ├── redisKey.js │ └── code.js ├── routers │ ├── user.js │ ├── common.js │ ├── group.js │ ├── friend.js │ └── routerScanner.js ├── package.json ├── lib │ ├── asyncRedis.js │ ├── mongodb.js │ ├── util.js │ └── busboyUpload.js ├── models │ ├── groupApply.js │ ├── groupRelation.js │ ├── friendRelation.js │ ├── friendApply.js │ ├── images.js │ ├── groupInfo.js │ ├── user.js │ └── basic.js ├── app.js ├── middleware │ ├── basic.js │ └── auth.js └── controller │ ├── baseController.js │ ├── friend.js │ ├── common.js │ └── user.js ├── java-websocket ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── thankjava │ │ │ └── wchat │ │ │ ├── db │ │ │ ├── mongo │ │ │ │ ├── GroupInfoMapper.java │ │ │ │ ├── GroupApplyMapper.java │ │ │ │ ├── FriendApplyMapper.java │ │ │ │ ├── FriendRelationMapper.java │ │ │ │ ├── GroupRelationMapper.java │ │ │ │ ├── UserMapper.java │ │ │ │ ├── BaseMapper.java │ │ │ │ └── impl │ │ │ │ │ ├── GroupInfoMapperImpl.java │ │ │ │ │ ├── GroupApplyMapperImpl.java │ │ │ │ │ ├── FriendApplyMapperImpl.java │ │ │ │ │ ├── FriendRelationMapperImpl.java │ │ │ │ │ ├── UserMapperImpl.java │ │ │ │ │ └── GroupRelationMapperImpl.java │ │ │ └── entity │ │ │ │ ├── BaseEntity.java │ │ │ │ ├── GroupRelation.java │ │ │ │ ├── FriendRelation.java │ │ │ │ ├── GroupApply.java │ │ │ │ ├── User.java │ │ │ │ ├── GroupInfo.java │ │ │ │ └── FriendApply.java │ │ │ ├── lib │ │ │ └── websocket │ │ │ │ ├── callback │ │ │ │ ├── OnConnCloseListener.java │ │ │ │ ├── OnMessageListener.java │ │ │ │ └── ConnectionVerifyListener.java │ │ │ │ ├── WS.java │ │ │ │ ├── core │ │ │ │ └── WSImpl.java │ │ │ │ ├── entity │ │ │ │ ├── Message.java │ │ │ │ └── ConVerifyResult.java │ │ │ │ ├── Session.java │ │ │ │ └── BasicWebSocket.java │ │ │ ├── consts │ │ │ ├── CookieName.java │ │ │ ├── MongoTableName.java │ │ │ ├── EventType.java │ │ │ ├── RedisKeyManager.java │ │ │ └── ResponseCode.java │ │ │ ├── bean │ │ │ ├── controller │ │ │ │ ├── friend │ │ │ │ │ ├── FriendDel.java │ │ │ │ │ ├── FriendAddReply.java │ │ │ │ │ └── FriendAdd.java │ │ │ │ ├── group │ │ │ │ │ ├── GroupAdd.java │ │ │ │ │ └── GroupAddReply.java │ │ │ │ └── chat │ │ │ │ │ └── ChatMsg.java │ │ │ ├── notice │ │ │ │ ├── chat │ │ │ │ │ └── ChatMsgPush.java │ │ │ │ ├── notice │ │ │ │ │ ├── OnlinePush.java │ │ │ │ │ └── OfflinePush.java │ │ │ │ ├── group │ │ │ │ │ ├── GroupUser.java │ │ │ │ │ ├── GroupAddReplyPush.java │ │ │ │ │ ├── GroupAddPush.java │ │ │ │ │ └── GroupJoinPush.java │ │ │ │ └── friend │ │ │ │ │ ├── FriendAddReplyPush.java │ │ │ │ │ └── FriendAddPush.java │ │ │ ├── RequestContext.java │ │ │ ├── ResponseContext.java │ │ │ └── MsgPushContext.java │ │ │ ├── util │ │ │ ├── RedisUtil.java │ │ │ ├── Utils.java │ │ │ └── WSUtil.java │ │ │ ├── ws │ │ │ ├── anno │ │ │ │ ├── WSController.java │ │ │ │ └── WSProcess.java │ │ │ └── core │ │ │ │ ├── OnMessageCallBack.java │ │ │ │ ├── OnConnCloseCallBack.java │ │ │ │ └── ConnectionVerifyCallBack.java │ │ │ ├── controller │ │ │ ├── Notice.java │ │ │ └── Chat.java │ │ │ ├── App.java │ │ │ └── notice │ │ │ ├── FriendEventPush.java │ │ │ ├── StatusChangeEventPush.java │ │ │ ├── GroupEventPush.java │ │ │ └── ChatEventPush.java │ │ └── resources │ │ ├── mongodb.properties │ │ ├── redis.properties │ │ └── logback.xml └── pom.xml ├── README.md └── .gitignore /html-web/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /node-http/config/upload.json: -------------------------------------------------------------------------------- 1 | { 2 | "path": "/thankjava/released/nginx/source" 3 | } -------------------------------------------------------------------------------- /html-web/static/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/wchat/HEAD/html-web/static/bg.jpg -------------------------------------------------------------------------------- /html-web/static/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/wchat/HEAD/html-web/static/test.jpg -------------------------------------------------------------------------------- /html-web/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /html-web/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/wchat/HEAD/html-web/static/favicon.ico -------------------------------------------------------------------------------- /html-web/src/assets/images/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/wchat/HEAD/html-web/src/assets/images/test.jpg -------------------------------------------------------------------------------- /node-http/extends/date.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description: Date 函数拓展 5 | */ -------------------------------------------------------------------------------- /node-http/config/redis.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "127.0.0.1", 3 | "port": 6379, 4 | "password": "", 5 | "no_ready_check": true 6 | } -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/wchat/HEAD/html-web/src/assets/styles/iconfont/iconfont.eot -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/wchat/HEAD/html-web/src/assets/styles/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazy-koala/wchat/HEAD/html-web/src/assets/styles/iconfont/iconfont.woff -------------------------------------------------------------------------------- /node-http/config/mongo.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": "127.0.0.1", 3 | "port": 27010, 4 | "username": "test", 5 | "password": "test", 6 | "dbname": "test" 7 | } -------------------------------------------------------------------------------- /node-http/extends/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description: 5 | */ 6 | require('./date'); 7 | require('colour'); -------------------------------------------------------------------------------- /node-http/const/whitePathname.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/4 4 | * @Description: 不进行token校验的url前缀汇总 5 | */ 6 | module.exports = [ 7 | '/login', 8 | '/registe' 9 | ]; -------------------------------------------------------------------------------- /html-web/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 | -------------------------------------------------------------------------------- /html-web/.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 | -------------------------------------------------------------------------------- /node-http/const/cookiesName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/4 4 | * @Description: cookie name 5 | */ 6 | module.exports = { 7 | COOKIE_NAME_TOKEN: 'token', 8 | COOKIE_NAME_UID: 'uid', 9 | }; -------------------------------------------------------------------------------- /node-http/const/allowedMimeType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description: 允许上传的文件MimeType 5 | */ 6 | module.exports = [ 7 | "image/png", 8 | "image/jpeg", 9 | "image/gif" 10 | ]; -------------------------------------------------------------------------------- /html-web/src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /node-http/const/reg.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/16 4 | * @Description: 5 | */ 6 | module.exports = { 7 | // 用户账号校验正则 8 | USERNAME: /^[a-zA-Z0-9_-]{4,16}$/, 9 | PASSWORD: /^[a-zA-Z0-9_-]{6,20}$/, 10 | }; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/GroupInfoMapper.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo; 2 | 3 | import com.thankjava.wchat.db.entity.GroupInfo; 4 | 5 | public interface GroupInfoMapper extends BaseMapper { 6 | } 7 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/GroupApplyMapper.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo; 2 | 3 | import com.thankjava.wchat.db.entity.GroupApply; 4 | 5 | public interface GroupApplyMapper extends BaseMapper { 6 | } 7 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/FriendApplyMapper.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo; 2 | 3 | import com.thankjava.wchat.db.entity.FriendApply; 4 | 5 | public interface FriendApplyMapper extends BaseMapper { 6 | } 7 | -------------------------------------------------------------------------------- /java-websocket/src/main/resources/mongodb.properties: -------------------------------------------------------------------------------- 1 | mongo.pool.connectionsPerHost=50 2 | mongo.pool.threadsAllowedToBlockForConnectionMultiplier=50 3 | mongo.pool.maxWaitTime=120000 4 | mongo.pool.connectTimeout=60000 5 | 6 | mongo.connString=mongodb://test:test@thankjava.com:27010/test -------------------------------------------------------------------------------- /java-websocket/src/main/resources/redis.properties: -------------------------------------------------------------------------------- 1 | redis.pool.maxTotal=100 2 | redis.pool.maxIdle=50 3 | redis.pool.maxWaitMillis=10000 4 | redis.pool.testOnBorrow=true 5 | redis.pool.testOnReturn=true 6 | redis.timeout=30000 7 | 8 | redis.ip=127.0.0.1 9 | redis.port=6379 10 | redis.pwd= -------------------------------------------------------------------------------- /html-web/.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 | -------------------------------------------------------------------------------- /html-web/.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 | -------------------------------------------------------------------------------- /node-http/routers/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/9 4 | * @Description:处理用户相关模块 5 | */ 6 | const Router = require('koa-router'); 7 | 8 | const router = new Router({ 9 | prefix: '/user/' 10 | }); 11 | 12 | router.use(require('../controller/user')); 13 | 14 | module.exports = router.routes(); -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/callback/OnConnCloseListener.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket.callback; 2 | 3 | import com.thankjava.wchat.lib.websocket.entity.ConVerifyResult; 4 | 5 | public interface OnConnCloseListener { 6 | 7 | void doProcess(ConVerifyResult conVerifyResult); 8 | } 9 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/consts/CookieName.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.consts; 2 | 3 | public enum CookieName { 4 | 5 | TOKEN_KEY("token"), 6 | UID_KEY("uid"); 7 | 8 | CookieName(String code) { 9 | this.code = code; 10 | } 11 | 12 | public String code; 13 | 14 | 15 | } 16 | -------------------------------------------------------------------------------- /node-http/routers/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/13 4 | * @Description: 普通路由匹配 /* 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/' 11 | }); 12 | 13 | router.use(require('../controller/common')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /node-http/routers/group.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/14 4 | * @Description: 群组模块相关 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/group/' 11 | }); 12 | 13 | router.use(require('../controller/group')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /node-http/routers/friend.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/14 4 | * @Description: 好友模块相关 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const router = new Router({ 10 | prefix: '/friend/' 11 | }); 12 | 13 | router.use(require('../controller/friend')); 14 | 15 | module.exports = router.routes(); -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/FriendRelationMapper.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo; 2 | 3 | import com.thankjava.wchat.db.entity.FriendRelation; 4 | 5 | /** 6 | * @Author: acexy@thankjava.com 7 | * 2018/8/14 8 | * @Description: 9 | **/ 10 | public interface FriendRelationMapper extends BaseMapper { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/GroupRelationMapper.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo; 2 | 3 | import com.thankjava.wchat.db.entity.GroupRelation; 4 | 5 | import java.util.List; 6 | 7 | public interface GroupRelationMapper extends BaseMapper { 8 | 9 | public List selectByGroupId(String groupId); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/controller/friend/FriendDel.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.controller.friend; 2 | 3 | public class FriendDel { 4 | 5 | private String userId; 6 | 7 | public String getUserId() { 8 | return userId; 9 | } 10 | 11 | public void setUserId(String userId) { 12 | this.userId = userId; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /node-http/const/redisKey.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/1 4 | * @Description: redisKey管理 5 | */ 6 | const util = require('util'); 7 | 8 | module.exports.AUTH_TOKEN = function (token) { // string 9 | return util.format('wchat:token:%s', token); 10 | }; // 用户token信息鉴权缓存 11 | 12 | module.exports.USER_ONLINE = function() { 13 | return 'wchat:user:online'; 14 | }; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/callback/OnMessageListener.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket.callback; 2 | 3 | import com.thankjava.wchat.lib.websocket.entity.Message; 4 | 5 | /** 6 | * @Author: acexy@thankjava.com 7 | * 2018/8/9 8 | * @Description: 接受到消息时触发 9 | **/ 10 | public interface OnMessageListener { 11 | 12 | void doProcess(Message message); 13 | } 14 | -------------------------------------------------------------------------------- /java-websocket/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/consts/MongoTableName.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.consts; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/14 6 | * @Description: 7 | **/ 8 | public enum MongoTableName { 9 | 10 | user, 11 | friend_apply, 12 | friend_relation, 13 | group_info, 14 | group_relation, 15 | group_apply, 16 | 17 | ; 18 | public String value(){ 19 | return this.name(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /node-http/config/basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8006, 3 | "domain": "wchat.thankjava.com", 4 | "headImgGirl": "https://source.thankjava.com/2018/8/24/a57dd8b67769699a441b6c86322122b9.png", 5 | "headImgBoy": "https://source.thankjava.com/2018/8/24/942fe2bdf151c0a0d22ff6aaf82aeae6.png", 6 | "headImgUnknown": "https://source.thankjava.com/2018/8/30/1b1f33ac687abfba41235ea3f65ee312.jpg", 7 | "headImgGroup": "https://source.thankjava.com/2018/8/30/92b17c3b78f75415964dc5532a8f519e.png" 8 | } -------------------------------------------------------------------------------- /html-web/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Index from '@/components/Index' 4 | import Login from '@/components/Login' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'Login', 13 | component: Login 14 | }, 15 | { 16 | path: '/index', 17 | name: 'Index', 18 | component: Index 19 | } 20 | ], 21 | mode: 'history' 22 | }) 23 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo; 2 | 3 | import com.thankjava.wchat.db.entity.User; 4 | 5 | /** 6 | * @Author: acexy@thankjava.com 7 | * 2018/8/14 8 | * @Description: 9 | **/ 10 | public interface UserMapper extends BaseMapper { 11 | 12 | /** 13 | * 通过userId查询用户 14 | * @param username 15 | * @return 16 | */ 17 | public User selectByUsername(String username); 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /html-web/src/assets/scripts/urlList.js: -------------------------------------------------------------------------------- 1 | const urlList = { 2 | "getFriendList": "/api/friend/get_friend_list", 3 | "getGroupList": "/api/group/get_group_list", 4 | "getGroupFriendList": '/api/group/get_group_user_list', 5 | // 获取个人基本信息 6 | "getUserInfo": '/api/user/get_info', 7 | 8 | // 未处理请求列表 9 | "getFriendApplyList": '/api/user/friend_apply_list', 10 | 11 | // 未处理群请求列表 12 | "getGroupApplyList": '/api/user/group_apply_list' 13 | } 14 | 15 | export default urlList; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/util/RedisUtil.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.util; 2 | 3 | 4 | import com.thankjava.toolkit3d.core.db.BasicDBManagerBuilder; 5 | import com.thankjava.toolkit3d.core.db.redis.RedisManager; 6 | 7 | public class RedisUtil { 8 | 9 | private final static RedisManager redisManager = BasicDBManagerBuilder.buildRedisManager("./config/redis.properties"); 10 | 11 | public static RedisManager getRedisManager() { 12 | return redisManager; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/ws/anno/WSController.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.ws.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @Author: acexy@thankjava.com 10 | * 2018/8/9 11 | * @Description: 12 | **/ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.TYPE) 15 | public @interface WSController { 16 | 17 | String path(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/ws/anno/WSProcess.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.ws.anno; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * @Author: acexy@thankjava.com 10 | * 2018/8/9 11 | * @Description: 12 | **/ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target(ElementType.METHOD) 15 | public @interface WSProcess { 16 | 17 | String path(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/controller/Notice.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.controller; 2 | 3 | import com.thankjava.wchat.bean.RequestContext; 4 | import com.thankjava.wchat.ws.anno.WSController; 5 | import com.thankjava.wchat.ws.anno.WSProcess; 6 | 7 | /** 8 | * @Author: acexy@thankjava.com 9 | * 2018/8/10 10 | * @Description: 11 | **/ 12 | @WSController(path = "notice") 13 | public class Notice { 14 | 15 | 16 | @WSProcess(path = "event") 17 | public void event(RequestContext ctx) { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /html-web/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import * as actions from './actions'; 4 | import * as getters from './getters'; 5 | import state from './state'; 6 | import mutations from './mutations'; 7 | import createLogger from 'vuex/dist/logger'; 8 | 9 | Vue.use(Vuex); 10 | const debug = process.env.NODE_ENV !== 'production' 11 | 12 | export default new Vuex.Store({ 13 | actions, 14 | getters, 15 | state, 16 | mutations, 17 | strict: debug, //开发环境使用严格模式 18 | plugins: debug ? [createLogger()] : [] 19 | }) -------------------------------------------------------------------------------- /node-http/const/code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/13 4 | * @Description: 5 | */ 6 | module.exports = { 7 | INVALID_ACCOUNT: "1000", // 不存在的用户账号 8 | PASSWORD_ERROR: "1001", // 密码错误 9 | THE_SAME_PASSWORD: "1002", // 修改的密码和原密码相同 10 | INVALID_NEW_PASSWORD: "1003", // 密码长度过低/过高 11 | EXISTING_USER: "1005", // 存在的用户 12 | EXISTING_GROUP_NAME: "1007", // 群帐号已存在 13 | ERROR_ACCOUNT_OR_EMAIL: "1010", // 无效的邮箱或者帐号 14 | NOT_YOUR_FRIEND: "1011", // 对方还不是你的好友 15 | }; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/callback/ConnectionVerifyListener.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket.callback; 2 | 3 | import com.thankjava.wchat.lib.websocket.entity.ConVerifyResult; 4 | import org.java_websocket.handshake.ClientHandshake; 5 | 6 | /** 7 | * @Author: acexy@thankjava.com 8 | * 2018/8/6 9 | * @Description: 连接验证 10 | **/ 11 | public interface ConnectionVerifyListener { 12 | 13 | /** 14 | * doProcess 15 | * @param handshake 16 | * @return 17 | */ 18 | ConVerifyResult doProcess(ClientHandshake handshake); 19 | 20 | } -------------------------------------------------------------------------------- /node-http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-http-srv", 3 | "version": "1.0.0", 4 | "description": "wchat http server", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "bluebird": "^3.5.1", 11 | "busboy": "^0.2.14", 12 | "colour": "^0.7.1", 13 | "koa": "^2.5.1", 14 | "koa-bodyparser": "^4.2.1", 15 | "koa-router": "^7.4.0", 16 | "md5": "^2.2.1", 17 | "mongoose": "5.7.5", 18 | "nodemailer": "^4.6.8", 19 | "redis": "^2.8.0", 20 | "uuid": "^3.2.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/controller/group/GroupAdd.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.controller.group; 2 | 3 | public class GroupAdd { 4 | 5 | private String groupId; 6 | private String remark; 7 | 8 | public String getGroupId() { 9 | return groupId; 10 | } 11 | 12 | public void setGroupId(String groupId) { 13 | this.groupId = groupId; 14 | } 15 | 16 | public String getRemark() { 17 | return remark; 18 | } 19 | 20 | public void setRemark(String remark) { 21 | this.remark = remark; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/consts/EventType.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.consts; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/10 6 | * @Description: 7 | **/ 8 | public enum EventType { 9 | 10 | chat_f2f, 11 | chat_group, 12 | 13 | friend_add, 14 | friend_deleted, 15 | friend_status_change, 16 | friend_add_result, 17 | 18 | group_invite, 19 | group_kicked_out, 20 | group_add, 21 | group_join, 22 | group_leave, 23 | group_add_result, 24 | group_invite_result, 25 | 26 | forced_logout, 27 | ; 28 | } 29 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/controller/group/GroupAddReply.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.controller.group; 2 | 3 | public class GroupAddReply { 4 | 5 | private String groupApplyId; 6 | private Boolean agree; 7 | 8 | public String getGroupApplyId() { 9 | return groupApplyId; 10 | } 11 | 12 | public void setGroupApplyId(String groupApplyId) { 13 | this.groupApplyId = groupApplyId; 14 | } 15 | 16 | public Boolean getAgree() { 17 | return agree; 18 | } 19 | 20 | public void setAgree(Boolean agree) { 21 | this.agree = agree; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /node-http/lib/asyncRedis.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/1 4 | * @Description:redis 5 | */ 6 | const redis = require('redis'); 7 | const bluebird = require('bluebird'); 8 | 9 | bluebird.promisifyAll(redis.RedisClient.prototype); 10 | bluebird.promisifyAll(redis.Multi.prototype); 11 | 12 | const client = redis.createClient(require('../config/redis.json')); 13 | 14 | console.log('=> redis: '.magenta + 'connected'.grey); 15 | module.exports.client = client; 16 | module.exports.closeClient = () => client.quit(); 17 | 18 | client.on('error', (err) => { 19 | console.log('=> redis: '.magenta + 'error'.red); 20 | console.error(err); 21 | }); -------------------------------------------------------------------------------- /html-web/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 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/controller/friend/FriendAddReply.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.controller.friend; 2 | 3 | /** 4 | * 处理好友申请 5 | */ 6 | public class FriendAddReply { 7 | 8 | private String friendApplyId; 9 | private Boolean agree; 10 | 11 | public String getFriendApplyId() { 12 | return friendApplyId; 13 | } 14 | 15 | public void setFriendApplyId(String friendApplyId) { 16 | this.friendApplyId = friendApplyId; 17 | } 18 | 19 | public Boolean getAgree() { 20 | return agree; 21 | } 22 | 23 | public void setAgree(Boolean agree) { 24 | this.agree = agree; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /html-web/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const SET_FRIENDLIST = 'SET_FRIENDLIST'; 2 | 3 | export const SET_CURRENT_FRIEND = 'SET_CURRENT_FRIEND'; 4 | 5 | export const GET_USERINFO = 'GET_USERINFO'; 6 | 7 | export const UPDATE_TYPE = 'UPDATE_TYPE'; 8 | 9 | export const SET_CURRENT_GROUP = 'SET_CURRENT_GROUP'; 10 | 11 | export const SET_GROUPLIST = 'SET_GROUPLIST'; 12 | 13 | export const SET_SESSIONS = 'SET_SESSIONS'; 14 | 15 | export const SET_GROUP_USER_LIST = 'SET_GROUP_USER_LIST'; 16 | 17 | export const SET_GROUP_APPLY_LIST = 'SET_GROUP_APPLY_LIST'; 18 | 19 | export const SET_FRIEND_APPLY_LIST = 'SET_FRIEND_APPLY_LIST'; 20 | 21 | export const SET_GROUP_SESSIONS = 'SET_GROUP_SESSIONS'; 22 | 23 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/BaseMapper.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo; 2 | 3 | 4 | import com.thankjava.toolkit3d.core.db.BasicDBManagerBuilder; 5 | import com.thankjava.toolkit3d.core.db.mongodb.MongoManager; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Author: acexy@thankjava.com 11 | * 2018/8/14 12 | * @Description: 13 | **/ 14 | public interface BaseMapper { 15 | 16 | MongoManager mongo = BasicDBManagerBuilder.buildMongoManager("./config/mongodb.properties"); 17 | 18 | T selectById(String id); 19 | 20 | String insert(T t); 21 | 22 | List selectByCondition(T t); 23 | 24 | boolean updateById(T t); 25 | 26 | void deleteById(String id); 27 | } 28 | -------------------------------------------------------------------------------- /html-web/src/assets/scripts/ws/apiRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 请求非长连的API 3 | * @param url 4 | * @param data 5 | * @param callback 6 | */ 7 | export function wsRequest(url, data, callback, failedCallback) { 8 | 9 | let ws = new WebSocket(url); 10 | 11 | ws.onopen = event => { 12 | if (ws && ws.readyState == WebSocket.OPEN) { 13 | ws.send(JSON.stringify(data)); 14 | } 15 | }; 16 | 17 | ws.onclose = event => { 18 | failedCallback(); 19 | }; 20 | 21 | ws.onerror = event => { 22 | failedCallback(); 23 | }; 24 | 25 | ws.onmessage = event => { 26 | // fixme 调试输出 27 | console.log(event.data); 28 | callback(JSON.parse(event.data)); 29 | ws.close(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.entity; 2 | 3 | import com.alibaba.fastjson.annotation.JSONField; 4 | 5 | /** 6 | * @Author: acexy@thankjava.com 7 | * 2018/8/14 8 | * @Description: 9 | **/ 10 | public class BaseEntity { 11 | 12 | @JSONField(name = "_id") 13 | private String id; 14 | 15 | private Long createTime; 16 | 17 | public String getId() { 18 | return id; 19 | } 20 | public void setId(String id) { 21 | this.id = id; 22 | } 23 | public Long getCreateTime() { 24 | return createTime; 25 | } 26 | public void setCreateTime(Long createTime) { 27 | this.createTime = createTime; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/controller/friend/FriendAdd.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.controller.friend; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/14 6 | * @Description: 好友新增申请 7 | **/ 8 | public class FriendAdd { 9 | 10 | // 添加好友时发送的备注申请 11 | private String remark; 12 | 13 | // 请求添加的用户id 14 | private String userId; 15 | 16 | public String getRemark() { 17 | return remark; 18 | } 19 | 20 | public void setRemark(String remark) { 21 | this.remark = remark; 22 | } 23 | 24 | public String getUserId() { 25 | return userId; 26 | } 27 | 28 | public void setUserId(String userId) { 29 | this.userId = userId; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/App.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat; 2 | 3 | import com.thankjava.wchat.lib.websocket.BasicWebSocket; 4 | import com.thankjava.wchat.ws.core.ConnectionVerifyCallBack; 5 | import com.thankjava.wchat.ws.core.OnConnCloseCallBack; 6 | import com.thankjava.wchat.ws.core.OnMessageCallBack; 7 | import org.java_websocket.WebSocketImpl; 8 | 9 | /** 10 | * @Author: acexy@thankjava.com 11 | * 2018/8/3 12 | * @Description: 13 | **/ 14 | public class App { 15 | 16 | public static void main(String[] args) { 17 | 18 | new BasicWebSocket( 19 | 8004, 20 | new ConnectionVerifyCallBack(), 21 | new OnMessageCallBack(), 22 | new OnConnCloseCallBack() 23 | ).run(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/WS.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket; 2 | 3 | import com.thankjava.wchat.consts.ResponseCode; 4 | import org.java_websocket.WebSocket; 5 | 6 | /** 7 | * @Author: acexy@thankjava.com 8 | * 2018/8/6 9 | * @Description: 10 | **/ 11 | public interface WS { 12 | 13 | /** 14 | * 向指定用户发送 WebSocket 文本信息 15 | * 16 | * @param sessionId 17 | * @param msgText 18 | * @return 19 | */ 20 | ResponseCode sendMsg(String sessionId, String msgText); 21 | 22 | /** 23 | * 关闭连接 24 | * @param sessionId 25 | */ 26 | void closeConn(String sessionId); 27 | 28 | /** 29 | * 判断连接是否存在有效 30 | * @param sessionId 31 | * @return 32 | */ 33 | boolean existConn(String sessionId); 34 | 35 | } 36 | -------------------------------------------------------------------------------- /node-http/models/groupApply.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 18/8/20 4 | * @Description: 5 | */ 6 | const baseModel = require('./basic.js'); 7 | 8 | class groupApply extends baseModel { 9 | 10 | Schema() { 11 | return { 12 | goGroupId: {type: String, required: true}, 13 | applyUserId: {type: String, required: true}, 14 | processed: {type: Boolean}, 15 | remark: {type: String}, 16 | }; 17 | }; 18 | 19 | SchemaName() { 20 | return 'group_apply'; 21 | }; 22 | 23 | selectById(id) { 24 | return this.model.find({_id: baseModel.typeObject(id)}).exec(); 25 | } 26 | 27 | selectByToGroupIdAndUnprocessed(groupId) { 28 | return this.model.find({toGroupId: groupId, processed: false}).exec(); 29 | } 30 | 31 | } 32 | 33 | module.exports = new groupApply(); -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/controller/chat/ChatMsg.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.controller.chat; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/10 6 | * @Description: 7 | **/ 8 | public class ChatMsg { 9 | 10 | private String toUserId; 11 | private String toGroupId; 12 | private String msg; 13 | 14 | public String getToUserId() { 15 | return toUserId; 16 | } 17 | 18 | public void setToUserId(String toUserId) { 19 | this.toUserId = toUserId; 20 | } 21 | 22 | public String getToGroupId() { 23 | return toGroupId; 24 | } 25 | 26 | public void setToGroupId(String toGroupId) { 27 | this.toGroupId = toGroupId; 28 | } 29 | 30 | public String getMsg() { 31 | return msg; 32 | } 33 | 34 | public void setMsg(String msg) { 35 | this.msg = msg; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/entity/GroupRelation.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.entity; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/14 6 | * @Description: 7 | **/ 8 | public class GroupRelation extends BaseEntity { 9 | 10 | private String groupId; 11 | private String userId; 12 | private String remark; 13 | 14 | public String getGroupId() { 15 | return groupId; 16 | } 17 | 18 | public void setGroupId(String groupId) { 19 | this.groupId = groupId; 20 | } 21 | 22 | public String getUserId() { 23 | return userId; 24 | } 25 | 26 | public void setUserId(String userId) { 27 | this.userId = userId; 28 | } 29 | 30 | public String getRemark() { 31 | return remark; 32 | } 33 | 34 | public void setRemark(String remark) { 35 | this.remark = remark; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /node-http/models/groupRelation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 18/8/20 4 | * @Description: 5 | */ 6 | const baseModel = require('./basic.js'); 7 | 8 | class groupRelation extends baseModel { 9 | 10 | Schema() { 11 | return { 12 | groupId: {type: String, required: true}, 13 | userId: {type: String, required: true}, 14 | remark: {type: String, required: false}, 15 | }; 16 | }; 17 | 18 | SchemaName() { 19 | return 'group_relation'; 20 | }; 21 | 22 | save(groupRelation) { 23 | return new this.model(groupRelation).save(); 24 | } 25 | 26 | selectByUserId(userId) { 27 | return this.model.find({userId: userId}).exec(); 28 | } 29 | 30 | selectByGroupId(groupId) { 31 | return this.model.find({groupId: groupId}).exec(); 32 | } 33 | 34 | } 35 | 36 | module.exports = new groupRelation(); -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/chat/ChatMsgPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.chat; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/10 6 | * @Description: 7 | **/ 8 | public class ChatMsgPush { 9 | 10 | private String toGroupId; 11 | private String msg; 12 | 13 | 14 | public ChatMsgPush(String toGroupId, String msg) { 15 | this.toGroupId = toGroupId; 16 | this.msg = msg; 17 | } 18 | 19 | public ChatMsgPush(String msg) { 20 | this.msg = msg; 21 | } 22 | 23 | public String getToGroupId() { 24 | return toGroupId; 25 | } 26 | 27 | public void setToGroupId(String toGroupId) { 28 | this.toGroupId = toGroupId; 29 | } 30 | 31 | public String getMsg() { 32 | return msg; 33 | } 34 | 35 | public void setMsg(String msg) { 36 | this.msg = msg; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /node-http/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/11 4 | * @Description: app.js 5 | */ 6 | require('./extends'); 7 | 8 | const basicConfig = require('./config/basic'); 9 | const mongodb = require('./lib/mongodb'); 10 | const Koa = require('koa'); 11 | const bodyParser = require('koa-bodyparser'); 12 | const routerScanner = require('./routers/routerScanner'); 13 | 14 | mongodb.open(function () { 15 | 16 | const app = new Koa(); 17 | 18 | app.proxy = true; 19 | 20 | app.use(bodyParser({ 21 | enableTypes: ['json','form'] 22 | })); 23 | 24 | app.use(require('./middleware/basic')); 25 | app.use(require('./middleware/auth')); 26 | 27 | app.use(routerScanner.routes()); 28 | app.use(routerScanner.allowedMethods()); 29 | 30 | app.listen(basicConfig.port, () => { 31 | console.log('=> koa: '.cyan + 'listened port = '.grey + String(basicConfig.port).blue); 32 | }); 33 | }); -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/entity/FriendRelation.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.entity; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/14 6 | * @Description: 好友关系中间信息 7 | **/ 8 | public class FriendRelation extends BaseEntity { 9 | 10 | private String userId; 11 | private String friendUserId; 12 | private String remark; 13 | 14 | public String getUserId() { 15 | return userId; 16 | } 17 | 18 | public void setUserId(String userId) { 19 | this.userId = userId; 20 | } 21 | 22 | public String getFriendUserId() { 23 | return friendUserId; 24 | } 25 | 26 | public void setFriendUserId(String friendUserId) { 27 | this.friendUserId = friendUserId; 28 | } 29 | 30 | public String getRemark() { 31 | return remark; 32 | } 33 | 34 | public void setRemark(String remark) { 35 | this.remark = remark; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /node-http/models/friendRelation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 18/8/20 4 | * @Description: 5 | */ 6 | const baseModel = require('./basic.js'); 7 | 8 | class friendRelation extends baseModel { 9 | 10 | Schema() { 11 | return { 12 | userId: {type: String, required: true}, 13 | friendUserId: {type: String, required: true}, 14 | remark: {type: String, required: false}, 15 | }; 16 | }; 17 | 18 | SchemaName() { 19 | return 'friend_relation'; 20 | }; 21 | 22 | 23 | selectByUserId(userId) { 24 | return this.model.find({userId: userId}).exec(); 25 | } 26 | 27 | selectByConditionOnlyOne(friendRelation) { 28 | return this.model.findOne(friendRelation).exec(); 29 | } 30 | 31 | updateById(id, friendRelation) { 32 | return this.model.updateOne({_id: baseModel.typeObject(id)}, friendRelation).exec(); 33 | } 34 | } 35 | 36 | module.exports = new friendRelation(); -------------------------------------------------------------------------------- /html-web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | // root: true, 5 | // parserOptions: { 6 | // parser: 'babel-eslint' 7 | // }, 8 | // env: { 9 | // browser: true, 10 | // }, 11 | // extends: [ 12 | // // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | // 'plugin:vue/essential', 15 | // // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | // 'standard' 17 | // ], 18 | // // required to lint *.vue files 19 | // plugins: [ 20 | // 'vue' 21 | // ], 22 | // // add your custom rules here 23 | // rules: { 24 | // // allow async-await 25 | // 'generator-star-spacing': 'off', 26 | // // allow debugger during development 27 | // 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 28 | // } 29 | } 30 | -------------------------------------------------------------------------------- /html-web/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 {Button, Form, FormItem, Input, Card} from 'element-ui' 7 | import ElementUI from 'element-ui'; 8 | import 'element-ui/lib/theme-chalk/index.css'; 9 | Vue.config.productionTip = false 10 | 11 | import "./assets/styles/reset.css" 12 | import "./assets/styles/animate.css" 13 | import "./assets/styles/iconfont.css" 14 | // import store from './store/store.js' 15 | import store from './store/index.js' 16 | import $ from 'jquery' 17 | 18 | // Vue.use(Button) 19 | // Vue.use(Form) 20 | // Vue.use(FormItem) 21 | // Vue.use(Input) 22 | // Vue.use(Card) 23 | Vue.use(ElementUI) 24 | Vue.config.productionTip = false 25 | 26 | /* eslint-disable no-new */ 27 | new Vue({ 28 | el: '#app', 29 | router, 30 | store, 31 | components: { App }, 32 | template: '' 33 | }) 34 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/impl/GroupInfoMapperImpl.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo.impl; 2 | 3 | import com.thankjava.wchat.consts.MongoTableName; 4 | import com.thankjava.wchat.db.entity.GroupInfo; 5 | import com.thankjava.wchat.db.mongo.GroupInfoMapper; 6 | 7 | import java.util.List; 8 | 9 | public class GroupInfoMapperImpl implements GroupInfoMapper { 10 | 11 | private final String tableName = MongoTableName.group_info.value(); 12 | 13 | 14 | @Override 15 | public GroupInfo selectById(String id) { 16 | return mongo.findByObjectId(tableName, id, GroupInfo.class); 17 | } 18 | 19 | @Override 20 | public String insert(GroupInfo groupInfo) { 21 | return null; 22 | } 23 | 24 | @Override 25 | public List selectByCondition(GroupInfo groupInfo) { 26 | return null; 27 | } 28 | 29 | @Override 30 | public boolean updateById(GroupInfo groupInfo) { 31 | return false; 32 | } 33 | 34 | @Override 35 | public void deleteById(String id) { 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/notice/OnlinePush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.notice; 2 | 3 | public class OnlinePush { 4 | 5 | private String username; 6 | private String nickname; 7 | private String headImg; 8 | private String sign; 9 | 10 | private String status = "1"; 11 | 12 | public String getUsername() { 13 | return username; 14 | } 15 | 16 | public void setUsername(String username) { 17 | this.username = username; 18 | } 19 | 20 | public String getNickname() { 21 | return nickname; 22 | } 23 | 24 | public void setNickname(String nickname) { 25 | this.nickname = nickname; 26 | } 27 | 28 | public String getHeadImg() { 29 | return headImg; 30 | } 31 | 32 | public void setHeadImg(String headImg) { 33 | this.headImg = headImg; 34 | } 35 | 36 | public String getSign() { 37 | return sign; 38 | } 39 | 40 | public void setSign(String sign) { 41 | this.sign = sign; 42 | } 43 | 44 | public String getStatus() { 45 | return status; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/util/Utils.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.util; 2 | 3 | import com.thankjava.wchat.consts.CookieName; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class Utils { 11 | 12 | static Logger logger = LoggerFactory.getLogger(Utils.class); 13 | 14 | /** 15 | * 从cookie字符串中解析出指定的cookie值 16 | * 17 | * @param cookieStr 18 | * @return 19 | */ 20 | public static String getValueForCookieStr(String cookieStr, CookieName cookieName) { 21 | logger.info("cookieStr = " + cookieStr + " cookieName = " + cookieName.code); 22 | String[] cookies = cookieStr.split("; "); 23 | Map cookieKV = new HashMap<>(); 24 | String[] tmp; 25 | for (String cookie : cookies) { 26 | tmp = cookie.trim().split("="); 27 | if (tmp == null || tmp.length != 2) { 28 | return null; 29 | } 30 | cookieKV.put(tmp[0].trim(), tmp[1].trim()); 31 | } 32 | return cookieKV.get(cookieName.code); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/core/WSImpl.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket.core; 2 | 3 | import com.thankjava.wchat.consts.ResponseCode; 4 | import com.thankjava.wchat.lib.websocket.Session; 5 | import com.thankjava.wchat.lib.websocket.WS; 6 | import org.java_websocket.WebSocket; 7 | 8 | /** 9 | * @Author: acexy@thankjava.com 10 | * 2018/8/7 11 | * @Description: 12 | **/ 13 | final class WSImpl extends Session implements WS { 14 | 15 | private WSImpl() { 16 | } 17 | 18 | @Override 19 | public ResponseCode sendMsg(String sessionId, String msgText) { 20 | WebSocket conn = Session.getConn(sessionId); 21 | if (conn == null) return ResponseCode.TARGET_OFFLINE; 22 | conn.send(msgText); 23 | return ResponseCode.SUCCESS; 24 | } 25 | 26 | @Override 27 | public void closeConn(String sessionId) { 28 | WebSocket conn = Session.getConn(sessionId); 29 | if (conn == null) return; 30 | conn.close(); 31 | } 32 | 33 | @Override 34 | public boolean existConn(String sessionId) { 35 | return Session.getConn(sessionId) != null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/notice/OfflinePush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.notice; 2 | 3 | public class OfflinePush { 4 | 5 | private String status = "0"; 6 | 7 | private String username; 8 | private String nickname; 9 | private String headImg; 10 | private String sign; 11 | 12 | public OfflinePush(){} 13 | 14 | public String getStatus() { 15 | return status; 16 | } 17 | 18 | public String getUsername() { 19 | return username; 20 | } 21 | 22 | public void setUsername(String username) { 23 | this.username = username; 24 | } 25 | 26 | public String getNickname() { 27 | return nickname; 28 | } 29 | 30 | public void setNickname(String nickname) { 31 | this.nickname = nickname; 32 | } 33 | 34 | public String getHeadImg() { 35 | return headImg; 36 | } 37 | 38 | public void setHeadImg(String headImg) { 39 | this.headImg = headImg; 40 | } 41 | 42 | public String getSign() { 43 | return sign; 44 | } 45 | 46 | public void setSign(String sign) { 47 | this.sign = sign; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/group/GroupUser.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.group; 2 | 3 | public class GroupUser { 4 | 5 | private String userId; 6 | private String nickname; 7 | private String headImg; 8 | private String sign; 9 | private String sex; 10 | 11 | public String getUserId() { 12 | return userId; 13 | } 14 | 15 | public void setUserId(String userId) { 16 | this.userId = userId; 17 | } 18 | 19 | public String getNickname() { 20 | return nickname; 21 | } 22 | 23 | public void setNickname(String nickname) { 24 | this.nickname = nickname; 25 | } 26 | 27 | public String getHeadImg() { 28 | return headImg; 29 | } 30 | 31 | public void setHeadImg(String headImg) { 32 | this.headImg = headImg; 33 | } 34 | 35 | public String getSign() { 36 | return sign; 37 | } 38 | 39 | public void setSign(String sign) { 40 | this.sign = sign; 41 | } 42 | 43 | public String getSex() { 44 | return sex; 45 | } 46 | 47 | public void setSex(String sex) { 48 | this.sex = sex; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /node-http/models/friendApply.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 18/8/20 4 | * @Description: 5 | */ 6 | const baseModel = require('./basic.js'); 7 | 8 | class friendApply extends baseModel { 9 | 10 | Schema() { 11 | return { 12 | toUserId: {type: String, required: true}, 13 | applyUserId: {type: String, required: true}, 14 | agree: {type: Boolean, required: false}, 15 | processed: {type: Boolean, required: false}, 16 | remark: {type: String, required: false}, 17 | }; 18 | }; 19 | 20 | SchemaName() { 21 | return 'friend_apply'; 22 | }; 23 | 24 | selectByUserId(userId) { 25 | return this.model.find({toUserId: userId}).exec(); 26 | } 27 | 28 | selectByToUserIdAndUnprocessed(toUserId) { 29 | return this.model.find({toUserId: toUserId, processed: false}).exec(); 30 | } 31 | 32 | selectByConditionOnlyOne(friendRelation) { 33 | return this.model.findOne(friendRelation).exec(); 34 | } 35 | 36 | updateById(id, friendRelation) { 37 | return this.model.updateOne({_id: baseModel.typeObject(id)}, friendRelation).exec(); 38 | } 39 | } 40 | 41 | module.exports = new friendApply(); -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/impl/GroupApplyMapperImpl.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo.impl; 2 | 3 | import com.thankjava.wchat.consts.MongoTableName; 4 | import com.thankjava.wchat.db.entity.GroupApply; 5 | import com.thankjava.wchat.db.mongo.GroupApplyMapper; 6 | 7 | import java.util.List; 8 | 9 | public class GroupApplyMapperImpl implements GroupApplyMapper { 10 | 11 | private final String tableName = MongoTableName.group_apply.value(); 12 | 13 | @Override 14 | public GroupApply selectById(String id) { 15 | return mongo.findByObjectId(tableName, id, GroupApply.class); 16 | } 17 | 18 | @Override 19 | public String insert(GroupApply groupApply) { 20 | return mongo.insertOne(tableName, groupApply); 21 | } 22 | 23 | @Override 24 | public List selectByCondition(GroupApply groupApply) { 25 | return mongo.findMany(tableName, groupApply, GroupApply.class); 26 | } 27 | 28 | @Override 29 | public boolean updateById(GroupApply groupApply) { 30 | return mongo.updateOneByObjectId(tableName, groupApply, groupApply.getId()); 31 | } 32 | 33 | @Override 34 | public void deleteById(String id) { 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/consts/RedisKeyManager.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.consts; 2 | 3 | /** 4 | * RedisKey 管理 5 | */ 6 | public class RedisKeyManager { 7 | 8 | private RedisKeyManager() { 9 | } 10 | 11 | private final static String BASIC_KEY_PREFIX = "wchat"; 12 | 13 | /** 14 | * 创建缓存key 15 | * 16 | * @param keys 17 | * @return 18 | */ 19 | private static String buildKey(String... keys) { 20 | if (keys == null || keys.length == 0) { 21 | return ""; 22 | } 23 | StringBuilder stringBuilder = new StringBuilder(BASIC_KEY_PREFIX); 24 | for (String key : keys) { 25 | stringBuilder.append(":"); 26 | stringBuilder.append(key); 27 | } 28 | return stringBuilder.toString(); 29 | } 30 | 31 | 32 | /** 33 | * cookie 用户 token 34 | * 35 | * @param token 36 | * @return 37 | */ 38 | public static String TOKEN_KEY(String token) { 39 | return buildKey("token", token); 40 | } 41 | 42 | /** 43 | * 在线用户key集合 44 | * @return 45 | */ 46 | public static String ONLINE_SET_KEY() { 47 | return buildKey("user", "online"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/consts/ResponseCode.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.consts; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/10 6 | * @Description: 7 | **/ 8 | public enum ResponseCode { 9 | 10 | SUCCESS("0000", "处理完成"), 11 | FAILED("9998", "处理失败"), 12 | BAD_REQUEST_PARAMETER("9997", "请求参数错误"), 13 | 14 | TARGET_OFFLINE("8999", "目标已离线"), 15 | 16 | // chat 0001 - 0100 17 | NOT_YOUR_FRIEND("0001", "对方不是你的好友"), 18 | 19 | // friend 0101 - 0200 20 | FRIEND_APPLY_PROCESSED("0101", "该好友申请已处理"), 21 | USE_NOT_EXIST("0102", "不存在的用户"), 22 | ALREADY_FRIEND_ELATION("0103", "该用户已经是你的好友"), 23 | CAN_NOT_ADD_YOURSELF("0104", "不能添加自己"), 24 | EXIST_FRIEND_APPLY("0105", "已经发送过好友申请"), 25 | 26 | // group 0201 - 0300 27 | GROUP_NOT_EXIST("0201", "不存在的群组信息"), 28 | ALREADY_GROUP_ELATION("0202", "已经加入该群"), 29 | EXIST_GROUP_APPLY("0203", "已经发起过群组申请"), 30 | GROUP_APPLY_NOT_EXIST("0204", "不存在的群组申请id"), 31 | GROUP_APPLY_PROCESSED("0205", "该群组申请已处理"), 32 | 33 | 34 | 35 | ; 36 | 37 | ResponseCode(String code, String desc) { 38 | this.code = code; 39 | this.desc = desc; 40 | } 41 | 42 | public String code; 43 | public String desc; 44 | } 45 | -------------------------------------------------------------------------------- /node-http/lib/mongodb.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/15 4 | * @Description: mongoose 5 | */ 6 | const mongoose = require('mongoose'); 7 | 8 | const mongoConfig = require('../config/mongo'); 9 | 10 | module.exports.open = (cb) => { 11 | 12 | const connStr = 'mongodb://' + mongoConfig.username + ':' + mongoConfig.password + '@' + mongoConfig.host + ':' + mongoConfig.port + '/' + mongoConfig.dbname; 13 | 14 | mongoose.connect(connStr, {useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true}); 15 | 16 | let db = mongoose.connection; 17 | 18 | db.on('error', function (err) { 19 | console.error('=> mongoose: '.green + ' error'.red); 20 | console.error(err); 21 | }); 22 | 23 | db.once('open', function () { 24 | console.log('=> mongoose: '.green + 'connected'.grey); 25 | cb(); 26 | }); 27 | }; 28 | 29 | const models = new Map(); 30 | 31 | module.exports.loadModel = (modelName, schema) => { 32 | 33 | let model = models.get(modelName); 34 | if (model) return model; 35 | 36 | console.log('=> mongoose: '.green + 'loadModel modelName = '.grey + modelName.blue); 37 | model = mongoose.model(modelName, schema, modelName); 38 | models.set(modelName, model); 39 | 40 | return model; 41 | }; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/impl/FriendApplyMapperImpl.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo.impl; 2 | 3 | import com.thankjava.wchat.consts.MongoTableName; 4 | import com.thankjava.wchat.db.entity.FriendApply; 5 | import com.thankjava.wchat.db.mongo.FriendApplyMapper; 6 | 7 | import java.util.List; 8 | 9 | public class FriendApplyMapperImpl implements FriendApplyMapper { 10 | 11 | private final String tableName = MongoTableName.friend_apply.value(); 12 | 13 | @Override 14 | public FriendApply selectById(String id) { 15 | return mongo.findByObjectId(tableName, id, FriendApply.class); 16 | } 17 | 18 | @Override 19 | public String insert(FriendApply friendApply) { 20 | friendApply.setCreateTime(System.currentTimeMillis()); 21 | return mongo.insertOne(tableName, friendApply); 22 | } 23 | 24 | @Override 25 | public List selectByCondition(FriendApply friendApply) { 26 | return mongo.findMany(tableName, friendApply, FriendApply.class); 27 | } 28 | 29 | @Override 30 | public boolean updateById(FriendApply friendApply) { 31 | return mongo.updateOneByObjectId(tableName, friendApply, friendApply.getId()); 32 | } 33 | 34 | @Override 35 | public void deleteById(String id) { 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /html-web/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 | -------------------------------------------------------------------------------- /node-http/lib/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/5/28 4 | * @Description:utils 5 | */ 6 | const md5 = require('md5'); 7 | const uuid = require('uuid/v4'); 8 | const fs = require('fs'); 9 | 10 | /** 11 | * md5 12 | * @param content 13 | * @returns {*} 14 | */ 15 | module.exports.md5 = content => md5(content); 16 | /** 17 | * uuid生成器 18 | * @returns {string} 19 | */ 20 | module.exports.uuid = () => String(uuid()).replace(/-/g, ''); 21 | 22 | /** 23 | * 文件删除 24 | */ 25 | module.exports.fsDel = uris => { 26 | for (let i in uris) { 27 | fs.unlink(uris[i], err => { 28 | if (err) { 29 | console.log('=> util.fsDel: '.cyan + 'uri = '.grey + uris[i] + ' error'.red); 30 | console.error(err) 31 | } 32 | }); 33 | } 34 | }; 35 | 36 | /** 37 | * 生成指定范围随机数 38 | * @param min 39 | * @param max 40 | * @returns {number} 41 | */ 42 | const randomMin2Max = (min, max) => { 43 | return Math.floor(Math.random() * (max - min + 1) + min); 44 | }; 45 | 46 | module.exports.randomMin2Max = randomMin2Max; 47 | 48 | /** 49 | * 生成指定长度的随机数 0-9 50 | * @param length 51 | * @returns {string} 52 | */ 53 | module.exports.randomNum = length => { 54 | let code = ''; 55 | for (var index = 0; index < length; index++) { 56 | code += randomMin2Max(0, 9); 57 | } 58 | return code; 59 | }; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/entity/GroupApply.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.entity; 2 | 3 | /** 4 | * 好友申请记录 5 | */ 6 | public class GroupApply extends BaseEntity{ 7 | 8 | private String toGroupId; 9 | private String applyUserId; 10 | 11 | // 是否同意 12 | private Boolean agree; 13 | 14 | // 是否已处理 15 | private Boolean processed; 16 | 17 | // 申请备注 18 | private String remark; 19 | 20 | public String getToGroupId() { 21 | return toGroupId; 22 | } 23 | 24 | public void setToGroupId(String toGroupId) { 25 | this.toGroupId = toGroupId; 26 | } 27 | 28 | public String getApplyUserId() { 29 | return applyUserId; 30 | } 31 | 32 | public void setApplyUserId(String applyUserId) { 33 | this.applyUserId = applyUserId; 34 | } 35 | 36 | public String getRemark() { 37 | return remark; 38 | } 39 | 40 | public void setRemark(String remark) { 41 | this.remark = remark; 42 | } 43 | 44 | public Boolean getAgree() { 45 | return agree; 46 | } 47 | 48 | public void setAgree(Boolean agree) { 49 | this.agree = agree; 50 | } 51 | 52 | public Boolean getProcessed() { 53 | return processed; 54 | } 55 | 56 | public void setProcessed(Boolean processed) { 57 | this.processed = processed; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /html-web/src/assets/scripts/common.js: -------------------------------------------------------------------------------- 1 | import $axios from 'axios'; 2 | import Qs from 'qs'; 3 | import urlList from './urlList.js'; 4 | function formatTime (time) { 5 | let tempTime; 6 | time ? (tempTime = new Date(parseInt(time, 10))) : (tempTime= new Date()); 7 | return tempTime.getFullYear() + "-" 8 | + (tempTime.getMonth() + 1) + "-" 9 | + tempTime.getDate() + " " 10 | + (tempTime.getHours() < 10 ? "0" + tempTime.getHours() : tempTime.getHours()) + ":" 11 | + (tempTime.getMinutes() < 10 ? "0" + tempTime.getMinutes() : tempTime.getMinutes()) + ":" 12 | + (tempTime.getSeconds() < 10 ? "0" + tempTime.getSeconds() : tempTime.getSeconds()); 13 | } 14 | 15 | function trimString (value) { 16 | return (value || '').replace(/\s/g, ''); 17 | } 18 | 19 | function axios (options) { 20 | let url = urlList[options.url] || ""; 21 | let method = options.method || 'get'; 22 | let data = options.data || {}; 23 | if (!url) { 24 | return; 25 | } 26 | 27 | if (method == 'post') { 28 | data = QS.stringify(data); 29 | } 30 | 31 | return new Promise((resolve, reject) => { 32 | $axios[method](url, { 33 | params: data 34 | }).then(res => { 35 | resolve(res.data); 36 | }).catch(err =>{ 37 | reject(err.data) 38 | }) 39 | }) 40 | 41 | } 42 | 43 | export default {formatTime, trimString, axios}; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/RequestContext.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.thankjava.wchat.db.entity.User; 5 | import org.java_websocket.WebSocket; 6 | 7 | /** 8 | * @Author: acexy@thankjava.com 9 | * 2018/8/9 10 | * @Description: 11 | **/ 12 | public class RequestContext { 13 | 14 | // 消息来源于哪一个用户的userId 15 | private String fromUserId; 16 | private User authUser; 17 | private WebSocket conn; 18 | private JSONObject data; 19 | 20 | public RequestContext(String fromUserId, WebSocket conn, User authUser, JSONObject data) { 21 | this.fromUserId = fromUserId; 22 | this.conn = conn; 23 | this.authUser = authUser; 24 | this.data = data; 25 | } 26 | 27 | public String getFromUserId() { 28 | return fromUserId; 29 | } 30 | 31 | public T getData(Class tClass) { 32 | try { 33 | return data.toJavaObject(tClass); 34 | } catch (Exception e) { 35 | return null; 36 | } 37 | } 38 | 39 | public User getAuthUser() { 40 | return authUser; 41 | } 42 | 43 | /** 44 | * 向该请求返回数据 45 | * 46 | * @param responseContext 47 | */ 48 | public void response(ResponseContext responseContext) { 49 | conn.send(responseContext.toJsonString()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/impl/FriendRelationMapperImpl.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo.impl; 2 | 3 | import com.thankjava.wchat.consts.MongoTableName; 4 | import com.thankjava.wchat.db.entity.FriendRelation; 5 | import com.thankjava.wchat.db.mongo.FriendRelationMapper; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @Author: acexy@thankjava.com 11 | * 2018/8/14 12 | * @Description: 13 | **/ 14 | public class FriendRelationMapperImpl implements FriendRelationMapper { 15 | 16 | private final String tableName = MongoTableName.friend_relation.value(); 17 | 18 | @Override 19 | public FriendRelation selectById(String id) { 20 | return null; 21 | } 22 | 23 | @Override 24 | public String insert(FriendRelation friendRelation) { 25 | friendRelation.setCreateTime(System.currentTimeMillis()); 26 | return mongo.insertOne(tableName, friendRelation); 27 | } 28 | 29 | @Override 30 | public List selectByCondition(FriendRelation friendRelation) { 31 | return mongo.findMany(tableName, friendRelation, FriendRelation.class); 32 | } 33 | 34 | @Override 35 | public boolean updateById(FriendRelation friendRelation) { 36 | return false; 37 | } 38 | 39 | @Override 40 | public void deleteById(String id) { 41 | mongo.deleteOneByObjectId(tableName, id); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /html-web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | wchat 9 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 45 | 46 | -------------------------------------------------------------------------------- /html-web/src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | 3 | const mutations = { 4 | [types.SET_FRIENDLIST](state, list) { 5 | state.friendList = list; 6 | }, 7 | 8 | [types.SET_CURRENT_FRIEND](state, data) { 9 | state.currentFriend = { 10 | friendId: data.id, 11 | friendName: data.name 12 | } 13 | }, 14 | 15 | [types.GET_USERINFO](state, user) { 16 | state.user = user; 17 | }, 18 | 19 | [types.UPDATE_TYPE](state, type) { 20 | state.tabType = type; 21 | }, 22 | 23 | [types.SET_GROUPLIST](state, list) { 24 | state.groupList = list; 25 | }, 26 | 27 | [types.SET_CURRENT_GROUP](state, data) { 28 | state.currentGroup = { 29 | groupId: data.id, 30 | groupName: data.name 31 | } 32 | }, 33 | 34 | [types.SET_SESSIONS](state, sessions) { 35 | state.sessions = sessions; 36 | }, 37 | 38 | [types.SET_GROUP_USER_LIST](state, list) { 39 | state.groupFriendList = list; 40 | }, 41 | 42 | [types.SET_GROUP_APPLY_LIST](state, list) { 43 | state.groupApplyList = list; 44 | }, 45 | 46 | [types.SET_FRIEND_APPLY_LIST](state, list) { 47 | state.friendApplyList = list; 48 | }, 49 | 50 | [types.SET_GROUP_SESSIONS](state, sessions) { 51 | state.groupSessions =sessions; 52 | } 53 | 54 | } 55 | 56 | export default mutations; 57 | 58 | 59 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/impl/UserMapperImpl.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo.impl; 2 | 3 | import com.thankjava.wchat.consts.MongoTableName; 4 | import com.thankjava.wchat.db.entity.User; 5 | import com.thankjava.wchat.db.mongo.UserMapper; 6 | import org.bson.Document; 7 | 8 | import java.util.Date; 9 | import java.util.List; 10 | 11 | /** 12 | * @Author: acexy@thankjava.com 13 | * 2018/8/14 14 | * @Description: 15 | **/ 16 | public class UserMapperImpl implements UserMapper { 17 | 18 | private final String tableName = MongoTableName.user.value(); 19 | 20 | @Override 21 | public User selectById(String id) { 22 | return mongo.findByObjectId(tableName, id, User.class); 23 | } 24 | 25 | @Override 26 | public String insert(User user) { 27 | user.setCreateTime(System.currentTimeMillis()); 28 | return mongo.insertOne(tableName, user); 29 | } 30 | 31 | @Override 32 | public List selectByCondition(User user) { 33 | return mongo.findMany(tableName, user, User.class); 34 | } 35 | 36 | @Override 37 | public boolean updateById(User user) { 38 | return false; 39 | } 40 | 41 | @Override 42 | public void deleteById(String id) { 43 | 44 | } 45 | 46 | @Override 47 | public User selectByUsername(String username) { 48 | return mongo.findOne(tableName, new Document("username", username), User.class); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/entity/Message.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket.entity; 2 | 3 | import com.thankjava.wchat.db.entity.User; 4 | import org.java_websocket.WebSocket; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | 8 | /** 9 | * @Author: acexy@thankjava.com 10 | * 2018/8/9 11 | * @Description: 12 | **/ 13 | public class Message { 14 | 15 | private ConVerifyResult conVerifyResult; 16 | private WebSocket conn; 17 | private String message; 18 | 19 | public Message(ConVerifyResult conVerifyResult, WebSocket conn, String message) { 20 | 21 | this.conVerifyResult = conVerifyResult; 22 | this.conn = conn; 23 | this.message = message; 24 | 25 | } 26 | 27 | public String getFromSessionId() { 28 | return conVerifyResult.getSessionId(); 29 | } 30 | 31 | public String getFromUserId() { 32 | return conVerifyResult.getUserId(); 33 | } 34 | 35 | public String getMessage() { 36 | return message; 37 | } 38 | 39 | public WebSocket getConn() { 40 | return conn; 41 | } 42 | 43 | public User getBindData() { 44 | return (User) conVerifyResult.getBindData(); 45 | } 46 | 47 | /** 48 | * 执行controller逻辑 49 | */ 50 | public void doProcess(Object... args) throws InvocationTargetException, IllegalAccessException { 51 | conVerifyResult.getProcess().invoke(conVerifyResult.getController(), args); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/ResponseContext.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean; 2 | 3 | import com.thankjava.toolkit3d.core.fastjson.FastJson; 4 | import com.thankjava.wchat.consts.ResponseCode; 5 | 6 | /** 7 | * @Author: acexy@thankjava.com 8 | * 2018/8/10 9 | * @Description: 10 | **/ 11 | public class ResponseContext{ 12 | 13 | private String code; 14 | private String desc; 15 | private Object data; 16 | 17 | public ResponseContext(ResponseCode responseCode) { 18 | this.code = responseCode.code; 19 | this.desc = responseCode.desc; 20 | } 21 | 22 | public ResponseContext(ResponseCode responseCode, String desc) { 23 | this.code = responseCode.code; 24 | this.desc = desc; 25 | } 26 | 27 | public ResponseContext(ResponseCode responseCode, Object data) { 28 | this.code = responseCode.code; 29 | this.desc = responseCode.desc; 30 | this.data = data; 31 | } 32 | 33 | public ResponseContext(ResponseCode responseCode, String desc, Object data) { 34 | this.code = responseCode.code; 35 | this.desc = desc; 36 | this.data = data; 37 | } 38 | 39 | public String getCode() { 40 | return code; 41 | } 42 | 43 | public String getDesc() { 44 | return desc; 45 | } 46 | 47 | public Object getData() { 48 | return data; 49 | } 50 | 51 | public String toJsonString() { 52 | return FastJson.toJSONString(this); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/notice/FriendEventPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.notice; 2 | 3 | import com.thankjava.wchat.bean.MsgPushContext; 4 | import com.thankjava.wchat.bean.ResponseContext; 5 | import com.thankjava.wchat.bean.notice.friend.FriendAddPush; 6 | import com.thankjava.wchat.bean.notice.friend.FriendAddReplyPush; 7 | import com.thankjava.wchat.consts.ResponseCode; 8 | import com.thankjava.wchat.util.WSUtil; 9 | 10 | /** 11 | * @Author: acexy@thankjava.com 12 | * 2018/8/14 13 | * @Description: 14 | **/ 15 | public class FriendEventPush { 16 | 17 | 18 | /** 19 | * 推送请求添加好友 20 | * 21 | * @param msgPushContext 22 | * @return 23 | */ 24 | public ResponseContext pushFriendAdd(MsgPushContext msgPushContext) { 25 | ResponseCode responseCode = WSUtil.sendMsg(msgPushContext); 26 | return new ResponseContext(responseCode); 27 | } 28 | 29 | public ResponseContext pushFriendAddReply(MsgPushContext msgPushContext) { 30 | WSUtil.sendMsg(msgPushContext); 31 | return new ResponseContext(ResponseCode.SUCCESS); 32 | } 33 | 34 | public ResponseContext pushFriendDel(MsgPushContext msgPushContext) { 35 | ResponseCode responseCode = WSUtil.sendMsg(msgPushContext); 36 | if (responseCode == ResponseCode.TARGET_OFFLINE) { 37 | return new ResponseContext(ResponseCode.SUCCESS); 38 | } 39 | return new ResponseContext(responseCode); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/mongo/impl/GroupRelationMapperImpl.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.mongo.impl; 2 | 3 | import com.thankjava.wchat.consts.MongoTableName; 4 | import com.thankjava.wchat.db.entity.GroupRelation; 5 | import com.thankjava.wchat.db.mongo.GroupRelationMapper; 6 | import org.bson.Document; 7 | 8 | import java.util.List; 9 | 10 | public class GroupRelationMapperImpl implements GroupRelationMapper { 11 | 12 | private final String tableName = MongoTableName.group_relation.value(); 13 | 14 | @Override 15 | public GroupRelation selectById(String id) { 16 | return mongo.findByObjectId(tableName, id, GroupRelation.class); 17 | } 18 | 19 | @Override 20 | public String insert(GroupRelation groupRelation) { 21 | groupRelation.setCreateTime(System.currentTimeMillis()); 22 | return mongo.insertOne(tableName, groupRelation); 23 | } 24 | 25 | @Override 26 | public List selectByCondition(GroupRelation groupRelation) { 27 | return mongo.findMany(tableName, groupRelation, GroupRelation.class); 28 | } 29 | 30 | @Override 31 | public boolean updateById(GroupRelation groupRelation) { 32 | return false; 33 | } 34 | 35 | @Override 36 | public void deleteById(String id) { 37 | 38 | } 39 | 40 | public List selectByGroupId(String groupId) { 41 | return mongo.findMany(tableName, new Document("groupId", groupId), GroupRelation.class); 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/util/WSUtil.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.util; 2 | 3 | import com.thankjava.toolkit.core.thread.ThreadPool; 4 | import com.thankjava.wchat.bean.MsgPushContext; 5 | import com.thankjava.wchat.consts.ResponseCode; 6 | import com.thankjava.wchat.lib.websocket.WS; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * 包装之后的简单WS处理方法 12 | */ 13 | public class WSUtil { 14 | 15 | private static WS ws; 16 | private static ThreadPool threadPool = new ThreadPool(); 17 | static Logger logger = LoggerFactory.getLogger(WSUtil.class); 18 | 19 | /** 20 | * 同步发起通知 21 | * 22 | * @param msgPushContext 23 | * @return 24 | */ 25 | public static ResponseCode sendMsg(MsgPushContext msgPushContext) { 26 | return ws.sendMsg(msgPushContext.getToUserId().concat(";/notice/event"), msgPushContext.toJsonString()); 27 | } 28 | 29 | /** 30 | * 异步发起通知 31 | * 32 | * @param msgPushContext 33 | */ 34 | public static void sendMsgAsync(MsgPushContext msgPushContext) { 35 | 36 | threadPool.execute(() -> { 37 | ws.sendMsg(msgPushContext.getToUserId().concat(";/notice/event"), msgPushContext.toJsonString()); 38 | }); 39 | 40 | } 41 | 42 | public static boolean isOnline(String userId) { 43 | return ws.existConn(userId.concat(";/notice/event")); 44 | } 45 | 46 | public static void closeConn(String sessionId) { 47 | ws.closeConn(sessionId); 48 | } 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |  2 |  3 |  4 |  5 |  6 |  7 | --- 8 | > ### 基于WebScoket的即时IM聊天工具 9 | - Why 10 | 11 | ``` 12 | QQ微信或者其他沟通工具不能使用(公司限制)?那就Try this 与外界获得联系。😄 13 | 类似wechat网页版,只需双方注册然后互相加为好友就可以进行即时通讯。🌹 14 | ``` 15 | 16 | - 技术栈 17 | 18 | ``` 19 | Browser <---------> WebSocket-Srv 20 | | | 21 | | | 22 | V V 23 | HTTP-Srv -----------> MongoDB & Redis 24 | 25 | > vue (UI) 26 | > node (http srv) 27 | > java (websocket srv jdk>=1.8.0) 28 | 29 | ``` 30 | - 接口文档 31 | 32 | - [http接口文档](https://github.com/lazy-koala/wchat/blob/master/doc/api/http.md) 33 | - [websocket接口文档](https://github.com/lazy-koala/wchat/blob/master/doc/api/websocket.md) 34 | 35 | - ChangeLog 36 | 37 | - 1.0.1 bugfix&接口调优 38 | 39 | - 1.0.0 初始版本 40 | 41 | > #### 关于我们 42 | 43 | [](https://github.com/lazy-koala/) 44 | 45 | [](https://github.com/qazyuan/) [](https://github.com/acexy/) 46 | 47 | --- 48 | > #### 备注 49 | - 由于服务端不记录任何对话内容(包括日志,数据记录等均不追踪用户聊天内容),所以暂无历史记录 50 | -------------------------------------------------------------------------------- /html-web/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 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/friend/FriendAddReplyPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.friend; 2 | 3 | /** 4 | * 好友处理添加申请的结果推送 5 | */ 6 | public class FriendAddReplyPush { 7 | 8 | // 是否同意 9 | private Boolean agree; 10 | 11 | // 好友的userId 12 | private String userId; 13 | private String username; 14 | private String nickname; 15 | private String headImg; 16 | private String sign; 17 | 18 | public Boolean getAgree() { 19 | return agree; 20 | } 21 | 22 | public void setAgree(Boolean agree) { 23 | this.agree = agree; 24 | } 25 | 26 | public String getUserId() { 27 | return userId; 28 | } 29 | 30 | public void setUserId(String userId) { 31 | this.userId = userId; 32 | } 33 | 34 | public String getUsername() { 35 | return username; 36 | } 37 | 38 | public void setUsername(String username) { 39 | this.username = username; 40 | } 41 | 42 | public String getNickname() { 43 | return nickname; 44 | } 45 | 46 | public void setNickname(String nickname) { 47 | this.nickname = nickname; 48 | } 49 | 50 | public String getHeadImg() { 51 | return headImg; 52 | } 53 | 54 | public void setHeadImg(String headImg) { 55 | this.headImg = headImg; 56 | } 57 | 58 | public String getSign() { 59 | return sign; 60 | } 61 | 62 | public void setSign(String sign) { 63 | this.sign = sign; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/entity/User.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.entity; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/10 6 | * @Description: 用户表信息 7 | **/ 8 | public class User extends BaseEntity{ 9 | 10 | private String username; 11 | private String password; 12 | private String nickname; 13 | private String headImg; 14 | private String sign; 15 | private String sex; // -1 - 未设置 1 - 男 0 - 女 16 | 17 | public String getUsername() { 18 | return username; 19 | } 20 | 21 | public void setUsername(String username) { 22 | this.username = username; 23 | } 24 | 25 | public String getPassword() { 26 | return password; 27 | } 28 | 29 | public void setPassword(String password) { 30 | this.password = password; 31 | } 32 | 33 | public String getNickname() { 34 | return nickname; 35 | } 36 | 37 | public void setNickname(String nickname) { 38 | this.nickname = nickname; 39 | } 40 | 41 | public String getHeadImg() { 42 | return headImg; 43 | } 44 | 45 | public void setHeadImg(String headImg) { 46 | this.headImg = headImg; 47 | } 48 | 49 | public String getSign() { 50 | return sign; 51 | } 52 | 53 | public void setSign(String sign) { 54 | this.sign = sign; 55 | } 56 | 57 | public String getSex() { 58 | return sex; 59 | } 60 | 61 | public void setSex(String sex) { 62 | this.sex = sex; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/entity/GroupInfo.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.entity; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/14 6 | * @Description: 7 | **/ 8 | public class GroupInfo extends BaseEntity { 9 | 10 | // 群组名 11 | private String groupNickname; 12 | 13 | // 群帐号 14 | private String groupName; 15 | 16 | // 群主 17 | private String ownerUserId; 18 | 19 | // 简介 20 | private String introduction; 21 | 22 | // 头像 23 | private String headImg; 24 | 25 | 26 | public String getGroupNickname() { 27 | return groupNickname; 28 | } 29 | 30 | public void setGroupNickname(String groupNickname) { 31 | this.groupNickname = groupNickname; 32 | } 33 | 34 | public String getGroupName() { 35 | return groupName; 36 | } 37 | 38 | public void setGroupName(String groupName) { 39 | this.groupName = groupName; 40 | } 41 | 42 | public String getIntroduction() { 43 | return introduction; 44 | } 45 | 46 | public void setIntroduction(String introduction) { 47 | this.introduction = introduction; 48 | } 49 | 50 | public String getHeadImg() { 51 | return headImg; 52 | } 53 | 54 | public void setHeadImg(String headImg) { 55 | this.headImg = headImg; 56 | } 57 | 58 | public String getOwnerUserId() { 59 | return ownerUserId; 60 | } 61 | 62 | public void setOwnerUserId(String ownerUserId) { 63 | this.ownerUserId = ownerUserId; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /html-web/src/assets/styles/reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font: inherit; 23 | /*font-size: 62.5%;*/ 24 | vertical-align: baseline; 25 | font-family: "Microsoft YaHei"; 26 | font-family: "Microsoft JhengHei"; 27 | } 28 | /* HTML5 display-role reset for older browsers */ 29 | article, aside, details, figcaption, figure, 30 | footer, header, hgroup, menu, nav, section { 31 | display: block; 32 | } 33 | 34 | html { 35 | font-size: 100px; 36 | height: 100%; 37 | } 38 | 39 | body { 40 | height: 100%; 41 | line-height: 1; 42 | font-size: 0.14rem; 43 | } 44 | ol, ul { 45 | list-style: none; 46 | } 47 | blockquote, q { 48 | quotes: none; 49 | } 50 | blockquote:before, blockquote:after, 51 | q:before, q:after { 52 | content: ''; 53 | content: none; 54 | } 55 | table { 56 | border-collapse: collapse; 57 | border-spacing: 0; 58 | } -------------------------------------------------------------------------------- /node-http/middleware/basic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/12 4 | * @Description:基础中间件 5 | */ 6 | 7 | module.exports = async (ctx, next) => { 8 | 9 | let startTime = beforeController(ctx); 10 | await next(); 11 | afterController(ctx, startTime); 12 | 13 | }; 14 | 15 | const beforeController = ctx => { 16 | let requestContent = ''; 17 | if (ctx.method == "GET") { 18 | requestContent = ctx.query ? JSON.stringify(ctx.query) : 'null'; 19 | } else if (ctx.method == "POST") { 20 | requestContent = ctx.request.body ? JSON.stringify(ctx.request.body) : 'null'; 21 | } 22 | 23 | // 防止敏感信息打印 取消当前日志输出 24 | let logInfo = '=> koa: '.cyan + '<<< requestContent = '.grey + requestContent.blue + ' | url = '.grey + ctx.url.blue; 25 | console.log(logInfo); 26 | 27 | return Date.now(); 28 | }; 29 | 30 | /** 31 | * 公共处理 controller 完毕后执行 32 | * @param ctx 33 | */ 34 | const afterController = (ctx, startTime) => { 35 | 36 | if (ctx.response.status == 404) { 37 | ctx.body = {message: '404 Or Invalid Response'}; 38 | } else if (ctx.response.status == 405) { 39 | ctx.body = {message: '405 Method Not Allowed'}; 40 | } 41 | 42 | // 防止敏感信息打印 取消当前日志输出 43 | let logInfo = '=> koa: '.cyan + '>>> responseContent = '.grey + '%s' + ' | url = '.grey + '%s'.blue; 44 | logInfo += ' | status = '.grey + '%s'.blue + ' | costTime = '.grey + '%s'.blue + ' ms'.grey; 45 | 46 | console.log(logInfo, ctx.body ? JSON.stringify(ctx.body).blue : 'no data response'.red, ctx.url, ctx.response.status, Date.now() - startTime); 47 | 48 | }; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/friend/FriendAddPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.friend; 2 | 3 | /** 4 | * @Author: acexy@thankjava.com 5 | * 2018/8/14 6 | * @Description: 7 | **/ 8 | public class FriendAddPush { 9 | 10 | // 好友申请记录id 11 | private String friendApplyId; 12 | 13 | private String nickname; 14 | private String headImg; 15 | private String username; 16 | private String remark; 17 | private String sign; 18 | 19 | public String getNickname() { 20 | return nickname; 21 | } 22 | 23 | public void setNickname(String nickname) { 24 | this.nickname = nickname; 25 | } 26 | 27 | public String getHeadImg() { 28 | return headImg; 29 | } 30 | 31 | public void setHeadImg(String headImg) { 32 | this.headImg = headImg; 33 | } 34 | 35 | public String getUsername() { 36 | return username; 37 | } 38 | 39 | public void setUsername(String username) { 40 | this.username = username; 41 | } 42 | 43 | public String getRemark() { 44 | return remark; 45 | } 46 | 47 | public void setRemark(String remark) { 48 | this.remark = remark; 49 | } 50 | 51 | public String getFriendApplyId() { 52 | return friendApplyId; 53 | } 54 | 55 | public void setFriendApplyId(String friendApplyId) { 56 | this.friendApplyId = friendApplyId; 57 | } 58 | 59 | public String getSign() { 60 | return sign; 61 | } 62 | 63 | public void setSign(String sign) { 64 | this.sign = sign; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/db/entity/FriendApply.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.db.entity; 2 | 3 | /** 4 | * 好友申请记录 5 | */ 6 | public class FriendApply extends BaseEntity{ 7 | 8 | private String toUserId; 9 | private String applyUserId; 10 | 11 | // 是否同意 12 | private Boolean agree; 13 | 14 | // 是否已处理 15 | private Boolean processed; 16 | 17 | // 好友申请备注 18 | private String remark; 19 | 20 | private Boolean isApplyUserRead; 21 | 22 | public String getToUserId() { 23 | return toUserId; 24 | } 25 | 26 | public void setToUserId(String toUserId) { 27 | this.toUserId = toUserId; 28 | } 29 | 30 | public String getApplyUserId() { 31 | return applyUserId; 32 | } 33 | 34 | public void setApplyUserId(String applyUserId) { 35 | this.applyUserId = applyUserId; 36 | } 37 | 38 | public String getRemark() { 39 | return remark; 40 | } 41 | 42 | public void setRemark(String remark) { 43 | this.remark = remark; 44 | } 45 | 46 | public Boolean getAgree() { 47 | return agree; 48 | } 49 | 50 | public void setAgree(Boolean agree) { 51 | this.agree = agree; 52 | } 53 | 54 | public Boolean getProcessed() { 55 | return processed; 56 | } 57 | 58 | public void setProcessed(Boolean processed) { 59 | this.processed = processed; 60 | } 61 | 62 | public Boolean getApplyUserRead() { 63 | return isApplyUserRead; 64 | } 65 | 66 | public void setApplyUserRead(Boolean applyUserRead) { 67 | isApplyUserRead = applyUserRead; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /node-http/routers/routerScanner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/12 4 | * @Description:基础路由扫描器 5 | */ 6 | const Router = require('koa-router'); 7 | const path = require('path'); 8 | const fs = require('fs'); 9 | 10 | // 自动扫描业务的路由定制 11 | const scanRouters = () => { 12 | 13 | let router = new Router(); 14 | let routersDirPath = __dirname; 15 | let routers = findFiles(routersDirPath); 16 | 17 | if (routers.length > 0) { 18 | console.log('=> koa: '.cyan + 'load routers:'.grey); 19 | routers.forEach((routerFilePath) => { 20 | console.log(' > '.black + path.relative(routersDirPath, routerFilePath).blue); 21 | router.use(require(routerFilePath)); 22 | }); 23 | } 24 | 25 | return router; 26 | }; 27 | 28 | const findFiles = (dirpath) => { 29 | let filesPath = []; 30 | 31 | // 提供了子目录扫描 32 | // function doFind(_path) { 33 | // let files = fs.readdirSync(_path); 34 | // files.forEach((val, index) => { 35 | // let fPath = path.join(_path, val); 36 | // let stats = fs.statSync(fPath); 37 | // if (stats.isDirectory()) doFind(fPath); 38 | // if (stats.isFile()) filesPath.push(fPath); 39 | // }); 40 | // 41 | // } 42 | 43 | function doFind(_path) { 44 | let files = fs.readdirSync(_path); 45 | files.forEach((val, index) => { 46 | let fPath = path.join(_path, val); 47 | let stats = fs.statSync(fPath); 48 | if (stats.isFile() && fPath != __filename) filesPath.push(fPath); 49 | }); 50 | } 51 | 52 | doFind(dirpath); 53 | return filesPath; 54 | }; 55 | 56 | module.exports = scanRouters(); -------------------------------------------------------------------------------- /node-http/models/images.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/19 4 | * @Description:图片模块数据库 5 | */ 6 | const baseModel = require('./basic.js'); 7 | 8 | class images extends baseModel { 9 | 10 | Schema() { 11 | return { 12 | userId: {type: String, required: true}, 13 | url: {type: String, required: true}, 14 | remark: {type: String, required: false}, 15 | }; 16 | }; 17 | 18 | SchemaName() { 19 | return 'images'; 20 | }; 21 | 22 | save(images) { 23 | return new this.model(images).save(); 24 | } 25 | 26 | saveMany(imagesArray) { 27 | return this.model.insertMany(imagesArray); 28 | } 29 | 30 | selectByPage(page) { 31 | return baseModel.selectByPage(page, this.model); 32 | } 33 | 34 | selectOwnByIds(ids, userId) { 35 | let condition = {userId: userId}; 36 | if (typeof ids === 'string') { 37 | condition._id = ids; 38 | } else if (Array.isArray(ids)) { 39 | condition._id = { 40 | '$in': ids 41 | }; 42 | } else { 43 | return false; 44 | } 45 | return this.model.find(condition).exec(); 46 | } 47 | 48 | removeOwnManyById(ids, userId) { 49 | let condition = {userId: userId}; 50 | if (typeof ids === 'string') { 51 | condition._id = ids; 52 | } else if (Array.isArray(ids)) { 53 | condition._id = { 54 | '$in': ids 55 | }; 56 | } else { 57 | return false; 58 | } 59 | return this.model.remove(condition).exec(); 60 | } 61 | 62 | } 63 | 64 | module.exports = new images(); -------------------------------------------------------------------------------- /node-http/middleware/auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/12 4 | * @Description:鉴权中间件 5 | */ 6 | 7 | const url = require('url'); 8 | 9 | const asyncRedisClient = require('../lib/asyncRedis').client; 10 | const redisKey = require('../const/redisKey'); 11 | const cookiesName = require('../const/cookiesName'); 12 | const whitePathname = require('../const/whitePathname'); 13 | 14 | module.exports = async (ctx, next) => { 15 | 16 | if (!auth(url.parse(ctx.url, true).pathname)) { 17 | // 是否需要鉴权 18 | let token = ctx.cookies.get(cookiesName.COOKIE_NAME_TOKEN); 19 | let uid = ctx.cookies.get(cookiesName.COOKIE_NAME_UID); 20 | if (token && uid) { 21 | 22 | let userInfo = await asyncRedisClient.getAsync(redisKey.AUTH_TOKEN(token)); 23 | 24 | if (!userInfo) { // 鉴权失败 25 | ctx.body = {message: 'unauthorized'}; 26 | ctx.status = 401; 27 | return; 28 | } 29 | 30 | userInfo = JSON.parse(userInfo); 31 | 32 | if (uid != userInfo.id) { 33 | ctx.body = {message: 'unauthorized'}; 34 | ctx.status = 401; 35 | return; 36 | } 37 | 38 | ctx.state.authInfo = userInfo; 39 | 40 | } else { 41 | // 鉴权参数不足 42 | ctx.body = {message: 'unauthorized'}; 43 | ctx.status = 401; 44 | return; 45 | } 46 | } 47 | 48 | // 鉴权完成的用户上下文中添加用户信息 49 | await next(); 50 | }; 51 | 52 | const auth = pathname => { 53 | for (let index in whitePathname) { 54 | if (pathname.startsWith(whitePathname[index])) { 55 | return true; 56 | } 57 | } 58 | return false; 59 | }; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/MsgPushContext.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean; 2 | 3 | import com.thankjava.toolkit3d.core.fastjson.FastJson; 4 | import com.thankjava.wchat.consts.EventType; 5 | 6 | /** 7 | * @Author: acexy@thankjava.com 8 | * 2018/8/10 9 | * @Description: 10 | **/ 11 | public class MsgPushContext { 12 | 13 | 14 | private String fromUserId; 15 | private String toUserId; 16 | private EventType eventType; 17 | private T data; 18 | private Long time = System.currentTimeMillis(); 19 | 20 | public MsgPushContext(EventType eventType, String fromUserId, String toUserId) { 21 | this.eventType = eventType; 22 | this.fromUserId = fromUserId; 23 | this.toUserId = toUserId; 24 | } 25 | 26 | /** 27 | * 28 | * @param eventType 29 | * @param fromUserId 消息来着谁 30 | * @param toUserId 推送目标 31 | * @param data 32 | */ 33 | public MsgPushContext(EventType eventType, String fromUserId, String toUserId, T data) { 34 | this.eventType = eventType; 35 | this.fromUserId = fromUserId; 36 | this.toUserId = toUserId; 37 | this.data = data; 38 | } 39 | 40 | public String getFromUserId() { 41 | return fromUserId; 42 | } 43 | 44 | public EventType getEventType() { 45 | return eventType; 46 | } 47 | 48 | public T getData() { 49 | return data; 50 | } 51 | 52 | public String getToUserId() { 53 | return toUserId; 54 | } 55 | 56 | public String toJsonString() { 57 | return FastJson.toJSONString(this); 58 | } 59 | 60 | public void setToUserId(String toUserId) { 61 | this.toUserId = toUserId; 62 | } 63 | 64 | public Long getTime() { 65 | return time; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/entity/ConVerifyResult.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket.entity; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | /** 6 | * @Author: acexy@thankjava.com 7 | * 2018/8/6 8 | * @Description:验证连接动作的返回处理结果 9 | **/ 10 | public class ConVerifyResult { 11 | 12 | public ConVerifyResult() { 13 | 14 | } 15 | 16 | public ConVerifyResult(String userId, String path, T bindData, Method process, Object controller) { 17 | this.allowConnect = true; 18 | this.userId = userId; 19 | this.path = path; 20 | this.bindData = bindData; 21 | this.sessionId = userId.concat(";").concat(path); 22 | this.process = process; 23 | this.controller = controller; 24 | } 25 | 26 | /** 27 | * ws对应controller处理方法 28 | */ 29 | public Method process; 30 | 31 | // 是否允许进行WebSocket连接 32 | private boolean allowConnect = false; 33 | 34 | private String userId; 35 | 36 | // 当前连接建立的请求地址 37 | private String path; 38 | 39 | // 全局唯一sessionId (每个ws连接唯一) 40 | private String sessionId; 41 | 42 | // 为认证成功的连接绑定数据 43 | private T bindData; 44 | 45 | private Object controller; 46 | 47 | 48 | public boolean isAllowConnect() { 49 | return allowConnect; 50 | } 51 | 52 | public T getBindData() { 53 | return bindData; 54 | } 55 | 56 | public String getPath() { 57 | return path; 58 | } 59 | 60 | public String getSessionId() { 61 | return sessionId; 62 | } 63 | 64 | public Method getProcess() { 65 | return process; 66 | } 67 | 68 | public Object getController() { 69 | return controller; 70 | } 71 | 72 | public String getUserId() { 73 | return userId; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/ws/core/OnMessageCallBack.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.ws.core; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.thankjava.toolkit3d.core.fastjson.FastJson; 5 | import com.thankjava.wchat.bean.ResponseContext; 6 | import com.thankjava.wchat.consts.ResponseCode; 7 | import com.thankjava.wchat.lib.websocket.callback.OnMessageListener; 8 | import com.thankjava.wchat.lib.websocket.entity.Message; 9 | import com.thankjava.wchat.bean.RequestContext; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * @Author: acexy@thankjava.com 15 | * 2018/8/9 16 | * @Description: 17 | **/ 18 | public class OnMessageCallBack implements OnMessageListener { 19 | 20 | static Logger logger = LoggerFactory.getLogger(OnMessageCallBack.class); 21 | 22 | @Override 23 | public void doProcess(Message message) { 24 | try { 25 | JSONObject data; 26 | try { 27 | data = FastJson.toJSONObject(message.getMessage()); 28 | } catch (Exception e) { 29 | logger.error("ws 请求参数异常 sessionId = " + message.getFromSessionId() + " | msg = " + message.getMessage(), e); 30 | message.getConn().send(new ResponseContext(ResponseCode.BAD_REQUEST_PARAMETER).toJsonString()); 31 | return; 32 | } 33 | logger.info("get message: " + message.getMessage()); 34 | message.doProcess(new RequestContext( 35 | message.getFromUserId(), 36 | message.getConn(), 37 | message.getBindData(), 38 | data 39 | ) 40 | ); 41 | } catch (Exception e) { 42 | logger.error("ws 执行异常 sessionId = " + message.getFromSessionId() + " | msg = " + message.getMessage(), e); 43 | message.getConn().send(new ResponseContext(ResponseCode.FAILED).toJsonString()); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /html-web/src/assets/scripts/ws/msgSender.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 18/8/16 4 | * @Description: 建立一个长期维持的用于发送消息的websocket 5 | */ 6 | let ws; 7 | 8 | let path; 9 | 10 | export function wsMsgSender(host) { 11 | path = host + '/ws/chat/send_msg'; 12 | ws = new WebSocket(path); 13 | } 14 | 15 | const send = (toUserId, toGroupId, msg) => { 16 | let data = { 17 | toUserId: toUserId, 18 | msg: msg 19 | }; 20 | if (toGroupId) { 21 | data.toGroupId = toGroupId; 22 | } 23 | return data; 24 | }; 25 | 26 | /** 27 | * 发送好友消息 28 | * @param toUserId 29 | * @param toGroupId 30 | * @param msg 31 | */ 32 | export function send2Friend(toUserId, msg, callback) { 33 | if (ws && ws.readyState == WebSocket.OPEN) { 34 | ws.send(JSON.stringify(send(toUserId, null, msg))); 35 | ws.onmessage = event => { 36 | // FIXME: 调试日志 37 | console.log(event.data); 38 | callback(JSON.parse(event.data)); 39 | } 40 | } else { 41 | // 断线重连 42 | ws = new WebSocket(path); 43 | ws.onopen = event => { 44 | ws.send(JSON.stringify(send(toUserId, null, msg))); 45 | ws.onmessage = event => { 46 | // FIXME: 调试日志 47 | console.log(event.data); 48 | callback(JSON.parse(event.data)); 49 | } 50 | }; 51 | } 52 | } 53 | 54 | /** 55 | * 发送群消息 56 | * @param toGroupId 57 | * @param msg 58 | * @param callback 59 | */ 60 | export function send2Group(toGroupId, msg, callback) { 61 | if (ws && ws.readyState == WebSocket.OPEN) { 62 | ws.send(JSON.stringify(send(null, toGroupId, msg))); 63 | ws.onmessage = event => { 64 | callback(JSON.parse(event.data)); 65 | } 66 | } else { 67 | ws = new WebSocket(path); 68 | ws.onopen = event => { 69 | ws.send(JSON.stringify(send(null, toGroupId, msg))); 70 | ws.onmessage = event => { 71 | // FIXME: 调试日志 72 | console.log(event.data); 73 | callback(JSON.parse(event.data)); 74 | } 75 | }; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /node-http/models/groupInfo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 18/8/20 4 | * @Description: 5 | */ 6 | const baseModel = require('./basic.js'); 7 | 8 | class groupInfo extends baseModel { 9 | 10 | Schema() { 11 | return { 12 | groupNickname: {type: String, required: true}, 13 | groupName: {type: String, required: true, unique: true}, 14 | ownerUserId: {type: String, required: true}, 15 | introduction: {type: String, required: true}, 16 | headImg: {type: String, required: false}, 17 | }; 18 | }; 19 | 20 | SchemaName() { 21 | return 'group_info'; 22 | }; 23 | 24 | save(groupInfo) { 25 | return new this.model(groupInfo).save(); 26 | } 27 | 28 | selectByGroupName(groupName) { 29 | return this.model.findOne({groupName: groupName}).exec(); 30 | } 31 | 32 | selectByConditionOnlyOne(friendRelation) { 33 | return this.model.findOne(friendRelation).exec(); 34 | } 35 | 36 | updateById(id, friendRelation) { 37 | return this.model.updateOne({_id: baseModel.typeObject(id)}, friendRelation).exec(); 38 | } 39 | 40 | selectByIds(ids) { 41 | let queryIds = []; 42 | for (let index in ids) { 43 | queryIds.push(baseModel.typeObject(ids[index])); 44 | } 45 | return this.model.find({_id: {"$in": queryIds}}).exec(); 46 | } 47 | 48 | selectByOwnerUserId(ownerUserId) { 49 | return this.model.find({ownerUserId: ownerUserId}).exec() 50 | } 51 | 52 | selectByGroupNicknameOrGroupName(groupName, groupNickname) { 53 | let query = {}; 54 | if (groupName && groupNickname) { 55 | query = {"$or": [{groupName: groupName}, {groupNickname: {"$regex": groupNickname, "$options": 'i'}}]}; 56 | } else { 57 | if (groupName) query.groupName = groupName; 58 | if (groupNickname) query.groupNickname = {"$regex": groupNickname, "$options": 'i'}; 59 | } 60 | return this.model.find(query).exec(); 61 | } 62 | } 63 | 64 | module.exports = new groupInfo(); -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/group/GroupAddReplyPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.group; 2 | 3 | import java.util.List; 4 | 5 | public class GroupAddReplyPush { 6 | 7 | private boolean agree; 8 | private String groupId; 9 | private String groupNickname; 10 | private String groupName; 11 | private String groupHeadImg; 12 | private String ownerUserId; 13 | private String introduction; 14 | private List groupUsers; 15 | 16 | public boolean isAgree() { 17 | return agree; 18 | } 19 | 20 | public void setAgree(boolean agree) { 21 | this.agree = agree; 22 | } 23 | 24 | public String getGroupId() { 25 | return groupId; 26 | } 27 | 28 | public void setGroupId(String groupId) { 29 | this.groupId = groupId; 30 | } 31 | 32 | public String getGroupNickname() { 33 | return groupNickname; 34 | } 35 | 36 | public void setGroupNickname(String groupNickname) { 37 | this.groupNickname = groupNickname; 38 | } 39 | 40 | public String getGroupName() { 41 | return groupName; 42 | } 43 | 44 | public void setGroupName(String groupName) { 45 | this.groupName = groupName; 46 | } 47 | 48 | public String getGroupHeadImg() { 49 | return groupHeadImg; 50 | } 51 | 52 | public void setGroupHeadImg(String groupHeadImg) { 53 | this.groupHeadImg = groupHeadImg; 54 | } 55 | 56 | public String getIntroduction() { 57 | return introduction; 58 | } 59 | 60 | public void setIntroduction(String introduction) { 61 | this.introduction = introduction; 62 | } 63 | 64 | public String getOwnerUserId() { 65 | return ownerUserId; 66 | } 67 | 68 | public void setOwnerUserId(String ownerUserId) { 69 | this.ownerUserId = ownerUserId; 70 | } 71 | 72 | public List getGroupUsers() { 73 | return groupUsers; 74 | } 75 | 76 | public void setGroupUsers(List groupUsers) { 77 | this.groupUsers = groupUsers; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/Session.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket; 2 | 3 | import com.thankjava.toolkit.bean.thread.TaskEntity; 4 | import com.thankjava.toolkit.core.thread.ThreadTask; 5 | import org.java_websocket.WebSocket; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import org.java_websocket.WebSocketImpl; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * @Author: acexy@thankjava.com 16 | * 2018/8/3 17 | * @Description: 所有已建立的有效连接汇总 18 | **/ 19 | public class Session { 20 | 21 | static Logger logger = LoggerFactory.getLogger((Session.class)); 22 | 23 | private static final Map aliveConnection = new HashMap<>(); 24 | 25 | static { 26 | 27 | if (WebSocketImpl.DEBUG == true) { 28 | 29 | ThreadTask threadTask = new ThreadTask(1); 30 | TaskEntity taskEntity = new TaskEntity(5, 15, () -> { 31 | logger.debug("all aliveConnection size = " + aliveConnection.size()); 32 | for (String sessionId : aliveConnection.keySet()) { 33 | logger.debug(" --> sessionId = " + sessionId); 34 | } 35 | }); 36 | 37 | threadTask.addTaskAtFixedRate(taskEntity); 38 | } 39 | 40 | } 41 | 42 | // 新增连接 43 | protected static void putConn(String sessionId, WebSocket conn) { 44 | WebSocket currConn = aliveConnection.get(sessionId); 45 | if (currConn != null) { 46 | currConn.close(); 47 | currConn = null; 48 | } 49 | aliveConnection.put(sessionId, conn); 50 | } 51 | 52 | // 获取连接 53 | protected static WebSocket getConn(String sessionId) { 54 | return aliveConnection.get(sessionId); 55 | } 56 | 57 | // 删除连接 58 | protected static void delConn(String sessionId) { 59 | WebSocket currConn = aliveConnection.get(sessionId); 60 | if (currConn != null) { 61 | currConn.close(); 62 | currConn = null; 63 | } 64 | aliveConnection.remove(sessionId); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # vuepress build output 72 | .vuepress/dist 73 | 74 | # Serverless directories 75 | .serverless 76 | ### Java template 77 | # Compiled class file 78 | *.class 79 | 80 | # Log file 81 | # BlueJ files 82 | *.ctxt 83 | 84 | # Mobile Tools for Java (J2ME) 85 | .mtj.tmp/ 86 | 87 | # Package Files # 88 | *.jar 89 | *.war 90 | *.nar 91 | *.ear 92 | *.zip 93 | *.tar.gz 94 | *.rar 95 | 96 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 97 | hs_err_pid* 98 | 99 | 100 | # Editor directories and files 101 | .vscode 102 | *.suo 103 | *.ntvs* 104 | *.njsproj 105 | *.sln 106 | 107 | #自定义 108 | .idea 109 | target 110 | .DS_Store 111 | .project 112 | *.iml -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/notice/StatusChangeEventPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.notice; 2 | 3 | import com.thankjava.wchat.bean.MsgPushContext; 4 | import com.thankjava.wchat.bean.notice.notice.OfflinePush; 5 | import com.thankjava.wchat.bean.notice.notice.OnlinePush; 6 | import com.thankjava.wchat.db.entity.FriendRelation; 7 | import com.thankjava.wchat.db.mongo.FriendRelationMapper; 8 | import com.thankjava.wchat.db.mongo.impl.FriendRelationMapperImpl; 9 | import com.thankjava.wchat.util.WSUtil; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import java.util.List; 14 | 15 | public class StatusChangeEventPush { 16 | 17 | private FriendRelationMapper friendRelationMapper = new FriendRelationMapperImpl(); 18 | static Logger logger = LoggerFactory.getLogger(StatusChangeEventPush.class); 19 | 20 | public void pushOnline(MsgPushContext msgPushContext) { 21 | doPush(msgPushContext); 22 | } 23 | 24 | public void pushOffline(MsgPushContext msgPushContext) { 25 | doPush(msgPushContext); 26 | } 27 | 28 | public void pushForcedLogout(MsgPushContext msgPushContext) { 29 | try { 30 | WSUtil.sendMsg(msgPushContext); 31 | WSUtil.closeConn(msgPushContext.getToUserId().concat(";/notice/event")); 32 | } catch (Exception e) { 33 | 34 | } 35 | 36 | } 37 | 38 | private void doPush(MsgPushContext msgPushContext) { 39 | 40 | FriendRelation query = new FriendRelation(); 41 | 42 | query.setUserId(msgPushContext.getFromUserId()); 43 | 44 | List friendRelations = friendRelationMapper.selectByCondition(query); 45 | 46 | if (friendRelations == null || friendRelations.isEmpty()) return; 47 | 48 | for (FriendRelation friendRelation : friendRelations) { 49 | 50 | WSUtil.sendMsgAsync(new MsgPushContext<>( 51 | msgPushContext.getEventType(), 52 | msgPushContext.getFromUserId(), 53 | friendRelation.getFriendUserId(), 54 | msgPushContext.getData() 55 | )); 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /html-web/src/store/getters.js: -------------------------------------------------------------------------------- 1 | export const user = state => state.user; 2 | 3 | // 当前会话好友 4 | export const currentFriend = state => state.currentFriend; 5 | 6 | // 好友会话列表 7 | export const sessions = state=> state.sessions; 8 | 9 | // 当前好友会话列表 10 | export const currentSession = (state) => { 11 | return state.sessions[state.currentFriend.friendId]; 12 | } 13 | 14 | // 好友信息 15 | export const friendInfo = (state) => (id) => { 16 | let friendList = state.friendList; 17 | for(let item in friendList) { 18 | if (friendList[item].userId == id) { 19 | return friendList[item]; 20 | } 21 | } 22 | 23 | return state.user; 24 | } 25 | 26 | // 群组信息 27 | export const GroupInfo = (state) => (id) => { 28 | let groupList = state.groupList; 29 | for(let item in groupList) { 30 | if (groupList[item].groupId == id) { 31 | return groupList[item]; 32 | } 33 | } 34 | } 35 | 36 | // 好友列表 37 | export const friendList = (state) => { 38 | return state.friendList; 39 | } 40 | 41 | // 群组列表 42 | export const groupList = (state) => { 43 | return state.groupList; 44 | } 45 | 46 | // 当前会话群组聊天信息 47 | export const currentGroupSession = (state) => { 48 | return state.groupSessions[state.currentGroup.groupId]; 49 | } 50 | 51 | // 当前会话群组id和name 52 | export const currentGroup = state => state.currentGroup; 53 | 54 | // 当前会话全组的好友列表 55 | export const currentFriendList = (state) => (id) => { 56 | let friendList = state.groupFriendList; 57 | for(let item in friendList) { 58 | if (item == id) { 59 | return friendList[item]; 60 | } 61 | } 62 | } 63 | 64 | // 某群组好友列表 65 | export const hasGroupFriendList = (state) => (groupId) => { 66 | let groupFriend = state.groupFriendList[groupId] || {}; 67 | if(JSON.stringify(groupFriend) == '{}') { 68 | return false; 69 | } else { 70 | return true; 71 | } 72 | } 73 | 74 | export const filterKey = state => state.filterKey; 75 | 76 | // tab类型获取 77 | export const tabType = state => state.tabType; 78 | 79 | 80 | // 好友申请列表 81 | export const friendApplyList = state => state.friendApplyList; 82 | 83 | // 申请加群列表 84 | export const groupApplyList = state => state.groupApplyList; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/notice/GroupEventPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.notice; 2 | 3 | import com.thankjava.wchat.bean.MsgPushContext; 4 | import com.thankjava.wchat.bean.ResponseContext; 5 | import com.thankjava.wchat.bean.notice.group.GroupAddPush; 6 | import com.thankjava.wchat.bean.notice.group.GroupAddReplyPush; 7 | import com.thankjava.wchat.bean.notice.group.GroupJoinPush; 8 | import com.thankjava.wchat.consts.ResponseCode; 9 | import com.thankjava.wchat.db.entity.GroupRelation; 10 | import com.thankjava.wchat.db.mongo.GroupRelationMapper; 11 | import com.thankjava.wchat.db.mongo.impl.GroupRelationMapperImpl; 12 | import com.thankjava.wchat.util.WSUtil; 13 | 14 | import java.util.List; 15 | 16 | public class GroupEventPush { 17 | 18 | private GroupRelationMapper groupRelationMapper = new GroupRelationMapperImpl(); 19 | 20 | public ResponseContext pushGroupAdd(MsgPushContext msgPushContext) { 21 | ResponseCode responseCode = WSUtil.sendMsg(msgPushContext); 22 | if (responseCode == ResponseCode.TARGET_OFFLINE) { 23 | return new ResponseContext(ResponseCode.SUCCESS); 24 | } 25 | return new ResponseContext(responseCode); 26 | } 27 | 28 | public void pushGroupAddReply(MsgPushContext msgPushContext) { 29 | WSUtil.sendMsgAsync(msgPushContext); 30 | } 31 | 32 | public void pushGroupUserJoin(MsgPushContext msgPushContext) { 33 | GroupRelation groupRelation = new GroupRelation(); 34 | groupRelation.setGroupId(msgPushContext.getData().getGroupId()); 35 | List groupRelations = groupRelationMapper.selectByCondition(groupRelation); 36 | if (groupRelations != null & !groupRelations.isEmpty()) { 37 | for (GroupRelation relation : groupRelations) { 38 | if (relation.getUserId().equals(msgPushContext.getData().getUserId())) continue; 39 | WSUtil.sendMsgAsync(new MsgPushContext<>( 40 | msgPushContext.getEventType(), 41 | msgPushContext.getFromUserId(), 42 | relation.getUserId(), 43 | msgPushContext.getData() 44 | )); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/notice/ChatEventPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.notice; 2 | 3 | import com.thankjava.wchat.consts.EventType; 4 | import com.thankjava.wchat.bean.MsgPushContext; 5 | import com.thankjava.wchat.bean.ResponseContext; 6 | import com.thankjava.wchat.bean.notice.chat.ChatMsgPush; 7 | import com.thankjava.wchat.consts.ResponseCode; 8 | import com.thankjava.wchat.db.entity.GroupRelation; 9 | import com.thankjava.wchat.db.mongo.GroupRelationMapper; 10 | import com.thankjava.wchat.db.mongo.impl.GroupRelationMapperImpl; 11 | import com.thankjava.wchat.util.WSUtil; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * @Author: acexy@thankjava.com 17 | * 2018/8/10 18 | * @Description: 消息推送 19 | **/ 20 | public class ChatEventPush { 21 | 22 | private GroupRelationMapper groupRelationMapper = new GroupRelationMapperImpl(); 23 | 24 | /** 25 | * 推送聊天消息 26 | * 27 | * @param msgPushContext 28 | * @return 29 | */ 30 | public ResponseContext pushChatMsg(MsgPushContext msgPushContext) { 31 | 32 | EventType eventType = msgPushContext.getEventType(); 33 | ResponseCode responseCode = null; 34 | 35 | if (eventType == EventType.chat_group) { 36 | 37 | ChatMsgPush chatMsgPush = msgPushContext.getData(); 38 | 39 | GroupRelation query = new GroupRelation(); 40 | query.setGroupId(chatMsgPush.getToGroupId()); 41 | 42 | List groupRelations = groupRelationMapper.selectByCondition(query); 43 | if (groupRelations == null || groupRelations.isEmpty()) { 44 | return new ResponseContext(ResponseCode.SUCCESS); 45 | } 46 | 47 | // 推送群组内所有成员消息 48 | for (GroupRelation groupRelation : groupRelations) { 49 | if (!groupRelation.getUserId().equals(msgPushContext.getFromUserId())) { 50 | WSUtil.sendMsgAsync(new MsgPushContext<>( 51 | msgPushContext.getEventType(), 52 | msgPushContext.getFromUserId(), 53 | groupRelation.getUserId(), 54 | msgPushContext.getData() 55 | )); 56 | } 57 | } 58 | 59 | responseCode = ResponseCode.SUCCESS; 60 | } else { 61 | responseCode = WSUtil.sendMsg(msgPushContext); 62 | } 63 | return new ResponseContext(responseCode); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/ws/core/OnConnCloseCallBack.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.ws.core; 2 | 3 | import com.thankjava.toolkit.core.reflect.BeanCopierUtil; 4 | import com.thankjava.toolkit.core.thread.ThreadTask; 5 | import com.thankjava.wchat.bean.MsgPushContext; 6 | import com.thankjava.wchat.bean.notice.notice.OfflinePush; 7 | import com.thankjava.wchat.consts.EventType; 8 | import com.thankjava.wchat.consts.RedisKeyManager; 9 | import com.thankjava.wchat.lib.websocket.callback.OnConnCloseListener; 10 | import com.thankjava.wchat.lib.websocket.entity.ConVerifyResult; 11 | import com.thankjava.wchat.notice.StatusChangeEventPush; 12 | import com.thankjava.wchat.util.RedisUtil; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | public class OnConnCloseCallBack implements OnConnCloseListener { 17 | 18 | private StatusChangeEventPush statusChangeEventPush = new StatusChangeEventPush(); 19 | 20 | private static Logger logger = LoggerFactory.getLogger(OnConnCloseCallBack.class); 21 | 22 | // 离线推送做delay时间控制 23 | private static ThreadTask threadTask = new ThreadTask(50); 24 | 25 | // 延时时间 26 | final int delayTime = 3; 27 | 28 | @Override 29 | public void doProcess(ConVerifyResult conVerifyResult) { 30 | if ("/notice/event".equals(conVerifyResult.getPath())) { 31 | 32 | // 如果当前在线则执行下线 并准备推送离线信息 33 | if (RedisUtil.getRedisManager().sismember(RedisKeyManager.ONLINE_SET_KEY(), conVerifyResult.getUserId())) { 34 | 35 | RedisUtil.getRedisManager().srem(RedisKeyManager.ONLINE_SET_KEY(), conVerifyResult.getUserId()); 36 | 37 | threadTask.addTaskRunOnce(delayTime, () -> { 38 | 39 | // 延迟指定时间后 如果确实离线才推送 40 | if (!RedisUtil.getRedisManager().sismember(RedisKeyManager.ONLINE_SET_KEY(), conVerifyResult.getUserId())) { 41 | 42 | logger.info("push offline userId = " + conVerifyResult.getUserId()); 43 | 44 | MsgPushContext msgPushContext = new MsgPushContext<>( 45 | EventType.friend_status_change, 46 | conVerifyResult.getUserId(), 47 | null, 48 | BeanCopierUtil.copy(conVerifyResult.getBindData(), OfflinePush.class) 49 | ); 50 | 51 | statusChangeEventPush.pushOffline(msgPushContext); 52 | } 53 | 54 | }); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /html-web/src/components/common/FriendList.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | {{item.nickname || item.username}} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 80 | -------------------------------------------------------------------------------- /html-web/src/assets/scripts/ws/noticeEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 与服务端建立获取事件通知的websocket连接 3 | * @author acexy@thankjava.com 4 | * @param url websocket 地址 ws://xxx.com/notice/event 5 | * @param onOpen 当与服务端建立好连接后回调该函数 6 | * @param onError 当与服务端发生通讯异常时回调该函数 7 | * @param onClose 当连接被关闭回调该函数 8 | * @param eventCallbackFn 注册通知事件的业务逻辑函数 例如: 9 | * { 10 | * chat_f2f: function(data) { 11 | * 12 | * }, 13 | * } 14 | * 该模块将在服务端通知过来后根据通知类型调用对应的业务函数 15 | * @param offlineCallback 掉线回调 16 | */ 17 | 18 | let logout = false; 19 | let reTryCon; 20 | let ws; 21 | 22 | export function wsNoticeEvent(host, onOpen, onError, onClose, eventCallbackFn, offlineCallback) { 23 | ws = new WebSocket(host + '/ws/notice/event'); 24 | ws.onopen = event => { 25 | reTryCon = 0; 26 | if (onOpen) onOpen(event); 27 | }; 28 | 29 | ws.onclose = event => { 30 | if (onClose) onClose(event); 31 | console.log("与服务器断开连接..."); 32 | if (event.code == 1008) { 33 | console.log('鉴权失败,重新登录'); 34 | logout = true; 35 | offlineCallback(); 36 | return; 37 | } 38 | if (!logout) { 39 | console.log("重连服务器..."); 40 | // 断线重连 41 | wsNoticeEvent(host, onOpen, onError, onClose, eventCallbackFn, offlineCallback); 42 | reTryCon++; 43 | if (reTryCon > 5) { 44 | logout = true; 45 | reTryCon = 0; 46 | console.log("重连失败!"); 47 | offlineCallback(); 48 | } 49 | } 50 | }; 51 | 52 | ws.onerror = event => { 53 | console.log("与服务端连接异常: " + ws.readyState); 54 | if (ws.readyState == WebSocket.CLOSED || ws.readyState == WebSocket.CLOSING) { 55 | if (!logout) { 56 | console.log("重连服务器"); 57 | // 断线重连 58 | wsNoticeEvent(host, onOpen, onError, onClose, eventCallbackFn, offlineCallback); 59 | reTryCon++; 60 | if (reTryCon > 5) { 61 | logout = true; 62 | reTryCon = 0; 63 | console.log("重连失败"); 64 | offlineCallback(); 65 | } 66 | } 67 | } 68 | if (onError) onError(event); 69 | }; 70 | 71 | ws.onmessage = event => { 72 | let data = JSON.parse(event.data); 73 | //TODO: 调试输出 74 | console.log(event.data); 75 | let eventType = data.eventType; 76 | if (eventType == 'forced_logout') logout = true; 77 | let fn = eventCallbackFn[eventType]; 78 | if (fn) { 79 | fn(data); 80 | } 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /node-http/models/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/15 4 | * @Description:用户模块数据库 5 | */ 6 | 7 | const baseModel = require('./basic.js'); 8 | 9 | class user extends baseModel { 10 | 11 | Schema() { 12 | return { 13 | username: {type: String, required: true, unique: true}, 14 | password: {type: String, required: true}, 15 | nickname: {type: String, required: true}, 16 | headImg: {type: String, required: false}, 17 | sign: {type: String, required: false}, 18 | sex: {type: String, required: false, default: "-1"} 19 | }; 20 | }; 21 | 22 | SchemaName() { 23 | return 'user'; 24 | }; 25 | 26 | save(user) { 27 | return new this.model(user).save(); 28 | } 29 | 30 | /** 31 | * 通过帐号查询用户 32 | * @param username 33 | * @returns {Promise} 34 | */ 35 | selectByUsername(username) { 36 | return this.model.findOne({username: username}).exec(); 37 | } 38 | 39 | /** 40 | * 条件查询用户信息 41 | * @param condition 42 | * @returns {Promise} 43 | */ 44 | selectByConditionOnlyOne(condition) { 45 | return this.model.findOne(condition).exec(); 46 | } 47 | 48 | /** 49 | * 用于搜索用户 50 | * @param username 51 | * @param nickname 52 | * @returns {Promise} 53 | */ 54 | selectByNicknameOrUsername(username, nickname) { 55 | let query = {}; 56 | if (username && nickname) { 57 | query = {"$or": [{username: username}, {nickname: {"$regex": nickname, "$options": 'i'}}]}; 58 | } else { 59 | if (username) query.username = username; 60 | if (nickname) query.nickname = {"$regex": nickname, "$options": 'i'}; 61 | } 62 | return this.model.find(query).exec(); 63 | } 64 | 65 | /** 66 | * 通过帐号更新密码 67 | * @param username 68 | * @param password 69 | * @returns {Promise} 70 | */ 71 | updatePasswordByUsername(username, password) { 72 | return this.model.updateOne({username: username}, {password: password}).exec(); 73 | } 74 | 75 | selectById(id) { 76 | return this.model.findOne({_id: baseModel.typeObject(id)}).exec(); 77 | } 78 | 79 | selectByIds(ids) { 80 | let queryIds = []; 81 | for (let index in ids) { 82 | queryIds.push(baseModel.typeObject(ids[index])); 83 | } 84 | return this.model.find({_id: {"$in": queryIds}}).exec(); 85 | 86 | } 87 | 88 | updateByUsername(condition, username) { 89 | return this.model.updateOne({username: username}, condition).exec(); 90 | } 91 | } 92 | 93 | module.exports = new user(); -------------------------------------------------------------------------------- /node-http/controller/baseController.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/5/28 4 | * @Description: 5 | */ 6 | 7 | const code = require('../const/code'); 8 | const reg = require('../const/reg'); 9 | 10 | /** 11 | * 移除cookie 12 | * @param ctx 13 | * @param cookieNames 14 | */ 15 | module.exports.removeCookie = (ctx, cookieNames) => { 16 | if (Array.isArray(cookieNames)) { 17 | cookieNames.every((element) => { 18 | ctx.cookies.set(element, null); 19 | return true; 20 | }) 21 | } 22 | ctx.cookies.set(cookieNames, null); 23 | }; 24 | 25 | /** 26 | * 获取cookie 27 | * @param ctx 28 | * @param cookieName 29 | * @returns {*} 30 | */ 31 | module.exports.getCookie = (ctx, cookieName) => ctx.cookies.get(cookieName); 32 | 33 | // 设置cookie 34 | module.exports.setCookie = (ctx, cookieName, value, option) => { 35 | ctx.cookies.set(cookieName, value, option); 36 | }; 37 | 38 | /** 39 | * 响应请求参数错误 40 | * @param ctx 41 | * @param message 42 | */ 43 | module.exports.response400 = (ctx, message) => { 44 | ctx.response.status = 400; 45 | ctx.body = responseBuilder(undefined, message ? message : '请求参数错误'); 46 | }; 47 | 48 | /** 49 | * 响应处理失败的请求 50 | * @param ctx 51 | * @param message 52 | */ 53 | module.exports.response500 = (ctx, message) => { 54 | ctx.response.status = 500; 55 | ctx.body = responseBuilder(undefined, message ? message : '系统异常'); 56 | }; 57 | 58 | /** 59 | * 响应正常请求 60 | * @param ctx 61 | * @param message 62 | * @param data 63 | */ 64 | module.exports.response = (ctx, message, data) => { 65 | ctx.body = responseBuilder(undefined, message, data); 66 | }; 67 | 68 | /** 69 | * 响应含有code的正常请求 70 | * @param ctx 71 | * @param code 72 | * @param message 73 | * @param data 74 | */ 75 | module.exports.responseWithCode = (ctx, code, message, data) => { 76 | ctx.body = responseBuilder(code, message, data); 77 | }; 78 | 79 | // 创建返回对象数据 80 | const responseBuilder = (code, message, data) => { 81 | 82 | if (!data && typeof message !== 'string') { 83 | data = message; 84 | message = undefined; 85 | } 86 | 87 | return { 88 | code: code, 89 | message: message || '请求完成', 90 | data: data 91 | }; 92 | }; 93 | 94 | module.exports.CODE = code; 95 | module.exports.REG = reg; 96 | 97 | module.exports.CONSTS = { 98 | AUTH_COOKIE_EXPIRES_DAY: 1, // 认证cookie的过期时间 (天) 99 | REG_MAIL_MINUTE: 15, // 注册邮件验证码邮箱时间 100 | FORGET_MAIL_MINUTE: 15, // 忘记密码邮件验证码邮箱时间 101 | MAX_LIVING_TOKEN_NUMBER: 5, // 最大长期登录的token数 102 | UPDATE_MAIL_MINUTE: 15, // 修改邮箱验证时间 103 | }; -------------------------------------------------------------------------------- /html-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wchat-web", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "qazyuan <1508326059@qq.com>", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "vue": "^2.5.2", 16 | "vue-router": "^3.0.1", 17 | "vuex": "^3.0.1" 18 | }, 19 | "devDependencies": { 20 | "autoprefixer": "^7.1.2", 21 | "babel-core": "^6.22.1", 22 | "babel-eslint": "^8.2.1", 23 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 24 | "babel-loader": "^7.1.1", 25 | "babel-plugin-syntax-jsx": "^6.18.0", 26 | "babel-plugin-transform-runtime": "^6.22.0", 27 | "babel-plugin-transform-vue-jsx": "^3.5.0", 28 | "babel-preset-env": "^1.3.2", 29 | "babel-preset-stage-2": "^6.22.0", 30 | "chalk": "^2.0.1", 31 | "copy-webpack-plugin": "^4.0.1", 32 | "css-loader": "^0.28.0", 33 | "element-ui": "^2.4.6", 34 | "eslint": "^4.15.0", 35 | "eslint-config-standard": "^10.2.1", 36 | "eslint-friendly-formatter": "^3.0.0", 37 | "eslint-loader": "^1.7.1", 38 | "eslint-plugin-import": "^2.7.0", 39 | "eslint-plugin-node": "^5.2.0", 40 | "eslint-plugin-promise": "^3.4.0", 41 | "eslint-plugin-standard": "^3.0.1", 42 | "eslint-plugin-vue": "^4.0.0", 43 | "extract-text-webpack-plugin": "^3.0.0", 44 | "file-loader": "^1.1.4", 45 | "friendly-errors-webpack-plugin": "^1.6.1", 46 | "html-webpack-plugin": "^2.30.1", 47 | "jquery": "^3.3.1", 48 | "js-cookie": "^2.2.0", 49 | "node-notifier": "^5.1.2", 50 | "node-sass": "^4.9.2", 51 | "optimize-css-assets-webpack-plugin": "^3.2.0", 52 | "ora": "^1.2.0", 53 | "portfinder": "^1.0.13", 54 | "postcss-import": "^11.0.0", 55 | "postcss-loader": "^2.0.8", 56 | "postcss-url": "^7.2.1", 57 | "rimraf": "^2.6.0", 58 | "sass-loader": "^7.1.0", 59 | "semver": "^5.3.0", 60 | "shelljs": "^0.7.6", 61 | "uglifyjs-webpack-plugin": "^1.1.1", 62 | "url-loader": "^1.1.1", 63 | "vue-loader": "^13.3.0", 64 | "vue-style-loader": "^3.0.1", 65 | "vue-template-compiler": "^2.5.2", 66 | "webpack": "^3.6.0", 67 | "webpack-bundle-analyzer": "^2.9.0", 68 | "webpack-dev-server": "^2.9.1", 69 | "webpack-merge": "^4.1.0" 70 | }, 71 | "engines": { 72 | "node": ">= 6.0.0", 73 | "npm": ">= 3.0.0" 74 | }, 75 | "browserslist": [ 76 | "> 1%", 77 | "last 2 versions", 78 | "not ie <= 8" 79 | ] 80 | } 81 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/group/GroupAddPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.group; 2 | 3 | public class GroupAddPush { 4 | 5 | // 好友申请记录id 6 | private String groupApplyId; 7 | 8 | private String groupId; 9 | private String groupNickname; 10 | private String groupName; 11 | private String groupHeadImg; 12 | 13 | private String userId; 14 | private String nickname; 15 | private String headImg; 16 | private String username; 17 | private String remark; 18 | 19 | private String introduction; 20 | 21 | public String getGroupApplyId() { 22 | return groupApplyId; 23 | } 24 | 25 | public void setGroupApplyId(String groupApplyId) { 26 | this.groupApplyId = groupApplyId; 27 | } 28 | 29 | public String getGroupNickname() { 30 | return groupNickname; 31 | } 32 | 33 | public void setGroupNickname(String groupNickname) { 34 | this.groupNickname = groupNickname; 35 | } 36 | 37 | public String getGroupName() { 38 | return groupName; 39 | } 40 | 41 | public void setGroupName(String groupName) { 42 | this.groupName = groupName; 43 | } 44 | 45 | public String getGroupHeadImg() { 46 | return groupHeadImg; 47 | } 48 | 49 | public void setGroupHeadImg(String groupHeadImg) { 50 | this.groupHeadImg = groupHeadImg; 51 | } 52 | 53 | public String getNickname() { 54 | return nickname; 55 | } 56 | 57 | public void setNickname(String nickname) { 58 | this.nickname = nickname; 59 | } 60 | 61 | public String getHeadImg() { 62 | return headImg; 63 | } 64 | 65 | public void setHeadImg(String headImg) { 66 | this.headImg = headImg; 67 | } 68 | 69 | public String getUsername() { 70 | return username; 71 | } 72 | 73 | public void setUsername(String username) { 74 | this.username = username; 75 | } 76 | 77 | public String getRemark() { 78 | return remark; 79 | } 80 | 81 | public void setRemark(String remark) { 82 | this.remark = remark; 83 | } 84 | 85 | public String getGroupId() { 86 | return groupId; 87 | } 88 | 89 | public void setGroupId(String groupId) { 90 | this.groupId = groupId; 91 | } 92 | 93 | public String getUserId() { 94 | return userId; 95 | } 96 | 97 | public void setUserId(String userId) { 98 | this.userId = userId; 99 | } 100 | 101 | public String getIntroduction() { 102 | return introduction; 103 | } 104 | 105 | public void setIntroduction(String introduction) { 106 | this.introduction = introduction; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /node-http/models/basic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/6/15 4 | * @Description: 5 | */ 6 | const mongoose = require('mongoose'); 7 | const schema = mongoose.Schema; 8 | const mongodb = require('../lib/mongodb'); 9 | 10 | class baseModel { 11 | 12 | constructor() { 13 | 14 | this.schema = new schema(addDefaultField(this.Schema()), {versionKey: false}); 15 | this.name = this.SchemaName(); 16 | this.model = mongodb.loadModel(this.name, this.schema); 17 | 18 | // 添加默认字段 19 | function addDefaultField(customizeSchema) { 20 | 21 | customizeSchema.createTime = {type: Number, default: Date.now}; 22 | 23 | return customizeSchema; 24 | } 25 | } 26 | 27 | 28 | Schema() { 29 | }; 30 | 31 | SchemaName() { 32 | }; 33 | } 34 | 35 | module.exports = baseModel; 36 | 37 | /** 38 | * 提供统一分页查询 39 | * @param page {pageSize:,pageNumber,query:{},sort:{}} query: 查询条件 sort: 排序方式 {field:-1/1} 同mongo标准 40 | * @param model 41 | * @returns {Promise} 42 | */ 43 | module.exports.selectByPage = (page, model) => new Promise(resolve => { 44 | 45 | let query = {}; 46 | 47 | let sort = {createTime: -1}; // 默认排序规则 按时间降序 48 | if (page.query) { 49 | query = page.query; 50 | } 51 | 52 | if (page.sort) { 53 | sort = page.sort; 54 | } 55 | 56 | let fields = null; 57 | if (page.fields) { 58 | fields = page.fields; 59 | } 60 | 61 | model.count(query, (err, count) => { 62 | if (err) { 63 | console.error(err); 64 | resolve(null); 65 | } else { 66 | page.pageNumber = page.pageNumber || 1; 67 | page.pageSize = page.pageSize || 10; 68 | let response = { 69 | pageSize: page.pageSize, 70 | pageNumber: page.pageNumber, 71 | pageCount: Math.ceil(count / page.pageSize), 72 | totalCount: count, 73 | hasNext: false 74 | }; 75 | if (count == 0) { 76 | resolve(response); 77 | } else { 78 | model.find(query, fields, {sort: sort}).skip(page.pageSize * (page.pageNumber - 1)).limit(page.pageSize).exec((err, docs) => { 79 | if (err) { 80 | console.error(err); 81 | resolve(null); 82 | } else { 83 | response.list = docs; 84 | response.hasNext = count > page.pageSize * page.pageNumber; 85 | resolve(response); 86 | } 87 | }); 88 | } 89 | } 90 | }); 91 | }); 92 | // 93 | module.exports.typeMixed = schema.Types.Mixed; 94 | module.exports.typeObject = mongoose.Types.ObjectId; 95 | module.exports.typeDecimal128 = schema.Types.Decimal128; 96 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/bean/notice/group/GroupJoinPush.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.bean.notice.group; 2 | 3 | public class GroupJoinPush { 4 | 5 | private String groupId; 6 | private String groupNickname; 7 | private String groupName; 8 | private String groupHeadImg; 9 | private String introduction; 10 | private String ownerUserId; 11 | 12 | private String userId; 13 | private String username; 14 | private String nickname; 15 | private String sex; 16 | private String headImg; 17 | private String sign; 18 | 19 | public String getGroupId() { 20 | return groupId; 21 | } 22 | 23 | public void setGroupId(String groupId) { 24 | this.groupId = groupId; 25 | } 26 | 27 | public String getGroupNickname() { 28 | return groupNickname; 29 | } 30 | 31 | public void setGroupNickname(String groupNickname) { 32 | this.groupNickname = groupNickname; 33 | } 34 | 35 | public String getGroupName() { 36 | return groupName; 37 | } 38 | 39 | public void setGroupName(String groupName) { 40 | this.groupName = groupName; 41 | } 42 | 43 | public String getGroupHeadImg() { 44 | return groupHeadImg; 45 | } 46 | 47 | public void setGroupHeadImg(String groupHeadImg) { 48 | this.groupHeadImg = groupHeadImg; 49 | } 50 | 51 | public String getIntroduction() { 52 | return introduction; 53 | } 54 | 55 | public void setIntroduction(String introduction) { 56 | this.introduction = introduction; 57 | } 58 | 59 | public String getUserId() { 60 | return userId; 61 | } 62 | 63 | public void setUserId(String userId) { 64 | this.userId = userId; 65 | } 66 | 67 | public String getUsername() { 68 | return username; 69 | } 70 | 71 | public void setUsername(String username) { 72 | this.username = username; 73 | } 74 | 75 | public String getNickname() { 76 | return nickname; 77 | } 78 | 79 | public void setNickname(String nickname) { 80 | this.nickname = nickname; 81 | } 82 | 83 | public String getSex() { 84 | return sex; 85 | } 86 | 87 | public void setSex(String sex) { 88 | this.sex = sex; 89 | } 90 | 91 | public String getHeadImg() { 92 | return headImg; 93 | } 94 | 95 | public void setHeadImg(String headImg) { 96 | this.headImg = headImg; 97 | } 98 | 99 | public String getSign() { 100 | return sign; 101 | } 102 | 103 | public void setSign(String sign) { 104 | this.sign = sign; 105 | } 106 | 107 | public String getOwnerUserId() { 108 | return ownerUserId; 109 | } 110 | 111 | public void setOwnerUserId(String ownerUserId) { 112 | this.ownerUserId = ownerUserId; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /html-web/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 | -------------------------------------------------------------------------------- /html-web/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 | const webpack=require('webpack') 7 | 8 | function resolve (dir) { 9 | return path.join(__dirname, '..', dir) 10 | } 11 | 12 | const createLintingRule = () => ({ 13 | test: /\.(js|vue)$/, 14 | loader: 'eslint-loader', 15 | enforce: 'pre', 16 | include: [resolve('src'), resolve('test')], 17 | options: { 18 | formatter: require('eslint-friendly-formatter'), 19 | emitWarning: !config.dev.showEslintErrorsInOverlay 20 | } 21 | }) 22 | 23 | module.exports = { 24 | context: path.resolve(__dirname, '../'), 25 | entry: { 26 | app: './src/main.js' 27 | }, 28 | output: { 29 | path: config.build.assetsRoot, 30 | filename: '[name].js', 31 | publicPath: process.env.NODE_ENV === 'production' 32 | ? config.build.assetsPublicPath 33 | : config.dev.assetsPublicPath 34 | }, 35 | resolve: { 36 | extensions: ['.js', '.vue', '.json'], 37 | alias: { 38 | 'vue$': 'vue/dist/vue.esm.js', 39 | '@': resolve('src'), 40 | 'jquery': 'jquery' 41 | } 42 | }, 43 | plugins: [ 44 | new webpack.ProvidePlugin({ 45 | $:"jquery", 46 | jQuery:"jquery", 47 | "windows.jQuery":"jquery" 48 | }) 49 | ], 50 | module: { 51 | rules: [ 52 | ...(config.dev.useEslint ? [createLintingRule()] : []), 53 | { 54 | test: /\.vue$/, 55 | loader: 'vue-loader', 56 | options: vueLoaderConfig 57 | }, 58 | { 59 | test: /\.js$/, 60 | loader: 'babel-loader', 61 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 62 | }, 63 | { 64 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 65 | loader: 'url-loader', 66 | options: { 67 | limit: 10000, 68 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 69 | } 70 | }, 71 | { 72 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 73 | loader: 'url-loader', 74 | options: { 75 | limit: 10000, 76 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 77 | } 78 | }, 79 | { 80 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 81 | loader: 'url-loader', 82 | options: { 83 | limit: 10000, 84 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 85 | } 86 | } 87 | ] 88 | }, 89 | node: { 90 | // prevent webpack from injecting useless setImmediate polyfill because Vue 91 | // source contains it (although only uses it if it's native). 92 | setImmediate: false, 93 | // prevent webpack from injecting mocks to Node native modules 94 | // that does not make sense for the client 95 | dgram: 'empty', 96 | fs: 'empty', 97 | net: 'empty', 98 | tls: 'empty', 99 | child_process: 'empty' 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /node-http/controller/friend.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | const friendRelationModel = require('../models/friendRelation'); 3 | const userModel = require('../models/user'); 4 | const asyncRedisClient = require('../lib/asyncRedis').client; 5 | const redisKey = require('../const/redisKey'); 6 | const baseController = require('./baseController'); 7 | 8 | module.exports = new Router( 9 | 10 | ).post('set_remark', async ctx => { 11 | 12 | let params = ctx.request.body; 13 | if (!params) return baseController.response400(ctx); 14 | if (!params.friendUserId || !params.remark) return baseController.response400(ctx); 15 | let friendRelation = await friendRelationModel.selectByConditionOnlyOne({ 16 | userId: ctx.state.authInfo.id, 17 | friendUserId: params.friendUserId 18 | }); 19 | if (!friendRelation) return baseController.responseWithCode(ctx, baseController.CODE.NOT_YOUR_FRIEND); 20 | await friendRelationModel.updateById(friendRelation._id, {remark: params.remark}); 21 | baseController.response(ctx); 22 | 23 | }).get("search_user", async ctx => { 24 | 25 | let param = ctx.query; 26 | if (!param.nickname && !param.username) return baseController.response400(ctx); 27 | let users = await userModel.selectByNicknameOrUsername(param.username, param.nickname); 28 | if (users) { 29 | return baseController.response(ctx, {list: user2Objects(users)}); 30 | } 31 | baseController.response(ctx); 32 | 33 | }).get('get_friend_list', async ctx => { 34 | 35 | let friendRelations = await friendRelationModel.selectByUserId(ctx.state.authInfo.id); 36 | if (friendRelations) { 37 | let ids = []; 38 | for (let index = 0, len = friendRelations.length; index < len; index++) { 39 | ids.push(friendRelations[index].friendUserId); 40 | } 41 | let users = await userModel.selectByIds(ids); 42 | let list = []; 43 | if (users) { 44 | let user; 45 | for (let index = 0, len = users.length; index < len; index++) { 46 | user = user2Object(users[index]); 47 | user.remark = friendRelations[index].remark; 48 | user.status = String(await asyncRedisClient.sismemberAsync(redisKey.USER_ONLINE(), users[index]._id.toString())); 49 | list.push(user); 50 | } 51 | baseController.response(ctx, {list: list}); 52 | return; 53 | } 54 | } 55 | baseController.response(ctx); 56 | 57 | }).routes(); 58 | 59 | const user2Object = user => { 60 | return { 61 | userId: user._id.toString(), 62 | username: user.username, 63 | nickname: user.nickname, 64 | headImg: user.headImg, 65 | sex: user.sex, 66 | sign: user.sign, 67 | }; 68 | }; 69 | 70 | const user2Objects = (users, friendRelations) => { 71 | let list = []; 72 | let user; 73 | for (let index in users) { 74 | user = user2Object(users[index]); 75 | if (friendRelations) { 76 | user.remark = friendRelations[index].remark; 77 | } 78 | list.push(user); 79 | } 80 | return list; 81 | }; -------------------------------------------------------------------------------- /html-web/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 | // '/api': { //代理地址 15 | // target: 'http://apidoc.thankjava.com', //需要代理的地址 16 | // changeOrigin: true, //是否跨域 17 | // secure: false, 18 | // baseURL: 'http://apidoc.thankjava.com', 19 | // pathRewrite: { 20 | // '^/api': '/mock/42/api' //本身的接口地址没有 '/api' 这种通用前缀,所以要rewrite,如果本身有则去掉 21 | // } 22 | // } 23 | '/api': { //代理地址 24 | target: 'https://wchat.thankjava.com', //需要代理的地址 25 | changeOrigin: true, //是否跨域 26 | secure: false, 27 | baseURL: 'https://wchat.thankjava.com/index' 28 | } 29 | }, 30 | 31 | // Various Dev Server settings 32 | host: 'localhost', // can be overwritten by process.env.HOST 33 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 34 | autoOpenBrowser: false, 35 | errorOverlay: true, 36 | notifyOnErrors: true, 37 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 38 | 39 | // Use Eslint Loader? 40 | // If true, your code will be linted during bundling and 41 | // linting errors and warnings will be shown in the console. 42 | useEslint: false, 43 | // If true, eslint errors and warnings will also be shown in the error overlay 44 | // in the browser. 45 | showEslintErrorsInOverlay: false, 46 | 47 | /** 48 | * Source Maps 49 | */ 50 | 51 | // https://webpack.js.org/configuration/devtool/#development 52 | devtool: 'cheap-module-eval-source-map', 53 | 54 | // If you have problems debugging vue-files in devtools, 55 | // set this to false - it *may* help 56 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 57 | cacheBusting: true, 58 | 59 | cssSourceMap: true 60 | }, 61 | 62 | build: { 63 | // Template for index.html 64 | index: path.resolve(__dirname, '../dist/index.html'), 65 | 66 | // Paths 67 | assetsRoot: path.resolve(__dirname, '../dist'), 68 | assetsSubDirectory: 'static', 69 | assetsPublicPath: '/', 70 | 71 | /** 72 | * Source Maps 73 | */ 74 | 75 | productionSourceMap: false, 76 | // https://webpack.js.org/configuration/devtool/#production 77 | devtool: '#source-map', 78 | 79 | // Gzip off by default as many popular static hosts such as 80 | // Surge or Netlify already gzip all static assets for you. 81 | // Before setting to `true`, make sure to: 82 | // npm install --save-dev compression-webpack-plugin 83 | productionGzip: false, 84 | productionGzipExtensions: ['js', 'css'], 85 | 86 | // Run the build command with an extra argument to 87 | // View the bundle analyzer report after build finishes: 88 | // `npm run build --report` 89 | // Set to `true` or `false` to always turn it on or off 90 | bundleAnalyzerReport: process.env.npm_config_report 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /html-web/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 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/controller/Chat.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.controller; 2 | 3 | import com.thankjava.wchat.consts.EventType; 4 | import com.thankjava.wchat.bean.MsgPushContext; 5 | import com.thankjava.wchat.bean.ResponseContext; 6 | import com.thankjava.wchat.bean.controller.chat.ChatMsg; 7 | import com.thankjava.wchat.bean.notice.chat.ChatMsgPush; 8 | import com.thankjava.wchat.consts.ResponseCode; 9 | import com.thankjava.wchat.db.entity.FriendRelation; 10 | import com.thankjava.wchat.db.mongo.FriendRelationMapper; 11 | import com.thankjava.wchat.db.mongo.impl.FriendRelationMapperImpl; 12 | import com.thankjava.wchat.notice.ChatEventPush; 13 | import com.thankjava.wchat.ws.anno.WSController; 14 | import com.thankjava.wchat.ws.anno.WSProcess; 15 | import com.thankjava.wchat.bean.RequestContext; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * @Author: acexy@thankjava.com 21 | * 2018/8/9 22 | * @Description: 处理发送及时聊天信息 23 | **/ 24 | @WSController(path = "chat") 25 | public class Chat { 26 | 27 | private ChatEventPush chatEventPush = new ChatEventPush(); 28 | private FriendRelationMapper friendRelationMapper = new FriendRelationMapperImpl(); 29 | 30 | @WSProcess(path = "send_msg") 31 | public void sendMsg(RequestContext ctx) { 32 | 33 | ChatMsg chatMsg = ctx.getData(ChatMsg.class); 34 | 35 | String toUserId = chatMsg.getToUserId(); 36 | String toGroupId = chatMsg.getToGroupId(); 37 | 38 | if ((toUserId == null || toUserId.length() == 0) && toGroupId == null) { 39 | ctx.response(new ResponseContext(ResponseCode.BAD_REQUEST_PARAMETER)); 40 | return; 41 | } 42 | 43 | if (ctx.getFromUserId().equals(toUserId)) { 44 | ctx.response(new ResponseContext(ResponseCode.BAD_REQUEST_PARAMETER, "不能向自己发送消息")); 45 | return; 46 | } 47 | 48 | EventType eventType = toGroupId == null ? EventType.chat_f2f : EventType.chat_group; 49 | 50 | MsgPushContext msgPushContext = null; 51 | if (eventType == EventType.chat_f2f) { 52 | 53 | // TODO: 可以考虑使用缓存 54 | FriendRelation friendRelation = new FriendRelation(); 55 | friendRelation.setUserId(ctx.getFromUserId()); 56 | friendRelation.setFriendUserId(toUserId); 57 | List friendRelations = friendRelationMapper.selectByCondition(friendRelation); 58 | if (friendRelations == null || friendRelations.isEmpty()) { 59 | ctx.response(new ResponseContext(ResponseCode.NOT_YOUR_FRIEND)); 60 | return; 61 | } 62 | 63 | msgPushContext = new MsgPushContext<>( 64 | eventType, 65 | ctx.getFromUserId(), 66 | chatMsg.getToUserId(), 67 | new ChatMsgPush( 68 | chatMsg.getMsg() 69 | ) 70 | ); 71 | 72 | } else { 73 | 74 | // TODO: 可以考虑使用缓存 75 | msgPushContext = new MsgPushContext<>( 76 | eventType, 77 | ctx.getFromUserId(), 78 | chatMsg.getToUserId(), 79 | new ChatMsgPush( 80 | chatMsg.getToGroupId(), 81 | chatMsg.getMsg() 82 | ) 83 | ); 84 | } 85 | 86 | ctx.response(chatEventPush.pushChatMsg(msgPushContext)); 87 | } 88 | } -------------------------------------------------------------------------------- /html-web/src/store/state.js: -------------------------------------------------------------------------------- 1 | // import {loadSearch, loadPlay, loadFavorite} from "common/js/cache" 2 | 3 | const state = { 4 | // 当前登陆用户信息 5 | user: {}, 6 | 7 | // 当前会好好友 8 | currentFriend: { 9 | // 当前选中的会话用户id 10 | // friendId: '', 11 | 12 | // 当前选中的会话用户名称 13 | // friendName: '', 14 | }, 15 | 16 | // 好友会话列表 17 | /** 字段注释 18 | 'userId': [ 19 | { 20 | content: '', 21 | date: 1548830741228, 22 | self: true, 23 | code: '' 24 | }, 25 | { 26 | content: '', 27 | date: 1548830741228, 28 | self: true, 29 | code: '' 30 | } 31 | 32 | ] 33 | **/ 34 | sessions: {}, 35 | 36 | // 好友列表 37 | /* 38 | "username": "111", // * 账号 39 | "nickname": "111", // * 昵称 40 | "email": "111@thankjava.com", // * 电子邮箱(脱敏) 41 | "headImg": "./static/test.jpg", 42 | "sex": "0", // * 性别 0-女 1-男 -1-未设置 43 | "sign": "", // * 个性签名 44 | "remark": "", 45 | 'id': '', 46 | 'isRead': true //是否有未读消息 47 | */ 48 | friendList: [], 49 | 50 | // 群列表 51 | /* 52 | "ownerUserId": "111", // 53 | "ownerUername": "111", // 54 | "ownerNickname": "111", // 55 | "ownerHeadImg": "", // 56 | "ownerSex": "0", // 57 | "ownerSign": "lalla", // 58 | "remark": "111", // 59 | "groupId": "groupIdA", // * 60 | "groupName": "group11", // * 61 | "groupNickname": "group11", // * 62 | "groupHeadImg": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcT25zoJIJipKHfP-7Dte2GoX0oSWMoi0lRruGAJj2hypP9qKxSZ", // * 63 | "introduction": "group11" 64 | */ 65 | groupList: [], 66 | 67 | // 群会话列表 68 | groupSessions: { 69 | // 'groupIdA': [ 70 | // { 71 | // content: 'testtesttesttesttesttesttesttest', 72 | // date: 1548830741228, 73 | // self: false, 74 | // userId: '111', 75 | // code: '' 76 | // }, 77 | // { 78 | // content: 'testtesttesttesttesttesttesttest', 79 | // date: 1548830745228, 80 | // self: true, 81 | // userId: '222', 82 | // code: '' 83 | // } 84 | // ] 85 | }, 86 | 87 | // 当前选中的会话群组 88 | currentGroup: { 89 | // groupId: '', 90 | // groupName: '', 91 | }, 92 | 93 | 94 | // 群成员列表 95 | /*{ 96 | 'groupId': { 97 | userId1: { 98 | "userId": "111", 99 | "username": "111", 100 | "nickname": "111", 101 | "headImg": "", 102 | "sex": "0", 103 | "sign": "lalla", 104 | "remark": "111" 105 | }, 106 | userId1: { 107 | 108 | }... 109 | } 110 | 111 | }*/ 112 | groupFriendList: {}, 113 | 114 | // 过滤出只包含这个key的会话 115 | filterKey: '', 116 | friendApplyList: [], 117 | groupApplyList: [], 118 | tabType: '0', //0 代表好友列表 1代表群组 119 | } 120 | 121 | export default state; -------------------------------------------------------------------------------- /html-web/src/components/indexItem/Info.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{user.nickname}} 7 | 8 | 9 | 10 | 11 | 12 | {{user.sign}} 13 | 16 | 17 | 18 | 80 | -------------------------------------------------------------------------------- /html-web/src/components/indexItem/TextArea.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 84 | -------------------------------------------------------------------------------- /node-http/controller/common.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/13 4 | * @Description:登录相关处理 5 | */ 6 | 7 | const Router = require('koa-router'); 8 | 9 | const cookiesName = require('../const/cookiesName'); 10 | const baseController = require('./baseController'); 11 | const util = require('../lib/util'); 12 | const asyncRedisClient = require('../lib/asyncRedis').client; 13 | const redisKey = require('../const/redisKey'); 14 | const userModel = require('../models/user'); 15 | const baseConfig = require('../config/basic'); 16 | 17 | module.exports = new Router( 18 | 19 | ).post('login', async (ctx) => { 20 | 21 | // 登录 22 | let params = ctx.request.body; 23 | if (!params || Object.keys(params).length == 0) { 24 | return baseController.response400(ctx); 25 | } 26 | 27 | let username = params.username; 28 | let password = params.password; 29 | 30 | if (!username || !password) { 31 | return baseController.response400(ctx, '帐号或密码为空'); 32 | } 33 | 34 | let user = await userModel.selectByUsername(username); 35 | if (!user) { 36 | return baseController.responseWithCode(ctx, baseController.CODE.INVALID_ACCOUNT, '账号不存在'); 37 | } 38 | 39 | if (util.md5(user.username + password) != user.password) { 40 | return baseController.responseWithCode(ctx, baseController.CODE.PASSWORD_ERROR, '密码错误'); 41 | } 42 | 43 | await doLogin(ctx, user); 44 | 45 | baseController.response(ctx); 46 | 47 | }).post('registe', async ctx => { 48 | 49 | let params = ctx.request.body; 50 | 51 | if (!params) return baseController.response400(ctx); 52 | if (!params.username || !params.password || !params.nickname) { 53 | return baseController.response400(ctx); 54 | } 55 | 56 | if (!baseController.REG.USERNAME.test(params.username)) return baseController.response400(ctx, '用户名不合法'); 57 | if (!baseController.REG.PASSWORD.test(params.password)) return baseController.response400(ctx, '密码不合法'); 58 | 59 | let user = await userModel.selectByUsername(params.username); 60 | if (user) return baseController.responseWithCode(ctx, baseController.CODE.EXISTING_USER, '用户账号已存在'); 61 | 62 | user = await userModel.save({ 63 | username: params.username, 64 | password: util.md5(params.username + params.password), 65 | nickname: params.nickname, 66 | sex: params.sex ? params.sex : '-1', 67 | sign: params.sign, 68 | headImg: params.sex == '0' ? baseConfig.headImgGirl : (params.sex == '1' ? baseConfig.headImgBoy : baseConfig.headImgUnknown) 69 | }); 70 | 71 | await doLogin(ctx, user); 72 | baseController.response(ctx); 73 | 74 | }).routes(); 75 | 76 | 77 | const doLogin = async (ctx, user) => { 78 | let nowTime = Date.now(); 79 | let token = util.md5(util.uuid() + nowTime + user.username); 80 | 81 | let userInfo = { 82 | id: user._id.toString(), 83 | username: user.username, 84 | nickname: user.nickname, 85 | headImg: user.headImg, 86 | sign: user.sign, 87 | sex: user.sex 88 | }; 89 | 90 | let userInfoJsonStr = JSON.stringify(userInfo); 91 | 92 | let authEx = baseController.CONSTS.AUTH_COOKIE_EXPIRES_DAY * 24 * 60 * 60; 93 | 94 | // 写入redis验证数据 95 | await asyncRedisClient.setAsync(redisKey.AUTH_TOKEN(token), userInfoJsonStr, 'EX', authEx); 96 | 97 | // 登录成功 98 | let cookieOpt = { 99 | httpOnly: true, 100 | path: '/', 101 | domain: baseConfig.domain 102 | }; 103 | 104 | cookieOpt.maxAge = authEx * 1000; 105 | 106 | // 创建cookies会话凭证信息 107 | baseController.setCookie(ctx, cookiesName.COOKIE_NAME_TOKEN, token, cookieOpt); 108 | 109 | cookieOpt.httpOnly = false; 110 | 111 | baseController.setCookie(ctx, cookiesName.COOKIE_NAME_UID, userInfo.id, cookieOpt); 112 | 113 | }; -------------------------------------------------------------------------------- /html-web/src/components/common/GroupInfoCard.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | {{groupInfo.groupNickname || groupInfo.groupName }} 13 | 14 | 15 | 16 | 账号: 17 | {{groupInfo.groupName}} 18 | 19 | 20 | 21 | 22 | 23 | 备注: {{groupInfo.remark}} 24 | 简介: {{groupInfo.introduction}} 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 117 | -------------------------------------------------------------------------------- /node-http/lib/busboyUpload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author:acexy@thankjava.com 3 | * 2018/6/14 4 | * @Description:通过busboy解析post数据完成上传动作 5 | */ 6 | const Busboy = require('busboy'); 7 | 8 | const fs = require('fs'); 9 | const uploadConfig = require('../config/upload'); 10 | const util = require('../lib/util'); 11 | const path = require('path'); 12 | const allowedMimeType = require('../const/allowedMimeType'); 13 | 14 | module.exports.upload = (ctx) => new Promise(resolve => { 15 | 16 | let nodeHttpReq = ctx.req; 17 | 18 | let busboy = new Busboy({headers: nodeHttpReq.headers}); 19 | 20 | let uploadResult = []; 21 | busboy.on('file', function (fieldname, file, filename, encoding, mimetype) { // 每次接收文件就触发 22 | 23 | if (allowedMimeType.indexOf(mimetype) != -1) { 24 | 25 | console.log('=> busboyUpload'.cyan + ' begin to upload filename = '.grey + filename.blue); 26 | 27 | let result = { 28 | fileName: filename, 29 | uploadTime: Date.now(), 30 | }; 31 | 32 | let basePath = uploadConfig.path; 33 | let date = new Date(); 34 | let dirPath = path.join(String(date.getFullYear()), String(date.getMonth() + 1), String(date.getDate())); 35 | let filePath = path.join(basePath, dirPath); 36 | mkdirsSync(filePath); 37 | let fileName = util.md5(util.uuid() + Date.now()) + '.' + filenameSuffix(filename); 38 | let uriPath = path.join(dirPath, fileName); 39 | let absPath = path.join(filePath, fileName); 40 | 41 | 42 | // ------------------------------------------------- 43 | // 由于前端使用blob方式不再使用当前模式 44 | // file.pipe(fs.createWriteStream(absPath)); 45 | 46 | let bfs = []; 47 | file.on('data', (chunk) => { 48 | bfs.push(chunk); 49 | }); 50 | // ------------------------------------------------- 51 | 52 | file.on('end', () => { 53 | 54 | let buf = Buffer.concat(bfs); 55 | let imgBase64 = buf.toString(); 56 | fs.writeFileSync(absPath, Buffer.from(imgBase64, 'base64')); 57 | 58 | result.path = path.sep + uriPath; 59 | result.flag = true; 60 | result.message = '上传完成'; 61 | console.log('=> busboyUpload'.cyan + ' finished to upload filename = '.grey + filename.blue + ' path = '.grey + absPath.blue); 62 | uploadResult.push(result); 63 | }); 64 | } else { 65 | console.log('=> busboyUpload'.cyan + ' not allowed mimetype filename = '.grey + filename.blue + ' mimetype = '.grey + mimetype.blue); 66 | file.resume(); // 丢弃数据, 在监听了file事件后必须要处理file流,否则busboy不会触发finish事件 67 | uploadResult.push({ 68 | flag: false, 69 | fileName: filename, 70 | message: '不允许的的文件上传格式' 71 | }); 72 | } 73 | 74 | }); 75 | 76 | let fields = {}; 77 | 78 | busboy.on('field', function (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) { 79 | if (fields[fieldname]) { 80 | if (Array.isArray(fields[fieldname])) { 81 | let array = fields[fieldname]; 82 | array.push(val); 83 | fields[fieldname] = array; 84 | } else { 85 | fields[fieldname] = [fields[fieldname], val]; 86 | } 87 | } else { 88 | fields[fieldname] = val; 89 | } 90 | }); 91 | 92 | busboy.on('finish', function () { 93 | resolve({ 94 | flag: true, 95 | uploadResult: uploadResult, 96 | fields: fields 97 | }); 98 | 99 | }); 100 | 101 | busboy.on('error', function (err) { 102 | console.log(err); 103 | resolve({ 104 | flag: false, 105 | }); 106 | }); 107 | 108 | nodeHttpReq.pipe(busboy); 109 | }); 110 | 111 | /** 112 | * 解析文件名后缀 113 | * @param filename 114 | * @returns {*|string} 115 | */ 116 | const filenameSuffix = filename => { 117 | let array = filename.split('.'); 118 | return array[array.length - 1]; 119 | }; 120 | 121 | const mkdirsSync = dirname => { 122 | if (fs.existsSync(dirname)) { 123 | return true 124 | } else { 125 | if (mkdirsSync(path.dirname(dirname))) { 126 | fs.mkdirSync(dirname); 127 | return true 128 | } 129 | } 130 | }; -------------------------------------------------------------------------------- /java-websocket/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 4.0.0 5 | com.thankjava.wchat 6 | java-websocket 7 | 1.0.1 8 | 9 | java-websocket 10 | 11 | 12 | 13 | UTF-8 14 | UTF-8 15 | 16 | 1.1.3 17 | 1.5.0 18 | 1.2.49 19 | 1.7.25 20 | 1.2.3 21 | 2.9.0 22 | 3.8.0 23 | 24 | 25 | 26 | 27 | 28 | 29 | com.thankjava.toolkit 30 | fast-toolkit 31 | ${fast-toolkit.version} 32 | 33 | 34 | com.thankjava.toolkit3d 35 | fast-toolkit3d 36 | ${fast-toolkit.version} 37 | 38 | 39 | 40 | org.java-websocket 41 | Java-WebSocket 42 | ${java.websocket.version} 43 | 44 | 45 | 46 | 47 | com.alibaba 48 | fastjson 49 | ${fastjson.version} 50 | 51 | 52 | 53 | org.slf4j 54 | slf4j-api 55 | ${org.slf4j.version} 56 | 57 | 58 | 59 | ch.qos.logback 60 | logback-classic 61 | ${ch.qos.logback.version} 62 | 63 | 64 | 65 | redis.clients 66 | jedis 67 | ${redis.clients.version} 68 | 69 | 70 | 71 | org.mongodb 72 | mongodb-driver 73 | ${mongo.version} 74 | 75 | 76 | 77 | 78 | 79 | java-websocket 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-surefire-plugin 84 | 2.5 85 | 86 | true 87 | 88 | 89 | 90 | org.apache.maven.plugins 91 | maven-compiler-plugin 92 | 3.1 93 | 94 | 1.8 95 | 1.8 96 | utf-8 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-source-plugin 102 | 2.1.2 103 | 104 | 105 | attach-sources 106 | verify 107 | 108 | jar-no-fork 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /node-http/controller/user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Author: acexy@thankjava.com 3 | * 2018/7/9 4 | * @Description: 5 | */ 6 | const Router = require('koa-router'); 7 | const baseController = require('./baseController'); 8 | const userModel = require('../models/user'); 9 | const friendApplyModel = require('../models/friendApply'); 10 | const groupInfoModel = require('../models/groupInfo'); 11 | const groupApplyModel = require('../models/groupApply'); 12 | const uploadConfig = require('../config/upload'); 13 | 14 | 15 | module.exports = new Router( 16 | 17 | ).post('change_pwd', async ctx => { 18 | 19 | let params = ctx.request.body; 20 | if (!params) return baseController.response400(ctx); 21 | if (!params.oldPassword || !params.newPassword || !params.verifyPassword) return baseController.response400(ctx); 22 | if (!baseController.REG.PASSWORD.test(params.password)) return baseController.response400(ctx, '密码不合法'); 23 | if (params.newPassword != params.verifyPassword) return baseController.response400(ctx, '两次密码不一致'); 24 | if (params.oldPassword == params.newPassword) return baseController.response400(ctx, '原密码和新密码相同'); 25 | let authInfo = ctx.state.authInfo; 26 | let user = await userModel.selectById(authInfo.id); 27 | if (user.password != util.md5(user.username + params.newPassword)) return baseController.responseWithCode(ctx, baseController.CODE.PASSWORD_ERROR, '原密码错误'); 28 | await userModel.updatePasswordByUsername(user.username, util.md5(user.username + params.newPassword)); 29 | baseController.response(ctx); 30 | 31 | }).post('update_uinfo', async ctx => { 32 | 33 | // TODO: 34 | 35 | }).get('get_info', async ctx => { 36 | baseController.response(ctx, await getUserInfo(ctx)); 37 | }).get('friend_apply_list', async ctx => { 38 | let friendApplies = await friendApplyModel.selectByToUserIdAndUnprocessed(ctx.state.authInfo.id); 39 | if (friendApplies) { 40 | let list = await toFriendApplyList(friendApplies); 41 | return baseController.response(ctx, {list: list}); 42 | } 43 | baseController.response(ctx); 44 | }).get('group_apply_list', async ctx => { 45 | let groupInfos = await groupInfoModel.selectByOwnerUserId(ctx.state.authInfo.id); 46 | if (groupInfos) { 47 | let applyList = []; 48 | for (let index in groupInfos) { 49 | let groupApplies = await groupApplyModel.selectByToGroupIdAndUnprocessed(groupInfos[index]._id.toString()); 50 | if (groupApplies && groupApplies.length > 0) { 51 | for (let i in groupApplies) { 52 | let user = await userModel.selectById(groupApplies[i].applyUserId); 53 | applyList.push(toGroupApply(user, groupInfos[index], groupApplies[i])); 54 | } 55 | } 56 | } 57 | if (applyList.length > 0) return baseController.response(ctx, {list: applyList}); 58 | } 59 | baseController.response(ctx); 60 | }).routes(); 61 | 62 | const toFriendApplyList = async friendApplies => { 63 | let list = []; 64 | for (let index in friendApplies) { 65 | let object = {}; 66 | object.friendApplyId = friendApplies[index]._id; 67 | let user = await userModel.selectById(friendApplies[index].applyUserId); 68 | object.fromUserId = friendApplies[index].applyUserId; 69 | object.username = user.username; 70 | object.nickname = user.nickname; 71 | object.headImg = user.headImg; 72 | object.sign = user.sign; 73 | object.sex = user.sex; 74 | object.remark = friendApplies[index].remark; 75 | object.createTime = friendApplies[index].createTime; 76 | list.push(object); 77 | } 78 | return list; 79 | }; 80 | 81 | const getUserInfo = async ctx => { 82 | let user = await userModel.selectById(ctx.state.authInfo.id); 83 | return { 84 | id: user._id, 85 | username: user.username, 86 | nickname: user.nickname, 87 | headImg: user.headImg, 88 | sign: user.sign, 89 | sex: user.sex 90 | }; 91 | }; 92 | const toGroupApply = (user, groupInfo, groupApply) => { 93 | let object = {}; 94 | object.groupApplyId = groupApply._id.toString(); 95 | object.fromUserId = groupApply.applyUserId; 96 | object.remark = groupApply.remark; 97 | object.groupName = groupInfo.groupName; 98 | object.groupNickname = groupInfo.groupNickname; 99 | object.groupHeadImg = groupInfo.groupHeadImg; 100 | object.username = user.username; 101 | object.nickname = user.nickname; 102 | object.headImg = user.headImg; 103 | object.createTime = groupApply.createTime; 104 | return object; 105 | }; -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/lib/websocket/BasicWebSocket.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.lib.websocket; 2 | 3 | import com.thankjava.toolkit.core.reflect.ReflectUtil; 4 | import com.thankjava.wchat.lib.websocket.callback.OnConnCloseListener; 5 | import com.thankjava.wchat.lib.websocket.callback.OnMessageListener; 6 | import com.thankjava.wchat.lib.websocket.entity.ConVerifyResult; 7 | import com.thankjava.wchat.lib.websocket.callback.ConnectionVerifyListener; 8 | import com.thankjava.wchat.lib.websocket.entity.Message; 9 | import com.thankjava.wchat.util.WSUtil; 10 | import org.java_websocket.WebSocket; 11 | import org.java_websocket.handshake.ClientHandshake; 12 | import org.java_websocket.server.WebSocketServer; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.lang.reflect.Constructor; 17 | import java.lang.reflect.InvocationTargetException; 18 | import java.net.InetSocketAddress; 19 | import java.nio.ByteBuffer; 20 | 21 | /** 22 | * @Author: acexy@thankjava.com 23 | * 2018/8/3 24 | * @Description: WebSocket核心封装 25 | **/ 26 | public class BasicWebSocket extends WebSocketServer { 27 | 28 | static Logger logger = LoggerFactory.getLogger(BasicWebSocket.class); 29 | 30 | // 验证WebSocket连接 初始链接数据信息 31 | private ConnectionVerifyListener connectionVerifyListener; 32 | private OnMessageListener onMessageListener; 33 | private OnConnCloseListener onConnCloseListener; 34 | 35 | public BasicWebSocket(int port, ConnectionVerifyListener connectionVerifyListener, OnMessageListener onMessageListener, OnConnCloseListener onConnCloseListener) { 36 | super(new InetSocketAddress(port)); 37 | if (connectionVerifyListener == null || onMessageListener == null || onConnCloseListener == null) throw new RuntimeException(); 38 | this.connectionVerifyListener = connectionVerifyListener; 39 | this.onMessageListener = onMessageListener; 40 | this.onConnCloseListener = onConnCloseListener; 41 | logger.info("WebSocket Listened " + port); 42 | } 43 | 44 | @Override 45 | public void onOpen(WebSocket conn, ClientHandshake handshake) { 46 | ConVerifyResult conVerifyResult = connectionVerifyListener.doProcess(handshake); 47 | if (conVerifyResult == null) throw new RuntimeException("ConVerifyResult can not be null"); 48 | if (conn == null) { 49 | logger.error("ws connected but conn is null"); 50 | return; 51 | } 52 | if (conVerifyResult.isAllowConnect()) { 53 | Session.putConn(conVerifyResult.getSessionId(), conn); 54 | conn.setAttachment(conVerifyResult); 55 | logger.info("ws connected host = " + conn.getRemoteSocketAddress().getHostString() + " sessionId = " + conVerifyResult.getSessionId()); 56 | } else { 57 | logger.info("ws refuse path = " + conn.getResourceDescriptor() + " host = " + conn.getRemoteSocketAddress().getHostString()); 58 | conn.close(1008, "Unauthorized request"); 59 | } 60 | } 61 | 62 | @Override 63 | public void onClose(WebSocket conn, int code, String reason, boolean remote) { 64 | if (conn == null) return; 65 | ConVerifyResult conVerifyResult = conn.getAttachment(); 66 | if (conVerifyResult == null) return; 67 | logger.debug("conn closing sessionId = " + conVerifyResult.getSessionId()); 68 | Session.delConn(conVerifyResult.getSessionId()); 69 | onConnCloseListener.doProcess(conVerifyResult); 70 | } 71 | 72 | @Override 73 | public void onMessage(WebSocket conn, String message) { 74 | ConVerifyResult conVerifyResult = conn.getAttachment(); 75 | logger.debug("onMessage sessionId = " + conVerifyResult.getSessionId() + " message = " + message); 76 | onMessageListener.doProcess(new Message(conVerifyResult, conn, message)); 77 | } 78 | 79 | @Override 80 | public void onMessage(WebSocket conn, ByteBuffer message) { 81 | logger.debug("received byte buffer data"); 82 | } 83 | 84 | @Override 85 | public void onError(WebSocket conn, Exception e) { 86 | logger.error("websocket error", e); 87 | } 88 | 89 | @Override 90 | public void onStart() { 91 | try { 92 | Class clazz = Class.forName("com.thankjava.wchat.lib.websocket.core.WSImpl"); 93 | Constructor constructor = clazz.getDeclaredConstructors()[0]; 94 | constructor.setAccessible(true); 95 | ReflectUtil.setFiledVal(WSUtil.class, "ws", constructor.newInstance()); 96 | } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e) { 97 | e.printStackTrace(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /html-web/src/assets/styles/iconfont.css: -------------------------------------------------------------------------------- 1 | 2 | @font-face {font-family: "el"; 3 | src: url('iconfont/iconfont.eot?t=1538186245466'); /* IE9*/ 4 | src: url('iconfont/iconfont.eot?t=1538186245466#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAr8AAsAAAAAD+QAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8dkxpY21hcAAAAYAAAACzAAACSshNJ2tnbHlmAAACNAAABnYAAAi85WW5O2hlYWQAAAisAAAALwAAADYSyT4TaGhlYQAACNwAAAAeAAAAJAfeA49obXR4AAAI/AAAABEAAAA0NAEAAGxvY2EAAAkQAAAAHAAAABwMSg7AbWF4cAAACSwAAAAfAAAAIAEcAKFuYW1lAAAJTAAAAUEAAAIleYFhx3Bvc3QAAAqQAAAAagAAAI3FJDPEeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMbzKZm7438AQw9zD0AAUZgTJAQDl6gxeeJzlkrENwjAQRb8TEyBOQUGBmIE6RbpUGSYL0GUVCirmir6sLBG+fQVCYQPOepbu63Rn3TeAHYBS3IQH3AsOKZ5SXdZL1Fn3uCs/4yTFs2Bgx54Dp9jGeRnXFSBYb9VNOHW54Pp1gKPmBL3Ho1LFHgfNKdCovPrR49+iyffjkxGG9gY6IznFwtAu5YaRXGYwksvsDG0a7I3kNgdD2wcnQz4gtkb6KXE25A2W0UDzBpcKOo0AeJx1VltsHGcVnvP/s3PbmX8uO5fdnd0dz64941ts6vXOZJPYTtqgppGcG02Q2ofkoVFs0sQSAVGVQi1UGZAKxqUKeSqqepEQQu1LVR7AiiM1vKZ9oC1FKBXvICRTAdJOODNrRwiE/euc+S/n8p9z/u8sRzn8o5fIPa7M2dwYxwEDsQXeIqQzQGdgPplzbSEVxBmIccwnaQscBoS7eZfn794cUtPz2p5XEgxJJbwD1apXA4fce7h/8+7gz5AfaXvfMwEUUb+VsDZAmyU/y80XPrxHPueOcz/gXuW4scLYIk3RiySahShBlpvvoHMMvBboULB8uI4t7K07guuFi7AA85EY9tBXlF9Ep1F0lqB4lCwBKoqKja6L0igVQMF0UujIVRR6nUqv43ScrtPtdXvkjdbqFKUglhUiMoEnFIDyVLW1dKZ9INGcMs4AeFIq6QJVShLhp1cbFQagW2Shwmw2+EAQ6yvTtgz5LhFoucQDoYTniWQok52Rw01Dzme55lKZikBkXlBAdiZXmoq0EW7g+J06qoFQ95zQFIO650lUPOAZBxvWCM+PWI2DhndApJLn1QPRDMt6oIlsTJV99qTiADjKk6z5nXOSRMuRJgYschVfV0TqVk1GZZbYyoSr1Sg/5k4oTqLJlJlVl4qK3pDVlqm3JK2jllQego2NNo5h3h7cprfp0aJ2Rv5f7TBoR3FvEeZcj3Dbg1JpsD2ktu+P+34DkEJB6NGHe9uDwZfzNRx/xXP2/ZygTShq5TY1OY3jKmgsyasljXigS+yjQIJvgCCdtiD7S/bFW7AufrLegp0luPKU8Zvsm1Dbq7XX4TNO4kyujjoY2O5cMh+N/c/Hr6Nud7nbhXCPB3v8D5Cz5S78F89VE4zJb+kVep5rcrPobhjlL0b0EhILxOtigEx0Gos0NTFGWLrtaAGwNPPw4IxCdoM31r8diOeVw+9/MCJIU4cADk3BJ1N9gP4XlWZFtwCifmzBpwfvm/I2gwqQSjP7uAf9qeydqT4h/Sk4O9XPPltmlQpblluVZhQ1K608dg+26Q49xnVyz8zhg3LMsC008crd0LTdwzCXpGaIpIf+bWTvgm+XHT97pxaGNTjrO2Ubk3WmFgKE8N7eRy17t2GX7cb+qWJSxAJjfYbscowL0SJeHO9fXDbtA+3iHKFkaq88cAkenF4lZPX0kJ5989lNSjef3WNkl6ycWl4hZGX51ArJLsILq8bEeDxprL4Aw7jfpz+io5zBtdBWr5tfLw931yk5YW9MiGJbiJF6WJDk8i26efXqJr2VfUqr2edJikBDmvxPtvgmsDJMXHqR0hcvZR+X71z9mjT9uCw/Pk0Xnnn++WcW8Eo82lqnf6Iv462OcGvcD7GKvKLul6DAlrjAL5ENB8kRpZKD5yJZQrzCf7ztEGRwnxYglSLEFcjjCjrso20kCqihnevqiLYgtnVcw1leMK4gxmncET08mw7hL0yi3ar50tMXX9IcSzE0VtbS0cmfnpB0udz2J+ctQ61If7twgwp+Rxt1DD+o2b6shpV6nd648E8aRKOTTBP7350IDqiCqkg81XzN8kVVNRVBr1anxyV1XlVLJQXRS9XI27U/Gu2vq6Om8YhhVK/bwpFtJbCA/kqaht2R6y8TunnddrqjrcP++GOOP+pTVVbR8UcOHWnFafYv+soak3hmf0mRrAqAqei6UK6wtVco3CvHitjQyiU38Kx+aM1UbRWFqcBU7CAVuaw2DTguUEqEhqZXZwc7ta/wP77D76qNhvqPr/5dVS6KR96XD72N+SoVdXiHEnzvEb7Kp7nvY4UUKBUPEQupY+/1Diz7/yhUCHvDZEHSS+cXMP7YjjAFw+4yTPJDBXPJQq42GVvEfewzDPKU4rMW86Tq+OV4tlekuTidvzHyrXYM5Nq5c9cIxO2n1sBi5FFmwdqH6QlCTqQFhYODjG5du7ZFs3HsD9gkeKEejIdME1yWPobgbi+dBKthwelHTeLZzrFEc0VNG4mDusAXAiCZx86A5SN6nIRf+Kdm0SCanT3lj7125SgaRMNHr7yGAXgiGVpOnhj8fARNomEIKOV5ygs8D9j2ZpvtrsS7dUbZ+X6Quu3nTs4xy2Jzxy+bNHbTID2vU63u8nI3bM6qFIVQNNdADWpePj48fPI5bh8jLpDfI1NzVNrvH3H+IyP7Jd3Z2tqhBYX1/S+kePzfabhfRQAAeJxjYGRgYADiTz1hh+P5bb4ycLMwgMD1K39ZEfT/BhYG5h4gl4OBCSQKAFqfC74AeJxjYGRgYG7438AQwwJkMTCwMDCAaSTACwBHLAJ3AAB4nGNhYGBgIYQZUfkABfMANgAAAAAAAAAAQgEeAVgBeAGoAfYCLgJkApoDcgRABF54nGNgZGBg4GWYysDKAAJMQMwFhAwM/8F8BgAYqQG+AHicXZE7TsNAFEWv80M4EgUI6NBAQQHCTlLSRkr6FCnoHGecj8YfjSeRshIKVkBBwSoo2ANr4WbyQoGtd3XefZ8Z2QDO8YMAh+eSceAADWYHbuAE18JNZjfCLbISbqOLO+EO/SfhEI+Ihbu4wAs3BK1TZg/IhQNuehVu4Axvwk3678Itvh/CbVzhU7hD/0s4xBTfwl3cB7fh0OrE6bma7dQqLYusLFyozUQvNiax2mgz1bZelYXqRz1txrrQ9thfbxcD5zKV2TJXIw5qY0pV2XKtUxctnaue4zgTP0rLnMcPYaGRwFHn/DQz7KgrpChRIPPq2KdhMKEusCElfsr4mFItas7sexX6iNDzlTG18NX/+2tsuWlA1/EMxbCczkkjOXE/b8gKla+t6aT0Iyz9VIVn/qT474bH/sjfPP8FKwJZ6gAAAHicbYrBDoIwEAX30dIiCr9oNtkGm8hWazYNfL0Qr85tMkMd/RjpPxM6OHj0CIgYcMGIK26YMJNvXDWyiH1SjadkXYJYfnDxa6nJvZq4o/tz6FtZWZ2yzvt2f5vuJnZMGoo+syaiL6zXHM0AAA==') format('woff'), 6 | url('iconfont/iconfont.ttf?t=1538186245466') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('iconfont/iconfont.svg?t=1538186245466#el') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | .el { 11 | font-family:"el" !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .el-icon-warn:before { content: "\e641"; } 19 | 20 | .el-icon-adduser:before { content: "\e8e4"; } 21 | 22 | .el-icon-warning:before { content: "\e836"; } 23 | 24 | .el-icon-duihao:before { content: "\e60b"; } 25 | 26 | .el-icon-more:before { content: "\e60a"; } 27 | 28 | .el-icon-pwd:before { content: "\e63a"; } 29 | 30 | .el-icon-add:before { content: "\e647"; } 31 | 32 | .el-icon-user:before { content: "\e67f"; } 33 | 34 | .el-icon-woman:before { content: "\e600"; } 35 | 36 | .el-icon-nan:before { content: "\e601"; } 37 | 38 | .el-icon-zy_qunzuduoren:before { content: "\e602"; } 39 | 40 | .el-icon-online:before { content: "\ea6b"; } 41 | 42 | -------------------------------------------------------------------------------- /html-web/src/components/common/InfoCard.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | {{user | name}} 13 | 14 | 15 | 16 | 账号: 17 | {{user.username}} 18 | 19 | 20 | 21 | 22 | 23 | 备注: {{user.remark}} 24 | 签名: {{user.sign}} 25 | 邮箱: {{user.email}} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 83 | -------------------------------------------------------------------------------- /java-websocket/src/main/java/com/thankjava/wchat/ws/core/ConnectionVerifyCallBack.java: -------------------------------------------------------------------------------- 1 | package com.thankjava.wchat.ws.core; 2 | 3 | import com.thankjava.toolkit.core.reflect.BeanCopierUtil; 4 | import com.thankjava.toolkit3d.core.fastjson.FastJson; 5 | import com.thankjava.wchat.bean.MsgPushContext; 6 | import com.thankjava.wchat.bean.notice.notice.OnlinePush; 7 | import com.thankjava.wchat.consts.CookieName; 8 | import com.thankjava.wchat.consts.EventType; 9 | import com.thankjava.wchat.consts.RedisKeyManager; 10 | import com.thankjava.wchat.controller.Friend; 11 | import com.thankjava.wchat.controller.Group; 12 | import com.thankjava.wchat.controller.Notice; 13 | import com.thankjava.wchat.db.entity.User; 14 | import com.thankjava.wchat.lib.websocket.entity.ConVerifyResult; 15 | import com.thankjava.wchat.lib.websocket.callback.ConnectionVerifyListener; 16 | import com.thankjava.wchat.notice.StatusChangeEventPush; 17 | import com.thankjava.wchat.util.RedisUtil; 18 | import com.thankjava.wchat.util.Utils; 19 | import com.thankjava.wchat.util.WSUtil; 20 | import com.thankjava.wchat.ws.anno.WSController; 21 | import com.thankjava.wchat.ws.anno.WSProcess; 22 | import com.thankjava.wchat.controller.Chat; 23 | import org.java_websocket.handshake.ClientHandshake; 24 | import org.slf4j.Logger; 25 | import org.slf4j.LoggerFactory; 26 | 27 | import java.lang.reflect.Method; 28 | import java.util.HashMap; 29 | import java.util.HashSet; 30 | import java.util.Map; 31 | import java.util.Set; 32 | 33 | public class ConnectionVerifyCallBack implements ConnectionVerifyListener { 34 | 35 | private static final Map processes = new HashMap<>(); 36 | private static final Map controllers = new HashMap<>(); 37 | private StatusChangeEventPush statusChangeEventPush = new StatusChangeEventPush(); 38 | 39 | static { 40 | 41 | Set controllers = new HashSet<>(); 42 | 43 | // 初始化controller 44 | controllers.add(Chat.class); 45 | controllers.add(Notice.class); 46 | controllers.add(Friend.class); 47 | controllers.add(Group.class); 48 | 49 | scanController(controllers); 50 | } 51 | 52 | static Logger logger = LoggerFactory.getLogger(ConnectionVerifyCallBack.class); 53 | 54 | 55 | /** 56 | * 扫描controller 用于将执行方法绑定到请求上下文中 57 | * 58 | * @param controllerClasses 59 | */ 60 | private static void scanController(Set controllerClasses) { 61 | StringBuilder stringBuilder; 62 | for (Class clazz : controllerClasses) { 63 | WSController ws = (WSController) clazz.getAnnotation(WSController.class); 64 | if (ws == null) continue; 65 | Method[] methods = clazz.getDeclaredMethods(); 66 | for (Method method : methods) { 67 | stringBuilder = new StringBuilder(); 68 | WSProcess wsProcess = method.getAnnotation(WSProcess.class); 69 | if (wsProcess == null) continue; 70 | stringBuilder.append("/").append(ws.path()).append("/").append(wsProcess.path()); 71 | processes.put(stringBuilder.toString(), method); 72 | try { 73 | controllers.put(stringBuilder.toString(), clazz.newInstance()); 74 | } catch (InstantiationException e) { 75 | e.printStackTrace(); 76 | } catch (IllegalAccessException e) { 77 | e.printStackTrace(); 78 | } 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * 进行连接校验和连接数据信息绑定 85 | */ 86 | @Override 87 | public ConVerifyResult doProcess(ClientHandshake handshake) { 88 | 89 | String path = handshake.getResourceDescriptor(); 90 | Method process = processes.get(path); 91 | 92 | if (process == null) { 93 | logger.debug("无效的ws请求地址 path = " + path); 94 | return new ConVerifyResult(); 95 | } 96 | 97 | // 没有cookie信息 98 | if (!handshake.hasFieldValue("Cookie")) return new ConVerifyResult(); 99 | 100 | String token = Utils.getValueForCookieStr(handshake.getFieldValue("Cookie"), CookieName.TOKEN_KEY); 101 | String uid = Utils.getValueForCookieStr(handshake.getFieldValue("Cookie"), CookieName.UID_KEY); 102 | 103 | if (token == null || uid == null) return new ConVerifyResult(); 104 | String authJson = RedisUtil.getRedisManager().get(RedisKeyManager.TOKEN_KEY(token)); 105 | if (authJson == null) return new ConVerifyResult(); 106 | 107 | User user = FastJson.toObject(authJson, User.class); 108 | if (!user.getId().equals(uid)) return new ConVerifyResult(); 109 | 110 | if (path.equals("/notice/event")) { 111 | if (WSUtil.isOnline(user.getId())) { 112 | 113 | // 当前已经在线则向目标推送强制退出 114 | statusChangeEventPush.pushForcedLogout(new MsgPushContext(EventType.forced_logout, "system", user.getId())); 115 | } else { 116 | 117 | // 向好友推送上线通知 118 | OnlinePush onlinePush = BeanCopierUtil.copy(user, OnlinePush.class); 119 | statusChangeEventPush.pushOnline(new MsgPushContext<>( 120 | EventType.friend_status_change, 121 | uid, 122 | null, 123 | onlinePush 124 | )); 125 | 126 | } 127 | } 128 | 129 | RedisUtil.getRedisManager().sadd(RedisKeyManager.ONLINE_SET_KEY(), uid); 130 | 131 | return new ConVerifyResult(uid, path, user, process, controllers.get(path)); 132 | } 133 | 134 | } 135 | --------------------------------------------------------------------------------
{{item.nickname || item.username}}
{{user.nickname}}
{{user.sign}}