Compare commits

..

No commits in common. "7e327e2131366367c097744744190a44736a4de8" and "bd14bb36c466c51eaac6863cac4cbd68fb92551b" have entirely different histories.

8 changed files with 49 additions and 243 deletions

View File

@ -1,7 +1,6 @@
package org.dromara.pangu.application.controller; package org.dromara.pangu.application.controller;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.IdUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R; import org.dromara.common.core.domain.R;
import org.dromara.common.log.annotation.Log; import org.dromara.common.log.annotation.Log;
@ -9,15 +8,11 @@ import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController; 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.domain.PgApplication;
import org.dromara.pangu.application.service.IPgApiDictService;
import org.dromara.pangu.application.service.IPgApplicationService; import org.dromara.pangu.application.service.IPgApplicationService;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
/** /**
* 第三方应用管理 * 第三方应用管理
* *
@ -30,7 +25,6 @@ import java.util.List;
public class PgApplicationController extends BaseController { public class PgApplicationController extends BaseController {
private final IPgApplicationService applicationService; private final IPgApplicationService applicationService;
private final IPgApiDictService apiDictService;
@SaCheckPermission("business:application:list") @SaCheckPermission("business:application:list")
@GetMapping("/list") @GetMapping("/list")
@ -64,31 +58,4 @@ public class PgApplicationController extends BaseController {
public R<Void> remove(@PathVariable Long[] appIds) { public R<Void> remove(@PathVariable Long[] appIds) {
return toAjax(applicationService.deleteByIds(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());
}
} }

View File

@ -1,18 +0,0 @@
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();
}

View File

@ -1,30 +0,0 @@
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);
}
}

View File

@ -1,6 +1,5 @@
package org.dromara.pangu.application.service.impl; package org.dromara.pangu.application.service.impl;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -45,39 +44,9 @@ public class PgApplicationServiceImpl implements IPgApplicationService {
@Override @Override
public int insert(PgApplication app) { public int insert(PgApplication app) {
// 自动生成应用编码YY + 6位序号
String appCode = generateAppCode();
app.setAppCode(appCode);
// 自动生成32位应用密钥
app.setAppSecret(IdUtil.fastSimpleUUID());
return baseMapper.insert(app); 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 @Override
public int update(PgApplication app) { public int update(PgApplication app) {
return baseMapper.updateById(app); return baseMapper.updateById(app);

View File

@ -9,7 +9,7 @@ import request from '@/utils/request'
*/ */
export function getApplicationList(params) { export function getApplicationList(params) {
return request({ return request({
url: '/business/application/list', url: '/api/application/list',
method: 'get', method: 'get',
params params
}) })
@ -18,9 +18,9 @@ export function getApplicationList(params) {
/** /**
* 获取应用详情 * 获取应用详情
*/ */
export function getApplicationDetail(appId) { export function getApplicationDetail(id) {
return request({ return request({
url: `/business/application/${appId}`, url: `/api/application/${id}`,
method: 'get' method: 'get'
}) })
} }
@ -30,7 +30,7 @@ export function getApplicationDetail(appId) {
*/ */
export function addApplication(data) { export function addApplication(data) {
return request({ return request({
url: '/business/application', url: '/api/application',
method: 'post', method: 'post',
data data
}) })
@ -41,7 +41,7 @@ export function addApplication(data) {
*/ */
export function updateApplication(data) { export function updateApplication(data) {
return request({ return request({
url: '/business/application', url: '/api/application',
method: 'put', method: 'put',
data data
}) })
@ -50,9 +50,9 @@ export function updateApplication(data) {
/** /**
* 删除应用 * 删除应用
*/ */
export function deleteApplication(appId) { export function deleteApplication(id) {
return request({ return request({
url: `/business/application/${appId}`, url: `/api/application/${id}`,
method: 'delete' method: 'delete'
}) })
} }
@ -60,10 +60,10 @@ export function deleteApplication(appId) {
/** /**
* 重置应用密钥 * 重置应用密钥
*/ */
export function resetAppSecret(appId) { export function resetAppSecret(id) {
return request({ return request({
url: `/business/application/resetSecret/${appId}`, url: `/api/application/${id}/resetSecret`,
method: 'put' method: 'post'
}) })
} }
@ -72,7 +72,7 @@ export function resetAppSecret(appId) {
*/ */
export function getApiAuthOptions() { export function getApiAuthOptions() {
return request({ return request({
url: '/business/application/apiList', url: '/api/application/authOptions',
method: 'get' method: 'get'
}) })
} }

View File

@ -19,18 +19,18 @@
<el-form-item label="应用编码" prop="appCode"> <el-form-item label="应用编码" prop="appCode">
<el-input v-model="form.appCode" placeholder="保存后自动生成" disabled /> <el-input v-model="form.appCode" placeholder="保存后自动生成" disabled />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> <el-form-item label="应用描述" prop="description">
<el-input <el-input
v-model="form.remark" v-model="form.description"
type="textarea" type="textarea"
placeholder="请输入备注说明" placeholder="请输入应用描述"
:rows="3" :rows="3"
maxlength="200" maxlength="200"
show-word-limit show-word-limit
/> />
</el-form-item> </el-form-item>
<el-form-item label="联系人" prop="contactPerson"> <el-form-item label="联系人" prop="contactName">
<el-input v-model="form.contactPerson" placeholder="请输入联系人" maxlength="20" /> <el-input v-model="form.contactName" placeholder="请输入联系人" maxlength="20" />
</el-form-item> </el-form-item>
<el-form-item label="联系电话" prop="contactPhone"> <el-form-item label="联系电话" prop="contactPhone">
<el-input v-model="form.contactPhone" placeholder="请输入联系电话" maxlength="11" /> <el-input v-model="form.contactPhone" placeholder="请输入联系电话" maxlength="11" />
@ -44,14 +44,14 @@
inactive-text="停用" inactive-text="停用"
/> />
</el-form-item> </el-form-item>
<el-form-item label="接口授权" prop="apiCodes"> <el-form-item label="接口授权" prop="apiAuth">
<el-checkbox-group v-model="form.apiCodes"> <el-checkbox-group v-model="form.apiAuth">
<el-checkbox <el-checkbox
v-for="item in apiAuthOptions" v-for="item in apiAuthOptions"
:key="item.apiCode" :key="item.value"
:label="item.apiCode" :label="item.value"
> >
{{ item.apiName }} {{ item.label }}
</el-checkbox> </el-checkbox>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
@ -68,9 +68,9 @@
* 应用管理 - 新增/编辑弹窗 * 应用管理 - 新增/编辑弹窗
* @author pangu * @author pangu
*/ */
import { addApplication, getApiAuthOptions, updateApplication } from '@/api/pangu/application'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { computed, nextTick, reactive, ref } from 'vue' import { computed, nextTick, reactive, ref } from 'vue'
import request from '@/utils/request'
const emit = defineEmits(['success']) const emit = defineEmits(['success'])
@ -81,14 +81,14 @@ const isEdit = ref(false)
const apiAuthOptions = ref([]) const apiAuthOptions = ref([])
const form = reactive({ const form = reactive({
appId: null, id: null,
appName: '', appName: '',
appCode: '', appCode: '',
remark: '', description: '',
contactPerson: '', contactName: '',
contactPhone: '', contactPhone: '',
status: '0', status: '0',
apiCodes: [] apiAuth: []
}) })
const rules = { const rules = {
@ -105,9 +105,9 @@ const dialogTitle = computed(() => isEdit.value ? '编辑应用' : '新增应用
// //
const loadAuthOptions = async () => { const loadAuthOptions = async () => {
try { try {
const res = await request.get('/business/application/apiList') const res = await getApiAuthOptions()
if (res.code === 200) { if (res.code === 200) {
apiAuthOptions.value = res.data || [] apiAuthOptions.value = res.data
} }
} catch (error) { } catch (error) {
console.error('加载接口授权选项失败:', error) console.error('加载接口授权选项失败:', error)
@ -123,14 +123,14 @@ const open = (row = null) => {
isEdit.value = true isEdit.value = true
nextTick(() => { nextTick(() => {
Object.assign(form, { Object.assign(form, {
appId: row.appId, id: row.id,
appName: row.appName, appName: row.appName,
appCode: row.appCode, appCode: row.appCode,
remark: row.remark || '', description: row.description || '',
contactPerson: row.contactPerson || '', contactName: row.contactName || '',
contactPhone: row.contactPhone || '', contactPhone: row.contactPhone || '',
status: row.status || '0', status: row.status || '0',
apiCodes: row.apiCodes || [] apiAuth: row.apiAuth || []
}) })
}) })
} else { } else {
@ -144,8 +144,8 @@ const handleSubmit = async () => {
await formRef.value.validate() await formRef.value.validate()
submitLoading.value = true submitLoading.value = true
const method = isEdit.value ? 'put' : 'post' const api = isEdit.value ? updateApplication : addApplication
const res = await request[method]('/business/application', form) const res = await api(form)
if (res.code === 200) { if (res.code === 200) {
ElMessage.success(isEdit.value ? '修改成功' : '新增成功') ElMessage.success(isEdit.value ? '修改成功' : '新增成功')
@ -165,14 +165,14 @@ const handleSubmit = async () => {
const handleClosed = () => { const handleClosed = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
Object.assign(form, { Object.assign(form, {
appId: null, id: null,
appName: '', appName: '',
appCode: '', appCode: '',
remark: '', description: '',
contactPerson: '', contactName: '',
contactPhone: '', contactPhone: '',
status: '0', status: '0',
apiCodes: [] apiAuth: []
}) })
} }

View File

@ -1,88 +0,0 @@
<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>

View File

@ -33,9 +33,14 @@
<el-table v-loading="loading" :data="tableData" border stripe :header-cell-style="{ background: '#f5f7fa', color: '#606266' }" style="width: 100%"> <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="appName" label="应用名称" min-width="150" show-overflow-tooltip />
<el-table-column prop="appCode" label="应用编码" width="120" /> <el-table-column prop="appCode" label="应用编码" width="120" />
<el-table-column prop="contactPerson" label="联系人" width="100" /> <el-table-column prop="apis" label="授权接口" min-width="250">
<el-table-column prop="contactPhone" label="联系电话" width="120" /> <template #default="{ row }">
<el-table-column prop="remark" label="备注" min-width="200" show-overflow-tooltip /> <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="status" label="状态" width="80" align="center"> <el-table-column prop="status" label="状态" width="80" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="row.status === '0' ? 'success' : 'danger'"> <el-tag :type="row.status === '0' ? 'success' : 'danger'">
@ -44,6 +49,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="createTime" label="创建时间" width="160" /> <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"> <el-table-column label="操作" width="180" fixed="right" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button> <el-button type="primary" link :icon="Edit" @click="handleEdit(row)">编辑</el-button>
@ -150,7 +156,7 @@ const handleResetSecret = async (row) => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(async () => { }).then(async () => {
const res = await request.put(`/business/application/resetSecret/${row.appId}`) const res = await request.put(`/business/application/resetSecret/${row.id}`)
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('重置成功') ElMessage.success('重置成功')
secretDialogRef.value?.open(res.data) secretDialogRef.value?.open(res.data)
@ -165,7 +171,7 @@ const handleDelete = (row) => {
cancelButtonText: '取消', cancelButtonText: '取消',
type: 'warning' type: 'warning'
}).then(async () => { }).then(async () => {
const res = await request.delete(`/business/application/${row.appId}`) const res = await request.delete(`/business/application/${row.id}`)
if (res.code === 200) { if (res.code === 200) {
ElMessage.success('删除成功') ElMessage.success('删除成功')
getList() getList()