Merge branch 'develop'

This commit is contained in:
kercylan98 2023-07-12 19:16:05 +08:00
commit a661da790c
31 changed files with 1049 additions and 318 deletions

View File

@ -122,12 +122,6 @@ func (slf *Activity[PlayerID, ActivityData, PlayerData]) IsInvalid() bool {
current := activityOffset.Now()
if slf.beforeShow.IsZero() && !slf.afterShow.IsZero() {
return current.After(slf.afterShow) || current.Equal(slf.afterShow)
} else if !slf.beforeShow.IsZero() && slf.afterShow.IsZero() {
end := slf.GetEnd()
return current.After(end) || current.Equal(end)
} else if !slf.beforeShow.IsZero() && !slf.afterShow.IsZero() {
end := slf.GetEnd()
return current.After(end) || current.Equal(end)
} else {
end := slf.GetEnd()
return current.After(end) || current.Equal(end)

14
game/task/errors.go Normal file
View File

@ -0,0 +1,14 @@
package task
import "errors"
var (
// ErrTaskNotFinish 任务未完成
ErrTaskNotFinish = errors.New("task not finish")
// ErrTaskRewardReceived 任务奖励已领取
ErrTaskRewardReceived = errors.New("task reward received")
// ErrTaskNotStart 任务未开始
ErrTaskNotStart = errors.New("task not start")
// ErrTaskFail 任务失败
ErrTaskFail = errors.New("task fail")
)

View File

@ -1,32 +1,35 @@
package task
type (
TaskDoneEventHandle func(eventId int64, task *Task)
RefreshTaskCountEvent func(taskType int, increase int64)
RefreshTaskChildCountEvent func(taskType int, key any, increase int64)
)
var taskDoneEventHandles = make(map[int64]map[int64]TaskDoneEventHandle)
var (
refreshTaskCountEventHandles = make(map[int]RefreshTaskCountEvent)
refreshTaskChildCountEventHandles = make(map[int]RefreshTaskChildCountEvent)
)
// RegTaskDoneEvent 注册任务完成事件
func RegTaskDoneEvent(id int64, eventId int64, handle TaskDoneEventHandle) {
events, exist := taskDoneEventHandles[id]
if !exist {
events = map[int64]TaskDoneEventHandle{}
taskDoneEventHandles[id] = events
}
events[eventId] = handle
// RegRefreshTaskCount 注册任务计数刷新事件
func RegRefreshTaskCount(taskType int, handler RefreshTaskCountEvent) {
refreshTaskCountEventHandles[taskType] = handler
}
// UnRegTaskDoneEvent 取消注册任务完成事件
func UnRegTaskDoneEvent(id int64, eventId int64) {
events, exist := taskDoneEventHandles[id]
if exist {
delete(events, eventId)
// OnRefreshTaskCount 触发任务计数刷新事件
func OnRefreshTaskCount(taskType int, increase int64) {
if handler, ok := refreshTaskCountEventHandles[taskType]; ok {
handler(taskType, increase)
}
}
// OnTaskDoneEvent 任务完成事件
func OnTaskDoneEvent(task *Task) {
for eventId, handle := range taskDoneEventHandles[task.id] {
handle(eventId, task)
// RegRefreshTaskChildCount 注册任务子计数刷新事件
func RegRefreshTaskChildCount(taskType int, handler RefreshTaskChildCountEvent) {
refreshTaskChildCountEventHandles[taskType] = handler
}
// OnRefreshTaskChildCount 触发任务子计数刷新事件
func OnRefreshTaskChildCount(taskType int, key any, increase int64) {
if handler, ok := refreshTaskChildCountEventHandles[taskType]; ok {
handler(taskType, key, increase)
}
}

View File

@ -1,17 +1,85 @@
package task
import (
"github.com/kercylan98/minotaur/utils/offset"
"time"
)
type Option func(task *Task)
// WithInitCount 通过初始化计数的方式创建任务
func WithInitCount(count int) Option {
// WithChildCount 通过初始化子计数的方式创建任务
func WithChildCount(key any, childCount int64) Option {
return func(task *Task) {
task.count = count
if task.childCount == nil {
task.childCount = make(map[any]int64)
}
if task.childCondition == nil {
task.childCondition = make(map[any]int64)
}
task.childCount[key] = childCount
}
}
// WithDone 通过指定任务完成计数的方式创建任务
func WithDone(done int) Option {
// WithChild 通过指定子计数的方式创建任务
// - 只有当子计数与主计数均达到条件时,任务才会完成
// - 通常用于多条件的任务
func WithChild(key any, childCondition int64) Option {
return func(task *Task) {
task.done = done
if task.childCount == nil {
task.childCount = make(map[any]int64)
}
if task.childCondition == nil {
task.childCondition = make(map[any]int64)
}
task.childCondition[key] = childCondition
}
}
// WithDisableNotStartGetReward 禁止未开始的任务领取奖励
func WithDisableNotStartGetReward() Option {
return func(task *Task) {
task.disableNotStartGetReward = true
}
}
// WithCount 通过初始化计数的方式创建任务
func WithCount(count int64) Option {
return func(task *Task) {
task.SetCount(count)
}
}
// WithStartTime 通过指定开始时间的方式创建任务
// - 只有当时间在开始时间之后,任务才会开始计数
func WithStartTime(startTime time.Time) Option {
return func(task *Task) {
task.start = startTime
}
}
// WithOffsetTime 通过指定偏移时间的方式创建任务
func WithOffsetTime(offset *offset.Time) Option {
return func(task *Task) {
task.offset = offset
}
}
// WithLimitedTime 通过限时的方式创建任务
func WithLimitedTime(limitTime time.Duration) Option {
return func(task *Task) {
task.limitTime = limitTime
}
}
// WithFront 通过指定任务前置任务的方式创建任务
// - 当前置任务未完成时,当前任务不会开始计数
func WithFront(fronts ...*Task) Option {
return func(task *Task) {
if task.fronts == nil {
task.fronts = make(map[int64]*Task)
}
for _, front := range fronts {
task.fronts[front.GetID()] = front
}
}
}

View File

@ -1,10 +1,10 @@
package task
const (
StateDoing State = iota // 进行中
StateDone // 已完成
StateAccept State = iota // 已接受
StateFinish // 已完成
StateReward // 已领取
StateFail // 已失败
)
// State 任务状态
type State byte
type State uint16

View File

@ -1,67 +1,204 @@
package task
// NewTask 新建任务
func NewTask(id int64, options ...Option) *Task {
import (
"github.com/kercylan98/minotaur/utils/hash"
"github.com/kercylan98/minotaur/utils/offset"
"time"
)
// NewTask 创建任务
func NewTask(id int64, taskType int, condition int64, options ...Option) *Task {
task := &Task{
id: id,
condition: condition,
state: StateAccept,
}
for _, option := range options {
option(task)
}
if task.count > task.done {
task.count = task.done
if task.start.IsZero() {
if task.offset != nil {
task.start = task.offset.Now()
} else {
task.start = time.Now()
}
}
for key := range task.childCount {
if !hash.Exist(task.childCondition, key) {
delete(task.childCount, key)
}
}
if task.count == task.condition {
task.state = StateFinish
}
task.Add(0)
return task
}
// Task 通用任务数据结构
type Task struct {
id int64 // 任务ID
count int // 任务计数
done int // 任务完成计数
reward bool // 是否已领取奖励
taskType int // 任务类型
count int64 // 任务主计数
condition int64 // 任务完成需要的计数条件
childCount map[any]int64 // 任务子计数
childCondition map[any]int64 // 任务子计数条件
state State // 任务状态
start time.Time // 任务开始时间
limitTime time.Duration // 任务限时
fronts map[int64]*Task // 任务前置任务
disableNotStartGetReward bool // 禁止未开始的任务领取奖励
offset *offset.Time // 任务偏移时间
}
// GetID 获取任务ID
func (slf *Task) GetID() int64 {
return slf.id
}
// GetType 获取任务类型
func (slf *Task) GetType() int {
return slf.taskType
}
// Reset 重置任务
func (slf *Task) Reset() {
slf.count = 0
slf.reward = false
switch slf.GetState() {
case StateDone:
OnTaskDoneEvent(slf)
slf.state = StateAccept
for key := range slf.childCount {
delete(slf.childCount, key)
}
}
// Add 增加任务计数
func (slf *Task) Add(count int) {
if count != 0 {
slf.count += count
if slf.count < 0 {
slf.count = 0
} else if slf.count > slf.done {
slf.count = slf.done
// GetFronts 获取前置任务
func (slf *Task) GetFronts() map[int64]*Task {
return slf.fronts
}
// GetFrontsWithState 获取特定状态的前置任务
func (slf *Task) GetFrontsWithState(state State) map[int64]*Task {
fronts := make(map[int64]*Task)
for id, front := range slf.fronts {
if front.GetState() == state {
fronts[id] = front
}
}
return fronts
}
// FrontsIsFinish 判断前置任务是否完成
func (slf *Task) FrontsIsFinish() bool {
for _, front := range slf.fronts {
state := front.GetState()
if state == StateAccept || state == StateFail {
return false
}
}
return true
}
// GetReward 获取任务奖励
// - 当任务状态为 StateFinish 时,调用 rewardHandle 函数
// - 当任务状态不为 StateFinish 或奖励函数发生错误时,返回错误
func (slf *Task) GetReward(rewardHandle func() error) error {
if !slf.IsStart() {
return ErrTaskNotStart
}
switch slf.GetState() {
case StateDone:
OnTaskDoneEvent(slf)
case StateAccept:
return ErrTaskNotFinish
case StateReward:
return ErrTaskRewardReceived
case StateFail:
return ErrTaskFail
}
if err := rewardHandle(); err != nil {
return err
}
slf.state = StateReward
return nil
}
// GetState 获取任务状态
func (slf *Task) GetState() State {
if slf.count >= slf.done {
if slf.reward {
return StateReward
}
return StateDone
}
return StateDoing
return slf.state
}
// Reward 返回是否领取过奖励,并且设置任务为领取过奖励的状态
func (slf *Task) Reward() bool {
reward := slf.reward
slf.reward = true
return reward
// IsStart 判断任务是否开始
func (slf *Task) IsStart() bool {
var current time.Time
if slf.offset != nil {
current = slf.offset.Now()
} else {
current = time.Now()
}
if current.Before(slf.start) {
return false
} else if slf.limitTime > 0 && current.Sub(slf.start) >= slf.limitTime {
return false
}
return true
}
// SetCount 设置计数
func (slf *Task) SetCount(count int64) {
if !slf.IsStart() || !slf.FrontsIsFinish() {
return
}
slf.count = count
if slf.count >= slf.condition {
slf.count = slf.condition
} else if slf.count < 0 {
slf.count = 0
}
slf.refreshState()
}
// AddCount 增加计数
func (slf *Task) AddCount(count int64) {
slf.SetCount(slf.count + count)
}
// GetCount 获取计数
func (slf *Task) GetCount() int64 {
return slf.count
}
// GetCondition 获取计数条件
func (slf *Task) GetCondition() int64 {
return slf.condition
}
// SetChildCount 设置子计数
func (slf *Task) SetChildCount(key any, count int64) {
if !slf.IsStart() || !slf.FrontsIsFinish() || !hash.Exist(slf.childCondition, key) {
return
}
if condition := slf.childCondition[key]; count > condition {
count = condition
} else if count < 0 {
count = 0
}
slf.childCount[key] = count
slf.refreshState()
}
// AddChildCount 增加子计数
func (slf *Task) AddChildCount(key any, count int64) {
slf.SetChildCount(key, slf.childCount[key]+count)
}
// refreshState 刷新任务状态
func (slf *Task) refreshState() {
slf.state = StateFinish
if slf.count != slf.condition {
slf.state = StateAccept
return
}
for key, condition := range slf.childCondition {
if slf.childCount[key] != condition {
slf.state = StateAccept
return
}
}
}

View File

@ -1,6 +1,24 @@
package server
import "time"
import (
"github.com/kercylan98/minotaur/utils/log"
"time"
)
type (
RunMode = log.RunMode
)
const (
RunModeDev RunMode = log.RunModeDev
RunModeProd RunMode = log.RunModeProd
RunModeTest RunMode = log.RunModeTest
)
const (
serverMultipleMark = "Minotaur Multiple Server"
serverMark = "Minotaur Server"
)
const (
DefaultMessageBufferSize = 1024

View File

@ -11,6 +11,10 @@ import (
"time"
)
const (
nasMark = "Cross.Nats"
)
func NewNats(url string, options ...NatsOption) *Nats {
n := &Nats{
url: url,
@ -43,10 +47,10 @@ func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, p
nats.ReconnectWait(time.Second*5),
nats.MaxReconnects(-1),
nats.DisconnectErrHandler(func(conn *nats.Conn, err error) {
log.Error("Cross.Nats", zap.String("info", "disconnect"), zap.Error(err))
log.Error(nasMark, zap.String("info", "disconnect"), zap.Error(err))
}),
nats.ReconnectHandler(func(conn *nats.Conn) {
log.Info("Cross.Nats", zap.String("info", "reconnect"))
log.Info(nasMark, zap.String("info", "reconnect"))
}),
)
}
@ -59,7 +63,7 @@ func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, p
message := slf.messagePool.Get()
defer slf.messagePool.Release(message)
if err := json.Unmarshal(msg.Data, &message); err != nil {
log.Error("Cross.Nats", zap.Error(err))
log.Error(nasMark, zap.Error(err))
return
}
packetHandle(message.ServerId, message.Packet)

View File

@ -3,9 +3,7 @@ package server
import (
"encoding/json"
"fmt"
"github.com/kercylan98/minotaur/utils/str"
"reflect"
"runtime/debug"
)
const (
@ -69,11 +67,14 @@ func (slf *Message) String() string {
}
attrs = append(attrs, attr)
}
raw, _ := json.Marshal(attrs)
s := string(raw)
if s == str.None {
var s string
if len(slf.attrs) == 0 {
s = "NoneAttr"
} else {
raw, _ := json.Marshal(attrs)
s = string(raw)
}
return fmt.Sprintf("[%s] %s", slf.t, s)
}
@ -93,7 +94,7 @@ func PushPacketMessage(srv *Server, conn *Conn, packet []byte, mark ...any) {
func PushErrorMessage(srv *Server, err error, action MessageErrorAction, mark ...any) {
msg := srv.messagePool.Get()
msg.t = MessageTypeError
msg.attrs = append([]any{err, action, string(debug.Stack())}, mark...)
msg.attrs = append([]any{err, action}, mark...)
srv.pushMessage(msg)
}
@ -128,6 +129,6 @@ func PushTickerMessage(srv *Server, caller func(), mark ...any) {
func PushAsyncMessage(srv *Server, caller func() error, callback func(err error), mark ...any) {
msg := srv.messagePool.Get()
msg.t = MessageTypeAsync
msg.attrs = append([]any{caller, callback, string(debug.Stack())}, mark...)
msg.attrs = append([]any{caller, callback}, mark...)
srv.pushMessage(msg)
}

View File

@ -60,14 +60,14 @@ func (slf *MultipleServer) Run() {
}
wait.Wait()
log.Info("Server", zap.String("Minotaur Multiple Server", "===================================================================="))
log.Info("Server", zap.String(serverMultipleMark, "===================================================================="))
for _, server := range slf.servers {
log.Info("Server", zap.String("Minotaur Multiple Server", "RunningInfo"),
log.Info("Server", zap.String(serverMultipleMark, "RunningInfo"),
zap.Any("network", server.network),
zap.String("listen", server.addr),
)
}
log.Info("Server", zap.String("Minotaur Multiple Server", "===================================================================="))
log.Info("Server", zap.String(serverMultipleMark, "===================================================================="))
systemSignal := make(chan os.Signal, 1)
signal.Notify(systemSignal, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)

View File

@ -37,7 +37,6 @@ type runtime struct {
certFile, keyFile string // TLS文件
messagePoolSize int // 消息池大小
messageChannelSize int // 消息通道大小
prod bool // 是否为生产模式
ticker *timer.Ticker // 定时器
websocketReadDeadline time.Duration // websocket连接超时时间
websocketCompression int // websocket压缩等级
@ -184,10 +183,11 @@ func WithGRPCServerOptions(options ...grpc.ServerOption) Option {
}
}
// WithProd 通过生产模式运行服务器
func WithProd() Option {
// WithRunMode 通过特定模式运行服务器
// - 默认为 RunModeDev
func WithRunMode(mode RunMode) Option {
return func(srv *Server) {
srv.prod = true
srv.runMode = mode
}
}

View File

@ -20,6 +20,7 @@ import (
"net/http"
"os"
"os/signal"
"runtime/debug"
"strings"
"sync/atomic"
"syscall"
@ -60,7 +61,7 @@ func New(network Network, options ...Option) *Server {
server.antsPoolSize = DefaultAsyncPoolSize
}
var err error
server.ants, err = ants.NewPool(server.antsPoolSize, ants.WithLogger(log.Logger()))
server.ants, err = ants.NewPool(server.antsPoolSize, ants.WithLogger(log.GetLogger()))
if err != nil {
panic(err)
}
@ -91,6 +92,7 @@ type Server struct {
messageChannel chan *Message // 消息管道
multiple *MultipleServer // 多服务器模式下的服务器
multipleRuntimeErrorChan chan error // 多服务器模式下的运行时错误
runMode RunMode // 运行模式
}
// Run 使用特定地址运行服务器
@ -156,8 +158,8 @@ func (slf *Server) Run(addr string) error {
slf.isRunning = true
slf.OnStartBeforeEvent()
if err := gnet.Serve(slf.gServer, protoAddr,
gnet.WithLogger(log.Logger().Sugar()),
gnet.WithLogLevel(super.If(slf.IsProd(), logging.ErrorLevel, logging.DebugLevel)),
gnet.WithLogger(log.GetLogger()),
gnet.WithLogLevel(super.If(slf.runMode == RunModeProd, logging.ErrorLevel, logging.DebugLevel)),
gnet.WithTicker(true),
gnet.WithMulticore(true),
); err != nil {
@ -201,8 +203,12 @@ func (slf *Server) Run(addr string) error {
}
})
case NetworkHttp:
if slf.prod {
log.SetProd(slf.prod)
switch slf.runMode {
case RunModeDev:
gin.SetMode(gin.DebugMode)
case RunModeTest:
gin.SetMode(gin.TestMode)
case RunModeProd:
gin.SetMode(gin.ReleaseMode)
}
go func() {
@ -259,7 +265,7 @@ func (slf *Server) Run(addr string) error {
conn := newWebsocketConn(slf, ws, ip)
for k, v := range request.URL.Query() {
if len(v) == 1 {
conn.SetData(k, v)
conn.SetData(k, v[0])
} else {
conn.SetData(k, v)
}
@ -307,12 +313,12 @@ func (slf *Server) Run(addr string) error {
}
if slf.multiple == nil {
log.Info("Server", zap.String("Minotaur Server", "===================================================================="))
log.Info("Server", zap.String("Minotaur Server", "RunningInfo"),
log.Info("Server", zap.String(serverMark, "===================================================================="))
log.Info("Server", zap.String(serverMark, "RunningInfo"),
zap.Any("network", slf.network),
zap.String("listen", slf.addr),
)
log.Info("Server", zap.String("Minotaur Server", "===================================================================="))
log.Info("Server", zap.String(serverMark, "===================================================================="))
slf.OnStartFinishEvent()
signal.Notify(slf.systemSignal, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
@ -359,16 +365,6 @@ func (slf *Server) CloseConn(id string) {
}
}
// IsProd 是否为生产模式
func (slf *Server) IsProd() bool {
return slf.prod
}
// IsDev 是否为开发模式
func (slf *Server) IsDev() bool {
return !slf.prod
}
// GetID 获取服务器id
func (slf *Server) GetID() int64 {
if slf.cross == nil {
@ -391,7 +387,7 @@ func (slf *Server) Shutdown() {
}
// shutdown 停止运行服务器
func (slf *Server) shutdown(err error, stack ...string) {
func (slf *Server) shutdown(err error) {
slf.OnStopEvent()
defer func() {
if slf.multipleRuntimeErrorChan != nil {
@ -426,13 +422,9 @@ func (slf *Server) shutdown(err error, stack ...string) {
}
if err != nil {
var s string
if len(stack) > 0 {
s = stack[0]
}
if slf.multiple != nil {
slf.multiple.RegExitEvent(func() {
log.ErrorWithStack("Server", s, zap.Any("network", slf.network), zap.String("listen", slf.addr),
log.Panic("Server", zap.Any("network", slf.network), zap.String("listen", slf.addr),
zap.String("action", "shutdown"), zap.String("state", "exception"), zap.Error(err))
})
for i, server := range slf.multiple.servers {
@ -442,7 +434,7 @@ func (slf *Server) shutdown(err error, stack ...string) {
}
}
} else {
log.ErrorWithStack("Server", s, zap.Any("network", slf.network), zap.String("listen", slf.addr),
log.Panic("Server", zap.Any("network", slf.network), zap.String("listen", slf.addr),
zap.String("action", "shutdown"), zap.String("state", "exception"), zap.Error(err))
}
} else {
@ -479,10 +471,10 @@ func (slf *Server) pushMessage(message *Message) {
slf.messageChannel <- message
}
func (slf *Server) low(message *Message, present time.Time) {
func (slf *Server) low(message *Message, present time.Time, expect time.Duration) {
cost := time.Since(present)
if cost > time.Millisecond*100 {
log.Warn("Server", zap.String("LowExecCost", cost.String()), zap.Any("Message", message))
if cost > expect {
log.Warn("Server", zap.String("type", "low-message"), zap.String("cost", cost.String()), zap.String("message", message.String()), zap.Stack("stack"))
slf.OnMessageLowExecEvent(message, cost)
}
}
@ -508,19 +500,24 @@ func (slf *Server) dispatchMessage(msg *Message) {
present := time.Now()
defer func() {
if err := recover(); err != nil {
log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("MessageAttrs", msg.attrs), zap.Any("error", err))
stack := string(debug.Stack())
log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("MessageAttrs", msg.attrs), zap.Any("error", err), zap.String("stack", stack))
fmt.Println(stack)
if e, ok := err.(error); ok {
slf.OnMessageErrorEvent(msg, e)
}
}
if msg.t != MessageTypeAsync {
if msg.t == MessageTypeAsync {
return
}
super.Handle(cancel)
slf.low(msg, present)
slf.low(msg, present, time.Millisecond*100)
if !slf.isShutdown.Load() {
slf.messagePool.Release(msg)
}
}
}()
var attrs = msg.attrs
@ -530,12 +527,12 @@ func (slf *Server) dispatchMessage(msg *Message) {
var wst = int(packet[len(packet)-1])
slf.OnConnectionReceivePacketEvent(attrs[0].(*Conn), Packet{Data: packet[:len(packet)-1], WebsocketType: wst})
case MessageTypeError:
err, action, stack := attrs[0].(error), attrs[1].(MessageErrorAction), attrs[2].(string)
err, action := attrs[0].(error), attrs[1].(MessageErrorAction)
switch action {
case MessageErrorActionNone:
log.ErrorWithStack("Server", stack, zap.Error(err))
log.Panic("Server", zap.Error(err))
case MessageErrorActionShutdown:
slf.shutdown(err, stack)
slf.shutdown(err)
default:
log.Warn("Server", zap.String("not support message error action", action.String()))
}
@ -549,13 +546,16 @@ func (slf *Server) dispatchMessage(msg *Message) {
if err := slf.ants.Submit(func() {
defer func() {
if err := recover(); err != nil {
log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("MessageAttrs", msg.attrs), zap.Any("error", err))
stack := string(debug.Stack())
log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("error", err), zap.String("stack", stack))
fmt.Println(stack)
if e, ok := err.(error); ok {
slf.OnMessageErrorEvent(msg, e)
}
}
super.Handle(cancel)
slf.low(msg, present)
slf.low(msg, present, time.Second)
if !slf.isShutdown.Load() {
slf.messagePool.Release(msg)
}

View File

@ -48,10 +48,6 @@ func (slf *Map[Key, Value]) GetExist(key Key) (Value, bool) {
return value, exist
}
func (slf *Map[Key, Value]) Length() int {
return len(slf.data)
}
func (slf *Map[Key, Value]) Delete(key Key) {
delete(slf.data, key)
}

8
utils/geometry/errors.go Normal file
View File

@ -0,0 +1,8 @@
package geometry
import "errors"
var (
// ErrUnexplainedDirection 错误的方向
ErrUnexplainedDirection = errors.New("unexplained direction")
)

View File

@ -48,7 +48,7 @@ func GetDirectionNextWithCoordinate[V generic.SignedNumber](direction Direction,
case DirectionRight:
nx, ny = x+1, y
default:
panic("unexplained direction")
panic(ErrUnexplainedDirection)
}
return
}
@ -66,7 +66,7 @@ func GetDirectionNextWithPoint[V generic.SignedNumber](direction Direction, poin
case DirectionRight:
return NewPoint(x+1, y)
default:
panic("unexplained direction")
panic(ErrUnexplainedDirection)
}
}
@ -83,7 +83,7 @@ func GetDirectionNextWithPos[V generic.SignedNumber](direction Direction, width,
case DirectionRight:
return pos + 1
default:
panic("unexplained direction")
panic(ErrUnexplainedDirection)
}
}

View File

@ -36,6 +36,15 @@ func ToMap[V any](slice []V) map[int]V {
return m
}
// ToIterator 将切片转换为 Iterator
func ToIterator[V comparable](slice []V) map[V]struct{} {
var m = make(map[V]struct{})
for _, v := range slice {
m[v] = struct{}{}
}
return m
}
// ToMapBool 将切片转换为 mapvalue作为Key
func ToMapBool[V comparable](slice []V) map[V]bool {
var m = make(map[V]bool)

View File

@ -5,7 +5,6 @@ type MapReadonly[Key comparable, Value any] interface {
Get(key Key) Value
Exist(key Key) bool
GetExist(key Key) (Value, bool)
Length() int
Range(handle func(key Key, value Value))
RangeSkip(handle func(key Key, value Value) bool)
RangeBreakout(handle func(key Key, value Value) bool)

5
utils/log/core.go Normal file
View File

@ -0,0 +1,5 @@
package log
import "go.uber.org/zap/zapcore"
type Core = zapcore.Core

26
utils/log/encoder.go Normal file
View File

@ -0,0 +1,26 @@
package log
import (
"go.uber.org/zap/zapcore"
"time"
)
type Encoder = zapcore.Encoder
// NewEncoder 创建一个 Minotaur 默认使用的编码器
func NewEncoder() Encoder {
return zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "level",
EncodeLevel: zapcore.CapitalLevelEncoder,
TimeKey: "ts",
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format(time.DateTime))
},
CallerKey: "file",
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(int64(d) / 1000000)
},
})
}

159
utils/log/field.go Normal file
View File

@ -0,0 +1,159 @@
package log
import (
"go.uber.org/zap"
)
type Field = zap.Field
var (
// Skip 构造一个无操作字段,这在处理其他 Field 构造函数中的无效输入时通常很有用
Skip = zap.Skip
// Binary 构造一个携带不透明二进制 blob 的字段。二进制数据以适合编码的格式进行序列化。例如JSON 编码器对二进制 blob 进行 base64 编码。要记录 UTF-8 编码文本,请使用 ByteString
Binary = zap.Binary
// Bool 构造一个带有布尔值的字段
Bool = zap.Bool
// BoolP 构造一个带有布尔值的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
BoolP = zap.Boolp
// ByteString 构造一个将 UTF-8 编码文本作为 [] 字节传送的字段。要记录不透明的二进制 blob不一定是有效的 UTF-8请使用 Binary
ByteString = zap.ByteString
// Complex128 构造一个带有复数的字段。与大多数数字字段不同这需要分配将complex128转换为interface{}
Complex128 = zap.Complex128
// Complex128P 构造一个带有complex128 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Complex128P = zap.Complex128p
// Complex64 构造一个带有复数的字段。与大多数数字字段不同这需要分配将complex64转换为interface{}
Complex64 = zap.Complex64
// Complex64P 构造一个带有complex64 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Complex64P = zap.Complex64p
// Float64 构造一个带有 float64 的字段。浮点值的表示方式取决于编码器,因此封送处理必然是惰性的
Float64 = zap.Float64
// Float64P 构造一个带有 float64 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Float64P = zap.Float64p
// Float32 构造一个带有 float32 的字段。浮点值的表示方式取决于编码器,因此封送处理必然是惰性的
Float32 = zap.Float32
// Float32P 构造一个带有 float32 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Float32P = zap.Float32p
// Int constructs a field with the given key and value.
Int = zap.Int
// IntP 构造一个带有 int 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
IntP = zap.Intp
// Int64 使用给定的键和值构造一个字段.
Int64 = zap.Int64
// Int64P 构造一个带有 int64 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Int64P = zap.Int64p
// Int32 使用给定的键和值构造一个字段
Int32 = zap.Int32
// Int32P 构造一个带有 int32 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”.
Int32P = zap.Int32p
// Int16 使用给定的键和值构造一个字段
Int16 = zap.Int16
// Int16P 构造一个带有 int16 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Int16P = zap.Int16p
// Int8 使用给定的键和值构造一个字段
Int8 = zap.Int8
// Int8P 构造一个带有 int8 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Int8P = zap.Int8p
// String 使用给定的键和值构造一个字段
String = zap.String
// StringP 构造一个带有字符串的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
StringP = zap.Stringp
// Uint 使用给定的键和值构造一个字段
Uint = zap.Uint
// UintP 构造一个带有 uint 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
UintP = zap.Uintp
// Uint64 使用给定的键和值构造一个字段
Uint64 = zap.Uint64
// Uint64P 构造一个带有 uint64 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Uint64P = zap.Uint64p
// Uint32 使用给定的键和值构造一个字段
Uint32 = zap.Uint32
// Uint32P 构造一个带有 uint32 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Uint32P = zap.Uint32p
// Uint16 使用给定的键和值构造一个字段
Uint16 = zap.Uint16
// Uint16P 构造一个带有 uint16 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Uint16P = zap.Uint16p
// Uint8 使用给定的键和值构造一个字段
Uint8 = zap.Uint8
// Uint8P 构造一个带有 uint8 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
Uint8P = zap.Uint8p
// Uintptr 使用给定的键和值构造一个字段
Uintptr = zap.Uintptr
// UintptrP 构造一个带有 uintptr 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
UintptrP = zap.Uintptrp
// Reflect 使用给定的键和任意对象构造一个字段。它使用适当的编码、基于反射的方式将几乎任何对象延迟序列化到日志记录上下文中但它相对较慢且分配繁重。在测试之外Any 始终是更好的选择
// - 如果编码失败(例如,尝试将 map[int]string 序列化为 JSONReflect 将在最终日志输出中包含错误消息
Reflect = zap.Reflect
// Namespace 命名空间在记录器的上下文中创建一个命名的、隔离的范围。所有后续字段都将添加到新的命名空间中
// - 这有助于防止将记录器注入子组件或第三方库时发生按键冲突
Namespace = zap.Namespace
// Stringer 使用给定的键和值的 String 方法的输出构造一个字段。 Stringer 的 String 方法被延迟调用
Stringer = zap.Stringer
// Time 使用给定的键和值构造一个 Field。编码器控制时间的序列化方式
Time = zap.Time
// TimeP 构造一个带有 time.Time 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
TimeP = zap.Timep
// Stack 构造一个字段,在提供的键下存储当前 goroutine 的堆栈跟踪。请记住,进行堆栈跟踪是急切且昂贵的(相对而言);此操作会进行分配并花费大约两微秒的时间
Stack = zap.Stack
// StackSkip 构造一个与 Stack 类似的字段,但也会从堆栈跟踪顶部跳过给定数量的帧
StackSkip = zap.StackSkip
// Duration 使用给定的键和值构造一个字段。编码器控制持续时间的序列化方式
Duration = zap.Duration
// DurationP 构造一个带有 time.Duration 的字段。返回的 Field 将在适当的时候安全且显式地表示“nil”
DurationP = zap.Durationp
// Object 使用给定的键和 ObjectMarshaler 构造一个字段。它提供了一种灵活但仍然类型安全且高效的方法来将类似映射或结构的用户定义类型添加到日志记录上下文。该结构的 MarshalLogObject 方法被延迟调用
Object = zap.Object
// Inline 构造一个与 Object 类似的 Field但它会将提供的 ObjectMarshaler 的元素添加到当前命名空间
Inline = zap.Inline
// Any 接受一个键和一个任意值,并选择将它们表示为字段的最佳方式,仅在必要时才回退到基于反射的方法。
// 由于 byteuint8 和 runeint32 是别名Any 无法区分它们。为了尽量减少意外情况,[]byte 值被视为二进制 blob字节值被视为 uint8而 runes 始终被视为整数
Any = zap.Any
)

110
utils/log/level.go Normal file
View File

@ -0,0 +1,110 @@
package log
import (
"github.com/kercylan98/minotaur/utils/hash"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
type Level = zapcore.Level
type LevelEnablerFunc = zap.LevelEnablerFunc
const (
// DebugLevel 调试级别日志通常非常庞大,并且通常在生产中被禁用
DebugLevel Level = zapcore.DebugLevel
// InfoLevel 是默认的日志记录优先级
InfoLevel Level = zapcore.InfoLevel
// WarnLevel 日志比信息更重要,但不需要单独的人工审核
WarnLevel Level = zapcore.WarnLevel
// ErrorLevel 日志具有高优先级。如果应用程序运行顺利,它不应该生成任何错误级别的日志
ErrorLevel Level = zapcore.ErrorLevel
// DPanicLevel 日志是特别重要的错误。在开发中,记录器在写入消息后会出现恐慌
DPanicLevel Level = zapcore.DPanicLevel
// PanicLevel 记录一条消息,然后出现恐慌
PanicLevel Level = zapcore.PanicLevel
// FatalLevel 记录一条消息,然后调用 os.Exit(1)
FatalLevel Level = zapcore.FatalLevel
)
var (
levels = []Level{DebugLevel, InfoLevel, WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, FatalLevel}
defaultLevelPartition = map[Level]func() LevelEnablerFunc{
DebugLevel: DebugLevelPartition,
InfoLevel: InfoLevelPartition,
WarnLevel: WarnLevelPartition,
ErrorLevel: ErrorLevelPartition,
DPanicLevel: DPanicLevelPartition,
PanicLevel: PanicLevelPartition,
FatalLevel: FatalLevelPartition,
}
)
// Levels 返回所有日志级别
func Levels() []Level {
return levels
}
// MultiLevelPartition 返回一个 LevelEnablerFunc该函数在指定的多个级别时返回 true
// - 该函数被用于划分不同级别的日志输出
func MultiLevelPartition(levels ...Level) LevelEnablerFunc {
var levelMap = hash.ToIterator(levels)
return func(level zapcore.Level) bool {
return hash.Exist(levelMap, level)
}
}
// DebugLevelPartition 返回一个 LevelEnablerFunc该函数在 DebugLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func DebugLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == DebugLevel
}
}
// InfoLevelPartition 返回一个 LevelEnablerFunc该函数在 InfoLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func InfoLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == InfoLevel
}
}
// WarnLevelPartition 返回一个 LevelEnablerFunc该函数在 WarnLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func WarnLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == WarnLevel
}
}
// ErrorLevelPartition 返回一个 LevelEnablerFunc该函数在 ErrorLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func ErrorLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == ErrorLevel
}
}
// DPanicLevelPartition 返回一个 LevelEnablerFunc该函数在 DPanicLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func DPanicLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == DPanicLevel
}
}
// PanicLevelPartition 返回一个 LevelEnablerFunc该函数在 PanicLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func PanicLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == PanicLevel
}
}
// FatalLevelPartition 返回一个 LevelEnablerFunc该函数在 FatalLevel 时返回 true
// - 该函数被用于划分不同级别的日志输出
func FatalLevelPartition() LevelEnablerFunc {
return func(level zapcore.Level) bool {
return level == FatalLevel
}
}

View File

@ -2,187 +2,169 @@ package log
import (
"fmt"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"github.com/kercylan98/minotaur/utils/str"
"github.com/kercylan98/minotaur/utils/times"
rotateLogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"io"
"os"
"runtime/debug"
"time"
"path/filepath"
"strings"
)
var (
logger *zap.Logger
prod bool
logPath string
logDevWrite bool
logTime = 7
)
func init() {
logger = newLogger()
if prod && len(logPath) == 0 {
Warn("Logger", zap.String("Tip", "in production mode, if the log file output directory is not set, only the console will be output"))
}
}
func newLogger() *zap.Logger {
encoder := zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
MessageKey: "msg",
LevelKey: "level",
EncodeLevel: zapcore.CapitalLevelEncoder,
TimeKey: "ts",
EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendString(t.Format(time.DateTime))
// NewLog 创建一个日志记录器
func NewLog(options ...Option) *Log {
log := &Log{
filename: func(level Level) string {
return fmt.Sprintf("%s.log", level.String())
},
CallerKey: "file",
EncodeCaller: zapcore.ShortCallerEncoder,
EncodeDuration: func(d time.Duration, enc zapcore.PrimitiveArrayEncoder) {
enc.AppendInt64(int64(d) / 1000000)
rotateFilename: func(level Level) string {
return strings.Join([]string{level.String(), "%Y%m%d.log"}, ".")
},
})
infoLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl == zapcore.InfoLevel
})
debugLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl <= zapcore.FatalLevel
})
errorLevel := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
return lvl >= zapcore.ErrorLevel
})
var cores zapcore.Core
if !prod {
if len(logPath) > 0 && logDevWrite {
infoWriter := getWriter(fmt.Sprintf("%s/info.log", logPath), logTime)
errorWriter := getWriter(fmt.Sprintf("%s/error.log", logPath), logTime)
cores = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), debugLevel),
)
} else {
cores = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), debugLevel),
)
levelPartition: defaultLevelPartition,
}
} else {
if len(logPath) == 0 {
cores = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), debugLevel),
)
} else {
infoWriter := getWriter(fmt.Sprintf("%s/info.log", logPath), logTime)
errorWriter := getWriter(fmt.Sprintf("%s/error.log", logPath), logTime)
cores = zapcore.NewTee(
zapcore.NewCore(encoder, zapcore.AddSync(infoWriter), infoLevel),
zapcore.NewCore(encoder, zapcore.AddSync(errorWriter), errorLevel),
zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), errorLevel),
)
for _, option := range options {
option(log)
}
if len(log.rotateOptions) == 0 {
log.rotateOptions = []rotateLogs.Option{
rotateLogs.WithMaxAge(times.Week),
rotateLogs.WithRotationTime(times.Day),
}
}
return zap.New(cores, zap.AddCaller(), zap.AddCallerSkip(1))
if len(log.cores) == 0 {
var encoder = NewEncoder()
switch log.mode {
case RunModeDev:
var partition LevelEnablerFunc = func(lvl Level) bool {
return true
}
func getWriter(filename string, times int) io.Writer {
hook, err := rotatelogs.New(
filename+".%Y%m%d",
rotatelogs.WithLinkName(filename),
rotatelogs.WithMaxAge(time.Hour*24*7),
rotatelogs.WithRotationTime(time.Hour*time.Duration(times)),
log.cores = append(log.cores, zapcore.NewCore(encoder, os.Stdout, partition))
case RunModeTest, RunModeProd:
if log.mode == RunModeTest {
infoRotate, err := rotateLogs.New(
filepath.Join(log.rotateLogDir, log.rotateFilename(InfoLevel)),
append([]rotateLogs.Option{rotateLogs.WithLinkName(filepath.Join(log.logDir, log.filename(InfoLevel)))}, log.rotateOptions...)...,
)
if err != nil {
panic(err)
}
return hook
errRotate, err := rotateLogs.New(
filepath.Join(log.rotateLogDir, log.rotateFilename(ErrorLevel)),
append([]rotateLogs.Option{rotateLogs.WithLinkName(filepath.Join(log.logDir, log.filename(ErrorLevel)))}, log.rotateOptions...)...,
)
if err != nil {
panic(err)
}
if log.logDir != str.None {
log.cores = append(log.cores, zapcore.NewCore(encoder, zapcore.AddSync(infoRotate), LevelEnablerFunc(func(lvl Level) bool { return lvl < ErrorLevel })))
log.cores = append(log.cores, zapcore.NewCore(encoder, zapcore.AddSync(errRotate), LevelEnablerFunc(func(lvl Level) bool { return lvl >= ErrorLevel })))
log.cores = append(log.cores, zapcore.NewCore(encoder, os.Stdout, LevelEnablerFunc(func(lvl Level) bool { return lvl < ErrorLevel })))
log.cores = append(log.cores, zapcore.NewCore(encoder, os.Stdout, LevelEnablerFunc(func(lvl Level) bool { return lvl >= ErrorLevel })))
}
} else {
infoRotate, err := rotateLogs.New(
filepath.Join(log.rotateLogDir, log.rotateFilename(InfoLevel)),
append([]rotateLogs.Option{rotateLogs.WithLinkName(filepath.Join(log.logDir, log.filename(InfoLevel)))}, log.rotateOptions...)...,
)
if err != nil {
panic(err)
}
errRotate, err := rotateLogs.New(
filepath.Join(log.rotateLogDir, log.rotateFilename(ErrorLevel)),
append([]rotateLogs.Option{rotateLogs.WithLinkName(filepath.Join(log.logDir, log.filename(ErrorLevel)))}, log.rotateOptions...)...,
)
if err != nil {
panic(err)
}
if log.logDir != str.None {
log.cores = append(log.cores, zapcore.NewCore(encoder, zapcore.AddSync(infoRotate), LevelEnablerFunc(func(lvl Level) bool { return lvl == InfoLevel })))
log.cores = append(log.cores, zapcore.NewCore(encoder, zapcore.AddSync(errRotate), LevelEnablerFunc(func(lvl Level) bool { return lvl >= ErrorLevel })))
}
}
}
}
type MLogger struct {
*zap.Logger
log.zap = zap.New(zapcore.NewTee(log.cores...), zap.AddCaller(), zap.AddCallerSkip(1))
log.sugar = log.zap.Sugar()
return log
}
func (slf *MLogger) Printf(format string, args ...interface{}) {
slf.Info(fmt.Sprintf(format, args...))
type Log struct {
zap *zap.Logger
sugar *zap.SugaredLogger
filename func(level Level) string
rotateFilename func(level Level) string
rotateOptions []rotateLogs.Option
levelPartition map[Level]func() LevelEnablerFunc
cores []Core
mode RunMode
logDir string
rotateLogDir string
}
func Logger() *MLogger {
return &MLogger{logger}
func (slf *Log) Debugf(format string, args ...interface{}) {
slf.sugar.Debugf(format, args...)
}
func Info(msg string, fields ...zap.Field) {
logger.Info(msg, fields...)
func (slf *Log) Infof(format string, args ...interface{}) {
slf.sugar.Infof(format, args...)
}
func Warn(msg string, fields ...zap.Field) {
logger.Warn(msg, fields...)
func (slf *Log) Warnf(format string, args ...interface{}) {
slf.sugar.Warnf(format, args...)
}
func Debug(msg string, fields ...zap.Field) {
logger.Debug(msg, fields...)
func (slf *Log) Errorf(format string, args ...interface{}) {
slf.sugar.Errorf(format, args...)
}
func Error(msg string, fields ...zap.Field) {
logger.Error(msg, fields...)
fmt.Println(string(debug.Stack()))
func (slf *Log) Fatalf(format string, args ...interface{}) {
slf.sugar.Fatalf(format, args...)
}
func ErrorHideStack(msg string, fields ...zap.Field) {
logger.Error(msg, fields...)
func (slf *Log) Printf(format string, args ...interface{}) {
slf.sugar.Infof(format, args...)
}
// ErrorWithStack 通过额外的堆栈信息打印错误日志
func ErrorWithStack(msg, stack string, fields ...zap.Field) {
logger.Error(msg, fields...)
var stackMerge string
if len(stack) > 0 {
stackMerge = stack
}
stackMerge += string(debug.Stack())
fmt.Println(stackMerge)
// Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func (slf *Log) Debug(msg string, fields ...Field) {
slf.zap.Debug(msg, fields...)
}
// SetProd 设置生产环境模式
func SetProd(isProd bool) {
if prod == isProd {
return
}
prod = isProd
if logger != nil {
_ = logger.Sync()
}
logger = newLogger()
// Info 在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func (slf *Log) Info(msg string, fields ...Field) {
slf.zap.Info(msg, fields...)
}
// SetLogDir 设置日志输出目录
func SetLogDir(dir string) {
logPath = dir
if logger != nil {
_ = logger.Sync()
}
logger = newLogger()
// Warn 在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func (slf *Log) Warn(msg string, fields ...Field) {
slf.zap.Warn(msg, fields...)
}
// SetWriteFileWithDev 设置开发环境下写入文件
func SetWriteFileWithDev(isWrite bool) {
if isWrite == logDevWrite {
return
}
logDevWrite = isWrite
if logger != nil {
_ = logger.Sync()
}
logger = newLogger()
// Error 在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func (slf *Log) Error(msg string, fields ...Field) {
slf.zap.Error(msg, fields...)
}
// SetLogRotate 设置日志切割时间
func SetLogRotate(t int) {
logTime = t
if logger != nil {
_ = logger.Sync()
// DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 如果记录器处于开发模式,它就会出现 panicDPanic 的意思是“development panic”。这对于捕获可恢复但不应该发生的错误很有用
func (slf *Log) DPanic(msg string, fields ...Field) {
slf.zap.DPanic(msg, fields...)
}
logger = newLogger()
// Panic 在 PanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 即使禁用了 PanicLevel 的日志记录,记录器也会出现 panic
func (slf *Log) Panic(msg string, fields ...Field) {
slf.zap.Panic(msg, fields...)
}
// Fatal 在 FatalLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 然后记录器调用 os.Exit(1),即使 FatalLevel 的日志记录被禁用
func (slf *Log) Fatal(msg string, fields ...Field) {
slf.zap.Fatal(msg, fields...)
}

79
utils/log/logger.go Normal file
View File

@ -0,0 +1,79 @@
package log
import (
"github.com/panjf2000/ants/v2"
"github.com/panjf2000/gnet/pkg/logging"
)
var logger Logger = NewLog()
// Logger 适用于 Minotaur 的日志接口
type Logger interface {
ants.Logger
logging.Logger
// Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
Debug(msg string, fields ...Field)
// Info 在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
Info(msg string, fields ...Field)
// Warn 在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
Warn(msg string, fields ...Field)
// Error 在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
Error(msg string, fields ...Field)
// DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 如果记录器处于开发模式,它就会出现 panicDPanic 的意思是“development panic”。这对于捕获可恢复但不应该发生的错误很有用
DPanic(msg string, fields ...Field)
// Panic 在 PanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 即使禁用了 PanicLevel 的日志记录,记录器也会出现 panic
Panic(msg string, fields ...Field)
// Fatal 在 FatalLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 然后记录器调用 os.Exit(1),即使 FatalLevel 的日志记录被禁用
Fatal(msg string, fields ...Field)
}
// Debug 在 DebugLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Debug(msg string, fields ...Field) {
logger.Debug(msg, fields...)
}
// Info 在 InfoLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Info(msg string, fields ...Field) {
logger.Info(msg, fields...)
}
// Warn 在 WarnLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Warn(msg string, fields ...Field) {
logger.Warn(msg, fields...)
}
// Error 在 ErrorLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
func Error(msg string, fields ...Field) {
logger.Error(msg, fields...)
}
// DPanic 在 DPanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 如果记录器处于开发模式,它就会出现 panicDPanic 的意思是“development panic”。这对于捕获可恢复但不应该发生的错误很有用
func DPanic(msg string, fields ...Field) {
logger.DPanic(msg, fields...)
}
// Panic 在 PanicLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 即使禁用了 PanicLevel 的日志记录,记录器也会出现 panic
func Panic(msg string, fields ...Field) {
logger.Panic(msg, fields...)
}
// Fatal 在 FatalLevel 记录一条消息。该消息包括在日志站点传递的任何字段以及记录器上累积的任何字段
// - 然后记录器调用 os.Exit(1),即使 FatalLevel 的日志记录被禁用
func Fatal(msg string, fields ...Field) {
logger.Fatal(msg, fields...)
}
// SetLogger 设置日志记录器
func SetLogger(log Logger) {
logger = log
}
// GetLogger 获取日志记录器
func GetLogger() Logger {
return logger
}

52
utils/log/options.go Normal file
View File

@ -0,0 +1,52 @@
package log
import (
rotateLogs "github.com/lestrrat-go/file-rotatelogs"
)
type Option func(log *Log)
// WithRunMode 设置运行模式
// - 默认的运行模式为: RunModeDev
// - 当 handle 不为空时,将会调用 handle(),并将返回值添加到日志记录器中,同时将会抑制默认的日志记录器
func WithRunMode(mode RunMode, handle func() Core) Option {
return func(log *Log) {
log.mode = mode
if handle != nil {
log.cores = append(log.cores, handle())
}
}
}
// WithFilename 设置日志文件名
// - 默认的日志文件名为: {level}.log
func WithFilename(filename func(level Level) string) Option {
return func(log *Log) {
log.filename = filename
}
}
// WithRotateFilename 设置日志分割文件名
// - 默认的日志分割文件名为: {level}.%Y%m%d.log
func WithRotateFilename(filename func(level Level) string) Option {
return func(log *Log) {
log.rotateFilename = filename
}
}
// WithRotateOption 设置日志分割选项
// - 默认的日志分割选项为: WithMaxAge(7天), WithRotationTime(1天)
func WithRotateOption(options ...rotateLogs.Option) Option {
return func(log *Log) {
log.rotateOptions = options
}
}
// WithLogDir 设置日志文件夹
// - 默认情况下不会设置日志文件夹,日志将不会被文件存储
func WithLogDir(logDir, rotateLogDir string) Option {
return func(log *Log) {
log.logDir = logDir
log.rotateLogDir = rotateLogDir
}
}

16
utils/log/run_mode.go Normal file
View File

@ -0,0 +1,16 @@
package log
const (
// RunModeDev 开发模式是默认的运行模式,同时也是最基础的运行模式
// - 开发模式下,将会输出所有级别的日志到控制台
// - 默认不再输出日志到文件
RunModeDev RunMode = iota
// RunModeTest 测试模式是一种特殊的运行模式,用于测试
// - 测试模式下,将会输出所有级别的日志到控制台和文件
RunModeTest
// RunModeProd 生产模式是一种特殊的运行模式,用于生产
// - 生产模式下,将会输出 InfoLevel 及以上级别的日志到控制台和文件
RunModeProd
)
type RunMode uint8

View File

@ -8,6 +8,7 @@ import (
const (
DefaultTolerance = 0.0001 // 默认误差范围
Zero = 0 // 零
)
// GetDefaultTolerance 获取默认误差范围

View File

@ -2,6 +2,18 @@ package str
const (
None = "" // 空字符串
Dunno = "?" // 未知
CenterDot = "·" // 中点
Dot = "." // 点
Slash = "/" // 斜杠
)
var (
NoneBytes = []byte("") // 空字符串
DunnoBytes = []byte("?") // 未知
CenterDotBytes = []byte("·") // 中点
DotBytes = []byte(".") // 点
SlashBytes = []byte("/") // 斜杠
)
// FirstUpper 首字母大写

View File

@ -17,12 +17,12 @@ func HideSensitivity(str string) (result string) {
if len(res[0]) < 3 {
resString := "***"
result = resString + "@" + res[1]
} else {
return result
}
resRs := []rune(str)
res2 := string(resRs[0:3])
resString := res2 + "***"
result = resString + "@" + res[1]
}
return result
} else {
reg := `^1[0-9]\d{9}$`
@ -31,8 +31,8 @@ func HideSensitivity(str string) (result string) {
if mobileMatch {
rs := []rune(str)
result = string(rs[0:5]) + "****" + string(rs[7:11])
} else {
return
}
nameRune := []rune(str)
lens := len(nameRune)
@ -47,7 +47,6 @@ func HideSensitivity(str string) (result string) {
} else if lens > 4 {
result = string(nameRune[:2]) + "***" + string(nameRune[lens-2:lens])
}
}
return
}
}

53
utils/super/stack.go Normal file
View File

@ -0,0 +1,53 @@
package super
import (
"runtime/debug"
)
// NewStackGo 返回一个用于获取上一个协程调用的堆栈信息的收集器
func NewStackGo() *StackGo {
return new(StackGo)
}
// StackGo 用于获取上一个协程调用的堆栈信息
// - 应当最先运行 Wait 函数,然后在其他协程中调用 Stack 函数或者 GiveUp 函数
// - 适用于跨协程同步通讯,例如单线程的消息处理统计耗时打印堆栈信息
type StackGo struct {
stack chan *struct{} // 消息堆栈
collect chan []byte // 消息堆栈收集
}
// Wait 等待收集消息堆栈
// - 在调用 Wait 函数后,当前协程将会被挂起,直到调用 Stack 或 GiveUp 函数
func (slf *StackGo) Wait() {
slf.stack = make(chan *struct{}, 0)
if s := <-slf.stack; s != nil {
slf.collect <- debug.Stack()
}
close(slf.stack)
slf.stack = nil
}
// Stack 获取消息堆栈
// - 在调用 Wait 函数后调用该函数,将会返回上一个协程的堆栈信息
// - 在调用 GiveUp 函数后调用该函数,将会 panic
func (slf *StackGo) Stack() []byte {
if slf.stack == nil {
return nil
}
slf.collect = make(chan []byte, 1)
slf.stack <- &struct{}{}
stack := <-slf.collect
close(slf.collect)
slf.collect = nil
return stack
}
// GiveUp 放弃收集消息堆栈
// - 在调用 Wait 函数后调用该函数,将会放弃收集消息堆栈并且释放资源
// - 在调用 GiveUp 函数后调用 Stack 函数,将会 panic
func (slf *StackGo) GiveUp() {
if slf.stack != nil {
slf.stack <- nil
}
}

View File

@ -74,14 +74,6 @@ func (slf *Map[Key, Value]) GetExist(key Key) (Value, bool) {
return value, exist
}
func (slf *Map[Key, Value]) Length() int {
if !slf.atom {
slf.lock.RLock()
defer slf.lock.RUnlock()
}
return len(slf.data)
}
func (slf *Map[Key, Value]) Delete(key Key) {
if !slf.atom {
slf.lock.Lock()

View File

@ -87,12 +87,6 @@ func (slf *MapSegment[Key, Value]) GetExist(key Key) (value Value, exist bool) {
return slf.segments[s].GetExist(key)
}
func (slf *MapSegment[Key, Value]) Length() int {
slf.lock.RLock()
defer slf.lock.RUnlock()
return len(slf.cache)
}
func (slf *MapSegment[Key, Value]) Delete(key Key) {
slf.lock.Lock()
s, exist := slf.cache[key]