添加项目文件。

This commit is contained in:
2026-10-12 16:22:04 +08:00
parent 7c68a0ca0a
commit aad11be43d
1000 changed files with 120807 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
@@ -0,0 +1,50 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"urls": "http://*:8004",
"AllowedHosts": "*",
"JwtConfig": {
"TokenKey": "token",
"Issuer": "admin",
"Audience": "", // "Audience": "",
"SecretKey": "henanhuashengchongwuyouxiangongsikejizhongxin",
"ValidateIssuer": false,
"ValidateAudience": false,
"ValidateIssuerSigningKey": false,
"ValidateLifetime": false
},
"ConnectionStrings": {
"PgSqlConnection": "host=localhost;database=data_0524;user id=data_0524;password=123456;"
},
"CapConfig": {
"Host": "localhost",
"UserName": "admin",
"Password": "123456",
"VirtualHost": "/",
"PGLogHost": "host=localhost;database=data_0524;user id=data_0524;password=123456;"
},
"RedisConfig": {
"Host": "localhost",
"Port": 6378,
"UserName": "",
"Password": "123456",
"Database": 2
},
"MongoDbSettings": {
"username": "root",
"password": "root23456",
"ServerUrl": "mongodb://localhost:27018/?authSource=admin"
},
"WXConfig": {
"AppId": "wxceab9ce5b4474219",
"Secret": "5a291d7b295c76da579611da3f83e059",
"Url": "https://api.weixin.qq.com"
}
}
@@ -0,0 +1,56 @@
using DotNetCore.CAP.Messages;
using FishRoomService.MQ;
namespace FishRoomHost.Extension
{
public static class CapExtension
{
public static IServiceCollection AddCap(this IServiceCollection services, IConfiguration configuration)
{
services.AddCap(x =>
{
x.UseDashboard();
x.UseRabbitMQ(rb =>
{
rb.HostName = configuration["CapConfig:Host"];
rb.UserName = configuration["CapConfig:UserName"];
rb.Password = configuration["CapConfig:Password"];
rb.VirtualHost = configuration["CapConfig:VirtualHost"];
rb.ExchangeName = "cap.exchange";
});
x.UsePostgreSql(op =>
{
op.ConnectionString = configuration["CapConfig:PGLogHost"];
op.Schema = "public";
});
x.SucceedMessageExpiredAfter = 7 * 24 * 3600;
x.FailedRetryCount = 5;
x.FailedRetryInterval = 10;
x.FailedThresholdCallback = ObjFailedThresholdCallback;
});
services.AddTransient<IRoomMQService, RoomMQService>();
return services;
}
private static async void ObjFailedThresholdCallback(FailedInfo failed)
{
var exception = failed.Message.Headers["cap-exception"] ?? string.Empty;
var name = failed.Message.GetName();
if (failed.MessageType == MessageType.Publish)
{
}
else
{
}
}
}
}
@@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.StackExchangeRedis" Version="8.0.0" />
<PackageReference Include="StackExchange.Redis" Version="2.7.4" />
<PackageReference Include="NLog.Web.AspNetCore" Version="6.1.2" />
<PackageReference Include="Autofac" Version="8.3.0" />
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="10.0.0" />
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="DotNetCore.CAP" Version="8.3.4" />
<PackageReference Include="DotNetCore.CAP.Dashboard" Version="8.3.4" />
<PackageReference Include="DotNetCore.CAP.PostgreSql" Version="8.3.4" />
<PackageReference Include="DotNetCore.CAP.RabbitMQ" Version="8.3.4" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="8.0.17" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FishRoomService\FishRoomService.csproj" />
<ProjectReference Include="..\..\Infrastructure\CommonInfrastructure\CommonInfrastructure.csproj" />
<ProjectReference Include="..\..\Infrastructure\SharpAbp.Infrastructure.Api\SharpAbp.Infrastructure.Api.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\nlog.config" Link="nlog.config" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
<None Update="docker-compose.yaml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,169 @@
using System.Text;
using Common;
using CommonDomain;
using CommonService;
using Newtonsoft.Json;
namespace FishRoomHost.Middleware
{
public class ApiLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ApiLoggingMiddleware> _logger;
private readonly IApiLogService _apiLogService;
public ApiLoggingMiddleware(RequestDelegate next, ILogger<ApiLoggingMiddleware> logger, IApiLogService apiLogService)
{
_next = next;
_logger = logger;
_apiLogService = apiLogService;
}
public async Task Invoke(HttpContext httpContext)
{
var request = httpContext.Request;
if (request.Path.StartsWithSegments(new PathString("/api")) && request.Method.ToLower() != "options")
{
var requestTime = DateTime.Now;
var requestBodyContent = string.Empty;
var originalRequest = httpContext.Response.Body;
if (request.Method == "POST")
{
request.EnableBuffering();
requestBodyContent = await new StreamReader(request.Body).ReadToEndAsync();
request.Body.Position = 0;
}
try
{
using (var responseBody = new MemoryStream())
{
var response = httpContext.Response;
response.Body = responseBody;
await _next(httpContext);
string responseBodyContent = null;
responseBodyContent = await FormatResponse(response);
await responseBody.CopyToAsync(originalRequest);
string token = string.Empty, version = string.Empty, userAgent = string.Empty, source = string.Empty, deviceId = string.Empty, device = string.Empty, ip = string.Empty,sign=string.Empty;
if (httpContext.Request != null && httpContext.Request.Headers != null)
{
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Token"]))
{
token = httpContext.Request.Headers["Token"] + ""; }
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Version"]))
{
version = httpContext.Request.Headers["Version"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["User-Agent"]))
{
userAgent = httpContext.Request.Headers["User-Agent"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Source"]))
{
source = httpContext.Request.Headers["Source"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["DeviceId"]))
{
deviceId = httpContext.Request.Headers["DeviceId"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Device"]))
{
device = httpContext.Request.Headers["Device"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Ip"]))
{
ip = httpContext.Request.Headers["Ip"] + "";
}
if (!string.IsNullOrEmpty(httpContext.Request.Headers["Sign"]))
{
sign = httpContext.Request.Headers["Sign"] + "";
}
}
string allResponseContent = responseBodyContent;
try
{
if (!string.IsNullOrEmpty(responseBodyContent))
{
BaseResponse<object> baseResponse = JsonConvert.DeserializeObject<BaseResponse<object>>(responseBodyContent);
if (baseResponse != null && baseResponse.Data != null)
{
baseResponse.Data = null;
}
responseBodyContent = JsonConvert.SerializeObject(baseResponse);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "API响应日志脱敏序列化失败 path={Path}", request.Path);
}
var now = DateTime.Now;
var apilog = new ApiLog()
{
requestTime = now,
accessToken = token,
clientIP = httpContext.Connection.RemoteIpAddress.ToString(),
consumeTime = (DateTime.Now - requestTime).Milliseconds,
requestHead= JsonConvert.SerializeObject(httpContext.Request.Headers),
requestUrl = request.Path,
requestMethod = request.Method.ToLower(),
requestQuery = request.QueryString.ToString(),
requestBody = requestBodyContent,
httpStatus = httpContext.Response.StatusCode,
responseBody = responseBodyContent,
userAgent = userAgent,
device= device,
deviceId= deviceId,
ip=ip,
source=source,
version=version,
sign=sign
};
await _apiLogService.InsertOneAsync(apilog);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "API日志记录失败 path={Path}", request.Path);
}
finally
{
httpContext.Response.Body = originalRequest;
}
}
else
{
await _next(httpContext);
}
}
private async Task<string> FormatRequest(Microsoft.AspNetCore.Http.HttpRequest request)
{
var body = request.Body;
request.EnableBuffering();
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
await request.Body.ReadAsync(buffer, 0, buffer.Length);
var bodyAsText = Encoding.UTF8.GetString(buffer);
request.Body = body;
return bodyAsText;
}
private async Task<string> FormatResponse(HttpResponse response)
{
response.Body.Seek(0, SeekOrigin.Begin);
string text = await new StreamReader(response.Body).ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
//return $"{response.StatusCode}: {text}";
return text;
}
}
public static class ApiLoggingMiddlewareExtensions
{
public static IApplicationBuilder UseApiLogging(this IApplicationBuilder app)
{
return app.UseMiddleware<ApiLoggingMiddleware>();
}
}
}
@@ -0,0 +1,37 @@
using Autofac;
using CommonService;
using FishRoomService.RoomService;
using MongoRepository;
namespace FishRoomHost.Middleware
{
public class AutofacModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterGeneric(typeof(MongoBaseRepository<>)).As(typeof(IMongoBaseRepository<>)).InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(MongoBaseService<>)).As(typeof(IMongoBaseService<>)).InstancePerLifetimeScope();
builder.RegisterType<RedisService.RedisService>()
.As<RedisService.IRedisService>()
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(typeof(ApiLogService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(typeof(FishingRoomService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterAssemblyTypes(typeof(UserService.User.UserService).Assembly)
.Where(t => t.Name.EndsWith("Service"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
}
}
}
@@ -0,0 +1,29 @@
using Microsoft.AspNetCore.SignalR;
namespace FishRoomHost.Middleware
{
public class HubExceptionFilter : IHubFilter
{
private readonly ILogger<HubExceptionFilter> _logger;
public HubExceptionFilter(ILogger<HubExceptionFilter> logger)
{
_logger = logger;
}
public async ValueTask<object?> InvokeMethodAsync(
HubInvocationContext invocationContext,
Func<HubInvocationContext, ValueTask<object?>> next)
{
try
{
return await next(invocationContext);
}
catch (Exception e)
{
_logger.LogError(e, "Hub 调用异常: {Method}", invocationContext.HubMethodName);
return null;
}
}
}
}
@@ -0,0 +1,65 @@
using Common.Utils;
using Microsoft.AspNetCore.SignalR;
using System.Collections.Concurrent;
namespace FishRoomHost.Middleware
{
public class MovementFilter : IHubFilter
{
public async ValueTask<object> InvokeMethodAsync(
HubInvocationContext context,
Func<HubInvocationContext, ValueTask<object>> next)
{
var arguments = context.HubMethodArguments;
{
//if (context.HubMethodName == "ChangeUserData" )
//{
// var obj = arguments[0].ToJson().ToObject<ChangeUserDataParam>();
// if (obj.Type == 8 || obj.Type == 6 || obj.Type == 7 || obj.Type == 4)
// {
// if (!RateLimiter.Allow(context.Context.ConnectionId))
// {
// }
// }
//}
}
return await next(context);
}
}
public static class RateLimiter
{
private static readonly ConcurrentDictionary<string, RateData> _rates = new();
public static bool Allow(string connectionId)
{
var now = DateTime.UtcNow;
var rate = _rates.GetOrAdd(connectionId, id => new RateData());
if (rate.Tokens < 30)
{
rate.Tokens++;
rate.LastUpdate = now;
return true;
}
if ((now - rate.LastUpdate).TotalMilliseconds > 33)
{
rate.Tokens = 1;
rate.LastUpdate = now;
return true;
}
return false;
}
private class RateData
{
public int Tokens { get; set; } = 30;
public DateTime LastUpdate { get; set; } = DateTime.UtcNow;
}
}
}
+218
View File
@@ -0,0 +1,218 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Common;
using Common.Request;
using CommonService;
using FishRoomHost.Extension;
using FishRoomHost.Middleware;
using FishRoomService;
using FishRoomService.Hubs;
using FishRoomService.RoomService;
using JWTHelper;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using MongoRepository.Config;
using NLog.Extensions.Logging;
using NLog.Web;
using RedisHelper;
using SharpAbp.Infrastructure.Api.Middleware;
using StackExchange.Redis;
using System.Text.Json;
using static Common.Utils.DateTimeTools;
Environment.SetEnvironmentVariable("SERVICE_NAME",
Environment.GetEnvironmentVariable("SERVICE_NAME")
?? System.Reflection.Assembly.GetEntryAssembly()?.GetName().Name
?? "FishRoomHost");
Environment.SetEnvironmentVariable("INSTANCE_ID",
Environment.GetEnvironmentVariable("INSTANCE_ID") ?? "default");
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
string instanceId = Environment.GetEnvironmentVariable("INSTANCE_ID") ?? "Unknown";
builder.Services.AddSingleton(sp => new AppInstanceId { Id = instanceId });
builder.Services.AddControllers();
builder.Logging.AddNLog();
builder.Host.UseNLog();
builder.Configuration.AddJsonFile(Path.Combine("Config", "system.json"), optional: true);
builder.Services.Configure<MongoDbSettings>(builder.Configuration.GetSection(nameof(MongoDbSettings)));
builder.Services.AddSingleton<IMongoDbSettings>(sp => sp.GetRequiredService<IOptions<MongoDbSettings>>().Value);
string redisConn = builder.Configuration.GetConnectionString("Redis");
if (string.IsNullOrWhiteSpace(redisConn))
{
var redisSection = builder.Configuration.GetSection("RedisConfig");
var host = redisSection["Host"];
var port = redisSection["Port"] ?? "6379";
var password = redisSection["Password"];
var database = redisSection["Database"] ?? "0";
if (string.IsNullOrWhiteSpace(host))
{
throw new InvalidOperationException("未找到 Redis 配置:请在 ConnectionStrings:Redis 或 RedisConfig 节中配置 Redis 地址。");
}
redisConn = $"{host}:{port}";
if (!string.IsNullOrEmpty(password))
redisConn += $",password={password}";
if (!string.IsNullOrEmpty(database))
redisConn += $",defaultDatabase={database}";
}
builder.Services.AddSingleton<IConnectionMultiplexer>(sp =>
{
return ConnectionMultiplexer.Connect(redisConn);
});
builder.Services.AddSignalR(hubOptions =>
{
hubOptions.MaximumReceiveMessageSize = 64 * 1024;
hubOptions.StreamBufferCapacity = 512;
hubOptions.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
hubOptions.KeepAliveInterval = TimeSpan.FromSeconds(5);
hubOptions.AddFilter<MovementFilter>();
hubOptions.AddFilter<HubExceptionFilter>();
})
.AddJsonProtocol();
builder.Services.Configure<FormOptions>(x =>
{
x.MultipartBodyLengthLimit = 1024 * 1024 * 16;
});
builder.Services.AddMemoryCache();
builder.Services.AddControllers().AddJsonOptions(configure =>
{
configure.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter());
});
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddSingleton<IHubSend<FishRoomHub>, HubSend<FishRoomHub>>();
builder.Services.AddSingleton<IRequestApi, RequestApi>();
builder.Services.AddAutoMapper(typeof(AutoMapperConfig));
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "self" });
builder.Services.AddCors(options =>
{
options.AddPolicy("Cors", policy =>
{
policy
.SetIsOriginAllowed(_ => true)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
#region jwt
builder.Services.AddCustomJwt(builder.Configuration);
#endregion
#region redis
builder.Services.AddRedisCacheSingleton(builder.Configuration);
#endregion
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
{
builder.RegisterModule<AutofacModule>();
});
#region Cap
builder.Services.AddCap(builder.Configuration);
#endregion
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
}
app.UseApiLogging();
app.UseRouting();
app.UseGlobalExceptionHandler();
app.UseStaticFiles();
app.UseCors("Cors");
app.UseAuthentication();
app.UseAuthorization();
app.MapHub<FishRoomHub>("/fishingroomhub", options =>
{
options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets;
});
app.MapControllers();
app.MapHealthChecks("/health", new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json";
var result = new
{
status = report.Status.ToString(),
checks = report.Entries.Select(e => new
{
name = e.Key,
status = e.Value.Status.ToString(),
description = e.Value.Description,
duration = e.Value.Duration.TotalMilliseconds
}),
totalDuration = report.TotalDuration.TotalMilliseconds
};
await context.Response.WriteAsync(JsonSerializer.Serialize(result));
}
});
using (var scope = app.Services.CreateScope())
{
var logger = scope.ServiceProvider.GetRequiredService<ILogger<Program>>();
try
{
var redis = scope.ServiceProvider.GetRequiredService<RedisService.IRedisService>();
if (redis.GetGameRoomAllList()?.Any() != true)
{
var roomService = scope.ServiceProvider.GetRequiredService<IFishingRoomService>();
await roomService.InitRoom();
}
}
catch (Exception ex)
{
logger.LogError(ex, "[Startup] 游戏房间初始化失败");
throw;
}
}
app.Run();
@@ -0,0 +1,22 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:6603",
"sslPort": 44370
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:8004",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -0,0 +1,8 @@
name: webapi
services:
0524fishroom:
image: 0524fishroom
container_name: '0524fishroom'
restart: always
ports:
- '5244:80'