├── .dockerignore
├── .gitattributes
├── .gitignore
├── Application
├── Application.csproj
├── ApplicationService.cs
├── MongoContextService.cs
├── ScopedApplicationService.cs
├── SqlDbContextService.cs
└── TransientApplicationService.cs
├── Core.Web
├── ApiController.cs
├── ApiEventFilterAttribute.cs
├── ApiExceptionFilterAttribute.cs
├── Conventions
│ ├── ApiExplorerGroupNameConvention.cs
│ └── ServiceTemplateConvention.cs
├── Core.Web.csproj
├── Extensions
│ ├── ActionContextExtensions.cs
│ ├── ApiVersioningExtensions.cs
│ ├── ExceptionLessExtensions.cs
│ ├── InvalidModelStateExtensions.cs
│ ├── JwtParserExtensions.cs
│ ├── SwaggerDocEndpointExtensions.cs
│ ├── SwaggerGenOptionsExtensions.cs
│ └── SwaggerJwtAuthExtensions.cs
├── JsonPatchs
│ ├── JsonPatchDocument.cs
│ ├── JsonPatchInputFormatter.cs
│ ├── JsonPatchOperation.cs
│ └── TranscodingReadStream.cs
├── Jwts
│ ├── JwtOptions.cs
│ └── JwtParserMiddleware.cs
├── Middleware.cs
├── ModelBinding
│ └── StringPropertyTrimModelBinderProvider.cs
├── ServiceOptions.cs
└── Swaggers
│ ├── SwaggerApiVersionHeaderFilter.cs
│ ├── SwaggerGenOptionsConfigureOptions.cs
│ ├── SwaggerOptionsConfigureOptions.cs
│ ├── SwaggerRequiredParameterFilter.cs
│ ├── SwaggerRequiredSchemaFilter.cs
│ └── SwaggerUIOptionsConfigureOptions.cs
├── Core
├── ApiEvent.cs
├── ApiResult.cs
├── Apk
│ ├── ApKReader.cs
│ ├── ApkMedia.cs
│ └── ManifestReader.cs
├── Code.cs
├── Core.csproj
├── HostedBackgroundService.cs
├── HostedService.cs
├── HttpApis
│ ├── GatewayAttribute.cs
│ ├── GatewayOptions.cs
│ └── ServiceCollectionHttpApiExtensions.cs
├── IApiEventPublisher.cs
├── IConfigureOptions.cs
├── IScopeServiceProvider.cs
├── IScopedDependency.cs
├── ISingletonDependency.cs
├── ITransientDependency.cs
├── Jwt
│ ├── JwtClaimsPrincipal.cs
│ └── RSJwt.cs
├── Menus
│ ├── MenuGroup.cs
│ └── MenuItem.cs
├── Page.cs
├── RegisterAttribute.cs
├── ScopeServiceProvider.cs
├── ServiceCollectionDependencyExtensions.cs
├── ServiceCollectionOptionsExtensions.cs
└── Xls
│ ├── XlsColumnAttribute.cs
│ ├── XlsColumnParser.cs
│ ├── XlsDoc.cs
│ └── XlsSheet.cs
├── Corefx
├── AsyncRoot.cs
├── Buffers
│ ├── ArrayPool.cs
│ ├── BufferWriter.cs
│ ├── IArrayOwner.cs
│ └── MemoryOwnerExtensions.cs
├── Collections
│ └── Concurrent
│ │ └── ConcurrentCache.cs
├── Converter.cs
├── Corefx.csproj
├── Data
│ ├── Csv.cs
│ └── CsvExtensions.cs
├── Disposable.cs
├── EnumExtensions.cs
├── Guid16.cs
├── Linq
│ ├── Expressions
│ │ ├── Condition.cs
│ │ ├── ConditionItem.cs
│ │ ├── Operator.cs
│ │ ├── Predicate.cs
│ │ └── PredicateExtensions.cs
│ ├── IEnumerableExtensions.cs
│ └── IQueryableExtensions.cs
├── Net
│ ├── Mail
│ │ └── EMail.cs
│ └── Sockets
│ │ └── SocketAwaitableEventArgs.cs
├── NullableExtensions.cs
├── ObjectId.cs
├── Pipelines
│ ├── IMiddleware.cs
│ ├── IPipelineBuilder.cs
│ ├── InvokeDelegate.cs
│ ├── PipelineBuilder.cs
│ └── PipelineBuilderExtensions.cs
├── Reflection
│ ├── Lambda.cs
│ ├── Method.cs
│ └── Property.cs
├── Security
│ ├── DES.cs
│ └── MD5.cs
├── StringExtensions.cs
├── Text.Json.Serialization
│ ├── JsonLocalDateTimeConverter.cs
│ ├── JsonStringToEnumConverter.cs
│ └── JsonStringToNumberConverter.cs
├── Threading.Tasks
│ ├── ManualTask.cs
│ └── TaskPipeScheduler.cs
└── TypeExtensions.cs
├── Domain
├── DbSetExtensions.cs
├── Domain.csproj
├── ICreatorable.cs
├── IEntity.cs
├── IEntityView.cs
├── IIntercomable.cs
├── IMongoEntity.cs
├── IStringIdable.cs
├── ITenantable.cs
├── IUser.cs
├── IUserAccessor.cs
├── MongoDbContext.cs
├── MongoDbSet.cs
├── PagingExtensions.cs
├── SqlDbContext.cs
└── StringTrimFlagAttribute.cs
├── README.md
├── Web.Benchmark
├── DemoContext.cs
├── Program.cs
└── Web.Benchmark.csproj
├── Web.Host
├── Controllers
│ ├── v1
│ │ └── ValuesController.cs
│ └── v2
│ │ └── ValuesController.cs
├── Dockerfile
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Startup.cs
├── Web.Host.csproj
├── app.json
├── appsettings.Development.json
├── appsettings.json
├── build.cmd
├── docker.build.cmd
├── docker.build.sh
└── docker.run.sh
├── Web.Template.sln
└── Web.Test
├── 001.apk
├── 1.xls
├── 1.xlsx
├── Core
├── ApkTest.cs
├── MapTest.cs
└── XlsDocTest.cs
├── Corefx
└── IEnumerableExtensionsTest.cs
└── Web.Test.csproj
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.dockerignore
2 | **/.env
3 | **/.git
4 | **/.gitignore
5 | **/.vs
6 | **/.vscode
7 | **/*.*proj.user
8 | **/azds.yaml
9 | **/charts
10 | **/bin
11 | **/obj
12 | **/Dockerfile
13 | **/Dockerfile.develop
14 | **/docker-compose.yml
15 | **/docker-compose.*.yml
16 | **/*.dbmdl
17 | **/*.jfm
18 | **/secrets.dev.yaml
19 | **/values.dev.yaml
20 | **/.toolstarget
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/Application/Application.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Application/ApplicationService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Application
4 | {
5 | ///
6 | /// 表示服务抽象类
7 | ///
8 | public abstract class ApplicationService : Disposable
9 | {
10 | ///
11 | /// 释放资源
12 | ///
13 | /// 是否也释放非托管资源
14 | protected override void Dispose(bool disposing)
15 | {
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Application/MongoContextService.cs:
--------------------------------------------------------------------------------
1 | using Core;
2 | using Domain;
3 | using System;
4 | using System.Linq.Expressions;
5 | using System.Threading.Tasks;
6 |
7 | namespace Application
8 | {
9 | ///
10 | /// 表示基于Mongodb的抽象服务
11 | ///
12 | public abstract class MongoContextService : ScopedApplicationService where T : class, IMongoEntity
13 | {
14 | ///
15 | /// 获取db名
16 | ///
17 | public string DbName => "Talkback";
18 |
19 | ///
20 | /// 获取mongodb
21 | ///
22 | protected MongoDbContext MongoDb { get; }
23 |
24 | ///
25 | /// 基于Mongodb的抽象服务
26 | ///
27 | ///
28 | public MongoContextService(MongoDbContext mongoDb)
29 | {
30 | this.MongoDb = mongoDb;
31 | }
32 |
33 | ///
34 | /// 保存日志
35 | ///
36 | /// 日志
37 | ///
38 | public virtual async Task AddAsync(T log)
39 | {
40 | var dbName = this.GetDbName(DateTime.Now);
41 | await this.MongoDb.Set(dbName).AddAsync(log);
42 | }
43 |
44 | ///
45 | /// 条件删除第一条
46 | ///
47 | /// 日志
48 | ///
49 | public virtual async Task RemoveOneAsync(Expression> where)
50 | {
51 | var dbName = this.GetDbName(DateTime.Now);
52 | return await this.MongoDb.Set(dbName).RemoveOneAsync(where);
53 | }
54 |
55 | ///
56 | /// 条件删除
57 | ///
58 | /// 条件
59 | ///
60 | public virtual async Task RemoveAsync(Expression> where)
61 | {
62 | var dbName = this.GetDbName(DateTime.Now);
63 | return await this.MongoDb.Set(dbName).RemoveAsync(where);
64 | }
65 |
66 | ///
67 | /// 返回分页查询
68 | ///
69 | /// 获取哪个月
70 | /// 条件
71 | /// 页面索引
72 | /// 页面大小
73 | ///
74 | public virtual Task> GetPageAsync(DateTime month, Expression> where, int pageIndex, int pageSize)
75 | {
76 | var dbName = this.GetDbName(month);
77 | return this.MongoDb.Set(dbName).ToPageAsync(where, item => item.CreateTime, false, pageIndex, pageSize);
78 | }
79 |
80 | ///
81 | /// 返回dbName
82 | ///
83 | /// 月分
84 | ///
85 | protected virtual string GetDbName(DateTime month)
86 | {
87 | return $"{this.DbName}_{DateTime.Now.ToString("yyMM")}";
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Application/ScopedApplicationService.cs:
--------------------------------------------------------------------------------
1 | using Core;
2 |
3 | namespace Application
4 | {
5 | ///
6 | /// 表示Scoped模式注册的应用服务抽象类
7 | /// 每个请求产生一个实例
8 | ///
9 | public abstract class ScopedApplicationService : ApplicationService, IScopedDependency
10 | {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Application/SqlDbContextService.cs:
--------------------------------------------------------------------------------
1 | using Core;
2 | using Domain;
3 | using Microsoft.EntityFrameworkCore;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Diagnostics.CodeAnalysis;
7 | using System.Linq;
8 | using System.Linq.Expressions;
9 | using System.Threading.Tasks;
10 |
11 | namespace Application
12 | {
13 | ///
14 | /// 表示基于数据库的抽象服务
15 | ///
16 | ///
17 | public abstract class SqlDbContextService : ScopedApplicationService where T : class, IStringIdable
18 | {
19 | ///
20 | /// 获取db上下文
21 | ///
22 | protected SqlDbContext Db { get; }
23 |
24 | ///
25 | /// 基于数据库的抽象服务
26 | ///
27 | ///
28 | public SqlDbContextService(SqlDbContext db)
29 | {
30 | this.Db = db;
31 | }
32 |
33 | ///
34 | /// 检测是否存在
35 | ///
36 | ///
37 | ///
38 | public Task ExistsAsync([NotNull]Expression> where)
39 | {
40 | return this.Db.Set().AnyAsync(where);
41 | }
42 |
43 | ///
44 | /// 根据Id获取记录
45 | ///
46 | ///
47 | ///
48 | public ValueTask FindAsync(string id)
49 | {
50 | return this.Db.Set().FindAsync(id);
51 | }
52 |
53 | ///
54 | /// 根据条件获取一条记录
55 | ///
56 | ///
57 | ///
58 | public async Task FindAsync(Expression> where)
59 | {
60 | return await this.Db.Set().Where(where).FirstOrDefaultAsync();
61 | }
62 |
63 | ///
64 | /// 根据Id获取记录
65 | ///
66 | ///
67 | ///
68 | ///
69 | public Task FindAsync(string id, [NotNull]Expression> selector)
70 | {
71 | return this.Db.Set().Where(item => item.Id == id).Select(selector).FirstOrDefaultAsync();
72 | }
73 |
74 | ///
75 | /// 根据Id获取记录
76 | ///
77 | ///
78 | ///
79 | public Task FindManyAsync(IEnumerable id)
80 | {
81 | var where = Predicate.CreateOrEqual(item => item.Id, id);
82 | return this.FindManyAsync(where);
83 | }
84 |
85 | ///
86 | /// 根据条件获取记录
87 | ///
88 | ///
89 | ///
90 | public Task FindManyAsync([NotNull]Expression> where)
91 | {
92 | return this.Db.Set().Where(where).ToArrayAsync();
93 | }
94 |
95 | ///
96 | /// 根据条件获取记录
97 | ///
98 | ///
99 | ///
100 | ///
101 | public Task FindManyAsync([NotNull]Expression> where, [NotNull]Expression> selector)
102 | {
103 | return this.Db.Set().Where(where).Select(selector).ToArrayAsync();
104 | }
105 |
106 | ///
107 | /// 获取记录分页数据
108 | ///
109 | /// 排序
110 | /// 当前页码索引,从0开始
111 | /// 每页数据
112 | /// 查询条件
113 | ///
114 | public Task> GetPageAsync([NotNull]string orderBy, int pageIndex, int pageSize, [NotNull]Expression> where)
115 | {
116 | return this.Db.Set().Where(where).ToPageAsync(orderBy, pageIndex, pageSize);
117 | }
118 |
119 |
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/Application/TransientApplicationService.cs:
--------------------------------------------------------------------------------
1 | using Core;
2 |
3 | namespace Application
4 | {
5 | ///
6 | /// 表示Transient模式注册的应用服务抽象类
7 | /// 每次获取产生一个实例
8 | ///
9 | public abstract class TransientApplicationService : ApplicationService, ITransientDependency
10 | {
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Core.Web/ApiController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq.Expressions;
5 | using System.Reflection;
6 |
7 | namespace Core.Web
8 | {
9 | ///
10 | /// 表示Api控制器
11 | /// 提供查询条件表达式的获取
12 | ///
13 | [ApiController]
14 | [ApiExceptionFilter]
15 | [Route("api/[service]/[controller]")]
16 | public abstract class ApiController : ControllerBase
17 | {
18 | ///
19 | /// 从请求Query获取查询表达式
20 | ///
21 | ///
22 | ///
23 | ///
24 | protected Expression> GetQueryPredicate(bool trueWhenNull = true)
25 | {
26 | return this.GetQueryCondition().ToAndPredicate(trueWhenNull);
27 | }
28 |
29 | ///
30 | /// 从请求Query获取查询条件
31 | ///
32 | ///
33 | ///
34 | protected virtual Condition GetQueryCondition()
35 | {
36 | var items = this.GetQueryConditionItems();
37 | return new Condition(items);
38 | }
39 |
40 | ///
41 | /// 从Query获取条件项
42 | ///
43 | ///
44 | ///
45 | private IEnumerable> GetQueryConditionItems()
46 | {
47 | bool TryGetTrimValue(PropertyInfo property, string prefx, out string value)
48 | {
49 | if (this.Request.Query.TryGetValue($"{prefx}{property.Name}", out var stringValues))
50 | {
51 | value = stringValues.Count == 0 ? null : stringValues[0].NullThenEmpty().Trim();
52 | return value.IsNullOrEmpty() == false;
53 | }
54 |
55 | value = null;
56 | return false;
57 | }
58 |
59 | foreach (var property in ConditionItem.TypeProperties)
60 | {
61 | if (TryGetTrimValue(property, null, out var value))
62 | {
63 | yield return new ConditionItem(property, value, null);
64 | }
65 |
66 | if (property.PropertyType.IsValueType)
67 | {
68 | if (TryGetTrimValue(property, "min", out var minValue))
69 | {
70 | yield return new ConditionItem(property, minValue, Operator.GreaterThanOrEqual);
71 | }
72 | if (TryGetTrimValue(property, "max", out var maxValue))
73 | {
74 | yield return new ConditionItem(property, maxValue, Operator.LessThanOrEqual);
75 | }
76 | }
77 | }
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Core.Web/ApiEventFilterAttribute.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc.Filters;
2 | using Microsoft.Extensions.DependencyInjection;
3 | using System;
4 | using System.Linq;
5 | using System.Security.Claims;
6 | using System.Threading.Tasks;
7 |
8 | namespace Core.Web
9 | {
10 | ///
11 | /// Api事件过滤器
12 | /// 使用IApiEventPublisher发布将请求Api的内容
13 | ///
14 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
15 | public class ApiEventFilterAttribute : Attribute, IAsyncActionFilter, IOrderedFilter
16 | {
17 | ///
18 | /// 获取或设置是否启用
19 | /// 默认为true
20 | ///
21 | public bool Enbale { get; set; } = true;
22 |
23 | ///
24 | /// 获取或设置排序顺序
25 | ///
26 | public int Order { get; set; } = 0;
27 |
28 | ///
29 | /// Action执行
30 | ///
31 | ///
32 | ///
33 | ///
34 | public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
35 | {
36 | await next.Invoke();
37 |
38 | if (this.Enbale == true)
39 | {
40 | await this.OnActionExecutedAsync(context);
41 | }
42 | }
43 |
44 | ///
45 | /// Action执行后
46 | ///
47 | ///
48 | ///
49 | private async Task OnActionExecutedAsync(ActionExecutingContext context)
50 | {
51 | var publisher = context.HttpContext.RequestServices.GetService();
52 | if (publisher == null)
53 | {
54 | return;
55 | }
56 |
57 | var request = context.HttpContext.Request;
58 | var apiEvent = new ApiEvent
59 | {
60 | Uri = new Uri(request.Path + request.QueryString, UriKind.Relative),
61 | UserId = this.GetUserId(context.HttpContext.User),
62 | CreateTime = DateTime.Now
63 | };
64 |
65 | var api = context.GetApiDescription();
66 | var contentParameter = api.ParameterDescriptions
67 | .Where(item => item.Source.Id.EqualsIgnoreCase("BODY") || item.Source.Id.EqualsIgnoreCase("FORM"))
68 | .FirstOrDefault();
69 |
70 | if (contentParameter != null && context.ActionArguments.TryGetValue(contentParameter.Name, out var content) == true)
71 | {
72 | apiEvent.Content = content;
73 | }
74 |
75 | await publisher.PulishAsync(api.RelativePath, apiEvent);
76 | }
77 |
78 |
79 | ///
80 | /// 返回用户信息Id
81 | ///
82 | ///
83 | ///
84 | protected virtual string GetUserId(ClaimsPrincipal user)
85 | {
86 | return user.FindFirstValue("sub");
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/Core.Web/ApiExceptionFilterAttribute.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 | using Microsoft.AspNetCore.Mvc.Filters;
3 | using Microsoft.Extensions.DependencyInjection;
4 | using Microsoft.Extensions.Logging;
5 | using System;
6 | using System.Linq;
7 |
8 | namespace Core.Web
9 | {
10 | ///
11 | /// 表示Api异常处理过滤器
12 | /// 将异常转换为IApiResult返回值
13 | ///
14 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
15 | public class ApiExceptionFilterAttribute : Attribute, IExceptionFilter, IOrderedFilter
16 | {
17 | ///
18 | /// 获取或设置是否启用
19 | /// 默认为true
20 | ///
21 | public bool Enbale { get; set; } = true;
22 |
23 | ///
24 | /// 获取或设置排序顺序
25 | ///
26 | public int Order { get; set; } = 0;
27 |
28 | ///
29 | /// 异常时
30 | ///
31 | ///
32 | public virtual void OnException(ExceptionContext context)
33 | {
34 | if (this.Enbale == false)
35 | {
36 | return;
37 | }
38 |
39 | var apiResultType = context.GetApiDescription()
40 | .SupportedResponseTypes
41 | .Select(item => item.Type)
42 | .Where(item => item.IsInheritFrom())
43 | .FirstOrDefault();
44 |
45 | if (apiResultType == null)
46 | {
47 | apiResultType = typeof(ApiResult