mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-06 15:46:37 +08:00
chore(release): bump version to v1.2.0
### 🛠 修复 - 修复了异步任务列表在成功状态下允许重试、失败状态下无重试按钮的问题。 - 修复了启用推送事件时,在无可用推送渠道配置下仍尝试触发并阻塞的问题。 - 修复了 WebDAV 存储驱动在 Put/Get/Delete 等操作中由于丢弃 context.Context 导致 HTTP 链路追踪断裂(生成无源 Root Span)的问题。 - 修复了结构化日志在没有 active span 时仍强制打印全零 traceID/spanID 产生的日志冗余噪音。 ### ⚡️ 优化与改进 - 实现了全新的系统通知推送机制,支持 Telegram Bot、Lark 机器人及自定义 Webhook 等多种推送渠道。 - 优化了路由结构设计,按照 V1 分类与业务模块实现扁平化的路由解耦。 - 引入了全局 OpenTelemetry 链路追踪(Tracer)框架,集成 Gin, GORM 与 Redis 自动化耗时度量,并补充了统一的全局错误处理中间件。 - 优化了采样器命名,将 ParentBasedErrorAwareSampler 重命名为更契合其真实机制的 ParentBasedRatioSampler。 - 重构并统一了项目架构为基于 Feature 的功能模块化结构,将 internal/util/ 拆分得更加纯净,优化了验证码等公共库提取(pkg/cap)。 - 实现了推送事件与自定义通道的 Redis 缓存机制,极大降低了推送触发时的高频 DB 查询压力。 ### 💄 其他/体验 - 优化了前端界面布局,同步系统菜单与侧边栏配置显示。 - 优化了前端自定义通道表格的布局与样式,使其与事件管理 Tab 页面保持一致。 - 更新了项目开发技能手册(Skills),包括新增接口路由规范(new-api)和异步任务开发指南(new-async-task)。
This commit is contained in:
+39
-14
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -14,40 +15,62 @@ import (
|
|||||||
"github.com/studio-b12/gowebdav"
|
"github.com/studio-b12/gowebdav"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type contextTransport struct {
|
||||||
|
ctx context.Context
|
||||||
|
parent http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *contextTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
return t.parent.RoundTrip(req.WithContext(t.ctx))
|
||||||
|
}
|
||||||
|
|
||||||
type webDAVBackend struct {
|
type webDAVBackend struct {
|
||||||
client *gowebdav.Client
|
endpoint string
|
||||||
|
username string
|
||||||
|
password string
|
||||||
basePath string
|
basePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWebDAVBackend(cfg WebDAVConfig) (*webDAVBackend, error) {
|
func newWebDAVBackend(cfg WebDAVConfig) (*webDAVBackend, error) {
|
||||||
client := gowebdav.NewClient(strings.TrimRight(cfg.Endpoint, "/"), cfg.Username, cfg.Password)
|
|
||||||
client.SetTransport(httppool.DefaultTransport())
|
|
||||||
return &webDAVBackend{
|
return &webDAVBackend{
|
||||||
client: client,
|
endpoint: strings.TrimRight(cfg.Endpoint, "/"),
|
||||||
|
username: cfg.Username,
|
||||||
|
password: cfg.Password,
|
||||||
basePath: strings.Trim(cfg.BasePath, "/"),
|
basePath: strings.Trim(cfg.BasePath, "/"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *webDAVBackend) Put(_ context.Context, key string, body io.Reader, size int64, _ string) (PutResult, error) {
|
func (b *webDAVBackend) newClient(ctx context.Context) *gowebdav.Client {
|
||||||
|
client := gowebdav.NewClient(b.endpoint, b.username, b.password)
|
||||||
|
client.SetTransport(&contextTransport{
|
||||||
|
ctx: ctx,
|
||||||
|
parent: httppool.DefaultTransport(),
|
||||||
|
})
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *webDAVBackend) Put(ctx context.Context, key string, body io.Reader, size int64, _ string) (PutResult, error) {
|
||||||
key = b.key(key)
|
key = b.key(key)
|
||||||
|
client := b.newClient(ctx)
|
||||||
if dir := path.Dir(key); dir != "." && dir != "/" {
|
if dir := path.Dir(key); dir != "." && dir != "/" {
|
||||||
if err := b.client.MkdirAll(dir, storageDirPerm); err != nil {
|
if err := client.MkdirAll(dir, storageDirPerm); err != nil {
|
||||||
return PutResult{}, fmt.Errorf("create WebDAV directory: %w", err)
|
return PutResult{}, fmt.Errorf("create WebDAV directory: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := b.client.WriteStreamWithLength(key, body, size, storageFilePerm); err != nil {
|
if err := client.WriteStreamWithLength(key, body, size, storageFilePerm); err != nil {
|
||||||
return PutResult{}, fmt.Errorf("put WebDAV object: %w", err)
|
return PutResult{}, fmt.Errorf("put WebDAV object: %w", err)
|
||||||
}
|
}
|
||||||
return PutResult{Key: key}, nil
|
return PutResult{Key: key}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *webDAVBackend) Get(_ context.Context, key string) (*Object, error) {
|
func (b *webDAVBackend) Get(ctx context.Context, key string) (*Object, error) {
|
||||||
key = b.key(key)
|
key = b.key(key)
|
||||||
info, err := b.client.Stat(key)
|
client := b.newClient(ctx)
|
||||||
|
info, err := client.Stat(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("stat WebDAV object: %w", err)
|
return nil, fmt.Errorf("stat WebDAV object: %w", err)
|
||||||
}
|
}
|
||||||
body, err := b.client.ReadStream(key)
|
body, err := client.ReadStream(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("get WebDAV object: %w", err)
|
return nil, fmt.Errorf("get WebDAV object: %w", err)
|
||||||
}
|
}
|
||||||
@@ -58,15 +81,17 @@ func (b *webDAVBackend) Get(_ context.Context, key string) (*Object, error) {
|
|||||||
return &Object{Body: body, ContentLength: info.Size(), ContentType: contentType}, nil
|
return &Object{Body: body, ContentLength: info.Size(), ContentType: contentType}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *webDAVBackend) Delete(_ context.Context, key string) error {
|
func (b *webDAVBackend) Delete(ctx context.Context, key string) error {
|
||||||
if err := b.client.Remove(b.key(key)); err != nil {
|
client := b.newClient(ctx)
|
||||||
|
if err := client.Remove(b.key(key)); err != nil {
|
||||||
return fmt.Errorf("delete WebDAV object: %w", err)
|
return fmt.Errorf("delete WebDAV object: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *webDAVBackend) Test(_ context.Context) error {
|
func (b *webDAVBackend) Test(ctx context.Context) error {
|
||||||
if err := b.client.Connect(); err != nil {
|
client := b.newClient(ctx)
|
||||||
|
if err := client.Connect(); err != nil {
|
||||||
return fmt.Errorf("connect WebDAV: %w", err)
|
return fmt.Errorf("connect WebDAV: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ func getLogLevelForConfig(cfg Config) zapcore.Level {
|
|||||||
func getTraceIDFields(ctx context.Context) []zap.Field {
|
func getTraceIDFields(ctx context.Context) []zap.Field {
|
||||||
span := trace.SpanFromContext(ctx)
|
span := trace.SpanFromContext(ctx)
|
||||||
spanContext := span.SpanContext()
|
spanContext := span.SpanContext()
|
||||||
|
if !spanContext.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return []zap.Field{
|
return []zap.Field{
|
||||||
zap.String("traceID", spanContext.TraceID().String()),
|
zap.String("traceID", spanContext.TraceID().String()),
|
||||||
zap.String("spanID", spanContext.SpanID().String()),
|
zap.String("spanID", spanContext.SpanID().String()),
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParentBasedErrorAwareSampler 创建父级感知的概率采样器
|
// ParentBasedRatioSampler 创建父级感知的概率采样器
|
||||||
// - 如果父 Span 已采样,则子 Span 也采样
|
// - 如果父 Span 已采样,则子 Span 也采样
|
||||||
// - 如果父 Span 未采样,则子 Span 也不采样
|
// - 如果父 Span 未采样,则子 Span 也不采样
|
||||||
// - 如果是根 Span,按 samplingRate 概率采样
|
// - 如果是根 Span,按 samplingRate 概率采样
|
||||||
func ParentBasedErrorAwareSampler(samplingRate float64) sdktrace.Sampler {
|
func ParentBasedRatioSampler(samplingRate float64) sdktrace.Sampler {
|
||||||
return sdktrace.ParentBased(
|
return sdktrace.ParentBased(
|
||||||
sdktrace.TraceIDRatioBased(samplingRate),
|
sdktrace.TraceIDRatioBased(samplingRate),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ func newTracerProvider(cfg Config) (*sdktrace.TracerProvider, error) {
|
|||||||
tracerProvider := sdktrace.NewTracerProvider(
|
tracerProvider := sdktrace.NewTracerProvider(
|
||||||
sdktrace.WithBatcher(traceExporter),
|
sdktrace.WithBatcher(traceExporter),
|
||||||
sdktrace.WithResource(r),
|
sdktrace.WithResource(r),
|
||||||
sdktrace.WithSampler(ParentBasedErrorAwareSampler(cfg.SamplingRate)),
|
sdktrace.WithSampler(ParentBasedRatioSampler(cfg.SamplingRate)),
|
||||||
)
|
)
|
||||||
return tracerProvider, nil
|
return tracerProvider, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user