From 831519875b995e3421f576280bc11d286493d473 Mon Sep 17 00:00:00 2001 From: guzeng Date: Tue, 17 Aug 2021 17:48:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E5=8F=8A=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2=E4=BC=98=E6=83=A0=E5=88=B8=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client.go | 223 +++++++++++++++++++++++++++++++++++++++++++++++++ client_test.go | 17 ++++ conn.go | 26 ++++++ coupon.pb.go | 198 +++++++++++++++++++++++++++++++++++++++++++ coupon.proto | 23 +++++ go.mod | 11 +++ go.sum | 26 ++++++ 7 files changed, 524 insertions(+) create mode 100644 client.go create mode 100644 client_test.go create mode 100644 conn.go create mode 100644 coupon.pb.go create mode 100644 coupon.proto create mode 100644 go.mod create mode 100644 go.sum diff --git a/client.go b/client.go new file mode 100644 index 0000000..71d18e3 --- /dev/null +++ b/client.go @@ -0,0 +1,223 @@ +package couponrpc + +import ( + "crypto/md5" + "encoding/hex" + "encoding/json" + "errors" + "log" + "strconv" + "strings" + "time" + + "git.tetele.net/tgo/crypter" + + "github.com/golang/protobuf/proto" +) + +type AvailableReqArg struct { + Dbname string `json:"dbname"` + UserCouponId string `json:"user_coupon_id"` + UserId string `json:"user_id"` + ProductId string `json:"product_id"` +} + +type AvailableRes struct { + Available bool `json:"available"` + Money string `json:"money"` + Name string `json:"name"` +} + +type UseReqArg struct { + Dbname string `json:"dbname"` + UserCouponId string `json:"user_coupon_id"` + OrderSn string `json:"order_sn"` +} + +type UseRes struct { + Success bool +} + +/** + * 优惠券是否可用 + * @param data {"user_id":"","product_id":"","coupon_id":""} + * @param return is_available,name,money,error + */ +func IsAvailable(dbname, user_id, product_id, user_coupon_id string, url ...string) (*AvailableRes, error) { + + conn, err := rpc_server_conn(url...) + if err != nil { + return nil, err + } + defer conn.Close() + + arg := AvailableReqArg{dbname, user_coupon_id, user_id, product_id} + + data_json, err := json.Marshal(arg) + if err != nil { + return nil, err + } + now_int64 := time.Now().Unix() + + encryData := crypter.DesEn(string(data_json), "conaapon") + + now := strconv.FormatInt(now_int64, 10) + + sign := Sign(encryData, now) + + req := &CouponRequest{proto.String(encryData), proto.String(now), proto.String(sign), nil} + + res := &CouponResponse{} + + err = conn.IsAvailable(req, res) + + if err != nil { + log.Println("coupon rpc error:", err) + return nil, err + } + + res_data := res.GetData() + + if res_data != "" { + + time_int64, err := strconv.ParseInt(res.GetTime(), 10, 64) + if err != nil { + return nil, err + } + + now_int64 = time.Now().Unix() + + if now_int64-time_int64 > 10 || time_int64-now_int64 > 10 { + //时间误差前后10秒,返回 + return nil, errors.New("返回时间错误") + } + + check_sign := CheckSign(res.GetSign(), res_data, res.GetTime()) + if !check_sign { + return nil, errors.New("返回数据签名错误") + } + + //解密 + res_data_de := crypter.DesDe(res_data, "conaapon") + + var res_arr AvailableRes + + err = json.Unmarshal([]byte(res_data_de), &res_arr) + + if err != nil { + return nil, err + } + return &res_arr, nil + } + + return nil, nil +} + +/** + * 使用优惠券 + * @param data {"user_coupon_id":"","order_sn":""} + * @param return is_available,name,money,error + */ +func Use(dbname, user_coupon_id, order_sn string, url ...string) (*UseRes, error) { + + var coupon_rpc_url string = "127.0.0.1:7972" + if len(url) > 0 && url[0] != "" { + coupon_rpc_url = url[0] + } + conn, _, err := DialCouponService("tcp", coupon_rpc_url) + if err != nil { + return nil, err + } + defer conn.Close() + + arg := UseReqArg{dbname, user_coupon_id, order_sn} + + data_json, err := json.Marshal(arg) + if err != nil { + return nil, err + } + now_int64 := time.Now().Unix() + + encryData := crypter.DesEn(string(data_json), "conaapon") + + now := strconv.FormatInt(now_int64, 10) + + sign := Sign(encryData, now) + + req := &CouponRequest{proto.String(encryData), proto.String(now), proto.String(sign), nil} + + res := &CouponResponse{} + + err = conn.Use(req, res) + + if err != nil { + log.Println("coupon rpc error:", err) + return nil, err + } + + res_data := res.GetData() + + if res_data != "" { + + time_int64, err := strconv.ParseInt(res.GetTime(), 10, 64) + if err != nil { + return nil, err + } + + now_int64 = time.Now().Unix() + + if now_int64-time_int64 > 10 || time_int64-now_int64 > 10 { + //时间误差前后10秒,返回 + return nil, errors.New("返回时间错误") + } + + check_sign := CheckSign(res.GetSign(), res_data, res.GetTime()) + if !check_sign { + return nil, errors.New("返回数据签名错误") + } + + //解密 + res_data_de := crypter.DesDe(res_data, "conaapon") + + var res_arr UseRes + + err = json.Unmarshal([]byte(res_data_de), &res_arr) + + if err != nil { + return nil, err + } + return &res_arr, nil + } + + return nil, nil +} + +/** + * 签名 + */ +func Sign(data string, salt string) string { + + var build strings.Builder + + build.WriteString(data) + build.WriteString(salt) + build.WriteString("cou$%po87n") + + data_str := build.String() + + h := md5.New() + h.Write([]byte(data_str)) // 需要加密的字符串 + return hex.EncodeToString(h.Sum(nil)) // 输出加密结果 + +} + +/** + * 验证签名 + */ +func CheckSign(sign_str, data, salt string) bool { + sign := Sign(data, salt) + if strings.Compare(sign_str, sign) > -1 { + return true + } + return false +} diff --git a/client_test.go b/client_test.go new file mode 100644 index 0000000..564d9f5 --- /dev/null +++ b/client_test.go @@ -0,0 +1,17 @@ +package couponrpc + +import ( + "testing" +) + +func Test_IsAvailable(t *testing.T) { + + dbname := "shop_v2" + user_id := "2" + product_id := "2" + user_coupon_id := "3" + res, err := IsAvailable(dbname, user_id, product_id, user_coupon_id) + t.Log(res) + t.Log(err) + +} diff --git a/conn.go b/conn.go new file mode 100644 index 0000000..a287d5a --- /dev/null +++ b/conn.go @@ -0,0 +1,26 @@ +package couponrpc + +import ( + "git.tetele.net/tgo/conf" +) + +const DES_KEY = "conaapon" + +func rpc_server_conn(url ...string) (*CouponServiceClient, error) { + + var rpc_url string + if len(url) > 0 && url[0] != "" { + rpc_url = url[0] + } else if conf.COUPON_RPC_URL != "" { + rpc_url = conf.COUPON_RPC_URL + } else { + rpc_url = "127.0.0.1:" + conf.COUPON_RPC_PORT + } + + conn, _, err := DialCouponService("tcp", rpc_url) + if err != nil { + return nil, err + } + + return conn, nil +} diff --git a/coupon.pb.go b/coupon.pb.go new file mode 100644 index 0000000..aabad07 --- /dev/null +++ b/coupon.pb.go @@ -0,0 +1,198 @@ +// Code generated by protoc-gen-go. +// source: coupon.proto +// DO NOT EDIT! + +/* +Package couponrpc is a generated protocol buffer package. + +It is generated from these files: + coupon.proto + +It has these top-level messages: + CouponRequest + CouponResponse +*/ +package couponrpc + +import proto "github.com/chai2010/protorpc/proto" +import math "math" + +import "io" +import "log" +import "net" +import "net/rpc" +import "time" +import protorpc "github.com/chai2010/protorpc" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = math.Inf + +// 是否可用请求结构 +type CouponRequest struct { + Data *string `protobuf:"bytes,1,opt,name=data" json:"data,omitempty"` + Time *string `protobuf:"bytes,2,opt,name=time" json:"time,omitempty"` + Sign *string `protobuf:"bytes,3,opt,name=sign" json:"sign,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CouponRequest) Reset() { *m = CouponRequest{} } +func (m *CouponRequest) String() string { return proto.CompactTextString(m) } +func (*CouponRequest) ProtoMessage() {} + +func (m *CouponRequest) GetData() string { + if m != nil && m.Data != nil { + return *m.Data + } + return "" +} + +func (m *CouponRequest) GetTime() string { + if m != nil && m.Time != nil { + return *m.Time + } + return "" +} + +func (m *CouponRequest) GetSign() string { + if m != nil && m.Sign != nil { + return *m.Sign + } + return "" +} + +// 是否可用响应结构 +type CouponResponse struct { + Data *string `protobuf:"bytes,1,opt,name=data" json:"data,omitempty"` + Time *string `protobuf:"bytes,2,opt,name=time" json:"time,omitempty"` + Sign *string `protobuf:"bytes,3,opt,name=sign" json:"sign,omitempty"` + XXX_unrecognized []byte `json:"-"` +} + +func (m *CouponResponse) Reset() { *m = CouponResponse{} } +func (m *CouponResponse) String() string { return proto.CompactTextString(m) } +func (*CouponResponse) ProtoMessage() {} + +func (m *CouponResponse) GetData() string { + if m != nil && m.Data != nil { + return *m.Data + } + return "" +} + +func (m *CouponResponse) GetTime() string { + if m != nil && m.Time != nil { + return *m.Time + } + return "" +} + +func (m *CouponResponse) GetSign() string { + if m != nil && m.Sign != nil { + return *m.Sign + } + return "" +} + +func init() { +} + +type CouponService interface { + IsAvailable(in *CouponRequest, out *CouponResponse) error + Use(in *CouponRequest, out *CouponResponse) error +} + +// AcceptCouponServiceClient accepts connections on the listener and serves requests +// for each incoming connection. Accept blocks; the caller typically +// invokes it in a go statement. +func AcceptCouponServiceClient(lis net.Listener, x CouponService) { + srv := rpc.NewServer() + if err := srv.RegisterName("CouponService", x); err != nil { + log.Fatal(err) + } + + for { + conn, err := lis.Accept() + if err != nil { + log.Fatalf("lis.Accept(): %v\n", err) + } + go srv.ServeCodec(protorpc.NewServerCodec(conn)) + } +} + +// RegisterCouponService publish the given CouponService implementation on the server. +func RegisterCouponService(srv *rpc.Server, x CouponService) error { + if err := srv.RegisterName("CouponService", x); err != nil { + return err + } + return nil +} + +// NewCouponServiceServer returns a new CouponService Server. +func NewCouponServiceServer(x CouponService) *rpc.Server { + srv := rpc.NewServer() + if err := srv.RegisterName("CouponService", x); err != nil { + log.Fatal(err) + } + return srv +} + +// ListenAndServeCouponService listen announces on the local network address laddr +// and serves the given CouponService implementation. +func ListenAndServeCouponService(network, addr string, x CouponService) error { + lis, err := net.Listen(network, addr) + if err != nil { + return err + } + defer lis.Close() + + srv := rpc.NewServer() + if err := srv.RegisterName("CouponService", x); err != nil { + return err + } + + for { + conn, err := lis.Accept() + if err != nil { + log.Fatalf("lis.Accept(): %v\n", err) + } + go srv.ServeCodec(protorpc.NewServerCodec(conn)) + } +} + +type CouponServiceClient struct { + *rpc.Client +} + +// NewCouponServiceClient returns a CouponService rpc.Client and stub to handle +// requests to the set of CouponService at the other end of the connection. +func NewCouponServiceClient(conn io.ReadWriteCloser) (*CouponServiceClient, *rpc.Client) { + c := rpc.NewClientWithCodec(protorpc.NewClientCodec(conn)) + return &CouponServiceClient{c}, c +} + +func (c *CouponServiceClient) IsAvailable(in *CouponRequest, out *CouponResponse) error { + return c.Call("CouponService.IsAvailable", in, out) +} +func (c *CouponServiceClient) Use(in *CouponRequest, out *CouponResponse) error { + return c.Call("CouponService.Use", in, out) +} + +// DialCouponService connects to an CouponService at the specified network address. +func DialCouponService(network, addr string) (*CouponServiceClient, *rpc.Client, error) { + c, err := protorpc.Dial(network, addr) + if err != nil { + return nil, nil, err + } + return &CouponServiceClient{c}, c, nil +} + +// DialCouponServiceTimeout connects to an CouponService at the specified network address. +func DialCouponServiceTimeout(network, addr string, + timeout time.Duration) (*CouponServiceClient, *rpc.Client, error) { + c, err := protorpc.DialTimeout(network, addr, timeout) + if err != nil { + return nil, nil, err + } + return &CouponServiceClient{c}, c, nil +} diff --git a/coupon.proto b/coupon.proto new file mode 100644 index 0000000..e8b7661 --- /dev/null +++ b/coupon.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; +package couponrpc; + +// 是否可用请求结构 +message CouponRequest { + string data = 1; + string time = 2; + string sign = 3; +} + +// 是否可用响应结构 +message CouponResponse { + string data = 1; + string time = 2; + string sign = 3; +} + + +// rpc方法 +service CouponService { + rpc isAvailable (CouponRequest) returns (CouponResponse); // 优惠券是否可用 + rpc use (CouponRequest) returns (CouponResponse); // 使用优惠券 +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..c259a0c --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module git.tetele.net/tgo/couponrpc + +go 1.14 + +require ( + git.tetele.net/tgo/conf v0.33.1 + git.tetele.net/tgo/crypter v0.2.2 + github.com/chai2010/protorpc v1.1.3 + github.com/golang/protobuf v1.5.2 + golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..836a891 --- /dev/null +++ b/go.sum @@ -0,0 +1,26 @@ +git.tetele.net/tgo/conf v0.33.1 h1:ZEIv3Vq35RCv5f3T3Uz97s2mkZLl7W5OlmXvzI3/sS8= +git.tetele.net/tgo/conf v0.33.1/go.mod h1:AWVIBEDE5dtotthUgR0SWaR2Qa6/f+O5WQ3s7Tj8q7A= +git.tetele.net/tgo/crypter v0.2.2 h1:YMQJh2Gj5Po4ZfelJUmXBKi01UbmtiSy3bmqRfnYQMo= +git.tetele.net/tgo/crypter v0.2.2/go.mod h1:vfvRLZA8+lHNgNXneOcgvVhDyuv25ZRb+C6xHOmXNx0= +github.com/chai2010/protorpc v1.1.3 h1:VJK5hIoZn0XCGol0GmbxZkUG6FbTI5LP2Lam6RVd15w= +github.com/chai2010/protorpc v1.1.3/go.mod h1:/wO0kiyVdu7ug8dCMrA2yDr2vLfyhsLEuzLa9J2HJ+I= +github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ= +golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=