-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
249 lines (210 loc) · 6.88 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package main
import (
// "errors"
"context"
"database/sql"
"fmt"
"log"
"math/rand"
"net/http"
"net/url"
"os"
"strings"
"time"
_ "github.com/denisenkom/go-mssqldb"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
_ "github.com/joho/godotenv/autoload"
)
//資料庫結構
type Data struct {
originalUrl string
shortUrl_key string
create_date string
expire_date string
call_time int
}
//讀取環境變數
var (
AZURE_SQL_SERVER = os.Getenv("AZURE_SQL_SERVER")
AZURE_SQL_PORT = os.Getenv("AZURE_SQL_PORT")
AZURE_USERNAME = os.Getenv("AZURE_USERNAME")
AZURE_PASSWORD = os.Getenv("AZURE_PASSWORD")
AZURE_SQL_DB = os.Getenv("AZURE_SQL_DB")
)
func main() {
server := SetupServer()
server.Run()
}
func SetupServer() *gin.Engine {
server := gin.Default()
server.GET("/", HelloWorld)
server.POST("/create", CreateShortURL)
server.GET("/load/:key", LoadShortURL)
return server
}
func HelloWorld(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Hello World!"})
return
}
//建立 ShortUrl 資料的 Router
func CreateShortURL(c *gin.Context) {
// 與 Azure Database Server 連線
connString := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=%s;", AZURE_SQL_SERVER, AZURE_USERNAME, AZURE_PASSWORD, AZURE_SQL_PORT, AZURE_SQL_DB)
var db *sql.DB
var err error
// Create connection pool
db, err = sql.Open("sqlserver", connString)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
log.Fatal("Error creating connection pool: ", err.Error())
return
}
ctx := context.Background()
err = db.PingContext(ctx)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
log.Fatal(err.Error())
return
}
fmt.Println("Connected!")
defer db.Close()
//取得前端傳來的資訊
type Query_Json struct {
OriginalUrl string `json:"originalUrl"`
}
var query Query_Json
if err := c.ShouldBindJSON(&query); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
//如果傳入資訊缺少originalUrl,回傳錯誤訊息
if query.OriginalUrl == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "originalUrl can't be empty"})
return
}
//確認是否為可用的URL
_, err = url.ParseRequestURI(query.OriginalUrl)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "This originalUrl is invalid."})
return
}
//檢查此網址是否已經建立,若已建立則回傳該Key,並重置過期時間
var exist_data Data
row := db.QueryRow("SELECT * FROM DemoTable WHERE original_url = @url", sql.Named("url", query.OriginalUrl))
err = row.Scan(&exist_data.originalUrl, &exist_data.shortUrl_key, &exist_data.create_date, &exist_data.expire_date, &exist_data.call_time)
//如果此網址已在資料庫中
if err != sql.ErrNoRows {
todayStr := time.Now().Format("2006-01-02")
expiredateStr := time.Now().AddDate(3, 0, 0).Format("2006-01-02") //增加三年
//更新設定日期及期限
_, err := db.Exec("UPDATE DemoTable SET create_date = @createDate, expire_date = @expireDate WHERE shortUrl_key = @key",
sql.Named("createDate", todayStr), sql.Named("expireDate", expiredateStr), sql.Named("key", exist_data.shortUrl_key))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, gin.H{
"message": "short url created successfully",
"shortURL": c.Request.Host + "/load/" + exist_data.shortUrl_key,
"expire_date": expiredateStr,
})
return
}
//如果是尚未登陸的網址
} else {
var newKey string
//在GO裡面沒有While迴圈概念,只能用for執行
for {
//產生新的Key
newKey = CreateBase62Key(6)
//檢查是否使用過
err := db.QueryRow("SELECT * FROM DemoTable WHERE shortUrl_key = @key", sql.Named("key", newKey)).Scan()
//如果找不到Row代表沒用過,跳脫For迴圈
if err == sql.ErrNoRows {
break
}
}
todayStr := time.Now().Format("2006-01-02")
expiredateStr := time.Now().AddDate(3, 0, 0).Format("2006-01-02") //增加三年
//插入新資料
_, err := db.Exec(
"INSERT INTO DemoTable (original_url, shortUrl_key, create_date, expire_date, call_time) VALUES (@url, @key, @createDate, @expireDate, @callTime)",
sql.Named("url", query.OriginalUrl),
sql.Named("key", newKey),
sql.Named("createDate", todayStr),
sql.Named("expireDate", expiredateStr),
sql.Named("callTime", 0),
)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} else {
c.JSON(http.StatusOK, gin.H{
"message": "short url created successfully",
"shortURL": c.Request.Host + "/load/" + newKey,
"expire_date": expiredateStr,
})
return
}
}
}
//呼叫短連結
func LoadShortURL(c *gin.Context) {
// 與 Azure Database Server 連線
connString := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=%s;", AZURE_SQL_SERVER, AZURE_USERNAME, AZURE_PASSWORD, AZURE_SQL_PORT, AZURE_SQL_DB)
var db *sql.DB
var err error
db, err = sql.Open("sqlserver", connString)
if err != nil {
fmt.Println(err.Error())
log.Fatal("Error creating connection pool: ", err.Error())
}
ctx := context.Background()
err = db.PingContext(ctx)
if err != nil {
fmt.Println(err.Error())
log.Fatal(err.Error())
}
fmt.Printf("Connected!")
defer db.Close()
//取得URL中的Key
key := c.Param("key")
var exist_data Data
fmt.Println(exist_data)
row := db.QueryRow("SELECT * FROM DemoTable WHERE shortUrl_key =@key", sql.Named("key", key))
err = row.Scan(&exist_data.originalUrl, &exist_data.shortUrl_key, &exist_data.create_date, &exist_data.expire_date, &exist_data.call_time)
//如果找不到Row
if err == sql.ErrNoRows {
c.JSON(http.StatusBadRequest, gin.H{"error": "undefined shortURL"})
return
//找到對應資料
} else {
//該ShortURL 呼叫次數加一
_, err := db.Exec("UPDATE DemoTable SET call_time = @callTime WHERE shortUrl_key = @key",
sql.Named("callTime", exist_data.call_time+1),
sql.Named("key", key))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
//重新導向至原連結
c.Redirect(http.StatusMovedPermanently, exist_data.originalUrl)
return
}
}
//以隨機方式產生Base62的Key
func CreateBase62Key(keyLen int) string {
//Base62字符
base62 := "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"
//儲存Key的容器
var keyBuilder strings.Builder
// keyBuilder.Grow(keyLen)
//迴圈方式產生keyLen個字元的Key
for i := 0; i < keyLen; i++ {
//隨機選擇一個字元並加入
base62_index := rand.Intn(len(base62))
keyBuilder.WriteByte(base62[base62_index])
}
return keyBuilder.String()
}