refactor: 消息模块重构,infra包路径简写等

This commit is contained in:
meilin.huang
2025-07-27 21:02:48 +08:00
parent e96379b6c0
commit 6ad6c69660
149 changed files with 969 additions and 1098 deletions

View File

@@ -10,16 +10,16 @@ require (
github.com/gin-gonic/gin v1.10.1
github.com/glebarez/sqlite v1.11.0
github.com/go-gormigrate/gormigrate/v2 v2.1.4
github.com/go-ldap/ldap/v3 v3.4.8
github.com/go-ldap/ldap/v3 v3.4.11
github.com/go-playground/locales v0.14.1
github.com/go-playground/universal-translator v0.18.1
github.com/go-playground/validator/v10 v10.26.0
github.com/go-playground/validator/v10 v10.27.0
github.com/go-sql-driver/mysql v1.9.3
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/golang-jwt/jwt/v5 v5.2.3
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250508043914-ed57fa5c5274
github.com/microsoft/go-mssqldb v1.9.1
github.com/lionsoul2014/ip2region/binding/golang v0.0.0-20250630080345-f9402614f6ba
github.com/microsoft/go-mssqldb v1.9.2
github.com/mojocn/base64Captcha v1.3.8 //
github.com/pkg/errors v0.9.1
github.com/pkg/sftp v1.13.9
@@ -32,33 +32,32 @@ require (
github.com/tidwall/gjson v1.18.0
github.com/veops/go-ansiterm v0.0.5
go.mongodb.org/mongo-driver/v2 v2.2.2 // mongo
golang.org/x/crypto v0.39.0 // ssh
golang.org/x/crypto v0.40.0 // ssh
golang.org/x/oauth2 v0.30.0
golang.org/x/sync v0.15.0
golang.org/x/sync v0.16.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
// gorm
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.30.0
gorm.io/gorm v1.30.1
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/boombuler/barcode v1.1.0 // indirect
github.com/bytedance/sonic v1.14.0 // indirect
github.com/bytedance/sonic/loader v0.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
@@ -66,15 +65,16 @@ require (
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
@@ -83,20 +83,20 @@ require (
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
golang.org/x/arch v0.14.0 // indirect
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/image v0.23.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
golang.org/x/arch v0.19.0 // indirect
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
golang.org/x/image v0.29.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
modernc.org/libc v1.66.4 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.38.1 // indirect
)

View File

@@ -47,7 +47,7 @@ func (a *AccountLogin) ReqConfs() *req.Confs {
// @router /auth/accounts/login [post]
func (a *AccountLogin) Login(rc *req.Ctx) {
loginForm := req.BindJsonAndValid[*form.LoginForm](rc)
loginForm := req.BindJson[*form.LoginForm](rc)
ctx := rc.MetaCtx
accountLoginSecurity := config.GetAccountLoginSecurity()
@@ -96,7 +96,7 @@ type OtpVerifyInfo struct {
// OTP双因素校验
func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
otpVerify := req.BindJsonAndValid[*form.OtpVerfiy](rc)
otpVerify := req.BindJson[*form.OtpVerfiy](rc)
ctx := rc.MetaCtx
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)

View File

@@ -6,13 +6,13 @@ import (
"mayfly-go/internal/auth/config"
"mayfly-go/internal/auth/imsg"
"mayfly-go/internal/auth/pkg/otp"
msgapp "mayfly-go/internal/msg/application"
msgentity "mayfly-go/internal/msg/domain/entity"
msgdto "mayfly-go/internal/msg/application/dto"
"mayfly-go/internal/pkg/event"
sysapp "mayfly-go/internal/sys/application"
sysentity "mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/cache"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/global"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/netx"
@@ -114,14 +114,12 @@ func saveLogin(ctx context.Context, account *sysentity.Account, ip string) {
// 偷懒为了方便直接获取accountApp
biz.ErrIsNil(sysapp.GetAccountApp().Update(context.TODO(), updateAccount))
// 创建登录消息
loginMsg := &msgentity.Msg{
RecipientId: int64(account.Id),
Msg: i18n.TC(ctx, imsg.LoginMsg, "ip", ip, "time", timex.DefaultFormat(now)),
Type: 1,
}
loginMsg.CreateTime = &now
loginMsg.Creator = account.Username
loginMsg.CreatorId = account.Id
msgapp.GetMsgApp().Create(context.TODO(), loginMsg)
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, &msgdto.MsgTmplSendEvent{
TmplChannel: msgdto.MsgTmplLogin,
Params: collx.M{
"ip": ip,
"time": timex.DefaultFormat(now),
},
ReceiverIds: []uint64{account.Id},
})
}

View File

@@ -47,7 +47,7 @@ func (a *LdapLogin) GetLdapEnabled(rc *req.Ctx) {
// @router /auth/ldap/login [post]
func (a *LdapLogin) Login(rc *req.Ctx) {
loginForm := req.BindJsonAndValid[*form.LoginForm](rc)
loginForm := req.BindJson[*form.LoginForm](rc)
ctx := rc.MetaCtx
accountLoginSecurity := config.GetAccountLoginSecurity()
// 判断是否有开启登录验证码校验
@@ -197,14 +197,14 @@ func dial(ldapConf *config.LdapLogin) (*ldap.Conn, error) {
InsecureSkipVerify: ldapConf.SkipTLSVerify,
}
if ldapConf.SecurityProtocol == "LDAPS" {
conn, err := ldap.DialTLS("tcp", addr, tlsConfig)
conn, err := ldap.DialURL("ldaps://"+addr, ldap.DialWithTLSConfig(tlsConfig))
if err != nil {
return nil, errors.Errorf("dial TLS: %v", err)
}
return conn, nil
}
conn, err := ldap.Dial("tcp", addr)
conn, err := ldap.DialURL("ldap://" + addr)
if err != nil {
return nil, errors.Errorf("dial: %v", err)
}

View File

@@ -15,7 +15,6 @@ var En = map[i18n.MsgId]string{
ErrOtpCheckRestrict: "Two-factor validation failed more than 5 times. Try again in 10 minutes",
ErrOtpCheckFail: "Two-factor authentication authorization code is incorrect",
ErrAccountNotAvailable: "Account is not available",
LoginMsg: "Log in to [{{.ip}}]-[{{.time}}]",
ErrUsernameOrPwdErr: "Wrong username or password",
ErrOauth2NoAutoRegister: "the system does not enable automatic registration, please ask the administrator to add the corresponding account first",
}

View File

@@ -23,7 +23,6 @@ const (
ErrOtpCheckRestrict
ErrOtpCheckFail
ErrAccountNotAvailable
LoginMsg
ErrUsernameOrPwdErr
ErrOauth2NoAutoRegister
)

View File

@@ -15,7 +15,6 @@ var Zh_CN = map[i18n.MsgId]string{
ErrOtpCheckRestrict: "双因素校验失败超过5次, 请10分钟后再试",
ErrOtpCheckFail: "双因素认证授权码不正确",
ErrAccountNotAvailable: "账号不可用",
LoginMsg: "于[{{.ip}}]-[{{.time}}]登录",
ErrUsernameOrPwdErr: "用户名或密码错误",
ErrOauth2NoAutoRegister: "系统未开启自动注册, 请先让管理员添加对应账号",
}

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/initialize"
"mayfly-go/internal/auth/api"
"mayfly-go/internal/auth/application"
"mayfly-go/internal/auth/infrastructure/persistence"
"mayfly-go/internal/auth/infra/persistence"
)
func init() {

View File

@@ -11,15 +11,13 @@ import (
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/imsg"
"mayfly-go/internal/event"
msgapp "mayfly-go/internal/msg/application"
msgdto "mayfly-go/internal/msg/application/dto"
"mayfly-go/internal/pkg/event"
"mayfly-go/internal/pkg/utils"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/global"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
@@ -36,7 +34,6 @@ type Db struct {
instanceApp application.Instance `inject:"T"`
dbApp application.Db `inject:"T"`
dbSqlExecApp application.DbSqlExec `inject:"T"`
msgApp msgapp.Msg `inject:"T"`
tagApp tagapp.TagTree `inject:"T"`
}
@@ -135,7 +132,7 @@ func (d *Db) DeleteDb(rc *req.Ctx) {
/** 数据库操作相关、执行sql等 ***/
func (d *Db) ExecSql(rc *req.Ctx) {
form := req.BindJsonAndValid[*form.DbSqlExecForm](rc)
form := req.BindJson[*form.DbSqlExecForm](rc)
ctx, cancel := context.WithTimeout(rc.MetaCtx, time.Duration(config.GetDbms().SqlExecTl)*time.Second)
defer cancel()
@@ -167,17 +164,6 @@ func (d *Db) ExecSql(rc *req.Ctx) {
rc.ResData = execRes
}
// progressCategory sql文件执行进度消息类型
const progressCategory = "execSqlFileProgress"
// progressMsg sql文件执行进度消息
type progressMsg struct {
Id string `json:"id"`
Title string `json:"title"`
ExecutedStatements int `json:"executedStatements"`
Terminated bool `json:"terminated"`
}
// 执行sql文件
func (d *Db) ExecSqlFile(rc *req.Ctx) {
multipart, err := rc.GetRequest().MultipartReader()
@@ -246,7 +232,11 @@ func (d *Db) DumpSql(rc *req.Ctx) {
if len(msg) > 0 {
msg = "DB dump error: " + msg
rc.GetWriter().Write([]byte(msg))
d.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.DbDumpErr), msg))
global.EventBus.Publish(rc.MetaCtx, event.EventTopicMsgTmplSend, &msgdto.MsgTmplSendEvent{
TmplChannel: msgdto.MsgTmplDbDumpFail,
Params: collx.M{"error": msg},
ReceiverIds: []uint64{la.Id},
})
}
}()

View File

@@ -87,7 +87,7 @@ func (d *DataSyncTask) DeleteTask(rc *req.Ctx) {
}
func (d *DataSyncTask) ChangeStatus(rc *req.Ctx) {
form := req.BindJsonAndValid[*form.DataSyncTaskStatusForm](rc)
form := req.BindJson[*form.DataSyncTaskStatusForm](rc)
rc.ReqParam = form
task, err := d.dataSyncTaskApp.GetById(form.Id)

View File

@@ -30,7 +30,7 @@ func (d *DbSql) ReqConfs() *req.Confs {
// @router /api/db/:dbId/sql [post]
func (d *DbSql) SaveSql(rc *req.Ctx) {
dbSqlForm := req.BindJsonAndValid[*form.DbSqlSaveForm](rc)
dbSqlForm := req.BindJson[*form.DbSqlSaveForm](rc)
rc.ReqParam = dbSqlForm
dbId := getDbId(rc)

View File

@@ -93,7 +93,7 @@ func (d *DbTransferTask) DeleteTask(rc *req.Ctx) {
}
func (d *DbTransferTask) ChangeStatus(rc *req.Ctx) {
form := req.BindJsonAndValid[*form.DbTransferTaskStatusForm](rc)
form := req.BindJson[*form.DbTransferTaskStatusForm](rc)
rc.ReqParam = form
task, err := d.dbTransferTaskApp.GetById(form.Id)
@@ -136,7 +136,7 @@ func (d *DbTransferTask) FileDel(rc *req.Ctx) {
}
func (d *DbTransferTask) FileRun(rc *req.Ctx) {
fm := req.BindJsonAndValid[*form.DbTransferFileRunForm](rc)
fm := req.BindJson[*form.DbTransferFileRunForm](rc)
rc.ReqParam = fm

View File

@@ -13,19 +13,19 @@ import (
"mayfly-go/internal/db/imsg"
flowapp "mayfly-go/internal/flow/application"
flowentity "mayfly-go/internal/flow/domain/entity"
msgapp "mayfly-go/internal/msg/application"
msgdto "mayfly-go/internal/msg/application/dto"
"mayfly-go/internal/pkg/event"
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/jsonx"
"mayfly-go/pkg/utils/stringx"
"mayfly-go/pkg/ws"
"strings"
"time"
)
type sqlExecParam struct {
@@ -71,7 +71,6 @@ type dbSqlExecAppImpl struct {
dbSqlExecRepo repository.DbSqlExec `inject:"T"`
flowProcdefApp flowapp.Procdef `inject:"T"`
msgApp msgapp.Msg `inject:"T"`
}
func createSqlExecRecord(ctx context.Context, execSqlReq *dto.DbSqlExecReq, sql string) *entity.DbSqlExec {
@@ -206,38 +205,55 @@ func (d *dbSqlExecAppImpl) ExecReader(ctx context.Context, execReader *dto.SqlRe
la := contextx.GetLoginAccount(ctx)
needSendMsg := la != nil && clientId != ""
startTime := time.Now()
executedStatements := 0
progressId := stringx.Rand(32)
msgEvent := &msgdto.MsgTmplSendEvent{
TmplChannel: msgdto.MsgTmplSqlScriptRunSuccess,
Params: collx.M{"filename": filename, "db": dbConn.Info.GetLogDesc()},
}
progressMsgEvent := &msgdto.MsgTmplSendEvent{
TmplChannel: msgdto.MsgTmplSqlScriptRunProgress,
Params: collx.M{
"id": progressId,
"title": filename,
"executedStatements": executedStatements,
"terminated": false,
"clientId": clientId,
},
}
if needSendMsg {
msgEvent.ReceiverIds = []uint64{la.Id}
progressMsgEvent.ReceiverIds = []uint64{la.Id}
}
defer func() {
if needSendMsg {
progressMsgEvent.Params["terminated"] = true
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, progressMsgEvent)
}
if err := recover(); err != nil {
errInfo := anyx.ToString(err)
logx.Errorf("exec sql reader error: %s", errInfo)
if needSendMsg {
errInfo = stringx.Truncate(errInfo, 300, 10, "...")
d.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.SqlScriptRunFail), fmt.Sprintf("[%s][%s] execution failure: [%s]", filename, dbConn.Info.GetLogDesc(), errInfo)).WithClientId(clientId))
msgEvent.TmplChannel = msgdto.MsgTmplSqlScriptRunFail
msgEvent.Params["error"] = errInfo
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
}
}
}()
executedStatements := 0
progressId := stringx.Rand(32)
if needSendMsg {
defer ws.SendJsonMsg(ws.UserId(la.Id), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
Id: progressId,
Title: filename,
ExecutedStatements: executedStatements,
Terminated: true,
}).WithCategory(progressCategory))
}
tx, _ := dbConn.Begin()
err := sqlparser.SQLSplit(execReader.Reader, ';', func(sql string) error {
if executedStatements%50 == 0 {
if needSendMsg {
ws.SendJsonMsg(ws.UserId(la.Id), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
Id: progressId,
Title: filename,
ExecutedStatements: executedStatements,
Terminated: false,
}).WithCategory(progressCategory))
progressMsgEvent.Params["executedStatements"] = executedStatements
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, progressMsgEvent)
}
}
@@ -249,12 +265,18 @@ func (d *dbSqlExecAppImpl) ExecReader(ctx context.Context, execReader *dto.SqlRe
})
if err != nil {
_ = tx.Rollback()
if needSendMsg {
msgEvent.TmplChannel = msgdto.MsgTmplSqlScriptRunFail
msgEvent.Params["error"] = err.Error()
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
}
return err
}
_ = tx.Commit()
if needSendMsg {
d.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.T(imsg.SqlScriptRunSuccess), "execution success").WithClientId(clientId))
msgEvent.Params["cost"] = fmt.Sprintf("%dms", time.Since(startTime).Milliseconds())
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
}
return nil
}

View File

@@ -14,10 +14,7 @@ var En = map[i18n.MsgId]string{
LogDbRunSql: "DB - Run SQL",
LogDbDump: "DB - Export DB",
SqlScriptRunFail: "sql script failed to execute",
SqlScriptRunSuccess: "sql script executed successfully",
SqlScripRunProgress: "sql execution progress",
DbDumpErr: "Database export failed",
ErrDbNameExist: "The database name already exists in this instance",
ErrDbNotAccess: "The operation permissions of database [{{.dbName}}] are not configured",

View File

@@ -24,10 +24,7 @@ const (
LogDbRunSqlFile
LogDbDump
SqlScriptRunFail
SqlScriptRunSuccess
SqlScripRunProgress
DbDumpErr
ErrDbNameExist
ErrDbNotAccess

View File

@@ -14,10 +14,7 @@ var Zh_CN = map[i18n.MsgId]string{
LogDbRunSql: "DB-运行SQL",
LogDbDump: "DB-导出数据库",
SqlScriptRunFail: "sql脚本执行失败",
SqlScriptRunSuccess: "sql脚本执行成功",
SqlScripRunProgress: "sql执行进度",
DbDumpErr: "数据库导出失败",
ErrDbNameExist: "该实例下数据库名已存在",
ErrDbNotAccess: "未配置数据库【{{.dbName}}】的操作权限",

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/initialize"
"mayfly-go/internal/db/api"
"mayfly-go/internal/db/application"
"mayfly-go/internal/db/infrastructure/persistence"
"mayfly-go/internal/db/infra/persistence"
)
func init() {

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/initialize"
"mayfly-go/internal/es/api"
"mayfly-go/internal/es/application"
"mayfly-go/internal/es/infrastructure/persistence"
"mayfly-go/internal/es/infra/persistence"
)
func init() {

View File

@@ -1,15 +0,0 @@
package persistence
import (
"mayfly-go/internal/file/domain/entity"
"mayfly-go/internal/file/domain/repository"
"mayfly-go/pkg/base"
)
type fileRepoImpl struct {
base.RepoImpl[*entity.File]
}
func newFileRepo() repository.File {
return &fileRepoImpl{}
}

View File

@@ -1,9 +0,0 @@
package persistence
import (
"mayfly-go/pkg/ioc"
)
func InitIoc() {
ioc.Register(newFileRepo(), ioc.WithComponentName("FileRepo"))
}

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/initialize"
"mayfly-go/internal/file/api"
"mayfly-go/internal/file/application"
"mayfly-go/internal/file/infrastructure/persistence"
"mayfly-go/internal/file/infra/persistence"
)
func init() {

View File

@@ -97,7 +97,7 @@ func (a *Procdef) Save(rc *req.Ctx) {
}
func (a *Procdef) SaveFlowDef(rc *req.Ctx) {
form := req.BindJsonAndValid[*form.ProcdefFlow](rc)
form := req.BindJson[*form.ProcdefFlow](rc)
rc.ReqParam = form
biz.ErrIsNil(a.procdefApp.SaveFlowDef(rc.MetaCtx, &dto.SaveFlowDef{

View File

@@ -47,7 +47,7 @@ func (p *Procinst) GetProcinstPage(rc *req.Ctx) {
}
func (p *Procinst) ProcinstStart(rc *req.Ctx) {
startForm := req.BindJsonAndValid[*form.ProcinstStart](rc)
startForm := req.BindJson[*form.ProcinstStart](rc)
_, err := p.procinstApp.StartProc(rc.MetaCtx, startForm.ProcdefId, &dto.StarProc{
BizType: startForm.BizType,
BizForm: jsonx.ToStr(startForm.BizForm),

View File

@@ -74,7 +74,7 @@ func (p *ProcinstTask) GetTasks(rc *req.Ctx) {
}
func (p *ProcinstTask) PassTask(rc *req.Ctx) {
auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc)
auditForm := req.BindJson[*form.ProcinstTaskAudit](rc)
rc.ReqParam = auditForm
la := rc.GetLoginAccount()
@@ -84,7 +84,7 @@ func (p *ProcinstTask) PassTask(rc *req.Ctx) {
}
func (p *ProcinstTask) RejectTask(rc *req.Ctx) {
auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc)
auditForm := req.BindJson[*form.ProcinstTaskAudit](rc)
rc.ReqParam = auditForm
la := rc.GetLoginAccount()
@@ -94,7 +94,7 @@ func (p *ProcinstTask) RejectTask(rc *req.Ctx) {
}
func (p *ProcinstTask) BackTask(rc *req.Ctx) {
auditForm := req.BindJsonAndValid[*form.ProcinstTaskAudit](rc)
auditForm := req.BindJson[*form.ProcinstTaskAudit](rc)
rc.ReqParam = auditForm
biz.ErrIsNil(p.procinstTaskApp.BackTask(rc.MetaCtx, dto.UserTaskOp{TaskId: auditForm.Id, Remark: auditForm.Remark}))
}

View File

@@ -2,11 +2,11 @@ package application
import (
"context"
"mayfly-go/internal/event"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/imsg"
"mayfly-go/internal/flow/infrastructure/persistence"
"mayfly-go/internal/flow/infra/persistence"
msgdto "mayfly-go/internal/msg/application/dto"
"mayfly-go/internal/pkg/event"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/global"
"strings"

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/initialize"
"mayfly-go/internal/flow/api"
"mayfly-go/internal/flow/application"
"mayfly-go/internal/flow/infrastructure/persistence"
"mayfly-go/internal/flow/infra/persistence"
)
func init() {

View File

@@ -2,7 +2,6 @@ package api
import (
"fmt"
"mayfly-go/internal/event"
"mayfly-go/internal/machine/api/form"
"mayfly-go/internal/machine/api/vo"
"mayfly-go/internal/machine/application"
@@ -12,6 +11,7 @@ import (
"mayfly-go/internal/machine/imsg"
"mayfly-go/internal/machine/mcm"
"mayfly-go/internal/pkg/consts"
"mayfly-go/internal/pkg/event"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"

View File

@@ -12,15 +12,14 @@ import (
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/imsg"
"mayfly-go/internal/machine/mcm"
msgapp "mayfly-go/internal/msg/application"
msgdto "mayfly-go/internal/msg/application/dto"
"mayfly-go/internal/pkg/event"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/global"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/timex"
"mime/multipart"
@@ -36,7 +35,6 @@ import (
type MachineFile struct {
machineFileApp application.MachineFile `inject:"T"`
msgApp msgapp.Msg `inject:"T"`
}
func (mf *MachineFile) ReqConfs() *req.Confs {
@@ -106,7 +104,7 @@ func (m *MachineFile) DeleteFile(rc *req.Ctx) {
/*** sftp相关操作 */
func (m *MachineFile) CreateFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid[*form.CreateFileForm](rc)
opForm := req.BindJson[*form.CreateFileForm](rc)
path := opForm.Path
attrs := collx.Kvs("path", path)
@@ -239,7 +237,7 @@ func (m *MachineFile) GetFileStat(rc *req.Ctx) {
}
func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
opForm := req.BindJsonAndValid[*form.WriteFileContentForm](rc)
opForm := req.BindJson[*form.WriteFileContentForm](rc)
path := opForm.Path
mi, err := m.machineFileApp.WriteFileContent(rc.MetaCtx, opForm.MachineFileOp, []byte(opForm.Content))
@@ -264,14 +262,6 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
file, _ := fileheader.Open()
defer file.Close()
la := rc.GetLoginAccount()
defer func() {
if anyx.ToString(recover()) != "" {
logx.Errorf("upload file error: %s", err)
m.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.TC(ctx, imsg.ErrFileUploadFail), fmt.Sprintf("%s: \n<-e : %s", i18n.TC(ctx, imsg.ErrFileUploadFail), err)))
}
}()
opForm := &dto.MachineFileOp{
MachineId: machineId,
AuthCertName: authCertName,
@@ -281,9 +271,27 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
mi, err := m.machineFileApp.UploadFile(ctx, opForm, fileheader.Filename, file)
rc.ReqParam = collx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", path, fileheader.Filename))
// 发送文件上传结果消息
msgEvent := &msgdto.MsgTmplSendEvent{
TmplChannel: msgdto.MsgTmplMachineFileUploadSuccess,
Params: collx.M{
"filename": fileheader.Filename,
"path": path,
},
ReceiverIds: []uint64{rc.GetLoginAccount().Id},
}
if err != nil {
msgEvent.Params["error"] = err.Error()
msgEvent.TmplChannel = msgdto.MsgTmplMachineFileUploadFail
}
if mi != nil {
msgEvent.Params["machineName"] = mi.Name
msgEvent.Params["machineIp"] = mi.Ip
}
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
biz.ErrIsNilAppendErr(err, "upload file error: %s")
// 保存消息并发送文件上传成功通知
m.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.TC(ctx, imsg.MsgUploadFileSuccess), fmt.Sprintf("[%s] -> %s[%s:%s]", fileheader.Filename, mi.Name, mi.Ip, path)))
}
type FolderFile struct {
@@ -350,6 +358,17 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
}
}
msgEvent := &msgdto.MsgTmplSendEvent{
TmplChannel: msgdto.MsgTmplMachineFileUploadSuccess,
Params: collx.M{
"filename": folderName,
"path": basePath,
"machineName": mi.Name,
"machineIp": mi.Ip,
},
ReceiverIds: []uint64{rc.GetLoginAccount().Id},
}
// 分组处理
groupNum := 30
chunks := collx.ArraySplit(folderFiles, groupNum)
@@ -359,7 +378,6 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
wg.Add(len(chunks))
isSuccess := true
la := rc.GetLoginAccount()
for _, chunk := range chunks {
go func(files []FolderFile, wg *sync.WaitGroup) {
defer func() {
@@ -370,7 +388,11 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
logx.Errorf("upload file error: %s", err)
switch t := err.(type) {
case *errorx.BizError:
m.msgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.TC(ctx, imsg.ErrFileUploadFail), fmt.Sprintf("%s: \n<-e errCode: %d, errMsg: %s", i18n.TC(ctx, imsg.ErrFileUploadFail), t.Code(), t.Error())))
{
msgEvent.TmplChannel = msgdto.MsgTmplMachineFileUploadFail
msgEvent.Params["error"] = t.Error()
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
}
}
}
}()
@@ -394,13 +416,12 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
// 等待所有协程执行完成
wg.Wait()
if isSuccess {
// 保存消息并发送文件上传成功通知
m.msgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.TC(ctx, imsg.MsgUploadFileSuccess), fmt.Sprintf("[%s] -> %s[%s:%s]", folderName, mi.Name, mi.Ip, basePath)))
global.EventBus.Publish(ctx, event.EventTopicMsgTmplSend, msgEvent)
}
}
func (m *MachineFile) RemoveFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid[*form.RemoveFileForm](rc)
opForm := req.BindJson[*form.RemoveFileForm](rc)
mi, err := m.machineFileApp.RemoveFile(rc.MetaCtx, opForm.MachineFileOp, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "path", opForm)
@@ -408,21 +429,21 @@ func (m *MachineFile) RemoveFile(rc *req.Ctx) {
}
func (m *MachineFile) CopyFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid[*form.CopyFileForm](rc)
opForm := req.BindJson[*form.CopyFileForm](rc)
mi, err := m.machineFileApp.Copy(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...)
biz.ErrIsNilAppendErr(err, "file copy error: %s")
rc.ReqParam = collx.Kvs("machine", mi, "cp", opForm)
}
func (m *MachineFile) MvFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid[*form.CopyFileForm](rc)
opForm := req.BindJson[*form.CopyFileForm](rc)
mi, err := m.machineFileApp.Mv(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "mv", opForm)
biz.ErrIsNilAppendErr(err, "file move error: %s")
}
func (m *MachineFile) Rename(rc *req.Ctx) {
renameForm := req.BindJsonAndValid[*form.RenameForm](rc)
renameForm := req.BindJson[*form.RenameForm](rc)
mi, err := m.machineFileApp.Rename(rc.MetaCtx, renameForm.MachineFileOp, renameForm.Newname)
rc.ReqParam = collx.Kvs("machine", mi, "rename", renameForm)
biz.ErrIsNilAppendErr(err, "file rename error: %s")

View File

@@ -7,7 +7,7 @@ import (
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/internal/machine/imsg"
"mayfly-go/internal/machine/infrastructure/cache"
"mayfly-go/internal/machine/infra/cache"
"mayfly-go/internal/machine/mcm"
tagapp "mayfly-go/internal/tag/application"
tagdto "mayfly-go/internal/tag/application/dto"

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/initialize"
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/application"
"mayfly-go/internal/machine/infrastructure/persistence"
"mayfly-go/internal/machine/infra/persistence"
)
func init() {

View File

@@ -2,13 +2,13 @@ package api
import (
"context"
"mayfly-go/internal/event"
"mayfly-go/internal/mongo/api/form"
"mayfly-go/internal/mongo/api/vo"
"mayfly-go/internal/mongo/application"
"mayfly-go/internal/mongo/domain/entity"
"mayfly-go/internal/mongo/imsg"
"mayfly-go/internal/pkg/consts"
"mayfly-go/internal/pkg/event"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
@@ -146,7 +146,7 @@ func (m *Mongo) Collections(rc *req.Ctx) {
}
func (m *Mongo) RunCommand(rc *req.Ctx) {
commandForm := req.BindJsonAndValid[*form.MongoRunCommand](rc)
commandForm := req.BindJson[*form.MongoRunCommand](rc)
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
biz.ErrIsNil(err)
@@ -176,7 +176,7 @@ func (m *Mongo) RunCommand(rc *req.Ctx) {
}
func (m *Mongo) FindCommand(rc *req.Ctx) {
commandForm := req.BindJsonAndValid[*form.MongoFindCommand](rc)
commandForm := req.BindJson[*form.MongoFindCommand](rc)
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
biz.ErrIsNil(err)
@@ -211,7 +211,7 @@ func (m *Mongo) FindCommand(rc *req.Ctx) {
}
func (m *Mongo) UpdateByIdCommand(rc *req.Ctx) {
commandForm := req.BindJsonAndValid[*form.MongoUpdateByIdCommand](rc)
commandForm := req.BindJson[*form.MongoUpdateByIdCommand](rc)
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
biz.ErrIsNil(err)
@@ -235,7 +235,7 @@ func (m *Mongo) UpdateByIdCommand(rc *req.Ctx) {
}
func (m *Mongo) DeleteByIdCommand(rc *req.Ctx) {
commandForm := req.BindJsonAndValid[*form.MongoUpdateByIdCommand](rc)
commandForm := req.BindJson[*form.MongoUpdateByIdCommand](rc)
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
biz.ErrIsNil(err)
@@ -258,7 +258,7 @@ func (m *Mongo) DeleteByIdCommand(rc *req.Ctx) {
}
func (m *Mongo) InsertOneCommand(rc *req.Ctx) {
commandForm := req.BindJsonAndValid[*form.MongoInsertCommand](rc)
commandForm := req.BindJson[*form.MongoInsertCommand](rc)
conn, err := m.mongoApp.GetMongoConn(rc.MetaCtx, m.GetMongoId(rc))
biz.ErrIsNil(err)

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/initialize"
"mayfly-go/internal/mongo/api"
"mayfly-go/internal/mongo/application"
"mayfly-go/internal/mongo/infrastructure/persistence"
"mayfly-go/internal/mongo/infra/persistence"
)
func init() {

View File

@@ -14,12 +14,14 @@ type Msg struct {
func (m *Msg) ReqConfs() *req.Confs {
reqs := [...]*req.Conf{
req.NewGet("/self", m.GetMsgs),
req.NewGet("/self/unread/count", m.GetUnreadCount),
req.NewGet("/self/read", m.ReadMsg),
}
return req.NewConfs("/msgs", reqs[:]...)
}
// 获取账号接收的消息列表
// GetMsgs 获取账号接收的消息列表
func (m *Msg) GetMsgs(rc *req.Ctx) {
condition := &entity.Msg{
RecipientId: int64(rc.GetLoginAccount().Id),
@@ -28,3 +30,25 @@ func (m *Msg) GetMsgs(rc *req.Ctx) {
biz.ErrIsNil(err)
rc.ResData = res
}
// GetUnreadCount 获取账号接收的未读消息数量
func (m *Msg) GetUnreadCount(rc *req.Ctx) {
condition := &entity.Msg{
RecipientId: int64(rc.GetLoginAccount().Id),
Status: entity.MsgStatusUnRead,
}
rc.ResData = m.msgApp.CountByCond(condition)
}
// ReadMsg 将账号接收的未读消息标记为已读
func (m *Msg) ReadMsg(rc *req.Ctx) {
cond := &entity.Msg{
RecipientId: int64(rc.GetLoginAccount().Id),
Status: entity.MsgStatusUnRead,
}
cond.Id = uint64(rc.QueryInt("id"))
biz.ErrIsNil(m.msgApp.UpdateByCond(rc.MetaCtx, &entity.Msg{
Status: entity.MsgStatusRead,
}, cond))
}

View File

@@ -74,7 +74,7 @@ func (m *MsgTmpl) DelMsgTmpls(rc *req.Ctx) {
func (m *MsgTmpl) SendMsg(rc *req.Ctx) {
code := rc.PathParam("code")
form := req.BindJsonAndValid[*form.SendMsg](rc)
form := req.BindJson[*form.SendMsg](rc)
rc.ReqParam = form

View File

@@ -15,3 +15,7 @@ func InitIoc() {
func GetMsgApp() Msg {
return ioc.Get[Msg]("MsgApp")
}
func GetMsgTmplApp() MsgTmpl {
return ioc.Get[MsgTmpl]("MsgTmplApp")
}

View File

@@ -2,35 +2,93 @@ package dto
import (
"mayfly-go/internal/msg/domain/entity"
"mayfly-go/internal/msg/imsg"
"mayfly-go/internal/msg/msgx"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
)
type MsgTmplSave struct {
model.ExtraData
Id uint64 `json:"id"`
Name string `json:"name"`
Remark string `json:"remark"`
Status entity.MsgTmplStatus `json:"status" `
Title string `json:"title"`
Tmpl string `json:"type"`
MsgType msgx.MsgType `json:"msgType"`
ChannelIds []uint64 `json:"channelIds"`
}
// MsgTmplBizSave 消息模板关联业务信息
type MsgTmplBizSave struct {
TmplId uint64 // 消息模板id
BizId uint64 // 业务id
BizType string
}
// BizMsgTmplSend 业务消息模板发送消息
type BizMsgTmplSend struct {
BizId uint64 // 业务id
BizType string
type MsgTmplSendEvent struct {
TmplChannel *MsgTmplChannel
Params map[string]any // 模板占位符参数
ReceiverIds []uint64 // 接收人id
}
type MsgTmplChannel struct {
Tmpl *entity.MsgTmpl
Channels []*entity.MsgChannel
}
var (
MsgChannelSite = &entity.MsgChannel{
Type: msgx.ChannelTypeSiteMsg,
Status: entity.ChannelStatusEnable,
}
MsgChannelWs = &entity.MsgChannel{
Type: msgx.ChannelTypeWs,
Status: entity.ChannelStatusEnable,
}
)
var (
MsgTmplLogin = newMsgTmpl(entity.MsgTypeNotify,
entity.MsgSubtypeUserLogin,
entity.MsgStatusRead,
imsg.LoginMsg,
MsgChannelSite)
MsgTmplMachineFileUploadSuccess = newMsgTmpl(entity.MsgTypeNotify,
entity.MsgSubtypeMachineFileUploadSuccess,
entity.MsgStatusRead,
imsg.MachineFileUploadSuccessMsg,
MsgChannelSite, MsgChannelWs)
MsgTmplMachineFileUploadFail = newMsgTmpl(entity.MsgTypeNotify,
entity.MsgSubtypeMachineFileUploadFail,
entity.MsgStatusRead,
imsg.MachineFileUploadFailMsg,
MsgChannelSite, MsgChannelWs)
MsgTmplDbDumpFail = newMsgTmpl(entity.MsgTypeNotify,
entity.MsgSubtypeDbDumpFail,
entity.MsgStatusRead,
imsg.DbDumpFailMsg,
MsgChannelSite, MsgChannelWs)
MsgTmplSqlScriptRunFail = newMsgTmpl(entity.MsgTypeNotify,
entity.MsgSubtypeSqlScriptRunFail,
entity.MsgStatusRead,
imsg.SqlScriptRunFailMsg,
MsgChannelSite, MsgChannelWs)
MsgTmplSqlScriptRunSuccess = newMsgTmpl(entity.MsgTypeNotify,
entity.MsgSubtypeSqlScriptRunSuccess,
entity.MsgStatusRead,
imsg.SqlScriptRunSuccessMsg,
MsgChannelSite, MsgChannelWs)
MsgTmplSqlScriptRunProgress = &MsgTmplChannel{
Tmpl: &entity.MsgTmpl{
ExtraData: model.ExtraData{
Extra: collx.M{
"category": "sqlScriptRunProgress",
},
},
},
Channels: []*entity.MsgChannel{MsgChannelWs},
}
)
func newMsgTmpl(mtype entity.MsgType, subtype entity.MsgSubtype, status entity.MsgStatus, msgId i18n.MsgId, channels ...*entity.MsgChannel) *MsgTmplChannel {
msgTmpl := &entity.MsgTmpl{}
msgTmpl.SetExtraValue("msgId", msgId)
msgTmpl.SetExtraValue("subtype", subtype)
msgTmpl.SetExtraValue("type", mtype)
msgTmpl.SetExtraValue("status", status)
return &MsgTmplChannel{
Tmpl: msgTmpl,
Channels: channels,
}
}

View File

@@ -0,0 +1,43 @@
package dto
import (
"mayfly-go/internal/msg/domain/entity"
"mayfly-go/internal/msg/msgx"
"mayfly-go/pkg/model"
)
type MsgTmplSave struct {
model.ExtraData
Id uint64 `json:"id"`
Name string `json:"name"`
Remark string `json:"remark"`
Status entity.MsgTmplStatus `json:"status" `
Title string `json:"title"`
Tmpl string `json:"type"`
MsgType msgx.MsgType `json:"msgType"`
ChannelIds []uint64 `json:"channelIds"`
}
// MsgTmplBizSave 消息模板关联业务信息
type MsgTmplBizSave struct {
TmplId uint64 // 消息模板id
BizId uint64 // 业务id
BizType string
}
// BizMsgTmplSend 业务消息模板发送消息
type BizMsgTmplSend struct {
BizId uint64 // 业务id
BizType string
Params map[string]any // 模板占位符参数
ReceiverIds []uint64 // 接收人id
}
type MsgTmplSend struct {
Tmpl *entity.MsgTmpl
Channels []*entity.MsgChannel
Params map[string]any // 模板占位符参数
ReceiverIds []uint64 // 接收人id
}

View File

@@ -1,54 +0,0 @@
package dto
import "mayfly-go/pkg/utils/anyx"
// ************** 系统消息 **************
const SuccessSysMsgType = 1
const ErrorSysMsgType = 0
const InfoSysMsgType = 2
// websocket消息
type SysMsg struct {
Type int `json:"type"` // 消息类型
Category string `json:"category"` // 消息类别
Title string `json:"title"` // 消息标题
Msg string `json:"msg"` // 消息内容
ClientId string
}
func (sm *SysMsg) WithTitle(title string) *SysMsg {
sm.Title = title
return sm
}
func (sm *SysMsg) WithCategory(category string) *SysMsg {
sm.Category = category
return sm
}
func (sm *SysMsg) WithMsg(msg any) *SysMsg {
sm.Msg = anyx.ToString(msg)
return sm
}
func (sm *SysMsg) WithClientId(clientId string) *SysMsg {
sm.ClientId = clientId
return sm
}
// 普通消息
func InfoSysMsg(title string, msg any) *SysMsg {
return &SysMsg{Type: InfoSysMsgType, Title: title, Msg: anyx.ToString(msg)}
}
// 成功消息
func SuccessSysMsg(title string, msg any) *SysMsg {
return &SysMsg{Type: SuccessSysMsgType, Title: title, Msg: anyx.ToString(msg)}
}
// 错误消息
func ErrSysMsg(title string, msg any) *SysMsg {
return &SysMsg{Type: ErrorSysMsgType, Title: title, Msg: anyx.ToString(msg)}
}

View File

@@ -1,27 +1,30 @@
package application
import (
"cmp"
"context"
"mayfly-go/internal/msg/application/dto"
"mayfly-go/internal/msg/domain/entity"
"mayfly-go/internal/msg/domain/repository"
"mayfly-go/internal/msg/msgx"
"mayfly-go/pkg/base"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/model"
"mayfly-go/pkg/ws"
"time"
"mayfly-go/pkg/utils/stringx"
)
type Msg interface {
msgx.MsgSender
base.App[*entity.Msg]
GetPageList(condition *entity.Msg, pageParam model.PageParam, orderBy ...string) (*model.PageResult[*entity.Msg], error)
Create(ctx context.Context, msg *entity.Msg)
// 创建消息并通过ws发送
CreateAndSend(la *model.LoginAccount, msg *dto.SysMsg)
}
var _ (Msg) = (*msgAppImpl)(nil)
type msgAppImpl struct {
base.AppImpl[*entity.Msg, repository.Msg]
msgRepo repository.Msg `inject:"T"`
}
@@ -29,13 +32,29 @@ func (a *msgAppImpl) GetPageList(condition *entity.Msg, pageParam model.PagePara
return a.msgRepo.GetPageList(condition, pageParam)
}
func (a *msgAppImpl) Create(ctx context.Context, msg *entity.Msg) {
a.msgRepo.Insert(ctx, msg)
}
func (a *msgAppImpl) Send(ctx context.Context, channel *msgx.Channel, msg *msgx.Msg) error {
// 存在i18n msgIdcontent则使用msgId翻译
if msgId := msg.TmplExtra.GetInt("msgId"); msgId != 0 {
msg.Content = i18n.TC(ctx, i18n.MsgId(msgId))
}
content, err := stringx.TemplateParse(msg.Content, msg.Params)
if err != nil {
return err
}
func (a *msgAppImpl) CreateAndSend(la *model.LoginAccount, wmsg *dto.SysMsg) {
now := time.Now()
msg := &entity.Msg{Type: 2, Msg: wmsg.Msg, RecipientId: int64(la.Id), CreateTime: &now, CreatorId: la.Id, Creator: la.Username}
a.msgRepo.Insert(context.TODO(), msg)
ws.SendJsonMsg(ws.UserId(la.Id), wmsg.ClientId, wmsg)
for _, receiver := range msg.Receivers {
msgEntity := &entity.Msg{
Msg: content,
RecipientId: int64(receiver.Id),
Type: entity.MsgType(msg.TmplExtra.GetInt("type")),
Subtype: entity.MsgSubtype(msg.TmplExtra.GetStr("subtype")),
Status: cmp.Or(entity.MsgStatus(msg.TmplExtra.GetInt("status")), entity.MsgStatusRead),
}
msgEntity.Extra = msg.Params
if err := a.Save(ctx, msgEntity); err != nil {
return err
}
}
return nil
}

View File

@@ -31,6 +31,8 @@ type MsgTmpl interface {
// Send 发送消息
Send(ctx context.Context, tmplCode string, params map[string]any, receiverId ...uint64) error
SendMsg(ctx context.Context, mts *dto.MsgTmplSend) error
// DeleteTmplChannel 删除指定渠道关联的模板
DeleteTmplChannel(ctx context.Context, channelId uint64) error
}
@@ -153,47 +155,52 @@ func (m *msgTmplAppImpl) Send(ctx context.Context, tmplCode string, params map[s
return err
}
// content, err := stringx.TemplateParse(tmpl.Tmpl, params)
// if err != nil {
// return err
// }
// toAll := len(receiverId) == 0
accounts, err := m.accountApp.GetByIds(receiverId)
if err != nil {
return err
}
return m.SendMsg(ctx, &dto.MsgTmplSend{
Tmpl: tmpl,
Channels: channels,
Params: params,
ReceiverIds: receiverId,
})
}
func (m *msgTmplAppImpl) SendMsg(ctx context.Context, mts *dto.MsgTmplSend) error {
tmpl := mts.Tmpl
msg := &msgx.Msg{
Content: tmpl.Tmpl,
Params: params,
Params: mts.Params,
Title: tmpl.Title,
Type: tmpl.MsgType,
ExtraData: tmpl.ExtraData,
TmplExtra: tmpl.Extra,
}
accounts, err := m.accountApp.GetByIds(mts.ReceiverIds)
if err != nil {
return err
}
if len(accounts) > 0 {
msg.Receivers = collx.ArrayMap(accounts, func(account *sysentity.Account) msgx.Receiver {
return msgx.Receiver{
ExtraData: account.ExtraData,
Email: account.Email,
Mobile: account.Mobile,
Id: account.Id,
Extra: account.Extra,
Email: account.Email,
Mobile: account.Mobile,
}
})
}
for _, channel := range channels {
for _, channel := range mts.Channels {
if channel.Status != entity.ChannelStatusEnable {
logx.Warnf("channel is disabled => %s", channel.Code)
continue
}
go func(channel *entity.MsgChannel) {
if err := msgx.Send(&msgx.Channel{
Type: channel.Type,
Name: channel.Name,
URL: channel.Url,
ExtraData: channel.ExtraData,
if err := msgx.Send(ctx, &msgx.Channel{
Type: channel.Type,
Name: channel.Name,
URL: channel.Url,
Extra: channel.Extra,
}, msg); err != nil {
logx.Errorf("send msg error => channel=%s, msg=%s, err -> %v", channel.Code, msg.Content, err)
}

View File

@@ -2,21 +2,49 @@ package entity
import (
"mayfly-go/pkg/model"
"time"
)
type Msg struct {
model.DeletedModel
model.ExtraData
model.CreateModel
CreateTime *time.Time `json:"createTime"`
CreatorId uint64 `json:"creatorId"`
Creator string `json:"creator" gorm:"size:50"`
Type int8 `json:"type"`
Msg string `json:"msg" gorm:"size:2000"`
RecipientId int64 `json:"recipientId"` // 接收人id-1为所有接收
Type MsgType `json:"type"` // 消息类型
Subtype MsgSubtype `json:"subtype" gorm:"size:100"` // 消息子类型
Status MsgStatus `json:"status"`
Msg string `json:"msg" gorm:"size:2000"`
RecipientId int64 `json:"recipientId"` // 接收人id-1为所有接收
}
func (a *Msg) TableName() string {
return "t_sys_msg"
}
type MsgType int8
const (
MsgTypeNotify MsgType = 1 // 通知
MsgTypeTodo MsgType = 2 // 代办
)
type MsgSubtype string
const (
// sys
MsgSubtypeUserLogin MsgSubtype = "user.login"
// machine
MsgSubtypeMachineFileUploadSuccess MsgSubtype = "machine.file.upload.success"
MsgSubtypeMachineFileUploadFail MsgSubtype = "machine.file.upload.fail"
// db
MsgSubtypeDbDumpFail MsgSubtype = "db.dump.fail"
MsgSubtypeSqlScriptRunFail MsgSubtype = "db.sqlscript.run.fail"
MsgSubtypeSqlScriptRunSuccess MsgSubtype = "db.sqlscript.run.success"
)
type MsgStatus int8
const (
MsgStatusRead MsgStatus = 1 // 已读
MsgStatusUnRead MsgStatus = -1 // 未读
)

View File

@@ -9,4 +9,13 @@ var En = map[i18n.MsgId]string{
LogMsgTmplSave: "Message template- save",
LogMsgTmplDelete: "Message template- delete",
LogMsgTmplSend: "Message template- send",
LoginMsg: "Log in to [{{.ip}}]-[{{.time}}]",
MachineFileUploadSuccessMsg: "[{{.filename}}] -> {{.machineName}}[{{.machineIp}}:{{.path}}]",
MachineFileUploadFailMsg: "[{{.filename}}] -> {{.machineName}}[{{.machineIp}}:{{.path}}]. error: {{.error}}",
DbDumpFailMsg: "Database dump failed, error: {{.error}}",
SqlScriptRunFailMsg: "Script {{.filename}} execution failed on database {{.db}}, error: {{.error}}",
SqlScriptRunSuccessMsg: "Script {{.filename}} executed successfully on database {{.db}}, cost {{.cost}}",
}

View File

@@ -17,4 +17,13 @@ const (
LogMsgTmplSave
LogMsgTmplDelete
LogMsgTmplSend
LoginMsg
MachineFileUploadSuccessMsg
MachineFileUploadFailMsg
DbDumpFailMsg
SqlScriptRunFailMsg
SqlScriptRunSuccessMsg
)

View File

@@ -9,4 +9,13 @@ var Zh_CN = map[i18n.MsgId]string{
LogMsgTmplSave: "消息模板-保存",
LogMsgTmplDelete: "消息模板-删除",
LogMsgTmplSend: "消息模板-发送",
LoginMsg: "于[{{.ip}}]-[{{.time}}]登录",
MachineFileUploadSuccessMsg: "[{{.filename}}] -> {{.machineName}}[{{.machineIp}}:{{.path}}]",
MachineFileUploadFailMsg: "[{{.filename}}] -> {{.machineName}}[{{.machineIp}}:{{.path}}]。错误信息:{{.error}}",
DbDumpFailMsg: "数据库dump失败错误信息{{.error}}",
SqlScriptRunFailMsg: "数据库 {{.db}} 的脚本 {{.filename}} 执行失败,错误:{{.error}}",
SqlScriptRunSuccessMsg: "数据库 {{.db}} 的脚本 {{.filename}} 执行成功,耗时 {{.cost}}",
}

View File

@@ -3,11 +3,12 @@ package init
import (
"context"
"mayfly-go/initialize"
"mayfly-go/internal/event"
"mayfly-go/internal/msg/api"
"mayfly-go/internal/msg/application"
"mayfly-go/internal/msg/application/dto"
"mayfly-go/internal/msg/infrastructure/persistence"
"mayfly-go/internal/msg/infra/persistence"
"mayfly-go/internal/msg/msgx"
"mayfly-go/internal/pkg/event"
"mayfly-go/pkg/eventbus"
"mayfly-go/pkg/global"
"mayfly-go/pkg/ioc"
@@ -24,9 +25,24 @@ func init() {
}
func Init() {
// 注册站内消息发送器
msgx.RegisterMsgSender(msgx.ChannelTypeSiteMsg, application.GetMsgApp())
msgTmplBizApp := ioc.Get[application.MsgTmplBiz]("MsgTmplBizApp")
global.EventBus.SubscribeAsync(event.EventTopicBizMsgTmplSend, "BizMsgTmplSend", func(ctx context.Context, event *eventbus.Event[any]) error {
return msgTmplBizApp.Send(ctx, event.Val.(dto.BizMsgTmplSend))
}, false)
msgTmplApp := ioc.Get[application.MsgTmpl]("MsgTmplApp")
global.EventBus.SubscribeAsync(event.EventTopicMsgTmplSend, "MsgTmplSend", func(ctx context.Context, event *eventbus.Event[any]) error {
eventVal := event.Val.(*dto.MsgTmplSendEvent)
return msgTmplApp.SendMsg(ctx, &dto.MsgTmplSend{
Tmpl: eventVal.TmplChannel.Tmpl,
Channels: eventVal.TmplChannel.Channels,
Params: eventVal.Params,
ReceiverIds: eventVal.ReceiverIds,
})
}, false)
}

View File

@@ -1,8 +1,9 @@
package msgx
import (
"context"
"fmt"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/collx"
)
type MsgType int8
@@ -15,6 +16,8 @@ const (
)
const (
ChannelTypeSiteMsg ChannelType = "siteMsg" // 站内消息
ChannelTypeWs ChannelType = "ws" // websocket
ChannelTypeEmail ChannelType = "email"
ChannelTypeDingBot ChannelType = "dingBot"
ChannelTypeQywxBot ChannelType = "qywxBot"
@@ -26,35 +29,35 @@ const (
)
// Send 发送消息
func Send(channel *Channel, msg *Msg) error {
func Send(ctx context.Context, channel *Channel, msg *Msg) error {
sender, err := GetMsgSender(channel.Type)
if err != nil {
return err
}
return sender.Send(channel, msg)
return sender.Send(ctx, channel, msg)
}
type Receiver struct {
model.ExtraData
Extra collx.M
Id uint64 // 接收人ID
Mobile string
Email string
}
type Msg struct {
model.ExtraData
Title string // 消息title
Type MsgType // 消息类型
Content string // 消息内容
Params map[string]any // 消息参数(替换消息中的占位符)
Title string // 消息title
Type MsgType // 消息类型
Content string // 模板消息内容
Params map[string]any // 消息参数(替换消息中的占位符)
TmplExtra collx.M // 模板消息额外信息
Receivers []Receiver // 消息接收人
}
// Channel 消息发送渠道信息
type Channel struct {
model.ExtraData
Extra collx.M
Type ChannelType // 渠道类型
Name string
@@ -64,7 +67,7 @@ type Channel struct {
// MsgSender 定义消息发送接口
type MsgSender interface {
// Send 发送消息
Send(channel *Channel, msg *Msg) error
Send(ctx context.Context, channel *Channel, msg *Msg) error
}
var messageSenders = make(map[ChannelType]MsgSender)

View File

@@ -1,6 +1,7 @@
package sender
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
@@ -40,7 +41,7 @@ type dingBotMsgResp struct {
// DingBotSender 钉钉机器人消息发送
type DingBotSender struct{}
func (d DingBotSender) Send(channel *msgx.Channel, msg *msgx.Msg) error {
func (d DingBotSender) Send(ctx context.Context, channel *msgx.Channel, msg *msgx.Msg) error {
// https://open.dingtalk.com/document/robots/custom-robot-access#title-72m-8ag-pqw
msgReq := dingBotMsgReq{}
@@ -73,7 +74,7 @@ func (d DingBotSender) Send(channel *msgx.Channel, msg *msgx.Msg) error {
}
timestamp := time.Now().UnixMilli()
sign, err := d.sign(channel.GetExtraString("secret"), timestamp)
sign, err := d.sign(channel.Extra.GetStr("secret"), timestamp)
if err != nil {
return err
}

View File

@@ -1,6 +1,7 @@
package sender
import (
"context"
"crypto/tls"
"encoding/base64"
"errors"
@@ -16,7 +17,7 @@ import (
type EmailSender struct{}
func (e EmailSender) Send(channel *msgx.Channel, msg *msgx.Msg) error {
func (e EmailSender) Send(ctx context.Context, channel *msgx.Channel, msg *msgx.Msg) error {
return e.SendEmail(channel, msg)
}
@@ -44,8 +45,8 @@ func (e EmailSender) SendEmail(channel *msgx.Channel, msg *msgx.Msg) error {
smtpPort = cast.ToInt(serverAndPort[1])
}
smtpAccount := channel.GetExtraString("smtpAccount")
smtpPassword := channel.GetExtraString("smtpPassword")
smtpAccount := channel.Extra.GetStr("smtpAccount")
smtpPassword := channel.Extra.GetStr("smtpPassword")
encodedSubject := fmt.Sprintf("=?UTF-8?B?%s?=", base64.StdEncoding.EncodeToString([]byte(subject)))
mail := []byte(fmt.Sprintf("To: %s\r\n"+

View File

@@ -1,6 +1,7 @@
package sender
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
@@ -34,7 +35,7 @@ type feishuBotMsgResp struct {
// FeishuBotSender 发送飞书机器人消息
type FeishuBotSender struct{}
func (f FeishuBotSender) Send(channel *msgx.Channel, msg *msgx.Msg) error {
func (f FeishuBotSender) Send(ctx context.Context, channel *msgx.Channel, msg *msgx.Msg) error {
// https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot
msgReq := feishuBotMsgReq{
MsgType: "text",
@@ -45,7 +46,7 @@ func (f FeishuBotSender) Send(channel *msgx.Channel, msg *msgx.Msg) error {
// 使用receiver参数替换消息内容中可能存在的接收人信息
if len(msg.Receivers) > 0 {
if to := collx.ArrayMapFilter(msg.Receivers, func(a msgx.Receiver) (string, bool) {
if uid := a.GetExtraString("feishuUserId"); uid != "" {
if uid := a.Extra.GetStr("feishuUserId"); uid != "" {
// 使用<at user_id="userId"></at>
return fmt.Sprintf(`<at user_id="%s"></at>`, uid), true
}
@@ -62,7 +63,7 @@ func (f FeishuBotSender) Send(channel *msgx.Channel, msg *msgx.Msg) error {
msgReq.Content.Text = content
if secret := channel.GetExtraString("secret"); secret != "" {
if secret := channel.Extra.GetStr("secret"); secret != "" {
timestamp := time.Now().Unix()
if sign, err := f.sign(secret, timestamp); err != nil {
return err

View File

@@ -1,6 +1,7 @@
package sender
import (
"context"
"errors"
"fmt"
"mayfly-go/internal/msg/msgx"
@@ -29,7 +30,7 @@ type qywxBotMsgResp struct {
// QywxBotSender 企业微信机器人消息发送
type QywxBotSender struct{}
func (e QywxBotSender) Send(channel *msgx.Channel, msg *msgx.Msg) error {
func (e QywxBotSender) Send(ctx context.Context, channel *msgx.Channel, msg *msgx.Msg) error {
// https://developer.work.weixin.qq.com/document/path/91770
msgReq := qywxBotMsgReq{}
@@ -38,7 +39,7 @@ func (e QywxBotSender) Send(channel *msgx.Channel, msg *msgx.Msg) error {
// 使用receiver参数替换消息内容中可能存在的接收人信息
if len(msg.Receivers) > 0 {
if to := collx.ArrayMapFilter(msg.Receivers, func(a msgx.Receiver) (string, bool) {
if uid := a.GetExtraString("qywxUserId"); uid != "" {
if uid := a.Extra.GetStr("qywxUserId"); uid != "" {
// 使用<@userId>用于@指定用户
return fmt.Sprintf("<@%s>", uid), true
}

View File

@@ -1,8 +1,11 @@
package sender
import "mayfly-go/internal/msg/msgx"
import (
"mayfly-go/internal/msg/msgx"
)
func init() {
msgx.RegisterMsgSender(msgx.ChannelTypeWs, WsSender{})
msgx.RegisterMsgSender(msgx.ChannelTypeEmail, EmailSender{})
msgx.RegisterMsgSender(msgx.ChannelTypeDingBot, DingBotSender{})
msgx.RegisterMsgSender(msgx.ChannelTypeQywxBot, QywxBotSender{})

View File

@@ -0,0 +1,40 @@
package sender
import (
"context"
"mayfly-go/internal/msg/msgx"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/utils/stringx"
"mayfly-go/pkg/ws"
"github.com/spf13/cast"
)
type WsSender struct{}
func (e WsSender) Send(ctx context.Context, channel *msgx.Channel, msg *msgx.Msg) error {
var err error
content := msg.Content
// 存在i18n msgIdcontent则使用msgId翻译
if msgId := msg.TmplExtra.GetInt("msgId"); msgId != 0 {
content = i18n.TC(ctx, i18n.MsgId(msgId))
}
if content != "" {
content, err = stringx.TemplateParse(content, msg.Params)
if err != nil {
return err
}
}
jsonMsg := msg.TmplExtra
jsonMsg["msg"] = content
jsonMsg["title"] = msg.Title
jsonMsg["params"] = msg.Params
for _, receiver := range msg.Receivers {
ws.SendJsonMsg(ws.UserId(receiver.Id), cast.ToString(msg.Params["clientId"]), jsonMsg)
}
return nil
}

View File

@@ -3,4 +3,5 @@ package event
const (
EventTopicResourceOp = "resource:op" // 资源操作主题
EventTopicBizMsgTmplSend = "biz:msgtmpl:send" // 发送业务关联的消息模板
EventTopicMsgTmplSend = "msgtmpl:send" // 发送消息模板
)

View File

@@ -1,7 +1,7 @@
package api
import (
"mayfly-go/internal/event"
"mayfly-go/internal/pkg/event"
"mayfly-go/internal/redis/api/form"
"mayfly-go/internal/redis/application/dto"
"mayfly-go/pkg/biz"

View File

@@ -17,7 +17,7 @@ import (
func (r *Redis) ScanKeys(rc *req.Ctx) {
ri := r.getRedisConn(rc)
form := req.BindJsonAndValid[*form.RedisScanForm](rc)
form := req.BindJson[*form.RedisScanForm](rc)
cmd := ri.GetCmdable()
ctx := context.Background()

View File

@@ -4,7 +4,7 @@ import (
"mayfly-go/initialize"
"mayfly-go/internal/redis/api"
"mayfly-go/internal/redis/application"
"mayfly-go/internal/redis/infrastructure/persistence"
"mayfly-go/internal/redis/infra/persistence"
)
func init() {

View File

@@ -108,7 +108,7 @@ func (a *Account) GetPermissions(rc *req.Ctx) {
func (a *Account) ChangePassword(rc *req.Ctx) {
ctx := rc.MetaCtx
form := req.BindJsonAndValid[*form.AccountChangePasswordForm](rc)
form := req.BindJson[*form.AccountChangePasswordForm](rc)
originOldPwd, err := utils.DefaultRsaDecrypt(form.OldPassword, true)
biz.ErrIsNilAppendErr(err, "Wrong to decrypt old password: %s")
@@ -307,7 +307,7 @@ func (a *Account) AccountResources(rc *req.Ctx) {
// 关联账号角色
func (a *Account) RelateRole(rc *req.Ctx) {
form := req.BindJsonAndValid[*form.AccountRoleForm](rc)
form := req.BindJson[*form.AccountRoleForm](rc)
rc.ReqParam = form
biz.ErrIsNil(a.roleApp.RelateAccountRole(rc.MetaCtx, form.Id, form.RoleId, consts.AccountRoleRelateType(form.RelateType)))
}

View File

@@ -92,7 +92,7 @@ func (r *Role) RoleResource(rc *req.Ctx) {
// 保存角色资源
func (r *Role) SaveResource(rc *req.Ctx) {
form := req.BindJsonAndValid[*form.RoleResourceForm](rc)
form := req.BindJson[*form.RoleResourceForm](rc)
rc.ReqParam = form
// 将,拼接的字符串进行切割并转换

Some files were not shown because too many files have changed in this diff Show More