서버, 관리 및 클라이언트를 포함한 인스턴트 메시징 응용 프로그램
배치 및 출시되었으며 고객 및 관리 측면을 경험하는 데 오신 것을 환영합니다.
기본 역할과 권한을 마음대로 변경하지 마십시오. 조금 사랑스럽고 문명화되지 않은 이름을 구성하지 마십시오.
Egg Framework 사용, IM 서비스의 서버 측면
모바일 인터넷 개발 이후 WeChat이 이끄는 인스턴트 메시징 서비스는 우리 삶의 모든 구석에 통합되어 회사의 일부 비즈니스에서 중요한 역할을 수행했습니다. 우리 회사는 인스턴트 메시징 서비스를 사용했지만 많은 맞춤형 요구를 달성 할 수 없습니다. 따라서 내부적으로 맞춤형 요구를 충족시키는 인스턴트 메시징 마이크로 서비스를 개발하기로 결정했습니다.
그 당시 백엔드가 사람들이 부족했기 때문에 socket.io 프레임 워크가 사용되었습니다. 몇 가지 예를 읽은 후, 나는 전체 플랫폼에서 사용하는 것이 정말 편리하다고 생각했습니다. 따라서이 마이크로 서비스는 프론트 엔드 팀에서 구현되었으며 현재 효과는 꽤 좋습니다.
커뮤니티는 현재이 분야에서 덜 간단한 콘텐츠를 가지고 있습니다 (공개 대화방은 하나뿐입니다). 또한 비즈니스 개발 프로세스 중에 PM이되는 것은 매우 불편하므로 고유 한 비즈니스 제품에서 벗어나 서버, 관리 및 고객을 포함한 간단한 기능과 회사 비즈니스를 혼합하지 않는 간단하고 완전한 IM 응용 프로그램을 실현하고 싶습니다. 고객의 모방 대상은 WeChat입니다. 왜냐하면 나는 그것에 매우 익숙하고 제품에 대해 너무 많이 생각할 필요가 없기 때문입니다. 또한, 그것을 시도하는 사람들은 그것에 매우 익숙하고 너무 많은 의사 소통 비용이 필요하지 않습니다.
완전한 인스턴트 메시징 서비스 세트를 개발하려면 다음 부분이 필요합니다.
엔터프라이즈 수준의 프레임 워크 및 응용 프로그램에서 태어났습니다
내가 Alibaba의 Egg.js 프레임 워크를 지원으로 선택한 이유는 그들이 대규모 구현과 보안에서 좋은 일을했기 때문입니다. 그들이 Nest를 선택하지 않은 이유는 socket.io 통합하는 것이 번거 롭기 때문입니다. ORM은 속편을 사용하고 데이터베이스는 MySQL입니다. 나는 전에 그것을 사용 했으므로 시작하기가 덜 어렵습니다.
상자 외부 프론트 엔드/디자인 솔루션
Ant Design Pro를 선택한 이유는 Vue Family Bucket에 익숙하기 때문입니다. 나는이 기회를 통해 전체 React 생태계의 개발 과정에 익숙해지고 중국의 두 가지 주요 개발 프레임 워크의 필수 차이와 다른 경로를 느끼고 싶습니다. Ant Design Pro는 몇 년 동안 출시되었으며 실제로 중소 기업에 효율성이 향상되었으며, 이는 제 요구에 적합합니다.
vue.js가 개발 한 표준 도구
@vue/cli를 사용하여 IM 서비스 클라이언트, 모바일 H5 프로젝트 및 UI 프레임 워크를 구축하고 UI 프레임 워크를 사용하여 오픈 소스 구성 요소 vue-page-stack을 통합하고 Huang의 더 나은 스크롤을 통합하여 IM의 기본 기능을 실현합니다.
프론트 엔지니어로서 대부분의 일상적인 작업은 엔티티 관계에 대해 생각할 필요가 없습니다. 그러나 실제 경험을 바탕으로 엔티티 관계를 이해하면 비즈니스 모델을 더 잘 이해하는 데 도움이 될 수 있습니다. 제품 및 비즈니스 이해의 개선은 우리에게 큰 도움이됩니다. 수요 검토 중에 많은 비논리적 인 측면을 찾을 수 있습니다 (제품 관리자에 대해 다시 불평 해야하는 이유). 이 시점에서 제안 할 수 있다면, 우리는 후속 프로세스에서 반복적 인 개발을 피하기 위해 주도권을 잡을 것이며, 동시에, 우리는 대립이 아닌 제품 측면에서 학생들과 비교적 좋은 상호 작용을 형성 할 수 있습니다. 다음은 더 중요한 엔티티 관계입니다.
위 그림에서, 우리는 사용자가 전체 관계 다이어그램의 핵심임을 알 수 있습니다. 다음은 다양한 엔티티 간의 관계입니다.
다음은 세션, 역할 및 권한에 대한 자세한 소개입니다.
인스턴트 메시징 응용 프로그램을 완료 할 때 가장 먼저 고려해야 할 것은 WeChat의 대화 창인 대화입니다. 대화와 메시지, 사용자 및 그룹의 관계에 대해 생각하고 마지막으로 다음과 같은 기본 관계를 형성하려면 많은 노력이 필요합니다.
다시 말해, 사용자는 세션과 직접적인 관계가 없으며 사용자의 해당 단일 채팅 및 그룹 채팅을 통해서만 세션을 얻을 수 있습니다. 이것은 다음과 같은 이점을 가질 수 있습니다.
유연하고 보편적이며 편리한 권한 관리 시스템을 설계하기 위해이 시스템은 RBAC (역할 기반 액세스 제어) 제어를 채택하여 이후 확장의 편의를 위해 일반 "사용자 역할 권한"플랫폼을 설계합니다.
RBAC (역할 기반 액세스 제어)는 사용자가 역할을 통한 권한과 관련된 것을 말합니다. 즉, 사용자는 몇 가지 역할을 수행하며 각 역할에는 몇 가지 권한이 있습니다 (물론 상충되는 역할과 권한을 결합하지 마십시오). 이러한 방식으로, "사용자 역할 투과성"의 승인 모델이 구성됩니다. 이 모델에는 일반적으로 사용자와 역할 사이, 역할과 권한 사이에 다량의 관계가 있습니다.
이 시스템에는 관리자, 일반 사용자, 금지 사용자 및 금지 된 사용자의 기본 역할이 있으며 다른 권한이 다른 역할에 할당됩니다. 따라서 관리 및 음성과 같은 인터페이스 라우팅을위한 통합 인증 (미들웨어를 통해) 처리를 수행해야합니다. 특정 방법과 방법은 백엔드 프로젝트에서 자세히 설명됩니다. 이 시스템은 일시적으로 사전 정의 된 역할 및 권한을 사용합니다. 향후 확장하려면 역할과 권한을 편집 할 수 있습니다.
WeChat의 관리 측면을 본 적이 없지만 관리자가 사용자 역할 및 권한을 구성하고 그룹 상태를 편집 할 수 있다고 상상할 수 있습니다.
등록 및 로그인 한 후 친구를 추가하고 그룹에 가입하여 개인 기본 정보 및 프로세스 응용 프로그램을 수정할 수 있습니다.
로그인 할 수 없습니다
예를 들어 : 이제 온라인으로 테스트 해야하는 새로운 버전의 개인 센터가 있습니다. 먼저, 새로운 역할 "Test Personal Center"를 작성한 다음 해당 권한을 역할에 할당하십시오. 그런 다음 일반 사용자를 그룹화하고 일부 사람들을 선택 하여이 역할을 구성하여 테스트 할 수 있습니다.
인스턴트 메시징 서비스의 핵심 커뮤니케이션 원칙에 대해 이야기 해 봅시다. 일반적인 HTTP 서비스와 마찬가지로 통신을위한 서버 및 클라이언트가 있지만 자세한 프로토콜 및 처리 방법은 다릅니다.
역사적 이유로 인해 주류 HTTP 프로토콜은 이제 무국적 프로토콜입니다 (HTTP2는 당분간 널리 사용되지 않습니다). 일반적으로 클라이언트는 요청을 적극적으로 시작한 다음 이에 응답합니다. 따라서 서버가 정보를 클라이언트에게 푸시한다는 것을 알기 위해서는 프론트 엔드가 백엔드를 적극적으로 투표해야합니다. 이 방법은 비효율적이며 오류가 발생하기 쉽습니다. 이것은 실제로 우리의 관리 홈페이지에서 이전 (5 초)에서 수행됩니다.
서버 측이 정보를 적극적으로 푸시 해야하는 이러한 요구를 달성하기 위해 HTML5는 단일 TCP 연결, 즉 WebSocket에서 전체 이중 통신을위한 프로토콜을 제공하기 시작했습니다. WebSocket을 사용하면 클라이언트와 서버 간의 데이터 교환이 더 쉬워 서 서버가 데이터를 클라이언트에 적극적으로 푸시 할 수 있습니다. WebSocket 프로토콜은 2008 년에 태어나 2011 년에 국제 표준이되었습니다. 현재 대부분의 브라우저는 이미 지원했습니다.
WebSocket의 사용은 매우 간단합니다.
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
WebSocket 프로토콜을 사용하여 서버에는 정보를 적극적으로 푸시하는 고급 무기가 있습니다. 그렇다면 구식 및 새로운 브라우저와 호환되는 방법이 있습니까? 실제로 많은 사람들이 이것을 생각하고 대답은 socket.io 입니다.
socket.io socket.io WebSocket 인터페이스를 추가로 캡슐화하고 통신을 위해 기존 브라우저에서 폴링 사용으로 자동 전환하여 통합 된 인터페이스 세트를 형성하여 개발 부담을 크게 줄일 수 있습니다. 주로 다음과 같은 장점이 있습니다.
이것은 socket.io 홈페이지입니다
가장 빠르고 가장 신뢰할 수있는 인스턴트 메시징 엔진 (가장 빠르고 가장 신뢰할 수있는 실시간 엔진을 특징으로)
사용하기가 정말 쉽습니다.
var io = require('socket.io')(80);
var cfg = require('./config.json');
var tw = require('node-tweet-stream')(cfg);
tw.track('socket.io');
tw.track('javascript');
tw.on('tweet', function(tweet){
io.emit('tweet', tweet);
});
서버 측 프로젝트에서 몇 가지 중요한 점에 중점을 두겠습니다. 대부분의 콘텐츠는 계란 공식 웹 사이트에서 볼 필요가 있습니다.
스캐 폴딩 npm init egg --type=simple 서버 프로젝트 초기화, MySQL 설치 (My IS 버전 8.0), 속편에 필요한 데이터베이스 링크 비밀번호를 구성하면 시작할 수 있습니다.
// 目录结构说明
├── package.json // 项目信息
├── app.js // 启动文件,其中有一些钩子函数
├── app
| ├── router.js // 路由
│ ├── controller
│ ├── service
│ ├── middleware // 中间件
│ ├── model // 实体模型
│ └── io // socket.io 相关
│ ├── controller
│ └── middleware // io独有的中间件
├── config // 配置文件
| ├── plugin.js // 插件配置文件
| └── config.default.js // 默认的配置文件
├── logs // server运行期间产生的log文件
└── public // 静态文件和上传文件目录
라우터는 주로 요청 URL과 실행 조치, 즉 app/router 수행하는 컨트롤러 간의 서신을 설명하는 데 사용됩니다.
app/middleware/auth.js 에 있습니다.app/middleware/admin.js 에있는 모든 관리 인터페이스에서 관리 권한 수표가 추가되었습니다. 이 시스템은 관리자 및 일반 커뮤니케이션 사용자와 다른 역할을 갖기 때문에 관리 및 커뮤니케이션 인터페이스 라우팅에는 통합 인증 처리가 필요합니다.
예를 들어, 관리 측면 경로 /v1/admin/... 이 시리즈의 모든 경로에 관리자 인증을 추가하고 싶습니다. 현재 미들웨어를 사용하여 인증 할 수 있습니다. 다음은 관리 라우터에서 미들웨어를 사용하는 특정 예입니다.
// middware
module.exports = () => {
return async function admin(ctx, next) {
let { session } = ctx;
// 判断admin权限
if (session.user && session.user.rights.some(right => right.keyName === 'admin')) {
await next();
} else {
ctx.redirect('/login');
}
};
};
// router
const admin = app.middleware.admin();
router.get('/api/v1/admin/rights', admin, controller.v1.admin.rightsIndex);
후속+MySQL 조합은 사용되었으며 계란에는 또한 관련 플러그인이 있습니다. 후속작은 노드 환경에서 사용되는 ORM으로 Postgres, MySQL, MariaDB, SQLite 및 Microsoft SQL Server를 지원하는 ORM입니다. 모델과 모델 사이의 직접적인 관계를 먼저 정의해야합니다. 관계가 확립 된 후에는 몇 가지 사전 설정 방법을 사용할 수 있습니다.
모델의 기본 정보는 처리하기가 더 쉽습니다. 주의를 기울여야 할 것은 엔티티, 즉 직원 간의 관계 설계입니다. 다음은 사용자의 관계 설명입니다
// User.js
module.exports = app => {
const { STRING } = app.Sequelize;
const User = app.model.define('user', {
provider: {
type: STRING
},
username: {
type: STRING,
unique: 'username'
},
password: {
type: STRING
}
});
User.associate = function() {
// One-To-One associations
app.model.User.hasOne(app.model.UserInfo);
// One-To-Many associations
app.model.User.hasMany(app.model.Apply);
// Many-To-Many associations
app.model.User.belongsToMany(app.model.Group, { through: 'user_group' });
app.model.User.belongsToMany(app.model.Role, { through: 'user_role' });
};
return User;
};
예를 들어, 사용자와 userInfo의 관계는 일대일 관계입니다. 정의 된 후 새 사용자를 생성 할 때 user.setUserInfo(userInfo) 사용할 수 있습니다. 이 사용자의 기본 정보를 얻으려면 user.getUserInfo() 사용할 수도 있습니다.
사용자와 신청 간의 관계는 일대일입니다. 즉, 사용자는 여러 응용 프로그램에 해당 할 수 있으며 현재 친구 및 그룹 응용 프로그램 만 적용됩니다.
응용 프로그램을 추가 할 때는 user.addApply(apply) 사용할 수 있으며이를 얻을 때 다음과 같이 사용할 수 있습니다.
const result = await ctx.model.Apply.findAndCountAll({
where: {
userId: ctx.session.user.id,
hasHandled: false
}
});
사용자와 그룹 간의 관계는 다수입니다. 즉, 사용자는 여러 그룹에 해당 할 수 있으며 그룹은 여러 사용자에게 해당 할 수 있습니다. 이런 식으로, 속편은이 관계를 달성하기 위해 중간 테이블 user_group을 설정합니다.
나는 보통 이것을 사용합니다 :
group.addUser(user); // 建立群组和用户的关系
user.getGroups(); // 获取用户的群组信息
계란은 Egg-Socket.io 플러그인을 제공합니다. Egg-Socket.io를 설치 한 후 Config/Plugin.js에서 플러그인을 열어야합니다. IO에는 자체 미들웨어 및 컨트롤러가 있습니다.
IO의 경로는 일반 HTTP 요청의 경로와 다릅니다. 여기의 경로는 미들웨어로 처리 할 수 없으므로 (성공하지 못했습니다) 컨트롤러의 금지 처리를 처리했습니다.
// 加入群
io.of('/').route('/v1/im/join', app.io.controller.im.join);
// 发送消息
io.of('/').route('/v1/im/new-message', app.io.controller.im.newMessage);
// 查询消息
io.of('/').route('/v1/im/get-messages', app.io.controller.im.getMessages);
참고 : 그룹과 친구 관계를 방으로 간주하여 (즉, 세션) ROMM에 직접 메시지를 보낼 수 있으며 내부의 모든 사람이받을 수 있습니다.
두 개의 기본 미들웨어가 있습니다. 하나는 연결 및 연결을 끊을 때 호출 된 연결 미들웨어이며 로그인 상태 및 프로세스 비즈니스 로직을 확인하는 데 사용됩니다. 다른 하나는 메시지가 전송 될 때마다 호출되는 패킷 미들웨어이며 로그를 인쇄하는 데 사용됩니다.
통화 권한이 사전 설정되므로 컨트롤러에서 처리됩니다.
// 对用户发言的权限进行判断
if (!ctx.session.user.rights.some(right => right.keyName === 'speak')) {
return;
}
채팅은 단일 채팅 및 그룹 채팅으로 나뉩니다. 채팅 정보에는 일시적으로 일반 텍스트, 사진, 비디오 및 포지셔닝 메시지가 포함되어 있으며 비즈니스에 따라 주문 또는 제품으로 확장 할 수 있습니다.
메시지의 구조 설계는 여러 타사 서비스의 설계를 나타냅니다. 또한이 프로젝트 자체의 상황과 함께 조정되었습니다. 마음대로 확장 할 수 있으며 다음과 같은 설명이 이루어집니다.
const Message = app.model.define('message', {
/**
* 消息类型:
* 0:单聊
* 1:群聊
*/
type: {
type: STRING
},
// 消息体
body: {
type: JSON
},
fromId: { type: INTEGER },
toId: { type: INTEGER }
});
본체는 메시지 본문을 저장하며 JSON을 사용하여 다른 메시지 형식을 저장하는 데 사용됩니다.
// 文本消息
{
"type": "txt",
"msg":"哈哈哈" //消息内容
}
// 图片消息
{
"type": "img",
"url": "http://nimtest.nos.netease.com/cbc500e8-e19c-4b0f-834b-c32d4dc1075e",
"ext":"jpg",
"w":360, //宽
"h":480, //高
"size": 388245
}
// 视频消息
{
"type": 'video',
"url": "http://nimtest.nos.netease.com/cbc500e8-e19c-4b0f-834b-c32d4dc1075e",
"ext":"mp4",
"w":360, //宽
"h":480, //高
"size": 388245
}
// 地理位置消息
{
"type": "loc",
"title":"中国 浙江省 杭州市 网商路 599号", //地理位置title
"lng":120.1908686708565, // 经度
"lat":30.18704515647036 // 纬度
}
현재 바이두의 토큰을 업데이트하는 것은 현재 하나뿐입니다. 여기에서는 매우 간단합니다. 공식 문서를 참조하십시오.
지능형 대화 사용자 정의 및 서비스 플랫폼 장치
이것은 매우 흥미 롭습니다. https://ai.baidu.com/ 에서 새 로봇을 만들고 해당 기술을 추가 할 수 있습니다. 나는 여기서 채팅 중이며 Smart Q & A 등이 있습니다.
시작하지 않으려면 ctx.service.baidu.getToken();
우선 구성 파일에서 구성해야합니다. 여기서 파일 크기와 크로스 사이트 iOS 비디오 파일 형식을 제한했습니다.
config.multipart = {
mode: 'file',
fileSize: '3mb',
fileExtensions: ['.mov']
};
Unified 인터페이스는 파일 업로드를 처리하는 데 사용됩니다. 핵심 문제는 파일 쓰기이며 파일은 프론트 엔드에서 전송 된 파일 목록입니다.
for (const file of ctx.request.files) {
// 生成文件路径,注意upload文件路径需要存在
const filePath = `./public/upload/${
Date.now() + Math.floor(Math.random() * 100000).toString() + '.' + file.filename.split('.').pop()
}`;
const reader = fs.createReadStream(file.filepath); // 创建可读流
const upStream = fs.createWriteStream(filePath); // 创建可写流
reader.pipe(upStream); // 可读流通过管道写入可写流
data.push({
url: filePath.slice(1)
});
}
저장 /public/upload/ 서버 디렉토리에 있습니다. 이 디렉토리에는 정적 파일 구성이 필요합니다.
config.static = {
prefix: '/public/',
dir: path.join(appInfo.baseDir, 'public')
};
이 장의 공식 계란 문서는 당신을 죽이는 것입니다. 예가 없습니다. 소스 코드를 읽어야합니다. 너무 끔찍합니다. 무슨 일이 일어나고 있는지 알아 내기 전에 오랫동안 공부했습니다.
계정 비밀번호 로그인을보다 자유롭게 제어하려면 계정 비밀번호 로그인에 여권을 사용하지 않지만 일반 인터페이스 인증 및 세션을 사용합니다.
다음은 타사 플랫폼을 사용하여 로그인하는 프로세스에 대한 자세한 설명입니다 (Github를 선택했습니다).
플러그인 열기 :
// config/plugin.js
module.exports.passport = {
enable: true,
package: 'egg-passport',
};
module.exports.passportGithub = {
enable: true,
package: 'egg-passport-github',
};
// config.default.js
config.passportGithub = {
key: 'your_clientID',
secret: 'your_clientSecret',
callbackURL: 'http://localhost:3000/api/v1/passport/github/callback' // 注意这里非常的关键,这里需要和你在github上面设置的Authorization callback URL一致
};
this.app.passport.verify(verify);
const github = app.passport.authenticate('github', { successRedirect: '/' }); // successRedirect就是最后校验完毕后前端会跳转的路由,我这里直接跳转到主页了
router.get('/v1/passport/github', github);
router.get('/v1/passport/github/callback', github);
/v1/passport/github 클릭 하고이 응용 프로그램에 대한 Github 권한을 시작하십시오. 성공 후 Github는 http://localhost:3000/v1/passport/github/callback?code=12313123123 으로 이동합니다. Githubpassport 플러그인은 Github에 대한 사용자의 정보를 얻을 것입니다. 자세한 정보를 얻은 후 app/passport/verify.js 의 사용자 정보를 확인하고 자체 플랫폼의 사용자 정보와 연결하고 세션에 값을 할당해야합니다. // verify.js
module.exports = async (ctx, githubUser) => {
const { service } = ctx;
const { provider, name, photo, displayName } = githubUser;
ctx.logger.info('githubUser', { provider, name, photo, displayName });
let user = await ctx.model.User.findOne({
where: {
username: name
}
});
if (!user) {
user = await ctx.model.User.create({
provider,
username: name
});
const userInfo = await ctx.model.UserInfo.create({
nickname: displayName,
photo
});
const role = await ctx.model.Role.findOne({
where: {
keyName: 'user'
}
});
user.setUserInfo(userInfo);
user.addRole(role);
await user.save();
}
const { rights, roles } = await service.user.getUserAttribute(user.id);
// 权限判断
if (!rights.some(item => item.keyName === 'login')) {
ctx.body = {
statusCode: '1',
errorMessage: '不具备登录权限'
};
return;
}
ctx.session.user = {
id: user.id,
roles,
rights
};
return githubUser;
};
위의 코드에주의하십시오. 첫 번째 승인 인 경우 사용자가 생성됩니다. 두 번째 승인 인 경우 사용자가 생성되었습니다.
시스템이 배포되거나 실행되면 일부 데이터와 테이블을 사전 설정해야하며 코드는 app.js 및 app/service/startup.js 에 있습니다.
논리는 프로젝트가 시작된 후 모델을 사용하여 테이블 구조를 데이터베이스에 동기화 한 다음 몇 가지 기본 데이터를 만들기 시작한다는 것입니다.
위의 것을 완료 한 후 초기 데이터가 완료되었으며 정상적으로 작동 할 수 있습니다.
Alibaba Cloud에서 구입 한 도메인 이름 인 Tencent Cloud의 서버 센터 (Centos)를 구입하여 설치 노드 (12.18.2), Nginx 및 MySQL8.0을 구입하여 Centos에서 직접 시작했습니다. 프론트 엔드는 리버스 프록시에 nginx를 사용합니다. 서버 리소스가 제한되어 있기 때문에 자동화 도구 Jenkins 및 Docker가 없으므로 업데이트 할 때 일부 수동 작업으로 이어집니다.