Compare commits
2 Commits
bd14bb36c4
...
7e327e2131
| Author | SHA1 | Date |
|---|---|---|
|
|
7e327e2131 | |
|
|
ec2d69a09f |
|
|
@ -1,6 +1,7 @@
|
|||
package org.dromara.pangu.application.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
|
|
@ -8,11 +9,15 @@ import org.dromara.common.log.enums.BusinessType;
|
|||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.pangu.application.domain.PgApiDict;
|
||||
import org.dromara.pangu.application.domain.PgApplication;
|
||||
import org.dromara.pangu.application.service.IPgApiDictService;
|
||||
import org.dromara.pangu.application.service.IPgApplicationService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 第三方应用管理
|
||||
*
|
||||
|
|
@ -25,6 +30,7 @@ import org.springframework.web.bind.annotation.*;
|
|||
public class PgApplicationController extends BaseController {
|
||||
|
||||
private final IPgApplicationService applicationService;
|
||||
private final IPgApiDictService apiDictService;
|
||||
|
||||
@SaCheckPermission("business:application:list")
|
||||
@GetMapping("/list")
|
||||
|
|
@ -58,4 +64,31 @@ public class PgApplicationController extends BaseController {
|
|||
public R<Void> remove(@PathVariable Long[] appIds) {
|
||||
return toAjax(applicationService.deleteByIds(appIds));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置应用密钥
|
||||
*/
|
||||
@SaCheckPermission("business:application:edit")
|
||||
@Log(title = "应用管理", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/resetSecret/{appId}")
|
||||
public R<PgApplication> resetSecret(@PathVariable Long appId) {
|
||||
PgApplication app = applicationService.selectById(appId);
|
||||
if (app == null) {
|
||||
return R.fail("应用不存在");
|
||||
}
|
||||
// 生成新的32位密钥
|
||||
String newSecret = IdUtil.fastSimpleUUID();
|
||||
app.setAppSecret(newSecret);
|
||||
applicationService.update(app);
|
||||
return R.ok(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API接口列表(用于授权选择)
|
||||
*/
|
||||
@SaCheckPermission("business:application:list")
|
||||
@GetMapping("/apiList")
|
||||
public R<List<PgApiDict>> apiList() {
|
||||
return R.ok(apiDictService.selectList());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package org.dromara.pangu.application.service;
|
||||
|
||||
import org.dromara.pangu.application.domain.PgApiDict;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API接口字典 Service 接口
|
||||
*
|
||||
* @author pangu
|
||||
*/
|
||||
public interface IPgApiDictService {
|
||||
|
||||
/**
|
||||
* 查询所有启用的API接口列表
|
||||
*/
|
||||
List<PgApiDict> selectList();
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package org.dromara.pangu.application.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.pangu.application.domain.PgApiDict;
|
||||
import org.dromara.pangu.application.mapper.PgApiDictMapper;
|
||||
import org.dromara.pangu.application.service.IPgApiDictService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* API接口字典 Service 实现
|
||||
*
|
||||
* @author pangu
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class PgApiDictServiceImpl implements IPgApiDictService {
|
||||
|
||||
private final PgApiDictMapper baseMapper;
|
||||
|
||||
@Override
|
||||
public List<PgApiDict> selectList() {
|
||||
LambdaQueryWrapper<PgApiDict> lqw = new LambdaQueryWrapper<>();
|
||||
lqw.eq(PgApiDict::getStatus, "0");
|
||||
lqw.orderByAsc(PgApiDict::getOrderNum);
|
||||
return baseMapper.selectList(lqw);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package org.dromara.pangu.application.service.impl;
|
||||
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
|
|
@ -44,9 +45,39 @@ public class PgApplicationServiceImpl implements IPgApplicationService {
|
|||
|
||||
@Override
|
||||
public int insert(PgApplication app) {
|
||||
// 自动生成应用编码:YY + 6位序号
|
||||
String appCode = generateAppCode();
|
||||
app.setAppCode(appCode);
|
||||
// 自动生成32位应用密钥
|
||||
app.setAppSecret(IdUtil.fastSimpleUUID());
|
||||
return baseMapper.insert(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成应用编码
|
||||
*/
|
||||
private String generateAppCode() {
|
||||
// 查询当前最大编码
|
||||
LambdaQueryWrapper<PgApplication> lqw = new LambdaQueryWrapper<>();
|
||||
lqw.select(PgApplication::getAppCode);
|
||||
lqw.likeRight(PgApplication::getAppCode, "YY");
|
||||
lqw.orderByDesc(PgApplication::getAppCode);
|
||||
lqw.last("LIMIT 1");
|
||||
PgApplication lastApp = baseMapper.selectOne(lqw);
|
||||
|
||||
int nextNum = 1;
|
||||
if (lastApp != null && StrUtil.isNotBlank(lastApp.getAppCode())) {
|
||||
String lastCode = lastApp.getAppCode();
|
||||
if (lastCode.length() > 2) {
|
||||
try {
|
||||
nextNum = Integer.parseInt(lastCode.substring(2)) + 1;
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return String.format("YY%06d", nextNum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(PgApplication app) {
|
||||
return baseMapper.updateById(app);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import request from '@/utils/request'
|
|||
*/
|
||||
export function getApplicationList(params) {
|
||||
return request({
|
||||
url: '/api/application/list',
|
||||
url: '/business/application/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
|
|
@ -18,9 +18,9 @@ export function getApplicationList(params) {
|
|||
/**
|
||||
* 获取应用详情
|
||||
*/
|
||||
export function getApplicationDetail(id) {
|
||||
export function getApplicationDetail(appId) {
|
||||
return request({
|
||||
url: `/api/application/${id}`,
|
||||
url: `/business/application/${appId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
@ -30,7 +30,7 @@ export function getApplicationDetail(id) {
|
|||
*/
|
||||
export function addApplication(data) {
|
||||
return request({
|
||||
url: '/api/application',
|
||||
url: '/business/application',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
|
|
@ -41,7 +41,7 @@ export function addApplication(data) {
|
|||
*/
|
||||
export function updateApplication(data) {
|
||||
return request({
|
||||
url: '/api/application',
|
||||
url: '/business/application',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
|
|
@ -50,9 +50,9 @@ export function updateApplication(data) {
|
|||
/**
|
||||
* 删除应用
|
||||
*/
|
||||
export function deleteApplication(id) {
|
||||
export function deleteApplication(appId) {
|
||||
return request({
|
||||
url: `/api/application/${id}`,
|
||||
url: `/business/application/${appId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
|
@ -60,10 +60,10 @@ export function deleteApplication(id) {
|
|||
/**
|
||||
* 重置应用密钥
|
||||
*/
|
||||
export function resetAppSecret(id) {
|
||||
export function resetAppSecret(appId) {
|
||||
return request({
|
||||
url: `/api/application/${id}/resetSecret`,
|
||||
method: 'post'
|
||||
url: `/business/application/resetSecret/${appId}`,
|
||||
method: 'put'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +72,7 @@ export function resetAppSecret(id) {
|
|||
*/
|
||||
export function getApiAuthOptions() {
|
||||
return request({
|
||||
url: '/api/application/authOptions',
|
||||
url: '/business/application/apiList',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,18 +19,18 @@
|
|||
<el-form-item label="应用编码" prop="appCode">
|
||||
<el-input v-model="form.appCode" placeholder="保存后自动生成" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用描述" prop="description">
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.description"
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入应用描述"
|
||||
placeholder="请输入备注说明"
|
||||
:rows="3"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="联系人" prop="contactName">
|
||||
<el-input v-model="form.contactName" placeholder="请输入联系人" maxlength="20" />
|
||||
<el-form-item label="联系人" prop="contactPerson">
|
||||
<el-input v-model="form.contactPerson" placeholder="请输入联系人" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="联系电话" prop="contactPhone">
|
||||
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" maxlength="11" />
|
||||
|
|
@ -44,14 +44,14 @@
|
|||
inactive-text="停用"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="接口授权" prop="apiAuth">
|
||||
<el-checkbox-group v-model="form.apiAuth">
|
||||
<el-form-item label="接口授权" prop="apiCodes">
|
||||
<el-checkbox-group v-model="form.apiCodes">
|
||||
<el-checkbox
|
||||
v-for="item in apiAuthOptions"
|
||||
:key="item.value"
|
||||
:label="item.value"
|
||||
:key="item.apiCode"
|
||||
:label="item.apiCode"
|
||||
>
|
||||
{{ item.label }}
|
||||
{{ item.apiName }}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
|
|
@ -68,9 +68,9 @@
|
|||
* 应用管理 - 新增/编辑弹窗
|
||||
* @author pangu
|
||||
*/
|
||||
import { addApplication, getApiAuthOptions, updateApplication } from '@/api/pangu/application'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, nextTick, reactive, ref } from 'vue'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
|
||||
|
|
@ -81,14 +81,14 @@ const isEdit = ref(false)
|
|||
const apiAuthOptions = ref([])
|
||||
|
||||
const form = reactive({
|
||||
id: null,
|
||||
appId: null,
|
||||
appName: '',
|
||||
appCode: '',
|
||||
description: '',
|
||||
contactName: '',
|
||||
remark: '',
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
status: '0',
|
||||
apiAuth: []
|
||||
apiCodes: []
|
||||
})
|
||||
|
||||
const rules = {
|
||||
|
|
@ -105,9 +105,9 @@ const dialogTitle = computed(() => isEdit.value ? '编辑应用' : '新增应用
|
|||
// 加载接口授权选项
|
||||
const loadAuthOptions = async () => {
|
||||
try {
|
||||
const res = await getApiAuthOptions()
|
||||
const res = await request.get('/business/application/apiList')
|
||||
if (res.code === 200) {
|
||||
apiAuthOptions.value = res.data
|
||||
apiAuthOptions.value = res.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载接口授权选项失败:', error)
|
||||
|
|
@ -123,14 +123,14 @@ const open = (row = null) => {
|
|||
isEdit.value = true
|
||||
nextTick(() => {
|
||||
Object.assign(form, {
|
||||
id: row.id,
|
||||
appId: row.appId,
|
||||
appName: row.appName,
|
||||
appCode: row.appCode,
|
||||
description: row.description || '',
|
||||
contactName: row.contactName || '',
|
||||
remark: row.remark || '',
|
||||
contactPerson: row.contactPerson || '',
|
||||
contactPhone: row.contactPhone || '',
|
||||
status: row.status || '0',
|
||||
apiAuth: row.apiAuth || []
|
||||
apiCodes: row.apiCodes || []
|
||||
})
|
||||
})
|
||||
} else {
|
||||
|
|
@ -144,8 +144,8 @@ const handleSubmit = async () => {
|
|||
await formRef.value.validate()
|
||||
submitLoading.value = true
|
||||
|
||||
const api = isEdit.value ? updateApplication : addApplication
|
||||
const res = await api(form)
|
||||
const method = isEdit.value ? 'put' : 'post'
|
||||
const res = await request[method]('/business/application', form)
|
||||
|
||||
if (res.code === 200) {
|
||||
ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
|
||||
|
|
@ -165,14 +165,14 @@ const handleSubmit = async () => {
|
|||
const handleClosed = () => {
|
||||
formRef.value?.resetFields()
|
||||
Object.assign(form, {
|
||||
id: null,
|
||||
appId: null,
|
||||
appName: '',
|
||||
appCode: '',
|
||||
description: '',
|
||||
contactName: '',
|
||||
remark: '',
|
||||
contactPerson: '',
|
||||
contactPhone: '',
|
||||
status: '0',
|
||||
apiAuth: []
|
||||
apiCodes: []
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="应用密钥"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-alert
|
||||
title="请妥善保管密钥,密钥重置后旧密钥将立即失效"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 16px;"
|
||||
/>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="应用名称">
|
||||
<span>{{ appInfo.appName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用编码">
|
||||
<span>{{ appInfo.appCode }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="应用密钥">
|
||||
<el-input
|
||||
v-model="appInfo.appSecret"
|
||||
readonly
|
||||
style="width: 280px;"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="handleCopy">
|
||||
<el-icon><DocumentCopy /></el-icon>
|
||||
复制
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 应用管理 - 密钥展示弹窗
|
||||
* @author pangu
|
||||
*/
|
||||
import { DocumentCopy } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { reactive, ref } from 'vue'
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const appInfo = reactive({
|
||||
appName: '',
|
||||
appCode: '',
|
||||
appSecret: ''
|
||||
})
|
||||
|
||||
// 打开弹窗
|
||||
const open = (row) => {
|
||||
visible.value = true
|
||||
Object.assign(appInfo, {
|
||||
appName: row.appName,
|
||||
appCode: row.appCode,
|
||||
appSecret: row.appSecret
|
||||
})
|
||||
}
|
||||
|
||||
// 复制密钥
|
||||
const handleCopy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(appInfo.appSecret)
|
||||
ElMessage.success('复制成功')
|
||||
} catch (error) {
|
||||
// 降级方案
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = appInfo.appSecret
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
ElMessage.success('复制成功')
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
|
@ -33,14 +33,9 @@
|
|||
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%">
|
||||
<el-table-column prop="appName" label="应用名称" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column prop="appCode" label="应用编码" width="120" />
|
||||
<el-table-column prop="apis" label="授权接口" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-for="api in (row.apis || []).slice(0, 3)" :key="api" size="small" style="margin-right: 4px; margin-bottom: 2px">
|
||||
{{ api }}
|
||||
</el-tag>
|
||||
<el-tag v-if="(row.apis || []).length > 3" size="small" type="info">+{{ row.apis.length - 3 }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactPerson" label="联系人" width="100" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="120" />
|
||||
<el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="status" label="状态" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === '0' ? 'success' : 'danger'">
|
||||
|
|
@ -49,7 +44,6 @@
|
|||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="160" />
|
||||
<el-table-column prop="createBy" label="创建人" width="100" />
|
||||
<el-table-column label="操作" width="180" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
|
||||
|
|
@ -156,7 +150,7 @@ const handleResetSecret = async (row) => {
|
|||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await request.put(`/business/application/resetSecret/${row.id}`)
|
||||
const res = await request.put(`/business/application/resetSecret/${row.appId}`)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('重置成功')
|
||||
secretDialogRef.value?.open(res.data)
|
||||
|
|
@ -171,7 +165,7 @@ const handleDelete = (row) => {
|
|||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(async () => {
|
||||
const res = await request.delete(`/business/application/${row.id}`)
|
||||
const res = await request.delete(`/business/application/${row.appId}`)
|
||||
if (res.code === 200) {
|
||||
ElMessage.success('删除成功')
|
||||
getList()
|
||||
|
|
|
|||
Loading…
Reference in New Issue