refactor: major project restructuring and cleanup
Changes: - Remove deprecated Fengling.Activity and YarpGateway.Admin projects - Add points processing services with distributed lock support - Update Vben frontend with gateway management pages - Add gateway config controller and database listener - Update routing to use header-mixed-nav layout - Add comprehensive test suites for Member services - Add YarpGateway integration tests - Update package versions in Directory.Packages.props Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
commit
ab8d12527e
25
.dockerignore
Normal file
25
.dockerignore
Normal file
@ -0,0 +1,25 @@
|
||||
**/.classpath
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
||||
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
398
.gitignore
vendored
Normal file
398
.gitignore
vendored
Normal file
@ -0,0 +1,398 @@
|
||||
## 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/
|
||||
# Exception: allow frontend scripts bin directory
|
||||
!src/frontend/scripts/**/bin/
|
||||
[Oo]bj/
|
||||
[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
|
||||
*.tlog
|
||||
*.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
|
||||
# Only ignore NuGet packages folders at project root level, not frontend workspace packages
|
||||
/[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
|
||||
|
||||
# Nuget personal access tokens and Credentials
|
||||
# nuget.config
|
||||
|
||||
# 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
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/csharp.code-snippets
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
/.vs
|
||||
|
||||
# Internal packages build outputs (generated by unbuild --stub, contains absolute paths)
|
||||
src/frontend/internal/**/dist/
|
||||
src/frontend/packages/**/dist/
|
||||
src/frontend/scripts/**/dist/
|
||||
34
Directory.Build.props
Normal file
34
Directory.Build.props
Normal file
@ -0,0 +1,34 @@
|
||||
<Project>
|
||||
<Import Project="$(MSBuildThisFileDirectory)\eng\versions.props"/>
|
||||
<PropertyGroup>
|
||||
<Authors>Fengling.Activity</Authors>
|
||||
<Product>Fengling.Activity</Product>
|
||||
<owners>Fengling.Activity</owners>
|
||||
<PackagePrefix>Fengling.Activity</PackagePrefix>
|
||||
<PackageIconUrl></PackageIconUrl>
|
||||
<PackageProjectUrl></PackageProjectUrl>
|
||||
<PackageLicenseUrl></PackageLicenseUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl></RepositoryUrl>
|
||||
<GenerateAssemblyConfigurationAttribute>True</GenerateAssemblyConfigurationAttribute>
|
||||
<GenerateAssemblyCompanyAttribute>True</GenerateAssemblyCompanyAttribute>
|
||||
<GenerateAssemblyProductAttribute>True</GenerateAssemblyProductAttribute>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);CS1591;NU1507;S125;CS9107;</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<PropertyGroup Condition="$(IsTestProject) != 'true'">
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>$(WarningsAsErrors);CS8625;CS8604;CS8602;CS8600;CS8618;CS8601;CS8603</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="$(IsTestProject) != 'true'">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
3
Directory.Build.targets
Normal file
3
Directory.Build.targets
Normal file
@ -0,0 +1,3 @@
|
||||
<Project>
|
||||
<!-- Keep empty for now, version management moved to Directory.Packages.props -->
|
||||
</Project>
|
||||
152
Directory.Packages.props
Normal file
152
Directory.Packages.props
Normal file
@ -0,0 +1,152 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Third-party package versions -->
|
||||
<NetCorePalVersion>3.2.1</NetCorePalVersion>
|
||||
<FastEndpointsVersion>7.1.1</FastEndpointsVersion>
|
||||
<TestcontainersVersion>4.9.0</TestcontainersVersion>
|
||||
<AspireVersion>13.1.0</AspireVersion>
|
||||
<OpenTelemetryVersion>1.14.0</OpenTelemetryVersion>
|
||||
<NetCorePalTestcontainerVersion>1.0.5</NetCorePalTestcontainerVersion>
|
||||
<NetCorePalAspireVersion>1.1.2</NetCorePalAspireVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="10.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.0.0" />
|
||||
<PackageVersion Include="AspNet.Security.OAuth.Feishu" Version="9.0.0" />
|
||||
<PackageVersion Include="AspNet.Security.OAuth.Weixin" Version="9.0.0" />
|
||||
|
||||
<!-- Database providers - framework specific versions -->
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
|
||||
<PackageVersion Include="DM.Microsoft.EntityFrameworkCore" Version="9.0.0.37033" />
|
||||
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||
<PackageVersion Include="Pomelo.EntityFrameworkCore.MySql.Json.Microsoft" Version="9.0.0" />
|
||||
<PackageVersion Include="MySql.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0" />
|
||||
<PackageVersion Include="DotNetCore.EntityFrameworkCore.GaussDB" Version="9.0.0" />
|
||||
<PackageVersion Include="DotNetCore.EntityFrameworkCore.KingbaseES" Version="9.0.0" />
|
||||
<PackageVersion Include="MongoDB.EntityFrameworkCore" Version="9.0.3" />
|
||||
<!-- ASP.NET Core and Microsoft packages - framework specific versions -->
|
||||
<PackageVersion Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.12.0" />
|
||||
|
||||
<!-- CAP packages for .NET 9.0+ -->
|
||||
<PackageVersion Include="DotNetCore.CAP.Dashboard" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.RabbitMQ" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.Kafka" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.AzureServiceBus" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.AmazonSQS" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.NATS" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.RedisStreams" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.Pulsar" Version="8.4.1" />
|
||||
<PackageVersion Include="DotNetCore.CAP.OpenTelemetry" Version="8.4.1" />
|
||||
|
||||
<!-- FastEndpoints -->
|
||||
<PackageVersion Include="FastEndpoints" Version="$(FastEndpointsVersion)" />
|
||||
<PackageVersion Include="FastEndpoints.Swagger" Version="$(FastEndpointsVersion)" />
|
||||
<PackageVersion Include="FastEndpoints.Swagger.Swashbuckle" Version="2.3.0" />
|
||||
|
||||
|
||||
<!-- Other packages -->
|
||||
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.0" />
|
||||
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.17" />
|
||||
<PackageVersion Include="Hangfire.Redis.StackExchange" Version="1.9.4" />
|
||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||
<PackageVersion Include="prometheus-net.AspNetCore.HealthChecks" Version="8.2.1" />
|
||||
<PackageVersion Include="Refit.HttpClientFactory" Version="8.0.0" />
|
||||
<PackageVersion Include="Refit.Newtonsoft.Json" Version="8.0.0" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.ClientInfo" Version="2.1.2" />
|
||||
<PackageVersion Include="Serilog.Sinks.OpenTelemetry" Version="4.1.0" />
|
||||
<PackageVersion Include="StackExchange.Redis" Version="2.9.32" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.2.0" />
|
||||
|
||||
<!-- Aspire packages -->
|
||||
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Docker" Version="13.1.0-preview.1.25616.3" />
|
||||
<PackageVersion Include="Aspire.Hosting.Testing" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.MySql" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.SqlServer" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.PostgreSQL" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.MongoDB" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Redis" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.RabbitMQ" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.Kafka" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Hosting.NATS" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.StackExchange.Redis" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Pomelo.EntityFrameworkCore.MySql" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Microsoft.EntityFrameworkCore.SqlServer" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Npgsql.EntityFrameworkCore.PostgreSQL" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="CommunityToolkit.Aspire.Hosting.MongoDB.Extensions" Version="13.0.0" />
|
||||
<PackageVersion Include="Aspire.RabbitMQ.Client" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Aspire.Confluent.Kafka" Version="$(AspireVersion)" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="10.1.0" />
|
||||
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="$(OpenTelemetryVersion)" />
|
||||
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="$(OpenTelemetryVersion)" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="$(OpenTelemetryVersion)" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="$(OpenTelemetryVersion)" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="$(OpenTelemetryVersion)" />
|
||||
<PackageVersion Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.13.0-beta.1" />
|
||||
<PackageVersion Include="Npgsql.OpenTelemetry" Version="8.0.8" />
|
||||
|
||||
<!-- NetCorePal packages -->
|
||||
<PackageVersion Include="NetCorePal.Context.AspNetCore" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Context.CAP" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Context.Shared" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.AspNetCore" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.CodeAnalysis" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedLocks.Redis" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedTransactions.CAP.MySql" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedTransactions.CAP.SqlServer" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedTransactions.CAP.PostgreSQL" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedTransactions.CAP.Sqlite" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedTransactions.CAP.DMDB" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedTransactions.CAP.MongoDB" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedTransactions.CAP.GaussDB" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.DistributedTransactions.CAP.KingbaseES" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Domain.Abstractions" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Jwt.StackExchangeRedis" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.MicrosoftServiceDiscovery" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.MultiEnv" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.NewtonsoftJson" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Primitives" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Repository.EntityFrameworkCore" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.OpenTelemetry.Diagnostics" Version="$(NetCorePalVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Aspire.Hosting.DMDB" Version="$(NetCorePalAspireVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Aspire.Hosting.OpenGauss" Version="$(NetCorePalAspireVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Aspire.Hosting.MongoDB" Version="$(NetCorePalAspireVersion)" />
|
||||
|
||||
<!-- Testing packages -->
|
||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||
<PackageVersion Include="Testcontainers" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="Testcontainers.MySql" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="Testcontainers.PostgreSql" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="Testcontainers.MongoDb" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="Testcontainers.MsSql" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="Testcontainers.RabbitMq" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="Testcontainers.Kafka" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="Testcontainers.Nats" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="Testcontainers.Redis" Version="$(TestcontainersVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Testcontainers.DMDB" Version="$(NetCorePalTestcontainerVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Testcontainers.KingbaseES" Version="$(NetCorePalTestcontainerVersion)" />
|
||||
<PackageVersion Include="NetCorePal.Testcontainers.OpenGauss" Version="$(NetCorePalTestcontainerVersion)" />
|
||||
<PackageVersion Include="xunit.v3" Version="3.2.1" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageVersion Include="FastEndpoints.Testing" Version="$(FastEndpointsVersion)" />
|
||||
|
||||
<!-- Code analysis -->
|
||||
<PackageVersion Include="SonarAnalyzer.CSharp" Version="10.3.0.106239" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
918
Fengling.Activity.sln.DotSettings
Normal file
918
Fengling.Activity.sln.DotSettings
Normal file
@ -0,0 +1,918 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
|
||||
|
||||
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=2CBD6971A7955044AD2624B84FB49E38/@KeyIndexDefined">False</s:Boolean>
|
||||
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=40C163D436D8ED48A6D01A0AFEFC5556/@KeyIndexDefined">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=567DCF4B487C244A9F6BB46E4E9F3B84/@KeyIndexDefined">False</s:Boolean>
|
||||
|
||||
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=7F2A1BE8D0078241A9AE7802038BAD3C/@KeyIndexDefined">False</s:Boolean>
|
||||
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=C4795E57DDEC1C4F97BBC8C7173EBBCA/@KeyIndexDefined">False</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Description/@EntryValue">post-processor</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Shortcut/@EntryValue">postproc</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Text/@EntryValue">sealed class $name$ : IPostProcessor<$dto$Request, $dto$Response>
|
||||
{
|
||||
public Task PostProcessAsync($dto$Request req, $dto$Response res, HttpContext ctx, IReadOnlyCollection<ValidationFailure> fails, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Field/=dto/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Field/=dto/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Field/=dto/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Field/=name/Expression/@EntryValue">constant("MyProcessor")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=05F0D1722D0C7745B8D9365C6C73D7A9/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Description/@EntryValue">test class</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Shortcut/@EntryValue">tstclass</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Text/@EntryValue">namespace Tests;
|
||||
|
||||
public class $name$Tests : TestClass<$fixture$Fixture>
|
||||
{
|
||||
public $name$Tests($fixture$Fixture f, ITestOutputHelper o) : base(f, o) { }
|
||||
|
||||
[Fact]
|
||||
public async Task $test_name$()
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=fixture/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=fixture/Expression/@EntryValue">constant("App")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=fixture/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=test_005Fname/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=test_005Fname/Expression/@EntryValue">constant("Name_Of_The_Test")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Field/=test_005Fname/Order/@EntryValue">2</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0A7BFA2DC992D84C93FE334B8E39B17E/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Description/@EntryValue">endpoint with request only</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Shortcut/@EntryValue">epreq</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Text/@EntryValue">sealed class $epName$Request
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $epName$Endpoint : Endpoint<$epName$Request>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
$verb$("$route$");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync($epName$Request r, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=epName/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=epName/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=epName/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=route/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=route/Expression/@EntryValue">constant("route-pattern")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=route/Order/@EntryValue">2</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=verb/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=verb/Expression/@EntryValue">constant("Post")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Field/=verb/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=0FB9C31AB3C94342BEDBDE36AED0492D/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Description/@EntryValue">创建命令</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Shortcut/@EntryValue">ncpcmd</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=11503521883F874CBBBCDE8259105A71/Text/@EntryValue">public record $name$Command() : ICommand;
|
||||
|
||||
public class $name$CommandValidator : AbstractValidator<$name$Command>
|
||||
{
|
||||
public $name$CommandValidator()
|
||||
{
|
||||
// 添加验证规则示例:
|
||||
// RuleFor(x => x.Property).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class $name$CommandHandler : ICommandHandler<$name$Command>
|
||||
{
|
||||
public async Task Handle(
|
||||
$name$Command request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 实现业务逻辑
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Description/@EntryValue">创建命令(含返回值)</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Shortcut/@EntryValue">ncpcmdres</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=18E2B14D9EF3204A8C2E916E06EA2F2A/Text/@EntryValue">public record $name$Command() : ICommand<$name$CommandResponse>;
|
||||
|
||||
public record $name$CommandResponse();
|
||||
|
||||
public class $name$CommandValidator : AbstractValidator<$name$Command>
|
||||
{
|
||||
public $name$CommandValidator()
|
||||
{
|
||||
// 添加验证规则示例:
|
||||
// RuleFor(x => x.Property).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public class $name$CommandHandler : ICommandHandler<$name$Command, $name$CommandResponse>
|
||||
{
|
||||
public async Task<$name$CommandResponse> Handle(
|
||||
$name$Command request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 实现业务逻辑
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Description/@EntryValue">endpoint request & response dtos</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Shortcut/@EntryValue">epdto</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Text/@EntryValue">sealed class $name$Request
|
||||
{
|
||||
$END$
|
||||
}
|
||||
|
||||
sealed class $name$Response
|
||||
{
|
||||
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1BC67AC84CCD7E4BAE97977346E7EA96/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Description/@EntryValue">创建聚合根</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Shortcut/@EntryValue">ncpar</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=1C6DBF7652D0F84A971A70E4BD4F009B/Text/@EntryValue">public partial record $name$Id : IInt64StronglyTypedId;
|
||||
|
||||
public class $name$ : Entity<$name$Id>, IAggregateRoot
|
||||
{
|
||||
protected $name$() { }
|
||||
}
|
||||
</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Description/@EntryValue">test fixture</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Shortcut/@EntryValue">tstfixture</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Text/@EntryValue">namespace Tests;
|
||||
|
||||
public class $name$Fixture : TestFixture<Program>
|
||||
{
|
||||
public $name$Fixture(IMessageSink s) : base(s) { }
|
||||
|
||||
protected override Task SetupAsync()
|
||||
{
|
||||
$END$
|
||||
}
|
||||
|
||||
protected override void ConfigureServices(IServiceCollection s)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override Task TearDownAsync()
|
||||
{
|
||||
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Field/=name/Expression/@EntryValue">constant("App")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=20C68DE5FC26A941B3663E08B95DABC0/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Applicability/=File/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/CustomProperties/=Extension/@EntryIndexedValue">cs</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/CustomProperties/=FileName/@EntryIndexedValue">Endpoint</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/CustomProperties/=ValidateFileName/@EntryIndexedValue">False</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Description/@EntryValue">FastEndpoints Feature File Set</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Text/@EntryValue">namespace $name_space$;
|
||||
|
||||
sealed class Request
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class Validator : Validator<Request>
|
||||
{
|
||||
public Validator()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Response
|
||||
{
|
||||
public string Message => "This endpoint hasn't been implemented yet!";
|
||||
}
|
||||
|
||||
sealed class Endpoint : Endpoint<Request, Response, Mapper>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
Post("$route$");
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(Request r, CancellationToken c)
|
||||
{
|
||||
await SendAsync(new Response());$END$
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Mapper : Mapper<Request, Response, object>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static class Data
|
||||
{
|
||||
|
||||
}</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/UITag/@EntryValue">Class/Interface</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Field/=name_005Fspace/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Field/=name_005Fspace/Expression/@EntryValue">fileDefaultNamespace()</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Field/=name_005Fspace/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Field/=route/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Field/=route/Expression/@EntryValue">constant("route-pattern")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Field/=route/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Scope/=E8F0594528C33E45BBFEC6CFE851095D/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=29F1FF6B85CF6B43810FB210D8429C05/Scope/=E8F0594528C33E45BBFEC6CFE851095D/Type/@EntryValue">InCSharpProjectFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Description/@EntryValue">event handler</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Shortcut/@EntryValue">evnt</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Text/@EntryValue">sealed class $name$ : IEvent
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $name$Handler : IEventHandler<$name$>
|
||||
{
|
||||
public Task HandleAsync($name$ e, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Field/=name/Expression/@EntryValue">constant("MyEvent")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=32B4A14C9D8E8E41B84D726F3BF3B83B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Description/@EntryValue">创建仓储</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Shortcut/@EntryValue">ncprepo</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=3990E0976664CC47941C4C99D9633785/Text/@EntryValue">public interface I$name$Repository : IRepository<$name$, $name$Id>;
|
||||
|
||||
public class $name$Repository(ApplicationDbContext context)
|
||||
: RepositoryBase<$name$, $name$Id, ApplicationDbContext>(context),
|
||||
I$name$Repository
|
||||
{
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Description/@EntryValue">endpoint data</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Shortcut/@EntryValue">epdat</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Text/@EntryValue">static class $name$Data
|
||||
{
|
||||
$END$
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4A753F31FB59794B92CC342B3D916AF0/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Description/@EntryValue">command handler with result</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Shortcut/@EntryValue">cmdres</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Text/@EntryValue">sealed class $name$ : ICommand<$name$Result>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $name$Result
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $name$Handler : ICommandHandler<$name$, $name$Result>
|
||||
{
|
||||
public Task<$name$Result> ExecuteAsync($name$ cmd, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Field/=name/Expression/@EntryValue">constant("MyCommand")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5608073DECE29447BBC916D9A45CB431/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Description/@EntryValue">command handler</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Shortcut/@EntryValue">cmd</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Text/@EntryValue">sealed class $name$ : ICommand
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $name$Handler : ICommandHandler<$name$>
|
||||
{
|
||||
public Task ExecuteAsync($name$ cmd, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Field/=name/Expression/@EntryValue">constant("MyCommand")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=5CB8B97F878E2A429499C0BAFFAACA08/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Description/@EntryValue">endpoint validator</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Shortcut/@EntryValue">epval</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Text/@EntryValue">sealed class $name$Validator : Validator<$name$Request>
|
||||
{
|
||||
public $name$Validator()
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6FE119A547576C409320655117DD5739/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Description/@EntryValue">global pre-processor</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Shortcut/@EntryValue">preproc_g</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Text/@EntryValue">sealed class $name$ : IGlobalPreProcessor
|
||||
{
|
||||
public Task PreProcessAsync(object r, HttpContext ctx, List<ValidationFailure> fails, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Field/=name/Expression/@EntryValue">constant("MyProcessor")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=708606B749489E419384C91A6AA44142/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Description/@EntryValue">endpoint with response only</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Shortcut/@EntryValue">epres</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Text/@EntryValue">sealed class $epName$Response
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $epName$Endpoint : EndpointWithoutRequest<$epName$Response>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
$verb$("$route$");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=epName/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=epName/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=epName/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=route/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=route/Expression/@EntryValue">constant("route-pattern")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=route/Order/@EntryValue">2</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=verb/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=verb/Expression/@EntryValue">constant("Get")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Field/=verb/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=7447D2E4580AF646ABA86511009839CD/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Description/@EntryValue">创建集成事件与事件处理器</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Field/=name/Expression/@EntryValue">constant("MyCreated")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Shortcut/@EntryValue">ncpie</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=90130789CFD7A44DBCAAE38E926129FB/Text/@EntryValue">public record $name$IntegrationEvent();
|
||||
|
||||
public class $name$IntegrationEventHandler(IMediator mediator) : IIntegrationEventHandler<$name$IntegrationEvent>
|
||||
{
|
||||
public Task HandleAsync($name$IntegrationEvent eventData, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// var cmd = new $name$Command(eventData.Id);
|
||||
// return mediator.Send(cmd, cancellationToken);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Description/@EntryValue">创建领域事件处理器</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Field/=name/Expression/@EntryValue">constant("MyCreated")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Shortcut/@EntryValue">ncpdeh</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AD34247A5DABAF44A7194A6B98ECE566/Text/@EntryValue">public class $name$DomainEventHandler(IMediator mediator)
|
||||
: IDomainEventHandler<$name$DomainEvent>
|
||||
{
|
||||
public async Task Handle($name$DomainEvent notification,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 实现业务逻辑
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Description/@EntryValue">endpoint vertical slice - NCP</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=descriptionText/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=descriptionText/Expression/@EntryValue">constant("Description text goes here...")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=descriptionText/Order/@EntryValue">4</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=epName/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=epName/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=epName/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=route/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=route/Expression/@EntryValue">constant("route-pattern")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=route/Order/@EntryValue">2</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=summaryText/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=summaryText/Expression/@EntryValue">constant("Summary text goes here...")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=summaryText/Order/@EntryValue">3</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=verb/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=verb/Expression/@EntryValue">constant("Post")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Field/=verb/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Shortcut/@EntryValue">epp</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2DD7B7A51002B49B63B79F74E955FBF/Text/@EntryValue">sealed class $epName$Endpoint(IMediator mediator) : Endpoint<$epName$Request, ResponseData<$epName$Response>>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
$verb$("$route$");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync($epName$Request r, CancellationToken c)
|
||||
{
|
||||
var cmd = new $epName$Command(r.Property1, r.Property2);
|
||||
var result = await mediator.Send(cmd, c);
|
||||
var res = new $epName$Response();
|
||||
await SendOkAsync(res.AsResponseData(), c);
|
||||
$END$
|
||||
}
|
||||
}
|
||||
|
||||
sealed record $epName$Request();
|
||||
|
||||
sealed record $epName$Response();
|
||||
|
||||
sealed class $epName$Validator : Validator<$epName$Request>
|
||||
{
|
||||
public $epName$Validator()
|
||||
{
|
||||
// RuleFor(x => x.Property).NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
sealed class $epName$Summary : Summary<$epName$Endpoint, $epName$Request>
|
||||
{
|
||||
public $epName$Summary()
|
||||
{
|
||||
Summary = "$summaryText$";
|
||||
Description = "$descriptionText$";
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Description/@EntryValue">pre-processor</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Shortcut/@EntryValue">preproc</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Text/@EntryValue">sealed class $name$ : IPreProcessor<$req$Request>
|
||||
{
|
||||
public Task PreProcessAsync($req$Request r, HttpContext ctx, List<ValidationFailure> fails, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Field/=name/Expression/@EntryValue">constant("MyProcessor")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Field/=req/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Field/=req/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Field/=req/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B2F01B6412BAED4FBEDBDF8C42CAE748/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Description/@EntryValue">创建集成事件转换器</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Field/=name/Expression/@EntryValue">constant("MyCreated")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Shortcut/@EntryValue">ncpiec</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=BF0EE1C126A9094D821F52F7BD08449B/Text/@EntryValue">public class $name$IntegrationEventConverter
|
||||
: IIntegrationEventConverter<$name$DomainEvent, $name$IntegrationEvent>
|
||||
{
|
||||
public $name$IntegrationEvent Convert($name$DomainEvent domainEvent)
|
||||
{
|
||||
// return new $name$IntegrationEvent(domainEvent.Id);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Description/@EntryValue">endpoint mapper</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Shortcut/@EntryValue">epmap</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Text/@EntryValue">sealed class $epName$Mapper : Mapper<$epName$Request, $epName$Response, $entity$>
|
||||
{
|
||||
public override $entity$ ToEntity($epName$Request r) => new()
|
||||
{
|
||||
$END$
|
||||
};
|
||||
|
||||
public override $epName$Response FromEntity($entity$ e) => new()
|
||||
{
|
||||
|
||||
};
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Field/=entity/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Field/=entity/Expression/@EntryValue">constant("YourEntity")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Field/=entity/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Field/=epName/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Field/=epName/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Field/=epName/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C4140DCF16198B44BA1CA74DD48BA21D/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Description/@EntryValue">endpoint vertical slice</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=descriptionText/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=descriptionText/Expression/@EntryValue">constant("Description text goes here...")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=descriptionText/Order/@EntryValue">5</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=summaryText/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=summaryText/Expression/@EntryValue">constant("Summary text goes here...")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=summaryText/Order/@EntryValue">4</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Shortcut/@EntryValue">epfull</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Text/@EntryValue">sealed class $epName$Endpoint : Endpoint<$epName$Request, $epName$Response, $epName$Mapper>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
$verb$("$route$");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync($epName$Request r, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}
|
||||
|
||||
sealed class $epName$Request
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $epName$Response
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $epName$Validator : Validator<$epName$Request>
|
||||
{
|
||||
public $epName$Validator()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
sealed class $epName$Mapper: Mapper<$epName$Request, $epName$Response, $entity$>
|
||||
{
|
||||
public override $entity$ ToEntity($epName$Request r) => new()
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
public override $epName$Response FromEntity($entity$ e) => new()
|
||||
{
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
sealed class $epName$Summary : Summary<$epName$Endpoint, $epName$Request>
|
||||
{
|
||||
public $epName$Summary()
|
||||
{
|
||||
Summary = "$summaryText$";
|
||||
Description = "$descriptionText$";
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=entity/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=entity/Expression/@EntryValue">constant("YourEntity")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=entity/Order/@EntryValue">3</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=epName/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=epName/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=epName/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=route/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=route/Expression/@EntryValue">constant("route-pattern")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=route/Order/@EntryValue">2</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=verb/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=verb/Expression/@EntryValue">constant("Post")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Field/=verb/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=CE7A652209B10B4CB66F65C1D485A3E1/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Description/@EntryValue">global post-processor</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Shortcut/@EntryValue">postproc_g</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Text/@EntryValue">sealed class $name$ : IGlobalPostProcessor
|
||||
{
|
||||
public Task PostProcessAsync(object req, object? res, HttpContext ctx, IReadOnlyCollection<ValidationFailure> fails, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Field/=name/Expression/@EntryValue">constant("MyProcessor")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E0AE343F732B3A4D82626D43412F1AEA/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Description/@EntryValue">test method</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Shortcut/@EntryValue">tstmethod</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Text/@EntryValue"> [Fact]
|
||||
public async Task $test_name$()
|
||||
{
|
||||
$END$
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Field/=test_005Fname/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Field/=test_005Fname/Expression/@EntryValue">constant("Name_Of_The_Test")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Field/=test_005Fname/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=E8C3ABB4F8689445AB1C755840C18308/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Description/@EntryValue">创建领域事件</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Field/=name/Expression/@EntryValue">constant("MyCreated")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Shortcut/@EntryValue">ncpde</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=EF36D18412E40944922BA7E05699EC20/Text/@EntryValue">public record $name$DomainEvent() : IDomainEvent;</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Description/@EntryValue">endpoint summary</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Shortcut/@EntryValue">epsum</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Text/@EntryValue">sealed class $name$Summary : Summary<$name$Endpoint, $name$Request>
|
||||
{
|
||||
public $name$Summary()
|
||||
{
|
||||
Summary = "$summaryText$";
|
||||
Description = "$descriptionText$";
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=descriptionText/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=descriptionText/Expression/@EntryValue">constant("Description text goes here...")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=descriptionText/Order/@EntryValue">2</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=name/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=name/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=name/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=summaryText/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=summaryText/Expression/@EntryValue">constant("Summary text goes here...")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Field/=summaryText/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=F98DF402F177C048910D14E90EA93054/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Description/@EntryValue">endpoint without request</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Shortcut/@EntryValue">epnoreq</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Text/@EntryValue">sealed class $My$Endpoint : EndpointWithoutRequest
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
$Get$("$route$");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync(CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Field/=Get/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Field/=Get/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Field/=My/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Field/=My/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Field/=route/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Field/=route/Order/@EntryValue">2</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FCF9C07A45226F41A7460D9D04E6E7C4/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Description/@EntryValue">endpoint with request & response</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Shortcut/@EntryValue">epreqres</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Text/@EntryValue">sealed class $epName$Request
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $epName$Response
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
sealed class $epName$Endpoint : Endpoint<$epName$Request, $epName$Response>
|
||||
{
|
||||
public override void Configure()
|
||||
{
|
||||
$verb$("$route$");
|
||||
AllowAnonymous();
|
||||
}
|
||||
|
||||
public override async Task HandleAsync($epName$Request r, CancellationToken c)
|
||||
{
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=epName/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=epName/Expression/@EntryValue">constant("My")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=epName/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=route/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=route/Expression/@EntryValue">constant("route-pattern")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=route/Order/@EntryValue">2</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=verb/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=verb/Expression/@EntryValue">constant("Post")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Field/=verb/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=FDE43092B97B5F45996AF67E4BAB0B34/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Description/@EntryValue">创建实体配置类</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Reformat/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Shortcut/@EntryValue">ncpconfig</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/ShortenQualifiedReferences/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Text/@EntryValue">public class $Entity$Configuration : IEntityTypeConfiguration<$Entity$>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<$Entity$> builder)
|
||||
{
|
||||
builder.ToTable("$table$");
|
||||
builder.HasKey(t => t.Id);
|
||||
builder.Property(t => t.Id)
|
||||
/*.UseSnowFlakeValueGenerator()*/ // 如果使用 SnowFlake ID 生成器,请取消注释
|
||||
/*.UseGuidVersion7ValueGenerator()*/ // 如果使用 Guid Version 7 ID 生成器,请取消注释
|
||||
;
|
||||
|
||||
// Configure other properties if needed
|
||||
$END$
|
||||
}
|
||||
}</s:String>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Field/=Entity/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Field/=Entity/Expression/@EntryValue">constant("Entity")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Field/=Entity/Order/@EntryValue">0</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Field/=table/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Field/=table/Expression/@EntryValue">constant("table")</s:String>
|
||||
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Field/=table/Order/@EntryValue">1</s:Int64>
|
||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Scope/=C3001E7C0DA78E4487072B7E050D86C5/@KeyIndexDefined">True</s:Boolean>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Scope/=C3001E7C0DA78E4487072B7E050D86C5/CustomProperties/=minimumLanguageVersion/@EntryIndexedValue">2.0</s:String>
|
||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=NCPCONFIG1234567890ABCDEF/Scope/=C3001E7C0DA78E4487072B7E050D86C5/Type/@EntryValue">InCSharpFile</s:String></wpf:ResourceDictionary>
|
||||
7
NuGet.config
Normal file
7
NuGet.config
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
|
||||
<!-- <add key="netcorepal-preview" value="https://www.myget.org/F/netcorepal/api/v3/index.json" /> -->
|
||||
</packageSources>
|
||||
</configuration>
|
||||
230
README.md
Normal file
230
README.md
Normal file
@ -0,0 +1,230 @@
|
||||
# Fengling.Activity
|
||||
|
||||
## 环境准备
|
||||
|
||||
### 使用 Aspire(推荐)
|
||||
|
||||
如果您的项目启用了 Aspire 支持(使用 `--UseAspire` 参数创建),只需要 Docker 环境即可,无需手动配置各种基础设施服务。
|
||||
|
||||
```bash
|
||||
# 仅需确保 Docker 环境运行
|
||||
docker version
|
||||
|
||||
# 直接运行 AppHost 项目,Aspire 会自动管理所有依赖服务
|
||||
cd src/Fengling.Activity.AppHost
|
||||
dotnet run
|
||||
```
|
||||
|
||||
Aspire 会自动为您:
|
||||
- 启动和管理数据库容器(MySQL、SQL Server、PostgreSQL、MongoDB 等)
|
||||
- 启动和管理消息队列容器(RabbitMQ、Kafka、NATS 等)
|
||||
- 启动和管理 Redis 容器
|
||||
- 提供统一的 Aspire Dashboard 界面查看所有服务状态
|
||||
- 自动配置服务间的连接字符串和依赖关系
|
||||
|
||||
访问 Aspire Dashboard(通常在 http://localhost:15888)可以查看和管理所有服务。
|
||||
|
||||
### 推荐方式:使用初始化脚本(不使用 Aspire 时)
|
||||
|
||||
如果您没有启用 Aspire,项目提供了完整的基础设施初始化脚本,支持快速搭建开发环境:
|
||||
|
||||
#### 使用 Docker Compose(推荐)
|
||||
```bash
|
||||
# 进入脚本目录
|
||||
cd scripts
|
||||
|
||||
# 启动默认基础设施 (MySQL + Redis + RabbitMQ)
|
||||
docker-compose up -d
|
||||
|
||||
# 使用 SQL Server 替代 MySQL
|
||||
docker-compose --profile sqlserver up -d
|
||||
|
||||
# 使用 PostgreSQL 替代 MySQL
|
||||
docker-compose --profile postgres up -d
|
||||
|
||||
# 使用 Kafka 替代 RabbitMQ
|
||||
docker-compose --profile kafka up -d
|
||||
|
||||
# 停止所有服务
|
||||
docker-compose down
|
||||
|
||||
# 停止并删除数据卷(完全清理)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
#### 使用初始化脚本
|
||||
```bash
|
||||
# Linux/macOS
|
||||
cd scripts
|
||||
./init-infrastructure.sh
|
||||
|
||||
# Windows PowerShell
|
||||
cd scripts
|
||||
.\init-infrastructure.ps1
|
||||
|
||||
# 清理环境
|
||||
./clean-infrastructure.sh # Linux/macOS
|
||||
.\clean-infrastructure.ps1 # Windows
|
||||
```
|
||||
|
||||
### 手动方式:单独运行 Docker 容器
|
||||
|
||||
如果需要手动控制每个容器,可以使用以下命令:
|
||||
|
||||
```bash
|
||||
# Redis
|
||||
docker run --restart unless-stopped --name netcorepal-redis -p 6379:6379 -v netcorepal_redis_data:/data -d redis:7.2-alpine redis-server --appendonly yes --databases 1024
|
||||
|
||||
# MySQL
|
||||
docker run --restart unless-stopped --name netcorepal-mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_CHARACTER_SET_SERVER=utf8mb4 -e MYSQL_COLLATION_SERVER=utf8mb4_unicode_ci -e TZ=Asia/Shanghai -v netcorepal_mysql_data:/var/lib/mysql -d mysql:8.0
|
||||
|
||||
# RabbitMQ
|
||||
docker run --restart unless-stopped --name netcorepal-rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest -v netcorepal_rabbitmq_data:/var/lib/rabbitmq -d rabbitmq:3.12-management-alpine
|
||||
```
|
||||
|
||||
### 服务访问信息
|
||||
|
||||
启动后,可以通过以下地址访问各个服务:
|
||||
|
||||
- **Redis**: `localhost:6379`
|
||||
- **MySQL**: `localhost:3306` (root/123456)
|
||||
- **RabbitMQ AMQP**: `localhost:5672` (guest/guest)
|
||||
- **RabbitMQ 管理界面**: http://localhost:15672 (guest/guest)
|
||||
- **SQL Server**: `localhost:1433` (sa/Test123456!)
|
||||
- **PostgreSQL**: `localhost:5432` (postgres/123456)
|
||||
- **Kafka**: `localhost:9092`
|
||||
- **Kafka UI**: http://localhost:8080
|
||||
|
||||
## IDE 代码片段配置
|
||||
|
||||
本模板提供了丰富的代码片段,帮助您快速生成常用的代码结构。
|
||||
|
||||
### Visual Studio 配置
|
||||
|
||||
运行以下 PowerShell 命令自动安装代码片段:
|
||||
|
||||
```powershell
|
||||
cd vs-snippets
|
||||
.\Install-VSSnippets.ps1
|
||||
```
|
||||
|
||||
或者手动安装:
|
||||
|
||||
1. 打开 Visual Studio
|
||||
2. 转到 `工具` > `代码片段管理器`
|
||||
3. 导入 `vs-snippets/NetCorePalTemplates.snippet` 文件
|
||||
|
||||
### VS Code 配置
|
||||
|
||||
VS Code 的代码片段已预配置在 `.vscode/csharp.code-snippets` 文件中,打开项目时自动生效。
|
||||
|
||||
### JetBrains Rider 配置
|
||||
|
||||
Rider 用户可以直接使用 `Fengling.Activity.sln.DotSettings` 文件中的 Live Templates 配置。
|
||||
|
||||
### 可用的代码片段
|
||||
|
||||
#### NetCorePal (ncp) 快捷键
|
||||
| 快捷键 | 描述 | 生成内容 |
|
||||
|--------|------|----------|
|
||||
| `ncpcmd` | NetCorePal 命令 | ICommand 实现(含验证器和处理器) |
|
||||
| `ncpcmdres` | 命令(含返回值) | ICommand<Response> 实现 |
|
||||
| `ncpar` | 聚合根 | Entity<Id> 和 IAggregateRoot |
|
||||
| `ncprepo` | NetCorePal 仓储 | IRepository 接口和实现 |
|
||||
| `ncpie` | 集成事件 | IntegrationEvent 和处理器 |
|
||||
| `ncpdeh` | 域事件处理器 | IDomainEventHandler 实现 |
|
||||
| `ncpiec` | 集成事件转换器 | IIntegrationEventConverter |
|
||||
| `ncpde` | 域事件 | IDomainEvent 记录 |
|
||||
|
||||
#### Endpoint (ep) 快捷键
|
||||
| 快捷键 | 描述 | 生成内容 |
|
||||
|--------|------|----------|
|
||||
| `epp` | FastEndpoint(NCP风格) | 完整的垂直切片实现 |
|
||||
| `epreq` | 仅请求端点 | Endpoint<Request> |
|
||||
| `epres` | 仅响应端点 | EndpointWithoutRequest<Response> |
|
||||
| `epdto` | 端点 DTOs | Request 和 Response 类 |
|
||||
| `epval` | 端点验证器 | Validator<Request> |
|
||||
| `epmap` | 端点映射器 | Mapper<Request, Response, Entity> |
|
||||
| `epfull` | 完整端点切片 | 带映射器的完整实现 |
|
||||
| `epsum` | 端点摘要 | Summary<Endpoint, Request> |
|
||||
| `epnoreq` | 无请求端点 | EndpointWithoutRequest |
|
||||
| `epreqres` | 请求响应端点 | Endpoint<Request, Response> |
|
||||
| `epdat` | 端点数据 | 静态数据类 |
|
||||
|
||||
更多详细配置请参考:[vs-snippets/README.md](vs-snippets/README.md)
|
||||
|
||||
## 依赖对框架与组件
|
||||
|
||||
+ [NetCorePal Cloud Framework](https://github.com/netcorepal/netcorepal-cloud-framework)
|
||||
+ [ASP.NET Core](https://github.com/dotnet/aspnetcore)
|
||||
+ [EFCore](https://github.com/dotnet/efcore)
|
||||
+ [CAP](https://github.com/dotnetcore/CAP)
|
||||
+ [MediatR](https://github.com/jbogard/MediatR)
|
||||
+ [FluentValidation](https://docs.fluentvalidation.net/en/latest)
|
||||
+ [Swashbuckle.AspNetCore.Swagger](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)
|
||||
|
||||
## 数据库迁移
|
||||
|
||||
```shell
|
||||
# 安装工具 SEE: https://learn.microsoft.com/zh-cn/ef/core/cli/dotnet#installing-the-tools
|
||||
dotnet tool install --global dotnet-ef --version 9.0.0
|
||||
|
||||
# 强制更新数据库
|
||||
dotnet ef database update -p src/Fengling.Activity.Infrastructure
|
||||
|
||||
# 创建迁移 SEE:https://learn.microsoft.com/zh-cn/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli
|
||||
dotnet ef migrations add InitialCreate -p src/Fengling.Activity.Infrastructure
|
||||
```
|
||||
|
||||
## 代码分析可视化
|
||||
|
||||
框架提供了强大的代码流分析和可视化功能,帮助开发者直观地理解DDD架构中的组件关系和数据流向。
|
||||
|
||||
### 🎯 核心特性
|
||||
|
||||
+ **自动代码分析**:通过源生成器自动分析代码结构,识别控制器、命令、聚合根、事件等组件
|
||||
+ **多种图表类型**:支持架构流程图、命令链路图、事件流程图、类图等多种可视化图表
|
||||
+ **交互式HTML可视化**:生成完整的交互式HTML页面,内置导航和图表预览功能
|
||||
+ **一键在线编辑**:集成"View in Mermaid Live"按钮,支持一键跳转到在线编辑器
|
||||
|
||||
### 🚀 快速开始
|
||||
|
||||
安装命令行工具来生成独立的HTML文件:
|
||||
|
||||
```bash
|
||||
# 安装全局工具
|
||||
dotnet tool install -g NetCorePal.Extensions.CodeAnalysis.Tools
|
||||
|
||||
# 进入项目目录并生成可视化文件
|
||||
cd src/Fengling.Activity.Web
|
||||
netcorepal-codeanalysis generate --output architecture.html
|
||||
```
|
||||
|
||||
### ✨ 主要功能
|
||||
|
||||
+ **交互式HTML页面**:
|
||||
+ 左侧树形导航,支持不同图表类型切换
|
||||
+ 内置Mermaid.js实时渲染
|
||||
+ 响应式设计,适配不同设备
|
||||
+ 专业的现代化界面
|
||||
|
||||
+ **一键在线编辑**:
|
||||
+ 每个图表右上角的"View in Mermaid Live"按钮
|
||||
+ 智能压缩算法优化URL长度
|
||||
+ 自动跳转到[Mermaid Live Editor](https://mermaid.live/)
|
||||
+ 支持在线编辑、导出图片、生成分享链接
|
||||
|
||||
### 📖 详细文档
|
||||
|
||||
完整的使用说明和示例请参考:
|
||||
|
||||
+ [代码流分析文档](https://netcorepal.github.io/netcorepal-cloud-framework/zh/code-analysis/code-flow-analysis/)
|
||||
+ [代码分析工具文档](https://netcorepal.github.io/netcorepal-cloud-framework/zh/code-analysis/code-analysis-tools/)
|
||||
|
||||
## 关于监控
|
||||
|
||||
这里使用了`prometheus-net`作为与基础设施prometheus集成的监控方案,默认通过地址 `/metrics` 输出监控指标。
|
||||
|
||||
更多信息请参见:[https://github.com/prometheus-net/prometheus-net](https://github.com/prometheus-net/prometheus-net)
|
||||
|
||||
|
||||
58
docker-compose.gateway.yml
Normal file
58
docker-compose.gateway.yml
Normal file
@ -0,0 +1,58 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Activity Service
|
||||
activity-service:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "5001:8080"
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ConnectionStrings__PostgreSQL=Host=postgres;Port=15432;Database=fengling_activity;Username=postgres;Password=postgres
|
||||
- ConnectionStrings__Redis=Host=redis;Port=6379
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
networks:
|
||||
- fengling-network
|
||||
|
||||
# YARP Gateway
|
||||
gateway:
|
||||
image: mcr.microsoft.com/dotnet/aspnet:10.0
|
||||
ports:
|
||||
- "5000:8080"
|
||||
volumes:
|
||||
- ./gateway-config.json:/app/gateway-config.json:ro
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
networks:
|
||||
- fengling-network
|
||||
|
||||
# PostgreSQL (shared instance)
|
||||
postgres:
|
||||
image: postgres:14
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- fengling-network
|
||||
|
||||
# Redis (shared instance)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
networks:
|
||||
- fengling-network
|
||||
|
||||
networks:
|
||||
fengling-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
273
docs/GATEWAY_INTEGRATION.md
Normal file
273
docs/GATEWAY_INTEGRATION.md
Normal file
@ -0,0 +1,273 @@
|
||||
# Activity Service Gateway Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes how to integrate the Activity Engine microservice with the YARP reverse proxy gateway.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Client Request
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ YARP Gateway │
|
||||
│ │
|
||||
│ Route: /api/activity/** │
|
||||
│ Cluster: activity-service │
|
||||
└─────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ Activity Service │
|
||||
│ http://localhost:5001 │
|
||||
│ │
|
||||
│ GET /api/activity/campaigns │
|
||||
│ POST /api/activity/campaigns │
|
||||
│ GET /api/activity/campaigns/.. │
|
||||
└─────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Configuration Steps
|
||||
|
||||
### Option 1: Using Command Line Script
|
||||
|
||||
#### Bash (Linux/macOS)
|
||||
```bash
|
||||
# Make script executable
|
||||
chmod +x scripts/gateway/register-activity-service.sh
|
||||
|
||||
# Register with global route (all tenants)
|
||||
./scripts/gateway/register-activity-service.sh \
|
||||
--gateway-url "http://localhost:5000" \
|
||||
--service-name "activity" \
|
||||
--cluster-id "activity-service" \
|
||||
--address "http://localhost:5001"
|
||||
|
||||
# Register with tenant-specific route
|
||||
./scripts/gateway/register-activity-service.sh \
|
||||
--gateway-url "http://localhost:5000" \
|
||||
--tenant-code "tenant001" \
|
||||
--service-name "activity" \
|
||||
--address "http://localhost:5001"
|
||||
```
|
||||
|
||||
#### PowerShell (Windows)
|
||||
```powershell
|
||||
# Register with global route
|
||||
pwsh scripts/gateway/Register-ActivityService.ps1 `
|
||||
-GatewayUrl "http://localhost:5000" `
|
||||
-ServiceName "activity" `
|
||||
-ClusterId "activity-service" `
|
||||
-InstanceAddress "http://localhost:5001" `
|
||||
-IsGlobal
|
||||
|
||||
# Register with tenant-specific route
|
||||
pwsh scripts/gateway/Register-ActivityService.ps1 `
|
||||
-GatewayUrl "http://localhost:5000" `
|
||||
-TenantCode "tenant001" `
|
||||
-ServiceName "activity" `
|
||||
-InstanceAddress "http://localhost:5001"
|
||||
```
|
||||
|
||||
### Option 2: Using Gateway Management API
|
||||
|
||||
#### 1. Add Service Instance
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/gateway/clusters/activity-service/instances" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"destinationId": "activity-1",
|
||||
"address": "http://localhost:5001",
|
||||
"weight": 1
|
||||
}'
|
||||
```
|
||||
|
||||
#### 2. Add Global Route
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/gateway/routes/global" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"serviceName": "activity",
|
||||
"clusterId": "activity-service",
|
||||
"pathPattern": "/api/activity/{**path}"
|
||||
}'
|
||||
```
|
||||
|
||||
#### 3. Reload Configuration
|
||||
```bash
|
||||
curl -X POST "http://localhost:5000/api/gateway/reload"
|
||||
```
|
||||
|
||||
### Option 3: Database Direct Insert
|
||||
|
||||
#### Insert Service Instance
|
||||
```sql
|
||||
INSERT INTO "ServiceInstances" (
|
||||
"Id",
|
||||
"ClusterId",
|
||||
"DestinationId",
|
||||
"Address",
|
||||
"Health",
|
||||
"Weight",
|
||||
"Status",
|
||||
"CreatedTime"
|
||||
)
|
||||
VALUES (
|
||||
EXTRACT(EPOCH FROM NOW())::bigint * 1000,
|
||||
'activity-service',
|
||||
'activity-1',
|
||||
'http://localhost:5001',
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
NOW()
|
||||
);
|
||||
```
|
||||
|
||||
#### Insert Global Route
|
||||
```sql
|
||||
INSERT INTO "TenantRoutes" (
|
||||
"Id",
|
||||
"TenantCode",
|
||||
"ServiceName",
|
||||
"ClusterId",
|
||||
"PathPattern",
|
||||
"Priority",
|
||||
"Status",
|
||||
"IsGlobal",
|
||||
"CreatedTime"
|
||||
)
|
||||
VALUES (
|
||||
EXTRACT(EPOCH FROM NOW())::bigint * 1000 + 1,
|
||||
'',
|
||||
'activity',
|
||||
'activity-service',
|
||||
'/api/activity/{**path}',
|
||||
0,
|
||||
1,
|
||||
true,
|
||||
NOW()
|
||||
);
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
After configuration, the following endpoints will be available through the gateway:
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| POST | `/api/activity/api/campaigns` | Create a new campaign |
|
||||
| GET | `/api/activity/api/campaigns` | List all campaigns |
|
||||
| GET | `/api/activity/api/campaigns/{id}` | Get campaign by ID |
|
||||
| POST | `/api/activity/api/campaigns/{id}/publish` | Publish a campaign |
|
||||
|
||||
## Health Check
|
||||
|
||||
The Activity service exposes a health check endpoint at `/health`:
|
||||
|
||||
```bash
|
||||
# Direct access
|
||||
curl http://localhost:5001/health
|
||||
|
||||
# Through gateway
|
||||
curl http://localhost:5000/api/activity/health
|
||||
```
|
||||
|
||||
## Multiple Instances
|
||||
|
||||
For high availability, add multiple service instances:
|
||||
|
||||
```bash
|
||||
# Add second instance
|
||||
curl -X POST "http://localhost:5000/api/gateway/clusters/activity-service/instances" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"destinationId": "activity-2",
|
||||
"address": "http://localhost:5002",
|
||||
"weight": 1
|
||||
}'
|
||||
|
||||
# Reload configuration
|
||||
curl -X POST "http://localhost:5000/api/gateway/reload"
|
||||
```
|
||||
|
||||
## Load Balancing
|
||||
|
||||
The gateway uses **Distributed Weighted Round Robin** load balancing by default. Adjust weights for traffic distribution:
|
||||
|
||||
```bash
|
||||
# Increase weight for a high-performance instance
|
||||
curl -X PUT "http://localhost:5000/api/gateway/instances/12345" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"weight": 2}'
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### View Routes
|
||||
```bash
|
||||
# Global routes
|
||||
curl http://localhost:5000/api/gateway/routes/global
|
||||
|
||||
# Tenant routes
|
||||
curl http://localhost:5000/api/gateway/tenants/{tenantCode}/routes
|
||||
```
|
||||
|
||||
### View Instances
|
||||
```bash
|
||||
curl http://localhost:5000/api/gateway/clusters/activity-service/instances
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Route Not Working
|
||||
1. Check if route is configured: `GET /api/gateway/routes/global`
|
||||
2. Verify instance health: `GET /api/gateway/clusters/{clusterId}/instances`
|
||||
3. Reload configuration: `POST /api/gateway/reload`
|
||||
|
||||
### 404 Not Found
|
||||
- Ensure the path pattern matches the request URL
|
||||
- Check if the route is active (Status = 1)
|
||||
|
||||
### 502 Bad Gateway
|
||||
- Verify the service instance is running
|
||||
- Check the service address is accessible from the gateway
|
||||
- Verify health check endpoint: `GET /health`
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### GwServiceInstance Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| ClusterId | string | Unique cluster identifier |
|
||||
| DestinationId | string | Unique destination identifier |
|
||||
| Address | string | Service address (http://host:port) |
|
||||
| Weight | int | Load balancing weight (default: 1) |
|
||||
| Health | int | Health status (1 = healthy) |
|
||||
| Status | int | Active status (1 = active) |
|
||||
|
||||
### GwTenantRoute Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| TenantCode | string | Tenant identifier (empty for global) |
|
||||
| ServiceName | string | Logical service name |
|
||||
| ClusterId | string | Target cluster identifier |
|
||||
| PathPattern | string | URL path pattern with placeholders |
|
||||
| Priority | int | Route priority (lower = higher priority) |
|
||||
| IsGlobal | bool | Whether route applies to all tenants |
|
||||
|
||||
## Path Pattern Syntax
|
||||
|
||||
YARP uses ASP.NET Core routing syntax for path patterns:
|
||||
|
||||
| Pattern | Description |
|
||||
|---------|-------------|
|
||||
| `/api/activity` | Exact match |
|
||||
| `/api/activity/{**path}` | Catch-all segment |
|
||||
| `/api/activity/{id}` | Parameter segment |
|
||||
| `/api/activity/{id}/rewards` | Multiple parameters |
|
||||
|
||||
For more details, see [ASP.NET Core Routing](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/routing)
|
||||
198
docs/NAMING_CONVENTION.md
Normal file
198
docs/NAMING_CONVENTION.md
Normal file
@ -0,0 +1,198 @@
|
||||
# Service Naming Convention
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the naming convention for all microservices in the Fengling platform.
|
||||
|
||||
## URL Structure
|
||||
|
||||
```
|
||||
{ServicePrefix}/{Version}/{Resource}/{Action}
|
||||
```
|
||||
|
||||
### Components
|
||||
|
||||
| Component | Description | Examples |
|
||||
|-----------|-------------|----------|
|
||||
| ServicePrefix | Unique service identifier | `activity`, `member`, `order` |
|
||||
| Version | API version | `v1`, `v2` |
|
||||
| Resource | Domain entity or resource | `campaigns`, `users`, `orders` |
|
||||
| Action | Optional action endpoint | `publish`, `cancel` |
|
||||
|
||||
## Service Registry
|
||||
|
||||
| Service | Prefix | Port | Health Endpoint |
|
||||
|---------|--------|------|-----------------|
|
||||
| Activity | `activity` | 5001 | `/health` |
|
||||
| Member | `member` | 5002 | `/health` |
|
||||
| Order | `order` | 5003 | `/health` |
|
||||
| Payment | `payment` | 5004 | `/health` |
|
||||
| RiskControl | `risk` | 5005 | `/health` |
|
||||
|
||||
## Cluster Naming Convention
|
||||
|
||||
```
|
||||
{ServicePrefix}-service
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `activity-service`
|
||||
- `member-service`
|
||||
- `order-service`
|
||||
- `payment-service`
|
||||
- `risk-service`
|
||||
|
||||
## Gateway Route Configuration
|
||||
|
||||
### Global Route Pattern
|
||||
```
|
||||
/{ServicePrefix}/{**path} -> {ServicePrefix}-service
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
**Activity Service:**
|
||||
```yaml
|
||||
Route:
|
||||
PathPattern: /activity/v1/{**path}
|
||||
ClusterId: activity-service
|
||||
|
||||
Endpoints:
|
||||
GET /activity/v1/campaigns # List campaigns
|
||||
POST /activity/v1/campaigns # Create campaign
|
||||
GET /activity/v1/campaigns/{id} # Get campaign
|
||||
POST /activity/v1/campaigns/{id}/publish # Publish campaign
|
||||
```
|
||||
|
||||
**Member Service:**
|
||||
```yaml
|
||||
Route:
|
||||
PathPattern: /member/v1/{**path}
|
||||
ClusterId: member-service
|
||||
|
||||
Endpoints:
|
||||
GET /member/v1/users # List users
|
||||
POST /member/v1/users # Create user
|
||||
GET /member/v1/users/{id} # Get user
|
||||
PUT /member/v1/users/{id} # Update user
|
||||
```
|
||||
|
||||
**Order Service:**
|
||||
```yaml
|
||||
Route:
|
||||
PathPattern: /order/v1/{**path}
|
||||
ClusterId: order-service
|
||||
|
||||
Endpoints:
|
||||
GET /order/v1/orders # List orders
|
||||
POST /order/v1/orders # Create order
|
||||
GET /order/v1/orders/{id} # Get order
|
||||
PUT /order/v1/orders/{id}/cancel # Cancel order
|
||||
```
|
||||
|
||||
## Gateway Registration
|
||||
|
||||
### Activity Service Registration Script
|
||||
|
||||
```bash
|
||||
# Register Activity Service
|
||||
./scripts/gateway/register-service.sh \
|
||||
--service-prefix "activity" \
|
||||
--gateway-url "http://localhost:5000" \
|
||||
--address "http://localhost:5001"
|
||||
|
||||
# After registration, access endpoints at:
|
||||
# GET http://localhost:5000/activity/v1/campaigns
|
||||
# POST http://localhost:5000/activity/v1/campaigns
|
||||
```
|
||||
|
||||
## Versioning Strategy
|
||||
|
||||
### Version Lifecycle
|
||||
|
||||
| Version | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| v1 | Active | Current stable API |
|
||||
| v2 | Planning | Next major version |
|
||||
| beta | Testing | Beta releases |
|
||||
|
||||
### Versioning Rules
|
||||
|
||||
1. **Major Version** (`v1`, `v2`): Breaking changes require version bump
|
||||
2. **Minor Updates**: Backward-compatible additions don't require version change
|
||||
3. **Deprecation**: Old versions should be supported for at least 6 months
|
||||
|
||||
## Multi-Tenancy Support
|
||||
|
||||
### Tenant-Specific Routes
|
||||
|
||||
For dedicated tenant instances:
|
||||
```
|
||||
/tenant/{tenantCode}/{ServicePrefix}/{Version}/{**path}
|
||||
```
|
||||
|
||||
Example:
|
||||
```
|
||||
/tenant/acme/activity/v1/campaigns
|
||||
```
|
||||
|
||||
## Health Check Endpoints
|
||||
|
||||
| Service | Health Path |
|
||||
|---------|-------------|
|
||||
| Activity | `/health` |
|
||||
| Member | `/health` |
|
||||
| Order | `/health` |
|
||||
| Payment | `/health` |
|
||||
| Risk | `/health` |
|
||||
|
||||
## Monitoring & Metrics
|
||||
|
||||
### Service Labels
|
||||
|
||||
| Label | Value |
|
||||
|-------|-------|
|
||||
| service | `activity` |
|
||||
| version | `v1` |
|
||||
| tenant | `{tenantCode}` |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Consistent Naming
|
||||
- Use lowercase for all components
|
||||
- Use hyphens for multi-word names: `risk-control` NOT `riskControl`
|
||||
- Avoid abbreviations: `campaigns` NOT `cmps`
|
||||
|
||||
### 2. Resource Naming
|
||||
- Use plural nouns for collections: `campaigns` NOT `campaignList`
|
||||
- Use singular for single resources: `campaign/{id}` NOT `campaigns/{id}`
|
||||
|
||||
### 3. Action Endpoints
|
||||
- Use HTTP verbs for CRUD: `GET`, `POST`, `PUT`, `DELETE`
|
||||
- Use specific verbs for actions: `publish`, `cancel`, `activate`
|
||||
|
||||
### 4. Path Parameters
|
||||
- Use descriptive names: `/campaigns/{campaignId}` NOT `/campaigns/{id}`
|
||||
- Consistent parameter naming across services
|
||||
|
||||
## Migration Guide
|
||||
|
||||
### Updating Existing Routes
|
||||
|
||||
**Before:**
|
||||
```
|
||||
/api/campaigns
|
||||
/api/orders
|
||||
```
|
||||
|
||||
**After:**
|
||||
```
|
||||
/activity/v1/campaigns
|
||||
/order/v1/orders
|
||||
```
|
||||
|
||||
**Migration Strategy:**
|
||||
1. Register new routes with v1
|
||||
2. Keep old routes active (alias)
|
||||
3. Update clients to use new format
|
||||
4. Remove old routes after transition period
|
||||
6
eng/versions.props
Normal file
6
eng/versions.props
Normal file
@ -0,0 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<VersionSuffix></VersionSuffix>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
7
global.json
Normal file
7
global.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.100",
|
||||
"allowPrerelease": true,
|
||||
"rollForward": "latestMinor"
|
||||
}
|
||||
}
|
||||
151
scripts/EXAMPLES.md
Normal file
151
scripts/EXAMPLES.md
Normal file
@ -0,0 +1,151 @@
|
||||
# Usage Examples
|
||||
|
||||
This document provides practical examples for using the infrastructure initialization scripts.
|
||||
|
||||
## Quick Start Examples
|
||||
|
||||
### Default Setup (MySQL + Redis + RabbitMQ)
|
||||
```bash
|
||||
# Using Docker Compose (Recommended)
|
||||
docker compose up -d
|
||||
|
||||
# Using shell script (Linux/macOS)
|
||||
./init-infrastructure.sh
|
||||
|
||||
# Using PowerShell (Windows)
|
||||
.\init-infrastructure.ps1
|
||||
```
|
||||
|
||||
### Different Database Options
|
||||
```bash
|
||||
# Use PostgreSQL instead of MySQL
|
||||
docker compose --profile postgres up -d
|
||||
|
||||
# Use SQL Server instead of MySQL
|
||||
docker compose --profile sqlserver up -d
|
||||
|
||||
# With PowerShell
|
||||
.\init-infrastructure.ps1 -Postgres
|
||||
.\init-infrastructure.ps1 -SqlServer
|
||||
```
|
||||
|
||||
### Different Message Queue Options
|
||||
```bash
|
||||
# Use Kafka instead of RabbitMQ
|
||||
docker compose --profile kafka up -d
|
||||
|
||||
# With PowerShell
|
||||
.\init-infrastructure.ps1 -Kafka
|
||||
```
|
||||
|
||||
### Cleanup Examples
|
||||
```bash
|
||||
# Stop services, keep data
|
||||
docker compose down
|
||||
./clean-infrastructure.sh
|
||||
.\clean-infrastructure.ps1
|
||||
|
||||
# Stop services and remove all data
|
||||
docker compose down -v
|
||||
./clean-infrastructure.sh --volumes
|
||||
.\clean-infrastructure.ps1 -Volumes
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Typical Development Session
|
||||
```bash
|
||||
# 1. Start infrastructure
|
||||
cd scripts
|
||||
docker compose up -d
|
||||
|
||||
# 2. Develop your application
|
||||
cd ../src/Fengling.Activity.Web
|
||||
dotnet run
|
||||
|
||||
# 3. Run tests
|
||||
cd ../../
|
||||
dotnet test
|
||||
|
||||
# 4. Stop infrastructure (keep data)
|
||||
cd scripts
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Clean Development Environment
|
||||
```bash
|
||||
# Clean slate - remove everything including data
|
||||
cd scripts
|
||||
docker compose down -v
|
||||
|
||||
# Start fresh
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Check Service Status
|
||||
```bash
|
||||
# List running containers
|
||||
docker ps
|
||||
|
||||
# Check specific service logs
|
||||
docker logs netcorepal-mysql
|
||||
docker logs netcorepal-redis
|
||||
docker logs netcorepal-rabbitmq
|
||||
|
||||
# Check service health
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### Port Already in Use
|
||||
```bash
|
||||
# Find what's using the port
|
||||
netstat -tulpn | grep :3306 # Linux
|
||||
netstat -ano | findstr :3306 # Windows
|
||||
|
||||
# Stop conflicting services
|
||||
sudo systemctl stop mysql # Linux
|
||||
net stop mysql80 # Windows
|
||||
```
|
||||
|
||||
#### Container Won't Start
|
||||
```bash
|
||||
# Remove problematic container and restart
|
||||
docker rm -f netcorepal-mysql
|
||||
docker compose up -d mysql
|
||||
```
|
||||
|
||||
#### Data Corruption
|
||||
```bash
|
||||
# Remove data volumes and start fresh
|
||||
docker compose down -v
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Connection Strings for Development
|
||||
|
||||
Update your `appsettings.Development.json` with these connection strings:
|
||||
|
||||
```json
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"Redis": "localhost:6379,defaultDatabase=0",
|
||||
"MySql": "Server=localhost;Port=3306;Database=abctemplate;Uid=root;Pwd=123456;",
|
||||
"SqlServer": "Server=localhost,1433;Database=abctemplate;User Id=sa;Password=Test123456!;TrustServerCertificate=true;",
|
||||
"PostgreSQL": "Host=localhost;Port=15432;Database=abctemplate;Username=postgres;Password=123456;"
|
||||
},
|
||||
"RabbitMQ": {
|
||||
"HostName": "localhost",
|
||||
"Port": 5672,
|
||||
"UserName": "guest",
|
||||
"Password": "guest",
|
||||
"VirtualHost": "/"
|
||||
},
|
||||
"Kafka": {
|
||||
"BootstrapServers": "localhost:9092"
|
||||
}
|
||||
}
|
||||
```
|
||||
56
scripts/README.md
Normal file
56
scripts/README.md
Normal file
@ -0,0 +1,56 @@
|
||||
# Infrastructure Initialization Scripts
|
||||
|
||||
This directory contains scripts to help developers quickly set up the infrastructure needed for development and debugging.
|
||||
|
||||
## Available Scripts
|
||||
|
||||
- `docker-compose.yml` - Complete infrastructure setup using Docker Compose
|
||||
- `init-infrastructure.sh` - Shell script for Linux/macOS
|
||||
- `init-infrastructure.ps1` - PowerShell script for Windows
|
||||
- `clean-infrastructure.sh` - Cleanup script for Linux/macOS
|
||||
- `clean-infrastructure.ps1` - Cleanup script for Windows
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using Docker Compose (Recommended)
|
||||
```bash
|
||||
# Start all infrastructure services
|
||||
docker-compose up -d
|
||||
|
||||
# Stop all services
|
||||
docker-compose down
|
||||
|
||||
# Stop and remove volumes (clean start)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
### Using Individual Scripts
|
||||
```bash
|
||||
# Linux/macOS
|
||||
./init-infrastructure.sh
|
||||
|
||||
# Windows PowerShell
|
||||
.\init-infrastructure.ps1
|
||||
```
|
||||
|
||||
## Infrastructure Components
|
||||
|
||||
The scripts will set up the following services:
|
||||
|
||||
### Database Options
|
||||
- **MySQL** (default): Port 3306, root password: 123456
|
||||
- **SQL Server**: Port 1433, SA password: Test123456!
|
||||
- **PostgreSQL**: Port 5432, postgres password: 123456
|
||||
|
||||
### Cache & Message Queue
|
||||
- **Redis**: Port 6379, no password
|
||||
- **RabbitMQ**: Ports 5672 (AMQP), 15672 (Management UI), guest/guest
|
||||
- **Kafka**: Port 9092 (when using Kafka option)
|
||||
|
||||
### Management Interfaces
|
||||
- RabbitMQ Management: http://localhost:15672 (guest/guest)
|
||||
- Kafka UI (if included): http://localhost:8080
|
||||
|
||||
## Configuration
|
||||
|
||||
The default configuration matches the test containers setup used in the project's integration tests.
|
||||
195
scripts/clean-infrastructure.ps1
Normal file
195
scripts/clean-infrastructure.ps1
Normal file
@ -0,0 +1,195 @@
|
||||
# NetCorePal Template - Infrastructure Cleanup Script (PowerShell)
|
||||
# This script stops and removes all infrastructure containers
|
||||
|
||||
param(
|
||||
[switch]$Volumes,
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Color functions for output
|
||||
function Write-Info {
|
||||
param([string]$Message)
|
||||
Write-Host "[INFO] $Message" -ForegroundColor Blue
|
||||
}
|
||||
|
||||
function Write-Success {
|
||||
param([string]$Message)
|
||||
Write-Host "[SUCCESS] $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Warning {
|
||||
param([string]$Message)
|
||||
Write-Host "[WARNING] $Message" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
function Write-Error {
|
||||
param([string]$Message)
|
||||
Write-Host "[ERROR] $Message" -ForegroundColor Red
|
||||
}
|
||||
|
||||
function Show-Help {
|
||||
Write-Host "NetCorePal Template - Infrastructure Cleanup" -ForegroundColor Green
|
||||
Write-Host "===========================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Usage: .\clean-infrastructure.ps1 [OPTIONS]"
|
||||
Write-Host ""
|
||||
Write-Host "Clean up NetCorePal Template infrastructure containers"
|
||||
Write-Host ""
|
||||
Write-Host "Options:"
|
||||
Write-Host " -Help Show this help message"
|
||||
Write-Host " -Volumes Also remove data volumes (WARNING: This will delete all data!)"
|
||||
Write-Host ""
|
||||
Write-Host "Examples:"
|
||||
Write-Host " .\clean-infrastructure.ps1 # Stop and remove containers, keep data"
|
||||
Write-Host " .\clean-infrastructure.ps1 -Volumes # Stop and remove containers and all data volumes"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Remove-Container {
|
||||
param([string]$ContainerName)
|
||||
|
||||
try {
|
||||
$exists = docker ps -a --format "table {{.Names}}" | Select-String "^$ContainerName$"
|
||||
if ($exists) {
|
||||
Write-Info "Stopping and removing $ContainerName..."
|
||||
|
||||
# Stop the container
|
||||
try {
|
||||
docker stop $ContainerName 2>$null | Out-Null
|
||||
Write-Info "$ContainerName stopped"
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not stop $ContainerName (may already be stopped)"
|
||||
}
|
||||
|
||||
# Remove the container
|
||||
try {
|
||||
docker rm $ContainerName 2>$null | Out-Null
|
||||
Write-Success "$ContainerName removed"
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not remove $ContainerName"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Info "$ContainerName not found, skipping..."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Error processing $ContainerName : $_"
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-Volumes {
|
||||
param([bool]$RemoveVolumes)
|
||||
|
||||
if ($RemoveVolumes) {
|
||||
Write-Info "Removing data volumes..."
|
||||
|
||||
$volumes = @(
|
||||
"netcorepal_redis_data",
|
||||
"netcorepal_mysql_data",
|
||||
"netcorepal_sqlserver_data",
|
||||
"netcorepal_postgres_data",
|
||||
"netcorepal_rabbitmq_data",
|
||||
"netcorepal_zookeeper_data",
|
||||
"netcorepal_zookeeper_logs",
|
||||
"netcorepal_kafka_data"
|
||||
)
|
||||
|
||||
foreach ($volume in $volumes) {
|
||||
try {
|
||||
$exists = docker volume ls --format "table {{.Name}}" | Select-String "^$volume$"
|
||||
if ($exists) {
|
||||
docker volume rm $volume 2>$null | Out-Null
|
||||
Write-Success "Volume $volume removed"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not remove volume $volume"
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Info "Preserving data volumes (use -Volumes to remove them)"
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-Network {
|
||||
try {
|
||||
$exists = docker network ls --format "table {{.Name}}" | Select-String "^netcorepal-network$"
|
||||
if ($exists) {
|
||||
Write-Info "Removing network netcorepal-network..."
|
||||
try {
|
||||
docker network rm netcorepal-network 2>$null | Out-Null
|
||||
Write-Success "Network removed"
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Could not remove network (may still be in use)"
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Error checking network: $_"
|
||||
}
|
||||
}
|
||||
|
||||
function Start-Cleanup {
|
||||
Write-Host ""
|
||||
Write-Info "Starting infrastructure cleanup..."
|
||||
Write-Host ""
|
||||
|
||||
# List of containers to clean up
|
||||
$containers = @(
|
||||
"netcorepal-redis",
|
||||
"netcorepal-mysql",
|
||||
"netcorepal-sqlserver",
|
||||
"netcorepal-postgres",
|
||||
"netcorepal-rabbitmq",
|
||||
"netcorepal-kafka",
|
||||
"netcorepal-kafka-ui",
|
||||
"netcorepal-zookeeper"
|
||||
)
|
||||
|
||||
# Clean up containers
|
||||
foreach ($container in $containers) {
|
||||
Remove-Container -ContainerName $container
|
||||
}
|
||||
|
||||
# Clean up volumes if requested
|
||||
Remove-Volumes -RemoveVolumes $Volumes
|
||||
|
||||
# Clean up network
|
||||
Remove-Network
|
||||
|
||||
Write-Host ""
|
||||
Write-Success "🎉 Infrastructure cleanup completed!"
|
||||
Write-Host ""
|
||||
if ($Volumes) {
|
||||
Write-Warning "⚠️ All data has been removed. You'll need to reinitialize your databases."
|
||||
}
|
||||
else {
|
||||
Write-Info "💾 Data volumes preserved. Data will be available when you restart the infrastructure."
|
||||
}
|
||||
Write-Host ""
|
||||
Write-Info "Use '.\init-infrastructure.ps1' to restart the infrastructure"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
Write-Host "🧹 NetCorePal Template - Infrastructure Cleanup" -ForegroundColor Green
|
||||
Write-Host "===============================================" -ForegroundColor Green
|
||||
|
||||
if ($Help) {
|
||||
Show-Help
|
||||
exit 0
|
||||
}
|
||||
|
||||
try {
|
||||
Start-Cleanup
|
||||
}
|
||||
catch {
|
||||
Write-Error "An error occurred during cleanup: $_"
|
||||
exit 1
|
||||
}
|
||||
177
scripts/clean-infrastructure.sh
Normal file
177
scripts/clean-infrastructure.sh
Normal file
@ -0,0 +1,177 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NetCorePal Template - Infrastructure Cleanup Script
|
||||
# This script stops and removes all infrastructure containers
|
||||
|
||||
set -e
|
||||
|
||||
echo "🧹 NetCorePal Template - Infrastructure Cleanup"
|
||||
echo "==============================================="
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to stop and remove container
|
||||
cleanup_container() {
|
||||
local container_name=$1
|
||||
|
||||
if docker ps -a --format 'table {{.Names}}' | grep -q "^$container_name$"; then
|
||||
print_status "Stopping and removing $container_name..."
|
||||
|
||||
# Stop the container
|
||||
if docker stop $container_name > /dev/null 2>&1; then
|
||||
print_status "$container_name stopped"
|
||||
else
|
||||
print_warning "Could not stop $container_name (may already be stopped)"
|
||||
fi
|
||||
|
||||
# Remove the container
|
||||
if docker rm $container_name > /dev/null 2>&1; then
|
||||
print_success "$container_name removed"
|
||||
else
|
||||
print_warning "Could not remove $container_name"
|
||||
fi
|
||||
else
|
||||
print_status "$container_name not found, skipping..."
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to remove volumes
|
||||
cleanup_volumes() {
|
||||
local remove_volumes=$1
|
||||
|
||||
if [ "$remove_volumes" = "true" ]; then
|
||||
print_status "Removing data volumes..."
|
||||
|
||||
local volumes=(
|
||||
"netcorepal_redis_data"
|
||||
"netcorepal_mysql_data"
|
||||
"netcorepal_sqlserver_data"
|
||||
"netcorepal_postgres_data"
|
||||
"netcorepal_rabbitmq_data"
|
||||
"netcorepal_zookeeper_data"
|
||||
"netcorepal_zookeeper_logs"
|
||||
"netcorepal_kafka_data"
|
||||
)
|
||||
|
||||
for volume in "${volumes[@]}"; do
|
||||
if docker volume ls --format 'table {{.Name}}' | grep -q "^$volume$"; then
|
||||
if docker volume rm "$volume" > /dev/null 2>&1; then
|
||||
print_success "Volume $volume removed"
|
||||
else
|
||||
print_warning "Could not remove volume $volume"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
print_status "Preserving data volumes (use --volumes to remove them)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to remove network
|
||||
cleanup_network() {
|
||||
if docker network ls --format 'table {{.Name}}' | grep -q "^netcorepal-network$"; then
|
||||
print_status "Removing network netcorepal-network..."
|
||||
if docker network rm netcorepal-network > /dev/null 2>&1; then
|
||||
print_success "Network removed"
|
||||
else
|
||||
print_warning "Could not remove network (may still be in use)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Main cleanup function
|
||||
main() {
|
||||
local remove_volumes=false
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--volumes|-v)
|
||||
remove_volumes=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo
|
||||
echo "Clean up NetCorePal Template infrastructure containers"
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " -h, --help Show this help message"
|
||||
echo " -v, --volumes Also remove data volumes (WARNING: This will delete all data!)"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 # Stop and remove containers, keep data"
|
||||
echo " $0 --volumes # Stop and remove containers and all data volumes"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
print_error "Unknown option: $1"
|
||||
echo "Use --help for usage information"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo
|
||||
print_status "Starting infrastructure cleanup..."
|
||||
echo
|
||||
|
||||
# List of containers to clean up
|
||||
local containers=(
|
||||
"netcorepal-redis"
|
||||
"netcorepal-mysql"
|
||||
"netcorepal-sqlserver"
|
||||
"netcorepal-postgres"
|
||||
"netcorepal-rabbitmq"
|
||||
"netcorepal-kafka"
|
||||
"netcorepal-kafka-ui"
|
||||
"netcorepal-zookeeper"
|
||||
)
|
||||
|
||||
# Clean up containers
|
||||
for container in "${containers[@]}"; do
|
||||
cleanup_container "$container"
|
||||
done
|
||||
|
||||
# Clean up volumes if requested
|
||||
cleanup_volumes "$remove_volumes"
|
||||
|
||||
# Clean up network
|
||||
cleanup_network
|
||||
|
||||
echo
|
||||
print_success "🎉 Infrastructure cleanup completed!"
|
||||
echo
|
||||
if [ "$remove_volumes" = "true" ]; then
|
||||
print_warning "⚠️ All data has been removed. You'll need to reinitialize your databases."
|
||||
else
|
||||
print_status "💾 Data volumes preserved. Data will be available when you restart the infrastructure."
|
||||
fi
|
||||
echo
|
||||
print_status "Use './init-infrastructure.sh' to restart the infrastructure"
|
||||
}
|
||||
|
||||
# Execute main function with all arguments
|
||||
main "$@"
|
||||
167
scripts/docker-compose.yml
Normal file
167
scripts/docker-compose.yml
Normal file
@ -0,0 +1,167 @@
|
||||
services:
|
||||
# Redis - Always included for caching and sessions
|
||||
redis:
|
||||
image: redis:7.2-alpine
|
||||
container_name: netcorepal-redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --appendonly yes --databases 1024
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# MySQL Database (default option)
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: netcorepal-mysql
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 123456
|
||||
MYSQL_CHARACTER_SET_SERVER: utf8mb4
|
||||
MYSQL_COLLATION_SERVER: utf8mb4_unicode_ci
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./mysql-init:/docker-entrypoint-initdb.d:ro
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p123456"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# SQL Server (alternative database option)
|
||||
sqlserver:
|
||||
image: mcr.microsoft.com/mssql/server:2022-latest
|
||||
container_name: netcorepal-sqlserver
|
||||
ports:
|
||||
- "1433:1433"
|
||||
environment:
|
||||
ACCEPT_EULA: Y
|
||||
MSSQL_SA_PASSWORD: Test123456!
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- sqlserver_data:/var/opt/mssql
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- sqlserver
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P Test123456! -Q 'SELECT 1'"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# PostgreSQL (alternative database option)
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
container_name: netcorepal-postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: 123456
|
||||
POSTGRES_DB: postgres
|
||||
TZ: Asia/Shanghai
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./postgres-init:/docker-entrypoint-initdb.d:ro
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- postgres
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# RabbitMQ (default message queue option)
|
||||
rabbitmq:
|
||||
image: rabbitmq:3.12-management-alpine
|
||||
container_name: netcorepal-rabbitmq
|
||||
ports:
|
||||
- "5672:5672"
|
||||
- "15672:15672"
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: guest
|
||||
RABBITMQ_DEFAULT_PASS: guest
|
||||
volumes:
|
||||
- rabbitmq_data:/var/lib/rabbitmq
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "rabbitmq-diagnostics", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# Kafka (alternative message queue option)
|
||||
zookeeper:
|
||||
image: confluentinc/cp-zookeeper:7.4.0
|
||||
container_name: netcorepal-zookeeper
|
||||
environment:
|
||||
ZOOKEEPER_CLIENT_PORT: 2181
|
||||
ZOOKEEPER_TICK_TIME: 2000
|
||||
volumes:
|
||||
- zookeeper_data:/var/lib/zookeeper/data
|
||||
- zookeeper_logs:/var/lib/zookeeper/log
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- kafka
|
||||
|
||||
kafka:
|
||||
image: confluentinc/cp-kafka:7.4.0
|
||||
container_name: netcorepal-kafka
|
||||
depends_on:
|
||||
- zookeeper
|
||||
ports:
|
||||
- "9092:9092"
|
||||
environment:
|
||||
KAFKA_BROKER_ID: 1
|
||||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
|
||||
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
|
||||
KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true'
|
||||
volumes:
|
||||
- kafka_data:/var/lib/kafka/data
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- kafka
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "kafka-broker-api-versions --bootstrap-server localhost:9092"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
# Kafka UI (optional management interface)
|
||||
kafka-ui:
|
||||
image: provectuslabs/kafka-ui:latest
|
||||
container_name: netcorepal-kafka-ui
|
||||
depends_on:
|
||||
- kafka
|
||||
ports:
|
||||
- "8080:8080"
|
||||
environment:
|
||||
KAFKA_CLUSTERS_0_NAME: local
|
||||
KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
|
||||
restart: unless-stopped
|
||||
profiles:
|
||||
- kafka
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
mysql_data:
|
||||
sqlserver_data:
|
||||
postgres_data:
|
||||
rabbitmq_data:
|
||||
zookeeper_data:
|
||||
zookeeper_logs:
|
||||
kafka_data:
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: netcorepal-network
|
||||
135
scripts/gateway/Register-ActivityService.ps1
Normal file
135
scripts/gateway/Register-ActivityService.ps1
Normal file
@ -0,0 +1,135 @@
|
||||
# Activity服务接入YARP网关配置脚本
|
||||
# PowerShell: pwsh Register-ActivityService.ps1 -GatewayUrl "http://localhost:5000"
|
||||
|
||||
param(
|
||||
[string]$GatewayUrl = "http://localhost:5000",
|
||||
[string]$ServiceName = "activity",
|
||||
[string]$ClusterId = "activity-service",
|
||||
[string]$PathPattern = "/api/activity/{**path}",
|
||||
[string]$InstanceAddress = "http://localhost:5001",
|
||||
[string]$DestinationId = "activity-1",
|
||||
[int]$Weight = 1,
|
||||
[switch]$IsGlobal = $true,
|
||||
[string]$TenantCode = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Register-ActivityService {
|
||||
param(
|
||||
[string]$GatewayUrl,
|
||||
[string]$ServiceName,
|
||||
[string]$ClusterId,
|
||||
[string]$PathPattern,
|
||||
[string]$InstanceAddress,
|
||||
[string]$DestinationId,
|
||||
[int]$Weight,
|
||||
[bool]$IsGlobal,
|
||||
[string]$TenantCode
|
||||
)
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "Activity Service Gateway Configuration" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Step 1: Add Service Instance (Cluster)
|
||||
Write-Host "[Step 1] Adding service instance to cluster..." -ForegroundColor Yellow
|
||||
$instanceBody = @{
|
||||
destinationId = $DestinationId
|
||||
address = $InstanceAddress
|
||||
weight = $Weight
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$instanceResponse = Invoke-RestMethod -Uri "$GatewayUrl/api/gateway/clusters/$ClusterId/instances" `
|
||||
-Method Post `
|
||||
-ContentType "application/json" `
|
||||
-Body $instanceBody
|
||||
|
||||
Write-Host " ✓ Instance added: $DestinationId -> $InstanceAddress" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
if ($_.Exception.Response.StatusCode -eq "BadRequest") {
|
||||
Write-Host " ℹ Instance already exists, skipping..." -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
|
||||
# Step 2: Add Route
|
||||
Write-Host ""
|
||||
Write-Host "[Step 2] Adding gateway route..." -ForegroundColor Yellow
|
||||
|
||||
if ($IsGlobal) {
|
||||
$routeBody = @{
|
||||
serviceName = $ServiceName
|
||||
clusterId = $ClusterId
|
||||
pathPattern = $PathPattern
|
||||
} | ConvertTo-Json
|
||||
|
||||
$routeUrl = "$GatewayUrl/api/gateway/routes/global"
|
||||
$routeDescription = "global route"
|
||||
}
|
||||
else {
|
||||
$routeBody = @{
|
||||
serviceName = $ServiceName
|
||||
pathPattern = $PathPattern
|
||||
} | ConvertTo-Json
|
||||
|
||||
$routeUrl = "$GatewayUrl/api/gateway/tenants/$TenantCode/routes"
|
||||
$routeDescription = "tenant route for $TenantCode"
|
||||
}
|
||||
|
||||
try {
|
||||
$routeResponse = Invoke-RestMethod -Uri $routeUrl `
|
||||
-Method Post `
|
||||
-ContentType "application/json" `
|
||||
-Body $routeBody
|
||||
|
||||
Write-Host " ✓ Route added: $PathPattern -> $ClusterId" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
if ($_.Exception.Response.StatusCode -eq "BadRequest") {
|
||||
Write-Host " ℹ Route already exists, skipping..." -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
|
||||
# Step 3: Reload Config
|
||||
Write-Host ""
|
||||
Write-Host "[Step 3] Reloading gateway configuration..." -ForegroundColor Yellow
|
||||
|
||||
$reloadResponse = Invoke-RestMethod -Uri "$GatewayUrl/api/gateway/reload" -Method Post
|
||||
Write-Host " ✓ Configuration reloaded" -ForegroundColor Green
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host "Configuration Complete!" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Service: $ServiceName" -ForegroundColor White
|
||||
Write-Host "Cluster: $ClusterId" -ForegroundColor White
|
||||
Write-Host "Route: $routeDescription" -ForegroundColor White
|
||||
Write-Host "Pattern: $PathPattern" -ForegroundColor White
|
||||
Write-Host "Target: $InstanceAddress" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Test the service at:" -ForegroundColor Yellow
|
||||
Write-Host " $GatewayUrl$($PathPattern.Replace('{**path}', 'campaigns'))" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Execute
|
||||
Register-ActivityService -GatewayUrl $GatewayUrl `
|
||||
-ServiceName $ServiceName `
|
||||
-ClusterId $ClusterId `
|
||||
-PathPattern $PathPattern `
|
||||
-InstanceAddress $InstanceAddress `
|
||||
-DestinationId $DestinationId `
|
||||
-Weight $Weight `
|
||||
-IsGlobal $IsGlobal.IsPresent `
|
||||
-TenantCode $TenantCode
|
||||
118
scripts/gateway/Register-Service.ps1
Normal file
118
scripts/gateway/Register-Service.ps1
Normal file
@ -0,0 +1,118 @@
|
||||
# Service Gateway Registration Script
|
||||
param(
|
||||
[string]$Prefix = "activity",
|
||||
[string]$Version = "v1",
|
||||
[string]$GatewayUrl = "http://localhost:5000",
|
||||
[string]$Address = "http://localhost:5001",
|
||||
[string]$DestinationId = $null,
|
||||
[int]$Weight = 1,
|
||||
[switch]$Global = $true,
|
||||
[string]$TenantCode = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Derived values
|
||||
$ClusterId = "$Prefix-service"
|
||||
if ([string]::IsNullOrEmpty($DestinationId)) {
|
||||
$DestinationId = "$Prefix-1"
|
||||
}
|
||||
$PathPattern = "/$Prefix/$Version/{**path}"
|
||||
|
||||
Write-Host "╔════════════════════════════════════════════════════════════╗" -ForegroundColor Cyan
|
||||
Write-Host "║ Service Gateway Registration ║" -ForegroundColor Cyan
|
||||
Write-Host "╚════════════════════════════════════════════════════════════╝" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Step 1: Add Service Instance
|
||||
Write-Host "[1/3] Adding service instance..." -ForegroundColor Yellow
|
||||
|
||||
$instanceBody = @{
|
||||
destinationId = $DestinationId
|
||||
address = $Address
|
||||
weight = $Weight
|
||||
} | ConvertTo-Json
|
||||
|
||||
try {
|
||||
$null = Invoke-RestMethod -Uri "$GatewayUrl/api/gateway/clusters/$ClusterId/instances" `
|
||||
-Method Post `
|
||||
-ContentType "application/json" `
|
||||
-Body $instanceBody
|
||||
Write-Host " ✓ Instance: $DestinationId -> $Address" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
if ($_.Exception.Response.StatusCode -eq "BadRequest") {
|
||||
Write-Host " ℹ Instance may already exist" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
|
||||
# Step 2: Add Route
|
||||
Write-Host ""
|
||||
Write-Host "[2/3] Configuring gateway route..." -ForegroundColor Yellow
|
||||
|
||||
if ($Global) {
|
||||
$routeBody = @{
|
||||
serviceName = $Prefix
|
||||
clusterId = $ClusterId
|
||||
pathPattern = $PathPattern
|
||||
} | ConvertTo-Json
|
||||
$routeType = "Global"
|
||||
}
|
||||
else {
|
||||
$routeBody = @{
|
||||
serviceName = $Prefix
|
||||
pathPattern = $PathPattern
|
||||
} | ConvertTo-Json
|
||||
$routeType = "Tenant [$TenantCode]"
|
||||
}
|
||||
|
||||
try {
|
||||
$routeUrl = if ($Global) {
|
||||
"$GatewayUrl/api/gateway/routes/global"
|
||||
}
|
||||
else {
|
||||
"$GatewayUrl/api/gateway/tenants/$TenantCode/routes"
|
||||
}
|
||||
$null = Invoke-RestMethod -Uri $routeUrl `
|
||||
-Method Post `
|
||||
-ContentType "application/json" `
|
||||
-Body $routeBody
|
||||
Write-Host " ✓ Route: $PathPattern -> $ClusterId" -ForegroundColor Green
|
||||
}
|
||||
catch {
|
||||
if ($_.Exception.Response.StatusCode -eq "BadRequest") {
|
||||
Write-Host " ℹ Route may already exist" -ForegroundColor Gray
|
||||
}
|
||||
else {
|
||||
throw $_
|
||||
}
|
||||
}
|
||||
|
||||
# Step 3: Reload Config
|
||||
Write-Host ""
|
||||
Write-Host "[3/3] Reloading gateway configuration..." -ForegroundColor Yellow
|
||||
$null = Invoke-RestMethod -Uri "$GatewayUrl/api/gateway/reload" -Method Post
|
||||
Write-Host " ✓ Configuration reloaded" -ForegroundColor Green
|
||||
|
||||
# Summary
|
||||
Write-Host ""
|
||||
Write-Host "════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " Service Registered Successfully!" -ForegroundColor Cyan
|
||||
Write-Host "════════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host " Service: $Prefix" -ForegroundColor White
|
||||
Write-Host " Version: $Version" -ForegroundColor White
|
||||
Write-Host " Cluster: $ClusterId" -ForegroundColor White
|
||||
Write-Host " Type: $routeType" -ForegroundColor White
|
||||
Write-Host " Pattern: $PathPattern" -ForegroundColor White
|
||||
Write-Host " Address: $Address" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host " Available Endpoints:" -ForegroundColor Yellow
|
||||
Write-Host " GET $GatewayUrl/$Prefix/$Version/campaigns" -ForegroundColor Cyan
|
||||
Write-Host " POST $GatewayUrl/$Prefix/$Version/campaigns" -ForegroundColor Cyan
|
||||
Write-Host " GET $GatewayUrl/$Prefix/$Version/campaigns/{id}" -ForegroundColor Cyan
|
||||
Write-Host " POST $GatewayUrl/$Prefix/$Version/campaigns/{id}/publish" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
185
scripts/gateway/register-service.sh
Normal file
185
scripts/gateway/register-service.sh
Normal file
@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
# Service Gateway Registration Script
|
||||
# Usage: ./register-service.sh [OPTIONS]
|
||||
#
|
||||
# Options:
|
||||
# -p, --prefix Service prefix (default: activity)
|
||||
# -v, --version API version (default: v1)
|
||||
# -g, --gateway-url Gateway URL (default: http://localhost:5000)
|
||||
# -a, --address Service address (default: http://localhost:5001)
|
||||
# -d, --destination Destination ID (default: {prefix}-1)
|
||||
# -w, --weight Instance weight (default: 1)
|
||||
# -G, --global Create global route (default: true)
|
||||
# -t, --tenant-code Tenant code for tenant-specific route
|
||||
# -h, --help Show this help
|
||||
|
||||
set -e
|
||||
|
||||
# Default values
|
||||
SERVICE_PREFIX="activity"
|
||||
API_VERSION="v1"
|
||||
GATEWAY_URL="http://localhost:5000"
|
||||
SERVICE_ADDRESS="http://localhost:5001"
|
||||
WEIGHT=1
|
||||
IS_GLOBAL=true
|
||||
TENANT_CODE=""
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
-p|--prefix)
|
||||
SERVICE_PREFIX="$2"
|
||||
shift 2
|
||||
;;
|
||||
-v|--version)
|
||||
API_VERSION="$2"
|
||||
shift 2
|
||||
;;
|
||||
-g|--gateway-url)
|
||||
GATEWAY_URL="$2"
|
||||
shift 2
|
||||
;;
|
||||
-a|--address)
|
||||
SERVICE_ADDRESS="$2"
|
||||
shift 2
|
||||
;;
|
||||
-d|--destination)
|
||||
DESTINATION_ID="$2"
|
||||
shift 2
|
||||
;;
|
||||
-w|--weight)
|
||||
WEIGHT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-G|--global)
|
||||
IS_GLOBAL=true
|
||||
shift
|
||||
;;
|
||||
-t|--tenant-code)
|
||||
IS_GLOBAL=false
|
||||
TENANT_CODE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
grep -A 50 '# Usage:' "$0" | tail -n +2 | head -n 20
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Set derived values
|
||||
CLUSTER_ID="${SERVICE_PREFIX}-service"
|
||||
DESTINATION_ID="${DESTINATION_ID:-${SERVICE_PREFIX}-1}"
|
||||
PATH_PATTERN="/${SERVICE_PREFIX}/${API_VERSION}/{**path}"
|
||||
|
||||
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${CYAN}║ Service Gateway Registration ║${NC}"
|
||||
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# Step 1: Add Service Instance
|
||||
echo -e "${YELLOW}[1/3] Adding service instance...${NC}"
|
||||
|
||||
INSTANCE_BODY=$(cat <<EOF
|
||||
{
|
||||
"destinationId": "$DESTINATION_ID",
|
||||
"address": "$SERVICE_ADDRESS",
|
||||
"weight": $WEIGHT
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||
"$GATEWAY_URL/api/gateway/clusters/$CLUSTER_ID/instances" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$INSTANCE_BODY")
|
||||
|
||||
HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -n 1)
|
||||
RESPONSE_BODY=$(echo "$HTTP_RESPONSE" | sed '$d')
|
||||
|
||||
if [ "$HTTP_CODE" -eq 200 ]; then
|
||||
echo -e " ${GREEN}✓ Instance: $DESTINATION_ID -> $SERVICE_ADDRESS${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}ℹ Instance may already exist${NC}"
|
||||
fi
|
||||
|
||||
# Step 2: Add Route
|
||||
echo ""
|
||||
echo -e "${YELLOW}[2/3] Configuring gateway route...${NC}"
|
||||
|
||||
if [ "$IS_GLOBAL" = true ]; then
|
||||
ROUTE_URL="$GATEWAY_URL/api/gateway/routes/global"
|
||||
|
||||
ROUTE_BODY=$(cat <<EOF
|
||||
{
|
||||
"serviceName": "$SERVICE_PREFIX",
|
||||
"clusterId": "$CLUSTER_ID",
|
||||
"pathPattern": "$PATH_PATTERN"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
ROUTE_TYPE="Global"
|
||||
else
|
||||
ROUTE_URL="$GATEWAY_URL/api/gateway/tenants/$TENANT_CODE/routes"
|
||||
ROUTE_TYPE="Tenant [$TENANT_CODE]"
|
||||
|
||||
ROUTE_BODY=$(cat <<EOF
|
||||
{
|
||||
"serviceName": "$SERVICE_PREFIX",
|
||||
"pathPattern": "$PATH_PATTERN"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
fi
|
||||
|
||||
HTTP_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \
|
||||
"$ROUTE_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$ROUTE_BODY")
|
||||
|
||||
HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -n 1)
|
||||
RESPONSE_BODY=$(echo "$HTTP_RESPONSE" | sed '$d')
|
||||
|
||||
if [ "$HTTP_CODE" -eq 200 ]; then
|
||||
echo -e " ${GREEN}✓ Route: $PATH_PATTERN -> $CLUSTER_ID${NC}"
|
||||
else
|
||||
echo -e " ${YELLOW}ℹ Route may already exist${NC}"
|
||||
fi
|
||||
|
||||
# Step 3: Reload Config
|
||||
echo ""
|
||||
echo -e "${YELLOW}[3/3] Reloading gateway configuration...${NC}"
|
||||
|
||||
curl -s -X POST "$GATEWAY_URL/api/gateway/reload" > /dev/null
|
||||
echo -e " ${GREEN}✓ Configuration reloaded${NC}"
|
||||
|
||||
# Summary
|
||||
echo ""
|
||||
echo -e "${CYAN}════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${CYAN} Service Registered Successfully!${NC}"
|
||||
echo -e "${CYAN}════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
echo -e " ${WHITE}Service:${NC} $SERVICE_PREFIX"
|
||||
echo -e " ${WHITE}Version:${NC} $API_VERSION"
|
||||
echo -e " ${WHITE}Cluster:${NC} $CLUSTER_ID"
|
||||
echo -e " ${WHITE}Type:${NC} $ROUTE_TYPE"
|
||||
echo -e " ${WHITE}Pattern:${NC} $PATH_PATTERN"
|
||||
echo -e " ${WHITE}Address:${NC} $SERVICE_ADDRESS"
|
||||
echo ""
|
||||
echo -e " ${CYAN}Available Endpoints:${NC}"
|
||||
echo -e " GET $GATEWAY_URL/${SERVICE_PREFIX}/${API_VERSION}/campaigns"
|
||||
echo -e " POST $GATEWAY_URL/${SERVICE_PREFIX}/${API_VERSION}/campaigns"
|
||||
echo -e " GET $GATEWAY_URL/${SERVICE_PREFIX}/${API_VERSION}/campaigns/\{id\}"
|
||||
echo -e " POST $GATEWAY_URL/${SERVICE_PREFIX}/${API_VERSION}/campaigns/\{id\}/publish"
|
||||
echo ""
|
||||
258
scripts/init-infrastructure.ps1
Normal file
258
scripts/init-infrastructure.ps1
Normal file
@ -0,0 +1,258 @@
|
||||
# NetCorePal Template - Infrastructure Initialization Script (PowerShell)
|
||||
# This script initializes the required infrastructure for development
|
||||
|
||||
param(
|
||||
[switch]$SqlServer,
|
||||
[switch]$Postgres,
|
||||
[switch]$Kafka,
|
||||
[switch]$Help
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Color functions for output
|
||||
function Write-Info {
|
||||
param([string]$Message)
|
||||
Write-Host "[INFO] $Message" -ForegroundColor Blue
|
||||
}
|
||||
|
||||
function Write-Success {
|
||||
param([string]$Message)
|
||||
Write-Host "[SUCCESS] $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Warning {
|
||||
param([string]$Message)
|
||||
Write-Host "[WARNING] $Message" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
function Write-Error {
|
||||
param([string]$Message)
|
||||
Write-Host "[ERROR] $Message" -ForegroundColor Red
|
||||
}
|
||||
|
||||
function Show-Help {
|
||||
Write-Host "NetCorePal Template - Infrastructure Initialization" -ForegroundColor Green
|
||||
Write-Host "=================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Usage: .\init-infrastructure.ps1 [OPTIONS]"
|
||||
Write-Host ""
|
||||
Write-Host "Initialize infrastructure containers for NetCorePal Template development"
|
||||
Write-Host ""
|
||||
Write-Host "Options:"
|
||||
Write-Host " -Help Show this help message"
|
||||
Write-Host " -SqlServer Use SQL Server database instead of MySQL"
|
||||
Write-Host " -Postgres Use PostgreSQL database instead of MySQL"
|
||||
Write-Host " -Kafka Use Kafka instead of RabbitMQ"
|
||||
Write-Host ""
|
||||
Write-Host "Examples:"
|
||||
Write-Host " .\init-infrastructure.ps1 # Start with MySQL and RabbitMQ (default)"
|
||||
Write-Host " .\init-infrastructure.ps1 -Postgres # Start with PostgreSQL and RabbitMQ"
|
||||
Write-Host " .\init-infrastructure.ps1 -Kafka # Start with MySQL and Kafka"
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
function Test-Docker {
|
||||
Write-Info "Checking Docker installation..."
|
||||
|
||||
try {
|
||||
$null = Get-Command docker -ErrorAction Stop
|
||||
}
|
||||
catch {
|
||||
Write-Error "Docker is not installed. Please install Docker Desktop first."
|
||||
Write-Host "Download from: https://www.docker.com/products/docker-desktop/" -ForegroundColor Cyan
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$null = docker info 2>$null
|
||||
}
|
||||
catch {
|
||||
Write-Error "Docker is not running. Please start Docker Desktop first."
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Success "Docker is installed and running"
|
||||
}
|
||||
|
||||
function Start-Container {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$Image,
|
||||
[string]$Ports,
|
||||
[string]$Environment,
|
||||
[string]$Volumes,
|
||||
[string]$AdditionalArgs
|
||||
)
|
||||
|
||||
Write-Info "Starting $Name container..."
|
||||
|
||||
# Stop and remove existing container if it exists
|
||||
$existingContainer = docker ps -a --format "table {{.Names}}" | Select-String "^$Name$"
|
||||
if ($existingContainer) {
|
||||
Write-Warning "Stopping existing $Name container..."
|
||||
docker stop $Name 2>$null | Out-Null
|
||||
docker rm $Name 2>$null | Out-Null
|
||||
}
|
||||
|
||||
# Build the docker run command
|
||||
$cmd = "docker run --restart unless-stopped --name $Name"
|
||||
if ($Ports) { $cmd += " $Ports" }
|
||||
if ($Environment) { $cmd += " $Environment" }
|
||||
if ($Volumes) { $cmd += " $Volumes" }
|
||||
if ($AdditionalArgs) { $cmd += " $AdditionalArgs" }
|
||||
$cmd += " -d $Image"
|
||||
|
||||
try {
|
||||
Invoke-Expression $cmd | Out-Null
|
||||
Write-Success "$Name container started successfully"
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Error "Failed to start $Name container: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-ForContainer {
|
||||
param(
|
||||
[string]$ContainerName,
|
||||
[int]$MaxAttempts = 30
|
||||
)
|
||||
|
||||
Write-Info "Waiting for $ContainerName to be healthy..."
|
||||
|
||||
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt++) {
|
||||
$running = docker ps --filter "name=$ContainerName" --filter "status=running" | Select-String $ContainerName
|
||||
if ($running) {
|
||||
Write-Success "$ContainerName is running"
|
||||
return $true
|
||||
}
|
||||
|
||||
Write-Host "." -NoNewline
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
Write-Host "" # New line after dots
|
||||
Write-Error "$ContainerName failed to start properly"
|
||||
return $false
|
||||
}
|
||||
|
||||
function Start-Infrastructure {
|
||||
Write-Host ""
|
||||
Write-Info "Starting infrastructure setup..."
|
||||
Write-Host ""
|
||||
|
||||
# Check prerequisites
|
||||
Test-Docker
|
||||
|
||||
# Start Redis
|
||||
$success = Start-Container -Name "netcorepal-redis" -Image "redis:7.2-alpine" `
|
||||
-Ports "-p 6379:6379" `
|
||||
-Volumes "-v netcorepal_redis_data:/data" `
|
||||
-AdditionalArgs "redis-server --appendonly yes --databases 1024"
|
||||
|
||||
if ($success) {
|
||||
Wait-ForContainer -ContainerName "netcorepal-redis" -MaxAttempts 15
|
||||
}
|
||||
|
||||
# Start Database
|
||||
if ($Postgres) {
|
||||
Write-Info "Setting up PostgreSQL database..."
|
||||
$success = Start-Container -Name "netcorepal-postgres" -Image "postgres:15-alpine" `
|
||||
-Ports "-p 5432:5432" `
|
||||
-Environment "-e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=123456 -e POSTGRES_DB=postgres -e TZ=Asia/Shanghai" `
|
||||
-Volumes "-v netcorepal_postgres_data:/var/lib/postgresql/data"
|
||||
|
||||
if ($success) {
|
||||
Wait-ForContainer -ContainerName "netcorepal-postgres" -MaxAttempts 30
|
||||
}
|
||||
}
|
||||
elseif ($SqlServer) {
|
||||
Write-Info "Setting up SQL Server database..."
|
||||
$success = Start-Container -Name "netcorepal-sqlserver" -Image "mcr.microsoft.com/mssql/server:2022-latest" `
|
||||
-Ports "-p 1433:1433" `
|
||||
-Environment "-e ACCEPT_EULA=Y -e MSSQL_SA_PASSWORD=Test123456! -e TZ=Asia/Shanghai" `
|
||||
-Volumes "-v netcorepal_sqlserver_data:/var/opt/mssql"
|
||||
|
||||
if ($success) {
|
||||
Wait-ForContainer -ContainerName "netcorepal-sqlserver" -MaxAttempts 30
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Info "Setting up MySQL database..."
|
||||
$success = Start-Container -Name "netcorepal-mysql" -Image "mysql:8.0" `
|
||||
-Ports "-p 3306:3306" `
|
||||
-Environment "-e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_CHARACTER_SET_SERVER=utf8mb4 -e MYSQL_COLLATION_SERVER=utf8mb4_unicode_ci -e TZ=Asia/Shanghai" `
|
||||
-Volumes "-v netcorepal_mysql_data:/var/lib/mysql"
|
||||
|
||||
if ($success) {
|
||||
Wait-ForContainer -ContainerName "netcorepal-mysql" -MaxAttempts 30
|
||||
}
|
||||
}
|
||||
|
||||
# Start Message Queue
|
||||
if ($Kafka) {
|
||||
Write-Info "Setting up Kafka message queue..."
|
||||
Write-Warning "Kafka setup requires Zookeeper. For full Kafka setup, please use Docker Compose:"
|
||||
Write-Host "docker-compose --profile kafka up -d" -ForegroundColor Cyan
|
||||
}
|
||||
else {
|
||||
Write-Info "Setting up RabbitMQ message queue..."
|
||||
$success = Start-Container -Name "netcorepal-rabbitmq" -Image "rabbitmq:3.12-management-alpine" `
|
||||
-Ports "-p 5672:5672 -p 15672:15672" `
|
||||
-Environment "-e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest" `
|
||||
-Volumes "-v netcorepal_rabbitmq_data:/var/lib/rabbitmq"
|
||||
|
||||
if ($success) {
|
||||
Wait-ForContainer -ContainerName "netcorepal-rabbitmq" -MaxAttempts 20
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Success "🎉 Infrastructure setup completed successfully!"
|
||||
Write-Host ""
|
||||
Write-Host "📋 Service Summary:" -ForegroundColor Cyan
|
||||
Write-Host "==================="
|
||||
Write-Host "✅ Redis: 81.68.223.70:16379,password=sl52788542"
|
||||
|
||||
if ($Postgres) {
|
||||
Write-Host "✅ PostgreSQL: localhost:5432 (postgres/123456)"
|
||||
}
|
||||
elseif ($SqlServer) {
|
||||
Write-Host "✅ SQL Server: localhost:1433 (sa/Test123456!)"
|
||||
}
|
||||
else {
|
||||
Write-Host "✅ MySQL: localhost:3306 (root/123456)"
|
||||
}
|
||||
|
||||
if (-not $Kafka) {
|
||||
Write-Host "✅ RabbitMQ: localhost:5672 (guest/guest)"
|
||||
Write-Host "📊 RabbitMQ Management UI: http://localhost:15672"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "💡 Tips:" -ForegroundColor Yellow
|
||||
Write-Host "• Use 'docker ps' to see running containers"
|
||||
Write-Host "• Use 'docker logs <container_name>' to check logs"
|
||||
Write-Host "• Use '.\clean-infrastructure.ps1' to stop and remove all containers"
|
||||
Write-Host ""
|
||||
Write-Info "Ready for development! 🚀"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
Write-Host "🚀 NetCorePal Template - Infrastructure Setup" -ForegroundColor Green
|
||||
Write-Host "==============================================" -ForegroundColor Green
|
||||
|
||||
if ($Help) {
|
||||
Show-Help
|
||||
exit 0
|
||||
}
|
||||
|
||||
try {
|
||||
Start-Infrastructure
|
||||
}
|
||||
catch {
|
||||
Write-Error "An error occurred during setup: $_"
|
||||
exit 1
|
||||
}
|
||||
200
scripts/init-infrastructure.sh
Normal file
200
scripts/init-infrastructure.sh
Normal file
@ -0,0 +1,200 @@
|
||||
#!/bin/bash
|
||||
|
||||
# NetCorePal Template - Infrastructure Initialization Script
|
||||
# This script initializes the required infrastructure for development
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 NetCorePal Template - Infrastructure Setup"
|
||||
echo "=============================================="
|
||||
|
||||
# Color codes for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Check if Docker is installed and running
|
||||
check_docker() {
|
||||
print_status "Checking Docker installation..."
|
||||
|
||||
if ! command -v docker &> /dev/null; then
|
||||
print_error "Docker is not installed. Please install Docker first."
|
||||
echo "Download from: https://www.docker.com/products/docker-desktop/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker info &> /dev/null; then
|
||||
print_error "Docker is not running. Please start Docker first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Docker is installed and running"
|
||||
}
|
||||
|
||||
# Function to run a Docker container with retry logic
|
||||
run_container() {
|
||||
local name=$1
|
||||
local image=$2
|
||||
local ports=$3
|
||||
local environment=$4
|
||||
local volumes=$5
|
||||
local additional_args=$6
|
||||
|
||||
print_status "Starting $name container..."
|
||||
|
||||
# Stop and remove existing container if it exists
|
||||
if docker ps -a --format 'table {{.Names}}' | grep -q "^$name$"; then
|
||||
print_warning "Stopping existing $name container..."
|
||||
docker stop $name > /dev/null 2>&1 || true
|
||||
docker rm $name > /dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Run the container
|
||||
local cmd="docker run --restart unless-stopped --name $name $ports $environment $volumes $additional_args -d $image"
|
||||
|
||||
if eval $cmd > /dev/null; then
|
||||
print_success "$name container started successfully"
|
||||
return 0
|
||||
else
|
||||
print_error "Failed to start $name container"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to wait for container to be healthy
|
||||
wait_for_container() {
|
||||
local container_name=$1
|
||||
local max_attempts=${2:-30}
|
||||
local attempt=1
|
||||
|
||||
print_status "Waiting for $container_name to be healthy..."
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if docker ps --filter "name=$container_name" --filter "status=running" | grep -q $container_name; then
|
||||
print_success "$container_name is running"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 2
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
print_error "$container_name failed to start properly"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
echo
|
||||
print_status "Starting infrastructure setup..."
|
||||
echo
|
||||
|
||||
# Check prerequisites
|
||||
check_docker
|
||||
|
||||
# Start Redis
|
||||
run_container "netcorepal-redis" "redis:7.2-alpine" \
|
||||
"-p 6379:6379" \
|
||||
"" \
|
||||
"-v netcorepal_redis_data:/data" \
|
||||
"redis-server --appendonly yes --databases 1024"
|
||||
|
||||
wait_for_container "netcorepal-redis" 15
|
||||
|
||||
# Start MySQL (default database)
|
||||
print_status "Setting up MySQL database..."
|
||||
run_container "netcorepal-mysql" "mysql:8.0" \
|
||||
"-p 3306:3306" \
|
||||
"-e MYSQL_ROOT_PASSWORD=123456 -e MYSQL_CHARACTER_SET_SERVER=utf8mb4 -e MYSQL_COLLATION_SERVER=utf8mb4_unicode_ci -e TZ=Asia/Shanghai" \
|
||||
"-v netcorepal_mysql_data:/var/lib/mysql" \
|
||||
""
|
||||
|
||||
wait_for_container "netcorepal-mysql" 30
|
||||
|
||||
# Start RabbitMQ (default message queue)
|
||||
print_status "Setting up RabbitMQ message queue..."
|
||||
run_container "netcorepal-rabbitmq" "rabbitmq:3.12-management-alpine" \
|
||||
"-p 5672:5672 -p 15672:15672" \
|
||||
"-e RABBITMQ_DEFAULT_USER=guest -e RABBITMQ_DEFAULT_PASS=guest" \
|
||||
"-v netcorepal_rabbitmq_data:/var/lib/rabbitmq" \
|
||||
""
|
||||
|
||||
wait_for_container "netcorepal-rabbitmq" 20
|
||||
|
||||
echo
|
||||
print_success "🎉 Infrastructure setup completed successfully!"
|
||||
echo
|
||||
echo "📋 Service Summary:"
|
||||
echo "==================="
|
||||
echo "✅ Redis: 81.68.223.70:16379,password=sl52788542"
|
||||
echo "✅ MySQL: localhost:3306 (root/123456)"
|
||||
echo "✅ RabbitMQ: localhost:5672 (guest/guest)"
|
||||
echo "📊 RabbitMQ Management UI: http://localhost:15672"
|
||||
echo
|
||||
echo "💡 Tips:"
|
||||
echo "• Use 'docker ps' to see running containers"
|
||||
echo "• Use 'docker logs <container_name>' to check logs"
|
||||
echo "• Use './clean-infrastructure.sh' to stop and remove all containers"
|
||||
echo
|
||||
print_status "Ready for development! 🚀"
|
||||
}
|
||||
|
||||
# Parse command line arguments
|
||||
case "${1:-}" in
|
||||
--help|-h)
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo
|
||||
echo "Initialize infrastructure containers for NetCorePal Template development"
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " -h, --help Show this help message"
|
||||
echo " --mysql Use MySQL database (default)"
|
||||
echo " --sqlserver Use SQL Server database"
|
||||
echo " --postgres Use PostgreSQL database"
|
||||
echo " --kafka Use Kafka instead of RabbitMQ"
|
||||
echo
|
||||
echo "Examples:"
|
||||
echo " $0 # Start with MySQL and RabbitMQ (default)"
|
||||
echo " $0 --postgres # Start with PostgreSQL and RabbitMQ"
|
||||
echo " $0 --kafka # Start with MySQL and Kafka"
|
||||
exit 0
|
||||
;;
|
||||
--sqlserver)
|
||||
print_status "SQL Server option will be implemented in Docker Compose version"
|
||||
print_status "For now, use: docker-compose --profile sqlserver up -d"
|
||||
exit 0
|
||||
;;
|
||||
--postgres)
|
||||
print_status "PostgreSQL option will be implemented in Docker Compose version"
|
||||
print_status "For now, use: docker-compose --profile postgres up -d"
|
||||
exit 0
|
||||
;;
|
||||
--kafka)
|
||||
print_status "Kafka option will be implemented in Docker Compose version"
|
||||
print_status "For now, use: docker-compose --profile kafka up -d"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
main
|
||||
;;
|
||||
esac
|
||||
18
scripts/mysql-init/01-init.sql
Normal file
18
scripts/mysql-init/01-init.sql
Normal file
@ -0,0 +1,18 @@
|
||||
-- MySQL Initialization Script for NetCorePal Template
|
||||
-- This script creates the necessary database and user for development
|
||||
|
||||
-- Create development database if it doesn't exist
|
||||
CREATE DATABASE IF NOT EXISTS `abctemplate` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Create a development user (optional - you can use root for development)
|
||||
-- CREATE USER IF NOT EXISTS 'devuser'@'%' IDENTIFIED BY 'devpass123';
|
||||
-- GRANT ALL PRIVILEGES ON `abctemplate`.* TO 'devuser'@'%';
|
||||
|
||||
-- Ensure root can connect from any host (for development only)
|
||||
-- ALTER USER 'root'@'%' IDENTIFIED BY '123456';
|
||||
-- GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
|
||||
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- Display completion message
|
||||
SELECT 'MySQL initialization completed successfully' AS message;
|
||||
24
scripts/postgres-init/01-init.sql
Normal file
24
scripts/postgres-init/01-init.sql
Normal file
@ -0,0 +1,24 @@
|
||||
-- PostgreSQL Initialization Script for NetCorePal Template
|
||||
-- This script creates the necessary database and user for development
|
||||
|
||||
-- Create development database if it doesn't exist
|
||||
SELECT 'CREATE DATABASE abctemplate'
|
||||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'abctemplate')\gexec
|
||||
|
||||
-- Create a development user (optional - you can use postgres for development)
|
||||
-- DO
|
||||
-- $do$
|
||||
-- BEGIN
|
||||
-- IF NOT EXISTS (
|
||||
-- SELECT FROM pg_catalog.pg_roles
|
||||
-- WHERE rolname = 'devuser') THEN
|
||||
-- CREATE ROLE devuser LOGIN PASSWORD 'devpass123';
|
||||
-- END IF;
|
||||
-- END
|
||||
-- $do$;
|
||||
|
||||
-- Grant privileges to development user
|
||||
-- GRANT ALL PRIVILEGES ON DATABASE abctemplate TO devuser;
|
||||
|
||||
-- Display completion message
|
||||
SELECT 'PostgreSQL initialization completed successfully' AS message;
|
||||
139
src/Fengling.Activity.Domain/Aggregates/Campaigns/Campaign.cs
Normal file
139
src/Fengling.Activity.Domain/Aggregates/Campaigns/Campaign.cs
Normal file
@ -0,0 +1,139 @@
|
||||
namespace Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.Events.Campaigns;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class Campaign : Entity<CampaignId>, IAggregateRoot
|
||||
{
|
||||
public TenantId TenantId { get; private set; } = null!;
|
||||
public CampaignName Name { get; private set; } = null!;
|
||||
public CampaignType Type { get; private set; }
|
||||
public CampaignStatus Status { get; private set; } = CampaignStatus.Draft;
|
||||
public string? Description { get; private set; }
|
||||
public TimeRange TimeRange { get; private set; } = null!;
|
||||
public int? MaxParticipants { get; private set; }
|
||||
public int? MaxRewardsPerMember { get; private set; }
|
||||
|
||||
private readonly List<ConditionConfig> _conditions = new();
|
||||
public IReadOnlyCollection<ConditionConfig> Conditions => _conditions.AsReadOnly();
|
||||
|
||||
private readonly List<ConstraintConfig> _constraints = new();
|
||||
public IReadOnlyCollection<ConstraintConfig> Constraints => _constraints.AsReadOnly();
|
||||
|
||||
private readonly List<ActionConfig> _actions = new();
|
||||
public IReadOnlyCollection<ActionConfig> Actions => _actions.AsReadOnly();
|
||||
|
||||
public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;
|
||||
public DateTime? UpdatedAt { get; private set; }
|
||||
public int Version { get; private set; } = 1;
|
||||
|
||||
private Campaign()
|
||||
{
|
||||
}
|
||||
|
||||
public static Campaign Create(
|
||||
TenantId tenantId,
|
||||
CampaignName name,
|
||||
CampaignType type,
|
||||
TimeRange timeRange,
|
||||
string? description = null)
|
||||
{
|
||||
var campaign = new Campaign
|
||||
{
|
||||
TenantId = tenantId,
|
||||
Name = name,
|
||||
Type = type,
|
||||
TimeRange = timeRange,
|
||||
Description = description,
|
||||
Status = CampaignStatus.Draft,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
campaign.AddDomainEvent(new CampaignCreatedEvent(campaign.Id, tenantId, name, type, timeRange, campaign.CreatedAt));
|
||||
return campaign;
|
||||
}
|
||||
|
||||
public void Publish()
|
||||
{
|
||||
if (Status != CampaignStatus.Draft && Status != CampaignStatus.Paused)
|
||||
throw new InvalidOperationException("Only draft or paused campaigns can be published");
|
||||
|
||||
if (!TimeRange.IsActive())
|
||||
throw new InvalidOperationException("Campaign time range must be active to publish");
|
||||
|
||||
Status = CampaignStatus.Published;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
Version++;
|
||||
AddDomainEvent(new CampaignPublishedEvent(Id, TenantId, DateTime.UtcNow));
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (Status != CampaignStatus.Published)
|
||||
throw new InvalidOperationException("Only published campaigns can be paused");
|
||||
|
||||
Status = CampaignStatus.Paused;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
Version++;
|
||||
AddDomainEvent(new CampaignPausedEvent(Id, TenantId, DateTime.UtcNow));
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
if (Status != CampaignStatus.Published)
|
||||
throw new InvalidOperationException("Only published campaigns can be completed");
|
||||
|
||||
Status = CampaignStatus.Completed;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
Version++;
|
||||
AddDomainEvent(new CampaignCompletedEvent(Id, TenantId, DateTime.UtcNow));
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
if (Status == CampaignStatus.Completed)
|
||||
throw new InvalidOperationException("Completed campaigns cannot be cancelled");
|
||||
|
||||
Status = CampaignStatus.Cancelled;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
Version++;
|
||||
AddDomainEvent(new CampaignCancelledEvent(Id, TenantId, DateTime.UtcNow));
|
||||
}
|
||||
|
||||
public void AddCondition(ConditionConfig condition)
|
||||
{
|
||||
_conditions.Add(condition);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void AddConstraint(ConstraintConfig constraint)
|
||||
{
|
||||
_constraints.Add(constraint);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void AddAction(ActionConfig action)
|
||||
{
|
||||
_actions.Add(action);
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateTimeRange(TimeRange timeRange)
|
||||
{
|
||||
TimeRange = timeRange;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateMaxParticipants(int? maxParticipants)
|
||||
{
|
||||
MaxParticipants = maxParticipants;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void UpdateMaxRewardsPerMember(int? maxRewards)
|
||||
{
|
||||
MaxRewardsPerMember = maxRewards;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
namespace Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.Events.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class ParticipationRecord : Entity<ParticipationRecordId>, IAggregateRoot
|
||||
{
|
||||
public CampaignId CampaignId { get; private set; } = null!;
|
||||
public TenantId TenantId { get; private set; } = null!;
|
||||
public Guid MemberId { get; private set; }
|
||||
public ParticipationStatus Status { get; private set; } = ParticipationStatus.Participating;
|
||||
public int CurrentProgress { get; private set; }
|
||||
public int? TargetProgress { get; private set; }
|
||||
public int RewardsGranted { get; private set; }
|
||||
public DateTime ParticipatedAt { get; private set; } = DateTime.UtcNow;
|
||||
public DateTime? CompletedAt { get; private set; }
|
||||
public DateTime? ExpiredAt { get; private set; }
|
||||
public DateTime? UpdatedAt { get; private set; }
|
||||
|
||||
private ParticipationRecord()
|
||||
{
|
||||
}
|
||||
|
||||
public static ParticipationRecord Create(
|
||||
CampaignId campaignId,
|
||||
TenantId tenantId,
|
||||
Guid memberId,
|
||||
int? targetProgress = null)
|
||||
{
|
||||
var record = new ParticipationRecord
|
||||
{
|
||||
CampaignId = campaignId,
|
||||
TenantId = tenantId,
|
||||
MemberId = memberId,
|
||||
TargetProgress = targetProgress,
|
||||
Status = ParticipationStatus.Participating,
|
||||
ParticipatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
record.AddDomainEvent(new TaskParticipatedEvent(record.Id, campaignId, tenantId, memberId, record.ParticipatedAt));
|
||||
return record;
|
||||
}
|
||||
|
||||
public void UpdateProgress(int progress)
|
||||
{
|
||||
CurrentProgress = progress;
|
||||
if (TargetProgress.HasValue && CurrentProgress >= TargetProgress.Value)
|
||||
{
|
||||
Complete();
|
||||
}
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
if (Status == ParticipationStatus.Completed)
|
||||
return;
|
||||
|
||||
Status = ParticipationStatus.Completed;
|
||||
CompletedAt = DateTime.UtcNow;
|
||||
AddDomainEvent(new TaskCompletedEvent(Id, CampaignId, TenantId, MemberId, CompletedAt.Value));
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
if (Status == ParticipationStatus.Cancelled)
|
||||
return;
|
||||
|
||||
Status = ParticipationStatus.Cancelled;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void Expire()
|
||||
{
|
||||
if (Status != ParticipationStatus.Participating)
|
||||
return;
|
||||
|
||||
Status = ParticipationStatus.Expired;
|
||||
ExpiredAt = DateTime.UtcNow;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public void GrantReward()
|
||||
{
|
||||
RewardsGranted++;
|
||||
UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
namespace Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
|
||||
public class ParticipationRecordId : IEquatable<ParticipationRecordId>
|
||||
{
|
||||
public Guid Value { get; }
|
||||
|
||||
private ParticipationRecordId(Guid value) => Value = value;
|
||||
|
||||
public static ParticipationRecordId New() => new(Guid.NewGuid());
|
||||
public static ParticipationRecordId FromGuid(Guid value) => new(value);
|
||||
public static ParticipationRecordId Parse(string value) => new(Guid.Parse(value));
|
||||
|
||||
public bool Equals(ParticipationRecordId? other) => other?.Value == Value;
|
||||
public override bool Equals(object? obj) => obj is ParticipationRecordId other && Equals(other);
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
public static implicit operator Guid(ParticipationRecordId id) => id.Value;
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
namespace Fengling.Activity.Domain.Aggregates.RewardGrantRecords;
|
||||
|
||||
using Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class RewardGrantRecord : Entity<RewardGrantRecordId>, IAggregateRoot
|
||||
{
|
||||
public CampaignId CampaignId { get; private set; } = null!;
|
||||
public TenantId TenantId { get; private set; } = null!;
|
||||
public Guid MemberId { get; private set; }
|
||||
public ParticipationRecordId? ParticipationRecordId { get; private set; }
|
||||
public RewardType RewardType { get; private set; }
|
||||
public int RewardAmount { get; private set; }
|
||||
public string? RewardCode { get; private set; }
|
||||
public string? Remark { get; private set; }
|
||||
public DateTime GrantedAt { get; private set; } = DateTime.UtcNow;
|
||||
|
||||
private RewardGrantRecord()
|
||||
{
|
||||
}
|
||||
|
||||
public static RewardGrantRecord Create(
|
||||
CampaignId campaignId,
|
||||
TenantId tenantId,
|
||||
Guid memberId,
|
||||
ParticipationRecordId? participationRecordId,
|
||||
RewardType rewardType,
|
||||
int rewardAmount,
|
||||
string? rewardCode = null,
|
||||
string? remark = null)
|
||||
{
|
||||
return new RewardGrantRecord
|
||||
{
|
||||
CampaignId = campaignId,
|
||||
TenantId = tenantId,
|
||||
MemberId = memberId,
|
||||
ParticipationRecordId = participationRecordId,
|
||||
RewardType = rewardType,
|
||||
RewardAmount = rewardAmount,
|
||||
RewardCode = rewardCode,
|
||||
Remark = remark,
|
||||
GrantedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
namespace Fengling.Activity.Domain.Aggregates.RewardGrantRecords;
|
||||
|
||||
public class RewardGrantRecordId : IEquatable<RewardGrantRecordId>
|
||||
{
|
||||
public Guid Value { get; }
|
||||
|
||||
private RewardGrantRecordId(Guid value) => Value = value;
|
||||
|
||||
public static RewardGrantRecordId New() => new(Guid.NewGuid());
|
||||
public static RewardGrantRecordId FromGuid(Guid value) => new(value);
|
||||
public static RewardGrantRecordId Parse(string value) => new(Guid.Parse(value));
|
||||
|
||||
public bool Equals(RewardGrantRecordId? other) => other?.Value == Value;
|
||||
public override bool Equals(object? obj) => obj is RewardGrantRecordId other && Equals(other);
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
public static implicit operator Guid(RewardGrantRecordId id) => id.Value;
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
10
src/Fengling.Activity.Domain/Enums/CampaignStatus.cs
Normal file
10
src/Fengling.Activity.Domain/Enums/CampaignStatus.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Fengling.Activity.Domain.Enums;
|
||||
|
||||
public enum CampaignStatus
|
||||
{
|
||||
Draft = 0,
|
||||
Published = 1,
|
||||
Paused = 2,
|
||||
Completed = 3,
|
||||
Cancelled = 4
|
||||
}
|
||||
12
src/Fengling.Activity.Domain/Enums/CampaignType.cs
Normal file
12
src/Fengling.Activity.Domain/Enums/CampaignType.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Fengling.Activity.Domain.Enums;
|
||||
|
||||
public enum CampaignType
|
||||
{
|
||||
Task = 1,
|
||||
Recurring = 2,
|
||||
Consumption = 3,
|
||||
Social = 4,
|
||||
Gamification = 5,
|
||||
MemberExclusive = 6,
|
||||
Festival = 7
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Fengling.Activity.Domain.Enums;
|
||||
|
||||
public enum ParticipationStatus
|
||||
{
|
||||
Participating = 1,
|
||||
Completed = 2,
|
||||
Expired = 3,
|
||||
Cancelled = 4
|
||||
}
|
||||
11
src/Fengling.Activity.Domain/Enums/RewardType.cs
Normal file
11
src/Fengling.Activity.Domain/Enums/RewardType.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Fengling.Activity.Domain.Enums;
|
||||
|
||||
public enum RewardType
|
||||
{
|
||||
Points = 1,
|
||||
Coupon = 2,
|
||||
Physical = 3,
|
||||
Virtual = 4,
|
||||
Currency = 5,
|
||||
Experience = 6
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Fengling.Activity.Domain.Events.Campaigns;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using NetCorePal.Extensions.Domain;
|
||||
|
||||
public record CampaignCancelledEvent(
|
||||
CampaignId CampaignId,
|
||||
TenantId TenantId,
|
||||
DateTime CancelledAt) : IDomainEvent;
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Fengling.Activity.Domain.Events.Campaigns;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using NetCorePal.Extensions.Domain;
|
||||
|
||||
public record CampaignCompletedEvent(
|
||||
CampaignId CampaignId,
|
||||
TenantId TenantId,
|
||||
DateTime CompletedAt) : IDomainEvent;
|
||||
@ -0,0 +1,13 @@
|
||||
namespace Fengling.Activity.Domain.Events.Campaigns;
|
||||
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using NetCorePal.Extensions.Domain;
|
||||
|
||||
public record CampaignCreatedEvent(
|
||||
CampaignId CampaignId,
|
||||
TenantId TenantId,
|
||||
CampaignName Name,
|
||||
CampaignType Type,
|
||||
TimeRange TimeRange,
|
||||
DateTime CreatedAt) : IDomainEvent;
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Fengling.Activity.Domain.Events.Campaigns;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using NetCorePal.Extensions.Domain;
|
||||
|
||||
public record CampaignPausedEvent(
|
||||
CampaignId CampaignId,
|
||||
TenantId TenantId,
|
||||
DateTime PausedAt) : IDomainEvent;
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Fengling.Activity.Domain.Events.Campaigns;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using NetCorePal.Extensions.Domain;
|
||||
|
||||
public record CampaignPublishedEvent(
|
||||
CampaignId CampaignId,
|
||||
TenantId TenantId,
|
||||
DateTime PublishedAt) : IDomainEvent;
|
||||
@ -0,0 +1,12 @@
|
||||
namespace Fengling.Activity.Domain.Events.ParticipationRecords;
|
||||
|
||||
using Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using NetCorePal.Extensions.Domain;
|
||||
|
||||
public record TaskCompletedEvent(
|
||||
ParticipationRecordId RecordId,
|
||||
CampaignId CampaignId,
|
||||
TenantId TenantId,
|
||||
Guid MemberId,
|
||||
DateTime CompletedAt) : IDomainEvent;
|
||||
@ -0,0 +1,12 @@
|
||||
namespace Fengling.Activity.Domain.Events.ParticipationRecords;
|
||||
|
||||
using Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using NetCorePal.Extensions.Domain;
|
||||
|
||||
public record TaskParticipatedEvent(
|
||||
ParticipationRecordId RecordId,
|
||||
CampaignId CampaignId,
|
||||
TenantId TenantId,
|
||||
Guid MemberId,
|
||||
DateTime ParticipatedAt) : IDomainEvent;
|
||||
21
src/Fengling.Activity.Domain/Fengling.Activity.Domain.csproj
Normal file
21
src/Fengling.Activity.Domain/Fengling.Activity.Domain.csproj
Normal file
@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NetCorePal.Extensions.CodeAnalysis" />
|
||||
<PackageReference Include="NetCorePal.Extensions.Domain.Abstractions" />
|
||||
<PackageReference Include="NetCorePal.Extensions.Primitives" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Update="SonarAnalyzer.CSharp">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
2
src/Fengling.Activity.Domain/GlobalUsings.cs
Normal file
2
src/Fengling.Activity.Domain/GlobalUsings.cs
Normal file
@ -0,0 +1,2 @@
|
||||
global using NetCorePal.Extensions.Domain;
|
||||
global using NetCorePal.Extensions.Primitives;
|
||||
@ -0,0 +1,17 @@
|
||||
namespace Fengling.Activity.Domain.Repositories;
|
||||
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public interface ICampaignRepository
|
||||
{
|
||||
Task<Campaign?> GetByIdAsync(CampaignId id, CancellationToken cancellationToken = default);
|
||||
Task<List<Campaign>> GetByTenantIdAsync(TenantId tenantId, CancellationToken cancellationToken = default);
|
||||
Task<List<Campaign>> GetByStatusAsync(CampaignStatus status, CancellationToken cancellationToken = default);
|
||||
Task<List<Campaign>> GetActiveCampaignsAsync(TenantId tenantId, CancellationToken cancellationToken = default);
|
||||
Task AddAsync(Campaign campaign, CancellationToken cancellationToken = default);
|
||||
Task UpdateAsync(Campaign campaign, CancellationToken cancellationToken = default);
|
||||
Task DeleteAsync(Campaign campaign, CancellationToken cancellationToken = default);
|
||||
Task<bool> ExistsAsync(CampaignId id, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
namespace Fengling.Activity.Domain.Repositories;
|
||||
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public interface IParticipationRecordRepository
|
||||
{
|
||||
Task<ParticipationRecord?> GetByIdAsync(ParticipationRecordId id, CancellationToken cancellationToken = default);
|
||||
Task<List<ParticipationRecord>> GetByCampaignIdAsync(CampaignId campaignId, CancellationToken cancellationToken = default);
|
||||
Task<List<ParticipationRecord>> GetByMemberIdAsync(TenantId tenantId, Guid memberId, CancellationToken cancellationToken = default);
|
||||
Task<ParticipationRecord?> GetByCampaignAndMemberAsync(CampaignId campaignId, Guid memberId, CancellationToken cancellationToken = default);
|
||||
Task<List<ParticipationRecord>> GetByStatusAsync(CampaignId campaignId, ParticipationStatus status, CancellationToken cancellationToken = default);
|
||||
Task<int> GetParticipationCountAsync(CampaignId campaignId, Guid memberId, CancellationToken cancellationToken = default);
|
||||
Task AddAsync(ParticipationRecord record, CancellationToken cancellationToken = default);
|
||||
Task UpdateAsync(ParticipationRecord record, CancellationToken cancellationToken = default);
|
||||
Task DeleteAsync(ParticipationRecord record, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
namespace Fengling.Activity.Domain.Repositories;
|
||||
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Aggregates.RewardGrantRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public interface IRewardGrantRecordRepository
|
||||
{
|
||||
Task<RewardGrantRecord?> GetByIdAsync(RewardGrantRecordId id, CancellationToken cancellationToken = default);
|
||||
Task<List<RewardGrantRecord>> GetByCampaignIdAsync(CampaignId campaignId, CancellationToken cancellationToken = default);
|
||||
Task<List<RewardGrantRecord>> GetByMemberIdAsync(TenantId tenantId, Guid memberId, CancellationToken cancellationToken = default);
|
||||
Task<int> GetRewardCountAsync(CampaignId campaignId, Guid memberId, CancellationToken cancellationToken = default);
|
||||
Task AddAsync(RewardGrantRecord record, CancellationToken cancellationToken = default);
|
||||
Task UpdateAsync(RewardGrantRecord record, CancellationToken cancellationToken = default);
|
||||
Task DeleteAsync(RewardGrantRecord record, CancellationToken cancellationToken = default);
|
||||
Task<List<RewardGrantRecord>> GetRewardsByDateRangeAsync(TenantId tenantId, DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -0,0 +1,158 @@
|
||||
namespace Fengling.Activity.Domain.Services;
|
||||
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.Aggregates.RewardGrantRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.Repositories;
|
||||
using Fengling.Activity.Domain.Strategies;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class CampaignExecutionService
|
||||
{
|
||||
private readonly ICampaignRepository _campaignRepository;
|
||||
private readonly IParticipationRecordRepository _participationRepository;
|
||||
private readonly IRewardGrantRecordRepository _rewardRepository;
|
||||
private readonly IStrategyFactory _strategyFactory;
|
||||
|
||||
public CampaignExecutionService(
|
||||
ICampaignRepository campaignRepository,
|
||||
IParticipationRecordRepository participationRepository,
|
||||
IRewardGrantRecordRepository rewardRepository,
|
||||
IStrategyFactory strategyFactory)
|
||||
{
|
||||
_campaignRepository = campaignRepository;
|
||||
_participationRepository = participationRepository;
|
||||
_rewardRepository = rewardRepository;
|
||||
_strategyFactory = strategyFactory;
|
||||
}
|
||||
|
||||
public async Task<CampaignExecutionResult> ExecuteCampaignAsync(
|
||||
CampaignId campaignId,
|
||||
TenantId tenantId,
|
||||
Guid memberId,
|
||||
string? memberLevel = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var campaign = await _campaignRepository.GetByIdAsync(campaignId, cancellationToken);
|
||||
if (campaign == null)
|
||||
{
|
||||
return CampaignExecutionResult.Fail("Campaign not found");
|
||||
}
|
||||
|
||||
if (campaign.TenantId != tenantId)
|
||||
{
|
||||
return CampaignExecutionResult.Fail("Campaign does not belong to this tenant");
|
||||
}
|
||||
|
||||
if (campaign.Status != CampaignStatus.Published)
|
||||
{
|
||||
return CampaignExecutionResult.Fail("Campaign is not active");
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
if (!campaign.TimeRange.Contains(now))
|
||||
{
|
||||
return CampaignExecutionResult.Fail("Campaign is not within valid time range");
|
||||
}
|
||||
|
||||
var existingRecord = await _participationRepository.GetByCampaignAndMemberAsync(campaignId, memberId, cancellationToken);
|
||||
if (existingRecord != null && existingRecord.Status != ParticipationStatus.Cancelled)
|
||||
{
|
||||
return CampaignExecutionResult.Fail("Member has already participated in this campaign");
|
||||
}
|
||||
|
||||
var context = new CampaignContext
|
||||
{
|
||||
CampaignId = campaignId.Value,
|
||||
TenantId = tenantId.Value,
|
||||
MemberId = memberId,
|
||||
MemberLevel = memberLevel,
|
||||
CurrentTime = now
|
||||
};
|
||||
|
||||
foreach (var condition in campaign.Conditions)
|
||||
{
|
||||
var strategy = _strategyFactory.GetConditionStrategy(condition.StrategyType);
|
||||
if (strategy == null)
|
||||
{
|
||||
return CampaignExecutionResult.Fail($"Condition strategy not found: {condition.StrategyType}");
|
||||
}
|
||||
|
||||
var result = await strategy.ExecuteAsync(context, condition, cancellationToken);
|
||||
if (!result.IsSatisfied)
|
||||
{
|
||||
return CampaignExecutionResult.Fail(result.Message ?? "Condition not met");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var constraint in campaign.Constraints)
|
||||
{
|
||||
var strategy = _strategyFactory.GetConstraintStrategy(constraint.StrategyType);
|
||||
if (strategy == null)
|
||||
{
|
||||
return CampaignExecutionResult.Fail($"Constraint strategy not found: {constraint.StrategyType}");
|
||||
}
|
||||
|
||||
var result = await strategy.ExecuteAsync(context, constraint, cancellationToken);
|
||||
if (!result.IsValid)
|
||||
{
|
||||
return CampaignExecutionResult.Fail(result.Message ?? "Constraint not satisfied");
|
||||
}
|
||||
}
|
||||
|
||||
var participation = ParticipationRecord.Create(campaignId, tenantId, memberId);
|
||||
|
||||
var rewards = new List<RewardGrantRecord>();
|
||||
foreach (var action in campaign.Actions)
|
||||
{
|
||||
var strategy = _strategyFactory.GetActionStrategy(action.StrategyType);
|
||||
if (strategy == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = await strategy.ExecuteAsync(context, action, cancellationToken);
|
||||
if (result.IsSuccess && result.Rewards != null)
|
||||
{
|
||||
var rewardType = Enum.Parse<RewardType>(result.Rewards.GetValueOrDefault("RewardType", "Points")?.ToString() ?? "Points");
|
||||
var amount = Convert.ToInt32(result.Rewards.GetValueOrDefault("Amount", 0));
|
||||
|
||||
if (amount > 0)
|
||||
{
|
||||
var reward = RewardGrantRecord.Create(
|
||||
campaignId,
|
||||
tenantId,
|
||||
memberId,
|
||||
participation.Id,
|
||||
rewardType,
|
||||
amount,
|
||||
result.Rewards.GetValueOrDefault("RewardCode")?.ToString(),
|
||||
result.Message);
|
||||
|
||||
rewards.Add(reward);
|
||||
participation.GrantReward();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return CampaignExecutionResult.Success(participation, rewards);
|
||||
}
|
||||
}
|
||||
|
||||
public record CampaignExecutionResult(
|
||||
bool IsSuccess,
|
||||
string? ErrorMessage,
|
||||
ParticipationRecord? Participation,
|
||||
List<RewardGrantRecord>? Rewards)
|
||||
{
|
||||
public static CampaignExecutionResult Success(ParticipationRecord participation, List<RewardGrantRecord> rewards)
|
||||
{
|
||||
return new CampaignExecutionResult(true, null, participation, rewards);
|
||||
}
|
||||
|
||||
public static CampaignExecutionResult Fail(string errorMessage)
|
||||
{
|
||||
return new CampaignExecutionResult(false, errorMessage, null, null);
|
||||
}
|
||||
}
|
||||
7
src/Fengling.Activity.Domain/Strategies/ActionResult.cs
Normal file
7
src/Fengling.Activity.Domain/Strategies/ActionResult.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Fengling.Activity.Domain.Strategies;
|
||||
|
||||
public record ActionResult(bool IsSuccess, string? Message = null, Dictionary<string, object>? Rewards = null)
|
||||
{
|
||||
public static ActionResult Success(Dictionary<string, object>? rewards = null) => new(true, null, rewards);
|
||||
public static ActionResult Fail(string message) => new(false, message);
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Actions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class CouponAction : IActionStrategy
|
||||
{
|
||||
public string StrategyType => "Coupon";
|
||||
|
||||
public Task<ActionResult> ExecuteAsync(CampaignContext context, ActionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var couponTemplateId = config.GetParameter<string>("CouponTemplateId");
|
||||
var quantity = config.GetParameter<int?>("Quantity") ?? 1;
|
||||
|
||||
var rewards = new Dictionary<string, object?>
|
||||
{
|
||||
{ "CouponTemplateId", couponTemplateId },
|
||||
{ "Quantity", quantity },
|
||||
{ "CampaignId", context.CampaignId.ToString() },
|
||||
{ "GrantedAt", DateTime.UtcNow }
|
||||
};
|
||||
|
||||
return Task.FromResult(ActionResult.Success(rewards));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Actions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class FixedRewardAction : IActionStrategy
|
||||
{
|
||||
public string StrategyType => "FixedReward";
|
||||
|
||||
public Task<ActionResult> ExecuteAsync(CampaignContext context, ActionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var rewardType = config.GetParameter<string>("RewardType") ?? "Points";
|
||||
var amount = config.GetParameter<int>("Amount");
|
||||
|
||||
var rewards = new Dictionary<string, object?>
|
||||
{
|
||||
{ "RewardType", rewardType },
|
||||
{ "Amount", amount },
|
||||
{ "CampaignId", context.CampaignId.ToString() },
|
||||
{ "GrantedAt", DateTime.UtcNow }
|
||||
};
|
||||
|
||||
return Task.FromResult(ActionResult.Success(rewards));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Actions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class LotteryAction : IActionStrategy
|
||||
{
|
||||
public string StrategyType => "Lottery";
|
||||
|
||||
public Task<ActionResult> ExecuteAsync(CampaignContext context, ActionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var lotteryPoolId = config.GetParameter<string>("LotteryPoolId");
|
||||
|
||||
var random = new Random();
|
||||
var prizes = context.AdditionalData.TryGetValue("LotteryPrizes", out var prizesObj)
|
||||
? prizesObj is List<Dictionary<string, object>> list ? list : null
|
||||
: null;
|
||||
|
||||
Dictionary<string, object>? selectedPrize = null;
|
||||
|
||||
if (prizes != null && prizes.Count > 0)
|
||||
{
|
||||
var totalWeight = prizes.Sum(p => p.TryGetValue("Weight", out var w) && w is int weight ? weight : 1);
|
||||
var randomValue = random.Next(totalWeight);
|
||||
var currentWeight = 0;
|
||||
|
||||
foreach (var prize in prizes)
|
||||
{
|
||||
var weight = prize.TryGetValue("Weight", out var w) && w is int i ? i : 1;
|
||||
currentWeight += weight;
|
||||
if (randomValue < currentWeight)
|
||||
{
|
||||
selectedPrize = prize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var rewards = new Dictionary<string, object?>
|
||||
{
|
||||
{ "LotteryPoolId", lotteryPoolId ?? "" },
|
||||
{ "CampaignId", context.CampaignId.ToString() },
|
||||
{ "GrantedAt", DateTime.UtcNow }
|
||||
};
|
||||
|
||||
if (selectedPrize != null)
|
||||
{
|
||||
rewards["Prize"] = selectedPrize;
|
||||
var name = selectedPrize.TryGetValue("Name", out var n) ? n?.ToString() : null;
|
||||
rewards["Message"] = $"Congratulations! You won: {name ?? "a prize"}";
|
||||
}
|
||||
else
|
||||
{
|
||||
rewards["Message"] = "Better luck next time!";
|
||||
}
|
||||
|
||||
return Task.FromResult(ActionResult.Success(rewards));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Actions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class PointsAction : IActionStrategy
|
||||
{
|
||||
public string StrategyType => "Points";
|
||||
|
||||
public Task<ActionResult> ExecuteAsync(CampaignContext context, ActionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var points = config.GetParameter<int>("Points");
|
||||
var source = config.GetParameter<string>("Source") ?? "Campaign";
|
||||
|
||||
var rewards = new Dictionary<string, object?>
|
||||
{
|
||||
{ "Points", points },
|
||||
{ "Source", source },
|
||||
{ "CampaignId", context.CampaignId.ToString() },
|
||||
{ "GrantedAt", DateTime.UtcNow }
|
||||
};
|
||||
|
||||
return Task.FromResult(ActionResult.Success(rewards));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Actions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class RandomRewardAction : IActionStrategy
|
||||
{
|
||||
public string StrategyType => "RandomReward";
|
||||
|
||||
public Task<ActionResult> ExecuteAsync(CampaignContext context, ActionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var minAmount = config.GetParameter<int>("MinAmount");
|
||||
var maxAmount = config.GetParameter<int>("MaxAmount");
|
||||
var rewardType = config.GetParameter<string>("RewardType") ?? "Points";
|
||||
|
||||
var random = new Random();
|
||||
var amount = random.Next(minAmount, maxAmount + 1);
|
||||
|
||||
var rewards = new Dictionary<string, object?>
|
||||
{
|
||||
{ "RewardType", rewardType },
|
||||
{ "Amount", amount },
|
||||
{ "CampaignId", context.CampaignId.ToString() },
|
||||
{ "GrantedAt", DateTime.UtcNow }
|
||||
};
|
||||
|
||||
return Task.FromResult(ActionResult.Success(rewards));
|
||||
}
|
||||
}
|
||||
11
src/Fengling.Activity.Domain/Strategies/CampaignContext.cs
Normal file
11
src/Fengling.Activity.Domain/Strategies/CampaignContext.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace Fengling.Activity.Domain.Strategies;
|
||||
|
||||
public class CampaignContext
|
||||
{
|
||||
public Guid CampaignId { get; set; }
|
||||
public Guid TenantId { get; set; }
|
||||
public Guid MemberId { get; set; }
|
||||
public string? MemberLevel { get; set; }
|
||||
public DateTime CurrentTime { get; set; } = DateTime.UtcNow;
|
||||
public Dictionary<string, object> AdditionalData { get; } = new();
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
namespace Fengling.Activity.Domain.Strategies;
|
||||
|
||||
public record ConditionResult(bool IsSatisfied, string? Message = null)
|
||||
{
|
||||
public static ConditionResult Success() => new(true);
|
||||
public static ConditionResult Fail(string message) => new(false, message);
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Conditions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class AccumulatedParticipationCondition : IConditionStrategy
|
||||
{
|
||||
public string StrategyType => "AccumulatedParticipation";
|
||||
|
||||
public Task<ConditionResult> ExecuteAsync(CampaignContext context, ConditionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var requiredCount = config.GetParameter<int>("RequiredCount");
|
||||
var campaignId = config.GetParameter<string>("CampaignId");
|
||||
|
||||
if (context.AdditionalData.TryGetValue("AccumulatedParticipationCount", out var countObj) &&
|
||||
countObj is int currentCount)
|
||||
{
|
||||
if (currentCount >= requiredCount)
|
||||
{
|
||||
return Task.FromResult(ConditionResult.Success());
|
||||
}
|
||||
|
||||
return Task.FromResult(ConditionResult.Fail($"Accumulated participation {currentCount} does not meet requirement {requiredCount}"));
|
||||
}
|
||||
|
||||
return Task.FromResult(ConditionResult.Success());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Conditions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class FirstTimeCondition : IConditionStrategy
|
||||
{
|
||||
public string StrategyType => "FirstTime";
|
||||
|
||||
public Task<ConditionResult> ExecuteAsync(CampaignContext context, ConditionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var checkFirstTimeOnly = config.GetParameter<bool>("CheckFirstTimeOnly");
|
||||
|
||||
if (!checkFirstTimeOnly)
|
||||
{
|
||||
return Task.FromResult(ConditionResult.Success());
|
||||
}
|
||||
|
||||
if (context.AdditionalData.TryGetValue("HasParticipatedBefore", out var hasParticipatedObj) &&
|
||||
hasParticipatedObj is bool hasParticipated && hasParticipated)
|
||||
{
|
||||
return Task.FromResult(ConditionResult.Fail("Member has already participated before"));
|
||||
}
|
||||
|
||||
return Task.FromResult(ConditionResult.Success());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Conditions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class MemberLevelCondition : IConditionStrategy
|
||||
{
|
||||
public string StrategyType => "MemberLevel";
|
||||
|
||||
public Task<ConditionResult> ExecuteAsync(CampaignContext context, ConditionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var requiredLevel = config.GetParameter<string>("RequiredLevel");
|
||||
var currentLevel = context.MemberLevel;
|
||||
|
||||
if (string.IsNullOrEmpty(requiredLevel))
|
||||
{
|
||||
return Task.FromResult(ConditionResult.Fail("Required level not configured"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(currentLevel))
|
||||
{
|
||||
return Task.FromResult(ConditionResult.Fail("Member level not available"));
|
||||
}
|
||||
|
||||
var isLevelMet = CompareLevels(currentLevel, requiredLevel);
|
||||
|
||||
return Task.FromResult(isLevelMet
|
||||
? ConditionResult.Success()
|
||||
: ConditionResult.Fail($"Member level {currentLevel} does not meet requirement {requiredLevel}"));
|
||||
}
|
||||
|
||||
private static bool CompareLevels(string current, string required)
|
||||
{
|
||||
var levelOrder = new[] { "Bronze", "Silver", "Gold", "Platinum", "Diamond", "VIP" };
|
||||
var currentIndex = Array.IndexOf(levelOrder, current);
|
||||
var requiredIndex = Array.IndexOf(levelOrder, required);
|
||||
|
||||
if (currentIndex == -1 || requiredIndex == -1)
|
||||
{
|
||||
return current.Equals(required, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return currentIndex >= requiredIndex;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Conditions;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class TimeRangeCondition : IConditionStrategy
|
||||
{
|
||||
public string StrategyType => "TimeRange";
|
||||
|
||||
public Task<ConditionResult> ExecuteAsync(CampaignContext context, ConditionConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var startHour = config.GetParameter<int>("StartHour");
|
||||
var endHour = config.GetParameter<int>("EndHour");
|
||||
var currentHour = context.CurrentTime.Hour;
|
||||
|
||||
if (currentHour >= startHour && currentHour < endHour)
|
||||
{
|
||||
return Task.FromResult(ConditionResult.Success());
|
||||
}
|
||||
|
||||
return Task.FromResult(ConditionResult.Fail($"Current time {currentHour}:00 is not within allowed range {startHour}:00-{endHour}:00"));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Fengling.Activity.Domain.Strategies;
|
||||
|
||||
public record ConstraintResult(bool IsValid, string? Message = null, int? RemainingCount = null)
|
||||
{
|
||||
public static ConstraintResult Pass() => new(true);
|
||||
public static ConstraintResult Fail(string message) => new(false, message);
|
||||
public static ConstraintResult LimitReached(string message, int remaining) => new(false, message, remaining);
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Constraints;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class DailyLimitConstraint : IConstraintStrategy
|
||||
{
|
||||
public string StrategyType => "DailyLimit";
|
||||
|
||||
public Task<ConstraintResult> ExecuteAsync(CampaignContext context, ConstraintConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var maxCount = config.GetParameter<int>("MaxCount");
|
||||
var currentDate = context.CurrentTime.Date.ToString("yyyy-MM-dd");
|
||||
|
||||
if (context.AdditionalData.TryGetValue($"DailyCount_{currentDate}", out var countObj) &&
|
||||
countObj is int currentCount)
|
||||
{
|
||||
if (currentCount >= maxCount)
|
||||
{
|
||||
return Task.FromResult(ConstraintResult.LimitReached($"Daily limit of {maxCount} reached", 0));
|
||||
}
|
||||
|
||||
return Task.FromResult(ConstraintResult.Pass());
|
||||
}
|
||||
|
||||
return Task.FromResult(ConstraintResult.Pass());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Constraints;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class FrequencyLimitConstraint : IConstraintStrategy
|
||||
{
|
||||
public string StrategyType => "FrequencyLimit";
|
||||
|
||||
public Task<ConstraintResult> ExecuteAsync(CampaignContext context, ConstraintConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var intervalMinutes = config.GetParameter<int>("IntervalMinutes");
|
||||
var lastParticipation = context.AdditionalData.TryGetValue("LastParticipationTime", out var timeObj)
|
||||
? timeObj is DateTime dt ? dt : (DateTime?)null
|
||||
: null;
|
||||
|
||||
if (lastParticipation.HasValue)
|
||||
{
|
||||
var timeSinceLast = (context.CurrentTime - lastParticipation.Value).TotalMinutes;
|
||||
if (timeSinceLast < intervalMinutes)
|
||||
{
|
||||
var waitMinutes = intervalMinutes - (int)timeSinceLast;
|
||||
return Task.FromResult(ConstraintResult.LimitReached($"Please wait {waitMinutes} minutes", waitMinutes));
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(ConstraintResult.Pass());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
namespace Fengling.Activity.Domain.Strategies.Constraints;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class TotalLimitConstraint : IConstraintStrategy
|
||||
{
|
||||
public string StrategyType => "TotalLimit";
|
||||
|
||||
public Task<ConstraintResult> ExecuteAsync(CampaignContext context, ConstraintConfig config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var maxCount = config.GetParameter<int>("MaxCount");
|
||||
|
||||
if (context.AdditionalData.TryGetValue("TotalCount", out var countObj) &&
|
||||
countObj is int currentCount)
|
||||
{
|
||||
if (currentCount >= maxCount)
|
||||
{
|
||||
return Task.FromResult(ConstraintResult.LimitReached($"Total limit of {maxCount} reached", 0));
|
||||
}
|
||||
|
||||
return Task.FromResult(ConstraintResult.Pass());
|
||||
}
|
||||
|
||||
return Task.FromResult(ConstraintResult.Pass());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Fengling.Activity.Domain.Strategies;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public interface IActionStrategy
|
||||
{
|
||||
string StrategyType { get; }
|
||||
Task<ActionResult> ExecuteAsync(CampaignContext context, ActionConfig config, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Fengling.Activity.Domain.Strategies;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public interface IConditionStrategy
|
||||
{
|
||||
string StrategyType { get; }
|
||||
Task<ConditionResult> ExecuteAsync(CampaignContext context, ConditionConfig config, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Fengling.Activity.Domain.Strategies;
|
||||
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public interface IConstraintStrategy
|
||||
{
|
||||
string StrategyType { get; }
|
||||
Task<ConstraintResult> ExecuteAsync(CampaignContext context, ConstraintConfig config, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
namespace Fengling.Activity.Domain.Strategies;
|
||||
|
||||
public interface IStrategyFactory
|
||||
{
|
||||
IConditionStrategy? GetConditionStrategy(string strategyType);
|
||||
IConstraintStrategy? GetConstraintStrategy(string strategyType);
|
||||
IActionStrategy? GetActionStrategy(string strategyType);
|
||||
}
|
||||
41
src/Fengling.Activity.Domain/ValueObjects/ActionConfig.cs
Normal file
41
src/Fengling.Activity.Domain/ValueObjects/ActionConfig.cs
Normal file
@ -0,0 +1,41 @@
|
||||
namespace Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class ActionConfig : IEquatable<ActionConfig>
|
||||
{
|
||||
public string StrategyType { get; }
|
||||
public IReadOnlyDictionary<string, object> Parameters { get; }
|
||||
|
||||
private ActionConfig(string strategyType, Dictionary<string, object> parameters)
|
||||
{
|
||||
StrategyType = strategyType;
|
||||
Parameters = parameters;
|
||||
}
|
||||
|
||||
public static ActionConfig Create(string strategyType, Dictionary<string, object> parameters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(strategyType))
|
||||
throw new ArgumentException("Strategy type cannot be empty");
|
||||
return new ActionConfig(strategyType, parameters);
|
||||
}
|
||||
|
||||
public T? GetParameter<T>(string key)
|
||||
{
|
||||
if (Parameters.TryGetValue(key, out var value) && value is T typedValue)
|
||||
return typedValue;
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool Equals(ActionConfig? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (StrategyType != other.StrategyType) return false;
|
||||
return Parameters.Count == other.Parameters.Count &&
|
||||
Parameters.All(p => other.Parameters.TryGetValue(p.Key, out var ov) &&
|
||||
(p.Value?.Equals(ov) ?? ov is null));
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => Equals(obj as ActionConfig);
|
||||
public override int GetHashCode() => HashCode.Combine(StrategyType, Parameters);
|
||||
public static bool operator ==(ActionConfig a, ActionConfig b) => a.Equals(b);
|
||||
public static bool operator !=(ActionConfig a, ActionConfig b) => !a.Equals(b);
|
||||
}
|
||||
18
src/Fengling.Activity.Domain/ValueObjects/CampaignId.cs
Normal file
18
src/Fengling.Activity.Domain/ValueObjects/CampaignId.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class CampaignId : IEquatable<CampaignId>
|
||||
{
|
||||
public Guid Value { get; }
|
||||
|
||||
private CampaignId(Guid value) => Value = value;
|
||||
|
||||
public static CampaignId New() => new(Guid.NewGuid());
|
||||
public static CampaignId FromGuid(Guid value) => new(value);
|
||||
public static CampaignId Parse(string value) => new(Guid.Parse(value));
|
||||
|
||||
public bool Equals(CampaignId? other) => other?.Value == Value;
|
||||
public override bool Equals(object? obj) => obj is CampaignId other && Equals(other);
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
public static implicit operator Guid(CampaignId id) => id.Value;
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
29
src/Fengling.Activity.Domain/ValueObjects/CampaignName.cs
Normal file
29
src/Fengling.Activity.Domain/ValueObjects/CampaignName.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class CampaignName : IEquatable<CampaignName>
|
||||
{
|
||||
public string Value { get; }
|
||||
|
||||
private CampaignName(string value) => Value = value;
|
||||
|
||||
public static CampaignName Create(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
throw new ArgumentException("Campaign name cannot be empty");
|
||||
if (name.Length > 200)
|
||||
throw new ArgumentException("Campaign name cannot exceed 200 characters");
|
||||
return new CampaignName(name.Trim());
|
||||
}
|
||||
|
||||
public bool Equals(CampaignName? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
return Value == other.Value;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => Equals(obj as CampaignName);
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
public static bool operator ==(CampaignName a, CampaignName b) => a.Equals(b);
|
||||
public static bool operator !=(CampaignName a, CampaignName b) => !a.Equals(b);
|
||||
public override string ToString() => Value;
|
||||
}
|
||||
41
src/Fengling.Activity.Domain/ValueObjects/ConditionConfig.cs
Normal file
41
src/Fengling.Activity.Domain/ValueObjects/ConditionConfig.cs
Normal file
@ -0,0 +1,41 @@
|
||||
namespace Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class ConditionConfig : IEquatable<ConditionConfig>
|
||||
{
|
||||
public string StrategyType { get; }
|
||||
public IReadOnlyDictionary<string, object> Parameters { get; }
|
||||
|
||||
private ConditionConfig(string strategyType, Dictionary<string, object> parameters)
|
||||
{
|
||||
StrategyType = strategyType;
|
||||
Parameters = parameters;
|
||||
}
|
||||
|
||||
public static ConditionConfig Create(string strategyType, Dictionary<string, object> parameters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(strategyType))
|
||||
throw new ArgumentException("Strategy type cannot be empty");
|
||||
return new ConditionConfig(strategyType, parameters);
|
||||
}
|
||||
|
||||
public T? GetParameter<T>(string key)
|
||||
{
|
||||
if (Parameters.TryGetValue(key, out var value) && value is T typedValue)
|
||||
return typedValue;
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool Equals(ConditionConfig? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (StrategyType != other.StrategyType) return false;
|
||||
return Parameters.Count == other.Parameters.Count &&
|
||||
Parameters.All(p => other.Parameters.TryGetValue(p.Key, out var ov) &&
|
||||
(p.Value?.Equals(ov) ?? ov is null));
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => Equals(obj as ConditionConfig);
|
||||
public override int GetHashCode() => HashCode.Combine(StrategyType, Parameters);
|
||||
public static bool operator ==(ConditionConfig a, ConditionConfig b) => a.Equals(b);
|
||||
public static bool operator !=(ConditionConfig a, ConditionConfig b) => !a.Equals(b);
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
namespace Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class ConstraintConfig : IEquatable<ConstraintConfig>
|
||||
{
|
||||
public string StrategyType { get; }
|
||||
public IReadOnlyDictionary<string, object> Parameters { get; }
|
||||
|
||||
private ConstraintConfig(string strategyType, Dictionary<string, object> parameters)
|
||||
{
|
||||
StrategyType = strategyType;
|
||||
Parameters = parameters;
|
||||
}
|
||||
|
||||
public static ConstraintConfig Create(string strategyType, Dictionary<string, object> parameters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(strategyType))
|
||||
throw new ArgumentException("Strategy type cannot be empty");
|
||||
return new ConstraintConfig(strategyType, parameters);
|
||||
}
|
||||
|
||||
public T? GetParameter<T>(string key)
|
||||
{
|
||||
if (Parameters.TryGetValue(key, out var value) && value is T typedValue)
|
||||
return typedValue;
|
||||
return default;
|
||||
}
|
||||
|
||||
public bool Equals(ConstraintConfig? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
if (StrategyType != other.StrategyType) return false;
|
||||
return Parameters.Count == other.Parameters.Count &&
|
||||
Parameters.All(p => other.Parameters.TryGetValue(p.Key, out var ov) &&
|
||||
(p.Value?.Equals(ov) ?? ov is null));
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => Equals(obj as ConstraintConfig);
|
||||
public override int GetHashCode() => HashCode.Combine(StrategyType, Parameters);
|
||||
public static bool operator ==(ConstraintConfig a, ConstraintConfig b) => a.Equals(b);
|
||||
public static bool operator !=(ConstraintConfig a, ConstraintConfig b) => !a.Equals(b);
|
||||
}
|
||||
18
src/Fengling.Activity.Domain/ValueObjects/TenantId.cs
Normal file
18
src/Fengling.Activity.Domain/ValueObjects/TenantId.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class TenantId : IEquatable<TenantId>
|
||||
{
|
||||
public Guid Value { get; }
|
||||
|
||||
private TenantId(Guid value) => Value = value;
|
||||
|
||||
public static TenantId New() => new(Guid.NewGuid());
|
||||
public static TenantId FromGuid(Guid value) => new(value);
|
||||
public static TenantId Parse(string value) => new(Guid.Parse(value));
|
||||
|
||||
public bool Equals(TenantId? other) => other?.Value == Value;
|
||||
public override bool Equals(object? obj) => obj is TenantId other && Equals(other);
|
||||
public override int GetHashCode() => Value.GetHashCode();
|
||||
public static implicit operator Guid(TenantId id) => id.Value;
|
||||
public override string ToString() => Value.ToString();
|
||||
}
|
||||
35
src/Fengling.Activity.Domain/ValueObjects/TimeRange.cs
Normal file
35
src/Fengling.Activity.Domain/ValueObjects/TimeRange.cs
Normal file
@ -0,0 +1,35 @@
|
||||
namespace Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
public class TimeRange : IEquatable<TimeRange>
|
||||
{
|
||||
public DateTime StartTime { get; }
|
||||
public DateTime EndTime { get; }
|
||||
|
||||
private TimeRange(DateTime startTime, DateTime endTime)
|
||||
{
|
||||
StartTime = startTime;
|
||||
EndTime = endTime;
|
||||
}
|
||||
|
||||
public static TimeRange Create(DateTime startTime, DateTime endTime)
|
||||
{
|
||||
if (startTime >= endTime)
|
||||
throw new ArgumentException("Start time must be before end time");
|
||||
return new TimeRange(startTime, endTime);
|
||||
}
|
||||
|
||||
public bool Contains(DateTime time) => time >= StartTime && time <= EndTime;
|
||||
public bool IsActive() => Contains(DateTime.UtcNow);
|
||||
public TimeSpan Duration() => EndTime - StartTime;
|
||||
|
||||
public bool Equals(TimeRange? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
return StartTime == other.StartTime && EndTime == other.EndTime;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => Equals(obj as TimeRange);
|
||||
public override int GetHashCode() => HashCode.Combine(StartTime, EndTime);
|
||||
public static bool operator ==(TimeRange a, TimeRange b) => a.Equals(b);
|
||||
public static bool operator !=(TimeRange a, TimeRange b) => !a.Equals(b);
|
||||
}
|
||||
30
src/Fengling.Activity.Infrastructure/ApplicationDbContext.cs
Normal file
30
src/Fengling.Activity.Infrastructure/ApplicationDbContext.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NetCorePal.Extensions.DistributedTransactions.CAP.Persistence;
|
||||
|
||||
namespace Fengling.Activity.Infrastructure;
|
||||
|
||||
public partial class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IMediator mediator)
|
||||
: AppDbContextBase(options, mediator)
|
||||
, IPostgreSqlCapDataStorage
|
||||
{
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
if (modelBuilder is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modelBuilder));
|
||||
}
|
||||
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
ConfigureStronglyTypedIdValueConverter(configurationBuilder);
|
||||
base.ConfigureConventions(configurationBuilder);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Fengling.Activity.Infrastructure;
|
||||
|
||||
public class DesignTimeApplicationDbContextFactory: IDesignTimeDbContextFactory<ApplicationDbContext>
|
||||
{
|
||||
public ApplicationDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
IServiceCollection services = new ServiceCollection();
|
||||
services.AddMediatR(c =>
|
||||
c.RegisterServicesFromAssemblies(typeof(DesignTimeApplicationDbContextFactory).Assembly));
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
{
|
||||
// change connectionstring if you want to run command “dotnet ef database update”
|
||||
options.UseNpgsql("Host=any;Database=any;Username=any;Password=any",
|
||||
b =>
|
||||
{
|
||||
b.MigrationsAssembly(typeof(DesignTimeApplicationDbContextFactory).Assembly.FullName);
|
||||
});
|
||||
});
|
||||
var provider = services.BuildServiceProvider();
|
||||
var dbContext = provider.CreateScope().ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
return dbContext;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.Aggregates.RewardGrantRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.EntityConfigurations;
|
||||
|
||||
public class CampaignEntityConfiguration : IEntityTypeConfiguration<Campaign>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Campaign> builder)
|
||||
{
|
||||
builder.ToTable("act_campaigns");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Id)
|
||||
.HasColumnName("id")
|
||||
.ValueGeneratedNever();
|
||||
|
||||
builder.Property(x => x.TenantId)
|
||||
.HasColumnName("tenant_id")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.Name)
|
||||
.HasColumnName("name")
|
||||
.HasMaxLength(200)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.Type)
|
||||
.HasColumnName("type")
|
||||
.HasConversion<int>()
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.Status)
|
||||
.HasColumnName("status")
|
||||
.HasConversion<int>()
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.Description)
|
||||
.HasColumnName("description")
|
||||
.HasMaxLength(1000);
|
||||
|
||||
builder.Property(x => x.TimeRange)
|
||||
.HasColumnName("time_range")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
builder.Property(x => x.MaxParticipants)
|
||||
.HasColumnName("max_participants");
|
||||
|
||||
builder.Property(x => x.MaxRewardsPerMember)
|
||||
.HasColumnName("max_rewards_per_member");
|
||||
|
||||
builder.Property(x => x.CreatedAt)
|
||||
.HasColumnName("created_at")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.UpdatedAt)
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
builder.Property(x => x.Version)
|
||||
.HasColumnName("version")
|
||||
.IsRequired();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.EntityConfigurations;
|
||||
|
||||
public class ParticipationRecordEntityConfiguration : IEntityTypeConfiguration<ParticipationRecord>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ParticipationRecord> builder)
|
||||
{
|
||||
builder.ToTable("act_participation_records");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Id)
|
||||
.HasColumnName("id")
|
||||
.ValueGeneratedNever();
|
||||
|
||||
builder.Property(x => x.CampaignId)
|
||||
.HasColumnName("campaign_id")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.TenantId)
|
||||
.HasColumnName("tenant_id")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.MemberId)
|
||||
.HasColumnName("member_id")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.Status)
|
||||
.HasColumnName("status")
|
||||
.HasConversion<int>()
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.CurrentProgress)
|
||||
.HasColumnName("current_progress")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.TargetProgress)
|
||||
.HasColumnName("target_progress");
|
||||
|
||||
builder.Property(x => x.RewardsGranted)
|
||||
.HasColumnName("rewards_granted")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.ParticipatedAt)
|
||||
.HasColumnName("participated_at")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.CompletedAt)
|
||||
.HasColumnName("completed_at");
|
||||
|
||||
builder.Property(x => x.ExpiredAt)
|
||||
.HasColumnName("expired_at");
|
||||
|
||||
builder.Property(x => x.UpdatedAt)
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
builder.HasIndex(x => new { x.CampaignId, x.MemberId })
|
||||
.HasDatabaseName("ix_participation_campaign_member");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Fengling.Activity.Domain.Aggregates.RewardGrantRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.EntityConfigurations;
|
||||
|
||||
public class RewardGrantRecordEntityConfiguration : IEntityTypeConfiguration<RewardGrantRecord>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<RewardGrantRecord> builder)
|
||||
{
|
||||
builder.ToTable("act_reward_grant_records");
|
||||
|
||||
builder.HasKey(x => x.Id);
|
||||
|
||||
builder.Property(x => x.Id)
|
||||
.HasColumnName("id")
|
||||
.ValueGeneratedNever();
|
||||
|
||||
builder.Property(x => x.CampaignId)
|
||||
.HasColumnName("campaign_id")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.TenantId)
|
||||
.HasColumnName("tenant_id")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.MemberId)
|
||||
.HasColumnName("member_id")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.ParticipationRecordId)
|
||||
.HasColumnName("participation_record_id");
|
||||
|
||||
builder.Property(x => x.RewardType)
|
||||
.HasColumnName("reward_type")
|
||||
.HasConversion<int>()
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.RewardAmount)
|
||||
.HasColumnName("reward_amount")
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(x => x.RewardCode)
|
||||
.HasColumnName("reward_code")
|
||||
.HasMaxLength(100);
|
||||
|
||||
builder.Property(x => x.Remark)
|
||||
.HasColumnName("remark")
|
||||
.HasMaxLength(500);
|
||||
|
||||
builder.Property(x => x.GrantedAt)
|
||||
.HasColumnName("granted_at")
|
||||
.IsRequired();
|
||||
|
||||
builder.HasIndex(x => new { x.CampaignId, x.MemberId })
|
||||
.HasDatabaseName("ix_reward_campaign_member");
|
||||
|
||||
builder.HasIndex(x => x.GrantedAt)
|
||||
.HasDatabaseName("ix_reward_granted_at");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design"/>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL"/>
|
||||
<PackageReference Include="NetCorePal.Extensions.DistributedTransactions.CAP.PostgreSQL"/>
|
||||
<PackageReference Include="NetCorePal.Extensions.Repository.EntityFrameworkCore"/>
|
||||
<PackageReference Include="NetCorePal.Extensions.Repository.EntityFrameworkCore.Snowflake"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Fengling.Activity.Domain\Fengling.Activity.Domain.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="SonarAnalyzer.CSharp">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Migrations\"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
6
src/Fengling.Activity.Infrastructure/GlobalUsings.cs
Normal file
6
src/Fengling.Activity.Infrastructure/GlobalUsings.cs
Normal file
@ -0,0 +1,6 @@
|
||||
global using NetCorePal.Extensions.Domain;
|
||||
global using NetCorePal.Extensions.Primitives;
|
||||
global using NetCorePal.Extensions.Repository;
|
||||
global using NetCorePal.Extensions.Repository.EntityFrameworkCore;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
140
src/Fengling.Activity.Infrastructure/Migrations/20260122054728_Init.Designer.cs
generated
Normal file
140
src/Fengling.Activity.Infrastructure/Migrations/20260122054728_Init.Designer.cs
generated
Normal file
@ -0,0 +1,140 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Fengling.Activity.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260122054728_Init")]
|
||||
partial class Init
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.CapLock", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Instance")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<DateTime?>("LastLockTime")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("CAPLock", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.PublishedMessage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Added")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ExpiresAt")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<int?>("Retries")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StatusName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("character varying(40)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName");
|
||||
|
||||
b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName");
|
||||
|
||||
b.ToTable("CAPPublishedMessage", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.ReceivedMessage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Added")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ExpiresAt")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Group")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(400)
|
||||
.HasColumnType("character varying(400)");
|
||||
|
||||
b.Property<int?>("Retries")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StatusName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName")
|
||||
.HasDatabaseName("IX_ExpiresAt_StatusName1");
|
||||
|
||||
b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName")
|
||||
.HasDatabaseName("IX_Version_ExpiresAt_StatusName1");
|
||||
|
||||
b.ToTable("CAPReceivedMessage", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Init : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CAPLock",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
Instance = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
LastLockTime = table.Column<DateTime>(type: "TIMESTAMP", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CAPLock", x => x.Key);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CAPPublishedMessage",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Version = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Content = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Retries = table.Column<int>(type: "integer", nullable: true),
|
||||
Added = table.Column<DateTime>(type: "TIMESTAMP", nullable: false),
|
||||
ExpiresAt = table.Column<DateTime>(type: "TIMESTAMP", nullable: true),
|
||||
StatusName = table.Column<string>(type: "character varying(40)", maxLength: 40, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CAPPublishedMessage", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CAPReceivedMessage",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Version = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: true),
|
||||
Name = table.Column<string>(type: "character varying(400)", maxLength: 400, nullable: false),
|
||||
Group = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
Content = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Retries = table.Column<int>(type: "integer", nullable: true),
|
||||
Added = table.Column<DateTime>(type: "TIMESTAMP", nullable: false),
|
||||
ExpiresAt = table.Column<DateTime>(type: "TIMESTAMP", nullable: true),
|
||||
StatusName = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CAPReceivedMessage", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExpiresAt_StatusName",
|
||||
table: "CAPPublishedMessage",
|
||||
columns: new[] { "ExpiresAt", "StatusName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Version_ExpiresAt_StatusName",
|
||||
table: "CAPPublishedMessage",
|
||||
columns: new[] { "Version", "ExpiresAt", "StatusName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExpiresAt_StatusName1",
|
||||
table: "CAPReceivedMessage",
|
||||
columns: new[] { "ExpiresAt", "StatusName" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Version_ExpiresAt_StatusName1",
|
||||
table: "CAPReceivedMessage",
|
||||
columns: new[] { "Version", "ExpiresAt", "StatusName" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CAPLock");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CAPPublishedMessage");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CAPReceivedMessage");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Fengling.Activity.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "9.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.CapLock", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Instance")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<DateTime?>("LastLockTime")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("CAPLock", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.PublishedMessage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Added")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ExpiresAt")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<int?>("Retries")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StatusName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(40)
|
||||
.HasColumnType("character varying(40)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName");
|
||||
|
||||
b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName");
|
||||
|
||||
b.ToTable("CAPPublishedMessage", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NetCorePal.Extensions.DistributedTransactions.CAP.Persistence.ReceivedMessage", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<DateTime>("Added")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("ExpiresAt")
|
||||
.HasColumnType("TIMESTAMP");
|
||||
|
||||
b.Property<string>("Group")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(400)
|
||||
.HasColumnType("character varying(400)");
|
||||
|
||||
b.Property<int?>("Retries")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StatusName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.Property<string>("Version")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex(new[] { "ExpiresAt", "StatusName" }, "IX_ExpiresAt_StatusName")
|
||||
.HasDatabaseName("IX_ExpiresAt_StatusName1");
|
||||
|
||||
b.HasIndex(new[] { "Version", "ExpiresAt", "StatusName" }, "IX_Version_ExpiresAt_StatusName")
|
||||
.HasDatabaseName("IX_Version_ExpiresAt_StatusName1");
|
||||
|
||||
b.ToTable("CAPReceivedMessage", (string)null);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.Repositories;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using Fengling.Activity.Infrastructure;
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.Repositories;
|
||||
|
||||
public class CampaignRepository : ICampaignRepository
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public CampaignRepository(ApplicationDbContext context) => _context = context;
|
||||
|
||||
public async Task<Campaign?> GetByIdAsync(CampaignId id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<Campaign>()
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Campaign>> GetByTenantIdAsync(TenantId tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<Campaign>()
|
||||
.Where(x => x.TenantId == tenantId)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Campaign>> GetByStatusAsync(CampaignStatus status, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<Campaign>()
|
||||
.Where(x => x.Status == status)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Campaign>> GetActiveCampaignsAsync(TenantId tenantId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
return await _context.Set<Campaign>()
|
||||
.Where(x => x.TenantId == tenantId &&
|
||||
x.Status == CampaignStatus.Published &&
|
||||
x.TimeRange.StartTime <= now &&
|
||||
x.TimeRange.EndTime >= now)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task AddAsync(Campaign campaign, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _context.Set<Campaign>().AddAsync(campaign, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(Campaign campaign, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_context.Set<Campaign>().Update(campaign);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(Campaign campaign, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_context.Set<Campaign>().Remove(campaign);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<bool> ExistsAsync(CampaignId id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<Campaign>()
|
||||
.AnyAsync(x => x.Id == id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Aggregates.ParticipationRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.Repositories;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using Fengling.Activity.Infrastructure;
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.Repositories;
|
||||
|
||||
public class ParticipationRecordRepository : IParticipationRecordRepository
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public ParticipationRecordRepository(ApplicationDbContext context) => _context = context;
|
||||
|
||||
public async Task<ParticipationRecord?> GetByIdAsync(ParticipationRecordId id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<ParticipationRecord>()
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<ParticipationRecord>> GetByCampaignIdAsync(CampaignId campaignId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<ParticipationRecord>()
|
||||
.Where(x => x.CampaignId == campaignId)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<ParticipationRecord>> GetByMemberIdAsync(TenantId tenantId, Guid memberId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<ParticipationRecord>()
|
||||
.Where(x => x.TenantId == tenantId && x.MemberId == memberId)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<ParticipationRecord?> GetByCampaignAndMemberAsync(CampaignId campaignId, Guid memberId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<ParticipationRecord>()
|
||||
.FirstOrDefaultAsync(x => x.CampaignId == campaignId && x.MemberId == memberId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<ParticipationRecord>> GetByStatusAsync(CampaignId campaignId, ParticipationStatus status, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<ParticipationRecord>()
|
||||
.Where(x => x.CampaignId == campaignId && x.Status == status)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<int> GetParticipationCountAsync(CampaignId campaignId, Guid memberId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<ParticipationRecord>()
|
||||
.CountAsync(x => x.CampaignId == campaignId && x.MemberId == memberId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task AddAsync(ParticipationRecord record, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _context.Set<ParticipationRecord>().AddAsync(record, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(ParticipationRecord record, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_context.Set<ParticipationRecord>().Update(record);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(ParticipationRecord record, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_context.Set<ParticipationRecord>().Remove(record);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Aggregates.RewardGrantRecords;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.Repositories;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
using Fengling.Activity.Infrastructure;
|
||||
|
||||
namespace Fengling.Activity.Infrastructure.Repositories;
|
||||
|
||||
public class RewardGrantRecordRepository : IRewardGrantRecordRepository
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
|
||||
public RewardGrantRecordRepository(ApplicationDbContext context) => _context = context;
|
||||
|
||||
public async Task<RewardGrantRecord?> GetByIdAsync(RewardGrantRecordId id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<RewardGrantRecord>()
|
||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<RewardGrantRecord>> GetByCampaignIdAsync(CampaignId campaignId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<RewardGrantRecord>()
|
||||
.Where(x => x.CampaignId == campaignId)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<RewardGrantRecord>> GetByMemberIdAsync(TenantId tenantId, Guid memberId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<RewardGrantRecord>()
|
||||
.Where(x => x.TenantId == tenantId && x.MemberId == memberId)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<int> GetRewardCountAsync(CampaignId campaignId, Guid memberId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<RewardGrantRecord>()
|
||||
.CountAsync(x => x.CampaignId == campaignId && x.MemberId == memberId, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task AddAsync(RewardGrantRecord record, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _context.Set<RewardGrantRecord>().AddAsync(record, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task UpdateAsync(RewardGrantRecord record, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_context.Set<RewardGrantRecord>().Update(record);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(RewardGrantRecord record, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_context.Set<RewardGrantRecord>().Remove(record);
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task<List<RewardGrantRecord>> GetRewardsByDateRangeAsync(TenantId tenantId, DateTime startDate, DateTime endDate, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Set<RewardGrantRecord>()
|
||||
.Where(x => x.TenantId == tenantId &&
|
||||
x.GrantedAt >= startDate &&
|
||||
x.GrantedAt <= endDate)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
148
src/Fengling.Activity.Web/Controllers/CampaignsController.cs
Normal file
148
src/Fengling.Activity.Web/Controllers/CampaignsController.cs
Normal file
@ -0,0 +1,148 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Fengling.Activity.Domain.Aggregates.Campaigns;
|
||||
using Fengling.Activity.Domain.Enums;
|
||||
using Fengling.Activity.Domain.Repositories;
|
||||
using Fengling.Activity.Domain.ValueObjects;
|
||||
|
||||
namespace Fengling.Activity.Web.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class CampaignsController : ControllerBase
|
||||
{
|
||||
private readonly ICampaignRepository _campaignRepository;
|
||||
|
||||
public CampaignsController(ICampaignRepository campaignRepository)
|
||||
{
|
||||
_campaignRepository = campaignRepository;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<CreateCampaignResponse>> CreateCampaign(
|
||||
[FromBody] CreateCampaignRequest request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var campaign = Campaign.Create(
|
||||
TenantId.FromGuid(request.TenantId),
|
||||
CampaignName.Create(request.Name),
|
||||
request.Type,
|
||||
TimeRange.Create(request.StartTime, request.EndTime),
|
||||
request.Description);
|
||||
|
||||
if (request.MaxParticipants.HasValue)
|
||||
{
|
||||
campaign.UpdateMaxParticipants(request.MaxParticipants);
|
||||
}
|
||||
|
||||
if (request.MaxRewardsPerMember.HasValue)
|
||||
{
|
||||
campaign.UpdateMaxRewardsPerMember(request.MaxRewardsPerMember);
|
||||
}
|
||||
|
||||
await _campaignRepository.AddAsync(campaign, cancellationToken);
|
||||
|
||||
return CreatedAtAction(nameof(GetCampaign), new { campaignId = campaign.Id.Value }, new CreateCampaignResponse
|
||||
{
|
||||
CampaignId = campaign.Id.Value,
|
||||
Name = campaign.Name.Value,
|
||||
Status = campaign.Status.ToString(),
|
||||
CreatedAt = campaign.CreatedAt
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{campaignId:guid}")]
|
||||
public async Task<ActionResult<GetCampaignResponse>> GetCampaign(Guid campaignId, CancellationToken cancellationToken)
|
||||
{
|
||||
var campaign = await _campaignRepository.GetByIdAsync(CampaignId.FromGuid(campaignId), cancellationToken);
|
||||
if (campaign == null)
|
||||
{
|
||||
return NotFound(new { error = "Campaign not found" });
|
||||
}
|
||||
|
||||
return Ok(new GetCampaignResponse
|
||||
{
|
||||
CampaignId = campaign.Id.Value,
|
||||
TenantId = campaign.TenantId.Value,
|
||||
Name = campaign.Name.Value,
|
||||
Type = campaign.Type.ToString(),
|
||||
Status = campaign.Status.ToString(),
|
||||
Description = campaign.Description,
|
||||
StartTime = campaign.TimeRange.StartTime,
|
||||
EndTime = campaign.TimeRange.EndTime,
|
||||
MaxParticipants = campaign.MaxParticipants,
|
||||
MaxRewardsPerMember = campaign.MaxRewardsPerMember,
|
||||
CreatedAt = campaign.CreatedAt,
|
||||
UpdatedAt = campaign.UpdatedAt
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{campaignId:guid}/publish")]
|
||||
public async Task<ActionResult<PublishCampaignResponse>> PublishCampaign(Guid campaignId, CancellationToken cancellationToken)
|
||||
{
|
||||
var campaign = await _campaignRepository.GetByIdAsync(CampaignId.FromGuid(campaignId), cancellationToken);
|
||||
if (campaign == null)
|
||||
{
|
||||
return NotFound(new { error = "Campaign not found" });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
campaign.Publish();
|
||||
await _campaignRepository.UpdateAsync(campaign, cancellationToken);
|
||||
|
||||
return Ok(new PublishCampaignResponse
|
||||
{
|
||||
CampaignId = campaign.Id.Value,
|
||||
Status = campaign.Status.ToString(),
|
||||
UpdatedAt = campaign.UpdatedAt
|
||||
});
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CreateCampaignRequest
|
||||
{
|
||||
public Guid TenantId { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public CampaignType Type { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime EndTime { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public int? MaxParticipants { get; set; }
|
||||
public int? MaxRewardsPerMember { get; set; }
|
||||
}
|
||||
|
||||
public class CreateCampaignResponse
|
||||
{
|
||||
public Guid CampaignId { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public string Status { get; set; } = "";
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class GetCampaignResponse
|
||||
{
|
||||
public Guid CampaignId { get; set; }
|
||||
public Guid TenantId { get; set; }
|
||||
public string Name { get; set; } = "";
|
||||
public string Type { get; set; } = "";
|
||||
public string Status { get; set; } = "";
|
||||
public string? Description { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime EndTime { get; set; }
|
||||
public int? MaxParticipants { get; set; }
|
||||
public int? MaxRewardsPerMember { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class PublishCampaignResponse
|
||||
{
|
||||
public Guid CampaignId { get; set; }
|
||||
public string Status { get; set; } = "";
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
}
|
||||
21
src/Fengling.Activity.Web/Dockerfile
Normal file
21
src/Fengling.Activity.Web/Dockerfile
Normal file
@ -0,0 +1,21 @@
|
||||
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
WORKDIR /app
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN dotnet restore "src/Fengling.Activity.Web/Fengling.Activity.Web.csproj"
|
||||
WORKDIR "/src/src/Fengling.Activity.Web"
|
||||
RUN dotnet build "Fengling.Activity.Web.csproj" -c Release -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "Fengling.Activity.Web.csproj" -c Release -o /app/publish
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "Fengling.Activity.Web.dll"]
|
||||
68
src/Fengling.Activity.Web/Fengling.Activity.Web.csproj
Normal file
68
src/Fengling.Activity.Web/Fengling.Activity.Web.csproj
Normal file
@ -0,0 +1,68 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<DockerfileContext>..\..</DockerfileContext>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNet.Security.OAuth.Feishu" />
|
||||
<PackageReference Include="AspNet.Security.OAuth.Weixin" />
|
||||
<PackageReference Include="DotNetCore.CAP.Dashboard" />
|
||||
<PackageReference Include="DotNetCore.CAP.RabbitMQ" />
|
||||
<PackageReference Include="FastEndpoints" />
|
||||
<PackageReference Include="FastEndpoints.Swagger" />
|
||||
<PackageReference Include="FastEndpoints.Swagger.Swashbuckle" />
|
||||
<PackageReference Include="FluentValidation.AspNetCore" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" />
|
||||
<PackageReference Include="Hangfire.Redis.StackExchange" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" />
|
||||
<PackageReference Include="NetCorePal.Context.AspNetCore" />
|
||||
<PackageReference Include="NetCorePal.Context.CAP" />
|
||||
<PackageReference Include="NetCorePal.Context.Shared" />
|
||||
<PackageReference Include="NetCorePal.Extensions.AspNetCore" />
|
||||
<PackageReference Include="NetCorePal.Extensions.CodeAnalysis" />
|
||||
<PackageReference Include="NetCorePal.Extensions.DistributedLocks.Redis" />
|
||||
<PackageReference Include="NetCorePal.Extensions.MultiEnv" />
|
||||
<PackageReference Include="NetCorePal.Extensions.MicrosoftServiceDiscovery" />
|
||||
<PackageReference Include="NetCorePal.Extensions.Primitives" />
|
||||
<PackageReference Include="NetCorePal.Extensions.Jwt.StackExchangeRedis" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore" />
|
||||
<PackageReference Include="prometheus-net.AspNetCore.HealthChecks" />
|
||||
<PackageReference Include="Refit.HttpClientFactory" />
|
||||
<PackageReference Include="Refit.Newtonsoft.Json" />
|
||||
<PackageReference Include="Serilog.AspNetCore" />
|
||||
<PackageReference Include="Serilog.Enrichers.ClientInfo" />
|
||||
<PackageReference Include="Serilog.Sinks.OpenTelemetry" />
|
||||
<PackageReference Include="StackExchange.Redis" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Fengling.Activity.Domain\Fengling.Activity.Domain.csproj" />
|
||||
<ProjectReference Include="..\Fengling.Activity.Infrastructure\Fengling.Activity.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="SonarAnalyzer.CSharp">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
10
src/Fengling.Activity.Web/GlobalUsings.cs
Normal file
10
src/Fengling.Activity.Web/GlobalUsings.cs
Normal file
@ -0,0 +1,10 @@
|
||||
global using NetCorePal.Extensions.AspNetCore;
|
||||
global using NetCorePal.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Fengling.Activity.Infrastructure;
|
||||
global using FluentValidation;
|
||||
global using NetCorePal.Extensions.Primitives;
|
||||
global using MediatR;
|
||||
global using NetCorePal.Extensions.Domain;
|
||||
global using NetCorePal.Extensions.Dto;
|
||||
global using NetCorePal.Extensions.DistributedTransactions;
|
||||
105
src/Fengling.Activity.Web/Program.cs
Normal file
105
src/Fengling.Activity.Web/Program.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Prometheus;
|
||||
using System.Reflection;
|
||||
using FastEndpoints;
|
||||
using Serilog;
|
||||
using Serilog.Formatting.Json;
|
||||
using FluentValidation.AspNetCore;
|
||||
using NetCorePal.Extensions.CodeAnalysis;
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.Enrich.WithClientIp()
|
||||
.WriteTo.Console(new JsonFormatter())
|
||||
.CreateLogger();
|
||||
|
||||
try
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Host.UseSerilog();
|
||||
|
||||
builder.Services.AddHealthChecks();
|
||||
builder.Services.AddControllers();
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
|
||||
builder.Services.AddFastEndpoints(o => o.IncludeAbstractValidators = true);
|
||||
|
||||
builder.Services.AddFluentValidationAutoValidation();
|
||||
builder.Services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
|
||||
builder.Services.AddKnownExceptionErrorModelInterceptor();
|
||||
|
||||
builder.Services.AddRepositories(typeof(ApplicationDbContext).Assembly);
|
||||
|
||||
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
||||
{
|
||||
options.UseNpgsql(builder.Configuration.GetConnectionString("PostgreSQL"));
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
options.EnableSensitiveDataLogging();
|
||||
}
|
||||
options.EnableDetailedErrors();
|
||||
});
|
||||
builder.Services.AddUnitOfWork<ApplicationDbContext>();
|
||||
builder.Services.AddContext().AddEnvContext().AddCapContextProcessor();
|
||||
builder.Services.AddIntegrationEvents(typeof(Program))
|
||||
.UseCap<ApplicationDbContext>(b =>
|
||||
{
|
||||
b.RegisterServicesFromAssemblies(typeof(Program));
|
||||
b.AddContextIntegrationFilters();
|
||||
});
|
||||
|
||||
builder.Services.AddCap(x =>
|
||||
{
|
||||
x.UseNetCorePalStorage<ApplicationDbContext>();
|
||||
x.ConsumerThreadCount = Environment.ProcessorCount;
|
||||
x.UseRabbitMQ(p => builder.Configuration.GetSection("RabbitMQ").Bind(p));
|
||||
x.UseDashboard();
|
||||
});
|
||||
|
||||
builder.Services.AddMediatR(cfg =>
|
||||
cfg.RegisterServicesFromAssemblies(Assembly.GetExecutingAssembly())
|
||||
.AddCommandLockBehavior()
|
||||
.AddKnownExceptionValidationBehavior()
|
||||
.AddUnitOfWorkBehaviors());
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (!app.Environment.IsProduction())
|
||||
{
|
||||
using var scope = app.Services.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
await dbContext.Database.MigrateAsync();
|
||||
}
|
||||
|
||||
app.UseKnownExceptionHandler();
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseRouting();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.UseFastEndpoints();
|
||||
app.UseHttpMetrics();
|
||||
app.MapHealthChecks("/health");
|
||||
app.MapMetrics();
|
||||
|
||||
await app.RunAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Fatal(ex, "Application terminated unexpectedly");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await Log.CloseAndFlushAsync();
|
||||
}
|
||||
|
||||
#pragma warning disable S1118
|
||||
public partial class Program
|
||||
#pragma warning restore S1118
|
||||
{
|
||||
}
|
||||
25
src/Fengling.Activity.Web/Properties/launchSettings.json
Normal file
25
src/Fengling.Activity.Web/Properties/launchSettings.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5511",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7435;http://localhost:5511",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Fengling.Activity.Web/appsettings.Development.json
Normal file
31
src/Fengling.Activity.Web/appsettings.Development.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"PostgreSQL": "Host=localhost;Database=dev;Username=postgres;Password=123456",
|
||||
"Redis": "81.68.223.70:16379,password=sl52788542"
|
||||
},
|
||||
"RabbitMQ": {
|
||||
"HostName": "localhost",
|
||||
"UserName": "guest",
|
||||
"Password": "guest",
|
||||
"VirtualHost": "/",
|
||||
"Port": 5672
|
||||
},
|
||||
"Services": {
|
||||
"user": {
|
||||
"https": [
|
||||
"https://user:8443"
|
||||
]
|
||||
},
|
||||
"user-v2": {
|
||||
"https": [
|
||||
"https://user-v2:8443"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/Fengling.Activity.Web/appsettings.json
Normal file
32
src/Fengling.Activity.Web/appsettings.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"PostgreSQL": "Host=localhost;Database=dev;Username=postgres;Password=123456",
|
||||
"Redis": "81.68.223.70:16379,password=sl52788542"
|
||||
},
|
||||
"RabbitMQ": {
|
||||
"HostName": "localhost",
|
||||
"UserName": "guest",
|
||||
"Password": "guest",
|
||||
"VirtualHost": "/",
|
||||
"Port": 5672
|
||||
},
|
||||
"Services": {
|
||||
"user": {
|
||||
"https": [
|
||||
"https://user:8443"
|
||||
]
|
||||
},
|
||||
"user-v2": {
|
||||
"https": [
|
||||
"https://user-v2:8443"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
83
vs-snippets/Install-VSSnippets.ps1
Normal file
83
vs-snippets/Install-VSSnippets.ps1
Normal file
@ -0,0 +1,83 @@
|
||||
# NetCorePal Template - Visual Studio Code Snippets Installer
|
||||
# Auto install Visual Studio code snippets
|
||||
|
||||
param(
|
||||
[string]$VisualStudioVersion = "2022",
|
||||
[switch]$ShowPathOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "NetCorePal Template - Visual Studio Code Snippets Installer" -ForegroundColor Green
|
||||
Write-Host "=================================================" -ForegroundColor Green
|
||||
|
||||
# Get current script directory
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$SnippetFile = Join-Path $ScriptDir "NetCorePalTemplates.snippet"
|
||||
|
||||
# Check if snippet file exists
|
||||
if (-not (Test-Path $SnippetFile)) {
|
||||
Write-Error "Snippet file not found: $SnippetFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Build Visual Studio snippets directory path
|
||||
$VSSnippetsPath = "$env:USERPROFILE\Documents\Visual Studio $VisualStudioVersion\Code Snippets\Visual C#\My Code Snippets"
|
||||
|
||||
Write-Host "Target directory: $VSSnippetsPath" -ForegroundColor Yellow
|
||||
|
||||
# If only showing path, don't execute installation
|
||||
if ($ShowPathOnly) {
|
||||
Write-Host ""
|
||||
Write-Host "Manual installation steps:" -ForegroundColor Cyan
|
||||
Write-Host "1. Ensure target directory exists: $VSSnippetsPath" -ForegroundColor White
|
||||
Write-Host "2. Copy file: $SnippetFile" -ForegroundColor White
|
||||
Write-Host "3. To target directory: $VSSnippetsPath" -ForegroundColor White
|
||||
Write-Host "4. Restart Visual Studio" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Or use Tools > Code Snippets Manager > Import in Visual Studio" -ForegroundColor Yellow
|
||||
return
|
||||
}
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
if (-not (Test-Path $VSSnippetsPath)) {
|
||||
Write-Host "Creating snippets directory..." -ForegroundColor Yellow
|
||||
New-Item -ItemType Directory -Path $VSSnippetsPath -Force | Out-Null
|
||||
}
|
||||
|
||||
# Copy snippet file
|
||||
$DestinationFile = Join-Path $VSSnippetsPath "NetCorePalTemplates.snippet"
|
||||
|
||||
try {
|
||||
Copy-Item -Path $SnippetFile -Destination $DestinationFile -Force
|
||||
Write-Host "Code snippets installed successfully!" -ForegroundColor Green
|
||||
Write-Host " Source file: $SnippetFile" -ForegroundColor Gray
|
||||
Write-Host " Target file: $DestinationFile" -ForegroundColor Gray
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Available snippet shortcuts:" -ForegroundColor Cyan
|
||||
Write-Host " postproc - PostProcessor class" -ForegroundColor White
|
||||
Write-Host " tstclass - Test class" -ForegroundColor White
|
||||
Write-Host " ncpcmd - NetCorePal command" -ForegroundColor White
|
||||
Write-Host " ncpcmdres - Command response" -ForegroundColor White
|
||||
Write-Host " evnt - Domain event" -ForegroundColor White
|
||||
Write-Host " ncprepo - Repository interface" -ForegroundColor White
|
||||
Write-Host " epp - FastEndpoint" -ForegroundColor White
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Usage:" -ForegroundColor Cyan
|
||||
Write-Host "1. Open C# file in Visual Studio" -ForegroundColor White
|
||||
Write-Host "2. Type shortcut (like 'postproc')" -ForegroundColor White
|
||||
Write-Host "3. Press Tab key twice" -ForegroundColor White
|
||||
Write-Host "4. Fill parameters and press Tab to switch to next parameter" -ForegroundColor White
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Note: If Visual Studio is running, restart it to load new snippets." -ForegroundColor Yellow
|
||||
}
|
||||
catch {
|
||||
Write-Error "Installation failed: $($_.Exception.Message)"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Installation completed!" -ForegroundColor Green
|
||||
1271
vs-snippets/NetCorePalTemplates.snippet
Normal file
1271
vs-snippets/NetCorePalTemplates.snippet
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user