Initial commit

This commit is contained in:
yan.y
2024-03-27 23:25:08 +08:00
commit 0884384e91
127 changed files with 9353 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
package web
import (
"fmt"
"strings"
"pet-house.com/core/server/database"
"pet-house.com/core/server/viper_server"
)
// Initialize initialize
func Initialize() error {
var cover string
if IsExist() {
fmt.Println("Your web config is initialized , reinitialized web will cover your web config.")
fmt.Println("Did you want to do it ? [Y/N]")
fmt.Scanln(&cover)
switch strings.ToUpper(cover) {
case "Y":
case "N":
return nil
default:
}
}
err := Remove()
if err != nil {
return err
}
err = initConfig()
if err != nil {
return err
}
fmt.Println("web iris-admin initialized finished!")
return nil
}
func initConfig() error {
var dbType string
fmt.Println("Please choose your database type: ")
fmt.Println("1. mysql (only support mysql now)")
fmt.Scanln(&dbType)
switch dbType {
case "1":
CONFIG.System.DbType = "mysql"
if err := database.Init(); err != nil {
return err
}
default:
CONFIG.System.DbType = "mysql"
if err := database.Init(); err != nil {
return err
}
}
var systemTimeFormat, systemAddr string
fmt.Println("Please input your system timeformat: ")
fmt.Printf("System timeformat is '%s'\n", CONFIG.System.TimeFormat)
fmt.Scanln(&systemTimeFormat)
if systemTimeFormat != "" {
CONFIG.System.TimeFormat = systemTimeFormat
}
fmt.Println("Please input your system addr: ")
fmt.Printf("System addr is '%s'\n", CONFIG.System.Addr)
fmt.Scanln(&systemAddr)
if systemAddr != "" {
CONFIG.System.Addr = systemAddr
}
err := viper_server.Init(getViperConfig())
if err != nil {
return err
}
return nil
}
+153
View File
@@ -0,0 +1,153 @@
package common
import (
"fmt"
"github.com/bwmarrin/snowflake"
"go.uber.org/zap"
"pet-house.com/core/g"
"pet-house.com/core/helper/str"
"pet-house.com/core/migration"
"pet-house.com/core/server/casbin"
"pet-house.com/core/server/database"
"pet-house.com/core/server/operation"
"pet-house.com/core/server/web"
"pet-house.com/core/server/web/web_gin"
"pet-house.com/core/server/web/web_iris"
"pet-house.com/core/server/zap_server"
)
// BeforeTestMainGin
func BeforeTestMainGin(party func(wi *web_gin.WebServer), seed func(wi *web_gin.WebServer, mc *migration.MigrationCmd)) (string, *web_gin.WebServer) {
fmt.Println("+++++ TEST BEGAIN +++++")
zap_server.CONFIG.LogInConsole = true
zap_server.Recover()
dbType := g.TestDbType
if dbType != "" {
web.CONFIG.System.DbType = dbType
}
web.Recover()
node, _ := snowflake.NewNode(1)
uuid := str.Join("gin", "_", node.Generate().String())
database.CONFIG.DbName = uuid
mysqlPwd := g.TestMysqlPwd
if mysqlPwd != "" {
database.CONFIG.Password = mysqlPwd
}
mysqlAddr := g.TestMysqlAddr
if mysqlAddr != "" {
database.CONFIG.Path = mysqlAddr
}
database.CONFIG.LogMode = true
database.Recover()
if database.Instance() == nil {
fmt.Println("database instance is nil")
return uuid, nil
}
wi := web_gin.Init()
party(wi)
web.StartTest(wi)
mc := migration.New()
fmt.Println("++++++ add data to database ++++++")
seed(wi, mc)
err := mc.Migrate()
if err != nil {
fmt.Printf("migrate fail: [%s]", err.Error())
return uuid, nil
}
err = mc.Seed()
if err != nil {
fmt.Printf("seed fail: [%s]", err.Error())
return uuid, nil
}
return uuid, wi
}
// BeforeTestMainIris
func BeforeTestMainIris(party func(wi *web_iris.WebServer), seed func(wi *web_iris.WebServer, mc *migration.MigrationCmd)) (string, *web_iris.WebServer) {
fmt.Println("+++++ TEST BEGAIN +++++")
zap_server.CONFIG.LogInConsole = true
zap_server.Recover()
dbType := g.TestDbType
if dbType != "" {
web.CONFIG.System.DbType = dbType
}
web.Recover()
node, _ := snowflake.NewNode(1)
uuid := str.Join("iris", "_", node.Generate().String())
database.CONFIG.DbName = uuid
mysqlPwd := g.TestMysqlPwd
if mysqlPwd != "" {
database.CONFIG.Password = mysqlPwd
}
mysqlAddr := g.TestMysqlAddr
if mysqlAddr != "" {
database.CONFIG.Path = mysqlAddr
}
database.CONFIG.LogMode = true
database.Recover()
if database.Instance() == nil {
fmt.Println("database instance is nil")
return uuid, nil
}
wi := web_iris.Init()
party(wi)
web.StartTest(wi)
mc := migration.New()
fmt.Println("++++++ add datas to database ++++++")
seed(wi, mc)
err := mc.Migrate()
if err != nil {
fmt.Printf("migrate fail: [%s]", err.Error())
return uuid, nil
}
err = mc.Seed()
if err != nil {
fmt.Printf("seed fail: [%s]", err.Error())
return uuid, nil
}
return uuid, wi
}
func AfterTestMain(uuid string, isDelDb bool) {
defer fmt.Println("++++++++ AFTER END ++++++++")
if isDelDb {
err := database.DorpDB(database.CONFIG.BaseDsn(), "mysql", uuid)
if err != nil {
zap_server.ZAPLOG.Error("delete database fail", zap.String("uuid", uuid), zap.String("err", err.Error()))
}
}
fmt.Println("++++++++ DELETE DATABASE ++++++++")
db, err := database.Instance().DB()
if err != nil {
zap_server.ZAPLOG.Error(str.Join("get database instance fail:", err.Error()))
}
if db != nil {
db.Close()
}
defer zap_server.Remove()
defer operation.Remove()
defer casbin.Remove()
defer web.Remove()
defer database.Remove()
}
+206
View File
@@ -0,0 +1,206 @@
package web
import (
"encoding/json"
"fmt"
"path/filepath"
"strconv"
"github.com/spf13/viper"
"pet-house.com/core/g"
"pet-house.com/core/helper/str"
"pet-house.com/core/server/viper_server"
)
var CONFIG = Web{
FileMaxSize: 1024, // upload file size limit 1024M
SessionTimeout: 60, // session timeout after 60 Minute
Cors: Cors{
AccessOrigin: "*",
AccessHeaders: "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-U-Id",
AccessMethods: "POST,GET",
AccessExposeHeaders: "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type",
AccessCredentials: "true",
},
Except: Route{
Uri: "",
Method: "",
},
Menu: Route{
Uri: "",
Method: "",
},
System: System{
Tls: false,
Level: "debug",
Addr: "127.0.0.1:8085",
DbType: "mysql",
TimeFormat: "2006-01-02 15:04:05",
},
Limit: Limit{
Disable: true,
Limit: 0,
Burst: 5,
},
Captcha: Captcha{
KeyLong: 4,
ImgWidth: 240,
ImgHeight: 80,
},
}
type Web struct {
FileMaxSize int64 `mapstructure:"file-max-size" json:"file-max-size" yaml:"file-max-siz"`
SessionTimeout int64 `mapstructure:"session-timeout" json:"session-timeout" yaml:"session-timeout"`
Except Route `mapstructure:"except" json:"except" yaml:"except"`
Menu Route `mapstructure:"menu" json:"menu" yaml:"menu"`
System System `mapstructure:"system" json:"system" yaml:"system"`
Limit Limit `mapstructure:"limit" json:"limit" yaml:"limit"`
Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
Cors Cors `mapstructure:"cors" json:"cors" yaml:"cors"`
}
type Cors struct {
AccessOrigin string `mapstructure:"access-origin" json:"burst" access-origin:"access-origin"`
AccessHeaders string `mapstructure:"access-headers" json:"access-headers" yaml:"access-headers"`
AccessMethods string `mapstructure:"access-methods" json:"access-methods" yaml:"access-methods"`
AccessExposeHeaders string `mapstructure:"access-expose-headers" json:"access-expose-headers" yaml:"access-expose-headers"`
AccessCredentials string `mapstructure:"access-credentials" json:"access-credentials" yaml:"access-credentials"`
}
type Route struct {
Uri string `mapstructure:"uri" json:"uri" yaml:"uri"`
Method string `mapstructure:"method" json:"method" yaml:"method"`
}
type Captcha struct {
KeyLong int `mapstructure:"key-long" json:"key-long" yaml:"key-long"`
ImgWidth int `mapstructure:"img-width" json:"img-width" yaml:"img-width"`
ImgHeight int `mapstructure:"img-height" json:"img-height" yaml:"img-height"`
}
type Limit struct {
Disable bool `mapstructure:"disable" json:"disable" yaml:"disable"`
Limit float64 `mapstructure:"limit" json:"limit" yaml:"limit"`
Burst int `mapstructure:"burst" json:"burst" yaml:"burst"`
}
type System struct {
Tls bool `mapstructure:"tls" json:"tls" yaml:"tls"` // debug,release,test
Level string `mapstructure:"level" json:"level" yaml:"level"` // debug,release,test
Addr string `mapstructure:"addr" json:"addr" yaml:"addr"`
StaticPrefix string `mapstructure:"static-prefix" json:"static-prefix" yaml:"static-prefix"`
WebPrefix string `mapstructure:"web-prefix" json:"web-prefix" yaml:"web-prefix"`
DbType string `mapstructure:"db-type" json:"db-type" yaml:"db-type"`
TimeFormat string `mapstructure:"time-format" json:"time-format" yaml:"time-format"`
}
// SetDefaultAddrAndTimeFormat
func SetDefaultAddrAndTimeFormat() {
if CONFIG.System.Addr == "" {
CONFIG.System.Addr = "127.0.0.1:8085"
}
if CONFIG.System.TimeFormat == "" {
CONFIG.System.TimeFormat = "2006-01-02 15:04:05"
}
}
// ToStaticUrl
func ToStaticUrl(uri string) string {
path := filepath.Join(CONFIG.System.Addr, CONFIG.System.StaticPrefix, uri)
if CONFIG.System.Tls {
return filepath.ToSlash(str.Join("https://", path))
}
return filepath.ToSlash(str.Join("http://", path))
}
// IsExist config file is exist
func IsExist() bool {
return getViperConfig().IsFileExist()
}
// Remove remove config file
func Remove() error {
return getViperConfig().Remove()
}
// Recover
func Recover() error {
b, err := json.Marshal(CONFIG)
if err != nil {
return err
}
return getViperConfig().Recover(b)
}
// getViperConfig get viper config
func getViperConfig() viper_server.ViperConfig {
maxSize := strconv.FormatInt(CONFIG.FileMaxSize, 10)
sessionTimeout := strconv.FormatInt(CONFIG.SessionTimeout, 10)
keyLong := strconv.FormatInt(int64(CONFIG.Captcha.KeyLong), 10)
imgWidth := strconv.FormatInt(int64(CONFIG.Captcha.ImgWidth), 10)
imgHeight := strconv.FormatInt(int64(CONFIG.Captcha.ImgHeight), 10)
limit := strconv.FormatInt(int64(CONFIG.Limit.Limit), 10)
burst := strconv.FormatInt(int64(CONFIG.Limit.Burst), 10)
disable := strconv.FormatBool(CONFIG.Limit.Disable)
tls := strconv.FormatBool(CONFIG.System.Tls)
configName := "web"
return viper_server.ViperConfig{
Debug: true,
Directory: g.ConfigDir,
Name: configName,
Type: g.ConfigType,
Watch: func(vi *viper.Viper) error {
if err := vi.Unmarshal(&CONFIG); err != nil {
return fmt.Errorf("get Unarshal error: %v", err)
}
// watch config file change
vi.SetConfigName(configName)
return nil
},
//
Default: []byte(`
{
"file-max-size": ` + maxSize + `,
"session-timeout": ` + sessionTimeout + `,
"except":
{
"uri": "` + CONFIG.Except.Uri + `",
"method": "` + CONFIG.Except.Method + `"
},
"menu":
{
"uri": "` + CONFIG.Menu.Uri + `",
"method": "` + CONFIG.Menu.Method + `"
},
"cors":
{
"access-origin": "` + CONFIG.Cors.AccessOrigin + `",
"access-headers": "` + CONFIG.Cors.AccessHeaders + `",
"access-methods": "` + CONFIG.Cors.AccessMethods + `",
"access-expose-headers": "` + CONFIG.Cors.AccessExposeHeaders + `",
"access-credentials": "` + CONFIG.Cors.AccessCredentials + `"
},
"captcha":
{
"key-long": ` + keyLong + `,
"img-width": ` + imgWidth + `,
"img-height": ` + imgHeight + `
},
"limit":
{
"limit": ` + limit + `,
"disable": ` + disable + `,
"burst": ` + burst + `
},
"system":
{
"tls": ` + tls + `,
"level": "` + CONFIG.System.Level + `",
"addr": "` + CONFIG.System.Addr + `",
"db-type": "` + CONFIG.System.DbType + `",
"time-format": "` + CONFIG.System.TimeFormat + `"
}
}`),
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"config": "charset=utf8mb4\u0026parseTime=True\u0026loc=Local",
"db-name": "iris-admin",
"log-mode": false,
"log-zap": "error",
"max-idle-conns": 0,
"max-open-conns": 0,
"password": "",
"path": "127.0.0.1:3306",
"username": "root"
}
+36
View File
@@ -0,0 +1,36 @@
{
"captcha": {
"img-height": 80,
"img-width": 240,
"key-long": 4
},
"cors": {
"access-credentials": "true",
"access-expose-headers": "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type",
"access-headers": "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-U-Id",
"access-methods": "POST,GET",
"access-origin": "*"
},
"except": {
"method": "",
"uri": ""
},
"file-max-size": 1024,
"limit": {
"burst": 5,
"disable": true,
"limit": 0
},
"menu": {
"method": "",
"uri": ""
},
"session-timeout": 60,
"system": {
"addr": "127.0.0.1:8085",
"db-type": "mysql",
"level": "debug",
"time-format": "2006-01-02 15:04:05",
"tls": false
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"director": "logs",
"encode-level": "LowercaseColorLevelEncoder",
"format": "console",
"level": "debug",
"link-name": "latest_log",
"log-in-console": false,
"prefix": "[IRIS-ADMIN]",
"show-line": true,
"stacktrace-key": "stacktrace"
}
+55
View File
@@ -0,0 +1,55 @@
package web
import (
"testing"
)
func TestIsExist(t *testing.T) {
t.Run("test web config IsExist function", func(t *testing.T) {
if !IsExist() {
t.Errorf("config's files is not exist.")
}
})
t.Run("Test Remove function", func(t *testing.T) {
if err := Remove(); err != nil {
t.Error(err)
}
if IsExist() {
t.Errorf("config's files remove is fail.")
}
})
}
func TestSetDefaultAddrAndTimeFormat(t *testing.T) {
CONFIG.System.Addr = ""
CONFIG.System.TimeFormat = ""
t.Run("test set defualt addr and time format", func(t *testing.T) {
SetDefaultAddrAndTimeFormat()
if CONFIG.System.Addr != "127.0.0.1:8085" {
t.Errorf("applyURI want %s but get %s", "127.0.0.1:8085", CONFIG.System.Addr)
}
if CONFIG.System.TimeFormat != "2006-01-02 15:04:05" {
t.Errorf("applyURI want %s but get %s", "2006-01-02 15:04:05", CONFIG.System.TimeFormat)
}
})
}
func TestToStaticUrl(t *testing.T) {
SetDefaultAddrAndTimeFormat()
CONFIG.System.StaticPrefix = "/admin"
t.Run("test to static url with tls", func(t *testing.T) {
CONFIG.System.Tls = true
staticPath := ToStaticUrl("/uploads/123.png")
if staticPath != "https://127.0.0.1:8085/admin/uploads/123.png" {
t.Errorf("applyURI want %s but get %s", "https://127.0.0.1:8085/admin/uploads/123.png", staticPath)
}
})
t.Run("test to static url with tls", func(t *testing.T) {
CONFIG.System.Tls = false
staticPath := ToStaticUrl("/uploads/123.png")
if staticPath != "http://127.0.0.1:8085/admin/uploads/123.png" {
t.Errorf("applyURI want %s but get %s", "https://127.0.0.1:8085/admin/uploads/123.png", staticPath)
}
})
}
+46
View File
@@ -0,0 +1,46 @@
package web
import (
"pet-house.com/core/server/viper_server"
"pet-house.com/core/server/zap_server"
)
// init
func init() {
viper_server.Init(getViperConfig())
}
type WebBaseFunc interface {
AddWebStatic(staticAbsPath, webPrefix string, paths ...string)
AddUploadStatic(staticAbsPath, webPrefix string)
InitRouter() error
Run()
}
// WebFunc
// - GetTestClient
// - GetTestLogin
// - AddWebStatic
// - AddUploadStatic
// - Run
type WebFunc interface {
WebBaseFunc
}
// Start
func Start(wf WebFunc) {
err := wf.InitRouter()
if err != nil {
zap_server.ZAPLOG.Error(err.Error())
return
}
wf.Run()
}
// StartTest
func StartTest(wf WebFunc) {
err := wf.InitRouter()
if err != nil {
zap_server.ZAPLOG.Error(err.Error())
}
}
+151
View File
@@ -0,0 +1,151 @@
package web_gin
import (
"errors"
"fmt"
"github.com/mattn/go-colorable"
"net/http"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/gin-gonic/gin"
"pet-house.com/core/helper/arr"
"pet-house.com/core/helper/dir"
"pet-house.com/core/helper/str"
"pet-house.com/core/server/web"
"pet-house.com/core/server/web/web_gin/middleware"
)
var ErrAuthDriverEmpty = errors.New("auth driver initialize fail")
// WebServer
// - app gin.Engine
// - idleConnsClosed
// - addr
// - timeFormat
// - staticPrefix
type WebServer struct {
app *gin.Engine
server
addr string
timeFormat string
webStatics []WebStatic
}
type WebStatic struct {
Prefix string
IndexFile []byte
}
// Init
func Init() *WebServer {
gin.SetMode(web.CONFIG.System.Level)
app := gin.Default()
if web.CONFIG.System.Tls {
app.Use(middleware.LoadTls())
}
app.Use(middleware.Cors())
registerValidation()
gin.DefaultWriter = colorable.NewColorableStdout()
web.SetDefaultAddrAndTimeFormat()
return &WebServer{
app: app,
addr: web.CONFIG.System.Addr,
timeFormat: web.CONFIG.System.TimeFormat,
}
}
// NoRoute for 404 http status
func (ws *WebServer) NoRoute() {
if len(ws.webStatics) == 0 {
return
}
ws.app.NoRoute(func(ctx *gin.Context) {
// excepte for /v0 /v1 and so on
reg := `^/v[0-9]+$|^(/v[0-9]+)/`
ok, _ := regexp.MatchString(reg, ctx.Request.RequestURI)
if ok {
ctx.Writer.WriteHeader(http.StatusNotFound)
ctx.Writer.Flush()
return
}
var indexFile []byte
for _, wp := range ws.webStatics {
// match /admin or /admin/***
reg := str.Join("^", wp.Prefix, "$|^(", wp.Prefix, ")/")
ok, err := regexp.MatchString(reg, ctx.Request.RequestURI)
if err != nil || !ok {
continue
}
indexFile = wp.IndexFile
}
ctx.Writer.WriteHeader(http.StatusOK)
ctx.Writer.Write(indexFile)
ctx.Writer.Header().Add("Accept", "text/html")
ctx.Writer.Flush()
})
}
// GetEngine return *gin.Engine
func (ws *WebServer) GetEngine() *gin.Engine {
return ws.app
}
// AddWebStatic
func (ws *WebServer) AddWebStatic(staticAbsPath, webPrefix string, paths ...string) {
webPrefixs := strings.Split(web.CONFIG.System.WebPrefix, ",")
wp := arr.NewCheckArrayType(2)
for _, webPrefix := range webPrefixs {
wp.Add(webPrefix)
}
if wp.Check(webPrefix) {
return
}
favicon := filepath.Join(staticAbsPath, "favicon.ico")
index := filepath.Join(staticAbsPath, "index.html")
ws.app.Static(str.Join(webPrefix, "/favicon.ico"), favicon)
ws.app.StaticFile(webPrefix, index)
if len(paths) > 0 {
for _, path := range paths {
static := filepath.Join(staticAbsPath, path)
ws.app.Static(path, static)
}
}
web.CONFIG.System.WebPrefix = str.Join(web.CONFIG.System.WebPrefix, ",", webPrefix)
file, _ := dir.ReadBytes(index)
webStatic := WebStatic{
Prefix: webPrefix,
IndexFile: file,
}
ws.webStatics = append(ws.webStatics, webStatic)
}
// AddUploadStatic
func (ws *WebServer) AddUploadStatic(webPrefix, staticAbsPath string) {
ws.app.StaticFS(webPrefix, http.Dir(staticAbsPath))
web.CONFIG.System.StaticPrefix = webPrefix
}
// Run
func (ws *WebServer) Run() {
ws.NoRoute()
s := initServer(web.CONFIG.System.Addr, ws.app)
time.Sleep(10 * time.Microsecond)
fmt.Printf("默认监听地址: http://%s\n", web.CONFIG.System.Addr)
s.ListenAndServe()
}
+39
View File
@@ -0,0 +1,39 @@
package web_gin
import (
"io/ioutil"
"net/http"
"testing"
"time"
"pet-house.com/core/server/database"
"pet-house.com/core/server/web"
"pet-house.com/core/server/zap_server"
)
func TestStart(t *testing.T) {
defer zap_server.Remove()
defer web.Remove()
defer database.Remove()
web.CONFIG.System.Addr = "127.0.0.1:18088"
go func() {
web.Start(Init())
}()
time.Sleep(3 * time.Second)
t.Run("test web start", func(t *testing.T) {
resp, err := http.Get("http://127.0.0.1:18088")
if err != nil {
t.Errorf("test web start get %v", err)
}
defer resp.Body.Close()
_, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("test web start get %v", err)
}
if resp.StatusCode != http.StatusNotFound {
t.Errorf("test web start want [%d] but get [%d]", http.StatusNotFound, resp.StatusCode)
}
})
}
@@ -0,0 +1,24 @@
package middleware
import (
"github.com/gin-gonic/gin"
"net/http"
"pet-house.com/core/server/web"
)
// Cors
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", web.CONFIG.Cors.AccessOrigin)
c.Header("Access-Control-Allow-Headers", web.CONFIG.Cors.AccessHeaders)
c.Header("Access-Control-Allow-Methods", web.CONFIG.Cors.AccessMethods)
c.Header("Access-Control-Expose-Headers", web.CONFIG.Cors.AccessExposeHeaders)
c.Header("Access-Control-Allow-Credentials", web.CONFIG.Cors.AccessCredentials)
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
c.Next()
}
}
@@ -0,0 +1,24 @@
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/unrolled/secure"
)
// LoadTls
func LoadTls() gin.HandlerFunc {
return func(c *gin.Context) {
middleware := secure.New(secure.Options{
SSLRedirect: true,
SSLHost: "127.0.0.1:443",
})
err := middleware.Process(c.Writer, c.Request)
if err != nil {
fmt.Println(err)
return
}
c.Next()
}
}
+20
View File
@@ -0,0 +1,20 @@
package request
// Paging common input parameter structure
type PageInfo struct {
Page int `json:"page" form:"page" binding:"required"`
PageSize int `json:"pageSize" form:"pageSize" binding:"required"`
OrderBy string `json:"orderBy" form:"orderBy"`
SortBy string `json:"sortBy" form:"sortBy"`
}
// Find by id structure
type IdBinding struct {
Id uint `json:"id" uri:"id" form:"id" binding:"required"`
}
type IdsBinding struct {
Ids []uint `json:"ids" form:"ids" binding:"required,dive,required"`
}
type Empty struct{}
+76
View File
@@ -0,0 +1,76 @@
package response
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
)
const (
ResponseOkMessage = "OK"
ResponseErrorMessage = "FAIL"
)
type Response struct {
Code int `json:"status"`
Data interface{} `json:"data,omitempty"`
Msg string `json:"message"`
}
func Result(code int, data interface{}, msg string, ctx *gin.Context) {
ctx.JSON(http.StatusOK, Response{code, data, msg})
}
func Ok(ctx *gin.Context) {
Result(http.StatusOK, map[string]interface{}{}, ResponseOkMessage, ctx)
}
func OkWithMessage(message string, ctx *gin.Context) {
Result(http.StatusOK, map[string]interface{}{}, message, ctx)
}
func OkWithData(data interface{}, ctx *gin.Context) {
Result(http.StatusOK, data, ResponseOkMessage, ctx)
}
func OkWithDetailed(data interface{}, message string, ctx *gin.Context) {
Result(http.StatusOK, data, message, ctx)
}
func Fail(ctx *gin.Context) {
Result(http.StatusBadRequest, map[string]interface{}{}, ResponseErrorMessage, ctx)
}
func UnauthorizedFailWithMessage(message string, ctx *gin.Context) {
Result(http.StatusUnauthorized, map[string]interface{}{}, message, ctx)
}
func UnauthorizedFailWithDetailed(data interface{}, message string, ctx *gin.Context) {
Result(http.StatusUnauthorized, data, message, ctx)
}
func ForbiddenFailWithMessage(message string, ctx *gin.Context) {
Result(http.StatusForbidden, map[string]interface{}{}, message, ctx)
}
func FailWithMessage(message string, ctx *gin.Context) {
Result(http.StatusBadRequest, map[string]interface{}{}, message, ctx)
}
func FailWithDetailed(data interface{}, message string, ctx *gin.Context) {
Result(http.StatusBadRequest, data, message, ctx)
}
type PageResult struct {
List interface{} `json:"list,omitempty"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"pageSize"`
}
type BaseResponse struct {
Id uint `json:"id"`
CreatedAt *time.Time `json:"createdAt"`
UpdatedAt *time.Time `json:"updatedAt"`
}
+90
View File
@@ -0,0 +1,90 @@
package web_gin
import (
"net/http"
"path/filepath"
"strings"
limit "github.com/aviddiviner/gin-limit"
"github.com/gin-contrib/pprof"
"github.com/gin-gonic/gin"
"pet-house.com/core/helper/arr"
"pet-house.com/core/server/web"
"pet-house.com/core/server/web/web_gin/middleware"
)
func (ws *WebServer) GetRouterGroup(relativePath string) *gin.RouterGroup {
return ws.app.Group(relativePath)
}
// InitRouter
func (ws *WebServer) InitRouter() error {
ws.app.Use(limit.MaxAllowed(50))
if web.CONFIG.System.Level == "debug" {
pprof.Register(ws.app)
}
router := ws.app.Group("/")
{
router.Use(middleware.Cors())
router.GET("/v0/version", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "IRIS-ADMIN is running!!!")
})
}
return nil
}
// GetSources
// - PermRoutes
// - NoPermRoutes
func (ws *WebServer) GetSources() ([]map[string]string, []map[string]string) {
methodExcepts := strings.Split(web.CONFIG.Except.Method, ";")
uriExcepts := strings.Split(web.CONFIG.Except.Uri, ";")
methodMenus := strings.Split(web.CONFIG.Menu.Method, ";")
uriMenus := strings.Split(web.CONFIG.Menu.Uri, ";")
routeLen := len(ws.app.Routes())
permRoutes := make([]map[string]string, 0, routeLen)
noPermRoutes := make([]map[string]string, 0, routeLen)
for _, r := range ws.app.Routes() {
bases := strings.Split(filepath.Base(r.Handler), ".")
if len(bases) != 2 {
continue
}
path := filepath.ToSlash(filepath.Clean(r.Path))
route := map[string]string{
"path": path,
"desc": bases[1],
"group": bases[0],
"method": r.Method,
"is_menu": "0",
}
if len(methodMenus) > 0 && len(uriMenus) > 0 && len(methodMenus) == len(uriMenus) {
for i := 0; i < len(methodMenus); i++ {
if strings.EqualFold(r.Method, strings.ToLower(methodMenus[i])) && strings.EqualFold(path, strings.ToLower(uriMenus[i])) {
route["is_menu"] = "1"
}
}
}
httpStatusType := arr.NewCheckArrayType(4)
httpStatusType.AddMutil(http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete)
if !httpStatusType.Check(r.Method) {
noPermRoutes = append(noPermRoutes, route)
continue
}
if len(methodExcepts) > 0 && len(uriExcepts) > 0 && len(methodExcepts) == len(uriExcepts) {
for i := 0; i < len(methodExcepts); i++ {
if strings.EqualFold(r.Method, strings.ToLower(methodExcepts[i])) && strings.EqualFold(path, strings.ToLower(uriExcepts[i])) {
noPermRoutes = append(noPermRoutes, route)
continue
}
}
}
permRoutes = append(permRoutes, route)
}
return permRoutes, noPermRoutes
}
+5
View File
@@ -0,0 +1,5 @@
package web_gin
type server interface {
ListenAndServe() error
}
+22
View File
@@ -0,0 +1,22 @@
package web_gin
import (
"fmt"
"syscall"
"time"
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)
func initServer(address string, router *gin.Engine) server {
s := endless.NewServer(address, router)
s.BeforeBegin = func(add string) {
fmt.Printf("Actual pid is %d\n", syscall.Getpid())
// save it somehow
}
s.ReadHeaderTimeout = 10 * time.Millisecond
s.WriteTimeout = 10 * time.Second
s.MaxHeaderBytes = 1 << 20
return s
}
+16
View File
@@ -0,0 +1,16 @@
package web_gin
import (
"time"
"github.com/fvbock/endless"
"github.com/gin-gonic/gin"
)
func initServer(address string, router *gin.Engine) server {
s := endless.NewServer(address, router)
s.ReadHeaderTimeout = 10 * time.Millisecond
s.WriteTimeout = 10 * time.Second
s.MaxHeaderBytes = 1 << 20
return s
}
+18
View File
@@ -0,0 +1,18 @@
package web_gin
import (
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func initServer(address string, router *gin.Engine) server {
return &http.Server{
Addr: address,
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
}
+19
View File
@@ -0,0 +1,19 @@
package web_gin
import (
"github.com/gin-gonic/gin/binding"
"pet-house.com/core/server/web"
)
func registerValidation() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("dev-required", validateDevRequired)
}
}
var validateDevRequired validator.Func = func(fl validator.FieldLevel) bool {
if web.CONFIG.System.Level == "release" {
return fl.Field().String() != ""
}
return true
}
@@ -0,0 +1,11 @@
{
"config": "charset=utf8mb4\u0026parseTime=True\u0026loc=Local",
"db-name": "iris-admin",
"log-mode": false,
"log-zap": "error",
"max-idle-conns": 0,
"max-open-conns": 0,
"password": "",
"path": "127.0.0.1:3306",
"username": "root"
}
+36
View File
@@ -0,0 +1,36 @@
{
"captcha": {
"img-height": 80,
"img-width": 240,
"key-long": 4
},
"cors": {
"access-credentials": "true",
"access-expose-headers": "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type",
"access-headers": "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-U-Id",
"access-methods": "POST,GET",
"access-origin": "*"
},
"except": {
"method": "",
"uri": ""
},
"file-max-size": 1024,
"limit": {
"burst": 5,
"disable": true,
"limit": 0
},
"menu": {
"method": "",
"uri": ""
},
"session-timeout": 60,
"system": {
"addr": "127.0.0.1:8085",
"db-type": "mysql",
"level": "debug",
"time-format": "2006-01-02 15:04:05",
"tls": false
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"director": "logs",
"encode-level": "LowercaseColorLevelEncoder",
"format": "console",
"level": "debug",
"link-name": "latest_log",
"log-in-console": false,
"prefix": "[IRIS-ADMIN]",
"show-line": true,
"stacktrace-key": "stacktrace"
}
+123
View File
@@ -0,0 +1,123 @@
package web_iris
import (
stdContext "context"
"errors"
"github.com/go-playground/validator/v10"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/context"
"strings"
"time"
"github.com/kataras/iris/v12/middleware/recover"
"pet-house.com/core/helper/arr"
"pet-house.com/core/helper/str"
"pet-house.com/core/server/web"
"pet-house.com/core/server/web/web_iris/middleware"
)
var ErrAuthDriverEmpty = errors.New("auth driver initialize fail")
// WebServer
// - app iris application
// - idleConnsClosed
// - addr
// - timeFormat
// - staticPrefix
type WebServer struct {
app *iris.Application
idleConnsClosed chan struct{}
parties []Party
addr string
timeFormat string
}
// Party
// - perfix
// - partyFunc
type Party struct {
Perfix string
PartyFunc func(index iris.Party)
}
func Init() *WebServer {
app := iris.New()
if web.CONFIG.System.Tls {
app.Use(middleware.LoadTls())
}
app.Use(recover.New())
app.Validator = validator.New()
app.Logger().SetLevel(web.CONFIG.System.Level)
idleConnsClosed := make(chan struct{})
iris.RegisterOnInterrupt(func() {
timeout := 10 * time.Second
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)
defer cancel()
app.Shutdown(ctx) // close all hosts
close(idleConnsClosed)
})
// 自定义应用程序配置
_ = iris.Configuration{
DisablePathCorrection: true,
DisableBodyConsumptionOnUnmarshal: true,
Charset: "UTF-8",
}
app.Configure(iris.WithCharset("UTF-8"), iris.WithOptimizations)
web.SetDefaultAddrAndTimeFormat()
return &WebServer{
app: app,
addr: web.CONFIG.System.Addr,
timeFormat: web.CONFIG.System.TimeFormat,
idleConnsClosed: idleConnsClosed,
}
}
func (ws *WebServer) GetEngine() *iris.Application {
return ws.app
}
func (ws *WebServer) AddModule(parties ...Party) {
ws.parties = append(ws.parties, parties...)
}
func (ws *WebServer) AddFrontFunc(f func(ctx *context.Context)) {
ws.app.Use(f)
}
func (ws *WebServer) AddWebStatic(staticAbsPath, webPrefix string, paths ...string) {
webPrefixs := strings.Split(web.CONFIG.System.WebPrefix, ",")
wp := arr.NewCheckArrayType(2)
for _, webPrefix := range webPrefixs {
wp.Add(webPrefix)
}
if wp.Check(webPrefix) {
return
}
fsOrDir := iris.Dir(staticAbsPath)
opt := iris.DirOptions{
IndexName: "index.html",
SPA: true,
}
ws.app.HandleDir(webPrefix, fsOrDir, opt)
web.CONFIG.System.WebPrefix = str.Join(web.CONFIG.System.WebPrefix, ",", webPrefix)
}
func (ws *WebServer) AddUploadStatic(webPrefix, staticAbsPath string) {
fsOrDir := iris.Dir(staticAbsPath)
ws.app.HandleDir(webPrefix, fsOrDir)
web.CONFIG.System.StaticPrefix = webPrefix
}
func (ws *WebServer) Run() {
ws.app.Listen(
ws.addr,
iris.WithoutInterruptHandler,
iris.WithoutServerError(iris.ErrServerClosed),
iris.WithOptimizations,
iris.WithTimeFormat(ws.timeFormat),
)
<-ws.idleConnsClosed
}
+39
View File
@@ -0,0 +1,39 @@
package web_iris
import (
"io/ioutil"
"net/http"
"testing"
"time"
"pet-house.com/core/server/database"
"pet-house.com/core/server/web"
"pet-house.com/core/server/zap_server"
)
func TestRun(t *testing.T) {
defer zap_server.Remove()
defer web.Remove()
defer database.Remove()
web.CONFIG.System.Addr = "127.0.0.1:18085"
go func() {
web.Start(Init())
}()
time.Sleep(3 * time.Second)
t.Run("test web run", func(t *testing.T) {
resp, err := http.Get("http://127.0.0.1:18085/v0/version")
if err != nil {
t.Errorf("test web start get %v", err)
}
defer resp.Body.Close()
s, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Errorf("test web start get %v", err)
}
if string(s) != "IRIS-ADMIN is running!!!" {
t.Errorf("test web start want %s but get %s", "Not Found", string(s))
}
})
}
@@ -0,0 +1,17 @@
package middleware
import (
"github.com/iris-contrib/middleware/cors"
"github.com/kataras/iris/v12/context"
)
// CrsAuth
func CrsAuth() context.Handler {
return cors.New(cors.Options{
AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts.
AllowedMethods: []string{"POST", "GET"},
AllowedHeaders: []string{"*"},
ExposedHeaders: []string{"Accept", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"},
AllowCredentials: true,
})
}
@@ -0,0 +1,15 @@
package middleware
import (
"github.com/iris-contrib/middleware/secure"
"github.com/kataras/iris/v12"
)
// LoadTls
func LoadTls() iris.Handler {
middleware := secure.New(secure.Options{
SSLRedirect: true,
SSLHost: "127.0.0.1:443",
})
return middleware.Handler
}
+102
View File
@@ -0,0 +1,102 @@
package web_iris
import (
"github.com/kataras/iris/v12"
"net/http"
"strings"
"time"
"github.com/kataras/iris/v12/middleware/pprof"
"github.com/kataras/iris/v12/middleware/rate"
"github.com/kataras/iris/v12/middleware/recover"
"pet-house.com/core/helper/arr"
"pet-house.com/core/server/web"
"pet-house.com/core/server/web/web_iris/middleware"
)
// InitRouter
func (ws *WebServer) InitRouter() error {
app := ws.app.Party("/").AllowMethods(iris.MethodOptions)
{
app.Get("/v0/version", func(ctx iris.Context) {
ctx.WriteString("IRIS-ADMIN is running!!!")
})
app.UseRouter(middleware.CrsAuth())
app.UseRouter(recover.New())
if !web.CONFIG.Limit.Disable {
limitV1 := rate.Limit(web.CONFIG.Limit.Limit, web.CONFIG.Limit.Burst, rate.PurgeEvery(time.Minute, 5*time.Minute))
app.Use(limitV1)
}
if web.CONFIG.System.Level == "debug" {
debug := func(index iris.Party) {
index.Get("/", func(ctx iris.Context) {
ctx.HTML("<h1>请点击<a href='/debug/pprof'>这里</a>打开调试页面")
})
index.Any("/pprof", pprof.New())
index.Any("/pprof/{action:path}", pprof.New())
}
app.PartyFunc("/debug", debug)
}
for _, party := range ws.parties {
app.PartyFunc(party.Perfix, party.PartyFunc)
}
}
// http test must build
if err := ws.app.Build(); err != nil {
return err
}
return nil
}
// GetSources
// - PermRoutes
// - NoPermRoutes
func (ws *WebServer) GetSources() ([]map[string]string, []map[string]string) {
methodExcepts := strings.Split(web.CONFIG.Except.Method, ";")
uris := strings.Split(web.CONFIG.Except.Uri, ";")
methodMenus := strings.Split(web.CONFIG.Menu.Method, ";")
uriMenus := strings.Split(web.CONFIG.Menu.Uri, ";")
routeLen := len(ws.app.GetRoutes())
permRoutes := make([]map[string]string, 0, routeLen)
noPermRoutes := make([]map[string]string, 0, routeLen)
for _, r := range ws.app.GetRoutes() {
route := map[string]string{
"path": r.Path,
"name": r.Name,
"act": r.Method,
}
if len(methodMenus) > 0 && len(uriMenus) > 0 && len(methodMenus) == len(uriMenus) {
for i := 0; i < len(methodMenus); i++ {
if strings.EqualFold(r.Method, strings.ToLower(methodMenus[i])) && strings.EqualFold(r.Path, strings.ToLower(uriMenus[i])) {
route["is_menu"] = "1"
}
}
}
httpStatusType := arr.NewCheckArrayType(4)
httpStatusType.AddMutil(http.MethodGet, http.MethodPost)
if !httpStatusType.Check(r.Method) {
noPermRoutes = append(noPermRoutes, route)
continue
}
if len(methodExcepts) > 0 && len(uris) > 0 && len(methodExcepts) == len(uris) {
for i := 0; i < len(methodExcepts); i++ {
if strings.EqualFold(r.Method, strings.ToLower(methodExcepts[i])) && strings.EqualFold(r.Path, strings.ToLower(uris[i])) {
noPermRoutes = append(noPermRoutes, route)
continue
}
}
}
permRoutes = append(permRoutes, route)
}
return permRoutes, noPermRoutes
}
@@ -0,0 +1,23 @@
package validate
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// ValidRequest
func ValidRequest(err interface{}) []string {
var errs []string
if err == nil {
return errs
}
if validateErrs, ok := err.(validator.ValidationErrors); ok {
for _, e := range validateErrs {
e := e
sErr := fmt.Errorf("%s param error: %v", e.Namespace(), e.Value())
errs = append(errs, sErr.Error())
}
}
return errs
}