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
+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
}