Ver código fonte

sample complete

shayne_lo 4 meses atrás
commit
49d31551dd
34 arquivos alterados com 2169 adições e 0 exclusões
  1. 30 0
      .dockerignore
  2. 63 0
      .gitattributes
  3. 261 0
      .gitignore
  4. 66 0
      DEV_Build.ps1
  5. 34 0
      Dockerfile
  6. 25 0
      EVCB_OCPP.DBAPI.sln
  7. 12 0
      EVCB_OCPP.DBAPI/ConnectionFactory/ISqliteConnectionConnectionFactory.cs
  8. 45 0
      EVCB_OCPP.DBAPI/ConnectionFactory/SqliteConnectionFactory.cs
  9. 19 0
      EVCB_OCPP.DBAPI/Controllers/HomeController.cs
  10. 27 0
      EVCB_OCPP.DBAPI/Controllers/MemDbController.cs
  11. 59 0
      EVCB_OCPP.DBAPI/Controllers/ServerMessageController.cs
  12. BIN
      EVCB_OCPP.DBAPI/DLL/EVCB_OCPP.Domain.dll
  13. 29 0
      EVCB_OCPP.DBAPI/Dockerfile
  14. 35 0
      EVCB_OCPP.DBAPI/EVCB_OCPP.DBAPI.csproj
  15. 141 0
      EVCB_OCPP.DBAPI/Helper/GroupHandler.cs
  16. 146 0
      EVCB_OCPP.DBAPI/Helper/GroupHandlerIO.cs
  17. 29 0
      EVCB_OCPP.DBAPI/Jobs/AddJobsExtention.cs
  18. 35 0
      EVCB_OCPP.DBAPI/Jobs/ServerMessageJob.cs
  19. 6 0
      EVCB_OCPP.DBAPI/Models/DBContext/FileDBContext.cs
  20. 6 0
      EVCB_OCPP.DBAPI/Models/DBContext/MemDBContext.cs
  21. 41 0
      EVCB_OCPP.DBAPI/Program.cs
  22. 47 0
      EVCB_OCPP.DBAPI/Properties/launchSettings.json
  23. 72 0
      EVCB_OCPP.DBAPI/Services/DbService/MainDbService.cs
  24. 144 0
      EVCB_OCPP.DBAPI/Services/MemDbService.cs
  25. 13 0
      EVCB_OCPP.DBAPI/Services/ServerMessageServices/IServerMessageService.cs
  26. 257 0
      EVCB_OCPP.DBAPI/Services/ServerMessageServices/MemDbServerMessageService.cs
  27. 156 0
      EVCB_OCPP.DBAPI/Services/ServerMessageServices/RedisServerMessageService.cs
  28. 205 0
      EVCB_OCPP.DBAPI/Services/ServerMessageServices/SourceDbServerMessageService.cs
  29. 58 0
      EVCB_OCPP.DBAPI/Startup.cs
  30. 8 0
      EVCB_OCPP.DBAPI/appsettings.Development.json
  31. 47 0
      EVCB_OCPP.DBAPI/appsettings.json
  32. 35 0
      Local_Build.ps1
  33. 6 0
      entrypoint.sh
  34. 12 0
      sshd_config

+ 30 - 0
.dockerignore

@@ -0,0 +1,30 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md
+!**/.gitignore
+!.git/HEAD
+!.git/config
+!.git/packed-refs
+!.git/refs/heads/**

+ 63 - 0
.gitattributes

@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=false
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain

+ 261 - 0
.gitignore

@@ -0,0 +1,261 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc

+ 66 - 0
DEV_Build.ps1

@@ -0,0 +1,66 @@
+# 設定 ASCII 藝術字的內容
+$asciiArt = @"
+  _____  ________      ________ _      ____  _____  __  __ ______ _   _ _______ 
+ |  __ \|  ____\ \    / /  ____| |    / __ \|  __ \|  \/  |  ____| \ | |__   __|
+ | |  | | |__   \ \  / /| |__  | |   | |  | | |__) | \  / | |__  |  \| |  | |   
+ | |  | |  __|   \ \/ / |  __| | |   | |  | |  ___/| |\/| |  __| | . ` |  | |   
+ | |__| | |____   \  /  | |____| |___| |__| | |    | |  | | |____| |\  |  | |   
+ |_____/|______|   \/   |______|______\____/|_|    |_|  |_|______|_| \_|  |_|   
+                                                                                
+                                                                                
+"@
+
+# 顯示 ASCII 藝術字
+Write-Host $asciiArt
+
+#第一次建立專案請先設定ACR Name
+$registryname="evdevcontainerregistry"
+$fullregistryname="evdevcontainerregistry.azurecr.io"
+#第一次建立專案請先設定專案名稱
+$imagerepositoryname="dbapi"
+$dev_prefix = "dbapi_test_"
+
+$username = az account show --query user.name
+$username = $username.TrimStart("""").Split('@')[0]
+
+$tagname= $dev_prefix + $username
+
+$fulltag=$fullregistryname+"/"+$imagerepositoryname+":"+$tagname
+$imagename = $imagerepositoryname+":"+$tagname
+
+
+$response = read-host  "please confirm that what you are currently uploading is a test version[ $fulltag ]. (y/n)"
+
+
+if ($response -eq "y") {
+ write-host "upload processing....."
+
+
+ #解除image鎖定
+ az acr repository update --name $registryname --image $imagename --delete-enabled true --write-enabled true
+
+ $ssha = git rev-parse --short head
+
+ Write-Host "ACR Login....."
+ $token = az acr login --name $registryname --expose-token --output tsv --query accessToken
+ $user = "00000000-0000-0000-0000-000000000000"
+ podman login $fullregistryname -u $user -p $token
+ 
+ #wite ssha to file
+ $ssha | Out-File gitcommit
+ 
+ podman build ./ -t  $fulltag --label [gitcommit=$ssha,author=$username]
+ podman push $fulltag
+ 
+ #remove ssha file
+ Remove-Item gitcommit
+
+ #鎖定image
+ az acr repository update --name $registryname --image $imagename --delete-enabled false --write-enabled false
+} else {
+ write-host "please modify the parameters with scripts."
+}
+
+
+
+

+ 34 - 0
Dockerfile

@@ -0,0 +1,34 @@
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
+WORKDIR /app
+EXPOSE 80
+EXPOSE 443
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends dialog \
+    && apt-get install -y --no-install-recommends openssh-server \
+	&& apt-get install -y tcpdump\
+	&& mkdir -p /run/sshd \
+    && echo "root:Docker!" | chpasswd 
+	
+COPY sshd_config /etc/ssh/sshd_config
+
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+WORKDIR /src
+COPY ["EVCB_OCPP.DBAPI/EVCB_OCPP.DBAPI.csproj", "EVCB_OCPP.DBAPI/"]
+RUN dotnet restore "EVCB_OCPP.DBAPI/EVCB_OCPP.DBAPI.csproj"
+COPY . .
+WORKDIR "/src/EVCB_OCPP.DBAPI"
+RUN dotnet build "EVCB_OCPP.DBAPI.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "EVCB_OCPP.DBAPI.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+
+COPY entrypoint.sh .
+RUN chmod +x /app/entrypoint.sh
+CMD ["/app/entrypoint.sh"]

+ 25 - 0
EVCB_OCPP.DBAPI.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.11.35327.3
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EVCB_OCPP.DBAPI", "EVCB_OCPP.DBAPI\EVCB_OCPP.DBAPI.csproj", "{84DF0932-DCEC-43F0-B16B-C8740DBE55A2}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{84DF0932-DCEC-43F0-B16B-C8740DBE55A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{84DF0932-DCEC-43F0-B16B-C8740DBE55A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{84DF0932-DCEC-43F0-B16B-C8740DBE55A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{84DF0932-DCEC-43F0-B16B-C8740DBE55A2}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {65FD4A87-2AFE-4216-9FC1-F3ABB6D58B3D}
+	EndGlobalSection
+EndGlobal

+ 12 - 0
EVCB_OCPP.DBAPI/ConnectionFactory/ISqliteConnectionConnectionFactory.cs

@@ -0,0 +1,12 @@
+using Microsoft.Data.SqlClient;
+using Microsoft.Data.Sqlite;
+using System.Data.Common;
+
+namespace EVCB_OCPP.DBAPI.ConnectionFactory;
+
+public interface ISqliteConnectionConnectionFactory<T> where T : class
+{
+    string ConnectionString { get; init; }
+    SqliteConnection Create();
+    Task<SqliteConnection> CreateAsync();
+}

+ 45 - 0
EVCB_OCPP.DBAPI/ConnectionFactory/SqliteConnectionFactory.cs

@@ -0,0 +1,45 @@
+using Microsoft.Data.SqlClient;
+using Microsoft.Data.Sqlite;
+using System.Data.Common;
+using System.Diagnostics;
+
+namespace EVCB_OCPP.DBAPI.ConnectionFactory;
+
+public class SqliteConnectionFactory<T> : ISqliteConnectionConnectionFactory<T> where T : class
+{
+    private readonly ILogger<SqliteConnectionFactory<T>> logger;
+
+    public required string ConnectionString { get; init; }
+    public SqliteConnectionFactory(ILogger<SqliteConnectionFactory<T>> logger)
+    {
+        this.logger = logger;
+    }
+
+    public SqliteConnection Create()
+    {
+        var sqlConnection = new SqliteConnection(ConnectionString);
+        sqlConnection.Open();
+        return sqlConnection;
+    }
+
+    public async Task<SqliteConnection> CreateAsync()
+    {
+        var timer = Stopwatch.StartNew();
+        long t0, t1;
+
+        var connectionStringBuilder = new SqliteConnectionStringBuilder(ConnectionString) {};
+        var sqlConnection = new SqliteConnection(connectionStringBuilder.ToString());
+        t0 = timer.ElapsedMilliseconds;
+
+        await sqlConnection.OpenAsync();
+        t1 = timer.ElapsedMilliseconds;
+        timer.Stop();
+
+        if (t1 > 500)
+        {
+            logger.LogWarning("{type} SqlConnection Open slow {create}/{open}", typeof(T), t0, t1);
+        }
+
+        return sqlConnection;
+    }
+}

+ 19 - 0
EVCB_OCPP.DBAPI/Controllers/HomeController.cs

@@ -0,0 +1,19 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace EVCB_OCPP.DBAPI.Controllers
+{
+    public class HomeController : Controller
+    {
+        public IActionResult Index()
+        {
+            string sshaString = "";
+
+            if (System.IO.File.Exists("ssha"))
+            {
+                sshaString = System.IO.File.ReadAllText("ssha");
+            }
+            return Ok($"Git commit:{sshaString}");
+            //return View();
+        }
+    }
+}

+ 27 - 0
EVCB_OCPP.DBAPI/Controllers/MemDbController.cs

@@ -0,0 +1,27 @@
+using EVCB_OCPP.DBAPI.Services;
+using Microsoft.AspNetCore.Mvc;
+
+namespace EVCB_OCPP.DBAPI.Controllers;
+
+[Route("MemDb")]
+public class MemDbController : Controller
+{
+    private readonly MemDbService memDbService;
+
+    public MemDbController(MemDbService memDbService)
+    {
+        this.memDbService = memDbService;
+    }
+
+    public IActionResult Index()
+    {
+        return Ok("MemDb");
+    }
+
+    [HttpGet("Usage")]
+    public async Task<IActionResult> GetMemoryUsage()
+    {
+        var result = await memDbService.GetMemoryUsage();
+        return Ok(result);
+    }
+}

+ 59 - 0
EVCB_OCPP.DBAPI/Controllers/ServerMessageController.cs

@@ -0,0 +1,59 @@
+using EVCB_OCPP.DBAPI.Services.ServerMessageServices;
+using Microsoft.AspNetCore.Mvc;
+
+namespace EVCB_OCPP.DBAPI.Controllers
+{
+    [Route("ServerMessage")]
+    public class ServerMessageController : Controller
+    {
+        private readonly IServerMessageService serverMessageService;
+
+        public ServerMessageController(IServerMessageService serverMessageService)
+        {
+            this.serverMessageService = serverMessageService;
+        }
+
+        [HttpGet]
+        public async Task<IActionResult> GetAllServerMessages()
+        {
+            var result = await serverMessageService.GetServerMessages();
+            return Json(result);
+        }
+
+        [HttpGet("waiting")]
+        public async Task<IActionResult> GetWaitingServerMessages()
+        {
+            var result = await serverMessageService.GetNeedSendToClientServerMessages();
+            return Json(result);
+        }
+
+        [HttpPost]
+        public async Task<IActionResult> AddServerMessage(
+            string ChargeBoxId, 
+            string OutAction,
+            string OutRequest,
+            string CreatedBy,
+            DateTime? CreatedOn = null,
+            string SerialNo = "", 
+            string InMessage = "",
+            CancellationToken token = default)
+        {
+            var result = await serverMessageService.AddServerMessage(ChargeBoxId, OutAction, OutRequest, CreatedBy, CreatedOn, SerialNo, InMessage, token);
+            return !string.IsNullOrEmpty(result) ? Ok(result) : BadRequest();
+        }
+
+        [HttpPut("evse")]
+        public async Task<IActionResult> SetServerMessageResponseReceived(int id, string InMessage = "", DateTime ReceivedOn = default)
+        {
+            var result = await serverMessageService.SetServerMessageResponseReceived(id, InMessage, ReceivedOn);
+            return result ? Ok() : BadRequest();
+        }
+
+        [HttpPut("server")]
+        public async Task<IActionResult> SetServerMessageServerHandling(int id, DateTime UpdatedOn = default)
+        {
+            var result = await serverMessageService.SetServerMessageServerHandling(id, UpdatedOn);
+            return result ? Ok() : BadRequest();
+        }
+    }
+}

BIN
EVCB_OCPP.DBAPI/DLL/EVCB_OCPP.Domain.dll


+ 29 - 0
EVCB_OCPP.DBAPI/Dockerfile

@@ -0,0 +1,29 @@
+# 請參閱 https://aka.ms/customizecontainer 了解如何自訂您的偵錯容器,以及 Visual Studio 如何使用此 Dockerfile 來組建您的映像,以加快偵錯速度。
+
+# 此階段用於以快速模式從 VS 執行時 (偵錯設定的預設值)
+FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
+WORKDIR /app
+EXPOSE 80
+EXPOSE 443
+
+
+# 此階段是用來組建服務專案
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+ARG BUILD_CONFIGURATION=Release
+WORKDIR /src
+COPY ["EVCB_OCPP.DBAPI/EVCB_OCPP.DBAPI.csproj", "EVCB_OCPP.DBAPI/"]
+RUN dotnet restore "./EVCB_OCPP.DBAPI/EVCB_OCPP.DBAPI.csproj"
+COPY . .
+WORKDIR "/src/EVCB_OCPP.DBAPI"
+RUN dotnet build "./EVCB_OCPP.DBAPI.csproj" -c $BUILD_CONFIGURATION -o /app/build
+
+# 此階段可用來發佈要複製到最終階段的服務專案
+FROM build AS publish
+ARG BUILD_CONFIGURATION=Release
+RUN dotnet publish "./EVCB_OCPP.DBAPI.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
+
+# 此階段用於生產環境,或以一般模式從 VS 執行時 (未使用偵錯設定時的預設值)
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "EVCB_OCPP.DBAPI.dll"]

+ 35 - 0
EVCB_OCPP.DBAPI/EVCB_OCPP.DBAPI.csproj

@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UserSecretsId>ed831747-82da-474a-bf8c-91dfbcddb2f1</UserSecretsId>
+    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Dapper" Version="2.1.35" />
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.20" />
+    <PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.20" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.20" />
+    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
+    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
+    <PackageReference Include="NLog.Web.AspNetCore" Version="5.3.14" />
+    <PackageReference Include="Quartz.Extensions.Hosting" Version="3.13.1" />
+    <PackageReference Include="ServiceStack.Redis" Version="8.4.0" />
+    <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Folder Include="DLL\" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="EVCB_OCPP.Domain">
+      <HintPath>DLL\EVCB_OCPP.Domain.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+</Project>

+ 141 - 0
EVCB_OCPP.DBAPI/Helper/GroupHandler.cs

@@ -0,0 +1,141 @@
+using Microsoft.IdentityModel.Tokens;
+using System.Collections.Concurrent;
+using System.Diagnostics;
+
+namespace EVCB_OCPP.DBAPI.Helper;
+
+public class GroupHandler<T>
+{
+    public GroupHandler(
+        Func<BundleHandlerData<T>, Task> handleFunc,
+        ILogger logger, int workerCnt = 1, int maxRetry = 10)
+    {
+        this.handleFunc = handleFunc;
+        this.logger = logger;
+
+        this.maxRetry = maxRetry;
+        workersLock = new SemaphoreSlim(workerCnt);
+        //singleWorkLock = new(_WorkerCnt);
+    }
+
+    private readonly Func<BundleHandlerData<T>, Task> handleFunc;
+    private readonly ILogger logger;
+    private readonly int maxRetry;
+    private readonly ConcurrentQueue<WaitParam<T>> waitList = new();
+
+    private SemaphoreSlim workersLock;// = new SemaphoreSlim(1);
+
+    public async Task HandleAsync(T param)
+    {
+        var waitData = new WaitParam<T>() { Data = param, Waiter = new SemaphoreSlim(0), Exception = null };
+        waitList.Enqueue(waitData);
+        TryStartHandler();
+        await waitData.Waiter.WaitAsync();
+        if (waitData.Exception is not null)
+        {
+            throw waitData.Exception;
+        }
+    }
+
+    private void TryStartHandler()
+    {
+        if (!workersLock.Wait(0))
+        {
+            return;
+        }
+
+        if (waitList.Count == 0)
+        {
+            workersLock.Release();
+            return;
+        }
+
+        _ = StartHandleTask();
+    }
+
+    private async Task StartHandleTask()
+    {
+        var timer = Stopwatch.StartNew();
+        List<long> times = new();
+
+        var requests = new List<WaitParam<T>>();
+
+        while (waitList.TryDequeue(out var handle))
+        {
+            requests.Add(handle);
+        }
+        times.Add(timer.ElapsedMilliseconds);
+
+        int cnt = 0;
+        Exception lastException = null;
+        var datas = requests.Select(x => x.Data).ToList();
+
+        for (; cnt < maxRetry; cnt++)
+        {
+            var bundleHandledata = new BundleHandlerData<T>(datas);
+            try
+            {
+                await handleFunc(bundleHandledata);
+            }
+            catch (Exception e)
+            {
+                lastException = e;
+            }
+
+            var completedRequests = requests.Where(x => bundleHandledata.CompletedDatas.Contains(x.Data)).ToList();
+            foreach (var request in completedRequests)
+            {
+                request.Waiter.Release();
+            }
+
+            datas = datas.Except(bundleHandledata.CompletedDatas).ToList();
+
+            if (datas.IsNullOrEmpty())
+            {
+                break;
+            }
+            logger.LogError(lastException?.Message);
+            times.Add(timer.ElapsedMilliseconds);
+        }
+
+        var uncompletedRequests = requests.Where(x => datas.Contains(x.Data)).ToList();
+        foreach (var request in uncompletedRequests)
+        {
+            request.Exception = lastException;
+            request.Waiter.Release();
+        }
+        workersLock.Release();
+
+        timer.Stop();
+        if (timer.ElapsedMilliseconds > 1000)
+        {
+            logger.LogWarning($"StartHandleTask {string.Join("/", times)}");
+        }
+
+        TryStartHandler();
+    }
+}
+
+public class BundleHandlerData<T>
+{
+    public List<T> Datas { get; set; }
+    public List<T> CompletedDatas { get; set; }
+
+    public BundleHandlerData(List<T> datas)
+    {
+        Datas = datas;
+        CompletedDatas = new();
+    }
+
+    public void AddCompletedData(T competedData)
+    {
+        CompletedDatas.Add(competedData);
+    }
+}
+
+internal class WaitParam<T>
+{
+    public T Data { get; init; }
+    public SemaphoreSlim Waiter { get; init; }
+    public Exception Exception { get; set; }
+}

+ 146 - 0
EVCB_OCPP.DBAPI/Helper/GroupHandlerIO.cs

@@ -0,0 +1,146 @@
+using System.Collections.Concurrent;
+using System.Diagnostics;
+
+namespace EVCB_OCPP.DBAPI.Helper;
+
+public class GroupHandler<TI, TO> where TI : class
+{
+    public GroupHandler(
+        Func<BundleHandlerData<TI, TO>, Task> handleFunc,
+        ILogger logger, int workerCnt = 1, int maxRetry = 10)
+    {
+        this.handleFunc = handleFunc;
+        this.logger = logger;
+
+        this.maxRetry = maxRetry;
+        this.workersLock = new SemaphoreSlim(workerCnt);
+        //singleWorkLock = new(_WorkerCnt);
+    }
+
+    private readonly Func<BundleHandlerData<TI, TO>, Task> handleFunc;
+    private readonly ILogger logger;
+    private readonly int maxRetry;
+    private readonly ConcurrentQueue<WaitParam<TI, TO>> waitList = new();
+
+    private readonly SemaphoreSlim workersLock;// = new SemaphoreSlim(1);
+
+    public async Task<TO> HandleAsync(TI param, CancellationToken token = default)
+    {
+        var waitData = new WaitParam<TI, TO>() { Data = param, Waiter = new SemaphoreSlim(0), Exception = null };
+        waitList.Enqueue(waitData);
+        TryStartHandler();
+        await waitData.Waiter.WaitAsync(token);
+        if (waitData.Exception is not null)
+        {
+            throw waitData.Exception;
+        }
+        return waitData.Result;
+    }
+
+    private void TryStartHandler()
+    {
+        if (!workersLock.Wait(0))
+        {
+            return;
+        }
+
+        if (waitList.Count == 0)
+        {
+            workersLock.Release();
+            return;
+        }
+
+        _ = StartHandleTask();
+    }
+
+    private async Task StartHandleTask()
+    {
+        var timer = Stopwatch.StartNew();
+        List<long> times = new();
+
+        var requests = new List<WaitParam<TI, TO>>();
+
+        while (waitList.TryDequeue(out var handle))
+        {
+            requests.Add(handle);
+        }
+        times.Add(timer.ElapsedMilliseconds);
+
+        int cnt = 0;
+        Exception lastException = null;
+        var datas = requests.Select(x => x.Data).ToList();
+
+        for (; cnt < maxRetry; cnt++)
+        {
+            var bundleHandledata = new BundleHandlerData<TI, TO>(datas);
+            try
+            {
+                await handleFunc(bundleHandledata);
+            }
+            catch (Exception e)
+            {
+                lastException = e;
+            }
+
+            var completedKeys = bundleHandledata.CompletedDatas.Select(x => x.Key).ToList();
+            var completedRequests = requests.Where(x => completedKeys.Any(y => y == x.Data)).ToList();
+            foreach (var request in completedRequests)
+            {
+                var result = bundleHandledata.CompletedDatas.FirstOrDefault(x => x.Key == request.Data);
+                request.Result = result.Value;
+                request.Waiter.Release();
+            }
+
+            //datas = datas.Except(bundleHandledata.CompletedDatas).ToList();
+            datas = datas.Where(x => !completedKeys.Contains(x)).ToList();
+
+            if (datas == null || datas.Count == 0)
+            {
+                break;
+            }
+            logger?.LogError(lastException?.Message);
+            times.Add(timer.ElapsedMilliseconds);
+        }
+
+        var uncompletedRequests = requests.Where(x => datas.Contains(x.Data)).ToList();
+        foreach (var request in uncompletedRequests)
+        {
+            request.Exception = lastException;
+            request.Waiter.Release();
+        }
+        workersLock.Release();
+
+        timer.Stop();
+        if (timer.ElapsedMilliseconds > 1000)
+        {
+            logger?.LogWarning($"StartHandleTask {string.Join("/", times)}");
+        }
+
+        TryStartHandler();
+    }
+}
+
+public class BundleHandlerData<TI, TO>
+{
+    public List<TI> Datas { get; set; }
+    public List<KeyValuePair<TI, TO>> CompletedDatas { get; set; }
+
+    public BundleHandlerData(List<TI> datas)
+    {
+        Datas = datas;
+        CompletedDatas = new();
+    }
+
+    public void AddCompletedData(TI competedData, TO result)
+    {
+        CompletedDatas.Add(new KeyValuePair<TI, TO>(competedData, result)); ;
+    }
+}
+
+internal class WaitParam<TI, TO>
+{
+    public TI Data { get; init; }
+    public TO Result { get; set; }
+    public SemaphoreSlim Waiter { get; init; }
+    public Exception Exception { get; set; }
+}

+ 29 - 0
EVCB_OCPP.DBAPI/Jobs/AddJobsExtention.cs

@@ -0,0 +1,29 @@
+using Quartz;
+
+namespace EVCB_OCPP.DBAPI.Jobs
+{
+    public static class AddJobsExtention
+    {
+        public static IServiceCollection AddJobs(this IServiceCollection services)
+        {
+            services.AddQuartz(q =>
+            {
+                q.UseMicrosoftDependencyInjectionJobFactory();
+
+                q.ScheduleJob<ServerMessageJob>(trigger =>
+                   trigger
+                   .WithIdentity("ServerMessageJobTrigger")
+                   .StartNow()
+                    .WithSimpleSchedule(x => x
+                        .WithInterval(TimeSpan.FromSeconds(10))
+                        .RepeatForever())
+               );
+            });
+            services.AddQuartzHostedService(opt =>
+            {
+                opt.WaitForJobsToComplete = true;
+            });
+            return services;
+        }
+    }
+}

+ 35 - 0
EVCB_OCPP.DBAPI/Jobs/ServerMessageJob.cs

@@ -0,0 +1,35 @@
+using EVCB_OCPP.DBAPI.Services.ServerMessageServices;
+using Quartz;
+
+namespace EVCB_OCPP.DBAPI.Jobs;
+
+[DisallowConcurrentExecution]
+public class ServerMessageJob : IJob
+{
+    public ServerMessageJob(
+        IServerMessageService serverMessageService,
+        ILogger<ServerMessageJob> logger)
+    {
+        this.serverMessageService = serverMessageService;
+        this.logger = logger;
+    }
+    private readonly IServerMessageService serverMessageService;
+    private readonly ILogger<ServerMessageJob> logger;
+
+    public async Task Execute(IJobExecutionContext context)
+    {
+        try
+        {
+            await ExecuteTrigger();
+        }
+        catch (Exception ex)
+        {
+            logger.LogError("ServerMessageTrigger  Ex:{0}", ex.ToString());
+        }
+    }
+
+    private Task ExecuteTrigger()
+    {
+        return serverMessageService.SaveCompletedMessageToDb();
+    }
+}

+ 6 - 0
EVCB_OCPP.DBAPI/Models/DBContext/FileDBContext.cs

@@ -0,0 +1,6 @@
+namespace EVCB_OCPP.DBAPI.Models.DBContext
+{
+    public class FileDBContext
+    {
+    }
+}

+ 6 - 0
EVCB_OCPP.DBAPI/Models/DBContext/MemDBContext.cs

@@ -0,0 +1,6 @@
+namespace EVCB_OCPP.DBAPI.Models.DBContext
+{
+    public class MemDBContext
+    {
+    }
+}

+ 41 - 0
EVCB_OCPP.DBAPI/Program.cs

@@ -0,0 +1,41 @@
+using EVCB_OCPP.DBAPI;
+using EVCB_OCPP.DBAPI.Jobs;
+using EVCB_OCPP.DBAPI.Services;
+using EVCB_OCPP.DBAPI.Services.DbService;
+using EVCB_OCPP.DBAPI.Services.ServerMessageServices;
+using EVCB_OCPP.Domain.Extensions;
+using NLog.Extensions.Logging;
+using NLog.Web;
+using ServiceStack.Redis;
+using SQLitePCL;
+
+raw.SetProvider(new SQLite3Provider_e_sqlite3());
+
+var builder = Host.CreateDefaultBuilder(args)
+    .ConfigureLogging((context, loggingBuilder) => {
+        loggingBuilder.ClearProviders();
+        NLog.LogManager.Configuration = new NLogLoggingConfiguration(context.Configuration.GetSection("NLog"));
+    })
+    .UseNLog()
+    .ConfigureWebHostDefaults(webBuilder =>
+    {
+        webBuilder.UseStartup<Startup>();
+        webBuilder.ConfigureKestrel(serverOptions =>
+        {
+            serverOptions.Limits.MaxRequestBodySize = null;
+        });
+    })
+    .ConfigureServices((context, services) => {
+        services.AddSingleton<IRedisClientsManager>(new RedisManagerPool(context.Configuration["RedisConnectionString"]));
+
+        services.AddJobs();
+
+        services.AddMainDbContext(context.Configuration);
+        services.AddTransient<IMainDbService, MainDbService>();
+
+        services.AddTransient<IServerMessageService, SourceDbServerMessageService>();
+        services.AddMemDbService();
+    });
+
+var app = builder.Build();
+await app.RunAsync();

+ 47 - 0
EVCB_OCPP.DBAPI/Properties/launchSettings.json

@@ -0,0 +1,47 @@
+{
+  "profiles": {
+    "http": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      },
+      "dotnetRunMessages": true,
+      "applicationUrl": "http://localhost:5088"
+    },
+    "https": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      },
+      "dotnetRunMessages": true,
+      "applicationUrl": "https://localhost:7109;http://localhost:5088"
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "Container (Dockerfile)": {
+      "commandName": "Docker",
+      "launchBrowser": true,
+      "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
+      "environmentVariables": {
+        "ASPNETCORE_URLS": "https://+:443;http://+:80"
+      },
+      "publishAllPorts": true,
+      "useSSL": true
+    }
+  },
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:63318",
+      "sslPort": 44321
+    }
+  }
+}

+ 72 - 0
EVCB_OCPP.DBAPI/Services/DbService/MainDbService.cs

@@ -0,0 +1,72 @@
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.Models.MainDb;
+using Microsoft.Extensions.Logging;
+using EVCB_OCPP.DBAPI.Helper;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore;
+using System.Diagnostics.CodeAnalysis;
+
+namespace EVCB_OCPP.DBAPI.Services.DbService;
+
+public interface IMainDbService
+{
+    Task<string> AddServerMessage(ServerMessage message, CancellationToken token = default);
+}
+
+public class MainDbService : IMainDbService
+{
+    public MainDbService(
+        ISqlConnectionFactory<MainDBContext> sqlConnectionFactory,
+        IDbContextFactory<MainDBContext> contextFactory,
+        ILogger<MainDbService> logger,
+        ILoggerFactory loggerFactory)
+    {
+        this.sqlConnectionFactory = sqlConnectionFactory;
+        this.contextFactory = contextFactory;
+        this.logger = logger;
+        this.loggerFactory = loggerFactory;
+
+        InitAddServerMessageHandler();
+    }
+    private readonly IDbContextFactory<MainDBContext> contextFactory;
+    private readonly ILogger<MainDbService> logger;
+    private readonly ILoggerFactory loggerFactory;
+    private readonly ISqlConnectionFactory<MainDBContext> sqlConnectionFactory;
+    private GroupHandler<ServerMessage, string> addServerMessageHandler;
+
+    public Task<string> AddServerMessage(ServerMessage message, CancellationToken token = default)
+    {
+        message.Id = 0;
+        return addServerMessageHandler.HandleAsync(message, token);
+    }
+
+    [MemberNotNull(nameof(addServerMessageHandler))]
+    private void InitAddServerMessageHandler()
+    {
+        if (addServerMessageHandler is not null)
+        {
+            throw new Exception($"{nameof(InitAddServerMessageHandler)} should only called once");
+        }
+
+        addServerMessageHandler = new GroupHandler<ServerMessage, string>(
+            handleFunc: BundleAddServerMessage,
+            logger: loggerFactory.CreateLogger("AddServerMessageHandler"));
+    }
+
+    private async Task BundleAddServerMessage(BundleHandlerData<ServerMessage, string> bundleHandlerData)
+    {
+        using var db = await contextFactory.CreateDbContextAsync();
+        using var trans = await db.Database.BeginTransactionAsync();
+
+        foreach (var message in bundleHandlerData.Datas)
+        {
+            await db.ServerMessage.AddAsync(message);
+        }
+
+        await db.SaveChangesAsync();
+        await trans.CommitAsync();
+
+        bundleHandlerData.CompletedDatas.AddRange(bundleHandlerData.Datas.Select(x => new KeyValuePair<ServerMessage, string>(x, x.SerialNo)));
+    }
+}

+ 144 - 0
EVCB_OCPP.DBAPI/Services/MemDbService.cs

@@ -0,0 +1,144 @@
+using Dapper;
+using EVCB_OCPP.DBAPI.ConnectionFactory;
+using EVCB_OCPP.DBAPI.Models.DBContext;
+using EVCB_OCPP.DBAPI.Services.ServerMessageServices;
+using Microsoft.Data.Sqlite;
+using Microsoft.Extensions.DependencyInjection;
+using Newtonsoft.Json;
+using SQLitePCL;
+using System.Diagnostics;
+
+namespace EVCB_OCPP.DBAPI.Services
+{
+    public static class MemDbServiceExtentions
+    {
+        public const string BackupFileName = "SqlLiteBackup.db";
+
+        public static IServiceCollection AddMemConnectionFactory(this IServiceCollection services)
+        {
+            services.AddSingleton<ISqliteConnectionConnectionFactory<MemDBContext>>(
+            (serviceProvider) =>
+            {
+                return new SqliteConnectionFactory<MemDBContext>(serviceProvider.GetRequiredService<ILogger<SqliteConnectionFactory<MemDBContext>>>())
+                {
+                    ConnectionString = "Data Source=InMemory;Mode=Memory;Cache=Shared"
+                };
+            });
+            
+            return services;
+        }
+
+        public static IServiceCollection AddFileCacheConnectionFactory(this IServiceCollection services)
+        {
+            services.AddSingleton<ISqliteConnectionConnectionFactory<FileDBContext>>(
+            (serviceProvider) =>
+            {
+                var safePath = GetSafeFolderPath(serviceProvider.GetRequiredService<IConfiguration>());
+                var backupFilePath = Path.Combine(safePath, BackupFileName);
+                return new SqliteConnectionFactory<FileDBContext>(serviceProvider.GetRequiredService<ILogger<SqliteConnectionFactory<FileDBContext>>>())
+                {
+                    ConnectionString = string.Format("Data Source= '{0}';", backupFilePath)
+                };
+            });
+            
+            return services;
+        }
+
+        public static IServiceCollection AddMemDbService(this IServiceCollection services)
+        {
+            services.AddFileCacheConnectionFactory();
+            services.AddMemConnectionFactory();
+            services.AddSingleton<MemDbService>();
+            services.AddHostedService<MemDbService>( x => x.GetRequiredService<MemDbService>());
+            return services;
+        }
+
+        internal static string GetSafeFolderPath(IConfiguration configuration)
+        {
+            var configSafeFolderPath = configuration["SafeFolderPath"];
+            return string.IsNullOrEmpty(configSafeFolderPath) ? Directory.GetCurrentDirectory() : configSafeFolderPath;
+        }
+    }
+
+    public class MemDbService : IHostedService
+    {
+        public MemDbService(
+            IConfiguration configuration,
+            ISqliteConnectionConnectionFactory<MemDBContext> memDbConnectionFactory,
+            ISqliteConnectionConnectionFactory<FileDBContext> fileDbConnectionFactory,
+            ILogger<MemDbService> logger
+            )
+        {
+            this.memDbConnectionFactory = memDbConnectionFactory;
+            this.fileDbConnectionFactory = fileDbConnectionFactory;
+            this.logger = logger;
+        }
+
+        private readonly ISqliteConnectionConnectionFactory<MemDBContext> memDbConnectionFactory;
+        private readonly ISqliteConnectionConnectionFactory<FileDBContext> fileDbConnectionFactory;
+        private readonly ILogger<MemDbService> logger;
+        private SqliteConnection? memConnectionCache = null;
+
+        public async Task StartAsync(CancellationToken cancellationToken)
+        {
+            Stopwatch stopwatch = Stopwatch.StartNew();
+
+            memConnectionCache = await memDbConnectionFactory.CreateAsync();
+
+            await LoadBackupedData();
+            await InitializeDbAsync();
+
+            stopwatch.Stop();
+            logger.LogDebug("MemDbService StartAsync cost {time} ms", stopwatch.ElapsedMilliseconds);
+        }
+
+        public async Task StopAsync(CancellationToken cancellationToken)
+        {
+            Stopwatch stopwatch = Stopwatch.StartNew();
+
+            await SaveDbToFileAsync();
+            await memConnectionCache!.DisposeAsync();
+
+            stopwatch.Stop();
+            logger.LogDebug("MemDbService StopAsync cost {time} ms", stopwatch.ElapsedMilliseconds);
+        }
+
+        public async Task<string> GetMemoryUsage()
+        {
+            var memoryUsed = raw.sqlite3_memory_used();
+            return $"Memory used by SQLite (in bytes): {memoryUsed}";
+
+            var cmd0 = """
+                PRAGMA page_size
+                """;
+            var cmd1 = """
+                PRAGMA page_count
+                """;
+
+            var connection = await memDbConnectionFactory.CreateAsync();
+            var page_size = await connection.QueryFirstOrDefaultAsync<int>(cmd0);
+            var page_count = await connection.QueryFirstOrDefaultAsync<int>(cmd1);
+            return $"{page_count}x{page_size}={page_count* page_size} (bytes):";
+        }
+
+        private async Task InitializeDbAsync()
+        {
+            var connection = await memDbConnectionFactory.CreateAsync();
+            await MemDbServerMessageService.IinitMemDbAsync(connection);
+        }
+
+        private async Task SaveDbToFileAsync()
+        {
+            using var memConnection = await memDbConnectionFactory.CreateAsync();
+            using var fileConnection = await fileDbConnectionFactory.CreateAsync();
+            memConnection.BackupDatabase(fileConnection);
+        }
+
+        private async Task LoadBackupedData()
+        {
+            using var memConnection = await memDbConnectionFactory.CreateAsync();
+            using var fileConnection = await fileDbConnectionFactory.CreateAsync();
+            fileConnection.BackupDatabase(memConnection);
+        }
+    }
+}

+ 13 - 0
EVCB_OCPP.DBAPI/Services/ServerMessageServices/IServerMessageService.cs

@@ -0,0 +1,13 @@
+using EVCB_OCPP.Domain.Models.MainDb;
+
+namespace EVCB_OCPP.DBAPI.Services.ServerMessageServices;
+
+public interface IServerMessageService
+{
+    Task<string> AddServerMessage(string ChargeBoxId, string OutAction, string OutRequest, string CreatedBy, DateTime? CreatedOn = null, string SerialNo = "", string InMessage = "", CancellationToken token = default);
+    Task<List<ServerMessage>> GetNeedSendToClientServerMessages();
+    Task<bool> SetServerMessageResponseReceived(int id, string InMessage = "", DateTime ReceivedOn = default);
+    Task<bool> SetServerMessageServerHandling(int id, DateTime UpdatedOn = default);
+    Task<List<ServerMessage>> GetServerMessages();
+    Task SaveCompletedMessageToDb();
+}

+ 257 - 0
EVCB_OCPP.DBAPI/Services/ServerMessageServices/MemDbServerMessageService.cs

@@ -0,0 +1,257 @@
+using Dapper;
+using EVCB_OCPP.DBAPI.ConnectionFactory;
+using EVCB_OCPP.DBAPI.Models.DBContext;
+using EVCB_OCPP.DBAPI.Services.DbService;
+using EVCB_OCPP.Domain.Models.MainDb;
+using Microsoft.Data.Sqlite;
+using Newtonsoft.Json;
+using System.Data;
+
+namespace EVCB_OCPP.DBAPI.Services.ServerMessageServices;
+
+public class MemDbServerMessageService : IServerMessageService
+{
+    public MemDbServerMessageService(
+        ISqliteConnectionConnectionFactory<MemDBContext> memDbConnectionFactory,
+        IMainDbService mainDbService,
+        ILogger<MemDbServerMessageService> logger)
+    {
+        this.memDbConnectionFactory = memDbConnectionFactory;
+        this.mainDbService = mainDbService;
+        this.logger = logger;
+    }
+    private readonly ISqliteConnectionConnectionFactory<MemDBContext> memDbConnectionFactory;
+    private readonly IMainDbService mainDbService;
+    private readonly ILogger<MemDbServerMessageService> logger;
+
+    public async Task<string> AddServerMessage(
+        string ChargeBoxId,
+        string OutAction,
+        string OutRequest,
+        string CreatedBy,
+        DateTime? CreatedOn = null,
+        string SerialNo = "",
+        string InMessage = "",
+        CancellationToken token = default)
+    {
+        if (string.IsNullOrEmpty(SerialNo))
+        {
+            SerialNo = Guid.NewGuid().ToString();
+        }
+        var _CreatedOn = CreatedOn ?? DateTime.UtcNow;
+
+        string _OutRequest = OutRequest is not null ? OutRequest : "";
+
+        var data = new ServerMessage()
+        {
+            ChargeBoxId = ChargeBoxId,
+            CreatedBy = CreatedBy,
+            CreatedOn = _CreatedOn,
+            OutAction = OutAction,
+            OutRequest = _OutRequest,
+            SerialNo = SerialNo,
+            InMessage = InMessage
+        };
+        await AddServerMessage(data, token: token);
+        return SerialNo;
+    }
+
+    public async Task<List<ServerMessage>> GetNeedSendToClientServerMessages()
+    {
+        DateTime dt = new DateTime(1991, 1, 1);
+        DateTime dateTimeNow = DateTime.UtcNow;
+        DateTime startDt = dateTimeNow.AddSeconds(-30);
+
+        var parms = new DynamicParameters();
+        parms.Add("ReceivedOn", dt);
+        parms.Add("UpdatedOn", dt);
+        parms.Add("CreatedOnStart", startDt);
+        parms.Add("CreatedOnEnd", dateTimeNow);
+
+        var cmd = """
+            SELECT * FROM ServerMessage
+            WHERE
+            ReceivedOn = @ReceivedOn and
+            UpdatedOn = @UpdatedOn and
+            CreatedOn >= @CreatedOnStart and
+            CreatedOn <= @CreatedOnEnd
+            """;
+
+        using var connection = await memDbConnectionFactory.CreateAsync();
+        var datas = await connection.QueryAsync<ServerMessage>(cmd, parms);
+        return datas.ToList();
+    }
+
+    public async Task<bool> SetServerMessageResponseReceived(int id, string InMessage = "", DateTime ReceivedOn = default)
+    {
+        DateTime _ReceivedOn = ReceivedOn == default ? DateTime.UtcNow : ReceivedOn;
+
+        var parms = new DynamicParameters();
+        parms.Add("Id", id);
+        parms.Add("InMessage", InMessage);
+        parms.Add("ReceivedOn", _ReceivedOn);
+
+        var cmd = """
+            UPDATE ServerMessage
+            SET InMessage = @InMessage , ReceivedOn = @ReceivedOn
+            WHERE Id = @Id
+            """;
+
+        using var connection = await memDbConnectionFactory.CreateAsync();
+        var cnts = await connection.ExecuteAsync(cmd, parms);
+        var success = cnts > 0;
+        if (!success)
+        {
+            logger.LogError("SetServerMessageResponseReceived failed {msg}", InMessage);
+        }
+        return success;
+    }
+
+    public async Task<bool> SetServerMessageServerHandling(int id, DateTime UpdatedOn = default)
+    {
+        DateTime _UpdatedOn = UpdatedOn == default ? DateTime.UtcNow : UpdatedOn;
+
+        var parms = new DynamicParameters();
+        parms.Add("Id", id);
+        parms.Add("UpdatedOn", _UpdatedOn);
+
+        var cmd = """
+            UPDATE ServerMessage
+            SET UpdatedOn = @UpdatedOn
+            WHERE Id = @Id
+            """;
+
+        using var connection = await memDbConnectionFactory.CreateAsync();
+        var cnts = await connection.ExecuteAsync(cmd, parms);
+        var success = cnts > 0;
+        if (!success)
+        {
+            logger.LogError("SetServerMessageServerHandling failed {msgid}", id);
+        }
+        return success;
+    }
+
+    public async Task<List<ServerMessage>> GetServerMessages()
+    {
+        string cmd = """
+            SELECT * FROM ServerMessage
+            """;
+        using var connection = await memDbConnectionFactory.CreateAsync();
+        var datas = await connection.QueryAsync<ServerMessage>(cmd);
+        return datas.ToList();
+    }
+
+    public static async Task IinitMemDbAsync(SqliteConnection connection)
+    {
+        var createTableResult = await connection.ExecuteAsync("""
+            CREATE TABLE IF NOT EXISTS ServerMessage (
+            Id INTEGER PRIMARY KEY NOT NULL,
+            SerialNo VARCHAR(36),
+            OutAction VARCHAR(30),
+            OutRequest TEXT,
+            InMessage TEXT,
+            CreatedOn DATE TIME NOT NULL,
+            CreatedBy VARCHAR(36),
+            ReceivedOn DATE TIME NOT NULL,
+            ChargeBoxId VARCHAR(50),
+            UpdatedOn DATE TIME NOT NULL
+            )
+            """);
+    }
+
+    public async Task SaveCompletedMessageToDb()
+    {
+        List<ServerMessage> completedMessages = await GetCompletedServerMessageFromMemDb();
+        List<Task<int>> addServerMessageTasks = new List<Task<int>>();
+        foreach (var msg in completedMessages)
+        {
+            addServerMessageTasks.Add(AsyncAddServerMessage(msg));
+        }
+        await Task.WhenAll(addServerMessageTasks);
+        var addedServerMessageId = addServerMessageTasks.Select(x => x.Result).Where(x => x > 0).ToList();
+        var removeResult = await RmoveSavedServerMessageFromMemDb(addedServerMessageId);
+    }
+
+    private async Task<int> AsyncAddServerMessage(ServerMessage message)
+    {
+        var memMessageId = message.Id;
+        var addServerMessageResult = await mainDbService.AddServerMessage(message);
+        if (!string.IsNullOrEmpty(addServerMessageResult))
+        {
+            return memMessageId;
+        }
+        return -1;
+    }
+
+    private Task<string> AddServerMessage(ServerMessage message, CancellationToken token = default)
+    {
+        return AddServerMessageDapper(message);
+    }
+
+    private async Task<string> AddServerMessageDapper(ServerMessage message)
+    {
+        var parameters = new DynamicParameters();
+        parameters.Add("@SerialNo", message.SerialNo, DbType.String, ParameterDirection.Input, 36);
+        parameters.Add("@OutAction", message.OutAction, DbType.String, ParameterDirection.Input, 30);
+        parameters.Add("@OutRequest", message.OutRequest, DbType.String, ParameterDirection.Input);
+        parameters.Add("@InMessage", message.InMessage, DbType.String, ParameterDirection.Input);
+        parameters.Add("@CreatedOn", message.CreatedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@CreatedBy", message.CreatedBy, DbType.String, ParameterDirection.Input, 36);
+        parameters.Add("@ReceivedOn", message.ReceivedOn, DbType.DateTime, ParameterDirection.Input);
+        parameters.Add("@ChargeBoxId", message.ChargeBoxId, DbType.String, ParameterDirection.Input, 30);
+        parameters.Add("@UpdatedOn", message.UpdatedOn, DbType.DateTime, ParameterDirection.Input);
+
+        using var conn = await memDbConnectionFactory.CreateAsync();
+        var resultCnt = await conn.ExecuteAsync("""
+        INSERT INTO ServerMessage
+        (SerialNo, OutAction, OutRequest, InMessage, CreatedOn, CreatedBy, ReceivedOn, ChargeBoxId, UpdatedOn)
+        VALUES (@SerialNo, @OutAction, @OutRequest, @InMessage, @CreatedOn, @CreatedBy, @ReceivedOn, @ChargeBoxId, @UpdatedOn)
+        """, parameters);
+        if (resultCnt != 1)
+        {
+            logger.LogError("AddServerMessageDapper failed msg:{msg}", JsonConvert.SerializeObject(message));
+            return string.Empty;
+        }
+        return message.SerialNo;
+    }
+
+    private async Task<List<ServerMessage>> GetCompletedServerMessageFromMemDb()
+    {
+        DateTime dateTimeNow = DateTime.UtcNow;
+        DateTime startDt = dateTimeNow.AddSeconds(-30);
+        DateTime dt = new DateTime(1991, 1, 1);
+
+        var cmd = """
+            SELECT * FROM ServerMessage
+            WHERE ReceivedOn != @NullDateTime or CreatedOn < @CreatedOnStart
+            """;
+
+        var parm = new DynamicParameters();
+        parm.Add("NullDateTime", dt);
+        parm.Add("CreatedOnStart", startDt);
+
+        using var connection = await memDbConnectionFactory.CreateAsync();
+        var datas = await connection.QueryAsync<ServerMessage>(cmd, parm);
+        return datas.ToList();
+    }
+
+    private async Task<bool> RmoveSavedServerMessageFromMemDb(List<int> ids)
+    {
+        var cmd = """
+            DELETE FROM ServerMessage
+            WHERE Id in @Ids
+            """;
+
+        var parm = new DynamicParameters();
+        parm.Add("Ids", ids);
+
+        using var connection = await memDbConnectionFactory.CreateAsync();
+        var datasCnt = await connection.ExecuteAsync(cmd, parm);
+        var success = datasCnt == ids.Count;
+        if (!success)
+        {
+            logger.LogError("RmoveSavedServerMessageFromMemDb failed {ids}", JsonConvert.SerializeObject(ids));
+        }
+        return datasCnt == ids.Count;
+    }
+}

+ 156 - 0
EVCB_OCPP.DBAPI/Services/ServerMessageServices/RedisServerMessageService.cs

@@ -0,0 +1,156 @@
+using EVCB_OCPP.DBAPI.Services.DbService;
+using EVCB_OCPP.Domain.Models.MainDb;
+using ServiceStack;
+using ServiceStack.Redis;
+
+namespace EVCB_OCPP.DBAPI.Services.ServerMessageServices;
+
+public class RedisServerMessageService : IServerMessageService
+{
+    private readonly IMainDbService mainDbService;
+    private readonly IRedisClientsManager redisClientsManager;
+
+    public RedisServerMessageService(
+        IMainDbService mainDbService,
+        IRedisClientsManager redisClientsManager
+        )
+    {
+        this.mainDbService = mainDbService;
+        this.redisClientsManager = redisClientsManager;
+    }
+
+    public async Task<string> AddServerMessage(
+        string ChargeBoxId,
+        string OutAction,
+        string OutRequest,
+        string CreatedBy,
+        DateTime? CreatedOn = null,
+        string SerialNo = "",
+        string InMessage = "",
+        CancellationToken token = default)
+    {
+        if (string.IsNullOrEmpty(SerialNo))
+        {
+            SerialNo = Guid.NewGuid().ToString();
+        }
+        var _CreatedOn = CreatedOn ?? DateTime.UtcNow;
+
+        string _OutRequest = OutRequest is not null ? OutRequest : "";
+
+        var data = new ServerMessage()
+        {
+            ChargeBoxId = ChargeBoxId,
+            CreatedBy = CreatedBy,
+            CreatedOn = _CreatedOn,
+            OutAction = OutAction,
+            OutRequest = _OutRequest,
+            SerialNo = SerialNo,
+            InMessage = InMessage
+        };
+
+        await StoreIntoRedis(data);
+
+        return SerialNo;
+    }
+
+    public async Task<List<ServerMessage>> GetNeedSendToClientServerMessages()
+    {
+        var allServerMessage = await GetServerMessages();
+        DateTime startDt = DateTime.UtcNow.AddSeconds(-30);
+
+        var seperatedServerMessage = allServerMessage.GroupBy(x => x.CreatedOn < startDt).ToDictionary(x => x.Key, x => x?.ToList());
+        var deprecatedServerMessage = seperatedServerMessage.GetValueOrDefault(true, null);
+
+        if (deprecatedServerMessage is not null)
+        {
+            var deprecatedServerMessageIds = deprecatedServerMessage.Select(x => x.Id);
+            await RemoveFromRedis(deprecatedServerMessageIds);
+
+            foreach (var servermessage in deprecatedServerMessage)
+            {
+                _ = AddServerMessageToDbAsync(servermessage);
+            }
+        }
+
+        return seperatedServerMessage.GetValueOrDefault(false, new List<ServerMessage>())!;
+    }
+
+    public async Task<bool> SetServerMessageResponseReceived(int id, string InMessage = "", DateTime ReceivedOn = default)
+    {
+        DateTime _ReceivedOn = ReceivedOn == default ? DateTime.UtcNow : ReceivedOn;
+
+        IRedisClientAsync redisClient = await redisClientsManager.GetClientAsync();
+        var redisServerMessage = redisClient.As<ServerMessage>();
+        var msg = await redisServerMessage.GetByIdAsync(id);
+        await redisServerMessage.DeleteByIdAsync(id);
+        await redisClient.DisposeAsync();
+
+        msg.Id = 0;
+        msg.InMessage = InMessage;
+        msg.ReceivedOn = _ReceivedOn;
+
+        var addServerMessageResult = await mainDbService.AddServerMessage(msg);
+        return !string.IsNullOrEmpty(addServerMessageResult);
+    }
+
+    public async Task<bool> SetServerMessageServerHandling(int id, DateTime UpdatedOn = default)
+    {
+        DateTime _UpdatedOn = UpdatedOn == default ? DateTime.UtcNow : UpdatedOn;
+
+        IRedisClientAsync redisClient = await redisClientsManager.GetClientAsync();
+        var redisServerMessage = redisClient.As<ServerMessage>();
+        var msg = await redisServerMessage.GetByIdAsync(id);
+
+        msg.UpdatedOn = _UpdatedOn;
+
+        await redisServerMessage.StoreAsync(msg);
+        await redisClient.DisposeAsync();
+        return true;
+    }
+
+    public async Task<List<ServerMessage>> GetServerMessages()
+    {
+        IRedisClientAsync redisClient = await redisClientsManager.GetClientAsync();
+        var allMessages = await redisClient.As<ServerMessage>().GetAllAsync();
+        await redisClient.DisposeAsync();
+        return allMessages?.ToList();
+    }
+
+    public Task SaveCompletedMessageToDb()
+    {
+        return Task.CompletedTask;
+    }
+
+    private async Task<int> AddServerMessageToDbAsync(ServerMessage message)
+    {
+        var memMessageId = message.Id;
+        var addServerMessageResult = await mainDbService.AddServerMessage(message);
+        if (!string.IsNullOrEmpty(addServerMessageResult))
+        {
+            return memMessageId;
+        }
+        return -1;
+    }
+
+    private async Task StoreIntoRedis(ServerMessage data)
+    {
+        await using IRedisClientAsync redisClient = await redisClientsManager.GetClientAsync();
+        var redisServerMessage = redisClient.As<ServerMessage>();
+        data.Id = (int)await redisServerMessage.GetNextSequenceAsync();
+        await redisServerMessage.StoreAsync(data);
+    }
+
+    private async Task RemoveFromRedis(int id)
+    {
+        await using IRedisClientAsync redisClient = await redisClientsManager.GetClientAsync();
+        var redisServerMessage = redisClient.As<ServerMessage>();
+        await redisServerMessage.DeleteByIdAsync(id);
+    }
+
+    private async Task RemoveFromRedis(IEnumerable<int> ids)
+    {
+        await using IRedisClientAsync redisClient = await redisClientsManager.GetClientAsync();
+        var redisServerMessage = redisClient.As<ServerMessage>();
+        await redisServerMessage.DeleteByIdsAsync(ids);
+    }
+}

+ 205 - 0
EVCB_OCPP.DBAPI/Services/ServerMessageServices/SourceDbServerMessageService.cs

@@ -0,0 +1,205 @@
+using Dapper;
+using EVCB_OCPP.DBAPI.Services.DbService;
+using EVCB_OCPP.Domain;
+using EVCB_OCPP.Domain.ConnectionFactory;
+using EVCB_OCPP.Domain.Models.MainDb;
+using Microsoft.EntityFrameworkCore;
+
+namespace EVCB_OCPP.DBAPI.Services.ServerMessageServices
+{
+    public class SourceDbServerMessageService : IServerMessageService
+    {
+        private readonly IMainDbService mainDbService;
+        private readonly ISqlConnectionFactory<MainDBContext> sqlConnectionFactory;
+
+        public SourceDbServerMessageService(
+            IMainDbService mainDbService, 
+            ISqlConnectionFactory<MainDBContext> sqlConnectionFactory)
+        {
+            this.mainDbService = mainDbService;
+            this.sqlConnectionFactory = sqlConnectionFactory;
+        }
+
+        public async Task<string> AddServerMessage(
+            string ChargeBoxId,
+            string OutAction,
+            string OutRequest,
+            string CreatedBy, 
+            DateTime? CreatedOn = null, 
+            string SerialNo = "", 
+            string InMessage = "", 
+            CancellationToken token = default)
+        {
+            if (string.IsNullOrEmpty(SerialNo))
+            {
+                SerialNo = Guid.NewGuid().ToString();
+            }
+            var _CreatedOn = CreatedOn ?? DateTime.UtcNow;
+
+            string _OutRequest = OutRequest is not null ? OutRequest : "";
+
+            var data = new ServerMessage()
+            {
+                ChargeBoxId = ChargeBoxId,
+                CreatedBy = CreatedBy,
+                CreatedOn = _CreatedOn,
+                OutAction = OutAction,
+                OutRequest = _OutRequest,
+                SerialNo = SerialNo,
+                InMessage = InMessage
+            };
+
+            await StoreIntoRTTable(data);
+
+            return SerialNo;
+        }
+
+        public async Task<List<ServerMessage>> GetNeedSendToClientServerMessages()
+        {
+            var allServerMessage = await GetServerMessages();
+            DateTime startDt = DateTime.UtcNow.AddSeconds(-30);
+
+            var seperatedServerMessage = allServerMessage.GroupBy(x => x.CreatedOn < startDt).ToDictionary(x => x.Key, x => x?.ToList());
+            var deprecatedServerMessage = seperatedServerMessage.GetValueOrDefault(true, null);
+
+            if (deprecatedServerMessage is not null)
+            {
+
+                foreach (var servermessage in deprecatedServerMessage)
+                {
+                    _ = RemoveFromRTTable(servermessage.Id);
+                    _ = AddServerMessageToDbAsync(servermessage);
+                }
+            }
+
+            return seperatedServerMessage.GetValueOrDefault(false, new List<ServerMessage>())!;
+        }
+
+        public Task<List<ServerMessage>> GetServerMessages()
+        {
+            return GetServerMessagesFromRTTable();
+        }
+
+        public Task SaveCompletedMessageToDb()
+        {
+            return Task.CompletedTask;
+        }
+
+        public async Task<bool> SetServerMessageResponseReceived(int id, string InMessage = "", DateTime ReceivedOn = default)
+        {
+            DateTime _ReceivedOn = ReceivedOn == default ? DateTime.UtcNow : ReceivedOn;
+
+            ServerMessage msg = await GetServerMessageFromRTTable(id);
+            if (msg is null)
+            {
+                return false;
+            }
+            await RemoveFromRTTable(id);
+
+            msg.Id = 0;
+            msg.InMessage = InMessage;
+            msg.ReceivedOn = _ReceivedOn;
+
+            var sn = await mainDbService.AddServerMessage(msg);
+            return string.IsNullOrEmpty(sn);
+        }
+
+        public Task<bool> SetServerMessageServerHandling(int id, DateTime UpdatedOn = default)
+        {
+            DateTime _UpdatedOn = UpdatedOn == default ? DateTime.UtcNow : UpdatedOn;
+            return SetServerMessageServerHandlingInRTTable(id, _UpdatedOn);
+        }
+
+        private async Task<int> AddServerMessageToDbAsync(ServerMessage message)
+        {
+            var memMessageId = message.Id;
+            var addServerMessageResult = await mainDbService.AddServerMessage(message);
+            if (!string.IsNullOrEmpty(addServerMessageResult))
+            {
+                return memMessageId;
+            }
+            return -1;
+        }
+
+        private async Task<bool> StoreIntoRTTable(ServerMessage data)
+        {
+            var cmd = """
+                INSERT INTO RTServerMessage ([SerialNo],[OutAction],[OutRequest],[InMessage],[CreatedOn],[CreatedBy],[ReceivedOn],[ChargeBoxId],[UpdatedOn])
+                VALUES (@SerialNo,@OutAction,@OutRequest,@InMessage,@CreatedOn,@CreatedBy,@ReceivedOn,@ChargeBoxId,@UpdatedOn)
+                """;
+
+            var pams = new DynamicParameters();
+            pams.Add("SerialNo", data.SerialNo, System.Data.DbType.String, size: 36);
+            pams.Add("OutAction", data.OutAction, System.Data.DbType.String, size:30);
+            pams.Add("OutRequest", data.OutRequest, System.Data.DbType.String);
+            pams.Add("InMessage", data.InMessage, System.Data.DbType.String);
+            pams.Add("CreatedOn", data.CreatedOn, System.Data.DbType.DateTime);
+            pams.Add("CreatedBy", data.CreatedBy, System.Data.DbType.String,size:36);
+            pams.Add("ReceivedOn", data.ReceivedOn, System.Data.DbType.DateTime);
+            pams.Add("ChargeBoxId", data.ChargeBoxId, System.Data.DbType.String, size:50);
+            pams.Add("UpdatedOn", data.UpdatedOn, System.Data.DbType.DateTime, size:50);
+
+            using var con = await sqlConnectionFactory.CreateAsync();
+            var cnts = await con.ExecuteAsync(cmd, pams);
+            return cnts > 0;
+        }
+
+        private async Task<List<ServerMessage>> GetServerMessagesFromRTTable()
+        {
+            var cmd = """
+                SELECT * FROM RTServerMessage
+                """;
+            using var con = await sqlConnectionFactory.CreateAsync();
+            var datas = await con.QueryAsync<ServerMessage>(cmd);
+            return datas?.ToList();
+        }
+
+        private async Task<ServerMessage> GetServerMessageFromRTTable(int id)
+        {
+            var cmd = """
+                SELECT * 
+                FROM RTServerMessage
+                WHERE [Id] = @Id
+                """;
+
+            var pams = new DynamicParameters();
+            pams.Add("Id", id, System.Data.DbType.Int32);
+
+            using var con = await sqlConnectionFactory.CreateAsync();
+            var data = await con.QueryFirstOrDefaultAsync<ServerMessage>(cmd, pams);
+            return data;
+        }
+
+        private async Task<bool> RemoveFromRTTable(int id)
+        {
+            var cmd = """
+                DELETE FROM RTServerMessage
+                WHERE [Id] = @Id
+                """;
+
+            var pams = new DynamicParameters();
+            pams.Add("Id", id, System.Data.DbType.Int32);
+
+            using var con = await sqlConnectionFactory.CreateAsync();
+            var cnts = await con.ExecuteAsync(cmd, pams);
+            return cnts > 0;
+        }
+
+        private async Task<bool> SetServerMessageServerHandlingInRTTable(int id, DateTime updatedOn)
+        {
+            var cmd = """
+                UPDATE RTServerMessage
+                SET [UpdatedOn] = @UpdatedOn
+                WHERE [Id] = @Id
+                """;
+
+            var pams = new DynamicParameters();
+            pams.Add("Id", id, System.Data.DbType.Int32);
+            pams.Add("UpdatedOn", updatedOn, System.Data.DbType.DateTime);
+
+            using var con = await sqlConnectionFactory.CreateAsync();
+            var cnts = await con.ExecuteAsync(cmd, pams);
+            return cnts > 0;
+        }
+    }
+}

+ 58 - 0
EVCB_OCPP.DBAPI/Startup.cs

@@ -0,0 +1,58 @@
+using Newtonsoft.Json;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace EVCB_OCPP.DBAPI;
+
+public class Startup
+{
+    public Startup(IConfiguration configuration)
+    {
+        Configuration = configuration;
+    }
+
+    public IConfiguration Configuration { get; }
+
+    public void ConfigureServices(IServiceCollection services)
+    {
+        JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
+        {
+            NullValueHandling = NullValueHandling.Ignore,
+            DateFormatString = "yyyy-MM-dd'T'HH':'mm':'ss'Z'"
+        };
+
+        services.AddControllersWithViews()
+            .AddControllersAsServices()
+                // Newtonsoft.Json is added for compatibility reasons
+                // The recommended approach is to use System.Text.Json for serialization
+                // Visit the following link for more guidance about moving away from Newtonsoft.Json to System.Text.Json
+                // https://docs.microsoft.com/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to
+                .AddNewtonsoftJson(options =>
+                {
+                    options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
+                    options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.None;
+                    options.SerializerSettings.DateFormatString = "yyyy/MM/dd'T'HH':'mm':'ss'Z'";
+                    options.UseMemberCasing();
+                });
+    }
+
+    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+    {
+        if (env.IsDevelopment())
+        {
+            app.UseDeveloperExceptionPage();
+        }
+        else
+        {
+            app.UseExceptionHandler("/Home/Error");
+        }
+
+        app.UseRouting();
+        //app.UseAuthorization();
+        app.UseEndpoints(endpoints =>
+        {
+            endpoints.MapControllerRoute(
+                name: "default",
+                pattern: "{controller=Home}/{action=Index}/{id?}");
+        });
+    }
+}

+ 8 - 0
EVCB_OCPP.DBAPI/appsettings.Development.json

@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}

+ 47 - 0
EVCB_OCPP.DBAPI/appsettings.json

@@ -0,0 +1,47 @@
+{
+  "RedisConnectionString": "localhost:6379",
+  "SafeFolderPath": "/home/",
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*",
+  "NLog": {
+    "targets": {
+      "async": true,
+      "f": {
+        "type": "File",
+        "keepFileOpen": false,
+        "fileName": "/home/logs/dbapi/${shortdate}.log",
+        "layout": "${longdate} ${uppercase:${level}} ${message}"
+      },
+      "Console": {
+        "type": "Console",
+        "layout": "${longdate} ${uppercase:${level}} ${callsite} ${message}"
+      }
+    },
+    "rules": [
+      {
+        "ruleName": "FileLog",
+        "logger": "EVCB_OCPP.*",
+        "minLevel": "Debug",
+        "writeTo": "f"
+      },
+      {
+        "ruleName": "ConsoleLog",
+        "logger": "EVCB_OCPP.*",
+        "minlevel": "Trace",
+        "writeTo": "console"
+      }
+    ]
+  },
+  "ConnectionStrings": {
+    "ConnectionLogDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_ConnectionLog;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=False;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;",
+    "MainDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_Main;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=False;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=1024;Connection Lifetime=0;Pooling=true;Min Pool Size=150;",
+    "MeterValueDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_MeterValue;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=False;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;",
+    "WebDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_Web;;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=False;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;",
+    "OnlineRecordDBContext": "data source=zerova-ev-dev.database.windows.net;initial catalog=StandardOCPP_OnlineRecord;persist security info=True;user id=azdevsoftware;password=1h52dev#az;MultipleActiveResultSets=False;App=EntityFramework;TrustServerCertificate=true;Max Pool Size=200;Connection Lifetime=0;Pooling=true;"
+  }
+}

+ 35 - 0
Local_Build.ps1

@@ -0,0 +1,35 @@
+# 設定 ASCII 藝術字的內容
+$asciiArt = @"
+  _____  ________      ________ _      ____  _____  __  __ ______ _   _ _______ 
+ |  __ \|  ____\ \    / /  ____| |    / __ \|  __ \|  \/  |  ____| \ | |__   __|
+ | |  | | |__   \ \  / /| |__  | |   | |  | | |__) | \  / | |__  |  \| |  | |   
+ | |  | |  __|   \ \/ / |  __| | |   | |  | |  ___/| |\/| |  __| | . ` |  | |   
+ | |__| | |____   \  /  | |____| |___| |__| | |    | |  | | |____| |\  |  | |   
+ |_____/|______|   \/   |______|______\____/|_|    |_|  |_|______|_| \_|  |_|   
+                                                                                
+                                                                                
+"@
+
+# 顯示 ASCII 藝術字
+Write-Host $asciiArt
+
+#第一次建立專案請先設定ACR Name
+$registryname="evdevcontainerregistry"
+$fullregistryname="evdevcontainerregistry.azurecr.io"
+#第一次建立專案請先設定專案名稱
+$imagerepositoryname="dbapi"
+$dev_prefix = "dbapi_test_8019"
+
+$tagname= "dbapi_test_8019"
+
+$fulltag=$fullregistryname+"/"+$imagerepositoryname+":"+$tagname
+$imagename = $imagerepositoryname+":"+$tagname
+
+ $ssha = git rev-parse --short head
+
+ #wite ssha to file
+ $ssha | Out-File gitcommit
+ 
+ podman build ./ -t  $fulltag --label [gitcommit=$ssha,author=$username]
+ #remove ssha file
+ Remove-Item gitcommit

+ 6 - 0
entrypoint.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+set -e
+service ssh start
+#dotnet run --project ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
+#/app/publish/EVCB_OCPP.WSServer
+exec dotnet EVCB_OCPP.DBAPI.dll

+ 12 - 0
sshd_config

@@ -0,0 +1,12 @@
+Port 			2222
+ListenAddress 		0.0.0.0
+LoginGraceTime 		180
+X11Forwarding 		yes
+Ciphers aes128-cbc,3des-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr
+MACs hmac-sha1,hmac-sha1-96
+StrictModes 		yes
+SyslogFacility 		DAEMON
+PasswordAuthentication 	yes
+PermitEmptyPasswords 	no
+PermitRootLogin 	yes
+Subsystem sftp internal-sftp