diff --git a/Dockerfile b/Dockerfile index 6095a97..e55054f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -58,6 +58,9 @@ ENV IM_ADMIN_API_SECRET= ENV IM_INTERNAL_API_BASE_URL=https://api.118im.com ENV IM_INTERNAL_API_SERVICE_NAME=hotgo-admin ENV IM_INTERNAL_API_SECRET= +ENV IM_WORKBENCH_API_BASE_URL=http://im_workbench:6061 +ENV IM_WORKBENCH_API_SERVICE_NAME=hotgo-admin +ENV IM_WORKBENCH_API_SECRET= ENV IM_CONFIG_API_SERVICE_NAME=im-server ENV IM_CONFIG_API_SECRET= diff --git a/server/internal/library/imworkbench/client.go b/server/internal/library/imworkbench/client.go new file mode 100644 index 0000000..0bef9e7 --- /dev/null +++ b/server/internal/library/imworkbench/client.go @@ -0,0 +1,182 @@ +package imworkbench + +import ( + "bytes" + "context" + "crypto/hmac" + cryptorand "crypto/rand" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "io" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +const maxResponseBody = 10 << 20 + +type Config struct { + BaseURL string + ServiceName string + Secret string + Timeout time.Duration +} + +type Client struct { + conf Config + http *http.Client +} + +type Page[T any] struct { + Items []T `json:"items"` + Total int `json:"total"` + Page int `json:"page"` + PerPage int `json:"per_page"` +} + +func DefaultClient(ctx context.Context) *Client { + timeout := g.Cfg().MustGet(ctx, "imWorkbenchApi.timeout", "5s").Duration() + if timeout <= 0 { + timeout = 5 * time.Second + } + secret := g.Cfg().MustGet(ctx, "imWorkbenchApi.serviceSecret").String() + if strings.HasPrefix(secret, "${") && strings.HasSuffix(secret, "}") { + secret = os.Getenv(strings.TrimSuffix(strings.TrimPrefix(secret, "${"), "}")) + } + if secret == "" { + secret = os.Getenv("IM_WORKBENCH_API_SECRET") + } + conf := Config{ + BaseURL: g.Cfg().MustGet(ctx, "imWorkbenchApi.baseUrl").String(), + ServiceName: g.Cfg().MustGet(ctx, "imWorkbenchApi.serviceName", "hotgo-admin").String(), + Secret: secret, + Timeout: timeout, + } + return &Client{conf: conf, http: &http.Client{Timeout: timeout}} +} + +func (c *Client) Get(ctx context.Context, path string, values url.Values, out any) error { + if values != nil { + if query := values.Encode(); query != "" { + path += "?" + query + } + } + return c.doJSON(ctx, http.MethodGet, path, nil, out) +} + +func (c *Client) Post(ctx context.Context, path string, in any, out any) error { + body, err := json.Marshal(in) + if err != nil { + return gerror.Wrap(err, "序列化工作台请求失败") + } + return c.doJSON(ctx, http.MethodPost, path, body, out) +} + +func (c *Client) doJSON(ctx context.Context, method string, path string, body []byte, out any) error { + req, err := c.newRequest(ctx, method, path, body) + if err != nil { + return err + } + resp, err := c.http.Do(req) + if err != nil { + g.Log().Warningf(ctx, "IM workbench request failed url:%s err:%+v", req.URL.String(), err) + return gerror.New("IM工作台服务暂不可用") + } + defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return parseError(resp) + } + if out == nil { + return nil + } + responseBody, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBody)) + if err != nil { + return gerror.Wrap(err, "读取IM工作台响应失败") + } + var envelope struct { + Code int `json:"code"` + Message string `json:"message"` + Reason string `json:"reason"` + Data json.RawMessage `json:"data"` + } + if err = json.Unmarshal(responseBody, &envelope); err == nil && len(envelope.Data) > 0 { + if envelope.Code != 0 { + return stableError(envelope.Message, envelope.Reason) + } + return json.Unmarshal(envelope.Data, out) + } + return json.Unmarshal(responseBody, out) +} + +func (c *Client) newRequest(ctx context.Context, method string, path string, body []byte) (*http.Request, error) { + baseURL := strings.TrimRight(c.conf.BaseURL, "/") + if baseURL == "" { + return nil, gerror.New("IM工作台API未配置") + } + if c.conf.Secret == "" { + return nil, gerror.New("IM工作台API密钥未配置") + } + req, err := http.NewRequestWithContext(ctx, method, baseURL+path, bytes.NewReader(body)) + if err != nil { + return nil, gerror.Wrap(err, "创建IM工作台请求失败") + } + timestamp := strconv.FormatInt(time.Now().UTC().Unix(), 10) + nonce, err := randomNonce() + if err != nil { + return nil, err + } + bodyHash := sha256.Sum256(body) + signingText := strings.Join([]string{ + method, + req.URL.RequestURI(), + hex.EncodeToString(bodyHash[:]), + timestamp, + nonce, + }, "\n") + mac := hmac.New(sha256.New, []byte(c.conf.Secret)) + _, _ = mac.Write([]byte(signingText)) + req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Service-Name", c.conf.ServiceName) + req.Header.Set("X-Timestamp", timestamp) + req.Header.Set("X-Nonce", nonce) + req.Header.Set("X-Signature", hex.EncodeToString(mac.Sum(nil))) + req.Header.Set("X-Request-ID", gctx.CtxId(ctx)) + return req, nil +} + +func parseError(resp *http.Response) error { + body, _ := io.ReadAll(io.LimitReader(resp.Body, 64<<10)) + var envelope struct { + Message string `json:"message"` + Reason string `json:"reason"` + } + _ = json.Unmarshal(body, &envelope) + return stableError(envelope.Message, envelope.Reason) +} + +func stableError(message string, reason string) error { + if message != "" { + return gerror.New(message) + } + if reason != "" { + return gerror.New(reason) + } + return gerror.New("IM工作台服务暂不可用") +} + +func randomNonce() (string, error) { + var b [16]byte + if _, err := cryptorand.Read(b[:]); err != nil { + return "", gerror.Wrap(err, "生成IM工作台请求随机数失败") + } + return hex.EncodeToString(b[:]), nil +} diff --git a/server/internal/logic/sys/im_feedback_config.go b/server/internal/logic/sys/im_feedback_config.go index 04c2704..54a5706 100644 --- a/server/internal/logic/sys/im_feedback_config.go +++ b/server/internal/logic/sys/im_feedback_config.go @@ -2,17 +2,13 @@ package sys import ( "context" + "hotgo/internal/library/imworkbench" "hotgo/internal/model/input/sysin" "hotgo/internal/service" - "github.com/gogf/gf/v2/database/gdb" - "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/os/gtime" ) -const imFeedbackConfigTable = "im_feedback_config" - type sSysImFeedbackConfig struct{} func NewSysImFeedbackConfig() *sSysImFeedbackConfig { return &sSysImFeedbackConfig{} } @@ -25,16 +21,14 @@ func (s *sSysImFeedbackConfig) Get(ctx context.Context, in *sysin.ImFeedbackConf if err = in.Filter(ctx); err != nil { return } - db, tenant, err := resolveImTenantDB(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return nil, err } - if err = s.ensureDefault(ctx, db); err != nil { + res = &sysin.ImFeedbackConfigModel{} + if err = imworkbench.DefaultClient(ctx).Get(ctx, "/internal/admin/workbench/feedback-config", nil, res); err != nil { return nil, err } - if err = s.model(ctx, db).OrderAsc("id").Limit(1).Scan(&res); err != nil { - return nil, gerror.Wrap(err, "获取IM意见反馈配置失败,请稍后重试!") - } if res != nil { res.TenantId = tenant.Id res.TenantName = tenant.Name @@ -46,13 +40,10 @@ func (s *sSysImFeedbackConfig) Update(ctx context.Context, in *sysin.ImFeedbackC if err = in.Filter(ctx); err != nil { return } - db, tenant, err := resolveImTenantDB(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return err } - if err = s.ensureDefault(ctx, db); err != nil { - return err - } categories := `["bug","suggestion","complaint","other"]` if in.Categories != nil && in.Categories.String() != "" && in.Categories.String() != "null" { categories = in.Categories.String() @@ -61,52 +52,15 @@ func (s *sSysImFeedbackConfig) Update(ctx context.Context, in *sysin.ImFeedbackC if in.AttachmentPolicy != nil && in.AttachmentPolicy.String() != "" && in.AttachmentPolicy.String() != "null" { attachmentPolicy = in.AttachmentPolicy.String() } - if _, err = db.Model(imFeedbackConfigTable).Safe().Ctx(ctx).Where("id", 1).Data(g.Map{ + if err = imworkbench.DefaultClient(ctx).Post(ctx, "/internal/admin/workbench/feedback-config", g.Map{ "categories": categories, "max_length": in.MaxLength, "attachment_policy": attachmentPolicy, "allow_contact": in.AllowContact, "diagnostics_notice": in.DiagnosticsNotice, - "updated_at": gtime.Now(), - }).Update(); err != nil { - return gerror.Wrap(err, "更新IM意见反馈配置失败,请稍后重试!") + }, nil); err != nil { + return err } _ = writeImAdminAudit(ctx, tenant.Id, "im_feedback_config_update", "im_feedback_config", 1, "success", g.Map{"allow_contact": in.AllowContact}) return nil } - -func (s *sSysImFeedbackConfig) model(ctx context.Context, db gdb.DB) *gdb.Model { - return db.Model(imFeedbackConfigTable).Safe().Ctx(ctx).Fields( - "id", - "CAST(categories AS CHAR) categories", - "max_length", - "CAST(attachment_policy AS CHAR) attachment_policy", - "allow_contact", - "diagnostics_notice", - "updated_at", - ) -} - -func (s *sSysImFeedbackConfig) ensureDefault(ctx context.Context, db gdb.DB) (err error) { - var count int - if count, err = db.Model(imFeedbackConfigTable).Safe().Ctx(ctx).Where("id", 1).Count(); err != nil { - return gerror.Wrap(err, "检查IM意见反馈默认配置失败,请稍后重试!") - } - if count > 0 { - return nil - } - _, err = db.Model(imFeedbackConfigTable).Safe().Ctx(ctx).Data(g.Map{ - "id": 1, - "categories": `["bug","suggestion","complaint","other"]`, - "max_length": 1000, - "attachment_policy": `{"max_files":5,"purpose":"feedback"}`, - "allow_contact": 1, - "diagnostics_notice": "仅在你同意时上传脱敏诊断信息。", - "created_at": gtime.Now(), - "updated_at": gtime.Now(), - }).Insert() - if err != nil { - return gerror.Wrap(err, "初始化IM意见反馈默认配置失败,请稍后重试!") - } - return nil -} diff --git a/server/internal/logic/sys/im_mailbox_config.go b/server/internal/logic/sys/im_mailbox_config.go index e92b2aa..8133a0d 100644 --- a/server/internal/logic/sys/im_mailbox_config.go +++ b/server/internal/logic/sys/im_mailbox_config.go @@ -2,17 +2,13 @@ package sys import ( "context" + "hotgo/internal/library/imworkbench" "hotgo/internal/model/input/sysin" "hotgo/internal/service" - "github.com/gogf/gf/v2/database/gdb" - "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/os/gtime" ) -const imMailboxConfigTable = "im_mailbox_config" - type sSysImMailboxConfig struct{} func NewSysImMailboxConfig() *sSysImMailboxConfig { return &sSysImMailboxConfig{} } @@ -25,16 +21,14 @@ func (s *sSysImMailboxConfig) Get(ctx context.Context, in *sysin.ImMailboxConfig if err = in.Filter(ctx); err != nil { return } - db, tenant, err := resolveImTenantDB(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return nil, err } - if err = s.ensureDefault(ctx, db); err != nil { + res = &sysin.ImMailboxConfigModel{} + if err = imworkbench.DefaultClient(ctx).Get(ctx, "/internal/admin/workbench/mailbox-config", nil, res); err != nil { return nil, err } - if err = s.model(ctx, db).Where("id", 1).Scan(&res); err != nil { - return nil, gerror.Wrap(err, "获取IM总经理信箱配置失败,请稍后重试!") - } if res != nil { res.TenantId = tenant.Id res.TenantName = tenant.Name @@ -46,13 +40,10 @@ func (s *sSysImMailboxConfig) Update(ctx context.Context, in *sysin.ImMailboxCon if err = in.Filter(ctx); err != nil { return } - db, tenant, err := resolveImTenantDB(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return err } - if err = s.ensureDefault(ctx, db); err != nil { - return err - } requiredFields := `["subject","content"]` if in.RequiredFields != nil && in.RequiredFields.String() != "" && in.RequiredFields.String() != "null" { requiredFields = in.RequiredFields.String() @@ -61,7 +52,7 @@ func (s *sSysImMailboxConfig) Update(ctx context.Context, in *sysin.ImMailboxCon if in.AttachmentPolicy != nil && in.AttachmentPolicy.String() != "" && in.AttachmentPolicy.String() != "null" { attachmentPolicy = in.AttachmentPolicy.String() } - if _, err = db.Model(imMailboxConfigTable).Safe().Ctx(ctx).Where("id", 1).Data(g.Map{ + if err = imworkbench.DefaultClient(ctx).Post(ctx, "/internal/admin/workbench/mailbox-config", g.Map{ "enabled": in.Enabled, "allow_anonymous": in.AllowAnonymous, "required_fields": requiredFields, @@ -69,53 +60,9 @@ func (s *sSysImMailboxConfig) Update(ctx context.Context, in *sysin.ImMailboxCon "attachment_policy": attachmentPolicy, "notice_title": in.NoticeTitle, "notice_content": in.NoticeContent, - "notice_updated_at": gtime.Now(), - "updated_at": gtime.Now(), - }).Update(); err != nil { - return gerror.Wrap(err, "更新IM总经理信箱配置失败,请稍后重试!") + }, nil); err != nil { + return err } _ = writeImAdminAudit(ctx, tenant.Id, "im_mailbox_config_update", "im_mailbox_config", 1, "success", g.Map{"enabled": in.Enabled}) return nil } - -func (s *sSysImMailboxConfig) model(ctx context.Context, db gdb.DB) *gdb.Model { - return db.Model(imMailboxConfigTable).Safe().Ctx(ctx).Fields( - "id", - "enabled", - "allow_anonymous", - "CAST(required_fields AS CHAR) required_fields", - "max_length", - "CAST(attachment_policy AS CHAR) attachment_policy", - "notice_title", - "notice_content", - "notice_updated_at", - "updated_at", - ) -} - -func (s *sSysImMailboxConfig) ensureDefault(ctx context.Context, db gdb.DB) (err error) { - var count int - if count, err = db.Model(imMailboxConfigTable).Safe().Ctx(ctx).Where("id", 1).Count(); err != nil { - return gerror.Wrap(err, "检查IM总经理信箱默认配置失败,请稍后重试!") - } - if count > 0 { - return nil - } - _, err = db.Model(imMailboxConfigTable).Safe().Ctx(ctx).Data(g.Map{ - "id": 1, - "enabled": 1, - "allow_anonymous": 1, - "required_fields": `["subject","content"]`, - "max_length": 5000, - "attachment_policy": `{"max_files":5,"purpose":"executive_mailbox"}`, - "notice_title": "总经理信箱", - "notice_content": "请如实填写诉求,匿名提交将按企业政策保护展示身份。", - "notice_updated_at": gtime.Now(), - "created_at": gtime.Now(), - "updated_at": gtime.Now(), - }).Insert() - if err != nil { - return gerror.Wrap(err, "初始化IM总经理信箱默认配置失败,请稍后重试!") - } - return nil -} diff --git a/server/internal/logic/sys/im_workbench.go b/server/internal/logic/sys/im_workbench.go index 551c173..8a0e04b 100644 --- a/server/internal/logic/sys/im_workbench.go +++ b/server/internal/logic/sys/im_workbench.go @@ -2,18 +2,15 @@ package sys import ( "context" - "hotgo/internal/consts" + "hotgo/internal/library/imworkbench" "hotgo/internal/model/input/sysin" "hotgo/internal/service" + "net/url" - "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/os/gtime" ) -const imWorkbenchAppsTable = "im_workbench_apps" - type sSysImWorkbench struct{} func NewSysImWorkbench() *sSysImWorkbench { return &sSysImWorkbench{} } @@ -26,47 +23,46 @@ func (s *sSysImWorkbench) List(ctx context.Context, in *sysin.ImWorkbenchAppList if err = in.Filter(ctx); err != nil { return } - db, tenant, err := resolveImTenantDB(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return } - if err = s.ensureDefaultApps(ctx, db); err != nil { - return - } - mod := s.model(ctx, db) + values := url.Values{} + values.Set("page", intToString(in.Page)) + values.Set("per_page", intToString(in.PerPage)) if in.Code != "" { - mod = mod.WhereLike("code", "%"+in.Code+"%") + values.Set("code", in.Code) } if in.Name != "" { - mod = mod.WhereLike("name", "%"+in.Name+"%") + values.Set("name", in.Name) } if in.Enabled != nil { - mod = mod.Where("enabled", *in.Enabled) + values.Set("enabled", intToString(*in.Enabled)) } - mod = mod.Page(in.Page, in.PerPage).OrderAsc("sort_order").OrderAsc("id") - if err = mod.ScanAndCount(&list, &totalCount, false); err != nil { - err = gerror.Wrap(err, "获取IM工作台模块失败,请稍后重试!") - return + var page imworkbench.Page[sysin.ImWorkbenchAppModel] + if err = imworkbench.DefaultClient(ctx).Get(ctx, "/internal/admin/workbench/apps", values, &page); err != nil { + return nil, 0, err } - for _, item := range list { + list = make([]*sysin.ImWorkbenchAppModel, 0, len(page.Items)) + for i := range page.Items { + item := page.Items[i] item.TenantId = tenant.Id item.TenantName = tenant.Name + list = append(list, &item) } - return + return list, page.Total, nil } func (s *sSysImWorkbench) Edit(ctx context.Context, in *sysin.ImWorkbenchAppEditInp) (err error) { if err = in.Filter(ctx); err != nil { return } - db, tenant, err := resolveImTenantDB(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return } - if err = s.ensureDefaultApps(ctx, db); err != nil { - return - } data := g.Map{ + "id": in.Id, "code": in.Code, "name": in.Name, "icon_file_id": in.IconFileId, @@ -75,25 +71,18 @@ func (s *sSysImWorkbench) Edit(ctx context.Context, in *sysin.ImWorkbenchAppEdit "unread_key": in.UnreadKey, "enabled": in.Enabled, "permission": in.Permission, - "updated_at": gtime.Now(), } action := "im_workbench_app_create" targetId := in.Id if in.Id > 0 { - if _, err = db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).WherePri(in.Id).Data(data).Update(); err != nil { - return gerror.Wrap(err, "更新IM工作台模块失败,请稍后重试!") - } action = "im_workbench_app_update" - } else { - data["created_at"] = gtime.Now() - if _, err = db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Data(data).Insert(); err != nil { - return gerror.Wrap(err, "新增IM工作台模块失败,请稍后重试!") - } - idValue, err := db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Fields("id").Where("code", in.Code).Value() - if err != nil { - return gerror.Wrap(err, "获取IM工作台模块ID失败,请稍后重试!") - } - targetId = idValue.Int64() + } + var res sysin.ImWorkbenchAppModel + if err = imworkbench.DefaultClient(ctx).Post(ctx, "/internal/admin/workbench/apps", data, &res); err != nil { + return err + } + if targetId <= 0 { + targetId = res.Id } _ = writeImAdminAudit(ctx, tenant.Id, action, "im_workbench_app", targetId, "success", g.Map{ "code": in.Code, "route": in.Route, "enabled": in.Enabled, "permission": in.Permission, @@ -105,21 +94,12 @@ func (s *sSysImWorkbench) Delete(ctx context.Context, in *sysin.ImWorkbenchAppDe if err = in.Filter(ctx); err != nil { return } - db, tenant, err := resolveImTenantDB(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return } - if err = s.ensureDefaultApps(ctx, db); err != nil { - return - } - mod := db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx) - if in.Id > 0 { - mod = mod.WherePri(in.Id) - } else { - mod = mod.Where("code", in.Code) - } - if _, err = mod.Data(g.Map{"enabled": consts.ImWorkbenchAppDisabled, "updated_at": gtime.Now()}).Update(); err != nil { - return gerror.Wrap(err, "禁用IM工作台模块失败,请稍后重试!") + if err = imworkbench.DefaultClient(ctx).Post(ctx, "/internal/admin/workbench/apps/disable", g.Map{"id": in.Id, "code": in.Code}, nil); err != nil { + return err } _ = writeImAdminAudit(ctx, tenant.Id, "im_workbench_app_disable", "im_workbench_app", in.Id, "success", g.Map{"code": in.Code}) return nil @@ -129,66 +109,23 @@ func (s *sSysImWorkbench) Reorder(ctx context.Context, in *sysin.ImWorkbenchAppR if err = in.Filter(ctx); err != nil { return } - db, tenant, err := resolveImTenantDB(ctx, in.TenantId) + tenant, err := resolveImAdminTenant(ctx, in.TenantId) if err != nil { return } - if err = s.ensureDefaultApps(ctx, db); err != nil { - return + items := make([]g.Map, 0, len(in.Items)) + for _, item := range in.Items { + items = append(items, g.Map{ + "code": item.Code, + "sort_order": item.SortOrder, + }) } - if err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { - for _, item := range in.Items { - if _, e := tx.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Where("code", item.Code). - Data(g.Map{"sort_order": item.SortOrder, "updated_at": gtime.Now()}).Update(); e != nil { - return e - } + if err = imworkbench.DefaultClient(ctx).Post(ctx, "/internal/admin/workbench/apps/reorder", g.Map{"items": items}, nil); err != nil { + if err.Error() == "" { + return gerror.New("调整IM工作台模块排序失败,请稍后重试!") } - return nil - }); err != nil { - return gerror.Wrap(err, "调整IM工作台模块排序失败,请稍后重试!") + return err } _ = writeImAdminAudit(ctx, tenant.Id, "im_workbench_app_reorder", "im_workbench_app", 0, "success", g.Map{"count": len(in.Items)}) return nil } - -func (s *sSysImWorkbench) model(ctx context.Context, db gdb.DB) *gdb.Model { - return db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Fields( - "id", - "code", - "name", - "icon_file_id", - "route", - "sort_order", - "unread_key", - "enabled", - "permission", - "created_at", - "updated_at", - ) -} - -func (s *sSysImWorkbench) ensureDefaultApps(ctx context.Context, db gdb.DB) (err error) { - defaults := []g.Map{ - {"code": "organization", "name": "组织架构", "route": consts.ImWorkbenchRouteOrganization, "sort_order": 10, "unread_key": "organization", "enabled": consts.ImWorkbenchAppEnabled, "permission": consts.ImWorkbenchPermissionEnabled}, - {"code": "meetings", "name": "会议通知", "route": consts.ImWorkbenchRouteMeetings, "sort_order": 20, "unread_key": "meetings", "enabled": consts.ImWorkbenchAppEnabled, "permission": consts.ImWorkbenchPermissionEnabled}, - {"code": "daily_reports", "name": "日报", "route": consts.ImWorkbenchRouteDailyReports, "sort_order": 30, "unread_key": "daily_reports", "enabled": consts.ImWorkbenchAppEnabled, "permission": consts.ImWorkbenchPermissionEnabled}, - {"code": "monthly_reports", "name": "月报", "route": consts.ImWorkbenchRouteMonthlyReports, "sort_order": 40, "unread_key": "monthly_reports", "enabled": consts.ImWorkbenchAppEnabled, "permission": consts.ImWorkbenchPermissionEnabled}, - {"code": "executive_mailbox", "name": "总经理信箱", "route": consts.ImWorkbenchRouteExecutiveMailbox, "sort_order": 50, "unread_key": "executive_mailbox", "enabled": consts.ImWorkbenchAppEnabled, "permission": consts.ImWorkbenchPermissionEnabled}, - } - now := gtime.Now() - for _, item := range defaults { - var count int - if count, err = db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Where("code", item["code"]).Count(); err != nil { - return gerror.Wrap(err, "检查IM工作台默认模块失败,请稍后重试!") - } - if count > 0 { - continue - } - item["created_at"] = now - item["updated_at"] = now - if _, err = db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Data(item).Insert(); err != nil { - return gerror.Wrap(err, "初始化IM工作台默认模块失败,请稍后重试!") - } - } - return nil -} diff --git a/server/manifest/config/config.example.yaml b/server/manifest/config/config.example.yaml index ec69c7e..2fcbb9e 100644 --- a/server/manifest/config/config.example.yaml +++ b/server/manifest/config/config.example.yaml @@ -319,6 +319,12 @@ imInternalApi: serviceName: "hotgo-admin" serviceSecret: "${IM_INTERNAL_API_SECRET}" +imWorkbenchApi: + baseUrl: "http://im_workbench:6061" + serviceName: "hotgo-admin" + serviceSecret: "${IM_WORKBENCH_API_SECRET}" + timeout: "5s" + imConfigApi: serviceName: "im-server" serviceSecret: "${IM_CONFIG_API_SECRET}" diff --git a/server/manifest/config/config.template b/server/manifest/config/config.template index 7d6a9f6..db4b688 100644 --- a/server/manifest/config/config.template +++ b/server/manifest/config/config.template @@ -316,6 +316,12 @@ imInternalApi: serviceName: "$IM_INTERNAL_API_SERVICE_NAME" serviceSecret: "$IM_INTERNAL_API_SECRET" +imWorkbenchApi: + baseUrl: "$IM_WORKBENCH_API_BASE_URL" + serviceName: "$IM_WORKBENCH_API_SERVICE_NAME" + serviceSecret: "$IM_WORKBENCH_API_SECRET" + timeout: "5s" + imConfigApi: serviceName: "$IM_CONFIG_API_SERVICE_NAME" serviceSecret: "$IM_CONFIG_API_SECRET" diff --git a/server/manifest/docker/entrypoint.sh b/server/manifest/docker/entrypoint.sh index 72a473a..619ded8 100644 --- a/server/manifest/docker/entrypoint.sh +++ b/server/manifest/docker/entrypoint.sh @@ -6,11 +6,12 @@ cd /app # Render the real config from the template using only the specific env vars we # expect — avoids accidentally substituting anything else that might contain a # literal '$' in the YAML. -envsubst '$MYSQL_DSN $REDIS_ADDR $REDIS_DB $REDIS_PASSWORD $TOKEN_SECRET_KEY $QUEUE_DRIVER $KAFKA_ADDRESS $KAFKA_VERSION $KAFKA_USERNAME $KAFKA_PASSWORD $KAFKA_SASL_MECHANISM $IM_ADMIN_API_BASE_URL $IM_ADMIN_API_SERVICE_ID $IM_ADMIN_API_SECRET $IM_INTERNAL_API_BASE_URL $IM_INTERNAL_API_SERVICE_NAME $IM_INTERNAL_API_SECRET $IM_CONFIG_API_SERVICE_NAME $IM_CONFIG_API_SECRET' \ +envsubst '$MYSQL_DSN $REDIS_ADDR $REDIS_DB $REDIS_PASSWORD $TOKEN_SECRET_KEY $QUEUE_DRIVER $KAFKA_ADDRESS $KAFKA_VERSION $KAFKA_USERNAME $KAFKA_PASSWORD $KAFKA_SASL_MECHANISM $IM_ADMIN_API_BASE_URL $IM_ADMIN_API_SERVICE_ID $IM_ADMIN_API_SECRET $IM_INTERNAL_API_BASE_URL $IM_INTERNAL_API_SERVICE_NAME $IM_INTERNAL_API_SECRET $IM_WORKBENCH_API_BASE_URL $IM_WORKBENCH_API_SERVICE_NAME $IM_WORKBENCH_API_SECRET $IM_CONFIG_API_SERVICE_NAME $IM_CONFIG_API_SECRET' \ < manifest/config/config.template > manifest/config/config.yaml echo "queue.driver=${QUEUE_DRIVER:-}" echo "queue.kafka.address=${KAFKA_ADDRESS:-}" echo "imAdminApi.baseUrl=${IM_ADMIN_API_BASE_URL:-}" +echo "imWorkbenchApi.baseUrl=${IM_WORKBENCH_API_BASE_URL:-}" exec ./hotgo