admin_controller.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. // Copyright (c) [2022] [巴拉迪维 BaratSemet]
  2. // [ohUrlShortener] is licensed under Mulan PSL v2.
  3. // You can use this software according to the terms and conditions of the Mulan PSL v2.
  4. // You may obtain a copy of Mulan PSL v2 at:
  5. // http://license.coscl.org.cn/MulanPSL2
  6. // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
  7. // See the Mulan PSL v2 for more details.
  8. package controller
  9. import (
  10. "fmt"
  11. "net/http"
  12. "strconv"
  13. "strings"
  14. "time"
  15. "ohurlshortener/core"
  16. "ohurlshortener/service"
  17. "ohurlshortener/utils"
  18. "ohurlshortener/utils/export"
  19. "github.com/dchest/captcha"
  20. "github.com/gin-gonic/gin"
  21. )
  22. const (
  23. DefaultPageNum = 1
  24. DefaultPageSize = 20
  25. )
  26. // LoginPage 登录页面
  27. func LoginPage(c *gin.Context) {
  28. c.HTML(http.StatusOK, "login.html", gin.H{
  29. "title": "登录 - ohUrlShortener",
  30. })
  31. }
  32. // Users Page
  33. func UsersPage(c *gin.Context) {
  34. strPage := c.DefaultQuery("page", strconv.Itoa(DefaultPageNum))
  35. strSize := c.DefaultQuery("size", strconv.Itoa(DefaultPageSize))
  36. page, err := strconv.Atoi(strPage)
  37. if err != nil {
  38. page = DefaultPageNum
  39. }
  40. size, err := strconv.Atoi(strSize)
  41. if err != nil {
  42. size = DefaultPageSize
  43. }
  44. found, err := service.GetPagedUsers(page, size)
  45. c.HTML(http.StatusOK, "users.html", gin.H{
  46. "title": "用户管理 - ohUrlShortener",
  47. "current_url": c.Request.URL.Path,
  48. "users": found,
  49. "error": err,
  50. "page": page,
  51. "size": size,
  52. "first_page": page == 1,
  53. "last_page": len(found) < size,
  54. })
  55. }
  56. // DoLogin 登录
  57. func DoLogin(c *gin.Context) {
  58. account := c.PostForm("account")
  59. password := c.PostForm("password")
  60. captchaText := c.PostForm("captcha-text")
  61. captchaId := c.PostForm("captcha-id")
  62. if utils.EmptyString(account) || utils.EmptyString(password) || len(account) < 5 || len(password) < 8 {
  63. c.HTML(http.StatusOK, "login.html", gin.H{
  64. "title": "错误 - ohUrlShortener",
  65. "error": "用户名或密码格式错误!",
  66. })
  67. return
  68. }
  69. if utils.EmptyString(captchaText) || utils.EmptyString(captchaId) || len(captchaText) < 6 {
  70. c.HTML(http.StatusOK, "login.html", gin.H{
  71. "title": "错误 - ohUrlShortener",
  72. "error": "验证码格式错误!",
  73. })
  74. return
  75. }
  76. // 验证码有效性验证
  77. if !captcha.VerifyString(captchaId, captchaText) {
  78. c.HTML(http.StatusOK, "login.html", gin.H{
  79. "title": "错误 - ohUrlShortener",
  80. "error": "验证码错误,请刷新页面再重新尝试!",
  81. })
  82. return
  83. }
  84. // 用户名密码有效性验证
  85. loginUser, err := service.Login(account, password)
  86. if err != nil || loginUser.IsEmpty() {
  87. c.HTML(http.StatusOK, "login.html", gin.H{
  88. "title": "错误 - ohUrlShortener",
  89. "error": err.Error(),
  90. })
  91. return
  92. }
  93. // Write Cookie to browser
  94. cValue, err := AdminCookieValue(loginUser)
  95. if err != nil {
  96. c.HTML(http.StatusOK, "login.html", gin.H{
  97. "title": "错误 - ohUrlShortener",
  98. "error": "内部错误,请联系管理员",
  99. })
  100. return
  101. }
  102. c.SetCookie("ohUrlShortenerAdmin", loginUser.Account, 3600, "/", "", false, true)
  103. c.SetCookie("ohUrlShortenerCookie", cValue, 3600, "/", "", false, true)
  104. c.Redirect(http.StatusFound, "/admin/dashboard")
  105. }
  106. // DoLogout 登出
  107. func DoLogout(c *gin.Context) {
  108. c.SetCookie("ohUrlShortenerAdmin", "", -1, "/", "", false, true)
  109. c.SetCookie("ohUrlShortenerCookie", "", -1, "/", "", false, true)
  110. c.Redirect(http.StatusFound, "/login")
  111. }
  112. // ServeCaptchaImage 生成验证码
  113. func ServeCaptchaImage(c *gin.Context) {
  114. captcha.Server(200, 45).ServeHTTP(c.Writer, c.Request)
  115. }
  116. // RequestCaptchaImage 请求验证码
  117. func RequestCaptchaImage(c *gin.Context) {
  118. imageId := captcha.New()
  119. c.JSON(http.StatusOK, core.ResultJsonSuccessWithData(imageId))
  120. }
  121. // ChangeState 修改状态
  122. func ChangeState(c *gin.Context) {
  123. destUrl := c.PostForm("dest_url")
  124. enable := c.PostForm("enable")
  125. if utils.EmptyString(destUrl) {
  126. c.JSON(http.StatusBadRequest, core.ResultJsonError("目标链接不存在!"))
  127. return
  128. }
  129. destEnable, err := strconv.ParseBool(enable)
  130. if err != nil {
  131. c.JSON(http.StatusBadRequest, core.ResultJsonError("参数不合法!"))
  132. return
  133. }
  134. result, er := service.ChangeState(destUrl, destEnable)
  135. if er != nil {
  136. c.JSON(http.StatusInternalServerError, core.ResultJsonError(er.Error()))
  137. return
  138. }
  139. c.JSON(http.StatusOK, core.ResultJsonSuccessWithData(result))
  140. }
  141. // DeleteShortUrl 删除短链接
  142. func DeleteShortUrl(c *gin.Context) {
  143. url := c.PostForm("short_url")
  144. if utils.EmptyString(strings.TrimSpace(url)) {
  145. c.JSON(http.StatusBadRequest, core.ResultJsonError("目标链接不存在!"))
  146. return
  147. }
  148. err := service.DeleteUrlAndAccessLogs(url)
  149. if err != nil {
  150. c.JSON(http.StatusInternalServerError, core.ResultJsonError(err.Error()))
  151. return
  152. }
  153. c.JSON(http.StatusOK, core.ResultJsonSuccess())
  154. }
  155. // GenerateShortUrl 生成短链接
  156. func GenerateShortUrl(c *gin.Context) {
  157. destUrl := c.PostForm("dest_url")
  158. memo := c.PostForm("memo")
  159. strOpenType := c.PostForm("open_type")
  160. openType, err := strconv.Atoi(strOpenType)
  161. if err != nil {
  162. openType = int(core.OpenInAll)
  163. }
  164. if utils.EmptyString(destUrl) {
  165. c.JSON(http.StatusBadRequest, core.ResultJsonError("目标链接不能为空!"))
  166. return
  167. }
  168. result, err := service.GenerateShortUrl(destUrl, memo, openType)
  169. if err != nil {
  170. c.JSON(http.StatusInternalServerError, core.ResultJsonError(err.Error()))
  171. return
  172. }
  173. json := map[string]string{
  174. "short_url": fmt.Sprintf("%s%s", utils.AppConfig.UrlPrefix, result),
  175. }
  176. c.JSON(http.StatusOK, core.ResultJsonSuccessWithData(json))
  177. }
  178. // StatsPage 统计页面
  179. func StatsPage(c *gin.Context) {
  180. url := c.DefaultQuery("url", "")
  181. strPage := c.DefaultQuery("page", strconv.Itoa(DefaultPageNum))
  182. strSize := c.DefaultQuery("size", strconv.Itoa(DefaultPageSize))
  183. page, err := strconv.Atoi(strPage)
  184. if err != nil {
  185. page = DefaultPageNum
  186. }
  187. size, err := strconv.Atoi(strSize)
  188. if err != nil {
  189. size = DefaultPageSize
  190. }
  191. urls, err := service.GetPagedUrlIpCountStats(strings.TrimSpace(url), page, size)
  192. c.HTML(http.StatusOK, "stats.html", gin.H{
  193. "title": "数据统计 - ohUrlShortener",
  194. "current_url": c.Request.URL.Path,
  195. "error": err,
  196. "shortUrls": urls,
  197. "page": page,
  198. "size": size,
  199. "prefix": utils.AppConfig.UrlPrefix,
  200. "first_page": page == 1,
  201. "last_page": len(urls) < size,
  202. "url": strings.TrimSpace(url),
  203. })
  204. }
  205. // SearchStatsPage 查询统计页面
  206. func SearchStatsPage(c *gin.Context) {
  207. url := c.DefaultQuery("url", "")
  208. strPage := c.DefaultQuery("page", strconv.Itoa(DefaultPageNum))
  209. strSize := c.DefaultQuery("size", strconv.Itoa(DefaultPageSize))
  210. page, err := strconv.Atoi(strPage)
  211. if err != nil {
  212. page = DefaultPageNum
  213. }
  214. size, err := strconv.Atoi(strSize)
  215. if err != nil {
  216. size = DefaultPageSize
  217. }
  218. urls, err := service.GetPagedUrlIpCountStats(strings.TrimSpace(url), page, size)
  219. c.HTML(http.StatusOK, "search_stats.html", gin.H{
  220. "title": "查询统计 - ohUrlShortener",
  221. "current_url": c.Request.URL.Path,
  222. "error": err,
  223. "shortUrls": urls,
  224. "page": page,
  225. "size": size,
  226. "prefix": utils.AppConfig.UrlPrefix,
  227. "first_page": page == 1,
  228. "last_page": len(urls) < size,
  229. "url": strings.TrimSpace(url),
  230. })
  231. }
  232. // UrlsPage 短链接列表页面
  233. func UrlsPage(c *gin.Context) {
  234. url := c.DefaultQuery("url", "")
  235. strPage := c.DefaultQuery("page", strconv.Itoa(DefaultPageNum))
  236. strSize := c.DefaultQuery("size", strconv.Itoa(DefaultPageSize))
  237. page, err := strconv.Atoi(strPage)
  238. if err != nil {
  239. page = DefaultPageNum
  240. }
  241. size, err := strconv.Atoi(strSize)
  242. if err != nil {
  243. size = DefaultPageSize
  244. }
  245. urls, err := service.GetPagesShortUrls(strings.TrimSpace(url), page, size)
  246. c.HTML(http.StatusOK, "urls.html", gin.H{
  247. "title": "短链接列表 - ohUrlShortener",
  248. "current_url": c.Request.URL.Path,
  249. "error": err,
  250. "shortUrls": urls,
  251. "page": page,
  252. "size": size,
  253. "prefix": utils.AppConfig.UrlPrefix,
  254. "first_page": page == 1,
  255. "last_page": len(urls) < size,
  256. "url": strings.TrimSpace(url),
  257. })
  258. }
  259. // AccessLogsPage 访问日志页面
  260. func AccessLogsPage(c *gin.Context) {
  261. url := c.DefaultQuery("url", "")
  262. strPage := c.DefaultQuery("page", strconv.Itoa(DefaultPageNum))
  263. strSize := c.DefaultQuery("size", strconv.Itoa(DefaultPageSize))
  264. start := c.DefaultQuery("start", "")
  265. end := c.DefaultQuery("end", "")
  266. page, err := strconv.Atoi(strPage)
  267. if err != nil {
  268. page = DefaultPageNum
  269. }
  270. size, err := strconv.Atoi(strSize)
  271. if err != nil {
  272. size = DefaultPageSize
  273. }
  274. totalCount, distinctIpCount, err := service.GetAccessLogsCount(strings.TrimSpace(url), start, end)
  275. logs, err := service.GetPagedAccessLogs(strings.TrimSpace(url), start, end, page, size)
  276. c.HTML(http.StatusOK, "access_logs.html", gin.H{
  277. "title": "访问日志查询 - ohUrlShortener",
  278. "current_url": c.Request.URL.Path,
  279. "error": err,
  280. "logs": logs,
  281. "page": page,
  282. "size": size,
  283. "prefix": utils.AppConfig.UrlPrefix,
  284. "first_page": page == 1,
  285. "last_page": len(logs) < size,
  286. "url": strings.TrimSpace(url),
  287. "total_count": totalCount,
  288. "unique_ip_count": distinctIpCount,
  289. "start_date": start,
  290. "end_date": end,
  291. })
  292. }
  293. // AccessLogsExport 导出访问日志
  294. func AccessLogsExport(c *gin.Context) {
  295. url := c.PostForm("url")
  296. logs, err := service.GetAllAccessLogs(strings.TrimSpace(url))
  297. if err != nil {
  298. c.HTML(http.StatusOK, "access_logs.html", gin.H{
  299. "title": "访问日志查询 - ohUrlShortener",
  300. "current_url": c.Request.URL.Path,
  301. "prefix": utils.AppConfig.UrlPrefix,
  302. "url": strings.TrimSpace(url),
  303. "error": err,
  304. })
  305. return
  306. }
  307. fileContent, err := export.AccessLogToExcel(logs)
  308. if err != nil {
  309. c.HTML(http.StatusOK, "access_logs.html", gin.H{
  310. "title": "访问日志查询 - ohUrlShortener",
  311. "current_url": c.Request.URL.Path,
  312. "prefix": utils.AppConfig.UrlPrefix,
  313. "url": strings.TrimSpace(url),
  314. "error": err,
  315. })
  316. return
  317. }
  318. attachmentName := "访问日志" + time.Now().Format("2006-01-02 150405") + ".xlsx"
  319. fileContentDisposition := "attachment;filename=\"" + attachmentName + "\""
  320. c.Header("Content-Disposition", fileContentDisposition)
  321. c.Data(http.StatusOK, "pplication/octet-stream", fileContent)
  322. }
  323. // DashboardPage 仪表盘页面
  324. func DashboardPage(c *gin.Context) {
  325. count, stats, err := service.GetSumOfUrlStats()
  326. if err != nil {
  327. c.HTML(http.StatusOK, "dashboard.html", gin.H{
  328. "title": "仪表盘 - ohUrlShortener",
  329. "current_url": c.Request.URL.Path,
  330. "error": err,
  331. })
  332. return
  333. }
  334. top25, er := service.GetTop25Url()
  335. if er != nil {
  336. c.HTML(http.StatusOK, "dashboard.html", gin.H{
  337. "title": "仪表盘 - ohUrlShortener",
  338. "current_url": c.Request.URL.Path,
  339. "error": er,
  340. })
  341. return
  342. }
  343. c.HTML(http.StatusOK, "dashboard.html", gin.H{
  344. "title": "仪表盘 - ohUrlShortener",
  345. "current_url": c.Request.URL.Path,
  346. "error": err,
  347. "total_count": count,
  348. "prefix": utils.AppConfig.UrlPrefix,
  349. "stats": stats,
  350. "top25": top25,
  351. })
  352. }