Initial commit
This commit is contained in:
@@ -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()
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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{}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package web_gin
|
||||
|
||||
type server interface {
|
||||
ListenAndServe() error
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user