مفهوم match making چیه؟ بیاید فرض کنیم شما وب سایت پونیشا رو راه انداختید. حالا میخواید به کسایی که ثبت سفارش پروژه میکنن پیشنهاد بدی که از بین این برنامه نویسایی که برای اجرا کردن پروژه ی تو درخواست فرستادن کدوم خوب تر هست. یعنی بیای بر اساس یه سری پارامتر به کسی که پروژه رو ثبت کرده بگی بر اساس مثلا خوش قولی و کیفیت کار فلان برنامه نویس امتیاز ۹۰ گرفته و فلانی امتیاز ۶۷ و ...
البته اصل این مفهوم رو من تا الان بیشتر توی game و business دیدم. مثالش میتونه یه چیزی مثل پکیج matchmaker و tiny-matchmaking. یا این ریپوزیتوری گیت هاب باشه.
پس در کل میشه گفت matchmaking میاد بر اساس پارامتر هایی مگه دو تا چیز چقدر با هم برابر هستن. چیزی که مشخصه اینه که امتیاز هر فرد توی اون پارامتر ها از قبل مشخص شده است. البته ممکنه که امتیاز number نباشه و boolean باشه.
برای این تعیین امتیاز هم الگوریتم داریم یا اینکه فرد مستقیم میاد اونو وارد میکنه. بعد از هر بار وارد شدن مقدار جدید باید مجموع/میانگین امتیاز فرد توی اون پارامتر دوباره حساب بشه. اگه قرار باشه میانگین حساب بشه باید مقادیر قبلی اون پارامتر رو داشته باشید تا بتونید میانگین بگیرید.
چیزی هم که ممکنه اتفاق بیفته اینه که شما بگی من یه حداقلی برای سیستمم تعیین میکنم، یعنی اگه کاربری امتیازش کمتر از اون شد اصلا توی نتیجه نهایی نشونش نمیدم. این ممکنه برات این مشکل رو بوجود بیاره که اصلا هیچ کاربری قبول نشه. پس بای به فکر بود که توی این شرایط چکار کرد.
چند دقیقه اول این فیلم رو ببینید.
خب قبل از هر چیزی بیاید مرحله به مرحله کار رو پیش ببریم. اول یه کالکشن ایجاد بکنیم تا روش بتونیم تست هامون رو اجرا کنیم. یه فایل به اسم point.schema.js ایجاد کنید :
// @ts-check const { Schema } = require('mongoose'); const pointSchema = new Schema({ type: { type: String, enum: ['Point'], default: 'Point', }, coordinates: { type: [Number], required: true, }, }); module.exports = { pointSchema };
ما type های مختلفی توی GeoJSON میتونیم داشته باشیم؛ Point که اشاره به یه نقطه روی نقشه داره، Polygon که یه محدوده رو در بر میگیره، LineString که توضیح دادنش با مثال ساده تره: فرض کن میخوای track جاهایی رو که رفتی نگهداری، در این صورت میتونی از این نوع استفاده بکنی. نوع های MultiPoint و MultiPolygon و MultiLineString هم داریم که چند تا نقطه یا polygon یا LineString رو نگه میدارن.
و یه فایل test.schema.js
// @ts-check const { Schema, model } = require('mongoose'); const { pointSchema } = require('./point.schema'); const COLLECTION_NAME = 'test'; const schemaName = new Schema( { location: { type: pointSchema, index: '2dsphere', }, }, { timestamps: true, collection: COLLECTION_NAME, }, ); const SchemaModel = model(COLLECTION_NAME, schemaName);
کاری که میخوایم بکنیم اینه که روی location یه matchmaking درست کنیم. خب مثل همیشه بیاید اول با کلیت آشنا بشیم. برای اینکه بتونید کوئری زیر رو اجرا بکنید باید اول روی فیلد GeoJSON خودتون index گذاری بکنید. به همین خاطر ما فیلد location رو index گذاری کردیم.
چند نوع ایندکس گذاری داریم:
برای جزئیات بیشتر این ویدئو رو ببینید:
حالا بیاید سراغ کوئری بریم:
const result = await SchemaModel.find({ location: { $near: { $maxDistance: 100000, $geometry: { type: 'Point', coordinates: [36.3, 59.41], }, }, }, });
اگرم چیزی به عنوان خروجی برگشت داده نشد اون عددای coordinates و maxDistance رو عوض کنید. این کوئری رکورد هایی رو بر میگردونه که near (نزدیک) geometry داده شده هستن.
حالا این maxDistance چی هست؟
You cannot combine the $near operator, which requires a special geospatial index, with a query operator or command that requires another special index. For example you cannot combine $near with the $text query.
مثلا اگه میخوای or بزنی و بگی چند تا عملگر near داشته باشی باید چند تا کوئری مجزا بزنی و نتیجه رو با هم ترکیب بکنی. نکته بعدی اینه که near باید توی root کوئری باشه و نمیتونی اونو بزاری توی level های پایین تر. همون طوری که توی این لینک توضیح داده شده.
خب ولی کاری که من اینجا میخوام بکنم اینه که یه کاربر داریم که ما ۴ تا لوکیشن مختلف ازش داریم:
حالا میخوایم بگیم که کدوم کاربرا، یکی از این لوکیشن هاشون به یه نقطه خاص، نزدیک هست. اون کاربرا باید اطلاعاتشون برگشت داده بشه. پس با operator (عملگر) near مشکل حل نمیشه، یا اینکه باید ۴ تا کوئری بزنیم. پس میریم سراغ عملگر geoWithin.
از این عملگر توی aggregate ها استفاده میکنیم. این عملگر داکیومنت هایی رو بر میگردونه که location اونها توی یه محدوده خاص هست. حالا این محدوده رو میتونی با polygon یا ... تعیین بکنی. ما عملگر های shape مختلفی توی MongoDB داریم:
این دو تا عملگر آخری برامون یه دایره میکشن. عملگری که در ادامه مخیوام توضیح بدم عملگر centerSphere هست. این عملگر یه آرایه میگیره. این آرایه دوتا عضو بیشتر قبول نمیکنه. ایندکس صفر باید coordinates مرکز دایره باشه و ایندکس دوم باید مشخص کننده شعاع دایره باشه.
این شعاع به مایل هست مثلا ۱۰ مایل. البته نکته ای که هست اینه که باید این ۱۰ مایل رو تقسیم بر شعاع استوایی زمین بکنید. شعاع استوایی زمین به مایل 3963.2 هست. پس در نهایت یه همچین چیزی داریم:
همون طوری که میبینید توی کوئری بالا گفتم تا شعاع یک کیلومتر بگرده و هر کی خونش یا دفتر کارش توی دایره ای که مرکز اون coordinates ای که دادم هست رو برام برگردونه.