반응형
개요
주소를 별명으로 저장할 수 있는 웹사이트를 만들어봅시다
프로젝트 구조
scss 같은 경우 제가 편리해서 사용하는 것이므로 무시해도 됩니다.
포스팅에서는 같은 구조에서 scss 폴더는 없어도 됩니다.
프로젝트 생성
프로젝트를 만들어줍시다
go mod init shortUrl.com
저희는 두가지 모듈을 사용합니다
go get github.com/joho/godotenv
go get github.com/go-sql-driver/mysql
그러면 go.mod 파일이 생기고 아래와 같을 것 입니다.
버전이 다를 수 있으니 참고하시기 바랍니다.
module shortUrl.com
go 1.20
require (
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/joho/godotenv v1.5.1 // indirect
)
MySQL 설정
MySQL을 설치하셨다는 가정하에 진행합니다.
.env 파일을 만들어 MySQL 연결정보를 입력합니다
아래는 예시입니다.
DBUSER=root
DBPASS=secret
DBNAME=short_url
DBHOST=127.0.0.1
DBPORT=3306
데이터베이스와 테이블을 만들어줍시다
CREATE DATABASE short_url
CREATE TABLE url
(
id INT AUTO_INCREMENT NOT NULL,
alias_url VARCHAR(128) NOT NULL,
full_url VARCHAR(128) NOT NULL,
PRIMARY KEY (`id`)
);
Go Code
main.go 파일을 만듭시다
route.Setting에 경우 아직 함수가 없어 에러가 발생할 것 입니다.
package main
import (
"shortUrl.com/route"
)
func main() {
route.Setting()
}
route/route.go
route 파일을 추가합시다.
package route
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"shortUrl.com/db"
"shortUrl.com/model"
"shortUrl.com/utils"
)
const (
Port = 4000
PublicPath = "/public"
)
// Route 세팅을
func Setting() {
// 정적 파일 연결
staticHandler := http.FileServer(http.Dir("." + PublicPath))
http.Handle(PublicPath+"/", http.StripPrefix(PublicPath, staticHandler))
http.HandleFunc("/", indexHandler)
// url, url/1 같은 형식을 urlhandler로 처리
http.HandleFunc("/url", urlHandler)
http.HandleFunc("/url/", urlHandler)
log.Printf("Listening on localhost:%d", Port)
// 서버 실행
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", Port), nil))
}
// indexHandler : /public/index.html을 보여줌
func indexHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
http.ServeFile(w, r, "."+PublicPath)
return
default:
fmt.Fprintf(w, "Sorry, only GET methods are supported.")
}
}
// REST API 요청 핸들러
func urlHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
getUrlHandler(w)
case http.MethodPost:
postUrlHandler(w, r)
case http.MethodPatch:
patchUrlHandler(w, r)
case http.MethodDelete:
deleteUrlHandler(w, r)
default:
fmt.Fprintf(w, "Sorry, only GET, POST, PATCH, DELETE methods are supported.")
}
}
// getUrlHandler : DB에 저장된 url 정보를 json 형식으로 반환
func getUrlHandler(w http.ResponseWriter) {
// json 으로 변환
jsonUrls, err := json.Marshal(db.GetUrlList())
utils.HandleErr(err)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, err = w.Write(jsonUrls)
utils.HandleErr(err)
}
// postUrlHandler : DB에 url 정보를 저장
func postUrlHandler(w http.ResponseWriter, r *http.Request) {
utils.HandleErr(r.ParseForm())
postData := r.PostForm
url := model.Url{
AliasURL: postData.Get("aliasUrl"),
FullURL: postData.Get("fullUrl"),
}
db.InsertUrl(url)
w.WriteHeader(http.StatusOK)
}
// patchUrlHandler : DB에 저장된 url 정보를 업데이트
func patchUrlHandler(w http.ResponseWriter, r *http.Request) {
var url model.Url
body, err := io.ReadAll(r.Body)
utils.HandleErr(err)
// body가 json 형식으로 바꿀 수 있어서 json으로 바꾸고 이를 구조체에 디코딩 해서 저장
if err := json.Unmarshal(body, &url); err != nil {
http.Error(w, "Failed to decode JSON data", http.StatusBadRequest)
return
}
db.PatchUrl(url, utils.GetThirdIndexUrl(r))
w.WriteHeader(http.StatusOK)
}
// deleteUrlHandler : DB에 저장된 url 정보를 삭제
func deleteUrlHandler(w http.ResponseWriter, r *http.Request) {
db.DeleteUrl(utils.GetThirdIndexUrl(r))
w.WriteHeader(http.StatusOK)
}
utils/utils.go
// Package utils contains functions to be used across the application
package utils
import (
"fmt"
"log"
"net/http"
"strings"
)
var logFn = log.Panic
// HandleErr : 에러가 발생시 처리
func HandleErr(err error) {
if err != nil {
fmt.Println("err", err)
logFn(err)
}
}
// GetThirdIndexUrl : url의 세번째 인덱스 반환
func GetThirdIndexUrl(r *http.Request) string {
parts := strings.Split(r.URL.Path, "/")
if len(parts) < 3 {
return ""
} else {
return parts[2]
}
}
model/url.go
package model
// URL 구조체
type Url struct {
Id int `json:"id"`
AliasURL string `json:"aliasUrl"`
FullURL string `json:"fullUrl"`
}
db/connection.go
package db
import (
"database/sql"
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/joho/godotenv"
"os"
"shortUrl.com/utils"
)
// init : 패키지 로드시 DB 연결 테스트
func init() {
utils.HandleErr(getDBConnection().Ping())
fmt.Println("DB Connected!")
}
// getDBConnection : DB connectionOpener 반환
func getDBConnection() *sql.DB {
// sql.Open의 경우 query 실행시에만 DB에 연결됨
db, err := sql.Open("mysql", getDataSourceName())
utils.HandleErr(err)
return db
}
// getDataSourceName : DB 연결 정보 반환
func getDataSourceName() string {
utils.HandleErr(godotenv.Load(".env"))
cfg := mysql.Config{
User: os.Getenv("DBUSER"),
Passwd: os.Getenv("DBPASS"),
Net: "tcp",
Addr: os.Getenv("DBHOST") + ":" + os.Getenv("DBPORT"),
DBName: os.Getenv("DBNAME"),
}
return cfg.FormatDSN()
}
db/function.go
package db
import (
"database/sql"
"log"
"shortUrl.com/model"
"shortUrl.com/utils"
)
// InsertUrl : url 정보를 DB에 저장
func InsertUrl(url model.Url) {
stmt, err := getDBConnection().Prepare("INSERT INTO url(alias_url, full_url) VALUES(?, ?)")
utils.HandleErr(err)
// defer : 함수가 종료되기 직전에 stmt 종료
defer func(stmt *sql.Stmt) {
err := stmt.Close()
utils.HandleErr(err)
}(stmt)
// Exec : 쿼리 실행
_, err = stmt.Exec(url.AliasURL, url.FullURL)
utils.HandleErr(err)
}
// GetUrlList : DB에 저장된 url 정보를 []model.url로 반환
func GetUrlList() []model.Url {
var urls []model.Url
rows, err := getDBConnection().Query("SELECT * FROM url")
utils.HandleErr(err)
defer func(rows *sql.Rows) {
err := rows.Close()
utils.HandleErr(err)
}(rows)
// row마다 돌며 url 정보를 urls에 저장
for rows.Next() {
var url model.Url
err := rows.Scan(&url.Id, &url.AliasURL, &url.FullURL)
if err != nil {
log.Fatal(err)
}
urls = append(urls, url)
}
return urls
}
// PatchUrl : DB에 저장된 url 정보를 업데이트
func PatchUrl(url model.Url, id string) {
stmt, err := getDBConnection().Prepare("UPDATE url SET alias_url=?, full_url=? WHERE id=?")
utils.HandleErr(err)
defer func(stmt *sql.Stmt) {
err := stmt.Close()
utils.HandleErr(err)
}(stmt)
_, err = stmt.Exec(url.AliasURL, url.FullURL, id)
utils.HandleErr(err)
}
// DeleteUrl : DB에 저장된 url 정보를 삭제
func DeleteUrl(id string) {
stmt, err := getDBConnection().Prepare("DELETE FROM url WHERE id=?")
utils.HandleErr(err)
defer func(stmt *sql.Stmt) {
err := stmt.Close()
utils.HandleErr(err)
}(stmt)
_, err = stmt.Exec(id)
utils.HandleErr(err)
}
Public Code
퍼블리싱 파일은 되도록이면 복사하도록 합시다.
public/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" type="text/css" href="/public/css/style.css">
<script src="https://code.jquery.com/jquery-3.7.0.min.js"
integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>
<title>Short URL</title>
</head>
<body>
<div class="main-container">
<!-- URL 단축하기 FORM-->
<h2>Short URL</h2>
<form class="post-container" action="/url" method="POST">
<div class="input-container">
<label for="aliasUrl">Alias URL : </label>
<input type="text" id="aliasUrl" name="alias-url" required>
</div>
<div class="input-container">
<label for="fullUrl">Full URL : </label>
<input type="url" id="fullUrl" name="full-url" required>
</div>
<input class="url-submit" type="submit" value="Save" class="submit">
</form>
<hr/>
<!-- 단축 URL 리스트 -->
<ul id="urlUl">
</ul>
</div>
<script src="/public/script/index.js"></script>
</body>
</html>
public/script/index.js
$("document").ready(function () {
getUrl()
// 단축하기 Form에 submit 이벤트가 발생하면 postUrl 함수를 실행
$(".post-container").submit(function (event) {
event.preventDefault();
postUrl();
});
// on을 통해 동적으로 추가될 li에 이벤트 처리를 할 수 있음
// 단축 URL 수정 이벤트
$("#urlUl").on("click", ".editButton", function (e) {
let listContainer = $(this).parent()
let editContainer = $(this).parent().parent().find(".edit-container")
listContainer.addClass("invisibility")
editContainer.removeClass("invisibility")
e.preventDefault()
})
// 단축 URL 수정 완료 이벤트
$("#urlUl").on("click", ".saveButton", function (e) {
editUrl($(this).data("id"), $(this).parent().parent().parent())
})
// 단축 URL 삭제 이벤트
$("#urlUl").on("click", ".deleteButton", function (e) {
e.preventDefault()
deleteUrl($(this).data("id"))
})
})
// 단축 URL 목록 조회
// 만약 http://localhost:4000가 아니면 수정 필요
function getUrl() {
$("#urlUl").empty()
$.ajax({
type: "GET",
url: "http://localhost:4000/url",
success: function (response) {
if (response === null) {
$("#urlUl").append("<li>등록된 URL이 없습니다.</li>")
} else {
response.forEach(data => {
$("#urlUl").append(`
<li>
<div class="edit-container invisibility">
<form class="edit-form">
<label for="aliasUrl">Alias Url</label>
<input type="text" class="aliasUrl" value="${data.aliasUrl}" required>
<label for="fullUrl">Full Url</label>
<input type="url" class="fullUrl" value="${data.fullUrl}" required>
<button type="button" class="saveButton" data-id='${data.id}'>Save</button>
</form>
</div>
<div class="url-list-container">
<a href='${data.fullUrl}' target="_blank" >${data.aliasUrl}</a>
<button class="editButton" data-id='${data.id}'>Edit</button>
<button class="deleteButton" data-id='${data.id}'>Delete</button>
</div>
</li>`)
})
}
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR)
console.log("API 요청 실패:", textStatus, errorThrown);
}
});
}
// 단축 URL 등록
function postUrl() {
// 입력 데이터 가져오기
let postData = {
aliasUrl: $("#aliasUrl").val(),
fullUrl: $("#fullUrl").val()
}
// POST 요청 보내기
$.ajax({
type: "POST",
url: "http://localhost:4000/url",
data: postData,
success: function (response) {
$("#aliasUrl").val('')
$("#fullUrl").val('')
getUrl()
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR)
console.log("API 요청 실패:", textStatus, errorThrown);
}
});
}
// 단축 URL 수정
function editUrl(id, listElement) {
let editContainer = listElement.find(".edit-container")
let data = JSON.stringify({
aliasUrl: editContainer.find(".aliasUrl").val(),
fullUrl: editContainer.find(".fullUrl").val()
})
$.ajax({
type: "PATCH",
url: "http://localhost:4000/url/" + id,
contentType: "application/json",
data: data,
success: function () {
getUrl()
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR)
console.log("API 요청 실패:", textStatus, errorThrown);
}
});
}
// 단축 URL 삭제
function deleteUrl(id) {
$.ajax({
type: "DELETE",
url: "http://localhost:4000/url/" + id,
success: function () {
getUrl()
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR)
console.log("API 요청 실패:", textStatus, errorThrown);
}
});
}
public/css/style.css
.main-container {
max-width: 600px;
margin: 0 auto;
}
.main-container .input-container {
text-align: center;
padding: 5px;
}
.main-container .input-container label {
display: inline-block;
width: 120px;
text-align: left;
}
.main-container .input-container input {
width: 400px;
padding: 5px;
border-radius: 5px;
border: 1px solid #3e3e3e;
}
.main-container input.submit {
margin-top: 10px;
padding: 5px 15px;
}
.main-container hr {
margin-top: 30px;
}
.main-container .invisibility {
display: none;
}
다음이 원본 위치입니다.
만약에 막히실 경우 참고 부탁드립니다.
https://github.com/DSeung001/learn-go/tree/master/shortUrl
반응형
'Go Lang > Study' 카테고리의 다른 글
[GoLang] Markdown을 HTML로 변환하기 (0) | 2023.11.05 |
---|---|
[GoLang] Interface와 덕 타이핑 (0) | 2023.11.05 |
[GoLang] 구조체 선언 시 메모리 최적화 하기 (1) | 2023.10.29 |
[GoLang] 실수 오차 없애기 (1) | 2023.10.28 |
[GoLang] Go언어란? (1) | 2023.10.24 |