[2024-07-13](UPDATE): 用户登录完成
This commit is contained in:
parent
3dc9fbeaf8
commit
64b776254e
@ -42,10 +42,10 @@ var picgoCmd = &cobra.Command{
|
|||||||
Use: "picgo",
|
Use: "picgo",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// 创建路由
|
// 创建路由
|
||||||
r := router.InitRouter()
|
router.InitRouter()
|
||||||
// 启动HTTP服务器
|
// 启动HTTP服务器
|
||||||
log.Println("Serving on http://localhost:8082")
|
log.Println("Serving on http://localhost:8082")
|
||||||
err := http.ListenAndServe(":8082", r)
|
err := http.ListenAndServe(":8082", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to start server: %v", err)
|
log.Fatalf("Failed to start server: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ func Verify(id string, code string) (ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// redis存储验证码和用户输入验证码对比
|
// redis存储验证码和用户输入验证码对比
|
||||||
if strings.ToLower(redisCode) == code {
|
if strings.ToLower(redisCode) == strings.ToLower(code) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -42,5 +42,5 @@ func GetCode(cid string) (code string, err error) {
|
|||||||
|
|
||||||
// captcha缓存key拼接
|
// captcha缓存key拼接
|
||||||
func genRedisKey(id string) (res string) {
|
func genRedisKey(id string) (res string) {
|
||||||
return corelib.AdminCaptchaKey + id
|
return corelib.CaptchaKey + id
|
||||||
}
|
}
|
||||||
|
13
corelib/json.go
Normal file
13
corelib/json.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package corelib
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
// JsonMarshal JSON封装
|
||||||
|
func JsonMarshal(src interface{}) (res []byte, err error) {
|
||||||
|
return json.Marshal(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JsonUnmarshal JSON解封
|
||||||
|
func JsonUnmarshal(data []byte, res interface{}) (err error) {
|
||||||
|
return json.Unmarshal(data, res)
|
||||||
|
}
|
@ -9,7 +9,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
RdbClient *redis.Client
|
RdbClient *redis.Client
|
||||||
AdminCaptchaKey = "admin:captcha:" // 验证码存储key
|
CaptchaKey = "picgo:captcha:" // 验证码存储key
|
||||||
|
UserKey = "picgo:user:"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRedis() {
|
func NewRedis() {
|
||||||
|
@ -15,7 +15,7 @@ type JsonResponse struct {
|
|||||||
// WriteJsonResponse 用于写入JSON响应
|
// WriteJsonResponse 用于写入JSON响应
|
||||||
func WriteJsonResponse(w http.ResponseWriter, status int, message string, data any) {
|
func WriteJsonResponse(w http.ResponseWriter, status int, message string, data any) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(http.StatusOK)
|
||||||
response := JsonResponse{
|
response := JsonResponse{
|
||||||
Status: status,
|
Status: status,
|
||||||
Message: message,
|
Message: message,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package corelib
|
package corelib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/boj/redistore.v1"
|
"github.com/boj/redistore"
|
||||||
"picgo/configs"
|
"picgo/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -9,10 +9,10 @@ var SessionStore *redistore.RediStore
|
|||||||
|
|
||||||
func NewSessionStore() {
|
func NewSessionStore() {
|
||||||
// Fetch new store.
|
// Fetch new store.
|
||||||
store, err := redistore.NewRediStore(10, "tcp", configs.Settings.Redis.Addr, configs.Settings.Redis.Password)
|
store, err := redistore.NewRediStore(10, "tcp", configs.Settings.Redis.Addr, configs.Settings.Redis.Password, []byte(configs.Settings.SessionsKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
SessionStore = store
|
SessionStore = store
|
||||||
defer store.Close()
|
//defer store.Close()
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TemplateHandler(w http.ResponseWriter, r *http.Request, data any, filenames ...string) {
|
func TemplateHandler(w http.ResponseWriter, data any, filenames ...string) {
|
||||||
// 解析模板文件
|
// 解析模板文件
|
||||||
tmpl, err := template.ParseFiles(filenames...)
|
tmpl, err := template.ParseFiles(filenames...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
62
data/user.go
Normal file
62
data/user.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"picgo/corelib"
|
||||||
|
"picgo/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SysUserSelectByUsername 通过用户名查询用户
|
||||||
|
func SysUserSelectByUsername(userName string) (model.SysUser, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
user model.SysUser
|
||||||
|
)
|
||||||
|
if user, err = SysUserGetCacheByUsername(userName); err == nil {
|
||||||
|
corelib.Logger.Infoln("SysUserSelectByUsername 从缓存获取用户信息成功: ", user.Username)
|
||||||
|
return user, nil
|
||||||
|
} else {
|
||||||
|
corelib.Logger.Infoln("SysUserSelectByUsername 从缓存获取用户信息失败: ", err)
|
||||||
|
}
|
||||||
|
if err = corelib.DbMysql.Model(model.SysUser{Username: userName}).First(&user).Error; err != nil {
|
||||||
|
corelib.Logger.Infoln("SysUserSelectByUsername 数据库中查询用户信息失败: ", err)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
if err = SysUserSetCacheByUsername(user); err != nil {
|
||||||
|
corelib.Logger.Infoln("SysUserSelectByUsername 缓存用户信息失败: ", err)
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SysUserSetCacheByUsername(user model.SysUser) error {
|
||||||
|
var (
|
||||||
|
jsonData []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if jsonData, err = corelib.JsonMarshal(user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := corelib.UserKey + user.Username
|
||||||
|
if err = corelib.RdbClient.Set(context.Background(), key, string(jsonData), 5*time.Minute).Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SysUserGetCacheByUsername redis获取用户信息
|
||||||
|
func SysUserGetCacheByUsername(userName string) (model.SysUser, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
user model.SysUser
|
||||||
|
userCache string
|
||||||
|
)
|
||||||
|
if userCache, err = corelib.RdbClient.Get(context.Background(), corelib.UserKey+userName).Result(); err != nil {
|
||||||
|
return model.SysUser{}, err
|
||||||
|
}
|
||||||
|
if err = corelib.JsonUnmarshal([]byte(userCache), &user); err != nil {
|
||||||
|
return model.SysUser{}, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
@ -6,12 +6,12 @@ server:
|
|||||||
port: 8181
|
port: 8181
|
||||||
host: localhost
|
host: localhost
|
||||||
node_id: 1
|
node_id: 1
|
||||||
log_path: tmp/admin-server.log
|
log_path: tmp/picgo.log
|
||||||
environment: dev
|
environment: dev
|
||||||
static_path: static
|
static_path: static
|
||||||
upload_path: static/pic
|
upload_path: static/pic
|
||||||
upload_max_size: 3
|
upload_max_size: 3
|
||||||
sessions_key: eyjhbgcioijiuzi1niisinr5cci6ikpx
|
sessions_key: afE+7AztxjvTc5MmQ/8RuQw1aabgOLMlAKGMKLseRkc=
|
||||||
session_name: picgo
|
session_name: picgo
|
||||||
# redis
|
# redis
|
||||||
redis:
|
redis:
|
||||||
|
7
go.mod
7
go.mod
@ -3,14 +3,18 @@ module picgo
|
|||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff
|
||||||
github.com/bwmarrin/snowflake v0.3.0
|
github.com/bwmarrin/snowflake v0.3.0
|
||||||
github.com/dlclark/regexp2 v1.11.2
|
github.com/dlclark/regexp2 v1.11.2
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
|
github.com/gorilla/csrf v1.7.2
|
||||||
|
github.com/gorilla/mux v1.8.1
|
||||||
github.com/llgcode/draw2d v0.0.0-20240627062922-0ed1ff131195
|
github.com/llgcode/draw2d v0.0.0-20240627062922-0ed1ff131195
|
||||||
github.com/redis/go-redis/v9 v9.5.4
|
github.com/redis/go-redis/v9 v9.5.4
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
golang.org/x/crypto v0.25.0
|
golang.org/x/crypto v0.25.0
|
||||||
|
gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/driver/mysql v1.5.7
|
gorm.io/driver/mysql v1.5.7
|
||||||
@ -22,7 +26,7 @@ require (
|
|||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/garyburd/redigo v1.6.4 // indirect
|
github.com/garyburd/redigo v1.6.4 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/gorilla/csrf v1.7.2 // indirect
|
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/gorilla/sessions v1.3.0 // indirect
|
github.com/gorilla/sessions v1.3.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
@ -32,5 +36,4 @@ require (
|
|||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
golang.org/x/image v0.18.0 // indirect
|
golang.org/x/image v0.18.0 // indirect
|
||||||
golang.org/x/text v0.16.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b // indirect
|
|
||||||
)
|
)
|
||||||
|
11
go.sum
11
go.sum
@ -1,3 +1,5 @@
|
|||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff h1:RmdPFa+slIr4SCBg4st/l/vZWVe9QJKMXGO60Bxbe04=
|
||||||
|
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
@ -19,10 +21,19 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ
|
|||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
|
||||||
|
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||||
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||||
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
|
github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg=
|
||||||
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
@ -17,6 +17,7 @@ func CaptchaHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
captchaId := getCaptchaId(r)
|
captchaId := getCaptchaId(r)
|
||||||
|
corelib.Logger.Info("captchaHandler cid: ", captchaId)
|
||||||
if res, err = generateCaptcha(captchaId); err != nil {
|
if res, err = generateCaptcha(captchaId); err != nil {
|
||||||
corelib.WriteJsonResponse(w, 1030, "生产验证码失败", nil)
|
corelib.WriteJsonResponse(w, 1030, "生产验证码失败", nil)
|
||||||
return
|
return
|
||||||
|
@ -11,9 +11,9 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Title string
|
Title string
|
||||||
Active string
|
Active string
|
||||||
}{
|
}{
|
||||||
Title: "Admin Dashboard",
|
Title: "Dashboard",
|
||||||
Active: r.URL.Path,
|
Active: r.URL.Path,
|
||||||
}
|
}
|
||||||
corelib.TemplateHandler(w, r, data, "view/layout.html", "view/index.html")
|
corelib.TemplateHandler(w, data, "view/layout.html", "view/index.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,35 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
"net/http"
|
"net/http"
|
||||||
"picgo/configs"
|
"picgo/configs"
|
||||||
"picgo/corelib"
|
"picgo/corelib"
|
||||||
"picgo/corelib/captcha"
|
"picgo/corelib/captcha"
|
||||||
|
"picgo/data"
|
||||||
"picgo/model"
|
"picgo/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
|
data := map[string]interface{}{
|
||||||
corelib.TemplateHandler(w, r, nil, "view/login.html")
|
csrf.TemplateTag: csrf.TemplateField(r),
|
||||||
|
}
|
||||||
|
//data := map[string]interface{}{csrf.TemplateTag: csrf.TemplateField(r)}
|
||||||
|
corelib.Logger.Info("data: ", data, data["csrfToken"])
|
||||||
|
corelib.TemplateHandler(w, data, "view/login.html")
|
||||||
|
//tmpl, err := template.ParseFiles("view/login.html")
|
||||||
|
//if err != nil {
|
||||||
|
// http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
//if err = tmpl.Execute(w, map[string]interface{}{
|
||||||
|
// csrf.TemplateTag: csrf.TemplateField(r),
|
||||||
|
//}); err != nil {
|
||||||
|
// http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
loginService(w, r)
|
loginService(w, r)
|
||||||
default:
|
default:
|
||||||
@ -33,11 +50,13 @@ func loginService(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cid := getCaptchaId(r)
|
cid := getCaptchaId(r)
|
||||||
|
corelib.Logger.Info("LoginRequest: ", res)
|
||||||
|
corelib.Logger.Info("login cid: ", cid)
|
||||||
if ok := captcha.Verify(cid, res.Captcha); !ok {
|
if ok := captcha.Verify(cid, res.Captcha); !ok {
|
||||||
corelib.WriteJsonResponse(w, 1040, "验证码错误", nil)
|
corelib.WriteJsonResponse(w, 1040, "验证码错误", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if user, err = sysUserSelectDataByUsername(res.Username); err != nil {
|
if user, err = data.SysUserSelectByUsername(res.Username); err != nil {
|
||||||
corelib.WriteJsonResponse(w, 1041, "用户不存在", nil)
|
corelib.WriteJsonResponse(w, 1041, "用户不存在", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -49,18 +68,19 @@ func loginService(w http.ResponseWriter, r *http.Request) {
|
|||||||
session, _ := corelib.SessionStore.Get(r, configs.Settings.Server.SessionName)
|
session, _ := corelib.SessionStore.Get(r, configs.Settings.Server.SessionName)
|
||||||
session.Values["username"] = user.Username
|
session.Values["username"] = user.Username
|
||||||
if err = session.Save(r, w); err != nil {
|
if err = session.Save(r, w); err != nil {
|
||||||
corelib.WriteJsonResponse(w, 1043, "回话保存失败", nil)
|
corelib.Logger.Infoln("session save err:", err)
|
||||||
|
corelib.WriteJsonResponse(w, 1043, "会话保存失败", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
corelib.WriteJsonResponse(w, 200, "登录成功", nil)
|
corelib.WriteJsonResponse(w, 200, "登录成功", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sysUserSelectDataByUsername 通过用户名查询用户
|
//// sysUserSelectDataByUsername 通过用户名查询用户
|
||||||
func sysUserSelectDataByUsername(userName string) (model.SysUser, error) {
|
//func sysUserSelectDataByUsername(userName string) (model.SysUser, error) {
|
||||||
var user model.SysUser
|
// var user model.SysUser
|
||||||
if err := corelib.DbMysql.Model(model.SysUser{Username: userName}).First(&user).Error; err != nil {
|
// if err := corelib.DbMysql.Model(model.SysUser{Username: userName}).First(&user).Error; err != nil {
|
||||||
return user, err
|
// return user, err
|
||||||
}
|
// }
|
||||||
return user, nil
|
// return user, nil
|
||||||
}
|
//}
|
||||||
|
@ -14,6 +14,6 @@ func ProfileHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Title: "Admin Dashboard",
|
Title: "Admin Dashboard",
|
||||||
Active: r.URL.Path,
|
Active: r.URL.Path,
|
||||||
}
|
}
|
||||||
corelib.TemplateHandler(w, r, data, "view/layout.html", "view/profile.html")
|
corelib.TemplateHandler(w, data, "view/layout.html", "view/profile.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,6 @@ func SettingsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Title: "Admin Dashboard",
|
Title: "Admin Dashboard",
|
||||||
Active: r.URL.Path,
|
Active: r.URL.Path,
|
||||||
}
|
}
|
||||||
corelib.TemplateHandler(w, r, data, "view/layout.html", "view/settings.html")
|
corelib.TemplateHandler(w, data, "view/layout.html", "view/settings.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,31 +4,22 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"picgo/configs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StaticHandler(w http.ResponseWriter, r *http.Request) {
|
func StaticHandler() http.Handler {
|
||||||
// 文件服务器的根目录
|
// 文件服务器的根目录
|
||||||
root := "./static"
|
root := configs.Settings.Server.StaticPath
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// 自定义文件处理器
|
// 获取文件路径
|
||||||
fileHandler := http.StripPrefix("/static/", http.FileServer(http.Dir(root)))
|
path := filepath.Join(root, r.URL.Path)
|
||||||
|
// 检查路径是否为目录
|
||||||
// 获取请求的路径
|
|
||||||
path := filepath.Join(root, r.URL.Path[len("/static/"):])
|
|
||||||
|
|
||||||
// 检查路径是否是一个目录
|
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err != nil {
|
if err == nil && info.IsDir() {
|
||||||
// 如果文件不存在,返回404
|
http.NotFound(w, r) // 如果是目录,则返回404
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if info.IsDir() {
|
// 否则提供文件
|
||||||
// 如果是目录,返回404
|
http.ServeFile(w, r, path)
|
||||||
http.NotFound(w, r)
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理文件
|
|
||||||
fileHandler.ServeHTTP(w, r)
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,35 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"picgo/configs"
|
||||||
|
"picgo/corelib"
|
||||||
|
"picgo/data"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoginMiddleware 登录 // 添加日志中间件到路由器 使用:r.Handle("/", LoginMiddleware(http.HandlerFunc(handler)))
|
// LoginMiddleware 登录 // 添加日志中间件到路由器 使用:r.Handle("/", LoginMiddleware(http.HandlerFunc(handler)))
|
||||||
func LoginMiddleware(next http.Handler) http.Handler {
|
func LoginMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// 记录请求时间和路径
|
resPath := r.URL.Path
|
||||||
start := time.Now()
|
if resPath == "/login" || resPath == "/captcha" || strings.HasPrefix(resPath, "/static") {
|
||||||
log.Printf("Started %s %s", r.Method, r.URL.Path)
|
|
||||||
|
|
||||||
// 调用下一个处理程序
|
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
session, _ := corelib.SessionStore.Get(r, configs.Settings.Server.SessionName)
|
||||||
|
username, ok := session.Values["username"].(string)
|
||||||
|
if !ok || username == "" {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
//user model.SysUser
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if _, err = data.SysUserSelectByUsername(username); err != nil {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 记录请求处理时间
|
next.ServeHTTP(w, r)
|
||||||
duration := time.Since(start)
|
|
||||||
log.Printf("Completed %s %s in %v", r.Method, r.URL.Path, duration)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
21
middleware/cors.go
Normal file
21
middleware/cors.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// CorsMiddleware 是处理跨域请求的中间件
|
||||||
|
func CorsMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-CSRF-Token")
|
||||||
|
w.Header().Set("Access-Control-Expose-Headers", "Content-Length, X-CSRF-Token")
|
||||||
|
|
||||||
|
// 处理预检请求
|
||||||
|
if r.Method == http.MethodOptions {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
@ -1,20 +1,42 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"picgo/configs"
|
||||||
"picgo/handler"
|
"picgo/handler"
|
||||||
|
"picgo/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitRouter() *http.ServeMux {
|
func InitRouter() *mux.Router {
|
||||||
var mux *http.ServeMux
|
var secure bool
|
||||||
// 创建新的路由器
|
if configs.Settings.Server.Environment == "dev" {
|
||||||
mux = http.NewServeMux()
|
secure = false
|
||||||
mux.HandleFunc("/", handler.IndexHandler)
|
} else {
|
||||||
mux.HandleFunc("/settings", handler.SettingsHandler)
|
secure = true
|
||||||
mux.HandleFunc("/profile", handler.ProfileHandler)
|
}
|
||||||
mux.HandleFunc("/static/", handler.StaticHandler)
|
// 设置CSRF保护
|
||||||
mux.HandleFunc("/login", handler.LoginHandler)
|
CSRF := csrf.Protect(
|
||||||
mux.HandleFunc("/api/v1/upload", handler.UploadFileHandler)
|
[]byte(configs.Settings.Server.SessionsKey),
|
||||||
mux.HandleFunc("/captcha", handler.CaptchaHandler)
|
csrf.Secure(secure), // 在开发环境中禁用HTTPS
|
||||||
return mux
|
csrf.RequestHeader("X-CSRF-Token"),
|
||||||
|
)
|
||||||
|
// 创建新的路由器
|
||||||
|
r := mux.NewRouter()
|
||||||
|
// 处理静态文件
|
||||||
|
//staticDir := "static"
|
||||||
|
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", handler.StaticHandler()))
|
||||||
|
r.HandleFunc("/login", handler.LoginHandler)
|
||||||
|
r.HandleFunc("/captcha", handler.CaptchaHandler)
|
||||||
|
|
||||||
|
r.HandleFunc("/settings", handler.SettingsHandler)
|
||||||
|
r.HandleFunc("/profile", handler.ProfileHandler)
|
||||||
|
r.HandleFunc("/api/v1/upload", handler.UploadFileHandler)
|
||||||
|
// 路由鉴权
|
||||||
|
r.Handle("/", middleware.LoginMiddleware(http.HandlerFunc(handler.IndexHandler))).Methods(http.MethodGet)
|
||||||
|
// 应用 CORS 中间件
|
||||||
|
http.Handle("/", middleware.CorsMiddleware(CSRF(r)))
|
||||||
|
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
22
static/img/add.svg
Normal file
22
static/img/add.svg
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>图标/常规/ic_增加表格</title>
|
||||||
|
<defs>
|
||||||
|
<rect id="path-1" x="0" y="0" width="24" height="24"></rect>
|
||||||
|
</defs>
|
||||||
|
<g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="图床" transform="translate(-643, -239)">
|
||||||
|
<g id="数据录入/上传/拖拽/拖拽点击" transform="translate(-61, 184)">
|
||||||
|
<g id="编组" transform="translate(313, 55)">
|
||||||
|
<g id="图标/常规/ic_增加表格" transform="translate(391, 0)">
|
||||||
|
<mask id="mask-2" fill="white">
|
||||||
|
<use xlink:href="#path-1"></use>
|
||||||
|
</mask>
|
||||||
|
<use id="矩形" fill="#4C5362" opacity="0" xlink:href="#path-1"></use>
|
||||||
|
<path d="M22.5,0 L22.5,10.5 L21,10.5 L21,1.5 L3,1.5 L3,22.5 L9,22.5 L9,24 L1.5,24 L1.5,0 L22.5,0 Z M17.25,12 L17.25,17.25 L22.5,17.25 L22.5,18.75 L17.25,18.75 L17.25,24 L15.75,24 L15.75,18.75 L10.5,18.75 L10.5,17.25 L15.75,17.25 L15.75,12 L17.25,12 Z" id="形状结合" fill="#4C5362" fill-rule="nonzero"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
54
static/js/common.js
Normal file
54
static/js/common.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
function Common() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误弹窗
|
||||||
|
Common.prototype.AlertError = function (msg) {
|
||||||
|
swal('提示', msg, 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 弹窗
|
||||||
|
Common.prototype.AlertToast = function (msg, type) {
|
||||||
|
swal({
|
||||||
|
'title': msg,
|
||||||
|
'text': '',
|
||||||
|
'type': type,
|
||||||
|
'showCancelButton': false,
|
||||||
|
'showConfirmButton': false,
|
||||||
|
'timer': 1000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 带确认按钮的弹窗
|
||||||
|
Common.prototype.AlertConfirm = function (params) {
|
||||||
|
swal({
|
||||||
|
'title': params['title'] ? params['title'] : '提示',
|
||||||
|
'showCancelButton': true,
|
||||||
|
'showConfirmButton': true,
|
||||||
|
'type': params['type'] ? params['type'] : '',
|
||||||
|
'confirmButtonText': params['confirmText'] ? params['confirmText'] : '确定',
|
||||||
|
'cancelButtonText': params['cancelText'] ? params['cancelText'] : '取消',
|
||||||
|
'text': params['text'] ? params['text'] : ''
|
||||||
|
}, function (isConfirm) {
|
||||||
|
if (isConfirm) {
|
||||||
|
if (params['confirmCallback']) {
|
||||||
|
params['confirmCallback']()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (params['cancelCallback']) {
|
||||||
|
params['cancelCallback']()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功弹窗
|
||||||
|
Common.prototype.AlertSuccessToast = function (msg) {
|
||||||
|
if (!msg) {
|
||||||
|
msg = '成功!'
|
||||||
|
}
|
||||||
|
this.AlertToast(msg, 'success')
|
||||||
|
}
|
||||||
|
|
||||||
|
var Alert = new Common()
|
||||||
|
|
@ -13,10 +13,48 @@ Login.prototype.RefreshCaptcha = function () {
|
|||||||
$("#captcha-img").attr('src', self.captchaUrl); //显示图片
|
$("#captcha-img").attr('src', self.captchaUrl); //显示图片
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
Login.prototype.LoginClick = function () {
|
||||||
|
let self = this;
|
||||||
|
let csrfToken = document.getElementsByName("gorilla.csrf.Token")[0].value
|
||||||
|
$("#login-btn").click(function () {
|
||||||
|
// 获取参数
|
||||||
|
let username = $("#login-username").val()
|
||||||
|
let password = $("#login-password").val()
|
||||||
|
let captcha = $("#login-captcha").val()
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/login',
|
||||||
|
method: 'POST',
|
||||||
|
data: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
captcha: captcha
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
"X-CSRF-Token": csrfToken
|
||||||
|
},
|
||||||
|
success: function (result) {
|
||||||
|
if (result["status"] === 200) {
|
||||||
|
Alert.AlertSuccessToast(result["message"])
|
||||||
|
window.location.href = "/"
|
||||||
|
} else {
|
||||||
|
Alert.AlertError(result["message"])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function (xhr, status, error) {
|
||||||
|
Alert.AlertError("服务器内部错误")
|
||||||
|
console.log('请求失败:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Login.prototype.run = function () {
|
Login.prototype.run = function () {
|
||||||
this.RefreshCaptcha()
|
this.RefreshCaptcha()
|
||||||
|
this.LoginClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构造执行入口
|
// 构造执行入口
|
||||||
|
@ -1,4 +1,23 @@
|
|||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
<h1 class="h2">Dashboard</h1>
|
<div style="background-color: #f2f4f7">
|
||||||
<p>Welcome to the Admin Panel.</p>
|
<div class="upload-zone"
|
||||||
|
style="height: 248px; background-color: rgb(255, 255, 255); border-radius: 6px; padding: 24px;">
|
||||||
|
<span class="ant-upload-wrapper" style="box-sizing: border-box; margin: 0; padding: 0; color: rgba(0, 0, 0, 0.88);font-size: 14px;line-height: 1.5714285714285714;list-style: none;">
|
||||||
|
<div class="css-3rel02 ant-upload ant-upload-drag">
|
||||||
|
<span tabindex="0" class="ant-upload ant-upload-btn" role="button">
|
||||||
|
<input type="file" accept="" multiple="" style="display: none;">
|
||||||
|
<div class="ant-upload-drag-container" style="display: table-cell;vertical-align: middle;">
|
||||||
|
<p class="ant-upload-drag-icon">
|
||||||
|
<img src="/static/img/add.svg">
|
||||||
|
</p>
|
||||||
|
<p class="ant-upload-text" style="font-size: 14px; margin: 0px;">拖拽文件到此区域或
|
||||||
|
<span style="color: rgb(26, 102, 255);">点此上传</span>
|
||||||
|
</p>
|
||||||
|
<p class="ant-upload-hint" style="font-size: 12px; margin: 4px 0px 0px;">支持上传照片、视频、ZIP、pdf等多种文件,最大支持100M,上传后支持复制url、base64、Markdown 照片三种方式</p>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -1,73 +1,50 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<title>{{.Title}}</title>
|
<link rel="shortcut icon" href="/static/img/favicon.ico">
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="/static/css/custom.css" rel="stylesheet">
|
<link href="/static/css/custom.css" rel="stylesheet">
|
||||||
|
<title>{{.Title}}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div class="container-fluid bg-dark" style="padding-left: 0; padding-right: 0">
|
||||||
|
<div class="container" style="padding-left: 0; padding-right: 0">
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
<a class="navbar-brand" href="#">Admin Panel</a>
|
<a class="navbar-brand" href="/">后台管理</a>
|
||||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
</button>
|
</button>
|
||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse pl-5" id="navbarNav">
|
||||||
<ul class="navbar-nav ml-auto">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item {{if eq .Active "/"}}active{{end}}">
|
<li class="nav-item active">
|
||||||
<a class="nav-link" href="/">Dashboard <span class="sr-only">(current)</span></a>
|
<a class="nav-link" href="/user">用户管理</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item {{if eq .Active "/settings"}}active{{end}}">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/settings">Settings</a>
|
<a class="nav-link" href="/domain">域名管理</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item {{if eq .Active "/profile"}}active{{end}}">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/profile">Profile</a>
|
<a class="nav-link" href="/picture">图片管理</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/upload">图片上传</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="container-fluid">
|
|
||||||
<div class="row">
|
|
||||||
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
|
|
||||||
<div class="sidebar-sticky">
|
|
||||||
<ul class="nav flex-column">
|
|
||||||
<li class="nav-item {{if eq .Active "/"}}active{{end}}">
|
|
||||||
<a class="nav-link" href="/">
|
|
||||||
<span data-feather="home"></span>
|
|
||||||
Dashboard
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item {{if eq .Active "/orders"}}active{{end}}">
|
|
||||||
<a class="nav-link" href="/orders">
|
|
||||||
<span data-feather="file"></span>
|
|
||||||
Orders
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item {{if eq .Active "/products"}}active{{end}}">
|
|
||||||
<a class="nav-link" href="/products">
|
|
||||||
<span data-feather="shopping-cart"></span>
|
|
||||||
Products
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item {{if eq .Active "/customers"}}active{{end}}">
|
|
||||||
<a class="nav-link" href="/customers">
|
|
||||||
<span data-feather="users"></span>
|
|
||||||
Customers
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</div>
|
||||||
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4">
|
|
||||||
|
<div class="container">
|
||||||
{{template "content" .}}
|
{{template "content" .}}
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script src="/static/js/jquery-3.3.1.min.js"></script>
|
||||||
<script src="/static/js/bootstrap.min.js"></script>
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
<script src="/static/js/feather-icons.js"></script>
|
<!--<script src="/static/js/feather-icons.js"></script>-->
|
||||||
<script>
|
<!--<script>-->
|
||||||
feather.replace()
|
<!-- feather.replace()-->
|
||||||
</script>
|
<!--</script>-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -3,23 +3,19 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<title>登录</title>
|
<link rel="shortcut icon" href="/static/img/favicon.ico">
|
||||||
|
<title>后台登录</title>
|
||||||
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
<link href="/static/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="/static/css/sweetalert.css" rel="stylesheet">
|
||||||
|
<script src="/static/js/jquery-3.3.1.min.js"></script>
|
||||||
|
<script src="/static/js/popper.min.js"></script>
|
||||||
|
<script src="/static/js/bootstrap.min.js"></script>
|
||||||
|
<script src="/static/js/feather-icons.js"></script>
|
||||||
|
<script src="/static/js/sweetalert.min.js"></script>
|
||||||
|
<script src="/static/js/common.js"></script>
|
||||||
<style>
|
<style>
|
||||||
.login-container {
|
.login-container {min-height: 100vh;display: flex;align-items: center;justify-content: center;}
|
||||||
min-height: 100vh;
|
.login-form {width: 100%;max-width: 420px;padding: 15px;margin: auto;border-radius: 12px;}
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 420px;
|
|
||||||
padding: 15px;
|
|
||||||
margin: auto;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -35,7 +31,7 @@
|
|||||||
<span data-feather="user"></span>
|
<span data-feather="user"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" name="username" placeholder="用户名" aria-label="用户名"
|
<input type="text" id="login-username" class="form-control" name="username" placeholder="用户名" aria-label="用户名"
|
||||||
aria-describedby="username-addon" required>
|
aria-describedby="username-addon" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -45,7 +41,7 @@
|
|||||||
<span data-feather="pocket"></span>
|
<span data-feather="pocket"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" class="form-control" name="password" placeholder="密码" aria-label="密码"
|
<input type="password" id="login-password" class="form-control" name="password" placeholder="密码" aria-label="密码"
|
||||||
aria-describedby="password-addon" required>
|
aria-describedby="password-addon" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -53,21 +49,17 @@
|
|||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text"> <span data-feather="image"></span></span>
|
<span class="input-group-text"> <span data-feather="image"></span></span>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" id="code" name="code" placeholder="验证码" required>
|
<input type="text" class="form-control" id="login-captcha" name="captcha" placeholder="验证码" required>
|
||||||
<div class="input-group-append" style="padding-left: 4px">
|
<div class="input-group-append" style="padding-left: 4px">
|
||||||
<img src="/captcha" style="width: 96px;border: 1px solid #ced4da;border-radius: 4px;" alt="验证码" id="captcha-img">
|
<img src="/captcha" style="width: 96px;border: 1px solid #ced4da;border-radius: 4px;" alt="验证码" id="captcha-img">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block">登录</button>
|
<button type="button" id="login-btn" class="btn btn-primary btn-block">登录</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Bootstrap JS and dependencies -->
|
<!-- Bootstrap JS and dependencies -->
|
||||||
<script src="/static/js/jquery-3.3.1.min.js"></script>
|
|
||||||
<script src="/static/js/popper.min.js"></script>
|
|
||||||
<script src="/static/js/bootstrap.min.js"></script>
|
|
||||||
<script src="/static/js/feather-icons.js"></script>
|
|
||||||
<script src="/static/js/login.js"></script>
|
<script src="/static/js/login.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Loading…
Reference in New Issue
Block a user