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