初始化项目

This commit is contained in:
zpc 2024-10-10 00:58:13 +08:00
parent 39ffb5c190
commit 3b0d56a0ce
60 changed files with 4362 additions and 0 deletions

230
.editorconfig Normal file
View File

@ -0,0 +1,230 @@
root = true
[*.cs]
charset = utf-8 #设置文件字符集为utf-8,在 Linux 系统中,通常推荐使用 UTF-8 而不是 UTF-8 with BOM。添加 BOM 可能会干扰那些不期望在文件开头出现非 ASCII 字节的软件对 UTF-8 的使用。
#### Core EditorConfig 选项 ####
# 缩进和间距
indent_size = 4
indent_style = space
tab_width = 4
# 新行首选项
end_of_line = crlf
insert_final_newline = false
#### .NET 编码约定 ####
# 组织 Using
dotnet_separate_import_directive_groups = true
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. 和 Me. 首选项
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# 语言关键字与 bcl 类型首选项
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# 括号首选项
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# 修饰符首选项
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# 表达式级首选项
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_collection_expression = when_types_loosely_match
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# 字段首选项
dotnet_style_readonly_field = false
# 参数首选项
dotnet_code_quality_unused_parameters = all:silent
# 禁止显示首选项
dotnet_remove_unnecessary_suppression_exclusions = none
# 新行首选项
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### c# 编码约定 ####
# var 首选项
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied 成员
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# 模式匹配首选项
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = false:silent
# Null 检查首选项
csharp_style_conditional_delegate_call = true
# 修饰符首选项
csharp_prefer_static_local_function = false
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = false
csharp_style_prefer_readonly_struct_member = true
# 代码块首选项
csharp_prefer_braces = true:suggestion
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = block_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_primary_constructors = true
csharp_style_prefer_top_level_statements = true
# 表达式级首选项
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = false:silent
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = false:silent
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_tuple_swap = false:silent
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable:silent
csharp_style_unused_value_expression_statement_preference = discard_variable
# "using" 指令首选项
csharp_using_directive_placement = outside_namespace:suggestion
# 新行首选项
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# 格式规则 ####
# 新行首选项
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# 缩进首选项
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# 空格键首选项
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# 包装首选项
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### 命名样式 ####
# 命名规则
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# 符号规范
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# 命名样式
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

63
.gitattributes vendored Normal file
View File

@ -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

365
.gitignore vendored Normal file
View File

@ -0,0 +1,365 @@
## 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
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# 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
nunit-*.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/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# 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
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# 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
# NuGet Symbol Packages
*.snupkg
# 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
*.appxbundle
*.appxupload
# 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
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).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/
# 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
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
output/
logs/

View File

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

View File

@ -0,0 +1,230 @@
root = true
[*.cs]
charset = utf-8 #设置文件字符集为utf-8,在 Linux 系统中,通常推荐使用 UTF-8 而不是 UTF-8 with BOM。添加 BOM 可能会干扰那些不期望在文件开头出现非 ASCII 字节的软件对 UTF-8 的使用。
#### Core EditorConfig 选项 ####
# 缩进和间距
indent_size = 4
indent_style = space
tab_width = 4
# 新行首选项
end_of_line = crlf
insert_final_newline = false
#### .NET 编码约定 ####
# 组织 Using
dotnet_separate_import_directive_groups = true
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. 和 Me. 首选项
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# 语言关键字与 bcl 类型首选项
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# 括号首选项
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# 修饰符首选项
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# 表达式级首选项
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_collection_expression = when_types_loosely_match
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# 字段首选项
dotnet_style_readonly_field = false
# 参数首选项
dotnet_code_quality_unused_parameters = all:silent
# 禁止显示首选项
dotnet_remove_unnecessary_suppression_exclusions = none
# 新行首选项
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### c# 编码约定 ####
# var 首选项
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied 成员
csharp_style_expression_bodied_accessors = true
csharp_style_expression_bodied_constructors = false
csharp_style_expression_bodied_indexers = true
csharp_style_expression_bodied_lambdas = true
csharp_style_expression_bodied_local_functions = false
csharp_style_expression_bodied_methods = false
csharp_style_expression_bodied_operators = false
csharp_style_expression_bodied_properties = true
# 模式匹配首选项
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = false:silent
# Null 检查首选项
csharp_style_conditional_delegate_call = true
# 修饰符首选项
csharp_prefer_static_local_function = false
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
csharp_style_prefer_readonly_struct = false
csharp_style_prefer_readonly_struct_member = true
# 代码块首选项
csharp_prefer_braces = true:suggestion
csharp_prefer_simple_using_statement = true
csharp_style_namespace_declarations = block_scoped
csharp_style_prefer_method_group_conversion = true
csharp_style_prefer_primary_constructors = true
csharp_style_prefer_top_level_statements = true
# 表达式级首选项
csharp_prefer_simple_default_expression = true
csharp_style_deconstructed_variable_declaration = true
csharp_style_implicit_object_creation_when_type_is_apparent = false:silent
csharp_style_inlined_variable_declaration = true
csharp_style_prefer_index_operator = false:silent
csharp_style_prefer_local_over_anonymous_function = true
csharp_style_prefer_null_check_over_type_check = true
csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_tuple_swap = false:silent
csharp_style_prefer_utf8_string_literals = true
csharp_style_throw_expression = true
csharp_style_unused_value_assignment_preference = discard_variable:silent
csharp_style_unused_value_expression_statement_preference = discard_variable
# "using" 指令首选项
csharp_using_directive_placement = outside_namespace:suggestion
# 新行首选项
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
csharp_style_allow_embedded_statements_on_same_line_experimental = true
#### C# 格式规则 ####
# 新行首选项
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# 缩进首选项
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# 空格键首选项
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# 包装首选项
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### 命名样式 ####
# 命名规则
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# 符号规范
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# 命名样式
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

View File

@ -0,0 +1,27 @@
namespace HuanMeng.DotNetCore.Base
{
public abstract class BLLBase<TDao> where TDao : DaoBase
{
/// <summary>
/// _serviceProvider,提供基本依赖注入支持
/// </summary>
protected readonly IServiceProvider _serviceProvider;
/// <summary>
/// _dao,提供数据访问支持
/// </summary>
public abstract TDao Dao { get; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="dao"></param>
public BLLBase(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}
}

View File

@ -0,0 +1,92 @@
using System.Runtime.Serialization;
using XLib.DotNetCore.Base;
namespace HuanMeng.DotNetCore.Base
{
/// <summary>
/// 接口和服务调用基础响应类
/// </summary>
/// <typeparam name="T"></typeparam>
[DataContract]
[Serializable]
public class BaseResponse<T> : IResponse
{
///// <summary>
///// Http状态码
///// </summary>
//[DataMember]
//public HttpStatusCode StatusCode { get; set; }
/// <summary>
/// 功能执行返回代码
/// </summary>
[DataMember]
public int Code { get; set; }
/// <summary>
/// 消息
/// </summary>
[DataMember]
public string Message { get; set; }
/// <summary>
/// 数据
/// </summary>
[DataMember]
public T? Data { get; set; }
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse()
{
//StatusCode = HttpStatusCode.OK;
Code = 0;
Message = "";
}
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse(int code, string message)
{
Code = code;
Message = message;
Data = default(T);
}
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse(int code, string message, T? data)
{
Code = code;
Message = message;
Data = data;
}
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse(ResonseCode code, string message, T? data)
{
Code = (int)code;
Message = message;
Data = data;
}
/// <summary>
/// ToString
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Code:{Code};Message:{Message}; Data:{Data}";
}
}
}

View File

@ -0,0 +1,32 @@
namespace HuanMeng.DotNetCore.Base
{
public class DaoBase
{
/// <summary>
/// _serviceProvider,提供基本依赖注入支持
/// </summary>
protected readonly IServiceProvider _serviceProvider;
///// <summary>
///// 构造函数
///// </summary>
//public DaoBase()
//{
// // 创建一个空的ServiceCollection
// var webApplication = WebApplication.Current;
// var services = new ServiceCollection();
// // 创建ServiceProvider实例
// _serviceProvider = services.BuildServiceProvider();
//}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="serviceProvider"></param>
public DaoBase(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
}
}

View File

@ -0,0 +1,195 @@
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.Base
{ /// <summary>
/// 基本数据库操作,需要安装
/// Microsoft.EntityFrameworkCore
/// Microsoft.EntityFrameworkCore.Relational
/// </summary>
/// <typeparam name="TDbContext"></typeparam>
public class EfCoreDaoBase<TDbContext> where TDbContext : DbContext
//, new()
{
private TDbContext _context;
public TDbContext context
{
get
{
//return _context ?? (_context = new TDbContext());
return _context;
}
//set { _context = value; }
}
//public EfCoreDaoBase() { }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="context"></param>
public EfCoreDaoBase(TDbContext context)
{
_context = context;
}
/// <summary>
/// 是否手动提交
/// </summary>
public bool IsManualSubmit { get; set; }
/// <summary>
/// SqlQueryRaw
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public T SqlQuery<T>(string sql, params object[] parameters) where T : class
{
var quiry = context.Database.SqlQueryRaw<T>(sql, parameters);
return quiry.FirstOrDefault();
}
/// <summary>
/// SqlQueryList
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public List<T> SqlQueryList<T>(string sql, params object[] parameters)
{
var quiry = context.Database.SqlQueryRaw<T>(sql, parameters);
return quiry.ToList();
}
/// <summary>
/// ExecuteSql
/// </summary>
/// <param name="sql"></param>
/// <param name="parameters"></param>
/// <returns></returns>
public int ExecuteSql(string sql, params object[] parameters)
{
int r = context.Database.ExecuteSqlRaw(sql, parameters);
context.SaveChanges();
return r;
}
/// <summary>
/// 添加实体
/// </summary>
/// <param name="entity"></param>
public void Add<T>(T entity) where T : class
{
context.Set<T>().Add(entity);
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 批量添加实体
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
public void AddRange<T>(List<T> entity) where T : class
{
context.Set<T>().AddRange(entity);
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 删除某个实体
/// </summary>
/// <param name="entity"></param>
public void Delete<T>(T entity) where T : class
{
context.Entry<T>(entity).State = EntityState.Deleted; //整条更新
//context.Set<T>().Remove(entity);
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 更新实体
/// </summary>
/// <param name="entity"></param>
public void Update<T>(T entity) where T : class
{
//一般不需要整条更新,按需更新字段即可
if (context.Entry<T>(entity).State == EntityState.Detached)
{
context.Set<T>().Attach(entity);
context.Entry<T>(entity).State = EntityState.Modified; //整条更新
}
if (!IsManualSubmit)
context.SaveChanges();
}
/// <summary>
/// 清除上下文跟踪(清除缓存) by wyg
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity"></param>
public void removeTracking<T>(T entity) where T : class
{
if (context.Entry<T>(entity).State != EntityState.Detached)
context.Entry<T>(entity).State = EntityState.Detached;
}
/// <summary>
/// 获取实体,从缓存中,根据键值获取
/// </summary>
/// <param name="keyName"></param>
/// <param name="keyValue"></param>
/// <returns></returns>
public T GetModel<T>(params object[] keyValues) where T : class
{
return context.Set<T>().Find(keyValues);
}
/// <summary>
/// 获取实体,根据条件获取,从数据库获取
/// </summary>
/// <param name="where">筛选条件</param>
/// <param name="isNoTracking">默认false会进行缓存并跟踪(可按需update字段)true不需要缓存和跟踪(一般只读查询使用)</param>
/// <returns></returns>
public T GetModel<T>(System.Linq.Expressions.Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
{
if (!isNoTracking)
return context.Set<T>().FirstOrDefault(where); //可按需update字段
return context.Set<T>().AsNoTracking<T>().FirstOrDefault(where); //一般只读查询使用
}
/// <summary>
/// 获取列表数据返回IQueryable
/// </summary>
/// <param name="where">筛选条件</param>
/// <param name="isNoTracking">默认false会进行缓存并跟踪(可按需update字段)true不需要缓存和跟踪(一般只读查询使用)</param>
/// <returns></returns>
public IQueryable<T> GetList<T>(System.Linq.Expressions.Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
{
if (!isNoTracking)
return context.Set<T>().Where(where).AsQueryable();
return context.Set<T>().Where(where).AsNoTracking<T>().AsQueryable();
}
public int GetCount<T>(System.Linq.Expressions.Expression<Func<T, bool>> where) where T : class
{
return context.Set<T>().AsNoTracking<T>().Count(where);
}
public bool Exists<T>(System.Linq.Expressions.Expression<Func<T, bool>> where) where T : class
{
//return context.Set<T>().AsNoTracking<T>().Any(where);
return context.Set<T>().AsNoTracking<T>().Count(where) > 0;
}
}
}

View File

@ -0,0 +1,23 @@
namespace XLib.DotNetCore.Base
{
/// <summary>
/// 接口和服务调用通用响应接口
/// </summary>
public interface IResponse
{
///// <summary>
///// Http状态码
///// </summary>
//HttpStatusCode StatusCode { get; set; }
/// <summary>
/// 功能执行返回代码
/// </summary>
int Code { get; set; }
/// <summary>
/// 消息
/// </summary>
string Message { get; set; }
}
}

View File

@ -0,0 +1,53 @@
namespace HuanMeng.DotNetCore.Base
{
/// <summary>
/// 响应编码参考,实际的项目使用可以自行定义
/// 基本规则:
/// 成功大于等于0
/// 失败小于0
/// </summary>
public enum ResonseCode
{
/// <summary>
/// Sign签名错误
/// </summary>
SignError = -999,
/// <summary>
/// jwt用户签名错误
/// </summary>
TwtError = -998,
/// <summary>
/// 用户验证失败
/// </summary>
Unauthorized = 401,
/// <summary>
/// 重复请求
/// </summary>
ManyRequests = 429,
/// <summary>
/// 正在处理中
/// </summary>
Processing = 102,
/// <summary>
/// 通用错误
/// </summary>
Error = -1,
/// <summary>
/// 参数错误
/// </summary>
ParamError = -2,
/// <summary>
/// 没找到数据记录
/// </summary>
NotFoundRecord = -3,
/// <summary>
/// 成功
/// </summary>
Success = 0,
}
}

View File

@ -0,0 +1,55 @@
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XLib.DotNetCore.CacheHelper
{
/// <summary>
/// 内存缓存帮助类
/// </summary>
public class MemoryCacheHelper
{
public static MemoryCache cache = new MemoryCache(new MemoryCacheOptions());
/// <summary>
/// 获取缓存
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="cacheName"></param>
/// <returns></returns>
public static T? GetCache<T>(string cacheName) where T : class, new()
{
return cache.TryGetValue(cacheName, out var value) ? value as T : null;
}
/// <summary>
/// 设置缓存
/// </summary>
/// <param name="cacheName"></param>
/// <param name="val"></param>
/// <param name="cacheTime">单位秒默认1小时</param>
public static void SetCache(string cacheName, object val, int cacheTime = 21000)
{
//数据量渐渐大了,每次因为很多都是同时缓存 所以在这里分流一下
if (cacheTime == 21000)
cacheTime = new Random().Next(21000, 43200);
cache.Set(cacheName, val, TimeSpan.FromSeconds(cacheTime));
}
/// <summary>
/// 删除缓存
/// </summary>
/// <param name="cacheName"></param>
public static void DelCache(string? cacheName = null)
{
if (!string.IsNullOrEmpty(cacheName))
cache.Remove(cacheName);
else
cache.Dispose();
}
}
}

View File

@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
namespace HuanMeng.DotNetCore.CustomExtension
{
/// <summary>
/// 添加跨域
/// </summary>
public static class CorsExtension
{
/// <summary>
/// 添加跨域
/// </summary>
/// <param name="services"></param>
/// <param name="policyName"></param>
public static void AddCustomCors(this IServiceCollection services, string policyName)
{
services.AddCors(options =>
{
options.AddPolicy(policyName,
builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
}
}
}

View File

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AlipayEasySDK.Kernel" Version="1.0.6" />
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="StackExchange.Redis" Version="2.8.16" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.Json
{
public class CustomDateTimeConverter : JsonConverter<DateTime>
{
private readonly string _format;
public CustomDateTimeConverter(string format)
{
_format = format;
}
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(_format));
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure.Interface
{
/// <summary>
/// jwt帮助类
/// </summary>
public interface IJwtAuthManager
{
/// <summary>
/// 用户刷新令牌只读词典
/// </summary>
IImmutableDictionary<string, JwtRefreshToken> UsersRefreshTokensReadOnlyDictionary { get; }
/// <summary>
/// 生成令牌
/// </summary>
/// <param name="username">用户名</param>
/// <param name="claims">用户的有关信息</param>
/// <param name="now"></param>
/// <returns></returns>
JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now);
/// <summary>
/// 刷新令牌
/// </summary>
/// <param name="refreshToken"></param>
/// <param name="accessToken"></param>
/// <param name="now"></param>
/// <returns></returns>
JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now);
/// <summary>
/// 删除过期的刷新令牌
/// </summary>
/// <param name="now"></param>
void RemoveExpiredRefreshTokens(DateTime now);
/// <summary>
/// 按用户名删除刷新令牌
/// </summary>
/// <param name="userName"></param>
void RemoveRefreshTokenByUserName(string userName);
/// <summary>
/// 解码JwtToken
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
(ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token);
}
}

View File

@ -0,0 +1,168 @@
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure
{
/// <summary>
/// jwt帮助类
/// </summary>
/// <param name="jwtTokenConfig"></param>
public class JwtAuthManager(JwtTokenConfig jwtTokenConfig) : IJwtAuthManager
{
/// <summary>
/// 保存刷新token
/// </summary>
public IImmutableDictionary<string, JwtRefreshToken> UsersRefreshTokensReadOnlyDictionary => _usersRefreshTokens.ToImmutableDictionary();
/// <summary>
/// 后面可以存储在数据库或分布式缓存中
/// </summary>
private readonly ConcurrentDictionary<string, JwtRefreshToken> _usersRefreshTokens = new();
/// <summary>
/// 获取加密字段
/// </summary>
private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Secret);
/// <summary>
/// 删除过期token
/// </summary>
/// <param name="now"></param>
public void RemoveExpiredRefreshTokens(DateTime now)
{
var expiredTokens = _usersRefreshTokens.Where(x => x.Value.ExpireAt < now).ToList();
foreach (var expiredToken in expiredTokens)
{
_usersRefreshTokens.TryRemove(expiredToken.Key, out _);
}
}
/// <summary>
/// 根据用户名删除token
/// </summary>
/// <param name="userName"></param>
public void RemoveRefreshTokenByUserName(string userName)
{
var refreshTokens = _usersRefreshTokens.Where(x => x.Value.UserName == userName).ToList();
foreach (var refreshToken in refreshTokens)
{
_usersRefreshTokens.TryRemove(refreshToken.Key, out _);
}
}
/// <summary>
/// 创建token
/// </summary>
/// <param name="username">用户名</param>
/// <param name="claims">用户项</param>
/// <param name="now">过期时间</param>
/// <returns></returns>
public JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now)
{
var shouldAddAudienceClaim = string.IsNullOrWhiteSpace(claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Aud)?.Value);
//创建token
var jwtToken = new JwtSecurityToken(
jwtTokenConfig.Issuer,
shouldAddAudienceClaim ? jwtTokenConfig.Audience : string.Empty,
claims,
expires: now.AddMinutes(jwtTokenConfig.AccessTokenExpiration),
signingCredentials: new SigningCredentials(new SymmetricSecurityKey(_secret), SecurityAlgorithms.HmacSha256Signature));
var accessToken = new JwtSecurityTokenHandler().WriteToken(jwtToken);
//创建刷新token
var refreshToken = new JwtRefreshToken
{
UserName = username,
TokenString = GenerateRefreshTokenString(),
ExpireAt = now.AddMinutes(jwtTokenConfig.RefreshTokenExpiration)
};
_usersRefreshTokens.AddOrUpdate(refreshToken.TokenString, refreshToken, (_, _) => refreshToken);
return new JwtAuthResult
{
AccessToken = accessToken,
RefreshToken = refreshToken
};
}
/// <summary>
/// 刷新token
/// </summary>
/// <param name="refreshToken"></param>
/// <param name="accessToken"></param>
/// <param name="now"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
public JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now)
{
var (principal, jwtToken) = DecodeJwtToken(accessToken);
if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature))
{
throw new SecurityTokenException("无效的token");
}
var userName = principal.Identity?.Name;
if (!_usersRefreshTokens.TryGetValue(refreshToken, out var existingRefreshToken))
{
throw new SecurityTokenException("token已失效");
}
if (existingRefreshToken.UserName != userName || existingRefreshToken.ExpireAt < now)
{
throw new SecurityTokenException("token不匹配");
}
//创建新的token
return GenerateTokens(userName, principal.Claims.ToArray(), now);
}
/// <summary>
/// 解析token
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
/// <exception cref="SecurityTokenException"></exception>
public (ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token)
{
if (string.IsNullOrWhiteSpace(token))
{
throw new SecurityTokenException("token不能为空");
}
var principal = new JwtSecurityTokenHandler()
.ValidateToken(token,
new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtTokenConfig.Issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(_secret),
ValidAudience = jwtTokenConfig.Audience,
ValidateAudience = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
},
out var validatedToken);
return (principal, validatedToken as JwtSecurityToken);
}
/// <summary>
/// 获取刷新的token
/// </summary>
/// <returns></returns>
private static string GenerateRefreshTokenString()
{
var randomNumber = new byte[32];
using var randomNumberGenerator = RandomNumberGenerator.Create();
randomNumberGenerator.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure
{
/// <summary>
/// 令牌
/// </summary>
public class JwtAuthResult
{
/// <summary>
/// 当前token
/// </summary>
[JsonPropertyName("accessToken")]
public string AccessToken { get; set; } = string.Empty;
/// <summary>
/// 刷新token
/// </summary>
[JsonPropertyName("refreshToken")]
public JwtRefreshToken RefreshToken { get; set; } = new();
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure
{
/// <summary>
/// 刷新token
/// </summary>
public class JwtRefreshToken
{
/// <summary>
/// 用户名
/// </summary>
[JsonPropertyName("username")]
public string UserName { get; set; } = string.Empty;
/// <summary>
/// token
/// </summary>
[JsonPropertyName("tokenString")]
public string TokenString { get; set; } = string.Empty;
/// <summary>
/// 过期时间
/// </summary>
[JsonPropertyName("expireAt")]
public DateTime ExpireAt { get; set; }
}
}

View File

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.JwtInfrastructure
{
/// <summary>
/// JwtToken 配置文件
/// </summary>
public class JwtTokenConfig
{
/// <summary>
/// 加密值
/// </summary>
[JsonPropertyName("secret")]
public string Secret { get; set; } = string.Empty;
/// <summary>
/// 颁发者
/// </summary>
[JsonPropertyName("issuer")]
public string Issuer { get; set; } = string.Empty;
/// <summary>
/// 受众
/// </summary>
[JsonPropertyName("audience")]
public string Audience { get; set; } = string.Empty;
/// <summary>
/// 令牌过期时间
/// </summary>
[JsonPropertyName("accessTokenExpiration")]
public int AccessTokenExpiration { get; set; }
/// <summary>
/// 刷新令牌过期时间(一般会比令牌过期时间长)
/// </summary>
[JsonPropertyName("refreshTokenExpiration")]
public int RefreshTokenExpiration { get; set; }
}
}

View File

@ -0,0 +1,71 @@
using HuanMeng.DotNetCore.Base;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System;
namespace HuanMeng.DotNetCore.MiddlewareExtend
{
/// <summary>
/// 异常中间件
/// </summary>
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (ArgumentNullException ex)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
context.Response.StatusCode = StatusCodes.Status200OK;
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.ParamError, ex.ParamName ?? "参数错误", null)
{
};
context.Response.ContentType = "application/json; charset=utf-8";
// 将异常信息写入 HTTP 响应
await context.Response.WriteAsync(JsonConvert.SerializeObject(baseResponse, settings));
}
catch (Exception ex)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
_logger.LogError(context.Request.Path.ToString(), ex, "异常记录");
// 设置 HTTP 响应状态码为 500
context.Response.ContentType = "application/json; charset=utf-8";
context.Response.StatusCode = StatusCodes.Status200OK;
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.Error, ex.Message, null);
// 将异常信息写入 HTTP 响应
await context.Response.WriteAsync(JsonConvert.SerializeObject(baseResponse, settings));
}
finally
{
}
}
}
}

View File

@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Http;
using System.Diagnostics;
namespace HuanMeng.DotNetCore.MiddlewareExtend
{
/// <summary>
/// 方法执行时间
/// </summary>
public class ExecutionTimeMiddleware(RequestDelegate _next)
{
public async Task Invoke(HttpContext context)
{
// 开始计时
var sw = Stopwatch.StartNew();
//在将响应标头发送到之前添加要调用的委托客户此处注册的回调按相反顺序运行。
context.Response.OnStarting(() =>
{
sw.Stop();
context.Response.Headers.TryAdd("X-Request-Duration", $"{sw.Elapsed.TotalMilliseconds} ms");
return Task.CompletedTask;
});
await _next(context);
}
}
}

View File

@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Builder;
namespace HuanMeng.DotNetCore.MiddlewareExtend
{
/// <summary>
/// 中间库扩展
/// </summary>
public static class MiddlewareExtends
{
/// <summary>
/// 加载全部中间件
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseMiddlewareAll(this IApplicationBuilder builder)
{
return builder
.UseExceptionMiddleware()
.UseExecutionTimeMiddleware()
.UseSignMiddleware()
;
}
/// <summary>
/// 异常中间件
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseExecutionTimeMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExecutionTimeMiddleware>();
}
/// <summary>
/// 执行时间中间件
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExceptionMiddleware>();
}
/// <summary>
/// 加密验证
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
public static IApplicationBuilder UseSignMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SignMiddleware>();
}
}
}

View File

@ -0,0 +1,101 @@
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using Newtonsoft.Json.Linq;
using HuanMeng.DotNetCore.Base;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace HuanMeng.DotNetCore.MiddlewareExtend
{
/// <summary>
/// 参数请求加密验证
/// </summary>
public class SignMiddleware
{
private readonly RequestDelegate _next;
private const string FixedString = "cccc"; // 固定字符串
public SignMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
// 读取请求体
context.Request.EnableBuffering(); // 启用请求流的多次读取功能
var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
context.Request.Body.Position = 0; // 重置请求体的位置
if (string.IsNullOrEmpty(requestBody))
{
await _next(context);
return;
}
// 解析请求体为 JSON 对象
var requestJson = JObject.Parse(requestBody);
// 获取请求中的 sign 值
var requestSign = requestJson["sign"]?.ToString();
if (string.IsNullOrEmpty(requestSign))
{
await _next(context);
return;
}
// 获取所有的键值对,并排序
var sortedKeys = requestJson.Properties()
.Where(p => p.Name != "sign")
.OrderBy(p => p.Name)
.Select(p => p.Value.ToString())
.ToList();
// 拼接所有的值,并加上固定字符串
var concatenatedValues = string.Join("", sortedKeys) + FixedString;
// 计算 MD5 哈希值
var md5Hash = ComputeMD5Hash(concatenatedValues);
// 验证 MD5 哈希值与请求中的 sign 是否匹配
if (md5Hash != requestSign)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
// 返回 500 错误
context.Response.StatusCode = 500;
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.SignError, "sign加密验证失败", null)
{
};
context.Response.ContentType = "application/json; charset=utf-8";
// 将异常信息写入 HTTP 响应
await context.Response.WriteAsync(JsonConvert.SerializeObject(baseResponse));
//await context.Response.WriteAsync("");
return;
}
// 调用下一个中间件
await _next(context);
}
/// <summary>
/// Md5加密
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
private string ComputeMD5Hash(string input)
{
using (var md5 = MD5.Create())
{
var inputBytes = Encoding.UTF8.GetBytes(input);
var hashBytes = md5.ComputeHash(inputBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant.Contract
{
/// <summary>
/// 基本多租户接口
/// </summary>
public interface IMultiTenant
{
/// <summary>
/// 租户ID
/// </summary>
Guid TenantId { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant.Contract
{
/// <summary>
/// 多租户DbContent接口
/// </summary>
public interface IMultiTenantDbContext
{
/// <summary>
/// 租户信息
/// </summary>
ITenantInfo TenantInfo { get; }
}
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant.Contract
{
/// <summary>
/// 多租户实体类接口
/// </summary>
public interface IMultiTenantEntity : IMultiTenant
{
}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant.Contract
{
/// <summary>
/// 租户信息接口
/// </summary>
public interface ITenantInfo : IMultiTenant
{
string? Identifier { get; set; }
/// <summary>
/// Gets or sets a display friendly name for the tenant.
/// </summary>
string? Name { get; set; }
/// <summary>
/// 数据库连接字符串
/// </summary>
string? ConnectionString { get; set; }
}
}

View File

@ -0,0 +1,101 @@
using HuanMeng.DotNetCore.MultiTenant.Contract;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant
{
/// <summary>
/// 基本多租户DbContext
/// </summary>
public class MultiTenantDbContext : DbContext, IMultiTenantDbContext
{
/// <summary>
/// 租户信息
/// </summary>
public ITenantInfo? TenantInfo { get; set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tenantInfo"></param>
public MultiTenantDbContext(ITenantInfo? tenantInfo)
{
this.TenantInfo = tenantInfo;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="tenantInfo"></param>
/// <param name="options"></param>
public MultiTenantDbContext(ITenantInfo? tenantInfo, DbContextOptions options)
: base(options)
{
if (tenantInfo == null)
{
tenantInfo = new TenantInfo()
{
TenantId = Guid.NewGuid(),
Identifier = "default",
Name = "default"
};
}
this.TenantInfo = tenantInfo;
}
public void SetTenantInfo(ITenantInfo tenantInfo)
{
this.TenantInfo = tenantInfo;
}
public override int SaveChanges()
{
if (TenantInfo?.TenantId != null)
{
var entries = ChangeTracker.Entries()
.Where(e => e.Entity is IMultiTenantEntity &&
(e.State == EntityState.Added || e.State == EntityState.Modified))
.Select(e => e.Entity as IMultiTenantEntity)
.ToList();
foreach (var entity in entries)
{
if (entity?.TenantId == null || entity?.TenantId == Guid.Empty)
{
entity.TenantId = TenantInfo.TenantId;
}
}
}
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
if (TenantInfo?.TenantId != null)
{
var entries = ChangeTracker.Entries()
.Where(e => e.Entity is IMultiTenantEntity &&
(e.State == EntityState.Added || e.State == EntityState.Modified))
.Select(e => e.Entity as IMultiTenantEntity)
.ToList();
foreach (var entity in entries)
{
if (entity?.TenantId == null || entity?.TenantId == Guid.Empty)
{
entity.TenantId = TenantInfo.TenantId;
}
}
}
return await base.SaveChangesAsync(cancellationToken);
}
}
}

View File

@ -0,0 +1,22 @@
using HuanMeng.DotNetCore.MultiTenant.Contract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant
{
/// <summary>
/// 基本多租户实体类
/// </summary>
public class MultiTenantEntity : IMultiTenantEntity
{
/// <summary>
/// 租户ID
/// </summary>
public virtual Guid TenantId { get; set; }
}
}

View File

@ -0,0 +1,41 @@
using HuanMeng.DotNetCore.MultiTenant.Contract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.MultiTenant
{
/// <summary>
/// 租户信息
/// </summary>
public class TenantInfo : ITenantInfo
{
public TenantInfo()
{
}
/// <summary>
/// 租户ID
/// </summary>
public Guid TenantId { get; set; }
/// <summary>
/// 租户字符串标志
/// </summary>
public string? Identifier { get; set; }
/// <summary>
/// 租户名称(描述说明)
/// </summary>
public string? Name { get; set; }
/// <summary>
/// 租户数据库连接字符串
/// </summary>
public string? ConnectionString { get; set; }
}
}

View File

@ -0,0 +1,58 @@

namespace HuanMeng.DotNetCore.Processors
{
/// <summary>
/// 任务处理器
/// </summary>
public abstract class BaseProcessor : ITaskProcessor
{
/// <summary>
/// 终止内部处理线程的最长等待时间(毫秒)
/// </summary>
protected const int WaitTimeMax_StopProc = 20000;
/// <summary>
/// 构造函数
/// </summary>
public BaseProcessor()
{
//加载配置
LoadSettings();
}
/// <summary>
/// 加载配置
/// </summary>
protected virtual void LoadSettings()
{
//初始化
// LogHelper.Info("BaseProcessor.LoadSettings");
}
/// <summary>
/// Dispose
/// </summary>
public virtual void Dispose()
{
}
/// <summary>
/// 执行任务
/// </summary>
public virtual void Run()
{
//LogHelper.Info("BaseProcessor.Run");
}
/// <summary>
/// 停止执行任务
/// </summary>
public virtual void Stop()
{
// LogHelper.Info("BaseProcessor.Stop");
}
}
}

View File

@ -0,0 +1,18 @@
namespace HuanMeng.DotNetCore.Processors
{
/// <summary>
/// 任务处理器接口
/// </summary>
public interface ITaskProcessor : IDisposable
{
/// <summary>
/// 运行任务
/// </summary>
void Run();
/// <summary>
/// 停止运行
/// </summary>
void Stop();
}
}

View File

@ -0,0 +1,81 @@
namespace HuanMeng.DotNetCore.Processors
{
/// <summary>
/// 包含处理线程的处理器基类
/// </summary>
public abstract class ThreadProcessor : BaseProcessor
{
protected Thread? _thread;
protected bool _isRunning;
protected string? _threadName;
/// <summary>
/// 构造函数
/// </summary>
public ThreadProcessor() { }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="threadName">线程名称</param>
public ThreadProcessor(string threadName)
{
_threadName = threadName;
}
/// <summary>
/// 执行处理
/// </summary>
/// <exception cref="InvalidOperationException">如果处理器已经在运行,抛出异常</exception>
public override void Run()
{
if (_isRunning)
{
throw new InvalidOperationException("重复执行线程");
}
_isRunning = true;
_thread = new Thread(Proc_Do)
{
Name = _threadName
};
_thread.Start();
}
/// <summary>
/// 线程处理函数,需要在子类中实现
/// </summary>
protected abstract void Proc_Do();
/// <summary>
/// 停止处理线程
/// </summary>
public override void Stop()
{
if (!_isRunning)
{
return;
}
_isRunning = false;
if (_thread != null && _thread.IsAlive)
{
_thread.Join(WaitTimeMax_StopProc); // 等待线程结束
_thread = null;
}
}
/// <summary>
/// 释放资源
/// </summary>
public override void Dispose()
{
Stop();
base.Dispose();
}
}
}

View File

@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.TextCensor
{
/// <summary>
/// 验证字符串是否违规
/// </summary>
//[Obsolete]
//public static class CheckTextVerification
//{
// static HashSet<string> ShieldStr = new HashSet<string>();
// static List<string> ShieldListStr = new List<string>();
// static string[] ShieldString = new string[0];
// static CheckTextVerification()
// {
// var ckPath = Path.GetFullPath("I:\\test\\PrayForBlessings\\PrayForBlessings\\ciku/");
// var filePath = Directory.EnumerateFiles(ckPath);
// foreach (var item in filePath)
// {
// CheckTextVerification.AddShieldString(item);
// }
// //
// }
// /// <summary>
// ///
// /// </summary>
// /// <param name="path"></param>
// /// <exception cref="Exception"></exception>
// public static void AddShieldString(string path)
// {
// if (!File.Exists(path))
// {
// throw new Exception("文件不存在");
// }
// HashSet<string> strings = new HashSet<string>();
// using (StreamReader reader = new StreamReader(path, UnicodeEncoding.UTF8))
// {
// while (reader.Peek() > 0)
// {
// var tempStr = (reader.ReadLine() ?? "");
// //string result = Regex.Replace(tempStr, pattern, "");
// if (!string.IsNullOrEmpty(tempStr))
// {
// var tempStringToLower = tempStr.ToLower().Split(new char[] { '、', ',' });
// string[] filteredArrayToLower = tempStringToLower.Where(s => !string.IsNullOrEmpty(s)).ToArray();
// strings.UnionWith(filteredArrayToLower);
// }
// }
// ShieldStr.UnionWith(strings);
// ShieldListStr.AddRange(ShieldStr);
// ShieldString = ShieldStr.ToArray();
// Console.WriteLine(string.Format($"[{Path.GetFileName(path)}]加载完毕,总共加载屏蔽字行数:{ShieldStr.Count}"));
// }
// }
// /// <summary>
// ///
// /// </summary>
// /// <param name="sourceTxt"></param>
// /// <returns></returns>
// public static bool VerifyTxt(string sourceTxt)
// {
// if (!string.IsNullOrEmpty(sourceTxt))
// {
// //先去掉空格,特殊字符
// string cleanedText = CleanedTextString(sourceTxt);
// foreach (var item in ShieldStr)
// {
// if (cleanedText.Contains(item))
// {
// return false;
// }
// }
// }
// return true;
// }
// /// <summary>
// ///
// /// </summary>
// /// <param name="sourceTxt"></param>
// /// <returns></returns>
// public static bool VerifyTxtList(string sourceTxt)
// {
// if (!string.IsNullOrEmpty(sourceTxt))
// {
// //先去掉空格,特殊字符
// string cleanedText = CleanedTextString(sourceTxt);
// foreach (var item in ShieldListStr)
// {
// if (cleanedText.Contains(item))
// {
// return false;//2.56
// }
// }
// }
// return true;
// }
// private static string replaceString = " ,.。,@*12345690_-";
// /// <summary>
// ///
// /// </summary>
// /// <param name="sourceTxt"></param>
// /// <returns></returns>
// private static string CleanedTextString(string sourceTxt)
// {
// string cleanedText = sourceTxt.ToLower()
// .Replace(" ", "")
// .Replace(",", "")
// .Replace(".", "")
// .Replace("。", "")
// .Replace("", "")
// .Replace("@", "")
// .Replace("-", "")
// .Replace("*", "")
// .Replace("1", "")
// .Replace("2", "")
// .Replace("3", "")
// .Replace("4", "")
// .Replace("5", "")
// .Replace("6", "")
// .Replace("9", "")
// .Replace("0", "")
// .Replace("_", "");
// return cleanedText;
// }
// public static bool VerifyTxtString(string sourceTxt)
// {
// if (!string.IsNullOrEmpty(sourceTxt))
// {
// //先去掉空格,特殊字符
// string cleanedText = CleanedTextString(sourceTxt);
// foreach (var item in ShieldString)
// {
// if (cleanedText.Contains(item))
// {
// return false;//2.56
// }
// }
// }
// return true;
// }
//}
}

View File

@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.TextCensor
{
/// <summary>
/// 文本审核接口
/// </summary>
public interface ITextCensor
{
/// <summary>
/// 文本审核
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
bool TextCensor(string text);
/// <summary>
/// 文本审核
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
Task<bool> TextCensorAsync(string text);
}
}

View File

@ -0,0 +1,145 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.TextCensor.SensitiveWord
{
public class SensitiveWordFilter : ITextCensor
{
/// <summary>
/// 定义Trie树节点
/// </summary>
private class TrieNode
{
/// <summary>
/// 标记是否是一个敏感词的结尾
/// </summary>
public bool IsEnd { get; set; }
/// <summary>
/// 存储子节点
/// </summary>
public Dictionary<char, TrieNode> Children { get; set; }
/// <summary>
///
/// </summary>
//public FrozenDictionary<char, TrieNode> fChildren { get; set; }
public TrieNode()
{
IsEnd = false;
Children = new Dictionary<char, TrieNode>();
}
}
/// <summary>
/// 根节点
/// </summary>
private TrieNode Root { get; set; }
public SensitiveWordFilter()
{
Root = new TrieNode();
}
/// <summary>
/// 添加敏感词到Trie树中
/// </summary>
/// <param name="word"></param>
public void AddSensitiveWord(string word)
{
TrieNode currentNode = Root;
word = CleanText(word);
foreach (char c in word.ToLower())
{
// 如果当前字符不存在于子节点中,则添加
if (!currentNode.Children.ContainsKey(c))
{
currentNode.Children[c] = new TrieNode();
}
currentNode = currentNode.Children[c];
}
currentNode.IsEnd = true; // 标记当前节点为敏感词结尾
}
/// <summary>
/// 定义要移除的字符集合,包括空格
/// </summary>
private static string replaceString = @"[ \.,。,@\-*12345690_]";
/// <summary>
/// 清理文字
/// </summary>
/// <param name="sourceTxt"></param>
/// <returns></returns>
public string CleanText(string sourceTxt)
{
if (string.IsNullOrEmpty(sourceTxt))
{
return string.Empty;
}
// 使用正则表达式替换所有匹配的字符
//string cleanedText = Regex.Replace(sourceTxt.ToLower(), replaceString, string.Empty);
string cleanedText = sourceTxt
.Replace(',', ' ')
.Replace('.', ' ')
.Replace('。', ' ')
.Replace('', ' ')
.Replace('@', ' ')
.Replace('-', ' ')
.Replace('*', ' ')
.Replace("1", string.Empty)
.Replace("2", string.Empty)
.Replace("3", string.Empty)
.Replace("4", string.Empty)
.Replace("5", string.Empty)
.Replace("6", string.Empty)
.Replace("9", string.Empty)
.Replace("0", string.Empty)
.Replace("_", string.Empty)
.Replace(" ", string.Empty).ToLower();
return cleanedText;
}
/// <summary>
/// 判断文本中是否包含敏感词
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public bool ContainsSensitiveWord(string text)
{
//过滤字符串
text = CleanText(text);
for (int i = 0; i < text.Length; i++)
{
TrieNode currentNode = Root;
int j = i;
// 从当前位置开始匹配敏感词
while (j < text.Length && currentNode.Children.ContainsKey(text[j]))
{
currentNode = currentNode.Children[text[j]];
// 如果当前节点是敏感词结尾返回true
if (currentNode.IsEnd)
{
return true;
}
j++;
}
}
return false;
}
public bool TextCensor(string text)
{
return ContainsSensitiveWord(text);
}
public Task<bool> TextCensorAsync(string text)
{
return Task.Run(() =>
{
return ContainsSensitiveWord(text);
});
}
}
}

View File

@ -0,0 +1,149 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.TextCensor.SensitiveWord
{
public class SensitiveWordFilterFrozen : ITextCensor
{
/// <summary>
/// 定义Trie树节点
/// </summary>
private class TrieNode
{
/// <summary>
/// 标记是否是一个敏感词的结尾
/// </summary>
public bool IsEnd { get; set; }
/// <summary>
/// 存储子节点
/// </summary>
public FrozenDictionary<char, TrieNode> Children { get; private set; }
public TrieNode()
{
IsEnd = false;
Children = null;
}
/// <summary>
/// 将子节点字典冻结为 FrozenDictionary
/// </summary>
public void FreezeChildren(Dictionary<char, TrieNode> children)
{
Children = children.ToFrozenDictionary();
}
}
/// <summary>
/// 根节点
/// </summary>
private TrieNode Root { get; set; }
public SensitiveWordFilterFrozen()
{
Root = new TrieNode();
}
/// <summary>
/// 添加敏感词到Trie树中
/// </summary>
/// <param name="word"></param>
public void AddSensitiveWord(string word)
{
TrieNode currentNode = Root;
word = CleanText(word);
foreach (char c in word.ToLower())
{
// 如果当前字符不存在于子节点中,则添加
if (currentNode.Children == null || !currentNode.Children.ContainsKey(c))
{
var children = currentNode.Children?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) ?? new Dictionary<char, TrieNode>();
children[c] = new TrieNode();
currentNode.FreezeChildren(children);
}
currentNode = currentNode.Children[c];
}
currentNode.IsEnd = true; // 标记当前节点为敏感词结尾
}
/// <summary>
/// 清理文字
/// </summary>
/// <param name="sourceTxt"></param>
/// <returns></returns>
public string CleanText(string sourceTxt)
{
if (string.IsNullOrEmpty(sourceTxt))
{
return string.Empty;
}
string cleanedText = sourceTxt
.Replace(',', ' ')
.Replace('.', ' ')
.Replace('。', ' ')
.Replace('', ' ')
.Replace('@', ' ')
.Replace('-', ' ')
.Replace('*', ' ')
.Replace("1", string.Empty)
.Replace("2", string.Empty)
.Replace("3", string.Empty)
.Replace("4", string.Empty)
.Replace("5", string.Empty)
.Replace("6", string.Empty)
.Replace("9", string.Empty)
.Replace("0", string.Empty)
.Replace("_", string.Empty)
.Replace(" ", string.Empty).ToLower();
return cleanedText;
}
/// <summary>
/// 判断文本中是否包含敏感词
/// </summary>
/// <param name="text"></param>
/// <returns></returns>
public bool ContainsSensitiveWord(string text)
{
//过滤字符串
text = CleanText(text);
for (int i = 0; i < text.Length; i++)
{
TrieNode currentNode = Root;
int j = i;
// 从当前位置开始匹配敏感词
while (j < text.Length && currentNode.Children != null && currentNode.Children.ContainsKey(text[j]))
{
currentNode = currentNode.Children[text[j]];
// 如果当前节点是敏感词结尾返回true
if (currentNode.IsEnd)
{
return true;
}
j++;
}
}
return false;
}
public bool TextCensor(string text)
{
return ContainsSensitiveWord(text);
}
public Task<bool> TextCensorAsync(string text)
{
return Task.Run(() =>
{
return ContainsSensitiveWord(text);
});
}
}
}

View File

@ -0,0 +1,122 @@
using HuanMeng.DotNetCore.TextCensor.SensitiveWord;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
namespace HuanMeng.DotNetCore.TextCensor
{
/// <summary>
///
/// </summary>
public static class TextCensorExtend
{
/// <summary>
/// 获取文本检测
/// </summary>
/// <param name="_dirPath"></param>
/// <returns></returns>
public static ITextCensor GetITextCensor(string _dirPath)
{
return GetSensitiveWordFilterFrozen(_dirPath)
//GetSensitiveWordFilter(_dirPath)
;
}
/// <summary>
///
/// </summary>
/// <param name="_dirPath"></param>
/// <returns></returns>
public static SensitiveWordFilter GetSensitiveWordFilter(string _dirPath)
{
SensitiveWordFilter sensitiveWordFilter = new SensitiveWordFilter();
var ckPath = Path.GetFullPath(_dirPath);
var filePath = Directory.EnumerateFiles(ckPath);
foreach (var item in filePath)
{
AddShieldString(item, sensitiveWordFilter);
}
return sensitiveWordFilter;
}
/// <summary>
///
/// </summary>
/// <param name="_dirPath"></param>
/// <returns></returns>
public static SensitiveWordFilterFrozen GetSensitiveWordFilterFrozen(string _dirPath)
{
SensitiveWordFilterFrozen sensitiveWordFilter = new SensitiveWordFilterFrozen();
var ckPath = Path.GetFullPath(_dirPath);
var filePath = Directory.EnumerateFiles(ckPath);
foreach (var item in filePath)
{
AddShieldString(item, sensitiveWordFilter);
}
return sensitiveWordFilter;
}
/// <summary>
///
/// </summary>
/// <param name="path"></param>
/// <exception cref="Exception"></exception>
public static void AddShieldString(string path, SensitiveWordFilterFrozen sensitiveWordFilter)
{
if (!File.Exists(path))
{
throw new Exception("文件不存在");
}
using (StreamReader reader = new StreamReader(path, UnicodeEncoding.UTF8))
{
while (reader.Peek() > 0)
{
var tempStr = (reader.ReadLine() ?? "");
if (!string.IsNullOrEmpty(tempStr))
{
sensitiveWordFilter.AddSensitiveWord(tempStr);
}
}
}
}
/// <summary>
///
/// </summary>
/// <param name="path"></param>
/// <exception cref="Exception"></exception>
public static void AddShieldString(string path, SensitiveWordFilter sensitiveWordFilter)
{
if (!File.Exists(path))
{
throw new Exception("文件不存在");
}
using (StreamReader reader = new StreamReader(path, UnicodeEncoding.UTF8))
{
while (reader.Peek() > 0)
{
var tempStr = (reader.ReadLine() ?? "");
if (!string.IsNullOrEmpty(tempStr))
{
sensitiveWordFilter.AddSensitiveWord(tempStr);
}
}
}
}
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.Json.Serialization;
namespace HuanMeng.DotNetCore.Utility.AssemblyHelper
{
/// <summary>
/// 保存各种程序集信息属性的类。
/// </summary>
[Serializable]
public class AssemblyInfo
{
/// <summary>
/// 获取或设置程序集的版本。
/// </summary>
public string Version { get; set; }
/// <summary>
/// 获取或设置程序集的文件版本。
/// </summary>
public string FileVersion { get; set; }
/// <summary>
/// 获取或设置程序集版本。
/// </summary>
public string AssemblyVersion { get; set; }
/// <summary>
/// 获取或设置程序集的信息性版本。
/// </summary>
public string InformationalVersion { get; set; }
///// <summary>
///// 获取或设置与程序集关联的公司名称。
///// </summary>
//public string Company { get; set; }
///// <summary>
///// 获取或设置与程序集关联的产品名称。
///// </summary>
//public string Product { get; set; }
/// <summary>
/// 获取或设置与程序集关联的版权信息。
/// </summary>
public string Copyright { get; set; }
/// <summary>
/// 获取或设置程序集的描述信息。
/// </summary>
public string Description { get; set; }
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace HuanMeng.DotNetCore.Utility.AssemblyHelper
{
/// <summary>
/// 用于检索程序集信息的辅助类。
/// </summary>
public static class AssemblyInfoHelper
{
/// <summary>
/// 从正在执行的程序集检索各种属性,并返回一个 AssemblyInfo 对象。
/// </summary>
/// <returns>包含程序集属性的 AssemblyInfo 对象。</returns>
public static AssemblyInfo GetAssemblyInfo()
{
// 获取正在执行的程序集
Assembly assembly = Assembly.GetExecutingAssembly();
// 创建并填充 AssemblyInfo 对象的相关属性
var assemblyInfo = new AssemblyInfo
{
Version = assembly.GetName().Version.ToString(),
FileVersion = assembly.GetCustomAttributes<AssemblyFileVersionAttribute>().FirstOrDefault()?.Version ?? "",
AssemblyVersion = assembly.GetCustomAttributes<AssemblyVersionAttribute>().FirstOrDefault()?.Version ?? "",
InformationalVersion = assembly.GetCustomAttributes<AssemblyInformationalVersionAttribute>().FirstOrDefault()?.InformationalVersion ?? "",
//Company = assembly.GetCustomAttributes<AssemblyCompanyAttribute>().FirstOrDefault()?.Company ?? "",
//Product = assembly.GetCustomAttributes<AssemblyProductAttribute>().FirstOrDefault()?.Product ?? "",
Copyright = assembly.GetCustomAttributes<AssemblyCopyrightAttribute>().FirstOrDefault()?.Copyright ?? "",
Description = assembly.GetCustomAttributes<AssemblyDescriptionAttribute>().FirstOrDefault()?.Description ?? ""
};
return assemblyInfo;
}
}
}

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace HuanMeng.DotNetCore.Utility
{
/// <summary>
/// 时间扩展
/// </summary>
public static class DateTimeExtensions
{
/// <summary>
/// 获取时间戳,秒
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static long ToUnixTimestamp(this DateTime dateTime)
{
return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalSeconds;
}
/// <summary>
/// 获取是时间戳,毫秒
/// </summary>
/// <param name="dateTime"></param>
/// <returns></returns>
public static long ToUnixTimestampMilliseconds(this DateTime dateTime)
{
return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalMilliseconds;
}
}
}

View File

@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.Utility
{
public static class HttpContextExtensions
{
/// <summary>
/// 获取IP地址
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public static string GetClientIpAddress(this HttpContext context)
{
// 尝试从X-Forwarded-For头部中获取IP地址
var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrEmpty(forwardedFor))
{
// 处理可能的多个IP地址通常第一个是客户端的真实IP
var ipAddresses = forwardedFor.Split(',');
if (ipAddresses.Length > 0)
{
return ipAddresses[0].Trim();
}
}
// 如果X-Forwarded-For头部不存在使用RemoteIpAddress
return context.Connection.RemoteIpAddress?.ToString();
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.Utility
{
/// <summary>
///
/// </summary>
public static class MD5Encryption
{
/// <summary>
/// md5加密
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static string ComputeMD5Hash(string input)
{
// 创建一个 MD5 哈希算法的实例
using (MD5 md5 = MD5.Create())
{
// 将输入字符串转换为字节数组
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
// 计算 MD5 哈希值
byte[] hashBytes = md5.ComputeHash(inputBytes);
// 将字节数组转换为十六进制字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hashBytes.Length; i++)
{
sb.Append(hashBytes[i].ToString("x2"));
}
// 返回哈希值字符串
return sb.ToString();
}
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace HuanMeng.DotNetCore.Utility;
/// <summary>
/// 手机号
/// </summary>
public class PhoneNumberValidator
{
// 正则表达式用于匹配手机号码。可以根据需要调整以适应不同的国家或地区。
private static readonly Regex phoneNumberRegex = new Regex(@"^(1[3-9]\d{9})$");
public static bool IsPhoneNumber(string input)
{
if (string.IsNullOrWhiteSpace(input))
{
return false;
}
return phoneNumberRegex.IsMatch(input);
}
}

View File

@ -0,0 +1,148 @@
using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace HuanMeng.DotNetCore.WeChat
{
/// <summary>
/// 小程序帮助类
/// </summary>
/// <param name="appId"></param>
/// <param name="secret"></param>
/// <param name="database"></param>
/// <param name="_httpClientFactory"></param>
public class MiniProgram(string appId, string secret, IDatabase database, IHttpClientFactory? _httpClientFactory)
{
public string AppId { get; set; } = appId;
string key = $"WeChat:{appId}";
// Method to get user's OpenID
public async Task<(string openId, string unionid, string session_key)> GetOpenid(string code)
{
string url = $"https://api.weixin.qq.com/sns/jscode2session?appid={appId}&secret={secret}&js_code={code}&grant_type=authorization_code";
var resUserInfo = await GetCurlData(url);
if (resUserInfo.ContainsKey("errcode"))
{
return (null, null, null);
}
string openid = resUserInfo.GetValueOrDefault("openid")?.ToString() ?? "";
string unionid = resUserInfo.GetValueOrDefault("unionid")?.ToString() ?? "";
string session_key = resUserInfo.GetValueOrDefault("session_key")?.ToString() ?? "";
return new(openid, unionid, session_key);
}
// Get user info based on access token and openid
public async Task<dynamic> GetUserInfoAsync(string openid)
{
var accessToken = await GetAccessToken();
string url = $"https://api.weixin.qq.com/sns/userinfo?access_token={accessToken}&openid={openid}&lang=zh_CN";
var response = await GetCurlData(url);
return response;
}
public async Task<string> GetAccessToken()
{
var accessTokenInfo = GetConfig();
if (accessTokenInfo != null)
{
return accessTokenInfo?.Access_token;
}
else
{
string url = $"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appId}&secret={secret}";
var resAccessToken = await GetCurlData(url);
if (resAccessToken.ContainsKey("errcode"))
{
throw new Exception("获取微信token失败");
}
string accessToken = resAccessToken["access_token"].ToString();
int expiresIn = Convert.ToInt32(resAccessToken["expires_in"]);
long accessTokenTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + expiresIn;
var data = new AccessToken(accessToken, accessTokenTime);
SetConfig(data);
return accessToken;
}
}
// Helper method for GET requests
private async Task<Dictionary<string, object>> GetCurlData(string url)
{
using HttpClient client = _httpClientFactory.CreateClient();
var response = await client.GetStringAsync(url);
return JsonConvert.DeserializeObject<Dictionary<string, object>>(response);
}
// Helper method for POST requests
private async Task<Dictionary<string, object>> PostCurlData(string url, object data)
{
using HttpClient client = _httpClientFactory.CreateClient();
var json = JsonConvert.SerializeObject(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
var result = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<Dictionary<string, object>>(result);
}
// Helper method for HTTP POST data
private async Task<string> HttpPostData(string url, object data)
{
using HttpClient client = _httpClientFactory.CreateClient();
var json = JsonConvert.SerializeObject(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
return await response.Content.ReadAsStringAsync();
}
private AccessToken? GetConfig()
{
var json = database.StringGet(key);
if (!string.IsNullOrEmpty(json))
{
return JsonConvert.DeserializeObject<AccessToken>(json);
}
return null;
}
private void SetConfig(AccessToken access)
{
var outTime = new TimeSpan(0, 0, 7000);
database.StringSet(key, JsonConvert.SerializeObject(access), outTime);
}
}
/// <summary>
///
/// </summary>
public class AccessToken
{
public string? Access_token { get; }
public long Access_token_time { get; }
public AccessToken(string? access_token, long access_token_time)
{
Access_token = access_token;
Access_token_time = access_token_time;
}
public AccessToken()
{
}
}
}

View File

@ -0,0 +1,122 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json.Linq;
namespace HuanMeng.DotNetCore.WeChat
{
/// <summary>
///
/// </summary>
public class WXBizDataCrypt
{
private string appId;
private string sessionKey;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="appId">小程序的 appId</param>
/// <param name="sessionKey">用户在小程序登录后获取的会话密钥</param>
public WXBizDataCrypt(string appId, string sessionKey)
{
this.appId = appId;
this.sessionKey = sessionKey;
}
/// <summary>
/// 检验数据的真实性,并且获取解密后的明文
/// </summary>
/// <param name="encryptedData">加密的用户数据</param>
/// <param name="iv">与用户数据一同返回的初始向量</param>
/// <param name="data">解密后的原文</param>
/// <returns>成功0失败返回对应的错误码</returns>
public int DecryptData(string encryptedData, string iv, out string data)
{
data = null;
// 检查 sessionKey 长度
if (sessionKey.Length != 24)
{
return ErrorCode.IllegalAesKey;
}
// 检查 iv 长度
if (iv.Length != 24)
{
return ErrorCode.IllegalIv;
}
try
{
byte[] aesKey = Convert.FromBase64String(sessionKey);
byte[] aesIV = Convert.FromBase64String(iv);
byte[] aesCipher = Convert.FromBase64String(encryptedData);
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = aesKey;
aesAlg.IV = aesIV;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
using (ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV))
{
using (MemoryStream msDecrypt = new MemoryStream(aesCipher))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// 解密后的明文
data = srDecrypt.ReadToEnd();
}
}
}
}
}
JObject dataObj = JObject.Parse(data);
// 检查 appId
if (dataObj["watermark"]["appid"].ToString() != this.appId)
{
return ErrorCode.IllegalBuffer;
}
return ErrorCode.OK;
}
catch
{
return ErrorCode.IllegalBuffer;
}
}
}
public static class ErrorCode
{
public const int OK = 0;
public const int IllegalAesKey = -41001;
public const int IllegalIv = -41002;
public const int IllegalBuffer = -41003;
}
public class WXBizDataCryptModel
{
/// <summary>
///
/// </summary>
public string EncryptedData;
/// <summary>
///
/// </summary>
public string Iv;
public int UserId { get; set; }
}
}

View File

@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="DbSqlServer\Db_User\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,355 @@
<#@ template hostSpecific="true" #>
<#@ assembly name="Microsoft.EntityFrameworkCore" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Relational" #>
<#@ assembly name="Microsoft.Extensions.DependencyInjection.Abstractions" #>
<#@ parameter name="Model" type="Microsoft.EntityFrameworkCore.Metadata.IModel" #>
<#@ parameter name="Options" type="Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions" #>
<#@ parameter name="NamespaceHint" type="System.String" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="Microsoft.EntityFrameworkCore" #>
<#@ import namespace="Microsoft.EntityFrameworkCore.Design" #>
<#@ import namespace="Microsoft.EntityFrameworkCore.Infrastructure" #>
<#@ import namespace="Microsoft.EntityFrameworkCore.Scaffolding" #>
<#@ import namespace="Microsoft.Extensions.DependencyInjection" #>
<#
if (!ProductInfo.GetVersion().StartsWith("8.0"))
{
Warning("Your templates were created using an older version of Entity Framework. Additional features and bug fixes may be available. See https://aka.ms/efcore-docs-updating-templates for more information.");
}
var services = (IServiceProvider)Host;
var providerCode = services.GetRequiredService<IProviderConfigurationCodeGenerator>();
var annotationCodeGenerator = services.GetRequiredService<IAnnotationCodeGenerator>();
var code = services.GetRequiredService<ICSharpHelper>();
var usings = new List<string>
{
"System",
"System.Collections.Generic",
"Microsoft.EntityFrameworkCore"
};
if (NamespaceHint != Options.ModelNamespace
&& !string.IsNullOrEmpty(Options.ModelNamespace))
{
usings.Add(Options.ModelNamespace);
}
if (!string.IsNullOrEmpty(NamespaceHint))
{
#>
namespace <#= NamespaceHint #>;
<#
}
#>
public partial class <#= Options.ContextName #> : DbContext
{
<#
if (!Options.SuppressOnConfiguring)
{
#>
public <#= Options.ContextName #>()
{
}
<#
}
#>
public <#= Options.ContextName #>(DbContextOptions<<#= Options.ContextName #>> options)
: base(options)
{
}
<#
foreach (var entityType in Model.GetEntityTypes().Where(e => !e.IsSimpleManyToManyJoinEntityType()))
{
#>
public virtual DbSet<<#= entityType.Name #>> <#= entityType.GetDbSetName() #> { get; set; }
<#
}
if (!Options.SuppressOnConfiguring)
{
#>
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
<#
if (!Options.SuppressConnectionStringWarning)
{
#>
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see https://go.microsoft.com/fwlink/?LinkId=723263.
<#
}
var useProviderCall = providerCode.GenerateUseProvider(Options.ConnectionString);
usings.AddRange(useProviderCall.GetRequiredUsings());
#>
=> optionsBuilder<#= code.Fragment(useProviderCall, indent: 3) #>;
<#
}
#>
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
<#
var anyConfiguration = false;
var modelFluentApiCalls = Model.GetFluentApiCalls(annotationCodeGenerator);
if (modelFluentApiCalls != null)
{
usings.AddRange(modelFluentApiCalls.GetRequiredUsings());
#>
modelBuilder<#= code.Fragment(modelFluentApiCalls, indent: 3) #>;
<#
anyConfiguration = true;
}
StringBuilder mainEnvironment;
foreach (var entityType in Model.GetEntityTypes().Where(e => !e.IsSimpleManyToManyJoinEntityType()))
{
// Save all previously generated code, and start generating into a new temporary environment
mainEnvironment = GenerationEnvironment;
GenerationEnvironment = new StringBuilder();
if (anyConfiguration)
{
WriteLine("");
}
var anyEntityTypeConfiguration = false;
#>
modelBuilder.Entity<<#= entityType.Name #>>(entity =>
{
<#
var key = entityType.FindPrimaryKey();
if (key != null)
{
var keyFluentApiCalls = key.GetFluentApiCalls(annotationCodeGenerator);
if (keyFluentApiCalls != null
|| (!key.IsHandledByConvention() && !Options.UseDataAnnotations))
{
if (keyFluentApiCalls != null)
{
usings.AddRange(keyFluentApiCalls.GetRequiredUsings());
}
#>
entity.HasKey(<#= code.Lambda(key.Properties, "e") #>)<#= code.Fragment(keyFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
}
}
var entityTypeFluentApiCalls = entityType.GetFluentApiCalls(annotationCodeGenerator)
?.FilterChain(c => !(Options.UseDataAnnotations && c.IsHandledByDataAnnotations));
if (entityTypeFluentApiCalls != null)
{
usings.AddRange(entityTypeFluentApiCalls.GetRequiredUsings());
if (anyEntityTypeConfiguration)
{
WriteLine("");
}
#>
entity<#= code.Fragment(entityTypeFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
}
foreach (var index in entityType.GetIndexes()
.Where(i => !(Options.UseDataAnnotations && i.IsHandledByDataAnnotations(annotationCodeGenerator))))
{
if (anyEntityTypeConfiguration)
{
WriteLine("");
}
var indexFluentApiCalls = index.GetFluentApiCalls(annotationCodeGenerator);
if (indexFluentApiCalls != null)
{
usings.AddRange(indexFluentApiCalls.GetRequiredUsings());
}
#>
entity.HasIndex(<#= code.Lambda(index.Properties, "e") #>, <#= code.Literal(index.GetDatabaseName()) #>)<#= code.Fragment(indexFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
}
var firstProperty = true;
foreach (var property in entityType.GetProperties())
{
var propertyFluentApiCalls = property.GetFluentApiCalls(annotationCodeGenerator)
?.FilterChain(c => !(Options.UseDataAnnotations && c.IsHandledByDataAnnotations)
&& !(c.Method == "IsRequired" && Options.UseNullableReferenceTypes && !property.ClrType.IsValueType));
if (propertyFluentApiCalls == null)
{
continue;
}
usings.AddRange(propertyFluentApiCalls.GetRequiredUsings());
if (anyEntityTypeConfiguration && firstProperty)
{
WriteLine("");
}
#>
entity.Property(e => e.<#= property.Name #>)<#= code.Fragment(propertyFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
firstProperty = false;
}
foreach (var foreignKey in entityType.GetForeignKeys())
{
var foreignKeyFluentApiCalls = foreignKey.GetFluentApiCalls(annotationCodeGenerator)
?.FilterChain(c => !(Options.UseDataAnnotations && c.IsHandledByDataAnnotations));
if (foreignKeyFluentApiCalls == null)
{
continue;
}
usings.AddRange(foreignKeyFluentApiCalls.GetRequiredUsings());
if (anyEntityTypeConfiguration)
{
WriteLine("");
}
#>
entity.HasOne(d => d.<#= foreignKey.DependentToPrincipal.Name #>).<#= foreignKey.IsUnique ? "WithOne" : "WithMany" #>(<#= foreignKey.PrincipalToDependent != null ? $"p => p.{foreignKey.PrincipalToDependent.Name}" : "" #>)<#= code.Fragment(foreignKeyFluentApiCalls, indent: 4) #>;
<#
anyEntityTypeConfiguration = true;
}
foreach (var skipNavigation in entityType.GetSkipNavigations().Where(n => n.IsLeftNavigation()))
{
if (anyEntityTypeConfiguration)
{
WriteLine("");
}
var left = skipNavigation.ForeignKey;
var leftFluentApiCalls = left.GetFluentApiCalls(annotationCodeGenerator, useStrings: true);
var right = skipNavigation.Inverse.ForeignKey;
var rightFluentApiCalls = right.GetFluentApiCalls(annotationCodeGenerator, useStrings: true);
var joinEntityType = skipNavigation.JoinEntityType;
if (leftFluentApiCalls != null)
{
usings.AddRange(leftFluentApiCalls.GetRequiredUsings());
}
if (rightFluentApiCalls != null)
{
usings.AddRange(rightFluentApiCalls.GetRequiredUsings());
}
#>
entity.HasMany(d => d.<#= skipNavigation.Name #>).WithMany(p => p.<#= skipNavigation.Inverse.Name #>)
.UsingEntity<Dictionary<string, object>>(
<#= code.Literal(joinEntityType.Name) #>,
r => r.HasOne<<#= right.PrincipalEntityType.Name #>>().WithMany()<#= code.Fragment(rightFluentApiCalls, indent: 6) #>,
l => l.HasOne<<#= left.PrincipalEntityType.Name #>>().WithMany()<#= code.Fragment(leftFluentApiCalls, indent: 6) #>,
j =>
{
<#
var joinKey = joinEntityType.FindPrimaryKey();
var joinKeyFluentApiCalls = joinKey.GetFluentApiCalls(annotationCodeGenerator);
if (joinKeyFluentApiCalls != null)
{
usings.AddRange(joinKeyFluentApiCalls.GetRequiredUsings());
}
#>
j.HasKey(<#= code.Arguments(joinKey.Properties.Select(e => e.Name)) #>)<#= code.Fragment(joinKeyFluentApiCalls, indent: 7) #>;
<#
var joinEntityTypeFluentApiCalls = joinEntityType.GetFluentApiCalls(annotationCodeGenerator);
if (joinEntityTypeFluentApiCalls != null)
{
usings.AddRange(joinEntityTypeFluentApiCalls.GetRequiredUsings());
#>
j<#= code.Fragment(joinEntityTypeFluentApiCalls, indent: 7) #>;
<#
}
foreach (var index in joinEntityType.GetIndexes())
{
var indexFluentApiCalls = index.GetFluentApiCalls(annotationCodeGenerator);
if (indexFluentApiCalls != null)
{
usings.AddRange(indexFluentApiCalls.GetRequiredUsings());
}
#>
j.HasIndex(<#= code.Literal(index.Properties.Select(e => e.Name).ToArray()) #>, <#= code.Literal(index.GetDatabaseName()) #>)<#= code.Fragment(indexFluentApiCalls, indent: 7) #>;
<#
}
foreach (var property in joinEntityType.GetProperties())
{
var propertyFluentApiCalls = property.GetFluentApiCalls(annotationCodeGenerator);
if (propertyFluentApiCalls == null)
{
continue;
}
usings.AddRange(propertyFluentApiCalls.GetRequiredUsings());
#>
j.IndexerProperty<<#= code.Reference(property.ClrType) #>>(<#= code.Literal(property.Name) #>)<#= code.Fragment(propertyFluentApiCalls, indent: 7) #>;
<#
}
#>
});
<#
anyEntityTypeConfiguration = true;
}
#>
});
<#
// If any signicant code was generated, append it to the main environment
if (anyEntityTypeConfiguration)
{
mainEnvironment.Append(GenerationEnvironment);
anyConfiguration = true;
}
// Resume generating code into the main environment
GenerationEnvironment = mainEnvironment;
}
foreach (var sequence in Model.GetSequences())
{
var needsType = sequence.Type != typeof(long);
var needsSchema = !string.IsNullOrEmpty(sequence.Schema) && sequence.Schema != sequence.Model.GetDefaultSchema();
var sequenceFluentApiCalls = sequence.GetFluentApiCalls(annotationCodeGenerator);
#>
modelBuilder.HasSequence<#= needsType ? $"<{code.Reference(sequence.Type)}>" : "" #>(<#= code.Literal(sequence.Name) #><#= needsSchema ? $", {code.Literal(sequence.Schema)}" : "" #>)<#= code.Fragment(sequenceFluentApiCalls, indent: 3) #>;
<#
}
if (anyConfiguration)
{
WriteLine("");
}
#>
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}
<#
mainEnvironment = GenerationEnvironment;
GenerationEnvironment = new StringBuilder();
foreach (var ns in usings.Distinct().OrderBy(x => x, new NamespaceComparer()))
{
#>
using <#= ns #>;
<#
}
WriteLine("");
GenerationEnvironment.Append(mainEnvironment);
#>

View File

@ -0,0 +1,173 @@
<#@ template hostSpecific="true" #>
<#@ assembly name="Microsoft.EntityFrameworkCore" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Design" #>
<#@ assembly name="Microsoft.EntityFrameworkCore.Relational" #>
<#@ assembly name="Microsoft.Extensions.DependencyInjection.Abstractions" #>
<#@ parameter name="EntityType" type="Microsoft.EntityFrameworkCore.Metadata.IEntityType" #>
<#@ parameter name="Options" type="Microsoft.EntityFrameworkCore.Scaffolding.ModelCodeGenerationOptions" #>
<#@ parameter name="NamespaceHint" type="System.String" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.ComponentModel.DataAnnotations" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="Microsoft.EntityFrameworkCore" #>
<#@ import namespace="Microsoft.EntityFrameworkCore.Design" #>
<#@ import namespace="Microsoft.Extensions.DependencyInjection" #>
<#
if (EntityType.IsSimpleManyToManyJoinEntityType())
{
// Don't scaffold these
return "";
}
var services = (IServiceProvider)Host;
var annotationCodeGenerator = services.GetRequiredService<IAnnotationCodeGenerator>();
var code = services.GetRequiredService<ICSharpHelper>();
var usings = new List<string>
{
"System",
"System.Collections.Generic"
};
if (Options.UseDataAnnotations)
{
usings.Add("System.ComponentModel.DataAnnotations");
usings.Add("System.ComponentModel.DataAnnotations.Schema");
usings.Add("Microsoft.EntityFrameworkCore");
}
if (!string.IsNullOrEmpty(NamespaceHint))
{
#>
namespace <#= NamespaceHint #>;
<#
}
if (!string.IsNullOrEmpty(EntityType.GetComment()))
{
#>
/// <summary>
/// <#= code.XmlComment(EntityType.GetComment()) #>
/// </summary>
<#
}
if (Options.UseDataAnnotations)
{
foreach (var dataAnnotation in EntityType.GetDataAnnotations(annotationCodeGenerator))
{
#>
<#= code.Fragment(dataAnnotation) #>
<#
}
}
#>
public partial class <#= EntityType.Name #>
{
<#
var firstProperty = true;
foreach (var property in EntityType.GetProperties().OrderBy(p => p.GetColumnOrder() ?? -1))
{
if (!firstProperty)
{
WriteLine("");
}
if (!string.IsNullOrEmpty(property.GetComment()))
{
#>
/// <summary>
/// <#= code.XmlComment(property.GetComment(), indent: 1) #>
/// </summary>
<#
}
if (Options.UseDataAnnotations)
{
var dataAnnotations = property.GetDataAnnotations(annotationCodeGenerator)
.Where(a => !(a.Type == typeof(RequiredAttribute) && Options.UseNullableReferenceTypes && !property.ClrType.IsValueType));
foreach (var dataAnnotation in dataAnnotations)
{
#>
<#= code.Fragment(dataAnnotation) #>
<#
}
}
usings.AddRange(code.GetRequiredUsings(property.ClrType));
var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !property.ClrType.IsValueType;
var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !property.ClrType.IsValueType;
#>
public <#= code.Reference(property.ClrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
firstProperty = false;
}
foreach (var navigation in EntityType.GetNavigations())
{
WriteLine("");
if (Options.UseDataAnnotations)
{
foreach (var dataAnnotation in navigation.GetDataAnnotations(annotationCodeGenerator))
{
#>
<#= code.Fragment(dataAnnotation) #>
<#
}
}
var targetType = navigation.TargetEntityType.Name;
if (navigation.IsCollection)
{
#>
public virtual ICollection<<#= targetType #>> <#= navigation.Name #> { get; set; } = new List<<#= targetType #>>();
<#
}
else
{
var needsNullable = Options.UseNullableReferenceTypes && !(navigation.ForeignKey.IsRequired && navigation.IsOnDependent);
var needsInitializer = Options.UseNullableReferenceTypes && navigation.ForeignKey.IsRequired && navigation.IsOnDependent;
#>
public virtual <#= targetType #><#= needsNullable ? "?" : "" #> <#= navigation.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
}
}
foreach (var skipNavigation in EntityType.GetSkipNavigations())
{
WriteLine("");
if (Options.UseDataAnnotations)
{
foreach (var dataAnnotation in skipNavigation.GetDataAnnotations(annotationCodeGenerator))
{
#>
<#= code.Fragment(dataAnnotation) #>
<#
}
}
#>
public virtual ICollection<<#= skipNavigation.TargetEntityType.Name #>> <#= skipNavigation.Name #> { get; set; } = new List<<#= skipNavigation.TargetEntityType.Name #>>();
<#
}
#>
}
<#
var previousOutput = GenerationEnvironment;
GenerationEnvironment = new StringBuilder();
foreach (var ns in usings.Distinct().OrderBy(x => x, new NamespaceComparer()))
{
#>
using <#= ns #>;
<#
}
WriteLine("");
GenerationEnvironment.Append(previousOutput);
#>

View File

@ -0,0 +1,59 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# 17
VisualStudioVersion = 17.10.35027.167
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudGaming", "CloudGaming\CloudGaming.csproj", "{0465FB68-91F3-408E-BD0E-B4D99D91F857}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1-core", "1-core", "{FCA3CA4B-1993-429A-B2E9-2B05DB44F10E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3-api", "3-api", "{51CB40D2-99F5-43E8-95B4-3A75C91736A6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5-console", "5-console", "{9F7EF36C-17BB-4F93-927E-F462FE3C9337}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "9-test", "9-test", "{0AD219C3-22E9-493C-835D-00694604BD68}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0-model", "0-model", "{A3F00FB0-49D6-48B1-99D9-4619634DF8D9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F3436760-3B85-4022-AB1C-1DB47976A540}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2-utlie", "2-utlie", "{46D0820D-4EC3-4C22-986D-E505CDEC52D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuanMeng.DotNetCore", "1-utile\HuanMeng.DotNetCore\HuanMeng.DotNetCore.csproj", "{0AEFD698-79ED-4A23-AB45-3BAE39B22E10}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudGaming.Model", "CloudGaming.Model\CloudGaming.Model.csproj", "{B0208C8A-63DE-4295-B740-B7A0D556E1E0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0465FB68-91F3-408E-BD0E-B4D99D91F857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0465FB68-91F3-408E-BD0E-B4D99D91F857}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0465FB68-91F3-408E-BD0E-B4D99D91F857}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0465FB68-91F3-408E-BD0E-B4D99D91F857}.Release|Any CPU.Build.0 = Release|Any CPU
{0AEFD698-79ED-4A23-AB45-3BAE39B22E10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0AEFD698-79ED-4A23-AB45-3BAE39B22E10}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0AEFD698-79ED-4A23-AB45-3BAE39B22E10}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0AEFD698-79ED-4A23-AB45-3BAE39B22E10}.Release|Any CPU.Build.0 = Release|Any CPU
{B0208C8A-63DE-4295-B740-B7A0D556E1E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B0208C8A-63DE-4295-B740-B7A0D556E1E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B0208C8A-63DE-4295-B740-B7A0D556E1E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B0208C8A-63DE-4295-B740-B7A0D556E1E0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0465FB68-91F3-408E-BD0E-B4D99D91F857} = {51CB40D2-99F5-43E8-95B4-3A75C91736A6}
{0AEFD698-79ED-4A23-AB45-3BAE39B22E10} = {46D0820D-4EC3-4C22-986D-E505CDEC52D2}
{B0208C8A-63DE-4295-B740-B7A0D556E1E0} = {A3F00FB0-49D6-48B1-99D9-4619634DF8D9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1D299D92-FA27-47A0-8D78-43D1FAFE7628}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>60edfd76-b8fa-477a-b105-6e6a7da42295</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DependencyValidation.Analyzers" Version="0.11.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@CloudGaming_HostAddress = http://localhost:5171
GET {{CloudGaming_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;
namespace CloudGaming.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();
}
}
}

View File

@ -0,0 +1,25 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["CloudGaming/CloudGaming.csproj", "CloudGaming/"]
RUN dotnet restore "./CloudGaming/CloudGaming.csproj"
COPY . .
WORKDIR "/src/CloudGaming"
RUN dotnet build "./CloudGaming.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./CloudGaming.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "CloudGaming.dll"]

View File

@ -0,0 +1,25 @@
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.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

View File

@ -0,0 +1,52 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5171"
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7081;http://localhost:5171"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:31403",
"sslPort": 44348
}
}
}

View File

@ -0,0 +1,13 @@
namespace CloudGaming
{
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; }
}
}

View File

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

View File

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