commit f98158622461512bbfe03ff3fd27e35c449cdeee Author: wangjian Date: Thu Oct 29 10:44:44 2020 +0800 1、项目初始化 diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..b96223e --- /dev/null +++ b/.drone.yml @@ -0,0 +1,71 @@ +kind: pipeline +name: job_risk_third +workspace: + base: /var/goproject/src + path: job_risk_third + +steps: + - name: build + image: golang:1.14.4 + environment: + GOOS: linux + GOARCH: amd64 + commands: + - export GOPATH=/var/goproject + - export PATH=$PATH:$GOROOT/bin + - go env -w GO111MODULE=on + - go env -w GOPROXY=https://goproxy.cn,direct + - go version + - go env + - go mod tidy + - go build -i -o bin/job_risk_third cmd/main.go + - name: develop deploy + image: appleboy/drone-scp + settings: + host: + from_secret: host + port: + from_secret: port + username: + from_secret: username + password: + from_secret: password + target: + from_secret: target + source: ./bin + rm: false + when: + branch: + - deploy_develop + - name: run + image: appleboy/drone-ssh + settings: + host: + from_secret: host + port: + from_secret: port + username: + from_secret: username + password: + from_secret: password + command_timeout: 2m + script: + - cd /server/job_risk_third/ + - rm -rf job_risk_third + - cp bin/job_risk_third job_risk_third + - cp restart.sh restart.sh + - ./restart.sh + - nohup ./job_risk_third -f config/job_risk_third.json 1>/dev/null 2>&1 & + - name: notification + image: lddsb/drone-dingtalk-message + settings: + token: 840fed65878154561a096c03a516ded894ab1b91efbe1643fe492dd93593e7a3 + type: markdown + secret: SEC9fe40cc775658cbef7a30b43fd852b642201754fdrone15df42cd339627e05cfde41e + tpl: ./drone/tpl/index.md + tips_title: 宁达用户服务构建结果 + success_pic: https://i.ibb.co/CB4HBLP/success-1.png + failure_pic: https://i.ibb.co/wM2rbGS/fail-1.png +trigger: + branch: + - deploy_develop \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f45c31 --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ +## 职务风险防范第三方服务 + +### 请求中研接口 + +#### 中研接入公共单元 + +#### 1、风险特征处置结果接口 + +- 接口地址 + +> /ndToZy/pushFeatureDeal + +- 请求方法 + +> POST + +- 请求参数 + +参数 | 类型 | 是否必须 | 备注 +----|----|----|---- +featureId | String | 是 | 风险特征ID +userId | String | 是 | 风险触发用户ID +dealId | String | 是 | 处置人ID +dealName | String | 是 | 处置人姓名 +dealTime | String | 是 | 处置时间,格式YYYY-MM-DD HH:MI:SS +dealStatus | String | 是 | 处置状态(1已处置、0未处置、2不处置) +dealResult | String | 是 | 处置结果 + +- 接口返回数据 + +``` +{ + "status": 200, + "message": "操作成功", + "data":{ + } +} +``` diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..a5fdc23 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,147 @@ +package main + +import ( + "context" + "flag" + "fmt" + "github.com/go-kit/kit/log" + kitprometheus "github.com/go-kit/kit/metrics/prometheus" + kitzipkin "github.com/go-kit/kit/tracing/zipkin" + "github.com/openzipkin/zipkin-go" + zipkinhttp "github.com/openzipkin/zipkin-go/reporter/http" + stdprometheus "github.com/prometheus/client_golang/prometheus" + "github.com/tal-tech/go-zero/core/logx" + "gorm.io/gorm" + "job_risk_third/config" + "job_risk_third/discover" + "job_risk_third/internal/endpoints" + "job_risk_third/internal/services" + "job_risk_third/internal/transports" + "job_risk_third/loggers" + "job_risk_third/model" + "net/http" + "os" + "os/signal" + "syscall" +) + +var configFile = flag.String("f", "config/job_risk_third.json", "the config file") + +func main() { + flag.Parse() + errChan := make(chan error) + ctx := context.Background() + config.Cfg = config.LoadConfig(*configFile) + logConf := logx.LogConf{ + ServiceName: config.Cfg.Get("name").(string), + Mode: config.Cfg.Get("log.mode").(string), + Path: config.Cfg.Get("log.path").(string), + Level: config.Cfg.Get("log.level").(string), + Compress: config.Cfg.Get("log.compress").(bool), + KeepDays: config.Cfg.GetInt("log.keep_days"), + StackCooldownMillis: config.Cfg.GetInt("log.stack_cooldown_millis"), + } + zipkinConf := config.ZipkinConfig{ + HostConfig: config.HostConfig{ + Address: config.Cfg.Get("zipkin.host.address").(string), + Port: config.Cfg.GetInt("zipkin.host.port"), + }, + ZipkinURL: config.Cfg.Get("zipkin.url").(string), + } + consulConf := config.ConsulConfig{ + HostConfig: config.HostConfig{ + Address: config.Cfg.Get("consul.host.address").(string), + Port: config.Cfg.GetInt("consul.host.port"), + }, + ServiceId: config.Cfg.Get("consul.service_id").(string), + } + logx.MustSetup(logConf) + var logger = loggers.New() + dbConf := &gorm.Config{ + SkipDefaultTransaction: false, + Logger: logger, + } + //初始化数据库和连接缓存 + model.New(config.Cfg.GetString("connstring"), dbConf) + fieldKeys := []string{"method"} + requestCount := kitprometheus.NewCounterFrom(stdprometheus.CounterOpts{ + Namespace: config.Cfg.Get("name").(string), + Subsystem: "job_risk_third_service", + Name: "request_count", + Help: "收到的请求数", + }, fieldKeys) + + requestLatency := kitprometheus.NewSummaryFrom(stdprometheus.SummaryOpts{ + Namespace: config.Cfg.Get("name").(string), + Subsystem: "job_risk_third_service", + Name: "request_latency", + Help: "请求的总持续时间(以微秒为单位)", + }, fieldKeys) + var zipkinTracer *zipkin.Tracer + { + var ( + err error + hostPort = fmt.Sprintf("%s:%d", zipkinConf.Address, zipkinConf.Port) + serviceName = "insigma_service" + useNoopTracer = zipkinConf.ZipkinURL == "" + reporters = zipkinhttp.NewReporter(zipkinConf.ZipkinURL) + ) + defer reporters.Close() + zEP, _ := zipkin.NewEndpoint(serviceName, hostPort) + zipkinTracer, err = zipkin.NewTracer( + reporters, zipkin.WithLocalEndpoint(zEP), zipkin.WithNoopTracer(useNoopTracer), + ) + if err != nil { + logx.Error(err) + os.Exit(1) + } + + logx.Slow("Zipkin", "type", "Native", "URL", zipkinConf.ZipkinURL) + } + go func() { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + errChan <- fmt.Errorf("%s", <-c) + }() + var svr services.JobRiskThirdServices + svr = services.JobRiskThirdService{} + svr = services.Metrics(requestCount, requestLatency)(svr) + + pushFeatureDealEndpoint := endpoints.MakePushFeatureDealEndpoint(svr) + pushFeatureDealEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "push_feature_deal")(pushFeatureDealEndpoint) + pushUserInfoEndpoint := endpoints.MakePushUserInfoEndpoint(svr) + pushUserInfoEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "push_user_info")(pushUserInfoEndpoint) + pushFeatureInfoEndpoint := endpoints.MakePushFeatureInfoEndpoint(svr) + pushFeatureInfoEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "push_feature_info")(pushFeatureInfoEndpoint) + //创建健康检查的Endpoint + healthEndpoint := endpoints.MakeHealthCheckEndpoint(svr) + healthEndpoint = kitzipkin.TraceEndpoint(zipkinTracer, "third_health_endpoint")(healthEndpoint) + endpts := endpoints.JobRiskThirdEndpoint{ + PushFeatureDealEndpoint: pushFeatureDealEndpoint, + PushUserInfoEndpoint: pushUserInfoEndpoint, + PushFeatureInfoEndpoint: pushFeatureInfoEndpoint, + HealthCheckEndpoint: healthEndpoint, + } + r := transports.MakeHttpHandler(ctx, endpts, zipkinTracer) + var l log.Logger + { + l = log.NewLogfmtLogger(os.Stderr) + l = log.With(l, "ts", log.DefaultTimestampUTC) + l = log.With(l, "caller", log.DefaultCaller) + } + //创建注册对象 + registar := discover.Register(consulConf.Address, fmt.Sprintf("%d", consulConf.Port), config.Cfg.Get("http.domain.address").(string), + fmt.Sprintf("%d", config.Cfg.GetInt("http.domain.port")), consulConf.ServiceId, config.Cfg.Get("name").(string), l) + + go func() { + fmt.Printf("Http Server start at port %.f \n", config.Cfg.Get("http.host.port")) + //启动前执行注册 + registar.Register() + + errChan <- http.ListenAndServe(fmt.Sprintf(":%.f", config.Cfg.Get("http.host.port")), r) + }() + err := <-errChan + //服务退出取消注册 + registar.Deregister() + logx.Error(err) +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..277990c --- /dev/null +++ b/config/config.go @@ -0,0 +1,52 @@ +package config + +import ( + "github.com/spf13/viper" + "github.com/tal-tech/go-zero/core/logx" +) + +type HostConfig struct { + Address string `json:"address"` + Port int `json:"port"` +} + +type ApplicationConfig struct { + Name string `json:"name"` + Rpc HostConfig `json:"rpc"` +} + +type Prometheus struct { + HostConfig +} + +type ZipkinConfig struct { + HostConfig + ZipkinURL string `json:"url"` +} + +type ConsulConfig struct { + HostConfig + ServiceId string `json:"service_id"` +} + +type RedisConfig struct { + HostConfig + Pass string `json:"pass"` + DB int `json:"db"` + PoolSize int `json:"pool_size"` +} + +var ( + Cfg *viper.Viper +) + +func LoadConfig(path string) (config *viper.Viper) { + config = viper.New() + config.SetConfigFile(path) + // 读取配置 + if err := config.ReadInConfig(); err != nil { + logx.Error(err) + } + logx.Infof("config %#v", config.Get("http")) + return +} diff --git a/config/job_risk_third.json b/config/job_risk_third.json new file mode 100644 index 0000000..e31082f --- /dev/null +++ b/config/job_risk_third.json @@ -0,0 +1,70 @@ +{ + "name": "job_risk_third", + "http": { + "host": { + "address": "0.0.0.0", + "port": 8016 + }, + "is_ssl": false, + "domain":{ + "address": "172.17.0.1", + "port": 8016 + } + }, + "rpc": { + "address": "0.0.0.0", + "port": 8017 + }, + "prometheus":{ + "host": { + "address": "127.0.0.1", + "port": 9091 + }, + "path": "/metrics" + }, + "zipkin": { + "host": { + "address": "127.0.0.1", + "port": 9091 + }, + "url": "http://zipkin.ningdatech.com/api/v2/spans" + }, + "consul": { + "host": { + "address": "http://consul.ningdatech.com", + "port": 80 + }, + "service_id": "453c150ad53744de9d479aea255079f1" + }, + "zy_host": { + "address": "http://183.129.215.106", + "port": 13422 + }, + "sign_server":{ + "url": "http://sign.ningdatech.com/encrypt" + }, + "sso": { + "url": "http://172.18.25.79:8080", + "app_name": "ndzwfxff", + "login": "/wsite/sso/verify", + "logout": "/wsite/sso/logout" + }, + "log": { + "mode": "console", + "path": "/dev/log/job_risk", + "level": "info", + "compress": true, + "keep_days": 1, + "stack_cool_down_millis": 100 + }, + "redis":{ + "host": { + "address": "120.26.44.207", + "port": 26379 + }, + "pass": "Ndkj1234", + "db": 10, + "pool_size": 10 + }, + "connstring": "root:NingdaKeji123!@tcp(120.26.44.207:23306)/ningda_dev_plat_new?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai" +} \ No newline at end of file diff --git a/discover/discover.go b/discover/discover.go new file mode 100644 index 0000000..a63bbb3 --- /dev/null +++ b/discover/discover.go @@ -0,0 +1,52 @@ +package discover + +import ( + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/sd" + "github.com/go-kit/kit/sd/consul" + "github.com/hashicorp/consul/api" + "github.com/tal-tech/go-zero/core/logx" + "os" + "strconv" +) + +func Register(consulHost, consulPort, svcHost, svcPort, svrId, svrName string, logger log.Logger) (registar sd.Registrar) { + + // 创建Consul客户端连接 + var client consul.Client + { + consulCfg := api.DefaultConfig() + consulCfg.Address = consulHost + ":" + consulPort + consulClient, err := api.NewClient(consulCfg) + if err != nil { + logx.Error("create consul client error:", err) + os.Exit(1) + } + + client = consul.NewClient(consulClient) + } + + // 设置Consul对服务健康检查的参数 + check := api.AgentServiceCheck{ + HTTP: "http://" + svcHost + ":" + svcPort + "/health", + Interval: "10s", + Timeout: "1s", + Notes: "Consul check service health status.", + } + + port, _ := strconv.Atoi(svcPort) + + //设置微服务想Consul的注册信息 + reg := api.AgentServiceRegistration{ + ID: svrId, + Name: "prometheus-" + svrName, + Address: svcHost, + Port: port, + Tags: []string{svrName, "JobRiskBizServices"}, + Check: &check, + } + + // 执行注册 + registar = consul.NewRegistrar(client, ®, logger) + return +} diff --git a/drone/tpl/index.md b/drone/tpl/index.md new file mode 100644 index 0000000..b3bb360 --- /dev/null +++ b/drone/tpl/index.md @@ -0,0 +1,15 @@ +![结果]([TPL_STATUS_PIC]) + +仓库:[TPL_REPO_SHORT_NAME] + +分支:[TPL_COMMIT_BRANCH] + +commit: [查看]([TPL_COMMIT_LINK]) + +结果:[TPL_BUILD_STATUS] + +耗时:[TPL_BUILD_CONSUMING]s + +作者:[[TPL_AUTHOR_NAME]([TPL_AUTHOR_EMAIL])](mailto:[TPL_AUTHOR_EMAIL]) + +详情:[查看]([TPL_BUILD_LINK]) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5de4c2d --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module job_risk_third + +go 1.14 + +require ( + github.com/VividCortex/gohistogram v1.0.0 // indirect + github.com/go-kit/kit v0.9.0 + github.com/go-redis/redis v6.15.7+incompatible + github.com/google/uuid v1.1.1 + github.com/gorilla/mux v1.8.0 + github.com/hashicorp/consul/api v1.1.0 + github.com/juju/ratelimit v1.0.1 + github.com/openzipkin/zipkin-go v0.2.5 + github.com/prometheus/client_golang v1.5.1 + github.com/spf13/viper v1.7.1 + github.com/tal-tech/go-zero v1.0.20 + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 + gorm.io/driver/mysql v1.0.2 + gorm.io/gorm v1.20.2 +) diff --git a/internal/endpoints/endpoints.go b/internal/endpoints/endpoints.go new file mode 100644 index 0000000..c661575 --- /dev/null +++ b/internal/endpoints/endpoints.go @@ -0,0 +1,77 @@ +package endpoints + +import ( + "context" + "github.com/go-kit/kit/endpoint" + "job_risk_third/internal/proto" + "job_risk_third/internal/services" +) + +type JobRiskThirdEndpoint struct { + PushFeatureDealEndpoint endpoint.Endpoint + PushUserInfoEndpoint endpoint.Endpoint + PushFeatureInfoEndpoint endpoint.Endpoint + HealthCheckEndpoint endpoint.Endpoint +} + +// MakeHealthCheckEndpoint 创建健康检查Endpoint +func MakeHealthCheckEndpoint(svc services.JobRiskThirdServices) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + status := svc.HealthCheck() + return proto.HealthResponse{Status: status}, nil + } +} + +//MakePushFeatureDealEndpoint 接收风险特征处置结果Endpoint +func MakePushFeatureDealEndpoint(svc services.JobRiskThirdServices) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(proto.PushFeatureDealRequest) + err = svc.PushFeatureDeal(req) + if err != nil { + return proto.BaseResponse{ + RespCode: 500, + RespMsg: "接收风险特征处置结果失败", + }, err + } + return proto.BaseResponse{ + RespCode: 200, + RespMsg: "成功", + }, err + } +} + +//MakePushUserInfoEndpoint 获取用户信息接口 +func MakePushUserInfoEndpoint(svc services.JobRiskThirdServices) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(proto.PushUserInfoRequest) + err = svc.PushUserInfo(req) + if err != nil { + return proto.BaseResponse{ + RespCode: 500, + RespMsg: "获取用户信息失败", + }, err + } + return proto.BaseResponse{ + RespCode: 200, + RespMsg: "成功", + }, err + } +} + +//MakePushFeatureInfoEndpoint 推送的风险结果信息 +func MakePushFeatureInfoEndpoint(svc services.JobRiskThirdServices) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + req := request.(proto.PushFeatureInfoRequest) + err = svc.PushFeatureInfo(req) + if err != nil { + return proto.BaseResponse{ + RespCode: 500, + RespMsg: "推送风险结果信息失败", + }, err + } + return proto.BaseResponse{ + RespCode: 200, + RespMsg: "成功", + }, err + } +} diff --git a/internal/proto/request.go b/internal/proto/request.go new file mode 100644 index 0000000..7417dc6 --- /dev/null +++ b/internal/proto/request.go @@ -0,0 +1,75 @@ +package proto + +const ( + //PushFeatureDeal 接收风险特征处置结果接口 + PushFeatureDeal = "/ndToZy/pushFeatureDeal" + //PushUserInfo 获取用户信息接口 + PushUserInfo = "/ndToZy/pushUserInfo" + //PushFeatureInfo 推送的风险结果信息 + PushFeatureInfo = "/ndToZy/pushFeatureInfo" +) + +//SignServiceRequest 签名服务接口请求格式 +type SignServiceRequest struct { + Obj string `json:"obj"` +} + +//ZyBaseRequest 中研服务请求 +type ZyBaseRequest struct { + Data string `json:"data"` + Sign string `json:"sign"` + Source string `json:"source"` + Salt string `json:"salt"` + Key string `json:"key"` +} + +//PushFeatureDealRequest 接收风险特征处置结果接口 +type PushFeatureDealRequest struct { + FeatureId string `json:"featureId" description:"风险特征ID"` + UserId string `json:"userId" description:"风险触发用户ID"` + ObjectId string `json:"objectId" description:"风险对象ID,4.1接口推送的objectId"` + DealId string `json:"dealId" description:"处置人ID"` + DealName string `json:"dealName" description:"处置人姓名"` + DealTime string `json:"dealTime" description:"处置时间,格式YYYY-MM-DD HH:MI:SS"` + DealStatus string `json:"dealStatus" description:"处置状态(1已处置、0未处置、2不处置)"` + DealResult string `json:"dealResult" description:"处置结果"` +} + +//PushUserInfoRequest 获取用户信息接口 +type PushUserInfoRequest struct { + UserId string `json:"userId" description:"用户ID"` + UserName string `json:"userName" description:"用户名"` + Name string `json:"name" description:"姓名"` + Position string `json:"position" description:"岗位"` + Job string `json:"job" description:"职务"` + DeptId string `json:"deptId" description:"所在部门/业务处室编码"` + DeptName string `json:"deptName" description:"所在部门/业务处室名称"` + LogTime string `json:"logTime" description:"登录时间"` + LogError string `json:"logError" description:"登录密码错误次数"` + Regorg string `json:"regorg" description:"登记机关"` +} + +//PushFeatureInfo 推送的风险结果信息 +type PushFeatureInfoRequest struct { + FeatureId string `json:"featureId" description:"风险特征ID"` + FeatureName string `json:"featureName" description:"风险名称"` + FeatureLevel string `json:"featureLevel" description:"风险等级"` + FeatureDeptId string `json:"featureDeptId" description:"风险所属部门"` + FeatureTime string `json:"featureTime" description:"触发时间,精确到时分秒"` + ObjectList []struct { + ObjId string `json:"objId" description:"对象ID(用户ID)"` + ObjName string `json:"objName" description:"对象名称(用户姓名)"` + ObjRegorg string `json:"objRegorg" description:"对象所在登记机关"` + ObjDeptid string `json:"objDeptid" description:"对象所在部门"` + ObjPosition string `json:"objPosition" description:"对象所在岗位"` + IsDeal string `json:"isDeal" description:"是否处置(1已处置;0未处置)"` + DealId string `json:"dealId" description:"处置人ID"` + DealName string `json:"dealName" description:"处置人姓名"` + DealTime string `json:"dealTime" description:"处置时间"` + DealStatus string `json:"dealStatus" description:"处置状态(0未处置;1已处置)"` + DealResult string `json:"dealResult" description:"处置结果"` + } `json:"objectList" description:"风险对象列表"` +} + +// HealthRequest 健康检查请求结构 +type HealthRequest struct{} diff --git a/internal/proto/response.go b/internal/proto/response.go new file mode 100644 index 0000000..bbedd61 --- /dev/null +++ b/internal/proto/response.go @@ -0,0 +1,21 @@ +package proto + +type BaseResponse struct { + RespCode int `json:"resp_code"` + RespMsg string `json:"resp_msg"` + Data interface{} `json:"data,omitempty"` +} + +type SignResponse struct { + Data string `json:"data"` +} + +type ZyResponse struct { + Status int `json:"status"` + Message string `json:"message"` +} + +// HealthResponse 健康检查响应结构 +type HealthResponse struct { + Status bool `json:"status"` +} diff --git a/internal/services/instrument.go b/internal/services/instrument.go new file mode 100644 index 0000000..3e26fc0 --- /dev/null +++ b/internal/services/instrument.go @@ -0,0 +1,86 @@ +package services + +import ( + "context" + "fmt" + "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/metrics" + "github.com/juju/ratelimit" + "golang.org/x/time/rate" + "job_risk_third/internal/proto" + "time" +) + +var ErrLimitExceed = fmt.Errorf("Rate limit exceed!") + +// NewTokenBucketLimitterWithJuju 使用juju/ratelimit创建限流中间件 +func NewTokenBucketLimitterWithJuju(bkt *ratelimit.Bucket) endpoint.Middleware { + return func(next endpoint.Endpoint) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + if bkt.TakeAvailable(1) == 0 { + return nil, ErrLimitExceed + } + return next(ctx, request) + } + } +} + +// NewTokenBucketLimitterWithBuildIn 使用x/time/rate创建限流中间件 +func NewTokenBucketLimitterWithBuildIn(bkt *rate.Limiter) endpoint.Middleware { + return func(next endpoint.Endpoint) endpoint.Endpoint { + return func(ctx context.Context, request interface{}) (response interface{}, err error) { + if !bkt.Allow() { + return nil, ErrLimitExceed + } + return next(ctx, request) + } + } +} + +// metricMiddleware 定义监控中间件,嵌入Service +// 新增监控指标项:requestCount和requestLatency +type metricMiddleware struct { + JobRiskThirdServices + requestCount metrics.Counter + requestLatency metrics.Histogram +} + +// Metrics 封装监控方法 +func Metrics(requestCount metrics.Counter, requestLatency metrics.Histogram) ServiceMiddleware { + return func(next JobRiskThirdServices) JobRiskThirdServices { + return metricMiddleware{ + next, + requestCount, + requestLatency} + } +} + +func (mw metricMiddleware) PushFeatureDeal(req proto.PushFeatureDealRequest) error { + defer func(beign time.Time) { + lvs := []string{"method", "PushFeatureDeal"} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(beign).Seconds()) + }(time.Now()) + + b := mw.JobRiskThirdServices.PushFeatureDeal(req) + return b +} + +func (mw metricMiddleware) PushUserInfo(req proto.PushUserInfoRequest) error { + defer func(beign time.Time) { + lvs := []string{"method", "PushUserInfo"} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(beign).Seconds()) + }(time.Now()) + return mw.JobRiskThirdServices.PushUserInfo(req) +} + +func (mw metricMiddleware) PushFeatureInfo(req proto.PushFeatureInfoRequest) error { + defer func(beign time.Time) { + lvs := []string{"method", "PushFeatureInfo"} + mw.requestCount.With(lvs...).Add(1) + mw.requestLatency.With(lvs...).Observe(time.Since(beign).Seconds()) + }(time.Now()) + + return mw.JobRiskThirdServices.PushFeatureInfo(req) +} diff --git a/internal/services/services.go b/internal/services/services.go new file mode 100644 index 0000000..4db04af --- /dev/null +++ b/internal/services/services.go @@ -0,0 +1,151 @@ +package services + +import ( + "encoding/json" + "fmt" + "github.com/tal-tech/go-zero/core/logx" + "job_risk_third/config" + "job_risk_third/internal/proto" + "job_risk_third/model" + "job_risk_third/pkg/utils" +) + +//职务风险防范系统第三方请求业务模块 +type JobRiskThirdServices interface { + //PushFeatureDeal 接收风险特征处置结果接口 + PushFeatureDeal(req proto.PushFeatureDealRequest) error + //PushUserInfo 获取用户信息接口 + PushUserInfo(req proto.PushUserInfoRequest) error + //PushFeatureInfo 推送的风险结果信息 + PushFeatureInfo(req proto.PushFeatureInfoRequest) error + + // HealthCheck check service health status + HealthCheck() bool +} + +type ServiceMiddleware func(JobRiskThirdServices) JobRiskThirdServices + +type JobRiskThirdService struct { +} + +// HealthCheck implement Service method +// 用于检查服务的健康状态,这里仅仅返回true。 +func (s JobRiskThirdService) HealthCheck() bool { + return true +} + +//PushFeatureDeal 接收风险特征处置结果接口 +func (s JobRiskThirdService) PushFeatureDeal(req proto.PushFeatureDealRequest) error { + jsBody, _ := json.Marshal(req) + body, err := RequestSign(jsBody, proto.PushFeatureDeal) + if err != nil { + return err + } + res := new(proto.ZyResponse) + err = json.Unmarshal(body, res) + if err != nil { + logx.Errorf("PushFeatureDeal call error: %s", err) + return err + } + if res.Status != 200 { + logx.Errorf("PushFeatureDeal call error: %s", res.Message) + return fmt.Errorf(res.Message) + } + return nil +} + +//PushUserInfo 获取用户信息接口 +func (s JobRiskThirdService) PushUserInfo(req proto.PushUserInfoRequest) error { + jsBody, _ := json.Marshal(req) + body, err := RequestSign(jsBody, proto.PushUserInfo) + if err != nil { + return err + } + res := new(proto.ZyResponse) + err = json.Unmarshal(body, res) + if err != nil { + logx.Errorf("PushUserInfo call error: %s", err) + return err + } + if res.Status != 200 { + logx.Errorf("PushUserInfo call error: %s", res.Message) + return fmt.Errorf(res.Message) + } + return nil +} + +//PushFeatureInfo 推送的风险结果信息 +func (s JobRiskThirdService) PushFeatureInfo(req proto.PushFeatureInfoRequest) error { + jsBody, _ := json.Marshal(req) + body, err := RequestSign(jsBody, proto.PushFeatureInfo) + if err != nil { + return err + } + res := new(proto.ZyResponse) + err = json.Unmarshal(body, res) + if err != nil { + logx.Errorf("PushFeatureInfo call error: %s", err) + return err + } + if res.Status != 200 { + logx.Errorf("PushFeatureInfo call error: %s", res.Message) + return fmt.Errorf(res.Message) + } + return nil +} + +func RequestSign(data []byte, url string) (body []byte, err error) { + item := new(model.TbRequestThirdLog) + reqMap := make(map[string]string) + reqMap["obj"] = string(data) + + headerMap := make(map[string]string) + headerMap["Content-Type"] = "application/x-www-form-urlencoded" + b, err := utils.HttpDo(config.Cfg.GetString("sign_server.url"), "POST", reqMap, headerMap) + if err != nil { + logx.Error(err) + return nil, err + } + item.SourceData = string(data) + item.CryptoData = string(b) + signResponse := new(proto.SignResponse) + err = json.Unmarshal(b, signResponse) + if err != nil { + logx.Error(err) + return nil, err + } + requestStruct := new(proto.ZyBaseRequest) + err = json.Unmarshal([]byte(signResponse.Data), requestStruct) + if err != nil { + logx.Error(err) + return nil, err + } + requestMap := make(map[string]string) + requestId := utils.NewId() + requestMap["data"] = requestStruct.Data + requestMap["key"] = requestStruct.Key + requestMap["salt"] = requestStruct.Salt + requestMap["sign"] = requestStruct.Sign + requestMap["source"] = requestStruct.Source + + //decryptMap := make(map[string]string) + //decryptMap["reqData"]=string(b) + //b, err = utils.HttpDo("http://sign.ningdatech.com/decrypt","POST",decryptMap,headerMap) + //if err!= nil{ + // logx.Error(err) + //} + //logx.Info(string(b)) + headerMap["Content-Type"] = "application/json" + resBody, err := utils.HttpDo(fmt.Sprintf("%s:%d%s", config.Cfg.GetString("zy_host.address"), + config.Cfg.GetInt("zy_host.port"), url), "POST", requestMap, headerMap) + if err != nil { + logx.Error(err) + return nil, err + } + item.RequestId = requestId + item.RequestUri = fmt.Sprintf("%s:%d%s", config.Cfg.GetString("zy_host.address"), + config.Cfg.GetInt("zy_host.port"), url) + item.ResultData = string(resBody) + go model.DB.Create(item) + return resBody, err +} diff --git a/internal/transports/merror.go b/internal/transports/merror.go new file mode 100644 index 0000000..ece6bde --- /dev/null +++ b/internal/transports/merror.go @@ -0,0 +1,21 @@ +package transports + +import ( + "context" + "encoding/json" + "job_risk_third/internal/proto" + "net/http" +) + +//自定义一个解码error函数 +func MyErrorEncoder(_ context.Context, err error, w http.ResponseWriter) { + res := proto.BaseResponse{ + RespCode: 500, + RespMsg: err.Error(), + } + contentType := "application/json,charset=utf-8" + w.Header().Set("content-type", contentType) + w.WriteHeader(200) + body, _ := json.Marshal(res) + w.Write(body) +} diff --git a/internal/transports/transports.go b/internal/transports/transports.go new file mode 100644 index 0000000..620ec3d --- /dev/null +++ b/internal/transports/transports.go @@ -0,0 +1,106 @@ +package transports + +import ( + "context" + "encoding/json" + "github.com/go-kit/kit/tracing/zipkin" + kithttp "github.com/go-kit/kit/transport/http" + "github.com/gorilla/mux" + gozipkin "github.com/openzipkin/zipkin-go" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/tal-tech/go-zero/core/logx" + "job_risk_third/internal/endpoints" + "job_risk_third/internal/proto" + "job_risk_third/pkg/utils" + "net/http" +) + +// MakeHttpHandler make http handler use mux +func MakeHttpHandler(_ context.Context, endpoint endpoints.JobRiskThirdEndpoint, zipkinTracer *gozipkin.Tracer) http.Handler { + r := mux.NewRouter() + zipkinServer := zipkin.HTTPServerTrace(zipkinTracer, zipkin.Name("http-transport")) + + options := []kithttp.ServerOption{ + kithttp.ServerErrorEncoder(MyErrorEncoder), + zipkinServer, + } + r.Methods("POST").Path("/api/v1/push/feature_deal").Handler(kithttp.NewServer( + endpoint.PushFeatureDealEndpoint, + decodePushFeatureDealRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/api/v1/push/user_info").Handler(kithttp.NewServer( + endpoint.PushUserInfoEndpoint, + decodePushUserInfoRequest, + encodeResponse, + options..., + )) + r.Methods("POST").Path("/api/v1/push/feature_info").Handler(kithttp.NewServer( + endpoint.PushFeatureInfoEndpoint, + decodePushFeatureInfoRequest, + encodeResponse, + options..., + )) + + // create health check handler + r.Methods("GET").Path("/health").Handler(kithttp.NewServer( + endpoint.HealthCheckEndpoint, + decodeHealthCheckRequest, + encodeResponse, + options..., + )) + r.Path("/metrics").Handler(promhttp.Handler()) + return r +} + +// decodeHealthCheckRequest decode request +func decodeHealthCheckRequest(_ context.Context, _ *http.Request) (interface{}, error) { + return proto.HealthRequest{}, nil +} + +func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error { + w.Header().Set("Content-Type", "application/json;charset=utf-8") + return json.NewEncoder(w).Encode(response) +} + +func decodePushFeatureDealRequest(_ context.Context, r *http.Request) (interface{}, error) { + if err := utils.Validation(r); err != nil { + return nil, err + } + body := utils.Param(r, "__json_param__") + var item proto.PushFeatureDealRequest + err := json.Unmarshal([]byte(body), &item) + if err != nil { + logx.Error(err) + return nil, utils.ErrorBadRequest + } + return item, nil +} +func decodePushUserInfoRequest(_ context.Context, r *http.Request) (interface{}, error) { + if err := utils.Validation(r); err != nil { + return nil, err + } + body := utils.Param(r, "__json_param__") + var item proto.PushUserInfoRequest + err := json.Unmarshal([]byte(body), &item) + if err != nil { + logx.Error(err) + return nil, utils.ErrorBadRequest + } + return item, nil +} + +func decodePushFeatureInfoRequest(_ context.Context, r *http.Request) (interface{}, error) { + if err := utils.Validation(r); err != nil { + return nil, err + } + body := utils.Param(r, "__json_param__") + var item proto.PushFeatureInfoRequest + err := json.Unmarshal([]byte(body), &item) + if err != nil { + logx.Error(err) + return nil, utils.ErrorBadRequest + } + return item, nil +} diff --git a/loggers/logs.go b/loggers/logs.go new file mode 100644 index 0000000..c023af4 --- /dev/null +++ b/loggers/logs.go @@ -0,0 +1,119 @@ +package loggers + +import ( + "context" + "github.com/tal-tech/go-zero/core/logx" + ll "gorm.io/gorm/logger" + "gorm.io/gorm/utils" + "time" +) + +// LogLevel +type LogLevel int + +// Writer log writer interface +type Writer interface { + Printf(string, ...interface{}) +} + +// Interface logger interface +type Interface interface { + LogMode(LogLevel) Interface + Info(context.Context, string, ...interface{}) + Warn(context.Context, string, ...interface{}) + Error(context.Context, string, ...interface{}) + Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) +} + +var ( +//Discard = New(log.New(ioutil.Discard, "", log.LstdFlags), Config{}) +//Default = New(log.New(os.Stdout, "\r\n", log.LstdFlags), Config{ +// SlowThreshold: 100 * time.Millisecond, +// LogLevel: Warn, +// Colorful: true, +//}) +//Recorder = traceRecorder{Interface: Default} +) + +func New() ll.Interface { + return &logger{} +} + +type logger struct { + Writer + ll.Config + infoStr, warnStr, errStr string + traceStr, traceErrStr, traceWarnStr string +} + +// LogMode log mode +func (l logger) LogMode(level ll.LogLevel) ll.Interface { + newlogger := l + newlogger.LogLevel = level + return &newlogger +} + +// Info print info +func (l logger) Info(ctx context.Context, msg string, data ...interface{}) { + logx.Infof(l.infoStr+msg, data...) +} + +// Warn print warn messages +func (l logger) Warn(ctx context.Context, msg string, data ...interface{}) { + logx.Infof(l.warnStr+msg, data...) +} + +// Error print error messages +func (l logger) Error(ctx context.Context, msg string, data ...interface{}) { + logx.Errorf(l.errStr+msg, data...) +} + +// Trace print sql message +func (l logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { + if l.LogLevel > 0 { + elapsed := time.Since(begin) + switch { + case err != nil && l.LogLevel >= ll.Error: + sql, rows := fc() + if rows == -1 { + logx.Error(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql) + //l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + logx.Error(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql) + //l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= ll.Warn: + sql, rows := fc() + if rows == -1 { + logx.Info(l.traceWarnStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + logx.Info(l.traceWarnStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + case l.LogLevel >= ll.Info: + sql, rows := fc() + if rows == -1 { + logx.Info(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + logx.Info(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + } + } +} + +type traceRecorder struct { + Interface + BeginAt time.Time + SQL string + RowsAffected int64 + Err error +} + +func (l traceRecorder) New() *traceRecorder { + return &traceRecorder{Interface: l.Interface} +} + +func (l *traceRecorder) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) { + l.BeginAt = begin + l.SQL, l.RowsAffected = fc() + l.Err = err +} diff --git a/model/index.go b/model/index.go new file mode 100644 index 0000000..e152aba --- /dev/null +++ b/model/index.go @@ -0,0 +1,70 @@ +package model + +import ( + "fmt" + "github.com/go-redis/redis" + "github.com/tal-tech/go-zero/core/logx" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "job_risk_third/config" + "os" + "time" +) + +var ( + DB *gorm.DB + Redis *redis.Client +) + +func New(dsn string, cfg *gorm.Config) { + DB, _ = newDbConnection(dsn, cfg) + batchSyncTable([]interface{}{ + &TbRequestThirdLog{}, + }) +} + +func NewRedis(redisConfig config.RedisConfig) { + Redis = connectRedis(redisConfig) + +} + +func newDbConnection(dsn string, cfg *gorm.Config) (db *gorm.DB, err error) { + db, err = gorm.Open(mysql.Open(dsn), cfg) + if err != nil { + logx.Error(err) + os.Exit(-1) + } + return +} + +func batchSyncTable(tables []interface{}) { + if len(tables) > 0 { + for _, v := range tables { + if err := DB.AutoMigrate(v); err != nil { + logx.Error(err) + return + } + } + } + return +} + +func connectRedis(c config.RedisConfig) *redis.Client { + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%d", c.Address, c.Port), + Password: c.Pass, // no password set + DB: c.DB, // use default DB + PoolSize: c.PoolSize, // Redis连接池大小 + MaxRetries: 3, // 最大重试次数 + IdleTimeout: 10 * time.Second, // 空闲链接超时时间 + }) + pong, err := client.Ping().Result() + if err == redis.Nil { + logx.Error("Redis异常,redis.Nil") + } else if err != nil { + logx.Errorf("Redis失败,%v", err) + } else { + logx.Infof("Redis连接成功,%s", pong) + } + return client +} diff --git a/model/tb_request_third_log.go b/model/tb_request_third_log.go new file mode 100644 index 0000000..8a5f0aa --- /dev/null +++ b/model/tb_request_third_log.go @@ -0,0 +1,13 @@ +package model + +import "gorm.io/gorm" + +//第三方请求日志(中研) +type TbRequestThirdLog struct { + gorm.Model + RequestId string `gorm:"type:VARCHAR(40)" description:"请求编号"` + RequestUri string `gorm:"type:VARCHAR(200)" description:"请求地址"` + SourceData string `gorm:"type:TEXT" description:"原始数据"` + CryptoData string `gorm:"type:TEXT" description:"加密后的数据"` + ResultData string `gorm:"type:TEXT" description:"请求返回的数据"` +} diff --git a/pkg/utils/cryption.go b/pkg/utils/cryption.go new file mode 100644 index 0000000..3961a05 --- /dev/null +++ b/pkg/utils/cryption.go @@ -0,0 +1,140 @@ +package utils + +import ( + "bytes" + "crypto" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "fmt" + "github.com/tal-tech/go-zero/core/logx" + "math" + "math/big" + r "math/rand" + "time" +) + +// RSAEncrypt RSA加密 +// plainText 要加密的数据 +// publicKey 公钥匙内容 +func RSAEncrypt(plainText []byte, publicKey string) (string, error) { + key, _ := base64.StdEncoding.DecodeString(publicKey) + pubKey, _ := x509.ParsePKIXPublicKey(key) + logx.Infof("%v", pubKey) + //解密pem格式的公钥 + //block, _ := pem.Decode([]byte(publicKey)) + //if block == nil { + // return "", fmt.Errorf("public key error") + //} + //// 解析公钥 + //pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + //if err != nil { + // return "", err + //} + // 类型断言 + pub := pubKey.(*rsa.PublicKey) + encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, pub, plainText) + return base64.StdEncoding.EncodeToString(encryptedData), err +} + +// RSADecrypt RSA解密 +// cipherText 需要解密的byte数据 +// privateKey 私钥匙内容 +func RSADecrypt(cipherText, privateKey string) (string, error) { + encryptedDecodeBytes, err := base64.StdEncoding.DecodeString(cipherText) + if err != nil { + return "", err + } + key, _ := base64.StdEncoding.DecodeString(privateKey) + prvKey, _ := x509.ParsePKCS1PrivateKey(key) + originalData, err := rsa.DecryptPKCS1v15(rand.Reader, prvKey, encryptedDecodeBytes) + return string(originalData), err +} + +func RSAPriEncrypt(cipherText, privateKey string) (string, error) { + key, _ := base64.StdEncoding.DecodeString(privateKey) + prvKey, _ := x509.ParsePKCS1PrivateKey(key) + rng := rand.Reader + hashed := sha256.Sum256([]byte(cipherText)) + signature, err := rsa.SignPKCS1v15(rng, prvKey, crypto.SHA256, hashed[:]) + if err != nil { + logx.Errorf("Error from signing: %s\n", err) + return "", err + } + + return fmt.Sprintf("%x", signature), nil +} + +//RangeRand 生成区间[-m, n]的安全随机数 +func RangeRand(min, max int64) string { + if min > max { + panic("the min is greater than max!") + } + + if min < 0 { + f64Min := math.Abs(float64(min)) + i64Min := int64(f64Min) + result, _ := rand.Int(rand.Reader, big.NewInt(max+1+i64Min)) + + return fmt.Sprintf("%d", result.Int64()-i64Min) + } else { + result, _ := rand.Int(rand.Reader, big.NewInt(max-min+1)) + return fmt.Sprintf("%d", min+result.Int64()) + } +} + +// Krand 随机字符串 +func Krand(size int, kind int) []byte { + ikind, kinds, result := kind, [][]int{{10, 48}, {26, 97}, {26, 65}}, make([]byte, size) + is_all := kind > 2 || kind < 0 + r.Seed(time.Now().UnixNano()) + for i := 0; i < size; i++ { + if is_all { // random ikind + ikind = r.Intn(3) + } + scope, base := kinds[ikind][0], kinds[ikind][1] + result[i] = uint8(base + r.Intn(scope)) + } + return result +} + +//AESEncrypt AES加密 +func AESEncrypt(origData, key, iv []byte) (string, error) { + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + blockSize := block.BlockSize() + origData = PKCS5Padding(origData, blockSize) + blockMode := cipher.NewCFBEncrypter(block, iv) + crypted := make([]byte, len(origData)) + blockMode.XORKeyStream(crypted, origData) + return base64.StdEncoding.EncodeToString(crypted), nil +} + +func PKCS5Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padtext...) +} + +/** + * 计算sha256值 + * + * @param paramMap + * @return 签名后的所有数据,原始数据+签名 + */ + +func Sha256(requestMap map[string]string) string { + str := "" + for k, v := range requestMap { + str += fmt.Sprintf("%s=%s&", k, v) + } + logx.Infof("requestMap %s", str) + sum := sha256.Sum256([]byte(str)) + return fmt.Sprintf("%x", sum) +} diff --git a/pkg/utils/httpRequest.go b/pkg/utils/httpRequest.go new file mode 100644 index 0000000..beee54b --- /dev/null +++ b/pkg/utils/httpRequest.go @@ -0,0 +1,77 @@ +package utils + +import ( + "encoding/json" + "fmt" + "github.com/tal-tech/go-zero/core/logx" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +type Callback func(result []byte) interface{} + +func HttpDoCallBack(reqUrl, method string, params map[string]string, header map[string]string, callback Callback) (data interface{}, err error) { + body, err := HttpDo(reqUrl, method, params, header) + if err != nil { + logx.Errorf("method %s, reqUrl %s error %s", method, reqUrl, err) + return nil, err + } + if len(body) > 0 { + data = callback(body) + return data, err + } + return nil, err +} + +func HttpDo(reqUrl, method string, params map[string]string, header map[string]string) (data []byte, err error) { + var paramStr []byte + paramStr, _ = json.Marshal(params) + client := &http.Client{} + req, err := http.NewRequest(strings.ToUpper(method), reqUrl, strings.NewReader(string(paramStr))) + if err != nil { + logx.Errorf("method %s, reqUrl %s error %s", method, reqUrl, err) + return nil, err + } + for k, v := range header { + req.Header.Set(k, v) + } + resp, err := client.Do(req) + if err != nil { + logx.Errorf("method %s, reqUrl %s error %s", method, reqUrl, err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + logx.Errorf("method %s, reqUrl %s error %s", method, reqUrl, err) + return nil, err + } + return body, nil +} + +func GetParameterString(params map[string][]string) string { + var paramStr = "" + for k, v := range params { + s, _ := url.QueryUnescape(strings.Join(v, ",")) + if len(paramStr) == 0 { + paramStr = fmt.Sprintf("%s=%s", k, s) + } else { + paramStr = fmt.Sprintf("%s&%s=%s", paramStr, k, s) + } + } + return paramStr +} + +func GetSign(data, url string) string { + reqMap := make(map[string]string) + reqMap["obj"] = data + headerMap := make(map[string]string) + headerMap["Content-Type"] = "application/json" + b, err := HttpDo(url, "POST", reqMap, headerMap) + if err != nil { + logx.Error(err) + } + return string(b) +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..de8282d --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,149 @@ +package utils + +import ( + "crypto/hmac" + "crypto/sha1" + "fmt" + "github.com/google/uuid" + "github.com/tal-tech/go-zero/core/logx" + "io/ioutil" + "job_risk_third/model" + "mime" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" +) + +var ( + ErrorBadRequest = fmt.Errorf("无效的请求参数") +) + +func Validation(r *http.Request) error { + if err := ParseHttpParams(r); err != nil { + return err + } + //vars := mux.Vars(r) + timestamp, err := strconv.ParseInt(Param(r, "timestamp"), 10, 64) + if err != nil { + return ErrorBadRequest + } + now := time.Now().UnixNano() / 1e6 + if timestamp > now+100000 || now >= timestamp+6000000 { + return fmt.Errorf("请求过期") + } + sign := Param(r, "sign") + if len(sign) == 0 { + return ErrorBadRequest + } + nonce := Param(r, "nonce") + if len(nonce) == 0 { + return ErrorBadRequest + } + Params := r.Form + var keys []string + for k := range Params { + keys = append(keys, k) + } + sort.Strings(keys) + str := "" + for _, v := range keys { + if v != "sign" && v != "nonce" && v != "__json_param__" { + udata := url.Values{} + udata.Set(v, Params.Get(v)) + strUrl := udata.Encode() + str += "&" + strUrl + } + if v == "__json_param__" { + udata := url.Values{} + udata.Set(Params.Get(v), "") + str += "&" + udata.Encode() + } + } + if len(str) > 1 && str[len(str)-1:] == "=" { + str = r.Method + str[1:len(str)-1] + } + logx.Infof("str : %s", str) + our := Sha1(str, nonce) + if sign != strings.Trim(our, " ") { + logx.Infof("our:[%s] sign: [%s] ", our, sign) + return fmt.Errorf("错误的签名") + } + logx.Info(r.RemoteAddr[:9]) + if r.RemoteAddr[:9] != "127.0.0.1" && r.RemoteAddr[:9] != "localhost" { + //判断是否重放请求 + hExists := model.Redis.Exists(nonce).Val() + if hExists > 0 { + return fmt.Errorf("请勿重复请求") + } + //防止业务数据重复提交 + hExists = model.Redis.Exists(sign).Val() + if hExists > 0 { + return fmt.Errorf("请勿重复提交数据") + } + model.Redis.Set(nonce, timestamp, time.Minute*5) + model.Redis.Set(sign, timestamp, time.Minute*5) + } + return nil +} + +func ParseHttpParams(r *http.Request) (err error) { + ct := r.Header.Get("Content-Type") + if ct == "" { + ct = "application/octet-stream" + } + ct, _, err = mime.ParseMediaType(ct) + switch { + case ct == "application/json": + result, err := ioutil.ReadAll(r.Body) + if err != nil { + logx.Errorf("ParseHttpParams error info : %v", err) + return err + } + if r.Form == nil { + r.Form = url.Values{} + } + r.Form.Set("__json_param__", string(result)) + return nil + } + return err +} + +//sha1加签 +func Sha1(query string, priKey string) string { + key := []byte(priKey) + mac := hmac.New(sha1.New, key) + mac.Write([]byte(query)) + //query = base64.StdEncoding.EncodeToString(mac.Sum(nil)) + //query = url.QueryEscape(query) + query = fmt.Sprintf("%x", mac.Sum(nil)) + return query +} + +func Param(r *http.Request, key string) string { + var value string + value = r.FormValue(key) + if len(value) > 0 { + return value + } + value = r.URL.Query().Get(key) + if len(value) > 0 { + return value + } + value = r.Header.Get(key) + if len(value) > 0 { + return value + } + if cookie, _ := r.Cookie(key); cookie != nil { + return cookie.Value + } + return value +} + +func NewId() string { + id := uuid.New().String() + id = strings.Replace(id, "-", "", -1) + return id +} diff --git a/restart.sh b/restart.sh new file mode 100644 index 0000000..024d097 --- /dev/null +++ b/restart.sh @@ -0,0 +1,18 @@ +#!/bin/bash +echo "Input process name first" + +port=8016 +#一、根据端口号查询对应的pid,两种都行 +pid=$(netstat -nlp | grep :$port | awk '{print $7}' | awk -F"/" '{ print $1 }'); +#pid=$(ps -ef | grep 你的进程或端口 | grep -v grep | awk '{print $2}') + +#二、杀掉对应的进程,如果pid不存在,则不执行 +if [ -n "$pid" ]; then + kill -9 $pid; +fi + +if [ $? -eq 0 ];then + echo "kill $pid success" +else + echo "kill $pid fail" +fi \ No newline at end of file diff --git a/test/test.http b/test/test.http new file mode 100644 index 0000000..57abde2 --- /dev/null +++ b/test/test.http @@ -0,0 +1,55 @@ +POST http://localhost:8888/api/v1/push/feature_info +Content-Type: application/json +timestamp:1603417115338 +nonce:jahvuwygw91 +sign:cb9ba0a24ffca47e91059877005faae9e4d35d49 + +{ + "featureId": "aaaaa", + "featureName": "bbbbbb", + "featureLevel": "C", + "featureDeptId": "dddddd", + "featureTime": "2020-10-22 15:00:00", + "objectList": [{ + "objId": "eeeee", + "objName": "ffffff", + "objRegorg": "gggggg", + "objDeptid": "hhhhhh", + "objPosition": "iiiiii", + "isDeal": "1", + "dealId": "jjjjjj", + "dealName": "kkkkkk", + "dealTime": "2020-10-22 15:00:00", + "dealStatus": "1", + "dealResult": "已处置,属于正常工作情况" + }] +} + +### +POST http://localhost:8888/api/v1/push/feature_deal +Content-Type: application/json +timestamp:1603417115338 +nonce:jahvuwygw91 +sign:e611e34594c395de0a92686feee1ec9ecde71552 + +{ + "featureId": "111111", + "userId": "222222", + "objectId": "333333", + "dealId": "444444", + "dealName": "李四", + "dealTime": "2020-10-24 15:00:00", + "dealStatus": "1", + "dealResult": "已处置,属于正常工作情况" +} + +### +POST http://localhost:8888/api/v1/push/user_info +Content-Type: application/json +timestamp:1603331042040 +nonce:jahvuwygw91 +sign:d997907f2b3d593178802de7deef44b3d8bb2d0f + +{"userId": "1","userName": "zhiwux","name": "职务","position": "测试","job": "","deptId": "3300000000","deptName": "浙江浙大网新中研软件有限公司","logTime": "2020-09-10 16:20:20","logError": "3","regorg": "3300000000"} + +### diff --git a/中研风险秘钥 - 副本 .txt b/中研风险秘钥 - 副本 .txt new file mode 100644 index 0000000..c7391ef --- /dev/null +++ b/中研风险秘钥 - 副本 .txt @@ -0,0 +1,12 @@ +中研公钥: +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAszV1Idb7/Fcmpc+i2BVpBrJkWe9IT9g250uuOaYinjnWLkA9oORx/YLbcNbi99HcnD89quZN4Wqy9q2qDJNyJubbOdqSFVSjr9+TnRXiCVspoHjM6imWz+poXUZ4HSAbs/1YWioOt76kTxCx82McyNwQLVKKYSobjV/x77QlD6KUrDJEfQoTWWyy4YKUc/mjPrzTYbrClgfAsuPNClCuDrPkwx+8nFlqGJrXdQJGJRyxNjkvTCfNctEZYSUJemJyUQBbXH2hLpyrQG9pF5URrToXSAksiTdCdPxLM60NS3knxV4cm2gip0x5MyOGqqX9MLPCFqjlNO3WyaPUNmwUgQIDAQAB + +中研私钥: +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCzNXUh1vv8Vyalz6LYFWkGsmRZ70hP2DbnS645piKeOdYuQD2g5HH9gttw1uL30dycPz2q5k3harL2raoMk3Im5ts52pIVVKOv35OdFeIJWymgeMzqKZbP6mhdRngdIBuz/VhaKg63vqRPELHzYxzI3BAtUophKhuNX/HvtCUPopSsMkR9ChNZbLLhgpRz+aM+vNNhusKWB8Cy480KUK4Os+TDH7ycWWoYmtd1AkYlHLE2OS9MJ81y0RlhJQl6YnJRAFtcfaEunKtAb2kXlRGtOhdICSyJN0J0/EszrQ1LeSfFXhybaCKnTHkzI4aqpf0ws8IWqOU07dbJo9Q2bBSBAgMBAAECggEAIeTLaYIKmJg3FAcoSmwKOB0HQ8cwywEeqTI0Gm0kgP55VrgJr+Nk98iHDllmBe7oJZkEZc03D5opjOQdlFFStq7U6aVAGc6vQrUravtXi+N1BQez4dnJzzsLUBDi6MdI1grlafAoZnIlC1sh/OFS8V5FpPzGdUgMe/mYfXh5xfHt0SPAjQsvYTKztOg1VddVYp1C2fJjwv+gxQgv1hCyGAMGq9k9yMQ6yGeN9C2m2a2AgbmNPmH45pdgQE/AvV4URJRtj9sMhFkt6Mtzi0o7684Gbl0xE2bDST9o+h8F/qewVB7CVKFObaVGFRH8h18/6JADOG+RwfcTE4IcDZm7QQKBgQDdnZ8+GGiTyR7Kbrhz0MddDB30nqx0tDHlcgDssFQ2moFRJVW9p/VzRpQB9DO7dmQhLWYi4eu2+bR3NTWVd3bS7I21sYJtgKJfhA9dKAxQmS4smhti1MVysZsOperIZbhAbLGQxsfqnKK7G8BnTdFxcJ8n6eJO3QTuj+VXsVsTeQKBgQDPA3i9Tfq7uCnzxksagJvPEcnxl/e8aWBNNK5Q4x1IH0SXz8W1I5ARkwGExjClLJN3FJM1jt6ar2ItUhUP5sX/3zKjmMCUXvwBN/VNaZ8eGc9bFpvI0z4E4j4PGueg/36LXBR8ZPW+agwJwjX2LBJq/20l8s5EMYS3WO1RIf7/SQKBgQDJwmOks7IZwcOfhpe1EQE/6/UlrIPTJ+45NsYytgGlSJqs1rGtncjvbvT2pm2moI1eSyeuYEIp7kHnOXEUJ5PtSWFmZjoZGUA6d09Jf1le02ZfQtnl61HrLli9SD5svXa2aH5sER0Wsg3RDN3o7sbcYSz0uJDJPZzs1+JzKMuC+QKBgQCAynMiq1IT2ebX0AVHrr3A3RtbYCVzpceRIPZWQoYkKbfeDxi8sixekqv+M+Ntz8bK7hUL3B/n8rdM5OVPqE6E+xKhL1aYuGNmSq8lg1HIQ1x7Ghy/m5TZKvxbH2z+ABZ1k0r3fURaO9XTeG1kA1VOFi2Mz3u+d2RPQVccA9+GaQKBgQDFjxlIgXrU3HXmy6lyuMHzbNChSvrR2pgOBCrAs/GmY4VG/jC8TVSvdjMhBakuLL5pyCDWWzcb4b7FAIfds+OR6CfQ6M7DBCVIhF1kT4K1UGF/Od0mtIwn3/pzLieIu+Pme+2EIvK/yA2LSE9iqJTTfQdM4ttbX0N8KkwyCkhBcQ== + + +宁达公钥: +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwTkJpYvb+n96f/heVUmdLX1dt+l8V9/NOkbFxG4nSdZmcAaAmrlE1/+Kw2i3i+LaqQTw8a80TTVCTl3w83mwxzwExtQkB1KkxdPNTxOINVKuurE4fD6tzi3ooRC/7QLBb1Q+NSsDOdheSkLlC67URmKtW2IMwX/uCwQ0mpPBI0IcAVF42xMDx4PK9PkwZQe16d8x9Aa+rVpn8AoFGSqT0OoSm5Z20QrUs6tRyWV+B0JGEzc1Mg1oCu6880nCCMlAfzHXC9QXRWnFjJthF99NQNZNww7pKYlCDhYe5G/nl1aWo/o3e5zVu1uo2vF/bT+/hXaVNcSXkM56Z057fCJMNQIDAQAB + +宁达私钥: +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBOQmli9v6f3p/+F5VSZ0tfV236XxX3806RsXEbidJ1mZwBoCauUTX/4rDaLeL4tqpBPDxrzRNNUJOXfDzebDHPATG1CQHUqTF081PE4g1Uq66sTh8Pq3OLeihEL/tAsFvVD41KwM52F5KQuULrtRGYq1bYgzBf+4LBDSak8EjQhwBUXjbEwPHg8r0+TBlB7Xp3zH0Br6tWmfwCgUZKpPQ6hKblnbRCtSzq1HJZX4HQkYTNzUyDWgK7rzzScIIyUB/MdcL1BdFacWMm2EX301A1k3DDukpiUIOFh7kb+eXVpaj+jd7nNW7W6ja8X9tP7+FdpU1xJeQznpnTnt8Ikw1AgMBAAECggEBAIc2R8bd8SkBLhMBFdou8luT4BJDxGylwzKltd04jCvCadq44iPjxAY2377Qt6ifLg6a05T82uewfl7ipCttG8S//kO4ziGFtqJtDb3pWCagXn9sZq+jGPMv8xtK4lOT2xLx67o/CDnhbFIhL7EWPZunj9Jj8bMyt7xjy//jp4Lol7ZGb8tedGOF8fxT+BMVWELKvDvATm563qEGnbB1Ca8JosNecZGH+D9ITvZq9dbZtePAKmzMKILoNMhJUwAYAXBedi+u4KnqBSK4uvIcUlnVmBRrASq6UnrYUvaXZ+CtVh2Dkvsd9qnP/aE7qwYoCnziv2pnKWVj1UfGIFdAan0CgYEA51qGpjnuzycHOVZnK3TVLzXGgggURgIJ8L/dGGV4oVUDmQpw1EroXEz17oQhfBGxzKNze2QShZKgzHe5cg7WL3JDYwPnziDTCmKsgaly0Fa/N5RD98+PqJTCj0kDYnBO3sKyS9RBQIkryr9DnOtQkbfgZVIi0gXejrq4QK9soEMCgYEA1c6bw/GvFtXfLdlqv0BQj7wLm7TLC0iqgQY/QI+qDag7PtrGB+UPglu1b8gToFRiWHA0ievz2uZ37ffNuCWbX+mApRua5aBt2mzHgRKaQirtKPuxoV4E74EMKsuOYOEuKctg6hH1ibWYxlxVubnPg4Qym0g2WHXKfS3SLNB9dicCgYABOn3UjCI0f2SObWMG3Av1wDdZoWlaJdCfsqUd6AwH70ehnGiU+ADb3JzBs3nqCr4C9Cs80H84rlqkO06EyIdioRyyfebRNWNpfrSjy56MdKl3RhZGTpfYsVGHKUAXWblRfX8s3+eozBGrdfCJ+MXowC003IbKzrUr1Nn9nfDZuQKBgFYy5A3NhJ+aPk5H14efsFsinzN5Ylr8QvGdySaIRTEYYDppDWnlaalOvAmDCpabLsMlCamJXVkljbh9LY1ObCPxChKG3J4zXdawAIcDLvn6QH9Dakv6kdbVmkgupQpd/rSO8FWuQ+XvNtbSJyWnygfl5llAddiYNLjfHls++zYFAoGAY3tMwRuTgHE0WR/N0TulvmbZCUz6y3lhQveQsi6hNqAMPz7cpvh9bwZgzZG2JHTUJOfC4lSlcy67YcF1qifgm8OjST0JiwhIlQ84XhW/TJe8obSTTddnX0hjPyL1Ocom8EYb7qY6ZWyohywMNIEG9EYQGehXlbDBVN/IBnX08mw=