[2024-07-13](UPDATE): 用户登录完成

This commit is contained in:
june 2024-07-13 20:33:20 +08:00
parent 3dc9fbeaf8
commit 64b776254e
27 changed files with 413 additions and 154 deletions

View File

@ -42,10 +42,10 @@ var picgoCmd = &cobra.Command{
Use: "picgo",
Run: func(cmd *cobra.Command, args []string) {
// 创建路由
r := router.InitRouter()
router.InitRouter()
// 启动HTTP服务器
log.Println("Serving on http://localhost:8082")
err := http.ListenAndServe(":8082", r)
err := http.ListenAndServe(":8082", nil)
if err != nil {
log.Fatalf("Failed to start server: %v", err)
}

View File

@ -61,7 +61,7 @@ func Verify(id string, code string) (ok bool) {
return
}
// redis存储验证码和用户输入验证码对比
if strings.ToLower(redisCode) == code {
if strings.ToLower(redisCode) == strings.ToLower(code) {
return true
}
return

View File

@ -42,5 +42,5 @@ func GetCode(cid string) (code string, err error) {
// captcha缓存key拼接
func genRedisKey(id string) (res string) {
return corelib.AdminCaptchaKey + id
return corelib.CaptchaKey + id
}

13
corelib/json.go Normal file
View 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)
}

View File

@ -8,8 +8,9 @@ import (
)
var (
RdbClient *redis.Client
AdminCaptchaKey = "admin:captcha:" // 验证码存储key
RdbClient *redis.Client
CaptchaKey = "picgo:captcha:" // 验证码存储key
UserKey = "picgo:user:"
)
func NewRedis() {

View File

@ -15,7 +15,7 @@ type JsonResponse struct {
// WriteJsonResponse 用于写入JSON响应
func WriteJsonResponse(w http.ResponseWriter, status int, message string, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
w.WriteHeader(http.StatusOK)
response := JsonResponse{
Status: status,
Message: message,

View File

@ -1,7 +1,7 @@
package corelib
import (
"gopkg.in/boj/redistore.v1"
"github.com/boj/redistore"
"picgo/configs"
)
@ -9,10 +9,10 @@ var SessionStore *redistore.RediStore
func NewSessionStore() {
// 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 {
panic(err)
}
SessionStore = store
defer store.Close()
//defer store.Close()
}

View File

@ -5,7 +5,7 @@ import (
"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...)
if err != nil {

62
data/user.go Normal file
View 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
}

View File

@ -6,12 +6,12 @@ server:
port: 8181
host: localhost
node_id: 1
log_path: tmp/admin-server.log
log_path: tmp/picgo.log
environment: dev
static_path: static
upload_path: static/pic
upload_max_size: 3
sessions_key: eyjhbgcioijiuzi1niisinr5cci6ikpx
sessions_key: afE+7AztxjvTc5MmQ/8RuQw1aabgOLMlAKGMKLseRkc=
session_name: picgo
# redis
redis:

7
go.mod
View File

@ -3,14 +3,18 @@ module picgo
go 1.22
require (
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff
github.com/bwmarrin/snowflake v0.3.0
github.com/dlclark/regexp2 v1.11.2
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/redis/go-redis/v9 v9.5.4
github.com/spf13/cobra v1.8.1
go.uber.org/zap v1.27.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/yaml.v3 v3.0.1
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/garyburd/redigo v1.6.4 // 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/sessions v1.3.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
@ -32,5 +36,4 @@ require (
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/image v0.18.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b // indirect
)

11
go.sum
View File

@ -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/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
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/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/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/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/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/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=

View File

@ -17,6 +17,7 @@ func CaptchaHandler(w http.ResponseWriter, r *http.Request) {
err error
)
captchaId := getCaptchaId(r)
corelib.Logger.Info("captchaHandler cid: ", captchaId)
if res, err = generateCaptcha(captchaId); err != nil {
corelib.WriteJsonResponse(w, 1030, "生产验证码失败", nil)
return

View File

@ -11,9 +11,9 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) {
Title string
Active string
}{
Title: "Admin Dashboard",
Title: "Dashboard",
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")
}
}

View File

@ -2,18 +2,35 @@ package handler
import (
"encoding/json"
"github.com/gorilla/csrf"
"net/http"
"picgo/configs"
"picgo/corelib"
"picgo/corelib/captcha"
"picgo/data"
"picgo/model"
)
func LoginHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
corelib.TemplateHandler(w, r, nil, "view/login.html")
data := map[string]interface{}{
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:
loginService(w, r)
default:
@ -33,11 +50,13 @@ func loginService(w http.ResponseWriter, r *http.Request) {
return
}
cid := getCaptchaId(r)
corelib.Logger.Info("LoginRequest: ", res)
corelib.Logger.Info("login cid: ", cid)
if ok := captcha.Verify(cid, res.Captcha); !ok {
corelib.WriteJsonResponse(w, 1040, "验证码错误", nil)
return
}
if user, err = sysUserSelectDataByUsername(res.Username); err != nil {
if user, err = data.SysUserSelectByUsername(res.Username); err != nil {
corelib.WriteJsonResponse(w, 1041, "用户不存在", nil)
return
}
@ -49,18 +68,19 @@ func loginService(w http.ResponseWriter, r *http.Request) {
session, _ := corelib.SessionStore.Get(r, configs.Settings.Server.SessionName)
session.Values["username"] = user.Username
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
}
w.Header().Set("Content-Type", "application/json")
corelib.WriteJsonResponse(w, 200, "登录成功", nil)
}
// sysUserSelectDataByUsername 通过用户名查询用户
func sysUserSelectDataByUsername(userName string) (model.SysUser, error) {
var user model.SysUser
if err := corelib.DbMysql.Model(model.SysUser{Username: userName}).First(&user).Error; err != nil {
return user, err
}
return user, nil
}
//// sysUserSelectDataByUsername 通过用户名查询用户
//func sysUserSelectDataByUsername(userName string) (model.SysUser, error) {
// var user model.SysUser
// if err := corelib.DbMysql.Model(model.SysUser{Username: userName}).First(&user).Error; err != nil {
// return user, err
// }
// return user, nil
//}

View File

@ -14,6 +14,6 @@ func ProfileHandler(w http.ResponseWriter, r *http.Request) {
Title: "Admin Dashboard",
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")
}
}

View File

@ -14,6 +14,6 @@ func SettingsHandler(w http.ResponseWriter, r *http.Request) {
Title: "Admin Dashboard",
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")
}
}

View File

@ -4,31 +4,22 @@ import (
"net/http"
"os"
"path/filepath"
"picgo/configs"
)
func StaticHandler(w http.ResponseWriter, r *http.Request) {
func StaticHandler() http.Handler {
// 文件服务器的根目录
root := "./static"
// 自定义文件处理器
fileHandler := http.StripPrefix("/static/", http.FileServer(http.Dir(root)))
// 获取请求的路径
path := filepath.Join(root, r.URL.Path[len("/static/"):])
// 检查路径是否是一个目录
info, err := os.Stat(path)
if err != nil {
// 如果文件不存在返回404
http.NotFound(w, r)
return
}
if info.IsDir() {
// 如果是目录返回404
http.NotFound(w, r)
return
}
// 处理文件
fileHandler.ServeHTTP(w, r)
root := configs.Settings.Server.StaticPath
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 获取文件路径
path := filepath.Join(root, r.URL.Path)
// 检查路径是否为目录
info, err := os.Stat(path)
if err == nil && info.IsDir() {
http.NotFound(w, r) // 如果是目录则返回404
return
}
// 否则提供文件
http.ServeFile(w, r, path)
})
}

View File

@ -1,23 +1,35 @@
package middleware
import (
"log"
"net/http"
"time"
"picgo/configs"
"picgo/corelib"
"picgo/data"
"strings"
)
// LoginMiddleware 登录 // 添加日志中间件到路由器 使用r.Handle("/", LoginMiddleware(http.HandlerFunc(handler)))
func LoginMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 记录请求时间和路径
start := time.Now()
log.Printf("Started %s %s", r.Method, r.URL.Path)
resPath := r.URL.Path
if resPath == "/login" || resPath == "/captcha" || strings.HasPrefix(resPath, "/static") {
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
View 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)
})
}

View File

@ -1,20 +1,42 @@
package router
import (
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
"net/http"
"picgo/configs"
"picgo/handler"
"picgo/middleware"
)
func InitRouter() *http.ServeMux {
var mux *http.ServeMux
func InitRouter() *mux.Router {
var secure bool
if configs.Settings.Server.Environment == "dev" {
secure = false
} else {
secure = true
}
// 设置CSRF保护
CSRF := csrf.Protect(
[]byte(configs.Settings.Server.SessionsKey),
csrf.Secure(secure), // 在开发环境中禁用HTTPS
csrf.RequestHeader("X-CSRF-Token"),
)
// 创建新的路由器
mux = http.NewServeMux()
mux.HandleFunc("/", handler.IndexHandler)
mux.HandleFunc("/settings", handler.SettingsHandler)
mux.HandleFunc("/profile", handler.ProfileHandler)
mux.HandleFunc("/static/", handler.StaticHandler)
mux.HandleFunc("/login", handler.LoginHandler)
mux.HandleFunc("/api/v1/upload", handler.UploadFileHandler)
mux.HandleFunc("/captcha", handler.CaptchaHandler)
return mux
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
View 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
View 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()

View File

@ -13,10 +13,48 @@ Login.prototype.RefreshCaptcha = function () {
$("#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 () {
this.RefreshCaptcha()
this.LoginClick()
}
// 构造执行入口

View File

@ -1,4 +1,23 @@
{{define "content"}}
<h1 class="h2">Dashboard</h1>
<p>Welcome to the Admin Panel.</p>
<div style="background-color: #f2f4f7">
<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}}

View File

@ -1,73 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta charset="utf-8">
<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/custom.css" rel="stylesheet">
<title>{{.Title}}</title>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">Admin Panel</a>
<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>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
<li class="nav-item {{if eq .Active "/"}}active{{end}}">
<a class="nav-link" href="/">Dashboard <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item {{if eq .Active "/settings"}}active{{end}}">
<a class="nav-link" href="/settings">Settings</a>
</li>
<li class="nav-item {{if eq .Active "/profile"}}active{{end}}">
<a class="nav-link" href="/profile">Profile</a>
</li>
</ul>
</div>
</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>
<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">
<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">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse pl-5" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="/user">用户管理</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 class="nav-item">
<a class="nav-link" href="/domain">域名管理</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 class="nav-item">
<a class="nav-link" href="/picture">图片管理</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 class="nav-item">
<a class="nav-link" href="/upload">图片上传</a>
</li>
</ul>
</div>
</nav>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-4">
{{template "content" .}}
</main>
</div>
</div>
<div class="container">
{{template "content" .}}
</div>
<script src="/static/js/jquery-3.3.1.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/feather-icons.js"></script>
<script>
feather.replace()
</script>
<!--<script src="/static/js/feather-icons.js"></script>-->
<!--<script>-->
<!-- feather.replace()-->
<!--</script>-->
</body>
</html>

View File

@ -3,23 +3,19 @@
<head>
<meta charset="UTF-8">
<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/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>
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.login-form {
width: 100%;
max-width: 420px;
padding: 15px;
margin: auto;
border-radius: 12px;
}
.login-container {min-height: 100vh;display: flex;align-items: center;justify-content: center;}
.login-form {width: 100%;max-width: 420px;padding: 15px;margin: auto;border-radius: 12px;}
</style>
</head>
<body>
@ -35,7 +31,7 @@
<span data-feather="user"></span>
</span>
</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>
</div>
@ -45,7 +41,7 @@
<span data-feather="pocket"></span>
</span>
</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>
</div>
@ -53,21 +49,17 @@
<div class="input-group-prepend">
<span class="input-group-text"> <span data-feather="image"></span></span>
</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">
<img src="/captcha" style="width: 96px;border: 1px solid #ced4da;border-radius: 4px;" alt="验证码" id="captcha-img">
</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>
</div>
</div>
</div>
<!-- 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>
</body>
</html>