leesam
2024-03-19 76208975bffec39eb62f8599a68d583a5cb6da18
[add]支持其他平台通过ApiKey调用系统相关接口
16个文件已修改
8个文件已添加
1291 ■■■■■ 已修改文件
pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java 106 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IUserApiKeyService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/IUserService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/UserApiKeyServiceImpl.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/UserApiKeyMapper.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/storager/dao/dto/UserApiKey.java 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserApiKeyController.java 251 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-docker.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/config/index.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/UserApiKeyManager.vue 296 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/UserManager.vue 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/addUserApiKey.vue 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/components/dialog/remarkUserApiKey.vue 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
web_src/src/router/index.js 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
打包/config/wvp-application.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
数据库/2.7.0/初始化-mysql-2.7.0.sql 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
数据库/2.7.0/初始化-postgresql-kingbase-2.7.0.sql 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
数据库/初始化-mysql.sql 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
数据库/初始化-postgresql-kingbase.sql 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -101,6 +101,10 @@
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
src/main/java/com/genersoft/iot/vmp/VManageBootstrap.java
@@ -9,6 +9,7 @@
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableScheduling;
@@ -24,6 +25,7 @@
@ServletComponentScan("com.genersoft.iot.vmp.conf")
@SpringBootApplication
@EnableScheduling
@EnableCaching
public class VManageBootstrap extends SpringBootServletInitializer {
    private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class);
src/main/java/com/genersoft/iot/vmp/conf/security/JwtAuthenticationFilter.java
@@ -51,8 +51,11 @@
        if (StringUtils.isBlank(jwt)) {
            jwt = request.getParameter(JwtUtils.getHeader());
            if (StringUtils.isBlank(jwt)) {
                chain.doFilter(request, response);
                return;
                jwt = request.getHeader(JwtUtils.getApiKeyHeader());
                if (StringUtils.isBlank(jwt)) {
                    chain.doFilter(request, response);
                    return;
                }
            }
        }
src/main/java/com/genersoft/iot/vmp/conf/security/JwtUtils.java
@@ -1,8 +1,12 @@
package com.genersoft.iot.vmp.conf.security;
import com.genersoft.iot.vmp.conf.security.dto.JwtUser;
import com.genersoft.iot.vmp.service.IUserApiKeyService;
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.jwk.JsonWebKeySet;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
@@ -20,8 +24,18 @@
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Map;
@Component
public class JwtUtils implements InitializingBean {
@@ -30,6 +44,8 @@
    public static final String HEADER = "access-token";
    public static final String API_KEY_HEADER = "api-key";
    private static final String AUDIENCE = "Audience";
    private static final String keyId = "3e79646c4dbc408383a9eed09f2b85ae";
@@ -37,15 +53,26 @@
    /**
     * token过期时间(分钟)
     */
    public static final long expirationTime = 30 * 24 * 60;
    public static final long EXPIRATION_TIME = 30 * 24 * 60;
    private static RsaJsonWebKey rsaJsonWebKey;
    private static IUserService userService;
    private static IUserApiKeyService userApiKeyService;
    public static String getApiKeyHeader() {
        return API_KEY_HEADER;
    }
    @Resource
    public void setUserService(IUserService userService) {
        JwtUtils.userService = userService;
    }
    @Resource
    public void setUserApiKeyService(IUserApiKeyService userApiKeyService) {
        JwtUtils.userApiKeyService = userApiKeyService;
    }
    @Override
@@ -62,14 +89,38 @@
     * @throws JoseException JoseException
     */
    private RsaJsonWebKey generateRsaJsonWebKey() throws JoseException {
        // ç”Ÿæˆä¸€ä¸ªRSA密钥对,该密钥对将用于JWT的签名和验证,包装在JWK中
        RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
        // ç»™JWK一个密钥ID
        rsaJsonWebKey.setKeyId(keyId);
        RsaJsonWebKey rsaJsonWebKey = null;
        try {
            URL url = getClass().getClassLoader().getResource("jwk.json");
            if (url != null) {
                URI uri = url.toURI();
                Path path = Paths.get(uri);
                if (Files.exists(path)) {
                    byte[] allBytes = Files.readAllBytes(path);
                    String jwkJson = new String(allBytes, StandardCharsets.UTF_8);
                    final JsonWebKeySet jsonWebKeySet = new JsonWebKeySet(jwkJson);
                    List<JsonWebKey> jsonWebKeys = jsonWebKeySet.getJsonWebKeys();
                    if (!jsonWebKeys.isEmpty()) {
                        JsonWebKey jsonWebKey = jsonWebKeys.get(0);
                        if (jsonWebKey instanceof RsaJsonWebKey) {
                            rsaJsonWebKey = (RsaJsonWebKey) jsonWebKey;
                        }
                    }
                }
            }
        } catch (URISyntaxException | IOException e) {
            // ignored
        }
        if (rsaJsonWebKey == null) {
            // ç”Ÿæˆä¸€ä¸ªRSA密钥对,该密钥对将用于JWT的签名和验证,包装在JWK中
            rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
            // ç»™JWK一个密钥ID
            rsaJsonWebKey.setKeyId(keyId);
        }
        return rsaJsonWebKey;
    }
    public static String createToken(String username) {
    public static String createToken(String username, Long expirationTime, Map<String, Object> extra) {
        try {
            /*
             * â€œiss” (issuer)  å‘行人
@@ -83,13 +134,17 @@
            claims.setGeneratedJwtId();
            claims.setIssuedAtToNow();
            // ä»¤ç‰Œå°†è¿‡æœŸçš„æ—¶é—´ åˆ†é’Ÿ
            claims.setExpirationTimeMinutesInTheFuture(expirationTime);
            if (expirationTime != null) {
                claims.setExpirationTimeMinutesInTheFuture(expirationTime);
            }
            claims.setNotBeforeMinutesInThePast(0);
            claims.setSubject("login");
            claims.setAudience(AUDIENCE);
            //添加自定义参数,必须是字符串类型
            claims.setClaim("userName", username);
            if (extra != null) {
                extra.forEach(claims::setClaim);
            }
            //jws
            JsonWebSignature jws = new JsonWebSignature();
            //签名算法RS256
@@ -104,8 +159,15 @@
        } catch (JoseException e) {
            logger.error("[Token生成失败]: {}", e.getMessage());
        }
        return null;
    }
    public static String createToken(String username, Long expirationTime) {
        return createToken(username, expirationTime, null);
    }
    public static String createToken(String username) {
        return createToken(username, EXPIRATION_TIME);
    }
    public static String getHeader() {
@@ -118,8 +180,8 @@
        try {
            JwtConsumer consumer = new JwtConsumerBuilder()
                    .setRequireExpirationTime()
                    .setMaxFutureValidityInMinutes(5256000)
                    //.setRequireExpirationTime()
                    //.setMaxFutureValidityInMinutes(5256000)
                    .setAllowedClockSkewInSeconds(30)
                    .setRequireSubject()
                    //.setExpectedIssuer("")
@@ -129,15 +191,27 @@
            JwtClaims claims = consumer.processToClaims(token);
            NumericDate expirationTime = claims.getExpirationTime();
            // åˆ¤æ–­æ˜¯å¦å³å°†è¿‡æœŸ, é»˜è®¤å‰©ä½™æ—¶é—´å°äºŽ5分钟未即将过期
            // å‰©ä½™æ—¶é—´ ï¼ˆç§’)
            long timeRemaining = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)) - expirationTime.getValue();
            if (timeRemaining < 5 * 60) {
                jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON);
            if (expirationTime != null) {
                // åˆ¤æ–­æ˜¯å¦å³å°†è¿‡æœŸ, é»˜è®¤å‰©ä½™æ—¶é—´å°äºŽ5分钟未即将过期
                // å‰©ä½™æ—¶é—´ ï¼ˆç§’)
                long timeRemaining = LocalDateTime.now().toEpochSecond(ZoneOffset.ofHours(8)) - expirationTime.getValue();
                if (timeRemaining < 5 * 60) {
                    jwtUser.setStatus(JwtUser.TokenStatus.EXPIRING_SOON);
                } else {
                    jwtUser.setStatus(JwtUser.TokenStatus.NORMAL);
                }
            } else {
                jwtUser.setStatus(JwtUser.TokenStatus.NORMAL);
            }
            Long apiKeyId = claims.getClaimValue("apiKeyId", Long.class);
            if (apiKeyId != null) {
                UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(apiKeyId.intValue());
                if (userApiKey == null || !userApiKey.isEnable()) {
                    jwtUser.setStatus(JwtUser.TokenStatus.EXPIRED);
                }
            }
            String username = (String) claims.getClaimValue("userName");
            User user = userService.getUserByUsername(username);
src/main/java/com/genersoft/iot/vmp/service/IUserApiKeyService.java
New file
@@ -0,0 +1,25 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey;
import com.github.pagehelper.PageInfo;
public interface IUserApiKeyService {
    int addApiKey(UserApiKey userApiKey);
    boolean isApiKeyExists(String apiKey);
    PageInfo<UserApiKey> getUserApiKeys(int page, int count);
    int enable(Integer id);
    int disable(Integer id);
    int remark(Integer id, String remark);
    int delete(Integer id);
    UserApiKey getUserApiKeyById(Integer id);
    int reset(Integer id, String apiKey);
}
src/main/java/com/genersoft/iot/vmp/service/IUserService.java
@@ -11,6 +11,8 @@
    boolean changePassword(int id, String password);
    User getUserById(int id);
    User getUserByUsername(String username);
    int addUser(User user);
src/main/java/com/genersoft/iot/vmp/service/impl/UserApiKeyServiceImpl.java
New file
@@ -0,0 +1,80 @@
package com.genersoft.iot.vmp.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.genersoft.iot.vmp.service.IUserApiKeyService;
import com.genersoft.iot.vmp.storager.dao.UserApiKeyMapper;
import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@DS("master")
public class UserApiKeyServiceImpl implements IUserApiKeyService {
    @Autowired
    UserApiKeyMapper userApiKeyMapper;
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Override
    public int addApiKey(UserApiKey userApiKey) {
        return userApiKeyMapper.add(userApiKey);
    }
    @Override
    public boolean isApiKeyExists(String apiKey) {
        return userApiKeyMapper.isApiKeyExists(apiKey);
    }
    @Override
    public PageInfo<UserApiKey> getUserApiKeys(int page, int count) {
        PageHelper.startPage(page, count);
        List<UserApiKey> userApiKeys = userApiKeyMapper.getUserApiKeys();
        return new PageInfo<>(userApiKeys);
    }
    @Cacheable(cacheNames = "userApiKey", key = "#id", sync = true)
    @Override
    public UserApiKey getUserApiKeyById(Integer id) {
        return userApiKeyMapper.selectById(id);
    }
    @CacheEvict(cacheNames = "userApiKey", key = "#id")
    @Override
    public int enable(Integer id) {
        return userApiKeyMapper.enable(id);
    }
    @CacheEvict(cacheNames = "userApiKey", key = "#id")
    @Override
    public int disable(Integer id) {
        return userApiKeyMapper.disable(id);
    }
    @CacheEvict(cacheNames = "userApiKey", key = "#id")
    @Override
    public int remark(Integer id, String remark) {
        return userApiKeyMapper.remark(id, remark);
    }
    @CacheEvict(cacheNames = "userApiKey", key = "#id")
    @Override
    public int delete(Integer id) {
        return userApiKeyMapper.delete(id);
    }
    @CacheEvict(cacheNames = "userApiKey", key = "#id")
    @Override
    public int reset(Integer id, String apiKey) {
        return userApiKeyMapper.apiKey(id, apiKey);
    }
}
src/main/java/com/genersoft/iot/vmp/service/impl/UserServiceImpl.java
@@ -32,6 +32,11 @@
    }
    @Override
    public User getUserById(int id) {
        return userMapper.selectById(id);
    }
    @Override
    public User getUserByUsername(String username) {
        return userMapper.getUserByUsername(username);
    }
src/main/java/com/genersoft/iot/vmp/storager/dao/UserApiKeyMapper.java
New file
@@ -0,0 +1,60 @@
package com.genersoft.iot.vmp.storager.dao;
import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
import java.util.List;
@Mapper
@Repository
public interface UserApiKeyMapper {
    @SelectKey(statement = "SELECT LAST_INSERT_ID() AS id", keyProperty = "id", before = false, resultType = Integer.class)
    @Insert("INSERT INTO wvp_user_api_key (user_id, app, api_key, expired_at, remark, enable, create_time, update_time) VALUES" +
            "(#{userId}, #{app}, #{apiKey}, #{expiredAt}, #{remark}, #{enable}, #{createTime}, #{updateTime})")
    int add(UserApiKey userApiKey);
    @Update(value = {"<script>" +
            "UPDATE wvp_user_api_key " +
            "SET update_time = #{updateTime} " +
            "<if test=\"app != null\">, app = #{app}</if>" +
            "<if test=\"apiKey != null\">, api_key = #{apiKey}</if>" +
            "<if test=\"expiredAt != null\">, expired_at = #{expiredAt}</if>" +
            "<if test=\"remark != null\">, username = #{remark}</if>" +
            "<if test=\"enable != null\">, enable = #{enable}</if>" +
            "WHERE id = #{id}" +
            " </script>"})
    int update(UserApiKey userApiKey);
    @Update("UPDATE wvp_user_api_key SET enable = true WHERE id = #{id}")
    int enable(@Param("id") int id);
    @Update("UPDATE wvp_user_api_key SET enable = false WHERE id = #{id}")
    int disable(@Param("id") int id);
    @Update("UPDATE wvp_user_api_key SET api_key = #{apiKey} WHERE id = #{id}")
    int apiKey(@Param("id") int id, @Param("apiKey") String apiKey);
    @Update("UPDATE wvp_user_api_key SET remark = #{remark} WHERE id = #{id}")
    int remark(@Param("id") int id, @Param("remark") String remark);
    @Delete("DELETE FROM wvp_user_api_key WHERE id = #{id}")
    int delete(@Param("id") int id);
    @Select("SELECT uak.id, uak.user_id, uak.app, uak.api_key, uak.expired_at, uak.remark, uak.enable, uak.create_time, uak.update_time, u.username AS username FROM wvp_user_api_key uak LEFT JOIN wvp_user u on u.id = uak.user_id WHERE uak.id = #{id}")
    UserApiKey selectById(@Param("id") int id);
    @Select("SELECT uak.id, uak.user_id, uak.app, uak.api_key, uak.expired_at, uak.remark, uak.enable, uak.create_time, uak.update_time, u.username AS username FROM wvp_user_api_key uak LEFT JOIN wvp_user u on u.id = uak.user_id WHERE uak.api_key = #{apiKey}")
    UserApiKey selectByApiKey(@Param("apiKey") String apiKey);
    @Select("SELECT uak.id, uak.user_id, uak.app, uak.api_key, uak.expired_at, uak.remark, uak.enable, uak.create_time, uak.update_time, u.username AS username FROM wvp_user_api_key uak LEFT JOIN wvp_user u on u.id = uak.user_id")
    List<UserApiKey> selectAll();
    @Select("SELECT uak.id, uak.user_id, uak.app, uak.api_key, uak.expired_at, uak.remark, uak.enable, uak.create_time, uak.update_time, u.username AS username FROM wvp_user_api_key uak LEFT JOIN wvp_user u on u.id = uak.user_id")
    List<UserApiKey> getUserApiKeys();
    @Select("SELECT COUNT(0) FROM wvp_user_api_key WHERE api_key = #{apiKey}")
    boolean isApiKeyExists(@Param("apiKey") String apiKey);
}
src/main/java/com/genersoft/iot/vmp/storager/dao/dto/UserApiKey.java
New file
@@ -0,0 +1,151 @@
package com.genersoft.iot.vmp.storager.dao.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import java.io.Serializable;
/**
 * ç”¨æˆ·ä¿¡æ¯
 */
@Schema(description = "用户ApiKey信息")
public class UserApiKey implements Serializable {
    /**
     * Id
     */
    @Schema(description = "Id")
    private int id;
    /**
     * ç”¨æˆ·Id
     */
    @Schema(description = "用户Id")
    private int userId;
    /**
     * åº”用名
     */
    @Schema(description = "应用名")
    private String app;
    /**
     * ApiKey
     */
    @Schema(description = "ApiKey")
    private String apiKey;
    /**
     * è¿‡æœŸæ—¶é—´ï¼ˆnull=永不过期)
     */
    @Schema(description = "过期时间(null=永不过期)")
    private String expiredAt;
    /**
     * å¤‡æ³¨ä¿¡æ¯
     */
    @Schema(description = "备注信息")
    private String remark;
    /**
     * æ˜¯å¦å¯ç”¨
     */
    @Schema(description = "是否启用")
    private boolean enable;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description = "创建时间")
    private String createTime;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @Schema(description = "更新时间")
    private String updateTime;
    /**
     * ç”¨æˆ·å
     */
    private String username;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getUserId() {
        return userId;
    }
    public void setUserId(int userId) {
        this.userId = userId;
    }
    public String getApp() {
        return app;
    }
    public void setApp(String app) {
        this.app = app;
    }
    public String getApiKey() {
        return apiKey;
    }
    public void setApiKey(String apiKey) {
        this.apiKey = apiKey;
    }
    public String getExpiredAt() {
        return expiredAt;
    }
    public void setExpiredAt(String expiredAt) {
        this.expiredAt = expiredAt;
    }
    public String getRemark() {
        return remark;
    }
    public void setRemark(String remark) {
        this.remark = remark;
    }
    public boolean isEnable() {
        return enable;
    }
    public void setEnable(boolean enable) {
        this.enable = enable;
    }
    public String getCreateTime() {
        return createTime;
    }
    public void setCreateTime(String createTime) {
        this.createTime = createTime;
    }
    public String getUpdateTime() {
        return updateTime;
    }
    public void setUpdateTime(String updateTime) {
        this.updateTime = updateTime;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
}
src/main/java/com/genersoft/iot/vmp/vmanager/user/UserApiKeyController.java
New file
@@ -0,0 +1,251 @@
package com.genersoft.iot.vmp.vmanager.user;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.security.JwtUtils;
import com.genersoft.iot.vmp.conf.security.SecurityUtils;
import com.genersoft.iot.vmp.service.IUserApiKeyService;
import com.genersoft.iot.vmp.service.IUserService;
import com.genersoft.iot.vmp.storager.dao.dto.User;
import com.genersoft.iot.vmp.storager.dao.dto.UserApiKey;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.github.pagehelper.PageInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@Tag(name = "用户ApiKey管理")
@RestController
@RequestMapping("/api/userApiKey")
public class UserApiKeyController {
    public static final int EXPIRATION_TIME = Integer.MAX_VALUE;
    @Autowired
    private IUserService userService;
    @Autowired
    private IUserApiKeyService userApiKeyService;
    /**
     * æ·»åŠ ç”¨æˆ·ApiKey
     *
     * @param userId
     * @param app
     * @param remark
     * @param expiresAt
     * @param enable
     */
    @PostMapping("/add")
    @Operation(summary = "添加用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER))
    @Parameter(name = "userId", description = "用户Id", required = true)
    @Parameter(name = "app", description = "应用名称", required = false)
    @Parameter(name = "remark", description = "备注信息", required = false)
    @Parameter(name = "expiredAt", description = "过期时间(不传代表永不过期)", required = false)
    @Transactional
    public synchronized void add(
            @RequestParam(required = true) int userId,
            @RequestParam(required = false) String app,
            @RequestParam(required = false) String remark,
            @RequestParam(required = false) String expiresAt,
            @RequestParam(required = false) Boolean enable
    ) {
        User user = userService.getUserById(userId);
        if (user == null) {
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户不存在");
        }
        Long expirationTime = null;
        if (expiresAt != null) {
            long timestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(expiresAt);
            expirationTime = (timestamp - System.currentTimeMillis()) / (60 * 1000);
            if (expirationTime < 0) {
                throw new ControllerException(ErrorCode.ERROR400.getCode(), "过期时间不能早于当前时间");
            }
        }
        UserApiKey userApiKey = new UserApiKey();
        userApiKey.setUserId(userId);
        userApiKey.setApp(app);
        userApiKey.setApiKey(null);
        userApiKey.setRemark(remark);
        userApiKey.setExpiredAt(expiresAt);
        userApiKey.setEnable(enable != null ? enable : false);
        userApiKey.setCreateTime(DateUtil.getNow());
        userApiKey.setUpdateTime(DateUtil.getNow());
        int addResult = userApiKeyService.addApiKey(userApiKey);
        if (addResult <= 0) {
            throw new ControllerException(ErrorCode.ERROR100);
        }
        String apiKey;
        do {
            Map<String, Object> extra = new HashMap<>(1);
            extra.put("apiKeyId", userApiKey.getId());
            apiKey = JwtUtils.createToken(user.getUsername(), expirationTime, extra);
        } while (userApiKeyService.isApiKeyExists(apiKey));
        int resetResult = userApiKeyService.reset(userApiKey.getId(), apiKey);
        if (resetResult <= 0) {
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
    /**
     * åˆ†é¡µæŸ¥è¯¢ApiKey
     *
     * @param page  å½“前页
     * @param count æ¯é¡µæŸ¥è¯¢æ•°é‡
     * @return åˆ†é¡µApiKey列表
     */
    @GetMapping("/userApiKeys")
    @Operation(summary = "分页查询用户", security = @SecurityRequirement(name = JwtUtils.HEADER))
    @Parameter(name = "page", description = "当前页", required = true)
    @Parameter(name = "count", description = "每页查询数量", required = true)
    @Transactional
    public PageInfo<UserApiKey> userApiKeys(@RequestParam(required = true) int page, @RequestParam(required = true) int count) {
        return userApiKeyService.getUserApiKeys(page, count);
    }
    @PostMapping("/enable")
    @Operation(summary = "启用用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER))
    @Parameter(name = "id", description = "用户ApiKeyId", required = true)
    @Transactional
    public void enable(@RequestParam(required = true) Integer id) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·id
        int currenRoleId = SecurityUtils.getUserInfo().getRole().getId();
        if (currenRoleId != 1) {
            // åªç”¨è§’色id为1才可以管理UserApiKey
            throw new ControllerException(ErrorCode.ERROR403);
        }
        UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id);
        if (userApiKey == null) {
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在");
        }
        int enableResult = userApiKeyService.enable(id);
        if (enableResult <= 0) {
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
    @PostMapping("/disable")
    @Operation(summary = "停用用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER))
    @Parameter(name = "id", description = "用户ApiKeyId", required = true)
    @Transactional
    public void disable(@RequestParam(required = true) Integer id) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·id
        int currenRoleId = SecurityUtils.getUserInfo().getRole().getId();
        if (currenRoleId != 1) {
            // åªç”¨è§’色id为1才可以管理UserApiKey
            throw new ControllerException(ErrorCode.ERROR403);
        }
        UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id);
        if (userApiKey == null) {
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在");
        }
        int disableResult = userApiKeyService.disable(id);
        if (disableResult <= 0) {
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
    @PostMapping("/reset")
    @Operation(summary = "重置用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER))
    @Parameter(name = "id", description = "用户ApiKeyId", required = true)
    @Transactional
    public void reset(@RequestParam(required = true) Integer id) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·id
        int currenRoleId = SecurityUtils.getUserInfo().getRole().getId();
        if (currenRoleId != 1) {
            // åªç”¨è§’色id为1才可以管理UserApiKey
            throw new ControllerException(ErrorCode.ERROR403);
        }
        UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id);
        if (userApiKey == null) {
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在");
        }
        User user = userService.getUserById(userApiKey.getUserId());
        if (user == null) {
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "用户不存在");
        }
        Long expirationTime = null;
        if (userApiKey.getExpiredAt() != null) {
            long timestamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestampMs(userApiKey.getExpiredAt());
            expirationTime = (timestamp - System.currentTimeMillis()) / (60 * 1000);
            if (expirationTime < 0) {
                throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey已失效");
            }
        }
        String apiKey;
        do {
            Map<String, Object> extra = new HashMap<>(1);
            extra.put("apiKeyId", userApiKey.getId());
            apiKey = JwtUtils.createToken(user.getUsername(), expirationTime, extra);
        } while (userApiKeyService.isApiKeyExists(apiKey));
        int resetResult = userApiKeyService.reset(id, apiKey);
        if (resetResult <= 0) {
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
    @PostMapping("/remark")
    @Operation(summary = "备注用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER))
    @Parameter(name = "id", description = "用户ApiKeyId", required = true)
    @Parameter(name = "remark", description = "用户ApiKey备注", required = false)
    @Transactional
    public void remark(@RequestParam(required = true) Integer id, @RequestParam(required = false) String remark) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·id
        int currenRoleId = SecurityUtils.getUserInfo().getRole().getId();
        if (currenRoleId != 1) {
            // åªç”¨è§’色id为1才可以管理UserApiKey
            throw new ControllerException(ErrorCode.ERROR403);
        }
        UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id);
        if (userApiKey == null) {
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在");
        }
        int remarkResult = userApiKeyService.remark(id, remark);
        if (remarkResult <= 0) {
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除用户ApiKey", security = @SecurityRequirement(name = JwtUtils.HEADER))
    @Parameter(name = "id", description = "用户ApiKeyId", required = true)
    @Transactional
    public void delete(@RequestParam(required = true) Integer id) {
        // èŽ·å–å½“å‰ç™»å½•ç”¨æˆ·id
        int currenRoleId = SecurityUtils.getUserInfo().getRole().getId();
        if (currenRoleId != 1) {
            // åªç”¨è§’色id为1才可以管理UserApiKey
            throw new ControllerException(ErrorCode.ERROR403);
        }
        UserApiKey userApiKey = userApiKeyService.getUserApiKeyById(id);
        if (userApiKey == null) {
            throw new ControllerException(ErrorCode.ERROR400.getCode(), "ApiKey不存在");
        }
        int deleteResult = userApiKeyService.delete(id);
        if (deleteResult <= 0) {
            throw new ControllerException(ErrorCode.ERROR100);
        }
    }
}
src/main/resources/application-dev.yml
@@ -10,6 +10,8 @@
    multipart:
      max-file-size: 10MB
      max-request-size: 100MB
  cache:
    type: redis
  # REDIS数据库配置
  redis:
    # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
src/main/resources/application-docker.yml
@@ -4,6 +4,8 @@
        multipart:
            max-file-size: 10MB
            max-request-size: 100MB
    cache:
        type: redis
    # REDIS数据库配置
    redis:
        # [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1
web_src/config/index.js
@@ -12,7 +12,7 @@
    assetsPublicPath: '/',
    proxyTable: {
      '/debug': {
        target: 'http://127.0.0.1:18082',
        target: 'http://127.0.0.1:8080',
        changeOrigin: true,
        pathRewrite: {
          '^/debug': '/'
web_src/src/components/UserApiKeyManager.vue
New file
@@ -0,0 +1,296 @@
<template>
  <div id="app" style="width: 100%">
    <div class="page-header" style="margin-bottom: 0">
      <div class="page-title">
        <el-page-header @back="goBack" content="ApiKey列表"></el-page-header>
      </div>
      <div class="page-header-btn">
        <el-button icon="el-icon-plus" size="mini" style="margin-right: 1rem;" type="primary" @click="addUserApiKey">
          æ·»åŠ ApiKey
        </el-button>
      </div>
    </div>
    <!--ApiKey列表-->
    <el-table :data="userList" style="width: 100%;font-size: 12px;" :height="winHeight"
              header-row-class-name="table-header">
      <el-table-column prop="user.username" label="用户名" min-width="120"/>
      <el-table-column prop="app" label="应用名" min-width="160"/>
      <el-table-column prop="apiKey" label="ApiKey" min-width="480"/>
      <el-table-column prop="enable" label="启用" width="120">
        <template #default="scope">
          <el-tag v-if="scope.row.enable">
            å¯ç”¨
          </el-tag>
          <el-tag v-else type="info">
            åœç”¨
          </el-tag>
        </template>
      </el-table-column>
      <el-table-column prop="expiredAt" label="过期时间" width="160"/>
      <el-table-column prop="remark" label="备注信息" min-width="160"/>
      <el-table-column label="操作" min-width="160" fixed="right">
        <template #default="scope">
          <el-button v-if="scope.row.enable"
                     size="medium" icon="el-icon-circle-close" type="text" @click="disableUserApiKey(scope.row)">
            åœç”¨
          </el-button>
          <el-button v-else
                     size="medium" icon="el-icon-circle-check" type="text" @click="enableUserApiKey(scope.row)">
            å¯ç”¨
          </el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button size="medium" icon="el-icon-refresh" type="text" @click="resetUserApiKey(scope.row)">
            é‡ç½®
          </el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button size="medium" icon="el-icon-edit" type="text" @click="remarkUserApiKey(scope.row)">
            å¤‡æ³¨
          </el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button size="medium" icon="el-icon-delete" type="text" @click="deleteUserApiKey(scope.row)"
                     style="color: #f56c6c">
            åˆ é™¤
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <addUserApiKey ref="addUserApiKey"></addUserApiKey>
    <remarkUserApiKey ref="remarkUserApiKey"></remarkUserApiKey>
    <el-pagination
      style="float: right"
      @size-change="handleSizeChange"
      @current-change="currentChange"
      :current-page="currentPage"
      :page-size="count"
      :page-sizes="[15, 25, 35, 50]"
      layout="total, sizes, prev, pager, next"
      :total="total">
    </el-pagination>
  </div>
</template>
<script>
import uiHeader from '../layout/UiHeader.vue'
import addUserApiKey from "./dialog/addUserApiKey.vue";
import remarkUserApiKey from './dialog/remarkUserApiKey.vue'
export default {
  name: 'userApiKeyManager',
  components: {
    uiHeader,
    addUserApiKey,
    remarkUserApiKey
  },
  data() {
    return {
      userList: [], //设备列表
      currentUser: {}, //当前操作设备对象
      winHeight: window.innerHeight - 200,
      currentPage: 1,
      count: 15,
      total: 0,
      getUserApiKeyListLoading: false
    };
  },
  mounted() {
    this.initParam();
    this.initData();
  },
  methods: {
    goBack() {
      this.$router.back()
    },
    initParam() {
      this.userId = this.$route.params.userId;
    },
    initData() {
      this.getUserApiKeyList();
    },
    currentChange(val) {
      this.currentPage = val;
      this.getUserApiKeyList();
    },
    handleSizeChange(val) {
      this.count = val;
      this.getUserApiKeyList();
    },
    getUserApiKeyList() {
      let that = this;
      this.getUserApiKeyListLoading = true;
      this.$axios({
        method: 'get',
        url: `/api/userApiKey/userApiKeys`,
        params: {
          page: that.currentPage,
          count: that.count
        }
      }).then((res) => {
        if (res.data.code === 0) {
          that.total = res.data.data.total;
          that.userList = res.data.data.list;
        }
        that.getUserApiKeyListLoading = false;
      }).catch((error) => {
        that.getUserApiKeyListLoading = false;
      });
    },
    addUserApiKey() {
      this.$refs.addUserApiKey.openDialog(this.userId, () => {
        this.$refs.addUserApiKey.close();
        this.$message({
          showClose: true,
          message: "ApiKey添加成功",
          type: "success",
        });
        setTimeout(this.getUserApiKeyList, 200)
      })
    },
    remarkUserApiKey(row) {
      this.$refs.remarkUserApiKey.openDialog(row.id, () => {
        this.$refs.remarkUserApiKey.close();
        this.$message({
          showClose: true,
          message: "备注修改成功",
          type: "success",
        });
        setTimeout(this.getUserApiKeyList, 200)
      })
    },
    enableUserApiKey(row) {
      let msg = "确定启用此ApiKey?"
      if (row.online !== 0) {
        msg = "<strong>确定启用此ApiKey?</strong>"
      }
      this.$confirm(msg, '提示', {
        dangerouslyUseHTMLString: true,
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        center: true,
        type: 'warning'
      }).then(() => {
        this.$axios({
          method: 'post',
          url: `/api/userApiKey/enable?id=${row.id}`
        }).then((res) => {
          this.$message({
            showClose: true,
            message: '启用成功',
            type: 'success'
          });
          this.getUserApiKeyList();
        }).catch((error) => {
          this.$message({
            showClose: true,
            message: '启用失败',
            type: 'error'
          });
          console.error(error);
        });
      }).catch(() => {
      });
    },
    disableUserApiKey(row) {
      let msg = "确定停用此ApiKey?"
      if (row.online !== 0) {
        msg = "<strong>确定停用此ApiKey?</strong>"
      }
      this.$confirm(msg, '提示', {
        dangerouslyUseHTMLString: true,
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        center: true,
        type: 'warning'
      }).then(() => {
        this.$axios({
          method: 'post',
          url: `/api/userApiKey/disable?id=${row.id}`
        }).then((res) => {
          this.$message({
            showClose: true,
            message: '停用成功',
            type: 'success'
          });
          this.getUserApiKeyList();
        }).catch((error) => {
          this.$message({
            showClose: true,
            message: '停用失败',
            type: 'error'
          });
          console.error(error);
        });
      }).catch(() => {
      });
    },
    resetUserApiKey(row) {
      let msg = "确定重置此ApiKey?"
      if (row.online !== 0) {
        msg = "<strong>确定重置此ApiKey?</strong>"
      }
      this.$confirm(msg, '提示', {
        dangerouslyUseHTMLString: true,
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        center: true,
        type: 'warning'
      }).then(() => {
        this.$axios({
          method: 'post',
          url: `/api/userApiKey/reset?id=${row.id}`
        }).then((res) => {
          this.$message({
            showClose: true,
            message: '重置成功',
            type: 'success'
          });
          this.getUserApiKeyList();
        }).catch((error) => {
          this.$message({
            showClose: true,
            message: '重置失败',
            type: 'error'
          });
          console.error(error);
        });
      }).catch(() => {
      });
    },
    deleteUserApiKey(row) {
      let msg = "确定删除此ApiKey?"
      if (row.online !== 0) {
        msg = "<strong>确定删除此ApiKey?</strong>"
      }
      this.$confirm(msg, '提示', {
        dangerouslyUseHTMLString: true,
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        center: true,
        type: 'warning'
      }).then(() => {
        this.$axios({
          method: 'delete',
          url: `/api/userApiKey/delete?id=${row.id}`
        }).then((res) => {
          this.$message({
            showClose: true,
            message: '删除成功',
            type: 'success'
          });
          this.getUserApiKeyList();
        }).catch((error) => {
          this.$message({
            showClose: true,
            message: '删除失败',
            type: 'error'
          });
          console.error(error);
        });
      }).catch(() => {
      });
    },
  }
}
</script>
<style>
</style>
web_src/src/components/UserManager.vue
@@ -23,6 +23,8 @@
          <el-divider direction="vertical"></el-divider>
          <el-button size="medium" icon="el-icon-edit" type="text" @click="changePushKey(scope.row)">修改pushkey</el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button size="medium" icon="el-icon-edit" type="text" @click="showUserApiKeyManager(scope.row)">管理ApiKey</el-button>
          <el-divider direction="vertical"></el-divider>
          <el-button size="medium" icon="el-icon-delete" type="text" @click="deleteUser(scope.row)"
                     style="color: #f56c6c">删除
          </el-button>
@@ -178,7 +180,10 @@
        setTimeout(this.getUserList, 200)
      })
    }
    },
    showUserApiKeyManager: function (row) {
      this.$router.push(`/userApiKeyManager/${row.id}`)
    },
  }
}
</script>
web_src/src/components/dialog/addUserApiKey.vue
New file
@@ -0,0 +1,139 @@
<template>
  <div id="addUserApiKey" v-loading="isLoading">
    <el-dialog
      title="添加ApiKey"
      width="40%"
      top="2rem"
      :close-on-click-modal="false"
      :visible.sync="showDialog"
      :destroy-on-close="true"
      @close="close()"
    >
      <div id="shared" style="margin-right: 20px;">
        <el-form ref="formRef" :model="form" :rules="rules" status-icon label-width="80px">
          <el-form-item label="应用名" prop="app">
            <el-input
              v-model="form.app"
              property="app"
              autocomplete="off"/>
          </el-form-item>
          <el-form-item label="启用状态" prop="enable" style="text-align: left">
            <el-switch
              v-model="form.enable"
              property="enable"
              active-text="启用"
              inactive-text="停用"/>
          </el-form-item>
          <el-form-item label="过期时间" prop="expiresAt" style="text-align: left">
            <el-date-picker v-model="form.expiresAt"
                            style="width: 100%"
                            property="expiresAt"
                            type="datetime"
                            value-format="yyyy-MM-dd HH:mm:ss"
                            format="yyyy-MM-dd HH:mm:ss"
                            placeholder="选择过期时间"/>
          </el-form-item>
          <el-form-item label="备注信息" prop="remark">
            <el-input v-model="form.remark"
                      type="textarea"
                      property="remark"
                      autocomplete="off"
                      :autosize="{ minRows: 5}"
                      maxlength="255"
                      show-word-limit/>
          </el-form-item>
          <el-form-item>
            <div style="float: right;">
              <el-button type="primary" @click="onSubmit">保存</el-button>
              <el-button @click="close">取消</el-button>
            </div>
          </el-form-item>
        </el-form>
      </div>
    </el-dialog>
  </div>
</template>
<script>
export default {
  name: 'addUserApiKey',
  props: {},
  computed: {},
  created() {
  },
  data() {
    return {
      userId: null,
      form: {
        app: null,
        enable: true,
        expiresAt: null,
        remark: null
      },
      rules: {
        app: [{required: true, trigger: 'blur', message: '应用名不能为空'}]
      },
      listChangeCallback: null,
      showDialog: false,
      isLoading: false
    };
  },
  methods: {
    resetForm() {
      this.form = {
        app: null,
        enable: true,
        expiresAt: null,
        remark: null
      }
    },
    openDialog(userId, callback) {
      this.resetForm()
      this.userId = userId
      this.listChangeCallback = callback
      this.showDialog = true
    },
    onSubmit() {
      this.$refs.formRef.validate((valid) => {
        if (valid) {
          this.$axios({
            method: 'post',
            url: '/api/userApiKey/add',
            params: {
              userId: this.userId,
              app: this.form.app,
              enable: this.form.enable,
              expiresAt: this.form.expiresAt,
              remark: this.form.remark,
            }
          }).then((res) => {
            if (res.data.code === 0) {
              this.$message({
                showClose: true,
                message: '添加成功',
                type: 'success'
              });
              this.showDialog = false
              if (this.listChangeCallback) {
                this.listChangeCallback()
              }
            } else {
              this.$message({
                showClose: true,
                message: res.data.msg,
                type: 'error'
              });
            }
          }).catch((error) => {
            console.error(error)
          });
        }
      });
    },
    close() {
      this.showDialog = false
    }
  },
};
</script>
web_src/src/components/dialog/remarkUserApiKey.vue
New file
@@ -0,0 +1,93 @@
<template>
  <div id="remarkUserApiKey" v-loading="isLoading">
    <el-dialog
      title="ApiKey备注"
      width="40%"
      top="2rem"
      :close-on-click-modal="false"
      :visible.sync="showDialog"
      :destroy-on-close="true"
      @close="close()"
    >
      <div id="shared" style="margin-right: 20px;">
        <el-form ref="form" :rules="rules" status-icon label-width="80px">
          <el-form-item label="备注" prop="oldPassword">
            <el-input type="textarea" v-model="form.remark" autocomplete="off" :autosize="{ minRows: 5}" maxlength="255" show-word-limit></el-input>
          </el-form-item>
          <el-form-item>
            <div style="float: right;">
              <el-button type="primary" @click="onSubmit">保存</el-button>
              <el-button @click="close">取消</el-button>
            </div>
          </el-form-item>
        </el-form>
      </div>
    </el-dialog>
  </div>
</template>
<script>
export default {
  name: "remarkUserApiKey",
  props: {},
  computed: {},
  created() {
  },
  data() {
    return {
      userApiKeyId: null,
      form: {
        remark: null
      },
      rules: {},
      listChangeCallback: null,
      showDialog: false,
      isLoading: false
    };
  },
  methods: {
    resetForm() {
      this.form = {
        remark: null
      }
    },
    openDialog(userApiKeyId, callback) {
      this.resetForm()
      this.userApiKeyId = userApiKeyId
      this.listChangeCallback = callback
      this.showDialog = true
    },
    onSubmit() {
      this.$axios({
        method: 'post',
        url: "/api/userApiKey/remark",
        params: {
          id: this.userApiKeyId,
          remark: this.form.remark
        }
      }).then((res) => {
        if (res.data.code === 0) {
          this.$message({
            showClose: true,
            message: '备注修改成功!',
            type: 'success'
          });
          this.showDialog = false;
          this.listChangeCallback()
        } else {
          this.$message({
            showClose: true,
            message: '备注修改失败',
            type: 'error'
          });
        }
      }).catch((error) => {
        console.error(error)
      });
    },
    close() {
      this.showDialog = false
    },
  },
};
</script>
web_src/src/router/index.js
@@ -20,7 +20,7 @@
import live from '../components/live.vue'
import deviceTree from '../components/common/DeviceTree.vue'
import userManager from '../components/UserManager.vue'
import userApiKeyManager from '../components/UserApiKeyManager.vue'
import wasmPlayer from '../components/common/jessibuca.vue'
import rtcPlayer from '../components/dialog/rtcPlayer.vue'
@@ -125,7 +125,13 @@
          path: '/userManager',
          name: 'userManager',
          component: userManager,
        },
        {
          path: '/userApiKeyManager/:userId',
          name: 'userApiKeyManager',
          component: userApiKeyManager,
        }
        ,
        ]
    },
    {
´ò°ü/config/wvp-application.yml
@@ -4,6 +4,8 @@
        multipart:
            max-file-size: 10MB
            max-request-size: 100MB
    cache:
        type: redis
    # REDIS数据库配置
    redis:
        # [可选] è¶…æ—¶æ—¶é—´
Êý¾Ý¿â/2.7.0/³õʼ»¯-mysql-2.7.0.sql
@@ -314,6 +314,17 @@
                                    parentId integer,
                                    path character varying(255)
);
create table wvp_user_api_key (
                                    id serial primary key ,
                                    user_id bigint,
                                    app character varying(255) ,
                                    api_key text,
                                    expired_at bigint,
                                    remark character varying(255),
                                    enable bool default true,
                                    create_time character varying(50),
                                    update_time character varying(50)
);
/*初始数据*/
Êý¾Ý¿â/2.7.0/³õʼ»¯-postgresql-kingbase-2.7.0.sql
@@ -314,7 +314,17 @@
                                    parentId integer,
                                    path character varying(255)
);
create table wvp_user_api_key (
                                  id serial primary key ,
                                  user_id bigint,
                                  app character varying(255) ,
                                  api_key text,
                                  expired_at bigint,
                                  remark character varying(255),
                                  enable bool default true,
                                  create_time character varying(50),
                                  update_time character varying(50)
);
/*初始数据*/
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
Êý¾Ý¿â/³õʼ»¯-mysql.sql
@@ -314,7 +314,17 @@
                                    parentId integer,
                                    path character varying(255)
);
create table wvp_user_api_key (
                                  id serial primary key ,
                                  user_id bigint,
                                  app character varying(255) ,
                                  api_key text,
                                  expired_at bigint,
                                  remark character varying(255),
                                  enable bool default true,
                                  create_time character varying(50),
                                  update_time character varying(50)
);
/*初始数据*/
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');
Êý¾Ý¿â/³õʼ»¯-postgresql-kingbase.sql
@@ -314,7 +314,17 @@
                                    parentId integer,
                                    path character varying(255)
);
create table wvp_user_api_key (
                                  id serial primary key ,
                                  user_id bigint,
                                  app character varying(255) ,
                                  api_key text,
                                  expired_at bigint,
                                  remark character varying(255),
                                  enable bool default true,
                                  create_time character varying(50),
                                  update_time character varying(50)
);
/*初始数据*/
INSERT INTO wvp_user VALUES (1, 'admin','21232f297a57a5a743894a0e4a801fc3',1,'2021-04-13 14:14:57','2021-04-13 14:14:57','3e80d1762a324d5b0ff636e0bd16f1e3');