Aplicativos de mensagens instantâneas, incluindo servidor, gerenciamento e cliente
Ele foi implantado e lançado, bem -vindo a experimentar o lado do cliente e do gerenciamento
Por favor, não altere a função e as permissões padrão à vontade. Por favor, sinta -se um pouco amoroso e não invente alguns nomes não civilizados.
Usando a estrutura do ovo, o lado do servidor do serviço IM
Desde o desenvolvimento da Internet móvel, os serviços de mensagens instantâneas lideradas pelo WeChat foram integradas em todos os cantos de nossas vidas e também desempenham um papel importante em alguns dos negócios da empresa. Nossa empresa usou serviços de mensagens instantâneas, mas muitas necessidades personalizadas não podem ser alcançadas. Portanto, decidiu -se desenvolver um microsserviço de mensagens instantâneas que atenda às necessidades personalizadas internamente.
A estrutura socket.io foi usada porque o back -end estava com falta de pessoas na época. Depois de ler alguns exemplos, achei realmente conveniente usar e suportado em toda a plataforma. Portanto, esse microsserviço foi implementado na equipe de front-end e o efeito é atualmente muito bom.
Atualmente, a comunidade tem conteúdo menos ou muito simples nessa área (há apenas uma sala de bate -papo pública). Além disso, é muito desconfortável ser PM durante o processo de desenvolvimento de negócios, por isso quero me afastar de algumas coisas de negócios exclusivas e realizar um aplicativo IM simples e completo que não mistura negócios da empresa com funções simples, incluindo servidor, gerenciamento e cliente. O objetivo da imitação do cliente é o WeChat, porque estou muito familiarizado com ele e não preciso pensar muito sobre o produto. Além disso, as pessoas que experimentam estão muito familiarizadas com isso e não exigem muitos custos de comunicação.
Para desenvolver um conjunto completo de serviços de mensagens instantâneas, são necessárias as seguintes peças:
Nascido para estruturas e aplicativos em nível corporativo
A razão pela qual escolhi a estrutura do ovo.js da Alibaba como suporte é que eles fizeram um bom trabalho na implementação e segurança em larga escala. A razão pela qual eles não escolheram o Nest é que é problemático integrar socket.io . ORM usa sequelizar e o banco de dados é MySQL. Eu já usei antes, por isso é menos difícil começar.
Solução de front-end/design pronta para uso
O motivo da escolha do Ant Design Pro é que estou familiarizado com o Vue Family Bucket. Quero aproveitar esta oportunidade para me familiarizar com o processo de desenvolvimento de todo o ecossistema do React e sentir as diferenças essenciais e diferentes caminhos das duas principais estruturas de desenvolvimento na China. O Ant Design Pro foi lançado há vários anos e, de fato, trouxe melhorias de eficiência para pequenas e médias empresas, o que é adequado para minhas necessidades.
Ferramentas padrão desenvolvidas por vue.js
Use @Vue/CLI para construir um cliente de serviço IM, um projeto Mobile H5 e a estrutura da interface do usuário usa Youzan Vant, que integra meu componente de código aberto Vue-Page Stack e melhor rolagem do Sr. Huang para realizar as funções básicas do IM
Como engenheiro de front-end, a maioria das tarefas diárias não exige pensar nos relacionamentos de entidade. No entanto, com base na minha experiência real, entender os relacionamentos da entidade pode nos ajudar a entender melhor os modelos de negócios. A melhoria do entendimento do produto e dos negócios é de grande ajuda para nós. Podemos encontrar muitos aspectos ilógicos durante a revisão da demanda (por que temos que reclamar do gerente do produto novamente). Se pudermos propor isso neste momento, tomaremos a iniciativa de evitar o desenvolvimento repetido no processo subsequente e, ao mesmo tempo, podemos formar uma interação relativamente boa com os alunos do lado do produto (em vez de confronto). Aqui estão alguns dos relacionamentos mais importantes da entidade:
A partir da figura acima, podemos ver que o usuário é o núcleo de todo o diagrama de relacionamento. O seguinte é a relação entre várias entidades:
A seguir, é apresentada uma introdução detalhada às sessões, funções e permissões:
Ao concluir um aplicativo de mensagens instantâneas, a primeira coisa que você precisa considerar é a conversa, que é a janela de conversa em nosso WeChat. É preciso muito esforço para pensar na relação entre conversas e mensagens, usuários e grupos e, finalmente, formar o seguinte relacionamento básico:
Em outras palavras, o usuário não tem relacionamento direto com a sessão e só pode obter a sessão através do bate -papo único correspondente do usuário e do bate -papo em grupo. Isso pode ter os seguintes benefícios:
Para projetar um sistema de gerenciamento de permissão flexível, universal e conveniente, este sistema adota o controle RBAC (controle de acesso baseado em função) para projetar uma plataforma geral de "Permissão de função do usuário", por conveniência da expansão posterior.
RBAC (controle de acesso baseado em função) refere-se ao usuário associado a permissões por meio de funções. Ou seja, um usuário tem várias funções e cada função tem várias permissões (é claro, não combine funções e permissões conflitantes). Dessa forma, é construído um modelo de autorização de "percorrer-se-role". Nesse modelo, geralmente há um relacionamento de muitos para muitos entre usuários e funções e entre funções e permissões.
Este sistema possui as funções padrão do administrador, usuário geral, usuário proibido e usuário banido e diferentes permissões são atribuídas a diferentes funções. Portanto, é necessário executar o processamento de autenticação unificada (através do middleware) para roteamento de interface, como gerenciamento e fala. Os métodos e métodos específicos serão explicados em detalhes no projeto de back-end. Este sistema usa temporariamente o método de funções e permissões predefinidas. Se você deseja expandir no futuro, pode editar funções e permissões.
Eu nunca vi o lado da administração do WeChat, mas você pode imaginar que os administradores podem configurar funções e permissões do usuário e editar o status do grupo:
Depois de se registrar e fazer login, você pode adicionar amigos e participar de grupos normalmente, modificar informações básicas e aplicativos de processo pessoais.
Incapaz de fazer login
Por exemplo: agora existe uma nova versão do centro pessoal que precisa ser testada online. Primeiro, crie uma nova função "Centro pessoal de teste" e atribua permissões correspondentes à função; Em seguida, agrupe os usuários comuns e selecione algumas pessoas para configurar essa função, para que você possa testá -la.
Vamos falar sobre o princípio principal de comunicação dos serviços de mensagens instantâneas. Como os serviços HTTP em geral, existe um servidor e cliente para comunicação, mas os métodos detalhados de protocolo e processamento são diferentes.
Por razões históricas, o protocolo HTTP convencional é agora um protocolo sem estado (HTTP2 não é amplamente utilizado por enquanto). Geralmente, o cliente inicia a solicitação ativamente e depois responde a ela. Portanto, para perceber que o servidor empurra as informações para o cliente, o front-end precisa pesquisar ativamente o back-end. Este método é ineficiente e propenso a erros. Isso é realmente feito em nossa página inicial de gerenciamento antes (5s uma vez).
Para alcançar essa necessidade de o lado do servidor pressionar ativamente as informações, o HTML5 começou a fornecer um protocolo para comunicação completa de duplex em uma única conexão TCP, como WebSocket. O WebSocket facilita a troca de dados entre clientes e servidores, permitindo que o servidor empurre ativamente dados para os clientes. O WebSocket Protocol nasceu em 2008 e se tornou um padrão internacional em 2011. Atualmente, a maioria dos navegadores já o apoiou.
O uso do WebSocket é bastante simples:
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.");
};
Com o Protocolo Websocket, o servidor possui armas avançadas para impulsionar ativamente as informações. Então, existe alguma maneira de ser compatível com navegadores antigos e novos? De fato, muitas pessoas pensam nisso, e a resposta é socket.io
socket.io socket.io encapsula ainda mais a interface WebSocket e pode alternar automaticamente para o uso da pesquisa em navegadores antigos para comunicação (nossos usuários não perceberão), formando um conjunto unificado de interfaces, reduzindo bastante o ônus do desenvolvimento. Ele tem principalmente as seguintes vantagens:
Este é o Socket.io Homepage
O mecanismo de mensagens instantâneo mais rápido e confiável (com o mecanismo em tempo real mais rápido e confiável)
É realmente fácil de usar:
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);
});
Vamos nos concentrar em vários pontos importantes no projeto do lado do servidor. A maior parte do conteúdo precisa ser visualizada no site oficial do ovo.
Use andaimes npm init egg --type=simple para inicializar o projeto do servidor, instalar mysql (my is versão 8.0), configure a senha do link do banco de dados necessária para sequela
// 目录结构说明
├── 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 // 静态文件和上传文件目录
O roteador é usado principalmente para descrever a correspondência entre o URL da solicitação e o controlador que adota especificamente a ação de execução, ou seja, app/router
app/middleware/auth.jsapp/middleware/admin.js Como esse sistema possui funções diferentes como administradores e usuários de comunicação geral, é necessário o processamento unificado de autenticação para o roteamento da interface de gerenciamento e comunicação.
Por exemplo, a rota lateral de gerenciamento /v1/admin/... , quero adicionar autenticação de administrador a todas as rotas nesta série. Neste momento, você pode usar o middleware para autenticar. A seguir, é apresentado um exemplo específico de uso do middleware no roteador de administrador.
// 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);
A combinação Sequelize+MySQL usada e o ovo também possuem plug-ins relacionados a sequelizar. O Sequelize é um ORM usado no ambiente do nó, suportando o PostGres, MySQL, MariaDB, SQLite e Microsoft SQL Server, o que é bastante conveniente de usar. Você precisa definir o relacionamento direto entre o modelo e o modelo primeiro. Depois que o relacionamento for estabelecido, você pode usar alguns métodos predefinidos.
As informações básicas do modelo são mais fáceis de processar. O que precisa ser dado a atenção é o design de relacionamento entre entidades, ou seja, associado. A seguir é a descrição do relacionamento do usuário
// 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;
};
Por exemplo, o relacionamento entre o usuário e o usuário é um relacionamento individual. Depois de definido, podemos usar user.setUserInfo(userInfo) ao criar um novo usuário. Quando você deseja obter as informações básicas deste usuário, você também pode usar user.getUserInfo()
A relação entre usuário e aplicação é um para muitos, ou seja, um usuário pode corresponder a vários aplicativos e, atualmente, apenas amigos e aplicativos de grupo são aplicados:
Ao adicionar um aplicativo, você pode usar user.addApply(apply) e, ao obtê -lo, pode usá -lo da seguinte forma:
const result = await ctx.model.Apply.findAndCountAll({
where: {
userId: ctx.session.user.id,
hasHandled: false
}
});
A relação entre usuário e grupo é muitos para muitos, ou seja, um usuário pode corresponder a vários grupos e um grupo pode corresponder a vários usuários. Dessa forma, a Sequelize estabelecerá uma tabela intermediária user_group para alcançar esse relacionamento.
Eu costumo usar isso:
group.addUser(user); // 建立群组和用户的关系
user.getGroups(); // 获取用户的群组信息
Ovo fornece o plug-in de ovo-soquete.io. Você precisa abrir o plug-in no config/plugin.js após a instalação do Egg-Socket.io. O IO tem seu próprio middleware e controlador.
A rota de IO é diferente da das solicitações gerais de HTTP. Observe que a rota aqui não pode ser processada com o middleware (eu não consegui), então lidei com o processamento de proibição no controlador.
// 加入群
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);
NOTA: Considero que o relacionamento do grupo e do amigo como uma sala (ou seja, uma sessão), para que eu possa enviar mensagens diretamente para o Romm, e todos os interior podem recebê -los.
Existem dois middleware padrão, um é o middleware de conexão chamado ao conectar e desconectar, que é usado para verificar o status de login e processar a lógica de negócios; o outro é o middleware do pacote chamado toda vez que uma mensagem for enviada, que é usada para imprimir o log
Como a permissão de chamada é predefinida, ela é processada no controlador
// 对用户发言的权限进行判断
if (!ctx.session.user.rights.some(right => right.keyName === 'speak')) {
return;
}
Os bate -papos são divididos em bate -papos únicos e bate -papos em grupo. As informações do bate -papo incluem temporariamente texto geral, fotos, vídeos e mensagens de posicionamento, que podem ser expandidas para pedidos ou produtos de acordo com o negócio.
O design da estrutura da mensagem refere-se ao design de vários serviços de terceiros e também foi ajustado em combinação com a situação deste projeto. Pode ser expandido à vontade, e a seguinte explicação é feita:
const Message = app.model.define('message', {
/**
* 消息类型:
* 0:单聊
* 1:群聊
*/
type: {
type: STRING
},
// 消息体
body: {
type: JSON
},
fromId: { type: INTEGER },
toId: { type: INTEGER }
});
O corpo armazena o corpo da mensagem, que é usado para armazenar diferentes formatos de mensagem usando 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 // 纬度
}
Há apenas um no momento, que é atualizar o token do Baidu. É bastante simples aqui, basta consultar a documentação oficial.
Unidade de plataforma de personalização e serviço de diálogo inteligente
Isso é bastante interessante. Você pode criar um novo robô e adicionar habilidades correspondentes em https://ai.baidu.com/ . Estou conversando aqui, e há perguntas e respostas inteligentes, etc.
Se você não deseja iniciar, poderá excluir ctx.service.baidu.getToken();
Primeiro de tudo, você precisa configurá -lo no arquivo de configuração. Eu limitei o tamanho do arquivo aqui e o formato de arquivo de vídeo iOS entre sites:
config.multipart = {
mode: 'file',
fileSize: '3mb',
fileExtensions: ['.mov']
};
Uma interface unificada é usada para lidar com uploads de arquivo. O problema principal é a gravação de arquivos e os arquivos são a lista de arquivos transmitida do front -end
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)
});
}
Eu armazio /public/upload/ no diretório do servidor. Este diretório requer configuração de arquivo estático:
config.static = {
prefix: '/public/',
dir: path.join(appInfo.baseDir, 'public')
};
O documento oficial do ovo deste capítulo é matá -lo, não há nada de exemplos, você deve ler o código -fonte. É tão terrível. Eu o estudei há muito tempo antes de descobrir o que está acontecendo.
Como quero controlar o login da senha da conta mais livremente, não uso o passaporte para o login da senha da conta, mas uso a autenticação e sessão da interface ordinária.
A seguir, é apresentada uma descrição detalhada do processo de login usando uma plataforma de terceiros (eu escolhi o GitHub):
Abra o plug-in:
// 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 no front -end e inicie a autorização do GitHub para este aplicativo. Após o sucesso, o Github irá para http://localhost:3000/v1/passport/github/callback?code=12313123123 . Nosso plug-in GithubPassport obterá as informações do usuário no GitHub. Depois de obter as informações detalhadas, precisamos verificar as informações do usuário no app/passport/verify.js e associá -las às informações do usuário de nossa própria plataforma e também atribuir um valor à sessão // 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;
};
Preste atenção ao código acima. Se for a primeira autorização, o usuário será criado. Se for a segunda autorização, o usuário foi criado.
Quando o sistema é implantado ou executado, alguns dados e tabelas precisam ser predefinidos e os códigos estão no app.js e app/service/startup.js
A lógica é que, após o início do projeto, use o modelo para sincronizar a estrutura da tabela no banco de dados e começar a criar alguns dados básicos:
Depois de concluir o acima, os dados iniciais foram concluídos e podem operar normalmente
Comprei o servidor centros em Tencent Cloud, o nome de domínio que comprei no Alibaba Cloud, o nó instalado (12.18.2), Nginx e MySQL8.0 e comecei diretamente no CentOS. O front-end usa o nginx para proxy reverso. Devido aos recursos limitados do servidor, não há ferramentas de automação Jenkins e Docker, o que leva a algumas operações manuais ao atualizar.