add device token
This commit is contained in:
243
src/DEVICE_TOKEN_API.md
Normal file
243
src/DEVICE_TOKEN_API.md
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# API مدیریت توکن و تنظیمات دستگاه
|
||||||
|
|
||||||
|
این سند توضیح دهنده API های جدید اضافه شده برای مدیریت توکن و تنظیمات دستگاه است.
|
||||||
|
|
||||||
|
## تغییرات در مدل داده
|
||||||
|
|
||||||
|
### فیلدهای جدید در `DeviceSettings`
|
||||||
|
|
||||||
|
1. **UploadIntervalMin** (int): فاصله زمانی آپلود داده به دقیقه (پیشفرض: 5)
|
||||||
|
2. **DevicePhoneNumber** (string): شماره تلفن دستگاه
|
||||||
|
3. **SimCardType** (enum, nullable): نوع سیمکارت (همراه اول/ایرانسل/رایتل)
|
||||||
|
4. **TokenCode** (string, nullable): کد توکن 5 رقمی
|
||||||
|
5. **VerificationCode** (string, nullable): کد تایید 5 رقمی
|
||||||
|
6. **TokenExpiresAt** (DateTime, nullable): تاریخ انقضای توکن
|
||||||
|
|
||||||
|
### Enum نوع سیمکارت
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public enum SimCardType
|
||||||
|
{
|
||||||
|
Hamrahe_Aval = 1, // همراه اول
|
||||||
|
Irancell = 2, // ایرانسل
|
||||||
|
Rightel = 3 // رایتل
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### 1. دریافت فاصله زمانی آپلود
|
||||||
|
|
||||||
|
**GET** `/api/DeviceToken/upload-interval`
|
||||||
|
|
||||||
|
دریافت مقدار `UPLOAD_INTERVAL_MIN` بر اساس شناسه دستگاه یا شماره تلفن.
|
||||||
|
|
||||||
|
#### پارامترها (Query String)
|
||||||
|
|
||||||
|
- `deviceId` (int, optional): شناسه دستگاه
|
||||||
|
- `devicePhoneNumber` (string, optional): شماره تلفن دستگاه
|
||||||
|
|
||||||
|
**نکته:** حداقل یکی از پارامترها باید ارسال شود.
|
||||||
|
|
||||||
|
#### مثال درخواست
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/DeviceToken/upload-interval?devicePhoneNumber=09123456789
|
||||||
|
```
|
||||||
|
|
||||||
|
#### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": null,
|
||||||
|
"uploadIntervalMin": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### پاسخ خطا
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "دستگاه یافت نشد",
|
||||||
|
"uploadIntervalMin": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. درخواست توکن دستگاه
|
||||||
|
|
||||||
|
**POST** `/api/DeviceToken/request-token`
|
||||||
|
|
||||||
|
تولید کد توکن 5 رقمی و ارسال آن از طریق پیامک به شماره دستگاه.
|
||||||
|
|
||||||
|
#### بدنه درخواست (JSON)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"devicePhoneNumber": "09123456789"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### مثال درخواست
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/DeviceToken/request-token
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"devicePhoneNumber": "09123456789"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "کد تایید با موفقیت ارسال شد",
|
||||||
|
"tokenCode": "12345"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**نکته:** پیامک حاوی کد توکن به شماره مشخص شده ارسال میشود. کد دارای اعتبار 10 دقیقه است.
|
||||||
|
|
||||||
|
#### محاسبه کد تایید
|
||||||
|
|
||||||
|
کد تایید بر اساس فرمول زیر محاسبه میشود:
|
||||||
|
|
||||||
|
```
|
||||||
|
VerificationCode = (TokenCode × 7 + 12345) % 100000
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. تایید توکن دستگاه
|
||||||
|
|
||||||
|
**POST** `/api/DeviceToken/verify-token`
|
||||||
|
|
||||||
|
تایید کد تایید و ارسال تنظیمات کدشده دستگاه از طریق پیامک.
|
||||||
|
|
||||||
|
#### بدنه درخواست (JSON)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"devicePhoneNumber": "09123456789",
|
||||||
|
"verificationCode": "98765"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### مثال درخواست
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/DeviceToken/verify-token
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"devicePhoneNumber": "09123456789",
|
||||||
|
"verificationCode": "98765"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### پاسخ موفق
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "تنظیمات با موفقیت ارسال شد",
|
||||||
|
"encodedSettings": "RGV2aWNlMDF8NQ=="
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**نکته:** تنظیمات به صورت کدشده Base64 ارسال میشود. فرمت قبل از کدگذاری: `{DeviceName}|{UploadIntervalMin}`
|
||||||
|
|
||||||
|
#### پاسخ خطا
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "کد تایید نادرست است",
|
||||||
|
"encodedSettings": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. بروزرسانی تنظیمات دستگاه
|
||||||
|
|
||||||
|
**PUT** `/api/DeviceSettings`
|
||||||
|
|
||||||
|
API موجود که حالا فیلدهای جدید را نیز پشتیبانی میکند.
|
||||||
|
|
||||||
|
#### بدنه درخواست (JSON)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"deviceId": 1,
|
||||||
|
"province": "تهران",
|
||||||
|
"city": "تهران",
|
||||||
|
"productType": "گلخانه",
|
||||||
|
"uploadIntervalMin": 5,
|
||||||
|
"devicePhoneNumber": "09123456789",
|
||||||
|
"simCardType": 1,
|
||||||
|
"minimumSmsIntervalMinutes": 15,
|
||||||
|
"minimumCallIntervalMinutes": 60
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## فلوی کاری (Workflow)
|
||||||
|
|
||||||
|
### سناریو: دریافت تنظیمات دستگاه
|
||||||
|
|
||||||
|
1. **دستگاه درخواست توکن میکند:**
|
||||||
|
```http
|
||||||
|
POST /api/DeviceToken/request-token
|
||||||
|
Body: { "devicePhoneNumber": "09123456789" }
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **سرور کد توکن تولید و ارسال میکند:**
|
||||||
|
- کد توکن 5 رقمی: مثلاً `12345`
|
||||||
|
- کد تایید محاسبه شده: `(12345 × 7 + 12345) % 100000 = 98760`
|
||||||
|
- پیامک حاوی کد توکن به شماره دستگاه ارسال میشود
|
||||||
|
|
||||||
|
3. **دستگاه کد تایید را محاسبه و ارسال میکند:**
|
||||||
|
```http
|
||||||
|
POST /api/DeviceToken/verify-token
|
||||||
|
Body: {
|
||||||
|
"devicePhoneNumber": "09123456789",
|
||||||
|
"verificationCode": "98760"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **سرور تنظیمات کدشده را ارسال میکند:**
|
||||||
|
- تنظیمات: `Device01|5`
|
||||||
|
- Base64: `RGV2aWNlMDF8NQ==`
|
||||||
|
- پیامک حاوی تنظیمات کدشده به شماره دستگاه ارسال میشود
|
||||||
|
|
||||||
|
5. **دستگاه تنظیمات را decode کرده و اعمال میکند**
|
||||||
|
|
||||||
|
## نکات امنیتی
|
||||||
|
|
||||||
|
1. کد توکن فقط 10 دقیقه اعتبار دارد
|
||||||
|
2. پس از تایید موفق، کدهای توکن و تایید از دیتابیس پاک میشوند
|
||||||
|
3. کدگذاری Base64 یک کدگذاری ساده است و برای امنیت بیشتر میتوان از روشهای پیچیدهتر استفاده کرد
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
Migration با نام `AddDeviceTokenAndPhoneFields` ایجاد و به دیتابیس اعمال شده است.
|
||||||
|
|
||||||
|
برای اعمال دستی (در صورت نیاز):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet ef database update --project GreenHome.Infrastructure --startup-project GreenHome.Api
|
||||||
|
```
|
||||||
|
|
||||||
|
## تست API ها
|
||||||
|
|
||||||
|
میتوانید از Swagger UI (که در حالت Development در `/scalar/v1` در دسترس است) برای تست API ها استفاده کنید.
|
||||||
|
|
||||||
|
یا از ابزارهایی مانند Postman/Insomnia با استفاده از نمونههای بالا.
|
||||||
|
|
||||||
128
src/GreenHome.Api/Controllers/DeviceTokenController.cs
Normal file
128
src/GreenHome.Api/Controllers/DeviceTokenController.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
using GreenHome.Application;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace GreenHome.Api.Controllers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کنترلر مدیریت توکن و تنظیمات دستگاه
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
public class DeviceTokenController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly IDeviceTokenService deviceTokenService;
|
||||||
|
private readonly ILogger<DeviceTokenController> logger;
|
||||||
|
|
||||||
|
public DeviceTokenController(
|
||||||
|
IDeviceTokenService deviceTokenService,
|
||||||
|
ILogger<DeviceTokenController> logger)
|
||||||
|
{
|
||||||
|
this.deviceTokenService = deviceTokenService;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت فاصله زمانی آپلود دستگاه
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceId">شناسه دستگاه (اختیاری)</param>
|
||||||
|
/// <param name="devicePhoneNumber">شماره تلفن دستگاه (اختیاری)</param>
|
||||||
|
/// <returns>فاصله زمانی آپلود به دقیقه</returns>
|
||||||
|
[HttpGet("upload-interval")]
|
||||||
|
public async Task<ActionResult<GetUploadIntervalResponse>> GetUploadInterval(
|
||||||
|
[FromQuery] int? deviceId,
|
||||||
|
[FromQuery] string? devicePhoneNumber,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!deviceId.HasValue && string.IsNullOrWhiteSpace(devicePhoneNumber))
|
||||||
|
{
|
||||||
|
return BadRequest(new GetUploadIntervalResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "حداقل یکی از پارامترهای deviceId یا devicePhoneNumber باید ارسال شود"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new GetUploadIntervalRequest
|
||||||
|
{
|
||||||
|
DeviceId = deviceId,
|
||||||
|
DevicePhoneNumber = devicePhoneNumber
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await deviceTokenService.GetUploadIntervalAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
return NotFound(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست توکن دستگاه (تولید و ارسال کد از طریق پیامک)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">درخواست شامل شماره تلفن دستگاه</param>
|
||||||
|
/// <returns>نتیجه درخواست</returns>
|
||||||
|
[HttpPost("request-token")]
|
||||||
|
public async Task<ActionResult<RequestDeviceTokenResponse>> RequestToken(
|
||||||
|
[FromBody] RequestDeviceTokenRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.DevicePhoneNumber))
|
||||||
|
{
|
||||||
|
return BadRequest(new RequestDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "شماره تلفن دستگاه الزامی است"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await deviceTokenService.RequestDeviceTokenAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
return BadRequest(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تایید توکن دستگاه (ارسال تنظیمات کدشده از طریق پیامک)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request">درخواست شامل شماره تلفن و کد تایید</param>
|
||||||
|
/// <returns>نتیجه تایید و تنظیمات کدشده</returns>
|
||||||
|
[HttpPost("verify-token")]
|
||||||
|
public async Task<ActionResult<VerifyDeviceTokenResponse>> VerifyToken(
|
||||||
|
[FromBody] VerifyDeviceTokenRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(request.DevicePhoneNumber))
|
||||||
|
{
|
||||||
|
return BadRequest(new VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "شماره تلفن دستگاه الزامی است"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(request.VerificationCode))
|
||||||
|
{
|
||||||
|
return BadRequest(new VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "کد تایید الزامی است"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await deviceTokenService.VerifyDeviceTokenAsync(request, cancellationToken);
|
||||||
|
|
||||||
|
if (!result.Success)
|
||||||
|
{
|
||||||
|
return BadRequest(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -66,6 +66,7 @@ builder.Services.AddScoped<GreenHome.Application.IUserDailyReportService, GreenH
|
|||||||
builder.Services.AddScoped<GreenHome.Application.IChecklistService, GreenHome.Infrastructure.ChecklistService>();
|
builder.Services.AddScoped<GreenHome.Application.IChecklistService, GreenHome.Infrastructure.ChecklistService>();
|
||||||
builder.Services.AddScoped<GreenHome.Application.IMonthlyReportService, GreenHome.Infrastructure.MonthlyReportService>();
|
builder.Services.AddScoped<GreenHome.Application.IMonthlyReportService, GreenHome.Infrastructure.MonthlyReportService>();
|
||||||
builder.Services.AddScoped<GreenHome.Application.IDevicePostService, GreenHome.Infrastructure.DevicePostService>();
|
builder.Services.AddScoped<GreenHome.Application.IDevicePostService, GreenHome.Infrastructure.DevicePostService>();
|
||||||
|
builder.Services.AddScoped<GreenHome.Application.IDeviceTokenService, GreenHome.Infrastructure.DeviceTokenService>();
|
||||||
|
|
||||||
// SMS Service Configuration
|
// SMS Service Configuration
|
||||||
builder.Services.AddIppanelSms(builder.Configuration);
|
builder.Services.AddIppanelSms(builder.Configuration);
|
||||||
|
|||||||
@@ -128,6 +128,13 @@ public sealed class DeviceSettingsDto
|
|||||||
public int MinimumCallIntervalMinutes { get; set; } = 60;
|
public int MinimumCallIntervalMinutes { get; set; } = 60;
|
||||||
public decimal? AreaSquareMeters { get; set; }
|
public decimal? AreaSquareMeters { get; set; }
|
||||||
|
|
||||||
|
public int UploadIntervalMin { get; set; } = 5;
|
||||||
|
public string DevicePhoneNumber { get; set; } = string.Empty;
|
||||||
|
public Domain.SimCardType? SimCardType { get; set; }
|
||||||
|
public string? TokenCode { get; set; }
|
||||||
|
public string? VerificationCode { get; set; }
|
||||||
|
public DateTime? TokenExpiresAt { get; set; }
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
public DateTime UpdatedAt { get; set; }
|
public DateTime UpdatedAt { get; set; }
|
||||||
}
|
}
|
||||||
@@ -313,3 +320,61 @@ public sealed class UserDailyReportFilter
|
|||||||
public int Page { get; set; } = 1;
|
public int Page { get; set; } = 1;
|
||||||
public int PageSize { get; set; } = 20;
|
public int PageSize { get; set; } = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DTOs برای مدیریت توکن دستگاه
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست دریافت فاصله زمانی آپلود
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GetUploadIntervalRequest
|
||||||
|
{
|
||||||
|
public int? DeviceId { get; set; }
|
||||||
|
public string? DevicePhoneNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پاسخ دریافت فاصله زمانی آپلود
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GetUploadIntervalResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public int? UploadIntervalMin { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست دریافت توکن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RequestDeviceTokenRequest
|
||||||
|
{
|
||||||
|
public required string DevicePhoneNumber { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پاسخ دریافت توکن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RequestDeviceTokenResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public string? TokenCode { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست تایید توکن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class VerifyDeviceTokenRequest
|
||||||
|
{
|
||||||
|
public required string DevicePhoneNumber { get; set; }
|
||||||
|
public required string VerificationCode { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// پاسخ تایید توکن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public string? EncodedSettings { get; set; }
|
||||||
|
}
|
||||||
23
src/GreenHome.Application/IDeviceTokenService.cs
Normal file
23
src/GreenHome.Application/IDeviceTokenService.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
namespace GreenHome.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// سرویس مدیریت توکن و تنظیمات دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public interface IDeviceTokenService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت فاصله زمانی آپلود بر اساس شماره تلفن یا شناسه دستگاه
|
||||||
|
/// </summary>
|
||||||
|
Task<GetUploadIntervalResponse> GetUploadIntervalAsync(GetUploadIntervalRequest request, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست توکن دستگاه (تولید و ارسال کد)
|
||||||
|
/// </summary>
|
||||||
|
Task<RequestDeviceTokenResponse> RequestDeviceTokenAsync(RequestDeviceTokenRequest request, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تایید توکن دستگاه (ارسال تنظیمات)
|
||||||
|
/// </summary>
|
||||||
|
Task<VerifyDeviceTokenResponse> VerifyDeviceTokenAsync(VerifyDeviceTokenRequest request, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,26 @@
|
|||||||
namespace GreenHome.Domain;
|
namespace GreenHome.Domain;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع سیم کارت
|
||||||
|
/// </summary>
|
||||||
|
public enum SimCardType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// همراه اول
|
||||||
|
/// </summary>
|
||||||
|
Hamrahe_Aval = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ایرانسل
|
||||||
|
/// </summary>
|
||||||
|
Irancell = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// رایتل
|
||||||
|
/// </summary>
|
||||||
|
Rightel = 3
|
||||||
|
}
|
||||||
|
|
||||||
public sealed class DeviceSettings
|
public sealed class DeviceSettings
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
@@ -46,6 +67,36 @@ public sealed class DeviceSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public decimal? AreaSquareMeters { get; set; }
|
public decimal? AreaSquareMeters { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// فاصله زمانی آپلود داده (به دقیقه)
|
||||||
|
/// </summary>
|
||||||
|
public int UploadIntervalMin { get; set; } = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// شماره تلفن دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public string DevicePhoneNumber { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// نوع سیم کارت
|
||||||
|
/// </summary>
|
||||||
|
public SimCardType? SimCardType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد توکن (5 رقمی)
|
||||||
|
/// </summary>
|
||||||
|
public string? TokenCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// کد تایید (5 رقمی)
|
||||||
|
/// </summary>
|
||||||
|
public string? VerificationCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تاریخ انقضای توکن
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? TokenExpiresAt { get; set; }
|
||||||
|
|
||||||
public DateTime CreatedAt { get; set; }
|
public DateTime CreatedAt { get; set; }
|
||||||
public DateTime UpdatedAt { get; set; }
|
public DateTime UpdatedAt { get; set; }
|
||||||
}
|
}
|
||||||
249
src/GreenHome.Infrastructure/DeviceTokenService.cs
Normal file
249
src/GreenHome.Infrastructure/DeviceTokenService.cs
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
using GreenHome.Application;
|
||||||
|
using GreenHome.Sms.Ippanel;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace GreenHome.Infrastructure;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// سرویس مدیریت توکن و تنظیمات دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DeviceTokenService : IDeviceTokenService
|
||||||
|
{
|
||||||
|
private readonly GreenHomeDbContext dbContext;
|
||||||
|
private readonly ISmsService smsService;
|
||||||
|
private readonly ILogger<DeviceTokenService> logger;
|
||||||
|
|
||||||
|
public DeviceTokenService(
|
||||||
|
GreenHomeDbContext dbContext,
|
||||||
|
ISmsService smsService,
|
||||||
|
ILogger<DeviceTokenService> logger)
|
||||||
|
{
|
||||||
|
this.dbContext = dbContext;
|
||||||
|
this.smsService = smsService;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// دریافت فاصله زمانی آپلود بر اساس شماره تلفن یا شناسه دستگاه
|
||||||
|
/// </summary>
|
||||||
|
public async Task<GetUploadIntervalResponse> GetUploadIntervalAsync(
|
||||||
|
GetUploadIntervalRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Domain.DeviceSettings? settings = null;
|
||||||
|
|
||||||
|
// جستجو بر اساس DeviceId یا DevicePhoneNumber
|
||||||
|
if (request.DeviceId.HasValue)
|
||||||
|
{
|
||||||
|
settings = await dbContext.DeviceSettings
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(ds => ds.DeviceId == request.DeviceId.Value, cancellationToken);
|
||||||
|
}
|
||||||
|
else if (!string.IsNullOrWhiteSpace(request.DevicePhoneNumber))
|
||||||
|
{
|
||||||
|
settings = await dbContext.DeviceSettings
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(ds => ds.DevicePhoneNumber == request.DevicePhoneNumber, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings == null)
|
||||||
|
{
|
||||||
|
return new GetUploadIntervalResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "دستگاه یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new GetUploadIntervalResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
UploadIntervalMin = settings.UploadIntervalMin
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error getting upload interval for device");
|
||||||
|
return new GetUploadIntervalResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "خطا در دریافت اطلاعات"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// درخواست توکن دستگاه (تولید و ارسال کد)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<RequestDeviceTokenResponse> RequestDeviceTokenAsync(
|
||||||
|
RequestDeviceTokenRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// پیدا کردن تنظیمات دستگاه بر اساس شماره تلفن
|
||||||
|
var settings = await dbContext.DeviceSettings
|
||||||
|
.FirstOrDefaultAsync(ds => ds.DevicePhoneNumber == request.DevicePhoneNumber, cancellationToken);
|
||||||
|
|
||||||
|
if (settings == null)
|
||||||
|
{
|
||||||
|
return new RequestDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "دستگاه با این شماره تلفن یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// تولید کد توکن 5 رقمی
|
||||||
|
var random = new Random();
|
||||||
|
var tokenCode = random.Next(10000, 99999).ToString();
|
||||||
|
|
||||||
|
// تولید کد تایید بر اساس فرمول: (TokenCode * 7 + 12345) % 100000
|
||||||
|
var verificationCode = ((int.Parse(tokenCode) * 7 + 12345) % 100000).ToString("D5");
|
||||||
|
|
||||||
|
// ذخیره کدها
|
||||||
|
settings.TokenCode = tokenCode;
|
||||||
|
settings.VerificationCode = verificationCode;
|
||||||
|
settings.TokenExpiresAt = DateTime.UtcNow.AddMinutes(10); // اعتبار 10 دقیقه
|
||||||
|
settings.UpdatedAt = DateTime.UtcNow;
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
// ارسال کد توکن از طریق پیامک
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await smsService.SendWebserviceSmsAsync(new WebserviceSmsRequest
|
||||||
|
{
|
||||||
|
Recipient = request.DevicePhoneNumber,
|
||||||
|
Message = $"کد تایید دستگاه شما: {tokenCode}\nاعتبار: 10 دقیقه"
|
||||||
|
}, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception smsEx)
|
||||||
|
{
|
||||||
|
logger.LogError(smsEx, "Error sending token SMS to {PhoneNumber}", request.DevicePhoneNumber);
|
||||||
|
return new RequestDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "خطا در ارسال پیامک"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.LogInformation("Token requested for device phone {PhoneNumber}", request.DevicePhoneNumber);
|
||||||
|
|
||||||
|
return new RequestDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "کد تایید با موفقیت ارسال شد",
|
||||||
|
TokenCode = tokenCode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error requesting device token");
|
||||||
|
return new RequestDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "خطا در درخواست توکن"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// تایید توکن دستگاه (ارسال تنظیمات)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<VerifyDeviceTokenResponse> VerifyDeviceTokenAsync(
|
||||||
|
VerifyDeviceTokenRequest request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// پیدا کردن تنظیمات دستگاه
|
||||||
|
var settings = await dbContext.DeviceSettings
|
||||||
|
.Include(ds => ds.Device)
|
||||||
|
.FirstOrDefaultAsync(ds => ds.DevicePhoneNumber == request.DevicePhoneNumber, cancellationToken);
|
||||||
|
|
||||||
|
if (settings == null)
|
||||||
|
{
|
||||||
|
return new VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "دستگاه با این شماره تلفن یافت نشد"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی انقضای توکن
|
||||||
|
if (settings.TokenExpiresAt == null || settings.TokenExpiresAt < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
return new VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "کد تایید منقضی شده است"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی کد تایید
|
||||||
|
if (settings.VerificationCode != request.VerificationCode)
|
||||||
|
{
|
||||||
|
return new VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "کد تایید نادرست است"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// رمزگذاری ساده تنظیمات: DeviceName|UploadIntervalMin به Base64
|
||||||
|
var deviceName = settings.Device.DeviceName;
|
||||||
|
var uploadInterval = settings.UploadIntervalMin;
|
||||||
|
var plainText = $"{deviceName}|{uploadInterval}";
|
||||||
|
var encodedSettings = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(plainText));
|
||||||
|
|
||||||
|
// ارسال تنظیمات کدشده از طریق پیامک
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await smsService.SendWebserviceSmsAsync(new WebserviceSmsRequest
|
||||||
|
{
|
||||||
|
Recipient = request.DevicePhoneNumber,
|
||||||
|
Message = $"تنظیمات دستگاه:\n{encodedSettings}"
|
||||||
|
}, cancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception smsEx)
|
||||||
|
{
|
||||||
|
logger.LogError(smsEx, "Error sending settings SMS to {PhoneNumber}", request.DevicePhoneNumber);
|
||||||
|
return new VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "خطا در ارسال پیامک"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// پاک کردن کدها بعد از استفاده موفق
|
||||||
|
settings.TokenCode = null;
|
||||||
|
settings.VerificationCode = null;
|
||||||
|
settings.TokenExpiresAt = null;
|
||||||
|
settings.UpdatedAt = DateTime.UtcNow;
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
|
logger.LogInformation("Device token verified for {PhoneNumber}", request.DevicePhoneNumber);
|
||||||
|
|
||||||
|
return new VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Message = "تنظیمات با موفقیت ارسال شد",
|
||||||
|
EncodedSettings = encodedSettings
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Error verifying device token");
|
||||||
|
return new VerifyDeviceTokenResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Message = "خطا در تایید توکن"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
1218
src/GreenHome.Infrastructure/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.Designer.cs
generated
Normal file
1218
src/GreenHome.Infrastructure/Migrations/20251217154130_AddDeviceTokenAndPhoneFields.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace GreenHome.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddDeviceTokenAndPhoneFields : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "DevicePhoneNumber",
|
||||||
|
table: "DeviceSettings",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: "");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "SimCardType",
|
||||||
|
table: "DeviceSettings",
|
||||||
|
type: "int",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "TokenCode",
|
||||||
|
table: "DeviceSettings",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "TokenExpiresAt",
|
||||||
|
table: "DeviceSettings",
|
||||||
|
type: "datetime2",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "UploadIntervalMin",
|
||||||
|
table: "DeviceSettings",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "VerificationCode",
|
||||||
|
table: "DeviceSettings",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "DevicePhoneNumber",
|
||||||
|
table: "DeviceSettings");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SimCardType",
|
||||||
|
table: "DeviceSettings");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "TokenCode",
|
||||||
|
table: "DeviceSettings");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "TokenExpiresAt",
|
||||||
|
table: "DeviceSettings");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "UploadIntervalMin",
|
||||||
|
table: "DeviceSettings");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "VerificationCode",
|
||||||
|
table: "DeviceSettings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -590,6 +590,10 @@ namespace GreenHome.Infrastructure.Migrations
|
|||||||
b.Property<int>("DeviceId")
|
b.Property<int>("DeviceId")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("DevicePhoneNumber")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.Property<decimal?>("Latitude")
|
b.Property<decimal?>("Latitude")
|
||||||
.HasColumnType("decimal(9,6)");
|
.HasColumnType("decimal(9,6)");
|
||||||
|
|
||||||
@@ -616,9 +620,24 @@ namespace GreenHome.Infrastructure.Migrations
|
|||||||
.HasMaxLength(100)
|
.HasMaxLength(100)
|
||||||
.HasColumnType("nvarchar(100)");
|
.HasColumnType("nvarchar(100)");
|
||||||
|
|
||||||
|
b.Property<int?>("SimCardType")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("TokenCode")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("TokenExpiresAt")
|
||||||
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
b.Property<DateTime>("UpdatedAt")
|
b.Property<DateTime>("UpdatedAt")
|
||||||
.HasColumnType("datetime2");
|
.HasColumnType("datetime2");
|
||||||
|
|
||||||
|
b.Property<int>("UploadIntervalMin")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<string>("VerificationCode")
|
||||||
|
.HasColumnType("nvarchar(max)");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("DeviceId")
|
b.HasIndex("DeviceId")
|
||||||
|
|||||||
Reference in New Issue
Block a user