optimize get unread message count logic and add cache base mechanism use redis

pull/385/head
Michael Li 1 year ago
parent a14921448e
commit bf4026c5af
No known key found for this signature in database

@ -43,7 +43,12 @@ func RegisterRelaxServant(e *gin.Engine, s Relax) {
return
}
resp, err := s.GetUnreadMsgCount(req)
s.Render(c, resp, err)
if err != nil {
s.Render(c, nil, err)
return
}
var rv _render_ = resp
rv.Render(c)
})
}

@ -6,7 +6,7 @@ require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868
github.com/alimy/mir/v4 v4.0.0
github.com/alimy/tryst v0.8.0
github.com/alimy/tryst v0.8.2
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible
github.com/allegro/bigcache/v3 v3.1.0
github.com/bufbuild/connect-go v1.10.0
@ -79,6 +79,7 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.6 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect

@ -125,8 +125,8 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:C
github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
github.com/alimy/mir/v4 v4.0.0 h1:MzGfmoLjjvR69jbZEmpKJO3tUuqB0RGRv1UWPbtukBg=
github.com/alimy/mir/v4 v4.0.0/go.mod h1:d58dBvw2KImcVbAUANrciEV/of0arMNsI9c/5UNCMMc=
github.com/alimy/tryst v0.8.0 h1:21iEWSDheTHW8iIhWEwII6YOpQeDSGT5u21nq6rM/AE=
github.com/alimy/tryst v0.8.0/go.mod h1:K//dPeoE/nnv2Jw8C3iPE7n8mO6LVqAxVmqbopM9nAk=
github.com/alimy/tryst v0.8.2 h1:azu5B58vS6m/ZeHovYGWjVvEOJN2llDIBLxuN3qtMtk=
github.com/alimy/tryst v0.8.2/go.mod h1:ua2eJbFrisHPh7z93Bgc0jNBE8Khu1SCx2p/6t3OzZI=
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k=
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=
@ -729,6 +729,8 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM=
github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=

@ -0,0 +1,30 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package conf
import (
"fmt"
"github.com/alimy/tryst/cache"
)
const (
_defaultKeyPoolSize = 128
)
// 以下包含一些在cache中会用到的池化后的key
var (
KeyUnreadMsg cache.KeyPool[int64]
)
func initCacheKeyPool() {
poolSize := _defaultKeyPoolSize
if poolSize < CacheSetting.KeyPoolSize {
poolSize = CacheSetting.KeyPoolSize
}
KeyUnreadMsg = cache.MustKeyPool[int64](poolSize, func(key int64) string {
return fmt.Sprintf("paopao:unreadmsg:%d", key)
})
}

@ -29,6 +29,8 @@ func MustRedisClient() rueidis.Client {
log.Fatalf("create a redis client failed: %s", err)
}
_redisClient = client
// 顺便初始化一下CacheKeyPool
initCacheKeyPool()
})
return _redisClient
}

@ -35,6 +35,7 @@ var (
DocsServerSetting *httpServerConf
MobileServerSetting *grpcServerConf
AppSetting *appConf
CacheSetting *cacheConf
EventManagerSetting *eventManagerConf
CacheIndexSetting *cacheIndexConf
SimpleCacheIndexSetting *simpleCacheIndexConf
@ -70,6 +71,7 @@ func setupSetting(suite []string, noDefault bool) error {
objects := map[string]any{
"App": &AppSetting,
"Cache": &CacheSetting,
"EventManager": &EventManagerSetting,
"PprofServer": &PprofServerSetting,
"WebServer": &WebServerSetting,
@ -117,6 +119,7 @@ func setupSetting(suite []string, noDefault bool) error {
}
}
CacheSetting.CientSideCacheExpire *= time.Second
EventManagerSetting.TickWaitTime *= time.Second
JWTSetting.Expire *= time.Second
SimpleCacheIndexSetting.CheckTickDuration *= time.Second

@ -5,6 +5,10 @@ App: # APP基础设置项
DefaultContextTimeout: 60
DefaultPageSize: 10
MaxPageSize: 100
Cache:
KeyPoolSize: 256 # 键的池大小, 设置范围[128, ++], 默认256
CientSideCacheExpire: 60 # 客户端缓存过期时间 默认60s
UnreadMsgExpire: 60 # 未读消息过期时间,单位秒, 默认60s
EventManager: # 事件管理器的配置参数
MinWorker: 10 # 最小后台工作者, 设置范围[5, ++], 默认10
MaxEventBuf: 100 # 最大log缓存条数, 设置范围[10, ++], 默认100

@ -96,6 +96,12 @@ type appConf struct {
MaxPageSize int
}
type cacheConf struct {
KeyPoolSize int
CientSideCacheExpire time.Duration
UnreadMsgExpire int64
}
type eventManagerConf struct {
MinWorker int
MaxEventBuf int

@ -97,3 +97,9 @@ type RedisCache interface {
SetRechargeStatus(ctx context.Context, tradeNo string) error
DelRechargeStatus(ctx context.Context, tradeNo string) error
}
type WebCache interface {
GetUnreadMsgCountResp(uid int64) ([]byte, error)
PutUnreadMsgCountResp(uid int64, data []byte) error
DelUnreadMsgCountResp(uid int64) error
}

@ -6,6 +6,7 @@ package cache
import (
"context"
"sync"
"time"
"github.com/allegro/bigcache/v3"
@ -14,6 +15,10 @@ import (
"github.com/sirupsen/logrus"
)
var (
_onceInit sync.Once
)
func NewRedisCache() core.RedisCache {
return &redisCache{
c: conf.MustRedisClient(),
@ -48,6 +53,11 @@ func NewRedisCacheIndexService(ips core.IndexPostsService, ams core.Authorizatio
return cacheIndex, cacheIndex
}
func NewWebCache() core.WebCache {
lazyInitial()
return _webCache
}
func NewSimpleCacheIndexService(indexPosts core.IndexPostsService) (core.CacheIndexService, core.VersionInfo) {
s := conf.SimpleCacheIndexSetting
cacheIndex := &simpleCacheIndexServant{
@ -88,3 +98,9 @@ func NewNoneCacheIndexService(indexPosts core.IndexPostsService) (core.CacheInde
}
return obj, obj
}
func lazyInitial() {
_onceInit.Do(func() {
_webCache = newWebCache()
})
}

@ -0,0 +1,66 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package cache
import (
"context"
"time"
"github.com/Masterminds/semver/v3"
"github.com/redis/rueidis"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/pkg/utils"
)
var (
_webCache core.WebCache = (*redisWebCache)(nil)
)
type redisWebCache struct {
cscExpire time.Duration
unreadMsgExpire int64
c rueidis.Client
}
func (s *redisWebCache) Name() string {
return "RedisWebCache"
}
func (s *redisWebCache) Version() *semver.Version {
return semver.MustParse("v0.1.0")
}
func (s *redisWebCache) GetUnreadMsgCountResp(uid int64) ([]byte, error) {
key := conf.KeyUnreadMsg.Get(uid)
res, err := rueidis.MGetCache(s.c, context.Background(), s.cscExpire, []string{key})
if err != nil {
return nil, err
}
message := res[key]
return message.AsBytes()
}
func (s *redisWebCache) PutUnreadMsgCountResp(uid int64, data []byte) error {
return s.c.Do(context.Background(), s.c.B().Set().
Key(conf.KeyUnreadMsg.Get(uid)).
Value(utils.String(data)).
ExSeconds(s.unreadMsgExpire).
Build()).
Error()
}
func (s *redisWebCache) DelUnreadMsgCountResp(uid int64) error {
return s.c.Do(context.Background(), s.c.B().Del().Key(conf.KeyUnreadMsg.Get(uid)).Build()).Error()
}
func newWebCache() *redisWebCache {
s := conf.CacheSetting
return &redisWebCache{
cscExpire: s.CientSideCacheExpire,
unreadMsgExpire: s.UnreadMsgExpire,
c: conf.MustRedisClient(),
}
}

@ -14,3 +14,9 @@ type BasePageInfo struct {
func (r *BasePageInfo) SetPageInfo(page int, pageSize int) {
r.Page, r.PageSize = page, pageSize
}
type JsonResp struct {
Code int `json:"code"`
Msg string `json:"msg,omitempty"`
Data any `json:"data,omitempty"`
}

@ -0,0 +1,15 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package joint
import (
stdJson "encoding/json"
"github.com/rocboss/paopao-ce/pkg/json"
)
func RespMarshal(data any) (stdJson.RawMessage, error) {
return json.Marshal(data)
}

@ -4,10 +4,31 @@
package web
import (
"encoding/json"
"net/http"
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/model/joint"
)
type GetUnreadMsgCountReq struct {
SimpleInfo `json:"-" binding:"-"`
}
type GetUnreadMsgCountResp struct {
Count int64 `json:"count"`
JsonResp json.RawMessage `json:"-"`
}
func (r *GetUnreadMsgCountResp) Render(c *gin.Context) {
if len(r.JsonResp) != 0 {
c.JSON(http.StatusOK, r.JsonResp)
} else {
c.JSON(http.StatusOK, &joint.JsonResp{
Code: 0,
Msg: "success",
Data: r,
})
}
}

@ -22,6 +22,7 @@ import (
"github.com/rocboss/paopao-ce/internal/dao"
"github.com/rocboss/paopao-ce/internal/dao/cache"
"github.com/rocboss/paopao-ce/internal/events"
"github.com/rocboss/paopao-ce/internal/model/joint"
"github.com/rocboss/paopao-ce/pkg/app"
"github.com/rocboss/paopao-ce/pkg/xerror"
)
@ -39,12 +40,6 @@ type DaoServant struct {
Redis core.RedisCache
}
type JsonResp struct {
Code int `json:"code"`
Msg string `json:"msg,omitempty"`
Data any `json:"data,omitempty"`
}
type SentryHubSetter interface {
SetSentryHub(hub *sentry.Hub)
}
@ -145,13 +140,13 @@ func bindAnySentry(c *gin.Context, obj any) mir.Error {
func RenderAny(c *gin.Context, data any, err mir.Error) {
if err == nil {
c.JSON(http.StatusOK, &JsonResp{
c.JSON(http.StatusOK, &joint.JsonResp{
Code: 0,
Msg: "success",
Data: data,
})
} else {
c.JSON(xerror.HttpStatusCode(err), &JsonResp{
c.JSON(xerror.HttpStatusCode(err), &joint.JsonResp{
Code: err.StatusCode(),
Msg: err.Error(),
})
@ -164,13 +159,13 @@ func (s *BaseServant) Bind(c *gin.Context, obj any) mir.Error {
func (s *BaseServant) Render(c *gin.Context, data any, err mir.Error) {
if err == nil {
c.JSON(http.StatusOK, &JsonResp{
c.JSON(http.StatusOK, &joint.JsonResp{
Code: 0,
Msg: "success",
Data: data,
})
} else {
c.JSON(xerror.HttpStatusCode(err), &JsonResp{
c.JSON(xerror.HttpStatusCode(err), &joint.JsonResp{
Code: err.StatusCode(),
Msg: err.Error(),
})

@ -37,6 +37,7 @@ type coreSrv struct {
*base.DaoServant
oss core.ObjectStorageService
wc core.WebCache
}
func (s *coreSrv) Chain() gin.HandlersChain {
@ -140,6 +141,8 @@ func (s *coreSrv) ReadMessage(req *web.ReadMessageReq) mir.Error {
logrus.Errorf("Ds.ReadMessage err: %s", err)
return web.ErrReadMessageFailed
}
// 清除未读消息数缓存,不需要处理错误
s.wc.DelUnreadMsgCountResp(req.Uid)
return nil
}
@ -168,6 +171,9 @@ func (s *coreSrv) SendUserWhisper(req *web.SendWhisperReq) mir.Error {
return web.ErrSendWhisperFailed
}
// 清除接收者未读消息缓存, 不需要处理错误
s.wc.DelUnreadMsgCountResp(req.UserID)
// 写入当日(自然日)计数缓存
s.Redis.IncrCountWhisper(ctx, req.Uid)
@ -365,9 +371,10 @@ func (s *coreSrv) TweetStarStatus(req *web.TweetStarStatusReq) (*web.TweetStarSt
return resp, nil
}
func newCoreSrv(s *base.DaoServant, oss core.ObjectStorageService) api.Core {
func newCoreSrv(s *base.DaoServant, oss core.ObjectStorageService, wc core.WebCache) api.Core {
return &coreSrv{
DaoServant: s,
oss: oss,
wc: wc,
}
}

@ -0,0 +1,84 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package web
import (
"encoding/json"
"fmt"
"github.com/alimy/tryst/event"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/ms"
"github.com/rocboss/paopao-ce/internal/events"
"github.com/rocboss/paopao-ce/internal/model/joint"
"github.com/rocboss/paopao-ce/internal/model/web"
)
type cacheUnreadMsgEvent struct {
event.UnimplementedEvent
ds core.DataService
wc core.WebCache
uid int64
}
type createMessageEvent struct {
event.UnimplementedEvent
ds core.DataService
wc core.WebCache
message *ms.Message
}
func onCacheUnreadMsgEvent(uid int64) {
events.OnEvent(&cacheUnreadMsgEvent{
ds: _ds,
wc: _wc,
uid: uid,
})
}
func onCreateMessageEvent(data *ms.Message) {
events.OnEvent(&createMessageEvent{
ds: _ds,
wc: _wc,
message: data,
})
}
func (e *cacheUnreadMsgEvent) Name() string {
return "cacheUnreadMsgEvent"
}
func (e *cacheUnreadMsgEvent) Action() error {
count, err := e.ds.GetUnreadCount(e.uid)
if err != nil {
return fmt.Errorf("cacheUnreadMsgEvent action occurs error: %w", err)
}
resp := &joint.JsonResp{
Code: 0,
Msg: "success",
Data: &web.GetUnreadMsgCountResp{
Count: count,
},
}
data, err := json.Marshal(resp)
if err != nil {
return fmt.Errorf("cacheUnreadMsgEvent action marshal resp occurs error: %w", err)
}
if err = e.wc.PutUnreadMsgCountResp(e.uid, data); err != nil {
return fmt.Errorf("cacheUnreadMsgEvent action put resp data to redis cache occurs error: %w", err)
}
return nil
}
func (e *createMessageEvent) Name() string {
return "createMessageEvent"
}
func (e *createMessageEvent) Action() (err error) {
if _, err = e.ds.CreateMessage(e.message); err == nil {
err = e.wc.DelUnreadMsgCountResp(e.message.ReceiverUserID)
}
return
}

@ -286,8 +286,7 @@ func (s *privSrv) CreateTweet(req *web.CreateTweetReq) (_ *web.CreateTweetResp,
}
// 创建消息提醒
// TODO: 优化消息提醒处理机制
go s.Ds.CreateMessage(&ms.Message{
onCreateMessageEvent(&ms.Message{
SenderUserID: req.User.ID,
ReceiverUserID: user.ID,
Type: ms.MsgTypePost,
@ -390,7 +389,7 @@ func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.Creat
// 创建用户消息提醒
commentMaster, err := s.Ds.GetUserByID(comment.UserID)
if err == nil && commentMaster.ID != req.Uid {
go s.Ds.CreateMessage(&ms.Message{
onCreateMessageEvent(&ms.Message{
SenderUserID: req.Uid,
ReceiverUserID: commentMaster.ID,
Type: ms.MsgTypeReply,
@ -402,7 +401,7 @@ func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.Creat
}
postMaster, err := s.Ds.GetUserByID(post.UserID)
if err == nil && postMaster.ID != req.Uid && commentMaster.ID != postMaster.ID {
go s.Ds.CreateMessage(&ms.Message{
onCreateMessageEvent(&ms.Message{
SenderUserID: req.Uid,
ReceiverUserID: postMaster.ID,
Type: ms.MsgTypeReply,
@ -416,7 +415,7 @@ func (s *privSrv) CreateCommentReply(req *web.CreateCommentReplyReq) (*web.Creat
user, err := s.Ds.GetUserByID(atUserID)
if err == nil && user.ID != req.Uid && commentMaster.ID != user.ID && postMaster.ID != user.ID {
// 创建消息提醒
go s.Ds.CreateMessage(&ms.Message{
onCreateMessageEvent(&ms.Message{
SenderUserID: req.Uid,
ReceiverUserID: user.ID,
Type: ms.MsgTypeReply,
@ -522,7 +521,7 @@ func (s *privSrv) CreateComment(req *web.CreateCommentReq) (_ *web.CreateComment
// 创建用户消息提醒
postMaster, err := s.Ds.GetUserByID(post.UserID)
if err == nil && postMaster.ID != req.Uid {
go s.Ds.CreateMessage(&ms.Message{
onCreateMessageEvent(&ms.Message{
SenderUserID: req.Uid,
ReceiverUserID: postMaster.ID,
Type: ms.MsgtypeComment,
@ -538,7 +537,7 @@ func (s *privSrv) CreateComment(req *web.CreateCommentReq) (_ *web.CreateComment
}
// 创建消息提醒
go s.Ds.CreateMessage(&ms.Message{
onCreateMessageEvent(&ms.Message{
SenderUserID: req.Uid,
ReceiverUserID: user.ID,
Type: ms.MsgtypeComment,

@ -8,10 +8,11 @@ import (
"github.com/alimy/mir/v4"
"github.com/gin-gonic/gin"
api "github.com/rocboss/paopao-ce/auto/api/v1"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/internal/servants/base"
"github.com/rocboss/paopao-ce/internal/servants/chain"
"github.com/rocboss/paopao-ce/pkg/xerror"
"github.com/sirupsen/logrus"
)
var (
@ -21,6 +22,7 @@ var (
type relaxSrv struct {
api.UnimplementedRelaxServant
*base.DaoServant
wc core.WebCache
}
func (s *relaxSrv) Chain() gin.HandlersChain {
@ -28,17 +30,22 @@ func (s *relaxSrv) Chain() gin.HandlersChain {
}
func (s *relaxSrv) GetUnreadMsgCount(req *web.GetUnreadMsgCountReq) (*web.GetUnreadMsgCountResp, mir.Error) {
count, err := s.Ds.GetUnreadCount(req.Uid)
if err != nil {
return nil, xerror.ServerError
}
if data, xerr := s.wc.GetUnreadMsgCountResp(req.Uid); xerr == nil && len(data) > 0 {
// logrus.Debugln("GetUnreadMsgCount get resp from cache")
return &web.GetUnreadMsgCountResp{
Count: count,
JsonResp: data,
}, nil
} else {
logrus.Warnf("GetUnreadMsgCount from cache occurs error: %s", xerr)
}
// 使用缓存机制特殊处理
onCacheUnreadMsgEvent(req.Uid)
return &web.GetUnreadMsgCountResp{}, nil
}
func newRelaxSrv(s *base.DaoServant) api.Relax {
func newRelaxSrv(s *base.DaoServant, wc core.WebCache) api.Relax {
return &relaxSrv{
DaoServant: s,
wc: wc,
}
}

@ -11,27 +11,31 @@ import (
"github.com/gin-gonic/gin"
api "github.com/rocboss/paopao-ce/auto/api/v1"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/dao"
"github.com/rocboss/paopao-ce/internal/dao/cache"
"github.com/rocboss/paopao-ce/internal/servants/base"
)
var (
_enablePhoneVerify bool
_disallowUserRegister bool
_ds core.DataService
_wc core.WebCache
_oss core.ObjectStorageService
_onceInitial sync.Once
)
// RouteWeb register web route
func RouteWeb(e *gin.Engine) {
lazyInitial()
oss := dao.ObjectStorageService()
ds := base.NewDaoServant()
// aways register servants
api.RegisterAdminServant(e, newAdminSrv(ds))
api.RegisterCoreServant(e, newCoreSrv(ds, oss))
api.RegisterRelaxServant(e, newRelaxSrv(ds))
api.RegisterCoreServant(e, newCoreSrv(ds, _oss, _wc))
api.RegisterRelaxServant(e, newRelaxSrv(ds, _wc))
api.RegisterLooseServant(e, newLooseSrv(ds))
api.RegisterPrivServant(e, newPrivSrv(ds, oss))
api.RegisterPrivServant(e, newPrivSrv(ds, _oss))
api.RegisterPubServant(e, newPubSrv(ds))
api.RegisterFollowshipServant(e, newFollowshipSrv(ds))
api.RegisterFriendshipServant(e, newFriendshipSrv(ds))
@ -48,5 +52,8 @@ func lazyInitial() {
_onceInitial.Do(func() {
_enablePhoneVerify = cfg.If("Sms")
_disallowUserRegister = cfg.If("Web:DisallowUserRegister")
_oss = dao.ObjectStorageService()
_ds = dao.DataService()
_wc = cache.NewWebCache()
})
}

@ -7,6 +7,7 @@ package utils
import (
"math/rand"
"time"
"unsafe"
)
type StrType int
@ -41,3 +42,10 @@ func RandStr(size int, kind StrType) []byte {
}
return result
}
func String(data []byte) string {
if size := len(data); size > 0 {
return unsafe.String(unsafe.SliceData(data), size)
}
return ""
}

Loading…
Cancel
Save