در این سوال میخوایم یک پیام رسان ساده با استفاده از گولنگ پیاده سازی کنیم. فقط این پیام رسان برای آن دسته از افرادی است که خیلی حوصله فضای مجازی را ندارند و دوست ندارند هربار که وارد برنامه میشوند با تعداد زیادی از پیام‌های جدید رو به رو شوند و به جایش دوست دارند هربار که خودشان خواستند سرور یکی از پیام‌های جدیدشان را (در صورت وجود) برایشان نمایش دهد.

جزئیات پروژه

پروژه‌ی اولیه را از این لینک دانلود کنید. ساختار فایل‌های پروژه به صورت زیر است:

.
├── go.mod
├── go.sum
├── message
│   └── message.go
├── server
│   └── server.go
├── test
│   └── sample_test.go
└── user
    └── user.go
Plain text

در هر کدام از فایل‌های go توابع و استراکت‌هایی وجود دارد که باید کاملشان کنید که در ادامه به آنها خواهیم پرداخت.

آن‌چه باید پیاده‌سازی کنید

در این پروژه هر کاربر میتواند به کاربر یا گروهی دیگر پیام ارسال کند. فقط برای ارسال پیام از سمت سرور به گیرنده چندتا شرط وجود دارد.

  • اول اینکه هر کاربر فقط باید یک پیام دریافت نشده داشته باشد. یعنی اگر چند کاربر به یک کاربر پیام فرستاده باشند، تا وقتی کاربر گیرنده آن یک پیام را نخواند بقیه پیام‌ها به او ارسال نخواهند شد.
  • همچنین تا کاربر پیام را خواند نباید پیام بعدی برای او فرستاده شود. به این گونه که برای هر پیام یک زمانی در نظر گرفته میشود (مثلا n میلی‌ثانیه) و سرور هر n میلی‌ثانیه یک بار چک میکند که اگر گیرنده پیام خوانده نشده نداشته باشد، پیام جدید را به او ارسال میکند.
    • هر کاربر ممکن است subscribed باشد یا نباشد که با توجه به این ویژگی عدد n برای هر پیام از طرف او مشخص میشود. به این صورت که اگر subscribed باشد سرور هر 110 میلی ثانیه یک بار سعی میکند پیام او را به گیرنده بفرستد و اگر subscribed نباشد هر 290 میلی ثانیه یک بار. دقت کنید که این تایمر از زمان فرستادن هر پیام شروع میشود (یعنی هر پیام تایمر جدا دارد). همچنین هر بار 1 میلی ثانیه از مدت زمان تایمر کم میشود. یعنی اگر پیامی اولین بار پس از 110 میلی ثانیه سعی شود فرستاده شود و ناموفق بوده باشد (گیرنده پیام خوانده نشده از قبل داشته)، دفعه بعد پس از 109 میلی ثانیه دوباره سرور تلاش میکند ارسالش کند.

فایل message.go

در این فایل استراکت حاوی اطلاعات پیام‌ها وجود دارد که نباید آن‌ را تغییر بدهید.

type Message struct {
    FromID                   int
    ToID                     int
    Text                     string
    MsInQueueBeforeBeingSent time.Duration
}
Go
message.go

فیلد اول نشان دهنده آیدی کاربر فرستنده است. فیلد دوم آیدی کاربر گیرنده. فیلد سوم متن پیام است. فیلد چهارم نشان دهنده زمانی است که طول کشیده است تا پیام به سمت گیرنده ارسال شود (به میلی ثانیه). درباره فیلد آخر دقت کنید که هربار سرور سعی میکند پیام را به گیرنده ارسال کند باید آپدیت شود (یعنی با زمان سیستم نباید کار کنید).

فایل server.go

در این فایل استراکت‌ زیر وجود دارد که باید خودتان کامل کنید.

type Server struct {

}
Go
server.go

همچنین تابع زیر را داریم که لیست کاربر و گروه‌ها را میگیرد و سرور را برمیگرداند.

func SetupServer(users []*user.User, groups []*user.Group) *Server {

}
Go
server.go

در ادامه تابع زیر را داریم که باید لیستی از پیام‌های موجود در صف سرور در آن لحظه را برگرداند. خروجی این تابع، ترتیب‌دار است. به این ترتیب پیامی که اول ارسال شده است باید اول لیست باشد.

func (s *Server) GetQueuedMessages() []message.Message {

}
Go
server.go

توجه: برای سرور امکان add و remove گروه‌ها و کاربر‌ها را نداریم. به این معنی که همان اطلاعاتی که موقع setup داشتیم، تا آخر برنامه معتبر است.

فایل user.go

این فایل شامل دو دسته از استراکت‌ها و توابع است که باید آنها را پیاده سازی کنید. قسمت اول مربوط به کاربرها است.

type User struct {

}

func NewUser(id int, sub bool) *User {

}

func (u *User) SendMessage(receiver int, text string) error {

}

func (u *User) ReceiveMessage() message.Message {

}
Go
user.go

تابع NewUser دو مقدار به عنوان ورودی میگیرد. اولی آیدی کاربر است. دومی یک بولین است که نشان میدهد این کاربر جز کاربرهای subscribed است یا خیر. در آخر یوزر ساخته شده را برمیگرداند.

تابع SendMessage نیز دو مقدار به عنوان ورودی میگیرد. اولی آیدی کاربر گیرنده و دومی متن پیام است. همچنین اگر آیدی گیرنده در سرور وجود نداشت، این تابع باید یک ارور برگرداند (متن ارور مهم نیست).

تابع ReceiveMessage: این تابع در صورت وجود پیام جدید برای کاربر، آن را برمیگرداند. در غیر این صورت یک استراکت خالی برمی‌گرداند

قسمت دوم مربوط به گروه‌ها است (که کاربرها میتوانند عضو آن‌ها شوند)

type Group struct {

}

func NewGroup(id int, users []*User) *Group {

}

func (g *Group) AddUser(u *User) string {

}

func (g *Group) RemoveUser(u *User) string {

}
Go
user.go

تابع NewGroup: آیدی گروه و لیست کاربرهای موجود در این گروه را میگیرد. تضمین میشود آیدی هیچ گروهی با هیچ کاربری یکسان نیست.

تابع AddUser: یک کاربر را میگیرد و به گروه اضافه میکند. اگر کاربر در گروه نبود و با موفقیت به گروه اضافه شد رشته بالا و اگر از قبل عضو گروه بود رشته پایین را برمیگرداند.

User <ID> added to group <ID>
User <ID> already in group <ID>
Plain text

تابع RemoveUser: این تابع کاربر را از گروه حذف میکند. اگر کاربر در گروه بود و حذف شد پیام بالا و اگر اصلا عضو گروه نبود پیام پایین را برمیگرداند.

User <ID> removed from group <ID>
User <ID> isn't in group <ID>
Plain text

درباره فرستادن پیام به گروه‌ها:

  • اگر کاربر آیدی یک گروه را به عنوان گیرنده انتخاب کرده باشد، پیام باید به اعضای همه گروه فرستاده شود.
  • اگر کاربر فرستنده عضو گروه بود، پیام نباید به خودش فرستاده شود.
  • اعضای گروه هنگام ارسال پیام مهم هستند. یعنی اگر پیامی ارسال شد و سپس عضوی حذف شد پیام باید به او ارسال شود و اگر پیامی فرستاده شد و سپس کاربری به گروه اضافه شد آن پیام نباید برایش ارسال شود.
  • هر پیام که به گروه فرستاده میشود فقط یک بار به صف پیام‌ها اضافه میشود، نه به تعداد کاربرها (یعنی فقط همان پیامی که ToID آن برابر آیدی گروه است) و هروقت به همه اعضای گروه ارسال شد از صف حذف میشود. همچنین هربار که این پیام به یکی از اعضای گروه ارسال میشود، فیلد MsInQueueBeforeBeingSent این پیام در صف آپدیت میشود. به این معنی که اگر پیام برای بعضی از اعضا در 110 میلی ثانیه ارسال شد، این فیلد 110 میشود و... بدیهی است اگر پیام در یک زمان به همه اعضا ارسال شود از queue حذف میشود و دیگر به این پیام دسترسی نخواهیم داشت و نیازی به آپدیت این فیلد نیست.

تضمین‌ها

  • کاربری وسط برنامه از سرور حذف یا به سرور اضافه نمیشود.
  • آیدی کاربرها و گروه‌ها هم پوشانی ندارد.
  • توابع پیاده سازی شده روی کاربرها و گروه‌ها فقط روی کاربرها و گروه‌های موجود در سرور صدا زده میشود. فقط در هنگام SendMessage لازم است چک کنید آیدی گیرنده در سرور وجود دارد یا خیر.

آن‌چه باید آپلود کنید

یک فایل زیپ آپلود کنید که وقتی آن را باز می‌کنیم دایرکتوری‌های user که شامل user.go است و server که شامل server.go است را میبینیم.


ارسال پاسخ برای این سؤال
فایلی انتخاب نشده است.