Files
kim.dev.6789 e50142a3b9 复制项目
2026-01-14 22:16:44 +08:00

234 lines
7.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package third
import (
"context"
"crypto/rand"
"net/url"
"strings"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/authverify"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/servererrs"
relationtb "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/constant"
"git.imall.cloud/openim/protocol/third"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil"
)
func genLogID() string {
const dataLen = 10
data := make([]byte, dataLen)
rand.Read(data)
chars := []byte("0123456789")
for i := 0; i < len(data); i++ {
if i == 0 {
data[i] = chars[1:][data[i]%9]
} else {
data[i] = chars[data[i]%10]
}
}
return string(data)
}
// extractKeyFromLogURL 从日志URL中提取S3的key
// URL格式: https://s3.jizhying.com/images/openim/data/hash/{hash}?...
// 或: https://chatall.oss-ap-southeast-1.aliyuncs.com/openim%2Fdata%2Fhash%2F{hash}
// key格式: openim/data/hash/{hash}不包含bucket名称
// bucket名称在URL路径的第一段如images需要去掉
func extractKeyFromLogURL(logURL string, bucketName string) string {
if logURL == "" {
return ""
}
parsedURL, err := url.Parse(logURL)
if err != nil {
return ""
}
// 获取路径部分,去掉开头的'/'
path := strings.TrimPrefix(parsedURL.Path, "/")
if path == "" {
return ""
}
// 如果配置了bucket名称且路径以bucket名称开头则去掉bucket名称前缀
if bucketName != "" && strings.HasPrefix(path, bucketName+"/") {
path = strings.TrimPrefix(path, bucketName+"/")
} else {
// 如果没有匹配到bucket名称尝试去掉路径的第一段可能是bucket名称
// 这种情况下假设路径的第一段是bucket名称
parts := strings.SplitN(path, "/", 2)
if len(parts) > 1 {
path = parts[1]
}
}
// URL.Path已经是解码后的路径所以直接返回即可
return path
}
func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq) (*third.UploadLogsResp, error) {
var dbLogs []*relationtb.Log
userID := mcontext.GetOpUserID(ctx)
platform := constant.PlatformID2Name[int(req.Platform)]
for _, fileURL := range req.FileURLs {
log := relationtb.Log{
Platform: platform,
UserID: userID,
CreateTime: time.Now(),
Url: fileURL.URL,
FileName: fileURL.Filename,
AppFramework: req.AppFramework,
Version: req.Version,
Ex: req.Ex,
}
for i := 0; i < 20; i++ {
id := genLogID()
logs, err := t.thirdDatabase.GetLogs(ctx, []string{id}, "")
if err != nil {
return nil, err
}
if len(logs) == 0 {
log.LogID = id
break
}
}
if log.LogID == "" {
return nil, servererrs.ErrData.WrapMsg("Log id gen error")
}
dbLogs = append(dbLogs, &log)
}
err := t.thirdDatabase.UploadLogs(ctx, dbLogs)
if err != nil {
return nil, err
}
return &third.UploadLogsResp{}, nil
}
func (t *thirdServer) DeleteLogs(ctx context.Context, req *third.DeleteLogsReq) (*third.DeleteLogsResp, error) {
if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err
}
userID := ""
logs, err := t.thirdDatabase.GetLogs(ctx, req.LogIDs, userID)
if err != nil {
return nil, err
}
var logIDs []string
for _, log := range logs {
logIDs = append(logIDs, log.LogID)
}
if ids := datautil.Single(req.LogIDs, logIDs); len(ids) > 0 {
return nil, errs.ErrRecordNotFound.WrapMsg("logIDs not found", "logIDs", ids)
}
// 在删除日志记录前先删除对应的S3文件
engine := t.config.RpcConfig.Object.Enable
if engine != "" && t.s3 != nil {
// 获取bucket名称从minio配置中
bucketName := ""
if engine == "minio" {
bucketName = t.config.MinioConfig.Bucket
}
for _, logRecord := range logs {
if logRecord.Url == "" {
continue
}
// 从URL中提取S3的key不包含bucket名称
key := extractKeyFromLogURL(logRecord.Url, bucketName)
if key == "" {
log.ZDebug(ctx, "DeleteLogs: cannot extract key from URL, skipping S3 deletion", "logID", logRecord.LogID, "url", logRecord.Url)
continue
}
// 直接使用key删除S3文件
log.ZInfo(ctx, "DeleteLogs: attempting to delete S3 file", "logID", logRecord.LogID, "url", logRecord.Url, "key", key, "bucket", bucketName, "engine", engine)
if err := t.s3.DeleteObject(ctx, key); err != nil {
// S3文件删除失败返回错误不删除数据库记录
log.ZError(ctx, "DeleteLogs: S3 file delete failed", err, "logID", logRecord.LogID, "url", logRecord.Url, "key", key, "bucket", bucketName, "engine", engine)
return nil, errs.WrapMsg(err, "failed to delete S3 file for log", "logID", logRecord.LogID, "url", logRecord.Url, "key", key)
}
log.ZInfo(ctx, "DeleteLogs: S3 file delete command executed successfully", "logID", logRecord.LogID, "url", logRecord.Url, "key", key, "bucket", bucketName, "engine", engine)
}
}
err = t.thirdDatabase.DeleteLogs(ctx, req.LogIDs, userID)
if err != nil {
return nil, err
}
return &third.DeleteLogsResp{}, nil
}
func dbToPbLogInfos(logs []*relationtb.Log) []*third.LogInfo {
db2pbForLogInfo := func(log *relationtb.Log) *third.LogInfo {
return &third.LogInfo{
Filename: log.FileName,
UserID: log.UserID,
Platform: log.Platform,
Url: log.Url,
CreateTime: log.CreateTime.UnixMilli(),
LogID: log.LogID,
SystemType: log.SystemType,
Version: log.Version,
Ex: log.Ex,
}
}
return datautil.Slice(logs, db2pbForLogInfo)
}
func (t *thirdServer) SearchLogs(ctx context.Context, req *third.SearchLogsReq) (*third.SearchLogsResp, error) {
if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err
}
var (
resp third.SearchLogsResp
userIDs []string
)
if req.StartTime > req.EndTime {
return nil, errs.ErrArgs.WrapMsg("startTime>endTime")
}
if req.StartTime == 0 && req.EndTime == 0 {
t := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC)
timestampMills := t.UnixNano() / int64(time.Millisecond)
req.StartTime = timestampMills
req.EndTime = time.Now().UnixNano() / int64(time.Millisecond)
}
total, logs, err := t.thirdDatabase.SearchLogs(ctx, req.Keyword, time.UnixMilli(req.StartTime), time.UnixMilli(req.EndTime), req.Pagination)
if err != nil {
return nil, err
}
pbLogs := dbToPbLogInfos(logs)
for _, log := range logs {
userIDs = append(userIDs, log.UserID)
}
userMap, err := t.userClient.GetUsersInfoMap(ctx, userIDs)
if err != nil {
return nil, err
}
for _, pbLog := range pbLogs {
if user, ok := userMap[pbLog.UserID]; ok {
pbLog.Nickname = user.Nickname
}
}
resp.LogsInfos = pbLogs
resp.Total = uint32(total)
return &resp, nil
}