Создайте полноценное приложение для обмена мгновенными сообщениями (2)

Node.js
Создайте полноценное приложение для обмена мгновенными сообщениями (2)

Служба приложений для обмена мгновенными сообщениями, весь пакет включает в себяСервер,сторона управленияиклиент, Добро пожаловать в службу поддержки Star и просмотрите исходный код.

Теперь развернуто в Интернете, добро пожаловать в опытклиентисторона управления

Давайте продолжим завершать полный сервис обмена мгновенными сообщениями. В этой статье основное внимание уделяется нескольким важным моментам в проекте на стороне сервера, которые я считаю важными. Большая часть контента должна отправиться на мой склад.исходный коди яйцоОфициальный сайтПроверять.

Подробности на стороне сервера

использовать строительные лесаnpm init egg --type=simpleИнициализируйте серверный проект, установите mysql (у меня версия 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

  1. Маршрутизация использует номер версии v1, что удобно для будущих обновлений.Относительно просто использовать restful напрямую для общих дополнений, удалений и изменений.
  2. В дополнение к интерфейсам входа и регистрации ко всем остальным http-интерфейсам добавлена ​​проверка сеанса для проверки статуса входа.app/middleware/auth.js
  3. Добавлена ​​проверка прав администратора в интерфейсе всех терминалов управления, расположенных по адресу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);

Связанные с базой данных

Используемая комбинация extensionize+mysql, у яйца также есть плагины, связанные с секвенированием,sequelizeЭто ORM, используемая в среде Node, поддерживает Postgres, MySQL, MariaDB, SQLite и Microsoft SQL Server и очень удобна в использовании. Необходимо определить непосредственную связь между моделью и моделью.После установления связи можно использовать некоторые предустановленные методы.

модель объекта модель

Основная информация модели относительно проста в обращении.На что необходимо обратить внимание, так это на структуру отношений между сущностями, а именно на ассоциации, ниже приводится описание отношений пользователя

// 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 и Apply — один ко многим, то есть пользователь может соответствовать нескольким собственным приложениям.В настоящее время существуют только приложения друзей и групповые приложения:

При добавлении приложения вы можете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(); // 获取用户的群组信息

Пункты к сведению

  1. Все операции продолжения основаны на Promise, и большую часть времени используется await для ожидания.
  2. После изменения атрибута экземпляра модели необходимо сохранить
  3. Когда нам нужно объединить данные модели и вернуть их во внешний интерфейс, нам нужно преобразовать их в данные с помощью get({plain: true}), а затем снова склеить их, например, при получении списка сеансов.

socketio

egg предоставляет плагин egg-socket.io, вам нужно открыть плагин в config/plugin.js после установки egg-socket.io, у io есть собственное промежуточное ПО и контроллер

сокетио-маршрутизация

Маршрутизация io отличается от общего http запроса.Обратите внимание, что middleware обработку здесь нельзя добавить к роутингу (у меня не получилось), поэтому обработка бана обрабатывается в контроллере.

// 加入群
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);

Примечание: я расцениваю групповые и дружеские отношения как комнату (то есть разговор), так что я могу отправить сообщение прямо в эту комнату, и все в ней могут его получить.

промежуточное ПО для сокетио

Существует два промежуточных ПО по умолчанию, одно промежуточное ПО подключения, которое вызывается при подключении и отключении, здесь используется для проверки статуса входа в систему и бизнес-логики обработки; другое промежуточное ПО для пакетов вызывается каждый раз при отправке сообщения, здесь используется для печати журнала

Из-за предустановленного разрешения отключения звука оно обрабатывается в контроллере.

// 对用户发言的权限进行判断
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            // 纬度
}

задача на время

В настоящее время есть только один способ — обновить токен baidu, что здесь довольно просто, просто обратитесь к официальной документации.

бот чат

Платформа настройки интеллектуального диалога и обслуживания UNIT

Это все еще довольно интересно, вы можетеhttps://ai.baidu.com/Создавайте новых роботов и добавляйте соответствующие навыки

  1. Создайте нового бота, управляйте навыками бота, хотя бы один
  2. Перейдите в «Список приложений» Baidu Cloud, чтобы создать и просмотреть ключ API/секретный ключ.
  3. Настройте параметры, связанные с baidu, в config.default.js, соответствующее описание интерфейса находится вздесь

Если вы не хотите начинать, вы можете удалить его в app.js и app/schedule/baidu.js.ctx.service.baidu.getToken();

загрузить файлы

Прежде всего, вам нужно настроить его в файле конфигурации.Я ограничиваю размер файла здесь, и круговой межсайтовый формат видеофайла ios:

config.multipart = {
  mode: 'file',
  fileSize: '3mb',
  fileExtensions: ['.mov']
};

Для обработки загрузки файлов используется единый интерфейс.Основная проблема заключается в записи файлов, а файлы — это список файлов, отправляемых из внешнего интерфейса.

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')
};

passport

Официальная документация по яйцам в этой главе вас убивает.Примеров нет.Вы должны прочитать исходный код.

Поскольку я хочу более свободно контролировать вход в учетную запись и пароль, я не использую паспорт для входа в учетную запись и пароль, а использую обычную аутентификацию интерфейса с помощью сеанса.

Далее подробное описание процесса входа с помощью сторонней платформы (я выбираю GitHub):

  1. существуетGitHub OAuth AppsСоздайте свое приложение и получите ключ и секрет
  2. Установите в проект egg-passport и egg-passport-github

Включите плагин:

// config/plugin.js
module.exports.passport = {
  enable: true,
  package: 'egg-passport',
};

module.exports.passportGithub = {
  enable: true,
  package: 'egg-passport-github',
};
  1. Конфигурация:
// config.default.js
config.passportGithub = {
  key: 'your_clientID',
  secret: 'your_clientSecret',
  callbackURL: 'http://localhost:3000/api/v1/passport/github/callback' // 注意这里非常的关键,这里需要和你在github上面设置的Authorization callback URL一致
};
  1. Включить паспорт в app.js
this.app.passport.verify(verify);
  1. Нам нужно настроить два маршрута запроса на получение паспорта, первый — это запрос, который мы нажали на странице входа, а второй — это callbackURL, который мы установили на предыдущем шаге, здесь в основном сторонняя платформа предоставит нам полезный код, а затем в соответствии с правилами авторизации OAuth2 для получения сведений о пользователе
const github = app.passport.authenticate('github', { successRedirect: '/' }); // successRedirect就是最后校验完毕后前端会跳转的路由,我这里直接跳转到主页了
router.get('/v1/passport/github', github);
router.get('/v1/passport/github/callback', github);
  1. В это время нажмите на переднюю часть/v1/passport/githubИнициирует github для авторизации этого приложения, после успеха github отправит 302 для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

Логика заключается в том, что после запуска проекта используйте модель для синхронизации структуры таблицы с базой данных, а затем начните создавать некоторые базовые данные:

  1. Создание новых ролей и разрешений и назначение разрешений ролям
  2. Создавайте разных пользователей и назначайте роли
  3. Дружба с некоторыми пользователями
  4. добавить приложение
  5. Создавайте группы и добавляйте людей

После выполнения вышеуказанного, даже если исходные данные заполнены, нормальная работа может быть выполнена.

развертывать

Я купил сервер centos в облаке Tencent и доменное имя, которое я купил в облаке Alibaba, установил node(12.18.2), nginx и mysql8.0, запустил непосредственно на centos и использовал nginx для обратного прокси-сервера во внешнем интерфейсе. Из-за ограниченных ресурсов сервера некоторые средства автоматизации Jenkins и Docker не используются, что приводит к некоторым ручным операциям при обновлении.

Продолжение следует, следующая статья объяснит технические трудности реализации интерфейса.