user viper
This commit is contained in:
@@ -1,23 +0,0 @@
|
|||||||
[mysql]
|
|
||||||
Type = mysql
|
|
||||||
Host = 47.93.160.42
|
|
||||||
Port = 3630
|
|
||||||
User = blog
|
|
||||||
Password = qI7=bL4@iJ
|
|
||||||
DBName = blog
|
|
||||||
Charset = utf8mb4
|
|
||||||
Prefix = gin_
|
|
||||||
;Prefix = gin_
|
|
||||||
|
|
||||||
[jwt]
|
|
||||||
SecretKey = \x13\xbf\xd2 1\xce\x8b\xc1\t\xc1=\xec\x07\x93\xd4\x9e\xbco\xb0Z
|
|
||||||
|
|
||||||
[project]
|
|
||||||
StaticUrlMapPath = {"assets/static/": "static/", "assets/docs/": "docs/", "media/": "media/"}
|
|
||||||
TemplateGlob = templates/**/*
|
|
||||||
MediaFilePath = "media/upload/"
|
|
||||||
|
|
||||||
[server]
|
|
||||||
Port = 7890
|
|
||||||
ReadTimeout = 60
|
|
||||||
WriteTimeout = 60
|
|
||||||
@@ -8,16 +8,13 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/go-ini/ini"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SqlDataBase struct {
|
type DataBaseConfig struct {
|
||||||
Type string
|
|
||||||
Host string
|
Host string
|
||||||
Port string
|
Port string
|
||||||
User string
|
User string
|
||||||
@@ -25,6 +22,9 @@ type SqlDataBase struct {
|
|||||||
DBName string
|
DBName string
|
||||||
Charset string
|
Charset string
|
||||||
Prefix string
|
Prefix string
|
||||||
|
Driver string
|
||||||
|
DSN string
|
||||||
|
MaxConns int `mapstructure:"max_conns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Jwt struct {
|
type Jwt struct {
|
||||||
@@ -32,75 +32,95 @@ type Jwt struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Project struct {
|
type Project struct {
|
||||||
StaticUrlMapPath string
|
StaticUrlMapPath []interface{}
|
||||||
TemplateGlob string
|
TemplateGlob string
|
||||||
MediaFilePath string
|
MediaFilePath string
|
||||||
CurrentTheme string
|
CurrentTheme string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type ServerConfig struct {
|
||||||
Port string
|
Port string
|
||||||
|
EnableGzip bool `mapstructure:"enable_gzip"`
|
||||||
|
CSRFSecret string `mapstructure:"csrf_secret"`
|
||||||
ReadTimeout time.Duration
|
ReadTimeout time.Duration
|
||||||
WriteTimeout time.Duration
|
WriteTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
type ThemeConfig struct {
|
||||||
DataBase = &SqlDataBase{}
|
Current string
|
||||||
JwtSecretKey = &Jwt{}
|
AllowUpload bool `mapstructure:"allow_upload"`
|
||||||
ProjectCfg = &Project{}
|
}
|
||||||
HttpServer = &Server{}
|
type SecurityConfig struct {
|
||||||
)
|
JWTSecret string `mapstructure:"jwt_secret"`
|
||||||
|
CORSAllowedOrigins []string `mapstructure:"cors_allowed_origins"`
|
||||||
func SetUp() {
|
}
|
||||||
cfg, err := ini.Load("config/conf.ini")
|
type Config struct {
|
||||||
if err != nil {
|
Env string
|
||||||
panic(err)
|
Server ServerConfig
|
||||||
}
|
DataBase DataBaseConfig
|
||||||
if err := cfg.Section("mysql").MapTo(DataBase); err != nil {
|
Theme ThemeConfig
|
||||||
panic(err)
|
JwtSecretKey Jwt
|
||||||
}
|
Project Project
|
||||||
if err := cfg.Section("jwt").MapTo(JwtSecretKey); err != nil {
|
Security SecurityConfig
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err := cfg.Section("project").MapTo(ProjectCfg); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
if err := cfg.Section("server").MapTo(HttpServer); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetUp1() {
|
func LoadConfig(configPath string) (*Config, error) {
|
||||||
|
// 1. 基础配置
|
||||||
|
viper.SetConfigFile(configPath)
|
||||||
// 设置配置文件的名称(不包括扩展名)
|
// 设置配置文件的名称(不包括扩展名)
|
||||||
viper.SetConfigName("config.yml")
|
viper.SetConfigName("config")
|
||||||
// 设置配置文件所在的目录
|
// 设置配置文件所在的目录
|
||||||
viper.AddConfigPath("./conf")
|
viper.AddConfigPath("./config")
|
||||||
// 设置配置文件的类型为YAML
|
// 设置配置文件的类型为YAML
|
||||||
viper.SetConfigType("yaml")
|
viper.SetConfigType("yaml")
|
||||||
|
// 3. 自动读取环境变量(自动转换大写和下划线)
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
viper.SetEnvPrefix("BLOG") // 环境变量前缀 BLOG_xxx
|
||||||
|
|
||||||
|
// 2. 设置默认值
|
||||||
|
viper.SetDefault("server.port", 3000)
|
||||||
|
viper.SetDefault("database.max_conns", 10)
|
||||||
|
viper.SetDefault("theme.allow_upload", false)
|
||||||
// 读取配置文件
|
// 读取配置文件
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
log.Fatalf("Error reading config file, %s", err)
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||||
|
return nil, fmt.Errorf("配置文件未找到: %s", configPath)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("读取配置文件失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//监控并重新读取配置文件
|
||||||
viper.WatchConfig()
|
viper.WatchConfig()
|
||||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||||
// 配置文件发生变更之后会调用的回调函数
|
// 配置文件发生变更之后会调用的回调函数
|
||||||
fmt.Println("Config file changed:", e.Name)
|
fmt.Println("Config file changed:", e.Name)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 5. 反序列化到结构体
|
||||||
|
var cfg Config
|
||||||
|
if err := viper.Unmarshal(&cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("配置解析失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. 校验必要配置项
|
||||||
|
// if cfg.Security.JWTSecret == "" {
|
||||||
|
// return nil, fmt.Errorf("安全配置错误: jwt_secret 必须设置")
|
||||||
|
// }
|
||||||
|
|
||||||
// 获取配置值
|
// 获取配置值
|
||||||
mysqlHost := viper.GetString("mysql.Host")
|
mysqlHost := viper.GetString("database.Host")
|
||||||
jwtSecretKey := viper.GetString("jwt.SecretKey")
|
jwtSecretKey := viper.GetString("jwt.SecretKey")
|
||||||
serverPort := viper.GetInt("server.Port")
|
serverPort := viper.GetInt("server.Port")
|
||||||
|
|
||||||
templateGlob := viper.GetString("project.TemplateGlob")
|
templateGlob := viper.GetString("project.TemplateGlob")
|
||||||
// 打印获取到的配置值
|
// 打印获取到的配置值
|
||||||
fmt.Printf("MySQL Host: %s\n", mysqlHost)
|
fmt.Printf("MySQL Host: %s\n", mysqlHost)
|
||||||
fmt.Printf("JWT Secret Key: %s\n", jwtSecretKey)
|
fmt.Printf("JWT Secret Key: %s\n", jwtSecretKey)
|
||||||
fmt.Printf("Server Port: %d\n", serverPort)
|
fmt.Printf("Server Port: %d\n", serverPort)
|
||||||
fmt.Printf("TemplateGlob: %s\n", templateGlob)
|
fmt.Printf("TemplateGlob: %s\n", templateGlob)
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCurrentTheme() string {
|
func GetCurrentTheme() string {
|
||||||
return "default"
|
return "default"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
database:
|
database:
|
||||||
Type: mysql
|
Driver: mysql
|
||||||
Host: 47.93.160.42
|
Host: 47.93.160.42
|
||||||
Port: 3630
|
Port: 3630
|
||||||
User: blog
|
User: blog
|
||||||
@@ -7,10 +7,13 @@ database:
|
|||||||
DBName: blog
|
DBName: blog
|
||||||
Charset: utf8mb4
|
Charset: utf8mb4
|
||||||
Prefix: gin_
|
Prefix: gin_
|
||||||
|
DSN: "mysql:mysql@tcp(47.93.160.42:3630)/gin_blog?charset=utf8mb4&parseTime=True&loc=Local"
|
||||||
# Prefix: gin_ # This line is commented out in the original .ini file
|
# Prefix: gin_ # This line is commented out in the original .ini file
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
SecretKey: "\x13\xbf\xd2 1\xce\x8b\xc1\t\xc1=\xec\x07\x93\xd4\x9e\xbco\xb0Z"
|
SecretKey: "\x13\xbf\xd2 1\xce\x8b\xc1\t\xc1=\xec\x07\x93\xd4\x9e\xbco\xb0Z"
|
||||||
|
security:
|
||||||
|
JWTSecret: "jwt_secret"
|
||||||
|
|
||||||
project:
|
project:
|
||||||
StaticUrlMapPath:
|
StaticUrlMapPath:
|
||||||
@@ -21,7 +24,7 @@ project:
|
|||||||
MediaFilePath: "media/upload/"
|
MediaFilePath: "media/upload/"
|
||||||
|
|
||||||
server:
|
server:
|
||||||
Port: 7890
|
Port: 8090
|
||||||
ReadTimeout: 60
|
ReadTimeout: 60
|
||||||
WriteTimeout: 60
|
WriteTimeout: 60
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"go_blog/models"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Home(c *gin.Context) {
|
func Home(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "home.html", gin.H{
|
|
||||||
"title": "首页",
|
var items []models.Content
|
||||||
|
models.DB.Select("*").Limit(5).Find(&items, "type = ?", "post")
|
||||||
|
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||||
|
"Items": items,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"go_blog/models"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ShowPost(c *gin.Context) {
|
func ShowPost(c *gin.Context) {
|
||||||
c.HTML(http.StatusOK, "home.html", gin.H{
|
|
||||||
"title": "首页",
|
id, err := strconv.ParseInt(c.Param("id"), 10, 32)
|
||||||
})
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var content = models.Content{Cid: int32(id)}
|
||||||
|
models.DB.First(&content)
|
||||||
|
c.HTML(http.StatusOK, "post.tmpl", content)
|
||||||
}
|
}
|
||||||
|
|||||||
146
main.go
146
main.go
@@ -1,47 +1,34 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"go_blog/config"
|
||||||
conf "go_blog/config"
|
|
||||||
"go_blog/controllers"
|
|
||||||
"go_blog/models"
|
"go_blog/models"
|
||||||
"go_blog/routers"
|
"go_blog/routers"
|
||||||
"go_blog/serializers"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"go_blog/themes"
|
"go_blog/themes"
|
||||||
|
|
||||||
"gorm.io/driver/mysql"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/schema"
|
|
||||||
"honnef.co/go/tools/config"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
const templatePath = "./templates/*"
|
const templatePath = "./templates/*"
|
||||||
|
|
||||||
func init() {
|
|
||||||
conf.SetUp()
|
|
||||||
models.SetUp()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
conf, err := config.LoadConfig("config.yml")
|
||||||
|
if err != nil {
|
||||||
|
panic("配置文件加载失败: " + err.Error())
|
||||||
|
}
|
||||||
|
models.InitDatabase(conf)
|
||||||
// 4. 初始化主题管理器
|
// 4. 初始化主题管理器
|
||||||
themeManager := themes.NewManager()
|
themeManager := themes.NewManager()
|
||||||
if err := themeManager.LoadTheme(conf.GetCurrentTheme()); err != nil {
|
// if err := themeManager.LoadTheme(conf.GetCurrentTheme()); err != nil {
|
||||||
panic("主题加载失败: " + err.Error())
|
// panic("主题加载失败: " + err.Error())
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 5. 创建Gin实例
|
// 5. 创建Gin实例
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
router.LoadHTMLGlob(templatePath)
|
router.LoadHTMLGlob(templatePath)
|
||||||
// 注册WebSocket路由
|
|
||||||
registerRoutes(router)
|
|
||||||
router.GET("/events", esSSE)
|
|
||||||
|
|
||||||
// 6. 注册中间件
|
// 6. 注册中间件
|
||||||
router.Use(
|
router.Use(
|
||||||
@@ -58,120 +45,7 @@ func main() {
|
|||||||
// 8. 注册路由
|
// 8. 注册路由
|
||||||
routers.RegisterRoutes(router)
|
routers.RegisterRoutes(router)
|
||||||
// 9. 启动服务
|
// 9. 启动服务
|
||||||
router.Run(":8910") //router.Run(":" + cfg.Server.Port)
|
router.Run(":" + conf.Server.Port) //router.Run()
|
||||||
}
|
|
||||||
func esSSE(c *gin.Context) {
|
|
||||||
w := c.Writer
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/event-stream")
|
|
||||||
w.Header().Set("Cache-Control", "no-cache")
|
|
||||||
w.Header().Set("Connection", "keep-alive")
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
|
|
||||||
_, ok := w.(http.Flusher)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
log.Panic("server not support") //浏览器不兼容
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := fmt.Fprintf(w, "id: aaa\ndata: %s\n\n", "dsdf")
|
|
||||||
_, er1r := fmt.Fprintf(w, "event: connecttime\ndata: %s\n\n", "connecttime")
|
|
||||||
if err != nil || er1r != nil {
|
|
||||||
print("error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerRoutes(r *gin.Engine) {
|
|
||||||
|
|
||||||
r.GET("/", func(c *gin.Context) {
|
|
||||||
var items []models.Content
|
|
||||||
models.DB.Select("*").Limit(5).Find(&items, "type = ?", "post")
|
|
||||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
|
||||||
"Items": items,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/page", func(c *gin.Context) {
|
|
||||||
var items []models.Content
|
|
||||||
var pager serializers.Pager
|
|
||||||
pager.InitPager(c)
|
|
||||||
offset := (pager.Page - 1) * pager.PageSize
|
|
||||||
models.DB.Select("*").Offset(offset).Limit(pager.PageSize).Find(&items, "type = ?", "post")
|
|
||||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
|
||||||
"Items": items,
|
|
||||||
"Pager": pager,
|
|
||||||
"Title": "文章列表",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
r.GET("/createcontent", func(c *gin.Context) {
|
|
||||||
c.HTML(http.StatusOK, "content.tmpl", nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/post/:id", func(c *gin.Context) {
|
|
||||||
id, err := strconv.ParseInt(c.Param("id"), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var content = models.Content{Cid: int32(id)}
|
|
||||||
models.DB.First(&content)
|
|
||||||
c.HTML(http.StatusOK, "post.tmpl", content)
|
|
||||||
})
|
|
||||||
user := getUserInfo()
|
|
||||||
r.GET("/login", func(c *gin.Context) {
|
|
||||||
c.HTML(200, "login.tmpl", map[string]interface{}{
|
|
||||||
"title": "这个是titile,传入templates中的",
|
|
||||||
"user": user,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
r.GET("/register", func(c *gin.Context) {
|
|
||||||
c.HTML(200, "register.tmpl", map[string]interface{}{
|
|
||||||
"title": "这个是titile,传入templates中的",
|
|
||||||
"user": user,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/ws", controllers.WebSocketHandler)
|
|
||||||
r.POST("/content", controllers.CreateContentHandler)
|
|
||||||
r.POST("/login", controllers.UsersLoginHandler)
|
|
||||||
r.POST("/register", controllers.UsersRegisterHandler)
|
|
||||||
r.POST("/setinfo", controllers.UsersSetInfoHandler)
|
|
||||||
r.POST("/setpwd", controllers.UsersSetPwdHandler)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func getUserInfo() models.User {
|
|
||||||
user := models.User{
|
|
||||||
Name: "user",
|
|
||||||
Gender: "male",
|
|
||||||
Age: 18,
|
|
||||||
Password: "nothings",
|
|
||||||
PasswordHash: []byte("nothings"),
|
|
||||||
}
|
|
||||||
return user
|
|
||||||
}
|
|
||||||
|
|
||||||
func initDatabase() {
|
|
||||||
// 1. 初始化配置
|
|
||||||
cfg, err := config.Load("config.yaml")
|
|
||||||
if err != nil {
|
|
||||||
panic("加载配置失败: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 初始化数据库
|
|
||||||
db, err := gorm.Open(mysql.Open(cfg.String()), &gorm.Config{
|
|
||||||
NamingStrategy: schema.NamingStrategy{
|
|
||||||
TablePrefix: conf.DataBase.Prefix,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic("数据库连接失败: " + err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 自动迁移数据模型
|
|
||||||
if err := db.AutoMigrate(&models.Article{}, &models.User{}); err != nil {
|
|
||||||
panic("数据库迁移失败: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义模板渲染器
|
// 自定义模板渲染器
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
conf "go_blog/config"
|
"go_blog/config"
|
||||||
"go_blog/pkg/util"
|
"go_blog/pkg/util"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,7 +19,8 @@ import (
|
|||||||
|
|
||||||
var DB *gorm.DB
|
var DB *gorm.DB
|
||||||
|
|
||||||
func SetUp() {
|
func InitDatabase(conf *config.Config) {
|
||||||
|
|
||||||
conUri := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local",
|
conUri := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local",
|
||||||
conf.DataBase.User,
|
conf.DataBase.User,
|
||||||
conf.DataBase.Password,
|
conf.DataBase.Password,
|
||||||
@@ -27,20 +28,23 @@ func SetUp() {
|
|||||||
conf.DataBase.Port,
|
conf.DataBase.Port,
|
||||||
conf.DataBase.DBName,
|
conf.DataBase.DBName,
|
||||||
conf.DataBase.Charset)
|
conf.DataBase.Charset)
|
||||||
|
// 2. 初始化数据库
|
||||||
db, err := gorm.Open(mysql.Open(conUri), &gorm.Config{
|
db, err := gorm.Open(mysql.Open(conUri), &gorm.Config{
|
||||||
NamingStrategy: schema.NamingStrategy{
|
NamingStrategy: schema.NamingStrategy{
|
||||||
TablePrefix: conf.DataBase.Prefix,
|
TablePrefix: conf.DataBase.Prefix,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic("数据库连接失败: " + err.Error())
|
||||||
}
|
}
|
||||||
DB = db
|
|
||||||
|
|
||||||
|
DB = db
|
||||||
|
// 3. 自动迁移数据模型
|
||||||
DB.AutoMigrate(&Account{})
|
DB.AutoMigrate(&Account{})
|
||||||
DB.AutoMigrate(&Content{})
|
DB.AutoMigrate(&Content{})
|
||||||
|
// if err := db.AutoMigrate(&models.Article{}, &models.User{}); err != nil {
|
||||||
|
// panic("数据库迁移失败: " + err.Error())
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseModel struct {
|
type BaseModel struct {
|
||||||
|
|||||||
@@ -7,12 +7,12 @@
|
|||||||
package jwt
|
package jwt
|
||||||
|
|
||||||
import (
|
import (
|
||||||
conf "go_blog/config"
|
|
||||||
"go_blog/models"
|
"go_blog/models"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 定义jwt载荷
|
// 定义jwt载荷
|
||||||
@@ -44,14 +44,14 @@ func GenToken(id uint64, username string) (string, error) {
|
|||||||
username,
|
username,
|
||||||
}
|
}
|
||||||
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
token, err := tokenClaims.SignedString([]byte(conf.JwtSecretKey.SecretKey))
|
token, err := tokenClaims.SignedString([]byte(viper.GetString("config.JwtSecretKey.secretKey")))
|
||||||
return token, err
|
return token, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证token合法性
|
// 验证token合法性
|
||||||
func ValidateJwtToken(token string) (*UserClaims, error) {
|
func ValidateJwtToken(token string) (*UserClaims, error) {
|
||||||
tokenClaims, err := jwt.ParseWithClaims(token, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
|
tokenClaims, err := jwt.ParseWithClaims(token, &UserClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
return []byte(conf.JwtSecretKey.SecretKey), nil
|
return []byte(viper.GetString("config.JwtSecretKey.secretKey")), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if tokenClaims != nil {
|
if tokenClaims != nil {
|
||||||
|
|||||||
@@ -1,13 +1,43 @@
|
|||||||
package routers // Add the package declaration at the top
|
package routers // Add the package declaration at the top
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go_blog/controllers"
|
"go_blog/controllers"
|
||||||
|
"go_blog/models"
|
||||||
|
"go_blog/serializers"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RegisterRoutes(r *gin.Engine) {
|
func RegisterRoutes(r *gin.Engine) {
|
||||||
|
|
||||||
|
// 注册WebSocket路由
|
||||||
|
r.GET("/events", esSSE)
|
||||||
|
|
||||||
|
r.GET("/page", func(c *gin.Context) {
|
||||||
|
var items []models.Content
|
||||||
|
var pager serializers.Pager
|
||||||
|
pager.InitPager(c)
|
||||||
|
offset := (pager.Page - 1) * pager.PageSize
|
||||||
|
models.DB.Select("*").Offset(offset).Limit(pager.PageSize).Find(&items, "type = ?", "post")
|
||||||
|
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||||
|
"Items": items,
|
||||||
|
"Pager": pager,
|
||||||
|
"Title": "文章列表",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
r.GET("/createcontent", func(c *gin.Context) {
|
||||||
|
c.HTML(http.StatusOK, "content.tmpl", nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
r.GET("/ws", controllers.WebSocketHandler)
|
||||||
|
r.POST("/content", controllers.CreateContentHandler)
|
||||||
|
r.POST("/login", controllers.UsersLoginHandler)
|
||||||
|
r.POST("/register", controllers.UsersRegisterHandler)
|
||||||
|
r.POST("/setinfo", controllers.UsersSetInfoHandler)
|
||||||
|
r.POST("/setpwd", controllers.UsersSetPwdHandler)
|
||||||
// Frontend routes (dynamic themes)
|
// Frontend routes (dynamic themes)
|
||||||
r.GET("/", controllers.Home)
|
r.GET("/", controllers.Home)
|
||||||
r.GET("/post/:id", controllers.ShowPost)
|
r.GET("/post/:id", controllers.ShowPost)
|
||||||
@@ -22,3 +52,35 @@ func RegisterRoutes(r *gin.Engine) {
|
|||||||
// Static files for themes
|
// Static files for themes
|
||||||
r.StaticFS("/themes", http.Dir("web/themes"))
|
r.StaticFS("/themes", http.Dir("web/themes"))
|
||||||
}
|
}
|
||||||
|
func getUserInfo() models.User {
|
||||||
|
user := models.User{
|
||||||
|
Name: "user",
|
||||||
|
Gender: "male",
|
||||||
|
Age: 18,
|
||||||
|
Password: "nothings",
|
||||||
|
PasswordHash: []byte("nothings"),
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
|
||||||
|
func esSSE(c *gin.Context) {
|
||||||
|
w := c.Writer
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/event-stream")
|
||||||
|
w.Header().Set("Cache-Control", "no-cache")
|
||||||
|
w.Header().Set("Connection", "keep-alive")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
|
_, ok := w.(http.Flusher)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
log.Panic("server not support") //浏览器不兼容
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := fmt.Fprintf(w, "id: aaa\ndata: %s\n\n", "dsdf")
|
||||||
|
_, er1r := fmt.Fprintf(w, "event: connecttime\ndata: %s\n\n", "connecttime")
|
||||||
|
if err != nil || er1r != nil {
|
||||||
|
print("error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,19 +22,19 @@ func (tm *ThemeManager) LoadTheme(themeName string) error {
|
|||||||
tm.CurrentTheme = themeName
|
tm.CurrentTheme = themeName
|
||||||
|
|
||||||
// Step 1: Validate the theme by reading theme.yaml
|
// Step 1: Validate the theme by reading theme.yaml
|
||||||
themeConfigPath := fmt.Sprintf("h:/code/go_blog/themes/%s/theme.yaml", themeName)
|
themeConfigPath := fmt.Sprintf("h:/code/go_blog/web/themes/%s/theme.yaml", themeName)
|
||||||
if _, err := os.Stat(themeConfigPath); os.IsNotExist(err) {
|
if _, err := os.Stat(themeConfigPath); os.IsNotExist(err) {
|
||||||
return fmt.Errorf("theme %s does not exist or is invalid", themeName)
|
return fmt.Errorf("theme %s does not exist or is invalid", themeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 2: Precompile all HTML templates and cache them
|
// Step 2: Precompile all HTML templates and cache them
|
||||||
tm.Templates = make(map[string]*template.Template)
|
tm.Templates = make(map[string]*template.Template)
|
||||||
templateDir := fmt.Sprintf("h:/code/go_blog/themes/%s/templates", themeName)
|
templateDir := fmt.Sprintf("h:/code/go_blog/web/themes/%s/tmplates/", themeName)
|
||||||
err := filepath.Walk(templateDir, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(templateDir, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !info.IsDir() && strings.HasSuffix(info.Name(), ".html") {
|
if !info.IsDir() && strings.HasSuffix(info.Name(), ".tmpl") {
|
||||||
tmpl, err := template.ParseFiles(path)
|
tmpl, err := template.ParseFiles(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
164
web/themes/default/templates/index.tmpl
Normal file
164
web/themes/default/templates/index.tmpl
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>{{ .Title}}</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<style type="text/css">
|
||||||
|
body{
|
||||||
|
background: #f8f8f8;
|
||||||
|
}
|
||||||
|
.layui-main {
|
||||||
|
width: 1140px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
width: 1170px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.header .layui-nav {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
.post-list {
|
||||||
|
width: 75%;
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.list-card {
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 20px 20px 10px 20px;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
.sidebar {
|
||||||
|
float: left;
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="layui-main">
|
||||||
|
<h1><a class="logo" href="https://www.hanxiaonuan.cn/">韩小暖的博客</a></h1>
|
||||||
|
</div>
|
||||||
|
<ul class="layui-nav">
|
||||||
|
<li class="layui-nav-item layui-hide-xs layui-this">
|
||||||
|
<a href="https://www.hanxiaonuan.cn/">首页</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="layui-nav-item layui-hide-xs ">
|
||||||
|
<a href="https://www.hanxiaonuan.cn/start-page.html" title="关于">关于</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<span class="layui-nav-bar"></span></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="post-list">
|
||||||
|
{{ range .Items }}
|
||||||
|
<div class="list-card">
|
||||||
|
<div><a href="/post/{{ .Cid }}">
|
||||||
|
<h1>Title: {{ .Title }}</h1>
|
||||||
|
</a>
|
||||||
|
<p> Slug: {{ .Slug }}</p>
|
||||||
|
<p>Text: {{ .Text }}</p>
|
||||||
|
<div>Created: {{ .Created }},</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
<div class="page-navigator">
|
||||||
|
共 {{ .Total }} 条,每页 {{ .PageSize }} 条,当前第 {{ .Page }} 页</div>
|
||||||
|
<div class="pagination">
|
||||||
|
{{ if .PrevPage }}
|
||||||
|
<a href="/page/{{ .PrevPage }}">上一页</a>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .NextPage }}
|
||||||
|
<a href="/page/{{ .NextPage }}">下一页</a>
|
||||||
|
{{ end }}
|
||||||
|
<a href="/">首页</a>
|
||||||
|
<a href="/page/{{ .Total }}">尾页</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar">
|
||||||
|
<div class="column">
|
||||||
|
<h3 class="title-sidebar"><i class="layui-icon"></i> 博客信息</h3>
|
||||||
|
<div class="personal-information">
|
||||||
|
<div class="user">
|
||||||
|
<img src="https://www.hanxiaonuan.cn/usr/uploads/2021/07/3991382612.jpg" alt="韩小暖的博客的头像"
|
||||||
|
class="rounded-circle avatar">
|
||||||
|
<div class="p-2">
|
||||||
|
<a class="user-name" target="_blank" href="https://www.hanxiaonuan.cn/">
|
||||||
|
韩小暖的博客</a>
|
||||||
|
<p class="introduction mt-1">这里是小暖的日常记录,欢迎!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="component">
|
||||||
|
<form class="layui-form" id="search" method="post" action="https://www.hanxiaonuan.cn/"
|
||||||
|
role="search">
|
||||||
|
<div class="layui-inline input">
|
||||||
|
<input type="text" id="s" name="s" class="layui-input" required="" lay-verify="required"
|
||||||
|
placeholder="输入关键字搜索">
|
||||||
|
</div>
|
||||||
|
<div class="layui-inline">
|
||||||
|
<button class="layui-btn layui-btn-sm layui-btn-primary"><i
|
||||||
|
class="layui-icon"></i></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<h3 class="title-sidebar"><i class="layui-icon"></i> 栏目分类</h3>
|
||||||
|
<ul class="layui-row layui-col-space5">
|
||||||
|
<li class="layui-col-md12 layui-col-xs6"><a
|
||||||
|
href="https://www.hanxiaonuan.cn/category/default/"><i class="layui-icon"></i>
|
||||||
|
默认分类<span class="layui-badge layui-bg-gray">12</span></a></li>
|
||||||
|
<li class="layui-col-md12 layui-col-xs6"><a href="https://www.hanxiaonuan.cn/category/zc/"><i
|
||||||
|
class="layui-icon"></i> 日常随想<span class="layui-badge layui-bg-gray">2</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="layui-col-md12 layui-col-xs6"><a
|
||||||
|
href="https://www.hanxiaonuan.cn/category/%E5%85%B3%E4%BA%8E%E6%88%BF%E5%AD%90/"><i
|
||||||
|
class="layui-icon"></i> 关于房子<span class="layui-badge layui-bg-gray">1</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="layui-col-md12 layui-col-xs6"><a
|
||||||
|
href="https://www.hanxiaonuan.cn/category/%E5%B7%A5%E4%BD%9C%E6%97%A5%E5%BF%97/"><i
|
||||||
|
class="layui-icon"></i> 工作日志<span class="layui-badge layui-bg-gray">0</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="tags">
|
||||||
|
<h3 class="title-sidebar"><i class="layui-icon"></i>标签云</h3>
|
||||||
|
<div>
|
||||||
|
<a class="layui-btn layui-btn-xs layui-btn-primary" style="color: rgb(231, 229, 26)"
|
||||||
|
href="https://www.hanxiaonuan.cn/tag/%E5%B0%8F%E6%9A%96/" title="小暖">小暖</a>
|
||||||
|
<a class="layui-btn layui-btn-xs layui-btn-primary" style="color: rgb(125, 196, 207)"
|
||||||
|
href="https://www.hanxiaonuan.cn/tag/%E6%84%9F%E6%82%9F/" title="感悟">感悟</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tags">
|
||||||
|
<h3 class="title-sidebar"><i class="layui-icon"></i>系统</h3>
|
||||||
|
<div>
|
||||||
|
<li class="last"><a href="https://www.hanxiaonuan.cn/admin/">进入后台 (admin)</a></li>
|
||||||
|
<li><a href="https://www.hanxiaonuan.cn/action/logout?_=5b09b0e1048bbd45bdddc56fc43667b2">退出</a>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
0
web/themes/default/theme.yaml
Normal file
0
web/themes/default/theme.yaml
Normal file
Reference in New Issue
Block a user