Aplikasi pesan instan, termasuk server, manajemen dan klien
Itu telah digunakan dan diluncurkan, selamat datang untuk mengalami klien dan sisi manajemen
Harap jangan mengubah peran default dan izin sesuka hati. Tolong rasakan sedikit cinta dan jangan membuat beberapa nama yang tidak beradab.
Menggunakan Kerangka Telur, Sisi Server Layanan IM
Sejak pengembangan internet seluler, layanan pesan instan yang dipimpin oleh WeChat telah diintegrasikan ke dalam setiap sudut kehidupan kita dan juga memainkan peran penting dalam beberapa bisnis perusahaan. Perusahaan kami menggunakan layanan pesan instan, tetapi banyak kebutuhan khusus tidak dapat dicapai. Oleh karena itu, diputuskan untuk mengembangkan layanan microsaging microsaging instan yang memenuhi kebutuhan khusus secara internal.
Kerangka kerja socket.io digunakan karena backend kekurangan orang pada waktu itu. Setelah membaca beberapa contoh, saya pikir itu benar -benar nyaman untuk digunakan dan didukung di seluruh platform. Oleh karena itu, layanan mikro ini diimplementasikan dalam tim front-end, dan efeknya saat ini cukup bagus.
Komunitas saat ini memiliki konten yang lebih sedikit atau terlalu sederhana di bidang ini (hanya ada satu ruang obrolan publik). Selain itu, sangat tidak nyaman menjadi PM selama proses pengembangan bisnis, jadi saya ingin melepaskan diri dari beberapa hal bisnis yang unik dan mewujudkan aplikasi IM sederhana dan lengkap yang tidak mencampur bisnis perusahaan dengan fungsi sederhana, termasuk server, manajemen, dan klien. Objek imitasi klien adalah WeChat, karena saya sangat akrab dengan itu dan tidak perlu terlalu banyak memikirkan produk. Selain itu, orang -orang yang mencobanya sangat akrab dengannya dan tidak memerlukan terlalu banyak biaya komunikasi.
Untuk mengembangkan satu set lengkap layanan pesan instan, bagian -bagian berikut diperlukan:
Lahir untuk Kerangka dan Aplikasi Tingkat Perusahaan
Alasan saya memilih kerangka kerja alibaba. Alasan mengapa mereka tidak memilih Nest adalah karena merepotkan untuk mengintegrasikan socket.io . ORM menggunakan sekuelisasi, dan database adalah MySQL. Saya telah menggunakannya sebelumnya, jadi kurang sulit untuk memulai.
Solusi front-end/desain out-of-the-box
Alasan untuk memilih Ant Design Pro adalah karena saya terbiasa dengan Vue Family Bucket. Saya ingin mengambil kesempatan ini untuk membiasakan diri dengan proses pengembangan seluruh ekosistem bereaksi dan merasakan perbedaan penting dan jalur yang berbeda dari dua kerangka kerja pembangunan utama di Cina. Ant Design Pro telah dirilis selama beberapa tahun, dan memang telah membawa peningkatan efisiensi ke perusahaan kecil dan menengah, yang tepat untuk kebutuhan saya.
Alat standar yang dikembangkan oleh vue.js
Gunakan @vue/cli untuk membangun klien layanan im, proyek h5 seluler, dan kerangka kerja UI menggunakan yozan vant, yang mengintegrasikan komponen open source saya vue-page-stack dan mr. huang lebih baik-scroll untuk mewujudkan fungsi dasar IM
Sebagai insinyur front-end, sebagian besar tugas harian tidak memerlukan pemikiran tentang hubungan entitas. Namun, berdasarkan pengalaman saya yang sebenarnya, pemahaman hubungan entitas dapat membantu kami lebih memahami model bisnis. Peningkatan pemahaman produk dan bisnis sangat membantu kami. Kami dapat menemukan banyak aspek yang tidak logis selama tinjauan permintaan (mengapa kami harus mengeluh tentang manajer produk lagi). Jika kita dapat mengusulkannya saat ini, kita akan mengambil inisiatif untuk menghindari pengembangan berulang dalam proses selanjutnya, dan pada saat yang sama, kita dapat membentuk interaksi yang relatif baik dengan siswa di sisi produk (bukan konfrontasi). Berikut adalah beberapa hubungan entitas yang lebih penting:
Dari gambar di atas, kita dapat melihat bahwa pengguna adalah inti dari seluruh diagram hubungan. Berikut ini adalah hubungan antara berbagai entitas:
Berikut ini adalah pengantar rinci untuk sesi, peran, dan izin:
Saat menyelesaikan aplikasi pesan instan, hal pertama yang perlu Anda pertimbangkan adalah percakapan, yang merupakan jendela percakapan di wechat kami. Dibutuhkan banyak upaya untuk memikirkan hubungan antara percakapan dan pesan, pengguna, dan grup, dan akhirnya membentuk hubungan dasar berikut:
Dengan kata lain, pengguna tidak memiliki hubungan langsung dengan sesi, dan hanya dapat memperoleh sesi melalui obrolan tunggal dan obrolan grup yang sesuai pengguna. Ini dapat memiliki manfaat berikut:
Untuk merancang sistem manajemen izin yang fleksibel, universal, dan nyaman, sistem ini mengadopsi kontrol RBAC (Kontrol Akses Berbasis Peran) untuk merancang platform "izin peran pengguna" umum untuk kenyamanan ekspansi kemudian.
RBAC (Kontrol Akses Berbasis Peran) mengacu pada pengguna yang dikaitkan dengan izin melalui peran. Artinya, pengguna memiliki beberapa peran, dan setiap peran memiliki beberapa izin (tentu saja, tidak menggabungkan peran dan izin yang saling bertentangan bersama -sama). Dengan cara ini, model otorisasi "permintaan-peran-pengguna" dibangun. Dalam model ini, umumnya ada hubungan banyak-ke-banyak antara pengguna dan peran dan antara peran dan izin.
Sistem ini memiliki peran default administrator, pengguna umum, pengguna yang dilarang dan pengguna yang dilarang, dan izin yang berbeda ditetapkan untuk peran yang berbeda. Oleh karena itu, perlu melakukan pemrosesan otentikasi terpadu (melalui middleware) untuk perutean antarmuka seperti manajemen dan ucapan. Metode dan metode spesifik akan dijelaskan secara rinci dalam proyek back-end. Sistem ini sementara menggunakan metode peran dan izin yang telah ditentukan. Jika Anda ingin berkembang di masa depan, Anda dapat mengedit peran dan izin.
Saya belum pernah melihat sisi manajemen WeChat, tetapi Anda dapat membayangkan bahwa administrator dapat mengonfigurasi peran dan izin pengguna dan mengedit status grup:
Setelah mendaftar dan masuk, Anda dapat menambahkan teman dan bergabung dengan grup secara normal, memodifikasi informasi dasar pribadi dan proses proses.
Tidak dapat masuk
Misalnya: Sekarang ada versi baru dari pusat pribadi yang perlu diuji secara online. Pertama, buat peran baru "uji pusat pribadi", dan kemudian tetapkan izin yang sesuai untuk peran tersebut; Kemudian grup pengguna biasa dan pilih beberapa orang untuk mengonfigurasi peran ini, sehingga Anda dapat mengujinya.
Mari kita bicara tentang prinsip komunikasi inti dari layanan pesan instan. Seperti layanan HTTP umum, ada server dan klien untuk komunikasi, tetapi protokol terperinci dan metode pemrosesan berbeda.
Karena alasan historis, protokol HTTP utama sekarang menjadi protokol tanpa kewarganegaraan (HTTP2 tidak banyak digunakan untuk saat ini). Secara umum, klien memulai permintaan secara aktif dan kemudian meresponsnya. Jadi untuk menyadari bahwa server mendorong informasi ke klien, front-end perlu secara aktif polling back-end. Metode ini tidak efisien dan rawan kesalahan. Ini memang dilakukan di beranda manajemen kami sebelumnya (5s sekali).
Untuk mencapai kebutuhan sisi server ini untuk secara aktif mendorong informasi, HTML5 mulai memberikan protokol untuk komunikasi dupleks penuh pada satu koneksi TCP, yaitu Websocket. WebSocket membuat pertukaran data antara klien dan server lebih mudah, memungkinkan server untuk secara aktif mendorong data ke klien. Protokol Websocket lahir pada tahun 2008 dan menjadi standar internasional pada tahun 2011. Saat ini, sebagian besar browser telah mendukungnya.
Penggunaan Websocket cukup sederhana:
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.");
};
Dengan protokol Websocket, server memiliki senjata canggih untuk secara aktif mendorong informasi. Jadi adakah cara untuk kompatibel dengan browser lama dan baru? Faktanya, banyak orang memikirkan hal ini, dan jawabannya adalah socket.io
socket.io socket.io selanjutnya merangkum antarmuka WebSocket , dan secara otomatis dapat beralih ke penggunaan jajak pendapat di browser lama untuk komunikasi (pengguna kami tidak akan memahami), membentuk serangkaian antarmuka terpadu, sangat mengurangi beban pengembangan. Ini terutama memiliki keunggulan berikut:
Ini adalah beranda socket.io
Mesin pesan instan tercepat dan paling andal (menampilkan mesin real-time tercepat dan paling andal)
Sangat mudah digunakan:
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);
});
Mari kita fokus pada beberapa poin penting dalam proyek sisi server. Sebagian besar konten perlu dilihat di situs web resmi telur.
Gunakan scaffolding npm init egg --type=simple untuk menginisialisasi proyek server, instal mysql (my is versi 8.0), konfigurasikan kata sandi tautan basis data yang diperlukan untuk sekejap, dll., Dan Anda dapat memulainya
// 目录结构说明
├── 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 // 静态文件和上传文件目录
Router terutama digunakan untuk menggambarkan korespondensi antara URL permintaan dan pengontrol yang secara khusus melakukan tindakan eksekusi, yaitu, app/router
app/middleware/auth.jsapp/middleware/admin.js Karena sistem ini memiliki peran yang berbeda sebagai administrator dan pengguna komunikasi umum, pemrosesan otentikasi terpadu diperlukan untuk rute antarmuka manajemen dan komunikasi.
Misalnya, rute sisi manajemen /v1/admin/... , saya ingin menambahkan otentikasi administrator ke semua rute dalam seri ini. Saat ini, Anda dapat menggunakan middleware untuk mengotentikasi. Berikut ini adalah contoh spesifik menggunakan middleware di router 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);
Kombinasi sekuelisasi+mySQL yang digunakan, dan telur juga memiliki plug-in yang terkait. Sequelize adalah ORM yang digunakan dalam lingkungan simpul, Postgres mendukung, MySQL, MariADB, SQLite dan Microsoft SQL Server, yang cukup nyaman untuk digunakan. Anda perlu mendefinisikan hubungan langsung antara model dan model terlebih dahulu. Setelah hubungan terbentuk, Anda dapat menggunakan beberapa metode yang telah ditetapkan.
Informasi dasar model lebih mudah diproses. Yang perlu diperhatikan adalah desain hubungan antara entitas, yaitu, associate. Berikut ini adalah deskripsi hubungan pengguna
// 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;
};
Misalnya, hubungan antara pengguna dan UserInfo adalah hubungan satu-ke-satu. Setelah didefinisikan, kami dapat menggunakan user.setUserInfo(userInfo) saat membuat pengguna baru. Ketika Anda ingin mendapatkan informasi dasar dari pengguna ini, Anda juga dapat menggunakan user.getUserInfo()
Hubungan antara pengguna dan Apply adalah satu-ke-banyak, yaitu pengguna dapat sesuai dengan banyak aplikasi, dan saat ini hanya teman dan aplikasi grup yang diterapkan:
Saat menambahkan aplikasi, Anda dapat menggunakan user.addApply(apply) , dan saat mendapatkannya, Anda dapat menggunakannya sebagai berikut:
const result = await ctx.model.Apply.findAndCountAll({
where: {
userId: ctx.session.user.id,
hasHandled: false
}
});
Hubungan antara pengguna dan grup banyak-ke-banyak, yaitu pengguna dapat sesuai dengan banyak grup, dan grup dapat sesuai dengan banyak pengguna. Dengan cara ini, sequelize akan membangun tabel menengah User_group untuk mencapai hubungan ini.
Saya biasanya menggunakan ini:
group.addUser(user); // 建立群组和用户的关系
user.getGroups(); // 获取用户的群组信息
Telur menyediakan plug-in telur-socket.io. Anda perlu membuka plug-in di config/plugin.js setelah memasang egg-socket.io. IO memiliki middleware dan pengontrolnya sendiri.
Rute IO berbeda dari permintaan HTTP umum. Perhatikan bahwa rute di sini tidak dapat diproses dengan middleware (saya tidak berhasil), jadi saya menangani pemrosesan larangan di pengontrol.
// 加入群
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);
Catatan: Saya menganggap hubungan kelompok dan teman sebagai ruangan (yaitu, sesi), sehingga saya dapat mengirim pesan langsung ke Romm, dan semua orang di dalam dapat menerimanya.
Ada dua middleware default, satu adalah middleware koneksi yang disebut saat menghubungkan dan memutuskan, yang digunakan untuk memverifikasi status login dan memproses logika bisnis; Yang lainnya adalah middleware paket yang dipanggil setiap kali pesan dikirim, yang digunakan untuk mencetak log
Karena izin panggilan telah ditetapkan, ia diproses dalam pengontrol
// 对用户发言的权限进行判断
if (!ctx.session.user.rights.some(right => right.keyName === 'speak')) {
return;
}
Obrolan dibagi menjadi obrolan tunggal dan obrolan grup. Informasi obrolan untuk sementara mencakup teks umum, gambar, video dan pesan penentuan posisi, yang dapat diperluas menjadi pesanan atau produk sesuai dengan bisnis.
Desain struktur pesan mengacu pada desain beberapa layanan pihak ketiga, dan juga telah disesuaikan dalam kombinasi dengan situasi proyek ini sendiri. Itu dapat diperluas sesuka hati, dan penjelasan berikut dibuat:
const Message = app.model.define('message', {
/**
* 消息类型:
* 0:单聊
* 1:群聊
*/
type: {
type: STRING
},
// 消息体
body: {
type: JSON
},
fromId: { type: INTEGER },
toId: { type: INTEGER }
});
Tubuh menyimpan badan pesan, yang digunakan untuk menyimpan format pesan yang berbeda menggunakan 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 // 纬度
}
Hanya ada satu saat ini, yaitu memperbarui tanda Baidu. Di sini cukup sederhana, lihat saja dokumentasi resmi.
Unit Platform Kustomisasi dan Layanan Intelligent Dialog
Ini cukup menarik. Anda dapat membuat robot baru dan menambahkan keterampilan yang sesuai di https://ai.baidu.com/ . Saya mengobrol di sini, dan ada tanya jawab yang cerdas, dll.
Jika Anda tidak ingin memulai, Anda dapat menghapus ctx.service.baidu.getToken();
Pertama -tama, Anda perlu mengonfigurasinya di file konfigurasi. Saya telah membatasi ukuran file di sini dan format file video iOS lintas situs:
config.multipart = {
mode: 'file',
fileSize: '3mb',
fileExtensions: ['.mov']
};
Antarmuka terpadu digunakan untuk menangani unggahan file. Masalah inti adalah penulisan file, dan file adalah daftar file yang dikirim dari ujung depan
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)
});
}
Saya menyimpan /public/upload/ di direktori server. Direktori ini membutuhkan konfigurasi file statis:
config.static = {
prefix: '/public/',
dir: path.join(appInfo.baseDir, 'public')
};
Dokumen telur resmi bab ini adalah untuk membunuh Anda, tidak ada contoh, Anda harus membaca kode sumber. Sangat mengerikan. Saya telah mempelajarinya sejak lama sebelum saya tahu apa yang terjadi.
Karena saya ingin mengontrol login kata sandi akun lebih bebas, saya tidak menggunakan paspor untuk login kata sandi akun, tetapi menggunakan otentikasi dan sesi antarmuka biasa.
Berikut ini adalah deskripsi terperinci tentang proses pencatatan dalam menggunakan platform pihak ketiga (saya memilih github):
Buka 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 di ujung depan dan inisiasi otorisasi github untuk aplikasi ini. Setelah sukses, GitHub akan pergi ke http://localhost:3000/v1/passport/github/callback?code=12313123123 . Plug-in GithubPassport kami akan mendapatkan informasi pengguna di GitHub. Setelah mendapatkan informasi terperinci, kita perlu memverifikasi informasi pengguna di app/passport/verify.js , dan mengaitkannya dengan informasi pengguna dari platform kami sendiri, dan juga menetapkan nilai ke sesi tersebut // 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;
};
Perhatikan kode di atas. Jika itu adalah otorisasi pertama, pengguna akan dibuat. Jika itu adalah otorisasi kedua, pengguna telah dibuat.
Saat sistem digunakan atau dijalankan, beberapa data dan tabel perlu ditetapkan, dan kodenya ada di app.js dan app/service/startup.js
Logikanya adalah bahwa setelah proyek dimulai, gunakan model untuk menyinkronkan struktur tabel ke dalam database, dan kemudian mulai membuat beberapa data dasar:
Setelah menyelesaikan hal di atas, data awal telah selesai dan dapat beroperasi secara normal
Saya membeli server CentOS di Tencent Cloud, nama domain yang saya beli di Alibaba Cloud, menginstal node (12.18.2), Nginx dan MySQL8.0, dan mulai langsung di CentOS. Front-end menggunakan nginx untuk proxy terbalik. Karena sumber daya server yang terbatas, tidak ada alat otomatisasi Jenkins dan Docker, yang mengarah ke beberapa operasi manual saat memperbarui.