main
han-joker 2 years ago
parent 3f194d2ec9
commit 9e40e7a290

2
.gitignore vendored

@ -0,0 +1,2 @@
.idea
logs

@ -0,0 +1,95 @@
package home
import (
"github.com/gin-gonic/gin"
"github.com/han-joker/moo-layout/api/home/models"
"github.com/han-joker/moo-layout/api/moo/dbm"
"github.com/han-joker/moo-layout/api/moo/logm"
"github.com/han-joker/moo-layout/api/tables"
"net/http"
"strconv"
)
func CategoryGets(c *gin.Context) {
query := models.CategoryQuery{}
if err := c.ShouldBind(&query); err != nil {
logm.Get().Info(err.Error())
}
db := dbm.Get().Model(&tables.Category{})
if len(query.Filters.Status) > 0 {
db.Where("status IN ?", query.Filters.Status)
}
rows := []tables.Category{}
if err := db.Order("sorter asc").Find(&rows).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
nested := make([]models.CategoryNested, 0)
for _, row := range rows {
if row.ID == query.Filters.ID {
nested = append(nested, models.CategoryNested{
Category: row,
})
break
}
}
if len(nested) == 0 {
nested = categoryChildren(rows, query.Filters.ID)
} else if len(nested) == 1 {
nested[0].Children = categoryChildren(rows, query.Filters.ID)
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"rows": nested,
})
}
func categoryChildren(rows []tables.Category, parentID uint) []models.CategoryNested {
children := make([]models.CategoryNested, 0)
for _, row := range rows {
if row.ParentID == parentID {
item := models.CategoryNested{
Category: row,
}
item.Children = categoryChildren(rows, row.ID)
children = append(children, item)
}
}
return children
}
func CategoryGet(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id"))
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row := tables.Category{}
if err := dbm.Get().Where("id=?", id).First(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(200, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
}

@ -0,0 +1,107 @@
package home
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/han-joker/moo-layout/api/home/models"
"github.com/han-joker/moo-layout/api/moo/dbm"
"github.com/han-joker/moo-layout/api/moo/logm"
"github.com/han-joker/moo-layout/api/tables"
"net/http"
"strings"
)
const (
contentsLimit = 10
)
func ContentGets(c *gin.Context) {
query := models.ContentsQuery{}
if err := c.ShouldBind(&query); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
rows := make([]tables.Content, 0)
db := dbm.Get().Model(&tables.Content{})
db.Where("status=?", tables.ContentStatusPublish)
if len(query.Filters.IDs) > 0 {
db.Where("id IN ?", query.Filters.IDs)
}
if query.Filters.CategoryID != 0 {
db.Where("category_id=?", query.Filters.CategoryID)
}
if len(query.Filters.Status) > 0 {
db.Where("status IN ?", query.Filters.Status)
}
if len(query.Filters.Promotes) > 0 {
pc := make([]string, len(query.Filters.Promotes))
pv := make([]interface{}, len(query.Filters.Promotes))
for i, p := range query.Filters.Promotes {
pc[i] = "promotes & ? > 0"
pv[i] = p
}
db.Where(strings.Join(pc, " OR "), pv...)
}
if query.Sorter.Field != "" && query.Sorter.Order != "" {
db.Order(fmt.Sprintf("%s %s", query.Sorter.Field, models.OrderText[query.Sorter.Order])).
Order("updated_at desc")
} else {
db.Order("updated_at desc")
}
db.Count(&query.Pagination.Total)
if query.Pagination.PageSize > 0 {
offset := (query.Pagination.Current - 1) * query.Pagination.PageSize
db.Limit(query.Pagination.PageSize).Offset(offset)
}
db.Find(&rows)
c.JSON(http.StatusOK, gin.H{
"error": nil,
"rows": rows,
"pagination": query.Pagination,
"filters": query.Filters,
"sorter": query.Sorter,
})
}
func ContentGet(c *gin.Context) {
query := models.ContentQuery{}
if err := c.ShouldBind(&query); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row := tables.Content{}
if err := dbm.Get().Where("status=?", tables.ContentStatusPublish).Where("id=?", query.ID).First(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
if query.WithCategory {
dbm.Get().Model(&row).Association("Category").Find(&row.Category)
}
if query.WithUser {
dbm.Get().Model(&row).Association("User").Find(&row.User)
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
}

@ -0,0 +1,19 @@
package models
import "github.com/han-joker/moo-layout/api/tables"
type CategoryQueryFilters struct {
ID uint `json:"id" form:"id"`
Status []int `json:"status" form:"status"`
}
type CategoryQuery struct {
Pagination `json:"pagination" form:"pagination"`
Sorter `json:"sorter" form:"sorter"`
Filters CategoryQueryFilters `json:"filters" form:"filters"`
}
type CategoryNested struct {
tables.Category
Children []CategoryNested `json:"children"`
}

@ -0,0 +1,27 @@
package models
var OrderText = map[string]string{
"ascend": "ASC",
"desend": "DESC",
}
type ContentsQueryFilters struct {
IDs []uint `json:"ids" form:"ids"`
CategoryID uint `json:"categoryId" form:"categoryId"`
Status []uint `json:"status" form:"status"`
Promotes []int `json:"promotes" form:"promotes"`
}
type ContentsQuery struct {
Pagination Pagination `json:"pagination" form:"pagination"`
Sorter Sorter `json:"sorter" form:"sorter"`
Filters ContentsQueryFilters `json:"filters" form:"filters"`
WithCategory bool `json:"withCategory" form:"withCategory"'`
WithUser bool `json:"withUser" form:"withUser"'`
}
type ContentQuery struct {
ID uint `json:"id" form:"id"`
WithCategory bool `json:"withCategory" form:"withCategory"'`
WithUser bool `json:"withUser" form:"withUser"'`
}

@ -0,0 +1,7 @@
package models
type Pagination struct {
Current int `json:"current" form:"current"`
Total int64 `json:"total" form:"total"`
PageSize int `json:"pageSize" form:"pageSize"`
}

@ -0,0 +1,6 @@
package models
type Sorter struct {
Field string `json:"field" form:"field"`
Order string `json:"order" form:"order"`
}

@ -0,0 +1,47 @@
package cachem
var pool = map[string]*cache{}
var optionDefault = Option{}
type cache struct {
option Option
}
type Option struct {
Name string
}
// New 创建对象
func New(option ...Option) *cache {
verifiedOption := optionVerify(option...)
return create(verifiedOption)
}
// Get 存在直接返回,否则创建、存储再返回
func Get(option ...Option) *cache {
verifiedOption := optionVerify(option...)
if !Has(verifiedOption.Name) {
pool[verifiedOption.Name] = create(verifiedOption)
}
return pool[verifiedOption.Name]
}
// Has 存在返回 true否则返回 false
func Has(name string) bool {
_, has := pool[name]
return has
}
func create(option Option) *cache {
return &cache{
option: option,
}
}
func optionVerify(option ...Option) Option {
opt := optionDefault
if len(option) > 0 {
opt = option[0]
}
return opt
}

@ -0,0 +1,30 @@
package cachem
import (
"testing"
)
func TestNew(t *testing.T) {
one := New()
two := New()
if one == two {
t.Error("new() same instance")
}
}
func TestGet(t *testing.T) {
one := Get()
two := Get()
if one != two {
t.Error("get not single instance")
}
three := Get(Option{
Name: "three",
})
four := Get(Option{
Name: "four",
})
if three == four {
t.Error("get() same instance")
}
}

@ -0,0 +1,435 @@
package confm
import (
"errors"
"gopkg.in/yaml.v3"
"log"
"os"
"strconv"
"strings"
)
//变量
//实例池
var pool = map[string]*config{}
//默认选项值
var optionDefault = Option{
Path: "./configs/",
Ext: ".yml",
Sep: ".",
}
//类型
//配置内容
type contents = map[string]map[string]interface{}
type config struct {
option Option
//配置文件内容解码缓存
contents
}
type Option struct {
Name string
Path string
Ext string
Sep string
}
// New 创建对象
func New(option ...Option) *config {
verifiedOption := optionVerify(option...)
return create(verifiedOption)
}
// Get 存在直接返回,否则创建、存储再返回
func Get(option ...Option) *config {
verifiedOption := optionVerify(option...)
if !Has(verifiedOption.Name) {
pool[verifiedOption.Name] = create(verifiedOption)
}
return pool[verifiedOption.Name]
}
// Has 存在返回 true否则返回 false
func Has(name string) bool {
_, has := pool[name]
return has
}
func create(option Option) *config {
return &config{
option: option,
contents: contents{},
}
}
func optionVerify(option ...Option) Option {
opt := optionDefault
if len(option) > 0 {
opt.Name = option[0].Name
if option[0].Path != "" {
opt.Path = option[0].Path
}
if option[0].Ext != "" {
opt.Ext = option[0].Ext
if !strings.HasPrefix(opt.Ext, ".") {
opt.Ext = "." + opt.Ext
}
}
if option[0].Sep != "" {
opt.Sep = option[0].Sep
}
}
return opt
}
//可导出包方法
// Bool Getter|Setter
func (c config) Bool(key string) bool {
valueIf, err := c.value(key)
if err != nil {
return false
}
value, ok := valueIf.(bool)
if !ok {
return false
}
return value
}
func (c config) String(key string) string {
valueIf, err := c.value(key)
if err != nil {
return ""
}
return assertString(valueIf)
}
func (c config) Int(key string) int {
valueIf, err := c.value(key)
if err != nil {
log.Println(err)
return 0
}
value, ok := valueIf.(int)
if !ok {
return 0
}
return value
}
func (c config) Uint(key string) uint {
valueIf, err := c.value(key)
if err != nil {
log.Println(err)
return 0
}
value, ok := valueIf.(uint)
if !ok {
return 0
}
return value
}
func (c config) Float64(key string) float64 {
valueIf, err := c.value(key)
if err != nil {
return 0
}
return assertFloat64(valueIf)
}
func (c config) Float32(key string) float32 {
valueIf, err := c.value(key)
if err != nil {
return 0
}
return assertFloat32(valueIf)
}
func (c config) BoolSlice(key string) []bool {
valueIf, err := c.value(key)
if err != nil {
return []bool{}
}
//
sli, ok := valueIf.([]interface{})
if !ok {
return []bool{}
}
value := make([]bool, len(sli))
for i, vi := range sli {
v, ok := vi.(bool)
if ok {
value[i] = v
}
}
return value
}
func (c config) IntSlice(key string) []int {
valueIf, err := c.value(key)
if err != nil {
return []int{}
}
//
sli, ok := valueIf.([]interface{})
if !ok {
return []int{}
}
value := make([]int, len(sli))
for i, vi := range sli {
v, ok := vi.(int)
if ok {
value[i] = v
}
}
return value
}
func (c config) StringSlice(key string) []string {
valueIf, err := c.value(key)
if err != nil {
return []string{}
}
//
sli, ok := valueIf.([]interface{})
if !ok {
return []string{}
}
value := make([]string, len(sli))
for i, vi := range sli {
value[i] = assertString(vi)
}
return value
}
func (c config) Float64Slice(key string) []float64 {
valueIf, err := c.value(key)
if err != nil {
return []float64{}
}
//
sli, ok := valueIf.([]interface{})
if !ok {
return []float64{}
}
value := make([]float64, len(sli))
for i, vi := range sli {
value[i] = assertFloat64(vi)
}
return value
}
func (c config) Float32Slice(key string) []float32 {
valueIf, err := c.value(key)
if err != nil {
return []float32{}
}
//
sli, ok := valueIf.([]interface{})
if !ok {
return []float32{}
}
value := make([]float32, len(sli))
for i, vi := range sli {
value[i] = assertFloat32(vi)
}
return value
}
func (c config) BoolMap(key string) map[string]bool {
valueIf, err := c.value(key)
if err != nil {
return map[string]bool{}
}
//
mp, ok := valueIf.(map[string]interface{})
if !ok {
return map[string]bool{}
}
value := map[string]bool{}
for k, vk := range mp {
value[k] = false
v, ok := vk.(bool)
if ok {
value[k] = v
}
}
return value
}
func (c config) IntMap(key string) map[string]int {
valueIf, err := c.value(key)
if err != nil {
return map[string]int{}
}
//
mp, ok := valueIf.(map[string]interface{})
if !ok {
return map[string]int{}
}
value := map[string]int{}
for k, vk := range mp {
value[k] = 0
v, ok := vk.(int)
if ok {
value[k] = v
}
}
return value
}
func (c config) Float64Map(key string) map[string]float64 {
valueIf, err := c.value(key)
if err != nil {
return map[string]float64{}
}
//
mp, ok := valueIf.(map[string]interface{})
if !ok {
return map[string]float64{}
}
value := map[string]float64{}
for k, vk := range mp {
value[k] = assertFloat64(vk)
}
return value
}
func (c config) Float32Map(key string) map[string]float32 {
valueIf, err := c.value(key)
if err != nil {
return map[string]float32{}
}
//
mp, ok := valueIf.(map[string]interface{})
if !ok {
return map[string]float32{}
}
value := map[string]float32{}
for k, vk := range mp {
value[k] = assertFloat32(vk)
}
return value
}
func (c config) StringMap(key string) map[string]string {
valueIf, err := c.value(key)
if err != nil {
return map[string]string{}
}
//
mp, ok := valueIf.(map[string]interface{})
if !ok {
return map[string]string{}
}
value := map[string]string{}
for k, vk := range mp {
value[k] = assertString(vk)
}
return value
}
//非导出方法
//利用 key 获取 值
func (c config) value(key string) (interface{}, error) {
filename, keys := c.parseKey(key)
if data, exists := c.contents[filename]; !exists {
var (
content = []byte{}
err error
)
if content, err = c.getContent(filename); err != nil {
log.Println(err)
return nil, err
}
if err = yaml.Unmarshal(content, &data); err != nil {
log.Println(err)
return nil, err
}
c.contents[filename] = data
}
//解析
for i, l, currLevel := 0, len(keys), c.contents[filename]; i < l; i++ {
//最后一级 key
if i == l-1 {
if value, exists := currLevel[keys[i]]; exists {
return value, nil
} else {
return nil, errors.New("key is not exists")
}
}
//不是最后一级,继续解析
var exists bool
currLevel, exists = currLevel[keys[i]].(map[string]interface{})
if !exists {
return nil, errors.New("key is not exists")
}
}
return nil, nil
}
//解析 key
func (c config) parseKey(key string) (string, []string) {
strs := strings.Split(key, c.option.Sep)
if len(strs) == 1 {
strs = append([]string{"app"}, strs...)
}
return strs[0], strs[1:]
}
//读取配置文件
func (c config) getContent(filename string) ([]byte, error) {
return os.ReadFile(c.option.Path + filename + c.option.Ext)
}
//断言 Interface{} to string
func assertString(valueIf interface{}) (value string) {
switch v := valueIf.(type) {
case string:
value = v
case int:
value = strconv.FormatInt(int64(v), 10) //string(v)
case float64:
value = strconv.FormatFloat(v, 'f', -1, 64)
case float32:
value = strconv.FormatFloat(float64(v), 'f', -1, 64)
case bool:
value = strconv.FormatBool(v)
}
return
}
//断言 Interface{} to float32
func assertFloat32(valueIf interface{}) (value float32) {
switch v := valueIf.(type) {
case float64:
value = float32(v)
case float32:
value = v
case int:
value = float32(v)
}
return
}
//断言 Interface{} to float64
func assertFloat64(valueIf interface{}) (value float64) {
switch v := valueIf.(type) {
case float64:
value = v
case float32:
value = float64(v)
case int:
value = float64(v)
}
return
}

@ -0,0 +1,20 @@
package confm
import (
"testing"
)
func TestInstance(t *testing.T) {
m1 := Get()
m2 := Get()
if m1 != m2 {
t.Error("no singleton")
}
}
func BenchmarkInstance(b *testing.B) {
for i := 0; i < b.N; i++ {
//New()
Get()
}
}

@ -0,0 +1,68 @@
package dbm
import (
"fmt"
"github.com/han-joker/moo-layout/api/moo/confm"
"github.com/han-joker/moo-layout/api/moo/logm"
"github.com/han-joker/moo-layout/api/moo/toolm"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
const defaultType = "sqlite"
type DSN = string
var pool = map[DSN]*gorm.DB{}
type Option struct {
DSN
Options []gorm.Option
}
// New 创建对象
func New(options ...Option) *gorm.DB {
option := verifyOption(options)
return create(option)
}
// Get 存在直接返回,否则创建、存储再返回
func Get(options ...Option) *gorm.DB {
option := verifyOption(options)
if !Has(option.DSN) {
pool[option.DSN] = create(option)
}
return pool[option.DSN]
}
func verifyOption(options []Option) Option {
t := toolm.StringDefault(confm.Get().String("app.databaseType"), defaultType)
option := Option{
DSN: confm.Get().String(fmt.Sprintf("%s.DSN", t)),
Options: []gorm.Option{},
}
if len(options) > 0 {
if options[0].DSN != "" {
option.DSN = options[0].DSN
}
if options[0].Options != nil {
option.Options = options[0].Options
}
}
return option
}
// Has 存在返回 true否则返回 false
func Has(dialector DSN) bool {
_, has := pool[dialector]
return has && pool[dialector] != nil
}
func create(option Option) *gorm.DB {
db, err := gorm.Open(sqlite.Open(option.DSN), option.Options...)
if err != nil {
logm.Get().Error(err)
}
return db
}

@ -0,0 +1,233 @@
package logm
import (
"fmt"
toolm2 "github.com/han-joker/moo-layout/api/moo/toolm"
"github.com/sirupsen/logrus"
"io"
"os"
"path"
"time"
)
//常量
//日志文件后缀
const fileExt = ".log"
//日志格式
const (
Text = iota
Json
)
// OutMode 预设值
const (
Console = iota // 控制台
File // 文件,单文件模式
FilePerDay // 每天一个文件
FilePerWeek // 每周一个文件
FilePerMonth // 每月一个文件
FilePerHour // 每小时一个文件
FilePerSize // 文件固定大小
User
)
//变量
//池
var pool = map[string]*log{}
//格式集合
var fmtContainer = []int{Text, Json}
//输出模式
var outModeContainer = []int{Console, File, FilePerDay, FilePerWeek, FilePerMonth, FilePerHour, FilePerSize, User}
//默认选项
var optionDefault = Option{
Fmt: Text,
Caller: false,
OutMode: Console,
Path: "./logs/",
FilePrefix: "moo",
SizeMax: 100 * 1024 * 1024, // 100 M Bytes
Output: os.Stdout,
}
//类型
//日志
type log struct {
*logrus.Logger
option Option
// cache
writers map[string]*os.File
}
// Option 选项
type Option struct {
Name string
Fmt int // Text, Json
Caller bool // true, false
OutMode int // Console, FILE, User
Path string // some path
FilePrefix string //
SizeMax int64
Output io.Writer
}
type Fields = logrus.Fields
// New 创建对象
func New(option ...Option) *log {
verifiedOption := optionVerify(option...)
l := create(verifiedOption)
l.refreshOutMode()
return l
}
// Get 存在直接返回,否则创建、存储再返回
func Get(option ...Option) *log {
verifiedOption := optionVerify(option...)
if !Has(verifiedOption.Name) {
pool[verifiedOption.Name] = create(verifiedOption)
}
pool[verifiedOption.Name].refreshOutMode()
return pool[verifiedOption.Name]
}
// Has 存在返回 true否则返回 false
func Has(name string) bool {
_, has := pool[name]
return has
}
func (l *log) syncLoggerOption() *log {
switch l.option.Fmt {
case Json:
l.Logger.SetFormatter(&logrus.JSONFormatter{})
case Text:
l.Logger.SetFormatter(&logrus.TextFormatter{})
default:
l.Logger.SetFormatter(&logrus.TextFormatter{})
}
l.Logger.SetReportCaller(l.option.Caller)
l.Logger.SetOutput(l.option.Output)
return l
}
func (l *log) refreshOutMode() *log {
// 初始默认值
l.Logger.SetOutput(optionDefault.Output)
filename := ""
switch l.option.OutMode {
case File:
filename = l.option.Path + "/" + l.option.FilePrefix + fileExt
case FilePerHour:
now := time.Now()
filename = l.option.Path + "/" +
l.option.FilePrefix +
fmt.Sprintf("%04d-%02d-%02d-%02d", now.Year(), now.Month(), now.Day(), now.Hour()) +
fileExt
case FilePerDay:
now := time.Now()
filename = l.option.Path + "/" +
l.option.FilePrefix +
fmt.Sprintf("%04d-%02d-%02d", now.Year(), now.Month(), now.Day()) +
fileExt
case FilePerMonth:
now := time.Now()
filename = l.option.Path + "/" +
l.option.FilePrefix +
fmt.Sprintf("%04d-%02d", now.Year(), now.Month()) +
fileExt
case FilePerWeek:
now := time.Now()
year, week := now.ISOWeek()
filename = l.option.Path + "/" +
l.option.FilePrefix +
fmt.Sprintf("%04d-%02d", year, week) +
fileExt
case FilePerSize:
filename = l.option.Path + "/" + l.option.FilePrefix + fileExt
if fileinfo, err := os.Stat(filename); err == nil {
if fileinfo.Size() >= l.option.SizeMax {
basename := path.Base(filename)
if file, exists := l.writers[basename]; exists {
if err := file.Close(); err != nil {
l.Info("can not close file")
} else {
delete(l.writers, basename)
}
}
now := time.Now()
newFilename := l.option.Path + "/" +
l.option.FilePrefix +
fmt.Sprintf("%s", now.Format("2006-01-02-15-04-05")) +
fileExt
if err := os.Rename(filename, newFilename); err != nil {
l.Info("can not rename log file")
}
}
}
case User:
l.Logger.SetOutput(l.option.Output)
default:
l.Logger.SetOutput(optionDefault.Output)
}
if filename != "" {
filepath := path.Dir(filename)
if err := os.MkdirAll(filepath, 0644); err == nil || os.IsExist(err) {
basename := path.Base(filename)
file, exists := l.writers[basename]
if !exists {
if file, err = os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666); err != nil {
l.Info("can not create log file")
}
}
if file != nil {
l.Logger.SetOutput(file)
l.writers[basename] = file
} else {
l.Info("can not open log file")
}
} else {
l.Info("can not make log path")
}
}
return l
}
func create(option Option) *log {
return (&log{
Logger: logrus.New(),
option: option,
writers: map[string]*os.File{},
}).syncLoggerOption()
}
func optionVerify(option ...Option) Option {
opt := optionDefault
if len(option) > 0 {
//设置选项
opt.Name = option[0].Name
if toolm2.IntSliceContains(option[0].Fmt, fmtContainer) {
opt.Fmt = option[0].Fmt
}
opt.Caller = option[0].Caller
if toolm2.IntSliceContains(option[0].OutMode, outModeContainer) {
opt.OutMode = option[0].OutMode
}
opt.Path = option[0].Path
opt.FilePrefix = toolm2.StringDefault(option[0].FilePrefix, opt.Name, optionDefault.FilePrefix)
opt.SizeMax = toolm2.Int64Default(option[0].SizeMax, optionDefault.SizeMax)
if _, ok := option[0].Output.(io.Writer); ok {
opt.Output = option[0].Output
}
}
return opt
}

@ -0,0 +1,54 @@
package logm
import (
"testing"
)
//
func TestNew(t *testing.T) {
one := New()
two := New()
if one == two {
t.Error("new error")
}
ins := New(Option{
OutMode: File,
Path: "./logs",
})
ins.Info("some message")
}
func TestGet(t *testing.T) {
one := Get()
two := Get()
if one != two {
t.Error("get error")
}
}
func TestField(t *testing.T) {
one := Get()
one.WithField("key", []int{1, 2, 3}).Info("Fields")
one.WithFields(Fields{"key": "one", "hello": 123, "xxx": []int{1, 2, 3}}).Info("Fields")
}
func BenchmarkNew(b *testing.B) {
for i := 0; i < b.N; i++ {
New()
}
}
func BenchmarkGet(b *testing.B) {
for i := 0; i < b.N; i++ {
Get()
}
}
func BenchmarkLog_Info(b *testing.B) {
for i := 0; i < b.N; i++ {
ins := Get(Option{
OutMode: FilePerSize,
SizeMax: 0.5 * 1024 * 1024,
Path: "./logs",
})
ins.Info("some message")
}
}

@ -0,0 +1,32 @@
package moo
type server struct {
// server kind
Kind string
}
// 服务单例池
var serverPool = map[string]*server{}
// 单例工厂,标识于 Kind
func Server(kind string) *server {
instance, exists := serverPool[kind]
if !exists {
instance = &server{
Kind: kind,
}
serverPool[kind] = instance
}
return instance
}
func (s *server) Start() {
switch s.Kind {
case "http":
s.StartHttp()
case "websocket":
fallthrough
case "ws":
s.StartWebSocket()
}
}

@ -0,0 +1,11 @@
package moo
import (
"github.com/han-joker/moo-layout/api/moo/confm"
"github.com/han-joker/moo-layout/routers"
)
func (s *server) StartHttp() {
r := routers.RouterInit()
r.Run(confm.Get().String("http.addr"))
}

@ -0,0 +1,41 @@
package moo
import (
"github.com/gorilla/websocket"
"github.com/han-joker/moo-layout/api/moo/confm"
"log"
"net/http"
)
func (s *server) StartWebSocket() {
http.HandleFunc("/", echo)
log.Fatal(http.ListenAndServe(confm.Get().String("websocket.addr"), nil))
}
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func echo(w http.ResponseWriter, r *http.Request) {
c, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer c.Close()
for {
mt, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
log.Printf("recv: %s", message)
err = c.WriteMessage(mt, message)
if err != nil {
log.Println("write:", err)
break
}
}
}

@ -0,0 +1,25 @@
package toolm
func StringDefault(value string, defValues ...string) string {
if value != "" {
return value
}
for _, v := range defValues {
if v != "" {
return v
}
}
return ""
}
func Int64Default(value int64, defValues ...int64) int64 {
if value != 0 {
return value
}
for _, v := range defValues {
if v != 0 {
return v
}
}
return 0
}

@ -0,0 +1,10 @@
package toolm
func IntSliceContains(value int, container []int) bool {
for _, v := range container {
if v == value {
return true
}
}
return false
}

@ -0,0 +1,49 @@
package toolm
import (
"crypto/hmac"
"crypto/sha256"
"fmt"
"math/rand"
"time"
)
const (
CharsAll = 1 << iota
Digit
LowerCase
UpperCase
)
const (
digit = "0123456789"
lowercase = "abcdefghijklmnopqrstuvwxyz"
uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
//octal = "01234567"
//hex = "0123456789abcdefg"
)
func RandString(l, chars int) string {
charset := ""
if chars&CharsAll > 0 {
charset = digit + lowercase + uppercase
}
charsetLen := len(charset)
seededRand := rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, l)
for i := range b {
b[i] = charset[seededRand.Intn(charsetLen)]
}
return string(b)
}
func Sha256HMacString(message, key string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(message))
expectedMAC := mac.Sum(nil)
return fmt.Sprintf("%X", expectedMAC)
}

@ -0,0 +1,265 @@
package panel
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/han-joker/moo-layout/api/moo/dbm"
"github.com/han-joker/moo-layout/api/moo/logm"
"github.com/han-joker/moo-layout/api/panel/models"
"github.com/han-joker/moo-layout/api/tables"
"net/http"
"strconv"
)
func CategoryPost(c *gin.Context) {
req := models.CategoryReq{}
if err := c.ShouldBind(&req); err != nil {
c.JSON(200, gin.H{
"error": err.Error(),
})
return
}
row := &tables.Category{
Name: req.Name,
Description: req.Description,
ParentID: req.ParentID,
Sorter: req.Sorter,
Status: req.Status,
}
if err := dbm.Get().Create(&row).Error; err != nil {
c.JSON(200, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, gin.H{
"error": nil,
"row": row,
})
}
func CategoryPut(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id"))
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row := tables.Category{}
if err := dbm.Get().Where("id=?", id).First(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
req := models.CategoryReq{}
if err := c.ShouldBind(&req); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row.Name = req.Name
row.Description = req.Description
row.Status = req.Status
row.Sorter = req.Sorter
row.ParentID = req.ParentID
if err := dbm.Get().Save(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
}
func CategoryGets(c *gin.Context) {
query := models.CategoryQuery{}
if err := c.ShouldBind(&query); err != nil {
}
rows := []tables.Category{}
if err := dbm.Get().Order("sorter asc").Find(&rows).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
nested := make([]models.CategoryNested, 0)
for _, row := range rows {
if row.ID == query.Filters.ID {
nested = append(nested, models.CategoryNested{
Category: row,
})
break
}
}
if len(nested) == 0 {
nested = categoryChildren(rows, query.Filters.ID)
} else if len(nested) == 1 {
nested[0].Children = categoryChildren(rows, query.Filters.ID)
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"rows": nested,
})
}
func categoryChildren(rows []tables.Category, parentID uint) []models.CategoryNested {
children := make([]models.CategoryNested, 0)
for _, row := range rows {
if row.ParentID == parentID {
item := models.CategoryNested{
Category: row,
}
item.Children = categoryChildren(rows, row.ID)
children = append(children, item)
}
}
return children
}
func CategoryGet(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id"))
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error":err.Error(),
})
return
}
row := tables.Category{}
if err := dbm.Get().Where("id=?", id).First(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
}
func CategoryDelete(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id"))
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row := tables.Category{}
if err := dbm.Get().Where("id=?", id).First(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
childrenSize := int64(0)
if err := dbm.Get().Model(&tables.Category{}).Where("parent_id=?", row.ID).Count(&childrenSize).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
if childrenSize > 0 {
err := errors.New("has children")
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
if err := dbm.Get().Delete(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
return
}
func CategoryCount(c *gin.Context) {
query := models.CategoryQuery{}
if err := c.ShouldBind(&query); err != nil {
}
rows := []tables.Category{}
if err := dbm.Get().Find(&rows).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
count := categoryCount(rows, query)
c.JSON(http.StatusOK, gin.H{
"error": nil,
"count": count,
})
}
func categoryCount(rows []tables.Category, query models.CategoryQuery) int64 {
count := int64(0)
for _, row := range rows {
statusCond := true
if query.Filters.Status > 0 {
statusCond = row.Status == query.Filters.Status
}
if row.ParentID == query.Filters.ParentID && statusCond {
count += 1
count += categoryCount(rows, models.CategoryQuery{
Filters: models.CategoryQueryFilters{
ParentID: row.ID,
Status: query.Filters.Status,
},
})
}
}
return count
}

@ -0,0 +1,260 @@
package panel
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/han-joker/moo-layout/api/moo/dbm"
"github.com/han-joker/moo-layout/api/moo/logm"
"github.com/han-joker/moo-layout/api/panel/models"
"github.com/han-joker/moo-layout/api/tables"
"net/http"
"strconv"
"strings"
"time"
)
const (
contentsLimit = 10
)
func ContentPost(c *gin.Context) {
req := models.ContentReq{}
if err := c.ShouldBind(&req); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row := &tables.Content{
Subject: req.Subject,
Content: req.Content,
Referer: req.Referer,
CategoryID: req.CategoryID,
Cover: req.Cover,
Status: req.Status,
Sorter: req.Sorter,
UserID: 1,
}
if row.Status == tables.ContentStatusPublish {
row.PublishTime = time.Now()
}
for _, p := range req.Promotes {
row.Promotes |= p
}
if err := dbm.Get().Create(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
}
func ContentPut(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id"))
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row := tables.Content{}
if err := dbm.Get().Where("id=?", id).First(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
req := models.ContentReq{}
if err := c.ShouldBind(&req); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row.Subject = req.Subject
row.Content = req.Content
row.Referer = req.Referer
row.CategoryID = req.CategoryID
row.Cover = req.Cover
row.Status = req.Status
row.Sorter = req.Sorter
if row.Status == tables.ContentStatusPublish {
row.PublishTime = time.Now()
}
row.Promotes = 0
for _, p := range req.Promotes {
row.Promotes |= p
}
if err := dbm.Get().Save(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
}
func ContentGets(c *gin.Context) {
query := models.ContentsQuery{}
if err := c.ShouldBind(&query); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
rows := make([]tables.Content, 0)
db := dbm.Get().Model(&tables.Content{})
if len(query.Filters.IDs) > 0 {
db.Where("id IN ?", query.Filters.IDs)
}
if query.Filters.CategoryID != 0 {
db.Where("category_id=?", query.Filters.CategoryID)
}
if len(query.Filters.Status) > 0 {
db.Where("status IN ?", query.Filters.Status)
}
if len(query.Filters.Promotes) > 0 {
pc := make([]string, len(query.Filters.Promotes))
pv := make([]interface{}, len(query.Filters.Promotes))
for i, p := range query.Filters.Promotes {
pc[i] = "promotes & ? > 0"
pv[i] = p
}
db.Where(strings.Join(pc, " OR "), pv...)
}
if query.Sorter.Field != "" && query.Sorter.Order != "" {
db.Order(fmt.Sprintf("%s %s", query.Sorter.Field, models.OrderText[query.Sorter.Order])).
Order("updated_at desc")
} else {
db.Order("updated_at desc")
}
db.Count(&query.Pagination.Total)
if query.Pagination.PageSize > 0 {
offset := (query.Pagination.Current - 1) * query.Pagination.PageSize
db.Limit(query.Pagination.PageSize).Offset(offset)
}
db.Find(&rows)
c.JSON(http.StatusOK, gin.H{
"error": nil,
"rows": rows,
"pagination": query.Pagination,
"filters": query.Filters,
"sorter": query.Sorter,
})
}
func ContentGet(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id"))
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row := tables.Content{}
if err := dbm.Get().Where("id=?", id).First(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
}
func ContentDelete(c *gin.Context) {
id, err := strconv.Atoi(c.Query("id"))
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
row := tables.Content{}
if err := dbm.Get().Where("id=?", id).First(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
if err := dbm.Get().Delete(&row).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"row": row,
})
}
func ContentCount(c *gin.Context) {
query := models.ContentsQuery{}
if err := c.ShouldBind(&query); err != nil {
}
db := dbm.Get().Model(&tables.Content{})
if query.Filters.CategoryID != 0 {
db.Where("category_id=?", query.Filters.CategoryID)
}
if len(query.Filters.Status) != 0 {
db.Where("status IN ?", query.Filters.Status)
}
count := int64(0)
if err := db.Debug().Count(&count).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"count": count,
})
}

@ -0,0 +1,94 @@
package middlewares
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/han-joker/moo-layout/api/moo/confm"
"github.com/han-joker/moo-layout/api/moo/dbm"
"github.com/han-joker/moo-layout/api/moo/logm"
"github.com/han-joker/moo-layout/api/panel/models"
"github.com/han-joker/moo-layout/api/tables"
"net/http"
"strconv"
"strings"
)
func JwtToken(c *gin.Context) {
// before request
header := models.UserJwtTokenHeader{}
if err := c.ShouldBindHeader(&header); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusForbidden, gin.H{
"error": err.Error(),
})
c.Abort()
return
}
tokenString := strings.Replace(header.Authorization, "Bearer ", "", 1)
token, err := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(confm.Get().String("app.signingKey")), nil
})
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusForbidden, gin.H{
"error": err.Error(),
})
c.Abort()
return
}
claims, ok := token.Claims.(*jwt.StandardClaims)
if !ok {
err := errors.New("token claim type error")
logm.Get().Info(err.Error())
c.JSON(http.StatusForbidden, gin.H{
"error": err.Error(),
})
c.Abort()
return
}
if !token.Valid {
err := errors.New("token valid error")
logm.Get().Info(err.Error())
c.JSON(http.StatusForbidden, gin.H{
"error": err.Error(),
})
c.Abort()
return
}
id, err := strconv.ParseUint(claims.Audience, 10, 0)
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusForbidden, gin.H{
"error": err.Error(),
})
c.Abort()
return
}
user := tables.User{}
if err := dbm.Get().Where("id=?", id).First(&user).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusForbidden, gin.H{
"error": err.Error(),
})
c.Abort()
return
}
if user.JWTToken != tokenString {
err := errors.New("token error")
logm.Get().Info(err.Error())
c.JSON(http.StatusForbidden, gin.H{
"error": err.Error(),
})
c.Abort()
return
}
c.Set("user", user)
c.Next()
}

@ -0,0 +1,27 @@
package models
import "github.com/han-joker/moo-layout/api/tables"
type CategoryQueryFilters struct {
ID uint `json:"id" form:"id"`
ParentID uint `json:"parent_id" form:"parent_id"`
Status int `json:"status" form:"status"`
}
type CategoryQuery struct {
Filters CategoryQueryFilters `json:"filters" form:"filters"`
}
type CategoryNested struct {
tables.Category
Children []CategoryNested `json:"children"`
}
type CategoryReq struct {
ID uint `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
ParentID uint `json:"parent_id" form:"parent_id"`
Sorter int `json:"sorter"`
Status int `json:"status"`
}

@ -0,0 +1,33 @@
package models
type ContentReq struct {
ID uint `json:"id" form:"id"`
Subject string `json:"subject" form:"subject"`
Content string `json:"content" form:"content"`
Referer string `json:"referer" form:"referer"`
CategoryID uint `json:"categoryId" form:"categoryId"`
Status int `json:"status" form:"status"`
Sorter int `json:"sorter" form:"sorter"`
Cover string `json:"cover" form:"cover"`
Promotes []int `json:"promotes" form:"promotes"`
}
type ContentsQueryFilters struct {
IDs []uint `json:"ids" form:"ids"`
CategoryID uint `json:"categoryId" form:"categoryId"`
Status []uint `json:"status" form:"status"`
Promotes []int `json:"promotes" form:"promotes"`
}
type ContentsQuery struct {
Pagination Pagination `json:"pagination" form:"pagination"`
Sorter Sorter `json:"sorter" form:"sorter"`
Filters ContentsQueryFilters `json:"filters" form:"filters"`
WithCategory bool `json:"withCategory" form:"withCategory"'`
WithUser bool `json:"withUser" form:"withUser"'`
}
var OrderText = map[string]string{
"ascend": "ASC",
"desend": "DESC",
}

@ -0,0 +1,7 @@
package models
type Pagination struct {
Current int `json:"current" form:"current"`
Total int64 `json:"total" form:"total"`
PageSize int `json:"pageSize" form:"pageSize"`
}

@ -0,0 +1,6 @@
package models
type Sorter struct {
Field string `json:"field" form:"field"`
Order string `json:"order" form:"order"`
}

@ -0,0 +1,18 @@
package models
type UserSignIn struct {
Username string `json:"username" form:"username"`
Password string `json:"password" form:"password"`
AutoSignIn bool `json:"autoSignIn" form:"autoSignIn"`
}
type UserJwtTokenHeader struct {
Authorization string `header:"Authorization" form:"authorization"`
}
type UserChangePassword struct {
ID uint `json:"id" form:"id"`
Password string `json:"password" form:"password"`
NewPassword string `json:"newPassword" form:"newPassword"`
NewPasswordConfirm string `json:"newPasswordConfirm" form:"newPasswordConfirm"`
}

@ -0,0 +1,173 @@
package panel
import (
"crypto/md5"
"errors"
"fmt"
"io"
"net/http"
"os"
"path"
"github.com/gin-gonic/gin"
"github.com/han-joker/moo-layout/api/moo/confm"
"github.com/han-joker/moo-layout/api/moo/logm"
"github.com/han-joker/moo-layout/api/moo/toolm"
)
func uploadConfig(c *gin.Context) {
c.JSONP(http.StatusOK, gin.H{
/* 上传图片配置项 */
"imageActionName": "uploadimage", /* 执行上传图片的action名称 */
"imageFieldName": "upfile", /* 提交的图片表单名称 */
"imageMaxSize": 2048000, /* 上传大小限制单位B */
"imageAllowFiles": []string{".png", ".jpg", ".jpeg", ".gif", ".bmp"}, /* 上传图片格式显示 */
"imageCompressEnable": true, /* 是否压缩图片,默认是true */
"imageCompressBorder": 1600, /* 图片压缩最长边限制 */
"imageInsertAlign": "none", /* 插入的图片浮动方式 */
"imageUrlPrefix": "", /* 图片访问路径前缀 */
"imagePathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
/* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */
/* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */
/* {time} 会替换成时间戳 */
/* {yyyy} 会替换成四位年份 */
/* {yy} 会替换成两位年份 */
/* {mm} 会替换成两位月份 */
/* {dd} 会替换成两位日期 */
/* {hh} 会替换成两位小时 */
/* {ii} 会替换成两位分钟 */
/* {ss} 会替换成两位秒 */
/* 非法字符 \ : * ? " < > | */
/* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */
/* 涂鸦图片上传配置项 */
"scrawlActionName": "uploadscrawl", /* 执行上传涂鸦的action名称 */
"scrawlFieldName": "upfile", /* 提交的图片表单名称 */
"scrawlPathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"scrawlMaxSize": 2048000, /* 上传大小限制单位B */
"scrawlUrlPrefix": "", /* 图片访问路径前缀 */
"scrawlInsertAlign": "none",
/* 截图工具上传 */
"snapscreenActionName": "uploadimage", /* 执行上传截图的action名称 */
"snapscreenPathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"snapscreenUrlPrefix": "", /* 图片访问路径前缀 */
"snapscreenInsertAlign": "none", /* 插入的图片浮动方式 */
/* 抓取远程图片配置 */
"catcherLocalDomain": []string{"127.0.0.1", "localhost", "img.baidu.com"},
"catcherActionName": "catchimage", /* 执行抓取远程图片的action名称 */
"catcherFieldName": "source", /* 提交的图片列表表单名称 */
"catcherPathFormat": "/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"catcherUrlPrefix": "", /* 图片访问路径前缀 */
"catcherMaxSize": 2048000, /* 上传大小限制单位B */
"catcherAllowFiles": []string{".png", ".jpg", ".jpeg", ".gif", ".bmp"}, /* 抓取图片格式显示 */
/* 上传视频配置 */
"videoActionName": "uploadvideo", /* 执行上传视频的action名称 */
"videoFieldName": "upfile", /* 提交的视频表单名称 */
"videoPathFormat": "/ueditor/php/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"videoUrlPrefix": "", /* 视频访问路径前缀 */
"videoMaxSize": 102400000, /* 上传大小限制单位B默认100MB */
"videoAllowFiles": []string{
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid"}, /* 上传视频格式显示 */
/* 上传文件配置 */
"fileActionName": "uploadfile", /* controller里,执行上传视频的action名称 */
"fileFieldName": "upfile", /* 提交的文件表单名称 */
"filePathFormat": "/ueditor/php/upload/file/{yyyy}{mm}{dd}/{time}{rand:6}", /* 上传保存路径,可以自定义保存路径和文件名格式 */
"fileUrlPrefix": "", /* 文件访问路径前缀 */
"fileMaxSize": 51200000, /* 上传大小限制单位B默认50MB */
"fileAllowFiles": []string{
".png", ".jpg", ".jpeg", ".gif", ".bmp",
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml",
}, /* 上传文件格式显示 */
/* 列出指定目录下的图片 */
"imageManagerActionName": "listimage", /* 执行图片管理的action名称 */
"imageManagerListPath": "/ueditor/php/upload/image/", /* 指定要列出图片的目录 */
"imageManagerListSize": 20, /* 每次列出文件数量 */
"imageManagerUrlPrefix": "", /* 图片访问路径前缀 */
"imageManagerInsertAlign": "none", /* 插入的图片浮动方式 */
"imageManagerAllowFiles": []string{".png", ".jpg", ".jpeg", ".gif", ".bmp"}, /* 列出的文件类型 */
/* 列出指定目录下的文件 */
"fileManagerActionName": "listfile", /* 执行文件管理的action名称 */
"fileManagerListPath": "/ueditor/php/upload/file/", /* 指定要列出文件的目录 */
"fileManagerUrlPrefix": "", /* 文件访问路径前缀 */
"fileManagerListSize": 20, /* 每次列出文件数量 */
"fileManagerAllowFiles": []string{
".png", ".jpg", ".jpeg", ".gif", ".bmp",
".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid",
".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml",
}, /* 列出的文件类型 */
})
}
func UploadUeditor(c *gin.Context) {
action := c.Query("action")
switch action {
case "config":
uploadConfig(c)
}
}
func Image(c *gin.Context) {
name := c.DefaultQuery("name", "file")
file, err := c.FormFile(name)
if err != nil {
logm.Get().Info("upload file not found")
c.JSON(http.StatusOK, gin.H{
"error": errors.New("code: upload file not found"),
})
return
}
dst := toolm.StringDefault(confm.Get().String("app.uploadPath"), "./")
dst += "contents/"
if err := os.MkdirAll(dst, 0755); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
f, err := file.Open()
if err != nil {
logm.Get().Info("upload file open failed")
c.JSON(http.StatusOK, gin.H{
"error": errors.New("code: upload file open failed"),
})
return
}
defer f.Close()
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
filename := fmt.Sprintf("%x", h.Sum(nil)) + path.Ext(file.Filename)
dst += filename
if err := c.SaveUploadedFile(file, dst); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"path": "contents/" + filename,
"url": confm.Get().String("app.resourceUrlBase") + "contents/" + filename,
})
}

@ -0,0 +1,175 @@
package panel
import (
"errors"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/han-joker/moo-layout/api/moo/confm"
"github.com/han-joker/moo-layout/api/moo/dbm"
"github.com/han-joker/moo-layout/api/moo/logm"
"github.com/han-joker/moo-layout/api/moo/toolm"
"github.com/han-joker/moo-layout/api/panel/models"
"github.com/han-joker/moo-layout/api/tables"
"gorm.io/gorm"
"net/http"
"strconv"
"time"
)
func UserSignIn(c *gin.Context) {
userSignIn := models.UserSignIn{}
if err := c.ShouldBind(&userSignIn); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
user := tables.User{}
if err := dbm.Get().Where("username = ?", userSignIn.Username).First(&user).Error;
errors.Is(err, gorm.ErrRecordNotFound) {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
if user.Password != toolm.Sha256HMacString(userSignIn.Password, user.PasswordSalt) {
err := errors.New("password error")
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
signingKey := []byte(confm.Get().String("app.signingKey"))
// Create the Claims
claims := &jwt.StandardClaims{
Audience: strconv.FormatUint(uint64(user.ID), 10),
ExpiresAt: time.Now().Add(30*24*3600 * time.Second).UnixNano(),
Issuer: confm.Get().String("app.issuer"),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString(signingKey)
if err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
user.JWTToken = tokenString
if err := dbm.Get().Save(&user).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"user": user,
})
}
func UserCheckJwtToken(c *gin.Context) {
user := tables.User{}
if userInterface, exists := c.Get("user"); !exists {
err := errors.New("user not found")
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
} else {
user = userInterface.(tables.User)
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"user": user,
})
}
func UserSignOut(c *gin.Context) {
user := tables.User{}
if userInterface, exists := c.Get("user"); !exists {
err := errors.New("user not found")
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
} else {
user = userInterface.(tables.User)
}
user.JWTToken = ""
dbm.Get().Save(&user)
c.JSON(http.StatusOK, gin.H{
"error": nil,
"user": user,
})
}
func UserChangePassword(c *gin.Context) {
req := models.UserChangePassword{}
if err := c.ShouldBind(&req); err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
if req.NewPassword != req.NewPasswordConfirm {
err := errors.New("password not equal error")
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
user := tables.User{}
if userInterface, exists := c.Get("user"); !exists {
err := errors.New("user not found")
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
} else {
user = userInterface.(tables.User)
}
if user.Password != toolm.Sha256HMacString(req.Password, user.PasswordSalt) {
err := errors.New("password error")
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
user.Password = toolm.Sha256HMacString(req.NewPassword, user.PasswordSalt)
if err := dbm.Get().Save(&user).Error; err != nil {
logm.Get().Info(err.Error())
c.JSON(http.StatusOK, gin.H{
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"error": nil,
"user": user,
})
}

@ -0,0 +1,18 @@
package tables
const (
CategoryStatusPublish = iota + 1 // 发布
CategoryStatusDraft // 草稿
)
type Category struct {
Model
Name string `gorm:"uniqueIndex" json:"name"`
Description string `gorm:"" json:"description"`
ParentID uint `gorm:"index" json:"parent_id"`
Status int `gorm:"" json:"status"`
Sorter int `gorm:"index" json:"sorter"`
Contents []Content `json:"contents"`
}

@ -0,0 +1,34 @@
package tables
import (
"time"
)
const (
ContentStatusPublish = 1 + iota
ContentStatusDraft
)
const (
ContentPromoteLatest = 1 << iota
ContentPromoteIndexImage
)
type Content struct {
Model
Subject string `gorm:"" json:"subject"`
Content string `gorm:"" json:"content"`
Cover string `gorm:"" json:"cover"`
Referer string `gorm:"" json:"referer"`
PublishTime time.Time `gorm:"" json:"publish_time"`
Status int `gorm:"" json:"status"` // 1 草稿2 发布
UserID uint `gorm:"index" json:"user_id"`
CategoryID uint `gorm:"index" json:"categoryId"`
Sorter int `gorm:"index" json:"sorter"`
Promotes int `gorm:"" json:"promotes"`
User User `json:"user"`
Category Category `json:"category"`
}

@ -0,0 +1,13 @@
package tables
import (
"gorm.io/gorm"
"time"
)
type Model struct {
ID uint `gorm:"primarykey" json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
}

@ -0,0 +1,14 @@
package tables
import (
"gorm.io/gorm"
)
type Role struct {
gorm.Model
Name string `gorm:"uniqueIndex;" json:"name"`
Key string `gorm:"uniqueIndex;" json:"key"`
Users []User `gorm:"many2many:role_user"`
}

@ -0,0 +1,15 @@
package tables
type User struct {
Model
Username string `gorm:"uniqueIndex;" json:"username"`
Password string `gorm:"" json:"-"`
PasswordSalt string `gorm:"" json:"-"`
JWTToken string `gorm:"" json:"jwtToken"`
Name string `gorm:"uniqueIndex;" json:"name"`
Email string `gorm:"uniqueIndex;" json:"email"`
Telephone string `gorm:"uniqueIndex;" json:"telephone"`
Roles []Role `gorm:"many2many:role_user;" json:"roles"`
}

@ -0,0 +1,14 @@
module github.com/han-joker/moo-layout
go 1.16
require (
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.7.4
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/gorilla/websocket v1.4.2
github.com/sirupsen/logrus v1.8.1
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.21.14
)

@ -0,0 +1,94 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.21.14 h1:NAR9A/3SoyiPVHouW/rlpMUZvuQZ6Z6UYGz+2tosSQo=
gorm.io/gorm v1.21.14/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=

@ -0,0 +1,10 @@
package main
import (
"github.com/han-joker/moo-layout/api/moo"
)
func main() {
//启动 websocket服务
moo.Server("http").Start()
}

@ -0,0 +1,102 @@
package main
import (
"github.com/han-joker/moo-layout/api/moo/confm"
"github.com/han-joker/moo-layout/api/moo/toolm"
"github.com/han-joker/moo-layout/api/tables"
"log"
"time"
)
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func main() {
// connect
db, err := gorm.Open(sqlite.Open(confm.Get().String("sqlite.DSN")), &gorm.Config{})
if err != nil {
log.Fatal(err)
}
// all tables
tbs := []interface{}{
&tables.User{},
&tables.Role{},
&tables.Category{},
&tables.Content{},
}
// drop tables
if err := db.Migrator().DropTable(tbs...); err != nil {
log.Fatal(err)
}
// create tables
if err := db.AutoMigrate(tbs...); err != nil {
log.Fatal(err)
}
// basic seed
// role
root := tables.Role{Name: "根", Key: "root"}
if result := db.Create(&root); result.Error != nil {
log.Fatal(result.Error)
}
// user
users := []tables.User{
{Username: "admin", Password: "", PasswordSalt: "", Name: "Admin", Roles: []tables.Role{root}},
}
for i, _ := range users {
users[i].PasswordSalt = toolm.RandString(6, toolm.CharsAll)
users[i].Password = toolm.Sha256HMacString("admin", users[i].PasswordSalt)
}
if result := db.Create(&users); result.Error != nil {
log.Fatal(result.Error)
}
// category
cats := []tables.Category{
{Model: tables.Model{ID: 1}, Name: "未分类", Status: tables.CategoryStatusPublish},
{Model: tables.Model{ID: 2}, Name: "新闻公告", Status: tables.CategoryStatusPublish},
{Model: tables.Model{ID: 3}, Name: "游戏介绍", Status: tables.CategoryStatusPublish},
{Model: tables.Model{ID: 4}, Name: "游戏特色", Status: tables.CategoryStatusPublish},
{Model: tables.Model{ID: 5}, Name: "特定内容", Status: tables.CategoryStatusPublish},
{Model: tables.Model{ID: 6}, Name: "怪物介绍", Status: tables.CategoryStatusPublish, ParentID: 3},
{Model: tables.Model{ID: 7}, Name: "NPC介绍", Status: tables.CategoryStatusPublish, ParentID: 3},
{Model: tables.Model{ID: 8}, Name: "武功介绍", Status: tables.CategoryStatusPublish, ParentID: 3},
{Model: tables.Model{ID: 9}, Name: "系统介绍", Status: tables.CategoryStatusPublish, ParentID: 3},
{Model: tables.Model{ID: 10}, Name: "装备预览", Status: tables.CategoryStatusPublish, ParentID: 3},
{Model: tables.Model{ID: 11}, Name: "武功", Status: tables.CategoryStatusPublish, ParentID: 4},
{Model: tables.Model{ID: 12}, Name: "离线挂机", Status: tables.CategoryStatusPublish, ParentID: 4},
{Model: tables.Model{ID: 13}, Name: "境界", Status: tables.CategoryStatusPublish, ParentID: 4},
{Model: tables.Model{ID: 14}, Name: "副本", Status: tables.CategoryStatusPublish, ParentID: 4},
}
if result := db.Create(&cats); result.Error != nil {
log.Fatal(result.Error)
}
contents := []tables.Content{
{Subject: "家长监护工程", Content: "<p>家长监护工程(请自行编辑)</p>", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "联系我们", Content: "<p>联系我们(请自行编辑)</p>", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "商务合作", Content: "<p>商务合作(请自行编辑)</p>", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "用户纠纷处理", Content: "<p>用户纠纷处理(请自行编辑)</p>", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "用户协议", Content: "<p>用户协议(请自行编辑)</p>", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "QRCode", Content: "<p>QRCode请自行编辑</p>", Cover: "contents/d45d56cdc40ff9ad6245b6a30b5c10a9.png", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "App Store 下载", Content: "<p>App Store 下载(请自行编辑)</p>", Cover: "contents/6ac066a9023d893deb030d8d04faaf4d.png", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "安卓下载", Content: "<p>安卓下载(请自行编辑)</p>", Cover: "contents/1d6e08823657c551a66bb25693e69678.png", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "Logo", Content: "<p>Logo请自行编辑</p>", Cover: "contents/fbc658d2b9c8209a0eaa9b9721e426c2.png", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "底部文字", Content: "健康游戏忠告:抵制不良游戏,拒绝盗版游戏。注意自我保护,谨防受骗上当。适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "Video Logo", Content: "<p>请自行编辑</p>", Cover: "contents/41af642d078b5cd5049f8eb4ee2472ed.png", CategoryID: 5, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "维护公告", Content: "<p>测试内容1</p>", Cover: "contents/e842edc945d88a4f3fa49a39a22c8920.png", CategoryID: 2, Status: tables.ContentStatusPublish, PublishTime: time.Now(), Promotes: tables.ContentPromoteIndexImage | tables.ContentPromoteLatest},
{Subject: "攻击类", Content: "<p>测试内容2</p>", Cover: "contents/25510c2d7e8c3dcc8c4e17b5787e1765.png", CategoryID: 11, Status: tables.ContentStatusPublish, PublishTime: time.Now(), Promotes: tables.ContentPromoteIndexImage},
{Subject: "步伐类", Content: "<p>测试内容3</p>", Cover: "contents/ca2d7c6650b11683d5af710583c5dbf0.png", CategoryID: 11, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "境界系统", Content: "<p>测试内容4</p>", Cover: "contents/9b17f01564d8a207de42e114aa5c9b2e.png", CategoryID: 13, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
{Subject: "出入境", Content: "<p>测试内容5</p>", Cover: "contents/cac4665a7441c3ceeea3b19bdd3d0ffd.png", CategoryID: 13, Status: tables.ContentStatusPublish, PublishTime: time.Now()},
}
if result := db.Create(&contents); result.Error != nil {
log.Fatal(result.Error)
}
log.Println("Migrated")
}
Loading…
Cancel
Save