[add]支持其他平台通过ApiKey调用系统相关接口

pull/1380/head
leesam 2024-03-19 16:53:01 +08:00
parent 00bb8d914a
commit 76208975bf
24 changed files with 1267 additions and 24 deletions

View File

@ -99,6 +99,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</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>

View File

@ -9,6 +9,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
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 @@ import java.util.Collections;
@ServletComponentScan("com.genersoft.iot.vmp.conf")
@SpringBootApplication
@EnableScheduling
@EnableCaching
public class VManageBootstrap extends SpringBootServletInitializer {
private final static Logger logger = LoggerFactory.getLogger(VManageBootstrap.class);

View File

@ -51,8 +51,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
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;
}
}
}

View File

@ -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.beans.factory.InitializingBean;
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 class JwtUtils implements InitializingBean {
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,17 +53,28 @@ public class JwtUtils implements InitializingBean {
/**
* 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
public void afterPropertiesSet() {
try {
@ -62,14 +89,38 @@ public class JwtUtils implements InitializingBean {
* @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 @@ public class JwtUtils implements InitializingBean {
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,10 +159,17 @@ public class JwtUtils implements InitializingBean {
} 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() {
return HEADER;
}
@ -118,8 +180,8 @@ public class JwtUtils implements InitializingBean {
try {
JwtConsumer consumer = new JwtConsumerBuilder()
.setRequireExpirationTime()
.setMaxFutureValidityInMinutes(5256000)
//.setRequireExpirationTime()
//.setMaxFutureValidityInMinutes(5256000)
.setAllowedClockSkewInSeconds(30)
.setRequireSubject()
//.setExpectedIssuer("")
@ -129,15 +191,27 @@ public class JwtUtils implements InitializingBean {
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);

View 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);
}

View File

@ -11,6 +11,8 @@ public interface IUserService {
boolean changePassword(int id, String password);
User getUserById(int id);
User getUserByUsername(String username);
int addUser(User user);

View 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);
}
}

View File

@ -31,6 +31,11 @@ public class UserServiceImpl implements IUserService {
return userMapper.update(user) > 0;
}
@Override
public User getUserById(int id) {
return userMapper.selectById(id);
}
@Override
public User getUserByUsername(String username) {
return userMapper.getUserByUsername(username);

View 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);
}

View 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;
}
}

View 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);
}
}
}

View File

@ -10,6 +10,8 @@ spring:
multipart:
max-file-size: 10MB
max-request-size: 100MB
cache:
type: redis
# REDIS数据库配置
redis:
# [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1

View File

@ -4,6 +4,8 @@ spring:
multipart:
max-file-size: 10MB
max-request-size: 100MB
cache:
type: redis
# REDIS数据库配置
redis:
# [必须修改] Redis服务器IP, REDIS安装在本机的,使用127.0.0.1

View File

@ -12,7 +12,7 @@ module.exports = {
assetsPublicPath: '/',
proxyTable: {
'/debug': {
target: 'http://127.0.0.1:18082',
target: 'http://127.0.0.1:8080',
changeOrigin: true,
pathRewrite: {
'^/debug': '/'

View 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>

View File

@ -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 @@ export default {
setTimeout(this.getUserList, 200)
})
}
},
showUserApiKeyManager: function (row) {
this.$router.push(`/userApiKeyManager/${row.id}`)
},
}
}
</script>

View 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>

View 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>

View File

@ -20,7 +20,7 @@ import media from '../components/setting/Media.vue'
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 @@ export default new VueRouter({
path: '/userManager',
name: 'userManager',
component: userManager,
},
{
path: '/userApiKeyManager/:userId',
name: 'userApiKeyManager',
component: userApiKeyManager,
}
,
]
},
{

View File

@ -4,6 +4,8 @@ spring:
multipart:
max-file-size: 10MB
max-request-size: 100MB
cache:
type: redis
# REDIS数据库配置
redis:
# [可选] 超时时间

View File

@ -314,6 +314,17 @@ create table wvp_resources_tree (
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)
);
/*初始数据*/

View File

@ -314,7 +314,17 @@ create table wvp_resources_tree (
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');

View File

@ -314,7 +314,17 @@ create table wvp_resources_tree (
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');

View File

@ -314,7 +314,17 @@ create table wvp_resources_tree (
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');