Просмотр исходного кода

管理功能静态页面完成

巴拉迪维 3 лет назад
Родитель
Сommit
e3475e8565

+ 2 - 2
air.toml

@@ -6,14 +6,14 @@ tmp_dir = "tmp"
   bin = "./tmp/ohurlshortener"
   cmd = "go build -o ./tmp/ohurlshortener ."
   delay = 1000
-  exclude_dir = ["assets", "tmp", "vendor", "testdata","docker"]
+  exclude_dir = ["tmp", "vendor", "testdata","docker"]
   exclude_file = []
   exclude_regex = ["_test.go"]
   exclude_unchanged = false
   follow_symlink = false
   full_bin = ""
   include_dir = []
-  include_ext = ["go", "tpl", "tmpl", "html"]
+  include_ext = ["go", "tpl", "tmpl", "html","css","js"]
   kill_delay = "0s"
   log = "build-errors.log"
   send_interrupt = false

+ 38 - 0
assets/admin.css

@@ -0,0 +1,38 @@
+
+#login-grid {
+  height: calc(100% - 125px); 
+  margin: 0; 
+}
+
+#login-grid>.column {
+  max-width: 500px;
+}
+
+#admin-left-menu {
+  max-width: 200px;    
+}
+
+#admin-top-menu {
+  display: none;
+  max-height: 60px;
+}
+
+#admin-right-content {  
+  padding-left: 200px;
+}
+
+@media only screen and (max-width: 550px) {
+
+  #admin-left-menu {
+    display: none;
+  }
+
+  #admin-top-menu {
+    display: block;
+  }
+
+  #admin-right-content {
+    padding-top: 60px;
+    padding-left: 0px;
+  }  
+}

+ 41 - 0
assets/admin.js

@@ -0,0 +1,41 @@
+$(document).ready(function() {
+  $('#login-form')
+    .form({
+      fields: {
+        account: {
+          identifier  : 'account',
+          rules: [
+            {
+              type   : 'empty',
+              prompt : 'Please enter account'
+            },
+            {
+              type   : 'length[5]',
+              prompt : 'Your account must be at least 5 characters'
+            }
+          ]
+        },
+        password: {
+          identifier  : 'password',
+          rules: [
+            {
+              type   : 'empty',
+              prompt : 'Please enter your password'
+            },
+            {
+              type   : 'length[8]',
+              prompt : 'Your password must be at least 8 characters'
+            }
+          ]
+        }
+      }
+    });
+  
+
+    $('#sidebar-menu').sidebar('attach events', '#sidebar-menu-toggler');
+
+    $('#new-shorturl-btn').click(function(){
+      $('#new-shorturl-modal').modal('show');
+    });
+
+});

+ 3 - 0
config.ini

@@ -9,6 +9,7 @@ host = 127.0.0.1:56379
 database= 0
 username=
 password=
+pool_size = 50
 
 [postgres]
 host = localhost
@@ -16,3 +17,5 @@ port = 55432
 user = postgres
 password = 0DePm!oG_12Cz^kd_m
 database = oh_url_shortener
+max_open_conn = 20
+max_idle_conn = 5

+ 35 - 0
controller/admin.go

@@ -9,6 +9,41 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
+func LoginPage(c *gin.Context) {
+	c.HTML(http.StatusOK, "login.html", gin.H{
+		"title": "登录 - ohUrlShortener",
+	})
+}
+
+func DoLogin(c *gin.Context) {
+	//TODO: Login logic
+}
+
+func DoLogout(c *gin.Context) {
+	//TODO: Login logic
+}
+
+func DashbaordPage(c *gin.Context) {
+	c.HTML(http.StatusOK, "dashboard.html", gin.H{
+		"title":       "仪表盘 - ohUrlShortener",
+		"current_url": c.Request.URL.Path,
+	})
+}
+
+func UrlsPage(c *gin.Context) {
+	c.HTML(http.StatusOK, "urls.html", gin.H{
+		"title":       "短链接列表 - ohUrlShortener",
+		"current_url": c.Request.URL.Path,
+	})
+}
+
+func AccessLogsPage(c *gin.Context) {
+	c.HTML(http.StatusOK, "access_logs.html", gin.H{
+		"title":       "访问日志查询 - ohUrlShortener",
+		"current_url": c.Request.URL.Path,
+	})
+}
+
 func ShortUrlsStats(c *gin.Context) {
 	url := c.Param("url")
 	if utils.EemptyString(url) {

+ 10 - 0
controller/handlers.go

@@ -0,0 +1,10 @@
+package controller
+
+import "github.com/gin-gonic/gin"
+
+func AdminAuthHandler() gin.HandlerFunc {
+	return func(c *gin.Context) {
+		c.Set("current_url", c.Request.URL.Path)
+		c.Next()
+	}
+}

+ 1 - 1
controller/short_url.go

@@ -41,7 +41,7 @@ func ShortUrlDetail(c *gin.Context) {
 		return
 	}
 
-	go service.NewAccessLog(url, c.ClientIP(), c.Request.UserAgent())
+	go service.NewAccessLog(url, c.ClientIP(), c.Request.UserAgent(), c.Request.Referer()) //TODO: add more params to access logs
 
 	c.Redirect(http.StatusFound, destUrl)
 }

+ 2 - 2
db/base.go

@@ -23,8 +23,8 @@ func InitDatabaseService() (*DatabaseService, error) {
 	if err != nil {
 		return dbService, err
 	}
-	conn.SetMaxOpenConns(10)
-	conn.SetMaxIdleConns(1)
+	conn.SetMaxOpenConns(utils.DatabaseConifg.MaxOpenConns)
+	conn.SetMaxIdleConns(utils.DatabaseConifg.MaxIdleConn)
 	conn.SetConnMaxLifetime(0) //always REUSE
 	dbService.Connection = conn
 	return dbService, nil

+ 32 - 19
main.go

@@ -19,18 +19,23 @@ import (
 	"golang.org/x/sync/errgroup"
 )
 
+const (
+	CONFIG_FILE               = "config.ini"
+	ACCESS_LOG_CLEAN_INTERVAL = 3 * time.Minute
+	WEB_READ_TIMEOUT          = 10 * time.Second
+	WEB_WRITE_TIMEOUT         = 10 * time.Second
+)
+
 var (
 	//go:embed assets/* templates/*
 	FS embed.FS
 
-	config_file = "config.ini"
-
 	group errgroup.Group
 )
 
 func init() {
 	//Things MUST BE DONE before app starts
-	_, err := utils.InitConfig(config_file)
+	_, err := utils.InitConfig(CONFIG_FILE)
 	utils.ExitOnError("Config initialization failed.", err)
 
 	_, err = redis.InitRedisService()
@@ -54,32 +59,32 @@ func main() {
 	serverWeb := &http.Server{
 		Addr:         fmt.Sprintf("127.0.0.1:%d", utils.AppConfig.Port),
 		Handler:      router01,
-		ReadTimeout:  time.Second * 10,
-		WriteTimeout: time.Second * 10,
+		ReadTimeout:  WEB_READ_TIMEOUT,
+		WriteTimeout: WEB_WRITE_TIMEOUT,
 	}
 
 	serverAdmin := &http.Server{
 		Addr:         fmt.Sprintf("127.0.0.1:%d", utils.AppConfig.AdminPort),
 		Handler:      router02,
-		ReadTimeout:  time.Second * 10,
-		WriteTimeout: time.Second * 10,
+		ReadTimeout:  WEB_READ_TIMEOUT,
+		WriteTimeout: WEB_WRITE_TIMEOUT,
 	}
 
 	group.Go(func() error {
+		log.Printf("[ohUrlShortener] portal starts at http://127.0.0.1:%d", utils.AppConfig.Port)
 		return serverWeb.ListenAndServe()
 	})
 
 	group.Go(func() error {
+		log.Printf("[ohUrlShortener] admin starts at http://127.0.0.1:%d", utils.AppConfig.AdminPort)
 		return serverAdmin.ListenAndServe()
 	})
 
 	group.Go(func() error {
+		log.Println("[ohUrlShortener] ticker starts to serve")
 		return startTicker()
 	})
 
-	log.Printf("[ohUrlShortener] portal starts http://127.0.0.1:%d", utils.AppConfig.Port)
-	log.Printf("[ohUrlShortener] admin starts http://127.0.0.1:%d", utils.AppConfig.AdminPort)
-
 	err = group.Wait()
 	utils.ExitOnError("Group failed,", err)
 }
@@ -93,7 +98,7 @@ func initializeRoute01() (http.Handler, error) {
 	}
 
 	router := gin.New()
-	router.Use(gin.Recovery(), utils.OhUrlShortenerLogFormat("Portal"))
+	router.Use(gin.Recovery(), utils.WebLogFormat("Portal"))
 
 	sub, err := fs.Sub(FS, "assets")
 	if err != nil {
@@ -128,7 +133,7 @@ func initializeRoute02() (http.Handler, error) {
 	}
 
 	router := gin.New()
-	router.Use(gin.Recovery(), utils.OhUrlShortenerLogFormat("Admin"))
+	router.Use(gin.Recovery(), utils.WebLogFormat("Admin"))
 
 	sub, err := fs.Sub(FS, "assets")
 	if err != nil {
@@ -143,7 +148,20 @@ func initializeRoute02() (http.Handler, error) {
 
 	router.SetHTMLTemplate(tmpl)
 
-	router.GET("/login", controller.ShortUrlDetail)
+	router.GET("/", func(ctx *gin.Context) {
+		ctx.Redirect(http.StatusTemporaryRedirect, "/login")
+	})
+	router.GET("/login", controller.LoginPage)
+	router.POST("/login", controller.DoLogin)
+	router.POST("/logout", controller.DoLogout)
+
+	admin := router.Group("/admin", controller.AdminAuthHandler())
+	admin.GET("/", func(ctx *gin.Context) {
+		ctx.Redirect(http.StatusTemporaryRedirect, "/admin/dashboard")
+	})
+	admin.GET("/dashboard", controller.DashbaordPage)
+	admin.GET("/urls", controller.UrlsPage)
+	admin.GET("/access_logs", controller.AccessLogsPage)
 	router.NoRoute(func(ctx *gin.Context) {
 		ctx.HTML(http.StatusNotFound, "error.html", gin.H{
 			"title":   "404 - ohUrlShortener",
@@ -156,16 +174,11 @@ func initializeRoute02() (http.Handler, error) {
 } //end of router01
 
 func startTicker() error {
-	//sleep for 60s to make sure main process is running
-	time.Sleep(60 * time.Second)
-
-	//Clear redis cache every 3 minutes
-	ticker := time.NewTicker(3 * time.Minute)
+	ticker := time.NewTicker(ACCESS_LOG_CLEAN_INTERVAL)
 	for range ticker.C {
 		log.Println("[StoreAccessLog] Start.")
 		if err := service.StoreAccessLogs(); err != nil {
 			log.Printf("Error while trying to store access_log %s", err)
-			return err
 		}
 		log.Println("[StoreAccessLog] Finish.")
 	}

+ 1 - 1
redis/redis_service.go

@@ -23,7 +23,7 @@ func InitRedisService() (*RedisService, error) {
 		DB:       utils.RedisConfig.Database,
 		Username: utils.RedisConfig.User,
 		Password: utils.RedisConfig.Password,
-		PoolSize: 200,
+		PoolSize: utils.RedisConfig.PoolSize,
 	})
 	_, err := redisClient.Ping(ctx).Result()
 	if err != nil {

+ 1 - 1
service/logs.go

@@ -14,7 +14,7 @@ import (
 
 const access_logs_prefix = "OH_ACCESS_LOGS#"
 
-func NewAccessLog(url string, ip string, useragent string) error {
+func NewAccessLog(url string, ip string, useragent string, referer string) error {
 
 	l := core.AccessLog{
 		ShortUrl:   url,

+ 1 - 1
service/logs_test.go

@@ -18,7 +18,7 @@ func TestStoreAccessLog(t *testing.T) {
 func TestNewAccessLog(t *testing.T) {
 	init4Test(t)
 	for i := 0; i < 500; i++ {
-		if err := NewAccessLog("=====heh1e99999", "127.2.0.1", "asdfsdfas"); err != nil {
+		if err := NewAccessLog("=====heh1e99999", "127.2.0.1", "asdfsdfas", "fffff"); err != nil {
 			t.Error(err)
 		}
 		time.Sleep(10 * time.Millisecond)

+ 60 - 0
templates/admin/access_logs.html

@@ -0,0 +1,60 @@
+{{define "access_logs.html" -}}
+{{template "header.html" . -}}
+{{template "sidebar.html" . -}}
+<div class="pusher">
+{{template "admin-left-menu.html" . -}}
+{{template "admin-top-menu.html" . -}}
+<div id="admin-right-content" class="ui basic segment">
+  <div class="ui grid stackable padded">
+    <div class="column">
+      <table class="ui celled striped table">
+        <thead>
+          <tr>
+            <th colspan="4">
+              <div class="ui labeled action input">
+                <div class="ui label">
+                  https://i.barats.cn/
+                </div>
+                <input type="text" placeholder="短链接">                
+              </div>
+              <div class="ui input">
+                <input type="text" placeholder="访问起始时间">
+              </div>                            
+              <div class="ui input">
+                <input type="text" placeholder="访问截至时间">
+              </div>
+              <div class="ui input">
+                <input type="text" placeholder="访问IP">
+              </div>                                                        
+              <button class="ui teal right labeled icon button"><i class="search icon"></i>查询</button>                
+            </th>
+          </tr>
+          <tr>
+            <th>短链接</th>
+            <th class="center aligned">访问时间</th>
+            <th class="center aligned">访问IP</th>
+            <th>UserAgent</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td><a href="https://i.barats.cn/A1HeJzob" target="_blank">A1HeJzob</a></td>            
+            <td class="center aligned">2022-03-01 12:23:34</td>
+            <td class="center aligned">192.168.0.1</td>
+            <td>Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36</td>                                  
+          </tr>
+        </tbody>
+        <tfoot>
+          <tr>            
+            <th colspan="4" class="center aligned">
+              <a class="ui small labeled icon button"><i class="arrow alternate circle left outline icon"></i> 上一页</a>
+              <a class="ui small labeled icon button"><i class="arrow alternate circle right outline icon"></i> 下一页</a>              
+            </th>
+          </tr>
+        </tfoot>
+      </table>
+    </div>
+  </div><!--end fo grid-->
+</div> 
+</div><!--end of pusher-->
+{{end -}}

+ 148 - 0
templates/admin/dashboard.html

@@ -0,0 +1,148 @@
+{{define "dashboard.html" -}}
+{{template "header.html" . -}}
+{{template "sidebar.html" . -}}
+<div class="pusher">
+{{template "admin-left-menu.html" . -}}
+{{template "admin-top-menu.html" . -}}
+<div id="admin-right-content" class="ui basic segment">
+  <div class="ui center aligned grid stackable padded">
+    <div class="four wide computer eight wide tablet sixteen wide mobile column">
+      <div class="ui fluid card">
+        <div class="content">
+          <div class="ui right floated header red">
+            <i class="icon shopping cart"></i>
+          </div>
+          <div class="header">
+            <div class="ui red header">
+              3958
+            </div>
+          </div>
+          <div class="meta">
+            总链接数
+          </div>
+          <div class="description">
+            当前总短链接数
+          </div>
+        </div>
+        <div class="extra content">
+          <div class="ui two buttons">
+            <a href="/admin/urls" target="_self" class="ui red button">查看详情</a>
+          </div>
+        </div>
+      </div>
+    </div><!--end of column-->
+    <div class="four wide computer eight wide tablet sixteen wide mobile column">
+      <div class="ui fluid card">
+        <div class="content">
+          <div class="ui right floated header green">
+            <i class="icon clock"></i>
+          </div>
+          <div class="header">
+            <div class="ui header green">57.6%</div>
+          </div>
+          <div class="meta">
+            今日点击量
+          </div>
+          <div class="description">
+            所有链接今日产生的总点击量
+          </div>
+        </div>
+        <div class="extra content">
+          <div class="ui two buttons">
+            <a href="/admin/access_logs" target="_self" class="ui green button">查看详情</a>
+          </div>
+        </div>
+      </div>
+    </div><!--end of column-->
+    <div class="four wide computer eight wide tablet sixteen wide mobile column">
+      <div class="ui fluid card">
+        <div class="content">
+          <div class="ui right floated header teal">
+            <i class="icon briefcase"></i>
+          </div>
+          <div class="header">
+            <div class="ui teal header">3112</div>
+          </div>
+          <div class="meta">
+            今日独立IP数
+          </div>
+          <div class="description">
+            所有短链接今日点击的总IP数
+          </div>
+        </div>
+        <div class="extra content">
+          <div class="ui two buttons">
+            <div class="ui teal button">查看详情</div>
+          </div>
+        </div>
+      </div>
+    </div><!--end of column-->
+  </div><!--end of grid-->
+  <div class="ui grid stackable padded">
+    <div class="four wide computer eight wide tablet sixteen wide mobile center aligned column">
+      <div class="ui purple statistic">
+        <div class="value">
+          1000+
+        </div>
+        <div class="label">
+          最近七日点击量
+        </div>
+      </div>
+    </div>
+    <div class="four wide computer eight wide tablet sixteen wide mobile center aligned column">
+      <div class="ui purple statistic">
+        <div class="value">
+          1000+
+        </div>
+        <div class="label">
+          最近七日独立IP数
+        </div>
+      </div>
+    </div>
+    <div class="four wide computer eight wide tablet sixteen wide mobile center aligned column">
+      <div class="ui purple statistic">
+        <div class="value">
+          1000+
+        </div>
+        <div class="label">
+          总点击量
+        </div>
+      </div>
+    </div>
+    <div class="four wide computer eight wide tablet sixteen wide mobile center aligned column">
+      <div class="ui purple statistic">
+        <div class="value">
+          1000+
+        </div>
+        <div class="label">
+          总独立IP数
+        </div>
+      </div>
+    </div> 
+  </div><!--end of grid-->   
+  <div class="ui grid stackable padded">
+    <div class="column">
+      <table class="ui celled striped table">
+        <thead>
+          <tr>
+            <th colspan="2">今日点击量 Top10</th>
+            <th class="center aligned">创建时间</th>
+            <th class="center aligned">点击量</th>
+            <th class="center aligned">独立IP数</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td class="collapsing"><a href="https://i.barats.cn/zzzzzz" target="_blank">https://i.barats.cn/zzzzzz</a></td>            
+            <td><a href="https://gitee.com/barat/ohurlshortener" target="_blank">https://gitee.com/barat/ohurlshortener</a></td>
+            <td class="center aligned">2022-03-01 12:23:45</td>
+            <td class="center aligned">10</td>
+            <td class="center aligned">10</td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </div><!--end fo grid-->
+</div><!--end of segment-->
+</div><!--end of pusher-->
+{{end -}}

+ 3 - 2
templates/admin/error.html

@@ -1,5 +1,5 @@
 {{define "error.html" -}}
-{{template "header.html" .}}
+{{- template "header.html" .}}
 <div class="ui inverted borderless large menu">
   <div class="ui container">
     <a class="ui inverted header item" target="_self" href="https://gitee.com/barat/ohurlshortener">          
@@ -7,7 +7,7 @@
     </a>                 
   </div>
 </div>
-<div class="ui top aligned grid" style="height: calc(100% - 160px); margin: 0;">
+<div class="ui top aligned grid" style="height: calc(100% - 125px); margin: 0;">
   <div class="ui center aligned column">    
     <div class="ui statistic">
       <div class="value">{{.code}}</div>
@@ -16,4 +16,5 @@
       <p>{{ .message }}</p>
   </div>
 </div>
+{{- template "footer.html"}}
 {{end -}}

+ 12 - 0
templates/admin/footer.html

@@ -0,0 +1,12 @@
+{{define "footer.html" -}}
+<div class="ui inverted vertical bottom attached footer segment" style="max-height: 60px; width: 100%;">
+  <div class="ui center aligned container">
+    <p>Copyright <i class="copyright outline icon"></i>{{now | date "2006" }} 巴拉迪维 版权所有 
+      <a href="https://gitee.com/barat/ohurlshortener" target="_blank">Gitee</a>
+      <a href="https://github.com/barats/ohurlshortener" target="_blank">Github</a>
+    </p>
+  </div><!--end of container-->
+</div><!--end of footer segment-->
+</body>
+</html>
+{{end -}}

+ 3 - 1
templates/admin/header.html

@@ -9,10 +9,12 @@
   <meta name="keywords" content="ohUrlShortener,url shorten,短域名,开源,open source,golang" />
   <meta name="description" content="一个适合中小型社区网站使用的短域名服务系统,支持短域名成产、查询及302转向,并顺带简单的点击量统计" />  
   <title>{{.title}}</title>
-  <link rel="stylesheet" type="text/css" href="/assets/semantic.min.css">  
   <link rel="shortcut icon" type="image/x-icon" href="/assets/favicon.ico" title="ohUrlShortener">
+  <link rel="stylesheet" type="text/css" href="/assets/semantic.min.css">  
+  <link rel="stylesheet" type="text/css" href="/assets/admin.css">    
   <script src="/assets/jquery.min.js" type="text/javascript"></script>
   <script src="/assets/semantic.min.js" type="text/javascript"></script>
+  <script src="/assets/admin.js" type="text/javascript"></script>
 </head>
 <body>
 {{end -}}

+ 38 - 0
templates/admin/login.html

@@ -0,0 +1,38 @@
+{{define "login.html" -}}
+{{- template "header.html" .}}
+<div class="ui inverted borderless large menu">
+  <div class="ui container">
+    <a class="ui inverted header item" target="_self" href="https://gitee.com/barat/ohurlshortener">          
+        <div class="content">ohUrlShortener</div>           
+    </a>                 
+  </div>
+</div>
+<div id="login-grid" class="ui middle aligned center aligned grid">
+  <div class="column">
+    <h2 class="ui image header">      
+      <div class="content">
+        ohUrlShortener Admin Login
+      </div>
+    </h2>
+    <form id="login-form" class="ui large form" action="/login" method="post">
+      <div class="ui stacked segment">
+        <div class="field">
+          <div class="ui left icon input">
+            <i class="user icon"></i>
+            <input type="text" name="account" placeholder="Account">
+          </div>
+        </div>
+        <div class="field">
+          <div class="ui left icon input">
+            <i class="lock icon"></i>
+            <input type="password" name="password" placeholder="Password">
+          </div>
+        </div>
+        <div class="ui fluid large black submit button">Login</div>
+      </div>  
+      <div class="ui error message"></div>
+    </form>
+  </div>
+</div>
+{{- template "footer.html" .}}
+{{end -}}

+ 34 - 0
templates/admin/menu.html

@@ -0,0 +1,34 @@
+{{define "sidebar.html" -}}
+<div id="sidebar-menu" class="ui inverted vertical sidebar menu left">
+  <div class="item">
+    <div class="ui inverted header">业务管理</div>
+    <div class="menu">
+      <a class="{{if eq .current_url "/admin/dashboard"}}active {{end}}item" target="_self" href="/admin/dashboard">仪表盘</a>
+      <a class="{{if eq .current_url "/admin/urls"}}active {{end}}item" target="_self" href="/admin/urls">短链接列表</a>
+      <a class="{{if eq .current_url "/admin/access_logs"}}active {{end}}item" target="_self"  href="/admin/access_logs">访问日志查询</a> 
+    </div>    
+  </div>   
+  <a class="item" href="/admin/logout" target="_self">安全退出</a>
+</div><!--end of sidebar-menu-->
+{{end -}}
+
+{{define "admin-top-menu.html" -}}
+<div id="admin-top-menu" class="ui top fixed inverted borderless menu">  
+  <div id="sidebar-menu-toggler" class="header item"><a><i class="big sidebar icon"></i></a>ohUrlShortener</div>  
+</div><!--end of admin-top-menu -->
+{{end -}}
+
+{{define "admin-left-menu.html" -}}
+<div id="admin-left-menu" class="ui inverted vertical left fixed menu"> 
+  <div class="ui inverted header item">ohUrlShortener</div> 
+  <div class="item">
+    <div class="ui inverted header">业务管理</div>
+    <div class="menu">
+      <a class="{{if eq .current_url "/admin/dashboard"}}active {{end}}item" target="_self" href="/admin/dashboard">仪表盘</a>
+      <a class="{{if eq .current_url "/admin/urls"}}active {{end}}item" target="_self" href="/admin/urls">短链接列表</a>
+      <a class="{{if eq .current_url "/admin/access_logs"}}active {{end}}item" target="_self"  href="/admin/access_logs">访问日志查询</a> 
+    </div>    
+  </div>      
+  <a class="item" target="_self" href="/admin/dashboard">安全退出</a>
+</div><!--end of left-menu-->
+{{end -}}

+ 93 - 0
templates/admin/urls.html

@@ -0,0 +1,93 @@
+{{define "urls.html" -}}
+{{template "header.html" . -}}
+{{template "sidebar.html" . -}}
+<div class="pusher">
+{{template "admin-left-menu.html" . -}}
+{{template "admin-top-menu.html" . -}}
+<div id="admin-right-content" class="ui basic segment">  
+  <div class="ui grid stackable padded">
+    <div class="column">
+      <table class="ui celled striped table">
+        <thead>
+          <tr>
+            <th colspan="3">
+              <div class="ui labeled action input">
+                <div class="ui label">
+                  https://i.barats.cn/
+                </div>
+                <input type="text" placeholder="短链接">
+                <button class="ui teal right labeled icon button"><i class="search icon"></i>查询</button>                
+              </div>              
+            </th>
+            <th colspan="7"><button id="new-shorturl-btn" class="ui primary button">新建短链接</button></th>
+          </tr>
+          <tr>            
+            <th>短链接</th>
+            <th>备注</th>            
+            <th class="center aligned">生成时间</th>
+            <th class="center aligned">目标链接</th>
+            <th class="center aligned">今日点击量</th>
+            <th class="center aligned">今日独立IP数</th>
+            <th class="center aligned">昨日点击量</th>
+            <th class="center aligned">昨日独立IP数</th>
+            <th class="center aligned">总点击量</th>
+            <th class="center aligned">总独立IP数</th>            
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td><a href="https://i.barats.cn/A1HeJzob" target="_blank">A1HeJzob</a></td>
+            <td>OH官网330特效节目推广</td>
+            <td class="center aligned">2022-03-01 12:23:34</td>
+            <td><a href="https://gitee.com/barat/ohurlshortener" target="_blank">打开链接</a></td>            
+            <td class="center aligned">22</td>
+            <td class="center aligned">33</td>
+            <td class="center aligned">44</td>
+            <td class="center aligned">55</td>
+            <td class="center aligned">66</td>
+            <td class="center aligned">77</td>                        
+          </tr>
+        </tbody>
+        <tfoot>
+          <tr>            
+            <th colspan="10" class="center aligned">
+              <a class="ui small labeled icon button"><i class="arrow alternate circle left outline icon"></i> 上一页</a>
+              <a class="ui small labeled icon button"><i class="arrow alternate circle right outline icon"></i> 下一页</a>              
+            </th>
+          </tr>
+        </tfoot>
+      </table>
+    </div>
+  </div><!--end fo grid-->
+</div><!--end of segment-->
+</div><!--end of pusher-->
+<div id="new-shorturl-modal"  class="ui modal">
+  <i class="close icon"></i>
+  <div class="header">
+    新建短链接
+  </div>
+  <div class="content">   
+    <div class="description">      
+      <div class="ui form">
+        <div class="field">
+          <label>目标链接</label>
+          <textarea rows="1"></textarea>
+        </div>
+        <div class="field">
+          <label>备注</label>
+          <textarea rows="2"></textarea>
+        </div>
+      </div>      
+    </div>
+  </div>
+  <div class="actions">
+    <div class="ui black deny button">
+      放弃
+    </div>
+    <div class="ui primary right labeled icon button">
+      确认新建
+      <i class="checkmark icon"></i>
+    </div>
+  </div>
+</div>
+{{end -}}

+ 11 - 5
utils/config.go

@@ -22,14 +22,17 @@ type RedisConfigInfo struct {
 	User     string
 	Password string
 	Database int
+	PoolSize int
 }
 
 type DatabaseConfigInfo struct {
-	Host     string
-	Port     int
-	User     string
-	Password string
-	DbName   string
+	Host         string
+	Port         int
+	User         string
+	Password     string
+	DbName       string
+	MaxOpenConns int
+	MaxIdleConn  int
 }
 
 func InitConfig(file string) (*ini.File, error) {
@@ -42,6 +45,8 @@ func InitConfig(file string) (*ini.File, error) {
 	section := cfg.Section("postgres")
 	DatabaseConifg.Host = section.Key("host").String()
 	DatabaseConifg.Port = section.Key("port").MustInt()
+	DatabaseConifg.MaxOpenConns = section.Key("max_open_conn").MustInt()
+	DatabaseConifg.MaxIdleConn = section.Key("max_idle_conn").MustInt()
 	DatabaseConifg.User = section.Key("user").String()
 	DatabaseConifg.Password = section.Key("password").String()
 	DatabaseConifg.DbName = section.Key("database").String()
@@ -57,6 +62,7 @@ func InitConfig(file string) (*ini.File, error) {
 	RedisConfig.User = redisSection.Key("user").String()
 	RedisConfig.Password = redisSection.Key("password").String()
 	RedisConfig.Database = redisSection.Key("database").MustInt()
+	RedisConfig.PoolSize = redisSection.Key("pool_size").MustInt()
 
 	return cfg, err
 }

+ 1 - 1
utils/log_format.go → utils/formatter.go

@@ -7,7 +7,7 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-func OhUrlShortenerLogFormat(server string) gin.HandlerFunc {
+func WebLogFormat(server string) gin.HandlerFunc {
 	return gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
 		if !strings.HasPrefix(param.Path, "/assets") {
 			return fmt.Sprintf("[%s | %s] %s %s %d %s \t%s %s %s \n",