feat: 菜单权限获取方式调整、前端代码优化、数据库新增数据方式调整

This commit is contained in:
meilin.huang
2023-04-13 20:11:22 +08:00
parent 8e64ba67fa
commit 1d858118d5
22 changed files with 329 additions and 233 deletions

View File

@@ -28,7 +28,7 @@ class Api {
/**
* 操作该权限即请求对应的url
* @param {Object} param 请求该权限的参数
* @param {Object} param 请求该api的参数
*/
request(param: any = null, options: any = null): Promise<any> {
return request.send(this, param, options);
@@ -36,7 +36,8 @@ class Api {
/**
* 操作该权限即请求对应的url
* @param {Object} param 请求该权限的参数
* @param {Object} param 请求该api的参数
* @param headers headers
*/
requestWithHeaders(param: any, headers: any): Promise<any> {
return request.sendWithHeaders(this, param, headers);
@@ -50,9 +51,41 @@ class Api {
* @param url url
* @param method 请求方法(get,post,put,delete...)
*/
static create(url: string, method: string) {
static create(url: string, method: string) :Api {
return new Api(url, method);
}
/**
* 创建get api
* @param url url
*/
static newGet(url: string): Api {
return Api.create(url, 'get');
}
/**
* new post api
* @param url url
*/
static newPost(url: string): Api {
return Api.create(url, 'post');
}
/**
* new put api
* @param url url
*/
static newPut(url: string): Api {
return Api.create(url, 'put');
}
/**
* new delete api
* @param url url
*/
static newDelete(url: string): Api {
return Api.create(url, 'delete');
}
}

View File

@@ -1,11 +1,11 @@
import request from './request'
import Api from './Api'
export default {
login: (param: any) => request.request('POST', '/sys/accounts/login', param),
changePwd: (param: any) => request.request('POST', '/sys/accounts/change-pwd', param),
getPublicKey: () => request.request('GET', '/common/public-key'),
getConfigValue: (param: any) => request.request('GET', '/sys/configs/value', param),
captcha: () => request.request('GET', '/sys/captcha'),
logout: (param: any) => request.request('POST', '/sys/accounts/logout/{token}', param),
getMenuRoute: (param: any) => request.request('Get', '/sys/resources/account', param)
login: Api.newPost("/sys/accounts/login"),
changePwd: Api.newPost("/sys/accounts/change-pwd"),
getPublicKey: Api.newGet("/common/public-key"),
getConfigValue: Api.newGet("/sys/configs/value"),
captcha: Api.newGet("/sys/captcha"),
logout: Api.newPost("/sys/accounts/logout/{token}"),
getPermissions: Api.newGet("/sys/accounts/permissions")
}

View File

@@ -22,7 +22,8 @@ export interface Result {
data?: any;
}
const baseUrl: string = config.baseApiUrl as string
const baseUrl: string = config.baseApiUrl
const baseWsUrl: string = config.baseWsUrl
/**
* 通知错误消息
@@ -115,9 +116,8 @@ function request(method: string, url: string, params: any = null, headers: any =
query.headers = headers
}
const lowMethod = method.toLowerCase();
// post和put使用json格式传参
if (lowMethod === 'post' || lowMethod === 'put') {
if (method === 'post' || method === 'put') {
query.data = params;
} else {
query.params = params;
@@ -155,6 +155,7 @@ function getApiUrl(url: string) {
return baseUrl + url + '?token=' + getSession('token');
}
export default {
request,
send,

View File

@@ -9,7 +9,7 @@ export async function getRsaPublicKey() {
if (publicKey) {
return publicKey
}
publicKey = await openApi.getPublicKey() as string
publicKey = await openApi.getPublicKey.request() as string
sessionStorage.setItem('RsaPublicKey', publicKey)
return publicKey
}

View File

@@ -11,7 +11,7 @@ const UseWartermarkConfigKey = "UseWartermark"
* @returns 配置值
*/
export async function getConfigValue(key: string) : Promise<string> {
return await openApi.getConfigValue({key}) as string
return await openApi.getConfigValue.request({key}) as string
}
/**

View File

@@ -46,8 +46,8 @@ export function initAllFun() {
useRoutesList().setRoutesList(setFilterMenuFun(dynamicRoutes[0].children, useUserInfo().userInfo.menus))
}
// 后端控制路由:模拟执行路由数据初始化
export function initBackEndControlRoutesFun() {
// 后端控制路由:执行路由数据初始化
export async function initBackEndControlRoutesFun() {
NextLoading.start(); // 界面 loading 动画开始执行
const token = getSession('token'); // 获取浏览器缓存 token 值
if (!token) {
@@ -55,11 +55,8 @@ export function initBackEndControlRoutesFun() {
return false
}
useUserInfo().setUserInfo({});
let menuRoute = getSession('menus')
if (!menuRoute) {
menuRoute = getBackEndControlRoutes(); // 获取路由
// const oldRoutes = res; // 获取接口原始路由未处理component
}
// 获取路由
let menuRoute = await getBackEndControlRoutes();
dynamicRoutes[0].children = backEndRouterConverter(menuRoute); // 处理路由component
// 添加404界面
router.addRoute(pathMatch);
@@ -72,8 +69,16 @@ export function initBackEndControlRoutesFun() {
}
// 后端控制路由isRequestRoutes 为 true则开启后端控制路由
export function getBackEndControlRoutes() {
return openApi.getMenuRoute({});
export async function getBackEndControlRoutes() {
try {
const menuAndPermission = await openApi.getPermissions.request();
// 赋值权限码,用于控制按钮等
useUserInfo().userInfo.permissions = menuAndPermission.permissions;
return menuAndPermission.menus;
} catch (e: any) {
console.error(e);
return []
}
}
// 后端控制路由,后端返回路由 转换为vue route
@@ -124,18 +129,18 @@ export function backEndRouterConverter(routes: any, parentPath: string = "/") {
* @returns 返回处理成函数后的 component
*/
export function dynamicImport(dynamicViewsModules: Record<string, Function>, component: string) {
const keys = Object.keys(dynamicViewsModules);
const matchKeys = keys.filter((key) => {
const k = key.replace(/..\/views|../, '');
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicViewsModules[matchKey];
}
if (matchKeys?.length > 1) {
return false;
}
const keys = Object.keys(dynamicViewsModules);
const matchKeys = keys.filter((key) => {
const k = key.replace(/..\/views|../, '');
return k.startsWith(`${component}`) || k.startsWith(`/${component}`);
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicViewsModules[matchKey];
}
if (matchKeys?.length > 1) {
return false;
}
}
// 多级嵌套数组处理成一维数组
@@ -167,7 +172,6 @@ export function formatTwoStageRoutes(arr: any) {
}
});
useKeepALiveNames().setCacheKeepAlive(cacheList);
// store.dispatch('keepAliveNames/setCacheKeepAlive', cacheList);
return newArr;
}
@@ -227,21 +231,22 @@ export function resetRoute() {
});
}
// 初始化方法执行
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
if (!isRequestRoutes) {
// 未开启后端控制路由
initAllFun();
} else if (isRequestRoutes) {
// 后端控制路由isRequestRoutes 为 true则开启后端控制路由
initBackEndControlRoutesFun();
export async function initRouter() {
// 初始化方法执行
const { isRequestRoutes } = useThemeConfig(pinia).themeConfig;
if (!isRequestRoutes) {
// 未开启后端控制路由
initAllFun();
} else if (isRequestRoutes) {
// 后端控制路由isRequestRoutes 为 true则开启后端控制路由
await initBackEndControlRoutesFun();
}
}
let SysWs: any;
// 路由加载前
router.beforeEach((to, from, next) => {
router.beforeEach(async (to, from, next) => {
NProgress.configure({ showSpinner: false });
if (to.meta.title) NProgress.start();
@@ -278,7 +283,10 @@ router.beforeEach((to, from, next) => {
if (!SysWs && to.path != '/machine/terminal') {
SysWs = sockets.sysMsgSocket();
}
if (useRoutesList().routesList.length > 0) {
if (useRoutesList().routesList.length == 0) {
await initRouter();
next({ path: to.path, query: to.query });
} else {
next();
}
});

View File

@@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import { getSession } from '@/common/utils/storage.ts';
import { getSession } from '@/common/utils/storage';
export const useUserInfo = defineStore('userInfo', {
state: (): UserInfoState => ({

View File

@@ -1,6 +1,6 @@
import Api from '@/common/Api';
export const indexApi = {
getIndexCount: Api.create("/common/index/count", 'get'),
getIndexCount: Api.newGet("/common/index/count"),
}

View File

@@ -63,7 +63,7 @@
import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { initBackEndControlRoutesFun } from '@/router/index';
import { initRouter } from '@/router/index';
import { setSession, setUserInfo2Session, setUseWatermark2Session } from '@/common/utils/storage';
import { formatAxis } from '@/common/utils/format';
import openApi from '@/common/openApi';
@@ -71,7 +71,6 @@ import { RsaEncrypt } from '@/common/rsa';
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
import { letterAvatar } from '@/common/utils/string';
import { useUserInfo } from '@/store/userInfo';
import { useThemeConfig } from '@/store/themeConfig';
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
@@ -138,7 +137,7 @@ const getCaptcha = async () => {
if (!state.isUseLoginCaptcha) {
return;
}
let res: any = await openApi.captcha();
let res: any = await openApi.captcha.request();
state.captchaImage = res.base64Captcha;
state.loginForm.cid = res.cid;
};
@@ -167,10 +166,9 @@ const onSignIn = async () => {
try {
const loginReq = { ...state.loginForm };
loginReq.password = await RsaEncrypt(originPwd);
loginRes = await openApi.login(loginReq);
loginRes = await openApi.login.request(loginReq);
// 存储 token 到浏览器缓存
setSession('token', loginRes.token);
setSession('menus', loginRes.menus);
} catch (e: any) {
state.loading.signIn = false;
state.loginForm.captcha = '';
@@ -192,8 +190,6 @@ const onSignIn = async () => {
// 头像
photo: letterAvatar(state.loginForm.username),
time: new Date().getTime(),
// // 菜单资源code数组
// menus: loginRes.menus,
permissions: loginRes.permissions,
lastLoginTime: loginRes.lastLoginTime,
lastLoginIp: loginRes.lastLoginIp,
@@ -202,20 +198,9 @@ const onSignIn = async () => {
// 存储用户信息到浏览器缓存
setUserInfo2Session(userInfos);
// 1、请注意执行顺序(存储用户信息到vuex)
useUserInfo().setUserInfo(userInfos)
// store.dispatch('userInfos/setUserInfos', userInfos);
if (!useThemeConfig().themeConfig.isRequestRoutes) {
// 前端控制路由2、请注意执行顺序
// await initAllFun();
await initBackEndControlRoutesFun();
signInSuccess();
} else {
// 模拟后端控制路由isRequestRoutes 为 true则开启后端控制路由
// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
await initBackEndControlRoutesFun();
// 执行完 initBackEndControlRoutesFun再执行 signInSuccess
signInSuccess();
}
useUserInfo().setUserInfo(userInfos);
await initRouter();
signInSuccess();
};
// 登录成功后的跳转
@@ -248,7 +233,7 @@ const changePwd = () => {
const changePwdReq: any = { ...form };
changePwdReq.oldPassword = await RsaEncrypt(form.oldPassword);
changePwdReq.newPassword = await RsaEncrypt(form.newPassword);
await openApi.changePwd(changePwdReq);
await openApi.changePwd.request(changePwdReq);
ElMessage.success('密码修改成功, 新密码已填充至登录密码框');
state.loginForm.password = state.changePwdDialog.form.newPassword;
state.changePwdDialog.visible = false;

View File

@@ -135,7 +135,7 @@ const state = reactive({
tabs,
dataTabsTableHeight: '600',
editorHeight: '600',
tagTreeHeight: window.innerHeight - 173 + 'px',
tagTreeHeight: window.innerHeight - 178 + 'px',
genSqlDialog: {
visible: false,
sql: '',

View File

@@ -2,27 +2,27 @@ import Api from '@/common/Api';
export const dbApi = {
// 获取权限列表
dbs: Api.create("/dbs", 'get'),
saveDb: Api.create("/dbs", 'post'),
getAllDatabase: Api.create("/dbs/databases", 'post'),
getDbPwd: Api.create("/dbs/{id}/pwd", 'get'),
deleteDb: Api.create("/dbs/{id}", 'delete'),
dumpDb: Api.create("/dbs/{id}/dump", 'post'),
tableInfos: Api.create("/dbs/{id}/t-infos", 'get'),
tableIndex: Api.create("/dbs/{id}/t-index", 'get'),
tableDdl: Api.create("/dbs/{id}/t-create-ddl", 'get'),
tableMetadata: Api.create("/dbs/{id}/t-metadata", 'get'),
columnMetadata: Api.create("/dbs/{id}/c-metadata", 'get'),
dbs: Api.newGet("/dbs"),
saveDb: Api.newPost("/dbs"),
getAllDatabase: Api.newPost("/dbs/databases"),
getDbPwd: Api.newGet("/dbs/{id}/pwd"),
deleteDb: Api.newDelete("/dbs/{id}"),
dumpDb: Api.newPost("/dbs/{id}/dump"),
tableInfos: Api.newGet("/dbs/{id}/t-infos"),
tableIndex: Api.newGet("/dbs/{id}/t-index"),
tableDdl: Api.newGet("/dbs/{id}/t-create-ddl"),
tableMetadata: Api.newGet("/dbs/{id}/t-metadata"),
columnMetadata: Api.newGet("/dbs/{id}/c-metadata"),
// 获取表即列提示
hintTables: Api.create("/dbs/{id}/hint-tables", 'get'),
sqlExec: Api.create("/dbs/{id}/exec-sql", 'post'),
hintTables: Api.newGet("/dbs/{id}/hint-tables"),
sqlExec: Api.newPost("/dbs/{id}/exec-sql"),
// 保存sql
saveSql: Api.create("/dbs/{id}/sql", 'post'),
saveSql: Api.newPost("/dbs/{id}/sql"),
// 获取保存的sql
getSql: Api.create("/dbs/{id}/sql", 'get'),
getSql: Api.newGet("/dbs/{id}/sql"),
// 获取保存的sql names
getSqlNames: Api.create("/dbs/{id}/sql-names", 'get'),
deleteDbSql: Api.create("/dbs/{id}/sql", 'delete'),
getSqlNames: Api.newGet("/dbs/{id}/sql-names"),
deleteDbSql: Api.newDelete("/dbs/{id}/sql"),
// 获取数据库sql执行记录
getSqlExecs: Api.create("/dbs/{dbId}/sql-execs", 'get'),
getSqlExecs: Api.newGet("/dbs/{dbId}/sql-execs"),
}

View File

@@ -6,7 +6,7 @@
</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="addRow()" type="primary" icon="plus" :underline="false"></el-link>
<el-link @click="onShowAddDataDialog()" type="primary" icon="plus" :underline="false"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="onDeleteData()" type="danger" icon="delete" :underline="false"></el-link>
@@ -37,8 +37,8 @@
</el-tooltip>
</el-col>
<el-col :span="16">
<el-input v-model="condition" placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可" clearable @clear="selectData" size="small"
style="width: 100%">
<el-input v-model="condition" placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可" clearable
@clear="selectData" size="small" style="width: 100%">
<template #prepend>
<el-popover trigger="click" :width="320" placement="right">
<template #reference>
@@ -64,9 +64,9 @@
</el-col>
</el-row>
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="datas"
:table="state.table" :column-names="columnNames" :loading="loading" :height="tableHeight"
:show-column-tip="true" :sortable="'custom'" @sort-change="(sort: any) => onTableSortChange(sort)"
<db-table ref="dbTableRef" :db-id="state.ti.dbId" :db="state.ti.db" :data="datas" :table="state.table"
:column-names="columnNames" :loading="loading" :height="tableHeight" :show-column-tip="true"
:sortable="'custom'" @sort-change="(sort: any) => onTableSortChange(sort)"
@selection-change="onDataSelectionChange" @change-updated-field="changeUpdatedField"></db-table>
<el-row type="flex" class="mt5" justify="center">
@@ -99,6 +99,26 @@
</span>
</template>
</el-dialog>
<el-dialog v-model="addDataDialog.visible" :title="addDataDialog.title" :destroy-on-close="true" width="600px">
<el-form ref="dataForm" :model="addDataDialog.data" label-width="160px">
<el-form-item v-for="column in columns" class="w100" :prop="column.columnName" :label="column.columnName"
:required="column.nullable != 'YES' && column.columnKey != 'PRI'">
<el-input-number v-if="DbInst.isNumber(column.columnType)"
v-model="addDataDialog.data[`${column.columnName}`]"
:placeholder="`${column.columnType} ${column.columnComment}`" class="w100" />
<el-input v-else v-model="addDataDialog.data[`${column.columnName}`]"
:placeholder="`${column.columnType} ${column.columnComment}`" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="closeAddDataDialog">取消</el-button>
<el-button type="primary" @click="addRow">确定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
@@ -113,6 +133,7 @@ import { dateStrFormat } from '@/common/utils/date';
import DbTable from '../DbTable.vue'
const emits = defineEmits(['genInsertSql'])
const dataForm: any = ref(null);
const props = defineProps({
data: {
@@ -136,7 +157,7 @@ const state = reactive({
condition: '', // 当前条件框的条件
loading: false, // 是否在加载数据
columnNames: [],
columns: [],
columns: [] as any,
pageNum: 1,
count: 0,
selectionDatas: [] as any,
@@ -149,6 +170,12 @@ const state = reactive({
condition: '=',
value: null
},
addDataDialog: {
data: {},
title: '',
placeholder: '',
visible: false,
},
tableHeight: '600',
hasUpdatedFileds: false,
});
@@ -163,6 +190,7 @@ const {
count,
hasUpdatedFileds,
conditionDialog,
addDataDialog,
} = toRefs(state);
watch(() => props.tableHeight, (newValue: any) => {
@@ -323,20 +351,43 @@ const cancelUpdateFields = () => {
dbTableRef.value.cancelUpdateFields();
}
const onShowAddDataDialog = async () => {
state.addDataDialog.title = `添加'${state.table}'表数据`
state.addDataDialog.visible = true;
};
const closeAddDataDialog = () => {
state.addDataDialog.visible = false;
state.addDataDialog.data = {};
}
// 添加新数据行
const addRow = async () => {
const columns = state.ti.getNowDb().getColumns(state.table);
// key: 字段名value: 字段名提示
let obj: any = {};
columns.forEach((item: any) => {
obj[`${item.columnName}`] = `'${item.columnComment || ''} ${item.columnName}[${item.columnType}]${item.nullable == 'YES' ? '' : '[not null]'}'`;
});
let columnNames = Object.keys(obj).join(',');
let values = Object.values(obj).join(',');
let sql = `INSERT INTO ${state.table} (${columnNames}) VALUES (${values});`;
state.ti.getNowDbInst().promptExeSql(state.ti.db, sql, null, () => {
onRefresh();
dataForm.value.validate(async (valid: boolean) => {
if (valid) {
const data = state.addDataDialog.data;
// key: 字段名value: 字段名提示
let obj: any = {};
for (let item of state.columns) {
const value = data[item.columnName]
if (!value) {
continue
}
obj[`${item.columnName}`] = DbInst.wrapValueByType(value);
}
let columnNames = Object.keys(obj).join(',');
let values = Object.values(obj).join(',');
let sql = `INSERT INTO ${state.table} (${columnNames}) VALUES (${values});`;
state.ti.getNowDbInst().promptExeSql(state.ti.db, sql, null, () => {
closeAddDataDialog();
onRefresh();
});
} else {
ElMessage.error('请正确填写数据信息');
return false;
}
});
};
</script>

View File

@@ -274,12 +274,21 @@ export class DbInst {
* 根据字段类型包装字段值,如为字符串等则添加‘’,数字类型则直接返回即可
*/
static wrapColumnValue(columnType: string, value: any) {
if (columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi)) {
if (this.isNumber(columnType)) {
return value;
}
return `'${value}'`;
};
/**
* 判断字段类型是否为数字类型
* @param columnType 字段类型
* @returns
*/
static isNumber(columnType: string) {
return columnType.match(/int|double|float|nubmer|decimal|byte|bit/gi);
};
/**
*
* @param str 字符串

View File

@@ -2,45 +2,45 @@ import Api from '@/common/Api';
export const machineApi = {
// 获取权限列表
list: Api.create("/machines", 'get'),
getMachinePwd: Api.create("/machines/{id}/pwd", 'get'),
info: Api.create("/machines/{id}/sysinfo", 'get'),
stats: Api.create("/machines/{id}/stats", 'get'),
process: Api.create("/machines/{id}/process", 'get'),
list: Api.newGet("/machines"),
getMachinePwd: Api.newGet("/machines/{id}/pwd"),
info: Api.newGet("/machines/{id}/sysinfo"),
stats: Api.newGet("/machines/{id}/stats"),
process: Api.newGet("/machines/{id}/process"),
// 终止进程
killProcess: Api.create("/machines/{id}/process", 'delete'),
closeCli: Api.create("/machines/{id}/close-cli", 'delete'),
testConn: Api.create("/machines/test-conn", 'post'),
killProcess: Api.newDelete("/machines/{id}/process"),
closeCli: Api.newDelete("/machines/{id}/close-cli"),
testConn: Api.newPost("/machines/test-conn"),
// 保存按钮
saveMachine: Api.create("/machines", 'post'),
saveMachine: Api.newPost("/machines"),
// 调整状态
changeStatus: Api.create("/machines/{id}/{status}", 'put'),
changeStatus: Api.newPut("/machines/{id}/{status}"),
// 删除机器
del: Api.create("/machines/{id}", 'delete'),
scripts: Api.create("/machines/{machineId}/scripts", 'get'),
runScript: Api.create("/machines/{machineId}/scripts/{scriptId}/run", 'get'),
saveScript: Api.create("/machines/{machineId}/scripts", 'post'),
deleteScript: Api.create("/machines/{machineId}/scripts/{scriptId}", 'delete'),
del: Api.newDelete("/machines/{id}"),
scripts: Api.newGet("/machines/{machineId}/scripts"),
runScript: Api.newGet("/machines/{machineId}/scripts/{scriptId}/run"),
saveScript: Api.newPost("/machines/{machineId}/scripts"),
deleteScript: Api.newDelete("/machines/{machineId}/scripts/{scriptId}"),
// 获取配置文件列表
files: Api.create("/machines/{id}/files", 'get'),
lsFile: Api.create("/machines/{machineId}/files/{fileId}/read-dir", 'get'),
rmFile: Api.create("/machines/{machineId}/files/{fileId}/remove", 'delete'),
uploadFile: Api.create("/machines/{machineId}/files/{fileId}/upload?token={token}", 'post'),
fileContent: Api.create("/machines/{machineId}/files/{fileId}/read", 'get'),
createFile: Api.create("/machines/{machineId}/files/{id}/create-file", 'post'),
files: Api.newGet("/machines/{id}/files"),
lsFile: Api.newGet("/machines/{machineId}/files/{fileId}/read-dir"),
rmFile: Api.newDelete("/machines/{machineId}/files/{fileId}/remove"),
uploadFile: Api.newPost("/machines/{machineId}/files/{fileId}/upload?token={token}"),
fileContent: Api.newGet("/machines/{machineId}/files/{fileId}/read"),
createFile: Api.newPost("/machines/{machineId}/files/{id}/create-file"),
// 修改文件内容
updateFileContent: Api.create("/machines/{machineId}/files/{id}/write", 'post'),
updateFileContent: Api.newPost("/machines/{machineId}/files/{id}/write"),
// 添加文件or目录
addConf: Api.create("/machines/{machineId}/files", 'post'),
addConf: Api.newPost("/machines/{machineId}/files"),
// 删除配置的文件or目录
delConf: Api.create("/machines/{machineId}/files/{id}", 'delete'),
terminal: Api.create("/api/machines/{id}/terminal", 'get'),
recDirNames: Api.create("/machines/rec/names", 'get')
delConf: Api.newDelete("/machines/{machineId}/files/{id}"),
terminal: Api.newGet("/api/machines/{id}/terminal"),
recDirNames: Api.newGet("/machines/rec/names")
}
export const authCertApi = {
baseList : Api.create("/sys/authcerts/base", 'get'),
list: Api.create("/sys/authcerts", 'get'),
save: Api.create("/sys/authcerts", 'post'),
delete: Api.create("/sys/authcerts/{id}", 'delete'),
baseList : Api.newGet("/sys/authcerts/base"),
list: Api.newGet("/sys/authcerts"),
save: Api.newPost("/sys/authcerts"),
delete: Api.newDelete("/sys/authcerts/{id}"),
}

View File

@@ -1,14 +1,14 @@
import Api from '@/common/Api';
export const mongoApi = {
mongoList : Api.create("/mongos", 'get'),
saveMongo : Api.create("/mongos", 'post'),
deleteMongo : Api.create("/mongos/{id}", 'delete'),
databases: Api.create("/mongos/{id}/databases", 'get'),
collections: Api.create("/mongos/{id}/collections", 'get'),
runCommand: Api.create("/mongos/{id}/run-command", 'post'),
findCommand: Api.create("/mongos/{id}/command/find", 'post'),
updateByIdCommand: Api.create("/mongos/{id}/command/update-by-id", 'post'),
deleteByIdCommand: Api.create("/mongos/{id}/command/delete-by-id", 'post'),
insertCommand: Api.create("/mongos/{id}/command/insert", 'post'),
mongoList : Api.newGet("/mongos"),
saveMongo : Api.newPost("/mongos"),
deleteMongo : Api.newDelete("/mongos/{id}"),
databases: Api.newGet("/mongos/{id}/databases"),
collections: Api.newGet("/mongos/{id}/collections"),
runCommand: Api.newPost("/mongos/{id}/run-command"),
findCommand: Api.newPost("/mongos/{id}/command/find"),
updateByIdCommand: Api.newPost("/mongos/{id}/command/update-by-id"),
deleteByIdCommand: Api.newPost("/mongos/{id}/command/delete-by-id"),
insertCommand: Api.newPost("/mongos/{id}/command/insert"),
}

View File

@@ -1,26 +1,26 @@
import Api from '@/common/Api';
export const redisApi = {
redisList : Api.create("/redis", 'get'),
getRedisPwd: Api.create("/redis/{id}/pwd", 'get'),
redisInfo: Api.create("/redis/{id}/info", 'get'),
clusterInfo: Api.create("/redis/{id}/cluster-info", 'get'),
saveRedis: Api.create("/redis", 'post'),
delRedis: Api.create("/redis/{id}", 'delete'),
redisList: Api.newGet("/redis"),
getRedisPwd: Api.newGet("/redis/{id}/pwd"),
redisInfo: Api.newGet("/redis/{id}/info"),
clusterInfo: Api.newGet("/redis/{id}/cluster-info"),
saveRedis: Api.newPost("/redis"),
delRedis: Api.newDelete("/redis/{id}"),
// 获取权限列表
scan: Api.create("/redis/{id}/{db}/scan", 'post'),
getStringValue: Api.create("/redis/{id}/{db}/string-value", 'get'),
saveStringValue: Api.create("/redis/{id}/{db}/string-value", 'post'),
getHashValue: Api.create("/redis/{id}/{db}/hash-value", 'get'),
hscan: Api.create("/redis/{id}/{db}/hscan", 'get'),
hget: Api.create("/redis/{id}/{db}/hget", 'get'),
hdel: Api.create("/redis/{id}/{db}/hdel", 'delete'),
saveHashValue: Api.create("/redis/{id}/{db}/hash-value", 'post'),
getSetValue: Api.create("/redis/{id}/{db}/set-value", 'get'),
saveSetValue: Api.create("/redis/{id}/{db}/set-value", 'post'),
del: Api.create("/redis/{id}/{db}/scan/{cursor}/{count}", 'delete'),
delKey: Api.create("/redis/{id}/{db}/key", 'delete'),
getListValue: Api.create("/redis/{id}/{db}/list-value", 'get'),
saveListValue: Api.create("/redis/{id}/{db}/list-value", 'post'),
setListValue: Api.create("/redis/{id}/{db}/list-value/lset", 'post'),
scan: Api.newPost("/redis/{id}/{db}/scan"),
getStringValue: Api.newGet("/redis/{id}/{db}/string-value"),
saveStringValue: Api.newPost("/redis/{id}/{db}/string-value"),
getHashValue: Api.newGet("/redis/{id}/{db}/hash-value"),
hscan: Api.newGet("/redis/{id}/{db}/hscan"),
hget: Api.newGet("/redis/{id}/{db}/hget"),
hdel: Api.newDelete("/redis/{id}/{db}/hdel"),
saveHashValue: Api.newPost("/redis/{id}/{db}/hash-value"),
getSetValue: Api.newGet("/redis/{id}/{db}/set-value"),
saveSetValue: Api.newPost("/redis/{id}/{db}/set-value"),
del: Api.newDelete("/redis/{id}/{db}/scan/{cursor}/{count}"),
delKey: Api.newDelete("/redis/{id}/{db}/key"),
getListValue: Api.newGet("/redis/{id}/{db}/list-value"),
saveListValue: Api.newPost("/redis/{id}/{db}/list-value"),
setListValue: Api.newPost("/redis/{id}/{db}/list-value/lset"),
}

View File

@@ -1,20 +1,20 @@
import Api from '@/common/Api';
export const tagApi = {
getAccountTags: Api.create("/tag-trees/account-has", 'get'),
listByQuery: Api.create("/tag-trees/query", 'get'),
getTagTrees: Api.create("/tag-trees", 'get'),
saveTagTree: Api.create("/tag-trees", 'post'),
delTagTree: Api.create("/tag-trees/{id}", 'delete'),
getAccountTags: Api.newGet("/tag-trees/account-has"),
listByQuery: Api.newGet("/tag-trees/query"),
getTagTrees: Api.newGet("/tag-trees"),
saveTagTree: Api.newPost("/tag-trees"),
delTagTree: Api.newDelete("/tag-trees/{id}"),
getTeams: Api.create("/teams", 'get'),
saveTeam: Api.create("/teams", 'post'),
delTeam: Api.create("/teams/{id}", 'delete'),
getTeams: Api.newGet("/teams"),
saveTeam: Api.newPost("/teams"),
delTeam: Api.newDelete("/teams/{id}"),
getTeamMem: Api.create("/teams/{teamId}/members", 'get'),
saveTeamMem: Api.create("/teams/{teamId}/members", 'post'),
delTeamMem: Api.create("/teams/{teamId}/members/{accountId}", 'delete'),
getTeamMem: Api.newGet("/teams/{teamId}/members"),
saveTeamMem: Api.newPost("/teams/{teamId}/members"),
delTeamMem: Api.newDelete("/teams/{teamId}/members/{accountId}"),
getTeamTagIds: Api.create("/teams/{teamId}/tags", 'get'),
saveTeamTags: Api.create("/teams/{teamId}/tags", 'post'),
getTeamTagIds: Api.newGet("/teams/{teamId}/tags"),
saveTeamTags: Api.newPost("/teams/{teamId}/tags"),
}

View File

@@ -1,8 +1,8 @@
import Api from '@/common/Api';
export const personApi = {
accountInfo: Api.create("/sys/accounts/self", 'get'),
updateAccount: Api.create("/sys/accounts/self", 'put'),
getMsgs: Api.create("/sys/accounts/msgs", 'get'),
accountInfo: Api.newGet("/sys/accounts/self"),
updateAccount: Api.newPut("/sys/accounts/self"),
getMsgs: Api.newGet("/sys/accounts/msgs"),
}

View File

@@ -1,43 +1,43 @@
import Api from '@/common/Api';
export const resourceApi = {
list: Api.create("/sys/resources", 'get'),
detail: Api.create("/sys/resources/{id}", 'get'),
save: Api.create("/sys/resources", 'post'),
update: Api.create("/sys/resources/{id}", 'put'),
del: Api.create("/sys/resources/{id}", 'delete'),
changeStatus: Api.create("/sys/resources/{id}/{status}", 'put')
list: Api.newGet("/sys/resources"),
detail: Api.newGet("/sys/resources/{id}"),
save: Api.newPost("/sys/resources"),
update: Api.newPut("/sys/resources/{id}"),
del: Api.newDelete("/sys/resources/{id}"),
changeStatus: Api.newPut("/sys/resources/{id}/{status}")
}
export const roleApi = {
list: Api.create("/sys/roles", 'get'),
save: Api.create("/sys/roles", 'post'),
update: Api.create("/sys/roles/{id}", 'put'),
del: Api.create("/sys/roles/{id}", 'delete'),
list: Api.newGet("/sys/roles"),
save: Api.newPost("/sys/roles"),
update: Api.newPut("/sys/roles/{id}"),
del: Api.newDelete("/sys/roles/{id}"),
// 获取指定角色拥有的资源id
roleResourceIds: Api.create("/sys/roles/{id}/resourceIds", 'get'),
roleResources: Api.create("/sys/roles/{id}/resources", 'get'),
saveResources: Api.create("/sys/roles/{id}/resources", 'post')
roleResourceIds: Api.newGet("/sys/roles/{id}/resourceIds"),
roleResources: Api.newGet("/sys/roles/{id}/resources"),
saveResources: Api.newPost("/sys/roles/{id}/resources")
}
export const accountApi = {
list: Api.create("/sys/accounts", 'get'),
save: Api.create("/sys/accounts", 'post'),
update: Api.create("/sys/accounts/{id}", 'put'),
del: Api.create("/sys/accounts/{id}", 'delete'),
changeStatus: Api.create("/sys/accounts/change-status/{id}/{status}", 'put'),
roleIds: Api.create("/sys/accounts/{id}/roleIds", 'get'),
roles: Api.create("/sys/accounts/{id}/roles", 'get'),
resources: Api.create("/sys/accounts/{id}/resources", 'get'),
saveRoles: Api.create("/sys/accounts/roles", 'post')
list: Api.newGet("/sys/accounts"),
save: Api.newPost("/sys/accounts"),
update: Api.newPut("/sys/accounts/{id}"),
del: Api.newDelete("/sys/accounts/{id}"),
changeStatus: Api.newPut("/sys/accounts/change-status/{id}/{status}"),
roleIds: Api.newGet("/sys/accounts/{id}/roleIds"),
roles: Api.newGet("/sys/accounts/{id}/roles"),
resources: Api.newGet("/sys/accounts/{id}/resources"),
saveRoles: Api.newPost("/sys/accounts/roles")
}
export const configApi = {
list: Api.create("/sys/configs", 'get'),
save: Api.create("/sys/configs", 'post'),
getValue: Api.create("/sys/configs/value", 'get'),
list: Api.newGet("/sys/configs"),
save: Api.newPost("/sys/configs"),
getValue: Api.newGet("/sys/configs/value"),
}
export const logApi = {
list: Api.create("/syslogs", "get")
list: Api.newGet("/syslogs")
}

View File

@@ -18,8 +18,8 @@ require (
golang.org/x/crypto v0.8.0 // ssh
gopkg.in/yaml.v3 v3.0.1
// gorm
gorm.io/driver/mysql v1.4.7
gorm.io/gorm v1.24.6
gorm.io/driver/mysql v1.5.0
gorm.io/gorm v1.25.0
)
require (

View File

@@ -39,6 +39,10 @@ func (a *Account) Login(rc *req.Ctx) {
biz.IsTrue(captcha.Verify(loginForm.Cid, loginForm.Captcha), "验证码错误")
}
clientIp := rc.GinCtx.ClientIP()
rc.ReqParam = loginForm.Username
rc.ReqParam = fmt.Sprintf("username: %s | ip: %s", loginForm.Username, clientIp)
originPwd, err := utils.DefaultRsaDecrypt(loginForm.Password, true)
biz.ErrIsNilAppendErr(err, "解密密码错误: %s")
@@ -50,6 +54,20 @@ func (a *Account) Login(rc *req.Ctx) {
// 校验密码强度是否符合
biz.IsTrueBy(CheckPasswordLever(originPwd), biz.NewBizErrCode(401, "您的密码安全等级较低,请修改后重新登录"))
// 保存登录消息
go a.saveLogin(account, clientIp)
rc.ResData = map[string]interface{}{
"token": req.CreateToken(account.Id, account.Username),
"name": account.Name,
"username": account.Username,
"lastLoginTime": account.LastLoginTime,
"lastLoginIp": account.LastLoginIp,
}
}
func (a *Account) GetPermissions(rc *req.Ctx) {
account := rc.LoginAccount
var resources vo.AccountResourceVOList
// 获取账号菜单资源
@@ -66,23 +84,9 @@ func (a *Account) Login(rc *req.Ctx) {
}
// 保存该账号的权限codes
req.SavePermissionCodes(account.Id, permissions)
clientIp := rc.GinCtx.ClientIP()
// 保存登录消息
go a.saveLogin(account, clientIp)
rc.ReqParam = fmt.Sprintf("登录ip: %s", clientIp)
// 赋值loginAccount 主要用于记录操作日志,因为操作日志保存请求上下文没有该信息不保存日志
rc.LoginAccount = &model.LoginAccount{Id: account.Id, Username: account.Username}
rc.ResData = map[string]interface{}{
"token": req.CreateToken(account.Id, account.Username),
"name": account.Name,
"username": account.Username,
"lastLoginTime": account.LastLoginTime,
"lastLoginIp": account.LastLoginIp,
"menus": menus.ToTrees(0),
"permissions": permissions,
"menus": menus.ToTrees(0),
"permissions": permissions,
}
}
@@ -143,7 +147,7 @@ func (a *Account) saveLogin(account *entity.Account, ip string) {
// 创建登录消息
loginMsg := &entity.Msg{
RecipientId: int64(account.Id),
Msg: fmt.Sprintf("于%s登录", now.Format("2006-01-02 15:04:05")),
Msg: fmt.Sprintf("于[%s]-[%s]登录", ip, now.Format("2006-01-02 15:04:05")),
Type: 1,
}
loginMsg.CreateTime = &now

View File

@@ -27,6 +27,11 @@ func InitAccountRouter(router *gin.RouterGroup) {
Handle(a.Login)
})
// 获取个人账号的权限资源信息
account.GET("/permissions", func(c *gin.Context) {
req.NewCtxWithGin(c).Handle(a.GetPermissions)
})
changePwdLog := req.NewLogInfo("用户修改密码").WithSave(true)
account.POST("change-pwd", func(g *gin.Context) {
req.NewCtxWithGin(g).