diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1c01d66 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d57b3d4 --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/src/CloudGaming/.dockerignore b/src/CloudGaming/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/src/CloudGaming/.dockerignore @@ -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/** \ No newline at end of file diff --git a/src/CloudGaming/.editorconfig b/src/CloudGaming/.editorconfig new file mode 100644 index 0000000..4bf21d8 --- /dev/null +++ b/src/CloudGaming/.editorconfig @@ -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 + +# 浠g爜鍧楅閫夐」 +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 diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/BLLBase.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/BLLBase.cs new file mode 100644 index 0000000..54d58b0 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/BLLBase.cs @@ -0,0 +1,27 @@ +namespace HuanMeng.DotNetCore.Base +{ + public abstract class BLLBase where TDao : DaoBase + { + /// + /// _serviceProvider,鎻愪緵鍩烘湰渚濊禆娉ㄥ叆鏀寔 + /// + protected readonly IServiceProvider _serviceProvider; + + + /// + /// _dao,鎻愪緵鏁版嵁璁块棶鏀寔 + /// + public abstract TDao Dao { get; } + + + /// + /// 鏋勯犲嚱鏁 + /// + /// + /// + public BLLBase(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/BaseResponse.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/BaseResponse.cs new file mode 100644 index 0000000..b75da7a --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/BaseResponse.cs @@ -0,0 +1,92 @@ + +using System.Runtime.Serialization; + +using XLib.DotNetCore.Base; + +namespace HuanMeng.DotNetCore.Base +{ + /// + /// 鎺ュ彛鍜屾湇鍔¤皟鐢ㄥ熀纭鍝嶅簲绫 + /// + /// + [DataContract] + [Serializable] + public class BaseResponse : IResponse + { + + ///// + ///// Http鐘舵佺爜 + ///// + //[DataMember] + //public HttpStatusCode StatusCode { get; set; } + + /// + /// 鍔熻兘鎵ц杩斿洖浠g爜 + /// + [DataMember] + public int Code { get; set; } + + /// + /// 娑堟伅 + /// + [DataMember] + public string Message { get; set; } + + /// + /// 鏁版嵁 + /// + [DataMember] + public T? Data { get; set; } + + /// + /// 鏋勯犲嚱鏁 + /// + public BaseResponse() + { + //StatusCode = HttpStatusCode.OK; + Code = 0; + Message = ""; + } + /// + /// 鏋勯犲嚱鏁 + /// + public BaseResponse(int code, string message) + { + Code = code; + Message = message; + Data = default(T); + + } + + + /// + /// 鏋勯犲嚱鏁 + /// + public BaseResponse(int code, string message, T? data) + { + Code = code; + Message = message; + Data = data; + } + + /// + /// 鏋勯犲嚱鏁 + /// + public BaseResponse(ResonseCode code, string message, T? data) + { + Code = (int)code; + Message = message; + Data = data; + } + + /// + /// ToString + /// + /// + public override string ToString() + { + return $"Code:{Code};Message:{Message}; Data:{Data}"; + } + + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/DaoBase.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/DaoBase.cs new file mode 100644 index 0000000..3cbf519 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/DaoBase.cs @@ -0,0 +1,32 @@ +锘縩amespace HuanMeng.DotNetCore.Base +{ + public class DaoBase + { + /// + /// _serviceProvider,鎻愪緵鍩烘湰渚濊禆娉ㄥ叆鏀寔 + /// + protected readonly IServiceProvider _serviceProvider; + + ///// + ///// 鏋勯犲嚱鏁 + ///// + //public DaoBase() + //{ + // // 鍒涘缓涓涓┖鐨凷erviceCollection + // var webApplication = WebApplication.Current; + // var services = new ServiceCollection(); + + // // 鍒涘缓ServiceProvider瀹炰緥 + // _serviceProvider = services.BuildServiceProvider(); + //} + + /// + /// 鏋勯犲嚱鏁 + /// + /// + public DaoBase(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs new file mode 100644 index 0000000..270a99b --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs @@ -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 +{ /// + /// 鍩烘湰鏁版嵁搴撴搷浣滐紝闇瑕佸畨瑁 + /// Microsoft.EntityFrameworkCore + /// Microsoft.EntityFrameworkCore.Relational + /// + /// + public class EfCoreDaoBase where TDbContext : DbContext + //, new() + { + private TDbContext _context; + public TDbContext context + { + get + { + //return _context ?? (_context = new TDbContext()); + return _context; + } + //set { _context = value; } + } + + //public EfCoreDaoBase() { } + + /// + /// 鏋勯犲嚱鏁 + /// + /// + public EfCoreDaoBase(TDbContext context) + { + _context = context; + } + /// + /// 鏄惁鎵嬪姩鎻愪氦 + /// + public bool IsManualSubmit { get; set; } + + /// + /// SqlQueryRaw + /// + /// + /// + /// + /// + public T SqlQuery(string sql, params object[] parameters) where T : class + { + var quiry = context.Database.SqlQueryRaw(sql, parameters); + return quiry.FirstOrDefault(); + } + + /// + /// SqlQueryList + /// + /// + /// + /// + /// + public List SqlQueryList(string sql, params object[] parameters) + { + var quiry = context.Database.SqlQueryRaw(sql, parameters); + return quiry.ToList(); + } + + /// + /// ExecuteSql + /// + /// + /// + /// + public int ExecuteSql(string sql, params object[] parameters) + { + int r = context.Database.ExecuteSqlRaw(sql, parameters); + context.SaveChanges(); + return r; + } + + /// + /// 娣诲姞瀹炰綋 + /// + /// + public void Add(T entity) where T : class + { + context.Set().Add(entity); + if (!IsManualSubmit) + context.SaveChanges(); + } + + /// + /// 鎵归噺娣诲姞瀹炰綋 + /// + /// + /// + public void AddRange(List entity) where T : class + { + context.Set().AddRange(entity); + if (!IsManualSubmit) + context.SaveChanges(); + } + /// + /// 鍒犻櫎鏌愪釜瀹炰綋 + /// + /// + public void Delete(T entity) where T : class + { + context.Entry(entity).State = EntityState.Deleted; //鏁存潯鏇存柊 + //context.Set().Remove(entity); + if (!IsManualSubmit) + context.SaveChanges(); + } + + /// + /// 鏇存柊瀹炰綋 + /// + /// + public void Update(T entity) where T : class + { + //涓鑸笉闇瑕佹暣鏉℃洿鏂帮紝鎸夐渶鏇存柊瀛楁鍗冲彲 + if (context.Entry(entity).State == EntityState.Detached) + { + context.Set().Attach(entity); + context.Entry(entity).State = EntityState.Modified; //鏁存潯鏇存柊 + } + if (!IsManualSubmit) + context.SaveChanges(); + } + + /// + /// 娓呴櫎涓婁笅鏂囪窡韪紙娓呴櫎缂撳瓨锛 by wyg + /// + /// + /// + public void removeTracking(T entity) where T : class + { + if (context.Entry(entity).State != EntityState.Detached) + context.Entry(entity).State = EntityState.Detached; + } + + /// + /// 鑾峰彇瀹炰綋,浠庣紦瀛樹腑锛屾牴鎹敭鍊艰幏鍙 + /// + /// + /// + /// + public T GetModel(params object[] keyValues) where T : class + { + return context.Set().Find(keyValues); + } + /// + /// 鑾峰彇瀹炰綋锛屾牴鎹潯浠惰幏鍙栵紝浠庢暟鎹簱鑾峰彇 + /// + /// 绛涢夋潯浠 + /// 榛樿false浼氳繘琛岀紦瀛樺苟璺熻釜(鍙寜闇update瀛楁)锛宼rue涓嶉渶瑕佺紦瀛樺拰璺熻釜(涓鑸彧璇绘煡璇娇鐢) + /// + public T GetModel(System.Linq.Expressions.Expression> where, bool isNoTracking = false) where T : class + { + if (!isNoTracking) + return context.Set().FirstOrDefault(where); //鍙寜闇update瀛楁 + return context.Set().AsNoTracking().FirstOrDefault(where); //涓鑸彧璇绘煡璇娇鐢 + } + + /// + /// 鑾峰彇鍒楄〃鏁版嵁锛岃繑鍥濱Queryable + /// + /// 绛涢夋潯浠 + /// 榛樿false浼氳繘琛岀紦瀛樺苟璺熻釜(鍙寜闇update瀛楁)锛宼rue涓嶉渶瑕佺紦瀛樺拰璺熻釜(涓鑸彧璇绘煡璇娇鐢) + /// + public IQueryable GetList(System.Linq.Expressions.Expression> where, bool isNoTracking = false) where T : class + { + if (!isNoTracking) + return context.Set().Where(where).AsQueryable(); + return context.Set().Where(where).AsNoTracking().AsQueryable(); + } + + public int GetCount(System.Linq.Expressions.Expression> where) where T : class + { + return context.Set().AsNoTracking().Count(where); + } + public bool Exists(System.Linq.Expressions.Expression> where) where T : class + { + //return context.Set().AsNoTracking().Any(where); + return context.Set().AsNoTracking().Count(where) > 0; + } + + + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/IResponse.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/IResponse.cs new file mode 100644 index 0000000..ecdf588 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/IResponse.cs @@ -0,0 +1,23 @@ +锘縩amespace XLib.DotNetCore.Base +{ + /// + /// 鎺ュ彛鍜屾湇鍔¤皟鐢ㄩ氱敤鍝嶅簲鎺ュ彛 + /// + public interface IResponse + { + ///// + ///// Http鐘舵佺爜 + ///// + //HttpStatusCode StatusCode { get; set; } + + /// + /// 鍔熻兘鎵ц杩斿洖浠g爜 + /// + int Code { get; set; } + + /// + /// 娑堟伅 + /// + string Message { get; set; } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/ResonseCode.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/ResonseCode.cs new file mode 100644 index 0000000..f5ffee2 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Base/ResonseCode.cs @@ -0,0 +1,53 @@ +namespace HuanMeng.DotNetCore.Base +{ + /// + /// 鍝嶅簲缂栫爜鍙傝冿紝瀹為檯鐨勯」鐩娇鐢ㄥ彲浠ヨ嚜琛屽畾涔 + /// 鍩烘湰瑙勫垯锛 + /// 鎴愬姛锛氬ぇ浜庣瓑浜0 + /// 澶辫触锛氬皬浜0 + /// + public enum ResonseCode + { + /// + /// Sign绛惧悕閿欒 + /// + SignError = -999, + /// + /// jwt鐢ㄦ埛绛惧悕閿欒 + /// + TwtError = -998, + + /// + /// 鐢ㄦ埛楠岃瘉澶辫触 + /// + Unauthorized = 401, + /// + /// 閲嶅璇锋眰 + /// + ManyRequests = 429, + + /// + /// 姝e湪澶勭悊涓 + /// + Processing = 102, + /// + /// 閫氱敤閿欒 + /// + Error = -1, + + /// + /// 鍙傛暟閿欒 + /// + ParamError = -2, + + /// + /// 娌℃壘鍒版暟鎹褰 + /// + NotFoundRecord = -3, + + /// + /// 鎴愬姛 + /// + Success = 0, + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/CacheHelper/MemoryCacheHelper.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/CacheHelper/MemoryCacheHelper.cs new file mode 100644 index 0000000..15b52f5 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/CacheHelper/MemoryCacheHelper.cs @@ -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 +{ + /// + /// 鍐呭瓨缂撳瓨甯姪绫 + /// + public class MemoryCacheHelper + { + public static MemoryCache cache = new MemoryCache(new MemoryCacheOptions()); + + /// + /// 鑾峰彇缂撳瓨 + /// + /// + /// + /// + public static T? GetCache(string cacheName) where T : class, new() + { + + return cache.TryGetValue(cacheName, out var value) ? value as T : null; + } + + /// + /// 璁剧疆缂撳瓨 + /// + /// + /// + /// 鍗曚綅绉掞紝榛樿1灏忔椂 + 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)); + } + + /// + /// 鍒犻櫎缂撳瓨 + /// + /// + public static void DelCache(string? cacheName = null) + { + if (!string.IsNullOrEmpty(cacheName)) + cache.Remove(cacheName); + else + cache.Dispose(); + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/CustomExtension/CorsExtension.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/CustomExtension/CorsExtension.cs new file mode 100644 index 0000000..92148b4 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/CustomExtension/CorsExtension.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection; + + +namespace HuanMeng.DotNetCore.CustomExtension +{ + /// + /// 娣诲姞璺ㄥ煙 + /// + public static class CorsExtension + { + /// + /// 娣诲姞璺ㄥ煙 + /// + /// + /// + public static void AddCustomCors(this IServiceCollection services, string policyName) + { + services.AddCors(options => + { + options.AddPolicy(policyName, + builder => + { + builder + .AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); + }); + + + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj new file mode 100644 index 0000000..beb049e --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj @@ -0,0 +1,25 @@ +锘 + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Json/CustomDateTimeConverter.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Json/CustomDateTimeConverter.cs new file mode 100644 index 0000000..d8897e6 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Json/CustomDateTimeConverter.cs @@ -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 + { + 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)); + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/Interface/IJwtAuthManager.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/Interface/IJwtAuthManager.cs new file mode 100644 index 0000000..23e2e1d --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/Interface/IJwtAuthManager.cs @@ -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 +{ + /// + /// jwt甯姪绫 + /// + public interface IJwtAuthManager + { + /// + /// 鐢ㄦ埛鍒锋柊浠ょ墝鍙璇嶅吀 + /// + IImmutableDictionary UsersRefreshTokensReadOnlyDictionary { get; } + + /// + /// 鐢熸垚浠ょ墝 + /// + /// 鐢ㄦ埛鍚 + /// 鐢ㄦ埛鐨勬湁鍏充俊鎭 + /// + /// + JwtAuthResult GenerateTokens(string username, Claim[] claims, DateTime now); + /// + /// 鍒锋柊浠ょ墝 + /// + /// + /// + /// + /// + JwtAuthResult Refresh(string refreshToken, string accessToken, DateTime now); + /// + /// 鍒犻櫎杩囨湡鐨勫埛鏂颁护鐗 + /// + /// + void RemoveExpiredRefreshTokens(DateTime now); + /// + /// 鎸夌敤鎴峰悕鍒犻櫎鍒锋柊浠ょ墝 + /// + /// + void RemoveRefreshTokenByUserName(string userName); + /// + /// 瑙g爜JwtToken + /// + /// + /// + (ClaimsPrincipal, JwtSecurityToken?) DecodeJwtToken(string token); + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtAuthManager.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtAuthManager.cs new file mode 100644 index 0000000..780f9b0 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtAuthManager.cs @@ -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 +{ + /// + /// jwt甯姪绫 + /// + /// + public class JwtAuthManager(JwtTokenConfig jwtTokenConfig) : IJwtAuthManager + { + /// + /// 淇濆瓨鍒锋柊token + /// + public IImmutableDictionary UsersRefreshTokensReadOnlyDictionary => _usersRefreshTokens.ToImmutableDictionary(); + /// + /// 鍚庨潰鍙互瀛樺偍鍦ㄦ暟鎹簱鎴栧垎甯冨紡缂撳瓨涓 + /// + private readonly ConcurrentDictionary _usersRefreshTokens = new(); + /// + /// 鑾峰彇鍔犲瘑瀛楁 + /// + private readonly byte[] _secret = Encoding.UTF8.GetBytes(jwtTokenConfig.Secret); + + /// + /// 鍒犻櫎杩囨湡token + /// + /// + 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 _); + } + } + + /// + /// 鏍规嵁鐢ㄦ埛鍚嶅垹闄oken + /// + /// + 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 _); + } + } + + /// + /// 鍒涘缓token + /// + /// 鐢ㄦ埛鍚 + /// 鐢ㄦ埛椤 + /// 杩囨湡鏃堕棿 + /// + 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 + }; + } + + /// + /// 鍒锋柊token + /// + /// + /// + /// + /// + /// + 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("鏃犳晥鐨則oken"); + } + + 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); + } + /// + /// 瑙f瀽token + /// + /// + /// + /// + 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); + } + + + /// + /// 鑾峰彇鍒锋柊鐨則oken + /// + /// + private static string GenerateRefreshTokenString() + { + var randomNumber = new byte[32]; + using var randomNumberGenerator = RandomNumberGenerator.Create(); + randomNumberGenerator.GetBytes(randomNumber); + return Convert.ToBase64String(randomNumber); + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtAuthResult.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtAuthResult.cs new file mode 100644 index 0000000..6bdbd7b --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtAuthResult.cs @@ -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 +{ + /// + /// 浠ょ墝 + /// + public class JwtAuthResult + { + /// + /// 褰撳墠token + /// + [JsonPropertyName("accessToken")] + public string AccessToken { get; set; } = string.Empty; + + /// + /// 鍒锋柊token + /// + [JsonPropertyName("refreshToken")] + public JwtRefreshToken RefreshToken { get; set; } = new(); + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtRefreshToken.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtRefreshToken.cs new file mode 100644 index 0000000..f68f04a --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtRefreshToken.cs @@ -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 +{ + /// + /// 鍒锋柊token + /// + public class JwtRefreshToken + { + /// + /// 鐢ㄦ埛鍚 + /// + [JsonPropertyName("username")] + public string UserName { get; set; } = string.Empty; + + /// + /// token + /// + [JsonPropertyName("tokenString")] + public string TokenString { get; set; } = string.Empty; + + /// + /// 杩囨湡鏃堕棿 + /// + [JsonPropertyName("expireAt")] + public DateTime ExpireAt { get; set; } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtTokenConfig.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtTokenConfig.cs new file mode 100644 index 0000000..a62d459 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/JwtInfrastructure/JwtTokenConfig.cs @@ -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 +{ + /// + /// JwtToken 閰嶇疆鏂囦欢 + /// + public class JwtTokenConfig + { + + /// + /// 鍔犲瘑鍊 + /// + [JsonPropertyName("secret")] + public string Secret { get; set; } = string.Empty; + + /// + /// 棰佸彂鑰 + /// + [JsonPropertyName("issuer")] + public string Issuer { get; set; } = string.Empty; + + /// + /// 鍙椾紬 + /// + [JsonPropertyName("audience")] + public string Audience { get; set; } = string.Empty; + + /// + /// 浠ょ墝杩囨湡鏃堕棿 + /// + [JsonPropertyName("accessTokenExpiration")] + public int AccessTokenExpiration { get; set; } + + /// + /// 鍒锋柊浠ょ墝杩囨湡鏃堕棿锛堜竴鑸細姣斾护鐗岃繃鏈熸椂闂撮暱锛 + /// + [JsonPropertyName("refreshTokenExpiration")] + public int RefreshTokenExpiration { get; set; } + + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/ExceptionMiddleware.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/ExceptionMiddleware.cs new file mode 100644 index 0000000..e3f089a --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/ExceptionMiddleware.cs @@ -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 +{ + /// + /// 寮傚父涓棿浠 + /// + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ExceptionMiddleware(RequestDelegate next, ILogger 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 baseResponse = new BaseResponse(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 baseResponse = new BaseResponse(ResonseCode.Error, ex.Message, null); + // 灏嗗紓甯镐俊鎭啓鍏 HTTP 鍝嶅簲 + await context.Response.WriteAsync(JsonConvert.SerializeObject(baseResponse, settings)); + } + finally + { + + } + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/ExecutionTimeMiddleware.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/ExecutionTimeMiddleware.cs new file mode 100644 index 0000000..6307f6c --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/ExecutionTimeMiddleware.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Http; + +using System.Diagnostics; + +namespace HuanMeng.DotNetCore.MiddlewareExtend +{ + /// + /// 鏂规硶鎵ц鏃堕棿 + /// + 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); + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/MiddlewareExtends.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/MiddlewareExtends.cs new file mode 100644 index 0000000..1a37e6d --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/MiddlewareExtends.cs @@ -0,0 +1,54 @@ +using Microsoft.AspNetCore.Builder; + +namespace HuanMeng.DotNetCore.MiddlewareExtend +{ + /// + /// 涓棿搴撴墿灞 + /// + public static class MiddlewareExtends + { + /// + /// 鍔犺浇鍏ㄩ儴涓棿浠 + /// + /// + /// + public static IApplicationBuilder UseMiddlewareAll(this IApplicationBuilder builder) + { + return builder + .UseExceptionMiddleware() + .UseExecutionTimeMiddleware() + .UseSignMiddleware() + ; + + } + + /// + /// 寮傚父涓棿浠 + /// + /// + /// + public static IApplicationBuilder UseExecutionTimeMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + /// + /// 鎵ц鏃堕棿涓棿浠 + /// + /// + /// + public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + + /// + /// 鍔犲瘑楠岃瘉 + /// + /// + /// + public static IApplicationBuilder UseSignMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/SignMiddleware.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/SignMiddleware.cs new file mode 100644 index 0000000..a27517a --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MiddlewareExtend/SignMiddleware.cs @@ -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 +{ + /// + /// 鍙傛暟璇锋眰鍔犲瘑楠岃瘉 + /// + 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; + } + // 瑙f瀽璇锋眰浣撲负 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 baseResponse = new BaseResponse(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); + } + /// + /// Md5鍔犲瘑 + /// + /// + /// + 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(); + } + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenant.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenant.cs new file mode 100644 index 0000000..a8a5b62 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenant.cs @@ -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 +{ + /// + /// 鍩烘湰澶氱鎴锋帴鍙 + /// + public interface IMultiTenant + { + /// + /// 绉熸埛ID + /// + Guid TenantId { get; set; } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenantDbContext.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenantDbContext.cs new file mode 100644 index 0000000..21b2ec2 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenantDbContext.cs @@ -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 +{ + /// + /// 澶氱鎴稤bContent鎺ュ彛 + /// + public interface IMultiTenantDbContext + { + /// + /// 绉熸埛淇℃伅 + /// + ITenantInfo TenantInfo { get; } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenantEntity.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenantEntity.cs new file mode 100644 index 0000000..7ab6888 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/IMultiTenantEntity.cs @@ -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 +{ + /// + /// 澶氱鎴峰疄浣撶被鎺ュ彛 + /// + public interface IMultiTenantEntity : IMultiTenant + { + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/ITenantInfo.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/ITenantInfo.cs new file mode 100644 index 0000000..2fe50e6 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/Contract/ITenantInfo.cs @@ -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 +{ + + /// + /// 绉熸埛淇℃伅鎺ュ彛 + /// + public interface ITenantInfo : IMultiTenant + { + + string? Identifier { get; set; } + + /// + /// Gets or sets a display friendly name for the tenant. + /// + string? Name { get; set; } + + /// + /// 鏁版嵁搴撹繛鎺ュ瓧绗︿覆 + /// + string? ConnectionString { get; set; } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/MultiTenantDbContext.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/MultiTenantDbContext.cs new file mode 100644 index 0000000..99c6ec3 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/MultiTenantDbContext.cs @@ -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 +{ + /// + /// 鍩烘湰澶氱鎴稤bContext + /// + public class MultiTenantDbContext : DbContext, IMultiTenantDbContext + { + /// + /// 绉熸埛淇℃伅 + /// + public ITenantInfo? TenantInfo { get; set; } + + + /// + /// 鏋勯犲嚱鏁 + /// + /// + public MultiTenantDbContext(ITenantInfo? tenantInfo) + { + this.TenantInfo = tenantInfo; + } + + /// + /// 鏋勯犲嚱鏁 + /// + /// + /// + 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 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); + } + + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/MultiTenantEntity.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/MultiTenantEntity.cs new file mode 100644 index 0000000..d7d58fa --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/MultiTenantEntity.cs @@ -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 +{ + /// + /// 鍩烘湰澶氱鎴峰疄浣撶被 + /// + public class MultiTenantEntity : IMultiTenantEntity + { + /// + /// 绉熸埛ID + /// + public virtual Guid TenantId { get; set; } + + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/TenantInfo.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/TenantInfo.cs new file mode 100644 index 0000000..04feb94 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/MultiTenant/TenantInfo.cs @@ -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 +{ + /// + /// 绉熸埛淇℃伅 + /// + public class TenantInfo : ITenantInfo + { + + public TenantInfo() + { + } + + /// + /// 绉熸埛ID + /// + public Guid TenantId { get; set; } + + /// + /// 绉熸埛瀛楃涓叉爣蹇 + /// + public string? Identifier { get; set; } + + /// + /// 绉熸埛鍚嶇О(鎻忚堪璇存槑) + /// + public string? Name { get; set; } + + /// + /// 绉熸埛鏁版嵁搴撹繛鎺ュ瓧绗︿覆 + /// + public string? ConnectionString { get; set; } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/BaseProcessor.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/BaseProcessor.cs new file mode 100644 index 0000000..17c7372 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/BaseProcessor.cs @@ -0,0 +1,58 @@ +锘 + +namespace HuanMeng.DotNetCore.Processors +{ + /// + /// 浠诲姟澶勭悊鍣 + /// + public abstract class BaseProcessor : ITaskProcessor + { + /// + /// 缁堟鍐呴儴澶勭悊绾跨▼鐨勬渶闀跨瓑寰呮椂闂(姣) + /// + protected const int WaitTimeMax_StopProc = 20000; + + + /// + /// 鏋勯犲嚱鏁 + /// + public BaseProcessor() + { + //鍔犺浇閰嶇疆 + LoadSettings(); + } + + /// + /// 鍔犺浇閰嶇疆 + /// + protected virtual void LoadSettings() + { + //鍒濆鍖 + // LogHelper.Info("BaseProcessor.LoadSettings"); + } + + /// + /// Dispose + /// + public virtual void Dispose() + { + } + + /// + /// 鎵ц浠诲姟 + /// + public virtual void Run() + { + //LogHelper.Info("BaseProcessor.Run"); + } + + /// + /// 鍋滄鎵ц浠诲姟 + /// + public virtual void Stop() + { + // LogHelper.Info("BaseProcessor.Stop"); + } + } + +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/ITaskProcessor.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/ITaskProcessor.cs new file mode 100644 index 0000000..18a187b --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/ITaskProcessor.cs @@ -0,0 +1,18 @@ +锘縩amespace HuanMeng.DotNetCore.Processors +{ + /// + /// 浠诲姟澶勭悊鍣ㄦ帴鍙 + /// + public interface ITaskProcessor : IDisposable + { + /// + /// 杩愯浠诲姟 + /// + void Run(); + + /// + /// 鍋滄杩愯 + /// + void Stop(); + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/ThreadProcessor.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/ThreadProcessor.cs new file mode 100644 index 0000000..128a742 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Processors/ThreadProcessor.cs @@ -0,0 +1,81 @@ +namespace HuanMeng.DotNetCore.Processors +{ + /// + /// 鍖呭惈澶勭悊绾跨▼鐨勫鐞嗗櫒鍩虹被 + /// + public abstract class ThreadProcessor : BaseProcessor + { + protected Thread? _thread; + protected bool _isRunning; + protected string? _threadName; + + /// + /// 鏋勯犲嚱鏁 + /// + public ThreadProcessor() { } + + /// + /// 鏋勯犲嚱鏁 + /// + /// 绾跨▼鍚嶇О + public ThreadProcessor(string threadName) + { + _threadName = threadName; + } + + /// + /// 鎵ц澶勭悊 + /// + /// 濡傛灉澶勭悊鍣ㄥ凡缁忓湪杩愯锛屾姏鍑哄紓甯 + public override void Run() + { + if (_isRunning) + { + throw new InvalidOperationException("閲嶅鎵ц绾跨▼"); + } + + _isRunning = true; + + _thread = new Thread(Proc_Do) + { + Name = _threadName + }; + _thread.Start(); + } + + /// + /// 绾跨▼澶勭悊鍑芥暟锛岄渶瑕佸湪瀛愮被涓疄鐜 + /// + protected abstract void Proc_Do(); + + /// + /// 鍋滄澶勭悊绾跨▼ + /// + public override void Stop() + { + if (!_isRunning) + { + return; + } + + _isRunning = false; + + if (_thread != null && _thread.IsAlive) + { + _thread.Join(WaitTimeMax_StopProc); // 绛夊緟绾跨▼缁撴潫 + _thread = null; + } + } + + /// + /// 閲婃斁璧勬簮 + /// + public override void Dispose() + { + Stop(); + base.Dispose(); + } + + + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/CheckTextVerification.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/CheckTextVerification.cs new file mode 100644 index 0000000..f99e78c --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/CheckTextVerification.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.DotNetCore.TextCensor +{ + /// + /// 楠岃瘉瀛楃涓叉槸鍚﹁繚瑙 + /// + //[Obsolete] + //public static class CheckTextVerification + //{ + // static HashSet ShieldStr = new HashSet(); + + // static List ShieldListStr = new List(); + // 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); + // } + // // + // } + + // /// + // /// + // /// + // /// + // /// + // public static void AddShieldString(string path) + // { + + // if (!File.Exists(path)) + // { + // throw new Exception("鏂囦欢涓嶅瓨鍦"); + // } + // HashSet strings = new HashSet(); + + // 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}")); + // } + // } + // /// + // /// + // /// + // /// + // /// + // 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; + // } + // /// + // /// + // /// + // /// + // /// + // 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_-"; + + // /// + // /// + // /// + // /// + // /// + // 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; + // } + //} +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/ITextCensor.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/ITextCensor.cs new file mode 100644 index 0000000..63b7e1a --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/ITextCensor.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.DotNetCore.TextCensor +{ + /// + /// 鏂囨湰瀹℃牳鎺ュ彛 + /// + public interface ITextCensor + { + /// + /// 鏂囨湰瀹℃牳 + /// + /// + /// + bool TextCensor(string text); + + /// + /// 鏂囨湰瀹℃牳 + /// + /// + /// + Task TextCensorAsync(string text); + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/SensitiveWord/SensitiveWordFilter.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/SensitiveWord/SensitiveWordFilter.cs new file mode 100644 index 0000000..7994def --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/SensitiveWord/SensitiveWordFilter.cs @@ -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 + { + /// + /// 瀹氫箟Trie鏍戣妭鐐 + /// + private class TrieNode + { + /// + /// 鏍囪鏄惁鏄竴涓晱鎰熻瘝鐨勭粨灏 + /// + public bool IsEnd { get; set; } + /// + /// 瀛樺偍瀛愯妭鐐 + /// + public Dictionary Children { get; set; } + /// + /// + /// + //public FrozenDictionary fChildren { get; set; } + public TrieNode() + { + IsEnd = false; + Children = new Dictionary(); + } + } + + /// + /// 鏍硅妭鐐 + /// + private TrieNode Root { get; set; } + public SensitiveWordFilter() + { + Root = new TrieNode(); + } + /// + /// 娣诲姞鏁忔劅璇嶅埌Trie鏍戜腑 + /// + /// + 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; // 鏍囪褰撳墠鑺傜偣涓烘晱鎰熻瘝缁撳熬 + } + /// + /// 瀹氫箟瑕佺Щ闄ょ殑瀛楃闆嗗悎锛屽寘鎷┖鏍 + /// + private static string replaceString = @"[ \.,銆傦紝@\-*12345690_]"; + /// + /// 娓呯悊鏂囧瓧 + /// + /// + /// + public string CleanText(string sourceTxt) + { + if (string.IsNullOrEmpty(sourceTxt)) + { + return string.Empty; + } + + // 浣跨敤姝e垯琛ㄨ揪寮忔浛鎹㈡墍鏈夊尮閰嶇殑瀛楃 + //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; + } + + /// + /// 鍒ゆ柇鏂囨湰涓槸鍚﹀寘鍚晱鎰熻瘝 + /// + /// + /// + 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]]; + // 濡傛灉褰撳墠鑺傜偣鏄晱鎰熻瘝缁撳熬锛岃繑鍥瀟rue + if (currentNode.IsEnd) + { + return true; + } + j++; + } + } + return false; + } + + public bool TextCensor(string text) + { + return ContainsSensitiveWord(text); + } + + public Task TextCensorAsync(string text) + { + return Task.Run(() => + { + return ContainsSensitiveWord(text); + }); + + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/SensitiveWord/SensitiveWordFilterFrozen.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/SensitiveWord/SensitiveWordFilterFrozen.cs new file mode 100644 index 0000000..fcfd5d2 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/SensitiveWord/SensitiveWordFilterFrozen.cs @@ -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 + { + /// + /// 瀹氫箟Trie鏍戣妭鐐 + /// + private class TrieNode + { + /// + /// 鏍囪鏄惁鏄竴涓晱鎰熻瘝鐨勭粨灏 + /// + public bool IsEnd { get; set; } + /// + /// 瀛樺偍瀛愯妭鐐 + /// + public FrozenDictionary Children { get; private set; } + + public TrieNode() + { + IsEnd = false; + Children = null; + } + + /// + /// 灏嗗瓙鑺傜偣瀛楀吀鍐荤粨涓 FrozenDictionary + /// + public void FreezeChildren(Dictionary children) + { + Children = children.ToFrozenDictionary(); + } + } + + /// + /// 鏍硅妭鐐 + /// + private TrieNode Root { get; set; } + + public SensitiveWordFilterFrozen() + { + Root = new TrieNode(); + } + + /// + /// 娣诲姞鏁忔劅璇嶅埌Trie鏍戜腑 + /// + /// + 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(); + children[c] = new TrieNode(); + currentNode.FreezeChildren(children); + } + currentNode = currentNode.Children[c]; + } + currentNode.IsEnd = true; // 鏍囪褰撳墠鑺傜偣涓烘晱鎰熻瘝缁撳熬 + } + + /// + /// 娓呯悊鏂囧瓧 + /// + /// + /// + 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; + } + + /// + /// 鍒ゆ柇鏂囨湰涓槸鍚﹀寘鍚晱鎰熻瘝 + /// + /// + /// + 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]]; + // 濡傛灉褰撳墠鑺傜偣鏄晱鎰熻瘝缁撳熬锛岃繑鍥瀟rue + if (currentNode.IsEnd) + { + return true; + } + j++; + } + } + return false; + } + + public bool TextCensor(string text) + { + return ContainsSensitiveWord(text); + } + + public Task TextCensorAsync(string text) + { + return Task.Run(() => + { + return ContainsSensitiveWord(text); + }); + + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/TextCensorExtend.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/TextCensorExtend.cs new file mode 100644 index 0000000..f5e2254 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/TextCensor/TextCensorExtend.cs @@ -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 +{ + /// + /// + /// + public static class TextCensorExtend + { + + /// + /// 鑾峰彇鏂囨湰妫娴 + /// + /// + /// + public static ITextCensor GetITextCensor(string _dirPath) + { + + return GetSensitiveWordFilterFrozen(_dirPath) + //GetSensitiveWordFilter(_dirPath) + ; + } + + + /// + /// + /// + /// + /// + 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; + } + + /// + /// + /// + /// + /// + 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; + } + + /// + /// + /// + /// + /// + 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); + + } + } + } + } + /// + /// + /// + /// + /// + 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); + + } + } + } + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfo.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfo.cs new file mode 100644 index 0000000..d1d089f --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.Json.Serialization; + +namespace HuanMeng.DotNetCore.Utility.AssemblyHelper +{ + /// + /// 淇濆瓨鍚勭绋嬪簭闆嗕俊鎭睘鎬х殑绫汇 + /// + [Serializable] + public class AssemblyInfo + { + /// + /// 鑾峰彇鎴栬缃▼搴忛泦鐨勭増鏈 + /// + public string Version { get; set; } + + /// + /// 鑾峰彇鎴栬缃▼搴忛泦鐨勬枃浠剁増鏈 + /// + public string FileVersion { get; set; } + + /// + /// 鑾峰彇鎴栬缃▼搴忛泦鐗堟湰銆 + /// + public string AssemblyVersion { get; set; } + + /// + /// 鑾峰彇鎴栬缃▼搴忛泦鐨勪俊鎭х増鏈 + /// + public string InformationalVersion { get; set; } + + ///// + ///// 鑾峰彇鎴栬缃笌绋嬪簭闆嗗叧鑱旂殑鍏徃鍚嶇О銆 + ///// + //public string Company { get; set; } + + ///// + ///// 鑾峰彇鎴栬缃笌绋嬪簭闆嗗叧鑱旂殑浜у搧鍚嶇О銆 + ///// + //public string Product { get; set; } + + /// + /// 鑾峰彇鎴栬缃笌绋嬪簭闆嗗叧鑱旂殑鐗堟潈淇℃伅銆 + /// + public string Copyright { get; set; } + + /// + /// 鑾峰彇鎴栬缃▼搴忛泦鐨勬弿杩颁俊鎭 + /// + public string Description { get; set; } + } + +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfoHelper.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfoHelper.cs new file mode 100644 index 0000000..28355a6 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/AssemblyHelper/AssemblyInfoHelper.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace HuanMeng.DotNetCore.Utility.AssemblyHelper +{ + /// + /// 鐢ㄤ簬妫绱㈢▼搴忛泦淇℃伅鐨勮緟鍔╃被銆 + /// + public static class AssemblyInfoHelper + { + /// + /// 浠庢鍦ㄦ墽琛岀殑绋嬪簭闆嗘绱㈠悇绉嶅睘鎬э紝骞惰繑鍥炰竴涓 AssemblyInfo 瀵硅薄銆 + /// + /// 鍖呭惈绋嬪簭闆嗗睘鎬х殑 AssemblyInfo 瀵硅薄銆 + public static AssemblyInfo GetAssemblyInfo() + { + // 鑾峰彇姝e湪鎵ц鐨勭▼搴忛泦 + Assembly assembly = Assembly.GetExecutingAssembly(); + + // 鍒涘缓骞跺~鍏 AssemblyInfo 瀵硅薄鐨勭浉鍏冲睘鎬 + var assemblyInfo = new AssemblyInfo + { + Version = assembly.GetName().Version.ToString(), + FileVersion = assembly.GetCustomAttributes().FirstOrDefault()?.Version ?? "", + AssemblyVersion = assembly.GetCustomAttributes().FirstOrDefault()?.Version ?? "", + InformationalVersion = assembly.GetCustomAttributes().FirstOrDefault()?.InformationalVersion ?? "", + //Company = assembly.GetCustomAttributes().FirstOrDefault()?.Company ?? "", + //Product = assembly.GetCustomAttributes().FirstOrDefault()?.Product ?? "", + Copyright = assembly.GetCustomAttributes().FirstOrDefault()?.Copyright ?? "", + Description = assembly.GetCustomAttributes().FirstOrDefault()?.Description ?? "" + }; + + return assemblyInfo; + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/DateTimeExtensions.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/DateTimeExtensions.cs new file mode 100644 index 0000000..deb92e9 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/DateTimeExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace HuanMeng.DotNetCore.Utility +{ + /// + /// 鏃堕棿鎵╁睍 + /// + public static class DateTimeExtensions + { + /// + /// 鑾峰彇鏃堕棿鎴筹紝绉 + /// + /// + /// + public static long ToUnixTimestamp(this DateTime dateTime) + { + return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalSeconds; + } + + /// + /// 鑾峰彇鏄椂闂存埑锛屾绉 + /// + /// + /// + public static long ToUnixTimestampMilliseconds(this DateTime dateTime) + { + return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalMilliseconds; + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs new file mode 100644 index 0000000..3904fb9 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/HttpContextExtensions.cs @@ -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 + { + /// + /// 鑾峰彇IP鍦板潃 + /// + /// + /// + public static string GetClientIpAddress(this HttpContext context) + { + // 灏濊瘯浠嶺-Forwarded-For澶撮儴涓幏鍙朓P鍦板潃 + var forwardedFor = context.Request.Headers["X-Forwarded-For"].FirstOrDefault(); + if (!string.IsNullOrEmpty(forwardedFor)) + { + // 澶勭悊鍙兘鐨勫涓狪P鍦板潃锛岄氬父绗竴涓槸瀹㈡埛绔殑鐪熷疄IP + var ipAddresses = forwardedFor.Split(','); + if (ipAddresses.Length > 0) + { + return ipAddresses[0].Trim(); + } + } + + // 濡傛灉X-Forwarded-For澶撮儴涓嶅瓨鍦紝浣跨敤RemoteIpAddress + return context.Connection.RemoteIpAddress?.ToString(); + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/MD5Encryption.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/MD5Encryption.cs new file mode 100644 index 0000000..496aabd --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/MD5Encryption.cs @@ -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 +{ + /// + /// + /// + public static class MD5Encryption + { + /// + /// md5鍔犲瘑 + /// + /// + /// + 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(); + } + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/PhoneNumberValidator.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/PhoneNumberValidator.cs new file mode 100644 index 0000000..e5b3289 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/Utility/PhoneNumberValidator.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace HuanMeng.DotNetCore.Utility; +/// +/// 鎵嬫満鍙 +/// +public class PhoneNumberValidator +{ + // 姝e垯琛ㄨ揪寮忕敤浜庡尮閰嶆墜鏈哄彿鐮併傚彲浠ユ牴鎹渶瑕佽皟鏁翠互閫傚簲涓嶅悓鐨勫浗瀹舵垨鍦板尯銆 + 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); + } +} + diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/WeChat/MiniProgram.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/WeChat/MiniProgram.cs new file mode 100644 index 0000000..a3b5328 --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/WeChat/MiniProgram.cs @@ -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 +{ + /// + /// 灏忕▼搴忓府鍔╃被 + /// + /// + /// + /// + /// + 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 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 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> GetCurlData(string url) + { + using HttpClient client = _httpClientFactory.CreateClient(); + var response = await client.GetStringAsync(url); + return JsonConvert.DeserializeObject>(response); + } + + // Helper method for POST requests + private async Task> 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>(result); + } + + // Helper method for HTTP POST data + private async Task 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(json); + } + return null; + } + + private void SetConfig(AccessToken access) + { + var outTime = new TimeSpan(0, 0, 7000); + database.StringSet(key, JsonConvert.SerializeObject(access), outTime); + } + + } + + /// + /// + /// + 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() + { + + } + } +} diff --git a/src/CloudGaming/1-utile/HuanMeng.DotNetCore/WeChat/WXBizDataCrypt.cs b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/WeChat/WXBizDataCrypt.cs new file mode 100644 index 0000000..55fde7b --- /dev/null +++ b/src/CloudGaming/1-utile/HuanMeng.DotNetCore/WeChat/WXBizDataCrypt.cs @@ -0,0 +1,122 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +using Newtonsoft.Json.Linq; + +namespace HuanMeng.DotNetCore.WeChat +{ + + /// + /// + /// + public class WXBizDataCrypt + { + private string appId; + private string sessionKey; + + /// + /// 鏋勯犲嚱鏁 + /// + /// 灏忕▼搴忕殑 appId + /// 鐢ㄦ埛鍦ㄥ皬绋嬪簭鐧诲綍鍚庤幏鍙栫殑浼氳瘽瀵嗛挜 + public WXBizDataCrypt(string appId, string sessionKey) + { + this.appId = appId; + this.sessionKey = sessionKey; + } + + /// + /// 妫楠屾暟鎹殑鐪熷疄鎬э紝骞朵笖鑾峰彇瑙e瘑鍚庣殑鏄庢枃 + /// + /// 鍔犲瘑鐨勭敤鎴锋暟鎹 + /// 涓庣敤鎴锋暟鎹竴鍚岃繑鍥炵殑鍒濆鍚戦噺 + /// 瑙e瘑鍚庣殑鍘熸枃 + /// 鎴愬姛0锛屽け璐ヨ繑鍥炲搴旂殑閿欒鐮 + 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)) + { + // 瑙e瘑鍚庣殑鏄庢枃 + 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 + { + /// + /// + /// + public string EncryptedData; + /// + /// + /// + public string Iv; + + + public int UserId { get; set; } + } + +} diff --git a/src/CloudGaming/CloudGaming.Model/CloudGaming.Model.csproj b/src/CloudGaming/CloudGaming.Model/CloudGaming.Model.csproj new file mode 100644 index 0000000..7c5c965 --- /dev/null +++ b/src/CloudGaming/CloudGaming.Model/CloudGaming.Model.csproj @@ -0,0 +1,20 @@ +锘 + + + net8.0 + enable + enable + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/src/CloudGaming/CloudGaming.Model/CodeTemplates/EFCore/DbContext.t4 b/src/CloudGaming/CloudGaming.Model/CodeTemplates/EFCore/DbContext.t4 new file mode 100644 index 0000000..1351902 --- /dev/null +++ b/src/CloudGaming/CloudGaming.Model/CodeTemplates/EFCore/DbContext.t4 @@ -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(); + var annotationCodeGenerator = services.GetRequiredService(); + var code = services.GetRequiredService(); + + var usings = new List + { + "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>( + <#= 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); +#> diff --git a/src/CloudGaming/CloudGaming.Model/CodeTemplates/EFCore/EntityType.t4 b/src/CloudGaming/CloudGaming.Model/CodeTemplates/EFCore/EntityType.t4 new file mode 100644 index 0000000..6c6c708 --- /dev/null +++ b/src/CloudGaming/CloudGaming.Model/CodeTemplates/EFCore/EntityType.t4 @@ -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(); + var code = services.GetRequiredService(); + + var usings = new List + { + "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())) + { +#> +/// +/// <#= code.XmlComment(EntityType.GetComment()) #> +/// +<# + } + + 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())) + { +#> + /// + /// <#= code.XmlComment(property.GetComment(), indent: 1) #> + /// +<# + } + + 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); +#> diff --git a/src/CloudGaming/CloudGaming.sln b/src/CloudGaming/CloudGaming.sln new file mode 100644 index 0000000..a3017d8 --- /dev/null +++ b/src/CloudGaming/CloudGaming.sln @@ -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 diff --git a/src/CloudGaming/CloudGaming/CloudGaming.csproj b/src/CloudGaming/CloudGaming/CloudGaming.csproj new file mode 100644 index 0000000..c33cc50 --- /dev/null +++ b/src/CloudGaming/CloudGaming/CloudGaming.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + 60edfd76-b8fa-477a-b105-6e6a7da42295 + Linux + + + + + + + + + diff --git a/src/CloudGaming/CloudGaming/CloudGaming.http b/src/CloudGaming/CloudGaming/CloudGaming.http new file mode 100644 index 0000000..badccdd --- /dev/null +++ b/src/CloudGaming/CloudGaming/CloudGaming.http @@ -0,0 +1,6 @@ +@CloudGaming_HostAddress = http://localhost:5171 + +GET {{CloudGaming_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/src/CloudGaming/CloudGaming/Controllers/WeatherForecastController.cs b/src/CloudGaming/CloudGaming/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..8d4c956 --- /dev/null +++ b/src/CloudGaming/CloudGaming/Controllers/WeatherForecastController.cs @@ -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 _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/src/CloudGaming/CloudGaming/Dockerfile b/src/CloudGaming/CloudGaming/Dockerfile new file mode 100644 index 0000000..b8af673 --- /dev/null +++ b/src/CloudGaming/CloudGaming/Dockerfile @@ -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"] \ No newline at end of file diff --git a/src/CloudGaming/CloudGaming/Program.cs b/src/CloudGaming/CloudGaming/Program.cs new file mode 100644 index 0000000..ff1441e --- /dev/null +++ b/src/CloudGaming/CloudGaming/Program.cs @@ -0,0 +1,25 @@ +锘縱ar 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(); diff --git a/src/CloudGaming/CloudGaming/Properties/launchSettings.json b/src/CloudGaming/CloudGaming/Properties/launchSettings.json new file mode 100644 index 0000000..06e2f4f --- /dev/null +++ b/src/CloudGaming/CloudGaming/Properties/launchSettings.json @@ -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 + } + } +} \ No newline at end of file diff --git a/src/CloudGaming/CloudGaming/WeatherForecast.cs b/src/CloudGaming/CloudGaming/WeatherForecast.cs new file mode 100644 index 0000000..ce39da2 --- /dev/null +++ b/src/CloudGaming/CloudGaming/WeatherForecast.cs @@ -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; } + } +} diff --git a/src/CloudGaming/CloudGaming/appsettings.Development.json b/src/CloudGaming/CloudGaming/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/CloudGaming/CloudGaming/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/CloudGaming/CloudGaming/appsettings.json b/src/CloudGaming/CloudGaming/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/src/CloudGaming/CloudGaming/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}