diff --git a/.gitignore b/.gitignore index f4d432a..b785c2f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,11 @@ # Dependency directories (remove the comment below to include it) # vendor/ +demo1 +*.exe +logs/ +go.sum +.DS_Store +.vscode/ +resource/ + diff --git a/consul/consul.go b/consul/consul.go new file mode 100644 index 0000000..3d7dd55 --- /dev/null +++ b/consul/consul.go @@ -0,0 +1,167 @@ +package consul + +import ( + "errors" + "fmt" + "math/rand" + "time" + + consulapi "github.com/hashicorp/consul/api" +) + +var ( + _client *consulapi.Client + conf *Config +) + +type Config struct { + Address string +} + +// Init 初始化consul连接 +func Init(config *Config) error { + if config == nil { + config = &Config{ + Address: "127.0.0.1:8500", + } + } + conf = config + if conf.Address == "" { + conf.Address = "127.0.0.1:8500" + } + return nil +} + +func New() (*consulapi.Client, error) { + if _client != nil { + return _client, nil + } + + // 创建连接consul服务配置 + config := consulapi.DefaultConfig() + config.Address = conf.Address + client, err := consulapi.NewClient(config) + if err != nil { + return nil, err + } + _client = client + return client, nil +} + +// RegisterAPI 注册api服务到consul +func RegisterAPI(name string, addr string, port int, tags ...string) error { + client, err := New() + if err != nil { + return err + } + if client == nil { + return errors.New("consul 实例空") + } + + // 创建注册到consul的服务到 + registration := new(consulapi.AgentServiceRegistration) + registration.ID = fmt.Sprintf("%s-%s:%d", name, addr, port) + registration.Name = name + registration.Port = port + registration.Tags = tags + registration.Address = addr + + // 增加consul健康检查回调函数 + check := new(consulapi.AgentServiceCheck) + check.HTTP = fmt.Sprintf("http://%s:%d/health/check", registration.Address, registration.Port) + check.Timeout = "5s" + check.Interval = "5s" + check.DeregisterCriticalServiceAfter = "30s" // 故障检查失败30s后 consul自动将注册服务删除 + registration.Check = check + + // 注册服务到consul + if err := client.Agent().ServiceRegister(registration); err != nil { + return err + } + return nil +} + +// DeRegister 取消consul注册的服务 +func DeRegister(name string, addr string, port int) error { + client, err := New() + if err != nil { + return err + } + if client == nil { + return errors.New("consul 实例空") + } + client.Agent().ServiceDeregister(fmt.Sprintf("%s-%s:%d", name, addr, port)) + return nil +} + +// FindNode 查找节点 +func FindNode(servicename, tag string) (*consulapi.AgentService, error) { + client, err := New() + if err != nil { + return nil, err + } + + if client == nil { + return nil, errors.New("consul 实例空") + } + services, _, err := client.Health().Service(servicename, tag, true, nil) + if err != nil { + return nil, err + } + l := len(services) + if l == 0 { + return nil, nil + } + if l == 1 { + return services[0].Service, nil + } + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return services[r.Intn(l)%l].Service, nil +} + +// CheckHeath 健康检查 +func CheckHeath(serviceid string) error { + client, err := New() + if err != nil { + return err + } + if client == nil { + return errors.New("consul 实例空") + } + // 健康检查 + // a, b, _ := client.Agent().AgentHealthServiceByID(serviceid) + return nil +} + +// KVPut test +func KVPut(key string, values *[]byte, flags uint64) (*consulapi.WriteMeta, error) { + client, err := New() + if err != nil { + return nil, err + } + if client == nil { + return nil, errors.New("consul 实例空") + } + + return client.KV().Put(&consulapi.KVPair{Key: key, Flags: flags, Value: *values}, nil) +} + +// KVGet 获取值 +func KVGet(key string, flags uint64) (*[]byte, error) { + client, err := New() + if err != nil { + return nil, err + } + if client == nil { + return nil, errors.New("consul 实例空") + } + + // KV get值 + data, _, _ := client.KV().Get(key, nil) + if data != nil { + return &data.Value, nil + } + + return nil, nil +} diff --git a/gin/auth-filter.go b/gin/auth-filter.go new file mode 100644 index 0000000..6a8458b --- /dev/null +++ b/gin/auth-filter.go @@ -0,0 +1,84 @@ +package gin + +import ( + "encoding/json" + + "github.com/gin-gonic/gin" + "myschools.me/suguo/snippet/redis" +) + +//从redis中认证用户 +func AuthUser() gin.HandlerFunc { + return func(c *gin.Context) { + token := c.GetHeader("Authorization") + claims := cacheGet(&token) + if claims == nil { + c.Abort() + return + } + c.Set("user", claims) + c.Next() + } +} + +//从redis中获取用户信息,最佳实践经验建议把此代码放service层 +func cacheGet(token *string) interface{} { + var user interface{} + b, err := redis.GetBytes(token) + if err != nil { + return nil + } + if err := json.Unmarshal(*b, &user); err != nil { + return nil + } + return &user +} + +// gin拦截,基于微服务的拦截 +// func AuthUserBS() gin.HandlerFunc { +// return func(c *gin.Context) { +// token := c.GetHeader("Authorization") +// claims := userAuthWithGrpc(&token) +// if claims == nil { +// yy.RespUnauth(c, "token无效或过期,请重新登录", nil, nil) +// c.Abort() +// } +// c.Set("user", claims) +// c.Next() +// } +// } + +// func userAuthWithGrpc(token *string) *yy.UserClaims { +// srv, err := consul.FindService("oauth", "v1") +// if err != nil { +// logrus.WithFields(logrus.Fields{ +// "func": "userAuthWithGrpc", +// }).Errorf("consul.FindServer: %s", err.Error()) +// return nil +// } +// defer srv.Close() +// client := pb.NewCertificationClient(srv) +// resp, err := client.Auth(context.Background(), &pb.CertificationAuthRequest{ +// Token: *token, +// }) +// if err != nil { +// logrus.WithFields(logrus.Fields{ +// "func": "userAuthWithGrpc", +// }).Errorf("client.Auth: %s", err.Error()) +// return nil +// } +// if resp.Result == "ok" { +// r := &yy.UserClaims{} +// if err := json.Unmarshal(resp.Data.Value, r); err != nil { +// logrus.WithFields(logrus.Fields{ +// "func": "userAuthWithGrpc", +// }).Errorf("json.Unmarshal: %s", err.Error()) +// return nil +// } +// return r +// } +// logrus.WithFields(logrus.Fields{ +// "func": "userAuthWithGrpc", +// }).Warnln("nil") +// return nil +// } diff --git a/gin/config.go b/gin/config.go new file mode 100644 index 0000000..0ff824b --- /dev/null +++ b/gin/config.go @@ -0,0 +1,11 @@ +package gin + +// GIN 配置 +type Config struct { + RootPath string + Addr string + Port int + Ssl bool + SslPem string + SslKey string +} diff --git a/gin/gin.go b/gin/gin.go new file mode 100644 index 0000000..3c56504 --- /dev/null +++ b/gin/gin.go @@ -0,0 +1,66 @@ +package gin + +import ( + "fmt" + "log" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/unrolled/secure" +) + +func Service(conf *Config) *Config { + if conf == nil { + conf = &Config{ + RootPath: "/", + Addr: "0.0.0.0", + Port: 8080, + Ssl: false, + SslPem: "server.pem", + SslKey: "server.key", + } + } + go func() { + router := gin.New() + routerSetup(router, &conf.RootPath) + + if conf.Ssl { + router.Use(tlsHandler(conf)) + } + + s := &http.Server{ + Addr: fmt.Sprintf("%s:%d", conf.Addr, conf.Port), + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + log.Printf("start service on %s", fmt.Sprintf("%s:%d", conf.Addr, conf.Port)) + + if conf.Ssl { + log.Fatal(s.ListenAndServeTLS(conf.SslPem, conf.SslKey)) + } else { + log.Fatal(s.ListenAndServe()) + } + }() + return conf +} + +func tlsHandler(conf *Config) gin.HandlerFunc { + return func(c *gin.Context) { + secureMiddleware := secure.New(secure.Options{ + SSLRedirect: true, + SSLHost: ":" + strconv.Itoa(conf.Port), + }) + err := secureMiddleware.Process(c.Writer, c.Request) + + // If there was an error, do not continue. + if err != nil { + return + } + + c.Next() + } +} diff --git a/gin/router.go b/gin/router.go new file mode 100644 index 0000000..90054bc --- /dev/null +++ b/gin/router.go @@ -0,0 +1,34 @@ +package gin + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "myschools.me/campus/demo-1/handler" +) + +// 路由配置 +func routerSetup(router *gin.Engine, rootpath *string) { + router.Use(gin.Recovery()) + router.GET(`/health/check`, handler.HealthCheck) + + r := router.Group(fmt.Sprintf("/%s", *rootpath)) + { + r.POST(`/register`) + r.GET(`/accountcheck/:accname`) + r.POST(`/login`) + r.POST(`/forgot`) + } + + ug := router.Group(`/user`) + { + ug.GET(`/choose/:orgid`) + ug.GET(`/detail`) + ug.POST(`/update`) + } + hell := router.Group("token") + { + hell.GET("verify", handler.TokenVerify) + + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1f1f1ae --- /dev/null +++ b/go.mod @@ -0,0 +1,53 @@ +module myschools.me/campus/demo-1 + +go 1.19 + +require ( + github.com/gin-gonic/gin v1.9.0 + github.com/hashicorp/consul/api v1.20.0 + github.com/unrolled/secure v1.13.0 + myschools.me/campus/campus-core v0.1.68 + myschools.me/suguo/snippet v1.0.4 +) + +require ( + github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect + github.com/bytedance/sonic v1.8.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/fatih/color v1.9.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.11.2 // indirect + github.com/goccy/go-json v0.10.0 // indirect + github.com/gomodule/redigo v1.8.9 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.1 // indirect + github.com/hashicorp/go-hclog v0.12.0 // indirect + github.com/hashicorp/go-immutable-radix v1.0.0 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/serf v0.10.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-colorable v0.1.6 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.9 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/crypto v0.5.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/gorm v1.24.3 // indirect +) diff --git a/handler/token-handler.go b/handler/token-handler.go new file mode 100644 index 0000000..54253b0 --- /dev/null +++ b/handler/token-handler.go @@ -0,0 +1,26 @@ +package handler + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + "myschools.me/campus/campus-core/campus" +) + +func HealthCheck(c *gin.Context) { + + c.JSON(http.StatusOK, nil) +} + +func TokenVerify(c *gin.Context) { + + reqid := campus.NewRequestID(nil) + token := c.GetHeader("Authorization") + if token != "112233" { + campus.RespBadRequest(c, "token err", errors.New("token err"), &reqid) + } else { + campus.RespSuccess(c, "helo_word", token+"helo_word", &reqid) + } + +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..e6550a5 --- /dev/null +++ b/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "time" + + "myschools.me/campus/campus-core/exceptionless" + "myschools.me/campus/demo-1/consul" + "myschools.me/campus/demo-1/gin" +) + +func main() { + // 日志初始化 + // exceptionless.Init(&exceptionless.Config{ + // ApiKey: os.Getenv("EXCEPTIONLESS_KEY"), + // ServerURL: os.Getenv("EXCEPTIONLESS_URL"), + // UpdateSettingsWhenIdleInterval: 0, + // }) + + //gin初始化,基于docker部署,不再需要调整默认值(80) + + consul.Init(&consul.Config{ + Address: "192.168.0.214:8500", + }) + + gin.Service(nil) + + if err := consul.RegisterAPI("demo1", "192.168.0.214", 8080, ""); err != nil { + fmt.Println("注册失败", err.Error()) + } + + // exceptionless.SubmitLog(fmt.Sprintf("campus-srv[%s:%d%s] service is running...", conf.Addr, conf.Port, conf.RootPath), "info") + // 服务停止相应 + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + _, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + exceptionless.SubmitLog("campus-srv service shutting down", "info") + os.Exit(0) +}