@@ -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 |
@@ -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":{ | |||||
} | |||||
} | |||||
``` |
@@ -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) | |||||
} |
@@ -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 | |||||
} |
@@ -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" | |||||
} |
@@ -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 | |||||
} |
@@ -0,0 +1,15 @@ | |||||
 | |||||
仓库:[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]) |
@@ -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 | |||||
) |
@@ -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 | |||||
} | |||||
} |
@@ -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{} |
@@ -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"` | |||||
} |
@@ -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) | |||||
} |
@@ -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 | |||||
} |
@@ -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) | |||||
} |
@@ -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 | |||||
} |
@@ -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 | |||||
} |
@@ -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 | |||||
} |
@@ -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:"请求返回的数据"` | |||||
} |
@@ -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) | |||||
} |
@@ -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) | |||||
} |
@@ -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 | |||||
} |
@@ -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 |
@@ -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"} | |||||
### |
@@ -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= |