📡 AisFeedParser
📋 فهرست مطالب
نمای کلی پروژه
AisFeedParser یک سامانه با کارایی بالا برای دریافت، تجزیه و ذخیرهسازی خوراک دادههای AIS (سیستم شناسایی خودکار) است. این پروژه با زبان C# و سکوی .NET 8 توسعه یافته و از معماری تمیز (Clean Architecture) پیروی میکند.
قابلیتهای اصلی
- دریافت جملههای NMEA با پروتکلهای TCP و UDP
- تجزیه پیامهای AIS انواع ۱-۵، ۱۸-۱۹، ۲۴، ۲۷
- ذخیرهسازی لاگهای خام ساعتی برای بازپخش (Replay)
- ذخیرهسازی دادهها در SQLite (توسعه) و SQL Server (تولید)
- ارائه REST API برای پرسوجوی دادههای کشتیها
- پایش سلامت ارائهدهندگان AIS
فناوریهای بهکار رفته
| فناوری | نسخه | کاربرد |
|---|---|---|
| .NET | 8.0 | پلتفرم اجرایی |
| ASP.NET Core | 8.0 | وب API و Swagger |
| Entity Framework Core | 9.0.8 | ORM و Migrations |
| Serilog | 9.0.0 | ثبت ساختاریافته رویدادها |
| xUnit | 2.6.6 | چارچوب آزمون واحد |
| SQL Server / SQLite | — | پایگاه داده |
| Azure DevOps | — | CI/CD |
| Docker | — | ظرفسازی و استقرار |
ساختار پروژه
AisFeedParser/
├── src/
│ ├── AisFeedParser.Domain/ # موجودیتها، رمزگشا، تجزیه NMEA
│ ├── AisFeedParser.Application/ # موارد استفاده، DTOها، انتزاعها
│ ├── AisFeedParser.Infrastructure/ # ماندگاری، سرویسها، مخزنها
│ └── AisFeedParser.Api/ # کنترلرهای ASP.NET Core، میانافزارها
├── tests/
│ ├── AisFeedParser.Domain.Tests/ # آزمونهای رمزگشا
│ └── AisFeedParser.Application.Tests/
├── docs/ # مستندات فنی (فارسی و انگلیسی)
├── openspec/ # مشخصات رسمی و پیشنهادات تغییر
└── ais_temp.txt # نمونه داده NMEA
معماری سیستم
این پروژه از معماری تمیز (Clean Architecture) یا معماری لایهای به سبک DDD-lite پیروی میکند. وابستگیها از بیرونیترین لایه (API) به سمت داخلیترین لایه (Domain) جهتگیری شدهاند. هر لایه فقط به لایه داخلیتر از خود وابسته است.
نمودار معماری
🔵 لایه Presentation (API)
کنترلرهای ASP.NET Core، Swagger، میانافزارها، پیکربندی برنامه.
🟢 لایه Application
موارد استفاده (Use Cases)، DTOها، واسطهای انتزاعی، تزریق وابستگی.
🟠 لایه Domain
موجودیتهای اصلی، رمزگشای AIS، تجزیهکننده NMEA (بدون وابستگی بیرونی).
🟣 لایه Infrastructure
پیادهسازی ماندگاری، سرویسهای پسزمینه، مخزنها، Channelها.
الگوهای طراحی بهکار رفته
| الگو | مکان استفاده | توضیح |
|---|---|---|
| Pipeline / Producer-Consumer | خط لوله اصلی | Ingestor → Channel → Parser → Channel → Writer |
| BackgroundService | همه سرویسهای پسزمینه | اجرای طولانیمدت با IHostedService |
| Channel-based Backpressure | اتصال میان سرویسها | System.Threading.Channels با DropOldest |
| Strategy | انتخاب نوع Writer | SQLite vs SQL Server بر اساس پیکربندی |
| Repository | دسترسی به داده | EF + In-Memory پیادهسازیها |
| Use Case | لایه کاربرد | کلاسهای سرویس تزریقی |
| Options | پیکربندی | IOptions<T> با binding قوی |
| DI Extension Methods | هر لایه | AddXxx() برای ثبت سرویسها |
| Exponential Backoff | اتصال مجدد | تاخیر تصاعدی با jitter |
| Bulk Insert | SqlBulkWriter | SqlBulkCopy با DataTable |
لایههای معماری
لایه Presentation (API) — AisFeedParser.Api
نقطه ورود برنامه. شامل:
| فایل | مسئولیت |
|---|---|
| Program.cs | راهاندازی Serilog، اجرای Startup |
| Startup.cs | ثبت سرویسها و میانافزارها |
| Controllers/VesselsController.cs | API کشتیها: latest, data |
| Controllers/VesselProfilesController.cs | API پروفایل کشتیها |
| Controllers/AdminController.cs | API مدیریتی: reconnect, last5 |
| Controllers/StatusController.cs | API وضعیت: dashboard HTML, summary |
| Controllers/AppController.cs | API برنامه: ایجاد و پرسوجوی کشتی |
لایه Application — AisFeedParser.Application
موارد استفاده و انتزاعهای کسبوکار:
| فایل | مسئولیت |
|---|---|
| UseCases/CreateOrGetVessel.cs | ایجاد یا دریافت کشتی |
| UseCases/GetLatestVesselsByMmsi.cs | دریافت آخرین کشتیها گروهبندی شده بر MMSI |
| UseCases/GetLatestVesselData.cs | دریافت داده کشتی در بازه زمانی |
| UseCases/GetVesselProfileByMmsi.cs | دریافت پروفایل یک کشتی |
| UseCases/SearchVesselProfilesByName.cs | جستجوی پروفایل بر اساس نام |
| Abstractions/IAisWriter.cs | واسط نوشتن دستهای داده |
| Abstractions/IAisProfileUpdater.cs | واسط بهروزرسانی پروفایل |
| Abstractions/IAisSourceSupervisor.cs | واسط اتصال مجدد |
| Abstractions/IVesselRepository.cs | واسط مخزن کشتی |
| Abstractions/IVesselProfileReadRepository.cs | واسط مخزن پروفایل |
| DTOs/VesselDto.cs | DTO ساده کشتی |
| DTOs/VesselLatestDataDto.cs | DTO غنی با خواص Effective* |
| DTOs/VesselProfileDto.cs | DTO پروفایل |
| Models/AisParsedRow.cs | رکورد پارس شده کامل (۹۹ پارامتر) |
لایه Domain — AisFeedParser.Domain
هسته برنامه بدون وابستگی خارجی:
| فایل | مسئولیت |
|---|---|
| Ais/AisDecoder.cs | رمزگشای بیتی AIS (۳۸۹ خط) |
| Ais/AisMessage.cs | مدل پیام رمزگشایی شده (۷۹ خاصیت) |
| Ais/Nmea.cs | تجزیه جمله NMEA |
| Entities/Vessel.cs | موجودیت کشتی |
| Entities/VesselProfile.cs | پروفایل تجمیعی کشتی |
لایه Infrastructure — AisFeedParser.Infrastructure
پیادهسازی ماندگاری و سرویسهای زیرساختی:
| سرویس | مسئولیت |
|---|---|
| MultiSocketIngestor.cs | دریافت TCP/UDP چندگانه با failover |
| ParserService.cs | تجزیه NMEA و رمزگشایی AIS |
| SqliteWriter.cs | نوشتن دستهای در SQLite |
| SqlBulkWriter.cs | نوشتن حجیم در SQL Server (۱۰۱۴ خط) |
| HourlyFileWriter.cs | نوشتن لاگ خام ساعتی |
| RawLogReplayer.cs | بازپخش لاگهای خام |
| ProviderStatusMonitor.cs | پایش سلامت ارائهدهنده |
| VesselProfileUpdater.cs | بهروزرسانی پروفایل کشتی |
| AppDbMigrator.cs | مهاجرت خودکار پایگاه داده |
| AisDataRepository.cs | پرسوجوی داده با ADO.NET خام |
| EfVesselRepository.cs | مخزن کشتی با EF Core |
| EfVesselProfileReadRepository.cs | مخزن پروفایل با EF Core |
| MultipartBuffer.cs | بافر پیامهای چندبخشی |
| ExponentialBackoff.cs | تاخیر تصاعدی تصادفی |
| HostConnectionTracker.cs | رهگیری آخرین داده دریافتی |
خط لوله داده (Data Pipeline)
داده از ارائهدهنده AIS وارد شده و از یک خط لوله ناهمزمان عبور میکند. هر مرحله از طریق Channelهای bounded با سیاست DropOldest به مرحله بعد متصل است.
نمودار خط لوله
TCP/UDP
Ingestor
Writer
N Workers
SQLite / SQL Server
مراحل خط لوله
| مرحله | ورودی | خروجی | توضیح |
|---|---|---|---|
| MultiSocketIngestor | پکتهای TCP/UDP | Channel<AisRawMessage> | اتصال به میزبانهای primary/passive، خطخوانی، prepend اطلاعات مبدأ |
| HourlyFileWriter | AisRawMessage | فایل لاگ | نوشتن همزمان لاگ خام با فرمت [IP:Port:Transport] |
| ParserService | Channel<AisRawMessage> | Channel<AisParsedRow> | اعتبارسنجی checksum، مونتاژ چندبخشی، رمزگشایی AIS، بهروزرسانی پروفایل |
| SqliteWriter / SqlBulkWriter | Channel<AisParsedRow> | پایگاه داده | نوشتن دستهای با PeriodicTimer (FlushMs) در تراکنش |
جزئیات Channelها
// Channel خطوط NMEA خام
Channel<AisRawMessage> lineChannel = Channel.CreateBounded<AisRawMessage>(
new BoundedChannelOptions(capacity) {
FullMode = BoundedChannelFullMode.DropOldest
});
// Channel ردیفهای پارس شده
Channel<AisParsedRow> parsedChannel = Channel.CreateBounded<AisParsedRow>(
new BoundedChannelOptions(capacity) {
FullMode = BoundedChannelFullMode.DropOldest
});
لایه دامنه (Domain)
AisDecoder.cs
کلاس استاتیک رمزگشای AIS. شامل ۳۸۹ خط کد. پیمایش بیتی با استفاده از BitArray
و متدهای کمکی SixBit()، ToBits()، Get()،
ToSignedCoord()، Decode6BitString().
انواع پیامهای پشتیبانی شده
| نوع | نام | طول بیت | فیلدهای کلیدی |
|---|---|---|---|
| ۱ | Position Report Class A | ۱۶۸ | Lat, Lon, SOG, COG, Heading, NavStatus |
| ۲ | Position Report Class A (Assigned) | ۱۶۸ | همان نوع ۱ |
| ۳ | Position Report Class A (Response) | ۱۶۸ | همان نوع ۱ |
| ۴ | Base Station Report | ۱۶۸ | Lat, Lon, UTC Timestamp |
| ۵ | Static and Voyage Related Data | ۴۲۴ | VesselName, CallSign, ShipType, IMO, Dimensions, Destination, ETA, Draught |
| ۱۸ | Standard Class B CS Position Report | ۱۶۸ | Lat, Lon, SOG, COG, UnitType (CS) |
| ۱۹ | Extended Class B CS Position Report | ۳۱۲ | Lat, Lon, SOG, COG, VesselName, ShipType, Dimensions |
| ۲۴ | Static Data Report | ۱۶۸ | Part A: Name, Part B: CallSign, ShipType, Dimensions |
| ۲۷ | Long Range AIS Broadcast | ۹۶ | Lat, Lon, SOG, COG (دقت کمتر) |
متدهای کلیدی
// متد اصلی رمزگشایی
public static AisMessage? Parse(string payload, int msgType, int fillBits)
// متدهای کمکی
private static BitArray SixBit(string payload) // تبدیل payload بیس-۶۴ به بیت
private static uint ToBits(BitArray bits, int start, int length) // استخراج بیت
private static double? ToSignedCoord(BitArray bits, int start, int length) // مختصات امضادار
private static string Decode6BitString(BitArray bits, int start, int length) // رشته ۶ بیتی
AisMessage.cs
مدل پیام رمزگشایی شده با ۷۹ خاصیت init. تمام فیلدهای مشترک و اختصاصی
هر نوع پیام پشتیبانی میشود:
فیلدهای مشترک
MessageType, Mmsi, RepeatIndicator
موقعیتی (۱-۳, ۱۸-۱۹, ۲۷)
Latitude, Longitude, Sog, Cog, Heading, NavStatus, RotRaw, PosAcc, Raim, Radio
ایستگاه پایه (۴)
UtcYear, UtcMonth, UtcDay, UtcHour, UtcMin, UtcSec
استاتیک/سفر (۵)
VesselName, CallSign, ShipType, IMO, Destination, ETA, Draught, DimBow, DimStern, DimPort, DimStar
داده استاتیک (۲۴)
PartNumber (A/B), VesselName (Part A), CallSign (Part B), ShipType (Part B), VendorId (Part B), DimBow (Part B)
Nmea.cs
تجزیهکننده استاتیک جملههای NMEA. جملههای پشتیبانی شده:
!AIVDM/!AIVDO— داده AIS با Checksum$GPGGA— موقعیت GPS$GPVTG— مسیر و سرعت$GPZDA— زمان و تاریخ$PSHI— اختصاصی
اعتبارسنجی XOR Checksum با TryValidate().
مونتاژ پیامهای چندبخشی (Multi-part) از طریق MultipartBuffer.
موجودیتها (Entities)
| موجودیت | خاصیتها | متدها |
|---|---|---|
| Vessel | Id (Guid), Mmsi, Name | سازنده با اعتبارسنجی MMSI (۹ رقمی) |
| VesselProfile | Mmsi, Name, CallSign, ShipType, Dimensions, IMO, Destination, ETA, Draught, EPFD, LastLat, LastLon, LastCog, LastSog, LastHeading, LastNavStatus, MessageCount | UpdateStaticFromType5(), UpdateStaticFromType24(), UpdatePosition() |
لایه کاربرد (Application)
این لایه شامل موارد استفاده (Use Cases) و واسطهای انتزاعی است. تمام منطق کسبوکار مختص به یک سناریوی خاص در کلاسهای Use Case کپسوله شده است.
موارد استفاده (Use Cases)
| Use Case | ورودی | خروجی | اعتبارسنجی |
|---|---|---|---|
| CreateOrGetVessel | string mmsi | Vessel | MMSI ۹ رقمی |
| GetLatestVesselsByMmsi | int? sinceMinutes (1-1440) | List<VesselLatestDataDto> | بازه ۱ تا ۱۴۴۰ دقیقه |
| GetLatestVesselData | string mmsi, DateTime? from, DateTime? to | List<VesselLatestDataDto> | حداکثر ۳۰ روز بازه |
| GetVesselProfileByMmsi | string mmsi | VesselProfileDto? | — |
| SearchVesselProfilesByName | string namePrefix, int page, int pageSize | VesselProfileSearchResponse | حداقل ۳ کاراکتر نام، صفحهبندی |
AisParsedRow — مدل جامع داده
کلاس AisParsedRow یک رکورد با ۹۹ پارامتر است که تمام دادههای ممکن
از جملههای AIS، NMEA و فراداده را در بر میگیرد. دارای ۶ متد کارخانه:
| متد کارخانه | منبع |
|---|---|
FromAis() | پیام AIS رمزگشایی شده |
FromNmeaGga() | جمله $GPGGA |
FromNmeaVtg() | جمله $GPVTG |
FromNmeaZda() | جمله $GPZDA |
FromPshi() | جمله اختصاصی $PSHI |
FromRaw() | خط خام NMEA (برای خطا) |
VesselLatestDataDto — DTO غنی
این DTO دادههای موقعیتی AIS را با دادههای پروفایل کشتی ترکیب میکند.
دارای خواص Effective* است که از پروفایل یا داده مستقیم پر میشود:
public sealed record VesselLatestDataDto
{
// داده مستقیم از AIS
public long Mmsi { get; init; }
public int? MsgType { get; init; }
public double? Lat, Lon, Cog, Sog;
public int? Heading, NavStatus;
// خواص ترکیبی (Effective)
public string? EffectiveName; // از پروفایل یا داده AIS
public string? EffectiveCallSign;
public string? EffectiveShipType;
public string? EffectiveDestination;
public string? EffectiveImo;
// ابعاد محاسبه شده
public int? OverallLength; // DimBow + DimStern
public int? OverallWidth; // DimPort + DimStar
}
لایه زیرساخت (Infrastructure)
MultiSocketIngestor — دریافت TCP/UDP
یک سرویس پسزمینه که از معماری host groups پشتیبانی میکند. هر گروه host شامل یک میزبان primary و چند میزبان passive است:
- TCP: از
TcpClientبا keep-alive، بافر خطی، و ارسال اولیه (init send) - UDP: از
UdpClientبا بافر دریافتی قابل تنظیم - Failover: در صورت قطع connection میزبان primary، به passiveها سوئیچ میکند
- HostConnectionTracker: آخرین زمان دریافت داده را رهگیری میکند (پنجره ۶۰ ثانیه)
- ExponentialBackoff: تاخیر تصاعدی با jitter تصادفی برای اتصال مجدد
ساختار پیکربندی میزبان
{
"Ais:Hosts": [
{
"Host": "ais.example.com",
"Port": 1234,
"Transport": "Tcp",
"Passives": [
{ "Host": "ais2.example.com", "Port": 1234, "Transport": "Tcp" }
]
}
]
}
ParserService — تجزیه NMEA
سرویس پسزمینه با N کارگر (قابل تنظیم). وظایف:
- خواندن خط از
lineChannel - اعتبارسنجی Checksum NMEA با
Nmea.TryValidate() - مونتاژ پیامهای چندبخشی با
MultipartBuffer - رمزگشایی payload AIS با
AisDecoder.Parse() - بهروزرسانی پروفایل کشتی با
IAisProfileUpdater - نوشتن ردیف پارس شده در
parsedChannel
SqlBulkWriter — نوشتن حجیم SQL Server
یکی از پیچیدهترین سرویسها با ۱۰۱۴ خط کد. ویژگیها:
| ویژگی | توضیح |
|---|---|
| ۹ جدول هدف | IngestLine, AisPositionReport, AisStaticVoyage, AisStaticData24, AisBaseStationReport, NmeaGga, NmeaVtg, NmeaZda, PshiRaw |
| SqlBulkCopy | نوشتن حجیم با DataTable |
| Batch Size | ۳۰۰ ردیف (پیشفرض) |
| Flush Interval | ۲۰۰ میلیثانیه (PeriodicTimer) |
| Schema Auto-create | CREATE TABLE IF NOT EXISTS |
| Index Creation | ایندکس روی Mmsi, MsgType, IngestedAtUtc |
| Stored Procedure | sp_GetLatestVesselsByMmsi |
| View | vw_LatestVesselPositions |
SqliteWriter — نوشتن SQLite
سرویس سادهتر با ۲۳۸ خط. از یک جدول AisJson استفاده میکند
با ستونهای کافی برای تمام انواع پیام. مهاجرت خودکار برای ستونهای جدید پشتیبانی میشود.
از INSERT INTO پارامترشده با تراکنش استفاده میکند.
AisDataRepository — پرسوجوی مستقیم
۶۱۰ خط کد با پیادهسازی موازی برای SQLite و SQL Server. پرسوجوهای اصلی:
GetLatestVesselsByMmsiAsync(): استفاده از CTE/ROW_NUMBER (SQLite) یا stored procedure (SQL Server)GetLatestVesselDataAsync(): پرسوجوی داده کشتی در بازه زمانی
سایر سرویسها
| سرویس | توضیح |
|---|---|
| HourlyFileWriter | نوشتن thread-safe لاگ خام ساعتی با قفل |
| RawLogReplayer | بازپخش لاگهای خام با throttle (خط/ثانیه) |
| ProviderStatusMonitor | پینگ TCP هر ۳۰ ثانیه |
| VesselProfileUpdater | بهروزرسانی VesselProfile در حافظه |
| AppDbMigrator | مهاجرت خودکار EF و Stored Procedure |
لایه API
این لایه شامل کنترلرهای ASP.NET Core، میانافزارها، Swagger و نقطه ورود برنامه است.
نقاط پایانی API
| متد | مسیر | کنترلر | توضیح |
|---|---|---|---|
| GET | /api/vessels/latest?sinceMinutes= | VesselsController | آخرین موقعیت کشتیها گروهبندی شده بر MMSI |
| GET | /api/vessels/data?mmsi=&from=&to= | VesselsController | داده موقعیتی یک کشتی در بازه زمانی |
| GET | /api/vessel-profiles/{mmsi} | VesselProfilesController | پروفایل یک کشتی |
| GET | /api/vessel-profiles/search?name=&page=&pageSize= | VesselProfilesController | جستجوی پروفایل بر اساس نام |
| POST | /api/app | AppController | ایجاد کشتی جدید |
| GET | /api/app/{mmsi} | AppController | دریافت کشتی |
| POST | /api/admin/reconnect | AdminController | اجبار به اتصال مجدد |
| GET | /api/admin/last5 | AdminController | ۵ داده آخر |
| GET | /api/admin/last?count=N | AdminController | N داده آخر |
| GET | /api/status/latest | StatusController | آخرین وضعیت JSON |
| GET | /api/status/dashboard | StatusController | داشبورد HTML |
| GET | /api/status/summary | StatusController | خلاصه وضعیت |
| GET | /api/status/current | StatusController | وضعیت جاری |
| GET | /api/status/ping-sql | StatusController | بررسی اتصال پایگاه داده |
راهاندازی (Startup.cs)
public sealed class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddPresentation(); // کنترلرها، Swagger
services.AddApplication(); // Use Cases
services.AddInfrastructure(configuration); // Infrastructure
}
public void Configure(IApplicationBuilder app)
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseRouting();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
}
ثبت ساختاریافته (Serilog)
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("logs/ais-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 14,
fileSizeLimitBytes: 50 * 1024 * 1024)
.CreateLogger();
پایگاه داده
این پروژه از دو نوع پایگاه داده پشتیبانی میکند: SQLite برای محیط توسعه و SQL Server برای محیط تولید.
SQLite — جدول AisJson
یک جدول با ستونهای کافی برای تمام انواع پیام AIS و NMEA:
CREATE TABLE IF NOT EXISTS AisJson (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
IngestedAtUtc TEXT NOT NULL,
SourceIp TEXT,
Mmsi INTEGER,
MsgType INTEGER,
Lat REAL,
Lon REAL,
Cog REAL,
Sog REAL,
Heading INTEGER,
RawLine TEXT,
ChecksumOk INTEGER,
ParserError TEXT,
RepeatIndicator INTEGER,
NavStatus INTEGER,
RotRaw INTEGER,
UtcSec INTEGER,
Maneuver INTEGER,
PosAcc INTEGER,
Raim INTEGER,
Radio INTEGER,
PayloadJson TEXT,
Transport TEXT,
Port INTEGER
);
SQL Server — ۹ جدول نرمال شده
| جدول | توضیح | فیلدهای اصلی |
|---|---|---|
IngestLine | ردیف خام ورودی | Id, IngestedAtUtc, SourceIp, Transport, Port, RawLine, ChecksumOk, ParserError |
AisPositionReport | انواع ۱-۳, ۱۸-۱۹, ۲۷ | Mmsi, MsgType, Lat, Lon, Sog, Cog, Heading, NavStatus, RotRaw, UtcSec, Maneuver, PosAcc, Raim, Radio |
AisStaticVoyage | نوع ۵ | Mmsi, VesselName, CallSign, ShipType, Imo, Destination, Eta, Draught, DimBow, DimStern, DimPort, DimStar |
AisStaticData24 | نوع ۲۴ | Mmsi, PartNumber, VesselName, CallSign, ShipType, VendorId, DimBow, DimStern, DimPort, DimStar |
AisBaseStationReport | نوع ۴ | Mmsi, Lat, Lon, UtcYear, UtcMonth, UtcDay, UtcHour, UtcMin, UtcSec |
NmeaGga | GPS $GPGGA | MmsiK, Lat, Lon, Altitude, GeoidSep, Hdop, Quality, Satellites |
NmeaVtg | مسیر $GPVTG | MmsiK, CourseTrue, CourseMagnetic, SpeedKnots, SpeedKph |
NmeaZda | زمان $GPZDA | MmsiK, UtcYear, UtcMonth, UtcDay, UtcHour, UtcMin, UtcSec |
PshiRaw | اختصاصی $PSHI | MmsiK, RawData |
Stored Procedure — sp_GetLatestVesselsByMmsi
CREATE PROCEDURE sp_GetLatestVesselsByMmsi
@SinceMinutes INT = 1440
AS
BEGIN
WITH Ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Mmsi ORDER BY IngestedAtUtc DESC) AS rn
FROM AisPositionReport
WHERE IngestedAtUtc >= DATEADD(MINUTE, -@SinceMinutes, GETUTCDATE())
)
SELECT r.*, vp.VesselName, vp.CallSign, vp.ShipType,
vp.Destination, vp.Imo, vp.DimBow, vp.DimStern, vp.DimPort, vp.DimStar
FROM Ranked r
LEFT JOIN VesselProfiles vp ON r.Mmsi = vp.Mmsi
WHERE r.rn = 1
ORDER BY r.IngestedAtUtc DESC;
END
جداول EF Core
Vessels— Id, Mmsi, NameVesselProfiles— تمام فیلدهای پروفایلProviderStatus— وضعیت ارائهدهندگان AIS
پیکربندی
پیکربندی برنامه در src/AisFeedParser.Api/appsettings.json قرار دارد
و از الگوی Options Pattern با کلاسهای strongly-typed استفاده میکند.
ساختار کامل پیکربندی
{
"Database": {
"UseInMemoryRepo": false, // استفاده از مخزن درونحافظهای
"ConnectionString": "Data Source=ais.db"
},
"Ais": {
"Hosts": [ // میزبانهای AIS
{
"Host": "ais.example.com",
"Port": 1234,
"Transport": "Tcp", // Tcp یا Udp
"Passives": [ ... ] // میزبانهای جایگزین
}
],
"ParserWorkers": 1, // تعداد کارگرهای ParserService
"ChannelCapacity": 50000, // ظرفیت Channelها
"InitSend": "HELLO\r\n", // پیام اولیه TCP
"RawLogFolder": "raw", // پوشه لاگ خام
"Reconnect": {
"InitialMs": 500, // تاخیر اولیه اتصال مجدد
"MaxMs": 30000 // حداکثر تاخیر
},
"Replay": {
"Enabled": true, // فعالسازی بازپخش
"Folder": "raw", // پوشه لاگ
"MaxLinesPerSecond": 1000 // نرخ بازپخش
}
},
"Sql": {
"Provider": "SqlServer", // SqlServer یا Sqlite
"ConnectionString": "...",
"Writer": {
"BatchSize": 300, // اندازه دسته نوشتن
"FlushMs": 200 // فاصله فلاش
}
},
"Serilog": {
"MinimumLevel": "Information",
"WriteTo": [
{ "Name": "Console" },
{
"Name": "File",
"Args": {
"path": "logs/ais-.log",
"rollingInterval": "Day",
"retainedFileCountLimit": 14,
"fileSizeLimitBytes": 52428800
}
}
]
}
}
کلاسهای Options
| کلاس | بخش پیکربندی | فیلدهای کلیدی |
|---|---|---|
| AisOptions | "Ais" | Hosts, ParserWorkers, ChannelCapacity, RawLogFolder, Reconnect, Replay |
| DatabaseOptions | "Database" | UseInMemoryRepo, ConnectionString |
| SqlOptions | "Sql" | Provider, ConnectionString, Writer (BatchSize, FlushMs) |
آزمونها
آزمونها با استفاده از چارچوب xUnit نوشته شدهاند. در مجموع بیش از ۵۰ آزمون واحد وجود دارد.
AisDecoderTests.cs (۳۵+ آزمون)
| دسته آزمون | تعداد | توضیح |
|---|---|---|
| Type 1/2/3 Position | ۵+ | Lat, Lon, SOG, COG, Heading, NavStatus, ROT |
| Type 4 Base Station | ۳+ | مختصات و UTC Timestamp |
| Type 5 Static/Voyage | ۵+ | نام، CallSign, IMO, ابعاد, ETA, پیشفرضها |
| Type 18 Class B | ۳+ | موقعیت و نوع CS |
| Type 19 Extended B | ۳+ | موقعیت + داده استاتیک |
| Type 24 Part A/B | ۴+ | نام (Part A), CallSign/ابعاد (Part B) |
| Type 27 Long Range | ۳+ | موقعیت با دقت کمتر |
| Edge Cases | ۶+ | مختصات null, تاریخ نامعتبر, نوع ناشناخته |
AisDecoderSampleDataTests.cs (۱۴+ آزمون)
آزمونهای مبتنی بر دادههای واقعی از فایل ais_temp.txt:
- تبدیل payload به بیت
- مونتاژ پیامهای چندبخشی
- محدوده مختصات (Lat: -90..90, Lon: -180..180)
- اعتبارسنجی ابعاد (غیرمنفی)
- معیار کارایی: پارس زیر ۱۰۰ میلیثانیه
ابزار کمکی SetBits
// متد کمکی برای تنظیم بیتها در آزمونها
private static string SetBits(string payload, int offset, int length, uint value)
{
var bits = SixBit(payload);
for (int i = 0; i < length; i++)
bits[offset + length - 1 - i] = ((value >> i) & 1) == 1;
return string.Concat(
Enumerable.Range(0, bits.Length / 6)
.Select(i => SixBitChars(
Enumerable.Range(0, 6).Select(j => bits[i * 6 + j] ? 1 : 0).ToArray()
))
);
}
استقرار و CI/CD
Azure DevOps Pipeline
# azure-build-pipelines.yml
trigger:
- main
pool:
vmImage: 'windows-latest'
steps:
- task: NuGetToolInstaller@1
- task: NuGetCommand@2
inputs:
restoreSolution: 'AisFeedParser.sln'
- task: DotNetCoreCLI@2
inputs:
command: 'build'
projects: 'src/**/*.csproj'
- task: DotNetCoreCLI@2
inputs:
command: 'test'
projects: 'tests/**/*.csproj'
enabled: false # فعلاً غیرفعال
- task: DotNetCoreCLI@2
inputs:
command: 'publish'
publishWebProjects: true
arguments: '--configuration Release --output $(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
Docker — استقرار ظرفسازی شده
Dockerfile (API)
چندمرحلهای: build با SDK 8.0، اجرا با aspnet 8.0.
docker-compose.yml
محیط SQL Server محلی برای توسعه و آزمایش.
متغیرهای محیطی
| متغیر | توضیح |
|---|---|
CONNECTIONSTRINGS__SQL | رشته اتصال SQL Server |
AIS__HOSTS__0__HOST | میزبان AIS |
ASPNETCORE_ENVIRONMENT | محیط اجرا (Development/Production) |
پیکربندی محیطها
| محیط | پایگاه داده | فایل پیکربندی |
|---|---|---|
| توسعه (Development) | SQLite (ais.db) | appsettings.Development.json |
| تولید (Production) | SQL Server | appsettings.Production.json |
توسعه مبتنی بر مشخصات
پیشنهادات تغییر (Changes)
| پیشنهاد | وضعیت | توضیح |
|---|---|---|
| CHANGE-001-GROUP-BY-MMSI-LATEST | تکمیل | گروهبندی آخرین دادهها بر MMSI |
| PHASE1_DECODER_ENHANCEMENT | تکمیل | بهبود رمزگشای فاز ۱ |
| PHASE2_VESSEL_PROFILES | تکمیل | پروفایل کشتی فاز ۲ |
| PHASE3_API_ENHANCEMENTS | تکمیل | بهبود API فاز ۳ |
| PHASE4_PERFORMANCE | تکمیل | بهینهسازی کارایی فاز ۴ |
| add-vessel-api-endpoints | در حال اجرا | افزودن نقاط پایانی API کشتی |
| add-vessel-profiles | در حال اجرا | افزودن پروفایل کشتی |
| optimize-vessel-performance | در حال اجرا | بهینهسازی کارایی کشتی |
گردش کار OpenSpec
- Proposal: ایجاد proposal در
openspec/changes/<نام>/proposal.md - Implementation: پیادهسازی مطابق proposal + بهروزرسانی spec
- Archive: بایگانی change در
openspec/changes/<نام>/
فایلهای .windsurf/workflows/ شامل خودکارسازیهای windsurf برای
این گردش کار هستند.
پیوست
راهنمای شروع سریع (Quick Start)
# ۱. کلون پروژه
git clone <repository-url>
cd ais-feed-parser
# ۲. باز کردن در Visual Studio
AisFeedParser.sln
# ۳. اجرا با SQLite (پیشفرض)
cd src/AisFeedParser.Api
dotnet run
# ۴. اجرا با SQL Server (نیازمند Docker)
docker-compose up -d # راهاندازی SQL Server
dotnet run --environment Production
ساختار فایلهای مستندات
| فایل | زبان | توضیح |
|---|---|---|
| README.md | انگلیسی | راهنمای اصلی |
| README.fa.md | فارسی | راهنمای فارسی |
| docs/Architecture.md | انگلیسی | معماری سیستم |
| docs/Configuration.md | انگلیسی | راهنمای پیکربندی |
| docs/DataModel.md | انگلیسی | مدل داده |
| docs/Deployment.md | انگلیسی | راهنمای استقرار |
| docs/Operations.md | انگلیسی | راهنمای عملیاتی |
| docs/ReplayAndRecovery.md | انگلیسی | بازپخش و بازیابی |
| docs/Troubleshooting.md | انگلیسی | رفع مشکلات |
| docs/fa/*.fa.md | فارسی | ترجمه فارسی تمام مستندات |
| docs/fa/FULL_DOCUMENTATION.md | فارسی | مستندات کامل فارسی |
خلاصه فایلها و آمار
| معیار | مقدار |
|---|---|
| تعداد کل فایلهای سورس | ۶۰+ فایل |
| خطوط کد تخمینی (C#) | ۵,۵۰۰+ خط |
| تعداد پروژهها | ۶ پروژه (۴ اصلی + ۲ تست) |
| تعداد کنترلرهای API | ۵ کنترلر |
| تعداد سرویسهای پسزمینه | ۶ سرویس |
| تعداد انواع پیام AIS پشتیبانی شده | ۱۰ نوع |
| تعداد آزمونهای واحد | ۵۰+ آزمون |
| پایگاه داده پشتیبانی شده | ۲ نوع (SQLite, SQL Server) |
دادههای تماس و منابع
- CI/CD: Azure DevOps Pipelines
- ظرفسازی: Docker + Docker Compose
- مستندات: پوشه
docs/وdocs/fa/