Browse Source

feat: 同步用户时,移除创建用户逻辑 (#3442)

pull/3447/head
ssongliu 11 months ago committed by GitHub
parent
commit
33484a9436
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      backend/app/api/v1/database_mysql.go
  2. 8
      backend/app/dto/database.go
  3. 37
      backend/app/service/database_mysql.go
  4. 1
      backend/router/ro_database.go
  5. 1
      backend/utils/mysql/client.go
  6. 24
      backend/utils/mysql/client/info.go
  7. 13
      backend/utils/mysql/client/local.go
  8. 13
      backend/utils/mysql/client/remote.go
  9. 72
      cmd/server/docs/docs.go
  10. 72
      cmd/server/docs/swagger.json
  11. 49
      cmd/server/docs/swagger.yaml
  12. 7
      frontend/src/api/interface/database.ts
  13. 7
      frontend/src/api/modules/database.ts
  14. 1
      frontend/src/lang/modules/en.ts
  15. 1
      frontend/src/lang/modules/tw.ts
  16. 1
      frontend/src/lang/modules/zh.ts
  17. 139
      frontend/src/views/database/mysql/bind/index.vue
  18. 40
      frontend/src/views/database/mysql/index.vue

31
backend/app/api/v1/database_mysql.go

@ -41,6 +41,37 @@ func (b *BaseApi) CreateMysql(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Bind user of mysql database
// @Description 绑定 mysql 数据库用户
// @Accept json
// @Param request body dto.BindUser true "request"
// @Success 200
// @Security ApiKeyAuth
// @Router /databases/bind [post]
// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"绑定 mysql 数据库名 [database] [username]","formatEN":"bind mysql database [database] [username]"}
func (b *BaseApi) BindUser(c *gin.Context) {
var req dto.BindUser
if err := helper.CheckBindAndValidate(&req, c); err != nil {
return
}
if len(req.Password) != 0 {
password, err := base64.StdEncoding.DecodeString(req.Password)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err)
return
}
req.Password = string(password)
}
if err := mysqlService.BindUser(req); err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
helper.SuccessWithData(c, nil)
}
// @Tags Database Mysql
// @Summary Update mysql database description
// @Description 更新 mysql 数据库库描述信息

8
backend/app/dto/database.go

@ -43,6 +43,14 @@ type MysqlDBCreate struct {
Description string `json:"description"`
}
type BindUser struct {
Database string `json:"database" validate:"required"`
DB string `json:"db" validate:"required"`
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Permission string `json:"permission" validate:"required"`
}
type MysqlLoadDB struct {
From string `json:"from" validate:"required,oneof=local remote"`
Type string `json:"type" validate:"required,oneof=mysql mariadb"`

37
backend/app/service/database_mysql.go

@ -35,6 +35,7 @@ type IMysqlService interface {
SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error)
ListDBOption() ([]dto.MysqlOption, error)
Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error)
BindUser(req dto.BindUser) error
LoadFromRemote(req dto.MysqlLoadDB) error
ChangeAccess(info dto.ChangeDBInfo) error
ChangePassword(info dto.ChangeDBInfo) error
@ -144,6 +145,42 @@ func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*mode
return &createItem, nil
}
func (u *MysqlService) BindUser(req dto.BindUser) error {
dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(req.Database), commonRepo.WithByName(req.DB))
if err != nil {
return err
}
cli, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {
return err
}
defer cli.Close()
if err := cli.CreateUser(client.CreateInfo{
Name: dbItem.Name,
Format: dbItem.Format,
Username: req.Username,
Password: req.Password,
Permission: req.Permission,
Version: version,
Timeout: 300,
}, false); err != nil {
return err
}
pass, err := encrypt.StringEncrypt(req.Password)
if err != nil {
return fmt.Errorf("decrypt database db password failed, err: %v", err)
}
if err := mysqlRepo.Update(dbItem.ID, map[string]interface{}{
"username": req.Username,
"password": pass,
"permission": req.Permission,
}); err != nil {
return err
}
return nil
}
func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error {
client, version, err := LoadMysqlClientByFrom(req.Database)
if err != nil {

1
backend/router/ro_database.go

@ -17,6 +17,7 @@ func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) {
baseApi := v1.ApiGroupApp.BaseApi
{
cmdRouter.POST("", baseApi.CreateMysql)
cmdRouter.POST("/bind", baseApi.BindUser)
cmdRouter.POST("load", baseApi.LoadDBFromRemote)
cmdRouter.POST("/change/access", baseApi.ChangeMysqlAccess)
cmdRouter.POST("/change/password", baseApi.ChangeMysqlPassword)

1
backend/utils/mysql/client.go

@ -14,6 +14,7 @@ import (
type MysqlClient interface {
Create(info client.CreateInfo) error
CreateUser(info client.CreateInfo, withDeleteDB bool) error
Delete(info client.DeleteInfo) error
ChangePassword(info client.PasswordChangeInfo) error

24
backend/utils/mysql/client/info.go

@ -4,9 +4,7 @@ import (
"crypto/tls"
"crypto/x509"
"errors"
"strings"
"github.com/1Panel-dev/1Panel/backend/utils/common"
"github.com/go-sql-driver/mysql"
)
@ -103,28 +101,6 @@ var formatMap = map[string]string{
"big5": "big5_chinese_ci",
}
func loadNameByDB(name, version string) string {
nameItem := common.ConvertToPinyin(name)
if strings.HasPrefix(version, "5.6") {
if len(nameItem) <= 16 {
return nameItem
}
return strings.TrimSuffix(nameItem[:10], "_") + "_" + common.RandStr(5)
}
if len(nameItem) <= 32 {
return nameItem
}
return strings.TrimSuffix(nameItem[:25], "_") + "_" + common.RandStr(5)
}
func randomPassword(user string) string {
passwdItem := user
if len(user) > 6 {
passwdItem = user[:6]
}
return passwdItem + "@" + common.RandStrAndNum(8)
}
func VerifyPeerCertFunc(pool *x509.CertPool) func([][]byte, [][]*x509.Certificate) error {
return func(rawCerts [][]byte, _ [][]*x509.Certificate) error {
if len(rawCerts) == 0 {

13
backend/utils/mysql/client/local.go

@ -307,19 +307,6 @@ func (r *Local) SyncDB(version string) ([]SyncDBInfo, error) {
}
}
if len(dataItem.Username) == 0 {
dataItem.Username = loadNameByDB(parts[0], version)
dataItem.Password = randomPassword(dataItem.Username)
if err := r.CreateUser(CreateInfo{
Name: parts[0],
Format: parts[1],
Version: version,
Username: dataItem.Username,
Password: dataItem.Password,
Permission: "%",
Timeout: 300,
}, false); err != nil {
global.LOG.Errorf("sync from remote server failed, err: create user failed %v", err)
}
dataItem.Permission = "%"
} else {
if isLocal {

13
backend/utils/mysql/client/remote.go

@ -333,19 +333,6 @@ func (r *Remote) SyncDB(version string) ([]SyncDBInfo, error) {
i++
}
if len(dataItem.Username) == 0 {
dataItem.Username = loadNameByDB(dbName, version)
dataItem.Password = randomPassword(dataItem.Username)
if err := r.CreateUser(CreateInfo{
Name: dbName,
Format: charsetName,
Version: version,
Username: dataItem.Username,
Password: dataItem.Password,
Permission: "%",
Timeout: 300,
}, false); err != nil {
return datas, fmt.Errorf("sync db from remote server failed, err: create user failed %v", err)
}
dataItem.Permission = "%"
} else {
if isLocal {

72
cmd/server/docs/docs.go

@ -3952,6 +3952,49 @@ const docTemplate = `{
}
}
},
"/databases/bind": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "绑定 mysql 数据库用户",
"consumes": [
"application/json"
],
"tags": [
"Database Mysql"
],
"summary": "Bind user of mysql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.BindUser"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"database",
"username"
],
"formatEN": "bind mysql database [database] [username]",
"formatZH": "绑定 mysql 数据库名 [database] [username]",
"paramKeys": []
}
}
},
"/databases/change/access": {
"post": {
"security": [
@ -10013,7 +10056,7 @@ const docTemplate = `{
"bodyKeys": [
"version"
],
"formatEN": "upgrade service =\u003e [version]",
"formatEN": "upgrade system =\u003e [version]",
"formatZH": "更新系统 =\u003e [version]",
"paramKeys": []
}
@ -13601,6 +13644,33 @@ const docTemplate = `{
}
}
},
"dto.BindUser": {
"type": "object",
"required": [
"database",
"db",
"password",
"permission",
"username"
],
"properties": {
"database": {
"type": "string"
},
"db": {
"type": "string"
},
"password": {
"type": "string"
},
"permission": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"dto.CaptchaResponse": {
"type": "object",
"properties": {

72
cmd/server/docs/swagger.json

@ -3945,6 +3945,49 @@
}
}
},
"/databases/bind": {
"post": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "绑定 mysql 数据库用户",
"consumes": [
"application/json"
],
"tags": [
"Database Mysql"
],
"summary": "Bind user of mysql database",
"parameters": [
{
"description": "request",
"name": "request",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/dto.BindUser"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"x-panel-log": {
"BeforeFunctions": [],
"bodyKeys": [
"database",
"username"
],
"formatEN": "bind mysql database [database] [username]",
"formatZH": "绑定 mysql 数据库名 [database] [username]",
"paramKeys": []
}
}
},
"/databases/change/access": {
"post": {
"security": [
@ -10006,7 +10049,7 @@
"bodyKeys": [
"version"
],
"formatEN": "upgrade service =\u003e [version]",
"formatEN": "upgrade system =\u003e [version]",
"formatZH": "更新系统 =\u003e [version]",
"paramKeys": []
}
@ -13594,6 +13637,33 @@
}
}
},
"dto.BindUser": {
"type": "object",
"required": [
"database",
"db",
"password",
"permission",
"username"
],
"properties": {
"database": {
"type": "string"
},
"db": {
"type": "string"
},
"password": {
"type": "string"
},
"permission": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"dto.CaptchaResponse": {
"type": "object",
"properties": {

49
cmd/server/docs/swagger.yaml

@ -137,6 +137,25 @@ definitions:
- bindAddress
- ipv6
type: object
dto.BindUser:
properties:
database:
type: string
db:
type: string
password:
type: string
permission:
type: string
username:
type: string
required:
- database
- db
- password
- permission
- username
type: object
dto.CaptchaResponse:
properties:
captchaID:
@ -7268,6 +7287,34 @@ paths:
summary: Load mysql base info
tags:
- Database Mysql
/databases/bind:
post:
consumes:
- application/json
description: 绑定 mysql 数据库用户
parameters:
- description: request
in: body
name: request
required: true
schema:
$ref: '#/definitions/dto.BindUser'
responses:
"200":
description: OK
security:
- ApiKeyAuth: []
summary: Bind user of mysql database
tags:
- Database Mysql
x-panel-log:
BeforeFunctions: []
bodyKeys:
- database
- username
formatEN: bind mysql database [database] [username]
formatZH: 绑定 mysql 数据库名 [database] [username]
paramKeys: []
/databases/change/access:
post:
consumes:
@ -11106,7 +11153,7 @@ paths:
BeforeFunctions: []
bodyKeys:
- version
formatEN: upgrade service => [version]
formatEN: upgrade system => [version]
formatZH: 更新系统 => [version]
paramKeys: []
/toolbox/clean:

7
frontend/src/api/interface/database.ts

@ -48,6 +48,13 @@ export namespace Database {
permission: string;
description: string;
}
export interface BindUser {
database: string;
db: string;
username: string;
password: string;
permission: string;
}
export interface MysqlLoadDB {
from: string;
type: string;

7
frontend/src/api/modules/database.ts

@ -19,6 +19,13 @@ export const addMysqlDB = (params: Database.MysqlDBCreate) => {
}
return http.post(`/databases`, request);
};
export const bindUser = (params: Database.BindUser) => {
let request = deepCopy(params) as Database.BindUser;
if (request.password) {
request.password = Base64.encode(request.password);
}
return http.post(`/databases/bind`, request);
};
export const loadDBFromRemote = (params: Database.MysqlLoadDB) => {
return http.post(`/databases/load`, params);
};

1
frontend/src/lang/modules/en.ts

@ -380,6 +380,7 @@ const message = {
'This port is the exposed port of the container. You need to save the modification separately and restart the container!',
loadFromRemote: 'Load from server',
userBind: 'Bind User',
loadFromRemoteHelper:
'This action will synchronize the database info on the server to 1Panel, do you want to continue?',
passwordHelper: 'Unable to retrieve, please modify',

1
frontend/src/lang/modules/tw.ts

@ -373,6 +373,7 @@ const message = {
confNotFound: '未能找到該應用配置文件請在應用商店升級該應用至最新版本後重試',
loadFromRemote: '從服務器獲取',
userBind: '綁定使用者',
loadFromRemoteHelper: '此操作將同步服務器上數據庫信息到 1Panel是否繼續',
passwordHelper: '無法獲取密碼請修改',
local: '本地',

1
frontend/src/lang/modules/zh.ts

@ -373,6 +373,7 @@ const message = {
confNotFound: '未能找到该应用配置文件请在应用商店升级该应用至最新版本后重试',
loadFromRemote: '从服务器获取',
userBind: '绑定用户',
loadFromRemoteHelper: '此操作将同步服务器上数据库信息到 1Panel是否继续',
passwordHelper: '无法获取密码请修改',
local: '本地',

139
frontend/src/views/database/mysql/bind/index.vue

@ -0,0 +1,139 @@
<template>
<div>
<el-drawer v-model="bindVisible" :destroy-on-close="true" :close-on-click-modal="false" width="30%">
<template #header>
<DrawerHeader :header="$t('database.userBind')" :resource="form.mysqlName" :back="handleClose" />
</template>
<el-form v-loading="loading" ref="changeFormRef" :model="form" :rules="rules" label-position="top">
<el-row type="flex" justify="center">
<el-col :span="22">
<el-form-item :label="$t('commons.login.username')" prop="username">
<el-input v-model="form.username"></el-input>
</el-form-item>
<el-form-item :label="$t('commons.login.password')" prop="password">
<el-input type="password" clearable show-password v-model="form.password"></el-input>
</el-form-item>
<el-form-item :label="$t('database.permission')" prop="permission">
<el-select v-model="form.permission">
<el-option value="%" :label="$t('database.permissionAll')" />
<el-option
v-if="form.from !== 'local'"
value="localhost"
:label="$t('terminal.localhost')"
/>
<el-option value="ip" :label="$t('database.permissionForIP')" />
</el-select>
</el-form-item>
<el-form-item v-if="form.permission === 'ip'" prop="permissionIPs">
<el-input
clearable
:autosize="{ minRows: 2, maxRows: 5 }"
type="textarea"
v-model="form.permissionIPs"
/>
<span class="input-help">{{ $t('database.remoteHelper') }}</span>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="loading" @click="bindVisible = false">
{{ $t('commons.button.cancel') }}
</el-button>
<el-button :disabled="loading" type="primary" @click="onSubmit(changeFormRef)">
{{ $t('commons.button.confirm') }}
</el-button>
</span>
</template>
</el-drawer>
<ConfirmDialog ref="confirmDialogRef" @confirm="onSubmit"></ConfirmDialog>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import i18n from '@/lang';
import { ElForm } from 'element-plus';
import { bindUser } from '@/api/modules/database';
import DrawerHeader from '@/components/drawer-header/index.vue';
import { Rules } from '@/global/form-rules';
import { MsgSuccess } from '@/utils/message';
import { checkIp } from '@/utils/util';
const loading = ref();
const bindVisible = ref(false);
type FormInstance = InstanceType<typeof ElForm>;
const changeFormRef = ref<FormInstance>();
const form = reactive({
database: '',
mysqlName: '',
username: '',
password: '',
permission: '',
permissionIPs: '',
});
const confirmDialogRef = ref();
const rules = reactive({
username: [Rules.requiredInput, Rules.name],
password: [Rules.paramComplexity],
permission: [Rules.requiredSelect],
permissionIPs: [{ validator: checkIPs, trigger: 'blur', required: true }],
});
function checkIPs(rule: any, value: any, callback: any) {
let ips = form.permissionIPs.split(',');
for (const item of ips) {
if (checkIp(item)) {
return callback(new Error(i18n.global.t('commons.rule.ip')));
}
}
callback();
}
interface DialogProps {
database: string;
mysqlName: string;
}
const acceptParams = (params: DialogProps): void => {
form.id = params.id;
form.database = params.database;
form.mysqlName = params.mysqlName;
bindVisible.value = true;
};
const emit = defineEmits<{ (e: 'search'): void }>();
const handleClose = () => {
bindVisible.value = false;
};
const onSubmit = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async (valid) => {
if (!valid) return;
let param = {
database: form.database,
db: form.mysqlName,
username: form.username,
password: form.password,
permission: form.permission === 'ip' ? form.permissionIPs : form.permission,
};
loading.value = true;
await bindUser(param)
.then(() => {
loading.value = false;
emit('search');
bindVisible.value = false;
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
})
.catch(() => {
loading.value = false;
});
});
};
defineExpose({
acceptParams,
});
</script>

40
frontend/src/views/database/mysql/index.vue

@ -124,10 +124,24 @@
<template #main v-if="currentDB">
<ComplexTable :pagination-config="paginationConfig" @sort-change="search" @search="search" :data="data">
<el-table-column :label="$t('commons.table.name')" prop="name" sortable />
<el-table-column :label="$t('commons.login.username')" prop="username" />
<el-table-column :label="$t('commons.login.username')" prop="username">
<template #default="{ row }">
<div class="flex items-center" v-if="row.username">
<span>
{{ row.username }}
</span>
</div>
<div v-else>
<el-button style="margin-left: -3px" type="primary" link @click="onBind(row)">
{{ $t('database.userBind') }}
</el-button>
</div>
</template>
</el-table-column>
<el-table-column :label="$t('commons.login.password')" prop="password">
<template #default="{ row }">
<div class="flex items-center" v-if="row.password">
<span v-if="row.username === ''">-</span>
<div class="flex items-center" v-if="row.password && row.username">
<div class="star-center" v-if="!row.showPassword">
<span>**********</span>
</div>
@ -154,10 +168,10 @@
<CopyButton :content="row.password" type="icon" />
</div>
</div>
<div v-else>
<el-link @click="onChangePassword(row)">
<span style="font-size: 12px">{{ $t('database.passwordHelper') }}</span>
</el-link>
<div v-if="row.password === '' && row.username">
<el-button style="margin-left: -3px" link type="primary" @click="onChangePassword(row)">
{{ $t('database.passwordHelper') }}
</el-button>
</div>
</template>
</el-table-column>
@ -221,6 +235,7 @@
</template>
</el-dialog>
<BindDialog ref="bindRef" @search="search" />
<PasswordDialog ref="passwordRef" @search="search" />
<RootPasswordDialog ref="connRef" />
<UploadDialog ref="uploadRef" />
@ -235,6 +250,7 @@
</template>
<script lang="ts" setup>
import BindDialog from '@/views/database/mysql/bind/index.vue';
import OperateDialog from '@/views/database/mysql/create/index.vue';
import DeleteDialog from '@/views/database/mysql/delete/index.vue';
import PasswordDialog from '@/views/database/mysql/password/index.vue';
@ -274,6 +290,7 @@ const dbOptionsRemote = ref<Array<Database.DatabaseOption>>([]);
const currentDB = ref<Database.DatabaseOption>();
const currentDBName = ref();
const bindRef = ref();
const checkRef = ref();
const deleteRef = ref();
@ -508,6 +525,14 @@ const onDelete = async (row: Database.MysqlDBInfo) => {
}
};
const onBind = async (row: Database.MysqlDBInfo) => {
let param = {
database: currentDBName.value,
mysqlName: row.name,
};
bindRef.value.acceptParams(param);
};
const onChangePassword = async (row: Database.MysqlDBInfo) => {
let param = {
id: row.id,
@ -525,6 +550,9 @@ const onChangePassword = async (row: Database.MysqlDBInfo) => {
const buttons = [
{
label: i18n.global.t('database.changePassword'),
disabled: (row: Database.MysqlDBInfo) => {
return !row.username;
},
click: (row: Database.MysqlDBInfo) => {
onChangePassword(row);
},

Loading…
Cancel
Save