feat: proxy workbench admin through service
This commit is contained in:
@@ -58,6 +58,9 @@ ENV IM_ADMIN_API_SECRET=
|
|||||||
ENV IM_INTERNAL_API_BASE_URL=https://api.118im.com
|
ENV IM_INTERNAL_API_BASE_URL=https://api.118im.com
|
||||||
ENV IM_INTERNAL_API_SERVICE_NAME=hotgo-admin
|
ENV IM_INTERNAL_API_SERVICE_NAME=hotgo-admin
|
||||||
ENV IM_INTERNAL_API_SECRET=
|
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_SERVICE_NAME=im-server
|
||||||
ENV IM_CONFIG_API_SECRET=
|
ENV IM_CONFIG_API_SECRET=
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -2,17 +2,13 @@ package sys
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"hotgo/internal/library/imworkbench"
|
||||||
"hotgo/internal/model/input/sysin"
|
"hotgo/internal/model/input/sysin"
|
||||||
"hotgo/internal/service"
|
"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/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const imFeedbackConfigTable = "im_feedback_config"
|
|
||||||
|
|
||||||
type sSysImFeedbackConfig struct{}
|
type sSysImFeedbackConfig struct{}
|
||||||
|
|
||||||
func NewSysImFeedbackConfig() *sSysImFeedbackConfig { return &sSysImFeedbackConfig{} }
|
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 {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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 {
|
if res != nil {
|
||||||
res.TenantId = tenant.Id
|
res.TenantId = tenant.Id
|
||||||
res.TenantName = tenant.Name
|
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 {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = s.ensureDefault(ctx, db); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
categories := `["bug","suggestion","complaint","other"]`
|
categories := `["bug","suggestion","complaint","other"]`
|
||||||
if in.Categories != nil && in.Categories.String() != "" && in.Categories.String() != "null" {
|
if in.Categories != nil && in.Categories.String() != "" && in.Categories.String() != "null" {
|
||||||
categories = in.Categories.String()
|
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" {
|
if in.AttachmentPolicy != nil && in.AttachmentPolicy.String() != "" && in.AttachmentPolicy.String() != "null" {
|
||||||
attachmentPolicy = in.AttachmentPolicy.String()
|
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,
|
"categories": categories,
|
||||||
"max_length": in.MaxLength,
|
"max_length": in.MaxLength,
|
||||||
"attachment_policy": attachmentPolicy,
|
"attachment_policy": attachmentPolicy,
|
||||||
"allow_contact": in.AllowContact,
|
"allow_contact": in.AllowContact,
|
||||||
"diagnostics_notice": in.DiagnosticsNotice,
|
"diagnostics_notice": in.DiagnosticsNotice,
|
||||||
"updated_at": gtime.Now(),
|
}, nil); err != nil {
|
||||||
}).Update(); err != nil {
|
return err
|
||||||
return gerror.Wrap(err, "更新IM意见反馈配置失败,请稍后重试!")
|
|
||||||
}
|
}
|
||||||
_ = writeImAdminAudit(ctx, tenant.Id, "im_feedback_config_update", "im_feedback_config", 1, "success", g.Map{"allow_contact": in.AllowContact})
|
_ = writeImAdminAudit(ctx, tenant.Id, "im_feedback_config_update", "im_feedback_config", 1, "success", g.Map{"allow_contact": in.AllowContact})
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,17 +2,13 @@ package sys
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"hotgo/internal/library/imworkbench"
|
||||||
"hotgo/internal/model/input/sysin"
|
"hotgo/internal/model/input/sysin"
|
||||||
"hotgo/internal/service"
|
"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/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const imMailboxConfigTable = "im_mailbox_config"
|
|
||||||
|
|
||||||
type sSysImMailboxConfig struct{}
|
type sSysImMailboxConfig struct{}
|
||||||
|
|
||||||
func NewSysImMailboxConfig() *sSysImMailboxConfig { return &sSysImMailboxConfig{} }
|
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 {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
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 {
|
if res != nil {
|
||||||
res.TenantId = tenant.Id
|
res.TenantId = tenant.Id
|
||||||
res.TenantName = tenant.Name
|
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 {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = s.ensureDefault(ctx, db); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
requiredFields := `["subject","content"]`
|
requiredFields := `["subject","content"]`
|
||||||
if in.RequiredFields != nil && in.RequiredFields.String() != "" && in.RequiredFields.String() != "null" {
|
if in.RequiredFields != nil && in.RequiredFields.String() != "" && in.RequiredFields.String() != "null" {
|
||||||
requiredFields = in.RequiredFields.String()
|
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" {
|
if in.AttachmentPolicy != nil && in.AttachmentPolicy.String() != "" && in.AttachmentPolicy.String() != "null" {
|
||||||
attachmentPolicy = in.AttachmentPolicy.String()
|
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,
|
"enabled": in.Enabled,
|
||||||
"allow_anonymous": in.AllowAnonymous,
|
"allow_anonymous": in.AllowAnonymous,
|
||||||
"required_fields": requiredFields,
|
"required_fields": requiredFields,
|
||||||
@@ -69,53 +60,9 @@ func (s *sSysImMailboxConfig) Update(ctx context.Context, in *sysin.ImMailboxCon
|
|||||||
"attachment_policy": attachmentPolicy,
|
"attachment_policy": attachmentPolicy,
|
||||||
"notice_title": in.NoticeTitle,
|
"notice_title": in.NoticeTitle,
|
||||||
"notice_content": in.NoticeContent,
|
"notice_content": in.NoticeContent,
|
||||||
"notice_updated_at": gtime.Now(),
|
}, nil); err != nil {
|
||||||
"updated_at": gtime.Now(),
|
return err
|
||||||
}).Update(); err != nil {
|
|
||||||
return gerror.Wrap(err, "更新IM总经理信箱配置失败,请稍后重试!")
|
|
||||||
}
|
}
|
||||||
_ = writeImAdminAudit(ctx, tenant.Id, "im_mailbox_config_update", "im_mailbox_config", 1, "success", g.Map{"enabled": in.Enabled})
|
_ = writeImAdminAudit(ctx, tenant.Id, "im_mailbox_config_update", "im_mailbox_config", 1, "success", g.Map{"enabled": in.Enabled})
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,18 +2,15 @@ package sys
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"hotgo/internal/consts"
|
"hotgo/internal/library/imworkbench"
|
||||||
"hotgo/internal/model/input/sysin"
|
"hotgo/internal/model/input/sysin"
|
||||||
"hotgo/internal/service"
|
"hotgo/internal/service"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const imWorkbenchAppsTable = "im_workbench_apps"
|
|
||||||
|
|
||||||
type sSysImWorkbench struct{}
|
type sSysImWorkbench struct{}
|
||||||
|
|
||||||
func NewSysImWorkbench() *sSysImWorkbench { return &sSysImWorkbench{} }
|
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 {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = s.ensureDefaultApps(ctx, db); err != nil {
|
values := url.Values{}
|
||||||
return
|
values.Set("page", intToString(in.Page))
|
||||||
}
|
values.Set("per_page", intToString(in.PerPage))
|
||||||
mod := s.model(ctx, db)
|
|
||||||
if in.Code != "" {
|
if in.Code != "" {
|
||||||
mod = mod.WhereLike("code", "%"+in.Code+"%")
|
values.Set("code", in.Code)
|
||||||
}
|
}
|
||||||
if in.Name != "" {
|
if in.Name != "" {
|
||||||
mod = mod.WhereLike("name", "%"+in.Name+"%")
|
values.Set("name", in.Name)
|
||||||
}
|
}
|
||||||
if in.Enabled != nil {
|
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")
|
var page imworkbench.Page[sysin.ImWorkbenchAppModel]
|
||||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
if err = imworkbench.DefaultClient(ctx).Get(ctx, "/internal/admin/workbench/apps", values, &page); err != nil {
|
||||||
err = gerror.Wrap(err, "获取IM工作台模块失败,请稍后重试!")
|
return nil, 0, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
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.TenantId = tenant.Id
|
||||||
item.TenantName = tenant.Name
|
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) {
|
func (s *sSysImWorkbench) Edit(ctx context.Context, in *sysin.ImWorkbenchAppEditInp) (err error) {
|
||||||
if err = in.Filter(ctx); err != nil {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = s.ensureDefaultApps(ctx, db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data := g.Map{
|
data := g.Map{
|
||||||
|
"id": in.Id,
|
||||||
"code": in.Code,
|
"code": in.Code,
|
||||||
"name": in.Name,
|
"name": in.Name,
|
||||||
"icon_file_id": in.IconFileId,
|
"icon_file_id": in.IconFileId,
|
||||||
@@ -75,25 +71,18 @@ func (s *sSysImWorkbench) Edit(ctx context.Context, in *sysin.ImWorkbenchAppEdit
|
|||||||
"unread_key": in.UnreadKey,
|
"unread_key": in.UnreadKey,
|
||||||
"enabled": in.Enabled,
|
"enabled": in.Enabled,
|
||||||
"permission": in.Permission,
|
"permission": in.Permission,
|
||||||
"updated_at": gtime.Now(),
|
|
||||||
}
|
}
|
||||||
action := "im_workbench_app_create"
|
action := "im_workbench_app_create"
|
||||||
targetId := in.Id
|
targetId := in.Id
|
||||||
if in.Id > 0 {
|
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"
|
action = "im_workbench_app_update"
|
||||||
} else {
|
}
|
||||||
data["created_at"] = gtime.Now()
|
var res sysin.ImWorkbenchAppModel
|
||||||
if _, err = db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Data(data).Insert(); err != nil {
|
if err = imworkbench.DefaultClient(ctx).Post(ctx, "/internal/admin/workbench/apps", data, &res); err != nil {
|
||||||
return gerror.Wrap(err, "新增IM工作台模块失败,请稍后重试!")
|
return err
|
||||||
}
|
}
|
||||||
idValue, err := db.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Fields("id").Where("code", in.Code).Value()
|
if targetId <= 0 {
|
||||||
if err != nil {
|
targetId = res.Id
|
||||||
return gerror.Wrap(err, "获取IM工作台模块ID失败,请稍后重试!")
|
|
||||||
}
|
|
||||||
targetId = idValue.Int64()
|
|
||||||
}
|
}
|
||||||
_ = writeImAdminAudit(ctx, tenant.Id, action, "im_workbench_app", targetId, "success", g.Map{
|
_ = writeImAdminAudit(ctx, tenant.Id, action, "im_workbench_app", targetId, "success", g.Map{
|
||||||
"code": in.Code, "route": in.Route, "enabled": in.Enabled, "permission": in.Permission,
|
"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 {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = s.ensureDefaultApps(ctx, db); err != nil {
|
if err = imworkbench.DefaultClient(ctx).Post(ctx, "/internal/admin/workbench/apps/disable", g.Map{"id": in.Id, "code": in.Code}, nil); err != nil {
|
||||||
return
|
return err
|
||||||
}
|
|
||||||
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工作台模块失败,请稍后重试!")
|
|
||||||
}
|
}
|
||||||
_ = writeImAdminAudit(ctx, tenant.Id, "im_workbench_app_disable", "im_workbench_app", in.Id, "success", g.Map{"code": in.Code})
|
_ = writeImAdminAudit(ctx, tenant.Id, "im_workbench_app_disable", "im_workbench_app", in.Id, "success", g.Map{"code": in.Code})
|
||||||
return nil
|
return nil
|
||||||
@@ -129,66 +109,23 @@ func (s *sSysImWorkbench) Reorder(ctx context.Context, in *sysin.ImWorkbenchAppR
|
|||||||
if err = in.Filter(ctx); err != nil {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db, tenant, err := resolveImTenantDB(ctx, in.TenantId)
|
tenant, err := resolveImAdminTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = s.ensureDefaultApps(ctx, db); err != nil {
|
items := make([]g.Map, 0, len(in.Items))
|
||||||
return
|
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 {
|
if err = imworkbench.DefaultClient(ctx).Post(ctx, "/internal/admin/workbench/apps/reorder", g.Map{"items": items}, nil); err != nil {
|
||||||
for _, item := range in.Items {
|
if err.Error() == "" {
|
||||||
if _, e := tx.Model(imWorkbenchAppsTable).Safe().Ctx(ctx).Where("code", item.Code).
|
return gerror.New("调整IM工作台模块排序失败,请稍后重试!")
|
||||||
Data(g.Map{"sort_order": item.SortOrder, "updated_at": gtime.Now()}).Update(); e != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return err
|
||||||
}); err != nil {
|
|
||||||
return gerror.Wrap(err, "调整IM工作台模块排序失败,请稍后重试!")
|
|
||||||
}
|
}
|
||||||
_ = writeImAdminAudit(ctx, tenant.Id, "im_workbench_app_reorder", "im_workbench_app", 0, "success", g.Map{"count": len(in.Items)})
|
_ = writeImAdminAudit(ctx, tenant.Id, "im_workbench_app_reorder", "im_workbench_app", 0, "success", g.Map{"count": len(in.Items)})
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -319,6 +319,12 @@ imInternalApi:
|
|||||||
serviceName: "hotgo-admin"
|
serviceName: "hotgo-admin"
|
||||||
serviceSecret: "${IM_INTERNAL_API_SECRET}"
|
serviceSecret: "${IM_INTERNAL_API_SECRET}"
|
||||||
|
|
||||||
|
imWorkbenchApi:
|
||||||
|
baseUrl: "http://im_workbench:6061"
|
||||||
|
serviceName: "hotgo-admin"
|
||||||
|
serviceSecret: "${IM_WORKBENCH_API_SECRET}"
|
||||||
|
timeout: "5s"
|
||||||
|
|
||||||
imConfigApi:
|
imConfigApi:
|
||||||
serviceName: "im-server"
|
serviceName: "im-server"
|
||||||
serviceSecret: "${IM_CONFIG_API_SECRET}"
|
serviceSecret: "${IM_CONFIG_API_SECRET}"
|
||||||
|
|||||||
@@ -316,6 +316,12 @@ imInternalApi:
|
|||||||
serviceName: "$IM_INTERNAL_API_SERVICE_NAME"
|
serviceName: "$IM_INTERNAL_API_SERVICE_NAME"
|
||||||
serviceSecret: "$IM_INTERNAL_API_SECRET"
|
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:
|
imConfigApi:
|
||||||
serviceName: "$IM_CONFIG_API_SERVICE_NAME"
|
serviceName: "$IM_CONFIG_API_SERVICE_NAME"
|
||||||
serviceSecret: "$IM_CONFIG_API_SECRET"
|
serviceSecret: "$IM_CONFIG_API_SECRET"
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ cd /app
|
|||||||
# Render the real config from the template using only the specific env vars we
|
# Render the real config from the template using only the specific env vars we
|
||||||
# expect — avoids accidentally substituting anything else that might contain a
|
# expect — avoids accidentally substituting anything else that might contain a
|
||||||
# literal '$' in the YAML.
|
# 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
|
< manifest/config/config.template > manifest/config/config.yaml
|
||||||
|
|
||||||
echo "queue.driver=${QUEUE_DRIVER:-}"
|
echo "queue.driver=${QUEUE_DRIVER:-}"
|
||||||
echo "queue.kafka.address=${KAFKA_ADDRESS:-}"
|
echo "queue.kafka.address=${KAFKA_ADDRESS:-}"
|
||||||
echo "imAdminApi.baseUrl=${IM_ADMIN_API_BASE_URL:-}"
|
echo "imAdminApi.baseUrl=${IM_ADMIN_API_BASE_URL:-}"
|
||||||
|
echo "imWorkbenchApi.baseUrl=${IM_WORKBENCH_API_BASE_URL:-}"
|
||||||
|
|
||||||
exec ./hotgo
|
exec ./hotgo
|
||||||
|
|||||||
Reference in New Issue
Block a user