diff --git a/conf/new.go b/conf/new.go index bbcb8e6..ffc0182 100644 --- a/conf/new.go +++ b/conf/new.go @@ -59,11 +59,11 @@ func NotNil(values ...string) { } } -func PrintInfo(port int) { +func PrintInfo(ip string, port int) { print.Success("[BSM - %s] Config Check Success.", vars.ServiceKey) print.Info("[BSM - %s] Service Name: %s", vars.ServiceKey, vars.ServiceKey) print.Info("[BSM - %s] Runtime Mode: %s", vars.ServiceKey, env.Runtime.Mode) - print.Info("[BSM - %s] Listen Addr: %s:%d", vars.ServiceKey, utils.GetLocationIP(), port) + print.Info("[BSM - %s] Listen Addr: %s:%d", vars.ServiceKey, ip, port) } func CheckPort(port int) int { @@ -73,3 +73,10 @@ func CheckPort(port int) int { } return port } + +func CheckIP(ip string) string { + if ip == "" { + return utils.GetLocationIP() + } + return ip +} diff --git a/conf/types.go b/conf/types.go index fef45d3..79477be 100644 --- a/conf/types.go +++ b/conf/types.go @@ -1,11 +1,11 @@ package conf type Base struct { - Service string `yaml:"Service"` // 服务名称 - Port int `yaml:"Port"` // 服务监听端口,0为自动随机端口 - Cache string `yaml:"Cache"` // REDIS缓存 - OnMicroService bool `yaml:"OnMicroService"` // 是否启用微服务 - SecretKey string `yaml:"SecretKey"` // 服务秘钥 + Service string `yaml:"Service"` // 服务名称 + Port int `yaml:"Port"` // 服务监听端口,0为自动随机端口 + Cache string `yaml:"Cache"` // REDIS缓存 + SecretKey string `yaml:"SecretKey"` // 服务秘钥 + BindIP string `yaml:"BindIP"` // 绑定IP } type DBConf struct { @@ -13,6 +13,11 @@ type DBConf struct { Source []string `yaml:"Source"` // 数据库连接 } +type MicroServiceConf struct { + Enable bool `yaml:"Enable"` // 是否启用微服务 + Anonymous []string `yaml:"Anonymous"` +} + type ApmConf struct { Name string // APM服务名称 Platform string `yaml:"Platform"` // APM平台:apm,skywalking diff --git a/print/print.go b/print/print.go index 7aef2c9..c49f1a5 100644 --- a/print/print.go +++ b/print/print.go @@ -15,24 +15,24 @@ func init() { // record INFO message. Color White func Info(format string, a ...interface{}) { - message := fmt.Sprintf("\033[37m[Info] "+format+"\033[0m\n", a...) + message := fmt.Sprintf("\033[37m[Info] "+format+"\033[0m\n", a...) logger.Print(message) } // record Warn message. Color Orange func Warn(format string, a ...interface{}) { - message := fmt.Sprintf("\033[33m[Warn] "+format+"\033[0m\n", a...) + message := fmt.Sprintf("\033[33m[Warn] "+format+"\033[0m\n", a...) logger.Print(message) } // record Success message. Color Green func Success(format string, a ...interface{}) { - message := fmt.Sprintf("\033[32m[Success] "+format+"\033[0m\n", a...) + message := fmt.Sprintf("\033[32m[Succ] "+format+"\033[0m\n", a...) logger.Print(message) } // record ERROR message. Color Red func Error(format string, a ...interface{}) { - message := fmt.Sprintf("\033[31m[Error] "+format+"\033[0m\n", a...) + message := fmt.Sprintf("\033[31m[Error] "+format+"\033[0m\n", a...) logger.Print(message) } diff --git a/service/meta.go b/service/meta.go new file mode 100644 index 0000000..4fe4fd8 --- /dev/null +++ b/service/meta.go @@ -0,0 +1,73 @@ +package service + +import ( + "context" + "encoding/json" + "strconv" + + "git.apinb.com/bsm-sdk/core/errcode" + "git.apinb.com/bsm-sdk/core/utils" + "google.golang.org/grpc/metadata" +) + +type Meta struct { + ID uint `json:"id"` + IDENTITY string `json:"identity"` + EXTEND map[string]string `json:"extend"` + CLIENT string `json:"client"` +} + +// 解析Context中MetaData的数据 +type ParseOptions struct { + RoleValue string // 判断角色的值 + MustPrivateAllow bool // 是否只允许私有IP访问 +} + +func ParseMetaCtx(ctx context.Context, opts *ParseOptions) (*Meta, error) { + // 解析metada中的信息并验证 + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return nil, errcode.ErrJWTAuthNotFound + } + + meta := &Meta{ + IDENTITY: md["authorization_identity"][0], + CLIENT: md["client"][0], + } + + if id, err := strconv.Atoi(md["authorization_id"][0]); err != nil { + return nil, errcode.ErrJWTAuthKeyId + } else { + meta.ID = uint(id) + } + + data := make(map[string]string) + if err := json.Unmarshal([]byte(md["authorization_extend"][0]), &data); err == nil { + meta.EXTEND = data + } + + if opts != nil { + if !meta.CheckRole("role", opts.RoleValue) { + return nil, errcode.ErrPermissionDenied + } + if opts.MustPrivateAllow { + if utils.IsPublicIP(meta.CLIENT) { + return nil, errcode.ErrPermissionDenied + } + } + } + + return meta, nil + +} + +func (m *Meta) CheckRole(roleKey, roleValue string) bool { + if roleValue == "" { + return true + } + if role, exists := m.EXTEND[roleKey]; !exists || role != roleValue { + return false + } else { + return true + } +} diff --git a/service/register.go b/service/register.go new file mode 100644 index 0000000..6d8d1c7 --- /dev/null +++ b/service/register.go @@ -0,0 +1,128 @@ +package service + +import ( + "context" + "strings" + "time" + + "git.apinb.com/bsm-sdk/core/print" + "git.apinb.com/bsm-sdk/core/vars" + clientv3 "go.etcd.io/etcd/client/v3" +) + +// ServiceRegister 创建租约注册服务 +type ServiceRegister struct { + cli *clientv3.Client //etcd client + leaseID clientv3.LeaseID //租约ID + //租约keepalieve相应chan + keepAliveChan <-chan *clientv3.LeaseKeepAliveResponse + key string //key + val string //value +} + +// NewRegister 注册服务至路由表. +// +// ec:EtcdConfig; +// +// routeKey: ServiceRouteRootPrefix + ServiceKey + "/" + utils.GetLocationIP() + addr; +// +// gs:grpc.Server; +func RegisterService(cli *clientv3.Client, routeKey string, methods []string, lease int64) (*ServiceRegister, error) { + + ser := &ServiceRegister{ + cli: cli, + key: routeKey, + val: strings.Join(methods, ","), + } + + //申请租约设置时间keepalive + if err := ser.putKeyWithLease(lease); err != nil { + return nil, err + } + + return ser, nil +} + +// 设置租约 +func (s *ServiceRegister) putKeyWithLease(lease int64) error { + //设置租约时间 + resp, err := s.cli.Grant(context.Background(), lease) + if err != nil { + return err + } + //注册服务并绑定租约 + _, err = s.cli.Put(context.Background(), s.key, s.val, clientv3.WithLease(resp.ID)) + if err != nil { + return err + } + //设置续租 定期发送需求请求 + leaseRespChan, err := s.cli.KeepAlive(context.Background(), resp.ID) + + if err != nil { + return err + } + s.leaseID = resp.ID + s.keepAliveChan = leaseRespChan + return nil +} + +// ListenLeaseRespChan 监听 续租情况 +func (s *ServiceRegister) ListenLeaseRespChan() { + for { + select { + case leaseKeepResp := <-s.keepAliveChan: + if leaseKeepResp == nil { + //log.Println("close lease.") + return + } else { + goto END + } + } + END: + time.Sleep(500 * time.Millisecond) + } +} + +// Close 注销服务 +func (s *ServiceRegister) Close() error { + //撤销租约 + if _, err := s.cli.Revoke(context.Background(), s.leaseID); err != nil { + return err + } + return s.cli.Close() +} + +func (s *ServiceRegister) SetAnonymous(key string, urls []string) { + // remove reppeat, clear service all anonymous uri. + anonymous, _ := s.cli.Get(context.Background(), key) + + var as []string + if len(anonymous.Kvs) > 0 { + val := string(anonymous.Kvs[0].Value) + as = strings.Split(val, ",") + } + + as = append(clearService(as), urls...) + newAnonymous := strings.Join(as, ",") + + // put anonymous to etcd + _, err := s.cli.Put(context.Background(), key, newAnonymous) + + if err != nil { + print.Error("[BSM Register] Anonymous Fail.") + } else { + print.Info("[BSM Register] Anonymous: %s", newAnonymous) + } + +} + +func clearService(as []string) (out []string) { + for _, v := range as { + if len(v) >= len(vars.ServiceKey) { + if v[0:len(vars.ServiceKey)] != vars.ServiceKey { + out = append(out, v) + } + } + } + return +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..3a9e74d --- /dev/null +++ b/service/service.go @@ -0,0 +1,98 @@ +package service + +import ( + "log" + "net" + "os" + "strconv" + "strings" + + "git.apinb.com/bsm-sdk/core/conf" + "git.apinb.com/bsm-sdk/core/env" + "git.apinb.com/bsm-sdk/core/print" + "git.apinb.com/bsm-sdk/core/vars" + clientv3 "go.etcd.io/etcd/client/v3" + "google.golang.org/grpc" +) + +type ( + // RegisterFn defines the method to register a server. + RegisterFn func(*grpc.Server) + + Service struct { + GrpcSrv *grpc.Server + Opts *Options + } + + Options struct { + Addr string + Conf *conf.MicroServiceConf + EtcdClient *clientv3.Client + } +) + +func New(srv *grpc.Server, opts *Options) *Service { + return &Service{GrpcSrv: srv, Opts: opts} +} + +func Addr(ip string, port int) string { + return net.JoinHostPort(ip, strconv.Itoa(port)) +} + +func (s *Service) Start() { + print.Info("[BSM - %s] Service Starting ...", vars.ServiceKey) + + // register to etcd. + if s.Opts.Conf != nil && s.Opts.Conf.Enable { + if s.Opts.EtcdClient == nil { + print.Error("[BSM Register] Etcd Client is nil.") + os.Exit(1) + } + // get methods + methods := FoundGrpcMethods(s.GrpcSrv) + + // set router key + routerKey := vars.ServiceRootPrefix + "Router/" + env.Runtime.Workspace + "/" + strings.ToLower(vars.ServiceKey) + "/" + s.Opts.Addr + + // register to etcd + register, err := RegisterService(s.Opts.EtcdClient, routerKey, methods, vars.ServiceLease) + if err != nil { + log.Panicf("[ERROR] %s Service Register:%s \n", vars.ServiceKey, err.Error()) + } + + anonKey := vars.ServiceRootPrefix + "Router/" + env.Runtime.Workspace + "/" + + register.SetAnonymous(anonKey, s.Opts.Conf.Anonymous) + + // service register lease + go register.ListenLeaseRespChan() + } + + // run grpc srv. + tcpListen, err := net.Listen("tcp", s.Opts.Addr) + if err != nil { + panic(err) + } + + print.Success("[BSM - %s] Service Grpc Register & Runing Success!", vars.ServiceKey) + if err := s.GrpcSrv.Serve(tcpListen); err != nil { + panic(err) + } + +} + +func (s *Service) Stop() { + s.GrpcSrv.GracefulStop() +} + +// found grpc methods. +func FoundGrpcMethods(s *grpc.Server) []string { + var mothods []string + for key, srv := range s.GetServiceInfo() { + srvName := strings.Split(key, ".")[1] + for _, mn := range srv.Methods { + mothods = append(mothods, srvName+"."+mn.Name) + } + } + return mothods +} diff --git a/vars/service.go b/vars/service.go index da0004c..4a9ae16 100644 --- a/vars/service.go +++ b/vars/service.go @@ -4,4 +4,7 @@ var ( RUN_MODE_DEV = "dev" RUN_MODE_TEST = "test" RUN_MODE_PROD = "prod" + + ServiceLease int64 = 60 + ServiceRootPrefix string = "/" )