diff --git a/.gitea/workflows/push-dev.yaml b/.gitea/workflows/push-dev.yaml new file mode 100644 index 0000000..e48a700 --- /dev/null +++ b/.gitea/workflows/push-dev.yaml @@ -0,0 +1,107 @@ +name: 部署开发环境 + +on: + push: + branches: + - develop + +env: + IMAGE_NAME: ${{ gitea.repository_owner }}/marking.ocr.service + PROJECT_NAME: Marking.OCR.Service + PUBLISH_PATH: marking.ocr.service + PUBLISH_HOST: 192.168.2.9 + PUBLISH_PORT: 22 + PUBLISH_REPLICAS: 1 #启动副本数量 + ASPNETCORE_ENVIRONMENT: Development + +jobs: + job1: + name: 编译发布 + runs-on: linux-amd + + steps: + - name: 下载源码 + uses: actions/checkout@v3 + + - name: 使用缓存 + uses: actions/cache@master + with: + cache_type: nuget + mount: | + nuget + + - name: 编译后端项目 + uses: actions/aspnet@8.0 + with: + project_name: ${{ env.PROJECT_NAME }} + + - name: 打包上传镜像 + uses: actions/docker@master + with: + registry: ${{ secrets.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + image: ${{ env.IMAGE_NAME }} + context: publish + tags: latest,${{ gitea.ref_name }} + + - name: 生成环境变量文件 + env: + env_file: | + REGISTRY=${{ secrets.DOCKER_REGISTRY }} + OWNER=${{ gitea.repository_owner }} + TAG=${{ gitea.ref_name }} + ASPNETCORE_ENVIRONMENT=${{ env.ASPNETCORE_ENVIRONMENT }} + REPLICAS=${{ env.PUBLISH_REPLICAS }} + run: | + echo "$env_file">.env + + - name: 复制发布脚本 + uses: docker://appleboy/drone-scp + with: + host: ${{ env.PUBLISH_HOST }} + port: ${{ env.PUBLISH_PORT }} + username: ${{ secrets.PUBLISH_USER_NAME }} + password: ${{ secrets.PUBLISH_PASSWORD }} + target: ${{ env.PUBLISH_PATH }} + source: docker-swarm.yaml,.env + + - name: 部署到服务器 + uses: docker://appleboy/drone-ssh + with: + host: ${{ env.PUBLISH_HOST }} + port: ${{ env.PUBLISH_PORT }} + username: ${{ secrets.PUBLISH_USER_NAME }} + password: ${{ secrets.PUBLISH_PASSWORD }} + script: | + if docker service ls | grep -q marking-admin-api; then + docker service rm marking-admin-api + fi + + cd ${{ env.PUBLISH_PATH }} + + [ -f .env ] && export $(sed '/^#/d' .env) + + docker stack deploy -c docker-swarm.yaml --with-registry-auth mk + + - name: 发送构建通知 + if: ${{ always() }} + uses: actions/webhook@master + with: + urls: https://oapi.dingtalk.com/robot/send?access_token=6ddafcada8f44f4bad4a7314c4d9bd19a895ded0a1ba1afdaff5dd01a5af6781 + content_type: application/json + template: | + { + "msgtype": "markdown", + "markdown": { + "title":"项目部署通知", + "text": "${{ job.status == 'success' && '✅' || '❌' }}**${{ gitea.repository }}**\n + >**构建结果**: ${{ job.status }} + >**构建详情**: [点击查看](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }}) + >**代码分支**: ${{ gitea.ref_name }} + >**提交标识**: ${{ gitea.sha }} + >**提交发起**: ${{ gitea.actor }} + >**提交信息**: ${{ gitea.event.head_commit.message }}\n + " + } + } diff --git a/.gitea/workflows/push-master.yaml b/.gitea/workflows/push-master.yaml new file mode 100644 index 0000000..b98d799 --- /dev/null +++ b/.gitea/workflows/push-master.yaml @@ -0,0 +1,124 @@ +name: 部署生产环境 + +on: + push: + branches: + - master + +env: + IMAGE_NAME: ${{ gitea.repository_owner }}/marking.admin.api + PROJECT_NAME: src/Marking.Admin.Entry + PUBLISH_PATH: marking.admin.api + PUBLISH_HOST: 10.255.255.146,10.255.255.74 + PUBLISH_PORT: 10022 + PUBLISH_REPLICAS: 2 #启动副本数量 + ASPNETCORE_ENVIRONMENT: Production + +jobs: + release: + name: 发布版本 + runs-on: linux + + outputs: + version: ${{ steps.publish_version.outputs.version }} + + steps: + - name: 下载源码 + uses: actions/checkout@v3 + + - name: 发布版本 + id: publish_version + uses: actions/auto-release@master + + job1: + name: 编译发布 + runs-on: linux-amd + needs: release + + steps: + - name: 下载源码 + uses: actions/checkout@v3 + + - name: 使用缓存 + uses: actions/cache@master + with: + cache_type: nuget + mount: | + nuget + + - name: 编译后端项目 + uses: actions/aspnet@6.0 + with: + project_name: ${{ env.PROJECT_NAME }} + + - name: 打包上传镜像 + uses: actions/docker@master + with: + registry: ${{ secrets.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + image: ${{ env.IMAGE_NAME }} + context: publish + tags: latest,${{ gitea.ref_name }},${{ needs.release.outputs.version }} + + - name: 生成环境变量文件 + env: + env_file: | + REGISTRY=${{ secrets.DOCKER_REGISTRY }} + OWNER=${{ gitea.repository_owner }} + TAG=${{ needs.release.outputs.version }} + ASPNETCORE_ENVIRONMENT=${{ env.ASPNETCORE_ENVIRONMENT }} + REPLICAS=${{ env.PUBLISH_REPLICAS }} + run: | + echo "$env_file">.env + + - name: 复制发布脚本 + uses: docker://appleboy/drone-scp + with: + host: ${{ env.PUBLISH_HOST }} + port: ${{ env.PUBLISH_PORT }} + username: ${{ secrets.PUBLISH_USER_NAME }} + password: ${{ secrets.PUBLISH_PASSWORD }} + target: ${{ env.PUBLISH_PATH }} + source: docker-swarm.yaml,.env + + - name: 部署到服务器 + uses: docker://appleboy/drone-ssh + with: + host: ${{ env.PUBLISH_HOST }} + port: ${{ env.PUBLISH_PORT }} + username: ${{ secrets.PUBLISH_USER_NAME }} + password: ${{ secrets.PUBLISH_PASSWORD }} + script: | + if docker service ls | grep -q marking-admin-api; then + docker service rm marking-admin-api + fi + + cd ${{ env.PUBLISH_PATH }} + + [ -f .env ] && export $(sed '/^#/d' .env) + + docker stack deploy -c docker-swarm.yaml --with-registry-auth mk + + - name: 发送构建通知 + if: ${{ always() }} + uses: actions/webhook@master + with: + urls: https://oapi.dingtalk.com/robot/send?access_token=6ddafcada8f44f4bad4a7314c4d9bd19a895ded0a1ba1afdaff5dd01a5af6781 + content_type: application/json + template: | + { + "msgtype": "markdown", + "markdown": { + "title":"项目部署通知", + "text": "${{ job.status == 'success' && '✅' || '❌' }}**${{ gitea.repository }}**\n + >**构建结果**: ${{ job.status }} + >**构建详情**: [点击查看](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }}) + >**代码分支**: ${{ gitea.ref_name }} + >**发布版本**: ${{ needs.release.outputs.version }} + >**提交标识**: ${{ gitea.sha }} + >**提交发起**: ${{ gitea.actor }} + >**提交信息**: ${{ gitea.event.head_commit.message }}\n + " + } + } diff --git a/.gitea/workflows/push-staging.yaml b/.gitea/workflows/push-staging.yaml new file mode 100644 index 0000000..81aec0e --- /dev/null +++ b/.gitea/workflows/push-staging.yaml @@ -0,0 +1,107 @@ +name: 部署测试环境 + +on: + push: + branches: + - staging + +env: + IMAGE_NAME: ${{ gitea.repository_owner }}/marking.admin.api + PROJECT_NAME: src/Marking.Admin.Entry + PUBLISH_PATH: marking.admin.api + PUBLISH_HOST: 10.255.255.3 + PUBLISH_PORT: 22 + PUBLISH_REPLICAS: 3 #启动副本数量 + ASPNETCORE_ENVIRONMENT: Staging + +jobs: + job1: + name: 编译发布 + runs-on: linux-amd + + steps: + - name: 下载源码 + uses: actions/checkout@v3 + + - name: 使用缓存 + uses: actions/cache@master + with: + cache_type: nuget + mount: | + nuget + + - name: 编译后端项目 + uses: actions/aspnet@6.0 + with: + project_name: ${{ env.PROJECT_NAME }} + + - name: 打包上传镜像 + uses: actions/docker@master + with: + registry: ${{ secrets.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + image: ${{ env.IMAGE_NAME }} + context: publish + tags: latest,${{ gitea.ref_name }} + + - name: 生成环境变量文件 + env: + env_file: | + REGISTRY=${{ secrets.DOCKER_REGISTRY }} + OWNER=${{ gitea.repository_owner }} + TAG=${{ gitea.ref_name }} + ASPNETCORE_ENVIRONMENT=${{ env.ASPNETCORE_ENVIRONMENT }} + REPLICAS=${{ env.PUBLISH_REPLICAS }} + run: | + echo "$env_file">.env + + - name: 复制发布脚本 + uses: docker://appleboy/drone-scp + with: + host: ${{ env.PUBLISH_HOST }} + port: ${{ env.PUBLISH_PORT }} + username: ${{ secrets.PUBLISH_USER_NAME }} + password: ${{ secrets.PUBLISH_PASSWORD }} + target: ${{ env.PUBLISH_PATH }} + source: docker-swarm.yaml,.env + + - name: 部署到服务器 + uses: docker://appleboy/drone-ssh + with: + host: ${{ env.PUBLISH_HOST }} + port: ${{ env.PUBLISH_PORT }} + username: ${{ secrets.PUBLISH_USER_NAME }} + password: ${{ secrets.PUBLISH_PASSWORD }} + script: | + if docker service ls | grep -q marking-admin-api; then + docker service rm marking-admin-api + fi + + cd ${{ env.PUBLISH_PATH }} + + [ -f .env ] && export $(sed '/^#/d' .env) + + docker stack deploy -c docker-swarm.yaml --with-registry-auth mk + + - name: 发送构建通知 + if: ${{ always() }} + uses: actions/webhook@master + with: + urls: https://oapi.dingtalk.com/robot/send?access_token=6ddafcada8f44f4bad4a7314c4d9bd19a895ded0a1ba1afdaff5dd01a5af6781 + content_type: application/json + template: | + { + "msgtype": "markdown", + "markdown": { + "title":"项目部署通知", + "text": "${{ job.status == 'success' && '✅' || '❌' }}**${{ gitea.repository }}**\n + >**构建结果**: ${{ job.status }} + >**构建详情**: [点击查看](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }}) + >**代码分支**: ${{ gitea.ref_name }} + >**提交标识**: ${{ gitea.sha }} + >**提交发起**: ${{ gitea.actor }} + >**提交信息**: ${{ gitea.event.head_commit.message }}\n + " + } + } diff --git a/.gitea/workflows/release-version.yaml b/.gitea/workflows/release-version.yaml new file mode 100644 index 0000000..f181d72 --- /dev/null +++ b/.gitea/workflows/release-version.yaml @@ -0,0 +1,107 @@ +name: 部署生产环境 + +on: + release: + types: + - published + +env: + IMAGE_NAME: ${{ gitea.repository_owner }}/marking.admin.api + PROJECT_NAME: src/Marking.Admin.Entry + PUBLISH_PATH: marking.admin.api + PUBLISH_HOST: 10.255.255.146,10.255.255.74 + PUBLISH_PORT: 10022 + PUBLISH_REPLICAS: 3 #启动副本数量 + ASPNETCORE_ENVIRONMENT: Production + +jobs: + job1: + name: 编译发布 + runs-on: linux-amd + + steps: + - name: 下载源码 + uses: actions/checkout@v3 + + - name: 使用缓存 + uses: actions/cache@master + with: + cache_type: nuget + mount: | + nuget + + - name: 编译后端项目 + uses: actions/aspnet@6.0 + with: + project_name: ${{ env.PROJECT_NAME }} + + - name: 打包上传镜像 + uses: actions/docker@master + with: + registry: ${{ secrets.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + image: ${{ env.IMAGE_NAME }} + context: publish + tags: latest,${{ gitea.ref_name }} + + - name: 生成环境变量文件 + env: + env_file: | + REGISTRY=${{ secrets.DOCKER_REGISTRY }} + OWNER=${{ gitea.repository_owner }} + TAG=${{ gitea.ref_name }} + ASPNETCORE_ENVIRONMENT=${{ env.ASPNETCORE_ENVIRONMENT }} + REPLICAS=${{ env.PUBLISH_REPLICAS }} + run: | + echo "$env_file">.env + + - name: 复制发布脚本 + uses: docker://appleboy/drone-scp + with: + host: ${{ env.PUBLISH_HOST }} + port: ${{ env.PUBLISH_PORT }} + username: ${{ secrets.PUBLISH_USER_NAME }} + password: ${{ secrets.PUBLISH_PASSWORD }} + target: ${{ env.PUBLISH_PATH }} + source: docker-swarm.yaml,.env + + - name: 部署到服务器 + uses: docker://appleboy/drone-ssh + with: + host: ${{ env.PUBLISH_HOST }} + port: ${{ env.PUBLISH_PORT }} + username: ${{ secrets.PUBLISH_USER_NAME }} + password: ${{ secrets.PUBLISH_PASSWORD }} + script: | + if docker service ls | grep -q marking-admin-api; then + docker service rm marking-admin-api + fi + + cd ${{ env.PUBLISH_PATH }} + + [ -f .env ] && export $(sed '/^#/d' .env) + + docker stack deploy -c docker-swarm.yaml --with-registry-auth mk + + - name: 发送构建通知 + if: ${{ always() }} + uses: actions/webhook@master + with: + urls: https://oapi.dingtalk.com/robot/send?access_token=6ddafcada8f44f4bad4a7314c4d9bd19a895ded0a1ba1afdaff5dd01a5af6781 + content_type: application/json + template: | + { + "msgtype": "markdown", + "markdown": { + "title":"项目部署通知", + "text": "${{ job.status == 'success' && '✅' || '❌' }}**${{ gitea.repository }}**\n + >**构建结果**: ${{ job.status }} + >**构建详情**: [点击查看](${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_number }}) + >**代码分支**: ${{ gitea.ref_name }} + >**提交标识**: ${{ gitea.sha }} + >**提交发起**: ${{ gitea.actor }} + >**提交信息**: ${{ gitea.event.release.target_commitish }}\n + " + } + } diff --git a/Marking.OCR.Service/BackgroundServices/CheckAndDeleteFilesService.cs b/Marking.OCR.Service/BackgroundServices/CheckAndDeleteFilesService.cs new file mode 100644 index 0000000..289c608 --- /dev/null +++ b/Marking.OCR.Service/BackgroundServices/CheckAndDeleteFilesService.cs @@ -0,0 +1,47 @@ +using Marking.OCR.Service.Helper; +using System.Timers; + +namespace Marking.OCR.Service.BackgroundServices +{ + + public class CheckAndDeleteFilesService : BackgroundService + { + private static System.Timers.Timer timer; + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Console.Out.WriteLineAsync("定时删除jpg文件已开启!" + App.HostEnvironment.EnvironmentName); + + timer = new System.Timers.Timer(24 * 60 * 60 * 1000); // 设置定时器为24小时 + timer.Elapsed += new ElapsedEventHandler(CheckAndDeleteFiles); + timer.Start(); + + await Task.CompletedTask; + } + + private static void CheckAndDeleteFiles(object source, ElapsedEventArgs e) + { + Console.WriteLine("开始执行删除文件,当前时间:" + DateTime.Now); + if (!Directory.Exists(ImageHelper.FilePath)) + { + Directory.CreateDirectory(ImageHelper.FilePath); + } + + string folderPath = ImageHelper.FilePath; + string[] files = Directory.GetFiles(folderPath, "*.jpg"); + Console.WriteLine("图片总数:" + files.Length); + foreach (string file in files) + { + DateTime creationTime = File.GetCreationTime(file); + TimeSpan timeSpan = DateTime.Now - creationTime; + if (timeSpan.TotalDays > 1) // 超过一天直接删除,避免占用磁盘空间 + { + File.Delete(file); + } + } + } + + } + +} + + diff --git a/Marking.OCR.Service/Controllers/DrawTemplateController.cs b/Marking.OCR.Service/Controllers/DrawTemplateController.cs new file mode 100644 index 0000000..251bd9c --- /dev/null +++ b/Marking.OCR.Service/Controllers/DrawTemplateController.cs @@ -0,0 +1,72 @@ +using Marking.OCR.Service.DTO; +using Marking.OCR.Service.Helper; +using Microsoft.AspNetCore.Mvc; +using OpenCvSharp; + +namespace Marking.OCR.Service.Controllers +{ + [ApiController] + [Route("draw")] + public class DrawTemplateController : ControllerBase + { + + + private readonly ILogger _logger; + private readonly ImageHelper _imageHelper; + + public DrawTemplateController(ILogger logger, + ImageHelper imageHelper) + { + _logger = logger; + this._imageHelper = imageHelper; + } + + /// + /// ȡģͼλϢ + /// + /// + [HttpGet("position")] + public async Task GetPositionAsync([FromQuery] LocationPointQueryDto position) + { + using var src = await _imageHelper.DownloadImageAsync(position.ImageUrl); + if (src == null) + { + throw Oops.Oh("ͼƬʧܣ"); + } + // ͼƬС + Cv2.Resize(src, src, new Size(position.ImageWidth, position.ImageHeight)); + //_imageHelper.Show(src); + + // üͼƬ + using var img_roi = new Mat(src, new Rect(position.X, position.Y, position.Width, position.Height)); + //_imageHelper.Show(img_roi); + + // תҶͼƬ + Cv2.CvtColor(img_roi, img_roi, ColorConversionCodes.BGR2GRAY); + //_imageHelper.Show(img_roi); + + using var imgHandle = _imageHelper.ImagePreprocessing(img_roi); + + var locationPoints = _imageHelper.FindLocationPoint(imgHandle, false); + if (!locationPoints.Any()) + { + throw Oops.Oh("ѡΧδҵλ㣬¿ѡ"); + } + if (locationPoints.Count() > 1) + { + throw Oops.Oh("ѡΧʶ𵽶λ㣬¿ѡ"); + } + + var locationPoint = locationPoints.FirstOrDefault(); + return new LocationPointViewDto + { + X = locationPoint.X + position.X, + Y = locationPoint.Y + position.Y, + Width = locationPoint.Width, + Height = locationPoint.Height + }; + } + } + + +} diff --git a/Marking.OCR.Service/Controllers/WeatherForecastController.cs b/Marking.OCR.Service/Controllers/WeatherForecastController.cs deleted file mode 100644 index f4210b8..0000000 --- a/Marking.OCR.Service/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Marking.OCR.Service.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } - } - - public class WeatherForecast - { - public DateOnly Date { get; set; } - public int TemperatureC { get; set; } - public string Summary { get; set; } - } -} diff --git a/Marking.OCR.Service/DTO/LocationPointQueryDto.cs b/Marking.OCR.Service/DTO/LocationPointQueryDto.cs new file mode 100644 index 0000000..973bf63 --- /dev/null +++ b/Marking.OCR.Service/DTO/LocationPointQueryDto.cs @@ -0,0 +1,18 @@ +namespace Marking.OCR.Service.DTO +{ + public class LocationPointQueryDto + { + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + + public int Height { get; set; } + + public string ImageUrl { get; set; } + + public int ImageWidth { get; set; } + + public int ImageHeight { get; set; } + + } +} diff --git a/Marking.OCR.Service/DTO/LocationPointViewDto.cs b/Marking.OCR.Service/DTO/LocationPointViewDto.cs new file mode 100644 index 0000000..1ae9c23 --- /dev/null +++ b/Marking.OCR.Service/DTO/LocationPointViewDto.cs @@ -0,0 +1,12 @@ +namespace Marking.OCR.Service.DTO +{ + public class LocationPointViewDto + { + + public int X { get; set; } + public int Y { get; set; } + public int Width { get; set; } + + public int Height { get; set; } + } +} diff --git a/Marking.OCR.Service/Dockerfile b/Marking.OCR.Service/Dockerfile new file mode 100644 index 0000000..f4b592e --- /dev/null +++ b/Marking.OCR.Service/Dockerfile @@ -0,0 +1,13 @@ +#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 dotnet/aspnet:8.0 + +WORKDIR /app + +EXPOSE 80 + +COPY . . + +ENV TZ=Asia/Shanghai + +ENTRYPOINT ["dotnet", "Marking.OCR.Service.dll"] diff --git a/Marking.OCR.Service/Helper/ImageHelper.cs b/Marking.OCR.Service/Helper/ImageHelper.cs new file mode 100644 index 0000000..54536ef --- /dev/null +++ b/Marking.OCR.Service/Helper/ImageHelper.cs @@ -0,0 +1,154 @@ +using OpenCvSharp; +using OpenCvSharp.Internal.Vectors; +using Qwit; +using static System.Runtime.InteropServices.JavaScript.JSType; + +namespace Marking.OCR.Service.Helper +{ + public class ImageHelper : Qwit.IScopedDependency + { + private readonly IHttpClientFactory _httpClientFactory; + /// + /// 图片保存文件的文件夹地址 + /// + public static string FilePath => Path.Combine(Directory.GetCurrentDirectory(), "images"); + + public ImageHelper(IHttpClientFactory httpClientFactory) + { + this._httpClientFactory = httpClientFactory; + } + + /// + /// 下载图片,缓存本地一天 + /// + /// + /// + public async Task DownloadImageAsync(string url) + { + //判断文件夹是否存在 + if (!Directory.Exists(FilePath)) + { + Directory.CreateDirectory(FilePath); + } + //文件名 + var filename = $@"{url.GetMd5String()}.jpg"; + //保存地址+文件名 + var savePath = Path.Combine(FilePath, filename); + + if (File.Exists(savePath)) + { + // 文件已存在 + return Cv2.ImRead(savePath); + } + + // 文件不存在,需下载 + var finalUrl = (url.StartsWith("http:") || url.StartsWith("https:")) ? url : $@"http:{url}"; + + // 下载图片 + using var httpclient = _httpClientFactory.CreateClient(); + var response = await httpclient.GetAsync(finalUrl); + if (response == null || !response.IsSuccessStatusCode) + { + return null; + } + + var imageBytes = await response.Content.ReadAsByteArrayAsync(); + + // 将字节数组保存为文件 + await System.IO.File.WriteAllBytesAsync(savePath, imageBytes); + + // 文件已存在 + return Cv2.ImRead(savePath); + } + + + public void Show(Mat mat, string title = "") + { +#if !DEBUG + return; +#endif + + Cv2.ImShow(string.IsNullOrWhiteSpace(title) ? DateTime.UtcNow.ToString() : title, mat); + Cv2.WaitKey(0); + } + + /// + /// 图像预处理 + /// + public Mat ImagePreprocessing(Mat mat, double threshValue = 210) + { + // 中值滤波 + Cv2.MedianBlur(mat, mat, 5); + + //Show(mat, "MedianBlur"); + // 高斯滤波 + Cv2.GaussianBlur(mat, mat, new Size(3, 3), 0); + //Show(mat, "GaussianBlur"); + + // 二值化 + Cv2.Threshold(mat, mat, threshValue, 255, ThresholdTypes.BinaryInv); + //Show(mat, "Threshold"); + + // 侵蚀 + Cv2.Erode(mat, mat, Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3))); + //Show(mat, "Erode"); + + // 膨胀 + Cv2.Dilate(mat, mat, Cv2.GetStructuringElement(MorphShapes.Rect, new Size(3, 3))); + //Show(mat, "Dilate"); + + return mat; + } + + /// + /// 查找图片定位点 + /// + /// + /// + public IEnumerable FindLocationPoint(Mat mat, bool showOutline = false) + { + List rects = new List(); + // 寻找图片轮廓 + Cv2.FindContours(mat, out Point[][] contours, out HierarchyIndex[] index, RetrievalModes.List, ContourApproximationModes.ApproxSimple); + + foreach (var contour in contours) + { + if (showOutline) + Show(DrawContours(mat, contour)); + + //外接矩形 + var rect = Cv2.BoundingRect(contour); + + // 计算图像内非零像素个数 + using Mat roi = new Mat(mat, rect); + int nonZeroPixels = Cv2.CountNonZero(roi); + + // 计算连通轮廓的面积 + var area = Cv2.ContourArea(contour); + + // 计算逼近轮廓 + var approxContour = Cv2.ApproxPolyDP(contour, Cv2.ArcLength(contour, true) * 0.05, true); + + if (Cv2.IsContourConvex(approxContour) + && rect.Width > 10 && rect.Width < 80 + && rect.Height > 10 && rect.Height < 80 + && area > 200 && area < 3000 + && nonZeroPixels > 200) + { + rects.Add(rect); + } + } + return rects; + } + + public Mat DrawContours(Mat mat, Point[] contour) + { + //转换颜色空间 + Mat mat_color = new Mat(); + Cv2.CvtColor(mat, mat_color, ColorConversionCodes.GRAY2BGR); + + Cv2.DrawContours(mat_color, new Point[][] { contour }, -1, Scalar.Red, 2); + return mat_color; + } + } +} diff --git a/Marking.OCR.Service/Marking.OCR.Service.csproj b/Marking.OCR.Service/Marking.OCR.Service.csproj index 9daa180..759e7c7 100644 --- a/Marking.OCR.Service/Marking.OCR.Service.csproj +++ b/Marking.OCR.Service/Marking.OCR.Service.csproj @@ -7,6 +7,11 @@ + + + + + diff --git a/Marking.OCR.Service/Packs/AppPack.cs b/Marking.OCR.Service/Packs/AppPack.cs new file mode 100644 index 0000000..e015824 --- /dev/null +++ b/Marking.OCR.Service/Packs/AppPack.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using Qwit; + +namespace Marking.OCR.Service.Packs +{ + [DependsByPack(typeof(MarkingPack))] + internal class AppPack : QwitPack + { + public override void ConfigureServices(IServiceCollection services) + { + } + + } +} \ No newline at end of file diff --git a/Marking.OCR.Service/Program.cs b/Marking.OCR.Service/Program.cs index 146c658..bf3406c 100644 --- a/Marking.OCR.Service/Program.cs +++ b/Marking.OCR.Service/Program.cs @@ -1,14 +1,29 @@ +using Marking; +using Marking.OCR.Service.BackgroundServices; +using Qwit; + var builder = WebApplication.CreateBuilder(args); // Add services to the container. - -builder.Services.AddControllers(); +builder.Services.AddQwitPack(); +builder.Services.AddControllers(options => +{ + // ȫ쳣ڴд try catch + options.Filters.Add(); + // ȫģ͸ֵĬֵ ͳһظʽ + options.Filters.Add(); + // ȫص½ + //options.Filters.Add(); + //ȫ־׷ٹ + options.Filters.Add(); +}); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +builder.Services.AddHostedService(); var app = builder.Build(); - +app.UseQwitPack(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { diff --git a/Marking.OCR.Service/appsettings.json b/Marking.OCR.Service/appsettings.json index d7435f9..84abf58 100644 --- a/Marking.OCR.Service/appsettings.json +++ b/Marking.OCR.Service/appsettings.json @@ -6,5 +6,17 @@ "ServerNodes": "http://config.23544.com:15000/,http://config.23544.com:15001/", //多个节点使用逗号分隔, "Tag": "marking", "Name": "阅卷后台管理" + }, + "QwitLogServer": { + "ServerUrl": "https://log.w.23544.com:8843", + "PushKey": "XTPsAhViiJ7rR7/bGM5vH756rD+Uejy8tiuWK/7qEAWRsVj0JqLWm6rpvDWgT322", + "CheckServer": false + }, + "WebPack": { + "Services": [ + "Marking.Infrastructure", + "Marking.Domain", + "Marking.WebExtensions" + ] } } diff --git a/Marking.OCR.Service/images/5113aa79559ec019a986cad6e2ce0d4d.jpg b/Marking.OCR.Service/images/5113aa79559ec019a986cad6e2ce0d4d.jpg new file mode 100644 index 0000000..3d67e8f Binary files /dev/null and b/Marking.OCR.Service/images/5113aa79559ec019a986cad6e2ce0d4d.jpg differ diff --git a/docker-swarm.yaml b/docker-swarm.yaml new file mode 100644 index 0000000..f8b7129 --- /dev/null +++ b/docker-swarm.yaml @@ -0,0 +1,14 @@ +version: "3" + +services: + admin-api: + image: ${REGISTRY:-harbor.w.23544.com:8843}/marking/marking.ocr.service:${TAG:-latest} + hostname: marking + environment: + TZ: Asia/Shanghai + ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Development} #默认为开发环境 + ports: + - 6555:80 + deploy: + mode: replicated + replicas: ${REPLICAS:-1} diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..42f21ea --- /dev/null +++ b/nuget.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file