diff --git a/.gitignore b/.gitignore index f7f2c57..743cf1a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ desktop.ini .vs packages *.zip +deploy diff --git a/Models/VNCServer.cs b/Models/VNCServer.cs index d3866a8..5bcbc6b 100644 --- a/Models/VNCServer.cs +++ b/Models/VNCServer.cs @@ -11,5 +11,7 @@ namespace VNCServerList.Models public string Description { get; set; } public string Password { get; set; } public string Argument { get; set; } + public byte[] Icon { get; set; } + public string IconBase64 { get; set; } // Base64 인코딩된 아이콘 데이터 } } \ No newline at end of file diff --git a/Services/DatabaseService.cs b/Services/DatabaseService.cs index f38982a..068ac47 100644 --- a/Services/DatabaseService.cs +++ b/Services/DatabaseService.cs @@ -36,6 +36,7 @@ namespace VNCServerList.Services [Description] [varchar](100) NULL, [Password] [varchar](20) NULL, [Argument] [varchar](50) NULL, + [Icon] [image] NULL, CONSTRAINT [PK_VNC_ServerList] PRIMARY KEY CLUSTERED ( [User] ASC, @@ -47,6 +48,16 @@ namespace VNCServerList.Services { command.ExecuteNonQuery(); } + + // Icon 컬럼이 존재하는지 확인하고 없으면 추가 + string checkIconColumnSql = @" + IF NOT EXISTS (SELECT * FROM sys.columns WHERE object_id = OBJECT_ID(N'[dbo].[VNC_ServerList]') AND name = 'Icon') + ALTER TABLE [dbo].[VNC_ServerList] ADD [Icon] [image] NULL"; + + using (var command = new SqlCommand(checkIconColumnSql, connection)) + { + command.ExecuteNonQuery(); + } } } @@ -72,7 +83,8 @@ namespace VNCServerList.Services Title = reader["Title"] == DBNull.Value ? null : reader["Title"].ToString(), Description = reader["Description"] == DBNull.Value ? null : reader["Description"].ToString(), Password = reader["Password"] == DBNull.Value ? null : reader["Password"].ToString(), - Argument = reader["Argument"] == DBNull.Value ? null : reader["Argument"].ToString() + Argument = reader["Argument"] == DBNull.Value ? null : reader["Argument"].ToString(), + Icon = reader["Icon"] == DBNull.Value ? null : (byte[])reader["Icon"] }); } } @@ -105,7 +117,8 @@ namespace VNCServerList.Services Title = reader["Title"] == DBNull.Value ? null : reader["Title"].ToString(), Description = reader["Description"] == DBNull.Value ? null : reader["Description"].ToString(), Password = reader["Password"] == DBNull.Value ? null : reader["Password"].ToString(), - Argument = reader["Argument"] == DBNull.Value ? null : reader["Argument"].ToString() + Argument = reader["Argument"] == DBNull.Value ? null : reader["Argument"].ToString(), + Icon = reader["Icon"] == DBNull.Value ? null : (byte[])reader["Icon"] }; } } @@ -121,8 +134,8 @@ namespace VNCServerList.Services { connection.Open(); string sql = @" - INSERT INTO VNC_ServerList ([User], [IP], [Category], [Title],[Description], [Password], [Argument]) - VALUES (@User, @IP, @Category, @Title,@Description, @Password, @Argument)"; + INSERT INTO VNC_ServerList ([User], [IP], [Category], [Title],[Description], [Password], [Argument], [Icon]) + VALUES (@User, @IP, @Category, @Title,@Description, @Password, @Argument, @Icon)"; using (var command = new SqlCommand(sql, connection)) { @@ -133,6 +146,7 @@ namespace VNCServerList.Services command.Parameters.AddWithValue("@Description", (object)server.Description ?? DBNull.Value); command.Parameters.AddWithValue("@Password", (object)server.Password ?? DBNull.Value); command.Parameters.AddWithValue("@Argument", (object)server.Argument ?? DBNull.Value); + command.Parameters.AddWithValue("@Icon", (object)server.Icon ?? DBNull.Value); return command.ExecuteNonQuery() > 0; } @@ -147,7 +161,7 @@ namespace VNCServerList.Services string sql = @" UPDATE VNC_ServerList SET [Category] = @Category, [Title] = @Title, [Description] = @Description, - [Password] = @Password, [Argument] = @Argument + [Password] = @Password, [Argument] = @Argument, [Icon] = @Icon WHERE [User] = @User AND [IP] = @IP"; using (var command = new SqlCommand(sql, connection)) @@ -159,6 +173,7 @@ namespace VNCServerList.Services command.Parameters.AddWithValue("@Description", (object)server.Description ?? DBNull.Value); command.Parameters.AddWithValue("@Password", (object)server.Password ?? DBNull.Value); command.Parameters.AddWithValue("@Argument", (object)server.Argument ?? DBNull.Value); + command.Parameters.AddWithValue("@Icon", (object)server.Icon ?? DBNull.Value); return command.ExecuteNonQuery() > 0; } @@ -173,7 +188,7 @@ namespace VNCServerList.Services string sql = @" UPDATE VNC_ServerList SET [IP] = @NewIP, [Category] = @Category, [Title] = @Title, [Description] = @Description, - [Password] = @Password, [Argument] = @Argument + [Password] = @Password, [Argument] = @Argument, [Icon] = @Icon WHERE [User] = @User AND [IP] = @OriginalIP"; using (var command = new SqlCommand(sql, connection)) @@ -186,6 +201,7 @@ namespace VNCServerList.Services command.Parameters.AddWithValue("@Description", (object)newServerData.Description ?? DBNull.Value); command.Parameters.AddWithValue("@Password", (object)newServerData.Password ?? DBNull.Value); command.Parameters.AddWithValue("@Argument", (object)newServerData.Argument ?? DBNull.Value); + command.Parameters.AddWithValue("@Icon", (object)newServerData.Icon ?? DBNull.Value); return command.ExecuteNonQuery() > 0; } diff --git a/Web/Controllers/VNCServerController.cs b/Web/Controllers/VNCServerController.cs index ca1f7e0..e66a719 100644 --- a/Web/Controllers/VNCServerController.cs +++ b/Web/Controllers/VNCServerController.cs @@ -2,6 +2,12 @@ using System; using System.Collections.Generic; using System.Web.Http; using System.Linq; +using System.Net.Http; +using System.Net; +using System.IO; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; using VNCServerList.Models; using VNCServerList.Services; @@ -75,6 +81,20 @@ namespace VNCServerList.Web.Controllers return BadRequest("서버 정보가 없습니다."); } + // 아이콘이 Base64로 전송된 경우 처리 + if (!string.IsNullOrEmpty(server.IconBase64)) + { + try + { + var iconData = Convert.FromBase64String(server.IconBase64); + server.Icon = ResizeImage(iconData, 128, 128); + } + catch + { + return BadRequest("아이콘 데이터 형식이 올바르지 않습니다."); + } + } + bool success = _databaseService.AddServer(server); if (success) { @@ -102,6 +122,20 @@ namespace VNCServerList.Web.Controllers return BadRequest("서버 정보가 없습니다."); } + // 아이콘이 Base64로 전송된 경우 처리 + if (!string.IsNullOrEmpty(server.IconBase64)) + { + try + { + var iconData = Convert.FromBase64String(server.IconBase64); + server.Icon = ResizeImage(iconData, 128, 128); + } + catch + { + return BadRequest("아이콘 데이터 형식이 올바르지 않습니다."); + } + } + // 원본 사용자명과 IP로 서버를 찾아서 업데이트 bool success = _databaseService.UpdateServerByUserAndIP(originalUser, originalIp, server); if (success) @@ -264,5 +298,154 @@ namespace VNCServerList.Web.Controllers return InternalServerError(ex); } } + + [HttpGet] + [Route("icon/{user}/{ip}")] + public HttpResponseMessage GetServerIcon(string user, string ip) + { + try + { + var server = _databaseService.GetServerByUserAndIP(user, ip); + if (server?.Icon == null || server.Icon.Length == 0) + { + return Request.CreateResponse(HttpStatusCode.NotFound); + } + + var response = Request.CreateResponse(HttpStatusCode.OK); + response.Content = new ByteArrayContent(server.Icon); + response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/png"); + return response; + } + catch (Exception ex) + { + return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message); + } + } + + private byte[] ResizeImage(byte[] imageData, int maxWidth = 128, int maxHeight = 128) + { + using (var originalStream = new MemoryStream(imageData)) + using (var originalImage = Image.FromStream(originalStream)) + { + // 원본 이미지 크기 + int originalWidth = originalImage.Width; + int originalHeight = originalImage.Height; + + // 리사이즈가 필요한지 확인 + if (originalWidth <= maxWidth && originalHeight <= maxHeight) + { + return imageData; // 리사이즈 불필요 + } + + // 새로운 크기 계산 (비율 유지) + double ratioX = (double)maxWidth / originalWidth; + double ratioY = (double)maxHeight / originalHeight; + double ratio = Math.Min(ratioX, ratioY); + + int newWidth = (int)(originalWidth * ratio); + int newHeight = (int)(originalHeight * ratio); + + // 새 이미지 생성 + using (var resizedImage = new Bitmap(newWidth, newHeight)) + using (var graphics = Graphics.FromImage(resizedImage)) + { + // 고품질 리사이즈 설정 + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + // 이미지 그리기 + graphics.DrawImage(originalImage, 0, 0, newWidth, newHeight); + + // PNG로 변환하여 반환 + using (var outputStream = new MemoryStream()) + { + resizedImage.Save(outputStream, ImageFormat.Png); + return outputStream.ToArray(); + } + } + } + } + + [HttpPost] + [Route("icon/{user}/{ip}")] + public IHttpActionResult UploadServerIcon(string user, string ip) + { + try + { + var httpRequest = System.Web.HttpContext.Current.Request; + if (httpRequest.Files.Count == 0) + { + return BadRequest("업로드된 파일이 없습니다."); + } + + var file = httpRequest.Files[0]; + if (file.ContentLength > 1024 * 1024) // 1MB 제한 + { + return BadRequest("파일 크기는 1MB를 초과할 수 없습니다."); + } + + using (var memoryStream = new MemoryStream()) + { + file.InputStream.CopyTo(memoryStream); + var originalIconData = memoryStream.ToArray(); + + // 이미지 리사이즈 (128x128로 제한) + var resizedIconData = ResizeImage(originalIconData, 128, 128); + + var server = _databaseService.GetServerByUserAndIP(user, ip); + if (server == null) + { + return NotFound(); + } + + server.Icon = resizedIconData; + bool success = _databaseService.UpdateServer(server); + + if (success) + { + return Ok(new { Message = "아이콘이 성공적으로 업로드되었습니다." }); + } + else + { + return BadRequest("아이콘 업로드에 실패했습니다."); + } + } + } + catch (Exception ex) + { + return InternalServerError(ex); + } + } + + [HttpDelete] + [Route("icon/{user}/{ip}")] + public IHttpActionResult DeleteServerIcon(string user, string ip) + { + try + { + var server = _databaseService.GetServerByUserAndIP(user, ip); + if (server == null) + { + return NotFound(); + } + + server.Icon = null; + bool success = _databaseService.UpdateServer(server); + + if (success) + { + return Ok(new { Message = "아이콘이 성공적으로 삭제되었습니다." }); + } + else + { + return BadRequest("아이콘 삭제에 실패했습니다."); + } + } + catch (Exception ex) + { + return InternalServerError(ex); + } + } } } \ No newline at end of file diff --git a/Web/Startup.cs b/Web/Startup.cs index bc64ab8..d6e1a53 100644 --- a/Web/Startup.cs +++ b/Web/Startup.cs @@ -25,6 +25,9 @@ namespace VNCServerList.Web // JSON 포맷터 설정 config.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore; + // 파일 업로드 설정 + config.Formatters.Remove(config.Formatters.XmlFormatter); + app.UseWebApi(config); // 정적 파일 서빙 설정 diff --git a/Web/wwwroot/index.html b/Web/wwwroot/index.html index 57627ad..8ea6655 100644 --- a/Web/wwwroot/index.html +++ b/Web/wwwroot/index.html @@ -63,6 +63,29 @@