feat: i18n

This commit is contained in:
meilin.huang
2024-11-20 22:43:53 +08:00
parent 74ae031853
commit 99a746085b
308 changed files with 8177 additions and 3880 deletions

View File

@@ -32,9 +32,9 @@ require (
github.com/tidwall/gjson v1.18.0
github.com/veops/go-ansiterm v0.0.5
go.mongodb.org/mongo-driver v1.16.0 // mongo
golang.org/x/crypto v0.28.0 // ssh
golang.org/x/crypto v0.29.0 // ssh
golang.org/x/oauth2 v0.23.0
golang.org/x/sync v0.8.0
golang.org/x/sync v0.9.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1
// gorm
@@ -93,8 +93,8 @@ require (
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect
golang.org/x/image v0.13.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"mayfly-go/internal/auth/api/form"
"mayfly-go/internal/auth/config"
"mayfly-go/internal/common/utils"
"mayfly-go/internal/auth/imsg"
msgapp "mayfly-go/internal/msg/application"
sysapp "mayfly-go/internal/sys/application"
sysentity "mayfly-go/internal/sys/domain/entity"
@@ -33,12 +33,13 @@ type AccountLogin struct {
// @router /auth/accounts/login [post]
func (a *AccountLogin) Login(rc *req.Ctx) {
loginForm := req.BindJsonAndValid(rc, new(form.LoginForm))
ctx := rc.MetaCtx
accountLoginSecurity := config.GetAccountLoginSecurity()
// 判断是否有开启登录验证码校验
if accountLoginSecurity.UseCaptcha {
// 校验验证码
biz.IsTrue(captcha.Verify(loginForm.Cid, loginForm.Captcha), "验证码错误")
biz.IsTrueI(ctx, captcha.Verify(loginForm.Cid, loginForm.Captcha), imsg.ErrCaptchaErr)
}
username := loginForm.Username
@@ -47,7 +48,7 @@ func (a *AccountLogin) Login(rc *req.Ctx) {
rc.ReqParam = collx.Kvs("username", username, "ip", clientIp)
originPwd, err := cryptox.DefaultRsaDecrypt(loginForm.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
biz.ErrIsNilAppendErr(err, "decryption password error: %s")
account := &sysentity.Account{Username: username}
err = a.AccountApp.GetByCond(model.NewModelCond(account).Columns("Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret"))
@@ -56,17 +57,17 @@ func (a *AccountLogin) Login(rc *req.Ctx) {
nowFailCount := cache.GetInt(failCountKey)
loginFailCount := accountLoginSecurity.LoginFailCount
loginFailMin := accountLoginSecurity.LoginFailMin
biz.IsTrue(nowFailCount < loginFailCount, "登录失败超过%d次, 请%d分钟后再试", loginFailCount, loginFailMin)
biz.IsTrueI(ctx, nowFailCount < loginFailCount, imsg.ErrLoginRestrict, "failCount", loginFailCount, "min", loginFailMin)
if err != nil || !cryptox.CheckPwdHash(originPwd, account.Password) {
nowFailCount++
cache.SetStr(failCountKey, strconv.Itoa(nowFailCount), time.Minute*time.Duration(loginFailMin))
panic(errorx.NewBiz(fmt.Sprintf("用户名或密码错误【当前登录失败%d次】", nowFailCount)))
panic(errorx.NewBizI(ctx, imsg.ErrLoginFail, "failCount", nowFailCount))
}
// 校验密码强度(新用户第一次登录密码与账号名一致)
biz.IsTrueBy(utils.CheckAccountPasswordLever(originPwd), errorx.NewBizCode(401, "您的密码安全等级较低,请修改后重新登录"))
rc.ResData = LastLoginCheck(account, accountLoginSecurity, clientIp)
// biz.IsTrueBy(utils.CheckAccountPasswordLever(originPwd), errorx.NewBizCode(401, "您的密码安全等级较低,请修改后重新登录"))
rc.ResData = LastLoginCheck(ctx, account, accountLoginSecurity, clientIp)
}
type OtpVerifyInfo struct {
@@ -82,15 +83,16 @@ type OtpVerifyInfo struct {
func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
otpVerify := new(form.OtpVerfiy)
req.BindJsonAndValid(rc, otpVerify)
ctx := rc.MetaCtx
tokenKey := fmt.Sprintf("otp:token:%s", otpVerify.OtpToken)
otpInfo := new(OtpVerifyInfo)
ok := cache.Get(tokenKey, otpInfo)
biz.IsTrue(ok, "otpToken错误或失效, 请重新登陆获取")
biz.IsTrueI(ctx, ok, imsg.ErrOtpTokenInvalid)
failCountKey := fmt.Sprintf("account:otp:failcount:%d", otpInfo.AccountId)
failCount := cache.GetInt(failCountKey)
biz.IsTrue(failCount < 5, "双因素校验失败超过5次, 请10分钟后再试")
biz.IsTrueI(ctx, failCount < 5, imsg.ErrOtpCheckRestrict)
otpStatus := otpInfo.OptStatus
accessToken := otpInfo.AccessToken
@@ -99,7 +101,7 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
if !otp.Validate(otpVerify.Code, otpSecret) {
cache.SetStr(failCountKey, strconv.Itoa(failCount+1), time.Minute*time.Duration(10))
panic(errorx.NewBiz("双因素认证授权码不正确"))
panic(errorx.NewBizI(ctx, imsg.ErrOtpCheckFail))
}
// 如果是未注册状态则更新account表的otpSecret信息
@@ -112,7 +114,7 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
la := &sysentity.Account{Username: otpInfo.Username}
la.Id = accountId
go saveLogin(la, getIpAndRegion(rc))
go saveLogin(ctx, la, getIpAndRegion(rc))
cache.Del(tokenKey)
rc.ResData = collx.Kvs("token", accessToken, "refresh_token", otpInfo.RefreshToken)
@@ -120,7 +122,7 @@ func (a *AccountLogin) OtpVerify(rc *req.Ctx) {
func (a *AccountLogin) RefreshToken(rc *req.Ctx) {
refreshToken := rc.Query("refresh_token")
biz.NotEmpty(refreshToken, "refresh_token不能为空")
biz.NotEmpty(refreshToken, "refresh_token cannot be empty")
accountId, username, err := req.ParseToken(refreshToken)
biz.IsTrueBy(err == nil, errorx.PermissionErr)

View File

@@ -4,12 +4,14 @@ import (
"context"
"fmt"
"mayfly-go/internal/auth/config"
"mayfly-go/internal/auth/imsg"
msgapp "mayfly-go/internal/msg/application"
msgentity "mayfly-go/internal/msg/domain/entity"
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/otp"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
@@ -27,8 +29,8 @@ const (
)
// 最后的登录校验共用。校验通过返回登录成功响应结果map
func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.AccountLoginSecurity, loginIp string) map[string]any {
biz.IsTrue(account.IsEnable(), "该账号不可用")
func LastLoginCheck(ctx context.Context, account *sysentity.Account, accountLoginSecurity *config.AccountLoginSecurity, loginIp string) map[string]any {
biz.IsTrueI(ctx, account.IsEnable(), imsg.ErrAccountNotAvailable)
username := account.Username
res := collx.M{
@@ -42,7 +44,7 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
otpStatus := OtpStatusNone
// 访问系统使用的token
accessToken, refreshToken, err := req.CreateToken(account.Id, username)
biz.ErrIsNilAppendErr(err, "token创建失败: %s")
biz.ErrIsNilAppendErr(err, "token create failed: %s")
// 若系统配置中设置开启otp双因素校验则进行otp校验
if accountLoginSecurity.UseOtp {
@@ -56,7 +58,7 @@ func LastLoginCheck(account *sysentity.Account, accountLoginSecurity *config.Acc
res["refresh_token"] = refreshToken
// 不进行otp二次校验则直接返回accessToken
// 保存登录消息
go saveLogin(account, loginIp)
go saveLogin(ctx, account, loginIp)
}
// 赋值otp状态
@@ -80,7 +82,7 @@ func useOtp(account *sysentity.Account, otpIssuer, accessToken string, refreshTo
AccountName: account.Username,
Issuer: otpIssuer,
})
biz.ErrIsNilAppendErr(err, "otp生成失败: %s")
biz.ErrIsNilAppendErr(err, "otp generate failed: %s")
otpUrl = key.URL()
otpSecret = key.Secret()
}
@@ -104,7 +106,7 @@ func getIpAndRegion(rc *req.Ctx) string {
}
// 保存更新账号登录信息
func saveLogin(account *sysentity.Account, ip string) {
func saveLogin(ctx context.Context, account *sysentity.Account, ip string) {
// 更新账号最后登录时间
now := time.Now()
updateAccount := &sysentity.Account{LastLoginTime: &now}
@@ -116,7 +118,7 @@ func saveLogin(account *sysentity.Account, ip string) {
// 创建登录消息
loginMsg := &msgentity.Msg{
RecipientId: int64(account.Id),
Msg: fmt.Sprintf("于[%s]-[%s]登录", ip, timex.DefaultFormat(now)),
Msg: i18n.TC(ctx, imsg.LoginMsg, "ip", ip, "time", timex.DefaultFormat(now)),
Type: 1,
}
loginMsg.CreateTime = &now

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"mayfly-go/internal/auth/api/form"
"mayfly-go/internal/auth/config"
"mayfly-go/internal/auth/imsg"
msgapp "mayfly-go/internal/msg/application"
sysapp "mayfly-go/internal/sys/application"
sysentity "mayfly-go/internal/sys/domain/entity"
@@ -40,12 +41,12 @@ func (a *LdapLogin) GetLdapEnabled(rc *req.Ctx) {
// @router /auth/ldap/login [post]
func (a *LdapLogin) Login(rc *req.Ctx) {
loginForm := req.BindJsonAndValid(rc, new(form.LoginForm))
ctx := rc.MetaCtx
accountLoginSecurity := config.GetAccountLoginSecurity()
// 判断是否有开启登录验证码校验
if accountLoginSecurity.UseCaptcha {
// 校验验证码
biz.IsTrue(captcha.Verify(loginForm.Cid, loginForm.Captcha), "验证码错误")
biz.IsTrueI(ctx, captcha.Verify(loginForm.Cid, loginForm.Captcha), imsg.ErrCaptchaErr)
}
username := loginForm.Username
@@ -54,27 +55,27 @@ func (a *LdapLogin) Login(rc *req.Ctx) {
rc.ReqParam = collx.Kvs("username", username, "ip", clientIp)
originPwd, err := cryptox.DefaultRsaDecrypt(loginForm.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
biz.ErrIsNilAppendErr(err, "decryption password error: %s")
// LDAP 用户本地密码为空,不允许本地登录
biz.NotEmpty(originPwd, "密码不能为空")
biz.NotEmpty(originPwd, "password cannot be empty")
failCountKey := fmt.Sprintf("account:login:failcount:%s", username)
nowFailCount := cache.GetInt(failCountKey)
loginFailCount := accountLoginSecurity.LoginFailCount
loginFailMin := accountLoginSecurity.LoginFailMin
biz.IsTrue(nowFailCount < loginFailCount, "登录失败超过%d次, 请%d分钟后再试", loginFailCount, loginFailMin)
biz.IsTrueI(ctx, nowFailCount < loginFailCount, imsg.ErrLoginRestrict, "failCount", loginFailCount, "min", loginFailMin)
var account *sysentity.Account
cols := []string{"Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret"}
account, err = a.getOrCreateUserWithLdap(username, originPwd, cols...)
account, err = a.getOrCreateUserWithLdap(ctx, username, originPwd, cols...)
if err != nil {
nowFailCount++
cache.SetStr(failCountKey, strconv.Itoa(nowFailCount), time.Minute*time.Duration(loginFailMin))
panic(errorx.NewBiz(fmt.Sprintf("用户名或密码错误【当前登录失败%d次】", nowFailCount)))
panic(errorx.NewBizI(ctx, imsg.ErrLoginFail, "failCount", nowFailCount))
}
rc.ResData = LastLoginCheck(account, accountLoginSecurity, clientIp)
rc.ResData = LastLoginCheck(ctx, account, accountLoginSecurity, clientIp)
}
func (a *LdapLogin) getUser(userName string, cols ...string) (*sysentity.Account, error) {
@@ -95,10 +96,10 @@ func (a *LdapLogin) createUser(userName, displayName string) {
biz.ErrIsNil(a.AccountApp.Update(context.TODO(), account))
}
func (a *LdapLogin) getOrCreateUserWithLdap(userName string, password string, cols ...string) (*sysentity.Account, error) {
func (a *LdapLogin) getOrCreateUserWithLdap(ctx context.Context, userName string, password string, cols ...string) (*sysentity.Account, error) {
userInfo, err := Authenticate(userName, password)
if err != nil {
return nil, errors.New("用户名密码错误")
return nil, errorx.NewBizI(ctx, imsg.ErrUsernameOrPwdErr)
}
account, err := a.getUser(userName, cols...)
@@ -121,7 +122,7 @@ type UserInfo struct {
func Authenticate(username, password string) (*UserInfo, error) {
ldapConf := config.GetLdapLogin()
if !ldapConf.Enable {
return nil, errors.Errorf("未启用 LDAP 登录")
return nil, errors.Errorf("LDAP login is not enabled")
}
conn, err := Connect(ldapConf)
if err != nil {

View File

@@ -8,6 +8,7 @@ import (
"mayfly-go/internal/auth/application"
"mayfly-go/internal/auth/config"
"mayfly-go/internal/auth/domain/entity"
"mayfly-go/internal/auth/imsg"
msgapp "mayfly-go/internal/msg/application"
sysapp "mayfly-go/internal/sys/application"
sysentity "mayfly-go/internal/sys/domain/entity"
@@ -52,42 +53,42 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
client, oauth := a.getOAuthClient()
code := rc.Query("code")
biz.NotEmpty(code, "code不能为空")
biz.NotEmpty(code, "code cannot be empty")
state := rc.Query("state")
biz.NotEmpty(state, "state不能为空")
biz.NotEmpty(state, "state canot be empty")
stateAction := cache.GetStr("oauth2:state:" + state)
biz.NotEmpty(stateAction, "state已过期, 请重新登录")
token, err := client.Exchange(rc, code)
biz.ErrIsNilAppendErr(err, "获取OAuth2 accessToken失败: %s")
biz.ErrIsNilAppendErr(err, "get OAuth2 accessToken fail: %s")
// 获取用户信息
httpCli := client.Client(rc.GetRequest().Context(), token)
resp, err := httpCli.Get(oauth.ResourceURL)
biz.ErrIsNilAppendErr(err, "获取用户信息失败: %s")
biz.ErrIsNilAppendErr(err, "get user info error: %s")
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
biz.ErrIsNilAppendErr(err, "读取响应的用户信息失败: %s")
biz.ErrIsNilAppendErr(err, "failed to read the response user information: %s")
// UserIdentifier格式为 type:fieldPath。如string:user.username 或 number:user.id
userIdTypeAndFieldPath := strings.Split(oauth.UserIdentifier, ":")
biz.IsTrue(len(userIdTypeAndFieldPath) == 2, "oauth2配置属性'UserIdentifier'不符合规则")
biz.IsTrue(len(userIdTypeAndFieldPath) == 2, "oauth2 configuration property 'UserIdentifier' is not compliant")
// 解析用户唯一标识
userIdFieldPath := userIdTypeAndFieldPath[1]
userId := ""
if userIdTypeAndFieldPath[0] == "string" {
userId, err = jsonx.GetStringByBytes(b, userIdFieldPath)
biz.ErrIsNilAppendErr(err, "解析用户唯一标识失败: %s")
biz.ErrIsNilAppendErr(err, "failed to resolve the user unique identity: %s")
} else {
intUserId, err := jsonx.GetIntByBytes(b, userIdFieldPath)
biz.ErrIsNilAppendErr(err, "解析用户唯一标识失败: %s")
biz.ErrIsNilAppendErr(err, "failed to resolve the user unique identity: %s")
userId = fmt.Sprintf("%d", intUserId)
}
biz.NotBlank(userId, "用户唯一标识字段值不能为空")
biz.NotBlank(userId, "the user unique identification field value cannot be null")
// 判断是登录还是绑定
if stateAction == "login" {
@@ -95,23 +96,23 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
} else if sAccountId, ok := strings.CutPrefix(stateAction, "bind:"); ok {
// 绑定
accountId, err := strconv.ParseUint(sAccountId, 10, 64)
biz.ErrIsNilAppendErr(err, "绑定用户失败: %s")
biz.ErrIsNilAppendErr(err, "failed to bind user: %s")
account := new(sysentity.Account)
account.Id = accountId
err = a.AccountApp.GetByCond(model.NewModelCond(account).Columns("username"))
biz.ErrIsNilAppendErr(err, "该账号不存在")
biz.ErrIsNilAppendErr(err, "this account does not exist")
rc.ReqParam = collx.Kvs("username", account.Username, "type", "bind")
err = a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
AccountId: accountId,
}, "account_id", "identity")
biz.IsTrue(err != nil, "该账号已被其他用户绑定")
biz.IsTrue(err != nil, "the account has been linked by another user")
err = a.Oauth2App.GetOAuthAccount(&entity.Oauth2Account{
Identity: userId,
}, "account_id", "identity")
biz.IsTrue(err != nil, "您已绑定其他账号")
biz.IsTrue(err != nil, "you are bound to another account")
now := time.Now()
err = a.Oauth2App.BindOAuthAccount(&entity.Oauth2Account{
@@ -120,14 +121,14 @@ func (a *Oauth2Login) OAuth2Callback(rc *req.Ctx) {
CreateTime: &now,
UpdateTime: &now,
})
biz.ErrIsNilAppendErr(err, "绑定用户失败: %s")
biz.ErrIsNilAppendErr(err, "failed to bind user: %s")
res := collx.M{
"action": "oauthBind",
"bind": true,
}
rc.ResData = res
} else {
panic(errorx.NewBiz("state不合法"))
panic(errorx.NewBiz("state is invalid"))
}
}
@@ -136,12 +137,12 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
// 查询用户是否存在
oauthAccount := &entity.Oauth2Account{Identity: userId}
err := a.Oauth2App.GetOAuthAccount(oauthAccount, "account_id", "identity")
ctx := rc.MetaCtx
var accountId uint64
isFirst := false
// 不存在,进行注册
if err != nil {
biz.IsTrue(oauth.AutoRegister, "系统未开启自动注册, 请先让管理员添加对应账号")
biz.IsTrueI(ctx, oauth.AutoRegister, imsg.ErrOauth2NoAutoRegister)
now := time.Now()
account := &sysentity.Account{
Model: model.Model{
@@ -163,7 +164,7 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
CreateTime: &now,
UpdateTime: &now,
})
biz.ErrIsNilAppendErr(err, "绑定用户失败: %s")
biz.ErrIsNilAppendErr(err, "failed to bind user: %s")
accountId = account.Id
isFirst = true
} else {
@@ -172,12 +173,12 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
// 进行登录
account, err := a.AccountApp.GetById(accountId, "Id", "Name", "Username", "Password", "Status", "LastLoginTime", "LastLoginIp", "OtpSecret")
biz.ErrIsNilAppendErr(err, "获取用户信息失败: %s")
biz.ErrIsNilAppendErr(err, "get user info error: %s")
clientIp := getIpAndRegion(rc)
rc.ReqParam = collx.Kvs("username", account.Username, "ip", clientIp, "type", "login")
res := LastLoginCheck(account, config.GetAccountLoginSecurity(), clientIp)
res := LastLoginCheck(ctx, account, config.GetAccountLoginSecurity(), clientIp)
res["action"] = "oauthLogin"
res["isFirstOauth2Login"] = isFirst
rc.ResData = res
@@ -185,8 +186,8 @@ func (a *Oauth2Login) doLoginAction(rc *req.Ctx, userId string, oauth *config.Oa
func (a *Oauth2Login) getOAuthClient() (*oauth2.Config, *config.Oauth2Login) {
oath2LoginConfig := config.GetOauth2Login()
biz.IsTrue(oath2LoginConfig.Enable, "请先配置oauth2或启用oauth2登录")
biz.IsTrue(oath2LoginConfig.ClientId != "", "oauth2 clientId不能为空")
biz.IsTrue(oath2LoginConfig.Enable, "please configure oauth2 or enable oauth2 login first")
biz.IsTrue(oath2LoginConfig.ClientId != "", "oauth2 clientId cannot be empty")
client := &oauth2.Config{
ClientID: oath2LoginConfig.ClientId,

View File

@@ -0,0 +1,21 @@
package imsg
import "mayfly-go/pkg/i18n"
var En = map[i18n.MsgId]string{
LogAccountLogin: "Account Login",
LogOauth2Callback: "Oauth2 Callback",
LogOauth2Unbind: "Oauth2 Unbind",
LogLdapLogin: "LDAP Login",
ErrCaptchaErr: "Captcha error",
ErrLoginRestrict: "login failed more than {{.failCount}} times, try again in {{.min}} minutes",
ErrLoginFail: "Wrong username or password [Login failed {{.failCount}} times]",
ErrOtpTokenInvalid: "otpToken error or invalid, please login again to obtain",
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

@@ -0,0 +1,29 @@
package imsg
import (
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/i18n"
)
func init() {
i18n.AppendLangMsg(i18n.Zh_CN, Zh_CN)
i18n.AppendLangMsg(i18n.En, En)
}
const (
LogAccountLogin = iota + consts.ImsgNumAuth
LogOauth2Callback
LogOauth2Unbind
LogLdapLogin
ErrCaptchaErr
ErrLoginRestrict
ErrLoginFail
ErrOtpTokenInvalid
ErrOtpCheckRestrict
ErrOtpCheckFail
ErrAccountNotAvailable
LoginMsg
ErrUsernameOrPwdErr
ErrOauth2NoAutoRegister
)

View File

@@ -0,0 +1,21 @@
package imsg
import "mayfly-go/pkg/i18n"
var Zh_CN = map[i18n.MsgId]string{
LogAccountLogin: "账号登录",
LogOauth2Callback: "Oauth2回调",
LogOauth2Unbind: "Oauth2解绑",
LogLdapLogin: "LDAP登录",
ErrCaptchaErr: "验证码错误",
ErrLoginRestrict: "登录失败超过{{.failCount}}次, 请{{.min}}分钟后再试",
ErrLoginFail: "用户名或密码错误【当前登录失败{{.failCount}}次】",
ErrOtpTokenInvalid: "otpToken错误或失效, 请重新登陆获取",
ErrOtpCheckRestrict: "双因素校验失败超过5次, 请10分钟后再试",
ErrOtpCheckFail: "双因素认证授权码不正确",
ErrAccountNotAvailable: "账号不可用",
LoginMsg: "于[{{.ip}}]-[{{.time}}]登录",
ErrUsernameOrPwdErr: "用户名或密码错误",
ErrOauth2NoAutoRegister: "系统未开启自动注册, 请先让管理员添加对应账号",
}

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/auth/api"
"mayfly-go/internal/auth/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -24,7 +25,7 @@ func Init(router *gin.RouterGroup) {
reqs := [...]*req.Conf{
// 用户账号密码登录
req.NewPost("/accounts/login", accountLogin.Login).Log(req.NewLogSave("用户登录")).DontNeedToken(),
req.NewPost("/accounts/login", accountLogin.Login).Log(req.NewLogSaveI(imsg.LogAccountLogin)).DontNeedToken(),
req.NewGet("/accounts/refreshToken", accountLogin.RefreshToken).DontNeedToken(),
@@ -44,15 +45,15 @@ func Init(router *gin.RouterGroup) {
req.NewGet("/oauth2/bind", oauth2Login.OAuth2Bind),
// oauth2回调地址
req.NewGet("/oauth2/callback", oauth2Login.OAuth2Callback).Log(req.NewLogSave("oauth2回调")).DontNeedToken(),
req.NewGet("/oauth2/callback", oauth2Login.OAuth2Callback).Log(req.NewLogSaveI(imsg.LogOauth2Callback)).DontNeedToken(),
req.NewGet("/oauth2/status", oauth2Login.Oauth2Status),
req.NewGet("/oauth2/unbind", oauth2Login.Oauth2Unbind).Log(req.NewLogSave("oauth2解绑")),
req.NewGet("/oauth2/unbind", oauth2Login.Oauth2Unbind).Log(req.NewLogSaveI(imsg.LogOauth2Unbind)),
// LDAP 登录
req.NewGet("/ldap/enabled", ldapLogin.GetLdapEnabled).DontNeedToken(),
req.NewPost("/ldap/login", ldapLogin.Login).Log(req.NewLogSave("LDAP 登录")).DontNeedToken(),
req.NewPost("/ldap/login", ldapLogin.Login).Log(req.NewLogSaveI(imsg.LogLdapLogin)).DontNeedToken(),
}
req.BatchSetGroup(rg, reqs[:])

View File

@@ -20,4 +20,14 @@ const (
ResourceTypeDb int8 = 2
ResourceTypeRedis int8 = 3
ResourceTypeMongo int8 = 4
// imsg起始编号
ImsgNumSys = 10000
ImsgNumAuth = 20000
ImsgNumTag = 30000
ImsgNumFlow = 40000
ImsgNumMachine = 50000
ImsgNumDb = 60000
ImsgNumRedis = 70000
ImsgNumMongo = 80000
)

View File

@@ -10,19 +10,16 @@ func CheckAccountPasswordLever(ps string) bool {
if len(ps) < 8 {
return false
}
num := `[0-9]{1}`
a_z := `[a-zA-Z]{1}`
symbol := `[!@#~$%^&*()+|_.,]{1}`
if b, err := regexp.MatchString(num, ps); !b || err != nil {
return false
}
if b, err := regexp.MatchString(a_z, ps); !b || err != nil {
return false
}
if b, err := regexp.MatchString(symbol, ps); !b || err != nil {
return false
}
return true
// 包含大写字母
charRegex := regexp.MustCompile(`[a-zA-Z]`)
// 包含数字
digitRegex := regexp.MustCompile(`[0-9]`)
// 包含特殊符号
specialCharRegex := regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`)
return charRegex.MatchString(ps) &&
digitRegex.MatchString(ps) &&
specialCharRegex.MatchString(ps)
}
// 使用config.yml的aes.key进行密码加密

View File

@@ -11,6 +11,7 @@ import (
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/dbm/sqlparser"
"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"
@@ -18,6 +19,7 @@ import (
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"
@@ -92,9 +94,7 @@ func (d *Db) DeleteDb(rc *req.Ctx) {
ctx := rc.MetaCtx
for _, v := range ids {
dbId := cast.ToUint64(v)
biz.NotBlank(dbId, "存在错误dbId")
biz.ErrIsNil(d.DbApp.Delete(ctx, dbId))
biz.ErrIsNil(d.DbApp.Delete(ctx, cast.ToUint64(v)))
}
}
@@ -110,10 +110,10 @@ func (d *Db) ExecSql(rc *req.Ctx) {
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, dbConn.Info.CodePath[0])
sqlStr, err := cryptox.AesDecryptByLa(form.Sql, rc.GetLoginAccount())
biz.ErrIsNilAppendErr(err, "sql解码失败: %s")
biz.ErrIsNilAppendErr(err, "sql decoding failure: %s")
rc.ReqParam = fmt.Sprintf("%s %s\n-> %s", dbConn.Info.GetLogDesc(), form.ExecId, sqlStr)
biz.NotEmpty(form.Sql, "sql不能为空")
biz.NotEmpty(form.Sql, "sql cannot be empty")
execReq := &application.DbSqlExecReq{
DbId: dbId,
@@ -146,9 +146,9 @@ type progressMsg struct {
// 执行sql文件
func (d *Db) ExecSqlFile(rc *req.Ctx) {
multipart, err := rc.GetRequest().MultipartReader()
biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
biz.ErrIsNilAppendErr(err, "failed to read sql file: %s")
file, err := multipart.NextPart()
biz.ErrIsNilAppendErr(err, "读取sql文件失败: %s")
biz.ErrIsNilAppendErr(err, "failed to read sql file: %s")
defer file.Close()
filename := file.FileName()
dbId := getDbId(rc)
@@ -166,14 +166,14 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
if len(errInfo) > 300 {
errInfo = errInfo[:300] + "..."
}
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s]执行失败: [%s]", filename, dbConn.Info.GetLogDesc(), errInfo)).WithClientId(clientId))
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg(i18n.T(imsg.SqlScriptRunFail), fmt.Sprintf("[%s][%s] execution failure: [%s]", filename, dbConn.Info.GetLogDesc(), errInfo)).WithClientId(clientId))
}
}()
executedStatements := 0
progressId := stringx.Rand(32)
laId := rc.GetLoginAccount().Id
defer ws.SendJsonMsg(ws.UserId(laId), clientId, msgdto.InfoSysMsg("sql脚本执行进度", &progressMsg{
defer ws.SendJsonMsg(ws.UserId(laId), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
Id: progressId,
Title: filename,
ExecutedStatements: executedStatements,
@@ -185,7 +185,7 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
err = sqlparser.SQLSplit(file, func(sql string) error {
select {
case <-ticker.C:
ws.SendJsonMsg(ws.UserId(laId), clientId, msgdto.InfoSysMsg("sql脚本执行进度", &progressMsg{
ws.SendJsonMsg(ws.UserId(laId), clientId, msgdto.InfoSysMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
Id: progressId,
Title: filename,
ExecutedStatements: executedStatements,
@@ -200,7 +200,7 @@ func (d *Db) ExecSqlFile(rc *req.Ctx) {
})
biz.ErrIsNilAppendErr(err, "%s")
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.SuccessSysMsg("sql脚本执行成功", fmt.Sprintf("sql脚本执行完成%s", rc.ReqParam)).WithClientId(clientId))
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.SuccessSysMsg(i18n.T(imsg.SqlScriptRunSuccess), fmt.Sprintf("sql脚本执行完成%s", rc.ReqParam)).WithClientId(clientId))
}
// 数据库dump
@@ -243,9 +243,9 @@ func (d *Db) DumpSql(rc *req.Ctx) {
defer func() {
msg := anyx.ToString(recover())
if len(msg) > 0 {
msg = "数据库导出失败: " + msg
msg = "DB dump error: " + msg
rc.GetWriter().Write([]byte(msg))
d.MsgApp.CreateAndSend(la, msgdto.ErrSysMsg("数据库导出失败", msg))
d.MsgApp.CreateAndSend(la, msgdto.ErrSysMsg(i18n.T(imsg.DbDumpErr), msg))
}
}()
@@ -263,26 +263,26 @@ func (d *Db) DumpSql(rc *req.Ctx) {
func (d *Db) TableInfos(rc *req.Ctx) {
res, err := d.getDbConn(rc).GetMetadata().GetTables()
biz.ErrIsNilAppendErr(err, "获取表信息失败: %s")
biz.ErrIsNilAppendErr(err, "get table error: %s")
rc.ResData = res
}
func (d *Db) TableIndex(rc *req.Ctx) {
tn := rc.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
biz.NotEmpty(tn, "tableName cannot be empty")
res, err := d.getDbConn(rc).GetMetadata().GetTableIndex(tn)
biz.ErrIsNilAppendErr(err, "获取表索引信息失败: %s")
biz.ErrIsNilAppendErr(err, "get table index error: %s")
rc.ResData = res
}
// @router /api/db/:dbId/c-metadata [get]
func (d *Db) ColumnMA(rc *req.Ctx) {
tn := rc.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
biz.NotEmpty(tn, "tableName cannot be empty")
dbi := d.getDbConn(rc)
res, err := dbi.GetMetadata().GetColumns(tn)
biz.ErrIsNilAppendErr(err, "获取数据库列信息失败: %s")
biz.ErrIsNilAppendErr(err, "get column metadata error: %s")
rc.ResData = res
}
@@ -330,9 +330,9 @@ func (d *Db) HintTables(rc *req.Ctx) {
func (d *Db) GetTableDDL(rc *req.Ctx) {
tn := rc.Query("tableName")
biz.NotEmpty(tn, "tableName不能为空")
biz.NotEmpty(tn, "tableName cannot be empty")
res, err := d.getDbConn(rc).GetMetadata().GetTableDDL(tn, false)
biz.ErrIsNilAppendErr(err, "获取表ddl失败: %s")
biz.ErrIsNilAppendErr(err, "get table DDL error: %s")
rc.ResData = res
}
@@ -343,7 +343,7 @@ func (d *Db) GetVersion(rc *req.Ctx) {
func (d *Db) GetSchemas(rc *req.Ctx) {
res, err := d.getDbConn(rc).GetMetadata().GetSchemas()
biz.ErrIsNilAppendErr(err, "获取schemas失败: %s")
biz.ErrIsNilAppendErr(err, "get schemas error: %s")
rc.ResData = res
}
@@ -352,24 +352,24 @@ func (d *Db) CopyTable(rc *req.Ctx) {
copy := req.BindJsonAndCopyTo[*dbi.DbCopyTable](rc, form, new(dbi.DbCopyTable))
conn, err := d.DbApp.GetDbConn(form.Id, form.Db)
biz.ErrIsNilAppendErr(err, "拷贝表失败: %s")
biz.ErrIsNilAppendErr(err, "copy table error: %s")
err = conn.GetDialect().CopyTable(copy)
if err != nil {
logx.Errorf("拷贝表失败: %s", err.Error())
logx.Errorf("copy table error: %s", err.Error())
}
biz.ErrIsNilAppendErr(err, "拷贝表失败: %s")
biz.ErrIsNilAppendErr(err, "copy table error: %s")
}
func getDbId(rc *req.Ctx) uint64 {
dbId := rc.PathParamInt("dbId")
biz.IsTrue(dbId > 0, "dbId错误")
biz.IsTrue(dbId > 0, "dbId error")
return uint64(dbId)
}
func getDbName(rc *req.Ctx) string {
db := rc.Query("db")
biz.NotEmpty(db, "db不能为空")
biz.NotEmpty(db, "db cannot be empty")
return db
}

View File

@@ -9,8 +9,9 @@ import (
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/cryptox"
"mayfly-go/pkg/utils/stringx"
"strconv"
"strings"
"github.com/may-fly/cast"
)
type DataSyncTask struct {
@@ -37,7 +38,7 @@ func (d *DataSyncTask) SaveTask(rc *req.Ctx) {
// 解码base64 sql
sqlStr, err := cryptox.AesDecryptByLa(task.DataSql, rc.GetLoginAccount())
biz.ErrIsNilAppendErr(err, "sql解码失败: %s")
biz.ErrIsNilAppendErr(err, "sql decoding failure: %s")
sql := stringx.TrimSpaceAndBr(sqlStr)
task.DataSql = sql
form.DataSql = sql
@@ -52,9 +53,7 @@ func (d *DataSyncTask) DeleteTask(rc *req.Ctx) {
ids := strings.Split(taskId, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
biz.ErrIsNil(d.DataSyncTaskApp.Delete(rc.MetaCtx, uint64(value)))
biz.ErrIsNil(d.DataSyncTaskApp.Delete(rc.MetaCtx, cast.ToUint64(v)))
}
}
@@ -65,7 +64,7 @@ func (d *DataSyncTask) ChangeStatus(rc *req.Ctx) {
if task.Status == entity.DataSyncTaskStatusEnable {
task, err := d.DataSyncTaskApp.GetById(task.Id)
biz.ErrIsNil(err, "该任务不存在")
biz.ErrIsNil(err, "task not found")
d.DataSyncTaskApp.AddCronJob(rc.MetaCtx, task)
} else {
d.DataSyncTaskApp.RemoveCronJobById(task.Id)
@@ -98,6 +97,6 @@ func (d *DataSyncTask) GetTask(rc *req.Ctx) {
func (d *DataSyncTask) getTaskId(rc *req.Ctx) uint64 {
instanceId := rc.PathParamInt("taskId")
biz.IsTrue(instanceId > 0, "instanceId 错误")
biz.IsTrue(instanceId > 0, "instanceId error")
return uint64(instanceId)
}

View File

@@ -14,8 +14,9 @@ import (
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"strconv"
"strings"
"github.com/may-fly/cast"
)
type Instance struct {
@@ -85,7 +86,7 @@ func (d *Instance) SaveInstance(rc *req.Ctx) {
func (d *Instance) GetInstance(rc *req.Ctx) {
dbId := getInstanceId(rc)
dbEntity, err := d.InstanceApp.GetById(dbId)
biz.ErrIsNil(err, "获取数据库实例错误")
biz.ErrIsNilAppendErr(err, "get db instance failed: %s")
rc.ResData = dbEntity
}
@@ -97,11 +98,7 @@ func (d *Instance) DeleteInstance(rc *req.Ctx) {
ids := strings.Split(idsStr, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "删除数据库实例失败: %s")
instanceId := uint64(value)
err = d.InstanceApp.Delete(rc.MetaCtx, instanceId)
biz.ErrIsNilAppendErr(err, "删除数据库实例失败: %s")
biz.ErrIsNilAppendErr(d.InstanceApp.Delete(rc.MetaCtx, cast.ToUint64(v)), "delete db instance failed: %s")
}
}
@@ -132,6 +129,6 @@ func (d *Instance) GetDbServer(rc *req.Ctx) {
func getInstanceId(rc *req.Ctx) uint64 {
instanceId := rc.PathParamInt("instanceId")
biz.IsTrue(instanceId > 0, "instanceId 错误")
biz.IsTrue(instanceId > 0, "instanceId error")
return uint64(instanceId)
}

View File

@@ -9,17 +9,19 @@ import (
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/dbm/sqlparser"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/imsg"
fileapp "mayfly-go/internal/file/application"
msgapp "mayfly-go/internal/msg/application"
msgdto "mayfly-go/internal/msg/application/dto"
tagapp "mayfly-go/internal/tag/application"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/model"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/anyx"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/stringx"
"mayfly-go/pkg/ws"
"strconv"
"strings"
"time"
@@ -67,12 +69,9 @@ func (d *DbTransferTask) DeleteTask(rc *req.Ctx) {
rc.ReqParam = taskId
ids := strings.Split(taskId, ",")
uids := make([]uint64, len(ids))
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
uids = append(uids, uint64(value))
}
uids := collx.ArrayMap[string, uint64](ids, func(val string) uint64 {
return cast.ToUint64(val)
})
biz.ErrIsNil(d.DbTransferTask.DeleteById(rc.MetaCtx, uids...))
}
@@ -83,7 +82,7 @@ func (d *DbTransferTask) ChangeStatus(rc *req.Ctx) {
_ = d.DbTransferTask.UpdateById(rc.MetaCtx, task)
task, err := d.DbTransferTask.GetById(task.Id)
biz.ErrIsNil(err, "该任务不存在")
biz.ErrIsNil(err, "task not found")
d.DbTransferTask.AddCronJob(rc.MetaCtx, task)
// 记录请求日志
@@ -127,10 +126,10 @@ func (d *DbTransferTask) FileRun(rc *req.Ctx) {
rc.ReqParam = fm
tFile, err := d.DbTransferFile.GetById(fm.Id)
biz.IsTrue(tFile != nil && err == nil, "文件不存在")
biz.IsTrue(tFile != nil && err == nil, "file not found")
targetDbConn, err := d.DbApp.GetDbConn(fm.TargetDbId, fm.TargetDbName)
biz.ErrIsNilAppendErr(err, "连接目标数据库失败: %s")
biz.ErrIsNilAppendErr(err, "failed to connect to the target database: %s")
biz.ErrIsNilAppendErr(d.TagApp.CanAccess(rc.GetLoginAccount().Id, targetDbConn.Info.CodePath...), "%s")
defer func() {
@@ -139,7 +138,7 @@ func (d *DbTransferTask) FileRun(rc *req.Ctx) {
if len(errInfo) > 300 {
errInfo = errInfo[:300] + "..."
}
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg("sql脚本执行失败", fmt.Sprintf("[%s][%s]执行失败: [%s]", tFile.FileKey, targetDbConn.Info.GetLogDesc(), errInfo)).WithClientId(fm.ClientId))
d.MsgApp.CreateAndSend(rc.GetLoginAccount(), msgdto.ErrSysMsg(i18n.T(imsg.SqlScriptRunFail), fmt.Sprintf("[%s][%s] run failed: [%s]", tFile.FileKey, targetDbConn.Info.GetLogDesc(), errInfo)).WithClientId(fm.ClientId))
}
}()
@@ -160,13 +159,13 @@ func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFile
defer ticker.Stop()
if err != nil {
biz.ErrIsNilAppendErr(err, "连接目标数据库失败: %s")
biz.ErrIsNilAppendErr(err, "failed to connect to the target database: %s")
}
err = sqlparser.SQLSplit(reader, func(sql string) error {
select {
case <-ticker.C:
ws.SendJsonMsg(ws.UserId(laId), fm.ClientId, msgdto.InfoSqlProgressMsg("sql脚本执行进度", &progressMsg{
ws.SendJsonMsg(ws.UserId(laId), fm.ClientId, msgdto.InfoSqlProgressMsg(i18n.T(imsg.SqlScripRunProgress), &progressMsg{
Id: progressId,
Title: filename,
ExecutedStatements: executedStatements,
@@ -180,8 +179,8 @@ func (d *DbTransferTask) fileRun(la *model.LoginAccount, fm *form.DbTransferFile
})
if err != nil {
biz.ErrIsNilAppendErr(err, "执行sql失败: %s")
biz.ErrIsNilAppendErr(err, "sql execution failed: %s")
}
d.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg("sql脚本执行成功", fmt.Sprintf("sql脚本执行完成:%s", filename)).WithClientId(fm.ClientId))
d.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.T(imsg.SqlScriptRunSuccess), fmt.Sprintf("sql execution successfully: %s", filename)).WithClientId(fm.ClientId))
}

View File

@@ -9,6 +9,7 @@ import (
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
"mayfly-go/internal/db/imsg"
tagapp "mayfly-go/internal/tag/application"
tagdto "mayfly-go/internal/tag/application/dto"
tagentity "mayfly-go/internal/tag/domain/entity"
@@ -76,13 +77,13 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
authCert, err := d.resourceAuthCertApp.GetAuthCert(dbEntity.AuthCertName)
if err != nil {
return errorx.NewBiz("授权凭证不存在")
return errorx.NewBiz("ac not found")
}
err = d.GetByCond(oldDb)
if dbEntity.Id == 0 {
if err == nil {
return errorx.NewBiz("该实例下数据库名已存在")
return errorx.NewBizI(ctx, imsg.ErrDbNameExist)
}
dbEntity.Code = stringx.Rand(10)
@@ -104,13 +105,13 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
// 如果存在该库,则校验修改的库是否为该库
if err == nil && oldDb.Id != dbEntity.Id {
return errorx.NewBiz("该实例下数据库名已存在")
return errorx.NewBizI(ctx, imsg.ErrDbNameExist)
}
dbId := dbEntity.Id
old, err := d.GetById(dbId)
if err != nil {
return errorx.NewBiz("该数据库不存在")
return errorx.NewBiz("db not found")
}
oldDbs := strings.Split(old.Database, " ")
@@ -149,7 +150,7 @@ func (d *dbAppImpl) SaveDb(ctx context.Context, dbEntity *entity.Db) error {
func (d *dbAppImpl) Delete(ctx context.Context, id uint64) error {
db, err := d.GetById(id)
if err != nil {
return errorx.NewBiz("该数据库不存在")
return errorx.NewBiz("db not found")
}
dbs := strings.Split(db.Database, " ")
for _, v := range dbs {
@@ -178,12 +179,12 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
return dbm.GetDbConn(dbId, dbName, func() (*dbi.DbInfo, error) {
db, err := d.GetById(dbId)
if err != nil {
return nil, errorx.NewBiz("数据库信息不存在")
return nil, errorx.NewBiz("db not found")
}
instance, err := d.dbInstanceApp.GetById(db.InstanceId)
if err != nil {
return nil, errorx.NewBiz("数据库实例不存在")
return nil, errorx.NewBiz("db instance not found")
}
di, err := d.dbInstanceApp.ToDbInfo(instance, db.AuthCertName, dbName)
@@ -195,7 +196,7 @@ func (d *dbAppImpl) GetDbConn(dbId uint64, dbName string) (*dbi.DbConn, error) {
checkDb := di.GetDatabase()
if db.GetDatabaseMode == entity.DbGetDatabaseModeAssign && !strings.Contains(" "+db.Database+" ", " "+checkDb+" ") {
return nil, errorx.NewBiz("未配置数据库【%s】的操作权限", dbName)
return nil, errorx.NewBizI(context.Background(), imsg.ErrDbNotAccess, "dbName", dbName)
}
return di, nil
@@ -210,10 +211,10 @@ func (d *dbAppImpl) GetDbConnByInstanceId(instanceId uint64) (*dbi.DbConn, error
dbs, err := d.ListByCond(&entity.Db{InstanceId: instanceId}, "id", "database")
if err != nil {
return nil, errorx.NewBiz("获取数据库列表失败")
return nil, errorx.NewBiz("failed to get database list")
}
if len(dbs) == 0 {
return nil, errorx.NewBiz("实例[%d]未配置数据库, 请先进行配置", instanceId)
return nil, errorx.NewBiz("DB instance [%d] Database is not configured, please configure it first", instanceId)
}
// 使用该实例关联的已配置数据库中的第一个库进行连接并返回
@@ -239,10 +240,10 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
}
writer.WriteString("\n-- ----------------------------")
writer.WriteString("\n-- 导出平台: mayfly-go")
writer.WriteString(fmt.Sprintf("\n-- 导出时间: %s ", time.Now().Format("2006-01-02 15:04:05")))
writer.WriteString(fmt.Sprintf("\n-- 导出数据库: %s ", dbName))
writer.WriteString(fmt.Sprintf("\n-- 数据库方言: %s ", cmp.Or(reqParam.TargetDbType, dbConn.Info.Type)))
writer.WriteString("\n-- Dump Platform: mayfly-go")
writer.WriteString(fmt.Sprintf("\n-- Dump Time: %s ", time.Now().Format("2006-01-02 15:04:05")))
writer.WriteString(fmt.Sprintf("\n-- Dump DB: %s ", dbName))
writer.WriteString(fmt.Sprintf("\n-- DB Dialect: %s ", cmp.Or(reqParam.TargetDbType, dbConn.Info.Type)))
writer.WriteString("\n-- ----------------------------\n\n")
// 获取目标元数据仅生成sql用于生成建表语句和插入数据不能用于查询
@@ -261,28 +262,28 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
srcMeta := dbConn.GetMetadata()
srcDialect := dbConn.GetDialect()
if len(tables) == 0 {
log("获取可导出的表信息...")
log("Gets the table information that can be export...")
ti, err := srcMeta.GetTables()
if err != nil {
log(fmt.Sprintf("获取表信息失败 %s", err.Error()))
log(fmt.Sprintf("Failed to get table info %s", err.Error()))
}
biz.ErrIsNil(err)
tables = make([]string, len(ti))
for i, table := range ti {
tables[i] = table.TableName
}
log(fmt.Sprintf("获取到%d张表", len(tables)))
log(fmt.Sprintf("Get %d tables", len(tables)))
}
if len(tables) == 0 {
log("不存在可导出的表, 结束导出")
return errorx.NewBiz("不存在可导出的表")
log("No table to export. End export")
return errorx.NewBiz("there is no table to export")
}
log("查询列信息...")
log("Querying column information...")
// 查询列信息后面生成建表ddl和insert都需要列信息
columns, err := srcMeta.GetColumns(tables...)
if err != nil {
log(fmt.Sprintf("查询列信息失败:%s", err.Error()))
log(fmt.Sprintf("Failed to query column information: %s", err.Error()))
}
biz.ErrIsNil(err)
@@ -301,18 +302,18 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
// 遍历获取每个表的信息
for _, tableName := range tables {
log(fmt.Sprintf("获取表[%s]信息...", tableName))
log(fmt.Sprintf("Get table [%s] information...", tableName))
quoteTableName := targetDialect.QuoteIdentifier(tableName)
// 查询表信息,主要是为了查询表注释
tbs, err := srcMeta.GetTables(tableName)
if err != nil {
log(fmt.Sprintf("获取表[%s]信息失败: %s", tableName, err.Error()))
log(fmt.Sprintf("Failed to get table [%s] information: %s", tableName, err.Error()))
return err
}
if len(tbs) <= 0 {
log(fmt.Sprintf("获取表[%s]信息失败: 没有查询到表信息", tableName))
return errorx.NewBiz(fmt.Sprintf("获取表信息失败:%s", tableName))
log(fmt.Sprintf("Failed to get table [%s] information: No table information was retrieved", tableName))
return errorx.NewBiz(fmt.Sprintf("Failed to get table information: %s", tableName))
}
tabInfo := dbi.Table{
TableName: tableName,
@@ -321,8 +322,8 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
// 生成表结构信息
if reqParam.DumpDDL {
log(fmt.Sprintf("生成表[%s]DDL...", tableName))
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表结构: %s \n-- ----------------------------\n", tableName))
log(fmt.Sprintf("Generate table [%s] DDL...", tableName))
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- Table structure: %s \n-- ----------------------------\n", tableName))
tbDdlArr := targetDialect.GenerateTableDDL(columnMap[tableName], tabInfo, true)
for _, ddl := range tbDdlArr {
writer.WriteString(ddl + ";\n")
@@ -332,7 +333,7 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
// 生成insert sql数据在索引前加速insert
if reqParam.DumpData {
log(fmt.Sprintf("生成表[%s]DML...", tableName))
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表数据: %s \n-- ----------------------------\n", tableName))
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- Data: %s \n-- ----------------------------\n", tableName))
dumpHelper.BeforeInsert(writer, quoteTableName)
// 获取列信息
@@ -356,17 +357,17 @@ func (d *dbAppImpl) DumpDb(ctx context.Context, reqParam *dto.DumpDb) error {
dumpHelper.AfterInsert(writer, tableName, columnMap[tableName])
}
log(fmt.Sprintf("获取表[%s]索引信息...", tableName))
log(fmt.Sprintf("Get table [%s] index information...", tableName))
indexs, err := srcMeta.GetTableIndex(tableName)
if err != nil {
log(fmt.Sprintf("获取表[%s]索引信息失败:%s", tableName, err.Error()))
log(fmt.Sprintf("Failed to get table [%s] index information: %s", tableName, err.Error()))
return err
}
if len(indexs) > 0 {
// 最后添加索引
log(fmt.Sprintf("生成表[%s]索引...", tableName))
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- 表索引: %s \n-- ----------------------------\n", tableName))
log(fmt.Sprintf("Generate table [%s] index...", tableName))
writer.WriteString(fmt.Sprintf("\n-- ----------------------------\n-- Table Index: %s \n-- ----------------------------\n", tableName))
sqlArr := targetDialect.GenerateIndexDDL(indexs, tabInfo)
for _, sqlStr := range sqlArr {
writer.WriteString(sqlStr + ";\n")

View File

@@ -104,9 +104,9 @@ func (app *dataSyncAppImpl) AddCronJob(ctx context.Context, taskEntity *entity.D
if taskEntity.Status == entity.DataSyncTaskStatusEnable {
taskId := taskEntity.Id
scheduler.AddFunByKey(key, taskEntity.TaskCron, func() {
logx.Infof("开始执行同步任务: %d", taskId)
logx.Infof("start the data synchronization task: %d", taskId)
if err := app.RunCronJob(ctx, taskId); err != nil {
logx.Errorf("定时执行数据同步任务失败: %s", err.Error())
logx.Errorf("the data synchronization task failed to execute at a scheduled time: %s", err.Error())
}
})
}
@@ -130,15 +130,15 @@ func (app *dataSyncAppImpl) RunCronJob(ctx context.Context, id uint64) error {
// 查询最新的任务信息
task, err := app.GetById(id)
if err != nil {
return errorx.NewBiz("任务不存在")
return errorx.NewBiz("task not found")
}
if task.RunningState == entity.DataSyncTaskRunStateRunning {
return errorx.NewBiz("该任务正在执行中")
return errorx.NewBiz("the task is in progress")
}
// 开始运行时,修改状态为运行中
app.changeRunningState(id, entity.DataSyncTaskRunStateRunning)
logx.InfofContext(ctx, "开始执行数据同步任务:%s => %s", task.TaskName, task.TaskKey)
logx.InfofContext(ctx, "start the data synchronization task: %s => %s", task.TaskName, task.TaskKey)
go func() {
// 通过占位符格式化sql
@@ -147,7 +147,7 @@ func (app *dataSyncAppImpl) RunCronJob(ctx context.Context, id uint64) error {
if task.UpdFieldVal != "0" && task.UpdFieldVal != "" && task.UpdField != "" {
srcConn, err := app.dbApp.GetDbConn(uint64(task.SrcDbId), task.SrcDbName)
if err != nil {
logx.ErrorfContext(ctx, "数据源连接不可用, %s", err.Error())
logx.ErrorfContext(ctx, "data source connection unavailable: %s", err.Error())
return
}
@@ -180,7 +180,7 @@ func (app *dataSyncAppImpl) RunCronJob(ctx context.Context, id uint64) error {
log, err := app.doDataSync(ctx, sql, task)
if err != nil {
log.ErrText = fmt.Sprintf("执行失败: %s", err.Error())
log.ErrText = fmt.Sprintf("execution failure: %s", err.Error())
logx.ErrorContext(ctx, log.ErrText)
log.Status = entity.DataSyncTaskStateFail
app.endRunning(task, log)
@@ -202,17 +202,17 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
// 获取源数据库连接
srcConn, err := app.dbApp.GetDbConn(uint64(task.SrcDbId), task.SrcDbName)
if err != nil {
return syncLog, errorx.NewBiz("连接源数据库失败: %s", err.Error())
return syncLog, errorx.NewBiz("failed to connect to the source database: %s", err.Error())
}
// 获取目标数据库连接
targetConn, err := app.dbApp.GetDbConn(uint64(task.TargetDbId), task.TargetDbName)
if err != nil {
return syncLog, errorx.NewBiz("连接目标数据库失败: %s", err.Error())
return syncLog, errorx.NewBiz("failed to connect to the target database: %s", err.Error())
}
targetDbTx, err := targetConn.Begin()
if err != nil {
return syncLog, errorx.NewBiz("开启目标数据库事务失败: %s", err.Error())
return syncLog, errorx.NewBiz("failed to start the target database transaction: %s", err.Error())
}
defer func() {
if r := recover(); r != nil {
@@ -227,7 +227,7 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
var fieldMap []map[string]string
err = json.Unmarshal([]byte(task.FieldMap), &fieldMap)
if err != nil {
return syncLog, errorx.NewBiz("解析字段映射json出错: %s", err.Error())
return syncLog, errorx.NewBiz("there was an error parsing the field map json: %s", err.Error())
}
var updFieldType dbi.DataType
@@ -265,7 +265,7 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
}
// 记录当前已同步的数据量
syncLog.ErrText = fmt.Sprintf("本次任务执行中,已同步:%d条", total)
syncLog.ErrText = fmt.Sprintf("during the execution of this task, %d has been synchronized", total)
logx.InfoContext(ctx, syncLog.ErrText)
syncLog.ResNum = total
app.saveLog(syncLog)
@@ -292,14 +292,14 @@ func (app *dataSyncAppImpl) doDataSync(ctx context.Context, sql string, task *en
// 如果是mssql暂不手动提交事务否则报错 mssql: The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
if err := targetDbTx.Commit(); err != nil {
if targetConn.Info.Type != dbi.DbTypeMssql {
return syncLog, errorx.NewBiz("数据同步-目标数据库事务提交失败: %s", err.Error())
return syncLog, errorx.NewBiz("data synchronization - The target database transaction failed to commit: %s", err.Error())
}
}
logx.InfofContext(ctx, "同步任务:[%s],执行完毕,保存记录成功:[%d]", task.TaskName, total)
logx.InfofContext(ctx, "synchronous task: [%s], finished execution, save records successfully: [%d]", task.TaskName, total)
// 保存执行成功日志
syncLog.ErrText = fmt.Sprintf("本次任务执行成功,新数据:%d 条", total)
syncLog.ErrText = fmt.Sprintf("the synchronous task was executed successfully. New data: %d", total)
syncLog.Status = entity.DataSyncTaskStateSuccess
syncLog.ResNum = total
app.endRunning(task, syncLog)
@@ -381,7 +381,7 @@ func (app *dataSyncAppImpl) srcData2TargetDb(srcRes []map[string]any, fieldMap [
// 运行过程中,判断状态是否为已关闭,是则结束运行,否则继续运行
taskParam, _ := app.GetById(task.Id)
if taskParam.RunningState == entity.DataSyncTaskRunStateStop {
return errorx.NewBiz("该任务已被手动终止")
return errorx.NewBiz("the task has been terminated manually")
}
return nil
@@ -415,7 +415,7 @@ func (app *dataSyncAppImpl) saveLog(log *entity.DataSyncLog) {
func (app *dataSyncAppImpl) InitCronJob() {
defer func() {
if err := recover(); err != nil {
logx.ErrorTrace("数据同步任务初始化失败: %s", err.(error))
logx.ErrorTrace("the data synchronization task failed to initialize: %s", err.(error))
}
}()

View File

@@ -8,6 +8,7 @@ import (
"mayfly-go/internal/db/dbm/dbi"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
"mayfly-go/internal/db/imsg"
tagapp "mayfly-go/internal/tag/application"
tagdto "mayfly-go/internal/tag/application/dto"
tagentity "mayfly-go/internal/tag/domain/entity"
@@ -49,8 +50,6 @@ type instanceAppImpl struct {
tagApp tagapp.TagTree `inject:"TagTreeApp"`
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
dbApp Db `inject:"DbApp"`
backupApp *DbBackupApp `inject:"DbBackupApp"`
restoreApp *DbRestoreApp `inject:"DbRestoreApp"`
}
var _ (Instance) = (*instanceAppImpl)(nil)
@@ -90,7 +89,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
tagCodePaths := instance.TagCodePaths
if len(authCerts) == 0 {
return 0, errorx.NewBiz("授权凭证信息不能为空")
return 0, errorx.NewBiz("ac cannot be empty")
}
// 查找是否存在该库
@@ -103,7 +102,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
err := app.GetByCond(oldInstance)
if instanceEntity.Id == 0 {
if err == nil {
return 0, errorx.NewBiz("该数据库实例已存在")
return 0, errorx.NewBizI(ctx, imsg.ErrDbInstExist)
}
instanceEntity.Code = stringx.Rand(10)
@@ -126,13 +125,13 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
// 如果存在该库,则校验修改的库是否为该库
if err == nil {
if oldInstance.Id != instanceEntity.Id {
return 0, errorx.NewBiz("该数据库实例已存在")
return 0, errorx.NewBizI(ctx, imsg.ErrDbInstExist)
}
} else {
// 根据host等未查到旧数据则需要根据id重新获取因为后续需要使用到code
oldInstance, err = app.GetById(instanceEntity.Id)
if err != nil {
return 0, errorx.NewBiz("该数据库实例不存在")
return 0, errorx.NewBiz("db instance not found")
}
}
@@ -160,23 +159,7 @@ func (app *instanceAppImpl) SaveDbInstance(ctx context.Context, instance *dto.Sa
func (app *instanceAppImpl) Delete(ctx context.Context, instanceId uint64) error {
instance, err := app.GetById(instanceId)
if err != nil {
return errorx.NewBiz("获取数据库实例错误数据库实例ID为: %d", instance.Id)
}
restore := &entity.DbRestore{
DbInstanceId: instanceId,
}
err = app.restoreApp.restoreRepo.GetByCond(restore)
if err == nil {
return errorx.NewBiz("不能删除数据库实例【%s】,请先删除关联的数据库恢复任务。", instance.Name)
}
backup := &entity.DbBackup{
DbInstanceId: instanceId,
}
err = app.backupApp.backupRepo.GetByCond(backup)
if err == nil {
return errorx.NewBiz("不能删除数据库实例【%s】,请先删除关联的数据库备份任务。", instance.Name)
return errorx.NewBiz("db instnace not found")
}
dbs, _ := app.dbApp.ListByCond(&entity.Db{
@@ -227,13 +210,13 @@ func (app *instanceAppImpl) GetDatabases(ed *entity.DbInstance, authCert *tagent
func (app *instanceAppImpl) GetDatabasesByAc(acName string) ([]string, error) {
ac, err := app.resourceAuthCertApp.GetAuthCert(acName)
if err != nil {
return nil, errorx.NewBiz("该授权凭证不存在")
return nil, errorx.NewBiz("db ac not found")
}
instance := &entity.DbInstance{Code: ac.ResourceCode}
err = app.GetByCond(instance)
if err != nil {
return nil, errorx.NewBiz("不存在该授权凭证对应的数据库实例信息")
return nil, errorx.NewBiz("the db instance information for this ac does not exist")
}
return app.getDatabases(instance, ac)
@@ -285,7 +268,7 @@ func (m *instanceAppImpl) genDbInstanceResourceTag(me *entity.DbInstance, authCe
InstanceId: me.Id,
})
if err != nil {
logx.Errorf("获取实例关联的数据库失败: %v", err)
logx.Errorf("failed to retrieve the database associated with the instance: %v", err)
}
authCertName2DbTags := make(map[string][]*tagdto.ResourceTag)

View File

@@ -9,6 +9,7 @@ import (
"mayfly-go/internal/db/dbm/sqlparser/sqlstmt"
"mayfly-go/internal/db/domain/entity"
"mayfly-go/internal/db/domain/repository"
"mayfly-go/internal/db/imsg"
flowapp "mayfly-go/internal/flow/application"
flowentity "mayfly-go/internal/flow/domain/entity"
"mayfly-go/pkg/contextx"
@@ -202,7 +203,7 @@ func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *fl
execSqlBizForm, err := jsonx.To(procinst.BizForm, new(FlowDbExecSqlBizForm))
if err != nil {
return nil, errorx.NewBiz("业务表单信息解析失败: %s", err.Error())
return nil, errorx.NewBiz("failed to parse the business form information: %s", err.Error())
}
dbConn, err := d.dbApp.GetDbConn(execSqlBizForm.DbId, execSqlBizForm.DbName)
@@ -225,7 +226,7 @@ func (d *dbSqlExecAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *fl
// 存在一条错误的sql则表示业务处理失败
for _, er := range execRes {
if er.ErrorMsg != "" {
return execRes, errorx.NewBiz("存在执行错误的sql")
return execRes, errorx.NewBizI(ctx, imsg.ErrExistRunFailSql)
}
}
@@ -264,7 +265,7 @@ func (d *dbSqlExecAppImpl) doSelect(ctx context.Context, sqlExecParam *sqlExecPa
if procdef := sqlExecParam.Procdef; procdef != nil {
if needStartProc := procdef.MatchCondition(DbSqlExecFlowBizType, collx.Kvs("stmtType", "select")); needStartProc {
return nil, errorx.NewBiz("该操作需要提交工单审批执行")
return nil, errorx.NewBizI(ctx, imsg.ErrNeedSubmitWorkTicket)
}
}
@@ -287,10 +288,10 @@ func (d *dbSqlExecAppImpl) doSelect(ctx context.Context, sqlExecParam *sqlExecPa
// 如果配置为0则不校验分页参数
if needCheckLimit && maxCount != 0 {
if limit == nil {
return nil, errorx.NewBiz("请完善分页信息后执行")
return nil, errorx.NewBizI(ctx, imsg.ErrNoLimitStmt)
}
if limit.RowCount > maxCount {
return nil, errorx.NewBiz("查询结果集数需小于系统配置的%d条", maxCount)
return nil, errorx.NewBizI(ctx, imsg.ErrLimitInvalid, "count", maxCount)
}
}
} else {
@@ -304,7 +305,7 @@ func (d *dbSqlExecAppImpl) doSelect(ctx context.Context, sqlExecParam *sqlExecPa
!strings.Contains(selectSql, " top ") {
// 判断是不是count语句
if !strings.Contains(selectSql, "count(") {
return nil, errorx.NewBiz("请完善分页信息后执行")
return nil, errorx.NewBizI(ctx, imsg.ErrNoLimitStmt)
}
}
}
@@ -319,7 +320,7 @@ func (d *dbSqlExecAppImpl) doOtherRead(ctx context.Context, sqlExecParam *sqlExe
if procdef := sqlExecParam.Procdef; procdef != nil {
if needStartProc := procdef.MatchCondition(DbSqlExecFlowBizType, collx.Kvs("stmtType", "read")); needStartProc {
return nil, errorx.NewBiz("该操作需要提交工单审批执行")
return nil, errorx.NewBizI(ctx, imsg.ErrNeedSubmitWorkTicket)
}
}
@@ -332,7 +333,7 @@ func (d *dbSqlExecAppImpl) doExecDDL(ctx context.Context, sqlExecParam *sqlExecP
if procdef := sqlExecParam.Procdef; procdef != nil {
if needStartProc := procdef.MatchCondition(DbSqlExecFlowBizType, collx.Kvs("stmtType", "ddl")); needStartProc {
return nil, errorx.NewBiz("该操作需要提交工单审批执行")
return nil, errorx.NewBizI(ctx, imsg.ErrNeedSubmitWorkTicket)
}
}
@@ -344,7 +345,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
if procdef := sqlExecParam.Procdef; procdef != nil {
if needStartProc := procdef.MatchCondition(DbSqlExecFlowBizType, collx.Kvs("stmtType", "update")); needStartProc {
return nil, errorx.NewBiz("该操作需要提交工单审批执行")
return nil, errorx.NewBizI(ctx, imsg.ErrNeedSubmitWorkTicket)
}
}
@@ -364,7 +365,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
tableSources := updatestmt.TableSources.TableSources
// 不支持多表更新记录旧值
if len(tableSources) != 1 {
logx.ErrorContext(ctx, "Update SQL - 记录旧值只支持单表更新")
logx.ErrorContext(ctx, "Update SQL - logging old values only supports single-table updates")
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
@@ -378,21 +379,21 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
}
if tableName == "" {
logx.ErrorContext(ctx, "Update SQL - 获取表名失败")
logx.ErrorContext(ctx, "Update SQL - failed to get table name")
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
execRecord.Table = tableName
whereStr := updatestmt.Where.GetText()
if whereStr == "" {
logx.ErrorContext(ctx, "Update SQL - 不存在where条件")
logx.ErrorContext(ctx, "Update SQL - there is no where condition")
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
// 获取表主键列名,排除使用别名
primaryKey, err := dbConn.GetMetadata().GetPrimaryKey(tableName)
if err != nil {
logx.ErrorfContext(ctx, "Update SQL - 获取主键列失败: %s", err.Error())
logx.ErrorfContext(ctx, "Update SQL - failed to get primary key column: %s", err.Error())
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
@@ -416,12 +417,12 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
nowRec++
res = append(res, row)
if nowRec == maxRec {
return errorx.NewBiz(fmt.Sprintf("Update SQL -超出更新最大查询条数限制: %d", maxRec))
return errorx.NewBiz(fmt.Sprintf("Update SQL - the maximum number of updated queries is exceeded: %d", maxRec))
}
return nil
})
if err != nil {
logx.ErrorfContext(ctx, "Update SQL - 获取更新旧值失败: %s", err.Error())
logx.ErrorfContext(ctx, "Update SQL - failed to get the updated old value: %s", err.Error())
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
execRecord.OldValue = jsonx.ToStr(res)
@@ -432,7 +433,7 @@ func (d *dbSqlExecAppImpl) doUpdate(ctx context.Context, sqlExecParam *sqlExecPa
func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecParam) (*DbSqlExecRes, error) {
if procdef := sqlExecParam.Procdef; procdef != nil {
if needStartProc := procdef.MatchCondition(DbSqlExecFlowBizType, collx.Kvs("stmtType", "delete")); needStartProc {
return nil, errorx.NewBiz("该操作需要提交工单审批执行")
return nil, errorx.NewBizI(ctx, imsg.ErrNeedSubmitWorkTicket)
}
}
@@ -453,7 +454,7 @@ func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecPa
tableSources := deletestmt.TableSources.TableSources
// 不支持多表删除记录旧值
if len(tableSources) != 1 {
logx.ErrorContext(ctx, "Delete SQL - 记录旧值只支持单表删除")
logx.ErrorContext(ctx, "Delete SQL - logging old values only supports single-table deletion")
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
@@ -467,14 +468,14 @@ func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecPa
}
if tableName == "" {
logx.ErrorContext(ctx, "Delete SQL - 获取表名失败")
logx.ErrorContext(ctx, "Delete SQL - failed to get table name")
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
execRecord.Table = tableName
whereStr := deletestmt.Where.GetText()
if whereStr == "" {
logx.ErrorContext(ctx, "Delete SQL - 不存在where条件")
logx.ErrorContext(ctx, "Delete SQL - there is no where condition")
return d.doExec(ctx, dbConn, sqlExecParam.Sql)
}
@@ -489,7 +490,7 @@ func (d *dbSqlExecAppImpl) doDelete(ctx context.Context, sqlExecParam *sqlExecPa
func (d *dbSqlExecAppImpl) doInsert(ctx context.Context, sqlExecParam *sqlExecParam) (*DbSqlExecRes, error) {
if procdef := sqlExecParam.Procdef; procdef != nil {
if needStartProc := procdef.MatchCondition(DbSqlExecFlowBizType, collx.Kvs("stmtType", "insert")); needStartProc {
return nil, errorx.NewBiz("该操作需要提交工单审批执行")
return nil, errorx.NewBizI(ctx, imsg.ErrNeedSubmitWorkTicket)
}
}

View File

@@ -119,7 +119,7 @@ func (app *dbTransferAppImpl) AddCronJob(ctx context.Context, taskEntity *entity
taskId := taskEntity.Id
scheduler.AddFunByKey(key, taskEntity.Cron, func() {
logx.Infof("开始执行同步任务: %d", taskId)
logx.Infof("start the synchronization task: %d", taskId)
logId, _ := app.CreateLog(ctx, taskId)
app.Run(ctx, taskId, logId)
})
@@ -175,10 +175,10 @@ func (app *dbTransferAppImpl) InitCronJob() {
func (app *dbTransferAppImpl) CreateLog(ctx context.Context, taskId uint64) (uint64, error) {
logId, err := app.logApp.CreateLog(ctx, &sysapp.CreateLogReq{
Description: "DBMS-执行数据迁移",
Description: "DBMS - Execution DB Transfer",
ReqParam: collx.Kvs("taskId", taskId),
Type: sysentity.SyslogTypeRunning,
Resp: "开始执行数据迁移...",
Resp: "Data transfer starts...",
})
return logId, err
}
@@ -186,12 +186,12 @@ func (app *dbTransferAppImpl) CreateLog(ctx context.Context, taskId uint64) (uin
func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64, logId uint64) {
task, err := app.GetById(taskId)
if err != nil {
logx.Errorf("创建DBMS-执行数据迁移日志失败:%v", err)
logx.Errorf("Create DBMS- Failed to perform data transfer log: %v", err)
return
}
if app.IsRunning(taskId) {
logx.Error("[%d]该任务正在运行中...", taskId)
logx.Error("[%d] the task is running...", taskId)
return
}
@@ -200,7 +200,7 @@ func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64, logId uint
task.LogId = logId
task.RunningState = entity.DbTransferTaskRunStateRunning
if err = app.UpdateById(ctx, task); err != nil {
logx.Errorf("更新任务执行状态失败")
logx.Errorf("failed to update task execution status")
return
}
@@ -211,7 +211,7 @@ func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64, logId uint
// 获取源库表信息
srcConn, err := app.dbApp.GetDbConn(uint64(task.SrcDbId), task.SrcDbName)
if err != nil {
app.EndTransfer(ctx, logId, taskId, "获取源库连接失败", err, nil)
app.EndTransfer(ctx, logId, taskId, "failed to obtain source db connection", err, nil)
return
}
@@ -220,14 +220,14 @@ func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64, logId uint
if task.CheckedKeys == "all" {
tables, err = srcConn.GetMetadata().GetTables()
if err != nil {
app.EndTransfer(ctx, logId, taskId, "获取源表信息失败", err, nil)
app.EndTransfer(ctx, logId, taskId, "failed to get source table information", err, nil)
return
}
} else {
tableNames := strings.Split(task.CheckedKeys, ",")
tables, err = srcConn.GetMetadata().GetTables(tableNames...)
if err != nil {
app.EndTransfer(ctx, logId, taskId, "获取源表信息失败", err, nil)
app.EndTransfer(ctx, logId, taskId, "failed to get source table information", err, nil)
return
}
}
@@ -240,7 +240,7 @@ func (app *dbTransferAppImpl) Run(ctx context.Context, taskId uint64, logId uint
defer app.logApp.Flush(logId, true)
app.transfer2Db(ctx, taskId, logId, task, srcConn, start, tables)
} else {
app.EndTransfer(ctx, logId, taskId, "迁移模式出错,目前仅支持迁移到文件或数据库", err, nil)
app.EndTransfer(ctx, logId, taskId, "error in transfer mode, only migrating to files or databases is currently supported", err, nil)
return
}
}
@@ -249,16 +249,16 @@ func (app *dbTransferAppImpl) transfer2Db(ctx context.Context, taskId uint64, lo
// 获取目标库表信息
targetConn, err := app.dbApp.GetDbConn(uint64(task.TargetDbId), task.TargetDbName)
if err != nil {
app.EndTransfer(ctx, logId, taskId, "获取目标库连接失败", err, nil)
app.EndTransfer(ctx, logId, taskId, "failed to get target db connection", err, nil)
return
}
// 迁移表
if err = app.transferDbTables(ctx, logId, task, srcConn, targetConn, tables); err != nil {
app.EndTransfer(ctx, logId, taskId, "迁移表失败", err, nil)
app.EndTransfer(ctx, logId, taskId, "transfer table failed", err, nil)
return
}
app.EndTransfer(ctx, logId, taskId, fmt.Sprintf("执行迁移完成,执行迁移任务[taskId = %d]完成, 耗时:%v", taskId, time.Since(start)), nil, nil)
app.EndTransfer(ctx, logId, taskId, fmt.Sprintf("execute transfer task [taskId = %d] complete, time: %v", taskId, time.Since(start)), nil, nil)
}
func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64, logId uint64, task *entity.DbTransferTask, srcConn *dbi.DbConn, start time.Time, tables []dbi.Table) {
@@ -276,7 +276,7 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
filename := fmt.Sprintf("dtf_%s_%s.sql", task.TaskName, timex.TimeNo())
fileKey, writer, saveFileFunc, err := app.fileApp.NewWriter(ctx, "", filename)
if err != nil {
app.EndTransfer(ctx, logId, taskId, "创建文件失败", err, nil)
app.EndTransfer(ctx, logId, taskId, "create file error", err, nil)
return
}
@@ -286,8 +286,8 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
tableNames = append(tableNames, table.TableName)
}
// 2、把源库数据迁移到文件
app.Log(ctx, logId, fmt.Sprintf("开始迁移表数据到文件: %s", filename))
app.Log(ctx, logId, fmt.Sprintf("目标库文件语言类型: %s", task.TargetFileDbType))
app.Log(ctx, logId, fmt.Sprintf("start transfer table data to files: %s", filename))
app.Log(ctx, logId, fmt.Sprintf("dialect type of target db file: %s", task.TargetFileDbType))
go func() {
var err error
@@ -310,12 +310,12 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
},
})
if err != nil {
app.EndTransfer(ctx, logId, taskId, "数据库迁移失败", err, nil)
app.EndTransfer(ctx, logId, taskId, "db transfer to file failed", err, nil)
tFile.Status = entity.DbTransferFileStatusFail
_ = app.transferFileApp.UpdateById(ctx, tFile)
return
}
app.EndTransfer(ctx, logId, taskId, "数据库迁移完成", err, nil)
app.EndTransfer(ctx, logId, taskId, "database transfer complete", err, nil)
tFile.Status = entity.DbTransferFileStatusSuccess
tFile.FileKey = fileKey
@@ -326,11 +326,11 @@ func (app *dbTransferAppImpl) transfer2File(ctx context.Context, taskId uint64,
func (app *dbTransferAppImpl) Stop(ctx context.Context, taskId uint64) error {
task, err := app.GetById(taskId)
if err != nil {
return errorx.NewBiz("任务不存在")
return errorx.NewBiz("task not found")
}
if task.RunningState != entity.DbTransferTaskRunStateRunning {
return errorx.NewBiz("该任务未在执行")
return errorx.NewBiz("the task is not being executed")
}
task.RunningState = entity.DbTransferTaskRunStateStop
if err = app.UpdateById(ctx, task); err != nil {
@@ -351,14 +351,14 @@ func (app *dbTransferAppImpl) transferDbTables(ctx context.Context, logId uint64
}
if len(tableNames) == 0 {
return errorx.NewBiz("没有需要迁移的表")
return errorx.NewBiz("there are no tables to migrate")
}
srcDialect := srcConn.GetDialect()
srcMetadata := srcConn.GetMetadata()
// 查询源表列信息
columns, err := srcMetadata.GetColumns(tableNames...)
if err != nil {
return errorx.NewBiz("获取源表列信息失败")
return errorx.NewBiz("failed to get the source table column information")
}
// 以表名分组,存放每个表的列信息
@@ -394,35 +394,35 @@ func (app *dbTransferAppImpl) transferDbTables(ctx context.Context, logId uint64
}
// 通过公共列信息生成目标库的建表语句,并执行目标库建表
app.Log(ctx, logId, fmt.Sprintf("开始创建目标表: 表名:%s", tbName))
app.Log(ctx, logId, fmt.Sprintf("start creating the target table: %s", tbName))
sqlArr := targetDialect.GenerateTableDDL(targetCols, tableMap[tbName], true)
for _, sqlStr := range sqlArr {
_, err := targetConn.Exec(sqlStr)
if err != nil {
return errorx.NewBiz(fmt.Sprintf("创建目标表失败: 表名:%s, error: %s", tbName, err.Error()))
return errorx.NewBiz(fmt.Sprintf("failed to create target table: %s, error: %s", tbName, err.Error()))
}
}
app.Log(ctx, logId, fmt.Sprintf("创建目标表成功: 表名:%s", tbName))
app.Log(ctx, logId, fmt.Sprintf("target table created successfully: %s", tbName))
// 迁移数据
app.Log(ctx, logId, fmt.Sprintf("开始迁移数据: 表名:%s", tbName))
app.Log(ctx, logId, fmt.Sprintf("start transfer data: %s", tbName))
total, err := app.transferData(ctx, logId, task.Id, tbName, targetCols, srcConn, targetConn)
if err != nil {
return errorx.NewBiz(fmt.Sprintf("迁移数据失败: 表名:%s, error: %s", tbName, err.Error()))
return errorx.NewBiz(fmt.Sprintf("failed to transfer => table: %s, error: %s", tbName, err.Error()))
}
app.Log(ctx, logId, fmt.Sprintf("迁移数据成功: 表名:%s, 数据:%d 条", tbName, total))
app.Log(ctx, logId, fmt.Sprintf("successfully transfer data => table: %s, data: %d entries", tbName, total))
// 有些数据库迁移完数据之后,需要更新表自增序列为当前表最大值
targetDialect.UpdateSequence(tbName, targetCols)
// 迁移索引信息
app.Log(ctx, logId, fmt.Sprintf("开始迁移索引: 表名:%s", tbName))
app.Log(ctx, logId, fmt.Sprintf("start transfer index => table: %s", tbName))
err = app.transferIndex(ctx, tableMap[tbName], srcConn, targetConn)
if err != nil {
return errorx.NewBiz(fmt.Sprintf("迁移索引失败: 表名:%s, error: %s", tbName, err.Error()))
return errorx.NewBiz(fmt.Sprintf("failed to transfer index => table: %s, error: %s", tbName, err.Error()))
}
app.Log(ctx, logId, fmt.Sprintf("迁移索引成功: 表名:%s", tbName))
app.Log(ctx, logId, fmt.Sprintf("successfully transfer index => table: %s", tbName))
}
return nil
@@ -440,7 +440,7 @@ func (app *dbTransferAppImpl) transferData(ctx context.Context, logId uint64, ta
srcDialect := srcConn.GetDialect()
srcConverter := srcDialect.GetDataHelper()
targetDialect := targetConn.GetDialect()
logExtraKey := fmt.Sprintf("`%s` 当前已迁移数据量: ", tableName)
logExtraKey := fmt.Sprintf("`%s` amount of transfer data currently: ", tableName)
// 游标查询源表数据,并批量插入目标表
_, err = srcConn.WalkTableRows(context.Background(), tableName, func(row map[string]any, columns []*dbi.QueryColumn) error {
@@ -455,7 +455,7 @@ func (app *dbTransferAppImpl) transferData(ctx context.Context, logId uint64, ta
if total%batchSize == 0 {
err = app.transfer2Target(taskId, targetConn, targetColumns, result, targetDialect, tableName)
if err != nil {
logx.ErrorfContext(ctx, "批量插入目标表数据失败: %v", err)
logx.ErrorfContext(ctx, "batch insert data to target table failed: %v", err)
return err
}
result = result[:0]
@@ -472,7 +472,7 @@ func (app *dbTransferAppImpl) transferData(ctx context.Context, logId uint64, ta
if len(result) > 0 {
err = app.transfer2Target(taskId, targetConn, targetColumns, result, targetDialect, tableName)
if err != nil {
logx.ErrorfContext(ctx, "批量插入目标表数据失败,表名:%s error: %v", tableName, err)
logx.ErrorfContext(ctx, "batch insert data to target table failed => table: %s, error: %v", tableName, err)
return 0, err
}
}
@@ -483,7 +483,7 @@ func (app *dbTransferAppImpl) transferData(ctx context.Context, logId uint64, ta
func (app *dbTransferAppImpl) transfer2Target(taskId uint64, targetConn *dbi.DbConn, targetColumns []dbi.Column, result []map[string]any, targetDialect dbi.Dialect, tbName string) error {
if !app.IsRunning(taskId) {
return errorx.NewBiz("迁移终止")
return errorx.NewBiz("transfer stopped")
}
tx, err := targetConn.Begin()
@@ -533,7 +533,7 @@ func (app *dbTransferAppImpl) transfer2Target(taskId uint64, targetConn *dbi.DbC
defer func() {
if r := recover(); r != nil {
tx.Rollback()
logx.Errorf("批量插入目标表数据失败: %v", r)
logx.Errorf("batch insert data to target table failed: %v", r)
}
}()
@@ -545,7 +545,7 @@ func (app *dbTransferAppImpl) transferIndex(ctx context.Context, tableInfo dbi.T
// 查询源表索引信息
indexs, err := srcConn.GetMetadata().GetTableIndex(tableInfo.TableName)
if err != nil {
logx.Error("获取索引信息失败", err)
logx.Errorf("failed to get index information: %s", err)
return err
}
if len(indexs) == 0 {
@@ -564,22 +564,22 @@ func (app *dbTransferAppImpl) transferIndex(ctx context.Context, tableInfo dbi.T
}
func (d *dbTransferAppImpl) TimerDeleteTransferFile() {
logx.Debug("开始定时删除迁移文件...")
logx.Debug("start deleting transfer files periodically...")
scheduler.AddFun("@every 100m", func() {
dts, err := d.ListByCond(model.NewCond().Eq("mode", entity.DbTransferTaskModeFile).Ge("file_save_days", 1))
if err != nil {
logx.Errorf("定时获取数据库迁移至文件任务失败: %s", err.Error())
logx.Errorf("the task to periodically get database transfer to file failed: %s", err.Error())
return
}
for _, dt := range dts {
needDelFiles, err := d.transferFileApp.ListByCond(model.NewCond().Eq("task_id", dt.Id).Le("create_time", time.Now().AddDate(0, 0, -dt.FileSaveDays)))
if err != nil {
logx.Errorf("定时获取迁移文件失败: %s", err.Error())
logx.Errorf("failed to obtain the transfer file periodically: %s", err.Error())
continue
}
for _, nf := range needDelFiles {
if err := d.transferFileApp.Delete(context.Background(), nf.Id); err != nil {
logx.Errorf("定时删除迁移文件失败: %s", err.Error())
logx.Errorf("failed to delete transfer files periodically: %s", err.Error())
}
}
}

View File

@@ -64,7 +64,7 @@ func (d *DbInfo) GetLogDesc() string {
// 连接数据库
func (dbInfo *DbInfo) Conn(meta Meta) (*DbConn, error) {
if meta == nil {
return nil, errorx.NewBiz("数据库元信息接口不能为空")
return nil, errorx.NewBiz("the database meta information interface cannot be empty")
}
// 赋值Meta方便后续获取dialect等
@@ -78,14 +78,14 @@ func (dbInfo *DbInfo) Conn(meta Meta) (*DbConn, error) {
conn, err := meta.GetSqlDb(dbInfo)
if err != nil {
logx.Errorf("连接db失败: %s:%d/%s, err:%s", dbInfo.Host, dbInfo.Port, database, err.Error())
return nil, errorx.NewBiz(fmt.Sprintf("数据库连接失败: %s", err.Error()))
logx.Errorf("db connection failed: %s:%d/%s, err:%s", dbInfo.Host, dbInfo.Port, database, err.Error())
return nil, errorx.NewBiz(fmt.Sprintf("db connection failed: %s", err.Error()))
}
err = conn.Ping()
if err != nil {
logx.Errorf("db ping失败: %s:%d/%s, err:%s", dbInfo.Host, dbInfo.Port, database, err.Error())
return nil, errorx.NewBiz(fmt.Sprintf("数据库连接失败: %s", err.Error()))
logx.Errorf("db ping failed: %s:%d/%s, err:%s", dbInfo.Host, dbInfo.Port, database, err.Error())
return nil, errorx.NewBiz(fmt.Sprintf("db connection failed: %s", err.Error()))
}
dbc := &DbConn{Id: GetDbConnId(dbInfo.Id, database), Info: dbInfo}
@@ -97,7 +97,7 @@ func (dbInfo *DbInfo) Conn(meta Meta) (*DbConn, error) {
// 设置闲置连接数
conn.SetMaxIdleConns(1)
dbc.db = conn
logx.Infof("连接db: %s:%d/%s", dbInfo.Host, dbInfo.Port, database)
logx.Infof("db connection: %s:%d/%s", dbInfo.Host, dbInfo.Port, database)
return dbc, nil
}

View File

@@ -3,7 +3,6 @@ package dbi
import (
"database/sql"
"errors"
"fmt"
"io"
"mayfly-go/internal/db/dbm/sqlparser"
"mayfly-go/internal/db/dbm/sqlparser/pgsql"
@@ -12,6 +11,8 @@ import (
pq "gitee.com/liuzongyang/libpq"
)
const DefaultQuoter = `"`
const (
// -1. 无操作
DuplicateStrategyNone = -1
@@ -31,9 +32,6 @@ type DbCopyTable struct {
// BaseDialect 基础dialect在DefaultDialect 都有默认的实现方法
type BaseDialect interface {
// GetIdentifierQuoteString 用于引用 SQL 标识符(关键字)的字符串
GetIdentifierQuoteString() string
// QuoteIdentifier quotes an "identifier" (e.g. a table or a column name) to be
// used as part of an SQL statement. For example:
//
@@ -106,33 +104,16 @@ type DefaultDialect struct {
var _ (BaseDialect) = (*DefaultDialect)(nil)
func (dd *DefaultDialect) GetIdentifierQuoteString() string {
return `"`
}
func (dx *DefaultDialect) QuoteIdentifier(name string) string {
quoter := dx.GetIdentifierQuoteString()
// 兼容mssql
if quoter == "[" {
return fmt.Sprintf("[%s]", name)
}
end := strings.IndexRune(name, 0)
if end > -1 {
name = name[:end]
}
return quoter + strings.Replace(name, quoter, quoter+quoter, -1) + quoter
return DefaultQuoter + strings.Replace(name, DefaultQuoter, DefaultQuoter+DefaultQuoter, -1) + DefaultQuoter
}
func (dx *DefaultDialect) RemoveQuote(name string) string {
quoter := dx.GetIdentifierQuoteString()
// 兼容mssql
if quoter == "[" {
return strings.Trim(name, "[]")
}
return strings.ReplaceAll(name, quoter, "")
return strings.ReplaceAll(name, DefaultQuoter, "")
}
func (dd *DefaultDialect) QuoteEscape(str string) string {

View File

@@ -25,7 +25,7 @@ type Meta interface {
// GetSqlDb 根据数据库信息获取sql.DB
GetSqlDb(*DbInfo) (*sql.DB, error)
// GetDialect 获取数据库方言, 若一些接口(如 GetIdentifierQuoteString不需要DbConn则可以传nil
// GetDialect 获取数据库方言, 若一些接口(如QuoteIdentifier不需要DbConn则可以传nil
GetDialect(*DbConn) Dialect
// GetMetadata 获取元数据信息接口

View File

@@ -201,7 +201,7 @@ func GetLocalSql(file, key string) string {
}
bytes, err := metasql.ReadFile(file)
biz.ErrIsNilAppendErr(err, "获取sql meta文件内容失败: %s")
biz.ErrIsNilAppendErr(err, "failed to get the contents of the sql meta file: %s")
allSql := string(bytes)
sqls := strings.Split(allSql, "---------------------------------------")

View File

@@ -362,8 +362,12 @@ func (md *MssqlDialect) GenerateIndexDDL(indexs []dbi.Index, tableInfo dbi.Table
return sqls
}
func (md *MssqlDialect) GetIdentifierQuoteString() string {
return "["
func (dx *MssqlDialect) QuoteIdentifier(name string) string {
return fmt.Sprintf("[%s]", name)
}
func (dx *MssqlDialect) RemoveQuote(name string) string {
return strings.Trim(name, "[]")
}
func (md *MssqlDialect) GetDataHelper() dbi.DataHelper {

View File

@@ -11,6 +11,8 @@ import (
"time"
)
const Quoter = "`"
type MysqlDialect struct {
dbi.DefaultDialect
@@ -188,8 +190,16 @@ func (md *MysqlDialect) genColumnBasicSql(column dbi.Column) string {
return columnSql
}
func (md *MysqlDialect) GetIdentifierQuoteString() string {
return "`"
func (dx *MysqlDialect) QuoteIdentifier(name string) string {
end := strings.IndexRune(name, 0)
if end > -1 {
name = name[:end]
}
return Quoter + strings.Replace(name, Quoter, Quoter+Quoter, -1) + Quoter
}
func (dx *MysqlDialect) RemoveQuote(name string) string {
return strings.ReplaceAll(name, Quoter, "")
}
func (md *MysqlDialect) QuoteLiteral(literal string) string {

View File

@@ -32,14 +32,17 @@ func (v *MysqlVisitor) VisitSqlStatements(ctx *mysqlparser.SqlStatementsContext)
}
func (v *MysqlVisitor) VisitSqlStatement(ctx *mysqlparser.SqlStatementContext) interface{} {
if ctx.DmlStatement() != nil {
if c := ctx.DmlStatement(); c != nil {
return ctx.DmlStatement().Accept(v)
}
if ctx.DdlStatement() != nil {
if c := ctx.DdlStatement(); c != nil {
return ctx.DdlStatement().Accept(v)
}
if ctx.AdministrationStatement() != nil {
return ctx.AdministrationStatement().Accept(v)
if c := ctx.AdministrationStatement(); c != nil {
return c.Accept(v)
}
if c := ctx.UtilityStatement(); c != nil {
return c.Accept(v)
}
return sqlstmt.NewNode(ctx.GetParser(), ctx)
@@ -81,6 +84,16 @@ func (v *MysqlVisitor) VisitAdministrationStatement(ctx *mysqlparser.Administrat
return sqlstmt.NewNode(ctx.GetParser(), ctx)
}
func (v *MysqlVisitor) VisitUtilityStatement(ctx *mysqlparser.UtilityStatementContext) interface{} {
if c := ctx.SimpleDescribeStatement(); c != nil {
return c.Accept(v)
}
if c := ctx.FullDescribeStatement(); c != nil {
return c.Accept(v)
}
return sqlstmt.NewNode(ctx.GetParser(), ctx)
}
func (v *MysqlVisitor) VisitSimpleSelect(ctx *mysqlparser.SimpleSelectContext) interface{} {
sss := new(sqlstmt.SimpleSelectStmt)
sss.Node = sqlstmt.NewNode(ctx.GetParser(), ctx)

View File

@@ -0,0 +1,5 @@
package imsg
import "mayfly-go/pkg/i18n"
var En = map[i18n.MsgId]string{}

View File

@@ -0,0 +1,52 @@
package imsg
import (
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/i18n"
)
func init() {
i18n.AppendLangMsg(i18n.Zh_CN, Zh_CN)
i18n.AppendLangMsg(i18n.En, En)
}
const (
// db inst
LogDbInstSave = iota + consts.ImsgNumDb
LogDbInstDelete
ErrDbInstExist
// db
LogDbSave
LogDbDelete
LogDbRunSql
LogDbRunSqlFile
LogDbDump
SqlScriptRunFail
SqlScriptRunSuccess
SqlScripRunProgress
DbDumpErr
ErrDbNameExist
ErrDbNotAccess
ErrExistRunFailSql
ErrNeedSubmitWorkTicket
ErrNoLimitStmt
ErrLimitInvalid
// db transfer
LogDtsSave
LogDtsDelete
LogDtsChangeStatus
LogDtsRun
LogDtsStop
LogDtsDeleteFile
LogDtsRunSqlFile
// data sync
LogDataSyncSave
LogDataSyncDelete
LogDataSyncChangeStatus
)

View File

@@ -0,0 +1,42 @@
package imsg
import "mayfly-go/pkg/i18n"
var Zh_CN = map[i18n.MsgId]string{
LogDbInstSave: "DB-保存数据库实例",
LogDbInstDelete: "DB-删除数据库实例",
ErrDbInstExist: "该数据库实例已存在",
// db
LogDbSave: "DB-保存数据库",
LogDbDelete: "DB-删除数据库",
LogDbRunSql: "DB-运行SQL",
LogDbDump: "DB-导出数据库",
SqlScriptRunFail: "sql脚本执行失败",
SqlScriptRunSuccess: "sql脚本执行成功",
SqlScripRunProgress: "sql脚本执行进度",
DbDumpErr: "数据库导出失败",
ErrDbNameExist: "该实例下数据库名已存在",
ErrDbNotAccess: "未配置数据库【{{.dbName}}】的操作权限",
ErrExistRunFailSql: "存在执行错误的sql",
ErrNeedSubmitWorkTicket: "该操作需要提交工单审批执行",
ErrNoLimitStmt: "请完善分页信息后执行",
ErrLimitInvalid: "查询结果集数需小于系统配置的{{.count}}条",
// db transfer
LogDtsSave: "dts-保存数据迁移任务",
LogDtsDelete: "dts-删除数据迁移任务",
LogDtsChangeStatus: "dts-启停任务",
LogDtsRun: "dts-执行数据迁移任务",
LogDtsStop: "dts-终止数据迁移任务",
LogDtsDeleteFile: "dts-删除迁移文件",
LogDtsRunSqlFile: "dts-执行sql文件",
// data sync
LogDataSyncSave: "datasync-保存数据同步任务",
LogDataSyncDelete: "datasync-删除数据同步任务",
LogDataSyncChangeStatus: "datasync-启停任务",
}

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/db/api"
"mayfly-go/internal/db/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -24,9 +25,9 @@ func InitDbRouter(router *gin.RouterGroup) {
// 获取数据库列表
req.NewGet("", d.Dbs),
req.NewPost("", d.Save).Log(req.NewLogSave("db-保存数据库信息")),
req.NewPost("", d.Save).Log(req.NewLogSaveI(imsg.LogDbSave)),
req.NewDelete(":dbId", d.DeleteDb).Log(req.NewLogSave("db-删除数据库信息")),
req.NewDelete(":dbId", d.DeleteDb).Log(req.NewLogSaveI(imsg.LogDbDelete)),
req.NewGet(":dbId/t-create-ddl", d.GetTableDDL),
@@ -34,11 +35,11 @@ func InitDbRouter(router *gin.RouterGroup) {
req.NewGet(":dbId/pg/schemas", d.GetSchemas),
req.NewPost(":dbId/exec-sql", d.ExecSql).Log(req.NewLog("db-执行Sql")),
req.NewPost(":dbId/exec-sql", d.ExecSql).Log(req.NewLogI(imsg.LogDbRunSql)),
req.NewPost(":dbId/exec-sql-file", d.ExecSqlFile).Log(req.NewLogSave("db-执行Sql文件")).RequiredPermissionCode("db:sqlscript:run"),
req.NewPost(":dbId/exec-sql-file", d.ExecSqlFile).Log(req.NewLogSaveI(imsg.LogDbRunSqlFile)).RequiredPermissionCode("db:sqlscript:run"),
req.NewGet(":dbId/dump", d.DumpSql).Log(req.NewLogSave("db-导出sql文件")).NoRes(),
req.NewGet(":dbId/dump", d.DumpSql).Log(req.NewLogSaveI(imsg.LogDbDump)).NoRes(),
req.NewGet(":dbId/t-infos", d.TableInfos),

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/db/api"
"mayfly-go/internal/db/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -22,16 +23,16 @@ func InitDbDataSyncRouter(router *gin.RouterGroup) {
req.NewGet(":taskId/logs", d.Logs).RequiredPermissionCode("db:sync:log"),
// 保存任务 /datasync/save
req.NewPost("save", d.SaveTask).Log(req.NewLogSave("datasync-保存数据同步任务信息")).RequiredPermissionCode("db:sync:save"),
req.NewPost("save", d.SaveTask).Log(req.NewLogSaveI(imsg.LogDataSyncSave)).RequiredPermissionCode("db:sync:save"),
// 获取单个详情 /datasync/:taskId
req.NewGet(":taskId", d.GetTask),
// 删除任务 /datasync/:taskId/del
req.NewDelete(":taskId/del", d.DeleteTask).Log(req.NewLogSave("datasync-删除数据同步任务信息")).RequiredPermissionCode("db:sync:del"),
req.NewDelete(":taskId/del", d.DeleteTask).Log(req.NewLogSaveI(imsg.LogDataSyncDelete)).RequiredPermissionCode("db:sync:del"),
// 启停用任务 /datasync/status
req.NewPost(":taskId/status", d.ChangeStatus).Log(req.NewLogSave("datasync-启停任务")).RequiredPermissionCode("db:sync:status"),
req.NewPost(":taskId/status", d.ChangeStatus).Log(req.NewLogSaveI(imsg.LogDataSyncChangeStatus)).RequiredPermissionCode("db:sync:status"),
// 立即执行任务 /datasync/run
req.NewPost(":taskId/run", d.Run),

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/db/api"
"mayfly-go/internal/db/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -20,27 +21,27 @@ func InitDbTransferRouter(router *gin.RouterGroup) {
req.NewGet("", d.Tasks),
// 保存任务 /datasync/save
req.NewPost("save", d.SaveTask).Log(req.NewLogSave("dts-保存数据迁移任务信息")).RequiredPermissionCode("db:transfer:save"),
req.NewPost("save", d.SaveTask).Log(req.NewLogSaveI(imsg.LogDtsSave)).RequiredPermissionCode("db:transfer:save"),
// 删除任务 /datasync/:taskId/del
req.NewDelete(":taskId/del", d.DeleteTask).Log(req.NewLogSave("dts-删除数据迁移任务信息")).RequiredPermissionCode("db:transfer:del"),
req.NewDelete(":taskId/del", d.DeleteTask).Log(req.NewLogSaveI(imsg.LogDtsDelete)).RequiredPermissionCode("db:transfer:del"),
// 启停用任务 /datasync/status
req.NewPost(":taskId/status", d.ChangeStatus).Log(req.NewLogSave("dts-启停任务")).RequiredPermissionCode("db:transfer:status"),
req.NewPost(":taskId/status", d.ChangeStatus).Log(req.NewLogSaveI(imsg.LogDtsChangeStatus)).RequiredPermissionCode("db:transfer:status"),
// 立即执行任务 /datasync/run
req.NewPost(":taskId/run", d.Run).Log(req.NewLog("dts-执行数据迁移任务")).RequiredPermissionCode("db:transfer:run"),
req.NewPost(":taskId/run", d.Run).Log(req.NewLogI(imsg.LogDtsRun)).RequiredPermissionCode("db:transfer:run"),
// 停止正在执行中的任务
req.NewPost(":taskId/stop", d.Stop).Log(req.NewLogSave("dts-终止数据迁移任务")).RequiredPermissionCode("db:transfer:run"),
req.NewPost(":taskId/stop", d.Stop).Log(req.NewLogSaveI(imsg.LogDtsStop)).RequiredPermissionCode("db:transfer:run"),
// 导出文件管理-列表
req.NewGet("/files/:taskId", d.Files),
// 导出文件管理-删除
req.NewPost("/files/del/:fileId", d.FileDel).Log(req.NewLogSave("dts-删除迁移文件")).RequiredPermissionCode("db:transfer:files:del"),
req.NewPost("/files/del/:fileId", d.FileDel).Log(req.NewLogSaveI(imsg.LogDtsDeleteFile)).RequiredPermissionCode("db:transfer:files:del"),
req.NewPost("/files/run", d.FileRun).Log(req.NewLogSave("dts-执行sql文件")).RequiredPermissionCode("db:transfer:files:run"),
req.NewPost("/files/run", d.FileRun).Log(req.NewLogSaveI(imsg.LogDtsRunSqlFile)).RequiredPermissionCode("db:transfer:files:run"),
}
req.BatchSetGroup(instances, reqs[:])

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/db/api"
"mayfly-go/internal/db/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -21,7 +22,7 @@ func InitInstanceRouter(router *gin.RouterGroup) {
req.NewPost("/test-conn", d.TestConn),
req.NewPost("", d.SaveInstance).Log(req.NewLogSave("db-保存数据库实例信息")),
req.NewPost("", d.SaveInstance).Log(req.NewLogSaveI(imsg.LogDbInstSave)),
req.NewGet(":instanceId", d.GetInstance),
@@ -33,7 +34,7 @@ func InitInstanceRouter(router *gin.RouterGroup) {
req.NewGet(":instanceId/server-info", d.GetDbServer),
req.NewDelete(":instanceId", d.DeleteInstance).Log(req.NewLogSave("db-删除数据库实例")),
req.NewDelete(":instanceId", d.DeleteInstance).Log(req.NewLogSaveI(imsg.LogDbInstDelete)),
}
req.BatchSetGroup(instances, reqs[:])

View File

@@ -15,7 +15,7 @@ type File struct {
func (f *File) GetFileByKeys(rc *req.Ctx) {
keysStr := rc.PathParam("keys")
biz.NotEmpty(keysStr, "keys不能为空")
biz.NotEmpty(keysStr, "keys cannot be empty")
var files []vo.SimpleFile
err := f.FileApp.ListByCondToAny(model.NewCond().In("file_key", strings.Split(keysStr, ",")), &files)
@@ -25,7 +25,7 @@ func (f *File) GetFileByKeys(rc *req.Ctx) {
func (f *File) GetFileContent(rc *req.Ctx) {
key := rc.PathParam("key")
biz.NotEmpty(key, "key不能为空")
biz.NotEmpty(key, "key cannot be empty")
filename, reader, err := f.FileApp.GetReader(rc.MetaCtx, key)
if err != nil {
@@ -38,9 +38,9 @@ func (f *File) GetFileContent(rc *req.Ctx) {
func (f *File) Upload(rc *req.Ctx) {
multipart, err := rc.GetRequest().MultipartReader()
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
biz.ErrIsNilAppendErr(err, "read file error: %s")
file, err := multipart.NextPart()
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
biz.ErrIsNilAppendErr(err, "read file error: %s")
defer file.Close()
fileKey, err := f.FileApp.Upload(rc.MetaCtx, rc.Query("fileKey"), file.FileName(), file)

View File

@@ -114,7 +114,7 @@ func (f *fileAppImpl) NewWriter(ctx context.Context, canEmptyFileKey string, fil
if e != nil {
err := *e
if err != nil {
logx.Errorf("写入文件业务逻辑处理失败: %s", err.Error())
logx.Errorf("the write file business logic failed: %s", err.Error())
// 删除已经创建的文件
f.remove(ctx, file)
return err
@@ -133,7 +133,7 @@ func (f *fileAppImpl) NewWriter(ctx context.Context, canEmptyFileKey string, fil
func (f *fileAppImpl) GetReader(ctx context.Context, fileKey string) (string, io.ReadCloser, error) {
file := &entity.File{FileKey: fileKey}
if err := f.GetByCond(file); err != nil {
return "", nil, errorx.NewBiz("文件不存在")
return "", nil, errorx.NewBiz("file not found")
}
r, err := os.Open(filepath.Join(config.GetFileConfig().BasePath, file.Path))
return file.Filename, r, err
@@ -142,7 +142,7 @@ func (f *fileAppImpl) GetReader(ctx context.Context, fileKey string) (string, io
func (f *fileAppImpl) Remove(ctx context.Context, fileKey string) error {
file := &entity.File{FileKey: fileKey}
if err := f.GetByCond(file); err != nil {
return errorx.NewBiz("文件不存在")
return errorx.NewBiz("file not found")
}
f.DeleteById(ctx, file.Id)
return f.remove(ctx, file)
@@ -172,7 +172,7 @@ func (f *fileAppImpl) newWriter(filename string) (string, io.WriteCloser, error)
func (f *fileAppImpl) remove(ctx context.Context, file *entity.File) error {
if err := os.Remove(filepath.Join(config.GetFileConfig().BasePath, file.Path)); err != nil {
logx.ErrorfContext(ctx, "删除旧文件[%s] 失败: %s", file.Path, err.Error())
logx.ErrorfContext(ctx, "failed to delete old file [%s]: %s", file.Path, err.Error())
return err
}
return nil

View File

@@ -11,8 +11,9 @@ import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"strconv"
"strings"
"github.com/may-fly/cast"
)
type Procdef struct {
@@ -55,8 +56,6 @@ func (p *Procdef) Delete(rc *req.Ctx) {
ids := strings.Split(idsStr, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
biz.ErrIsNilAppendErr(p.ProcdefApp.DeleteProcdef(rc.MetaCtx, uint64(value)), "删除失败:%s")
biz.ErrIsNilAppendErr(p.ProcdefApp.DeleteProcdef(rc.MetaCtx, cast.ToUint64(v)), "delete error: %s")
}
}

View File

@@ -54,7 +54,7 @@ func (p *Procinst) ProcinstCancel(rc *req.Ctx) {
func (p *Procinst) GetProcinstDetail(rc *req.Ctx) {
pi, err := p.ProcinstApp.GetById(uint64(rc.PathParamInt("id")))
biz.ErrIsNil(err, "流程实例不存在")
biz.ErrIsNil(err, "procinst not found")
pivo := new(vo.ProcinstVO)
structx.Copy(pivo, pi)

View File

@@ -36,7 +36,7 @@ func FlowBizHandle(ctx context.Context, bizHandleParam *BizHandleParam) (any, er
flowBizType := bizHandleParam.Procinst.BizType
if handler, ok := handlers[flowBizType]; !ok {
logx.Warnf("flow biz handler not found: bizType=%s", flowBizType)
return nil, errorx.NewBiz("业务处理器不存在")
return nil, errorx.NewBiz("flow biz handler not found")
} else {
return handler.FlowBizHandle(ctx, bizHandleParam)
}

View File

@@ -5,6 +5,7 @@ import (
"mayfly-go/internal/flow/application/dto"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/domain/repository"
"mayfly-go/internal/flow/imsg"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/base"
@@ -58,12 +59,12 @@ func (p *procdefAppImpl) SaveProcdef(ctx context.Context, defParam *dto.SaveProc
if def.Id == 0 {
if p.GetByCond(&entity.Procdef{DefKey: def.DefKey}) == nil {
return errorx.NewBiz("该流程实例key已存在")
return errorx.NewBizI(ctx, imsg.ErrProcdefKeyExist)
}
} else {
// 防止误修改key
def.DefKey = ""
if err := p.canModify(def.Id); err != nil {
if err := p.canModify(ctx, def.Id); err != nil {
return err
}
}
@@ -76,7 +77,7 @@ func (p *procdefAppImpl) SaveProcdef(ctx context.Context, defParam *dto.SaveProc
}
func (p *procdefAppImpl) DeleteProcdef(ctx context.Context, defId uint64) error {
if err := p.canModify(defId); err != nil {
if err := p.canModify(ctx, defId); err != nil {
return err
}
return p.DeleteById(ctx, defId)
@@ -105,12 +106,12 @@ func (p *procdefAppImpl) GetProcdefByResource(ctx context.Context, resourceType
}
// 判断该流程实例是否可以执行修改操作
func (p *procdefAppImpl) canModify(prodefId uint64) error {
func (p *procdefAppImpl) canModify(ctx context.Context, prodefId uint64) error {
if activeInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstStatusActive}); activeInstCount > 0 {
return errorx.NewBiz("存在运行中的流程实例,无法操作")
return errorx.NewBizI(ctx, imsg.ErrExistProcinstRunning)
}
if suspInstCount := p.procinstApp.CountByCond(&entity.Procinst{ProcdefId: prodefId, Status: entity.ProcinstStatusSuspended}); suspInstCount > 0 {
return errorx.NewBiz("存在挂起中的流程实例,无法操作")
return errorx.NewBizI(ctx, imsg.ErrExistProcinstSuspended)
}
return nil
}

View File

@@ -6,9 +6,11 @@ import (
"mayfly-go/internal/flow/application/dto"
"mayfly-go/internal/flow/domain/entity"
"mayfly-go/internal/flow/domain/repository"
"mayfly-go/internal/flow/imsg"
"mayfly-go/pkg/base"
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/anyx"
@@ -65,11 +67,11 @@ func (p *procinstAppImpl) GetProcinstTasks(condition *entity.ProcinstTaskQuery,
func (p *procinstAppImpl) StartProc(ctx context.Context, procdefId uint64, reqParam *dto.StarProc) (*entity.Procinst, error) {
procdef, err := p.procdefApp.GetById(procdefId)
if err != nil {
return nil, errorx.NewBiz("流程实例[%d]不存在", procdefId)
return nil, errorx.NewBiz("procdef not found")
}
if procdef.Status != entity.ProcdefStatusEnable {
return nil, errorx.NewBiz("该流程定义非启用状态")
return nil, errorx.NewBizI(ctx, imsg.ErrProcdefNotEnable)
}
bizKey := reqParam.BizKey
@@ -99,22 +101,22 @@ func (p *procinstAppImpl) StartProc(ctx context.Context, procdefId uint64, reqPa
func (p *procinstAppImpl) CancelProc(ctx context.Context, procinstId uint64) error {
procinst, err := p.GetById(procinstId)
if err != nil {
return errorx.NewBiz("流程不存在")
return errorx.NewBiz("procinst not found")
}
la := contextx.GetLoginAccount(ctx)
if la == nil {
return errorx.NewBiz("未登录")
return errorx.NewBiz("no login")
}
if procinst.CreatorId != la.Id {
return errorx.NewBiz("只能取消自己创建的流程")
return errorx.NewBizI(ctx, imsg.ErrProcinstCancelSelf)
}
procinst.Status = entity.ProcinstStatusCancelled
procinst.BizStatus = entity.ProcinstBizStatusNo
procinst.SetEnd()
return p.Tx(ctx, func(ctx context.Context) error {
return p.cancelInstTasks(ctx, procinstId, "流程已取消")
return p.cancelInstTasks(ctx, procinstId, i18n.T(imsg.ErrProcinstCancelled))
}, func(ctx context.Context) error {
return p.Save(ctx, procinst)
}, func(ctx context.Context) error {
@@ -239,13 +241,13 @@ func (p *procinstAppImpl) triggerProcinstStatusChangeEvent(ctx context.Context,
if procinst.Status != entity.ProcinstStatusCompleted {
procinst.Status = entity.ProcinstStatusTerminated
procinst.SetEnd()
p.cancelInstTasks(ctx, procinst.Id, "业务处理失败")
p.cancelInstTasks(ctx, procinst.Id, i18n.T(imsg.ErrBizHandlerFail))
}
procinst.BizStatus = entity.ProcinstBizStatusFail
if procinst.BizHandleRes == "" {
procinst.BizHandleRes = err.Error()
} else {
logx.Errorf("流程业务[%s]处理失败: %v", procinst.BizKey, err.Error())
logx.Errorf("process business [%s] processing failed: %v", procinst.BizKey, err.Error())
}
return p.UpdateById(ctx, procinst)
}
@@ -265,12 +267,12 @@ func (p *procinstAppImpl) triggerProcinstStatusChangeEvent(ctx context.Context,
func (p *procinstAppImpl) getAndValidInstTask(ctx context.Context, instTaskId uint64) (*entity.ProcinstTask, error) {
instTask, err := p.procinstTaskRepo.GetById(instTaskId)
if err != nil {
return nil, errorx.NewBiz("流程实例任务不存在")
return nil, errorx.NewBiz("procinst not found")
}
la := contextx.GetLoginAccount(ctx)
if instTask.Assignee != fmt.Sprintf("%d", la.Id) {
return nil, errorx.NewBiz("当前用户不是任务处理人,无法完成任务")
return nil, errorx.NewBiz("the current user is not a task handler and cannot complete the task")
}
return instTask, nil

View File

@@ -0,0 +1,24 @@
package imsg
import "mayfly-go/pkg/i18n"
var En = map[i18n.MsgId]string{
LogProcdefSave: "ProcDef - Save",
LogProcdefDelete: "ProcDef - Delete",
ErrProcdefKeyExist: "the process instance key already exists",
ErrExistProcinstRunning: "There is a running process instance that cannot be manipulated",
ErrExistProcinstSuspended: "There is a pending process instance that cannot be manipulated",
// procinst
LogProcinstStart: "Process - Start",
LogProcinstCancel: "Process - Cancel",
LogCompleteTask: "Process - Completion of task",
LogRejectTask: "Process - Task rejection",
LogBackTask: "Process - Task rejection",
ErrProcdefNotEnable: "The process defines a non-enabled state",
ErrProcinstCancelSelf: "You can only cancel processes you initiated",
ErrProcinstCancelled: "Process has been cancelled",
ErrBizHandlerFail: "Business process failure",
}

View File

@@ -0,0 +1,32 @@
package imsg
import (
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/i18n"
)
func init() {
i18n.AppendLangMsg(i18n.Zh_CN, Zh_CN)
i18n.AppendLangMsg(i18n.En, En)
}
const (
LogProcdefSave = iota + consts.ImsgNumFlow
LogProcdefDelete
ErrProcdefKeyExist
ErrExistProcinstRunning
ErrExistProcinstSuspended
// procinst
LogProcinstStart
LogProcinstCancel
LogCompleteTask
LogRejectTask
LogBackTask
ErrProcdefNotEnable
ErrProcinstCancelSelf
ErrProcinstCancelled
ErrBizHandlerFail
)

View File

@@ -0,0 +1,24 @@
package imsg
import "mayfly-go/pkg/i18n"
var Zh_CN = map[i18n.MsgId]string{
LogProcdefSave: "流程定义-保存",
LogProcdefDelete: "流程定义-删除",
ErrProcdefKeyExist: "该流程实例key已存在",
ErrExistProcinstRunning: "存在运行中的流程实例,无法操作",
ErrExistProcinstSuspended: "存在挂起中的流程实例,无法操作",
// procinst
LogProcinstStart: "流程-启动",
LogProcinstCancel: "流程-取消",
LogCompleteTask: "流程-任务完成",
LogRejectTask: "流程-任务拒绝",
LogBackTask: "流程-任务驳回",
ErrProcdefNotEnable: "该流程定义非启用状态",
ErrProcinstCancelSelf: "只能取消自己发起的流程",
ErrProcinstCancelled: "流程已取消",
ErrBizHandlerFail: "业务处理失败",
}

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/flow/api"
"mayfly-go/internal/flow/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -20,9 +21,9 @@ func InitProcdefouter(router *gin.RouterGroup) {
req.NewGet("/:resourceType/:resourceCode", p.GetProcdef),
req.NewPost("", p.Save).Log(req.NewLogSave("流程定义-保存")).RequiredPermissionCode("flow:procdef:save"),
req.NewPost("", p.Save).Log(req.NewLogSaveI(imsg.LogProcdefSave)).RequiredPermissionCode("flow:procdef:save"),
req.NewDelete(":id", p.Delete).Log(req.NewLogSave("流程定义-删除")).RequiredPermissionCode("flow:procdef:del"),
req.NewDelete(":id", p.Delete).Log(req.NewLogSaveI(imsg.LogProcdefDelete)).RequiredPermissionCode("flow:procdef:del"),
}
req.BatchSetGroup(reqGroup, reqs[:])

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/flow/api"
"mayfly-go/internal/flow/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -20,17 +21,17 @@ func InitProcinstRouter(router *gin.RouterGroup) {
req.NewGet("/:id", p.GetProcinstDetail),
req.NewPost("/start", p.ProcinstStart).Log(req.NewLogSave("流程-启动")),
req.NewPost("/start", p.ProcinstStart).Log(req.NewLogSaveI(imsg.LogProcinstStart)),
req.NewPost("/:id/cancel", p.ProcinstCancel).Log(req.NewLogSave("流程-取消")),
req.NewPost("/:id/cancel", p.ProcinstCancel).Log(req.NewLogSaveI(imsg.LogProcinstCancel)),
req.NewGet("/tasks", p.GetTasks),
req.NewPost("/tasks/complete", p.CompleteTask).Log(req.NewLogSave("流程-任务完成")),
req.NewPost("/tasks/complete", p.CompleteTask).Log(req.NewLogSaveI(imsg.LogCompleteTask)),
req.NewPost("/tasks/reject", p.RejectTask).Log(req.NewLogSave("流程-任务拒绝")),
req.NewPost("/tasks/reject", p.RejectTask).Log(req.NewLogSaveI(imsg.LogRejectTask)),
req.NewPost("/tasks/back", p.BackTask).Log(req.NewLogSave("流程-任务驳回")),
req.NewPost("/tasks/back", p.BackTask).Log(req.NewLogSaveI(imsg.LogBackTask)),
}
req.BatchSetGroup(reqGroup, reqs[:])

View File

@@ -10,6 +10,7 @@ import (
"mayfly-go/internal/machine/application/dto"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/guac"
"mayfly-go/internal/machine/imsg"
"mayfly-go/internal/machine/mcm"
tagapp "mayfly-go/internal/tag/application"
tagentity "mayfly-go/internal/tag/domain/entity"
@@ -85,7 +86,7 @@ func (m *Machine) Machines(rc *req.Ctx) {
func (m *Machine) SimpleMachieInfo(rc *req.Ctx) {
machineCodesStr := rc.Query("codes")
biz.NotEmpty(machineCodesStr, "codes不能为空")
biz.NotEmpty(machineCodesStr, "codes cannot be empty")
var vos []vo.SimpleMachineVO
m.MachineApp.ListByCondToAny(model.NewCond().In("code", strings.Split(machineCodesStr, ",")), &vos)
@@ -94,7 +95,7 @@ func (m *Machine) SimpleMachieInfo(rc *req.Ctx) {
func (m *Machine) MachineStats(rc *req.Ctx) {
cli, err := m.MachineApp.GetCli(GetMachineId(rc))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
biz.ErrIsNilAppendErr(err, "connection error: %s")
rc.ResData = cli.GetAllStats()
}
@@ -116,7 +117,7 @@ func (m *Machine) TestConn(rc *req.Ctx) {
machineForm := new(form.MachineForm)
me := req.BindJsonAndCopyTo(rc, machineForm, new(entity.Machine))
// 测试连接
biz.ErrIsNilAppendErr(m.MachineApp.TestConn(me, machineForm.AuthCerts[0]), "该机器无法连接: %s")
biz.ErrIsNilAppendErr(m.MachineApp.TestConn(me, machineForm.AuthCerts[0]), "connection error: %s")
}
func (m *Machine) ChangeStatus(rc *req.Ctx) {
@@ -132,9 +133,7 @@ func (m *Machine) DeleteMachine(rc *req.Ctx) {
ids := strings.Split(idsStr, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
m.MachineApp.Delete(rc.MetaCtx, uint64(value))
m.MachineApp.Delete(rc.MetaCtx, cast.ToUint64(v))
}
}
@@ -157,30 +156,30 @@ func (m *Machine) GetProcess(rc *req.Ctx) {
cmd += "| head -n " + fmt.Sprintf("%d", count)
cli, err := m.MachineApp.GetCli(GetMachineId(rc))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
biz.ErrIsNilAppendErr(err, "connection error: %s")
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.CodePath...), "%s")
res, err := cli.Run(cmd)
biz.ErrIsNilAppendErr(err, "获取进程信息失败: %s")
biz.ErrIsNil(err)
rc.ResData = res
}
// 终止进程
func (m *Machine) KillProcess(rc *req.Ctx) {
pid := rc.Query("pid")
biz.NotEmpty(pid, "进程id不能为空")
biz.NotEmpty(pid, "pid cannot be empty")
cli, err := m.MachineApp.GetCli(GetMachineId(rc))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
biz.ErrIsNilAppendErr(err, "connection error: %s")
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.CodePath...), "%s")
res, err := cli.Run("sudo kill -9 " + pid)
biz.ErrIsNil(err, "终止进程失败: %s", res)
biz.ErrIsNil(err, "kill fail: %s", res)
}
func (m *Machine) GetUsers(rc *req.Ctx) {
cli, err := m.MachineApp.GetCli(GetMachineId(rc))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
biz.ErrIsNilAppendErr(err, "connection error: %s")
res, err := cli.GetUsers()
biz.ErrIsNil(err)
rc.ResData = res
@@ -188,7 +187,7 @@ func (m *Machine) GetUsers(rc *req.Ctx) {
func (m *Machine) GetGroups(rc *req.Ctx) {
cli, err := m.MachineApp.GetCli(GetMachineId(rc))
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
biz.ErrIsNilAppendErr(err, "connection error: %s")
res, err := cli.GetGroups()
biz.ErrIsNil(err)
rc.ResData = res
@@ -204,17 +203,17 @@ func (m *Machine) WsSSH(g *gin.Context) {
wsConn.Close()
}
}()
biz.ErrIsNilAppendErr(err, "升级websocket失败: %s")
biz.ErrIsNilAppendErr(err, "Upgrade websocket fail: %s")
wsConn.WriteMessage(websocket.TextMessage, []byte("Connecting to host..."))
// 权限校验
rc := req.NewCtxWithGin(g).WithRequiredPermission(req.NewPermission("machine:terminal"))
if err = req.PermissionHandler(rc); err != nil {
panic(errorx.NewBiz(mcm.GetErrorContentRn("您没有权限操作该机器终端,请重新登录后再试~")))
panic(errorx.NewBiz(mcm.GetErrorContentRn("You do not have permission to operate the machine terminal, please log in again and try again ~")))
}
cli, err := m.MachineApp.NewCli(GetMachineAc(rc))
biz.ErrIsNilAppendErr(err, mcm.GetErrorContentRn("获取客户端连接失败: %s"))
biz.ErrIsNilAppendErr(err, mcm.GetErrorContentRn("connection error: %s"))
defer cli.Close()
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.CodePath...), mcm.GetErrorContentRn("%s"))
@@ -224,12 +223,12 @@ func (m *Machine) WsSSH(g *gin.Context) {
rows := rc.QueryIntDefault("rows", 32)
// 记录系统操作日志
rc.WithLog(req.NewLogSave("机器-终端操作"))
rc.WithLog(req.NewLogSaveI(imsg.LogMachineTerminalOp))
rc.ReqParam = cli.Info
req.LogHandler(rc)
err = m.MachineTermOpApp.TermConn(rc.MetaCtx, cli, wsConn, rows, cols)
biz.ErrIsNilAppendErr(err, mcm.GetErrorContentRn("连接失败: %s"))
biz.ErrIsNilAppendErr(err, mcm.GetErrorContentRn("connect fail: %s"))
}
func (m *Machine) MachineTermOpRecords(rc *req.Ctx) {
@@ -263,7 +262,7 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
rc := req.NewCtxWithGin(g).WithRequiredPermission(req.NewPermission("machine:terminal"))
if err = req.PermissionHandler(rc); err != nil {
panic(errorx.NewBiz(mcm.GetErrorContentRn("您没有权限操作该机器终端,请重新登录后再试~")))
panic(errorx.NewBiz(mcm.GetErrorContentRn("You do not have permission to operate the machine terminal, please log in again and try again ~")))
}
ac := GetMachineAc(rc)
@@ -344,12 +343,12 @@ func (m *Machine) WsGuacamole(g *gin.Context) {
func GetMachineId(rc *req.Ctx) uint64 {
machineId, _ := strconv.Atoi(rc.PathParam("machineId"))
biz.IsTrue(machineId != 0, "machineId错误")
biz.IsTrue(machineId != 0, "machineId error")
return uint64(machineId)
}
func GetMachineAc(rc *req.Ctx) string {
ac := rc.PathParam("ac")
biz.IsTrue(ac != "", "authCertName错误")
biz.IsTrue(ac != "", "authCertName error")
return ac
}

View File

@@ -65,7 +65,7 @@ func (m *MachineCronJob) Delete(rc *req.Ctx) {
func (m *MachineCronJob) RunCronJob(rc *req.Ctx) {
cronJobKey := rc.PathParam("key")
biz.NotEmpty(cronJobKey, "cronJob key不能为空")
biz.NotEmpty(cronJobKey, "cronJob key cannot be empty")
m.MachineCronJobApp.RunCronJob(cronJobKey)
}

View File

@@ -10,11 +10,13 @@ import (
"mayfly-go/internal/machine/application/dto"
"mayfly-go/internal/machine/config"
"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/pkg/biz"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/i18n"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/anyx"
@@ -73,43 +75,45 @@ func (m *MachineFile) CreateFile(rc *req.Ctx) {
var mi *mcm.MachineInfo
var err error
if opForm.Type == dir {
attrs["type"] = "目录"
attrs["type"] = "Folder"
mi, err = m.MachineFileApp.MkDir(rc.MetaCtx, opForm.MachineFileOp)
} else {
attrs["type"] = "文件"
attrs["type"] = "File"
mi, err = m.MachineFileApp.CreateFile(rc.MetaCtx, opForm.MachineFileOp)
}
attrs["machine"] = mi
rc.ReqParam = attrs
biz.ErrIsNilAppendErr(err, "创建目录失败: %s")
biz.ErrIsNil(err)
}
func (m *MachineFile) ReadFileContent(rc *req.Ctx) {
opForm := req.BindQuery(rc, new(dto.MachineFileOp))
readPath := opForm.Path
ctx := rc.MetaCtx
// 特殊处理rdp文件
if opForm.Protocol == entity.MachineProtocolRdp {
path := m.MachineFileApp.GetRdpFilePath(rc.GetLoginAccount(), opForm.Path)
fi, err := os.Stat(path)
biz.ErrIsNilAppendErr(err, "读取文件内容失败: %s")
biz.IsTrue(fi.Size() < max_read_size, "文件超过1m请使用下载查看")
biz.ErrIsNil(err)
biz.IsTrueI(ctx, fi.Size() < max_read_size, imsg.ErrFileTooLargeUseDownload)
datas, err := os.ReadFile(path)
biz.ErrIsNilAppendErr(err, "读取文件内容失败: %s")
biz.ErrIsNil(err)
rc.ResData = string(datas)
return
}
sftpFile, mi, err := m.MachineFileApp.ReadFile(rc.MetaCtx, opForm)
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
biz.ErrIsNil(err)
defer sftpFile.Close()
fileInfo, _ := sftpFile.Stat()
filesize := fileInfo.Size()
biz.IsTrueI(ctx, filesize < max_read_size, imsg.ErrFileTooLargeUseDownload)
biz.IsTrue(filesize < max_read_size, "文件超过1m请使用下载查看")
datas, err := io.ReadAll(sftpFile)
biz.ErrIsNilAppendErr(err, "读取文件内容失败: %s")
biz.ErrIsNil(err)
rc.ResData = string(datas)
}
@@ -136,7 +140,7 @@ func (m *MachineFile) DownloadFile(rc *req.Ctx) {
sftpFile, mi, err := m.MachineFileApp.ReadFile(rc.MetaCtx, opForm)
rc.ReqParam = collx.Kvs("machine", mi, "path", readPath)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
biz.ErrIsNilAppendErr(err, "open file error: %s")
defer sftpFile.Close()
rc.Download(sftpFile, fileName)
@@ -148,7 +152,7 @@ func (m *MachineFile) GetDirEntry(rc *req.Ctx) {
rc.ReqParam = fmt.Sprintf("path: %s", readPath)
fis, err := m.MachineFileApp.ReadDir(rc.MetaCtx, opForm)
biz.ErrIsNilAppendErr(err, "读取目录失败: %s")
biz.ErrIsNilAppendErr(err, "read dir error: %s")
fisVO := make([]vo.MachineFileInfo, 0)
for _, fi := range fis {
@@ -202,7 +206,7 @@ func (m *MachineFile) WriteFileContent(rc *req.Ctx) {
mi, err := m.MachineFileApp.WriteFileContent(rc.MetaCtx, opForm.MachineFileOp, []byte(opForm.Content))
rc.ReqParam = collx.Kvs("machine", mi, "path", path)
biz.ErrIsNilAppendErr(err, "打开文件失败: %s")
biz.ErrIsNilAppendErr(err, "open file error: %s")
}
func (m *MachineFile) UploadFile(rc *req.Ctx) {
@@ -212,10 +216,12 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
authCertName := rc.PostForm("authCertName")
fileheader, err := rc.FormFile("file")
biz.ErrIsNilAppendErr(err, "读取文件失败: %s")
biz.ErrIsNilAppendErr(err, "read form file error: %s")
ctx := rc.MetaCtx
maxUploadFileSize := config.GetMachine().UploadMaxFileSize
biz.IsTrue(fileheader.Size <= maxUploadFileSize, "文件大小不能超过%d字节", maxUploadFileSize)
biz.IsTrueI(ctx, fileheader.Size <= maxUploadFileSize, imsg.ErrUploadFileOutOfLimit, "size", maxUploadFileSize)
file, _ := fileheader.Open()
defer file.Close()
@@ -223,8 +229,8 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
la := rc.GetLoginAccount()
defer func() {
if anyx.ToString(recover()) != "" {
logx.Errorf("文件上传失败: %s", err)
m.MsgApp.CreateAndSend(la, msgdto.ErrSysMsg("文件上传失败", fmt.Sprintf("执行文件上传失败:\n<-e : %s", err)))
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)))
}
}()
@@ -235,11 +241,11 @@ func (m *MachineFile) UploadFile(rc *req.Ctx) {
Path: path,
}
mi, err := m.MachineFileApp.UploadFile(rc.MetaCtx, opForm, fileheader.Filename, file)
mi, err := m.MachineFileApp.UploadFile(ctx, opForm, fileheader.Filename, file)
rc.ReqParam = collx.Kvs("machine", mi, "path", fmt.Sprintf("%s/%s", path, fileheader.Filename))
biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
biz.ErrIsNilAppendErr(err, "upload file error: %s")
// 保存消息并发送文件上传成功通知
m.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg("文件上传成功", fmt.Sprintf("[%s]文件已成功上传至 %s[%s:%s]", fileheader.Filename, mi.Name, mi.Ip, path)))
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 {
@@ -249,18 +255,19 @@ type FolderFile struct {
func (m *MachineFile) UploadFolder(rc *req.Ctx) {
mf, err := rc.MultipartForm()
biz.ErrIsNilAppendErr(err, "获取表单信息失败: %s")
biz.ErrIsNilAppendErr(err, "get multipart form error: %s")
basePath := mf.Value["basePath"][0]
biz.NotEmpty(basePath, "基础路径不能为空")
biz.NotEmpty(basePath, "basePath cannot be empty")
fileheaders := mf.File["files"]
biz.IsTrue(len(fileheaders) > 0, "文件不能为空")
biz.IsTrue(len(fileheaders) > 0, "files cannot be empty")
allFileSize := collx.ArrayReduce(fileheaders, 0, func(i int64, fh *multipart.FileHeader) int64 {
return i + fh.Size
})
ctx := rc.MetaCtx
maxUploadFileSize := config.GetMachine().UploadMaxFileSize
biz.IsTrue(allFileSize <= maxUploadFileSize, "文件夹总大小不能超过%d字节", maxUploadFileSize)
biz.IsTrueI(ctx, allFileSize <= maxUploadFileSize, imsg.ErrUploadFileOutOfLimit, "size", maxUploadFileSize)
paths := mf.Value["paths"]
authCertName := mf.Value["authCertName"][0]
@@ -275,7 +282,7 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
}
if protocol == entity.MachineProtocolRdp {
m.MachineFileApp.UploadFiles(rc.MetaCtx, opForm, basePath, fileheaders, paths)
m.MachineFileApp.UploadFiles(ctx, opForm, basePath, fileheaders, paths)
return
}
@@ -295,7 +302,7 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
dir := filepath.Dir(path)
// 目录已建,则无需重复建
if !mkdirs[dir] {
biz.ErrIsNilAppendErr(sftpCli.MkdirAll(basePath+"/"+dir), "创建目录失败: %s")
biz.ErrIsNilAppendErr(sftpCli.MkdirAll(basePath+"/"+dir), "create dir error: %s")
mkdirs[dir] = true
}
folderFiles[i] = FolderFile{
@@ -321,10 +328,10 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
wg.Done()
if err := recover(); err != nil {
isSuccess = false
logx.Errorf("文件上传失败: %s", err)
logx.Errorf("upload file error: %s", err)
switch t := err.(type) {
case *errorx.BizError:
m.MsgApp.CreateAndSend(la, msgdto.ErrSysMsg("文件上传失败", fmt.Sprintf("执行文件上传失败:\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error())))
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())))
}
}
}()
@@ -335,10 +342,10 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
file, _ := fileHeader.Open()
defer file.Close()
logx.Debugf("上传文件夹: dir=%s -> filename=%s", dir, fileHeader.Filename)
logx.Debugf("upload folder: dir=%s -> filename=%s", dir, fileHeader.Filename)
createfile, err := sftpCli.Create(fmt.Sprintf("%s/%s/%s", basePath, dir, fileHeader.Filename))
biz.ErrIsNilAppendErr(err, "创建文件失败: %s")
biz.ErrIsNilAppendErr(err, "create file error: %s")
defer createfile.Close()
io.Copy(createfile, file)
}
@@ -349,7 +356,7 @@ func (m *MachineFile) UploadFolder(rc *req.Ctx) {
wg.Wait()
if isSuccess {
// 保存消息并发送文件上传成功通知
m.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg("文件上传成功", fmt.Sprintf("[%s]文件夹已成功上传至 %s[%s:%s]", folderName, mi.Name, mi.Ip, basePath)))
m.MsgApp.CreateAndSend(la, msgdto.SuccessSysMsg(i18n.TC(ctx, imsg.MsgUploadFileSuccess), fmt.Sprintf("[%s] -> %s[%s:%s]", folderName, mi.Name, mi.Ip, basePath)))
}
}
@@ -358,13 +365,13 @@ func (m *MachineFile) RemoveFile(rc *req.Ctx) {
mi, err := m.MachineFileApp.RemoveFile(rc.MetaCtx, opForm.MachineFileOp, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "path", opForm)
biz.ErrIsNilAppendErr(err, "删除文件失败: %s")
biz.ErrIsNilAppendErr(err, "remove file error: %s")
}
func (m *MachineFile) CopyFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
mi, err := m.MachineFileApp.Copy(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...)
biz.ErrIsNilAppendErr(err, "文件拷贝失败: %s")
biz.ErrIsNilAppendErr(err, "file copy error: %s")
rc.ReqParam = collx.Kvs("machine", mi, "cp", opForm)
}
@@ -372,14 +379,14 @@ func (m *MachineFile) MvFile(rc *req.Ctx) {
opForm := req.BindJsonAndValid(rc, new(form.CopyFileForm))
mi, err := m.MachineFileApp.Mv(rc.MetaCtx, opForm.MachineFileOp, opForm.ToPath, opForm.Paths...)
rc.ReqParam = collx.Kvs("machine", mi, "mv", opForm)
biz.ErrIsNilAppendErr(err, "文件移动失败: %s")
biz.ErrIsNilAppendErr(err, "file move error: %s")
}
func (m *MachineFile) Rename(rc *req.Ctx) {
renameForm := req.BindJsonAndValid(rc, new(form.RenameForm))
mi, err := m.MachineFileApp.Rename(rc.MetaCtx, renameForm.MachineFileOp, renameForm.Newname)
rc.ReqParam = collx.Kvs("machine", mi, "rename", renameForm)
biz.ErrIsNilAppendErr(err, "文件重命名失败: %s")
biz.ErrIsNilAppendErr(err, "file rename error: %s")
}
func getFileType(fm fs.FileMode) string {
@@ -394,6 +401,6 @@ func getFileType(fm fs.FileMode) string {
func GetMachineFileId(rc *req.Ctx) uint64 {
fileId := rc.PathParamInt("fileId")
biz.IsTrue(fileId != 0, "fileId错误")
biz.IsTrue(fileId != 0, "fileId error")
return uint64(fileId)
}

View File

@@ -51,29 +51,29 @@ func (m *MachineScript) RunMachineScript(rc *req.Ctx) {
scriptId := GetMachineScriptId(rc)
ac := GetMachineAc(rc)
ms, err := m.MachineScriptApp.GetById(scriptId, "MachineId", "Name", "Script")
biz.ErrIsNil(err, "该脚本不存在")
biz.ErrIsNil(err, "script not found")
script := ms.Script
// 如果有脚本参数,则用脚本参数替换脚本中的模板占位符参数
if params := rc.Query("params"); params != "" {
script, err = stringx.TemplateParse(ms.Script, jsonx.ToMap(params))
biz.ErrIsNilAppendErr(err, "脚本模板参数解析失败: %s")
biz.ErrIsNilAppendErr(err, "failed to parse the script template parameter: %s")
}
cli, err := m.MachineApp.GetCliByAc(ac)
biz.ErrIsNilAppendErr(err, "获取客户端连接失败: %s")
biz.ErrIsNilAppendErr(err, "connection error: %s")
biz.ErrIsNilAppendErr(m.TagApp.CanAccess(rc.GetLoginAccount().Id, cli.Info.CodePath...), "%s")
res, err := cli.Run(script)
// 记录请求参数
rc.ReqParam = collx.Kvs("machine", cli.Info, "scriptId", scriptId, "name", ms.Name)
if res == "" {
biz.ErrIsNilAppendErr(err, "执行命令失败:%s")
biz.ErrIsNilAppendErr(err, "failed to execute: %s")
}
rc.ResData = res
}
func GetMachineScriptId(rc *req.Ctx) uint64 {
scriptId := rc.PathParamInt("scriptId")
biz.IsTrue(scriptId > 0, "scriptId错误")
biz.IsTrue(scriptId > 0, "scriptId error")
return uint64(scriptId)
}

View File

@@ -7,6 +7,7 @@ import (
"mayfly-go/internal/machine/application/dto"
"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/mcm"
tagapp "mayfly-go/internal/tag/application"
@@ -85,7 +86,7 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *dto.SaveMachine
resourceType := tagentity.TagTypeMachine
if len(authCerts) == 0 {
return errorx.NewBiz("授权凭证信息不能为空")
return errorx.NewBiz("ac cannot be empty")
}
oldMachine := &entity.Machine{
@@ -95,7 +96,7 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *dto.SaveMachine
}
if me.SshTunnelMachineId > 0 {
if err := m.checkSshTunnelMachine(me.Ip, me.Port, me.SshTunnelMachineId, nil); err != nil {
if err := m.checkSshTunnelMachine(ctx, me.Ip, me.Port, me.SshTunnelMachineId, nil); err != nil {
return err
}
}
@@ -103,7 +104,7 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *dto.SaveMachine
err := m.GetByCond(oldMachine)
if me.Id == 0 {
if err == nil {
return errorx.NewBiz("该机器信息已存在")
return errorx.NewBizI(ctx, imsg.ErrMachineExist)
}
// 新增机器,默认启用状态
@@ -130,7 +131,7 @@ func (m *machineAppImpl) SaveMachine(ctx context.Context, param *dto.SaveMachine
// 如果存在该库,则校验修改的库是否为该库
if err == nil && oldMachine.Id != me.Id {
return errorx.NewBiz("该机器信息已存在")
return errorx.NewBizI(ctx, imsg.ErrMachineExist)
}
// 如果调整了SshTunnelMachineId Ip port等会查不到旧数据故需要根据id获取旧信息将code赋值给标签进行关联
if oldMachine.Code == "" {
@@ -197,7 +198,7 @@ func (m *machineAppImpl) ChangeStatus(ctx context.Context, id uint64, status int
func (m *machineAppImpl) Delete(ctx context.Context, id uint64) error {
machine, err := m.GetById(id)
if err != nil {
return errorx.NewBiz("机器信息不存在")
return errorx.NewBiz("machine not found")
}
// 关闭连接
mcm.DeleteCli(id)
@@ -258,24 +259,24 @@ func (m *machineAppImpl) GetSshTunnelMachine(machineId int) (*mcm.SshTunnelMachi
}
func (m *machineAppImpl) TimerUpdateStats() {
logx.Debug("开始定时收集并缓存服务器状态信息...")
logx.Debug("start collecting and caching machine state information periodically...")
scheduler.AddFun("@every 2m", func() {
machineIds, _ := m.ListByCond(model.NewModelCond(&entity.Machine{Status: entity.MachineStatusEnable, Protocol: entity.MachineProtocolSsh}).Columns("id"))
for _, ma := range machineIds {
go func(mid uint64) {
defer func() {
if err := recover(); err != nil {
logx.ErrorTrace(fmt.Sprintf("定时获取机器[id=%d]状态信息失败", mid), err.(error))
logx.ErrorTrace(fmt.Sprintf("failed to get machine [id=%d] status information on time", mid), err.(error))
}
}()
logx.Debugf("定时获取机器[id=%d]状态信息开始", mid)
logx.Debugf("time to get machine [id=%d] status information start", mid)
cli, err := m.GetCli(mid)
if err != nil {
logx.Errorf("定时获取机器[id=%d]状态信息失败, 获取机器cli失败: %s", mid, err.Error())
logx.Errorf("failed to get machine [id=%d] status information periodically, failed to get machine cli: %s", mid, err.Error())
return
}
cache.SaveMachineStats(mid, cli.GetAllStats())
logx.Debugf("定时获取机器[id=%d]状态信息结束", mid)
logx.Debugf("time to get the machine [id=%d] status information end", mid)
}(ma.Id)
}
})
@@ -296,7 +297,7 @@ func (m *machineAppImpl) ToMachineInfoByAc(authCertName string) (*mcm.MachineInf
Code: authCert.ResourceCode,
}
if err := m.GetByCond(machine); err != nil {
return nil, errorx.NewBiz("该授权凭证关联的机器信息不存在")
return nil, errorx.NewBiz("the machine information associated with the authorization credential does not exist")
}
return m.toMi(machine, authCert)
@@ -315,10 +316,10 @@ func (m *machineAppImpl) ToMachineInfoById(machineId uint64) (*mcm.MachineInfo,
func (m *machineAppImpl) getMachineAndAuthCert(machineId uint64) (*entity.Machine, *tagentity.ResourceAuthCert, error) {
me, err := m.GetById(machineId)
if err != nil {
return nil, nil, errorx.NewBiz("[%d]机器信息不存在", machineId)
return nil, nil, errorx.NewBiz("[%d] machine not found", machineId)
}
if me.Status != entity.MachineStatusEnable && me.Protocol == 1 {
return nil, nil, errorx.NewBiz("[%s]该机器已被停用", me.Code)
return nil, nil, errorx.NewBiz("[%s] machine has been disable", me.Code)
}
authCert, err := m.resourceAuthCertApp.GetResourceAuthCert(tagentity.TagTypeMachine, me.Code)
@@ -375,7 +376,7 @@ func (m *machineAppImpl) genMachineResourceTag(me *entity.Machine, authCerts []*
}
// checkSshTunnelMachine 校验ssh隧道机器是否存在循环隧道
func (m *machineAppImpl) checkSshTunnelMachine(ip string, port int, sshTunnelMachineId int, visited map[string]bool) error {
func (m *machineAppImpl) checkSshTunnelMachine(ctx context.Context, ip string, port int, sshTunnelMachineId int, visited map[string]bool) error {
if visited == nil {
visited = make(map[string]bool)
}
@@ -387,11 +388,11 @@ func (m *machineAppImpl) checkSshTunnelMachine(ip string, port int, sshTunnelMac
}
if visited[fmt.Sprintf("%s:%d", stm.Ip, stm.Port)] {
return errorx.NewBiz("存在循环隧道,请重新选择隧道机器")
return errorx.NewBizI(ctx, imsg.ErrSshTunnelCircular)
}
if stm.SshTunnelMachineId > 0 {
return m.checkSshTunnelMachine(stm.Ip, stm.Port, stm.SshTunnelMachineId, visited)
return m.checkSshTunnelMachine(ctx, stm.Ip, stm.Port, stm.SshTunnelMachineId, visited)
}
return nil
}

View File

@@ -54,7 +54,7 @@ func (m *machineCmdConfAppImpl) SaveCmdConf(ctx context.Context, cmdConfParam *d
func (m *machineCmdConfAppImpl) DeleteCmdConf(ctx context.Context, id uint64) error {
_, err := m.GetById(id)
if err != nil {
return errorx.NewBiz("该命令配置不存在")
return errorx.NewBiz("cmd config not found")
}
return m.Tx(ctx, func(ctx context.Context) error {
@@ -71,7 +71,7 @@ func (m *machineCmdConfAppImpl) GetCmdConfsByMachineTags(ctx context.Context, ta
var cmds []*MachineCmd
cmdConfIds, err := m.tagTreeRelateApp.GetRelateIds(ctx, tagentity.TagRelateTypeMachineCmd, tagPaths...)
if err != nil {
logx.Errorf("获取命令配置信息失败: %s", err.Error())
logx.Errorf("failed to get cmd config: %s", err.Error())
return cmds
}
if len(cmdConfIds) == 0 {
@@ -82,7 +82,7 @@ func (m *machineCmdConfAppImpl) GetCmdConfsByMachineTags(ctx context.Context, ta
for _, cmdConf := range cmdConfs {
for _, cmd := range cmdConf.Cmds {
if p, err := regexp.Compile(cmd); err != nil {
logx.Errorf("命令配置[%s],正则编译失败", cmd)
logx.Errorf("cmd config [%s], regex compilation failed", cmd)
} else {
cmds = append(cmds, &MachineCmd{CmdRegexp: p})
}

View File

@@ -76,7 +76,7 @@ func (m *machineCronJobAppImpl) SaveMachineCronJob(ctx context.Context, param *d
} else {
oldMcj, err := m.GetById(mcj.Id)
if err != nil {
return errorx.NewBiz("该计划任务不存在")
return errorx.NewBiz("cronjob not found")
}
mcj.Key = oldMcj.Key
}
@@ -102,7 +102,7 @@ func (m *machineCronJobAppImpl) Delete(ctx context.Context, id uint64) {
func (m *machineCronJobAppImpl) InitCronJob() {
defer func() {
if err := recover(); err != nil {
logx.ErrorTrace("机器计划任务初始化失败: %s", err.(error))
logx.ErrorTrace("the machine cronjob failed to initialize: %v", err.(error))
}
}()
@@ -193,9 +193,9 @@ func (m *machineCronJobAppImpl) runCronJob0(mid uint64, cronJob *entity.MachineC
if res == "" {
res = err.Error()
}
logx.Errorf("机器:[%d]执行[%s]计划任务失败: %s", mid, cronJob.Name, res)
logx.Errorf("machine[%d] failed to execute cronjob[%s]: %s", mid, cronJob.Name, res)
} else {
logx.Debugf("机器:[%d]执行[%s]计划任务成功, 执行结果: %s", mid, cronJob.Name, res)
logx.Debugf("machine[%d] successfully executed cronjob[%s], execution result: %s", mid, cronJob.Name, res)
}
}
execRes.Res = res

View File

@@ -105,7 +105,7 @@ func (m *machineFileAppImpl) GetMachineFile(condition *entity.MachineFile, cols
func (m *machineFileAppImpl) Save(ctx context.Context, mf *entity.MachineFile) error {
_, err := m.machineApp.GetById(mf.MachineId, "Name")
if err != nil {
return errorx.NewBiz("该机器不存在")
return errorx.NewBiz("machine not found")
}
if mf.Id != 0 {
@@ -177,7 +177,7 @@ func (m *machineFileAppImpl) GetDirSize(ctx context.Context, opParam *dto.Machin
//du: cannot access /proc/19087/fdinfo/3: No such file or directory\n
//18G /\n
if res == "" {
return "", errorx.NewBiz("获取目录大小失败: %s", err.Error())
return "", errorx.NewBiz("failed to get directory size: %s", err.Error())
}
strs := strings.Split(res, "\n")
res = strs[len(strs)-2]
@@ -243,7 +243,7 @@ func (m *machineFileAppImpl) CreateFile(ctx context.Context, opParam *dto.Machin
}
file, err := sftpCli.Create(path)
if err != nil {
return nil, errorx.NewBiz("创建文件失败: %s", err.Error())
return nil, errorx.NewBiz("failed to create file: %s", err.Error())
}
defer file.Close()
return mi, err
@@ -383,7 +383,7 @@ func (m *machineFileAppImpl) RemoveFile(ctx context.Context, opParam *dto.Machin
if err == nil {
return minfo, nil
}
logx.Errorf("使用命令rm删除文件失败: %s", res)
logx.Errorf("failed to delete the file using the command rm: %s", res)
sftpCli, err := mcli.GetSftpCli()
if err != nil {
@@ -408,13 +408,13 @@ func (m *machineFileAppImpl) Copy(ctx context.Context, opParam *dto.MachineFileO
// 打开源文件
srcFile, err := os.Open(srcPath)
if err != nil {
fmt.Println("Error opening source file:", err)
logx.Errorf("error opening source file: %v", err)
return nil, err
}
// 创建目标文件
destFile, err := os.Create(targetPath)
if err != nil {
fmt.Println("Error creating destination file:", err)
logx.Errorf("error creating destination file: %v", err)
return nil, err
}
io.Copy(destFile, srcFile)

View File

@@ -44,7 +44,7 @@ func (m *machineScriptAppImpl) Save(ctx context.Context, ms *entity.MachineScrip
if machineId := ms.MachineId; machineId != Common_Script_Machine_Id {
_, err := m.machineApp.GetById(machineId, "Name")
if err != nil {
return errorx.NewBiz("该机器不存在")
return errorx.NewBiz("machine not found")
}
}

View File

@@ -7,6 +7,7 @@ import (
"mayfly-go/internal/machine/config"
"mayfly-go/internal/machine/domain/entity"
"mayfly-go/internal/machine/domain/repository"
"mayfly-go/internal/machine/imsg"
"mayfly-go/internal/machine/mcm"
"mayfly-go/pkg/base"
"mayfly-go/pkg/contextx"
@@ -67,7 +68,7 @@ func (m *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsCon
fileKey, wc, saveFileFunc, err := m.fileApp.NewWriter(ctx, "", fmt.Sprintf("mto_%d_%s.cast", termOpRecord.MachineId, timex.TimeNo()))
if err != nil {
return errorx.NewBiz("创建终端回放记录文件失败: %s", err.Error())
return errorx.NewBiz("failed to create a terminal playback log file: %v", err)
}
defer saveFileFunc(&err)
@@ -90,7 +91,7 @@ func (m *machineTermOpAppImpl) TermConn(ctx context.Context, cli *mcm.Cli, wsCon
createTsParam.CmdFilterFuncs = []mcm.CmdFilterFunc{func(cmd string) error {
for _, cmdConf := range cmdConfs {
if cmdConf.CmdRegexp.Match([]byte(cmd)) {
return errorx.NewBiz("该命令已被禁用...")
return errorx.NewBizI(ctx, imsg.TerminalCmdDisable)
}
}
return nil
@@ -119,7 +120,7 @@ func (m *machineTermOpAppImpl) GetPageList(condition *entity.MachineTermOp, page
}
func (m *machineTermOpAppImpl) TimerDeleteTermOp() {
logx.Debug("开始定时删除机器终端回放记录...")
logx.Debug("start deleting machine terminal playback records every hour...")
scheduler.AddFun("@every 60m", func() {
startDate := time.Now().AddDate(0, 0, -config.GetMachine().TermOpSaveDays)
cond := &entity.MachineTermOpQuery{
@@ -132,7 +133,7 @@ func (m *machineTermOpAppImpl) TimerDeleteTermOp() {
for _, termOp := range termOps {
if err := m.DeleteTermOp(termOp); err != nil {
logx.Warnf("删除终端操作记录失败: %s", err.Error())
logx.Warnf("failed to delete terminal playback record: %s", err.Error())
}
}
})

View File

@@ -101,7 +101,7 @@ func WsToGuacd(ws *websocket.Conn, tunnel Tunnel, guacd io.Writer) {
for {
_, data, err := ws.ReadMessage()
if err != nil {
logx.Warnf("Error reading message from ws: %v", err)
logx.Warnf("error reading message from ws: %v", err)
_ = tunnel.Close()
return
}
@@ -112,7 +112,7 @@ func WsToGuacd(ws *websocket.Conn, tunnel Tunnel, guacd io.Writer) {
}
if _, err = guacd.Write(data); err != nil {
logx.Warnf("Failed writing to guacd: %v", err)
logx.Warnf("failed writing to guacd: %v", err)
return
}
}
@@ -124,7 +124,7 @@ func GuacdToWs(ws *websocket.Conn, tunnel Tunnel, guacd InstructionReader) {
for {
ins, err := guacd.ReadSome()
if err != nil {
logx.Warnf("Error reading message from guacd: %v", err)
logx.Warnf("error reading message from guacd: %v", err)
_ = tunnel.Close()
return
}
@@ -135,7 +135,7 @@ func GuacdToWs(ws *websocket.Conn, tunnel Tunnel, guacd InstructionReader) {
}
logx.Debugf("guacd msg: %s", string(ins))
if _, err = buf.Write(ins); err != nil {
logx.Warnf("Failed to buffer guacd to ws: %v", err)
logx.Warnf("failed to buffer guacd to ws: %v", err)
return
}
@@ -145,7 +145,7 @@ func GuacdToWs(ws *websocket.Conn, tunnel Tunnel, guacd InstructionReader) {
if errors.Is(err, websocket.ErrCloseSent) {
return
}
logx.Warnf("Failed sending message to ws: %v", err)
logx.Warnf("failed sending message to ws: %v", err)
return
}
buf.Reset()

View File

@@ -0,0 +1,35 @@
package imsg
import "mayfly-go/pkg/i18n"
var En = map[i18n.MsgId]string{
LogMachineSave: "Machine - Save",
LogMachineDelete: "Machine - Delete",
LogMachineChangeStatus: "Machine - Change Status",
LogMachineKillProcess: "Machine - Kill Process",
LogMachineTerminalOp: "Machine - Open Terminal",
ErrMachineExist: "The machine information already exists",
ErrSshTunnelCircular: "Circular tunnel exists, please select tunnel machine again",
// file
LogMachineFileConfSave: "Machine - New file config",
LogMachineFileConfDelete: "Machine - Delete file Config",
LogMachineFileRead: "Machine - Reading file contents",
LogMachineFileDownload: "Machine - File Download",
LogMachineFileModify: "Machine - Modifying file contents",
LogMachineFileCreate: "Machine - Create a file or directory",
LogMachineFileUpload: "Machine - File Upload",
LogMachineFileUploadFolder: "Machine - Folder Upload",
LogMachineFileDelete: "Machine - Delete a file or directory",
LogMachineFileCopy: "Machine - Copy File",
LogMachineFileMove: "Machine - Move File",
LogMachineFileRename: "Machine - Rename File",
ErrFileTooLargeUseDownload: "The file is over 1m, please use download to view",
ErrUploadFileOutOfLimit: "The file size cannot exceed {{.size}} bytes",
ErrFileUploadFail: "File upload failure",
MsgUploadFileSuccess: "File uploaded successfully",
TerminalCmdDisable: "This command has been disabled...",
}

View File

@@ -0,0 +1,43 @@
package imsg
import (
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/i18n"
)
func init() {
i18n.AppendLangMsg(i18n.Zh_CN, Zh_CN)
i18n.AppendLangMsg(i18n.En, En)
}
const (
LogMachineSave = iota + consts.ImsgNumMachine
LogMachineDelete
LogMachineChangeStatus
LogMachineKillProcess
LogMachineTerminalOp
ErrMachineExist
ErrSshTunnelCircular
// file
LogMachineFileConfSave
LogMachineFileConfDelete
LogMachineFileRead
LogMachineFileDownload
LogMachineFileModify
LogMachineFileCreate
LogMachineFileUpload
LogMachineFileUploadFolder
LogMachineFileDelete
LogMachineFileCopy
LogMachineFileMove
LogMachineFileRename
ErrFileTooLargeUseDownload
ErrUploadFileOutOfLimit
ErrFileUploadFail
MsgUploadFileSuccess
TerminalCmdDisable
)

View File

@@ -0,0 +1,35 @@
package imsg
import "mayfly-go/pkg/i18n"
var Zh_CN = map[i18n.MsgId]string{
LogMachineSave: "机器-保存",
LogMachineDelete: "机器-删除",
LogMachineChangeStatus: "机器-调整状态",
LogMachineKillProcess: "机器-终止进程",
LogMachineTerminalOp: "机器-终端操作",
ErrMachineExist: "该机器信息已存在",
ErrSshTunnelCircular: "存在循环隧道,请重新选择隧道机器",
// file
LogMachineFileConfSave: "机器-新增文件配置",
LogMachineFileConfDelete: "机器-删除文件配置",
LogMachineFileRead: "机器-读取文件内容",
LogMachineFileDownload: "机器-文件下载",
LogMachineFileModify: "机器-修改文件内容",
LogMachineFileCreate: "机器-创建文件or目录",
LogMachineFileUpload: "机器-文件上传",
LogMachineFileUploadFolder: "机器-文件夹上传",
LogMachineFileDelete: "机器-删除文件or文件夹",
LogMachineFileCopy: "机器-拷贝文件",
LogMachineFileMove: "机器-移动文件",
LogMachineFileRename: "机器-文件重命名",
ErrFileTooLargeUseDownload: "该文件超过1m请使用下载查看",
ErrUploadFileOutOfLimit: "文件大小不能超过{{.size}}字节",
ErrFileUploadFail: "文件上传失败",
MsgUploadFileSuccess: "文件上传成功",
TerminalCmdDisable: "该命令已被禁用...",
}

View File

@@ -21,14 +21,14 @@ type Cli struct {
// GetSftpCli 获取sftp client
func (c *Cli) GetSftpCli() (*sftp.Client, error) {
if c.sshClient == nil {
return nil, errorx.NewBiz("请先进行机器客户端连接")
return nil, errorx.NewBiz("please connect to the machine client first")
}
sftpclient := c.sftpClient
// 如果sftpClient为nil则连接
if sftpclient == nil {
sc, serr := sftp.NewClient(c.sshClient)
if serr != nil {
return nil, errorx.NewBiz("获取sftp client失败: %s", serr.Error())
return nil, errorx.NewBiz("failed to obtain the sftp client: %s", serr.Error())
}
sftpclient = sc
c.sftpClient = sftpclient
@@ -40,12 +40,12 @@ func (c *Cli) GetSftpCli() (*sftp.Client, error) {
// GetSession 获取session
func (c *Cli) GetSession() (*ssh.Session, error) {
if c.sshClient == nil {
return nil, errorx.NewBiz("请先进行机器客户端连接")
return nil, errorx.NewBiz("please connect to the machine client first")
}
session, err := c.sshClient.NewSession()
if err != nil {
logx.Errorf("获取机器客户端session失败: %s", err.Error())
return nil, errorx.NewBiz("获取会话失败, 请稍后重试...")
logx.Errorf("failed to retrieve the machine client session: %s", err.Error())
return nil, errorx.NewBiz("the acquisition session failed, please try again later...")
}
return session, nil
}
@@ -98,7 +98,7 @@ func (c *Cli) GetAllStats() *Stats {
stats := new(Stats)
res, err := c.Run(StatsShell)
if err != nil {
logx.Errorf("执行机器[id=%d, name=%s]运行状态信息脚本失败: %s", c.Info.Id, c.Info.Name, err.Error())
logx.Errorf("failed to execute machine [id=%d, name=%s] running status information script: %s", c.Info.Id, c.Info.Name, err.Error())
return stats
}

View File

@@ -75,7 +75,7 @@ func GetMachineCliById(machineId uint64) (*Cli, error) {
if machineCli != nil {
return machineCli, nil
}
return nil, errors.New("不存在该机器id的连接")
return nil, errors.New("no connection exists for this machine id")
}
// 删除指定机器缓存客户端,并关闭客户端连接

View File

@@ -46,12 +46,12 @@ func (mi *MachineInfo) GetTunnelId() string {
// 连接
func (mi *MachineInfo) Conn() (*Cli, error) {
logx.Infof("[%s]机器连接:%s:%d", mi.Name, mi.Ip, mi.Port)
logx.Infof("the machine[%s] is connecting: %s:%d", mi.Name, mi.Ip, mi.Port)
// 如果使用了ssh隧道则修改机器ip port为暴露的ip port
err := mi.IfUseSshTunnelChangeIpPort(false)
if err != nil {
return nil, errorx.NewBiz("ssh隧道连接失败: %s", err.Error())
return nil, errorx.NewBiz("ssh tunnel connection failed: %s", err.Error())
}
cli := &Cli{Info: mi}

View File

@@ -28,7 +28,7 @@ var (
type CheckSshTunnelMachineHasUseFunc func(int) bool
func startCheckUse() {
logx.Info("开启定时检测ssh隧道机器是否还有被使用")
logx.Info("start periodically checking if the ssh tunnel machine is still in use")
// 每十分钟检查一次隧道机器是否还有被使用
scheduler.AddFun("@every 10m", func() {
if !mutex.TryLock() {
@@ -37,7 +37,7 @@ func startCheckUse() {
defer mutex.Unlock()
// 遍历隧道机器,都未被使用将会被关闭
for mid, sshTunnelMachine := range sshTunnelMachines {
logx.Debugf("开始定时检查ssh隧道机器[%d]是否还有被使用...", mid)
logx.Debugf("periodically check if the ssh tunnel machine [%d] is still in use...", mid)
hasUse := false
for _, checkUseFunc := range checkSshTunnelMachineHasUseFuncs {
// 如果一个在使用则返回不关闭,不继续后续检查
@@ -129,10 +129,10 @@ func (stm *SshTunnelMachine) Close() {
}
if stm.SshClient != nil {
logx.Infof("ssh隧道机器[%d]未被使用, 关闭隧道...", stm.machineId)
logx.Infof("ssh tunnel machine [%d] is not in use, close tunnel...", stm.machineId)
err := stm.SshClient.Close()
if err != nil {
logx.Errorf("关闭ssh隧道机器[%d]发生错误: %s", stm.machineId, err.Error())
logx.Errorf("error in closing ssh tunnel machine [%d]: %s", stm.machineId, err.Error())
}
}
delete(sshTunnelMachines, stm.machineId)
@@ -159,7 +159,7 @@ func GetSshTunnelMachine(machineId int, getMachine func(uint64) (*MachineInfo, e
}
sshTunnelMachine = &SshTunnelMachine{SshClient: sshClient, machineId: machineId, tunnels: map[string]*Tunnel{}}
logx.Infof("初次连接ssh隧道机器[%d][%s:%d]", machineId, me.Ip, me.Port)
logx.Infof("connect to the ssh tunnel machine for the first time[%d][%s:%d]", machineId, me.Ip, me.Port)
sshTunnelMachines[machineId] = sshTunnelMachine
// 如果实用了隧道机器且还没开始定时检查是否还被实用,则执行定时任务检测隧道是否还被使用

View File

@@ -106,7 +106,7 @@ func (r TerminalSession) Stop() {
if r.terminal != nil {
if err := r.terminal.Close(); err != nil {
if err != io.EOF {
logx.Errorf("关闭机器ssh终端失败: %s", err.Error())
logx.Errorf("failed to close the machine ssh terminal: %s", err.Error())
}
}
}
@@ -129,7 +129,7 @@ func (ts TerminalSession) readFromTerminal() {
rn, size, err := ts.terminal.ReadRune()
if err != nil {
if err != io.EOF {
logx.Error("机器ssh终端读取消息失败: ", err)
logx.Error("the machine ssh terminal failed to read the message: ", err)
}
return
}
@@ -156,7 +156,7 @@ func (ts TerminalSession) writeToWebsocket() {
s := string(buf)
if err := ts.WriteToWs(s); err != nil {
logx.Error("机器ssh终端发送消息至websocket失败: ", err)
logx.Error("the machine ssh endpoint failed to send a message to the websocket: ", err)
return
}
@@ -197,14 +197,14 @@ func (ts *TerminalSession) receiveWsMsg() {
// read websocket msg
_, wsData, err := ts.wsConn.ReadMessage()
if err != nil {
logx.Debugf("机器ssh终端读取websocket消息失败: %s", err.Error())
logx.Debugf("the machine ssh terminal failed to read the websocket message: %s", err.Error())
return
}
// 解析消息
msgObj, err := parseMsg(wsData)
if err != nil {
ts.WriteToWs(GetErrorContentRn("消息内容解析失败..."))
logx.Error("机器ssh终端消息解析失败: ", err)
ts.WriteToWs(GetErrorContentRn("failed to parse the message content..."))
logx.Error("machine ssh terminal message parsing failed: ", err)
return
}
@@ -228,13 +228,13 @@ func (ts *TerminalSession) receiveWsMsg() {
_, err := ts.terminal.Write([]byte(msgObj.Msg))
if err != nil {
logx.Errorf("写入数据至ssh终端失败: %s", err)
ts.WriteToWs(GetErrorContentRn(fmt.Sprintf("写入数据至ssh终端失败: %s", err.Error())))
logx.Errorf("failed to write data to the ssh terminal: %s", err)
ts.WriteToWs(GetErrorContentRn(fmt.Sprintf("failed to write data to the ssh terminal: %s", err.Error())))
}
case Ping:
_, err := ts.terminal.SshSession.SendRequest("ping", true, nil)
if err != nil {
ts.WriteToWs(GetErrorContentRn("终端连接已断开..."))
ts.WriteToWs(GetErrorContentRn("the terminal connection has been disconnected..."))
return
}
}
@@ -254,7 +254,7 @@ func parseMsg(msg []byte) (*WsMsg, error) {
// 查找第一个 "|" 的位置
index := strings.Index(msgStr, MsgSplit)
if index == -1 {
return nil, errorx.NewBiz("消息内容不符合指定规则")
return nil, errorx.NewBiz("the message content does not conform to the specified rules")
}
// 获取消息类型, 提取第一个 "|" 之前的内容

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -35,15 +36,15 @@ func InitMachineRouter(router *gin.RouterGroup) {
req.NewGet(":machineId/groups", m.GetGroups),
req.NewDelete(":machineId/process", m.KillProcess).Log(req.NewLogSave("终止进程")).RequiredPermissionCode("machine:killprocess"),
req.NewPost("", m.SaveMachine).Log(req.NewLogSaveI(imsg.LogMachineSave)).RequiredPermission(saveMachineP),
req.NewPost("", m.SaveMachine).Log(req.NewLogSave("保存机器信息")).RequiredPermission(saveMachineP),
req.NewDelete(":machineId", m.DeleteMachine).Log(req.NewLogSaveI(imsg.LogMachineSave)),
req.NewPost("test-conn", m.TestConn),
req.NewPut(":machineId/:status", m.ChangeStatus).Log(req.NewLogSave("调整机器状态")).RequiredPermission(saveMachineP),
req.NewPut(":machineId/:status", m.ChangeStatus).Log(req.NewLogSaveI(imsg.LogMachineChangeStatus)).RequiredPermission(saveMachineP),
req.NewDelete(":machineId", m.DeleteMachine).Log(req.NewLogSave("删除机器")),
req.NewDelete(":machineId/process", m.KillProcess).Log(req.NewLogSaveI(imsg.LogMachineKillProcess)).RequiredPermissionCode("machine:killprocess"),
// 获取机器终端回放记录列表,目前具有保存机器信息的权限标识才有权限查看终端回放
req.NewGet(":machineId/term-recs", m.MachineTermOpRecords).RequiredPermission(saveMachineP),

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/machine/api"
"mayfly-go/internal/machine/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -19,13 +20,13 @@ func InitMachineFileRouter(router *gin.RouterGroup) {
// 获取指定机器文件列表
req.NewGet(":machineId/files", mf.MachineFiles),
req.NewPost(":machineId/files", mf.SaveMachineFiles).Log(req.NewLogSave("机器-新增文件配置")).RequiredPermissionCode("machine:file:add"),
req.NewPost(":machineId/files", mf.SaveMachineFiles).Log(req.NewLogSaveI(imsg.LogMachineFileConfSave)).RequiredPermissionCode("machine:file:add"),
req.NewDelete(":machineId/files/:fileId", mf.DeleteFile).Log(req.NewLogSave("机器-删除文件配置")).RequiredPermissionCode("machine:file:del"),
req.NewDelete(":machineId/files/:fileId", mf.DeleteFile).Log(req.NewLogSaveI(imsg.LogMachineFileConfDelete)).RequiredPermissionCode("machine:file:del"),
req.NewGet(":machineId/files/:fileId/read", mf.ReadFileContent).Log(req.NewLogSave("机器-读取文件内容")),
req.NewGet(":machineId/files/:fileId/read", mf.ReadFileContent).Log(req.NewLogSaveI(imsg.LogMachineFileRead)),
req.NewGet(":machineId/files/:fileId/download", mf.DownloadFile).NoRes().Log(req.NewLogSave("机器-文件下载")),
req.NewGet(":machineId/files/:fileId/download", mf.DownloadFile).NoRes().Log(req.NewLogSaveI(imsg.LogMachineFileDownload)),
req.NewGet(":machineId/files/:fileId/read-dir", mf.GetDirEntry),
@@ -33,21 +34,21 @@ func InitMachineFileRouter(router *gin.RouterGroup) {
req.NewGet(":machineId/files/:fileId/file-stat", mf.GetFileStat),
req.NewPost(":machineId/files/:fileId/write", mf.WriteFileContent).Log(req.NewLogSave("机器-修改文件内容")).RequiredPermissionCode("machine:file:write"),
req.NewPost(":machineId/files/:fileId/write", mf.WriteFileContent).Log(req.NewLogSaveI(imsg.LogMachineFileModify)).RequiredPermissionCode("machine:file:write"),
req.NewPost(":machineId/files/:fileId/create-file", mf.CreateFile).Log(req.NewLogSave("机器-创建文件or目录")),
req.NewPost(":machineId/files/:fileId/create-file", mf.CreateFile).Log(req.NewLogSaveI(imsg.LogMachineFileCreate)),
req.NewPost(":machineId/files/:fileId/upload", mf.UploadFile).Log(req.NewLogSave("机器-文件上传")).RequiredPermissionCode("machine:file:upload"),
req.NewPost(":machineId/files/:fileId/upload", mf.UploadFile).Log(req.NewLogSaveI(imsg.LogMachineFileUpload)).RequiredPermissionCode("machine:file:upload"),
req.NewPost(":machineId/files/:fileId/upload-folder", mf.UploadFolder).Log(req.NewLogSave("机器-文件夹上传")).RequiredPermissionCode("machine:file:upload"),
req.NewPost(":machineId/files/:fileId/upload-folder", mf.UploadFolder).Log(req.NewLogSaveI(imsg.LogMachineFileUploadFolder)).RequiredPermissionCode("machine:file:upload"),
req.NewPost(":machineId/files/:fileId/remove", mf.RemoveFile).Log(req.NewLogSave("机器-删除文件or文件夹")).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/remove", mf.RemoveFile).Log(req.NewLogSaveI(imsg.LogMachineFileDelete)).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/cp", mf.CopyFile).Log(req.NewLogSave("机器-拷贝文件")).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/cp", mf.CopyFile).Log(req.NewLogSaveI(imsg.LogMachineFileCopy)).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/mv", mf.MvFile).Log(req.NewLogSave("机器-移动文件")).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/mv", mf.MvFile).Log(req.NewLogSaveI(imsg.LogMachineFileMove)).RequiredPermissionCode("machine:file:rm"),
req.NewPost(":machineId/files/:fileId/rename", mf.Rename).Log(req.NewLogSave("机器-文件重命名")).RequiredPermissionCode("machine:file:write"),
req.NewPost(":machineId/files/:fileId/rename", mf.Rename).Log(req.NewLogSaveI(imsg.LogMachineFileRename)).RequiredPermissionCode("machine:file:write"),
}
req.BatchSetGroup(machineFile, reqs[:])

View File

@@ -16,9 +16,9 @@ import (
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"regexp"
"strconv"
"strings"
"github.com/may-fly/cast"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
@@ -55,7 +55,7 @@ func (m *Mongo) Mongos(rc *req.Ctx) {
func (m *Mongo) TestConn(rc *req.Ctx) {
form := &form.Mongo{}
mongo := req.BindJsonAndCopyTo[*entity.Mongo](rc, form, new(entity.Mongo))
biz.ErrIsNilAppendErr(m.MongoApp.TestConn(mongo), "连接失败: %s")
biz.ErrIsNilAppendErr(m.MongoApp.TestConn(mongo), "connection error: %s")
}
func (m *Mongo) Save(rc *req.Ctx) {
@@ -78,9 +78,7 @@ func (m *Mongo) DeleteMongo(rc *req.Ctx) {
ids := strings.Split(idsStr, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
m.MongoApp.Delete(rc.MetaCtx, uint64(value))
m.MongoApp.Delete(rc.MetaCtx, cast.ToUint64(v))
}
}
@@ -88,7 +86,7 @@ func (m *Mongo) Databases(rc *req.Ctx) {
conn, err := m.MongoApp.GetMongoConn(m.GetMongoId(rc))
biz.ErrIsNil(err)
res, err := conn.Cli.ListDatabases(context.TODO(), bson.D{})
biz.ErrIsNilAppendErr(err, "获取mongo所有库信息失败: %s")
biz.ErrIsNilAppendErr(err, "get mongo dbs error: %s")
rc.ResData = res
}
@@ -99,10 +97,10 @@ func (m *Mongo) Collections(rc *req.Ctx) {
global.EventBus.Publish(rc.MetaCtx, event.EventTopicResourceOp, conn.Info.CodePath[0])
db := rc.Query("database")
biz.NotEmpty(db, "database不能为空")
biz.NotEmpty(db, "database cannot be empty")
ctx := context.TODO()
res, err := conn.Cli.Database(db).ListCollectionNames(ctx, bson.D{})
biz.ErrIsNilAppendErr(err, "获取库集合信息失败: %s")
biz.ErrIsNilAppendErr(err, "get db collections error: %s")
rc.ResData = res
}
@@ -132,7 +130,7 @@ func (m *Mongo) RunCommand(rc *req.Ctx) {
commands,
).Decode(&bm)
biz.ErrIsNilAppendErr(err, "执行命令失败: %s")
biz.ErrIsNilAppendErr(err, "command execution failed: %s")
rc.ResData = bm
}
@@ -145,7 +143,7 @@ func (m *Mongo) FindCommand(rc *req.Ctx) {
limit := commandForm.Limit
if limit != 0 {
biz.IsTrue(limit <= 100, "limit不能超过100")
biz.IsTrue(limit <= 100, "the limit cannot exceed 100")
}
opts := options.Find().SetSort(commandForm.Sort).
SetSkip(commandForm.Skip).
@@ -163,7 +161,7 @@ func (m *Mongo) FindCommand(rc *req.Ctx) {
}
cur, err := cli.Database(commandForm.Database).Collection(commandForm.Collection).Find(ctx, commandForm.Filter, opts)
biz.ErrIsNilAppendErr(err, "命令执行失败: %s")
biz.ErrIsNilAppendErr(err, "command execution failed: %s")
var res []bson.M
cur.All(ctx, &res)
@@ -188,7 +186,7 @@ func (m *Mongo) UpdateByIdCommand(rc *req.Ctx) {
}
res, err := conn.Cli.Database(commandForm.Database).Collection(commandForm.Collection).UpdateByID(context.TODO(), docId, commandForm.Update)
biz.ErrIsNilAppendErr(err, "命令执行失败: %s")
biz.ErrIsNilAppendErr(err, "command execution failed: %s")
rc.ResData = res
}
@@ -211,7 +209,7 @@ func (m *Mongo) DeleteByIdCommand(rc *req.Ctx) {
}
res, err := conn.Cli.Database(commandForm.Database).Collection(commandForm.Collection).DeleteOne(context.TODO(), bson.D{{Key: "_id", Value: docId}})
biz.ErrIsNilAppendErr(err, "命令执行失败: %s")
biz.ErrIsNilAppendErr(err, "command execution failed: %s")
rc.ResData = res
}
@@ -223,13 +221,13 @@ func (m *Mongo) InsertOneCommand(rc *req.Ctx) {
rc.ReqParam = collx.Kvs("mongo", conn.Info, "cmd", commandForm)
res, err := conn.Cli.Database(commandForm.Database).Collection(commandForm.Collection).InsertOne(context.TODO(), commandForm.Doc)
biz.ErrIsNilAppendErr(err, "命令执行失败: %s")
biz.ErrIsNilAppendErr(err, "command execution failed: %s")
rc.ResData = res
}
// 获取请求路径上的mongo id
func (m *Mongo) GetMongoId(rc *req.Ctx) uint64 {
dbId := rc.PathParamInt("id")
biz.IsTrue(dbId > 0, "mongoId错误")
biz.IsTrue(dbId > 0, "mongoId error")
return uint64(dbId)
}

View File

@@ -5,6 +5,7 @@ import (
"mayfly-go/internal/common/consts"
"mayfly-go/internal/mongo/domain/entity"
"mayfly-go/internal/mongo/domain/repository"
"mayfly-go/internal/mongo/imsg"
"mayfly-go/internal/mongo/mgm"
tagapp "mayfly-go/internal/tag/application"
tagdto "mayfly-go/internal/tag/application/dto"
@@ -52,7 +53,7 @@ func (d *mongoAppImpl) GetPageList(condition *entity.MongoQuery, pageParam *mode
func (d *mongoAppImpl) Delete(ctx context.Context, id uint64) error {
mongoEntity, err := d.GetById(id)
if err != nil {
return errorx.NewBiz("mongo信息不存在")
return errorx.NewBiz("mongo not found")
}
mgm.CloseConn(id)
@@ -83,7 +84,7 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagCodePa
if m.Id == 0 {
if err == nil {
return errorx.NewBiz("该信息已存在")
return errorx.NewBizI(ctx, imsg.ErrMongoInfoExist)
}
// 生成随机编号
m.Code = stringx.Rand(10)
@@ -104,7 +105,7 @@ func (d *mongoAppImpl) SaveMongo(ctx context.Context, m *entity.Mongo, tagCodePa
// 如果存在该库,则校验修改的库是否为该库
if err == nil && oldMongo.Id != m.Id {
return errorx.NewBiz("该信息已存在")
return errorx.NewBizI(ctx, imsg.ErrMongoInfoExist)
}
// 如果调整了ssh等会查不到旧数据故需要根据id获取旧信息将code赋值给标签进行关联
if oldMongo.Code == "" {
@@ -137,7 +138,7 @@ func (d *mongoAppImpl) GetMongoConn(id uint64) (*mgm.MongoConn, error) {
return mgm.GetMongoConn(id, func() (*mgm.MongoInfo, error) {
me, err := d.GetById(id)
if err != nil {
return nil, errorx.NewBiz("mongo信息不存在")
return nil, errorx.NewBiz("mongo not found")
}
return me.ToMongoInfo(d.tagApp.ListTagPathByTypeAndCode(consts.ResourceTypeMongo, me.Code)...), nil
})

View File

@@ -0,0 +1,14 @@
package imsg
import "mayfly-go/pkg/i18n"
var En = map[i18n.MsgId]string{
LogMongoSave: "Mongo - Save",
LogMongoDelete: "Mongo-Delete",
LogMongoRunCmd: "Mongo - Run Cmd",
LogUpdateDocs: "Mongo - Update Documents",
LogDelDocs: "Mongo - Delete Documents",
LogInsertDocs: "Mongo - Insert Documents",
ErrMongoInfoExist: "that information already exists",
}

View File

@@ -0,0 +1,22 @@
package imsg
import (
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/i18n"
)
func init() {
i18n.AppendLangMsg(i18n.Zh_CN, Zh_CN)
i18n.AppendLangMsg(i18n.En, En)
}
const (
LogMongoSave = iota + consts.ImsgNumMongo
LogMongoDelete
LogMongoRunCmd
LogUpdateDocs
LogDelDocs
LogInsertDocs
ErrMongoInfoExist
)

View File

@@ -0,0 +1,14 @@
package imsg
import "mayfly-go/pkg/i18n"
var Zh_CN = map[i18n.MsgId]string{
LogMongoSave: "Mongo-保存",
LogMongoDelete: "Mongo-删除",
LogMongoRunCmd: "Mongo-执行命令",
LogUpdateDocs: "Mongo-更新文档",
LogDelDocs: "Mongo-删除文档",
LogInsertDocs: "Mongo-插入文档",
ErrMongoInfoExist: "该信息已存在",
}

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/mongo/api"
"mayfly-go/internal/mongo/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -28,9 +29,9 @@ func InitMongoRouter(router *gin.RouterGroup) {
req.NewPost("/test-conn", ma.TestConn),
req.NewPost("", ma.Save).Log(req.NewLogSave("mongo-保存信息")),
req.NewPost("", ma.Save).Log(req.NewLogSaveI(imsg.LogMongoSave)),
req.NewDelete(":id", ma.DeleteMongo).Log(req.NewLogSave("mongo-删除信息")),
req.NewDelete(":id", ma.DeleteMongo).Log(req.NewLogSaveI(imsg.LogMongoDelete)),
// 获取mongo下的所有数据库
req.NewGet(":id/databases", ma.Databases),
@@ -39,18 +40,18 @@ func InitMongoRouter(router *gin.RouterGroup) {
req.NewGet(":id/collections", ma.Collections),
// mongo runCommand
req.NewPost(":id/run-command", ma.RunCommand).Log(req.NewLogSave("mongo-runCommand")),
req.NewPost(":id/run-command", ma.RunCommand).Log(req.NewLogSaveI(imsg.LogMongoRunCmd)),
// 执行mongo find命令
req.NewPost(":id/command/find", ma.FindCommand),
req.NewPost(":id/command/update-by-id", ma.UpdateByIdCommand).RequiredPermission(saveDataPerm).Log(req.NewLogSave("mongo-更新文档")),
req.NewPost(":id/command/update-by-id", ma.UpdateByIdCommand).RequiredPermission(saveDataPerm).Log(req.NewLogSaveI(imsg.LogUpdateDocs)),
// 执行mongo delete by id命令
req.NewPost(":id/command/delete-by-id", ma.DeleteByIdCommand).RequiredPermission(req.NewPermission("mongo:data:del")).Log(req.NewLogSave("mongo-删除文档")),
req.NewPost(":id/command/delete-by-id", ma.DeleteByIdCommand).RequiredPermission(req.NewPermission("mongo:data:del")).Log(req.NewLogSaveI(imsg.LogDelDocs)),
// 执行mongo insert 命令
req.NewPost(":id/command/insert", ma.InsertOneCommand).RequiredPermission(saveDataPerm).Log(req.NewLogSave("mogno-插入文档")),
req.NewPost(":id/command/insert", ma.InsertOneCommand).RequiredPermission(saveDataPerm).Log(req.NewLogSaveI(imsg.LogInsertDocs)),
}
req.BatchSetGroup(m, reqs[:])

View File

@@ -13,7 +13,7 @@ import (
func (r *Redis) RunCmd(rc *req.Ctx) {
var cmdReq form.RunCmdForm
runCmdParam := req.BindJsonAndCopyTo(rc, &cmdReq, new(dto.RunCmd))
biz.IsTrue(len(cmdReq.Cmd) > 0, "redis命令不能为空")
biz.IsTrue(len(cmdReq.Cmd) > 0, "redis cmd cannot be empty")
redisConn := r.getRedisConn(rc)
biz.ErrIsNilAppendErr(r.TagApp.CanAccess(rc.GetLoginAccount().Id, redisConn.Info.CodePath...), "%s")

View File

@@ -98,7 +98,7 @@ func (r *Redis) KeyInfo(rc *req.Ctx) {
cmd := ri.GetCmdable()
ctx := context.Background()
ttl, err := cmd.TTL(ctx, key).Result()
biz.ErrIsNilAppendErr(err, "ttl失败: %s")
biz.ErrIsNilAppendErr(err, "ttl error: %s")
ttlInt := -1
if ttl != -1 {
@@ -106,7 +106,7 @@ func (r *Redis) KeyInfo(rc *req.Ctx) {
}
typeRes, err := cmd.Type(ctx, key).Result()
biz.ErrIsNilAppendErr(err, "获取key type失败: %s")
biz.ErrIsNilAppendErr(err, "get key type error: %s")
rc.ResData = &vo.KeyInfo{
Key: key,
@@ -118,7 +118,7 @@ func (r *Redis) KeyInfo(rc *req.Ctx) {
func (r *Redis) TtlKey(rc *req.Ctx) {
ri, key := r.checkKeyAndGetRedisConn(rc)
ttl, err := ri.GetCmdable().TTL(context.Background(), key).Result()
biz.ErrIsNilAppendErr(err, "ttl失败: %s")
biz.ErrIsNilAppendErr(err, "ttl error: %s")
if ttl == -1 {
rc.ResData = -1

View File

@@ -18,9 +18,9 @@ import (
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/stringx"
"strconv"
"strings"
"github.com/may-fly/cast"
"github.com/redis/go-redis/v9"
)
@@ -104,9 +104,7 @@ func (r *Redis) DeleteRedis(rc *req.Ctx) {
ids := strings.Split(idsStr, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
r.RedisApp.Delete(rc.MetaCtx, uint64(value))
r.RedisApp.Delete(rc.MetaCtx, cast.ToUint64(v))
}
}
@@ -123,7 +121,7 @@ func (r *Redis) RedisInfo(rc *req.Ctx) {
redisCli = ri.Cli
} else if mode == rdm.ClusterMode {
host := rc.Query("host")
biz.NotEmpty(host, "集群模式host信息不能为空")
biz.NotEmpty(host, "the cluster mode host info cannot be empty")
clusterClient := ri.ClusterCli
// 遍历集群的master节点找到该redis client
clusterClient.ForEachMaster(ctx, func(ctx context.Context, client *redis.Client) error {
@@ -141,7 +139,7 @@ func (r *Redis) RedisInfo(rc *req.Ctx) {
return nil
})
}
biz.NotNil(redisCli, "该实例不在该集群中")
biz.NotNil(redisCli, "the instance is not in the cluster")
}
var res string
@@ -151,7 +149,7 @@ func (r *Redis) RedisInfo(rc *req.Ctx) {
res, err = redisCli.Info(ctx, section).Result()
}
biz.ErrIsNilAppendErr(err, "获取redis info失败: %s")
biz.ErrIsNilAppendErr(err, "get redis info error: %s")
datas := strings.Split(res, "\r\n")
i := 0
@@ -190,7 +188,7 @@ func (r *Redis) RedisInfo(rc *req.Ctx) {
func (r *Redis) ClusterInfo(rc *req.Ctx) {
ri, err := r.RedisApp.GetRedisConn(uint64(rc.PathParamInt("id")), 0)
biz.ErrIsNil(err)
biz.IsEquals(ri.Info.Mode, rdm.ClusterMode, "非集群模式")
biz.IsEquals(ri.Info.Mode, rdm.ClusterMode, "non-cluster mode")
info, _ := ri.ClusterCli.ClusterInfo(context.Background()).Result()
nodesStr, _ := ri.ClusterCli.ClusterNodes(context.Background()).Result()
@@ -234,7 +232,7 @@ func (r *Redis) ClusterInfo(rc *req.Ctx) {
// 校验查询参数中的key为必填项并返回redis实例
func (r *Redis) checkKeyAndGetRedisConn(rc *req.Ctx) (*rdm.RedisConn, string) {
key := rc.Query("key")
biz.NotEmpty(key, "key不能为空")
biz.NotEmpty(key, "key cannot be empty")
return r.getRedisConn(rc), key
}

View File

@@ -9,6 +9,7 @@ import (
"mayfly-go/internal/redis/application/dto"
"mayfly-go/internal/redis/domain/entity"
"mayfly-go/internal/redis/domain/repository"
"mayfly-go/internal/redis/imsg"
"mayfly-go/internal/redis/rdm"
tagapp "mayfly-go/internal/tag/application"
tagdto "mayfly-go/internal/tag/application/dto"
@@ -56,7 +57,6 @@ type redisAppImpl struct {
tagApp tagapp.TagTree `inject:"TagTreeApp"`
procdefApp flowapp.Procdef `inject:"ProcdefApp"`
procinstApp flowapp.Procinst `inject:"ProcinstApp"`
resourceAuthCertApp tagapp.ResourceAuthCert `inject:"ResourceAuthCertApp"`
}
@@ -102,7 +102,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, param *dto.SaveRedis) erro
if re.Id == 0 {
if err == nil {
return errorx.NewBiz("该实例已存在")
return errorx.NewBizI(ctx, imsg.ErrRedisInfoExist)
}
// 生成随机编号
re.Code = stringx.Rand(10)
@@ -129,7 +129,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, param *dto.SaveRedis) erro
// 如果存在该库,则校验修改的库是否为该库
if err == nil && oldRedis.Id != re.Id {
return errorx.NewBiz("该实例已存在")
return errorx.NewBizI(ctx, imsg.ErrRedisInfoExist)
}
// 如果修改了redis实例的库信息则关闭旧库的连接
if oldRedis.Db != re.Db || oldRedis.SshTunnelMachineId != re.SshTunnelMachineId {
@@ -173,7 +173,7 @@ func (r *redisAppImpl) SaveRedis(ctx context.Context, param *dto.SaveRedis) erro
func (r *redisAppImpl) Delete(ctx context.Context, id uint64) error {
re, err := r.GetById(id)
if err != nil {
return errorx.NewBiz("redis信息不存在")
return errorx.NewBiz("redis not found")
}
// 如果存在连接,则关闭所有库连接信息
for _, dbStr := range strings.Split(re.Db, ",") {
@@ -204,7 +204,7 @@ func (r *redisAppImpl) GetRedisConn(id uint64, db int) (*rdm.RedisConn, error) {
// 缓存不存在则回调获取redis信息
re, err := r.GetById(id)
if err != nil {
return nil, errorx.NewBiz("redis信息不存在")
return nil, errorx.NewBiz("redis not found")
}
authCert, err := r.resourceAuthCertApp.GetResourceAuthCert(tagentity.TagTypeRedis, re.Code)
if err != nil {
@@ -216,7 +216,7 @@ func (r *redisAppImpl) GetRedisConn(id uint64, db int) (*rdm.RedisConn, error) {
func (r *redisAppImpl) RunCmd(ctx context.Context, redisConn *rdm.RedisConn, cmdParam *dto.RunCmd) (any, error) {
if redisConn == nil {
return nil, errorx.NewBiz("redis连接不存在")
return nil, errorx.NewBiz("redis connection not exist")
}
// 开启工单流程,则校验该流程是否需要校验
@@ -227,7 +227,7 @@ func (r *redisAppImpl) RunCmd(ctx context.Context, redisConn *rdm.RedisConn, cmd
cmdType = "write"
}
if needStartProc := procdef.MatchCondition(RedisRunCmdFlowBizType, collx.Kvs("cmdType", cmdType, "cmd", cmd)); needStartProc {
return nil, errorx.NewBiz("该操作需要提交工单审批执行")
return nil, errorx.NewBizI(ctx, imsg.ErrSubmitFlowRunCmd)
}
}
@@ -258,7 +258,7 @@ func (r *redisAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *flowap
runCmdParam, err := jsonx.To(procinst.BizForm, new(FlowRedisRunCmdBizForm))
if err != nil {
return nil, errorx.NewBiz("业务表单信息解析失败: %s", err.Error())
return nil, errorx.NewBiz("failed to parse the business form information: %s", err.Error())
}
redisConn, err := r.GetRedisConn(runCmdParam.Id, runCmdParam.Db)
@@ -283,7 +283,7 @@ func (r *redisAppImpl) FlowBizHandle(ctx context.Context, bizHandleParam *flowap
})
if hasErr {
return handleRes, errorx.NewBiz("存在命令执行失败")
return handleRes, errorx.NewBizI(ctx, imsg.ErrHasRunFailCmd)
}
return handleRes, nil
}

View File

@@ -0,0 +1,13 @@
package imsg
import "mayfly-go/pkg/i18n"
var En = map[i18n.MsgId]string{
LogRedisSave: "Redis - Save",
LogRedisDelete: "Redis - Delete",
LogRedisRunCmd: "Redis - Run Cmd",
ErrRedisInfoExist: "The Redis information already exists",
ErrSubmitFlowRunCmd: "This operation needs to submit a work ticket for approval",
ErrHasRunFailCmd: "A command failed to execute",
}

View File

@@ -0,0 +1,21 @@
package imsg
import (
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/i18n"
)
func init() {
i18n.AppendLangMsg(i18n.Zh_CN, Zh_CN)
i18n.AppendLangMsg(i18n.En, En)
}
const (
LogRedisSave = iota + consts.ImsgNumRedis
LogRedisDelete
LogRedisRunCmd
ErrRedisInfoExist
ErrSubmitFlowRunCmd
ErrHasRunFailCmd
)

View File

@@ -0,0 +1,13 @@
package imsg
import "mayfly-go/pkg/i18n"
var Zh_CN = map[i18n.MsgId]string{
LogRedisSave: "Redis-保存",
LogRedisDelete: "Redis-删除",
LogRedisRunCmd: "Redis-执行命令",
ErrRedisInfoExist: "该Redis信息已存在",
ErrSubmitFlowRunCmd: "该操作需要提交工单审批执行",
ErrHasRunFailCmd: "存在执行失败的命令",
}

View File

@@ -50,7 +50,7 @@ func (r *RedisConn) Close() {
mode := r.Info.Mode
if mode == StandaloneMode || mode == SentinelMode {
if err := r.Cli.Close(); err != nil {
logx.Errorf("关闭redis单机实例[%s]连接失败: %s", r.Id, err.Error())
logx.Errorf("close redis standalone instance [%s] connection failed: %s", r.Id, err.Error())
}
r.Cli = nil
return
@@ -58,7 +58,7 @@ func (r *RedisConn) Close() {
if mode == ClusterMode {
if err := r.ClusterCli.Close(); err != nil {
logx.Errorf("关闭redis集群实例[%s]连接失败: %s", r.Id, err.Error())
logx.Errorf("close redis cluster instance [%s] connection failed: %s", r.Id, err.Error())
}
r.ClusterCli = nil
}

View File

@@ -14,7 +14,7 @@ import (
var connCache = cache.NewTimedCache(consts.RedisConnExpireTime, 5*time.Second).
WithUpdateAccessTime(true).
OnEvicted(func(key any, value any) {
logx.Info(fmt.Sprintf("删除redis连接缓存 id = %s", key))
logx.Info(fmt.Sprintf("remove the redis connection cache id = %s", key))
value.(*RedisConn).Close()
})

View File

@@ -70,10 +70,10 @@ func (re *RedisInfo) connStandalone() (*RedisConn, error) {
_, e := cli.Ping(context.Background()).Result()
if e != nil {
cli.Close()
return nil, errorx.NewBiz("redis连接失败: %s", e.Error())
return nil, errorx.NewBiz("redis standalone connection failed: %s", e.Error())
}
logx.Infof("连接redis standalone: %s/%d", re.Host, re.Db)
logx.Infof("redis standalone connection: %s/%d", re.Host, re.Db)
rc := &RedisConn{Id: getConnId(re.Id, re.Db), Info: re}
rc.Cli = cli
@@ -95,10 +95,10 @@ func (re *RedisInfo) connCluster() (*RedisConn, error) {
_, e := cli.Ping(context.Background()).Result()
if e != nil {
cli.Close()
return nil, errorx.NewBiz("redis集群连接失败: %s", e.Error())
return nil, errorx.NewBiz("redis cluster connection failed: %s", e.Error())
}
logx.Infof("连接redis cluster: %s/%d", re.Host, re.Db)
logx.Infof("redis cluster connection: %s/%d", re.Host, re.Db)
rc := &RedisConn{Id: getConnId(re.Id, re.Db), Info: re}
rc.ClusterCli = cli
@@ -128,10 +128,10 @@ func (re *RedisInfo) connSentinel() (*RedisConn, error) {
_, e := cli.Ping(context.Background()).Result()
if e != nil {
cli.Close()
return nil, errorx.NewBiz("redis sentinel连接失败: %s", e.Error())
return nil, errorx.NewBiz("redis sentinel connection failed: %s", e.Error())
}
logx.Infof("连接redis sentinel: %s/%d", re.Host, re.Db)
logx.Infof("redis sentinel connection: %s/%d", re.Host, re.Db)
rc := &RedisConn{Id: getConnId(re.Id, re.Db), Info: re}
rc.Cli = cli

View File

@@ -2,6 +2,7 @@ package router
import (
"mayfly-go/internal/redis/api"
"mayfly-go/internal/redis/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/ioc"
"mayfly-go/pkg/req"
@@ -26,15 +27,15 @@ func InitRedisRouter(router *gin.RouterGroup) {
req.NewPost("/test-conn", rs.TestConn),
req.NewPost("", rs.Save).Log(req.NewLogSave("redis-保存信息")),
req.NewPost("", rs.Save).Log(req.NewLogSaveI(imsg.LogRedisSave)),
req.NewDelete(":id", rs.DeleteRedis).Log(req.NewLogSave("redis-删除信息")),
req.NewDelete(":id", rs.DeleteRedis).Log(req.NewLogSaveI(imsg.LogRedisDelete)),
req.NewGet("/:id/info", rs.RedisInfo),
req.NewGet(":id/cluster-info", rs.ClusterInfo),
req.NewPost(":id/:db/run-cmd", rs.RunCmd).Log(req.NewLogSave("redis-runCmd")),
req.NewPost(":id/:db/run-cmd", rs.RunCmd).Log(req.NewLogSaveI(imsg.LogRedisRunCmd)),
// 获取指定redis keys
req.NewPost(":id/:db/scan", rs.ScanKeys),

View File

@@ -8,6 +8,7 @@ import (
"mayfly-go/internal/sys/application"
"mayfly-go/internal/sys/consts"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/internal/sys/imsg"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/contextx"
"mayfly-go/pkg/model"
@@ -15,7 +16,6 @@ import (
"mayfly-go/pkg/utils/collx"
"mayfly-go/pkg/utils/cryptox"
"mayfly-go/pkg/utils/structx"
"strconv"
"strings"
"time"
@@ -62,28 +62,30 @@ func (a *Account) GetPermissions(rc *req.Ctx) {
}
func (a *Account) ChangePassword(rc *req.Ctx) {
ctx := rc.MetaCtx
form := req.BindJsonAndValid(rc, new(form.AccountChangePasswordForm))
originOldPwd, err := cryptox.DefaultRsaDecrypt(form.OldPassword, true)
biz.ErrIsNilAppendErr(err, "解密旧密码错误: %s")
biz.ErrIsNilAppendErr(err, "Wrong to decrypt old password: %s")
account := &entity.Account{Username: form.Username}
err = a.AccountApp.GetByCond(model.NewModelCond(account).Columns("Id", "Username", "Password", "Status"))
biz.ErrIsNil(err, "旧密码错误")
biz.IsTrue(cryptox.CheckPwdHash(originOldPwd, account.Password), "旧密码错误")
biz.IsTrue(account.IsEnable(), "该账号不可用")
biz.ErrIsNilI(ctx, err, imsg.ErrOldPasswordWrong)
biz.IsTrueI(ctx, cryptox.CheckPwdHash(originOldPwd, account.Password), imsg.ErrOldPasswordWrong)
biz.IsTrue(account.IsEnable(), "This account is not available")
originNewPwd, err := cryptox.DefaultRsaDecrypt(form.NewPassword, true)
biz.ErrIsNilAppendErr(err, "解密新密码错误: %s")
biz.IsTrue(utils.CheckAccountPasswordLever(originNewPwd), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
biz.ErrIsNilAppendErr(err, "Wrong to decrypt new password: %s")
biz.IsTrueI(ctx, utils.CheckAccountPasswordLever(originNewPwd), imsg.ErrAccountPasswordNotFollowRule)
updateAccount := new(entity.Account)
updateAccount.Id = account.Id
updateAccount.Password = cryptox.PwdHash(originNewPwd)
biz.ErrIsNil(a.AccountApp.Update(rc.MetaCtx, updateAccount), "更新账号密码失败")
biz.ErrIsNilAppendErr(a.AccountApp.Update(ctx, updateAccount), "failed to update account password: %s")
// 赋值loginAccount 主要用于记录操作日志,因为操作日志保存请求上下文没有该信息不保存日志
contextx.WithLoginAccount(rc.MetaCtx, &model.LoginAccount{
contextx.WithLoginAccount(ctx, &model.LoginAccount{
Id: account.Id,
Username: account.Username,
})
@@ -103,19 +105,20 @@ func (a *Account) UpdateAccount(rc *req.Ctx) {
// 账号id为登录者账号
updateAccount.Id = rc.GetLoginAccount().Id
ctx := rc.MetaCtx
if updateAccount.Password != "" {
biz.IsTrue(utils.CheckAccountPasswordLever(updateAccount.Password), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
biz.IsTrueI(ctx, utils.CheckAccountPasswordLever(updateAccount.Password), imsg.ErrAccountPasswordNotFollowRule)
updateAccount.Password = cryptox.PwdHash(updateAccount.Password)
}
oldAcc, err := a.AccountApp.GetById(updateAccount.Id)
biz.ErrIsNil(err, "账号信息不存在")
biz.ErrIsNilAppendErr(err, "Account does not exist: %s")
// 账号创建十分钟内允许修改用户名兼容oauth2首次登录修改用户名否则不允许修改
if oldAcc.CreateTime.Add(10 * time.Minute).Before(time.Now()) {
// 禁止更新用户名,防止误传被更新
updateAccount.Username = ""
}
biz.ErrIsNil(a.AccountApp.Update(rc.MetaCtx, updateAccount))
biz.ErrIsNil(a.AccountApp.Update(ctx, updateAccount))
}
/** 后台账号操作 **/
@@ -149,7 +152,7 @@ func (a *Account) SimpleAccounts(rc *req.Ctx) {
func (a *Account) AccountDetail(rc *req.Ctx) {
accountId := uint64(rc.PathParamInt("id"))
account, err := a.AccountApp.GetById(accountId)
biz.ErrIsNil(err, "账号不存在")
biz.ErrIsNilAppendErr(err, "Account does not exist: %s")
accountvo := new(vo.SimpleAccountVO)
structx.Copy(accountvo, account)
@@ -164,17 +167,21 @@ func (a *Account) SaveAccount(rc *req.Ctx) {
form.Password = "*****"
rc.ReqParam = form
ctx := rc.MetaCtx
if account.Id == 0 {
biz.NotEmpty(account.Password, "password is required")
biz.IsTrueI(ctx, utils.CheckAccountPasswordLever(account.Password), imsg.ErrAccountPasswordNotFollowRule)
account.Password = cryptox.PwdHash(account.Password)
biz.ErrIsNil(a.AccountApp.Create(rc.MetaCtx, account))
} else {
if account.Password != "" {
biz.IsTrue(utils.CheckAccountPasswordLever(account.Password), "密码强度必须8位以上且包含字⺟⼤⼩写+数字+特殊符号")
biz.IsTrueI(ctx, utils.CheckAccountPasswordLever(account.Password), imsg.ErrAccountPasswordNotFollowRule)
account.Password = cryptox.PwdHash(account.Password)
}
// 更新操作不允许修改用户名、防止误传更新
account.Username = ""
biz.ErrIsNil(a.AccountApp.Update(rc.MetaCtx, account))
biz.ErrIsNil(a.AccountApp.Update(ctx, account))
}
}
@@ -196,9 +203,7 @@ func (a *Account) DeleteAccount(rc *req.Ctx) {
ids := strings.Split(idsStr, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
biz.ErrIsNilAppendErr(a.AccountApp.Delete(rc.MetaCtx, uint64(value)), "删除失败:%s")
biz.ErrIsNil(a.AccountApp.Delete(rc.MetaCtx, cast.ToUint64(v)))
}
}

View File

@@ -9,6 +9,6 @@ import (
func GenerateCaptcha(rc *req.Ctx) {
id, image, err := captcha.Generate()
biz.ErrIsNilAppendErr(err, "获取验证码错误: %s")
biz.ErrIsNilAppendErr(err, "failed to generate the CAPTCHA: %s")
rc.ResData = collx.M{"base64Captcha": image, "cid": id}
}

View File

@@ -5,7 +5,9 @@ import (
"mayfly-go/internal/sys/application"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/pkg/biz"
"mayfly-go/pkg/config"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
)
type Config struct {
@@ -22,7 +24,7 @@ func (c *Config) Configs(rc *req.Ctx) {
func (c *Config) GetConfigValueByKey(rc *req.Ctx) {
key := rc.Query("key")
biz.NotEmpty(key, "key不能为空")
biz.NotEmpty(key, "key cannot be empty")
config := c.ConfigApp.GetConfig(key)
// 判断是否为公开配置
@@ -40,3 +42,9 @@ func (c *Config) SaveConfig(rc *req.Ctx) {
rc.ReqParam = form
biz.ErrIsNil(c.ConfigApp.Save(rc.MetaCtx, config))
}
// GetServerConfig 获取当前系统启动配置
func (c *Config) GetServerConfig(rc *req.Ctx) {
conf := config.Conf
rc.ResData = collx.Kvs("i18n", conf.Server.Lang)
}

View File

@@ -2,7 +2,7 @@ package form
type AccountCreateForm struct {
Id uint64 `json:"id"`
Name string `json:"name" binding:"required,max=16" msg:"required=姓名不能为空,max=姓名最大长度不能超过16位"`
Name string `json:"name" binding:"required,max=16" msg:"required=name cannot be blank,max=The maximum length of a name cannot exceed 16 characters"`
Username string `json:"username" binding:"pattern=account_username"`
Password string `json:"password"`
}

View File

@@ -24,7 +24,7 @@ func (r *Resource) GetAllResourceTree(rc *req.Ctx) {
func (r *Resource) GetById(rc *req.Ctx) {
res, err := r.ResourceApp.GetById(uint64(rc.PathParamInt("id")))
biz.ErrIsNil(err, "该资源不存在")
biz.ErrIsNil(err, "The resource does not exist")
rc.ResData = res
}
@@ -63,3 +63,10 @@ func (r *Resource) Sort(rc *req.Ctx) {
r.ResourceApp.Sort(rc.MetaCtx, sortE)
}
}
// GetResourceRoles
func (r *Resource) GetResourceRoles(rc *req.Ctx) {
rrs, err := r.ResourceApp.GetResourceRoles(uint64(rc.PathParamInt("id")))
biz.ErrIsNil(err)
rc.ResData = rrs
}

View File

@@ -8,7 +8,6 @@ import (
"mayfly-go/pkg/biz"
"mayfly-go/pkg/req"
"mayfly-go/pkg/utils/collx"
"strconv"
"strings"
"github.com/may-fly/cast"
@@ -50,9 +49,7 @@ func (r *Role) DelRole(rc *req.Ctx) {
ids := strings.Split(idsStr, ",")
for _, v := range ids {
value, err := strconv.Atoi(v)
biz.ErrIsNilAppendErr(err, "string类型转换为int异常: %s")
r.RoleApp.DeleteRole(rc.MetaCtx, uint64(value))
biz.ErrIsNil(r.RoleApp.DeleteRole(rc.MetaCtx, cast.ToUint64(v)))
}
}
@@ -79,7 +76,7 @@ func (r *Role) SaveResource(rc *req.Ctx) {
return cast.ToUint64(val)
})
biz.ErrIsNilAppendErr(r.RoleApp.SaveRoleResource(rc.MetaCtx, form.Id, newIds), "保存角色资源失败: %s")
biz.ErrIsNilAppendErr(r.RoleApp.SaveRoleResource(rc.MetaCtx, form.Id, newIds), "save role resource failed: %s")
}
// 查看角色关联的用户

View File

@@ -20,7 +20,7 @@ func (s *System) ConnectWs(g *gin.Context) {
defer func() {
if err := recover(); err != nil {
errInfo := anyx.ToString(err)
logx.Errorf("websocket连接失败: %s", errInfo)
logx.Errorf("websocket connect error: %s", errInfo)
if wsConn != nil {
wsConn.WriteMessage(websocket.TextMessage, []byte(errInfo))
wsConn.Close()
@@ -28,14 +28,14 @@ func (s *System) ConnectWs(g *gin.Context) {
}
}()
biz.ErrIsNilAppendErr(err, "%s")
biz.ErrIsNil(err)
clientId := g.Query("clientId")
biz.NotEmpty(clientId, "clientId不能为空")
biz.NotEmpty(clientId, "clientId cannot be empty")
// 权限校验
rc := req.NewCtxWithGin(g)
err = req.PermissionHandler(rc)
biz.ErrIsNil(err, "sys websocket没有权限连接")
biz.ErrIsNil(err, "sys-websocket connect without permission")
// 登录账号信息
la := rc.GetLoginAccount()

View File

@@ -4,10 +4,10 @@ import (
"context"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/internal/sys/domain/repository"
"mayfly-go/internal/sys/imsg"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
"mayfly-go/pkg/utils/cryptox"
)
type Account interface {
@@ -39,10 +39,8 @@ func (a *accountAppImpl) GetPageList(condition *entity.AccountQuery, pageParam *
func (a *accountAppImpl) Create(ctx context.Context, account *entity.Account) error {
if a.GetByCond(&entity.Account{Username: account.Username}) == nil {
return errorx.NewBiz("该账号用户名已存在")
return errorx.NewBizI(ctx, imsg.ErrUsernameExist)
}
// 默认密码为账号用户名
account.Password = cryptox.PwdHash(account.Username)
account.Status = entity.AccountEnable
return a.Insert(ctx, account)
}
@@ -52,7 +50,7 @@ func (a *accountAppImpl) Update(ctx context.Context, account *entity.Account) er
unAcc := &entity.Account{Username: account.Username}
err := a.GetByCond(unAcc)
if err == nil && unAcc.Id != account.Id {
return errorx.NewBiz("该用户名已存在")
return errorx.NewBizI(ctx, imsg.ErrUsernameExist)
}
}

View File

@@ -46,7 +46,7 @@ func (a *configAppImpl) Save(ctx context.Context, config *entity.Config) error {
oldConfig := a.GetConfig(config.Key)
if oldConfig.Permission != "all" && !strings.Contains(oldConfig.Permission, config.Modifier) {
return errorx.NewBiz("您无权修改该配置")
return errorx.NewBiz("You do not have permission to modify the configuration")
}
if err := a.UpdateById(ctx, config); err != nil {
@@ -67,7 +67,7 @@ func (a *configAppImpl) GetConfig(key string) *entity.Config {
}
if err := a.GetByCond(model.NewModelCond(config).Columns("Id", "Key", "Value", "Permission")); err != nil {
logx.Warnf("不存在key = [%s] 的系统配置", key)
logx.Warnf("There is no system configuration with key = [%s]", key)
} else {
cache.SetStr(SysConfigKeyPrefix+key, jsonx.ToStr(config), -1)
}

View File

@@ -0,0 +1,12 @@
package dto
import "time"
type ResourceRole struct {
RoleId uint64 `json:"roleId"`
RoleName string `json:"roleName"`
RoleCode string `json:"roleCode"`
RoleStatus int `json:"roleStatus"` // 角色状态
AllocateTime *time.Time `json:"allocateTime"` // 分配时间
Assigner string `json:"assigner"` // 分配人
}

View File

@@ -3,8 +3,10 @@ package application
import (
"context"
"mayfly-go/internal/common/consts"
"mayfly-go/internal/sys/application/dto"
"mayfly-go/internal/sys/domain/entity"
"mayfly-go/internal/sys/domain/repository"
"mayfly-go/internal/sys/imsg"
"mayfly-go/pkg/base"
"mayfly-go/pkg/errorx"
"mayfly-go/pkg/model"
@@ -26,14 +28,19 @@ type Resource interface {
Sort(ctx context.Context, re *entity.Resource) error
GetAccountResources(accountId uint64, toEntity any) error
GetResourceRoles(resourceId uint64) ([]*dto.ResourceRole, error)
}
type resourceAppImpl struct {
base.AppImpl[*entity.Resource, repository.Resource]
roleResourceRepo repository.RoleResource `inject:"RoleResourceRepo"`
roleApp Role `inject:"RoleApp"`
}
var _ (Resource) = (*resourceAppImpl)(nil)
// 注入ResourceRepo
func (r *resourceAppImpl) InjectResourceRepo(repo repository.Resource) {
r.Repo = repo
@@ -45,11 +52,11 @@ func (r *resourceAppImpl) Save(ctx context.Context, resource *entity.Resource) e
if resource.Code != "" {
oldRes, err := r.GetById(resource.Id, "Code")
if err != nil {
return errorx.NewBiz("更新失败, 该资源不存在")
return errorx.NewBiz("Resource does not exist")
}
// 如果修改了code则校验新code是否存在
if oldRes.Code != resource.Code {
if err := r.checkCode(resource.Code); err != nil {
if err := r.checkCode(ctx, resource.Code); err != nil {
return err
}
}
@@ -62,7 +69,7 @@ func (r *resourceAppImpl) Save(ctx context.Context, resource *entity.Resource) e
if pid := resource.Pid; pid != 0 {
pResource, err := r.GetById(uint64(pid))
if err != nil {
return errorx.NewBiz("该父资源不存在")
return errorx.NewBiz("pid does not exist")
}
resource.UiPath = pResource.UiPath + ui + entity.ResourceUiPathSp
} else {
@@ -72,7 +79,7 @@ func (r *resourceAppImpl) Save(ctx context.Context, resource *entity.Resource) e
if resource.Status == 0 {
resource.Status = entity.ResourceStatusEnable
}
if err := r.checkCode(resource.Code); err != nil {
if err := r.checkCode(ctx, resource.Code); err != nil {
return err
}
resource.Weight = int(time.Now().Unix())
@@ -82,7 +89,7 @@ func (r *resourceAppImpl) Save(ctx context.Context, resource *entity.Resource) e
func (r *resourceAppImpl) ChangeStatus(ctx context.Context, resourceId uint64, status int8) error {
resource, err := r.GetById(resourceId)
if err != nil {
return errorx.NewBiz("资源不存在")
return errorx.NewBiz("Resource does not exist")
}
resource.Status = status
return r.GetRepo().UpdateByUiPathLike(resource)
@@ -91,7 +98,7 @@ func (r *resourceAppImpl) ChangeStatus(ctx context.Context, resourceId uint64, s
func (r *resourceAppImpl) Sort(ctx context.Context, sortResource *entity.Resource) error {
resource, err := r.GetById(sortResource.Id)
if err != nil {
return errorx.NewBiz("资源不存在")
return errorx.NewBiz("Resource does not exist")
}
// 未改变父节点,则更新排序值即可
@@ -117,7 +124,7 @@ func (r *resourceAppImpl) Sort(ctx context.Context, sortResource *entity.Resourc
if sortResource.Pid != 0 {
newParentResource, err := r.GetById(uint64(sortResource.Pid))
if err != nil {
return errorx.NewBiz("父资源不存在")
return errorx.NewBiz("pid does not exist")
}
newParentResourceUiPath = newParentResource.UiPath
}
@@ -148,12 +155,12 @@ func (r *resourceAppImpl) Sort(ctx context.Context, sortResource *entity.Resourc
return r.UpdateByCond(ctx, updateMap, condition)
}
func (r *resourceAppImpl) checkCode(code string) error {
func (r *resourceAppImpl) checkCode(ctx context.Context, code string) error {
if strings.Contains(code, ",") {
return errorx.NewBiz("code不能包含','")
return errorx.NewBizI(ctx, imsg.ErrResourceCodeInvalid)
}
if r.CountByCond(&entity.Resource{Code: code}) != 0 {
return errorx.NewBiz("该code已存在")
return errorx.NewBizI(ctx, imsg.ErrResourceCodeExist)
}
return nil
}
@@ -161,7 +168,7 @@ func (r *resourceAppImpl) checkCode(code string) error {
func (r *resourceAppImpl) Delete(ctx context.Context, id uint64) error {
resource, err := r.GetById(id)
if err != nil {
return errorx.NewBiz("资源不存在")
return errorx.NewBiz("Resource does not exist")
}
// 删除当前节点及其所有子节点
@@ -185,3 +192,29 @@ func (r *resourceAppImpl) GetAccountResources(accountId uint64, toEntity any) er
return r.GetRepo().GetAccountResources(accountId, toEntity)
}
func (r *resourceAppImpl) GetResourceRoles(resourceId uint64) ([]*dto.ResourceRole, error) {
rr, err := r.roleApp.GetResourceRoles(resourceId)
if err != nil {
return nil, err
}
roleId2Rr := collx.ArrayToMap[*entity.RoleResource, uint64](rr, func(val *entity.RoleResource) uint64 { return val.RoleId })
roleIds := collx.MapKeys(roleId2Rr)
roles, err := r.roleApp.GetByIds(roleIds)
if err != nil {
return nil, err
}
return collx.ArrayMap[*entity.Role, *dto.ResourceRole](roles, func(val *entity.Role) *dto.ResourceRole {
role := roleId2Rr[val.Id]
return &dto.ResourceRole{
RoleId: val.Id,
RoleName: val.Name,
RoleCode: val.Code,
RoleStatus: val.Status,
AllocateTime: role.CreateTime,
Assigner: role.Creator,
}
}), nil
}

View File

@@ -28,6 +28,8 @@ type Role interface {
GetRoleResources(roleId uint64, toEntity any)
GetResourceRoles(resourceId uint64) ([]*entity.RoleResource, error)
// 保存角色资源关联记录
SaveRoleResource(ctx context.Context, roleId uint64, resourceIds []uint64) error
@@ -48,6 +50,8 @@ type roleAppImpl struct {
roleResourceRepo repository.RoleResource `inject:"RoleResourceRepo"`
}
var _ (Role) = (*roleAppImpl)(nil)
func (r *roleAppImpl) InjectRoleRepo(repo repository.Role) {
r.Repo = repo
}
@@ -90,6 +94,10 @@ func (m *roleAppImpl) GetRoleResources(roleId uint64, toEntity any) {
m.roleResourceRepo.GetRoleResources(roleId, toEntity)
}
func (m *roleAppImpl) GetResourceRoles(resourceId uint64) ([]*entity.RoleResource, error) {
return m.roleResourceRepo.SelectByCond(&entity.RoleResource{ResourceId: resourceId})
}
func (m *roleAppImpl) SaveRoleResource(ctx context.Context, roleId uint64, resourceIds []uint64) error {
oIds := m.GetRoleResourceIds(roleId)
@@ -129,7 +137,7 @@ func (m *roleAppImpl) RelateAccountRole(ctx context.Context, accountId, roleId u
err := m.accountRoleRepo.GetByCond(accountRole)
if err == nil {
return errorx.NewBiz("该用户已拥有该权限")
return errorx.NewBiz("The user already owns the role")
}
la := contextx.GetLoginAccount(ctx)

View File

@@ -0,0 +1,34 @@
package imsg
import "mayfly-go/pkg/i18n"
var En = map[i18n.MsgId]string{
// account
LogAccountCreate: "Create an account",
LogChangePassword: "Change password",
LogAccountChangeStatus: "Change account status",
LogResetOtpSecret: "Reset otp secret",
LogAccountDelete: "Deleting an account",
LogAssignUserRoles: "Assigning user roles",
ErrUsernameExist: "Username is already exists",
ErrOldPasswordWrong: "Old password wrong",
ErrAccountPasswordNotFollowRule: "Password must be at least 8 characters long and contain upper/lower case + number + special symbols",
// role
LogRoleSave: "Save role",
LogRoleDelete: "Deleting role",
LogAssignRoleResource: "Role association menu permissions",
// menu resource
LogResourceSave: "Save menu permissions",
LogResourceDelete: "Deleting menu permissions",
LogChangeResourceStatus: "Change menu permissions status",
LogSortResource: "Sort menu permissions",
ErrResourceCodeInvalid: "code cannot contain ','",
ErrResourceCodeExist: "The code already exists",
// sysconf
LogSaveSysConfig: "Save system configuration",
}

View File

@@ -0,0 +1,42 @@
package imsg
import (
"mayfly-go/internal/common/consts"
"mayfly-go/pkg/i18n"
)
func init() {
i18n.AppendLangMsg(i18n.Zh_CN, Zh_CN)
i18n.AppendLangMsg(i18n.En, En)
}
const (
// account
LogAccountCreate = iota + consts.ImsgNumSys
LogChangePassword
LogAccountChangeStatus
LogResetOtpSecret
LogAccountDelete
LogAssignUserRoles
ErrUsernameExist
ErrOldPasswordWrong
ErrAccountPasswordNotFollowRule
// role
LogRoleSave
LogRoleDelete
LogAssignRoleResource
// menu resource
LogResourceSave
LogResourceDelete
LogChangeResourceStatus
LogSortResource
ErrResourceCodeInvalid
ErrResourceCodeExist
// sysconf
LogSaveSysConfig
)

View File

@@ -0,0 +1,34 @@
package imsg
import "mayfly-go/pkg/i18n"
var Zh_CN = map[i18n.MsgId]string{
// account
LogAccountCreate: "创建账号",
LogChangePassword: "修改密码",
LogAccountChangeStatus: "修改账号状态",
LogResetOtpSecret: "重置OTP密钥",
LogAccountDelete: "删除账号",
LogAssignUserRoles: "关联用户角色",
ErrUsernameExist: "用户名已存在",
ErrOldPasswordWrong: "旧密码错误",
ErrAccountPasswordNotFollowRule: "密码强度必须8个字符以上且包含字⺟⼤⼩写+数字+特殊符号",
// role
LogRoleSave: "保存角色",
LogRoleDelete: "删除角色",
LogAssignRoleResource: "角色关联菜单权限",
// menu resource
LogResourceSave: "保存菜单权限",
LogResourceDelete: "删除菜单权限",
LogChangeResourceStatus: "修改菜单权限状态",
LogSortResource: "菜单权限排序",
ErrResourceCodeInvalid: "code不能包含','",
ErrResourceCodeExist: "该编号已存在",
// sysconf
LogSaveSysConfig: "保存系统配置",
}

View File

@@ -29,38 +29,48 @@ func (r *resourceRepoImpl) UpdateByUiPathLike(resource *entity.Resource) error {
}
func (r *resourceRepoImpl) GetAccountResources(accountId uint64, toEntity any) error {
sql := `SELECT
m.id,
m.pid,
m.weight,
m.name,
m.code,
m.meta,
m.type,
m.status
FROM
t_sys_resource m
WHERE
m.status = 1 AND m.is_deleted = 0
AND m.id IN (
SELECT DISTINCT
( rmb.resource_id )
FROM
t_sys_account_role p
JOIN t_sys_role r ON p.role_Id = r.id
AND p.account_id = ? AND p.is_deleted = 0
AND r.STATUS = 1 AND r.is_deleted = 0
JOIN t_sys_role_resource rmb ON rmb.role_id = r.id AND rmb.is_deleted = 0 UNION
SELECT
r.id
FROM
t_sys_resource r
JOIN t_sys_role_resource rr ON r.id = rr.resource_id
JOIN t_sys_role ro ON rr.role_id = ro.id
AND ro.status = 1 AND ro.code LIKE 'COMMON%' AND ro.is_deleted = 0 AND rr.is_deleted = 0
)
ORDER BY
m.pid ASC,
m.weight ASC`
sql := `
SELECT
m.id,
m.pid,
m.weight,
m.name,
m.code,
m.meta,
m.type,
m.status
FROM
t_sys_resource m
JOIN (
SELECT DISTINCT rmb.resource_id
FROM
t_sys_account_role p
JOIN t_sys_role r ON p.role_Id = r.id
JOIN t_sys_role_resource rmb ON rmb.role_id = r.id
WHERE
p.account_id = ?
AND p.is_deleted = 0
AND r.status = 1
AND r.is_deleted = 0
AND rmb.is_deleted = 0
UNION
SELECT
r.id
FROM
t_sys_resource r
JOIN t_sys_role_resource rr ON r.id = rr.resource_id
JOIN t_sys_role ro ON rr.role_id = ro.id
WHERE
ro.status = 1
AND ro.code LIKE 'COMMON%'
AND ro.is_deleted = 0
AND rr.is_deleted = 0
) AS subquery ON m.id = subquery.resource_id
WHERE
m.status = 1
AND m.is_deleted = 0
ORDER BY
m.pid ASC,
m.weight ASC;`
return r.SelectBySql(sql, toEntity, accountId)
}

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