From ca3a9a74088a4a30fdf5ab234bc6404d61df588c Mon Sep 17 00:00:00 2001 From: "seonkyu.kim" Date: Wed, 27 Nov 2024 17:58:07 +0900 Subject: [PATCH] =?UTF-8?q?[=E2=9C=A8]=20=EA=B0=81=EC=A2=85=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20Version=20=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Back.csproj | 10 +- Program.cs | 135 ++++++++++++--------- Program/Common/Data/AppDbContext.cs | 14 +++ Program/Common/Model/JwtSettings.cs | 9 ++ Program/Common/Model/Status.cs | 61 ++++++++++ Program/V1/Controllers/AppController.cs | 66 ++++++++++ Program/V1/Controllers/ErrorController.cs | 15 +++ Program/V1/Controllers/MemberController.cs | 29 +++++ Program/V1/Controllers/PushController.cs | 24 ++++ Program/V1/Controllers/UserController.cs | 46 +++++++ Program/V1/Models/Version.cs | 21 ++++ Program/V1/Services/UserService.cs | 6 + README.md | 10 +- SwaggerConfigure.cs | 92 ++++++++++++++ 14 files changed, 481 insertions(+), 57 deletions(-) create mode 100644 Program/Common/Data/AppDbContext.cs create mode 100644 Program/Common/Model/JwtSettings.cs create mode 100644 Program/Common/Model/Status.cs create mode 100644 Program/V1/Controllers/AppController.cs create mode 100644 Program/V1/Controllers/ErrorController.cs create mode 100644 Program/V1/Controllers/MemberController.cs create mode 100644 Program/V1/Controllers/PushController.cs create mode 100644 Program/V1/Controllers/UserController.cs create mode 100644 Program/V1/Models/Version.cs create mode 100644 Program/V1/Services/UserService.cs create mode 100644 SwaggerConfigure.cs diff --git a/Back.csproj b/Back.csproj index 65e2d3e..5d91557 100644 --- a/Back.csproj +++ b/Back.csproj @@ -8,8 +8,16 @@ + - + + + + + + + + diff --git a/Program.cs b/Program.cs index bce62c2..22377a4 100644 --- a/Program.cs +++ b/Program.cs @@ -1,73 +1,98 @@ -//var builder = WebApplication.CreateBuilder(args); -/* -var options = new WebApplicationOptions + +using Pomelo.EntityFrameworkCore; + +using System.Text; + +using Microsoft.EntityFrameworkCore; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; + +using AcaMate.Common.Models; +using AcaMate.V1.Services; +using AcaMate.Common.Data; +using AcaMate.V1.Controllers; + + +var builder = WebApplication.CreateBuilder(args); + +// DB 설정부 시작 +builder.Configuration.AddJsonFile("private/dbSetting.json", optional: true, reloadOnChange: true); +// var connectionString = builder.Configuration.GetConnectionString("MariaDbConnection"); +// builder.Services.AddDbContext(options => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))); +builder.Services.AddDbContext(optionsAction: (serviceProvider, options) => { - WebRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development" - ? "/src/publish/Debug/wwwroot" - : "/src/publish/Release/wwwroot" -}; -*/ + var httpContextAccessor = serviceProvider.GetRequiredService(); + var dbName = httpContextAccessor.HttpContext?.Request.Query["aca_code"].ToString(); + var baseConnectionString = builder.Configuration.GetConnectionString("MariaDbConnection"); + if (!string.IsNullOrEmpty(dbName)) + { + baseConnectionString = baseConnectionString.Replace("database=AcaMate", $"database={dbName}"); + } + + options.UseMySql(baseConnectionString, ServerVersion.AutoDetect(baseConnectionString)); +}); +builder.Services.AddHttpContextAccessor(); +// DB 설정부 끝 -// var currentDirectory = Directory.GetCurrentDirectory(); -// var webRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development" -// ? Path.Combine(currentDirectory, "publish/Debug/wwwroot") -// : Path.Combine(currentDirectory, "publish/Release/wwwroot"); -var webRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_WEBROOT"); -// builder.WebHost.UseWebRoot(webRootPath); +var dbString = builder.Configuration.GetConnectionString("MariaDbConnection"); +var userString = builder.Configuration.GetConnectionString("DBAccount"); -var options = new WebApplicationOptions { WebRootPath = webRootPath }; +// JWT 설정부 시작 +builder.Configuration.AddJsonFile("private/jwtSetting.json", optional: true, reloadOnChange: true); +builder.Services.Configure(builder.Configuration.GetSection("JwtSettings")); -var builder = WebApplication.CreateBuilder(options); +builder.Services.AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(options => + { + var jwtSettings = builder.Configuration.GetSection("JwtSettings").Get(); + options.TokenValidationParameters = new TokenValidationParameters { + ValidateIssuer = true, + ValidateAudience = true, + ValidateLifetime = true, + ValidateIssuerSigningKey = true, + ValidIssuer = jwtSettings.Issuer, + ValidAudience = jwtSettings.Audience, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)) + }; + }); +// JWT 설정부 끝 + +builder.Services.AddControllers(); +// 여기다가 API 있는 컨트롤러들 AddScoped 하면 되는건가? +// builder.Services.AddScoped(); // +// builder.Services.AddScoped(); -// var env = builder.Environment.EnvironmentName; -// string wwwrootPath = env == "Development" ? "/src/publish/Debug/wwwroot" : "/src/publish/Release/wwwroot"; -// builder.WebHost.UseWebRoot(wwwrootPath); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); + +// 스웨거 설정 추가 부분 +// builder.Services.AddSwaggerGen(); +builder.Services.AddCustomSwagger(); var app = builder.Build(); -// Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { - app.UseSwagger(); - app.UseSwaggerUI(); + // app.UseSwagger(); + // app.UseSwaggerUI(); + app.UseCustomSwaggerUI(); + app.UseDeveloperExceptionPage(); // 좀더 자세한 예외 정보 제공 +} +else +{ + app.UseExceptionHandler("/error"); + app.UseHsts(); } -app.UseExceptionHandler("/Error"); - // .UseStaticFiles() -app.UseStaticFiles(new StaticFileOptions - { - ServeUnknownFileTypes = true - }); -app.UseRouting(); -app.MapFallbackToFile("index.html"); -var summaries = new[] -{ - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" -}; +app.UseHttpsRedirection(); +app.UseAuthorization(); -app.MapGet("/weatherforecast", () => - { - var forecast = Enumerable.Range(1, 5).Select(index => - new WeatherForecast - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - return forecast; - }) - .WithName("GetWeatherForecast") - .WithOpenApi(); +app.MapControllers(); -app.Run(); - -record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) -{ - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); -} \ No newline at end of file +app.Run(); \ No newline at end of file diff --git a/Program/Common/Data/AppDbContext.cs b/Program/Common/Data/AppDbContext.cs new file mode 100644 index 0000000..d8385aa --- /dev/null +++ b/Program/Common/Data/AppDbContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using AcaMate.V1.Models; +using Version = AcaMate.V1.Models.Version; + +namespace AcaMate.Common.Data; +//database=AcaMate; +public class AppDbContext: DbContext +{ + public AppDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Versions { get; set; } +} \ No newline at end of file diff --git a/Program/Common/Model/JwtSettings.cs b/Program/Common/Model/JwtSettings.cs new file mode 100644 index 0000000..7f8d826 --- /dev/null +++ b/Program/Common/Model/JwtSettings.cs @@ -0,0 +1,9 @@ +namespace AcaMate.Common.Models; + +public class JwtSettings +{ + public string SecretKey { get; set; } + public string Issuer { get; set; } + public string Audience { get; set; } + public int ExpiryMinutes { get; set; } +} \ No newline at end of file diff --git a/Program/Common/Model/Status.cs b/Program/Common/Model/Status.cs new file mode 100644 index 0000000..60f4939 --- /dev/null +++ b/Program/Common/Model/Status.cs @@ -0,0 +1,61 @@ +namespace AcaMate.Common.Models; + +public class APIResponseStatus +{ + public Status Status { get; set; } + public T data { get; set; } +} + +public class Status +{ + public string Code { get; set; } + public string Message { get; set; } +} + +public static class ErrorResponse +{ + // private static readonly Lazy _instance = new Lazy(); + // public static ErrorResponse Instace => _instance.Value; + + // private ErrorResponse() + // { + // // 외부 초기화 방지 + // } + + public static readonly APIResponseStatus InvalidInputError = new APIResponseStatus + { + Status = new Status() + { + Code = "001", + Message = "입력 값이 유효하지 않습니다." + } + }; + + public static readonly APIResponseStatus NotFoundError = new APIResponseStatus + { + Status = new Status() + { + Code = "002", + Message = "알맞은 값을 찾을 수 없습니다." + } + }; + + public static readonly APIResponseStatus InternalSeverError = new APIResponseStatus + { + Status = new Status() + { + Code = "003", + Message = "통신에 오류가 발생하였습니다." + } + }; + + + public static readonly APIResponseStatus UnknownError = new APIResponseStatus + { + Status = new Status() + { + Code = "999", + Message = "알 수 없는 오류가 발생하였습니다.." + } + }; +} diff --git a/Program/V1/Controllers/AppController.cs b/Program/V1/Controllers/AppController.cs new file mode 100644 index 0000000..25de08f --- /dev/null +++ b/Program/V1/Controllers/AppController.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; +using AcaMate.Common.Data; +using AcaMate.Common.Models; +using Microsoft.IdentityModel.Tokens; +using Version = AcaMate.V1.Models.Version; + +namespace AcaMate.V1.Controllers; + +[ApiController] +[Route("/v1/in/app")] +[ApiExplorerSettings(GroupName = "공통")] +public class AppController : ControllerBase +{ + private readonly AppDbContext _dbContext; + + public AppController(AppDbContext dbContext) + { + _dbContext = dbContext; + } + + [HttpGet("version")] + [CustomOperation("앱 버전 확인","앱 버전을 확인해서 업데이트 여부 판단", "시스템")] + public IActionResult GetVersionData(string type) + { + if (string.IsNullOrEmpty(type)) + { + return BadRequest(ErrorResponse.InvalidInputError); + } + + try + { + var version = _dbContext.Versions.FirstOrDefault(v => v.os_type == (type == "I" ? "VO01" : "VO02")); + + if (version == null) + { + return NotFound(ErrorResponse.NotFoundError); + } + + var response = new APIResponseStatus + { + Status = new Status() + { + Code = "000", + Message = "정상" + }, + data = new Version() + { + os_type = (version.os_type == "VO01" ? "I" : (version.os_type == "VO02" ? "A" : "W")), + final_ver = version.final_ver, + force_ver = version.force_ver, + dev_ver = version.dev_ver, + choice_update_yn = version.choice_update_yn + } + }; + + string jsonString = JsonSerializer.Serialize(response); + + return Ok(jsonString); + } + catch (Exception ex) + { + return StatusCode(500, ErrorResponse.UnknownError); + } + } +} \ No newline at end of file diff --git a/Program/V1/Controllers/ErrorController.cs b/Program/V1/Controllers/ErrorController.cs new file mode 100644 index 0000000..cb5be62 --- /dev/null +++ b/Program/V1/Controllers/ErrorController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace AcaMate.V1.Controllers; + + +[ApiController] +[Route("error")] +public class ErrorController: ControllerBase +{ + [HttpGet] + public IActionResult HandleError() + { + return Problem("오류가 발생하였습니다. 잠시후 다시 시도해주세요."); + } +} \ No newline at end of file diff --git a/Program/V1/Controllers/MemberController.cs b/Program/V1/Controllers/MemberController.cs new file mode 100644 index 0000000..00d048a --- /dev/null +++ b/Program/V1/Controllers/MemberController.cs @@ -0,0 +1,29 @@ + +using System.Text.Json; +using AcaMate.Common.Data; +using AcaMate.Common.Models; + +using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.Mvc; + +namespace AcaMate.V1.Controllers; + +[ApiController] +[Route("v1/in/member")] +[ApiExplorerSettings(GroupName = "사업자 정보")] +public class MemberController: ControllerBase +{ + [HttpGet("business")] + public IActionResult GetBusinessData() + { + // return Ok("GOOD"); + return Ok("DB 참조"); + } + + + [HttpGet("/v1/out/member/business")] + public IActionResult SearchBusinessNo() + { + return Ok("외부 참조"); + } +} \ No newline at end of file diff --git a/Program/V1/Controllers/PushController.cs b/Program/V1/Controllers/PushController.cs new file mode 100644 index 0000000..bdd2e1b --- /dev/null +++ b/Program/V1/Controllers/PushController.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; + +namespace AcaMate.V1.Controllers; + +[ApiController] +[Route("v1/in/push")] +[ApiExplorerSettings(GroupName = "공통")] +public class PushController: ControllerBase +{ + [HttpGet()] + [CustomOperation("푸시 확인","저장된 양식을 확인 할 수 있다..", "푸시")] + public IActionResult GetPushData() + { + return Ok("SEND"); + } + + [HttpGet("send")] + [CustomOperation("푸시전송","저장된 양식으로, 사용자에게 푸시를 전송한다.", "푸시")] + public IActionResult SendPush() + { + return Ok("SEND"); + } +} \ No newline at end of file diff --git a/Program/V1/Controllers/UserController.cs b/Program/V1/Controllers/UserController.cs new file mode 100644 index 0000000..3952323 --- /dev/null +++ b/Program/V1/Controllers/UserController.cs @@ -0,0 +1,46 @@ +using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authentication; + +using AcaMate.V1.Services; + +namespace AcaMate.V1.Controllers; + + +[ApiController] +[Route("/v1/in/user")] +[ApiExplorerSettings(GroupName = "사용자")] +public class UserController: ControllerBase +{ + // private readonly UserController _userController; + + // private readonly UserService _userService; + // + // public UserController(UserService userService) + // { + // _userService = userService; + // } + + [HttpGet("snsLogin")] + public IActionResult SNSLogin() + { + var response = new + { + status = new + { + code = "000", + message = "정상" + }, + data = new + { + uid = "AC0000" + } + }; + + string jsonString = JsonSerializer.Serialize(response); + + return Ok(jsonString); + } + +} \ No newline at end of file diff --git a/Program/V1/Models/Version.cs b/Program/V1/Models/Version.cs new file mode 100644 index 0000000..ef25105 --- /dev/null +++ b/Program/V1/Models/Version.cs @@ -0,0 +1,21 @@ + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace AcaMate.V1.Models; + +[Table("version")] +public class Version +{ + [Key] + [MaxLength(4)] + public string os_type { get; set; } + [MaxLength(8)] + public string final_ver { get; set; } + [MaxLength(8)] + public string dev_ver { get; set; } + [MaxLength(8)] + public string force_ver { get; set; } + public bool choice_update_yn { get; set; } + +} \ No newline at end of file diff --git a/Program/V1/Services/UserService.cs b/Program/V1/Services/UserService.cs new file mode 100644 index 0000000..b406693 --- /dev/null +++ b/Program/V1/Services/UserService.cs @@ -0,0 +1,6 @@ +namespace AcaMate.V1.Services; + +public class UserService +{ + // priva +} \ No newline at end of file diff --git a/README.md b/README.md index 9d1c1b6..55ea7fa 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,12 @@ ### Skill - .NET Web API ### IDE -- JetBrains Rider \ No newline at end of file +- JetBrains Rider + +### 추가 패키지 +| No. | Name | Version | Description | +|:---:|:-------------------------------------------:|:-------:|:---------------------------| +| 1 | Microsoft.AspNetCore.OpenApi | 8.0.10 | OpenAPI 를 지원하기 위해 사용 | +| 2 | Microsoft.EntityFrameworkCore | 8.0.10 | 데이터베이스 작업을 간편하게 수행하기 위해 사용 | +| 3 | Pomelo.EntityFrameworkCore.MySql | 8.0.2 | MariaDB 연결 | +| 4 |Microsoft.AspNetCore.Authentication.JwtBearer| 8.0.10 | | diff --git a/SwaggerConfigure.cs b/SwaggerConfigure.cs new file mode 100644 index 0000000..6526728 --- /dev/null +++ b/SwaggerConfigure.cs @@ -0,0 +1,92 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.Annotations; +using Swashbuckle.AspNetCore.SwaggerGen; + + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +public class CustomOperationAttribute : Attribute +{ + public string Summary { get; } + public string Description { get; } + public string[] Tags { get; } + + public CustomOperationAttribute(string summary, string description, params string[] tags) + { + Summary = summary; + Description = description; + Tags = tags; + } +} + + + +public class CustomSwaggerOperationFilter : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var customSwaggerAttribute = context.MethodInfo.GetCustomAttributes(typeof(CustomOperationAttribute), false) + .FirstOrDefault() as CustomOperationAttribute; + + if (customSwaggerAttribute != null) + { + operation.Summary = customSwaggerAttribute.Summary; + operation.Description = customSwaggerAttribute.Description; + operation.Tags = customSwaggerAttribute.Tags + .Select(tag => new OpenApiTag { Name = tag }) + .ToList(); + } + } +} + +public static class SwaggerConfigure +{ + private static OpenApiInfo DocName(string title, string version) + { + return new OpenApiInfo + { + Title = title, + Version = version + }; + } + public static void AddCustomSwagger(this IServiceCollection services) + { + services.AddSwaggerGen(options => + { + options.EnableAnnotations(); + options.OperationFilter(); + options.SwaggerDoc("전체", DocName("AcaMate 전체 API","1.0.0")); + options.SwaggerDoc("공통",DocName("공통 API", "1.0.0")); + options.SwaggerDoc("사업자 정보", DocName("사업자 정보 API", "1.0.0")); + options.SwaggerDoc("사용자", DocName("사용자 API", "1.0.0")); + + options.DocInclusionPredicate((docName, apiDesc) => + { + if (docName == "전체") return true; // 전체 문서에 모든 API 포함 + if (docName == "공통" && apiDesc.GroupName == "공통") return true; + if (docName == "사업자 정보" && apiDesc.GroupName == "사업자 정보") return true; + if (docName == "사용자" && apiDesc.GroupName == "사용자") return true; + return false; + }); + + options.TagActionsBy(apiDesc => new[] { apiDesc.GroupName ?? "기타" }); + + + // options.TagActionsBy(apiDesc => apiDesc.ActionDescriptor.EndpointMetadata + // .OfType() + // .FirstOrDefault()?.Tags ?? new[] { "기타" }); + }); + } + + public static void UseCustomSwaggerUI(this IApplicationBuilder app) + { + app.UseSwagger(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/전체/swagger.json", "전체 API"); + options.SwaggerEndpoint("/swagger/공통/swagger.json", "공통 API"); + options.SwaggerEndpoint("/swagger/사용자/swagger.json", "사용자 API"); + options.SwaggerEndpoint("/swagger/사업자 정보/swagger.json", "사업자 정보 API"); + }); + } +} \ No newline at end of file