Przeglądaj źródła

tnb 添加 /api/open/** 接口, 采用 ak 和 sk 的方式让用户可以通过 api 发布稿件

reghao 1 miesiąc temu
rodzic
commit
1ecc96e9af
2 zmienionych plików z 269 dodań i 103 usunięć
  1. 22 1
      src/api/account.js
  2. 247 102
      src/views/my/MyOAuth.vue

+ 22 - 1
src/api/account.js

@@ -1,4 +1,4 @@
-import { get, post } from '@/utils/request'
+import {delete0, get, post, put} from '@/utils/request'
 
 const accountApi = {
   checkUsernameApi: '/api/user/account/check/username',
@@ -19,6 +19,7 @@ const accountApi = {
   oauthAppApi: '/api/account/oauth/create',
   loginRecordApi: '/api/account/record/list',
   signOutApi: '/api/account/deactivate',
+  apiKeyApi: '/api/account/apikey',
   updateAvatarApi: '/api/file/avatar/update'
 }
 
@@ -108,3 +109,23 @@ export function getLoginRecord() {
 export function signOut(loginId) {
   return post(accountApi.signOutApi + '/' + loginId)
 }
+
+// 1. 获取当前用户的所有 API Key 列表
+export function getApiKeyList() {
+  return get(accountApi.apiKeyApi)
+}
+
+// 2. 创建新的 API Key
+export function createApiKey(payload) {
+  return post(accountApi.apiKeyApi, payload)
+}
+
+// 3. 禁用 API Key
+export function disableApiKey(ak) {
+  return put(accountApi.apiKeyApi + '/disable', { ak: ak })
+}
+
+// 4. 删除 API Key
+export function deleteApiKey(ak) {
+  return delete0(accountApi.apiKeyApi + '/delete', { ak: ak })
+}

+ 247 - 102
src/views/my/MyOAuth.vue

@@ -1,134 +1,279 @@
 <template>
-  <el-main>
-    <el-row class="movie-list">
-      <el-col :md="20" style="padding-right: 5px; padding-left: 5px; padding-bottom: 5px">
-        <el-card class="box-card">
-          <div slot="header" class="clearfix">
-            <span>我的应用</span>
-            <el-button style="float: right; padding: 3px 0" type="text" @click="addAppDialog">添加</el-button>
-          </div>
-          <el-table
-            :data="dataList"
-            style="width: 100%"
-          >
-            <el-table-column
-              prop="clientId"
-              label="客户端 ID"
-            />
-            <el-table-column
-              prop="resourceIds"
-              label="资源 ID"
-            />
-            <el-table-column
-              prop="scope"
-              label="作用域"
-            />
-            <el-table-column
-              prop="authorizedGrantTypes"
-              label="授权类型"
-            />
-            <el-table-column
-              prop="autoapprove	"
-              label="自动授权"
+  <div class="api-key-container">
+    <el-card class="box-card">
+      <div slot="header" class="clearfix">
+        <span class="card-title">开发者中心 - API Key 管理</span>
+        <el-button
+          style="float: right;"
+          type="primary"
+          icon="el-icon-plus"
+          size="small"
+          @click="showCreateDialog"
+        >创建密钥</el-button>
+      </div>
+
+      <el-alert
+        title="安全提示:请妥善保管您的 Secret Access Key (SK)。开放平台接口调用涉及您的账户权限,切勿泄露给他人。"
+        type="warning"
+        show-icon
+        :closable="false"
+        style="margin-bottom: 20px;"
+      />
+
+      <el-table v-loading="loading" :data="apiKeyList" border style="width: 100%">
+        <el-table-column prop="appName" label="应用/脚本名称" min-width="150" />
+        <el-table-column prop="accessKey" label="Access Key ID (AK)" min-width="180" />
+        <el-table-column label="权限范围 (Scopes)" min-width="180">
+          <template slot-scope="scope">
+            <el-tag
+              v-for="item in splitScopes(scope.row.scopes)"
+              :key="item"
+              size="mini"
+              style="margin-right: 5px;"
             >
-              <template slot-scope="scope">
-                <span v-if="scope.row.autoapprove">
-                  是
-                </span>
-                <span v-else>
-                  否
-                </span>
-              </template>
-            </el-table-column>
-            <el-table-column
-              prop="webServerRedirectUri"
-              label="回调地址"
-            />
-            <el-table-column label="操作">
-              <template slot-scope="scope">
-                <el-button
-                  size="mini"
-                  @click="handleDetail(scope.$index, scope.row)"
-                >详情</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-        </el-card>
-      </el-col>
-    </el-row>
+              {{ item }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="状态" width="100" align="center">
+          <template slot-scope="scope">
+            <el-tag :type="scope.row.status === 1 ? 'success' : 'info'">
+              {{ scope.row.status === 1 ? '已启用' : '已禁用' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createTime" label="创建时间" width="160" />
+        <el-table-column label="操作" width="160" align="center">
+          <template slot-scope="scope">
+            <el-button
+              v-if="scope.row.status === 1"
+              size="mini"
+              type="text"
+              style="color: #E6A23C;"
+              @click="handleDisable(scope.row)"
+            >禁用</el-button>
+            <el-button
+              size="mini"
+              type="text"
+              style="color: #F56C6C;"
+              @click="handleDelete(scope.row)"
+            >删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-dialog title="创建开发者密钥" :visible.sync="createDialogVisible" width="500px">
+      <el-form ref="createForm" :model="createForm" :rules="rules" label-width="110px">
+        <el-form-item label="应用名称" prop="appName">
+          <el-input v-model="createForm.appName" placeholder="例如:Python自动上传脚本" />
+          <el-form-item label="权限范围" prop="scopes">
+            <el-checkbox-group v-model="createForm.scopes">
+              <el-checkbox label="video:write">发布视频 (video:write)</el-checkbox>
+              <el-checkbox label="video:read">读取视频数据 (video:read)</el-checkbox>
+            </el-checkbox-group>
+          </el-form-item>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button size="small" @click="createDialogVisible = false">取 消</el-button>
+        <el-button type="primary" size="small" :loading="submitLoading" @click="submitCreate">确 定</el-button>
+      </div>
+    </el-dialog>
 
     <el-dialog
-      title="OAuth 应用"
-      append-to-body
-      :visible.sync="oauthDialog"
-      width="30%"
-      center
+      title="🔑 密钥创建成功(请立即复制保存)"
+      :visible.sync="resultDialogVisible"
+      width="550px"
+      :close-on-click-modal="false"
+      :close-on-press-escape="false"
+      :show-close="false"
     >
-      <el-form ref="form" :model="appForm">
-        <el-form-item label="应用名">
-          <el-input v-model="appForm.name" style="margin-left: 5px" />
-        </el-form-item>
-        <el-form-item label="主页 URL">
-          <el-input v-model="appForm.homeUrl" style="margin-left: 5px" />
-        </el-form-item>
-        <el-form-item label="应用描述">
-          <el-input v-model="appForm.description" style="margin-left: 5px" />
-        </el-form-item>
-        <el-form-item label="回调 URL">
-          <el-input v-model="appForm.callbackUrl" style="margin-left: 5px" />
+      <el-alert
+        title="以下 Secret Key 仅在创建时显示一次。如果您关闭此窗口或刷新页面,将永远无法再次查看该密钥!"
+        type="error"
+        :closable="false"
+        style="margin-bottom: 20px;"
+      />
+
+      <el-form label-position="top">
+        <el-form-item label="Access Key ID (AK)">
+          <el-input v-model="generatedResult.accessKey" readonly>
+            <el-button slot="append" icon="el-icon-document-copy" @click="copyText(generatedResult.accessKey)">复制</el-button>
+          </el-input>
         </el-form-item>
-        <el-form-item>
-          <el-button
-            type="primary"
-            @click="addApp"
-          >确定</el-button>
+        <el-form-item label="Secret Access Key (SK)">
+          <el-input v-model="generatedResult.secretKey" readonly type="password" show-password>
+            <el-button slot="append" icon="el-icon-document-copy" @click="copyText(generatedResult.secretKey)">复制</el-button>
+          </el-input>
         </el-form-item>
       </el-form>
+
+      <div slot="footer" class="dialog-footer">
+        <el-button type="danger" size="small" style="width: 100%;" @click="closeResultDialog">我已安全复制并保存密钥,关闭窗口</el-button>
+      </div>
     </el-dialog>
-  </el-main>
+  </div>
 </template>
 
 <script>
-import { createApp, getOAuth, getOAuthApps, getToken } from '@/api/account'
+import { getApiKeyList, createApiKey, disableApiKey, deleteApiKey } from '@/api/account'
 
 export default {
-  name: 'MyOAuth',
+  name: 'ApiKeyManager',
   data() {
     return {
-      dataList: [],
-      oauthDialog: false,
-      appForm: {
-        name: ''
+      loading: false,
+      submitLoading: false,
+      apiKeyList: [],
+
+      // 创建表单相关
+      createDialogVisible: false,
+      createForm: {
+        appName: '',
+        scopes: ['video:write'] // 默认勾选视频发布
+      },
+      rules: {
+        appName: [
+          { required: true, message: '请输入应用或脚本名称', trigger: 'blur' },
+          { min: 2, max: 30, message: '长度在 2 到 30 个字符', trigger: 'blur' }
+        ],
+        scopes: [
+          { type: 'array', required: true, message: '请至少选择一个权限范围', trigger: 'change' }
+        ]
+      },
+
+      // 结果展示相关
+      resultDialogVisible: false,
+      generatedResult: {
+        accessKey: '',
+        secretKey: ''
       }
     }
   },
   created() {
-    document.title = '我的 OAuth 应用'
-    getOAuthApps().then(resp => {
-      if (resp.code === 0) {
-        this.dataList = resp.data
-      }
-    })
+    this.fetchData()
   },
   methods: {
-    addAppDialog() {
-      this.oauthDialog = true
+    // 获取列表数据
+    async fetchData() {
+      this.loading = true
+      try {
+        const res = await getApiKeyList()
+        if (res.code === 0) {
+          this.apiKeyList = res.data
+        }
+      } catch (error) {
+        this.$message.error('获取密钥列表失败')
+      } finally {
+        this.loading = false
+      }
+    },
+
+    // 打开创建弹窗
+    showCreateDialog() {
+      this.createForm.appName = ''
+      this.createForm.scopes = ['video:write']
+      this.createDialogVisible = true
+      this.$nextTick(() => {
+        if (this.$refs.createForm) this.$refs.createForm.clearValidate()
+      })
     },
-    addApp() {
-      createApp(this.appForm).then(resp => {
-        if (resp.code !== 0) {
-          this.$notify.warning({
-            message: resp.msg,
-            type: 'warning',
-            duration: 3000
-          })
+
+    // 提交创建
+    submitCreate() {
+      this.$refs.createForm.validate(async(valid) => {
+        if (!valid) return
+        this.submitLoading = true
+        try {
+          const res = await createApiKey(this.createForm)
+          if (res.code === 200) {
+            this.createDialogVisible = false
+            // 将后端返回的明文 AK/SK 赋予结果变量
+            this.generatedResult.accessKey = res.data.accessKey
+            this.generatedResult.secretKey = res.data.secretKey
+            // 唤起不可盲退的“成果展示”弹窗
+            this.resultDialogVisible = true
+          }
+        } catch (error) {
+          this.$message.error('创建密钥失败')
+        } finally {
+          this.submitLoading = false
         }
       })
-      this.oauthDialog = false
+    },
+
+    // 关闭结果弹窗,并刷新列表
+    closeResultDialog() {
+      this.resultDialogVisible = false
+      this.generatedResult.accessKey = ''
+      this.generatedResult.secretKey = ''
+      this.fetchData() // 重新加载列表(此时列表中不再有SK明文)
+    },
+
+    // 禁用密钥
+    handleDisable(row) {
+      this.$confirm(`确定要禁用应用【${row.appName}】的 API Key 吗?禁用后相关脚本将立即失效且无法恢复。`, '安全警告', {
+        confirmButtonText: '确定禁用',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async() => {
+        const res = await disableApiKey(row.accessKey)
+        if (res.code === 200) {
+          this.$message.success('该密钥已成功禁用')
+          this.fetchData()
+        }
+      }).catch(() => {})
+    },
+
+    // 删除密钥
+    handleDelete(row) {
+      this.$confirm(`确定要彻底删除应用【${row.appName}】的 API Key 吗?该操作不可逆!`, '危险操作提示', {
+        confirmButtonText: '确定删除',
+        cancelButtonText: '取消',
+        type: 'error'
+      }).then(async() => {
+        const res = await deleteApiKey(row.accessKey)
+        if (res.code === 200) {
+          this.$message.success('密钥已删除')
+          this.fetchData()
+        }
+      }).catch(() => {})
+    },
+
+    // 辅助工具:切分逗号分隔的权限范围字符串
+    splitScopes(scopesStr) {
+      return scopesStr ? scopesStr.split(',') : []
+    },
+
+    // 辅助工具:原生浏览器文本一键复制
+    copyText(text) {
+      const input = document.createElement('input')
+      input.value = text
+      document.body.appendChild(input)
+      input.select()
+      try {
+        document.execCommand('copy')
+        this.$message({ message: '已复制到剪贴板', type: 'success', duration: 1500 })
+      } catch (err) {
+        this.$message.error('复制失败,请手动选择复制')
+      }
+      document.body.removeChild(input)
     }
   }
 }
 </script>
 
 <style scoped>
+.api-key-container {
+  padding: 20px;
+}
+.card-title {
+  font-size: 16px;
+  font-weight: bold;
+  color: #303133;
+}
+.dialog-footer {
+  text-align: right;
+}
 </style>