From 6fa185003d168924a2f90c1b7640407e82455053 Mon Sep 17 00:00:00 2001 From: zzw <1464003642@qq.com> Date: Tue, 13 Oct 2026 11:05:09 +0800 Subject: [PATCH] feat: analysis gen qrcode --- README.md | 3 + deploy/apisix/lua/auth.lua | 18 +-- pkg/oss/oss.go | 1 - pkg/wechat/auth/token.go | 23 +++- pkg/wechat/qrcode/client.go | 13 ++ pkg/wechat/qrcode/types.go | 33 +++++ pkg/wechat/qrcode/unlimited.go | 88 ++++++++++++++ pkg/wechat/qrcode/wxacode.go | 75 ++++++++++++ rpc/analysis/analysis.pb | Bin 198 -> 13967 bytes rpc/analysis/analysis.proto | 21 ++-- rpc/analysis/pb/analysis.pb.go | 68 ++++++----- rpc/analysis/pb/analysis_grpc.pb.go | 76 ++++++------ services/analysis/analysis.go | 2 +- .../analysis/analysisauth/analysisAuth.go | 38 ++++++ services/analysis/analysisclient/analysis.go | 38 ------ services/analysis/internal/config/wechat.go | 45 +++++++ services/analysis/internal/dao/qrcode.go | 17 +++ .../internal/logic/generateQrcodeLogic.go | 114 ++++++++++++++++++ services/analysis/internal/logic/pinglogic.go | 30 ----- services/analysis/internal/logic/response.go | 31 +++++ .../internal/server/analysisAuthServer.go | 29 +++++ .../internal/server/analysisServer.go | 29 ----- services/analysis/run.toml | 61 ++++++++++ .../internal/logic/internalUploadLogic.go | 1 - 24 files changed, 671 insertions(+), 183 deletions(-) create mode 100644 pkg/wechat/qrcode/client.go create mode 100644 pkg/wechat/qrcode/types.go create mode 100644 pkg/wechat/qrcode/unlimited.go create mode 100644 pkg/wechat/qrcode/wxacode.go create mode 100644 services/analysis/analysisauth/analysisAuth.go delete mode 100644 services/analysis/analysisclient/analysis.go create mode 100644 services/analysis/internal/config/wechat.go create mode 100644 services/analysis/internal/dao/qrcode.go create mode 100644 services/analysis/internal/logic/generateQrcodeLogic.go delete mode 100644 services/analysis/internal/logic/pinglogic.go create mode 100644 services/analysis/internal/logic/response.go create mode 100644 services/analysis/internal/server/analysisAuthServer.go delete mode 100644 services/analysis/internal/server/analysisServer.go create mode 100644 services/analysis/run.toml diff --git a/README.md b/README.md index 819961d..5de1973 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,10 @@ mv order.proto ../../rpc/order/ 编辑 `rpc/order/order.proto`,把 `go_package` 改成本仓库模块路径: ```protobuf + +import "google/api/annotations.proto"; option go_package = "lone-services/rpc/order"; + ``` ### 3. 生成pb 文件 diff --git a/deploy/apisix/lua/auth.lua b/deploy/apisix/lua/auth.lua index 4a48ae1..4b3a5dc 100644 --- a/deploy/apisix/lua/auth.lua +++ b/deploy/apisix/lua/auth.lua @@ -11,18 +11,20 @@ function _M.access(conf, ctx) core.log.info("REQUEST_DEBUG: uri = "..request_uri) local white_list = { - ["/api/v3/sms/send"] = true, - ["/api/v3/login"] = true, - ["/api/v3/version"] = true, + ["/api/v3/sms/send"] = true, + ["/api/v3/login"] = true, + ["/api/v3/version"] = true, - ["/admin/v3/login"] = true, - ["/admin/v3/refresh"] = true, - ["/admin/v3/transit/bind"] = true, + ["/admin/v3/login"] = true, + ["/admin/v3/refresh"] = true, + ["/admin/v3/transit/bind"] = true, - ["/api/v3/express/sf"] = true, - ["/api/v3/express/jd"] = true, + ["/api/v3/express/sf"] = true, + ["/api/v3/express/jd"] = true, ["/api/v3/chore/oss/upload"] = true, + + ["/api/v2/analysis/qrcode/generate"] = true, } local user_key = core.request.header(ctx, "authorization") diff --git a/pkg/oss/oss.go b/pkg/oss/oss.go index 1beb3bc..4201770 100644 --- a/pkg/oss/oss.go +++ b/pkg/oss/oss.go @@ -124,7 +124,6 @@ func normalizeEndpoint(endpoint, bucket string) string { return ep } -// buildObjectKey: {oss.dir}{YYYYMMDD}/{filename} func buildObjectKey(configDir, filename string) string { base := strings.Trim(sanitizeDir(configDir), "/") if base != "" { diff --git a/pkg/wechat/auth/token.go b/pkg/wechat/auth/token.go index 21078dc..b14a7ed 100644 --- a/pkg/wechat/auth/token.go +++ b/pkg/wechat/auth/token.go @@ -1,8 +1,13 @@ package auth import ( + "context" + "crypto/md5" + "encoding/hex" "fmt" + "time" + "lone-services/pkg/redis" "lone-services/pkg/utils" jsoniter "github.com/json-iterator/go" @@ -17,10 +22,21 @@ type AccessTokenResult struct { ErrMsg string `json:"errmsg"` } -func (c *Client) AccessToken() (*AccessTokenResult, error) { +func (c *Client) AccessToken(ctx context.Context) (*AccessTokenResult, error) { if c == nil || c.AppID == utils.StringEmpty || c.Secret == utils.StringEmpty { return nil, fmt.Errorf("wechat app config missing") } + if ctx == nil { + ctx = context.Background() + } + + secretMd5 := md5.Sum([]byte(c.Secret)) + key := fmt.Sprintf("wechat:access_token:%s:%s", c.AppID, hex.EncodeToString(secretMd5[:])[:8]) + + token, err := redis.Client.Get(ctx, key).Result() + if err == nil && token != utils.StringEmpty { + return &AccessTokenResult{AccessToken: token}, nil + } body, err := utils.Curl(accessTokenURL).SetData(map[string]string{ "grant_type": "client_credential", @@ -41,5 +57,10 @@ func (c *Client) AccessToken() (*AccessTokenResult, error) { if result.AccessToken == utils.StringEmpty { return nil, fmt.Errorf("wechat access_token empty") } + + expire := time.Duration(result.ExpiresIn-300) * time.Second + if expire > 0 { + _ = redis.Client.Set(ctx, key, result.AccessToken, expire).Err() + } return &result, nil } diff --git a/pkg/wechat/qrcode/client.go b/pkg/wechat/qrcode/client.go new file mode 100644 index 0000000..6a3e333 --- /dev/null +++ b/pkg/wechat/qrcode/client.go @@ -0,0 +1,13 @@ +package qrcode + +type Client struct { + AppID string + Secret string +} + +func NewClient(appID, secret string) *Client { + return &Client{ + AppID: appID, + Secret: secret, + } +} diff --git a/pkg/wechat/qrcode/types.go b/pkg/wechat/qrcode/types.go new file mode 100644 index 0000000..7527cf3 --- /dev/null +++ b/pkg/wechat/qrcode/types.go @@ -0,0 +1,33 @@ +package qrcode + +type LineColor struct { + R int `json:"r"` + G int `json:"g"` + B int `json:"b"` +} + +type UnlimitedRequest struct { + Scene string `json:"scene"` + Page string `json:"page,omitempty"` + CheckPath *bool `json:"check_path,omitempty"` + EnvVersion string `json:"env_version,omitempty"` // release / trial / develop + Width int `json:"width,omitempty"` + AutoColor bool `json:"auto_color,omitempty"` + LineColor *LineColor `json:"line_color,omitempty"` + IsHyaline bool `json:"is_hyaline,omitempty"` +} + +// WxaCodeRequest 有数量限制的小程序码(getwxacode),path 可带 query。 +type WxaCodeRequest struct { + Path string `json:"path"` + EnvVersion string `json:"env_version,omitempty"` + Width int `json:"width,omitempty"` + AutoColor bool `json:"auto_color,omitempty"` + LineColor *LineColor `json:"line_color,omitempty"` + IsHyaline bool `json:"is_hyaline,omitempty"` +} + +type apiErrorBody struct { + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` +} diff --git a/pkg/wechat/qrcode/unlimited.go b/pkg/wechat/qrcode/unlimited.go new file mode 100644 index 0000000..d9ab7d6 --- /dev/null +++ b/pkg/wechat/qrcode/unlimited.go @@ -0,0 +1,88 @@ +package qrcode + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" + + "lone-services/pkg/utils" + "lone-services/pkg/wechat/auth" +) + +const unlimitedURL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit" + +func (c *Client) GetUnlimited(accessToken string, req *UnlimitedRequest) ([]byte, error) { + if accessToken == utils.StringEmpty { + return nil, fmt.Errorf("wechat access_token empty") + } + if req == nil || req.Scene == utils.StringEmpty { + return nil, fmt.Errorf("wechat qrcode scene empty") + } + + payload, err := json.Marshal(req) + if err != nil { + return nil, err + } + + url := fmt.Sprintf("%s?access_token=%s", unlimitedURL, accessToken) + httpClient := &http.Client{Timeout: 15 * time.Second} + resp, err := httpClient.Post(url, "application/json", bytes.NewReader(payload)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("wechat qrcode http status %d", resp.StatusCode) + } + + if err := parseImageOrError(body); err != nil { + return nil, err + } + return body, nil +} + +func (c *Client) CreateUnlimited(ctx context.Context, req *UnlimitedRequest) ([]byte, error) { + token, err := auth.NewClient(c.AppID, c.Secret).AccessToken(ctx) + if err != nil { + return nil, err + } + return c.GetUnlimited(token.AccessToken, req) +} + +func (c *Client) CreateUnlimitedBase64(ctx context.Context, req *UnlimitedRequest) (string, error) { + img, err := c.CreateUnlimited(ctx, req) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(img), nil +} + +func parseImageOrError(body []byte) error { + trim := bytes.TrimSpace(body) + if len(trim) == 0 { + return fmt.Errorf("wechat qrcode empty response") + } + if trim[0] == '{' || strings.HasPrefix(string(trim), "{\"errcode\"") { + var errBody apiErrorBody + if err := json.Unmarshal(trim, &errBody); err != nil { + return fmt.Errorf("wechat qrcode invalid error body: %w", err) + } + if errBody.ErrCode != utils.NumberZero { + return errors.New(errBody.ErrMsg) + } + return fmt.Errorf("wechat qrcode unexpected json response") + } + return nil +} diff --git a/pkg/wechat/qrcode/wxacode.go b/pkg/wechat/qrcode/wxacode.go new file mode 100644 index 0000000..e42d855 --- /dev/null +++ b/pkg/wechat/qrcode/wxacode.go @@ -0,0 +1,75 @@ +package qrcode + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "lone-services/pkg/utils" + "lone-services/pkg/wechat/auth" +) + +// 小程序码(有数量限制):https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/qrcode-link/qr-code/getQRCode.html +const wxaCodeURL = "https://api.weixin.qq.com/wxa/getwxacode" + +// BuildPath 拼小程序 path,如 pagesB/userLogin/index?session_id=xxx +func BuildPath(page string, params map[string]string) string { + if len(params) == 0 { + return page + } + query := url.Values{} + for k, v := range params { + query.Add(k, v) + } + return page + "?" + query.Encode() +} + +// GetWxaCode 使用已有 access_token 生成有限小程序码,成功返回图片二进制。 +func (c *Client) GetWxaCode(accessToken string, req *WxaCodeRequest) ([]byte, error) { + if accessToken == utils.StringEmpty { + return nil, fmt.Errorf("wechat access_token empty") + } + if req == nil || req.Path == utils.StringEmpty { + return nil, fmt.Errorf("wechat qrcode path empty") + } + + payload, err := json.Marshal(req) + if err != nil { + return nil, err + } + + apiURL := fmt.Sprintf("%s?access_token=%s", wxaCodeURL, accessToken) + httpClient := &http.Client{Timeout: 15 * time.Second} + resp, err := httpClient.Post(apiURL, "application/json", bytes.NewReader(payload)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("wechat qrcode http status %d", resp.StatusCode) + } + + if err := parseImageOrError(body); err != nil { + return nil, err + } + return body, nil +} + +// CreateWxaCode 通过 auth 拉取 access_token 后生成有限小程序码。 +func (c *Client) CreateWxaCode(ctx context.Context, req *WxaCodeRequest) ([]byte, error) { + token, err := auth.NewClient(c.AppID, c.Secret).AccessToken(ctx) + if err != nil { + return nil, err + } + return c.GetWxaCode(token.AccessToken, req) +} diff --git a/rpc/analysis/analysis.pb b/rpc/analysis/analysis.pb index 048a7ca07cace32650a0717572b2e2bbc77d4057..03dafbea4061ec94720a95f5337c479ab4655de5 100644 GIT binary patch literal 13967 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
8{h!MQ2I$p$5A)+<&(t4a)?ZgWkRDS3!_6 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 delta 116 zcmeCrJ;un)b(nFoi>Y$Fk}MZ{P-5En~9W?s4kqXMG_XAqdJB*(=Olv-Sn xpI4j;md{6$2eOSUxi}p25_2kxGmC|ExmW^#`h^7b5}`tRlNHURCuf?e0RRSBAA analysis.Request - 1, // 1: analysis.Analysis.Ping:output_type -> analysis.Response + 0, // 0: analysis.AnalysisAuth.GenerateQrcode:input_type -> analysis.GenerateQrcodeRequest + 1, // 1: analysis.AnalysisAuth.GenerateQrcode:output_type -> analysis.Response 1, // [1:2] is the sub-list for method output_type 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name diff --git a/rpc/analysis/pb/analysis_grpc.pb.go b/rpc/analysis/pb/analysis_grpc.pb.go index 187b7c4..760a40c 100644 --- a/rpc/analysis/pb/analysis_grpc.pb.go +++ b/rpc/analysis/pb/analysis_grpc.pb.go @@ -19,101 +19,101 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - Analysis_Ping_FullMethodName = "/analysis.Analysis/Ping" + AnalysisAuth_GenerateQrcode_FullMethodName = "/analysis.AnalysisAuth/GenerateQrcode" ) -// AnalysisClient is the client API for Analysis service. +// AnalysisAuthClient is the client API for AnalysisAuth service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type AnalysisClient interface { - Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) +type AnalysisAuthClient interface { + GenerateQrcode(ctx context.Context, in *GenerateQrcodeRequest, opts ...grpc.CallOption) (*Response, error) } -type analysisClient struct { +type analysisAuthClient struct { cc grpc.ClientConnInterface } -func NewAnalysisClient(cc grpc.ClientConnInterface) AnalysisClient { - return &analysisClient{cc} +func NewAnalysisAuthClient(cc grpc.ClientConnInterface) AnalysisAuthClient { + return &analysisAuthClient{cc} } -func (c *analysisClient) Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { +func (c *analysisAuthClient) GenerateQrcode(ctx context.Context, in *GenerateQrcodeRequest, opts ...grpc.CallOption) (*Response, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Response) - err := c.cc.Invoke(ctx, Analysis_Ping_FullMethodName, in, out, cOpts...) + err := c.cc.Invoke(ctx, AnalysisAuth_GenerateQrcode_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } -// AnalysisServer is the server API for Analysis service. -// All implementations must embed UnimplementedAnalysisServer +// AnalysisAuthServer is the server API for AnalysisAuth service. +// All implementations must embed UnimplementedAnalysisAuthServer // for forward compatibility. -type AnalysisServer interface { - Ping(context.Context, *Request) (*Response, error) - mustEmbedUnimplementedAnalysisServer() +type AnalysisAuthServer interface { + GenerateQrcode(context.Context, *GenerateQrcodeRequest) (*Response, error) + mustEmbedUnimplementedAnalysisAuthServer() } -// UnimplementedAnalysisServer must be embedded to have +// UnimplementedAnalysisAuthServer must be embedded to have // forward compatible implementations. // // NOTE: this should be embedded by value instead of pointer to avoid a nil // pointer dereference when methods are called. -type UnimplementedAnalysisServer struct{} +type UnimplementedAnalysisAuthServer struct{} -func (UnimplementedAnalysisServer) Ping(context.Context, *Request) (*Response, error) { - return nil, status.Error(codes.Unimplemented, "method Ping not implemented") +func (UnimplementedAnalysisAuthServer) GenerateQrcode(context.Context, *GenerateQrcodeRequest) (*Response, error) { + return nil, status.Error(codes.Unimplemented, "method GenerateQrcode not implemented") } -func (UnimplementedAnalysisServer) mustEmbedUnimplementedAnalysisServer() {} -func (UnimplementedAnalysisServer) testEmbeddedByValue() {} +func (UnimplementedAnalysisAuthServer) mustEmbedUnimplementedAnalysisAuthServer() {} +func (UnimplementedAnalysisAuthServer) testEmbeddedByValue() {} -// UnsafeAnalysisServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to AnalysisServer will +// UnsafeAnalysisAuthServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AnalysisAuthServer will // result in compilation errors. -type UnsafeAnalysisServer interface { - mustEmbedUnimplementedAnalysisServer() +type UnsafeAnalysisAuthServer interface { + mustEmbedUnimplementedAnalysisAuthServer() } -func RegisterAnalysisServer(s grpc.ServiceRegistrar, srv AnalysisServer) { - // If the following call panics, it indicates UnimplementedAnalysisServer was +func RegisterAnalysisAuthServer(s grpc.ServiceRegistrar, srv AnalysisAuthServer) { + // If the following call panics, it indicates UnimplementedAnalysisAuthServer was // embedded by pointer and is nil. This will cause panics if an // unimplemented method is ever invoked, so we test this at initialization // time to prevent it from happening at runtime later due to I/O. if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { t.testEmbeddedByValue() } - s.RegisterService(&Analysis_ServiceDesc, srv) + s.RegisterService(&AnalysisAuth_ServiceDesc, srv) } -func _Analysis_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Request) +func _AnalysisAuth_GenerateQrcode_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GenerateQrcodeRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(AnalysisServer).Ping(ctx, in) + return srv.(AnalysisAuthServer).GenerateQrcode(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: Analysis_Ping_FullMethodName, + FullMethod: AnalysisAuth_GenerateQrcode_FullMethodName, } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AnalysisServer).Ping(ctx, req.(*Request)) + return srv.(AnalysisAuthServer).GenerateQrcode(ctx, req.(*GenerateQrcodeRequest)) } return interceptor(ctx, in, info, handler) } -// Analysis_ServiceDesc is the grpc.ServiceDesc for Analysis service. +// AnalysisAuth_ServiceDesc is the grpc.ServiceDesc for AnalysisAuth service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) -var Analysis_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "analysis.Analysis", - HandlerType: (*AnalysisServer)(nil), +var AnalysisAuth_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "analysis.AnalysisAuth", + HandlerType: (*AnalysisAuthServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "Ping", - Handler: _Analysis_Ping_Handler, + MethodName: "GenerateQrcode", + Handler: _AnalysisAuth_GenerateQrcode_Handler, }, }, Streams: []grpc.StreamDesc{}, diff --git a/services/analysis/analysis.go b/services/analysis/analysis.go index 3f86326..8b73326 100644 --- a/services/analysis/analysis.go +++ b/services/analysis/analysis.go @@ -130,7 +130,7 @@ func main() { ctx := svc.NewServiceContext(c, db) s := zrpc.MustNewServer(rpcConf, func(grpcServer *grpc.Server) { - analysis.RegisterAnalysisServer(grpcServer, server.NewAnalysisServer(ctx)) + analysis.RegisterAnalysisAuthServer(grpcServer, server.NewAnalysisAuthServer(ctx)) if mode == service.DevMode || mode == service.TestMode { reflection.Register(grpcServer) } diff --git a/services/analysis/analysisauth/analysisAuth.go b/services/analysis/analysisauth/analysisAuth.go new file mode 100644 index 0000000..255d0f7 --- /dev/null +++ b/services/analysis/analysisauth/analysisAuth.go @@ -0,0 +1,38 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.10.1 +// Source: analysis.proto + +package analysisauth + +import ( + "context" + + "lone-services/rpc/analysis/pb" + + "github.com/zeromicro/go-zero/zrpc" + "google.golang.org/grpc" +) + +type ( + GenerateQrcodeRequest = analysis.GenerateQrcodeRequest + Response = analysis.Response + + AnalysisAuth interface { + GenerateQrcode(ctx context.Context, in *GenerateQrcodeRequest, opts ...grpc.CallOption) (*Response, error) + } + + defaultAnalysisAuth struct { + cli zrpc.Client + } +) + +func NewAnalysisAuth(cli zrpc.Client) AnalysisAuth { + return &defaultAnalysisAuth{ + cli: cli, + } +} + +func (m *defaultAnalysisAuth) GenerateQrcode(ctx context.Context, in *GenerateQrcodeRequest, opts ...grpc.CallOption) (*Response, error) { + client := analysis.NewAnalysisAuthClient(m.cli.Conn()) + return client.GenerateQrcode(ctx, in, opts...) +} diff --git a/services/analysis/analysisclient/analysis.go b/services/analysis/analysisclient/analysis.go deleted file mode 100644 index a31f142..0000000 --- a/services/analysis/analysisclient/analysis.go +++ /dev/null @@ -1,38 +0,0 @@ -// Code generated by goctl. DO NOT EDIT. -// goctl 1.10.1 -// Source: analysis.proto - -package analysisClient - -import ( - "context" - - "lone-services/rpc/analysis/pb" - - "github.com/zeromicro/go-zero/zrpc" - "google.golang.org/grpc" -) - -type ( - Request = analysis.Request - Response = analysis.Response - - Analysis interface { - Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) - } - - defaultAnalysis struct { - cli zrpc.Client - } -) - -func NewAnalysis(cli zrpc.Client) Analysis { - return &defaultAnalysis{ - cli: cli, - } -} - -func (m *defaultAnalysis) Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { - client := analysis.NewAnalysisClient(m.cli.Conn()) - return client.Ping(ctx, in, opts...) -} diff --git a/services/analysis/internal/config/wechat.go b/services/analysis/internal/config/wechat.go new file mode 100644 index 0000000..1ca72d3 --- /dev/null +++ b/services/analysis/internal/config/wechat.go @@ -0,0 +1,45 @@ +package config + +import ( + "fmt" + + "lone-services/pkg/utils" + "lone-services/pkg/wechat/qrcode" +) + +type WechatQrcodeConfig struct { + AppID string + Secret string + Page string + Width int + EnvVersion string +} + +func LoadWechatQrcodeConfig() (*WechatQrcodeConfig, error) { + + appID := utils.GetConfigString("wechat.appid") + secret := utils.GetConfigString("wechat.secret") + if appID == utils.StringEmpty || secret == utils.StringEmpty { + return nil, fmt.Errorf("wechat config missing") + } + + page := utils.GetConfigString("wechat.page") + + width := utils.GetConfigInt("wechat.width") + envVersion := utils.GetConfigString("wechat.env_version") + if envVersion == utils.StringEmpty { + envVersion = "trial" + } + + return &WechatQrcodeConfig{ + AppID: appID, + Secret: secret, + Page: page, + Width: width, + EnvVersion: envVersion, + }, nil +} + +func (c *WechatQrcodeConfig) NewQrcodeClient() *qrcode.Client { + return qrcode.NewClient(c.AppID, c.Secret) +} diff --git a/services/analysis/internal/dao/qrcode.go b/services/analysis/internal/dao/qrcode.go new file mode 100644 index 0000000..5aced9c --- /dev/null +++ b/services/analysis/internal/dao/qrcode.go @@ -0,0 +1,17 @@ +package dao + +import "time" + +const ( + RedisKeyQRSession = "qr:session:" + + QrcodeSessionPending = 1 // 待扫码 + QrcodeSessionScanned = 2 // 已扫码未登录 + QrcodeSessionLogined = 3 // 已扫码已登录 + + QrcodeSessionTTL = 5 * time.Minute +) + +type QrcodeSessionStatus struct { + Status int `json:"status"` +} diff --git a/services/analysis/internal/logic/generateQrcodeLogic.go b/services/analysis/internal/logic/generateQrcodeLogic.go new file mode 100644 index 0000000..588fc1a --- /dev/null +++ b/services/analysis/internal/logic/generateQrcodeLogic.go @@ -0,0 +1,114 @@ +package logic + +import ( + "context" + "fmt" + + "lone-services/pkg/redis" + "lone-services/pkg/utils" + "lone-services/pkg/wechat/qrcode" + analysis "lone-services/rpc/analysis/pb" + chore "lone-services/rpc/chore/pb" + "lone-services/services/analysis/internal/config" + "lone-services/services/analysis/internal/dao" + "lone-services/services/analysis/internal/svc" + + "github.com/google/uuid" + jsoniter "github.com/json-iterator/go" + "github.com/zeromicro/go-zero/core/logx" +) + +type GenerateQrcodeLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewGenerateQrcodeLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GenerateQrcodeLogic { + return &GenerateQrcodeLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +func (l *GenerateQrcodeLogic) GenerateQrcode(in *analysis.GenerateQrcodeRequest) (*analysis.Response, error) { + sessionID, err := l.createSession() + if err != nil { + l.Errorf("generate qrcode session: %v", err) + return outResponse(utils.Fail, "create qrcode session failed"), nil + } + + wxCfg, err := config.LoadWechatQrcodeConfig() + if err != nil { + l.Errorf("generate qrcode wechat config: %v", err) + return outResponse(utils.Fail, "wechat config missing"), nil + } + + img, err := wxCfg.NewQrcodeClient().CreateWxaCode(l.ctx, &qrcode.WxaCodeRequest{ + Path: qrcode.BuildPath(wxCfg.Page, map[string]string{ + "session_id": sessionID, + }), + Width: wxCfg.Width, + EnvVersion: wxCfg.EnvVersion, + AutoColor: false, + LineColor: &qrcode.LineColor{R: 255, G: 255, B: 255}, + IsHyaline: true, + }) + if err != nil { + l.Errorf("generate qrcode wechat: %v", err) + return outResponse(utils.Fail, err.Error()), nil + } + + qrURL, err := l.uploadQrcode(img) + if err != nil { + l.Errorf("generate qrcode upload: %v", err) + return outResponse(utils.Fail, "upload qrcode failed"), nil + } + + return okResponse(map[string]string{ + "session_id": sessionID, + "qrcode": qrURL, + }), nil +} + +func (l *GenerateQrcodeLogic) createSession() (string, error) { + if redis.Client == nil { + return "", fmt.Errorf("redis not initialized") + } + + sessionID := uuid.New().String() + payload, err := jsoniter.MarshalToString(dao.QrcodeSessionStatus{Status: dao.QrcodeSessionPending}) + if err != nil { + return "", err + } + + key := dao.RedisKeyQRSession + sessionID + if err := redis.Client.Set(l.ctx, key, payload, dao.QrcodeSessionTTL).Err(); err != nil { + return "", err + } + return sessionID, nil +} + +func (l *GenerateQrcodeLogic) uploadQrcode(content []byte) (string, error) { + if l.svcCtx.ChoreSvcName == utils.StringEmpty { + return "", fmt.Errorf("chore service name empty") + } + + cli, err := svc.GetRpcClient(l.svcCtx.ChoreSvcName) + if err != nil { + return "", err + } + + res, err := chore.NewChoreClient(cli.Conn()).InternalUpload(l.ctx, &chore.InternalUploadReq{ + Filename: uuid.New().String() + ".png", + Content: content, + }) + if err != nil { + return "", err + } + if res == nil || res.Url == utils.StringEmpty { + return "", fmt.Errorf("chore upload empty url") + } + return res.Url, nil +} diff --git a/services/analysis/internal/logic/pinglogic.go b/services/analysis/internal/logic/pinglogic.go deleted file mode 100644 index d6abd79..0000000 --- a/services/analysis/internal/logic/pinglogic.go +++ /dev/null @@ -1,30 +0,0 @@ -package logic - -import ( - "context" - - "lone-services/services/analysis/analysis" - "lone-services/services/analysis/internal/svc" - - "github.com/zeromicro/go-zero/core/logx" -) - -type PingLogic struct { - ctx context.Context - svcCtx *svc.ServiceContext - logx.Logger -} - -func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic { - return &PingLogic{ - ctx: ctx, - svcCtx: svcCtx, - Logger: logx.WithContext(ctx), - } -} - -func (l *PingLogic) Ping(in *analysis.Request) (*analysis.Response, error) { - // todo: add your logic here and delete this line - - return &analysis.Response{}, nil -} diff --git a/services/analysis/internal/logic/response.go b/services/analysis/internal/logic/response.go new file mode 100644 index 0000000..8fa2c0e --- /dev/null +++ b/services/analysis/internal/logic/response.go @@ -0,0 +1,31 @@ +package logic + +import ( + "lone-services/pkg/utils" + analysis "lone-services/rpc/analysis/pb" + + jsoniter "github.com/json-iterator/go" +) + +func okResponse(data any) *analysis.Response { + buf, _ := jsoniter.Marshal(data) + return &analysis.Response{ + Code: utils.Ok.Code, + Msg: utils.Ok.Msg, + Data: string(buf), + } +} + +func failResponse(status utils.Status) *analysis.Response { + return &analysis.Response{ + Code: status.Code, + Msg: status.Msg, + } +} + +func outResponse(status utils.Status, msg string) *analysis.Response { + return &analysis.Response{ + Code: status.Code, + Msg: msg, + } +} diff --git a/services/analysis/internal/server/analysisAuthServer.go b/services/analysis/internal/server/analysisAuthServer.go new file mode 100644 index 0000000..05dd613 --- /dev/null +++ b/services/analysis/internal/server/analysisAuthServer.go @@ -0,0 +1,29 @@ +// Code generated by goctl. DO NOT EDIT. +// goctl 1.10.1 +// Source: analysis.proto + +package server + +import ( + "context" + + "lone-services/rpc/analysis/pb" + "lone-services/services/analysis/internal/logic" + "lone-services/services/analysis/internal/svc" +) + +type AnalysisAuthServer struct { + svcCtx *svc.ServiceContext + analysis.UnimplementedAnalysisAuthServer +} + +func NewAnalysisAuthServer(svcCtx *svc.ServiceContext) *AnalysisAuthServer { + return &AnalysisAuthServer{ + svcCtx: svcCtx, + } +} + +func (s *AnalysisAuthServer) GenerateQrcode(ctx context.Context, in *analysis.GenerateQrcodeRequest) (*analysis.Response, error) { + l := logic.NewGenerateQrcodeLogic(ctx, s.svcCtx) + return l.GenerateQrcode(in) +} diff --git a/services/analysis/internal/server/analysisServer.go b/services/analysis/internal/server/analysisServer.go deleted file mode 100644 index a2ef685..0000000 --- a/services/analysis/internal/server/analysisServer.go +++ /dev/null @@ -1,29 +0,0 @@ -// Code generated by goctl. DO NOT EDIT. -// goctl 1.10.1 -// Source: analysis.proto - -package server - -import ( - "context" - - "lone-services/rpc/analysis/pb" - "lone-services/services/analysis/internal/logic" - "lone-services/services/analysis/internal/svc" -) - -type AnalysisServer struct { - svcCtx *svc.ServiceContext - analysis.UnimplementedAnalysisServer -} - -func NewAnalysisServer(svcCtx *svc.ServiceContext) *AnalysisServer { - return &AnalysisServer{ - svcCtx: svcCtx, - } -} - -func (s *AnalysisServer) Ping(ctx context.Context, in *analysis.Request) (*analysis.Response, error) { - l := logic.NewPingLogic(ctx, s.svcCtx) - return l.Ping(in) -} diff --git a/services/analysis/run.toml b/services/analysis/run.toml new file mode 100644 index 0000000..090d0de --- /dev/null +++ b/services/analysis/run.toml @@ -0,0 +1,61 @@ +[base] + login_out_time=43200 + login_refresh_out_time=83200 + name = "analysis-service" + listenOn = "0.0.0.0:11800" + mode = "dev" + +[log] + path = "logs" + serviceName = "analysis-service" + mode = "file" + encoding = "plain" + level = "info" + keepDays = 7 + maxSize = 50 + maxBackups = 5 + compress = false + +[mysql] + host = '39.106.171.204' + port = 33066 + user = 'root' + password = 'MOLXRZNOU4Y4' + database = 'dms-analysis' + charset = 'utf8mb4' + prefix = '' + debug = true + +[mysql_read] + host = '39.106.171.204' + port = 33066 + user = 'root' + password = 'MOLXRZNOU4Y4' + database = 'dms-analysis' + charset = 'utf8mb4' + prefix = '' + +[redis] + host = '39.106.171.204' + password = 'lLMLcuPpzSj' + port = 6379 + db = 0 + +[log-redis] + host = '39.106.171.204' + password = 'lLMLcuPpzSj' + port = 6379 + db = 3 + +[wechat] + appid = 'wxa6f5d68dcbc955c7' + secret = '657bc95ab1eb4462913c056802b677c1' + login_page = 'pagesB/userLogin/index' + width = 430 + env_version = 'trial' + +[services] + chore = "chore-service" + +[encrypt] + data_key = "u2t9T3luZtoRfhBstkFN6TiIMW38BA8a" \ No newline at end of file diff --git a/services/chore/internal/logic/internalUploadLogic.go b/services/chore/internal/logic/internalUploadLogic.go index de35493..75caddc 100644 --- a/services/chore/internal/logic/internalUploadLogic.go +++ b/services/chore/internal/logic/internalUploadLogic.go @@ -30,7 +30,6 @@ func NewInternalUploadLogic(ctx context.Context, svcCtx *svc.ServiceContext) *In } } -// InternalUpload 对内:服务间直传 OSS(无 HTTP 暴露) func (l *InternalUploadLogic) InternalUpload(in *chore.InternalUploadReq) (*chore.InternalUploadData, error) { var req validator.UploadValidator if msg := validate.ValidateFromProto(in, &req); msg != utils.StringEmpty {