|
@@ -45,18 +45,12 @@ func (l *CreateServiceLogic) CreateService(req *types.CreateServiceReq) (*types.
|
|
|
svcModel := l.svcCtx.ServiceModel.WithSession(session)
|
|
svcModel := l.svcCtx.ServiceModel.WithSession(session)
|
|
|
dirModel := l.svcCtx.DirectoryModel.WithSession(session)
|
|
dirModel := l.svcCtx.DirectoryModel.WithSession(session)
|
|
|
|
|
|
|
|
- // 从接入标识获取到秘钥
|
|
|
|
|
- apiSecret, err := l.svcCtx.ApplicationCredModel.FindByApiKey(l.ctx, req.Iden)
|
|
|
|
|
- fmt.Println("从接入标识获取到秘钥:", apiSecret)
|
|
|
|
|
- if err != nil {
|
|
|
|
|
- return fmt.Errorf("接入应用标识不存在: %w", err)
|
|
|
|
|
- }
|
|
|
|
|
// 1. 插服务
|
|
// 1. 插服务
|
|
|
svcData := &model.TService{
|
|
svcData := &model.TService{
|
|
|
Name: nullString(req.Name),
|
|
Name: nullString(req.Name),
|
|
|
Ip: nullString(req.Ip),
|
|
Ip: nullString(req.Ip),
|
|
|
Port: req.Port, // 端口 int 类型,int64 直传(0=未配置)
|
|
Port: req.Port, // 端口 int 类型,int64 直传(0=未配置)
|
|
|
- Iden: nullString(apiSecret),
|
|
|
|
|
|
|
+ Iden: nullString(req.Iden),
|
|
|
KeyIden: nullString(req.KeyIden),
|
|
KeyIden: nullString(req.KeyIden),
|
|
|
KeyAlgo: nullString(req.KeyAlgo),
|
|
KeyAlgo: nullString(req.KeyAlgo),
|
|
|
Status: nullString(req.Status),
|
|
Status: nullString(req.Status),
|
|
@@ -130,7 +124,8 @@ func (l *CreateServiceLogic) CreateService(req *types.CreateServiceReq) (*types.
|
|
|
}
|
|
}
|
|
|
} else if err := l.notifyFuseMount(created, dirList); err != nil {
|
|
} else if err := l.notifyFuseMount(created, dirList); err != nil {
|
|
|
l.Errorf("service created(id=%d), but fuse mount notify failed: %v", svcId, err)
|
|
l.Errorf("service created(id=%d), but fuse mount notify failed: %v", svcId, err)
|
|
|
- warnings = append(warnings, fmt.Sprintf("服务已创建,但通知外挂程序挂载失败: %v", err))
|
|
|
|
|
|
|
+ // err 内容即失败目录的逐条消息(带目录名,分号分隔),直接进警告,不再包前缀段
|
|
|
|
|
+ warnings = append(warnings, err.Error())
|
|
|
resp.MountStatus = "failed"
|
|
resp.MountStatus = "failed"
|
|
|
if uerr := l.svcCtx.ServiceModel.UpdateMountStatus(l.ctx, svcId, "failed"); uerr != nil {
|
|
if uerr := l.svcCtx.ServiceModel.UpdateMountStatus(l.ctx, svcId, "failed"); uerr != nil {
|
|
|
l.Errorf("update mount_status=failed failed(id=%d): %v", svcId, uerr)
|
|
l.Errorf("update mount_status=failed failed(id=%d): %v", svcId, uerr)
|
|
@@ -167,14 +162,15 @@ func (l *CreateServiceLogic) notifyFuseBrush(svc *model.TService, dirs []string)
|
|
|
return dispatchFuseBrush(l.ctx, l.svcCtx, svc, dirs)
|
|
return dispatchFuseBrush(l.ctx, l.svcCtx, svc, dirs)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// doFuseRequest 发送发往外挂程序的 HTTP 请求,统一携带逐服务的 X-TOKEN 鉴权头。
|
|
|
|
|
-// X-TOKEN 的值 = 服务表 t_service 的 iden(接入标识)字段;外挂程序拿它与
|
|
|
|
|
-// 自己环境变量 TOKEN 配的值恒定时间比对(见 FuseDemo 的 NewAuthInterceptor),
|
|
|
|
|
-// 因此每台外挂机的 TOKEN 要配成"接在该机上的服务"的 iden 值。
|
|
|
|
|
|
|
+// doFuseRequest 发送发往外挂程序的 HTTP 请求,统一携带 X-TOKEN 鉴权头。
|
|
|
|
|
+// 服务表 t_service 的 iden 存的是接入应用的 apiKey;外挂机 TOKEN 环境变量配的是
|
|
|
|
|
+// 密钥值 apiSecret。这里按 apiKey 查接入应用表(t_application_credentials)换算出
|
|
|
|
|
+// apiSecret 再作为 X-TOKEN 发送,外挂程序拿它与自己环境变量 TOKEN 恒定时间比对
|
|
|
|
|
+// (见 FuseDemo 的 NewAuthInterceptor)。
|
|
|
// client 按场景传:挂载/卸载/目录浏览用 fuseMountClient(10s),刷库用 fuseBrushClient(10min)。
|
|
// client 按场景传:挂载/卸载/目录浏览用 fuseMountClient(10s),刷库用 fuseBrushClient(10min)。
|
|
|
// 之所以不用 client.Post/client.Get 快捷方式:它们构造的请求没法逐个加 Header,
|
|
// 之所以不用 client.Post/client.Get 快捷方式:它们构造的请求没法逐个加 Header,
|
|
|
// 必须先 http.NewRequest 再 client.Do。
|
|
// 必须先 http.NewRequest 再 client.Do。
|
|
|
-func doFuseRequest(client *http.Client, iden, method, url string, body io.Reader, contentType string) (*http.Response, error) {
|
|
|
|
|
|
|
+func doFuseRequest(ctx context.Context, svcCtx *svc.ServiceContext, client *http.Client, iden, method, url string, body io.Reader, contentType string) (*http.Response, error) {
|
|
|
req, err := http.NewRequest(method, url, body)
|
|
req, err := http.NewRequest(method, url, body)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
@@ -182,7 +178,16 @@ func doFuseRequest(client *http.Client, iden, method, url string, body io.Reader
|
|
|
if contentType != "" {
|
|
if contentType != "" {
|
|
|
req.Header.Set("Content-Type", contentType)
|
|
req.Header.Set("Content-Type", contentType)
|
|
|
}
|
|
}
|
|
|
- req.Header.Set("X-TOKEN", iden)
|
|
|
|
|
|
|
+ // 按 iden(apiKey) 查接入应用表,取真正的密钥值 apiSecret 作为 X-TOKEN。
|
|
|
|
|
+ // iden 为空时(目录树接口的 iden 是可选项,新增弹窗未选接入标识就点"选择目录")跳过:
|
|
|
|
|
+ // 目标接口 /path/lists 在外挂侧本就免鉴权,不设头也能通。
|
|
|
|
|
+ if iden != "" {
|
|
|
|
|
+ apiSecret, err := svcCtx.ApplicationCredModel.FindByApiKey(ctx, iden)
|
|
|
|
|
+ if err != nil {
|
|
|
|
|
+ return nil, fmt.Errorf("接入标识(%s)对应的应用不存在,无法获取密钥: %w", iden, err)
|
|
|
|
|
+ }
|
|
|
|
|
+ req.Header.Set("X-TOKEN", apiSecret)
|
|
|
|
|
+ }
|
|
|
return client.Do(req)
|
|
return client.Do(req)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -232,7 +237,7 @@ func dispatchFuseBrush(ctx context.Context, svcCtx *svc.ServiceContext, svc *mod
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return fmt.Errorf("序列化刷库请求失败: %w", err)
|
|
return fmt.Errorf("序列化刷库请求失败: %w", err)
|
|
|
}
|
|
}
|
|
|
- resp, err := doFuseRequest(fuseBrushClient, svc.Iden.String, http.MethodPost, baseURL, bytes.NewReader(body), "application/json")
|
|
|
|
|
|
|
+ resp, err := doFuseRequest(ctx, svcCtx, fuseBrushClient, svc.Iden.String, http.MethodPost, baseURL, bytes.NewReader(body), "application/json")
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return fmt.Errorf("请求 %s 失败(外挂程序未启动?): %w", baseURL, err)
|
|
return fmt.Errorf("请求 %s 失败(外挂程序未启动?): %w", baseURL, err)
|
|
|
}
|
|
}
|
|
@@ -290,6 +295,9 @@ func (l *CreateServiceLogic) notifyFuseMount(svc *model.TService, dirs []string)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
baseURL := fmt.Sprintf("http://%s:%d/api/fuseMount", ip, port) // %d:port 是 int64,数字占位符
|
|
baseURL := fmt.Sprintf("http://%s:%d/api/fuseMount", ip, port) // %d:port 是 int64,数字占位符
|
|
|
|
|
+ // 逐目录尝试挂载,失败的目录各自记一条带目录名的消息后继续下一个,
|
|
|
|
|
+ // 全部尝试完才返回:多目录场景失败几个目录就聚合几条,成功目录不产生消息。
|
|
|
|
|
+ var msgs []string
|
|
|
for _, mount := range dirs {
|
|
for _, mount := range dirs {
|
|
|
dirLinuxPath := path.Clean(mount)
|
|
dirLinuxPath := path.Clean(mount)
|
|
|
cipherDir := dirLinuxPath + "-cipher"
|
|
cipherDir := dirLinuxPath + "-cipher"
|
|
@@ -300,30 +308,35 @@ func (l *CreateServiceLogic) notifyFuseMount(svc *model.TService, dirs []string)
|
|
|
}
|
|
}
|
|
|
body, err := json.Marshal(payload)
|
|
body, err := json.Marshal(payload)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return fmt.Errorf("序列化挂载请求失败: %w", err)
|
|
|
|
|
|
|
+ msgs = append(msgs, fmt.Sprintf("挂载目录 %s 失败: 序列化挂载请求失败: %v", dirLinuxPath, err))
|
|
|
|
|
+ continue
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 发起 POST(DoSimple 记录 type=2 接口日志,父 Span 指向本次入站请求)
|
|
// 发起 POST(DoSimple 记录 type=2 接口日志,父 Span 指向本次入站请求)
|
|
|
if _, callErr := audit.DoSimple(l.svcCtx.SpanRecorder, l.ctx, "FuseMount", baseURL, body,
|
|
if _, callErr := audit.DoSimple(l.svcCtx.SpanRecorder, l.ctx, "FuseMount", baseURL, body,
|
|
|
func() (map[string]interface{}, int64, error) {
|
|
func() (map[string]interface{}, int64, error) {
|
|
|
- // doFuseRequest 注入 X-TOKEN=svc.Iden(接入标识);client.Post 快捷方式带不了自定义头,
|
|
|
|
|
|
|
+ // doFuseRequest 注入 X-TOKEN(按 iden 换算的 apiSecret);client.Post 快捷方式带不了自定义头,
|
|
|
// 裸发会被外挂鉴权拦截器 401 拒绝
|
|
// 裸发会被外挂鉴权拦截器 401 拒绝
|
|
|
- resp, rerr := doFuseRequest(fuseMountClient, svc.Iden.String, http.MethodPost, baseURL, bytes.NewReader(body), "application/json")
|
|
|
|
|
|
|
+ resp, rerr := doFuseRequest(l.ctx, l.svcCtx, fuseMountClient, svc.Iden.String, http.MethodPost, baseURL, bytes.NewReader(body), "application/json")
|
|
|
if rerr != nil {
|
|
if rerr != nil {
|
|
|
- return nil, 0, fmt.Errorf("请求 %s 失败(外挂程序未启动?): %w", baseURL, rerr)
|
|
|
|
|
|
|
+ return nil, 0, fmt.Errorf("挂载目录 %s 失败(外挂程序未启动?): %w", dirLinuxPath, rerr)
|
|
|
}
|
|
}
|
|
|
// 读到 EOF 保证底层 TCP 连接池可以复用 Keep-Alive
|
|
// 读到 EOF 保证底层 TCP 连接池可以复用 Keep-Alive
|
|
|
raw, _ := io.ReadAll(resp.Body)
|
|
raw, _ := io.ReadAll(resp.Body)
|
|
|
resp.Body.Close()
|
|
resp.Body.Close()
|
|
|
status := int64(resp.StatusCode)
|
|
status := int64(resp.StatusCode)
|
|
|
if status >= http.StatusBadRequest {
|
|
if status >= http.StatusBadRequest {
|
|
|
- return nil, status, fmt.Errorf("挂载目录 %s 失败,外挂程序返回 HTTP %d: %s", mount, status, string(raw))
|
|
|
|
|
|
|
+ return nil, status, fmt.Errorf("挂载目录 %s 失败,外挂程序返回 HTTP %d: %s", dirLinuxPath, status, string(raw))
|
|
|
}
|
|
}
|
|
|
return map[string]interface{}{"status": status, "body": string(raw)}, status, nil
|
|
return map[string]interface{}{"status": status, "body": string(raw)}, status, nil
|
|
|
}); callErr != nil {
|
|
}); callErr != nil {
|
|
|
- return callErr
|
|
|
|
|
|
|
+ msgs = append(msgs, callErr.Error())
|
|
|
|
|
+ continue
|
|
|
}
|
|
}
|
|
|
l.Infof("fuse mount ok: %s -> %s", mount, mount+"-cipher")
|
|
l.Infof("fuse mount ok: %s -> %s", mount, mount+"-cipher")
|
|
|
}
|
|
}
|
|
|
|
|
+ if len(msgs) > 0 {
|
|
|
|
|
+ return fmt.Errorf("%s", strings.Join(msgs, "; "))
|
|
|
|
|
+ }
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|