Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 41 additions & 18 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,31 @@

## Project Overview

Multi-targeted .NET toolkit library providing reusable components for data access, HTTP requests, and message queuing. Published as NuGet packages (`JfYu.Data`, `JfYu.Request`, `JfYu.RabbitMQ`) with automated CI/CD via GitHub Actions.
Multi-targeted .NET toolkit library providing reusable components for data access, HTTP requests, message queuing, caching, document processing, and WeChat integration. Published as NuGet packages (`JfYu.Data`, `JfYu.Request`, `JfYu.RabbitMQ`, `JfYu.Redis`, `JfYu.Office`, `JfYu.WeChat`) with automated CI/CD via GitHub Actions.

## Architecture

### Component Structure

- **JfYu.Data**: EF Core read-write separation with multi-database support (SqlServer, MySql, Sqlite, InMemory)
- **JfYu.Request**: HTTP request abstraction supporting HttpClient/HttpWebRequest with configurable logging
- **JfYu.Request**: HTTP request abstraction supporting HttpClient with configurable logging, multiple named clients, and factory pattern
- **JfYu.RabbitMQ**: RabbitMQ client wrapper with async message publishing/consuming, automatic retry, and dead letter queue support
- **JfYu.Redis**: High-performance Redis client with Pub/Sub messaging, distributed locking, multiple serialization formats, and comprehensive data structure operations
- **JfYu.Office**: Excel and Word document manipulation library supporting multiple data sources, template-based generation, and high-performance streaming
- **JfYu.WeChat**: WeChat Mini Program integration with typed APIs for authentication, access token management, and phone number retrieval
- **JfYu.UnitTests**: Multi-framework tests (net481, net8.0, net9.0) with xUnit
- **JfYu.UnitTests**: Multi-framework tests (net481, net8.0, net9.0, net10.0) with xUnit
- **JfYu.Benchmark**: BenchmarkDotNet performance benchmarks comparing JfYu.Data Service layer vs raw EF Core

### Multi-Targeting Strategy

- **JfYu.Request**: `netstandard2.0;net8.0` for broad compatibility
- **JfYu.RabbitMQ**: `netstandard2.0;net8.0` for broad compatibility
- **JfYu.WeChat**: `netstandard2.0;net8.0` for broad compatibility
- **JfYu.Data**: `net8.0` only (requires modern EF Core features)
- **JfYu.UnitTests**: `net481;net8.0;net9.0` for comprehensive testing
- **JfYu.Request**: `netstandard2.0;net8.0;net9.0;net10.0` for broad compatibility
- **JfYu.RabbitMQ**: `netstandard2.0;net8.0;net9.0;net10.0` for broad compatibility
- **JfYu.Redis**: `netstandard2.0;net8.0;net9.0;net10.0` for broad compatibility
- **JfYu.Office**: `netstandard2.0;net8.0;net9.0;net10.0` for broad compatibility
- **JfYu.WeChat**: `netstandard2.0;net8.0;net9.0;net10.0` for broad compatibility
- **JfYu.Data**: `net8.0;net9.0;net10.0` (requires modern EF Core features)
- **JfYu.UnitTests**: `net481;net8.0;net9.0;net10.0` for comprehensive testing
- **JfYu.Benchmark**: `net8.0` for performance benchmarking
- Use `#if NET8_0_OR_GREATER` preprocessor directives to conditionally compile Data-dependent code

## Key Patterns
Expand All @@ -31,16 +37,21 @@ All libraries use extension methods for service registration:

```csharp
// JfYu.Data - Read/Write separation with random load balancing
services.AddJfYuDbContextService<DataContext>(options => {
services.AddJfYuDbContext<DataContext>(options => {
options.ConnectionString = "..."; // Master DB
options.ReadOnlyDatabases = [new DatabaseConfig { ... }]; // Slave DBs
});

// JfYu.Request - HTTP client with optional logging filters
services.AddJfYuHttpRequest(q => {
q.LoggingFields = JfYuLoggingFields.All;
q.RequestFilter = x => x; // Transform before logging
q.ResponseFilter = x => x; // Transform after logging
services.AddJfYuHttpClient(options => {
options.HttpClientName = "MyClient";
options.ConfigureClient = client => {
client.BaseAddress = new Uri("https://api.example.com");
};
}, filter => {
filter.LoggingFields = JfYuLoggingFields.All;
filter.RequestFilter = x => x; // Transform before logging
filter.ResponseFilter = x => x; // Transform after logging
});

// JfYu.RabbitMQ - Message queue with retry policy
Expand All @@ -63,14 +74,24 @@ services.AddMiniProgram(q => {
q.AppId = "wx1234567890abcdef"; // Mini Program AppId
q.Secret = "secret123..."; // Mini Program Secret
});

// JfYu.Redis - Redis client with serialization options
services.AddRedisService(options => {
options.EndPoints.Add(new RedisEndPoint { Host = "localhost", Port = 6379 });
options.UsingNewtonsoft(); // or options.UsingMsgPack()
});

// JfYu.Office - Excel and Word document services
services.AddJfYuExcel();
services.AddJfYuWord();
```

### Read-Write Separation Pattern

Service classes (`IService<T, TContext>`) automatically provide:

- `Context`: Master database for writes
- `ReadonlyContext`: Randomly selected slave for reads
- `_context`: Master database for writes
- `_readonlyContext`: Randomly selected slave for reads
- If no read replicas configured, falls back to master

### Configuration Binding
Expand Down Expand Up @@ -134,17 +155,19 @@ JfYu.Request provides `LogFilter` with:

## Common Pitfalls

- **Don't** reference JfYu.Data in net481 projects - it's net8.0 only
- **Don't** reference JfYu.Data in net481 projects - it's net8.0+ only
- **Don't** forget `CopyToOutputDirectory` for test assets (appsettings, test files)
- **Remember** xUnit collections share fixture state - use isolation techniques
- **Use** `ReadonlyContext` for queries to leverage read replicas
- **Use** `_readonlyContext` for queries to leverage read replicas
- **Set** `NoWarn` for SYSLIB0014 in JfYu.Request (intentional use of obsolete APIs for netstandard2.0 compat)

## Project Files Reference

- Service registration: `src/JfYu.Data/Extension/ContainerBuilderExtensions.cs`, `src/JfYu.Request/Extension/ContainerBuilderExtensions.cs`, `src/JfYu.RabbitMQ/ContainerBuilderExtensions.cs`, `src/JfYu.WeChat/ContainerBuilderExtensions.cs`
- Service registration: `src/JfYu.Data/Extension/ContainerBuilderExtensions.cs`, `src/JfYu.Request/Extension/ContainerBuilderExtensions.cs`, `src/JfYu.RabbitMQ/ContainerBuilderExtensions.cs`, `src/JfYu.Redis/ContainerBuilderExtensions.cs`, `src/JfYu.Office/ContainerBuilderExtensions.cs`, `src/JfYu.WeChat/ContainerBuilderExtensions.cs`
- Core service: `src/JfYu.Data/Service/Service.cs`
- RabbitMQ service: `src/JfYu.RabbitMQ/RabbitMQService.cs`
- Redis service: `src/JfYu.Redis/Implementation/RedisService.cs`
- WeChat service: `src/JfYu.WeChat/MiniProgram.cs`
- Test utilities: `src/JfYu.UnitTests/Common.cs`
- Benchmark: `src/JfYu.Benchmark/Program.cs`
- CI workflows: `.github/workflows/gate.yml`, `.github/workflows/deploy.yml`
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "9.0.x"
dotnet-version: "10.0.x"

- name: Restore dependencies
run: dotnet restore
Expand Down
45 changes: 26 additions & 19 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
name: Deploy
on:
push:
branches: [ "master" ]
branches: ["master"]
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-latest
permissions:
packages: write
defaults:
run:
working-directory: src
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
9.0.x
- name: Clean old packages
run: dotnet clean
- name: Build
run: dotnet build -c release
- name: Push NuGet packages
run: |
for file in $(find . -name "*.nupkg" ! -name "*.symbols.nupkg"); do
dotnet nuget push "$file" -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate;
done
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Clean old packages
run: dotnet clean
- name: Build
run: dotnet build -c release
- name: Push NuGet packages
run: |
for file in $(find . -name "*.nupkg" ! -name "*.symbols.nupkg"); do
dotnet nuget push "$file" -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json --skip-duplicate;
done
- name: Push to GitHub Packages
run: |
for file in $(find . -name "*.nupkg" ! -name "*.symbols.nupkg"); do
dotnet nuget push "$file" -k ${{ secrets.GITHUB_TOKEN }} -s https://nuget.pkg.github.com/jfwangncs/index.json --skip-duplicate;
done
23 changes: 0 additions & 23 deletions .github/workflows/devskim.yml

This file was deleted.

66 changes: 32 additions & 34 deletions .github/workflows/gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,20 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore

- name: Test (8.0)
run: dotnet test --no-restore --verbosity normal -f net8.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=opencover --logger "trx;LogFileName=results8.trx"
run: dotnet test --no-restore --verbosity normal -f net8.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --logger "trx;LogFileName=results8.trx"
- name: Upload test results (8.0)
uses: actions/upload-artifact@v4
with:
name: test-results-linux8
path: src/JfYu.UnitTests/TestResults/results8.trx

- name: Upload coverage results (8.0)
uses: actions/upload-artifact@v4
with:
name: test-results-coverage8
path: src/JfYu.UnitTests/TestResults/coverage.net8.0.opencover.xml

- name: Test (9.0)
run: dotnet test --no-restore --verbosity normal -f net9.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --logger "trx;LogFileName=results9.trx"
- name: Upload test results (9.0)
Expand All @@ -72,6 +67,19 @@ jobs:
name: test-results-linux9
path: src/JfYu.UnitTests/TestResults/results9.trx

- name: Test (10.0)
run: dotnet test --no-restore --verbosity normal -f net10.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=opencover --logger "trx;LogFileName=results10.trx"
- name: Upload test results (10.0)
uses: actions/upload-artifact@v4
with:
name: test-results-linux10
path: src/JfYu.UnitTests/TestResults/results10.trx

- name: Upload coverage results (10.0)
uses: actions/upload-artifact@v4
with:
name: test-results-linux-coverage10
path: src/JfYu.UnitTests/TestResults/coverage.net10.0.opencover.xml
win:
runs-on: windows-latest
permissions:
Expand All @@ -87,6 +95,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
Expand Down Expand Up @@ -117,6 +126,7 @@ jobs:
dotnet test --no-restore --verbosity normal -f net481 --logger "trx;LogFileName=results4.trx"
dotnet test --no-restore --verbosity normal -f net8.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --logger "trx;LogFileName=results8.trx"
dotnet test --no-restore --verbosity normal -f net9.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --logger "trx;LogFileName=results9.trx"
dotnet test --no-restore --verbosity normal -f net10.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --logger "trx;LogFileName=results10.trx"

- name: Upload test results (4.81)
uses: actions/upload-artifact@v4
Expand All @@ -136,20 +146,11 @@ jobs:
name: test-results-win9
path: src/JfYu.UnitTests/TestResults/results9.trx

- name: Upload coverage results (9.0)
- name: Upload test results (10.0)
uses: actions/upload-artifact@v4
with:
name: test-results-coverage9
path: src/JfYu.UnitTests/TestResults/coverage.net9.0.info

- name: Publish coverage report to coveralls.io (9.0)
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: src/JfYu.UnitTests/TestResults/coverage.net9.0.info
flag-name: win9
parallel: true

name: test-results-win10
path: src/JfYu.UnitTests/TestResults/results10.trx
mac:
runs-on: macos-latest
permissions:
Expand All @@ -165,6 +166,7 @@ jobs:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
Expand Down Expand Up @@ -207,7 +209,7 @@ jobs:

dotnet test --no-restore --verbosity normal -f net8.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --logger "trx;LogFileName=results8.trx"
dotnet test --no-restore --verbosity normal -f net9.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --logger "trx;LogFileName=results9.trx"

dotnet test --no-restore --verbosity normal -f net10.0 /p:CollectCoverage=true /p:CoverletOutput=TestResults/ /p:CoverletOutputFormat=lcov --logger "trx;LogFileName=results10.trx"
- name: Upload test results (8.0)
uses: actions/upload-artifact@v4
with:
Expand All @@ -220,15 +222,11 @@ jobs:
name: test-results-mac9
path: src/JfYu.UnitTests/TestResults/results9.trx

coverage:
needs: [win, ubuntu, mac]
runs-on: ubuntu-latest
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@master
- name: Upload test results (10.0)
uses: actions/upload-artifact@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
parallel-finished: true
name: test-results-mac10
path: src/JfYu.UnitTests/TestResults/results10.trx

sonarcloud:
needs: [win, ubuntu, mac]
Expand All @@ -248,26 +246,26 @@ jobs:
run_id: ${{ github.run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Download test results (win9)
- name: Download test results (win10)
uses: dawidd6/action-download-artifact@v9
with:
name: test-results-win9
name: test-results-win10
path: TestResults
run_id: ${{ github.run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Download test results (coverage8)
- name: Download test results (coverage10)
uses: dawidd6/action-download-artifact@v9
with:
name: test-results-coverage8
name: test-results-linux-coverage10
path: TestResults
run_id: ${{ github.run_id }}
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "9.0.x"
dotnet-version: "10.0.x"
- name: Install SonarScanner for .NET
run: dotnet tool install --global dotnet-sonarscanner
- name: SonarCloud Begin
Expand All @@ -277,7 +275,7 @@ jobs:
/o:"jfwang" \
/d:sonar.login="${{ secrets.SONAR_TOKEN }}" \
/d:sonar.cs.vstest.reportsPaths=../TestResults/*.trx \
/d:sonar.cs.opencover.reportsPaths=../TestResults/coverage.net8.0.opencover.xml
/d:sonar.cs.opencover.reportsPaths=../TestResults/coverage.net10.0.opencover.xml
- name: Build
run: dotnet build --no-incremental
- name: SonarCloud End
Expand Down
2 changes: 2 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/jfwangncs/jfYu</PackageProjectUrl>
<RepositoryUrl>https://github.com/jfwangncs/jfYu</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
Expand Down
Loading
Loading