Selaa lähdekoodia

국제 시세 페이지 신설 + 캐시 수리 + data.go.kr 키 교체

- 국제 시세 메뉴 복원: 구리/알루미늄/밀/섬유(면화)/설탕/커피/원자재 지수 7종
  (Alpha Vantage, 단일 라우트 Price/Global/{commodity} + 공용 뷰)
- IMemoryCache DI 도입: API 응답 1일 캐시로 무료 쿼터(25회/일) 보호
- 버그 수정: 천연가스 원화 환산 항상 0(int.TryParse), 페이지네이션 interval 초기화,
  환율 조회 레이스 → 3초 타임아웃 + 실패값 5분 캐시
- 금/석유/배출권: data.go.kr 신규 키 반영, API 오류 시 alert 표출

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
KIM-JINO5 1 päivä sitten
vanhempi
sitoutus
fc1c44912b

+ 5 - 0
Controllers/Price/Domestic/EmissionController.cs

@@ -56,6 +56,11 @@ namespace economy.Controllers.Price.Domestic
                     return row;
                 }).ToList();
             }
+            else
+            {
+                ViewBag.isError = true;
+                ViewBag.errorMessage = "시세 정보를 가져오지 못했습니다. (외부 API 오류)";
+            }
 
             var viewModel = new View<Request, Response>();
             viewModel.SelectedListPerPage = request.NumOfRows;

+ 5 - 0
Controllers/Price/Domestic/GoldController.cs

@@ -56,6 +56,11 @@ namespace economy.Controllers.Price.Domestic
                     return row;
                 }).ToList();
             }
+            else
+            {
+                ViewBag.isError = true;
+                ViewBag.errorMessage = "시세 정보를 가져오지 못했습니다. (외부 API 오류)";
+            }
 
             var viewModel = new View<Request, Response>();
             viewModel.SelectedListPerPage = request.NumOfRows;

+ 5 - 0
Controllers/Price/Domestic/OilController.cs

@@ -53,6 +53,11 @@ namespace economy.Controllers.Price.Domestic
                     return row;
                 }).ToList();
             }
+            else
+            {
+                ViewBag.isError = true;
+                ViewBag.errorMessage = "시세 정보를 가져오지 못했습니다. (외부 API 오류)";
+            }
 
             var viewModel = new View<Request, Response>();
             viewModel.SelectedListPerPage = request.NumOfRows;

+ 135 - 18
Controllers/Price/GlobalController.cs

@@ -1,11 +1,14 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc;
 using Microsoft.AspNetCore.Mvc.Filters;
 using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Caching.Memory;
+using System.Globalization;
 using economy.Helpers;
 using economy.Models;
 using economy.Models.Financial;
 using economy.Models.Price.Global;
 using NaturalGas = economy.Models.Price.Global.NaturalGas;
+using Commodity = economy.Models.Price.Global.Commodity;
 
 namespace economy.Controllers.Price.Global
 {
@@ -13,22 +16,27 @@ namespace economy.Controllers.Price.Global
     {
         private readonly Alpha_API _alphaAPI;
         private readonly KoreaEximGoKR _koreaEximGoKR;
+        private readonly IMemoryCache _cache;
         private Dictionary<string, string> _queryString;
-        private int _exchangeValue = 0;
 
-        public GlobalController(Alpha_API alphaAPI, KoreaEximGoKR koreaEximGoKR)
+        // 커모디티 화이트리스트 (Alpha Vantage 함수, 화면 제목, 원화 표시 여부)
+        private static readonly Dictionary<string, (string Function, string Title, bool HasKrw)> CommodityMap = new(StringComparer.OrdinalIgnoreCase)
+        {
+            ["copper"]         = ("COPPER",          "구리 시세",       true),
+            ["aluminum"]       = ("ALUMINUM",        "알루미늄 시세",   true),
+            ["wheat"]          = ("WHEAT",           "밀 시세",         true),
+            ["cotton"]         = ("COTTON",          "섬유(면화) 시세", true),
+            ["sugar"]          = ("SUGAR",           "설탕 시세",       true),
+            ["coffee"]         = ("COFFEE",          "커피 시세",       true),
+            ["allcommodities"] = ("ALL_COMMODITIES", "원자재 종합지수", false), // 지수(2016=100)라 원화 환산 없음
+        };
+
+        public GlobalController(Alpha_API alphaAPI, KoreaEximGoKR koreaEximGoKR, IMemoryCache cache)
         {
             _alphaAPI = alphaAPI;
             _koreaEximGoKR = koreaEximGoKR;
-
-            InitializeAsync();
-        }
-
-        // 초기화 메서드
-        public async Task InitializeAsync()
-        {
-            // 환율 조회
-            _exchangeValue = await new FinancialModel(_koreaEximGoKR).GetExchangeValue();
+            _cache = cache;
+            _queryString = [];
         }
 
         public override void OnActionExecuting(ActionExecutingContext context)
@@ -40,6 +48,33 @@ namespace economy.Controllers.Price.Global
             base.OnActionExecuting(context);
         }
 
+        // 환율 조회 (1시간 캐시 — 실패값 0은 캐시하지 않음)
+        private async Task<int> GetExchangeValueAsync()
+        {
+            if (_cache.TryGetValue("ExchangeValue_USD", out int cachedValue))
+            {
+                return cachedValue;
+            }
+
+            // 환율 API가 느리거나 장애일 때 시세 페이지까지 지연되지 않도록 최대 3초만 대기 (실패 시 원화 미표시)
+            var exchangeTask = new FinancialModel(_koreaEximGoKR).GetExchangeValue();
+            var completedTask = await Task.WhenAny(exchangeTask, Task.Delay(TimeSpan.FromSeconds(3)));
+
+            int exchangeValue = (completedTask == exchangeTask) ? await exchangeTask : 0;
+
+            if (exchangeValue > 0)
+            {
+                _cache.Set("ExchangeValue_USD", exchangeValue, TimeSpan.FromHours(1));
+            }
+            else
+            {
+                // 환율 API 장애 중 매 요청마다 3초씩 대기하지 않도록 실패값은 5분간만 유지
+                _cache.Set("ExchangeValue_USD", 0, TimeSpan.FromMinutes(5));
+            }
+
+            return exchangeValue;
+        }
+
         [HttpGet("Price/Global/NaturalGas")]
         public async Task<IActionResult> NaturalGas(NaturalGas.Request request)
         {
@@ -53,12 +88,14 @@ namespace economy.Controllers.Price.Global
                 return BadRequest("Invalid input provided.");
             }
 
-            GlobalModel globalModel = new GlobalModel(_alphaAPI);
+            int exchangeValue = await GetExchangeValueAsync();
+
+            GlobalModel globalModel = new GlobalModel(_alphaAPI, _cache);
             NaturalGas.Response itemList = await globalModel.GetNaturalGasPriceInfo(request);
 
             int total = 0;
 
-           if (itemList.Data != null && itemList.Data.Any())
+            if (itemList.Data != null && itemList.Data.Any())
             {
                 total = itemList.Data.Count;
                 int listNum = Common.CalcListNumber(total, request.PageNo, request.NumOfRows);
@@ -71,14 +108,19 @@ namespace economy.Controllers.Price.Global
                 {
                     row.Num = (listNum - index);
 
-                    if (int.TryParse(row.Value, out int value) && value > 0)
+                    if (decimal.TryParse(row.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal value) && value > 0)
                     {
-                        row.KRW = (value * _exchangeValue);
+                        row.KRW = (int)Math.Round(value * exchangeValue);
                     }
-                  
+
                     return row;
                 }).ToList();
             }
+            else
+            {
+                ViewBag.isError = true;
+                ViewBag.errorMessage = "국제 시세 데이터를 가져오지 못했습니다. (API 한도 초과 또는 일시 장애)";
+            }
 
             var viewModel = new View<NaturalGas.Request, NaturalGas.Response>();
             viewModel.SelectedListPerPage = request.NumOfRows;
@@ -87,7 +129,7 @@ namespace economy.Controllers.Price.Global
 
             var queryString = new
             {
-                date = request.Interval
+                interval = request.Interval
             };
 
             var pagination = new Pagination(total, request.PageNo, request.NumOfRows, queryString);
@@ -95,5 +137,80 @@ namespace economy.Controllers.Price.Global
 
             return View("/Views/Price/Global/NaturalGas.cshtml", viewModel);
         }
+
+        [HttpGet("Price/Global/{commodity}")]
+        public async Task<IActionResult> Commodity(Commodity.Request request)
+        {
+            if (!ModelState.IsValid)
+            {
+                return BadRequest(ModelState);
+            }
+
+            // 화이트리스트에 없는 상품은 404
+            if (request.Commodity is null || !CommodityMap.TryGetValue(request.Commodity, out var meta))
+            {
+                return NotFound();
+            }
+
+            // 원자재 계열은 monthly/quarterly/annual만 지원 (잘못된 값은 Alpha Vantage가 조용히 monthly로 대체하므로 사전 차단)
+            if (!new[] { "monthly", "quarterly", "annual" }.Contains(request.Interval))
+            {
+                return BadRequest("Invalid input provided.");
+            }
+
+            int exchangeValue = meta.HasKrw ? await GetExchangeValueAsync() : 0;
+
+            GlobalModel globalModel = new GlobalModel(_alphaAPI, _cache);
+            Commodity.Response itemList = await globalModel.GetCommodityPriceInfo(meta.Function, request.Interval);
+
+            int total = 0;
+
+            if (itemList.Data != null && itemList.Data.Any())
+            {
+                total = itemList.Data.Count;
+                int listNum = Common.CalcListNumber(total, request.PageNo, request.NumOfRows);
+
+                // 페이징 처리
+                int skip = (request.PageNo - 1) * request.NumOfRows;
+                int take = request.NumOfRows;
+
+                itemList.Data = itemList.Data.Skip(skip).Take(take).Select((row, index) =>
+                {
+                    row.Num = (listNum - index);
+
+                    if (decimal.TryParse(row.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal value) && value > 0)
+                    {
+                        row.KRW = Math.Round(value * exchangeValue);
+                    }
+
+                    row.Value = Common.NumberFormat(row.Value, "#,##0.##");
+
+                    return row;
+                }).ToList();
+            }
+            else
+            {
+                ViewBag.isError = true;
+                ViewBag.errorMessage = "국제 시세 데이터를 가져오지 못했습니다. (API 한도 초과 또는 일시 장애)";
+            }
+
+            ViewBag.Title = meta.Title;
+            ViewBag.HasKrw = meta.HasKrw;
+
+            var viewModel = new View<Commodity.Request, Commodity.Response>();
+            viewModel.SelectedListPerPage = request.NumOfRows;
+            viewModel.Request = request;
+            viewModel.Response = itemList;
+
+            var queryString = new
+            {
+                interval = request.Interval
+            };
+
+            var pagination = new Pagination(total, request.PageNo, request.NumOfRows, queryString);
+            viewModel.Pagination = pagination;
+
+            return View("/Views/Price/Global/Commodity.cshtml", viewModel);
+        }
     }
 }

+ 3 - 3
Models/Clients.cs

@@ -7,7 +7,7 @@ namespace economy.Models
     {
         public readonly HttpClient httpClient;
         public readonly string APIUrl = "http://apis.data.go.kr";
-        public readonly string APIKey = "ee10c32875a7f8594ab0642a19e65eae38b2bc949b97089327b9466b6a5bc996";
+        public readonly string APIKey = "13c11eacff586886b241a2d0b1b792bd09981f1a56e3661e3e838528e4c05db0";
 
         public DataGoKR(HttpClient e)
         {
@@ -21,7 +21,7 @@ namespace economy.Models
     {
         public readonly HttpClient httpClient;
         public readonly string APIUrl = "https://flower.at.or.kr";
-        public readonly string APIKey = "5F70416B443241BE846D0807744FC489";
+        public readonly string APIKey = "DB62A9CDE871406F8955034813B812EC";
 
         public FlowerAtOrKR(HttpClient e)
         {
@@ -77,7 +77,7 @@ namespace economy.Models
     {
         public readonly HttpClient httpClient;
         public readonly string APIUrl = "http://api.odcloud.kr";
-        public readonly string APIKey = "vQz8tIxrdhjerG6DE1w1hcVEli5S27LtIsCvx0axiieZmRgOpB4vToQ77VmvknAkIC9YjxlPx2gDZcl06S88Xw%3D%3D";
+        public readonly string APIKey = "13c11eacff586886b241a2d0b1b792bd09981f1a56e3661e3e838528e4c05db0";
 
         public NTS_API(HttpClient e)
         {

+ 58 - 11
Models/Price/GlobalModel.cs

@@ -1,4 +1,4 @@
-using System.Text.Json;
+using System.Text.Json;
 using Microsoft.Extensions.Caching.Memory;
 
 namespace economy.Models.Price.Global
@@ -8,16 +8,16 @@ namespace economy.Models.Price.Global
         private readonly Alpha_API _alphaAPI;
         private readonly IMemoryCache _cache;
 
-        public GlobalModel(Alpha_API alphaAPI)
+        public GlobalModel(Alpha_API alphaAPI, IMemoryCache cache)
         {
             _alphaAPI = alphaAPI;
-            _cache = new MemoryCache(new MemoryCacheOptions());
+            _cache = cache;
         }
-       
+
         // 천연가스 시세 조회
         public async Task<NaturalGas.Response> GetNaturalGasPriceInfo(NaturalGas.Request request)
         {
-            string cacheKey = "ExchangeValue";
+            string cacheKey = $"Alpha_NATURAL_GAS_{request.Interval}";
 
             // 캐시에 데이터가 존재하면 반환
             if (_cache.TryGetValue(cacheKey, out NaturalGas.Response cachedData))
@@ -41,18 +41,65 @@ namespace economy.Models.Price.Global
                 {
                     var jsonString = await response.Content.ReadAsStringAsync();
 
-                    using (var document = JsonDocument.Parse(jsonString))
-                    {
-                        parseData = JsonSerializer.Deserialize<NaturalGas.Response>(jsonString);
+                    parseData = JsonSerializer.Deserialize<NaturalGas.Response>(jsonString);
 
-                        // 캐시에 저장 (1일 동안 유지)
-                        _cache.Set(cacheKey, parseData, TimeSpan.FromDays(1));
+                    // 호출 한도 초과 시 HTTP 200 + Information 응답이 와서 Data가 null이 됨 — 빈 응답은 캐시하지 않음
+                    if (parseData.Data is null)
+                    {
+                        return new NaturalGas.Response();
                     }
 
+                    // 캐시에 저장 (1일 동안 유지)
+                    _cache.Set(cacheKey, parseData, TimeSpan.FromDays(1));
+                }
+
+                response.EnsureSuccessStatusCode();
+            }
+            catch (HttpRequestException e)
+            {
+                Console.WriteLine($"Request error: {e.Message}");
+            }
+
+            return parseData;
+        }
+
+        // 국제 원자재 시세 조회 (구리, 알루미늄, 밀, 섬유(면화), 설탕, 커피, 원자재 종합지수)
+        public async Task<Commodity.Response> GetCommodityPriceInfo(string function, string interval)
+        {
+            string cacheKey = $"Alpha_{function}_{interval}";
+
+            // 캐시에 데이터가 존재하면 반환
+            if (_cache.TryGetValue(cacheKey, out Commodity.Response cachedData))
+            {
+                return cachedData;
+            }
+
+            Commodity.Response parseData = new();
+
+            try
+            {
+                var uriBuilder = new UriBuilder(_alphaAPI.APIUrl)
+                {
+                    Path = "/query",
+                    Query = $"function={function}&interval={interval}&apikey={_alphaAPI.APIKey}"
+                };
+
+                var response = await _alphaAPI.httpClient.GetAsync(uriBuilder.Uri);
+
+                if (response.IsSuccessStatusCode)
+                {
+                    var jsonString = await response.Content.ReadAsStringAsync();
+
+                    parseData = JsonSerializer.Deserialize<Commodity.Response>(jsonString);
+
+                    // 호출 한도 초과 시 HTTP 200 + Information 응답이 와서 Data가 null이 됨 — 빈 응답은 캐시하지 않음
                     if (parseData.Data is null)
                     {
-                        return new NaturalGas.Response();
+                        return new Commodity.Response();
                     }
+
+                    // 캐시에 저장 (1일 동안 유지)
+                    _cache.Set(cacheKey, parseData, TimeSpan.FromDays(1));
                 }
 
                 response.EnsureSuccessStatusCode();

+ 31 - 0
Models/Request/Price/Global/Commodity.cs

@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Mvc;
+using System.ComponentModel.DataAnnotations;
+
+namespace economy.Models.Price.Global.Commodity
+{
+    /*
+     * 국제 원자재 시세 (구리, 알루미늄, 밀, 섬유(면화), 설탕, 커피, 원자재 종합지수)
+     * 예시 ) https://www.alphavantage.co/query?function=COPPER&interval=monthly&apikey=EFOBUHL5SFLBF2LN
+     */
+
+    // 검색요청 변수
+    public class Request
+    {
+        [BindProperty(Name = "commodity", SupportsGet = true)]
+        [StringLength(30, ErrorMessage = "상품 형식이 잘못되었습니다.")]
+        public string? Commodity { get; set; }
+
+        [BindProperty(Name = "page", SupportsGet = true)]
+        [Range(1, int.MaxValue, ErrorMessage = "페이지 허용량을 초과하였습니다.")]
+        public int PageNo { get; set; } = 1;
+
+        [BindProperty(Name = "perPage", SupportsGet = true)]
+        [Range(1, int.MaxValue, ErrorMessage = "출력 허용량을 초과하였습니다.")]
+        public int NumOfRows { get; set; } = 10;
+
+        [BindProperty(Name = "interval", SupportsGet = true)]
+        [Required(ErrorMessage = "검색 구간을 입력해주세요.")]
+        [StringLength(120, ErrorMessage = "검색 구간을 초과하였습니다.")]
+        public string Interval { get; set; } = "monthly"; // quarterly, annual
+    }
+}

+ 32 - 0
Models/Response/Price/Global/Commodity.cs

@@ -0,0 +1,32 @@
+using System.Text.Json.Serialization;
+
+namespace economy.Models.Price.Global.Commodity
+{
+    public class Response
+    {
+        [JsonPropertyName("name")]
+        public string Name { get; set; }
+
+        [JsonPropertyName("interval")]
+        public string Interval { get; set; }
+
+        [JsonPropertyName("unit")]
+        public string Unit { get; set; }
+
+        [JsonPropertyName("data")]
+        public List<Data> Data { get; set; }
+    }
+
+    public class Data
+    {
+        public int Num { get; set; }
+
+        [JsonPropertyName("date")]
+        public string Date { get; set; }
+
+        [JsonPropertyName("value")]
+        public string Value { get; set; }
+
+        public decimal KRW { get; set; } = 0;
+    }
+}

+ 2 - 0
Program.cs

@@ -16,6 +16,8 @@ builder.Services.AddHttpClient<FIFA_API>();
 builder.Services.AddHttpClient<NTS_API>();
 builder.Services.AddHttpClient<Alpha_API>();
 
+builder.Services.AddMemoryCache();
+
 // DB ¿¬°á
 builder.Services.AddDbContext<EconomyContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
 

+ 91 - 0
Views/Price/Global/Commodity.cshtml

@@ -0,0 +1,91 @@
+@model economy.Models.View<economy.Models.Price.Global.Commodity.Request, economy.Models.Price.Global.Commodity.Response>
+
+@{
+    bool hasKrw = (ViewBag.HasKrw == true);
+}
+
+<div class="container">
+    <h3>@ViewData["Title"]</h3>
+
+    <div class="row mt-3">
+        <div class="col align-self-center">
+            @((Model?.Response?.Data?.Count ?? 0).ToString("N0")) 개
+        </div>
+        <div class="col-auto">
+            <div class="row g-2">
+                <div class="col">
+                    <select name="interval" id="interval" class="form-select" form="fSearch">
+                        <option value="monthly" selected="@(Model.Request.Interval == "monthly")">월간</option>
+                        <option value="quarterly" selected="@(Model.Request.Interval == "quarterly")">분기</option>
+                        <option value="annual" selected="@(Model.Request.Interval == "annual")">연간</option>
+                    </select>
+                </div>
+                <div class="col">
+                    <select name="perPage" id="perPage" class="form-select" asp-for="SelectedListPerPage" asp-items="Model.ListPerPage" form="fSearch"></select>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <div class="table-responsive">
+        <table id="globalPriceList" class="table table-bordered table-hover">
+            <caption class="caption-top">
+                단위: @Model.Response.Unit
+            </caption>
+            <thead>
+                <tr>
+                    <th>번호</th>
+                    <th>날짜</th>
+                    @if (hasKrw)
+                    {
+                        <th>달러($)</th>
+                        <th>원화(\)</th>
+                    }
+                    else
+                    {
+                        <th>지수</th>
+                    }
+                </tr>
+            </thead>
+            <tbody>
+                @if (Model.Response.Data != null && Model.Response.Data?.Count > 0)
+                {
+                    @foreach (var row in Model.Response.Data)
+                    {
+                        <tr>
+                            <td>@row.Num</td>
+                            <td>@row.Date</td>
+                            <td>@row.Value</td>
+                            @if (hasKrw)
+                            {
+                                <td>@row.KRW.ToString("N0")</td>
+                            }
+                        </tr>
+                    }
+                }
+                else
+                {
+                    <tr>
+                        <td colspan="@(hasKrw ? 4 : 3)">
+                            No data.
+                        </td>
+                    </tr>
+                }
+            </tbody>
+        </table>
+    </div>
+
+    <form id="fSearch" method="get" accept-charset="UTF-8" rel="search" autocomplete="off" asp-controller="Global" asp-action="Commodity" asp-route-commodity="@Model.Request.Commodity">
+        <input type="hidden" name="page" value="@Model.Request.PageNo" />
+    </form>
+
+    @await Html.PartialAsync("~/Views/Component/Pagination.cshtml", Model.Pagination)
+
+</div>
+
+@section Scripts {
+    <script src="~/js/commodity.js" asp-append-version="true"></script>
+}
+@section Styles {
+    <link href="~/css/style.css" rel="stylesheet" asp-append-version="true" />
+}

+ 13 - 15
Views/Shared/_Layout.cshtml

@@ -3,11 +3,11 @@
 <head>
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
-    <title>@ViewData["Title"] - Economy</title>
+    <title>@ViewData["Title"] - 이것저것 생활 정보 모음</title>
     
     <meta http-equiv="refresh" content="3000"/>
-    <meta name="description" content="다양한 국제 시세 정보를 확인할 수 있습니다."/>
-    <meta name="keywords" content="각종 주요 광물, 상품 시세 정보"/>
+    <meta name="description" content="우리 생활에 꼭 알고가면 좋은 정보를 모아 보았습니다."/>
+    <meta name="keywords" content="각종 주요 광물, 금, 석유, 배출권, 화훼, 국제 시세, 생활 정보"/>
     <meta name="author" content="김진오"/>
     <meta name="naver-site-verification" content="534321e6b7402169fc787ae2f795049aef0232e3"/>
 
@@ -34,7 +34,7 @@
     <header>
         <nav class="navbar navbar-expand-md navbar-toggleable-md navbar-light bg-white border-bottom box-shadow mb-3">
             <div class="container">
-                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Economy API</a>
+                <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">이것저것</a>
                 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                     <span class="navbar-toggler-icon"></span>
                 </button>
@@ -63,7 +63,6 @@
                             </ul>
                         </li>
 
-                        @*
                         <li class="nav-item dropdown">
                             <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">국제 시세</a>
                             <ul class="dropdown-menu">
@@ -73,29 +72,28 @@
                                     <a class="nav-link text-dark" asp-area="" asp-controller="Global" asp-action="NaturalGas">천연가스</a>
                                 </li>
                                 <li>
-                                    <a class="nav-link text-dark" asp-area="" asp-controller="Flower" asp-action="Index">구리</a>
+                                    <a class="nav-link text-dark" asp-area="" asp-controller="Global" asp-action="Commodity" asp-route-commodity="copper">구리</a>
                                 </li>
                                 <li>
-                                    <a class="nav-link text-dark" asp-area="" asp-controller="Flower" asp-action="Index">알루미늄</a>
+                                    <a class="nav-link text-dark" asp-area="" asp-controller="Global" asp-action="Commodity" asp-route-commodity="aluminum">알루미늄</a>
                                 </li>
                                 <li>
-                                    <a class="nav-link text-dark" asp-area="" asp-controller="Flower" asp-action="Index">밀</a>
+                                    <a class="nav-link text-dark" asp-area="" asp-controller="Global" asp-action="Commodity" asp-route-commodity="wheat">밀</a>
                                 </li>
                                 <li>
-                                    <a class="nav-link text-dark" asp-area="" asp-controller="Flower" asp-action="Index">섬유</a>
+                                    <a class="nav-link text-dark" asp-area="" asp-controller="Global" asp-action="Commodity" asp-route-commodity="cotton">섬유</a>
                                 </li>
                                 <li>
-                                    <a class="nav-link text-dark" asp-area="" asp-controller="Flower" asp-action="Index">설탕</a>
+                                    <a class="nav-link text-dark" asp-area="" asp-controller="Global" asp-action="Commodity" asp-route-commodity="sugar">설탕</a>
                                 </li>
                                 <li>
-                                    <a class="nav-link text-dark" asp-area="" asp-controller="Flower" asp-action="Index">커피</a>
+                                    <a class="nav-link text-dark" asp-area="" asp-controller="Global" asp-action="Commodity" asp-route-commodity="coffee">커피</a>
                                 </li>
                                 <li>
-                                    <a class="nav-link text-dark" asp-area="" asp-controller="Flower" asp-action="Index">원자재</a>
+                                    <a class="nav-link text-dark" asp-area="" asp-controller="Global" asp-action="Commodity" asp-route-commodity="allcommodities">원자재</a>
                                 </li>
                             </ul>
                         </li>
-                        *@
 
                         <li class="nav-item">
                             <a class="nav-link text-dark" asp-area="" asp-controller="Days" asp-action="Holiday">공휴일/특일</a>
@@ -131,9 +129,9 @@
     </main>
   
     <footer class="border-top text-muted text-center">
-        <iframe src="https://ads-partners.coupang.com/widgets.html?id=825687&template=carousel&trackingCode=AF0305179&subId=&width=680&height=140&tsource=" width="680" height="140" frameborder="0" scrolling="no" referrerpolicy="unsafe-url" browsingtopics></iframe>
+        <iframe src="https://ads-partners.coupang.com/widgets.html?id=825687&template=carousel&trackingCode=AF0305179&subId=economy20240901&width=680&height=140&tsource=" width="680" height="140" frameborder="0" scrolling="no" referrerpolicy="unsafe-url" browsingtopics></iframe>
         <small>※ 쿠팡 파트너스 활동을 통해 일정액의 수수료를 제공받을수 있습니다.</small>
-        <a href="https://web.or.kr" ref="author" target="_blank">Copyright &copy; @DateTime.Now.Year All rights reserved.</a>
+        <a href="https://web.or.kr" ref="author" target="_blank">Copyright &copy; 2024~@DateTime.Now.Year All rights reserved.</a>
     </footer>
 
     <script src="~/lib/jquery/dist/jquery.min.js"></script>

+ 18 - 0
wwwroot/js/commodity.js

@@ -0,0 +1,18 @@
+class CommodityList {
+    constructor() {
+        this.form = document.getElementById("fSearch");
+    }
+
+    ChangePerPage(e) {
+        this.form.elements["page"].value = 1;
+        this.submit();
+    }
+
+    submit() {
+        this.form.submit();
+    }
+}
+
+const commodityList = new CommodityList();
+
+$(document).on("change", "#perPage, #interval", (e) => commodityList.ChangePerPage(e));