diff --git a/.gitignore b/.gitignore index 6f365d5c..f998f72f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ */node_modules/ **/vendor/ .idea +.vscode out diff --git a/build_release.sh b/build_release.sh index cc4c218c..462eea0b 100755 --- a/build_release.sh +++ b/build_release.sh @@ -106,7 +106,7 @@ function buildDocker() { imageVersion=$1 cd ${server_folder} imageName="mayflygo/mayfly-go:${imageVersion}" - docker build -t "${imageName}" . + docker build --platform linux/amd64 -t "${imageName}" . echo_green "docker镜像构建完成->[${imageName}]" echo_yellow "-------------------构建docker镜像结束-------------------" } @@ -197,4 +197,4 @@ function runBuild() { rm -rf ${server_folder}/static/static/index.html } -runBuild \ No newline at end of file +runBuild diff --git a/mayfly_go_web/src/common/openApi.ts b/mayfly_go_web/src/common/openApi.ts index d7404881..062d8ada 100644 --- a/mayfly_go_web/src/common/openApi.ts +++ b/mayfly_go_web/src/common/openApi.ts @@ -6,6 +6,7 @@ export default { changePwd: (param: any) => request.post('/sys/accounts/change-pwd', param), getPublicKey: () => request.get('/common/public-key'), getConfigValue: (params: any) => request.get('/sys/configs/value', params), + oauthConfig: () => request.get('/sys/configs/auth'), captcha: () => request.get('/sys/captcha'), logout: () => request.post('/sys/accounts/logout/{token}'), getPermissions: () => request.get('/sys/accounts/permissions'), diff --git a/mayfly_go_web/src/views/login/component/AccountLogin.vue b/mayfly_go_web/src/views/login/component/AccountLogin.vue index e72bdaf4..5f7ff958 100644 --- a/mayfly_go_web/src/views/login/component/AccountLogin.vue +++ b/mayfly_go_web/src/views/login/component/AccountLogin.vue @@ -2,37 +2,24 @@
- + - + - + @@ -47,21 +34,19 @@ - + - + - + @@ -73,28 +58,17 @@ - - + + - + @@ -265,10 +239,14 @@ const onSignIn = async () => { return; } state.showLoginFailTips = false; + loginResDeal(loginRes); +} + +const loginResDeal = (loginRes: any) => { // 用户信息 const userInfos = { name: loginRes.name, - username: state.loginForm.username, + username: loginRes.username, // 头像 photo: letterAvatar(state.loginForm.username), time: new Date().getTime(), @@ -297,6 +275,10 @@ const onSignIn = async () => { }, 400); }; +defineExpose({ + loginResDeal +}); + // 登录成功后的跳转 const signInSuccess = async (accessToken: string = '') => { // 存储 token 到浏览器缓存 diff --git a/mayfly_go_web/src/views/login/index.vue b/mayfly_go_web/src/views/login/index.vue index 0f337dc5..36e83c0c 100644 --- a/mayfly_go_web/src/views/login/index.vue +++ b/mayfly_go_web/src/views/login/index.vue @@ -9,7 +9,7 @@ - + - + +
+
+ + + + + + + +
@@ -160,18 +154,20 @@ 立即设置 - -
+
--> +
-
绑定QQ
-
已绑定QQ:110****566
+
绑定OAuth2
+
+ {{ authStatus.bind.oauth2 ? '已绑定' : '未绑定' }} +
- 立即设置 + 立即绑定
-
--> + @@ -186,6 +182,8 @@ import { personApi } from './api'; import { dateFormat } from '@/common/utils/date'; import { storeToRefs } from 'pinia'; import { useUserInfo } from '@/store/userInfo'; +import config from '@/common/config'; +import { getSession } from '@/common/utils/storage'; const { userInfo } = storeToRefs(useUserInfo()); const state = reactive({ @@ -208,9 +206,13 @@ const state = reactive({ accountForm: { password: '', }, + authStatus: { + enable: { oauth2: false }, + bind: { oauth2: false } + } }); -const { msgDialog, accountForm } = toRefs(state); +const { msgDialog, accountForm, authStatus } = toRefs(state); // 当前时间提示语 const currentTime = computed(() => { @@ -228,9 +230,10 @@ const roleInfo = computed(() => { return state.accountInfo.roles.map((val: any) => val.name).join('、'); }); -onMounted(() => { +onMounted(async () => { getAccountInfo(); getMsgs(); + state.authStatus = await personApi.authStatus.request() }); const getAccountInfo = async () => { @@ -242,6 +245,31 @@ const updateAccount = async () => { ElMessage.success('更新成功'); }; +const bindOAuth2 = () => { + // 小窗口打开oauth2鉴权 + let oauthWindoe = window.open(config.baseApiUrl + "/sys/auth/oauth2/bind?token=" + getSession('token'), "oauth2", "width=600,height=600"); + if (oauthWindoe) { + const handler = (e: any) => { + if (e.data.action === "oauthBind") { + oauthWindoe!.close(); + window.removeEventListener("message", handler); + // 处理登录token + console.log(e.data); + ElMessage.success('绑定成功'); + setTimeout(() => { + location.reload(); + }, 1000); + } + } + window.addEventListener("message", handler); + setInterval(() => { + if (oauthWindoe!.closed) { + window.removeEventListener("message", handler); + } + }, 1000); + } +} + const getMsgs = async () => { const res = await personApi.getMsgs.request(state.msgDialog.query); state.msgDialog.msgs = res; diff --git a/mayfly_go_web/src/views/system/api.ts b/mayfly_go_web/src/views/system/api.ts index 601c1895..7a21a751 100644 --- a/mayfly_go_web/src/views/system/api.ts +++ b/mayfly_go_web/src/views/system/api.ts @@ -43,3 +43,8 @@ export const configApi = { export const logApi = { list: Api.newGet('/syslogs'), }; + +export const authApi = { + info: Api.newGet('/sys/auth'), + saveOAuth2: Api.newPut('/sys/auth/oauth2'), +}; diff --git a/mayfly_go_web/src/views/system/auth/AuthInfo.vue b/mayfly_go_web/src/views/system/auth/AuthInfo.vue new file mode 100644 index 00000000..39fe8451 --- /dev/null +++ b/mayfly_go_web/src/views/system/auth/AuthInfo.vue @@ -0,0 +1,105 @@ + + + + diff --git a/mayfly_go_web/src/views/system/config/ConfigList.vue b/mayfly_go_web/src/views/system/config/ConfigList.vue index 87fc0aca..4626dff2 100755 --- a/mayfly_go_web/src/views/system/config/ConfigList.vue +++ b/mayfly_go_web/src/views/system/config/ConfigList.vue @@ -29,12 +29,17 @@ + " + + "" + + "") + } else if sAccountId, ok := strings.CutPrefix(stateAction, "bind:"); ok { + // 绑定 + accountId, err := strconv.ParseUint(sAccountId, 10, 64) + if err != nil { + biz.ErrIsNil(err, "绑定用户失败: "+err.Error()) + } + now := time.Now() + if err := a.AuthApp.BindOAuthAccount(&entity.OAuthAccount{ + AccountId: accountId, + Identity: oauthAccount.Identity, + CreateTime: &now, + UpdateTime: &now, + }); err != nil { + biz.ErrIsNil(err, "绑定用户失败: "+err.Error()) + } + res := map[string]any{ + "action": "oauthBind", + "bind": true, + } + b, err = json.Marshal(res) + biz.ErrIsNil(err, "数据序列化失败") + rc.GinCtx.Header("Content-Type", "text/html; charset=utf-8") + rc.GinCtx.Writer.WriteHeader(http.StatusOK) + _, _ = rc.GinCtx.Writer.WriteString("" + + "" + + "") + } else { + biz.ErrIsNil(errors.New("state不合法"), "state不合法") + } +} + +func (a *Auth) getOAuthClient() (*oauth2.Config, *vo.OAuth2VO, error) { + config := a.ConfigApp.GetConfig(AuthOAuth2Key) + oauth2Vo := &vo.OAuth2VO{} + if config.Value != "" { + if err := json.Unmarshal([]byte(config.Value), oauth2Vo); err != nil { + global.Log.Warnf("解析自定义oauth2配置失败,err:%s", err.Error()) + return nil, nil, errors.New("解析自定义oauth2配置失败") + } + } + if oauth2Vo.ClientID == "" { + biz.ErrIsNil(nil, "请先配置oauth2") + return nil, nil, errors.New("请先配置oauth2") + } + client := &oauth2.Config{ + ClientID: oauth2Vo.ClientID, + ClientSecret: oauth2Vo.ClientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: oauth2Vo.AuthorizationURL, + TokenURL: oauth2Vo.AccessTokenURL, + }, + RedirectURL: oauth2Vo.RedirectURL + "api/sys/auth/oauth2/callback", + Scopes: strings.Split(oauth2Vo.Scopes, ","), + } + return client, oauth2Vo, nil +} + +// GetInfo 获取认证平台信息 +func (a *Auth) GetInfo(rc *req.Ctx) { + config := a.ConfigApp.GetConfig(AuthOAuth2Key) + oauth2 := &vo.OAuth2VO{} + if config.Value != "" { + if err := json.Unmarshal([]byte(config.Value), oauth2); err != nil { + global.Log.Warnf("解析自定义oauth2配置失败,err:%s", err.Error()) + biz.ErrIsNil(err, "解析自定义oauth2配置失败") + } + } + rc.ResData = &vo.AuthVO{ + OAuth2VO: oauth2, + } +} + +func (a *Auth) SaveOAuth2(rc *req.Ctx) { + form := &form2.OAuth2Form{} + form = ginx.BindJsonAndValid(rc.GinCtx, form) + rc.ReqParam = form + // 先获取看看有没有 + config := a.ConfigApp.GetConfig(AuthOAuth2Key) + now := time.Now() + if config.Id == 0 { + config.CreatorId = rc.LoginAccount.Id + config.Creator = rc.LoginAccount.Username + config.CreateTime = &now + } + config.ModifierId = rc.LoginAccount.Id + config.Modifier = rc.LoginAccount.Username + config.UpdateTime = &now + config.Name = AuthOAuth2Name + config.Key = AuthOAuth2Key + config.Params = AuthOAuth2Param + b, err := json.Marshal(form) + if err != nil { + biz.ErrIsNil(err, "json marshal error") + return + } + config.Value = string(b) + config.Remark = AuthOAuth2Remark + a.ConfigApp.Save(config) +} + +func (a *Auth) OAuth2Bind(rc *req.Ctx) { + client, _, err := a.getOAuthClient() + if err != nil { + biz.ErrIsNil(err, "获取oauth2 client失败: "+err.Error()) + return + } + state := stringx.Rand(32) + cache.SetStr("oauth2:state:"+state, "bind:"+strconv.FormatUint(rc.LoginAccount.Id, 10), + 5*time.Minute) + rc.GinCtx.Redirect(http.StatusFound, client.AuthCodeURL(state)) +} + +func (a *Auth) Auth2Status(ctx *req.Ctx) { + res := &vo.AuthStatusVO{} + config := a.ConfigApp.GetConfig(AuthOAuth2Key) + if config.Value != "" { + oauth2 := &vo.OAuth2VO{} + if err := json.Unmarshal([]byte(config.Value), oauth2); err != nil { + global.Log.Warnf("解析自定义oauth2配置失败,err:%s", err.Error()) + biz.ErrIsNil(err, "解析自定义oauth2配置失败") + } else if oauth2.ClientID != "" { + res.Enable.OAuth2 = true + } + } + if res.Enable.OAuth2 { + err := a.AuthApp.GetOAuthAccount(&entity.OAuthAccount{ + AccountId: ctx.LoginAccount.Id, + }, "account_id", "identity") + if err != nil { + if err != gorm.ErrRecordNotFound { + biz.ErrIsNil(err, "查询用户失败: "+err.Error()) + } + } else { + res.Bind.OAuth2 = true + } + } + ctx.ResData = res +} diff --git a/server/internal/sys/api/config.go b/server/internal/sys/api/config.go index 2d59d450..59e3c782 100644 --- a/server/internal/sys/api/config.go +++ b/server/internal/sys/api/config.go @@ -1,11 +1,14 @@ package api import ( + "encoding/json" "mayfly-go/internal/sys/api/form" + "mayfly-go/internal/sys/api/vo" "mayfly-go/internal/sys/application" "mayfly-go/internal/sys/domain/entity" "mayfly-go/pkg/biz" "mayfly-go/pkg/ginx" + "mayfly-go/pkg/global" "mayfly-go/pkg/req" ) @@ -25,6 +28,21 @@ func (c *Config) GetConfigValueByKey(rc *req.Ctx) { rc.ResData = c.ConfigApp.GetConfig(key).Value } +func (c *Config) GetConfigValueByKeyWithNoToken(keys []string) func(rc *req.Ctx) { + keyMap := make(map[string]struct{}) + for _, key := range keys { + keyMap[key] = struct{}{} + } + return func(rc *req.Ctx) { + key := rc.GinCtx.Query("key") + biz.NotEmpty(key, "key不能为空") + if _, ok := keyMap[key]; !ok { + biz.ErrIsNil(nil, "无权限获取该配置信息") + } + rc.ResData = c.ConfigApp.GetConfig(key).Value + } +} + func (c *Config) SaveConfig(rc *req.Ctx) { form := &form.ConfigForm{} config := ginx.BindJsonAndCopyTo(rc.GinCtx, form, new(entity.Config)) @@ -32,3 +50,19 @@ func (c *Config) SaveConfig(rc *req.Ctx) { config.SetBaseInfo(rc.LoginAccount) c.ConfigApp.Save(config) } + +// AuthConfig auth相关配置 +func (c *Config) AuthConfig(rc *req.Ctx) { + resp := &vo.Auth2EnableVO{} + config := c.ConfigApp.GetConfig(AuthOAuth2Key) + oauth2 := &vo.OAuth2VO{} + if config.Value != "" { + if err := json.Unmarshal([]byte(config.Value), oauth2); err != nil { + global.Log.Warnf("解析自定义oauth2配置失败,err:%s", err.Error()) + biz.ErrIsNil(err, "解析自定义oauth2配置失败") + } else if oauth2.ClientID != "" { + resp.OAuth2 = true + } + } + rc.ResData = resp +} diff --git a/server/internal/sys/api/form/auth.go b/server/internal/sys/api/form/auth.go new file mode 100644 index 00000000..e75b9ced --- /dev/null +++ b/server/internal/sys/api/form/auth.go @@ -0,0 +1,13 @@ +package form + +type OAuth2Form struct { + ClientID string `json:"clientID" binding:"required"` + ClientSecret string `json:"clientSecret" binding:"required"` + AuthorizationURL string `json:"authorizationURL" binding:"required,url"` + AccessTokenURL string `json:"accessTokenURL" binding:"required,url"` + ResourceURL string `json:"resourceURL" binding:"required,url"` + RedirectURL string `json:"redirectURL" binding:"required,url"` + UserIdentifier string `json:"userIdentifier" binding:"required"` + Scopes string `json:"scopes"` + AutoRegister bool `json:"autoRegister"` +} diff --git a/server/internal/sys/api/vo/auth.go b/server/internal/sys/api/vo/auth.go new file mode 100644 index 00000000..dbc37e6e --- /dev/null +++ b/server/internal/sys/api/vo/auth.go @@ -0,0 +1,26 @@ +package vo + +type OAuth2VO struct { + ClientID string `json:"clientID"` + ClientSecret string `json:"clientSecret"` + AuthorizationURL string `json:"authorizationURL"` + AccessTokenURL string `json:"accessTokenURL"` + ResourceURL string `json:"resourceURL"` + RedirectURL string `json:"redirectURL"` + UserIdentifier string `json:"userIdentifier"` + Scopes string `json:"scopes"` + AutoRegister bool `json:"autoRegister"` +} + +type AuthVO struct { + *OAuth2VO `json:"oauth2"` +} + +type Auth2EnableVO struct { + OAuth2 bool `json:"oauth2"` +} + +type AuthStatusVO struct { + Enable Auth2EnableVO `json:"enable"` + Bind Auth2EnableVO `json:"bind"` +} diff --git a/server/internal/sys/application/application.go b/server/internal/sys/application/application.go index 08694abe..059aa04f 100644 --- a/server/internal/sys/application/application.go +++ b/server/internal/sys/application/application.go @@ -6,6 +6,7 @@ import ( var ( accountApp = newAccountApp(persistence.GetAccountRepo()) + authApp = newAuthApp(persistence.GetOAuthAccountRepo()) configApp = newConfigApp(persistence.GetConfigRepo()) resourceApp = newResourceApp(persistence.GetResourceRepo()) roleApp = newRoleApp(persistence.GetRoleRepo()) @@ -16,6 +17,10 @@ func GetAccountApp() Account { return accountApp } +func GetAuthApp() Auth { + return authApp +} + func GetConfigApp() Config { return configApp } diff --git a/server/internal/sys/application/auth.go b/server/internal/sys/application/auth.go new file mode 100644 index 00000000..a1772e64 --- /dev/null +++ b/server/internal/sys/application/auth.go @@ -0,0 +1,29 @@ +package application + +import ( + "mayfly-go/internal/sys/domain/entity" + "mayfly-go/internal/sys/domain/repository" +) + +type Auth interface { + GetOAuthAccount(condition *entity.OAuthAccount, cols ...string) error + BindOAuthAccount(e *entity.OAuthAccount) error +} + +func newAuthApp(oauthAccountRepo repository.OAuthAccount) Auth { + return &authAppImpl{ + oauthAccountRepo: oauthAccountRepo, + } +} + +type authAppImpl struct { + oauthAccountRepo repository.OAuthAccount +} + +func (a *authAppImpl) GetOAuthAccount(condition *entity.OAuthAccount, cols ...string) error { + return a.oauthAccountRepo.GetOAuthAccount(condition, cols...) +} + +func (a *authAppImpl) BindOAuthAccount(e *entity.OAuthAccount) error { + return a.oauthAccountRepo.SaveOAuthAccount(e) +} diff --git a/server/internal/sys/application/config.go b/server/internal/sys/application/config.go index 75cbe6d8..aab46466 100644 --- a/server/internal/sys/application/config.go +++ b/server/internal/sys/application/config.go @@ -17,7 +17,7 @@ type Config interface { Save(config *entity.Config) - // 获取指定key的配置信息, 不会返回nil, 若不存在则值都默认值即空字符串 + // GetConfig 获取指定key的配置信息, 不会返回nil, 若不存在则值都默认值即空字符串 GetConfig(key string) *entity.Config } diff --git a/server/internal/sys/domain/entity/auth.go b/server/internal/sys/domain/entity/auth.go new file mode 100644 index 00000000..c558ab06 --- /dev/null +++ b/server/internal/sys/domain/entity/auth.go @@ -0,0 +1,20 @@ +package entity + +import ( + "mayfly-go/pkg/model" + "time" +) + +type OAuthAccount struct { + model.DeletedModel + + AccountId uint64 `json:"accountId" gorm:"column:account_id;index:account_id,unique"` + Identity string `json:"identity" gorm:"column:identity;index:identity,unique"` + + CreateTime *time.Time `json:"createTime"` + UpdateTime *time.Time `json:"updateTime"` +} + +func (OAuthAccount) TableName() string { + return "t_oauth_account" +} diff --git a/server/internal/sys/domain/entity/config.go b/server/internal/sys/domain/entity/config.go index 1e7a4a9e..513ab419 100644 --- a/server/internal/sys/domain/entity/config.go +++ b/server/internal/sys/domain/entity/config.go @@ -12,14 +12,15 @@ const ( ConfigKeyUseLoginOtp string = "UseLoginOtp" // 是否开启otp双因素校验 ConfigKeyDbQueryMaxCount string = "DbQueryMaxCount" // 数据库查询的最大数量 ConfigKeyDbSaveQuerySQL string = "DbSaveQuerySQL" // 数据库是否记录查询相关sql + ConfigUseWartermark string = "UseWartermark" // 是否使用水印 ) type Config struct { model.Model Name string `json:"name"` // 配置名 Key string `json:"key"` // 配置key - Params string `json:"params"` - Value string `json:"value"` + Params string `json:"params" gorm:"column:params;type:varchar(1000)"` + Value string `json:"value" gorm:"column:value;type:varchar(1000)"` Remark string `json:"remark"` } diff --git a/server/internal/sys/domain/entity/resource.go b/server/internal/sys/domain/entity/resource.go index 32d8669c..095264b2 100644 --- a/server/internal/sys/domain/entity/resource.go +++ b/server/internal/sys/domain/entity/resource.go @@ -5,9 +5,9 @@ import "mayfly-go/pkg/model" type Resource struct { model.Model Pid int `json:"pid"` - UiPath string // 唯一标识路径 - Type int8 `json:"type"` // 1:菜单路由;2:资源(按钮等) - Status int8 `json:"status"` // 1:可用;-1:不可用 + UiPath string `json:"ui_path"` // 唯一标识路径 + Type int8 `json:"type"` // 1:菜单路由;2:资源(按钮等) + Status int8 `json:"status"` // 1:可用;-1:不可用 Code string `json:"code"` Name string `json:"name"` Weight int `json:"weight"` diff --git a/server/internal/sys/domain/entity/syslog.go b/server/internal/sys/domain/entity/syslog.go index 65f14aa3..ebda3fcf 100644 --- a/server/internal/sys/domain/entity/syslog.go +++ b/server/internal/sys/domain/entity/syslog.go @@ -15,8 +15,8 @@ type SysLog struct { Type int8 `json:"type"` Description string `json:"description"` - ReqParam string `json:"reqParam"` // 请求参数 - Resp string `json:"resp"` // 响应结构 + ReqParam string `json:"reqParam" gorm:"column:req_param;type:varchar(1000)"` // 请求参数 + Resp string `json:"resp" gorm:"column:resp;type:varchar(1000)"` // 响应结构 } func (a *SysLog) TableName() string { diff --git a/server/internal/sys/domain/repository/auth.go b/server/internal/sys/domain/repository/auth.go new file mode 100644 index 00000000..e5605c56 --- /dev/null +++ b/server/internal/sys/domain/repository/auth.go @@ -0,0 +1,10 @@ +package repository + +import "mayfly-go/internal/sys/domain/entity" + +type OAuthAccount interface { + // GetOAuthAccount 根据identity获取第三方账号信息 + GetOAuthAccount(condition *entity.OAuthAccount, cols ...string) error + + SaveOAuthAccount(e *entity.OAuthAccount) error +} diff --git a/server/internal/sys/infrastructure/persistence/auth.go b/server/internal/sys/infrastructure/persistence/auth.go new file mode 100644 index 00000000..ead99ff9 --- /dev/null +++ b/server/internal/sys/infrastructure/persistence/auth.go @@ -0,0 +1,24 @@ +package persistence + +import ( + "mayfly-go/internal/sys/domain/entity" + "mayfly-go/internal/sys/domain/repository" + "mayfly-go/pkg/gormx" +) + +type authAccountRepoImpl struct{} + +func newAuthAccountRepo() repository.OAuthAccount { + return new(authAccountRepoImpl) +} + +func (a *authAccountRepoImpl) GetOAuthAccount(condition *entity.OAuthAccount, cols ...string) error { + return gormx.GetBy(condition, cols...) +} + +func (a *authAccountRepoImpl) SaveOAuthAccount(e *entity.OAuthAccount) error { + if e.Id == 0 { + return gormx.Insert(e) + } + return gormx.UpdateById(e) +} diff --git a/server/internal/sys/infrastructure/persistence/persistence.go b/server/internal/sys/infrastructure/persistence/persistence.go index ca95c5d2..ad07b943 100644 --- a/server/internal/sys/infrastructure/persistence/persistence.go +++ b/server/internal/sys/infrastructure/persistence/persistence.go @@ -3,17 +3,22 @@ package persistence import "mayfly-go/internal/sys/domain/repository" var ( - accountRepo = newAccountRepo() - configRepo = newConfigRepo() - resourceRepo = newResourceRepo() - roleRepo = newRoleRepo() - syslogRepo = newSyslogRepo() + accountRepo = newAccountRepo() + authAccountRepo = newAuthAccountRepo() + configRepo = newConfigRepo() + resourceRepo = newResourceRepo() + roleRepo = newRoleRepo() + syslogRepo = newSyslogRepo() ) func GetAccountRepo() repository.Account { return accountRepo } +func GetOAuthAccountRepo() repository.OAuthAccount { + return authAccountRepo +} + func GetConfigRepo() repository.Config { return configRepo } diff --git a/server/internal/sys/router/auth.go b/server/internal/sys/router/auth.go new file mode 100644 index 00000000..0f0670a6 --- /dev/null +++ b/server/internal/sys/router/auth.go @@ -0,0 +1,35 @@ +package router + +import ( + "github.com/gin-gonic/gin" + msgapp "mayfly-go/internal/msg/application" + "mayfly-go/internal/sys/api" + "mayfly-go/internal/sys/application" + "mayfly-go/pkg/req" +) + +func InitSysAuthRouter(router *gin.RouterGroup) { + r := &api.Auth{ + ConfigApp: application.GetConfigApp(), + AuthApp: application.GetAuthApp(), + AccountApp: application.GetAccountApp(), + MsgApp: msgapp.GetMsgApp(), + } + rg := router.Group("sys/auth") + + baseP := req.NewPermission("system:auth:base") + + reqs := [...]*req.Conf{ + req.NewGet("", r.GetInfo).RequiredPermission(baseP), + + req.NewPut("/oauth2", r.SaveOAuth2).RequiredPermission(baseP), + + req.NewGet("/status", r.Auth2Status), + req.NewGet("/oauth2/bind", r.OAuth2Bind), + + req.NewGet("/oauth2/login", r.OAuth2Login).DontNeedToken(), + req.NewGet("/oauth2/callback", r.OAuth2Callback).NoRes().DontNeedToken(), + } + + req.BatchSetGroup(rg, reqs[:]) +} diff --git a/server/internal/sys/router/config.go b/server/internal/sys/router/config.go index 8839427f..e7511df2 100644 --- a/server/internal/sys/router/config.go +++ b/server/internal/sys/router/config.go @@ -3,6 +3,7 @@ package router import ( "mayfly-go/internal/sys/api" "mayfly-go/internal/sys/application" + "mayfly-go/internal/sys/domain/entity" "mayfly-go/pkg/req" "github.com/gin-gonic/gin" @@ -18,9 +19,17 @@ func InitSysConfigRouter(router *gin.RouterGroup) { req.NewGet("", r.Configs).RequiredPermission(baseP), // 获取指定配置key对应的值 - req.NewGet("/value", r.GetConfigValueByKey).DontNeedToken(), + req.NewGet("/value", r.GetConfigValueByKeyWithNoToken([]string{ + entity.ConfigKeyAccountLoginSecurity, + entity.ConfigKeyDbQueryMaxCount, + entity.ConfigKeyDbSaveQuerySQL, + entity.ConfigUseWartermark, + })).DontNeedToken(), - req.NewPost("", r.SaveConfig).Log(req.NewLogSave("保存系统配置信息")).RequiredPermissionCode("config:save"), + req.NewGet("/auth", r.AuthConfig).DontNeedToken(), + + req.NewPost("", r.SaveConfig).Log(req.NewLogSave("保存系统配置信息")). + RequiredPermissionCode("config:save"), } req.BatchSetGroup(configG, reqs[:]) diff --git a/server/internal/sys/router/router.go b/server/internal/sys/router/router.go index b94e305a..104a277e 100644 --- a/server/internal/sys/router/router.go +++ b/server/internal/sys/router/router.go @@ -10,4 +10,5 @@ func Init(router *gin.RouterGroup) { InitSystemRouter(router) InitSyslogRouter(router) InitSysConfigRouter(router) + InitSysAuthRouter(router) } diff --git a/server/migrations/2022.go b/server/migrations/2022.go new file mode 100644 index 00000000..a47596ac --- /dev/null +++ b/server/migrations/2022.go @@ -0,0 +1,107 @@ +package migrations + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" + entity2 "mayfly-go/internal/db/domain/entity" + "mayfly-go/internal/machine/domain/entity" + entity3 "mayfly-go/internal/mongo/domain/entity" + entity6 "mayfly-go/internal/msg/domain/entity" + entity4 "mayfly-go/internal/redis/domain/entity" + entity5 "mayfly-go/internal/sys/domain/entity" + entity7 "mayfly-go/internal/tag/domain/entity" +) + +// T2022 TODO 在此之前的数据库表结构初始化, 目前先使用mayfly-go.sql文件初始化数据库结构 +func T2022() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "2022", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&entity.AuthCert{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity.Machine{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity.MachineFile{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity.MachineMonitor{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity.MachineScript{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity.MachineCronJob{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity.MachineCronJobExec{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity.MachineCronJobRelate{}); err != nil { + return err + } + + if err := tx.AutoMigrate(&entity2.Db{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity2.DbSql{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity2.DbSqlExec{}); err != nil { + return err + } + + if err := tx.AutoMigrate(&entity3.Mongo{}); err != nil { + return err + } + + if err := tx.AutoMigrate(&entity4.Redis{}); err != nil { + return err + } + + if err := tx.AutoMigrate(&entity5.Account{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity5.AccountRole{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity5.Config{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity5.SysLog{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity5.Resource{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity5.Role{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity5.RoleResource{}); err != nil { + return err + } + + if err := tx.AutoMigrate(&entity6.Msg{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity7.TagTree{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity7.TagTreeTeam{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity7.Team{}); err != nil { + return err + } + if err := tx.AutoMigrate(&entity7.TeamMember{}); err != nil { + return err + } + + return nil + }, + Rollback: func(tx *gorm.DB) error { + return nil + }, + } +} diff --git a/server/migrations/20230720.go b/server/migrations/20230720.go new file mode 100644 index 00000000..fb80b674 --- /dev/null +++ b/server/migrations/20230720.go @@ -0,0 +1,80 @@ +package migrations + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" + "mayfly-go/internal/sys/api" + "mayfly-go/internal/sys/domain/entity" + "mayfly-go/pkg/model" + "time" +) + +// T20230720 三方登录表 +func T20230720() *gormigrate.Migration { + return &gormigrate.Migration{ + ID: "20230319", + Migrate: func(tx *gorm.DB) error { + // 添加路由权限 + res := &entity.Resource{ + Model: model.Model{ + DeletedModel: model.DeletedModel{Id: 133}, + }, + Pid: 4, + UiPath: "sys/auth", + Type: 1, + Status: 1, + Code: "system:auth", + Name: "登录认证", + Weight: 10000001, + Meta: "{\"component\":\"system/auth/AuthInfo\"," + + "\"icon\":\"User\",\"isKeepAlive\":true," + + "\"routeName\":\"AuthInfo\"}", + } + if err := insertResource(tx, res); err != nil { + return err + } + res = &entity.Resource{ + Model: model.Model{ + DeletedModel: model.DeletedModel{Id: 134}, + }, + Pid: 133, + UiPath: "sys/auth/base", + Type: 2, + Status: 1, + Code: "system:auth:base", + Name: "基本权限", + Weight: 10000000, + Meta: "null", + } + if err := insertResource(tx, res); err != nil { + return err + } + // 加大params字段长度 + now := time.Now() + if err := tx.AutoMigrate(&entity.Config{}); err != nil { + return err + } + if err := tx.Save(&entity.Config{ + Model: model.Model{ + CreateTime: &now, + CreatorId: 1, + Creator: "admin", + UpdateTime: &now, + ModifierId: 1, + Modifier: "admin", + }, + Name: api.AuthOAuth2Name, + Key: api.AuthOAuth2Key, + Params: api.AuthOAuth2Param, + Value: "{}", + Remark: api.AuthOAuth2Remark, + }).Error; err != nil { + return err + } + return tx.AutoMigrate(&entity.OAuthAccount{}) + }, + Rollback: func(tx *gorm.DB) error { + return nil + }, + } +} diff --git a/server/migrations/init.go b/server/migrations/init.go new file mode 100644 index 00000000..02e5361e --- /dev/null +++ b/server/migrations/init.go @@ -0,0 +1,68 @@ +package migrations + +import ( + "context" + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" + "mayfly-go/internal/sys/domain/entity" + "mayfly-go/pkg/model" + "mayfly-go/pkg/rediscli" + "time" +) + +// RunMigrations 数据库迁移操作 +func RunMigrations(db *gorm.DB) error { + // 添加分布式锁, 防止多个服务同时执行迁移 + if rediscli.GetCli() != nil { + if ok, err := rediscli.GetCli(). + SetNX(context.Background(), "migrations", "lock", time.Minute).Result(); err != nil { + return err + } else if !ok { + return nil + } + defer rediscli.Del("migrations") + } + return run(db, + T2022, + T20230720, + ) +} + +func run(db *gorm.DB, fs ...func() *gormigrate.Migration) error { + var ms []*gormigrate.Migration + for _, f := range fs { + ms = append(ms, f()) + } + m := gormigrate.New(db, &gormigrate.Options{ + TableName: "migrations", + IDColumnName: "id", + IDColumnSize: 200, + UseTransaction: true, + ValidateUnknownMigrations: true, + }, ms) + if err := m.Migrate(); err != nil { + return err + } + return nil +} + +func insertResource(tx *gorm.DB, res *entity.Resource) error { + now := time.Now() + res.CreateTime = &now + res.CreatorId = 1 + res.Creator = "admin" + res.UpdateTime = &now + res.ModifierId = 1 + res.Modifier = "admin" + if err := tx.Save(res).Error; err != nil { + return err + } + return tx.Save(&entity.RoleResource{ + DeletedModel: model.DeletedModel{}, + RoleId: 1, + ResourceId: res.Id, + CreateTime: &now, + CreatorId: 1, + Creator: "admin", + }).Error +} diff --git a/server/pkg/gormx/gormx.go b/server/pkg/gormx/gormx.go index ec57fb3b..92eff8cb 100644 --- a/server/pkg/gormx/gormx.go +++ b/server/pkg/gormx/gormx.go @@ -35,7 +35,7 @@ func GetByIdIn(model any, list any, ids []uint64, orderBy ...string) { // 若 error不为nil,则为不存在该记录 // @param model 数据库映射实体模型 func GetBy(model any, cols ...string) error { - return global.Db.Select(cols).Where(model).Scopes(UndeleteScope).First(model).Error + return global.Db.Debug().Select(cols).Where(model).Scopes(UndeleteScope).First(model).Error } // 根据model指定条件统计数量 diff --git a/server/pkg/model/model.go b/server/pkg/model/model.go index aea99702..8744c32b 100644 --- a/server/pkg/model/model.go +++ b/server/pkg/model/model.go @@ -15,7 +15,7 @@ const ( // 含有删除字段模型 type DeletedModel struct { Id uint64 `json:"id"` - IsDeleted int8 `json:"-"` + IsDeleted int8 `json:"-" gorm:"column:is_deleted;default:0"` DeleteTime *time.Time `json:"-"` } diff --git a/server/pkg/starter/run.go b/server/pkg/starter/run.go index 15e80789..9011769a 100644 --- a/server/pkg/starter/run.go +++ b/server/pkg/starter/run.go @@ -1,8 +1,10 @@ package starter import ( + "mayfly-go/migrations" "mayfly-go/initialize" "mayfly-go/pkg/config" + "mayfly-go/pkg/global" "mayfly-go/pkg/logger" "mayfly-go/pkg/req" ) @@ -25,6 +27,10 @@ func RunWebServer() { // 有配置redis信息,则初始化redis。多台机器部署需要使用redis存储验证码、权限、公私钥等 initRedis() + // 数据库升级操作 + if err := migrations.RunMigrations(global.Db); err != nil { + logger.Log.Fatalf("数据库升级失败: %v", err) + } // 初始化其他需要启动时运行的方法 initialize.InitOther()