瀏覽代碼

修改redis wechat客户端,实现稳定单例

kobe6258 9 月之前
父節點
當前提交
3adf694597
共有 4 個文件被更改,包括 469 次插入70 次删除
  1. 50 47
      component/cache/redis.go
  2. 43 17
      component/log/log_plugin.go
  3. 183 0
      component/wechat/http/request.go
  4. 193 6
      component/wechat/wechat_client.go

+ 50 - 47
component/cache/redis.go

@@ -7,15 +7,50 @@ import (
 	"github.com/go-redis/redis/v8"
 	"log"
 	"strings"
+	"sync"
 	"time"
 )
 
-func GetInstance(config string) (redisCache *RedisCache, err error) {
-	if config == "" {
-		log.Fatalf("创建redis失败")
+var (
+	redisCache *RedisCache
+	once       sync.Once
+)
+
+func GetInstance() *RedisCache {
+	once.Do(func() {
+		config := redisConfig.GetConfig("redis")
+		// 检查是否成功获取到RedisConfig实例
+		redisInstance, ok := config.(*redisConfig.RedisConfig)
+		if !ok {
+			// 处理错误情况,比如记录日志或返回错误信息
+			logger.Info("加载redis配置失败")
+			return // 或者采取其他适当的错误处理措施
+		}
+		// 获取Redis配置选项,这里假设GetConfig方法返回(*RedisOpts, error),需要检查错误
+		opts := redisInstance.GetConfig().(redisConfig.RedisOpts)
+		// 检查Host是否已设置,然后初始化或执行相应操作
+		if opts.Host != "" {
+			logger.Info("初始化redis")
+			// 这里可以添加初始化Redis的逻辑
+			redisCache = newRedis(opts)
+		} else {
+			logger.Info("Redis configuration is incomplete: Host is missing")
+		}
+	})
+	return redisCache
+}
+func newRedis(opts redisConfig.RedisOpts) *RedisCache {
+	if &opts == nil || opts.Host == "" {
+		log.Fatalf("redis 连接失败")
 	}
-	//	redisCache = NewRedis(redisCommon.ParseFromStr(""))
-	return
+	redisTemplate := redis.NewUniversalClient(&redis.UniversalOptions{
+		Addrs:        strings.Split(opts.Host, ";"),
+		DB:           opts.Database,
+		Password:     opts.Password,
+		IdleTimeout:  time.Second * time.Duration(opts.IdleTimeout),
+		MinIdleConns: opts.MaxIdle,
+	})
+	return &RedisCache{redisTemplate: redisTemplate}
 }
 
 // RedisCache
@@ -24,22 +59,24 @@ type RedisCache struct {
 	redisTemplate redis.UniversalClient
 }
 
-// Get 获取一个值
+// GetString  获取一个值
 func (r *RedisCache) GetString(key string) string {
 	return r.GetStringWithContext(context.Background(), key)
 
 }
+
+// GetStringWithContext  获取一个值
 func (r *RedisCache) GetStringWithContext(ctx context.Context, key string) (value string) {
 	value, _ = r.redisTemplate.Get(ctx, key).Result()
 	return
 }
 
-// SetContext 设置一个值
+// SetString 设置一个值
 func (r *RedisCache) SetString(key string, val string, timeout time.Duration) error {
 	return r.redisTemplate.SetEX(context.Background(), key, val, timeout).Err()
 }
 
-// SetContext 设置一个值
+// SetStringWithContext  设置一个值
 func (r *RedisCache) SetStringWithContext(ctx context.Context, key string, val string, timeout time.Duration) error {
 	return r.redisTemplate.SetEX(ctx, key, val, timeout).Err()
 }
@@ -49,7 +86,7 @@ func (r *RedisCache) IsExist(key string) bool {
 	return r.IsExistWithContext(context.Background(), key)
 }
 
-// IsExistContext 判断key是否存在
+// IsExistWithContext 判断key是否存在
 func (r *RedisCache) IsExistWithContext(ctx context.Context, key string) bool {
 	result, _ := r.redisTemplate.Exists(ctx, key).Result()
 	return result > 0
@@ -60,53 +97,19 @@ func (r *RedisCache) Delete(key string) error {
 	return r.DeleteWithContext(context.Background(), key)
 }
 
-// DeleteContext 删除
+// DeleteWithContext  删除
 func (r *RedisCache) DeleteWithContext(ctx context.Context, key string) error {
 	return r.redisTemplate.Del(ctx, key).Err()
 }
+
+// Do	执行指令
 func (r *RedisCache) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
 	return r.DoWithContext(context.Background(), commandName, args...)
 }
 
+// DoWithContext 执行指令
 func (r *RedisCache) DoWithContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) {
 	newArgs := []interface{}{commandName}
 	newArgs = append(newArgs, args...)
 	return r.redisTemplate.Do(ctx, newArgs...).Result()
 }
-
-func NewRedis(opts redisConfig.RedisOpts) *RedisCache {
-	if &opts == nil || opts.Host == "" {
-		log.Fatalf("redis 连接失败")
-	}
-	redisTemplate := redis.NewUniversalClient(&redis.UniversalOptions{
-		Addrs:        strings.Split(opts.Host, ";"),
-		DB:           opts.Database,
-		Password:     opts.Password,
-		IdleTimeout:  time.Second * time.Duration(opts.IdleTimeout),
-		MinIdleConns: opts.MaxIdle,
-	})
-	return &RedisCache{redisTemplate: redisTemplate}
-}
-
-func init() {
-	config := redisConfig.GetConfig("redis")
-	// 检查是否成功获取到RedisConfig实例
-	redisInstance, ok := config.(*redisConfig.RedisConfig)
-	if !ok {
-		// 处理错误情况,比如记录日志或返回错误信息
-		logger.Info("Failed to get RedisConfig instance")
-		return // 或者采取其他适当的错误处理措施
-	}
-
-	// 获取Redis配置选项,这里假设GetConfig方法返回(*RedisOpts, error),需要检查错误
-	opts := redisInstance.GetConfig().(redisConfig.RedisOpts)
-
-	// 检查Host是否已设置,然后初始化或执行相应操作
-	if opts.Host != "" {
-		logger.Info("初始化redis")
-		// 这里可以添加初始化Redis的逻辑
-		NewRedis(opts)
-	} else {
-		logger.Info("Redis configuration is incomplete: Host is missing")
-	}
-}

+ 43 - 17
component/log/log_plugin.go

@@ -16,29 +16,56 @@ const (
 )
 
 var (
-	loggerHandler *WechatLogger
+	loggerHandler *CustomLogger
 )
 
 type logger struct {
 	*logs.BeeLogger
 	filter string
 }
-type WechatLogger struct {
+
+type CustomLogger struct {
 	logs []*logger
 }
 
+// Logger interface
+type Logger interface {
+	Info(msg string)
+	Warn(msg string)
+	Error(msg string)
+}
+
 func Info(msg string) {
-	for _, appender := range loggerHandler.logs {
-		if appender.GetLevel() > logs.LevelInfo {
+	loggerHandler.Info(msg)
+}
+
+func Error(msg string) {
+	loggerHandler.Error(msg)
+}
+
+func Warn(msg string) {
+	loggerHandler.Warn(msg)
+}
+func (c *CustomLogger) Info(msg string) {
+	for _, appender := range c.logs {
+		if appender.GetLevel() >= logs.LevelInfo {
 			appender.Info(msg)
 		}
 
 	}
 }
 
-func Error(msg string) {
-	for _, logger := range loggerHandler.logs {
-		if logger.GetLevel() > logs.LevelError {
+func (c *CustomLogger) Error(msg string) {
+	for _, logger := range c.logs {
+		if logger.GetLevel() >= logs.LevelError {
+			logger.Error(msg)
+		}
+	}
+}
+
+func (c *CustomLogger) Warn(msg string) {
+	for _, logger := range c.logs {
+		if logger.GetLevel() >= logs.LevelWarning {
 			logger.Error(msg)
 		}
 	}
@@ -73,9 +100,12 @@ var terminalType = map[string]string{
 	"file":    logs.AdapterFile,
 }
 
+func GetInstance() Logger {
+	return loggerHandler
+}
 func initLogger(logCfg logConfig) {
 	if loggerHandler == nil {
-		loggerHandler = new(WechatLogger)
+		loggerHandler = new(CustomLogger)
 	}
 	if stringUtils.IsEmptyOrNil(logCfg.FilePath) {
 		logCfg.FilePath = DefalutLogFilePath
@@ -99,7 +129,7 @@ func initLogger(logCfg logConfig) {
 			os.MkdirAll(logCfg.FilePath, os.ModePerm)
 			// 打开文件
 			appender.FileName = path.Join(logCfg.FilePath, logFile)
-			logProps, err := ConvertAppenderToLog(&appender)
+			logProps, err := convertAppenderToLog(&appender)
 			if err != nil {
 				fmt.Println("初始化日志执行器失败:{%s} %v", appender.FileName, err)
 				continue
@@ -118,12 +148,8 @@ func initLogger(logCfg logConfig) {
 	}
 }
 
-func initFileLogger() {
-
-}
-
 // ConvertAppenderToLog 将 appender 结构体转换为 log 结构体
-func ConvertAppenderToLog(a *appender) (*logProps, error) {
+func convertAppenderToLog(a *appender) (*logProps, error) {
 	lvl, ok := levelTrans[a.Level]
 	if !ok {
 		return nil, fmt.Errorf("unknown log level: %s", a.Level)
@@ -139,15 +165,16 @@ func ConvertAppenderToLog(a *appender) (*logProps, error) {
 		Rotate:   a.Rotate,
 		Level:    lvl,
 		Color:    a.Color,
-		//Perm:     a.Perm,
 	}, nil
 }
 
+// logConfig 日志配置
 type logConfig struct {
 	FilePath  string     `json:"filepath" description:"日志路径"`
 	Appenders []appender `json:"appenders" description:"日志记录"`
 }
 
+// appender beeLogger JSON配置
 type appender struct {
 	Filter   string `json:"filter"`
 	Prefix   string `json:"perfix" description:"日志前缀"`
@@ -160,9 +187,9 @@ type appender struct {
 	Rotate   bool   `json:"rotate" description:"是否开启 logrotate,默认是 true"`
 	Level    string `json:"level" description:"日志保存的时候的级别,默认是 Trace 级别"`
 	Color    bool   `json:"color" description:"日志是否输出颜色"`
-	//Perm     string `json:"perm" description:"日志文件权限"`
 }
 
+// logProps beeLogger 配置
 type logProps struct {
 	Prefix   string `json:"perfix" description:"日志前缀"`
 	FileName string `json:"filename" description:"保存的文件名"`
@@ -173,5 +200,4 @@ type logProps struct {
 	Rotate   bool   `json:"rotate" description:"是否开启 logrotate,默认是 true"`
 	Level    int    `json:"level" description:"日志保存的时候的级别,默认是 Trace 级别"`
 	Color    bool   `json:"color" description:"日志是否输出颜色"`
-	//Perm     string `json:"perm" description:"日志文件权限"`
 }

+ 183 - 0
component/wechat/http/request.go

@@ -0,0 +1,183 @@
+package http
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"encoding/xml"
+	"eta_mini_ht_api/common/client"
+	"github.com/medivhzhan/weapp/v3/logger"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"os"
+)
+
+// ContentType 请求内容类型
+type ContentType uint
+
+const (
+	ContentTypePlain ContentType = iota
+	ContentTypeXML
+	ContentTypeJSON
+)
+
+func (ctp ContentType) String() string {
+	switch ctp {
+	case ContentTypeXML:
+		return "application/xml"
+	case ContentTypeJSON:
+		return "application/json"
+	default:
+		return "text/plain"
+	}
+}
+
+type Request struct {
+	http *client.HttpClient
+	// 获取日志记录器
+	logger      func() logger.Logger
+	contentType ContentType
+}
+
+func NewRequest(http *client.HttpClient, ctp ContentType, logger func() logger.Logger) *Request {
+	return &Request{
+		http:        http,
+		logger:      logger,
+		contentType: ctp,
+	}
+}
+
+func (cli *Request) Get(url string, response interface{}) error {
+	cli.logger().Info(context.Background(), "request url: %s", url)
+
+	resp, err := cli.http.Get(url)
+	if err != nil {
+		cli.logger().Error(context.Background(), "get error: %s", err)
+		return err
+	}
+	defer resp.Body.Close()
+
+	switch cli.contentType {
+	case ContentTypeXML:
+		return xml.NewDecoder(resp.Body).Decode(response)
+	case ContentTypeJSON:
+		return json.NewDecoder(resp.Body).Decode(response)
+	default:
+		return errors.New("invalid content type")
+	}
+}
+
+func (cli *Request) GetWithBody(url string) (*http.Response, error) {
+	cli.logger().Info(context.Background(), "request url: %s", url)
+	rsp, err := cli.http.Get(url)
+	if err != nil {
+		cli.logger().Error(context.Background(), "get with body error: %s", url)
+		return nil, err
+	}
+
+	return rsp, nil
+}
+
+func (cli *Request) Post(url string, params interface{}, response interface{}) error {
+	resp, err := cli.PostWithBody(url, params)
+	if err != nil {
+		cli.logger().Error(context.Background(), "post error: %s", err)
+		return err
+	}
+	defer resp.Body.Close()
+
+	switch cli.contentType {
+	case ContentTypeXML:
+		return xml.NewDecoder(resp.Body).Decode(response)
+	case ContentTypeJSON:
+		return json.NewDecoder(resp.Body).Decode(response)
+	default:
+		return errors.New("invalid content type")
+	}
+}
+
+func (cli *Request) PostWithBody(url string, params interface{}) (*http.Response, error) {
+	cli.logger().Info(context.Background(), "request url: %s", url)
+	cli.logger().Info(context.Background(), "request params: %+v", params)
+	buf := new(bytes.Buffer)
+	if params != nil {
+		switch cli.contentType {
+		case ContentTypeXML:
+			err := xml.NewEncoder(buf).Encode(params)
+			if err != nil {
+				return nil, err
+			}
+		case ContentTypeJSON:
+			enc := json.NewEncoder(buf)
+			enc.SetEscapeHTML(false)
+			err := enc.Encode(params)
+			if err != nil {
+				return nil, err
+			}
+		default:
+			return nil, errors.New("invalid content type")
+		}
+	}
+
+	rsp, err := cli.http.Post(url, cli.contentType.String(), buf)
+	if err != nil {
+		cli.logger().Error(context.Background(), "post with body error: %s", url)
+		return nil, err
+	}
+
+	return rsp, nil
+}
+
+func (cli *Request) FormPostWithFile(url, field, filename string, response interface{}) error {
+	file, err := os.Open(filename)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+
+	return cli.FormPost(url, field, filename, file, response)
+}
+
+func (cli *Request) FormPost(url, field, filename string, reader io.Reader, response interface{}) error {
+	cli.logger().Info(context.Background(), "request url: %s", url)
+	// Prepare a form that you will submit to that URL.
+	buf := new(bytes.Buffer)
+	w := multipart.NewWriter(buf)
+	fw, err := w.CreateFormFile(field, filename)
+	if err != nil {
+		return err
+	}
+
+	if _, err = io.Copy(fw, reader); err != nil {
+		return err
+	}
+
+	// Don't forget to close the multipart writer.
+	// If you don't close it, your request will be missing the terminating boundary.
+	w.Close()
+
+	// Now that you have a form, you can submit it to your handler.
+	req, err := http.NewRequest("POST", url, buf)
+	if err != nil {
+		return err
+	}
+	// Don't forget to set the content type, this will contain the boundary.
+	req.Header.Set("Content-Type", w.FormDataContentType())
+
+	resp, err := cli.http.Do(req)
+	if err != nil {
+		cli.logger().Error(context.Background(), "form post error: %s", err)
+		return err
+	}
+	defer resp.Body.Close()
+
+	switch cli.contentType {
+	case ContentTypeXML:
+		return xml.NewDecoder(resp.Body).Decode(response)
+	case ContentTypeJSON:
+		return json.NewDecoder(resp.Body).Decode(response)
+	default:
+		return errors.New("invalid content type")
+	}
+}

+ 193 - 6
component/wechat/wechat_client.go

@@ -1,15 +1,202 @@
 package wechat
 
 import (
-	"github.com/medivhzhan/weapp/v3"
+	"eta_mini_ht_api/component/cache"
+	"eta_mini_ht_api/component/log"
+	"github.com/medivhzhan/weapp/v3/auth"
+	"github.com/medivhzhan/weapp/v3/request"
+	"github.com/mitchellh/mapstructure"
+	"net/http"
+	"sync"
 )
 
-type WechatClient struct {
-	sdk *weapp.Client
+const (
+	baseURL = "https://api.weixin.qq.com"
+)
+
+var (
+	wechatClient *Client
+	once         sync.Once
+)
+
+type Client struct {
+	// HTTP请求客户端
+	request *request.Request
+	// 数据缓存器
+	cache *cache.RedisCache
+	// 日志记录器
+	logger logger.Logger
+	// 小程序后台配置: 小程序ID
+	appid string
+	// 小程序后台配置: 小程序密钥
+	secret string
+	// 用户自定义获取access_token的方法
+	accessTokenGetter AccessTokenGetter
+}
+
+func GetInstance() *Client {
+	once.Do(func() {
+		// 默认配置
+		wechatClient = NewClient("", "",
+			WithHttpClient(http.DefaultClient),
+			WithCache(cache.GetInstance()),
+			WithLogger(logger.GetInstance()),
+		)
+	})
+	return wechatClient
+}
+
+// 用户自定义获取access_token的方法
+type AccessTokenGetter func(appid, secret string) (token string, expireIn uint)
+
+// 初始化客户端并用自定义配置替换默认配置
+func NewClient(appid, secret string, opts ...func(*Client)) *Client {
+	cli := &Client{
+		appid:  appid,
+		secret: secret,
+	}
+
+	// 执行额外的配置函数
+	for _, fn := range opts {
+		fn(cli)
+	}
+
+	if cli.cache == nil {
+		//cli.cache = cache.NewMemoryCache()
+	}
+
+	if cli.request == nil {
+		//cli.request = request.NewRequest(http.DefaultClient, request.ContentTypeJSON, cli.Logger)
+	}
+
+	if cli.logger == nil {
+		//cli.logger = logger.NewLogger(log.New(os.Stdout, "\r\n", log.LstdFlags), logger.Info, true)
+	}
+
+	return cli
+}
+
+// WithHttpClient 自定义 HTTP Client
+func WithHttpClient(hc *http.Client) func(*Client) {
+	return func(cli *Client) {
+		//cli.request = request.NewRequest(hc, request.ContentTypeJSON, cli.Logger)
+	}
+}
+
+// WithCache 自定义缓存
+func WithCache(cc *cache.RedisCache) func(*Client) {
+	return func(cli *Client) {
+		cli.cache = cc
+	}
 }
 
-func GetInstance() *WechatClient {
-	return &WechatClient{
-		sdk: weapp.NewClient("", ""),
+// WithAccessTokenSetter 自定义获取access_token的方法
+func WithAccessTokenSetter(getter AccessTokenGetter) func(*Client) {
+	return func(cli *Client) {
+		cli.accessTokenGetter = getter
 	}
 }
+
+// WithLogger 自定义日志
+func WithLogger(logger logger.Logger) func(*Client) {
+	return func(cli *Client) {
+		cli.logger = logger
+	}
+}
+
+// POST 参数
+type requestParams map[string]interface{}
+
+// URL 参数
+type requestQueries map[string]interface{}
+
+// tokenAPI 获取带 token 的 API 地址
+func tokenAPI(api, token string) (string, error) {
+	queries := requestQueries{
+		"access_token": token,
+	}
+
+	return request.EncodeURL(api, queries)
+}
+
+// convert bool to int
+func bool2int(ok bool) uint8 {
+
+	if ok {
+		return 1
+	}
+
+	return 0
+}
+
+// Logger 获取日志记录器
+func (cli *Client) Logger() logger.Logger { return cli.logger }
+
+// AccessToken 获取小程序全局唯一后台接口调用凭据(access_token)。
+// 调调用绝大多数后台接口时都需使用 access_token,开发者需要进行妥善保存,注意缓存。
+func (cli *Client) AccessToken() (string, error) {
+	return "", nil
+	////key := cli.tokenCacheKey()
+	//data, ok := cli.cache.Get(key)
+	//if ok {
+	//	return data.(string), nil
+	//}
+	//
+	//if cli.accessTokenGetter != nil {
+	////	token, expireIn := cli.accessTokenGetter(cli.appid, cli.secret)
+	//	//cli.cache.Set(key, token, time.Duration(expireIn)*time.Second)
+	////	return token, nil
+	//} else {
+	//
+	//	req := auth.GetStableAccessTokenRequest{
+	//		Appid:     cli.appid,
+	//		Secret:    cli.secret,
+	//		GrantType: "client_credential",
+	//	}
+	//	rsp, err := cli.NewAuth().GetStableAccessToken(&req)
+	//	if err != nil {
+	//		return "", err
+	//	}
+	//
+	//	if err := rsp.GetResponseError(); err != nil {
+	//		return "", err
+	//	}
+	//
+	//	err = cli.cache.SetString(key, rsp.AccessToken, time.Duration(rsp.ExpiresIn)*time.Second)
+	//	if err != nil {
+	//		return "", err
+	//	}
+	//	return rsp.AccessToken, nil
+	//}
+}
+
+// 拼凑完整的 URI
+func (cli *Client) combineURI(url string, req interface{}, withToken bool) (string, error) {
+	output := make(map[string]interface{})
+	config := &mapstructure.DecoderConfig{
+		Metadata: nil,
+		Result:   &output,
+		TagName:  "query",
+	}
+	decoder, err := mapstructure.NewDecoder(config)
+	if err != nil {
+		return "", err
+	}
+	err = decoder.Decode(req)
+	if err != nil {
+		return "", err
+	}
+	if withToken {
+		token, err := cli.AccessToken()
+		if err != nil {
+			return "", err
+		}
+		output["access_token"] = token
+	}
+	return request.EncodeURL(baseURL+url, output)
+}
+
+// NewAuth 用户信息
+func (cli *Client) NewAuth() *auth.Auth {
+	return auth.NewAuth(cli.request, cli.combineURI)
+}