xf 1 år sedan
incheckning
4361c1634d
31 ändrade filer med 1886 tillägg och 0 borttagningar
  1. 63 0
      .gitattributes
  2. 344 0
      .gitignore
  3. 58 0
      FileStorage.sln
  4. 7 0
      NuGet.Config
  5. 25 0
      build/.dockerignore
  6. 4 0
      build/Dockerfile
  7. 33 0
      src/FileStorage.Host/Controllers/WeatherForecastController.cs
  8. 23 0
      src/FileStorage.Host/FileStorage.Host.csproj
  9. 7 0
      src/FileStorage.Host/FileStorageConsts.cs
  10. 23 0
      src/FileStorage.Host/Program.cs
  11. 31 0
      src/FileStorage.Host/Properties/launchSettings.json
  12. 104 0
      src/FileStorage.Host/StartupExtensions.cs
  13. 114 0
      src/FileStorage.Host/StartupHelper.cs
  14. 12 0
      src/FileStorage.Host/StorageConfiguration.cs
  15. 13 0
      src/FileStorage.Host/WeatherForecast.cs
  16. 8 0
      src/FileStorage.Host/appsettings.Development.json
  17. 9 0
      src/FileStorage.Host/appsettings.json
  18. 22 0
      src/FileStorage.Host/config/appsettings.Development.json
  19. 16 0
      src/FileStorage.Host/config/appsettings.json
  20. 74 0
      src/FileStorage.Host/config/appsettings.shared.Development.json
  21. 74 0
      src/FileStorage.Host/config/appsettings.shared.json
  22. 309 0
      src/FileStorage.Repository.SqlSugar/BaseRepository.cs
  23. 113 0
      src/FileStorage.Repository.SqlSugar/Extensions/SqlSugarExtensions.cs
  24. 24 0
      src/FileStorage.Repository.SqlSugar/Extensions/SqlSugarRepositoryExtensions.cs
  25. 299 0
      src/FileStorage.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs
  26. 13 0
      src/FileStorage.Repository.SqlSugar/FileStorage.Repository.SqlSugar.csproj
  27. 7 0
      src/FileStorage.Repository.SqlSugar/FileStorageDbContext.cs
  28. 8 0
      src/FileStorage/DefaultFileStorage.cs
  29. 20 0
      src/FileStorage/FileMetadata.cs
  30. 16 0
      src/FileStorage/FileStorage.csproj
  31. 13 0
      src/FileStorage/IFileStorage.cs

+ 63 - 0
.gitattributes

@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=auto
+
+###############################################################################
+# 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

+ 344 - 0
.gitignore

@@ -0,0 +1,344 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+*.Comments.xml
+document.xml
+Document.xml
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# 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
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.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
+
+# Visual Studio Trace Files
+*.e2e
+
+# 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
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# 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
+# Note: 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
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable 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
+*.appx
+
+# 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
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# 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
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- Backup*.rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# 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 personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb

+ 58 - 0
FileStorage.sln

@@ -0,0 +1,58 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.6.33815.320
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{69FAA362-CEA1-4244-A7D0-BCC18052A181}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{BFB560DE-62B8-425E-A4AD-08D9DD610628}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "01_Infrastructure", "01_Infrastructure", "{C459ABB0-80BD-4989-82FA-FDE742252444}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02_Domain", "02_Domain", "{57BA12BF-7F0A-4CA1-AFC9-224F7D86FDD2}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03_Application", "03_Application", "{6B729C9D-6E51-4BA2-A5E7-28196C89EEB7}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "04_Presentation", "04_Presentation", "{EF133CBA-8E92-4AC3-9862-6D1839C0C900}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileStorage", "src\FileStorage\FileStorage.csproj", "{061D19E9-B714-4FA9-98C8-8192AD1D8C41}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileStorage.Repository.SqlSugar", "src\FileStorage.Repository.SqlSugar\FileStorage.Repository.SqlSugar.csproj", "{EE31164F-2484-408B-B14E-8A91E24B9C45}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileStorage.Host", "src\FileStorage.Host\FileStorage.Host.csproj", "{100524FE-F65A-4F21-835E-30C955DDF498}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{061D19E9-B714-4FA9-98C8-8192AD1D8C41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{061D19E9-B714-4FA9-98C8-8192AD1D8C41}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{061D19E9-B714-4FA9-98C8-8192AD1D8C41}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{061D19E9-B714-4FA9-98C8-8192AD1D8C41}.Release|Any CPU.Build.0 = Release|Any CPU
+		{EE31164F-2484-408B-B14E-8A91E24B9C45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{EE31164F-2484-408B-B14E-8A91E24B9C45}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{EE31164F-2484-408B-B14E-8A91E24B9C45}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{EE31164F-2484-408B-B14E-8A91E24B9C45}.Release|Any CPU.Build.0 = Release|Any CPU
+		{100524FE-F65A-4F21-835E-30C955DDF498}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{100524FE-F65A-4F21-835E-30C955DDF498}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{100524FE-F65A-4F21-835E-30C955DDF498}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{100524FE-F65A-4F21-835E-30C955DDF498}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{C459ABB0-80BD-4989-82FA-FDE742252444} = {69FAA362-CEA1-4244-A7D0-BCC18052A181}
+		{57BA12BF-7F0A-4CA1-AFC9-224F7D86FDD2} = {69FAA362-CEA1-4244-A7D0-BCC18052A181}
+		{6B729C9D-6E51-4BA2-A5E7-28196C89EEB7} = {69FAA362-CEA1-4244-A7D0-BCC18052A181}
+		{EF133CBA-8E92-4AC3-9862-6D1839C0C900} = {69FAA362-CEA1-4244-A7D0-BCC18052A181}
+		{061D19E9-B714-4FA9-98C8-8192AD1D8C41} = {57BA12BF-7F0A-4CA1-AFC9-224F7D86FDD2}
+		{EE31164F-2484-408B-B14E-8A91E24B9C45} = {C459ABB0-80BD-4989-82FA-FDE742252444}
+		{100524FE-F65A-4F21-835E-30C955DDF498} = {EF133CBA-8E92-4AC3-9862-6D1839C0C900}
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {2FA856C0-6F2F-40B6-986F-145CEB238129}
+	EndGlobalSection
+EndGlobal

+ 7 - 0
NuGet.Config

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <packageSources>
+    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
+    <add key="fengwo.org" value="http://110.188.24.182:5555/v3/index.json" />
+  </packageSources>
+</configuration>

+ 25 - 0
build/.dockerignore

@@ -0,0 +1,25 @@
+**/.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

+ 4 - 0
build/Dockerfile

@@ -0,0 +1,4 @@
+FROM  mcr.microsoft.com/dotnet/aspnet:7.0
+WORKDIR /app
+COPY out/ .
+ENTRYPOINT ["dotnet", "Hotline.Api.dll"]

+ 33 - 0
src/FileStorage.Host/Controllers/WeatherForecastController.cs

@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace FileStorage.Host.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<WeatherForecastController> _logger;
+
+        public WeatherForecastController(ILogger<WeatherForecastController> logger)
+        {
+            _logger = logger;
+        }
+
+        [HttpGet(Name = "GetWeatherForecast")]
+        public IEnumerable<WeatherForecast> 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();
+        }
+    }
+}

+ 23 - 0
src/FileStorage.Host/FileStorage.Host.csproj

@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.7" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
+    <PackageReference Include="FluentValidation.AspNetCore" Version="11.2.2" />
+    <PackageReference Include="Mapster.DependencyInjection" Version="1.0.0" />
+    <PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.1.0" />
+    <PackageReference Include="Serilog.Sinks.MongoDB" Version="5.3.1" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\FileStorage.Repository.SqlSugar\FileStorage.Repository.SqlSugar.csproj" />
+    <ProjectReference Include="..\FileStorage\FileStorage.csproj" />
+  </ItemGroup>
+
+</Project>

+ 7 - 0
src/FileStorage.Host/FileStorageConsts.cs

@@ -0,0 +1,7 @@
+namespace FileStorage.Host
+{
+    public class FileStorageConsts
+    {
+        public const string LocalImpt = "Local";
+    }
+}

+ 23 - 0
src/FileStorage.Host/Program.cs

@@ -0,0 +1,23 @@
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers();
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+    app.UseSwagger();
+    app.UseSwaggerUI();
+}
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();

+ 31 - 0
src/FileStorage.Host/Properties/launchSettings.json

@@ -0,0 +1,31 @@
+{
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:42489",
+      "sslPort": 0
+    }
+  },
+  "profiles": {
+    "http": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "applicationUrl": "http://localhost:5238",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 104 - 0
src/FileStorage.Host/StartupExtensions.cs

@@ -0,0 +1,104 @@
+using FileStorage.Repository.SqlSugar.Extensions;
+using FluentValidation;
+using FluentValidation.AspNetCore;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Serilog;
+using XF.Domain.Dependency;
+using XF.Domain.Filters;
+using XF.Domain.Options;
+
+namespace FileStorage.Host;
+
+internal static class StartupExtensions
+{
+    const string CorsOrigins = "CorsOrigins";
+    internal static WebApplication ConfigureServices(this WebApplicationBuilder builder)
+    {
+        var services = builder.Services;
+        var configuration = builder.Configuration;
+
+        services.AddHttpContextAccessor();
+
+#if DEBUG
+        builder.WebHost.UseUrls("http://*:50102");
+#endif
+
+        services.Configure<StorageConfiguration>(d => configuration.GetSection(nameof(StorageConfiguration)).Bind(d));
+        
+        // Add services to the container.
+        services.BatchInjectServices();
+
+        //var storageConfig = configuration.GetSection(nameof(StorageConfiguration)).Get<StorageConfiguration>();
+        //if(storageConfig == null || string.IsNullOrEmpty(storageConfig.Impt) || storageConfig.Impt == FileStorageConsts.LocalImpt)
+        //{
+            
+        //}
+
+        services.AddControllers(options =>
+        {
+            options.Filters.Add<UnifyResponseFilter>();
+            options.Filters.Add<UserFriendlyExceptionFilter>();
+        });
+
+        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+        services.AddEndpointsApiExplorer();
+
+        //swagger
+        services.RegisterSwagger();
+        
+        /* CORS */
+        services.RegisterCors(configuration, CorsOrigins);
+
+        //mapster
+        services.RegisterMapper();
+
+        ////mediatr
+        //services.AddMediatR(d =>
+        //{
+        //    d.RegisterServicesFromAssembly(typeof(ApplicationStartupExtensions).Assembly);
+        //});
+        
+        //sqlsugar
+        services.AddSqlSugar(configuration, "Hotline");
+
+        ////cache
+        //services.AddCache(d =>
+        //    {
+        //        d.ConnectionString = configuration.GetConnectionString("Redis") ?? string.Empty;
+        //        d.Prefix = "Hotline";
+        //        d.TopicName = "hotline-topic";
+        //    });
+
+        ////validator
+        //services.AddFluentValidationAutoValidation(config =>
+        //{
+        //    config.DisableDataAnnotationsValidation = true;
+        //})
+        //    .AddValidatorsFromAssembly(typeof(AppContractsStartupExtensions).Assembly);
+        
+        return builder.Build();
+    }
+
+    internal static WebApplication ConfigurePipelines(this WebApplication app)
+    {
+        app.UseSerilogRequestLogging();
+        
+        var swaggerEnable = app.Configuration.GetSection("Swagger").Get<bool>();
+        // Configure the HTTP request pipeline.
+        if (swaggerEnable)
+        {
+            app.UseSwagger();
+            app.UseSwaggerUI();
+        }
+
+        app.UseCors(CorsOrigins);
+
+        app.UseAuthentication();
+        app.UseAuthorization();
+
+        app.MapControllers();
+
+        return app;
+    }
+}

+ 114 - 0
src/FileStorage.Host/StartupHelper.cs

@@ -0,0 +1,114 @@
+using System.Text;
+using Mapster;
+using MapsterMapper;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.OpenApi.Models;
+using XF.Domain.Entities;
+using XF.Domain.Exceptions;
+using XF.Domain.Options;
+
+namespace FileStorage.Host
+{
+    public static class StartupHelper
+    {
+        /// <summary>
+        /// Swagger
+        /// </summary>
+        /// <param name="services"></param>
+        /// <returns></returns>
+        public static IServiceCollection RegisterSwagger(this IServiceCollection services)
+        {
+            services.AddSwaggerGen(c =>
+            {
+                //添加文档
+                c.SwaggerDoc("v1", new OpenApiInfo() { Title = "FileStorage Api", Version = "v1.0", Description = "文件存储api" });
+                var files = Directory.GetFiles(AppContext.BaseDirectory).Where(d => d.EndsWith(".xml"));
+                foreach (var file in files)
+                {
+                    c.IncludeXmlComments(file, true);
+                }
+
+                var scheme = new OpenApiSecurityScheme()
+                {
+                    Description = "Authorization header. \r\nExample: 'Bearer ***'",
+                    Reference = new OpenApiReference
+                    {
+                        Type = ReferenceType.SecurityScheme,
+                        Id = "Authorization"
+                    },
+                    Scheme = "oauth2",
+                    Name = "Authorization",
+                    In = ParameterLocation.Header,
+                    Type = SecuritySchemeType.ApiKey,
+                };
+                c.AddSecurityDefinition("Authorization", scheme);
+                var requirement = new OpenApiSecurityRequirement();
+                requirement[scheme] = new List<string>();
+                c.AddSecurityRequirement(requirement);
+            });
+
+            return services;
+        }
+
+        /// <summary>
+        /// Cors
+        /// </summary>
+        /// <param name="services"></param>
+        /// <param name="configuration"></param>
+        /// <param name="corsOrigins"></param>
+        /// <returns></returns>
+        public static IServiceCollection RegisterCors(this IServiceCollection services, ConfigurationManager configuration, string corsOrigins)
+        {
+            services.AddCors(options =>
+            {
+                options.AddPolicy(name: corsOrigins,
+                    builder =>
+                    {
+                        var origins = configuration.GetSection("Cors:Origins").Get<string[]>();
+                        builder.SetIsOriginAllowed(a =>
+                            {
+                                return origins.Any(origin => origin.StartsWith("*.", StringComparison.Ordinal)
+                                    ? a.EndsWith(origin[1..], StringComparison.Ordinal)
+                                    : a.Equals(origin, StringComparison.Ordinal));
+                            })
+                            .AllowAnyHeader()
+                            .AllowAnyMethod()
+                            .AllowCredentials();
+                    });
+            });
+
+            return services;
+        }
+
+        /// <summary>
+        /// Mapper
+        /// </summary>
+        /// <param name="services"></param>
+        /// <returns></returns>
+        public static IServiceCollection RegisterMapper(this IServiceCollection services)
+        {
+            var config = TypeAdapterConfig.GlobalSettings;
+            config.ForDestinationType<IDataPermission>()
+                .Ignore(d => d.CreatorId)
+                .Ignore(d => d.CreatorOrgId)
+                .Ignore(d => d.CreatorOrgCode)
+                .Ignore(d => d.AreaId);
+            config.ForDestinationType<IWorkflow>()
+                .Ignore(d => d.WorkflowId)
+                .Ignore(d => d.ExpiredTimeConfigId)
+                .Ignore(d => d.AssignOrgCodes)
+                .Ignore(d => d.AssignUserIds);
+            config.ForDestinationType<IHasCreationTime>()
+                .Ignore(d => d.CreationTime);
+            config.ForDestinationType<IHasDeletionTime>().Ignore(d => d.DeletionTime);
+            config.ForDestinationType<ISoftDelete>().Ignore(d => d.IsDeleted);
+            config.ForDestinationType<IHasModificationTime>().Ignore(d => d.LastModificationTime);
+
+            services.AddSingleton(config);
+            services.AddScoped<IMapper, ServiceMapper>();
+
+            return services;
+        }
+
+    }
+}

+ 12 - 0
src/FileStorage.Host/StorageConfiguration.cs

@@ -0,0 +1,12 @@
+namespace FileStorage.Host
+{
+    public class StorageConfiguration
+    {
+        public string Impt { get; set; } = "default";
+    }
+
+    public class Local
+    {
+        public string Path { get; set; }
+    }
+}

+ 13 - 0
src/FileStorage.Host/WeatherForecast.cs

@@ -0,0 +1,13 @@
+namespace FileStorage.Host
+{
+    public class WeatherForecast
+    {
+        public DateOnly Date { get; set; }
+
+        public int TemperatureC { get; set; }
+
+        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
+
+        public string? Summary { get; set; }
+    }
+}

+ 8 - 0
src/FileStorage.Host/appsettings.Development.json

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

+ 9 - 0
src/FileStorage.Host/appsettings.json

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

+ 22 - 0
src/FileStorage.Host/config/appsettings.Development.json

@@ -0,0 +1,22 @@
+{
+  "AllowedHosts": "*",
+  "ConnectionStrings": {
+    "Hotline": "PORT=5432;DATABASE=filestorage;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "Redis": "110.188.24.182",
+    "MongoDB": "mongodb://192.168.100.121:27017"
+  },
+  "Swagger": true,
+  "Cors": {
+    "Origins": [ "http://localhost:8888", "http://admin.hotline.fw.com", "http://hotline.fw.com" ]
+  },
+  "DatabaseConfiguration": {
+    "ApplyDbMigrations": false,
+    "ApplySeed": false
+  },
+  "StorageConfiguration": {
+    "Impt": "",
+    "Local": {
+      "Path": "",
+    }
+  }
+}

+ 16 - 0
src/FileStorage.Host/config/appsettings.json

@@ -0,0 +1,16 @@
+{
+  "AllowedHosts": "*",
+  "ConnectionStrings": {
+    "Hotline": "PORT=5432;DATABASE=filestorage;HOST=110.188.24.182;PASSWORD=fengwo11!!;USER ID=dev;",
+    "Redis": "110.188.24.182",
+    "MongoDB": "mongodb://192.168.100.121:27017"
+  },
+  "Swagger": true,
+  "Cors": {
+    "Origins": [ "http://localhost:8888", "http://admin.hotline.fw.com", "http://hotline.fw.com" ]
+  },
+  "DatabaseConfiguration": {
+    "ApplyDbMigrations": false,
+    "ApplySeed": false
+  }
+}

+ 74 - 0
src/FileStorage.Host/config/appsettings.shared.Development.json

@@ -0,0 +1,74 @@
+{
+  "Serilog": {
+    "Using": [
+      "Serilog.Enrichers.Span",
+      "Serilog.Sinks.Console",
+      "Serilog.Sinks.Grafana.Loki"
+    ],
+    "MinimumLevel": {
+      "Default": "Information",
+      "Override": {
+        "Microsoft": "Warning",
+        "Microsoft.Hosting.Lifetime": "Information",
+        "Microsoft.AspNetCore.Authentication": "Debug",
+        "Microsoft.AspNetCore": "Warning",
+        "Microsoft.AspNetCore.SignalR": "Debug",
+        "Microsoft.AspNetCore.Http.Connections": "Debug",
+        "System": "Warning"
+      }
+    },
+    "WriteTo": [
+      {
+        "Name": "Console",
+        "Args": {
+          //"outputTemplate": "time=\"{Timestamp:yyyy-MM-dd HH:mm:ss}\" level={Level:w3} category={SourceContext} trace={TraceId}{NewLine}msg=\"{Message:lj}\"{NewLine}error=\"{Exception}\"{NewLine}",
+          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+          "theme": "Serilog.Sinks.SystemConsole.Themes.ConsoleTheme::None, Serilog.Sinks.Console"
+        }
+      },
+      //{
+      //  "Name": "GrafanaLoki",
+      //  "Args": {
+      //    "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+      //    "uri": "http://192.168.100.217:3100",
+      //    "labels": [
+      //      {
+      //        "key": "app",
+      //        "value": "hotline"
+      //      }
+      //    ],
+      //    "propertiesAsLabels": [
+      //      "fwhl"
+      //    ]
+      //  }
+      //}
+      //{
+      //  "Name": "Exeptionless",
+      //  "Args": {
+      //    //"outputTemplate": "time=\"{Timestamp:yyyy-MM-dd HH:mm:ss}\" level={Level:w3} category={SourceContext} trace={TraceId}{NewLine}msg=\"{Message:lj}\"{NewLine}error=\"{Exception}\"{NewLine}",
+      //    "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+      //    "theme": "Serilog.Sinks.SystemConsole.Themes.ConsoleTheme::None, Serilog.Sinks.Console"
+      //  }
+      //}
+      //{
+      //  "Name": "File",
+      //  "Args": {
+      //    "path": "logs/log-.txt",
+      //    "rollingInterval": "Day"
+      //  }
+      //},
+      //{
+      //  "Name": "MongoDBBson",
+      //  "Args": {
+      //    "databaseUrl": "mongodb://192.168.100.121:27017/hotline_logs",
+      //    "collectionName": "logs",
+      //    "cappedMaxSizeMb": "1024",
+      //    "cappedMaxDocuments": "50000",
+      //    "rollingInterval": "Day",
+      //    "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}"
+      //  }
+      //}
+    ],
+    "Enrich": [ "FromLogContext", "WithSpan" ]
+  }
+}

+ 74 - 0
src/FileStorage.Host/config/appsettings.shared.json

@@ -0,0 +1,74 @@
+{
+  "Serilog": {
+    "Using": [
+      "Serilog.Enrichers.Span",
+      "Serilog.Sinks.Console",
+      "Serilog.Sinks.Grafana.Loki"
+    ],
+    "MinimumLevel": {
+      "Default": "Information",
+      "Override": {
+        "Microsoft": "Warning",
+        "Microsoft.Hosting.Lifetime": "Information",
+        "Microsoft.AspNetCore.Authentication": "Debug",
+        "Microsoft.AspNetCore": "Warning",
+        "Microsoft.AspNetCore.SignalR": "Debug",
+        "Microsoft.AspNetCore.Http.Connections": "Debug",
+        "System": "Warning"
+      }
+    },
+    "WriteTo": [
+      {
+        "Name": "Console",
+        "Args": {
+          //"outputTemplate": "time=\"{Timestamp:yyyy-MM-dd HH:mm:ss}\" level={Level:w3} category={SourceContext} trace={TraceId}{NewLine}msg=\"{Message:lj}\"{NewLine}error=\"{Exception}\"{NewLine}",
+          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+          "theme": "Serilog.Sinks.SystemConsole.Themes.ConsoleTheme::None, Serilog.Sinks.Console"
+        }
+      },
+      //{
+      //  "Name": "GrafanaLoki",
+      //  "Args": {
+      //    "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+      //    "uri": "http://192.168.100.217:3100",
+      //    "labels": [
+      //      {
+      //        "key": "app",
+      //        "value": "hotline"
+      //      }
+      //    ],
+      //    "propertiesAsLabels": [
+      //      "fwhl"
+      //    ]
+      //  }
+      //}
+      //{
+      //  "Name": "Exeptionless",
+      //  "Args": {
+      //    //"outputTemplate": "time=\"{Timestamp:yyyy-MM-dd HH:mm:ss}\" level={Level:w3} category={SourceContext} trace={TraceId}{NewLine}msg=\"{Message:lj}\"{NewLine}error=\"{Exception}\"{NewLine}",
+      //    "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}",
+      //    "theme": "Serilog.Sinks.SystemConsole.Themes.ConsoleTheme::None, Serilog.Sinks.Console"
+      //  }
+      //}
+      //{
+      //  "Name": "File",
+      //  "Args": {
+      //    "path": "logs/log-.txt",
+      //    "rollingInterval": "Day"
+      //  }
+      //},
+      //{
+      //  "Name": "MongoDBBson",
+      //  "Args": {
+      //    "databaseUrl": "mongodb://192.168.100.121:27017/hotline_logs",
+      //    "collectionName": "logs",
+      //    "cappedMaxSizeMb": "1024",
+      //    "cappedMaxDocuments": "50000",
+      //    "rollingInterval": "Day",
+      //    "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {SourceContext} [{TraceId}]{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}"
+      //  }
+      //}
+    ],
+    "Enrich": [ "FromLogContext", "WithSpan" ]
+  }
+}

+ 309 - 0
src/FileStorage.Repository.SqlSugar/BaseRepository.cs

@@ -0,0 +1,309 @@
+using System.Linq.Expressions;
+using FileStorage.Repository.SqlSugar.Extensions;
+using SqlSugar;
+using XF.Domain.Entities;
+using XF.Domain.Repository;
+
+namespace FileStorage.Repository.SqlSugar
+{
+    public abstract class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity<string>, IHasCreationTime, IDataPermission, new()
+    {
+        protected ISugarUnitOfWork<FileStorageDbContext> Uow { get; }
+        protected ISqlSugarClient Db { get; }
+
+        public BaseRepository(ISugarUnitOfWork<FileStorageDbContext> uow)
+        {
+            Uow = uow;
+            Db = uow.Db;
+        }
+
+        public async Task<string> AddAsync(TEntity entity, CancellationToken cancellationToken = default)
+        {
+            var excEntity = await Db.Insertable(entity).ExecuteReturnEntityAsync();
+            return excEntity.Id;
+        }
+
+        /// <summary>
+        /// 批量插入(应用场景:小数据量,超出1万条建议另行实现)
+        /// </summary>
+        /// <param name="entities"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task AddRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default)
+        {
+            await Db.Insertable(entities).ExecuteCommandAsync();
+        }
+
+        public async Task RemoveAsync(TEntity entity, bool? soft = false, CancellationToken cancellationToken = default)
+        {
+            if (soft.HasValue && soft.Value)
+            {
+                await Db.Deleteable(entity).IsLogic().ExecuteCommandAsync("IsDeleted", true, "DeletionTime");
+            }
+            else
+            {
+                await Db.Deleteable(entity).ExecuteCommandAsync();
+            }
+        }
+
+        public async Task RemoveAsync(string id, bool? soft = false, CancellationToken cancellationToken = default)
+        {
+            if (soft.HasValue && soft.Value)
+            {
+                await Db.Deleteable<TEntity>().In(id).IsLogic().ExecuteCommandAsync("IsDeleted", true, "DeletionTime");
+            }
+            else
+            {
+                await Db.Deleteable<TEntity>().In(id).ExecuteCommandAsync();
+            }
+        }
+
+        public async Task RemoveAsync(Expression<Func<TEntity, bool>> predicate, bool? soft, CancellationToken cancellationToken = default)
+        {
+            if (soft.HasValue && soft.Value)
+            {
+                await Db.Deleteable<TEntity>().Where(predicate).IsLogic().ExecuteCommandAsync("IsDeleted", true, "DeletionTime");
+            }
+            else
+            {
+                await Db.Deleteable<TEntity>().Where(predicate).ExecuteCommandAsync();
+            }
+        }
+
+        public async Task RemoveRangeAsync(IEnumerable<TEntity> entities, CancellationToken cancellationToken = default)
+        {
+            await Db.Deleteable<TEntity>(entities).ExecuteCommandAsync();
+        }
+
+        public async Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default)
+        {
+            await Db.Updateable(entity)
+                .IgnoreColumns(ignoreAllNullColumns: true)
+                .IgnoreColumns(d => d.CreationTime)
+                .ExecuteCommandAsync();
+        }
+
+        public async Task UpdateRangeAsync(List<TEntity> entities, CancellationToken cancellationToken = default)
+        {
+            await Db.Updateable(entities)
+                .IgnoreColumns(d => d.CreationTime)
+                .ExecuteCommandAsync();
+        }
+
+        public async Task<TEntity?> GetAsync(string id, CancellationToken cancellationToken = default)
+        {
+            return await Db.Queryable<TEntity>().FirstAsync(d => d.Id == id);
+        }
+
+        public TEntity Get(string id)
+        {
+            return Db.Queryable<TEntity>().First(d => d.Id == id);
+        }
+
+        public TEntity Get(Expression<Func<TEntity, bool>> predicate)
+        {
+            return Db.Queryable<TEntity>().First(predicate);
+        }
+
+
+        public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
+        {
+            return await Db.Queryable<TEntity>().FirstAsync(predicate);
+        }
+
+        public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, bool isDesc, Expression<Func<TEntity, object>> orderBy, CancellationToken cancellationToken = default)
+        {
+            if (isDesc)
+                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Desc).FirstAsync(predicate);
+            else
+                return await Db.Queryable<TEntity>().OrderBy(orderBy, OrderByType.Asc).FirstAsync(predicate);
+        }
+
+
+        public async Task<List<TEntity>> QueryAsync(Expression<Func<TEntity, bool>>? predicate = null, params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
+        {
+            var query = Db.Queryable<TEntity>().Where(predicate ??= d => true);
+            if (whereIfs.Any())
+            {
+                foreach (var whereIf in whereIfs)
+                {
+                    query = query.WhereIF(whereIf.isWhere, whereIf.expression);
+                }
+            }
+
+            return await query.ToListAsync();
+        }
+
+        public Task<bool> AnyAsync(CancellationToken cancellationToken = default) => Db.Queryable<TEntity>().AnyAsync();
+
+        public Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) =>
+             Db.Queryable<TEntity>().AnyAsync(predicate);
+
+        public Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default) =>
+             Db.Queryable<TEntity>().CountAsync(predicate);
+
+        public ISugarQueryable<TEntity> Queryable(bool permissionVerify = false, bool includeDeleted = false)
+        {
+            if (includeDeleted)
+                Db.QueryFilter.Clear();
+
+            var query = Db.Queryable<TEntity>();
+            return query;
+        }
+
+        public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(TEntity entity) => Db.UpdateNav(entity);
+
+        public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(TEntity entity, UpdateNavRootOptions options) => Db.UpdateNav(entity, options);
+
+        public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(List<TEntity> entities) => Db.UpdateNav(entities);
+
+        public UpdateNavTaskInit<TEntity, TEntity> UpdateNav(List<TEntity> entities, UpdateNavRootOptions options) => Db.UpdateNav(entities, options);
+
+        public InsertNavTaskInit<TEntity, TEntity> AddNav(TEntity entity)
+        {
+            return Db.InsertNav(entity);
+        }
+
+        public InsertNavTaskInit<TEntity, TEntity> AddNav(TEntity entity, InsertNavRootOptions options)
+        {
+            return Db.InsertNav(entity, options);
+        }
+
+        public InsertNavTaskInit<TEntity, TEntity> AddNav(List<TEntity> entities)
+        {
+            return Db.InsertNav(entities);
+        }
+
+        public InsertNavTaskInit<TEntity, TEntity> AddNav(List<TEntity> entities, InsertNavRootOptions options)
+        {
+            return Db.InsertNav(entities, options);
+        }
+
+        public DeleteNavTaskInit<TEntity, TEntity> RemoveNav(TEntity entity) => Db.DeleteNav(entity);
+        public DeleteNavTaskInit<TEntity, TEntity> RemoveNav(List<TEntity> entities) => Db.DeleteNav(entities);
+
+        public DeleteNavTaskInit<TEntity, TEntity> RemoveNav(Expression<Func<TEntity, bool>> predicate) => Db.DeleteNav(predicate);
+
+        /// <summary>
+        /// 基础分页
+        /// </summary>
+        /// <param name="predicate"></param>
+        /// <param name="orderByCreator"></param>
+        /// <param name="pageIndex"></param>
+        /// <param name="pageSize"></param>
+        /// <param name="permissionVerify"></param>
+        /// <returns></returns>
+        public async Task<(int Total, List<TEntity> Items)> QueryPagedAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> orderByCreator,
+            int pageIndex,
+            int pageSize,
+            bool permissionVerify = false,
+            params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
+        {
+            RefAsync<int> total = 0;
+            var query = Db.Queryable<TEntity>().Where(predicate);
+
+            if (whereIfs.Any())
+            {
+                foreach (var whereIf in whereIfs)
+                {
+                    query = query.WhereIF(whereIf.isWhere, whereIf.expression);
+                }
+            }
+            var items = await orderByCreator(query).ToPageListAsync(pageIndex, pageSize, total);
+            return (total.Value, items);
+        }
+
+
+        public async Task<(int Total, List<TEntity> Items)> QueryPagedAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            int pageIndex,
+            int pageSize,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>>? includes = null,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>>? orderByCreator = null,
+            bool permissionVerify = false,
+            params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
+        {
+            RefAsync<int> total = 0;
+            var query = Db.Queryable<TEntity>().Where(predicate);
+
+            if (includes is not null)
+                query = includes(query);
+
+            if (whereIfs.Any())
+            {
+                foreach (var whereIf in whereIfs)
+                {
+                    query = query.WhereIF(whereIf.isWhere, whereIf.expression);
+                }
+            }
+            if (orderByCreator is not null)
+                query = orderByCreator(query);
+
+            var items = await query.ToPageListAsync(pageIndex, pageSize, total);
+            return (total.Value, items);
+        }
+
+        public async Task<List<TEntity>> QueryExtAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>>? includes = null,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>>? orderByCreator = null,
+            bool permissionVerify = false,
+            params (bool isWhere, Expression<Func<TEntity, bool>> expression)[] whereIfs)
+        {
+            var query = Db.Queryable<TEntity>().Where(predicate);
+
+            if (includes is not null)
+                query = includes(query);
+
+            if (whereIfs.Any())
+            {
+                foreach (var whereIf in whereIfs)
+                {
+                    query = query.WhereIF(whereIf.isWhere, whereIf.expression);
+                }
+            }
+
+            if (orderByCreator is not null)
+                query = orderByCreator(query);
+
+            return await query.ToListAsync();
+        }
+
+        public async Task<List<TEntity>> QueryExtAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> includes,
+            bool permissionVerify = false)
+        {
+            var query = Db.Queryable<TEntity>().Where(predicate);
+            query = includes(query);
+            return await query.ToListAsync();
+        }
+
+        public async Task<TEntity> GetExtAsync(
+            Expression<Func<TEntity, bool>> predicate,
+            Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> includes,
+            bool permissionVerify = false)
+        {
+            var query = Db.Queryable<TEntity>();
+            query = includes(query);
+            return await query.FirstAsync(predicate);
+        }
+
+        public async Task<TEntity> GetExtAsync(string id, Func<ISugarQueryable<TEntity>, ISugarQueryable<TEntity>> includes, bool permissionVerify = false)
+        {
+            var query = Db.Queryable<TEntity>();
+            query = includes(query);
+            return await query.FirstAsync(d => d.Id == id);
+        }
+
+        public async Task UpdateAsync(TEntity entity, bool ignoreNullColumns = true, CancellationToken cancellationToken = default)
+        {
+            await Db.Updateable(entity)
+                .IgnoreColumns(ignoreAllNullColumns: ignoreNullColumns)
+                .IgnoreColumns(d => d.CreationTime)
+                .ExecuteCommandAsync();
+        }
+    }
+}

+ 113 - 0
src/FileStorage.Repository.SqlSugar/Extensions/SqlSugarExtensions.cs

@@ -0,0 +1,113 @@
+using System.Collections;
+using System.Data;
+using System.Reflection;
+using SqlSugar;
+
+namespace FileStorage.Repository.SqlSugar.Extensions
+{
+    public static class SqlSugarExtensions
+    {
+        /// <summary>
+        /// List转Dictionary
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="list"></param>
+        /// <returns></returns>
+        public static List<Dictionary<string, object?>> ToDictionary<T>(this List<T> list)
+        {
+            var result = new List<Dictionary<string, object?>>();
+            if (list.Any())
+            {
+                foreach (var item in list)
+                {
+                    Dictionary<string, object?> dc = new();
+                    var properties = item.GetType().GetProperties();
+                    foreach (var property in properties)
+                    {
+                        if (IsIgnoreColumn(property)) continue;
+                        if (IsNavigateColumn(property)) continue;
+                        dc.Add(property.Name, property.GetValue(item));
+                    }
+                    result.Add(dc);
+                }
+            }
+
+            return result;
+        }
+
+        public static DataTable ToDataTable<T>(this List<T> list, string tableName)
+        {
+            var dt = new DataTable();
+            dt.TableName = tableName; //设置表名
+
+            if (list.Any())
+            {
+                PropertyInfo[] properties = list[0].GetType().GetProperties();
+                foreach (PropertyInfo property in properties)
+                {
+                    if (IsIgnoreColumn(property)) continue;
+                    if (IsNavigateColumn(property)) continue;
+                    Type colType = property.PropertyType;
+                    if (colType.IsGenericType && colType.GetGenericTypeDefinition() == typeof(Nullable<>))
+                    {
+                        colType = colType.GetGenericArguments()[0];
+                    }
+                    dt.Columns.Add(property.Name, colType);
+                }
+
+                foreach (var item in list)
+                {
+                    ArrayList tempList = new();
+                    //var properties = item.GetType().GetProperties();
+                    foreach (var property in properties)
+                    {
+                        if (IsIgnoreColumn(property)) continue;
+                        if (IsNavigateColumn(property)) continue;
+                        var obj = property.GetValue(item, null);
+                        tempList.Add(obj);
+                    }
+                    dt.LoadDataRow(tempList.ToArray(), true);
+                }
+
+                //for (int i = 0; i < list.Count; i++)
+                //{
+                //    ArrayList tempList = new();
+                //    foreach (PropertyInfo pi in propertys)
+                //    {
+                //        if (IsIgnoreColumn(pi))
+                //            continue;
+                //        object obj = pi.GetValue(list[i], null);
+                //        tempList.Add(obj);
+                //    }
+                //    object[] array = tempList.ToArray();
+                //    result.LoadDataRow(array, true);
+                //}
+            }
+
+            //var addRow = dt.NewRow();
+            //addRow["id"] = 0;
+            //addRow["price"] = 1;
+            //addRow["Name"] = "a";
+            //dt.Rows.Add(addRow);//添加数据
+
+            //var x = db.Storageable(dt).WhereColumns("id").ToStorage();//id作为主键
+            //x.AsInsertable.IgnoreColumns("id").ExecuteCommand();//如果是自增要添加IgnoreColumns
+            //x.AsUpdateable.ExecuteCommand();
+            return dt;
+        }
+
+        /// <summary>
+        /// 排除SqlSugar忽略的列
+        /// </summary>
+        /// <param name="pi"></param>
+        /// <returns></returns>
+        private static bool IsIgnoreColumn(PropertyInfo property)
+        {
+            var sc = property.GetCustomAttributes<SugarColumn>(false).FirstOrDefault(u => u.IsIgnore);
+            return sc != null;
+        }
+
+        private static bool IsNavigateColumn(PropertyInfo property) =>
+            property.GetCustomAttributes<Navigate>(false).Any();
+    }
+}

+ 24 - 0
src/FileStorage.Repository.SqlSugar/Extensions/SqlSugarRepositoryExtensions.cs

@@ -0,0 +1,24 @@
+using SqlSugar;
+using XF.Domain.Entities;
+
+namespace FileStorage.Repository.SqlSugar.Extensions
+{
+    public static class SqlSugarRepositoryExtensions
+    {
+        public static async Task<(int Total, List<TEntity> Items)> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> query, int pageIndex, int pageSize, CancellationToken cancellationToken = default)
+            where TEntity : class, IEntity<string>, new()
+        {
+            RefAsync<int> total = 0;
+            var items = await query.ToPageListAsync(pageIndex, pageSize, total);
+            return (total.Value, items);
+        }
+
+        //public static async Task<(int Total, List<TEntity> Items)> ToPagedListAsync<TEntity>(this ISugarQueryable<TEntity> query, PagedRequest dto, CancellationToken cancellationToken = default)
+        //    where TEntity : class, IEntity<string>, new()
+        //{
+        //    RefAsync<int> total = 0;
+        //    var items = await query.ToPageListAsync(dto.PageIndex, dto.PageSize, total);
+        //    return (total.Value, items);
+        //}
+    }
+}

+ 299 - 0
src/FileStorage.Repository.SqlSugar/Extensions/SqlSugarStartupExtensions.cs

@@ -0,0 +1,299 @@
+using System.Collections;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+using System.Linq.Expressions;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Serilog;
+using SqlSugar;
+using XF.Domain.Entities;
+using XF.Domain.Extensions;
+using XF.Domain.Options;
+using XF.Domain.Repository;
+using XF.Utility.SequentialId;
+
+namespace FileStorage.Repository.SqlSugar.Extensions
+{
+    public static class SqlSugarStartupExtensions
+    {
+        public static void AddSqlSugar(this IServiceCollection services, IConfiguration configuration, string dbName = "Hotline")
+        {
+            //多租户 new SqlSugarScope(List<ConnectionConfig>,db=>{});
+
+            SqlSugarScope sqlSugar = new SqlSugarScope(new ConnectionConfig()
+            {
+                DbType = DbType.PostgreSQL,
+                ConnectionString = configuration.GetConnectionString(dbName),
+                IsAutoCloseConnection = true,
+                ConfigureExternalServices = new ConfigureExternalServices
+                {
+                    EntityService = (property, column) =>
+                    {
+                        var attributes = property.GetCustomAttributes(true); //get all attributes 
+
+                        //if (attributes.Any(it => it is KeyAttribute))// by attribute set primarykey
+                        //{
+                        //    column.IsPrimarykey = true; //有哪些特性可以看 1.2 特性明细
+                        //}
+                        ////可以写多个,这边可以断点调试
+                        //// if (attributes.Any(it => it is NotMappedAttribute))
+                        ////{
+                        ////    column.IsIgnore= true; 
+                        ////}
+                        //if (attributes.Any(it => it is DbNullableAttribute))
+                        //{
+                        //    column.IsNullable = true;
+                        //}
+                        //if (attributes.Any(it => it is DbJsonAttribute))
+                        //{
+                        //    column.DataType = "varchar(3000)";
+                        //    column.IsJson = true;
+                        //}
+                        //if (attributes.Any(it => it is DbLengthAttribute))
+                        //{
+                        //    column.Length = (attributes.First(d => d is DbLengthAttribute) as DbLengthAttribute)?.MaxLength ?? 255;
+                        //}
+                        //if (attributes.Any(it => it is DbIgnoreAttribute))
+                        //{
+                        //    column.IsIgnore = true;
+                        //}
+                        if (property.PropertyType.IsGenericType &&
+                            property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
+                        {
+                            column.IsNullable = true;
+                        }
+
+                        if (column.PropertyName.ToLower() == "id" ||
+                            attributes.Any(it => it is KeyAttribute)) //是id的设为主键
+                        {
+                            column.IsPrimarykey = true;
+                            column.Length = 36;
+                        }
+
+                        //if (!column.DbColumnName.Contains("_"))
+                        //    column.DbColumnName = UtilMethods.ToUnderLine(column.DbColumnName);//ToUnderLine驼峰转下划线
+
+                        //column.ColumnDescription = (attributes.FirstOrDefault(d => d is DescriptionAttribute) as DescriptionAttribute)?.Description ?? string.Empty;
+                    },
+                    EntityNameService = (type, entity) =>
+                    {
+                        var attributes = type.GetCustomAttributes(true);
+                        //if (attributes.Any(it => it is TableAttribute))
+                        //{
+                        //    entity.DbTableName = (attributes.First(it => it is TableAttribute) as TableAttribute).UserName;
+                        //}
+
+                        entity.DbTableName = entity.DbTableName.ToSnakeCase();
+
+                        //if (!entity.DbTableName.Contains("_"))
+                        //    entity.DbTableName = UtilMethods.ToUnderLine(entity.DbTableName);//ToUnderLine驼峰转下划线方法
+                        if (attributes.Any(d => d is DescriptionAttribute))
+                        {
+                            entity.TableDescription =
+                                (attributes.First(d => d is DescriptionAttribute) as DescriptionAttribute)
+                                .Description;
+                        }
+                    }
+                },
+                MoreSettings = new ConnMoreSettings
+                {
+                    PgSqlIsAutoToLower = false,//增删查改支持驼峰表
+                    PgSqlIsAutoToLowerCodeFirst = false, // 建表建驼峰表。5.1.3.30 
+                }
+            },
+                SetDbAop
+            );
+
+            ISugarUnitOfWork<FileStorageDbContext> context = new SugarUnitOfWork<FileStorageDbContext>(sqlSugar);
+            services.AddSingleton(context);
+
+            InitDatabase(context, configuration);
+        }
+
+        private static void InitDatabase(ISugarUnitOfWork<FileStorageDbContext> context, IConfiguration configuration)
+        {
+            var dbOptions = configuration.GetSection("DatabaseConfiguration").Get<DatabaseOptions>() ?? new DatabaseOptions();
+            if (dbOptions.ApplyDbMigrations)
+            {
+                //context.Db.DbMaintenance.CreateDatabase();
+
+                //var types = typeof(User).Assembly.GetTypes()
+                //    .Where(d => d.GetInterfaces().Any(x => x == typeof(ITable) && !d.IsAbstract))
+                //    .Distinct()
+                //    .ToArray();
+
+                //context.Db.CodeFirst.InitTables(types);//根据types创建表
+            }
+
+            if (dbOptions.ApplySeed)
+            {
+                var seedDataTypes = AppDomain.CurrentDomain.GetAssemblies()
+                    .SelectMany(d => d.GetTypes())
+                    .Where(d => !d.IsInterface && !d.IsAbstract && d.IsClass
+                                && d.HasImplementedOf(typeof(ISeedData<>)));
+
+                foreach (var seedType in seedDataTypes)
+                {
+                    var instance = Activator.CreateInstance(seedType);
+
+                    var hasDataMethod = seedType.GetMethod("HasData");
+                    var seedData = ((IEnumerable)hasDataMethod?.Invoke(instance, null))?.Cast<object>();
+                    if (seedData == null) continue;
+
+                    var entityType = seedType.GetInterfaces().First().GetGenericArguments().First();
+
+                    var entityInfo = context.Db.EntityMaintenance.GetEntityInfo(entityType);
+                    if (entityInfo.Columns.Any(d => d.IsPrimarykey))
+                    {
+                        var storage = context.Db.StorageableByObject(seedData.ToList()).ToStorage();
+                        storage.AsInsertable.ExecuteCommand();
+                    }
+                    else
+                    {
+                        // 无主键则只进行插入
+                        if (!context.Db.Queryable(entityInfo.DbTableName, entityInfo.DbTableName).Any())
+                            context.Db.InsertableByObject(seedData.ToList()).ExecuteCommand();
+                    }
+                }
+            }
+        }
+
+        #region private
+
+        private static void SetDbAop(SqlSugarClient db)
+        {
+            /***写AOP等方法***/
+            db.Aop.OnLogExecuting = (sql, pars) =>
+            {
+                //Log.Information("Sql: {0}", sql);
+                //Log.Information("SqlParameters: {0}", string.Join(',', pars.Select(d => d.Value)));
+            };
+            db.Aop.OnError = (exp) =>//SQL报错
+            {
+                //exp.sql 这样可以拿到错误SQL,性能无影响拿到ORM带参数使用的SQL
+                Log.Error("SqlError: {0}", exp.Sql);
+
+                //5.0.8.2 获取无参数化 SQL  对性能有影响,特别大的SQL参数多的,调试使用
+                //UtilMethods.GetSqlString(DbType.SqlServer,exp.sql,exp.parameters)           
+            };
+            //db.Aop.OnExecutingChangeSql = (sql, pars) => //可以修改SQL和参数的值
+            //{
+            //    //sql=newsql
+            //    //foreach (var p in pars) //修改
+            //    //{
+
+            //    //}
+
+            //    return new KeyValuePair<string, SugarParameter[]>(sql, pars);
+            //};
+
+            db.Aop.OnLogExecuted = (sql, p) =>
+            {
+                //执行时间超过1秒
+                if (db.Ado.SqlExecutionTime.TotalSeconds > 1)
+                {
+                    //代码CS文件名
+                    var fileName = db.Ado.SqlStackTrace.FirstFileName;
+                    //代码行数
+                    var fileLine = db.Ado.SqlStackTrace.FirstLine;
+                    //方法名
+                    var FirstMethodName = db.Ado.SqlStackTrace.FirstMethodName;
+                    //db.Ado.SqlStackTrace.MyStackTraceList[1].xxx 获取上层方法的信息
+
+                    Log.Warning("slow query ==> fileName: {fileName}, fileLine: {fileLine}, FirstMethodName: {FirstMethodName}",
+                        fileName, fileLine, FirstMethodName);
+                }
+                //相当于EF的 PrintToMiniProfiler
+            };
+
+            db.Aop.DataExecuting = (oldValue, entityInfo) =>
+            {
+                //inset生效
+                if (entityInfo.PropertyName == "CreationTime" && entityInfo.OperationType == DataFilterType.InsertByObject)
+                {
+                    entityInfo.SetValue(DateTime.Now);//修改CreateTime字段
+                                                      //entityInfo有字段所有参数
+                }
+                //update生效        
+                else if (entityInfo.PropertyName == "LastModificationTime" && entityInfo.OperationType == DataFilterType.UpdateByObject)
+                {
+                    entityInfo.SetValue(DateTime.Now);//修改UpdateTime字段
+                }
+
+                //根据当前列修改另一列 可以么写
+                //if(当前列逻辑==XXX)
+                //var properyDate = entityInfo.EntityValue.GetType().GetProperty("Date");
+                //if(properyDate!=null)
+                //properyDate.SetValue(entityInfo.EntityValue,1);
+
+                else if (entityInfo.EntityColumnInfo.IsPrimarykey
+                         && entityInfo.EntityColumnInfo.PropertyName.ToLower() == "id"
+                         && entityInfo.OperationType == DataFilterType.InsertByObject) //通过主键保证只进一次事件
+                {
+                    var propertyId = entityInfo.EntityValue.GetType().GetProperty("Id");
+                    if (propertyId is not null)
+                    {
+                        var idValue = propertyId.GetValue(entityInfo.EntityValue);
+                        if (idValue is null)
+                            //这样每条记录就只执行一次 
+                            entityInfo.SetValue(SequentialStringGenerator.Create());
+                    }
+
+                }
+            };
+
+            SetDeletedEntityFilter(db);
+        }
+
+        private static void SetDeletedEntityFilter(SqlSugarClient db)
+        {
+            //var cacheKey = $"DbFilter:{db.CurrentConnectionConfig.ConfigId}:IsDeleted";
+            //var tableFilterItemList = db.DataCache.Get<List<TableFilterItem<object>>>(cacheKey);
+            //if (tableFilterItemList == null)
+            //{
+            //    // 获取基类实体数据表
+            //    var entityTypes = AppDomain.CurrentDomain.GetAssemblies()
+            //        .SelectMany(d => d.GetTypes())
+            //        .Where(d => !d.IsInterface
+            //                    && !d.IsAbstract
+            //                    && d.IsClass
+            //                    && d.GetInterfaces().Any(x => x == typeof(ISoftDelete)));
+            //    if (!entityTypes.Any()) return;
+
+            //    var tableFilterItems = new List<TableFilterItem<object>>();
+            //    foreach (var entityType in entityTypes)
+            //    {
+            //        if (entityType.GetProperty("IsDeleted") is null) continue;
+            //        //// 排除非当前数据库实体
+            //        //var tAtt = entityType.GetCustomAttribute<TenantAttribute>();
+            //        //if ((tAtt != null && (string)db.CurrentConnectionConfig.ConfigId != tAtt.configId.ToString()) ||
+            //        //    (tAtt == null && (string)db.CurrentConnectionConfig.ConfigId != SqlSugarConst.ConfigId))
+            //        //    continue;
+
+            //        var lambda = DynamicExpressionParser.ParseLambda(new[] {
+            //            Expression.Parameter(entityType, "d") },
+            //        typeof(bool),
+            //        $"{nameof(SoftDeleteEntity.IsDeleted)} == @0", false);
+            //        var tableFilterItem = new TableFilterItem<object>(entityType, lambda);
+            //        tableFilterItems.Add(tableFilterItem);
+            //        db.QueryFilter.Add(tableFilterItem);
+            //    }
+            //    db.DataCache.Add(cacheKey, tableFilterItems);
+            //}
+            //else
+            //{
+            //    tableFilterItemList.ForEach(u =>
+            //    {
+            //        db.QueryFilter.Add(u);
+            //    });
+            //}
+        }
+
+        #endregion
+    }
+
+    public class SqlSugarConst
+    {
+        public static string PrimaryKey = "Id";
+    }
+}

+ 13 - 0
src/FileStorage.Repository.SqlSugar/FileStorage.Repository.SqlSugar.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\FileStorage\FileStorage.csproj" />
+  </ItemGroup>
+
+</Project>

+ 7 - 0
src/FileStorage.Repository.SqlSugar/FileStorageDbContext.cs

@@ -0,0 +1,7 @@
+using SqlSugar;
+
+namespace FileStorage.Repository.SqlSugar;
+
+public class FileStorageDbContext : SugarUnitOfWork
+{
+}

+ 8 - 0
src/FileStorage/DefaultFileStorage.cs

@@ -0,0 +1,8 @@
+using XF.Domain.Dependency;
+
+namespace FileStorage;
+
+public class DefaultFileStorage : IFileStorage, IScopeDependency
+{
+
+}

+ 20 - 0
src/FileStorage/FileMetadata.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using XF.Domain.Repository;
+
+namespace FileStorage
+{
+    /// <summary>
+    /// 文件数据
+    /// </summary>
+    public class FileMetadata : Entity
+    {
+        /// <summary>
+        /// 所属客户端
+        /// </summary>
+        public string Client { get; set; }
+    }
+}

+ 16 - 0
src/FileStorage/FileStorage.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Mapster" Version="7.3.0" />
+    <PackageReference Include="MediatR" Version="12.0.1" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
+    <PackageReference Include="XF.Domain.Repository" Version="1.0.2" />
+  </ItemGroup>
+
+</Project>

+ 13 - 0
src/FileStorage/IFileStorage.cs

@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace FileStorage
+{
+    public interface IFileStorage
+    {
+
+    }
+}