[圖片上傳失敗...(image-c9b470-1662870789547)]
開過車或者坐過車的都對限速是深惡痛絕吧,不過為了大家安全,忍一忍吧。系統(tǒng)究竟是如何判斷車主是否超速那,本文主要介紹這個系統(tǒng)到底是如何設計的。本文基于微服務進行設計。
關(guān)于微服務,在微服務初探(0)和微服務-node.js中對微服務有簡單的介紹。通過這篇文章,可以進一步了解微服務。
注:本文內(nèi)容參考《Dapr For .NET developer》,專有名詞保留英文原文,不做翻譯。
Dapr
The Distributed Application Runtime (Dapr) provides APIs that simplify microservice connectivity. Whether your communication pattern is service to service invocation or pub/sub messaging, Dapr helps you write resilient and secured microservices.
Building blocks抽象了分布式應用能力的實現(xiàn),service只需要和Building blocks進行交互即可。
[圖片上傳失敗...(image-5830d9-1662870789547)]
Dapr的現(xiàn)在和安裝可參考Install the Dapr CLI。
限速設計
[圖片上傳失敗...(image-149a63-1662870789547)]
這里面fine是罰單,罰款的意思。
VehicleState包括了車牌號,進入限速區(qū)域(Cameral)時間,退出限速區(qū)域(Cameral)時間。
public record struct VehicleState
{
public string LicenseNumber { get; init; }
public DateTime EntryTimestamp { get; init; }
public DateTime? ExitTimestamp { get; init; }
public VehicleState(string licenseNumber, DateTime entryTimestamp, DateTime? exitTimestamp = null)
{
this.LicenseNumber = licenseNumber;
this.EntryTimestamp = entryTimestamp;
this.ExitTimestamp = exitTimestamp;
}
}
據(jù)此就可以計算出平均時速,然后再與規(guī)定的時速進行比較,如果超過了,不好意思,您將收到一封郵件,也可以用短信等方式發(fā)送給車主。而車主的郵件信息通過VehicleRegistrationService來獲取。
車輛信息的結(jié)構(gòu)體定義如下:
public record struct VehicleInfo(string VehicleId, string Brand, string Model, string OwnerName, string OwnerEmail);
代碼實現(xiàn)
windows平臺下需要將start-maildev.ps1修改為,修改參考maildev/maildev。
docker run -d -p 4000:1080 -p 4025:1025 --name dtc-maildev maildev/maildev
-d: 后臺運行容器,并返回容器ID;
(1) CameralSimulation.cs
模擬進入和退出代碼:
// simulate entry
DateTime entryTimestamp = DateTime.Now;
var vehicleRegistered = new VehicleRegistered
{
Lane = _camNumber,
LicenseNumber = GenerateRandomLicenseNumber(),
Timestamp = entryTimestamp
};
await _trafficControlService.SendVehicleEntryAsync(vehicleRegistered);
Console.WriteLine($"Simulated ENTRY of vehicle with license-number {vehicleRegistered.LicenseNumber} in lane {vehicleRegistered.Lane}");
// simulate exit
TimeSpan exitDelay = TimeSpan.FromSeconds(_rnd.Next(_minExitDelayInS, _maxExitDelayInS) + _rnd.NextDouble());
Task.Delay(exitDelay).Wait();
vehicleRegistered.Timestamp = DateTime.Now;
vehicleRegistered.Lane = _rnd.Next(1, 4);
await _trafficControlService.SendVehicleExitAsync(vehicleRegistered);
Console.WriteLine($"Simulated EXIT of vehicle with license-number {vehicleRegistered.LicenseNumber} in lane {vehicleRegistered.Lane}");
跟蹤 _trafficControlService.SendVehicleEntryAsync
public async Task SendVehicleEntryAsync(VehicleRegistered vehicleRegistered)
{
var eventJson = JsonSerializer.Serialize(vehicleRegistered);
var message = new MqttApplicationMessageBuilder()
.WithTopic("trafficcontrol/entrycam")
.WithPayload(Encoding.UTF8.GetBytes(eventJson))
.WithAtMostOnceQoS()
.Build();
await _client.PublishAsync(message, CancellationToken.None);
}
使用Input Binding來發(fā)布消息??勺粉櫟较⒌挠嗛喺呤莟rafficcontrolservice。
(2)TrafficController.cs
[HttpPost("entrycam")]
public async Task<ActionResult> VehicleEntryAsync(VehicleRegistered msg)
{
try
{
// log entry
_logger.LogInformation($"ENTRY detected in lane {msg.Lane} at {msg.Timestamp.ToString("hh:mm:ss")} " +
$"of vehicle with license-number {msg.LicenseNumber}.");
// store vehicle state
var vehicleState = new VehicleState(msg.LicenseNumber, msg.Timestamp, null);
await _vehicleStateRepository.SaveVehicleStateAsync(vehicleState);
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while processing ENTRY");
return StatusCode(500);
}
}
跟蹤_vehicleStateRepository.SaveVehicleStateAsync(vehicleState)
public async Task SaveVehicleStateAsync(VehicleState vehicleState)
{
await _daprClient.SaveStateAsync<VehicleState>(
DAPR_STORE_NAME, vehicleState.LicenseNumber, vehicleState);
}
使用state management building block來保存VehicleState。
同理可知退出時會調(diào)用trafficcontrolservice的VehicleExitAsync:
[HttpPost("exitcam")]
public async Task<ActionResult> VehicleExitAsync(VehicleRegistered msg, [FromServices] DaprClient daprClient)
{
try
{
// get vehicle state
var state = await _vehicleStateRepository.GetVehicleStateAsync(msg.LicenseNumber);
if (state == default(VehicleState))
{
return NotFound();
}
// log exit
_logger.LogInformation($"EXIT detected in lane {msg.Lane} at {msg.Timestamp.ToString("hh:mm:ss")} " +
$"of vehicle with license-number {msg.LicenseNumber}.");
// update state
var exitState = state.Value with { ExitTimestamp = msg.Timestamp };
await _vehicleStateRepository.SaveVehicleStateAsync(exitState);
// handle possible speeding violation
int violation = _speedingViolationCalculator.DetermineSpeedingViolationInKmh(exitState.EntryTimestamp, exitState.ExitTimestamp.Value);
if (violation > 0)
{
_logger.LogInformation($"Speeding violation detected ({violation} KMh) of vehicle" +
$"with license-number {state.Value.LicenseNumber}.");
var speedingViolation = new SpeedingViolation
{
VehicleId = msg.LicenseNumber,
RoadId = _roadId,
ViolationInKmh = violation,
Timestamp = msg.Timestamp
};
// publish speedingviolation (Dapr publish / subscribe)
await daprClient.PublishEventAsync("pubsub", "speedingviolations", speedingViolation);
}
return Ok();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error occurred while processing EXIT");
return StatusCode(500);
}
}
如果存在超速的話會調(diào)用:
await daprClient.PublishEventAsync("pubsub", "speedingviolations", speedingViolation)
根據(jù)配置文件
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: pubsub
namespace: dapr-trafficcontrol
spec:
type: pubsub.rabbitmq
version: v1
metadata:
- name: host
value: "amqp://localhost:5672"
- name: durable
value: "false"
- name: deletedWhenUnused
value: "false"
- name: autoAck
value: "false"
- name: reconnectWait
value: "0"
- name: concurrency
value: parallel
scopes:
- trafficcontrolservice
- finecollectionservice
可追蹤到消息的訂閱者是finecollectionservice。
(3)FineCollectionService
[Topic("pubsub", "speedingviolations")]
[Route("collectfine")]
[HttpPost()]
public async Task<ActionResult> CollectFine(SpeedingViolation speedingViolation, [FromServices] DaprClient daprClient)
{
decimal fine = _fineCalculator.CalculateFine(_fineCalculatorLicenseKey!, speedingViolation.ViolationInKmh);
// get owner info (Dapr service invocation)
var vehicleInfo = _vehicleRegistrationService.GetVehicleInfo(speedingViolation.VehicleId).Result;
// log fine
string fineString = fine == 0 ? "tbd by the prosecutor" : $"{fine} Euro";
_logger.LogInformation($"Sent speeding ticket to {vehicleInfo.OwnerName}. " +
$"Road: {speedingViolation.RoadId}, Licensenumber: {speedingViolation.VehicleId}, " +
$"Vehicle: {vehicleInfo.Brand} {vehicleInfo.Model}, " +
$"Violation: {speedingViolation.ViolationInKmh} Km/h, Fine: {fineString}, " +
$"On: {speedingViolation.Timestamp.ToString("dd-MM-yyyy")} " +
$"at {speedingViolation.Timestamp.ToString("hh:mm:ss")}.");
// send fine by email (Dapr output binding)
var body = EmailUtils.CreateEmailBody(speedingViolation, vehicleInfo, fineString);
var metadata = new Dictionary<string, string>
{
["emailFrom"] = "noreply@cfca.gov",
["emailTo"] = vehicleInfo.OwnerEmail,
["subject"] = $"Speeding violation on the {speedingViolation.RoadId}"
};
await daprClient.InvokeBindingAsync("sendmail", "create", body, metadata);
return Ok();
}
其中vehicleInfo 的值是通過service invocation調(diào)用VehicleRegistrationService獲取的。
最后通過Output Binding來發(fā)送郵件:
await daprClient.InvokeBindingAsync("sendmail", "create", body, metadata);
運行
- start-all.ps1
- dapr run --app-id vehicleregistrationservice --app-port 6002 --dapr-http-port 3602 --dapr-grpc-port 60002 --config ../dapr/config/config.yaml --components-path ../dapr/components dotnet run
- dapr run --app-id finecollectionservice --app-port 6001 --dapr-http-port 3601 --dapr-grpc-port 60001 --config ../dapr/config/config.yaml --components-path ../dapr/components dotnet run
- dapr run --app-id trafficcontrolservice --app-port 6000 --dapr-http-port 3600 --dapr-grpc-port 60000 --config ../dapr/config/config.yaml --components-path ../dapr/components dotnet run
- cd Simulation && dotnet run
最終結(jié)果:
[圖片上傳失敗...(image-c915c-1662870789547)]
車輛的進入退出信息都會實時捕獲到,如果有超速信息,會通過郵件發(fā)送。
[圖片上傳失敗...(image-878039-1662870789547)]
寫在最后
可視化版本
cd .\VisualSimulation\ && dotnet run
[圖片上傳失敗...(image-cee7e8-1662870789547)]
視頻可參見: https://live.csdn.net/v/238245
rabbitmq管理界面
默認賬號密碼都是guest。
[圖片上傳失敗...(image-db9747-1662870789547)]
公眾號
更多內(nèi)容,歡迎關(guān)注我的微信公眾號: 半夏之夜的無情劍客。
[圖片上傳失敗...(image-a6573f-1662870789547)]