diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d420007 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,321 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# C# files +[*.cs] + + +# Default severity for analyzer diagnostics with category 'Interoperability' +dotnet_analyzer_diagnostic.category-Interoperability.severity = none + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = tab +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Code Actions #### + +# Type members +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_throwing_properties + +# Symbol search +dotnet_search_reference_assemblies = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +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 + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_prefer_system_hash_code = true +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 +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 + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = all + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false:silent +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent + +# Pattern matching preferences +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 = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true:suggestion + + +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent + +# Expression-level preferences +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = outside_namespace:silent + +# New line preferences +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# Formatting Rules #### + +# New line preferences +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 + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = no_change +csharp_indent_switch_labels = true + +# Space preferences +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 + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +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 + +# Symbol specifications +dotnet_naming_rule.private_or_internal_field_should_be__开头.severity = suggestion +dotnet_naming_rule.private_or_internal_field_should_be__开头.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be__开头.style = _开头 + +dotnet_naming_rule.private_or_internal_field_should_be__开头.severity = none +dotnet_naming_rule.private_or_internal_field_should_be__开头.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be__开头.style = _开头 +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.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.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 = + +# Naming styles +dotnet_naming_style._开头.required_prefix = _ +dotnet_naming_style._开头.required_suffix = +dotnet_naming_style._开头.word_separator = +dotnet_naming_style._开头.capitalization = camel_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 + +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_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:silent +dotnet_style_prefer_inferred_anonymous_type_member_names = true:silent +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion + + +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf + +# IDE0305: Simplify collection initialization +# dotnet_style_prefer_collection_expression = when_types_exactly_match +# dotnet_style_prefer_collection_expression = when_types_loosely_match + +dotnet_diagnostic.IDE0305.severity = silent + +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent + +# IDE1006: Naming Styles +dotnet_diagnostic.IDE1006.severity = none + +# IDE0059: Unnecessary assignment of a value +dotnet_diagnostic.IDE0059.severity = silent + +# CS0168: Variable is declared but never used +dotnet_diagnostic.CS0168.severity = silent + +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_namespace_match_folder = true:suggestion \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5e2a23b..b8452d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,365 @@ -.vs -.svn -Bin -Bin.Oem -NotUse -obj -*.psd -packages -.user -bin -*.rar -*.zip -/Update/Update/*.user +## 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 -IISProfile.pubxml -*/**/logs +*.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 + +!Store.User \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..36df23c --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,23 @@ + + + + + net472 + $(DefaultTargetFramework) + 12.0 + true + false + true + annotations + CS1591 + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/KJD_WebService.sln b/KJD_WebService.sln index 84af2e3..a8a48da 100644 --- a/KJD_WebService.sln +++ b/KJD_WebService.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.9.12105.275 stable +VisualStudioVersion = 18.9.12105.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KJD_WebService", "KJD_WebService\KJD_WebService.csproj", "{A6C03FE1-142A-4C97-99A9-67FD915836B6}" EndProject @@ -9,6 +9,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".test", ".test", "{AA240C1D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServiceTest", "test\WebServiceTest\WebServiceTest.csproj", "{348EB476-1813-419E-910F-0D27E58B3D19}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{15CB4E89-94B5-4851-A58F-0A56FBCD9527}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + Directory.Build.props = Directory.Build.props + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.User", "Store.User\Store.User.csproj", "{20BA615B-6EAC-47EA-87FE-DD740D6F3AD7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Store.Framework", "Store.Framework\Store.Framework.csproj", "{CD6FB773-0431-4FAE-9E30-65953118FEBF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -23,6 +34,14 @@ Global {348EB476-1813-419E-910F-0D27E58B3D19}.Debug|Any CPU.Build.0 = Debug|Any CPU {348EB476-1813-419E-910F-0D27E58B3D19}.Release|Any CPU.ActiveCfg = Release|Any CPU {348EB476-1813-419E-910F-0D27E58B3D19}.Release|Any CPU.Build.0 = Release|Any CPU + {20BA615B-6EAC-47EA-87FE-DD740D6F3AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {20BA615B-6EAC-47EA-87FE-DD740D6F3AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {20BA615B-6EAC-47EA-87FE-DD740D6F3AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {20BA615B-6EAC-47EA-87FE-DD740D6F3AD7}.Release|Any CPU.Build.0 = Release|Any CPU + {CD6FB773-0431-4FAE-9E30-65953118FEBF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD6FB773-0431-4FAE-9E30-65953118FEBF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD6FB773-0431-4FAE-9E30-65953118FEBF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD6FB773-0431-4FAE-9E30-65953118FEBF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/KJD_WebService/BLL/WXXD/WXOemManage.cs b/KJD_WebService/BLL/WXXD/WXOemManage.cs index 89f29ab..df19a4c 100644 --- a/KJD_WebService/BLL/WXXD/WXOemManage.cs +++ b/KJD_WebService/BLL/WXXD/WXOemManage.cs @@ -17,6 +17,10 @@ namespace KJD_Web.BLL.WXXD { public class WXOemManage { + /// + /// 店铺续费手续费 15 + /// + const int ShopRenewFee = 15; /// /// 手续费 /// @@ -603,7 +607,7 @@ namespace KJD_Web.BLL.WXXD WHERE A.oem_id = '{MysqlUtils.ReplaceSqlSpecial(oem_id)}'"; var userDt = mySql.ExecuteDataTable(sql); if (!string.IsNullOrEmpty(mySql.Message) || userDt == null || userDt.Rows.Count == 0) - return 0; + return 0;//找不到用户或数据库错误返回 List userLst = userDt.Rows.Cast() .Select(r => r["user_name"].ParseToString()) @@ -611,80 +615,130 @@ namespace KJD_Web.BLL.WXXD .Distinct() .ToList(); if (userLst.Count == 0) - return 0; + return 0;//用户列表空 返回 + + decimal total_amount = 0m; // 总金额(元) + decimal rebate_amount = 0m; // 正数部分(累计返利) + string now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + + + // 2. 该oem所有账户的未结算流水,按id升序 sql = "SELECT * FROM `wxxd`.`user_payment_record` WHERE user_name IN ('" + string.Join("','", userLst) + "') AND type IN (20,23,26,16,31,34) AND settlement=0 AND is_deleted=0 ORDER BY id ASC"; var flowDt = mySql.ExecuteDataTable(sql); - if (!string.IsNullOrEmpty(mySql.Message) || flowDt == null || flowDt.Rows.Count == 0) + if (!string.IsNullOrEmpty(mySql.Message)) return 0; - decimal total_amount = 0m; // 总金额(元) - decimal rebate_amount = 0m; // 正数部分(累计返利) - HashSet deductedOrderSet = new HashSet(); // type=23 已扣减的订单 - HashSet giftsOrderSet = new HashSet(); // type=34 礼物已扣减的订单 - List settleIdLst = new List(); // 参与结算的流水id - foreach (DataRow row in flowDt.Rows) + + + if (flowDt != null && flowDt.Rows.Count > 0) { - int type = row["type"].ParseToInt(); - decimal amount = row["amount"].ParseToDecimal(); - decimal oem_amount = row["oem_amount"].ParseToDecimal(); - long flowId = row["id"].ParseToLong(); + HashSet deductedOrderSet = new HashSet(); // type=23 已扣减的订单 + HashSet giftsOrderSet = new HashSet(); // type=34 礼物已扣减的订单 + List settleIdLst = new List(); // 参与结算的流水id - decimal itemAmount = 0m; - if (type == 20) - itemAmount = amount - 2.2m; - else if (type == 26) - itemAmount = amount - 2.5m; - else if (type == 16) - itemAmount = oem_amount; - else if (type == 31) - itemAmount = oem_amount; - else if (type == 23) + foreach (DataRow row in flowDt.Rows) { - // 同一个订单只能减少一次 - string orderId = row["order_id"].ParseToString(); - if (orderId.IsNullOrEmpty() || deductedOrderSet.Contains(orderId)) - continue; - deductedOrderSet.Add(orderId); - itemAmount = -(amount - 2.2m); - } - else if (type == 34) - { - // 同一个订单只能减少一次 - string orderId = row["order_id"].ParseToString(); - if (orderId.IsNullOrEmpty() || giftsOrderSet.Contains(orderId)) - continue; - giftsOrderSet.Add(orderId); - itemAmount = -oem_amount; - } - else - { - continue; // 其他类型不计入 + int type = row["type"].ParseToInt(); + decimal amount = row["amount"].ParseToDecimal(); + decimal oem_amount = row["oem_amount"].ParseToDecimal(); + long flowId = row["id"].ParseToLong(); + + decimal itemAmount = 0m; + if (type == 20) + itemAmount = amount - 2.2m; + else if (type == 26) + itemAmount = amount - 2.5m; + else if (type == 16) + itemAmount = oem_amount; + else if (type == 31) + itemAmount = oem_amount; + else if (type == 23) + { + // 同一个订单只能减少一次 + string orderId = row["order_id"].ParseToString(); + if (orderId.IsNullOrEmpty() || deductedOrderSet.Contains(orderId)) + continue; + deductedOrderSet.Add(orderId); + itemAmount = -(amount - 2.2m); + } + else if (type == 34) + { + // 同一个订单只能减少一次 + string orderId = row["order_id"].ParseToString(); + if (orderId.IsNullOrEmpty() || giftsOrderSet.Contains(orderId)) + continue; + giftsOrderSet.Add(orderId); + itemAmount = -oem_amount; + } + else + { + continue; // 其他类型不计入 + } + + total_amount += itemAmount; + rebate_amount += itemAmount; + + settleIdLst.Add(flowId); } - total_amount += itemAmount; - rebate_amount += itemAmount; + if (settleIdLst.Count == 0) + return 0; - settleIdLst.Add(flowId); + //更新用户流水记录表结算 + sql = "UPDATE wxxd.user_payment_record SET settlement=1,settlement_time='" + now + "' WHERE id IN (" + string.Join(",", settleIdLst) + ")"; + var flowRst = mySql.ExecuteAffectedRows(sql); + if (!string.IsNullOrEmpty(mySql.Message) || flowRst <= 0) + throw new Exception("标记流水结算失败"); } - if (settleIdLst.Count == 0) + + + + + + //读取用户店铺续费 + sql = "SELECT id,delta_points FROM `wxxd`.`user_points_record` WHERE user_name IN ('" + string.Join("','", userLst) + "') AND option_type ='续费' AND settlement=0 ORDER BY id ASC"; + var userPointsDt = mySql.ExecuteDataTable(sql); + if (!string.IsNullOrEmpty(mySql.Message)) return 0; + if (userPointsDt != null && userPointsDt.Rows.Count > 0) //更新用户流水记录表结算 + { + int count = userPointsDt.Rows.Count; + //结算商铺续费 + List settleIdLst = new List(); // 参与结算的流水id + + foreach (DataRow row in userPointsDt.Rows) + { + long flowId = row["id"].ParseToLong(); + decimal points = row["delta_points"].ParseToDecimal(); + + decimal itemAmount = points * 1;//比例1:1 + itemAmount = itemAmount - ShopRenewFee; + total_amount += itemAmount; + rebate_amount += itemAmount; + settleIdLst.Add(flowId); + } + + sql = "UPDATE wxxd.user_points_record SET settlement=1,settlement_time='" + now + "' WHERE id IN (" + string.Join(",", settleIdLst) + ")"; + var flowRst = mySql.ExecuteAffectedRows(sql); + if (!string.IsNullOrEmpty(mySql.Message) || flowRst <= 0) + throw new Exception("商铺流水结算失败"); + } + + long total_fen = (long)Math.Round(total_amount * 100m); long rebate_fen = (long)Math.Round(rebate_amount * 100m); - string now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); - // 3. 写oem金额 + 更新流水状态(同一事务) - sql = "UPDATE wxxd.oem SET available_balance=available_balance+" + total_fen + - ", total_rebate_amount=total_rebate_amount+" + rebate_fen + - " WHERE oem_id='" + oem_id + "'"; - var oemRst = mySql.ExecuteAffectedRows(sql); - if (!string.IsNullOrEmpty(mySql.Message) || oemRst <= 0) - throw new Exception("结算流水到账户失败"); - sql = "UPDATE wxxd.user_payment_record SET settlement=1,settlement_time='" + now + "' WHERE id IN (" + string.Join(",", settleIdLst) + ")"; - var flowRst = mySql.ExecuteAffectedRows(sql); - - if (!string.IsNullOrEmpty(mySql.Message) || flowRst <= 0) - throw new Exception("标记流水结算失败"); + if (total_fen > 0 || rebate_fen > 0) + { + //写oem金额 + 更新流水状态 + sql = "UPDATE wxxd.oem SET available_balance=available_balance+" + total_fen + + ", total_rebate_amount=total_rebate_amount+" + rebate_fen + + " WHERE oem_id='" + oem_id + "'"; + var oemRst = mySql.ExecuteAffectedRows(sql); + if (!string.IsNullOrEmpty(mySql.Message) || oemRst <= 0) + throw new Exception("结算流水到账户失败"); + } return total_fen; } diff --git a/KJD_WebService/BLL/WXXD/WXUserManage.cs b/KJD_WebService/BLL/WXXD/WXUserManage.cs index 1fc072a..24db4bb 100644 --- a/KJD_WebService/BLL/WXXD/WXUserManage.cs +++ b/KJD_WebService/BLL/WXXD/WXUserManage.cs @@ -902,9 +902,9 @@ namespace KJD_Web.BLL.WXXD { return "{\"code\":500,\"msg\":\"操作对象不能为空\"}"; } - if (delta_points <= 0 && option_type == "续费") + if (option_type == "续费") { - return "{\"code\":500,\"msg\":\"续费金额错误,需大于0\"}"; + return "{\"code\":500,\"msg\":\"续费金额错误\"}"; } if (delta_points == 0) { diff --git a/KJD_WebService/Controllers/AsmxApiController.cs b/KJD_WebService/Controllers/AsmxApiController.cs index e6aea10..ecd8520 100644 --- a/KJD_WebService/Controllers/AsmxApiController.cs +++ b/KJD_WebService/Controllers/AsmxApiController.cs @@ -1,4 +1,6 @@ using KJD_Web.BLL.WXXD; +using KJD_Web.BLL.WXXD.Requests; +using KJD_Web.Utils; using System; using System.Collections.Generic; using System.IO; @@ -26,6 +28,30 @@ namespace KJD_Web.Controllers // return base.ExecuteAsync(controllerContext, cancellationToken); //} + protected bool ValidateSign(SignRequestBase request, bool isUserLogin = false) + { + if (request == null || string.IsNullOrEmpty(request.time) || string.IsNullOrEmpty(request.code)) + { + return false; + } + + int time; + if (!int.TryParse(request.time, out time)) + { + return false; + } + if (request.code != EncrypUtil.Encryption(time)) + { + return false; + } + if (!isUserLogin && string.IsNullOrWhiteSpace(request.user_name)) + { + return false; + } + + return true; + } + protected string GetRequestJsonString() { //Context.Response.ContentType = "text/html;charset=utf-8"; diff --git a/KJD_WebService/Controllers/WX/WXUserController.cs b/KJD_WebService/Controllers/WX/WXUserController.cs index e830efb..2988125 100644 --- a/KJD_WebService/Controllers/WX/WXUserController.cs +++ b/KJD_WebService/Controllers/WX/WXUserController.cs @@ -17,7 +17,7 @@ namespace KJD_Web.Controllers { [RoutePrefix("wxxd_user")] [Route("{action}")] - public class WXUserController : AsmxApiController + public partial class WXUserController : AsmxApiController { //GetE57Authorization diff --git a/KJD_WebService/Controllers/WX/WXUser_N_Controller.cs b/KJD_WebService/Controllers/WX/WXUser_N_Controller.cs new file mode 100644 index 0000000..c6afe2b --- /dev/null +++ b/KJD_WebService/Controllers/WX/WXUser_N_Controller.cs @@ -0,0 +1,51 @@ +using KJD_Web.Model; +using KJD_Web.Requests.Users; +using KJD_Web.Services.Users; +using MAX.Core.Logger; +using System; +using System.Web.Http; + +namespace KJD_Web.Controllers +{ + public partial class WXUserController : AsmxApiController + { + private readonly UserService _userService; + private readonly ILoggerFacade _logger; + + public WXUserController(UserService userService, ILoggerFacade logger) + { + _userService = userService ?? throw new ArgumentNullException(nameof(userService)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// 用户续费商铺。 + /// + [HttpPost] + public IHttpActionResult shop_renew([FromBody] ShopRenewRequest request) + { + if (!ValidateSign(request)) + { + return Ok(ApiResult.Fail("校验失败", 999)); + } + try + { + var response = _userService.ShopRenew(request); + return Ok(ApiResult.Success(response, "续费成功")); + } + catch (ArgumentException ex) + { + return Ok(ApiResult.Fail(ex.Message, ResultCode.ValidationError)); + } + catch (InvalidOperationException ex) + { + return Ok(ApiResult.Fail(ex.Message, ResultCode.BusinessError)); + } + catch (Exception ex) + { + _logger.Error(ex, "商铺续费失败, UserName=" + (request == null ? null : request.UserName) + ", ShopId=" + (request == null ? null : request.ShopId)); + return Ok(ApiResult.Fail("续费失败,请稍后重试", ResultCode.ServerError)); + } + } + } +} diff --git a/KJD_WebService/Entitys/ShopEntity.cs b/KJD_WebService/Entitys/ShopEntity.cs new file mode 100644 index 0000000..b5a4abd --- /dev/null +++ b/KJD_WebService/Entitys/ShopEntity.cs @@ -0,0 +1,144 @@ +using FreeSql.DataAnnotations; +using System; + +namespace YourNamespace +{ + /// + ///shop 表 + /// + [Table(Name = "wxxd.shop")] + public class ShopEntity + { + /// + /// 表id + /// + [Column(Name = "id", IsPrimary = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// login_name + /// + [Column(Name = "login_name")] + public string LoginName { get; set; } + + /// + /// login_pass + /// + [Column(Name = "login_pass")] + public string LoginPass { get; set; } + + /// + /// cookie + /// + [Column(Name = "cookie")] + public string Cookie { get; set; } + + /// + /// cookie2 + /// + [Column(Name = "cookie2")] + public string Cookie2 { get; set; } + + /// + /// anti_contents + /// + [Column(Name = "anti_contents")] + public string AntiContents { get; set; } + + /// + /// openid + /// + [Column(Name = "openid")] + public string Openid { get; set; } + + /// + /// shop_id + /// + [Column(Name = "shop_id")] + public string ShopId { get; set; } + + /// + /// shop_account + /// + [Column(Name = "shop_account")] + public string ShopAccount { get; set; } + + /// + /// sub_user_name + /// + [Column(Name = "sub_user_name")] + public string SubUserName { get; set; } + + /// + /// user_name + /// + [Column(Name = "user_name")] + public string UserName { get; set; } + + /// + /// 店铺分组名称(非子账号分组) + /// + [Column(Name = "group_name")] + public string GroupName { get; set; } + + /// + /// add_time + /// + [Column(Name = "add_time")] + public DateTime? AddTime { get; set; } + + /// + /// mdy_time + /// + [Column(Name = "mdy_time")] + public DateTime? MdyTime { get; set; } + + /// + /// 店铺所属公司 + /// + [Column(Name = "company_name")] + public string CompanyName { get; set; } + + /// + /// 备注 + /// + [Column(Name = "notes")] + public string Notes { get; set; } + + /// + /// login_status + /// + [Column(Name = "login_status")] + public string LoginStatus { get; set; } + + /// + /// 店铺每月商品上传限额剩余 + /// + [Column(Name = "add_product_remaining")] + public int AddProductRemaining { get; set; } + + /// + /// 上传限额所处月份 + /// + [Column(Name = "add_product_month")] + public byte AddProductMonth { get; set; } + + /// + /// 分配的二级超管的user_name + /// + [Column(Name = "operate_sub_sa_name")] + public string OperateSubSaName { get; set; } + + /// + /// 分配二级超管的时间 + /// + [Column(Name = "add_sub_sa_time")] + public DateTime? AddSubSaTime { get; set; } + + /// + /// login_status2 + /// + [Column(Name = "login_status2")] + public string LoginStatus2 { get; set; } + } +} \ No newline at end of file diff --git a/KJD_WebService/Entitys/UserEntity.cs b/KJD_WebService/Entitys/UserEntity.cs new file mode 100644 index 0000000..e38387b --- /dev/null +++ b/KJD_WebService/Entitys/UserEntity.cs @@ -0,0 +1,156 @@ +using FreeSql.DataAnnotations; +using System; + +namespace KJD_Web.Entitys +{ + /// + /// 用户表 + /// + [Table(Name = "wxxd.user")] + public class UserEntity + { + /// + /// ID + /// + [Column(Name = "id", IsPrimary = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// 用户昵称 + /// + [Column(Name = "nick_name")] + public string NickName { get; set; } + + /// + /// 用户名称(登录账户) + /// + [Column(Name = "user_name")] + public string UserName { get; set; } + + /// + /// 登录密码 + /// + [Column(Name = "password")] + public string Password { get; set; } + + /// + /// card_id + /// + [Column(Name = "card_id")] + public string CardId { get; set; } + + /// + /// 用户分组 3主号 4客服 5加盟 8登录 9采购 10加盟 11上货 + /// + [Column(Name = "group_id")] + public byte GroupId { get; set; } + + /// + /// 父账户用户名 + /// + [Column(Name = "parent_user_name")] + public string ParentUserName { get; set; } + + /// + /// oem编号,仅oem用户有值 + /// + [Column(Name = "oem_id")] + public string OemId { get; set; } + + /// + /// 手机号 + /// + [Column(Name = "mobile")] + public string Mobile { get; set; } + + /// + /// 添加时间 + /// + [Column(Name = "add_time")] + public DateTime? AddTime { get; set; } + + /// + /// 修改时间 + /// + [Column(Name = "mdy_time")] + public DateTime? MdyTime { get; set; } + + /// + /// 公司名称 + /// + [Column(Name = "company_name")] + public string CompanyName { get; set; } + + /// + /// 备注 + /// + [Column(Name = "notes")] + public string Notes { get; set; } + + /// + /// 超管等级 0 普通用户 1 超级管理员 + /// + [Column(Name = "sa_level")] + public short? SaLevel { get; set; } + + /// + /// 账户权限 + /// + [Column(Name = "authority")] + public string Authority { get; set; } + + /// + /// 子账号被禁止展示的功能页面 + /// + [Column(Name = "forbidden_page")] + public string ForbiddenPage { get; set; } + + /// + /// 账户积分,1积分=1元 + /// + [Column(Name = "points")] + public decimal Points { get; set; } + + /// + /// 积分历史累计充值金额 + /// + [Column(Name = "recharge_points_total")] + public decimal? RechargePointsTotal { get; set; } + + /// + /// 店铺每续费一个月需要的积分,单位元 + /// + [Column(Name = "shop_points_per_month")] + public int ShopPointsPerMonth { get; set; } + + /// + /// 可用余额 + /// + [Column(Name = "balance")] + public decimal? Balance { get; set; } + + /// + /// 累计充值金额 + /// + [Column(Name = "recharge_amount_total")] + public decimal? RechargeAmountTotal { get; set; } + + /// + /// 每单中转仓快递费用,单位元,两位小数 + /// + [Column(Name = "postage_per_order")] + public decimal? PostagePerOrder { get; set; } + + /// + /// 每单中转仓退货快递费用,单位元,两位小数 + /// + [Column(Name = "refund_postage_per_order")] + public decimal? RefundPostagePerOrder { get; set; } + + /// + /// 礼物每单额外收取费用,单位元,两位小数 + /// + [Column(Name = "gift_oem_amount_per_order")] + public decimal? GiftOemAmountPerOrder { get; set; } + } +} \ No newline at end of file diff --git a/KJD_WebService/Entitys/UserPointsRecordEntity.cs b/KJD_WebService/Entitys/UserPointsRecordEntity.cs new file mode 100644 index 0000000..4e226fa --- /dev/null +++ b/KJD_WebService/Entitys/UserPointsRecordEntity.cs @@ -0,0 +1,109 @@ +using FreeSql.DataAnnotations; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace KJD_Web.Entitys +{ + + [Table(Name = "wxxd.user_points_record")] + public class UserPointsRecordEntity + { + /// + /// 主键(自增) + /// + [Column(Name = "id", IsPrimary = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// 用户名(NOT NULL) + /// + [Column(Name = "user_name")] + public string UserName { get; set; } + + /// + /// 用户昵称(NOT NULL) + /// + [Column(Name = "user_nick")] + public string UserNick { get; set; } + + /// + /// 操作记录金额,单位元,最小1元(NOT NULL) + /// + [Column(Name = "delta_points")] + public decimal DeltaPoints { get; set; } + + /// + /// 充值或消费后的当前金额(允许 NULL) + /// + [Column(Name = "new_points")] + public decimal? NewPoints { get; set; } + + /// + /// 操作类型 消费 充值 转入 转出(NOT NULL) + /// + [Column(Name = "option_type")] + public string OptionType { get; set; } + + /// + /// 操作者类型 管理员 用户(NOT NULL) + /// + [Column(Name = "operator_type")] + public string OperatorType { get; set; } + + /// + /// 实际操作者用户名(NOT NULL) + /// + [Column(Name = "operator_name")] + public string OperatorName { get; set; } + + /// + /// 消费店铺编号(消费才有值,充值为空,允许 NULL) + /// + [Column(Name = "shop_id")] + public string? ShopId { get; set; } + + /// + /// 消费店铺名称(消费才有值,充值为空,允许 NULL) + /// + [Column(Name = "shop_account")] + public string? ShopAccount { get; set; } + + /// + /// 备注(允许 NULL) + /// + [Column(Name = "remark")] + public string? Remark { get; set; } + + /// + /// (点数转出时的)对方账户(允许 NULL) + /// + [Column(Name = "counterparty_account_name")] + public string? CounterpartyAccountName { get; set; } + + /// + /// 支付宝订单的外部订单号(允许 NULL) + /// + [Column(Name = "out_trade_no")] + public string? OutTradeNo { get; set; } + + /// + /// 添加时间(NOT NULL,默认 CURRENT_TIMESTAMP) + /// + [Column(Name = "add_time")] + public DateTime AddTime { get; set; } + + /// + /// OEM是否结算 0未结算 1已结算 + /// + [Column(Name = "settlement")] + public bool Settlement { get; set; } + /// + /// 结算时间 + /// + [Column(Name = "settlement_time")] + public DateTime? SettlementTime { get; set; } + } + +} \ No newline at end of file diff --git a/KJD_WebService/Entitys/UserPointsShopExpire.cs b/KJD_WebService/Entitys/UserPointsShopExpire.cs new file mode 100644 index 0000000..287abec --- /dev/null +++ b/KJD_WebService/Entitys/UserPointsShopExpire.cs @@ -0,0 +1,43 @@ +using FreeSql.DataAnnotations; +using System; + +namespace KJD_Web.Entitys +{ + /// + /// 用户积分商店过期 + /// + [Table(Name = "wxxd.user_points_shop_expire")] + public class UserPointsShopExpireEntity + { + /// + /// ID + /// + [Column(Name = "id", IsPrimary = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// 商店ID + /// + [Column(Name = "shop_id")] + public string ShopId { get; set; } + + /// + /// 过期时间 + /// + // 数据库列名末尾包含 U+200C,不能改成普通 expire_time。 + [Column(Name = "expire_time\u200c")] + public int ExpireTime { get; set; } + + /// + /// 添加时间 + /// + [Column(Name = "add_time")] + public DateTime AddTime { get; set; } + + /// + /// 修改时间 + /// + [Column(Name = "mdy_time")] + public DateTime MdyTime { get; set; } + } +} \ No newline at end of file diff --git a/KJD_WebService/Global.asax.cs b/KJD_WebService/Global.asax.cs index a0613f3..c744a28 100644 --- a/KJD_WebService/Global.asax.cs +++ b/KJD_WebService/Global.asax.cs @@ -1,6 +1,7 @@ using Autofac; using Autofac.Integration.WebApi; using FreeSql; +using KJD_Web.Repository; using KJD_Web.Utils; using MAX.Core.DependencyInjection; using MAX.Core.Logger; @@ -37,14 +38,37 @@ namespace KJD_Web var builder = new ContainerBuilder(); var appAssembly = Assembly.GetExecutingAssembly(); + var connection = ConfigurationManager.ConnectionStrings["ConnKJD"]; + if (connection == null || string.IsNullOrWhiteSpace(connection.ConnectionString)) + { + throw new ConfigurationErrorsException("未配置连接字符串:ConnKJD"); + } + // ImageCacheService 使用独立的 imagecache 数据库。 - builder.Register(_ => CreateImageCacheFreeSql()) - .As() - .SingleInstance(); + builder.Register(_ => + { + return new FreeSqlBuilder() + .UseConnectionString( + DataType.MySql, + connection.ConnectionString) + .Build(); + }) + .As() + .SingleInstance(); + + builder.RegisterGeneric(typeof(AutofacRepository<>)) + .As(typeof(IBaseRepository<>)) + .UsingConstructor(typeof(IFreeSql)) + .InstancePerRequest(); + // 注册 Web API 所有 Controller builder.RegisterApiControllers(appAssembly); builder.RegisterInstance(NLogger.Instance).As().SingleInstance(); + + + + #region 注册所有IOC接口 Type typeSingleton = typeof(ISingletonDependency); @@ -87,14 +111,13 @@ namespace KJD_Web { throw new ConfigurationErrorsException("未配置连接字符串:ConnKJD"); } - - var connectionBuilder = new MySqlConnectionStringBuilder(connection.ConnectionString) - { - Database = "imagecache" - }; + //var connectionBuilder = new MySqlConnectionStringBuilder(connection.ConnectionString) + //{ + // Database = "imagecache" + //}; return new FreeSqlBuilder() - .UseConnectionString(DataType.MySql, connectionBuilder.ConnectionString) + .UseConnectionString(DataType.MySql, connection.ConnectionString) .UseAutoSyncStructure(false) .Build(); } diff --git a/KJD_WebService/KJD_WebService.csproj b/KJD_WebService/KJD_WebService.csproj index 36f220f..caa9b92 100644 --- a/KJD_WebService/KJD_WebService.csproj +++ b/KJD_WebService/KJD_WebService.csproj @@ -26,18 +26,18 @@ - 12 - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - false + 12 + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + false - 12 + 12 true pdbonly true @@ -60,9 +60,15 @@ ..\packages\FreeSql.3.5.311\lib\net451\FreeSql.dll + + ..\packages\FreeSql.DbContext.3.5.311\lib\net45\FreeSql.DbContext.dll + ..\packages\FreeSql.Provider.MySql.3.5.311\lib\net452\FreeSql.Provider.MySql.dll + + ..\packages\FreeSql.Repository.3.5.311\lib\net45\FreeSql.Repository.dll + ..\packages\Google.Protobuf.3.19.4\lib\net45\Google.Protobuf.dll @@ -186,6 +192,7 @@ + @@ -211,6 +218,11 @@ + + + + + @@ -249,7 +261,11 @@ + + + + diff --git a/KJD_WebService/Model/ShopModel/ShopParam.cs b/KJD_WebService/Model/ShopModel/ShopParam.cs index 12ea3c1..cfcd62f 100644 --- a/KJD_WebService/Model/ShopModel/ShopParam.cs +++ b/KJD_WebService/Model/ShopModel/ShopParam.cs @@ -1,10 +1,12 @@ -using System; +using FreeSql.DataAnnotations; +using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace KJD_Web.Model.ShopModel { + [Table(Name = "wxxd.shop")] public class ShopParam { public int id { get; set; } diff --git a/KJD_WebService/Model/UserLoginResultParam.cs b/KJD_WebService/Model/UserLoginResultParam.cs index 8ac1038..83fe7a0 100644 --- a/KJD_WebService/Model/UserLoginResultParam.cs +++ b/KJD_WebService/Model/UserLoginResultParam.cs @@ -1,10 +1,12 @@ -using System; +using FreeSql.DataAnnotations; +using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace KJD_Web.Model { + [Table(Name = "wxxd.user")] public class UserLoginResultParam { public int id { get; set; } @@ -80,7 +82,7 @@ namespace KJD_Web.Model /// /// 用户积分点数 /// - public int points { get; set; } + public decimal points { get; set; } /// /// 单店铺续费1个月消耗的点数 /// diff --git a/KJD_WebService/NotFoundMessageFilter.cs b/KJD_WebService/NotFoundMessageFilter.cs index 6fc8afd..89ede5c 100644 --- a/KJD_WebService/NotFoundMessageFilter.cs +++ b/KJD_WebService/NotFoundMessageFilter.cs @@ -35,7 +35,10 @@ namespace KJD_Web HttpRequestMessage request, CancellationToken cancellationToken) { var response = await base.SendAsync(request, cancellationToken); - + var originalBody = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"[HTTP] {request.Method} {request.RequestUri}"); + Console.WriteLine($"[HTTP] Status: {(int)response.StatusCode} {response.StatusCode}"); + Console.WriteLine($"[HTTP] Body: {originalBody}"); if (response.StatusCode != HttpStatusCode.OK) { response.Content = new StringContent( diff --git a/KJD_WebService/Repository/AutofacRepository.cs b/KJD_WebService/Repository/AutofacRepository.cs new file mode 100644 index 0000000..6254a2b --- /dev/null +++ b/KJD_WebService/Repository/AutofacRepository.cs @@ -0,0 +1,17 @@ +using FreeSql; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace KJD_Web.Repository +{ + public class AutofacRepository : BaseRepository + where TEntity : class + { + public AutofacRepository(IFreeSql fsql) + : base(fsql) + { + } + } +} \ No newline at end of file diff --git a/KJD_WebService/Repository/UserPointsRecordRepository.cs b/KJD_WebService/Repository/UserPointsRecordRepository.cs new file mode 100644 index 0000000..f09341f --- /dev/null +++ b/KJD_WebService/Repository/UserPointsRecordRepository.cs @@ -0,0 +1,19 @@ +//using FreeSql; +//using KJD_Web.Entitys; +//using MAX.Core.DependencyInjection; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Web; + +//namespace KJD_Web.Repository +//{ + +// public class UserPointsRecordRepository : BaseRepository, IScopedDependency +// { +// public UserPointsRecordRepository(IFreeSql fsql) : base(fsql) +// { + +// } +// } +//} \ No newline at end of file diff --git a/KJD_WebService/Requests/Users/ShopRenewRequest.cs b/KJD_WebService/Requests/Users/ShopRenewRequest.cs new file mode 100644 index 0000000..194ace6 --- /dev/null +++ b/KJD_WebService/Requests/Users/ShopRenewRequest.cs @@ -0,0 +1,34 @@ +using KJD_Web.BLL.WXXD.Requests; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace KJD_Web.Requests.Users +{ + /// + /// 用户续费商铺 + /// + public class ShopRenewRequest : SignRequestBase + { + public string UserName + { + get { return user_name; } + set { user_name = value; } + } + /// + /// 店铺ID + /// + public string ShopId { get; set; } + + /// + /// 续费时长数值,例如 3;范围 1-120 个月 + /// + public int RenewDuration { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + } +} \ No newline at end of file diff --git a/KJD_WebService/Responses/Users/ShopRenewResponse.cs b/KJD_WebService/Responses/Users/ShopRenewResponse.cs new file mode 100644 index 0000000..ad16754 --- /dev/null +++ b/KJD_WebService/Responses/Users/ShopRenewResponse.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace KJD_Web.Dtos.Users +{ + public class ShopRenewResponse + { + public string ShopId { get; set; } + public int ExpireTime‌ { get; set; } + public int UserPoints { get; set; } + } +} \ No newline at end of file diff --git a/KJD_WebService/Services/Users/UserService.cs b/KJD_WebService/Services/Users/UserService.cs new file mode 100644 index 0000000..cd0418c --- /dev/null +++ b/KJD_WebService/Services/Users/UserService.cs @@ -0,0 +1,220 @@ +using FreeSql; +using KJD_Web.Dtos.Users; +using KJD_Web.Entitys; +using KJD_Web.Model; +using KJD_Web.Model.ShopModel; +using KJD_Web.Requests.Users; +using KJD_Web.Utils; +using MAX.Core.DependencyInjection; +using System; +using YourNamespace; + +namespace KJD_Web.Services.Users +{ + public class UserService : ITransientDependency + { + private const int MinShopPointsPerMonth = 30; + private const int UserNameMaxLength = 20; + private const int ShopIdMaxLength = 50; + private const int RemarkMaxLength = 255; + private const int MaxRenewDuration = 120; + + private readonly IFreeSql _freeSql; + + public UserService(IFreeSql freeSql) + { + _freeSql = freeSql ?? throw new ArgumentNullException(nameof(freeSql)); + } + + /// + /// 商铺续费 + /// + /// 续费请求 + public ShopRenewResponse ShopRenew(ShopRenewRequest request) + { + ShopRenewResponse response = new ShopRenewResponse(); + ValidateRequest(request); + + string userName = request.UserName.Trim(); + string shopId = request.ShopId.Trim(); + string remark = string.IsNullOrWhiteSpace(request.Remark) ? null : request.Remark.Trim(); + + _freeSql.Transaction(() => + { + UserEntity user = _freeSql.Select() + .Where(a => a.UserName == userName) + .ForUpdate() + .First(); + if (user == null) + { + throw new InvalidOperationException("不存在的用户"); + } + + ShopEntity shop = _freeSql.Select() + .Where(a => a.ShopId == shopId && a.UserName == user.UserName) + .ForUpdate() + .First(); + if (shop == null) + { + throw new InvalidOperationException("不存在的商铺"); + } + if (!string.Equals(shop.UserName, userName, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException("该商铺不属于当前用户"); + } + + int shopPointsPerMonth = Math.Max(MinShopPointsPerMonth, user.ShopPointsPerMonth); + int deltaPoints = shopPointsPerMonth * request.RenewDuration; + if (user.Points < deltaPoints) + { + throw new InvalidOperationException("余额不足"); + } + + int newPoints = (int)user.Points - deltaPoints; + DateTime now = DateTime.Now; + + UserPointsShopExpireEntity currentExpire = _freeSql.Select() + .Where(a => a.ShopId == shopId) + .ForUpdate() + .First(); + + DateTime expireBaseTime = now; + if (currentExpire != null && currentExpire.ExpireTime > 0) + { + DateTime currentExpireTime = TimeUtils.StampToDateTime(currentExpire.ExpireTime); + if (currentExpireTime > expireBaseTime) + { + expireBaseTime = currentExpireTime; + } + } + + int newExpireTime = CalculateNewExpireTime(expireBaseTime, request.RenewDuration); + + int affectedUser = _freeSql.Update() + .Set(a => a.Points, newPoints) + .Where(a => a.UserName == userName && a.Points == user.Points) + .ExecuteAffrows(); + if (affectedUser != 1) + { + throw new InvalidOperationException("账户已发生改变,请刷新账户后重新操作"); + } + + for (int i = 0; i < request.RenewDuration; i++) + { + decimal recordNewPoints = user.Points - (decimal)shopPointsPerMonth * (i + 1); + UserPointsRecordEntity item = new UserPointsRecordEntity + { + OptionType = "续费", + OperatorType = "用户", + OperatorName = userName, + UserName = userName, + UserNick = user.NickName, + DeltaPoints = shopPointsPerMonth, + NewPoints = recordNewPoints, + ShopId = shop.ShopId, + ShopAccount = shop.ShopAccount, + Remark = remark, + AddTime = now + }; + + int affectedRecord = _freeSql.Insert(item).ExecuteAffrows(); + if (affectedRecord != 1) + { + throw new InvalidOperationException("新增操作记录失败,请稍候重试"); + } + } + + if (currentExpire == null) + { + UserPointsShopExpireEntity expire = new UserPointsShopExpireEntity + { + ShopId = shopId, + ExpireTime = newExpireTime, + AddTime = now, + MdyTime = now + }; + + int affectedExpire = _freeSql.Insert(expire).ExecuteAffrows(); + if (affectedExpire != 1) + { + throw new InvalidOperationException("新增商铺过期时间失败,请稍候重试"); + } + } + else + { + int affectedExpire = _freeSql.Update() + .Set(a => a.ExpireTime, newExpireTime) + .Set(a => a.MdyTime, now) + .Where(a => a.Id == currentExpire.Id && a.ExpireTime == currentExpire.ExpireTime) + .ExecuteAffrows(); + if (affectedExpire != 1) + { + throw new InvalidOperationException("店铺已发生改变,请刷新店铺后重新操作"); + } + } + + response.ShopId = request.ShopId; + response.UserPoints = newPoints; + response.ExpireTime‌ = newExpireTime; + }); + + return response; + } + + private static void ValidateRequest(ShopRenewRequest request) + { + if (request == null) + { + throw new ArgumentException("请求参数不能为空", nameof(request)); + } + if (string.IsNullOrWhiteSpace(request.UserName)) + { + throw new ArgumentException("用户名不能为空", nameof(request.UserName)); + } + if (request.UserName.Trim().Length > UserNameMaxLength) + { + throw new ArgumentException("用户名长度不能超过20个字符", nameof(request.UserName)); + } + if (string.IsNullOrWhiteSpace(request.ShopId)) + { + throw new ArgumentException("店铺ID不能为空", nameof(request.ShopId)); + } + if (request.ShopId.Trim().Length > ShopIdMaxLength) + { + throw new ArgumentException("店铺ID长度不能超过50个字符", nameof(request.ShopId)); + } + if (request.RenewDuration <= 0) + { + throw new ArgumentException("续费时长必须大于0", nameof(request.RenewDuration)); + } + if (request.RenewDuration > MaxRenewDuration) + { + throw new ArgumentException("续费时长不能超过120个月", nameof(request.RenewDuration)); + } + if (!string.IsNullOrEmpty(request.Remark) && request.Remark.Trim().Length > RemarkMaxLength) + { + throw new ArgumentException("备注长度不能超过255个字符", nameof(request.Remark)); + } + } + + private static int CalculateNewExpireTime(DateTime expireBaseTime, int renewDuration) + { + DateTime newExpireDateTime; + try + { + newExpireDateTime = expireBaseTime.AddMonths(renewDuration); + } + catch (ArgumentOutOfRangeException) + { + throw new InvalidOperationException("续费时长超出允许范围"); + } + + if (newExpireDateTime > TimeUtils.StampToDateTime(int.MaxValue)) + { + throw new InvalidOperationException("续费时间超出系统支持范围"); + } + + return TimeUtils.DateTimeToTimestampSeconds(newExpireDateTime); + } + } +} diff --git a/KJD_WebService/Web.config b/KJD_WebService/Web.config index 4759219..5a5d82d 100644 --- a/KJD_WebService/Web.config +++ b/KJD_WebService/Web.config @@ -5,8 +5,8 @@ --> - - + + diff --git a/KJD_WebService/packages.config b/KJD_WebService/packages.config index 342f432..d741829 100644 --- a/KJD_WebService/packages.config +++ b/KJD_WebService/packages.config @@ -3,7 +3,9 @@ + + diff --git a/Store.Framework/Class1.cs b/Store.Framework/Class1.cs new file mode 100644 index 0000000..769542d --- /dev/null +++ b/Store.Framework/Class1.cs @@ -0,0 +1,6 @@ +namespace Store.Framework; + +public class Class1 +{ + +} diff --git a/Store.Framework/Store.Framework.csproj b/Store.Framework/Store.Framework.csproj new file mode 100644 index 0000000..742e991 --- /dev/null +++ b/Store.Framework/Store.Framework.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + diff --git a/Store.User/Class1.cs b/Store.User/Class1.cs new file mode 100644 index 0000000..4f5b7c7 --- /dev/null +++ b/Store.User/Class1.cs @@ -0,0 +1,6 @@ +namespace Store.User; + +public class Class1 +{ + +} diff --git a/Store.User/Store.User.csproj b/Store.User/Store.User.csproj new file mode 100644 index 0000000..69c64e0 --- /dev/null +++ b/Store.User/Store.User.csproj @@ -0,0 +1,5 @@ + + + + +