From 0389e4002fcf556c5e48dd65efead815db314f61 Mon Sep 17 00:00:00 2001 From: misaki <1018407731@qq.com> Date: Fri, 18 Apr 2025 11:00:47 +0800 Subject: [PATCH] 3 --- 1.xlsx | Bin 0 -> 6861 bytes Controllers/FortranController.cs | 74 ++++ Dockerfile | 88 +++++ Fortran/SUB_UNITHYDRO.f90 | 113 ++++++ FortranWebApi.csproj | 27 ++ HttpRequests/fortranwebapi.http | 22 ++ Models/ApiResponse.cs | 31 ++ Models/FortranParameter.cs | 22 ++ Models/FortranRequest.cs | 13 + Models/FortranRequestWrapper.cs | 7 + Program.cs | 80 +++++ Properties/launchSettings.json | 14 + Services/FortranInteropService.cs | 562 ++++++++++++++++++++++++++++++ appsettings.json | 16 + 14 files changed, 1069 insertions(+) create mode 100644 1.xlsx create mode 100644 Controllers/FortranController.cs create mode 100644 Dockerfile create mode 100644 Fortran/SUB_UNITHYDRO.f90 create mode 100644 FortranWebApi.csproj create mode 100644 HttpRequests/fortranwebapi.http create mode 100644 Models/ApiResponse.cs create mode 100644 Models/FortranParameter.cs create mode 100644 Models/FortranRequest.cs create mode 100644 Models/FortranRequestWrapper.cs create mode 100644 Program.cs create mode 100644 Properties/launchSettings.json create mode 100644 Services/FortranInteropService.cs create mode 100644 appsettings.json diff --git a/1.xlsx b/1.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..1b8aebfcf39465a7c4ab5d5f24d98f7b11ac1294 GIT binary patch literal 6861 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 literal 0 HcmV?d00001 diff --git a/Controllers/FortranController.cs b/Controllers/FortranController.cs new file mode 100644 index 0000000..46a63e2 --- /dev/null +++ b/Controllers/FortranController.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Mvc; +using FortranWebApi.Models; +using FortranWebApi.Services; +using System.Text.Json; + +namespace FortranWebApi.Controllers +{ + [ApiController] + [Route("[controller]")] + public class FortranCalculateController : ControllerBase + { + private readonly FortranInteropService _fortranService; + private readonly ILogger _logger; + + public FortranCalculateController(FortranInteropService fortranService, ILogger logger) + { + _fortranService = fortranService; + _logger = logger; + } + + [HttpPost] + public IActionResult Post([FromBody] FortranRequestWrapper wrapper) + { + try + { + if (string.IsNullOrEmpty(wrapper.Text)) + { + return BadRequest(new + { + message = "请求文本不能为空", + success = false, + data = (object)null + }); + } + + string result = _fortranService.ProcessFortranRequest(wrapper.Text); + + // 使用驼峰命名法解析结果 + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + WriteIndented = true + }; + + var resultObj = JsonSerializer.Deserialize(result, options); + + // 返回新的格式 + var response = new ApiResponse + { + Message = "Success", + Success = true, + Data = new ApiResponseData + { + Value = resultObj + } + }; + + return Ok(response); + } + catch (Exception ex) + { + _logger.LogError(ex, "处理Fortran请求时发生错误"); + return StatusCode(500, new + { + message = ex.Message, + success = false, + data = (object)null + }); + } + } + } +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..807f2d9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,88 @@ +# ===== 第一阶段:构建阶段 ===== +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build + +# 配置 NuGet 使用国内镜像源 +RUN dotnet nuget add source https://mirrors.cloud.tencent.com/nuget/ \ + && dotnet nuget disable source nuget.org + +# 配置 apt-get 使用 apt-cacher-ng 作为代理 +RUN echo 'Acquire::http::Proxy "http://192.168.1.140:3142";' > /etc/apt/apt.conf.d/01proxy + +# 创建并配置 Debian 镜像源 +RUN echo "deb https://mirrors.ustc.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list && \ + echo "deb https://mirrors.ustc.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \ + echo "deb https://mirrors.ustc.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list + +# 允许使用不安全的软件源 +RUN echo 'Acquire::AllowInsecureRepositories "true";' > /etc/apt/apt.conf.d/99allow-insecure && \ + echo 'Acquire::AllowDowngradeToInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99allow-insecure + +WORKDIR /src + +# 安装 Fortran 编译器 +RUN apt-get update && apt-get install -y --no-install-recommends \ + gfortran \ + && rm -rf /var/lib/apt/lists/* + +# 复制 Fortran 源文件并编译 +COPY Fortran/*.f90 /src/Fortran/ +WORKDIR /src/Fortran + +# 编译所有 .f90 文件为 .o 文件 +RUN for file in *.f90; do gfortran -fPIC -c "$file" -o "${file%.f90}.o"; done + +# 链接所有 .o 文件为共享库 +RUN gfortran -shared *.o -o libSUB_UNITHYDRO.so + +# 回到主工作目录 +WORKDIR /src + +# 只复制项目文件 +COPY ["FortranWebApi.csproj", "./"] +RUN dotnet restore + +# 复制源代码 +COPY . . + +# 合并 build 和 publish 命令 +RUN dotnet publish -c Release -o /app/publish + +# 复制编译好的 .so 文件到发布目录 +RUN mkdir -p /app/publish && \ + cp /src/Fortran/libSUB_UNITHYDRO.so /app/publish/ + +# ===== 第二阶段:运行阶段 ===== +FROM mcr.microsoft.com/dotnet/aspnet:8.0 + +# 配置 apt-get 使用 apt-cacher-ng 作为代理 +RUN echo 'Acquire::http::Proxy "http://192.168.1.140:3142";' > /etc/apt/apt.conf.d/01proxy + +# 创建并配置 Debian 镜像源 +RUN echo "deb https://mirrors.ustc.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list && \ + echo "deb https://mirrors.ustc.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list && \ + echo "deb https://mirrors.ustc.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list + +# 允许使用不安全的软件源 +RUN echo 'Acquire::AllowInsecureRepositories "true";' > /etc/apt/apt.conf.d/99allow-insecure && \ + echo 'Acquire::AllowDowngradeToInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99allow-insecure + +# 安装运行时依赖 +RUN apt-get update && apt-get install -y --no-install-recommends \ + libc6-dev \ + libgfortran5 \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +COPY --from=build /app/publish . + +# 确保 .so 文件的权限正确 +RUN chmod 755 /app/libSUB_UNITHYDRO.so + +# 设置 LD_LIBRARY_PATH +ENV LD_LIBRARY_PATH=/app + +# 设置端口和监听地址 +ENV ASPNETCORE_URLS=http://+:5000 +EXPOSE 5000 + +ENTRYPOINT ["dotnet", "FortranWebApi.dll"] \ No newline at end of file diff --git a/Fortran/SUB_UNITHYDRO.f90 b/Fortran/SUB_UNITHYDRO.f90 new file mode 100644 index 0000000..c7b805e --- /dev/null +++ b/Fortran/SUB_UNITHYDRO.f90 @@ -0,0 +1,113 @@ + + !鵥λߺʵĽ̺ͺˮ + + SUBROUTINE SUB_UNITHYDRO(S,& ! + QT,& !̽ + M,& !ʱ + Q,& !͵λ + N,& !λʱ + IM,& !ˮ + FC,& !ȶ + QF,& ! + T,& !ÿʱεʱ + A,& !澶ʱıֵ + RUNOFF_U,& !澶 + RUNOFF_G,& !¾ + RUNOFF)& !ܵĺˮ + bind(C, name="SUB_UNITHYDRO") + !DEC$ ATTRIBUTES DLLEXPORT::SUB_UNITHYDRO + integer :: m,n + real :: S,Qt(m),q(n),Im,fc,Qf,t,a + real :: Qsum(m),Qj_sum(m),Qj_t(m),Qj_g(m),Qj_u(m) + real :: runoff_ut(100,100),runoff_u(100),runoff_g(100),W(100) + integer :: x,y + real :: Wg,Tg,Qmg + real :: Pa !ǰӰ + real :: Qj_eu(100) !ѵ澻0ĵ + real :: Qj_gs=0 !¾ + integer :: i,j ! + + !************** 㼰ķָ ************** + + !ۼƽ + Qsum(1)=Qt(1) + do i=2,m + Qsum(i)=Qsum(i-1)+Qt(i) + end do + + ! + Pa=Im*2/3 + do i=1,m + if(Qsum(i)<=(Im-Pa)) then + Qj_t(i)=0 + Qj_g(i)=0 + else + Qj_t(i)=Qsum(i)-(Im-Pa) + Qj_g(i)=(Qj_t(i)/Qt(i))*t*fc + exit + end if + end do + do while(iQt(i+1)) Qj_g(i+1)=Qt(i+1) !¾ + i=i+1 + end do + Qj_sum(1)=Qj_t(1) + Qj_u(1)=Qj_t(1)-Qj_g(1) + do i=2,m + Qj_sum(i)=Qj_sum(i-1)+Qj_t(i) !ۼƾ + Qj_u(i)=Qj_t(i)-Qj_g(i) !澻 + end do + + !******************** ˮ ************************** + + !澶************************************* + + x=0 + do i=1,m + if(Qj_u(i)/=0) then + x=x+1 + Qj_eu(x)=Qj_u(i) + end if + end do + runoff_ut=0 + runoff_u=0 + y=n+x + + !λ߷ʱε澶 + do i=1,n + do j=1,x + runoff_ut(j,i+j-1)=q(i)*Qj_eu(j)/10.0 + end do + end do + + !澶 + do i=1,y + do j=1,x + runoff_u(i)=runoff_u(i)+runoff_ut(j,i) + end do + end do + + !¾********************************* + runoff_g(1)=0 + do i=1,m + Qj_gs=Qj_gs+Qj_g(i) + end do + Wg=1000*Qj_gs*S + Tg=a*(y-2)*t + Qmg=2*Wg/(Tg*3600) + + do i=2,y + runoff_g(i)=runoff_g(i-1)+Qmg/(a*(y-2)/2) + if(i>(a*(y-2)/2+1)) runoff_g(i)=runoff_g(i-1)-Qmg/(a*(y-2)/2) + end do + + !ܵĺˮ********************************* + do i=1,y + W(i)=runoff_u(i)+runoff_g(i)+Qf + end do + + end subroutine + + diff --git a/FortranWebApi.csproj b/FortranWebApi.csproj new file mode 100644 index 0000000..6a4d551 --- /dev/null +++ b/FortranWebApi.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + + PreserveNewest + + + + \ No newline at end of file diff --git a/HttpRequests/fortranwebapi.http b/HttpRequests/fortranwebapi.http new file mode 100644 index 0000000..3577c23 --- /dev/null +++ b/HttpRequests/fortranwebapi.http @@ -0,0 +1,22 @@ +@FortranWebApi_HostAddress = http://localhost:5000 + +### 测试Fortran请求 + +POST {{FortranWebApi_HostAddress}}/FortranCalculate +Content-Type: application/json + +{ + "text": "{\"FuncName\":\"calculate_main\",\"ClassName\":\"\",\"Par\":[ {"Name":"S","DataType":"1","ArrayType":"0","IsOut":"2","Data":0}, + {"Name":"QT","DataType":"1","ArrayType":"1","IsOut":"2","Data":[]}, + {"Name":"M","DataType":"0","ArrayType":"0","IsOut":"2","Data":0}, + {"Name":"Q","DataType":"1","ArrayType":"1","IsOut":"2","Data":[]}, + {"Name":"N","DataType":"0","ArrayType":"0","IsOut":"2","Data":0}, + {"Name":"IM","DataType":"1","ArrayType":"0","IsOut":"2","Data":0}, + {"Name":"FC","DataType":"1","ArrayType":"0","IsOut":"2","Data":0}, + {"Name":"QF","DataType":"1","ArrayType":"0","IsOut":"2","Data":0}, + {"Name":"T","DataType":"1","ArrayType":"0","IsOut":"2","Data":0}, + {"Name":"A","DataType":"1","ArrayType":"0","IsOut":"2","Data":0}, + {"Name":"RUNOFF_U","DataType":"1","ArrayType":"1","IsOut":"1","Data":[]}, + {"Name":"RUNOFF_G","DataType":"1","ArrayType":"1","IsOut":"1","Data":[]}, + {"Name":"RUNOFF","DataType":"1","ArrayType":"1","IsOut":"1","Data":[]}]}" +} \ No newline at end of file diff --git a/Models/ApiResponse.cs b/Models/ApiResponse.cs new file mode 100644 index 0000000..3845aa5 --- /dev/null +++ b/Models/ApiResponse.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace FortranWebApi.Models +{ + public class ApiResponse + { + [JsonPropertyName("message")] + public string Message { get; set; } = "Success"; + + [JsonPropertyName("success")] + public bool Success { get; set; } = true; + + [JsonPropertyName("data")] + public ApiResponseData? Data { get; set; } + } + + public class ApiResponseData + { + [JsonPropertyName("contentType")] + public string? ContentType { get; set; } + + [JsonPropertyName("serializerSettings")] + public object? SerializerSettings { get; set; } + + [JsonPropertyName("statusCode")] + public int? StatusCode { get; set; } + + [JsonPropertyName("value")] + public T? Value { get; set; } + } +} \ No newline at end of file diff --git a/Models/FortranParameter.cs b/Models/FortranParameter.cs new file mode 100644 index 0000000..78d68f0 --- /dev/null +++ b/Models/FortranParameter.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace FortranWebApi.Models +{ + public class FortranParameter + { + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + [JsonPropertyName("dataType")] + public string DataType { get; set; } = "0"; + + [JsonPropertyName("arrayType")] + public string ArrayType { get; set; } = "0"; + + [JsonPropertyName("isOut")] + public string IsOut { get; set; } = "2"; + + [JsonPropertyName("data")] + public object? Data { get; set; } + } +} \ No newline at end of file diff --git a/Models/FortranRequest.cs b/Models/FortranRequest.cs new file mode 100644 index 0000000..e17fc88 --- /dev/null +++ b/Models/FortranRequest.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace FortranWebApi.Models +{ + public class FortranRequest + { + public string FuncName { get; set; } = string.Empty; + public string ClassName { get; set; } = string.Empty; + + [JsonPropertyName("par")] + public List Parameters { get; set; } = new List(); + } +} \ No newline at end of file diff --git a/Models/FortranRequestWrapper.cs b/Models/FortranRequestWrapper.cs new file mode 100644 index 0000000..49c36c2 --- /dev/null +++ b/Models/FortranRequestWrapper.cs @@ -0,0 +1,7 @@ +namespace FortranWebApi.Models +{ + public class FortranRequestWrapper + { + public string Text { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..ac334f1 --- /dev/null +++ b/Program.cs @@ -0,0 +1,80 @@ +using Serilog; +using FortranWebApi.Services; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +// 设置库搜索路径 +string currentDir = Directory.GetCurrentDirectory(); +Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", + $"{Environment.GetEnvironmentVariable("LD_LIBRARY_PATH")}:{currentDir}"); + +var builder = WebApplication.CreateBuilder(args); + +// 添加健康检查服务 +builder.Services.AddHealthChecks(); + +// 配置Serilog +Log.Logger = new LoggerConfiguration() + .WriteTo.Console(outputTemplate: + "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") + .WriteTo.File("logs/log-.txt", + rollingInterval: RollingInterval.Day, + outputTemplate: + "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") + .CreateLogger(); + +builder.Host.UseSerilog(); // 将Serilog添加到Host + +// 配置JSON序列化选项,使用驼峰命名法 +builder.Services.AddControllers().AddJsonOptions(options => +{ + options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase; + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.PropertyNameCaseInsensitive = true; +}); + +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddSingleton(); + +// 配置CORS +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy(policy => + { + policy.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseCors(); +app.UseAuthorization(); +// 映射健康检查端点 +app.MapHealthChecks("/health"); +app.MapControllers(); + +try +{ + Log.Information("启动应用程序..."); + app.Run(); +} +catch (Exception ex) +{ + Log.Fatal(ex, "应用程序启动失败"); +} +finally +{ + Log.CloseAndFlush(); +} \ No newline at end of file diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..295de27 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "profiles": { + "FortranWebApi": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://0.0.0.0:5000" + } + } +} \ No newline at end of file diff --git a/Services/FortranInteropService.cs b/Services/FortranInteropService.cs new file mode 100644 index 0000000..e256640 --- /dev/null +++ b/Services/FortranInteropService.cs @@ -0,0 +1,562 @@ +using System.Runtime.InteropServices; +using FortranWebApi.Models; +using System.Text.Json; +using System.Runtime.InteropServices.Marshalling; +using System.IO; + +namespace FortranWebApi.Services +{ + public class FortranInteropService + { + // 静态构造函数设置DLL导入解析器 + static FortranInteropService() + { + // 添加当前目录到库搜索路径 + NativeLibrary.SetDllImportResolver(typeof(FortranInteropService).Assembly, (name, assembly, path) => + { + if (name == "libSUB_UNITHYDRO.so") + { + // 尝试从当前目录加载 + string currentDir = Directory.GetCurrentDirectory(); + string libraryPath = Path.Combine(currentDir, "libSUB_UNITHYDRO.so"); + + if (File.Exists(libraryPath)) + { + return NativeLibrary.Load(libraryPath); + } + } + + // 回退到默认加载行为 + return IntPtr.Zero; + }); + } + + // DllImport声明 + [DllImport("libSUB_UNITHYDRO.so", CallingConvention = CallingConvention.Cdecl, EntryPoint = "SUB_UNITHYDRO")] + private static extern void SUB_UNITHYDRO( + ref float S, + float[] QT, + ref int M, + float[] Q, + ref int N, + ref float IM, + ref float FC, + ref float QF, + ref float T, + ref float A, + float[] RUNOFF_U, + float[] RUNOFF_G, + float[] RUNOFF + ); + + public string ProcessFortranRequest(string requestText) + { + try + { + // 解析请求 + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + WriteIndented = true + }; + + var request = JsonSerializer.Deserialize(requestText, options); + if (request == null) + { + throw new ArgumentException("无效的请求格式"); + } + + // 准备输入参数 + var parameters = request.Parameters; + + // 提取参数 + float S = GetFloatParameter(parameters, "S"); + float[] QT = GetFloatArrayParameter(parameters, "QT"); + int M = GetIntParameter(parameters, "M"); + float[] Q = GetFloatArrayParameter(parameters, "Q"); + int N = GetIntParameter(parameters, "N"); + float IM = GetFloatParameter(parameters, "IM"); + float FC = GetFloatParameter(parameters, "FC"); + float QF = GetFloatParameter(parameters, "QF"); + float T = GetFloatParameter(parameters, "T"); + float A = GetFloatParameter(parameters, "A"); + + + // 准备数组参数 + float[] RUNOFF_U = new float[10000]; // 输出数组,初始大小为10000 + float[] RUNOFF_G = new float[10000]; // 输出数组,初始大小为10000 + float[] RUNOFF = new float[10000]; // 输出数组,初始大小为10000 + + + // 调用Fortran函数 + SUB_UNITHYDRO( + ref S, + QT, + ref M, + Q, + ref N, + ref IM, + ref FC, + ref QF, + ref T, + ref A, + RUNOFF_U, + RUNOFF_G, + RUNOFF + ); + + // 更新输出参数 + UpdateArrayParameter(parameters, "RUNOFF_U", RUNOFF_U); + UpdateArrayParameter(parameters, "RUNOFF_G", RUNOFF_G); + UpdateArrayParameter(parameters, "RUNOFF", RUNOFF); + + + // 处理输出数组 + // 处理输出数组 RUNOFF_U + // 注意:没有找到明确的长度参数,使用非零元素数量 + { + int nonZeroCount = 0; + for (int i = 0; i < RUNOFF_U.Length; i++) + { + if (RUNOFF_U[i] != 0) nonZeroCount = i + 1; + } + if (nonZeroCount > 0) + { + float[] resultArray = new float[nonZeroCount]; + Array.Copy(RUNOFF_U, resultArray, nonZeroCount); + UpdateArrayParameter(parameters, "RUNOFF_U", resultArray); + } + } + // 处理输出数组 RUNOFF_G + // 注意:没有找到明确的长度参数,使用非零元素数量 + { + int nonZeroCount = 0; + for (int i = 0; i < RUNOFF_G.Length; i++) + { + if (RUNOFF_G[i] != 0) nonZeroCount = i + 1; + } + if (nonZeroCount > 0) + { + float[] resultArray = new float[nonZeroCount]; + Array.Copy(RUNOFF_G, resultArray, nonZeroCount); + UpdateArrayParameter(parameters, "RUNOFF_G", resultArray); + } + } + // 处理输出数组 RUNOFF + // 注意:没有找到明确的长度参数,使用非零元素数量 + { + int nonZeroCount = 0; + for (int i = 0; i < RUNOFF.Length; i++) + { + if (RUNOFF[i] != 0) nonZeroCount = i + 1; + } + if (nonZeroCount > 0) + { + float[] resultArray = new float[nonZeroCount]; + Array.Copy(RUNOFF, resultArray, nonZeroCount); + UpdateArrayParameter(parameters, "RUNOFF", resultArray); + } + } + + + // 返回结果 - 只返回原始请求的结构,但包含更新后的参数 + var result = new FortranRequest + { + FuncName = request.FuncName, + ClassName = request.ClassName, + Parameters = parameters + }; + + return JsonSerializer.Serialize(result, options); + } + catch (Exception ex) + { + return JsonSerializer.Serialize(new { error = ex.Message }); + } + } + + private float GetFloatParameter(List parameters, string name) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return 0.0f; + } + + if (param.Data is JsonElement element) + { + if (element.ValueKind == JsonValueKind.Number) + { + return element.GetSingle(); + } + } + + return Convert.ToSingle(param.Data); + } + private double GetDoubleParameter(List parameters, string name) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return 0.0; + } + + if (param.Data is JsonElement element) + { + if (element.ValueKind == JsonValueKind.Number) + { + return element.GetSingle(); + } + } + + return Convert.ToDouble(param.Data); + } + private int GetIntParameter(List parameters, string name) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return 0; + } + + if (param.Data is JsonElement element) + { + if (element.ValueKind == JsonValueKind.Number) + { + return element.GetInt32(); + } + } + + return Convert.ToInt32(param.Data); + } + private int[] GetIntArrayParameter(List parameters, string name) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return Array.Empty(); + } + + if (param.Data is JsonElement element && element.ValueKind == JsonValueKind.Array) + { + var array = new List(); + foreach (var item in element.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Number) + { + array.Add(item.GetInt32()); + } + } + return array.ToArray(); + } + + if (param.Data is System.Collections.IEnumerable enumerable && !(param.Data is string)) + { + var array = new List(); + foreach (var item in enumerable) + { + array.Add(Convert.ToInt32(item)); + } + return array.ToArray(); + } + + return Array.Empty(); + } + private float[] GetFloatArrayParameter(List parameters, string name) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return Array.Empty(); + } + + if (param.Data is JsonElement element && element.ValueKind == JsonValueKind.Array) + { + var array = new List(); + foreach (var item in element.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Number) + { + array.Add(item.GetSingle()); + } + } + return array.ToArray(); + } + + if (param.Data is System.Collections.IEnumerable enumerable && !(param.Data is string)) + { + var array = new List(); + foreach (var item in enumerable) + { + array.Add(Convert.ToSingle(item)); + } + return array.ToArray(); + } + + return Array.Empty(); + } + private double[] GetDoubleArrayParameter(List parameters, string name) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return Array.Empty(); + } + + if (param.Data is JsonElement element && element.ValueKind == JsonValueKind.Array) + { + var array = new List(); + foreach (var item in element.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Number) + { + array.Add(item.GetSingle()); + } + } + return array.ToArray(); + } + + if (param.Data is System.Collections.IEnumerable enumerable && !(param.Data is string)) + { + var array = new List(); + foreach (var item in enumerable) + { + array.Add(Convert.ToSingle(item)); + } + return array.ToArray(); + } + + return Array.Empty(); + } + private void UpdateParameter(List parameters, string name, object value) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param != null) + { + param.Data = value; + } + } + + private void UpdateArrayParameter(List parameters, string name, float[] value) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param != null) + { + param.Data = value; + } + } + private void UpdateDoubleArrayParameter(List parameters, string name, double[] value) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param != null) + { + param.Data = value; + } + } + private void UpdateIntArrayParameter(List parameters, string name, int[] value) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param != null) + { + param.Data = value; + } + } + // 添加新的辅助方法来处理二维数组 + private double[,] GetDouble2DArrayParameter(List parameters, string name, int rows, int cols) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return new double[rows, cols]; + } + + double[,] result = new double[rows, cols]; + + if (param.Data is JsonElement element && element.ValueKind == JsonValueKind.Array) + { + int index = 0; + foreach (var item in element.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Number) + { + int row = index / cols; + int col = index % cols; + if (row < rows && col < cols) + { + result[row, col] = item.GetDouble(); + } + index++; + } + } + } + else if (param.Data is System.Collections.IEnumerable enumerable && !(param.Data is string)) + { + int index = 0; + foreach (var item in enumerable) + { + int row = index / cols; + int col = index % cols; + if (row < rows && col < cols) + { + result[row, col] = Convert.ToDouble(item); + } + index++; + } + } + + return result; + } + + // 添加更新二维数组参数的方法 + private void UpdateDouble2DArrayParameter(List parameters, string name, double[,] value) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param != null) + { + // 将二维数组转换为一维数组以便于JSON序列化 + int rows = value.GetLength(0); + int cols = value.GetLength(1); + double[] flatArray = new double[rows * cols]; + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + flatArray[i * cols + j] = value[i, j]; + } + } + param.Data = flatArray; + } + } + // float版本 + private float[,] GetFloat2DArrayParameter(List parameters, string name, int rows, int cols) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return new float[rows, cols]; + } + + float[,] result = new float[rows, cols]; + + if (param.Data is JsonElement element && element.ValueKind == JsonValueKind.Array) + { + int index = 0; + foreach (var item in element.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Number) + { + int row = index / cols; + int col = index % cols; + if (row < rows && col < cols) + { + result[row, col] = item.GetSingle(); + } + index++; + } + } + } + else if (param.Data is System.Collections.IEnumerable enumerable && !(param.Data is string)) + { + int index = 0; + foreach (var item in enumerable) + { + int row = index / cols; + int col = index % cols; + if (row < rows && col < cols) + { + result[row, col] = Convert.ToSingle(item); + } + index++; + } + } + + return result; + } + + private void UpdateFloat2DArrayParameter(List parameters, string name, float[,] value) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param != null) + { + int rows = value.GetLength(0); + int cols = value.GetLength(1); + float[] flatArray = new float[rows * cols]; + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + flatArray[i * cols + j] = value[i, j]; + } + } + param.Data = flatArray; + } + } + + // int版本 + private int[,] GetInt2DArrayParameter(List parameters, string name, int rows, int cols) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param == null || param.Data == null) + { + return new int[rows, cols]; + } + + int[,] result = new int[rows, cols]; + + if (param.Data is JsonElement element && element.ValueKind == JsonValueKind.Array) + { + int index = 0; + foreach (var item in element.EnumerateArray()) + { + if (item.ValueKind == JsonValueKind.Number) + { + int row = index / cols; + int col = index % cols; + if (row < rows && col < cols) + { + result[row, col] = item.GetInt32(); + } + index++; + } + } + } + else if (param.Data is System.Collections.IEnumerable enumerable && !(param.Data is string)) + { + int index = 0; + foreach (var item in enumerable) + { + int row = index / cols; + int col = index % cols; + if (row < rows && col < cols) + { + result[row, col] = Convert.ToInt32(item); + } + index++; + } + } + + return result; + } + + private void UpdateInt2DArrayParameter(List parameters, string name, int[,] value) + { + var param = parameters.FirstOrDefault(p => p.Name == name); + if (param != null) + { + int rows = value.GetLength(0); + int cols = value.GetLength(1); + int[] flatArray = new int[rows * cols]; + for (int i = 0; i < rows; i++) + { + for (int j = 0; j < cols; j++) + { + flatArray[i * cols + j] = value[i, j]; + } + } + param.Data = flatArray; + } + } + } +} \ No newline at end of file diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..dcb0750 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://0.0.0.0:5000" + } + } + } +} \ No newline at end of file