添加命令数据验证和其他杂项修改

This commit is contained in:
2021-02-24 16:28:08 +08:00
parent 12ecdf3159
commit 41794aa1bc
32 changed files with 310 additions and 484 deletions

View File

@@ -1,4 +1,5 @@
using FluentValidation;
using Domain.Exceptions;
using FluentValidation;
using MediatR;
using Microsoft.Extensions.Logging;
using QRCodeService.Extensions;
@@ -11,11 +12,11 @@ namespace QRCodeService.Application.Behaviors
public class ValidatorBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly ILogger<ValidatorBehavior<TRequest, TResponse>> _logger;
private readonly IValidator<TRequest>[] _validators;
private readonly IValidator<TRequest> _validator;
public ValidatorBehavior(IValidator<TRequest>[] validators, ILogger<ValidatorBehavior<TRequest, TResponse>> logger)
public ValidatorBehavior(IValidator<TRequest> validator, ILogger<ValidatorBehavior<TRequest, TResponse>> logger)
{
_validators = validators;
_validator = validator;
_logger = logger;
}
@@ -25,18 +26,14 @@ namespace QRCodeService.Application.Behaviors
_logger.LogInformation("----- Validating command {CommandType}", typeName);
var failures = _validators
.Select(v => v.Validate(request))
.SelectMany(result => result.Errors)
.Where(error => error != null)
.ToList();
var failures = _validator.Validate(request).Errors;
if (failures.Any())
{
_logger.LogWarning("Validation errors - {CommandType} - Command: {@Command} - Errors: {@ValidationErrors}", typeName, request, failures);
//throw new OrderingDomainException(
// $"Command Validation Errors for type {typeof(TRequest).Name}", new ValidationException("Validation exception", failures));
throw new DomainException(
$"Command Validation Errors for type {typeof(TRequest).Name}", new ValidationException("Validation exception", failures));
}
return await next();

View File

@@ -1,4 +1,5 @@
using MediatR;
using Domain.AggregateModel.LinkAggregate;
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -7,8 +8,14 @@ using System.Threading.Tasks;
namespace QRCodeService.Application.Commands
{
public class CreateLinkCommand : IRequest<bool>
public class CreateLinkCommand : IRequest<Link>
{
public int AppId { get; set; }
public string SuffixUrl { get; set; }
public CreateLinkCommand(string suffixUrl, int appId)
{
SuffixUrl = suffixUrl;
AppId = appId;
}
}
}

View File

@@ -1,4 +1,7 @@
using MediatR;
using Domain.AggregateModel.AppAggregate;
using Domain.AggregateModel.LinkAggregate;
using Domain.Exceptions;
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -8,11 +11,33 @@ using System.Threading.Tasks;
namespace QRCodeService.Application.Commands
{
public class CreateLinkCommandHandler : IRequestHandler<CreateLinkCommand, bool>
public class CreateLinkCommandHandler : IRequestHandler<CreateLinkCommand, Link>
{
public Task<bool> Handle(CreateLinkCommand request, CancellationToken cancellationToken)
readonly IAppRepository appRepository;
readonly ILinkRepository linkRepository;
public CreateLinkCommandHandler(ILinkRepository linkRepository, IAppRepository appRepository)
{
throw new NotImplementedException();
this.linkRepository = linkRepository;
this.appRepository = appRepository;
}
async Task<Link> IRequestHandler<CreateLinkCommand, Link>.Handle(CreateLinkCommand request, CancellationToken cancellationToken)
{
var app = await appRepository.GetAsync(request.AppId);
if (app == null)
{
throw new DomainException("app not found");
}
var link = new Link(app.BaseUrl, request.SuffixUrl, request.AppId);
var dbLink = await linkRepository.GetAsync(link.ShortCode);
if (dbLink != null)
{
throw new DomainException("url has been registed");
}
link = linkRepository.Add(link);
await linkRepository.UnitOfWork.SaveEntitiesAsync();
return link;
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Dapper;
using MySqlConnector;
using Microsoft.Extensions.Configuration;
namespace QRCodeService.Application.Queries
{
@@ -13,9 +14,9 @@ namespace QRCodeService.Application.Queries
{
readonly string _connectionString;
public LinkQueries(string connectionString)
public LinkQueries(IConfiguration configuration)
{
_connectionString = connectionString;
_connectionString = configuration.GetConnectionString("default");
}
public async Task<Link> GetLinkAsync(string shortCode)

View File

@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QRCodeService.Controllers.Api
{
[Route("api/v1/[controller]")]
[ApiController]
public class AppController:ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return Ok();
}
[HttpPost]
public IActionResult Create(object input)
{
return Ok();
}
}
}

View File

@@ -0,0 +1,46 @@
using Domain.AggregateModel.AppAggregate;
using Domain.AggregateModel.LinkAggregate;
using Microsoft.AspNetCore.Mvc;
using QRCodeService.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediatR;
using QRCodeService.Application.Queries;
using QRCodeService.Application.Commands;
namespace QRCodeService.Controllers.Api
{
[Route("api/v1/[controller]")]
[ApiController]
public class LinkController:ControllerBase
{
readonly IMediator mediator;
readonly ILinkQueries queries;
public LinkController(IMediator mediator, ILinkQueries queries)
{
this.mediator = mediator;
this.queries = queries;
}
[HttpGet]
public IActionResult Get()
{
return Ok();
}
[HttpPost]
public async Task<IActionResult> Create(CreateLinkModel input)
{
var command = new CreateLinkCommand(input.SuffixUrl,1);
var link = await mediator.Send(command);
if (link==null)
{
return BadRequest();
}
return Ok(link.ShortCode);
}
}
}

View File

@@ -1,15 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QRCodeService.Controllers
{
[Route("api/v1/[controller]")]
public class AppController:ControllerBase
{
}
}

View File

@@ -1,58 +0,0 @@
using Domain.AggregateModel.LinkAggregate;
using Microsoft.AspNetCore.Mvc;
using QRCodeService.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
using Domain.AggregateModel.AppAggregate;
using Base62;
using QRCodeService.Extensions;
namespace QRCodeService.Controllers
{
/// <summary>
/// 生成二维码图片
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class GenController:ControllerBase
{
readonly ILinkRepository linkRepository;
readonly IAppRepository appRepository;
public GenController(ILinkRepository linkRepository, IAppRepository appRepository)
{
this.linkRepository = linkRepository;
this.appRepository = appRepository;
}
[HttpPost]
public async Task<IActionResult> CreateLink(GenInputModel model)
{
var app = await appRepository.GetAsync(model.AppId);
if (app == null)
{
return BadRequest();
}
using(var md5 = MD5.Create())
{
var result = md5.ComputeHash(Encoding.UTF8.GetBytes($"{model.AppId}{model.SuffixUrl}{model.Time}{app.AppKey}"));
var sign = BitConverter.ToString(result);
if (sign != model.Sign)
{
return BadRequest();
}
}
var shortCode = $"{model.SuffixUrl}".ToMD5().ToBase62();
var link = new Link(app.BaseUrl, model.SuffixUrl,1);
linkRepository.Add(link);
await linkRepository.UnitOfWork.SaveEntitiesAsync();
return Ok(link.ShortCode);
}
}
}

View File

@@ -1,30 +0,0 @@
using Domain.AggregateModel.LinkAggregate;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QRCodeService.Controllers
{
/// <summary>
/// 跳转url请求的地址
/// </summary>
[Route("{shortCode}")]
public class GoController: Controller
{
//readonly ILinkRepository LinkRepository;
//public GoController(ILinkRepository linkRepository)
//{
// LinkRepository = linkRepository;
//}
[HttpGet]
public async Task<IActionResult> Index(string shortcode)
{
var a = new { Url = "asdasd" };
//var link = await LinkRepository.GetAsync(shortcode);
return View(a);
}
}
}

View File

@@ -14,6 +14,7 @@ namespace QRCodeService.Controllers
public class ImageController : ControllerBase
{
[Route("{shortCode}")]
[HttpGet]
public IActionResult Get(string shortCode)
{
var qrCodeGenerator = new QRCodeGenerator();

View File

@@ -10,6 +10,7 @@ using Microsoft.Extensions.Logging;
namespace QRCodeService.Controllers
{
[Route("[controller]/[action]")]
[ApiController]
public class PlaygroundController:ControllerBase
{
private readonly ILogger<PlaygroundController> logger;
@@ -18,7 +19,7 @@ namespace QRCodeService.Controllers
{
this.logger = logger;
}
[HttpGet]
public IActionResult GenShortCode(string url)
{
logger.LogInformation("duduledule");

View File

@@ -0,0 +1,27 @@
using Microsoft.AspNetCore.Mvc;
using QRCodeService.Application.Queries;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QRCodeService.Controllers
{
[Route("{shortCode}")]
public class RedirectController:Controller
{
private readonly ILinkQueries queries;
public RedirectController(ILinkQueries queries)
{
this.queries = queries;
}
[HttpGet]
public async Task<IActionResult> Index(string shortCode)
{
var link = await queries.GetLinkAsync(shortCode);
return View(link);
}
}
}

View File

@@ -1,73 +0,0 @@
// <auto-generated />
using System;
using Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace QRCodeService.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20210224014442_InitialCreate")]
partial class InitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 64)
.HasAnnotation("ProductVersion", "5.0.3");
modelBuilder.Entity("Domain.AggregateModel.AppAggregate.App", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AppKey")
.HasColumnType("longtext");
b.Property<string>("BaseUrl")
.HasColumnType("longtext");
b.Property<string>("Remarks")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("Apps");
});
modelBuilder.Entity("Domain.AggregateModel.LinkAggregate.Link", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<int>("AppId")
.HasColumnType("int");
b.Property<string>("BaseUrl")
.HasColumnType("longtext");
b.Property<string>("FullUrl")
.HasColumnType("longtext");
b.Property<string>("ShortCode")
.HasColumnType("longtext");
b.Property<string>("SuffixUrl")
.HasColumnType("longtext");
b.Property<DateTime>("Time")
.HasColumnType("datetime(6)");
b.HasKey("Id");
b.ToTable("Links");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,72 +0,0 @@
// <auto-generated />
using System;
using Infrastructure;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace QRCodeService.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20210224015439_AddConfiguration")]
partial class AddConfiguration
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("Relational:MaxIdentifierLength", 64)
.HasAnnotation("ProductVersion", "5.0.3");
modelBuilder.Entity("Domain.AggregateModel.AppAggregate.App", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("AppKey")
.HasColumnType("longtext");
b.Property<string>("BaseUrl")
.HasColumnType("longtext");
b.Property<string>("Remarks")
.HasColumnType("longtext");
b.HasKey("Id");
b.ToTable("App");
});
modelBuilder.Entity("Domain.AggregateModel.LinkAggregate.Link", b =>
{
b.Property<string>("ShortCode")
.HasColumnType("varchar(255)");
b.Property<int>("AppId")
.HasColumnType("int");
b.Property<string>("BaseUrl")
.HasColumnType("longtext");
b.Property<string>("FullUrl")
.HasColumnType("longtext");
b.Property<int>("Id")
.HasColumnType("int");
b.Property<string>("SuffixUrl")
.HasColumnType("longtext");
b.Property<DateTime>("Time")
.HasColumnType("datetime(6)");
b.HasKey("ShortCode");
b.ToTable("Link");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,102 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace QRCodeService.Migrations
{
public partial class AddConfiguration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_Links",
table: "Links");
migrationBuilder.DropPrimaryKey(
name: "PK_Apps",
table: "Apps");
migrationBuilder.RenameTable(
name: "Links",
newName: "Link");
migrationBuilder.RenameTable(
name: "Apps",
newName: "App");
migrationBuilder.AlterColumn<string>(
name: "ShortCode",
table: "Link",
type: "varchar(255)",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "longtext",
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "Link",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AddPrimaryKey(
name: "PK_Link",
table: "Link",
column: "ShortCode");
migrationBuilder.AddPrimaryKey(
name: "PK_App",
table: "App",
column: "Id");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_Link",
table: "Link");
migrationBuilder.DropPrimaryKey(
name: "PK_App",
table: "App");
migrationBuilder.RenameTable(
name: "Link",
newName: "Links");
migrationBuilder.RenameTable(
name: "App",
newName: "Apps");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "Links",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<string>(
name: "ShortCode",
table: "Links",
type: "longtext",
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(255)");
migrationBuilder.AddPrimaryKey(
name: "PK_Links",
table: "Links",
column: "Id");
migrationBuilder.AddPrimaryKey(
name: "PK_Apps",
table: "Apps",
column: "Id");
}
}
}

View File

@@ -1,80 +0,0 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace QRCodeService.Migrations
{
public partial class SetProperty : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_Link",
table: "Link");
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "Link",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AlterColumn<string>(
name: "ShortCode",
table: "Link",
type: "varchar(11)",
maxLength: 11,
nullable: true,
oldClrType: typeof(string),
oldType: "varchar(255)");
migrationBuilder.AddPrimaryKey(
name: "PK_Link",
table: "Link",
column: "Id");
migrationBuilder.CreateIndex(
name: "IX_Link_ShortCode",
table: "Link",
column: "ShortCode",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropPrimaryKey(
name: "PK_Link",
table: "Link");
migrationBuilder.DropIndex(
name: "IX_Link_ShortCode",
table: "Link");
migrationBuilder.AlterColumn<string>(
name: "ShortCode",
table: "Link",
type: "varchar(255)",
nullable: false,
defaultValue: "",
oldClrType: typeof(string),
oldType: "varchar(11)",
oldMaxLength: 11,
oldNullable: true);
migrationBuilder.AlterColumn<int>(
name: "Id",
table: "Link",
type: "int",
nullable: false,
oldClrType: typeof(int),
oldType: "int")
.OldAnnotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
migrationBuilder.AddPrimaryKey(
name: "PK_Link",
table: "Link",
column: "ShortCode");
}
}
}

View File

@@ -9,8 +9,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace QRCodeService.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20210224015858_SetProperty")]
partial class SetProperty
[Migration("20210224065420_init")]
partial class init
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{

View File

@@ -4,12 +4,12 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace QRCodeService.Migrations
{
public partial class InitialCreate : Migration
public partial class init : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Apps",
name: "App",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
@@ -20,16 +20,16 @@ namespace QRCodeService.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_Apps", x => x.Id);
table.PrimaryKey("PK_App", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Links",
name: "Link",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
ShortCode = table.Column<string>(type: "longtext", nullable: true),
ShortCode = table.Column<string>(type: "varchar(11)", maxLength: 11, nullable: true),
BaseUrl = table.Column<string>(type: "longtext", nullable: true),
SuffixUrl = table.Column<string>(type: "longtext", nullable: true),
FullUrl = table.Column<string>(type: "longtext", nullable: true),
@@ -38,17 +38,23 @@ namespace QRCodeService.Migrations
},
constraints: table =>
{
table.PrimaryKey("PK_Links", x => x.Id);
table.PrimaryKey("PK_Link", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Link_ShortCode",
table: "Link",
column: "ShortCode",
unique: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Apps");
name: "App");
migrationBuilder.DropTable(
name: "Links");
name: "Link");
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QRCodeService.Models
{
public class CreateLinkModel
{
public string SuffixUrl { get; set; }
public string Time { get; set; }
}
}

View File

@@ -1,4 +1,9 @@
using Domain.AggregateModel.AppAggregate;
using Domain.AggregateModel.LinkAggregate;
using FluentValidation;
using FluentValidation.AspNetCore;
using Infrastructure;
using Infrastructure.Repositories;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -12,6 +17,9 @@ using Microsoft.OpenApi.Models;
using Pomelo.EntityFrameworkCore.MySql;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using QRCodeService.Application.Behaviors;
using QRCodeService.Application.Commands;
using QRCodeService.Application.Queries;
using QRCodeService.Application.Validations;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -38,12 +46,14 @@ namespace QRCodeService
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "QRCodeService", Version = "v1" });
});
//MediatR
services.AddMediatR(Assembly.GetExecutingAssembly());
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidatorBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TransactionBehaviour<,>));
//validator
services.AddTransient<IValidator<CreateLinkCommand>, CreateLinkCommandValidator>();
//MediatR+
services.AddMediatR(Assembly.GetExecutingAssembly())
.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>))
.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidatorBehavior<,>))
.AddTransient(typeof(IPipelineBehavior<,>), typeof(TransactionBehaviour<,>));
//EFCore
services.AddDbContext<AppDbContext>(
dbContextOptions => dbContextOptions
@@ -57,6 +67,12 @@ namespace QRCodeService
.EnableSensitiveDataLogging()
.EnableDetailedErrors()
);
//Repository
services.AddScoped<IAppRepository, AppRepository>();
services.AddScoped<ILinkRepository, LinkRepository>();
services.AddScoped<ILinkQueries, LinkQueries>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.

View File

@@ -7,6 +7,9 @@
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"default": "server=localhost;user=root;password=root;database=qrcode"
},
"Serilog": {
"Using": [ "SeriLog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.Async" ],
"MinimumLevel": {