Files
chat-deploy/internal/rpc/admin/application.go
kim.dev.6789 b7f8db7d08 复制项目
2026-01-14 22:35:45 +08:00

290 lines
9.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.

package admin
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
admindb "git.imall.cloud/openim/chat/pkg/common/db/table/admin"
"git.imall.cloud/openim/chat/pkg/common/mctx"
"git.imall.cloud/openim/chat/pkg/protocol/admin"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil"
"github.com/redis/go-redis/v9"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
func IsNotFound(err error) bool {
switch errs.Unwrap(err) {
case redis.Nil, mongo.ErrNoDocuments:
return true
default:
return false
}
}
func (o *adminServer) db2pbApplication(val *admindb.Application) *admin.ApplicationVersion {
return &admin.ApplicationVersion{
Id: val.ID.Hex(),
Platform: val.Platform,
Version: val.Version,
Url: val.Url,
Text: val.Text,
Force: val.Force,
Latest: val.Latest,
Hot: val.Hot,
CreateTime: val.CreateTime.UnixMilli(),
}
}
// LatestVersionAPIResponse 外部 API 响应结构
type LatestVersionAPIResponse struct {
Success bool `json:"success"`
ErrorCode string `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
ShowType int `json:"showType"`
Data struct {
APKPath string `json:"apk_path"`
APKSize int64 `json:"apk_size"`
AppLogo string `json:"app_logo"`
AppName string `json:"app_name"`
CreatedAt string `json:"created_at"`
Success bool `json:"success"`
Version string `json:"version"`
} `json:"data"`
}
func (o *adminServer) LatestApplicationVersion(ctx context.Context, req *admin.LatestApplicationVersionReq) (*admin.LatestApplicationVersionResp, error) {
// 从系统配置读取 build_app_id
buildAppIDConfig, err := o.ChatDatabase.GetSystemConfig(ctx, "build_app_id")
if err != nil {
log.ZWarn(ctx, "Failed to get build_app_id from system config, falling back to database", err)
// 如果获取配置失败,回退到从数据库读取
res, err := o.Database.LatestVersion(ctx, req.Platform)
if err == nil {
return &admin.LatestApplicationVersionResp{Version: o.db2pbApplication(res)}, nil
} else if IsNotFound(err) {
return &admin.LatestApplicationVersionResp{}, nil
} else {
return nil, err
}
}
buildAppID := buildAppIDConfig.Value
if buildAppID == "" {
log.ZWarn(ctx, "build_app_id is empty in system config, falling back to database", nil)
// 如果配置值为空,回退到从数据库读取
res, err := o.Database.LatestVersion(ctx, req.Platform)
if err == nil {
return &admin.LatestApplicationVersionResp{Version: o.db2pbApplication(res)}, nil
} else if IsNotFound(err) {
return &admin.LatestApplicationVersionResp{}, nil
} else {
return nil, err
}
}
// 调用外部 API
apiURL := "https://down.imall.cloud/api/download/latest"
requestBody := map[string]string{
"app_id": buildAppID,
}
jsonData, err := json.Marshal(requestBody)
if err != nil {
log.ZError(ctx, "Failed to marshal request body", err, "buildAppID", buildAppID)
return nil, errs.ErrInternalServer.WrapMsg("failed to prepare request")
}
httpReq, err := http.NewRequestWithContext(ctx, "POST", apiURL, bytes.NewBuffer(jsonData))
if err != nil {
log.ZError(ctx, "Failed to create HTTP request", err, "url", apiURL)
return nil, errs.ErrInternalServer.WrapMsg("failed to create request")
}
httpReq.Header.Set("Content-Type", "application/json")
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Do(httpReq)
if err != nil {
log.ZError(ctx, "Failed to call external API", err, "url", apiURL)
// API 调用失败,回退到从数据库读取
res, err := o.Database.LatestVersion(ctx, req.Platform)
if err == nil {
return &admin.LatestApplicationVersionResp{Version: o.db2pbApplication(res)}, nil
} else if IsNotFound(err) {
return &admin.LatestApplicationVersionResp{}, nil
} else {
return nil, err
}
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
log.ZError(ctx, "Failed to read response body", err)
// 读取响应失败,回退到从数据库读取
res, err := o.Database.LatestVersion(ctx, req.Platform)
if err == nil {
return &admin.LatestApplicationVersionResp{Version: o.db2pbApplication(res)}, nil
} else if IsNotFound(err) {
return &admin.LatestApplicationVersionResp{}, nil
} else {
return nil, err
}
}
if resp.StatusCode != http.StatusOK {
log.ZWarn(ctx, "External API returned non-200 status", nil, "statusCode", resp.StatusCode, "body", string(body))
// API 返回非 200回退到从数据库读取
res, err := o.Database.LatestVersion(ctx, req.Platform)
if err == nil {
return &admin.LatestApplicationVersionResp{Version: o.db2pbApplication(res)}, nil
} else if IsNotFound(err) {
return &admin.LatestApplicationVersionResp{}, nil
} else {
return nil, err
}
}
var apiResp LatestVersionAPIResponse
if err := json.Unmarshal(body, &apiResp); err != nil {
log.ZError(ctx, "Failed to unmarshal API response", err, "body", string(body))
// 解析响应失败,回退到从数据库读取
res, err := o.Database.LatestVersion(ctx, req.Platform)
if err == nil {
return &admin.LatestApplicationVersionResp{Version: o.db2pbApplication(res)}, nil
} else if IsNotFound(err) {
return &admin.LatestApplicationVersionResp{}, nil
} else {
return nil, err
}
}
if !apiResp.Success {
log.ZWarn(ctx, "External API returned success=false", nil, "errorCode", apiResp.ErrorCode, "errorMessage", apiResp.ErrorMessage)
// API 返回失败,回退到从数据库读取
res, err := o.Database.LatestVersion(ctx, req.Platform)
if err == nil {
return &admin.LatestApplicationVersionResp{Version: o.db2pbApplication(res)}, nil
} else if IsNotFound(err) {
return &admin.LatestApplicationVersionResp{}, nil
} else {
return nil, err
}
}
// 解析创建时间
var createTime time.Time
if apiResp.Data.CreatedAt != "" {
createTime, err = time.Parse(time.RFC3339, apiResp.Data.CreatedAt)
if err != nil {
log.ZWarn(ctx, "Failed to parse created_at", err, "createdAt", apiResp.Data.CreatedAt)
createTime = time.Now()
}
} else {
createTime = time.Now()
}
// 转换为 ApplicationVersion 格式
version := &admin.ApplicationVersion{
Id: "", // 外部 API 没有 ID
Platform: req.Platform,
Version: apiResp.Data.Version,
Url: apiResp.Data.APKPath,
Text: fmt.Sprintf("应用名称: %s", apiResp.Data.AppName),
Force: false, // 外部 API 没有提供此字段
Latest: true, // 从 latest 接口获取的肯定是最新版本
Hot: false, // 外部 API 没有提供此字段
CreateTime: createTime.UnixMilli(),
}
return &admin.LatestApplicationVersionResp{Version: version}, nil
}
func (o *adminServer) AddApplicationVersion(ctx context.Context, req *admin.AddApplicationVersionReq) (*admin.AddApplicationVersionResp, error) {
if _, err := mctx.CheckAdmin(ctx); err != nil {
return nil, err
}
val := &admindb.Application{
ID: primitive.NewObjectID(),
Platform: req.Platform,
Version: req.Version,
Url: req.Url,
Text: req.Text,
Force: req.Force,
Latest: req.Latest,
Hot: req.Hot,
CreateTime: time.Now(),
}
if err := o.Database.AddVersion(ctx, val); err != nil {
return nil, err
}
return &admin.AddApplicationVersionResp{}, nil
}
func (o *adminServer) UpdateApplicationVersion(ctx context.Context, req *admin.UpdateApplicationVersionReq) (*admin.UpdateApplicationVersionResp, error) {
if _, err := mctx.CheckAdmin(ctx); err != nil {
return nil, err
}
oid, err := primitive.ObjectIDFromHex(req.Id)
if err != nil {
return nil, errs.ErrArgs.WrapMsg("invalid id " + err.Error())
}
update := make(map[string]any)
putUpdate(update, "platform", req.Platform)
putUpdate(update, "version", req.Version)
putUpdate(update, "url", req.Url)
putUpdate(update, "text", req.Text)
putUpdate(update, "force", req.Force)
putUpdate(update, "latest", req.Latest)
putUpdate(update, "hot", req.Hot)
if err := o.Database.UpdateVersion(ctx, oid, update); err != nil {
return nil, err
}
return &admin.UpdateApplicationVersionResp{}, nil
}
func (o *adminServer) DeleteApplicationVersion(ctx context.Context, req *admin.DeleteApplicationVersionReq) (*admin.DeleteApplicationVersionResp, error) {
if _, err := mctx.CheckAdmin(ctx); err != nil {
return nil, err
}
ids := make([]primitive.ObjectID, 0, len(req.Id))
for _, id := range req.Id {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, errs.ErrArgs.WrapMsg("invalid id " + err.Error())
}
ids = append(ids, oid)
}
if err := o.Database.DeleteVersion(ctx, ids); err != nil {
return nil, err
}
return &admin.DeleteApplicationVersionResp{}, nil
}
func (o *adminServer) PageApplicationVersion(ctx context.Context, req *admin.PageApplicationVersionReq) (*admin.PageApplicationVersionResp, error) {
total, res, err := o.Database.PageVersion(ctx, req.Platform, req.Pagination)
if err != nil {
return nil, err
}
return &admin.PageApplicationVersionResp{
Total: total,
Versions: datautil.Slice(res, o.db2pbApplication),
}, nil
}
func putUpdate[T any](update map[string]any, name string, val interface{ GetValuePtr() *T }) {
ptrVal := val.GetValuePtr()
if ptrVal == nil {
return
}
update[name] = *ptrVal
}