feat: 新增 API Key 模块

This commit is contained in:
click33
2025-04-04 23:36:59 +08:00
parent 8cff63b0fc
commit 601d8b1373
37 changed files with 2868 additions and 7 deletions

View File

@@ -11,6 +11,7 @@
<modules>
<module>sa-token-demo-alone-redis</module>
<module>sa-token-demo-alone-redis-cluster</module>
<module>sa-token-demo-apikey</module>
<module>sa-token-demo-beetl</module>
<module>sa-token-demo-bom-import</module>
<module>sa-token-demo-caffeine</module>

View File

@@ -0,0 +1,67 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-demo-apikey</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.14</version>
<relativePath/>
</parent>
<!-- 定义 Sa-Token 版本号 -->
<properties>
<sa-token.version>1.41.0</sa-token.version>
</properties>
<dependencies>
<!-- springboot依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档https://sa-token.cc/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>${sa-token.version}</version>
</dependency>
<!-- 提供Redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 热刷新 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>provided</scope>
</dependency>
<!-- @ConfigurationProperties -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,16 @@
package com.pj;
import cn.dev33.satoken.SaManager;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SaTokenApiKeyApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenApiKeyApplication.class, args);
System.out.println("\n启动成功Sa-Token 配置如下:" + SaManager.getConfig());
System.out.println("\n测试访问http://localhost:8081/index.html");
}
}

View File

@@ -0,0 +1,31 @@
package com.pj.mock;
import cn.dev33.satoken.apikey.loader.SaApiKeyDataLoader;
import cn.dev33.satoken.apikey.model.ApiKeyModel;
import org.springframework.beans.factory.annotation.Autowired;
/**
* API Key 数据加载器实现类 (从数据库查询)
*
* @author click33
* @since 2025/4/4
*/
//@Component // 打开此注解后springboot 会自动注入此组件,打开 Sa-Token API Key 模块的数据库模式
public class SaApiKeyDataLoaderImpl implements SaApiKeyDataLoader {
@Autowired
SaApiKeyMockMapper apiKeyMockMapper;
// 指定框架不再维护 API Key 索引信息,而是由我们手动从数据库维护
@Override
public Boolean getIsRecordIndex() {
return false;
}
// 根据 apiKey 从数据库获取 ApiKeyModel 信息 (实现此方法无需为数据做缓存处理,框架内部已包含缓存逻辑)
@Override
public ApiKeyModel getApiKeyModelFromDatabase(String apiKey) {
return apiKeyMockMapper.getApiKeyModel(apiKey);
}
}

View File

@@ -0,0 +1,42 @@
package com.pj.mock;
import cn.dev33.satoken.apikey.model.ApiKeyModel;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* 模拟数据库操作类
*
* @author click33
* @since 2025/4/4
*/
@Service
public class SaApiKeyMockMapper {
// 添加模拟测试数据
public static final Map<String, ApiKeyModel> map = new HashMap<>();
static {
ApiKeyModel ak1 = new ApiKeyModel();
ak1.setLoginId(10001); // 设置绑定的用户 id
ak1.setApiKey("AK-NAO6u57zbOWCmLaiVQuVW2tyt3rHpZrXkaQp"); // 设置 API Key 值
ak1.setTitle("test"); // 设置名称
ak1.setExpiresTime(System.currentTimeMillis() + 2592000); // 设置失效时间13位时间戳-1=永不失效
map.put(ak1.getApiKey(), ak1);
ApiKeyModel ak2 = new ApiKeyModel();
ak2.setLoginId(10001); // 设置绑定的用户 id
ak2.setApiKey("AK-NxcO63u57zbOWCmLaiVQuVWXssRwAxFcAxcFF"); // 设置 API Key 值
ak2.setTitle("commit2"); // 设置名称
ak1.addScope("commit", "pull"); // 设置权限范围
ak2.setExpiresTime(System.currentTimeMillis() + 2592000); // 设置失效时间13位时间戳-1=永不失效
map.put(ak2.getApiKey(), ak2);
}
// 返回指定 API Key 对应的 ApiKeyModel
public ApiKeyModel getApiKeyModel(String apiKey) {
return map.get(apiKey);
}
}

View File

@@ -0,0 +1,24 @@
package com.pj.satoken;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import cn.dev33.satoken.util.SaResult;
/**
* 全局异常处理
*/
@RestControllerAdvice
public class GlobalException {
// 全局异常拦截(拦截项目中的所有异常)
@ExceptionHandler
public SaResult handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) throws Exception {
e.printStackTrace();
return SaResult.error(e.getMessage());
}
}

View File

@@ -0,0 +1,76 @@
package com.pj.satoken;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaHttpMethod;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* [Sa-Token 权限认证] 配置类
* @author click33
*
*/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
/**
* 注册 Sa-Token 拦截器打开注解鉴权功能
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
/**
* 注册 [Sa-Token 全局过滤器]
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
// 指定 [拦截路由] 与 [放行路由]
.addInclude("/**")// .addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(obj -> {
// 输出 API 请求日志,方便调试代码
// SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());
})
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
e.printStackTrace();
return SaResult.error(e.getMessage());
})
// 前置函数:在每次认证函数之前执行
.setBeforeAuth(obj -> {
// ---------- 设置一些安全响应头 ----------
SaHolder.getResponse()
// 允许指定域访问跨域资源
.setHeader("Access-Control-Allow-Origin", "*")
// 允许所有请求方式
.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE")
// 有效时间
.setHeader("Access-Control-Max-Age", "3600")
// 允许的header参数
.setHeader("Access-Control-Allow-Headers", "*");
// 如果是预检请求,则立即返回到前端
SaRouter.match(SaHttpMethod.OPTIONS)
.free(r -> System.out.println("--------OPTIONS预检请求不做处理"))
.back();
})
;
}
}

View File

@@ -0,0 +1,65 @@
package com.pj.test;
import cn.dev33.satoken.apikey.SaApiKeyUtil;
import cn.dev33.satoken.apikey.model.ApiKeyModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* API Key 相关接口
*
* @author click33
*/
@RestController
public class ApiKeyController {
// 返回当前登录用户拥有的 ApiKey 列表
@RequestMapping("/myApiKeyList")
public SaResult myApiKeyList() {
List<ApiKeyModel> apiKeyList = SaApiKeyUtil.getApiKeyList(StpUtil.getLoginId());
return SaResult.data(apiKeyList);
}
// 创建一个新的 ApiKey并返回
@RequestMapping("/createApiKey")
public SaResult createApiKey() {
ApiKeyModel akModel = SaApiKeyUtil.createApiKeyModel(StpUtil.getLoginId()).setTitle("test");
SaApiKeyUtil.saveApiKey(akModel);
return SaResult.data(akModel);
}
// 修改 ApiKey
@RequestMapping("/updateApiKey")
public SaResult updateApiKey(ApiKeyModel akModel) {
// 先验证一下是否为本人的 ApiKey
SaApiKeyUtil.checkApiKeyLoginId(akModel.getApiKey(), StpUtil.getLoginId());
// 修改
ApiKeyModel akModel2 = SaApiKeyUtil.getApiKey(akModel.getApiKey());
akModel2.setTitle(akModel.getTitle());
akModel2.setExpiresTime(akModel.getExpiresTime());
akModel2.setIsValid(akModel.getIsValid());
akModel2.setScopes(akModel.getScopes());
SaApiKeyUtil.saveApiKey(akModel2);
return SaResult.ok();
}
// 删除 ApiKey
@RequestMapping("/deleteApiKey")
public SaResult deleteApiKey(String apiKey) {
SaApiKeyUtil.checkApiKeyLoginId(apiKey, StpUtil.getLoginId());
SaApiKeyUtil.deleteApiKey(apiKey);
return SaResult.ok();
}
// 删除当前用户所有 ApiKey
@RequestMapping("/deleteMyAllApiKey")
public SaResult deleteMyAllApiKey() {
SaApiKeyUtil.deleteApiKeyByLoginId(StpUtil.getLoginId());
return SaResult.ok();
}
}

View File

@@ -0,0 +1,55 @@
package com.pj.test;
import cn.dev33.satoken.annotation.SaCheckApiKey;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.apikey.SaApiKeyUtil;
import cn.dev33.satoken.apikey.model.ApiKeyModel;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* API Key 资源 相关接口
*
* @author click33
*/
@RestController
public class ApiKeyResourcesController {
// 必须携带有效的 ApiKey 才能访问
@SaCheckApiKey
@RequestMapping("/akRes1")
public SaResult akRes1() {
ApiKeyModel akModel = SaApiKeyUtil.currentApiKey();
System.out.println("当前 ApiKey: " + akModel);
return SaResult.ok("调用成功");
}
// 必须携带有效的 ApiKey ,且具有 userinfo 权限
@SaCheckApiKey(scope = "userinfo")
@RequestMapping("/akRes2")
public SaResult akRes2() {
ApiKeyModel akModel = SaApiKeyUtil.currentApiKey();
System.out.println("当前 ApiKey: " + akModel);
return SaResult.ok("调用成功");
}
// 必须携带有效的 ApiKey ,且同时具有 userinfo、chat 权限
@SaCheckApiKey(scope = {"userinfo", "chat"})
@RequestMapping("/akRes3")
public SaResult akRes3() {
ApiKeyModel akModel = SaApiKeyUtil.currentApiKey();
System.out.println("当前 ApiKey: " + akModel);
return SaResult.ok("调用成功");
}
// 必须携带有效的 ApiKey ,且具有 userinfo、chat 其中之一权限
@SaCheckApiKey(scope = {"userinfo", "chat"}, mode = SaMode.OR)
@RequestMapping("/akRes4")
public SaResult akRes4() {
ApiKeyModel akModel = SaApiKeyUtil.currentApiKey();
System.out.println("当前 ApiKey: " + akModel);
return SaResult.ok("调用成功");
}
}

View File

@@ -0,0 +1,37 @@
package com.pj.test;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 登录 Controller
*
* @author click33
*/
@RestController
public class LoginController {
// 登录
@RequestMapping("login")
public SaResult login(@RequestParam(defaultValue="10001") String id) {
StpUtil.login(id);
return SaResult.ok().set("satoken", StpUtil.getTokenValue());
}
// 查询当前登录人
@RequestMapping("getLoginId")
public SaResult getLoginId() {
return SaResult.data(StpUtil.getLoginId());
}
// 注销
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}

View File

@@ -0,0 +1,43 @@
# 端口
server:
port: 8081
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称 (同时也是 cookie 名称)
token-name: satoken
# 开启日志信息
is-log: true
# API Key 相关配置
api-key:
# API Key 前缀
prefix: AK-
# API Key 有效期,-1=永久有效默认30天 (修改此配置项不会影响到已创建的 API Key
timeout: 2592000
# 框架是否记录索引信息
is-record-index: true
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
password:
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0

View File

@@ -0,0 +1,137 @@
// 服务器接口主机地址
var baseUrl = "http://localhost:8081";
// 封装一下Ajax
function ajax(path, data, successFn, errorFn) {
console.log(baseUrl + path);
fetch(baseUrl + path, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'satoken': localStorage.getItem('satoken')
},
body: serializeToQueryString(data),
})
.then(response => response.json())
.then(res => {
console.log('返回数据:', res);
if(res.code == 200) {
successFn(res);
} else {
if(errorFn) {
errorFn(res);
} else {
showMsg('错误:' + res.msg);
}
}
})
.catch(error => {
console.error('请求失败:', error);
return alert("异常:" + JSON.stringify(error));
});
}
// ------------ 工具方法 ---------------
// 从url中查询到指定名称的参数值
function getParam(name, defaultValue) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] == name) {
return pair[1];
}
}
return (defaultValue == undefined ? null : defaultValue);
}
// 将 json 对象序列化为kv字符串形如name=Joh&age=30&active=true
function serializeToQueryString(obj) {
return Object.entries(obj)
.filter(([_, value]) => value != null) // 过滤 null 和 undefined
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
}
// 随机生成字符串
function randomString(len) {
len = len || 32;
var $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890';
var maxPos = $chars.length;
var str = '';
for (i = 0; i < len; i++) {
str += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return str;
}
// 带动画的弹出提示
function showMsg(message) {
const alertBox = document.createElement('div');
// 初始样式(包含隐藏状态)
Object.assign(alertBox.style, {
position: 'fixed',
left: '50%',
top: '50%',
transform: 'translate(-50%, -50%) scale(0.8) translateY(-30px)', // 初始缩放+位移
opacity: '0',
background: 'rgba(0, 0, 0, 0.85)',
color: 'white',
padding: '16px 32px',
borderRadius: '8px',
transition: 'all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55)', // 弹性动画曲线
pointerEvents: 'none',
whiteSpace: 'nowrap',
fontSize: '16px',
boxShadow: '0 4px 12px rgba(0,0,0,0.25)' // 添加投影增强立体感
});
alertBox.textContent = message;
document.body.appendChild(alertBox);
// 强制重绘确保动画触发
void alertBox.offsetHeight;
// 应用入场动画
Object.assign(alertBox.style, {
opacity: '1',
transform: 'translate(-50%, -50%) scale(1) translateY(-20px)'
});
// 自动消失逻辑
setTimeout(() => {
Object.assign(alertBox.style, {
opacity: '0',
transform: 'translate(-50%, -50%) scale(0.9) translateY(-20px)'
});
alertBox.addEventListener('transitionend', () => {
alertBox.remove();
}, {
once: true
});
}, 3000);
}
// 将日期格式化 yyyy-MM-dd HH:mm:ss
function formatDateTime(date) {
date = new Date(date);
// 补零函数
const pad = (n, len) => n.toString().padStart(len, '0');
// 分解时间组件
const year = date.getFullYear();
const month = pad(date.getMonth() + 1, 2); // 0-11 → 1-12
const day = pad(date.getDate(), 2);
const hours = pad(date.getHours(), 2); // 24小时制
const minutes = pad(date.getMinutes(), 2);
const seconds = pad(date.getSeconds(), 2);
const milliseconds = pad(date.getMilliseconds(), 3);
// 拼接格式
// return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${milliseconds}`;
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}

View File

@@ -0,0 +1,197 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body{background-color: #EFF6FF;}
td{padding: 5px 10px;}
button{cursor: pointer;}
table{margin-top: 10px; width: 100%;}
[name=title]{width: 100px;}
.change-tr{background-color: #F5E5F5 ;}
.remark{margin-left: 10px; color: #999;}
</style>
</head>
<body>
<div style="width: 1200px; margin: auto;">
<h1>Sa-Token - API Key 测试页</h1>
<h2>登录</h2>
<div>当前登录人:<b class="curr-uid" style="color: green"></b></div>
<span>输入账号 id 登录:</span>
<input name="loginId" />
<button onclick="doLogin()">登录</button>
<button onclick="doLogout()()">注销</button>
<h2>API Key 列表</h2>
<button onclick="createApiKey()">+ 创建 API Key</button>
<table cellspacing="0" border="1">
<tr>
<th>名称</th>
<th style="width: 435px;">API Key</th>
<th>权限(多个用逗号隔开)</th>
<th style="width: 190px;">过期时间</th>
<th style="width: 80px;">是否生效</th>
<th style="width: 170px;">操作</th>
</tr>
<tbody class="ak-tbody">
<!-- <tr class="ak-xxxx">
<td><input name="title" value="xx" /></td>
<td>AK-EG9BKM4bel7OqRoixNvSQ1a6DYusNfEXDjPr</td>
<td><input name="scopes" value="aaa" /></td>
<td><input name="expiresTime" value="2020-02-02 01:50:20" type="datetime-local" /></td>
<td>
<label><input name="isValid" checked type="checkbox" />生效</label>
</td>
<td>
<button onclick="updateApiKey('xxx')">修改</button>
<button onclick="useApiKey('xxx')">使用</button>
<button onclick="deleteApiKey('xxx')">删除</button>
</td>
</tr> -->
</tbody>
</table>
<h2>调用 API</h2>
<div style="line-height: 30px;">
<span>使用的 API Key</span>
<input name="api-key" style="width: 600px;"/> <br>
<button onclick="callAPI('/akRes1')">调用接口 1 </button> <span class="remark">需要正确的 API Key</span> <br>
<button onclick="callAPI('/akRes2')">调用接口 2 </button> <span class="remark">需要具备 Scope: userinfo</span> <br>
<button onclick="callAPI('/akRes3')">调用接口 3 </button> <span class="remark">需要具备 Scope: userinfo,chat (需要全部具备)</span> <br>
<button onclick="callAPI('/akRes4')">调用接口 4 </button> <span class="remark">需要具备 Scope: userinfo,chat (具备其一即可)</span> <br>
</div>
<div style="height: 200px;"></div>
</div>
<script src="common.js"></script>
<script>
// 登录
function doLogin() {
var loginId = document.querySelector("[name=loginId]").value;
if (loginId === "") {
return alert("请输入账号 id");
}
ajax("/login", {id: loginId}, function (res) {
localStorage.setItem("satoken", res.satoken);
showMsg('登录成功');
setTimeout(function(){
location.reload();
}, 1000);
})
}
// 查询当前登录人
function getLoginInfo() {
ajax("/getLoginId", {}, function (res) {
document.querySelector(".curr-uid").innerHTML = res.data;
document.querySelector('[name=loginId]').value = res.data;
myApiKeyList();
}, function(){
document.querySelector(".curr-uid").innerHTML = '未登录';
document.querySelector('[name=loginId]').value = '10001';
})
}
getLoginInfo();
// 注销登录
function doLogout() {
ajax("/logout", {}, function (res) {
showMsg('注销成功');
setTimeout(function(){
location.reload();
}, 1000)
})
}
</script>
<script>
// 渲染一个 API Key 对象到表格
function renderApiKey(ak) {
const trDom = `
<tr class="ak-${ak.apiKey}">
<td><input name="title" value="${ak.title}" oninput="changeTr('${ak.apiKey}')"/></td>
<td>${ak.apiKey}</td>
<td><input name="scopes" value="${ak.scopes.join(',')}" oninput="changeTr('${ak.apiKey}')" /></td>
<td><input name="expiresTime" value="${formatDateTime(ak.expiresTime)}" type="datetime-local" oninput="changeTr('${ak.apiKey}')"/></td>
<td>
<label><input name="isValid" ${ak.isValid ? 'checked' : ''} type="checkbox" oninput="changeTr('${ak.apiKey}')"/>生效</label>
</td>
<td>
<button onclick="updateApiKey('${ak.apiKey}')">修改</button>
<button onclick="useApiKey('${ak.apiKey}')">使用</button>
<button onclick="deleteApiKey('${ak.apiKey}')">删除</button>
</td>
</tr>
`;
document.querySelector('.ak-tbody').innerHTML = document.querySelector('.ak-tbody').innerHTML + trDom;
}
// 查询当前所有 API Key
function myApiKeyList() {
ajax("/myApiKeyList", {}, function (res) {
res.data.forEach(function(item){
renderApiKey(item);
})
})
}
// 创建 ApiKey
function createApiKey() {
if(document.querySelector(".curr-uid").innerHTML === '未登录') {
return alert('请先登录');
}
ajax("/createApiKey", {}, function (res) {
renderApiKey(res.data);
showMsg('创建成功');
})
}
// 使用
function useApiKey(apiKey){
document.querySelector('[name=api-key]').value = apiKey;
showMsg('已填充至输入框,请调用接口');
}
// 修改
function updateApiKey(apiKey) {
const tr = document.querySelector(".ak-" + apiKey);
const data = {
apiKey: apiKey,
title: tr.querySelector('[name=title]').value,
scopes: tr.querySelector('[name=scopes]').value,
expiresTime: new Date(tr.querySelector('[name=expiresTime]').value).getTime(),
isValid: tr.querySelector('[name=isValid]').checked,
}
ajax("/updateApiKey", data, function (res) {
showMsg('修改成功');
tr.classList.remove('change-tr');
})
}
// 删除
function deleteApiKey(apiKey) {
ajax("/deleteApiKey", {apiKey: apiKey}, function (res) {
showMsg('删除成功');
const tr = document.querySelector(".ak-" + apiKey);
tr.remove();
})
}
// 指定行的输入框变动
function changeTr(apiKey) {
const tr = document.querySelector(".ak-" + apiKey);
tr.classList.add('change-tr');
}
</script>
<script>
// 调用指定接口
function callAPI(apiPath) {
const apiKey = document.querySelector('[name=api-key]').value;
if(!apiKey) {
return showMsg('请先填写 API Key')
}
ajax(apiPath, {apikey: apiKey}, function (res) {
showMsg(res.msg);
})
}
</script>
</body>
</html>