(feat)Support for managing large models with Dify #1830;2、add user access token; #1829; 3、support change password #1824 (#1839)

This commit is contained in:
zhaodongsheng
2024-10-22 13:58:58 +08:00
committed by GitHub
parent bdb20ca462
commit 0ddcdf93ec
34 changed files with 1341 additions and 45 deletions

View File

@@ -3,6 +3,7 @@ package com.tencent.supersonic.auth.api.authentication.adaptor;
import javax.servlet.http.HttpServletRequest;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
import com.tencent.supersonic.common.pojo.User;
@@ -27,4 +28,16 @@ public interface UserAdaptor {
List<User> getUserByOrg(String key);
Set<String> getUserAllOrgId(String userName);
String getPassword(String userName);
void resetPassword(String userName, String password, String newPassword);
UserToken generateToken(String name, String userName, long expireTime);
void deleteUserToken(Long id);
UserToken getUserToken(Long id);
List<UserToken> getUserTokens(String userName);
}

View File

@@ -0,0 +1,20 @@
package com.tencent.supersonic.auth.api.authentication.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserToken {
private Integer id;
private String name;
private String userName;
private String token;
private Long expireTime;
private Date createDate;
private Date expireDate;
}

View File

@@ -0,0 +1,15 @@
package com.tencent.supersonic.auth.api.authentication.request;
import javax.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class UserTokenReq {
@NotBlank(message = "name can not be null")
private String name;
@NotBlank(message = "expireTime can not be null")
private long expireTime;
}

View File

@@ -4,6 +4,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
import com.tencent.supersonic.common.pojo.User;
@@ -30,4 +31,16 @@ public interface UserService {
List<User> getUserByOrg(String key);
List<Organization> getOrganizationTree();
String getPassword(String userName);
void resetPassword(String userName, String password, String newPassword);
UserToken generateToken(String name, String userName, long expireTime);
List<UserToken> getUserTokens(String userName);
UserToken getUserToken(Long id);
void deleteUserToken(Long id);
}

View File

@@ -1,12 +1,16 @@
package com.tencent.supersonic.auth.authentication.adaptor;
import javax.servlet.http.HttpServletRequest;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.tencent.supersonic.auth.api.authentication.adaptor.UserAdaptor;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository;
import com.tencent.supersonic.auth.authentication.utils.TokenService;
import com.tencent.supersonic.common.pojo.User;
@@ -15,8 +19,8 @@ import com.tencent.supersonic.common.util.ContextUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -106,6 +110,68 @@ public class DefaultUserAdaptor implements UserAdaptor {
}
}
@Override
public String getPassword(String userName) {
UserDO userDO = getUser(userName);
if (userDO == null) {
throw new RuntimeException("user not exist,please register");
}
return userDO.getPassword();
}
@Override
public void resetPassword(String userName, String password, String newPassword) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
Optional<UserDO> userDOOptional = Optional.ofNullable(getUser(userName));
UserDO userDO = userDOOptional
.orElseThrow(() -> new RuntimeException("User does not exist, please register"));
try {
validateOldPassword(userDO, password);
updatePassword(userDO, newPassword, userRepository);
} catch (PasswordEncryptionException e) {
throw new RuntimeException("Password encryption error, please try again", e);
}
}
private void validateOldPassword(UserDO userDO, String password)
throws PasswordEncryptionException {
String oldPassword = encryptPassword(password, userDO.getSalt());
if (!userDO.getPassword().equals(oldPassword)) {
throw new RuntimeException("Old password is not correct, please try again");
}
}
private void updatePassword(UserDO userDO, String newPassword, UserRepository userRepository)
throws PasswordEncryptionException {
try {
byte[] salt = AESEncryptionUtil.generateSalt(userDO.getName());
userDO.setSalt(AESEncryptionUtil.getStringFromBytes(salt));
userDO.setPassword(AESEncryptionUtil.encrypt(newPassword, salt));
userRepository.updateUser(userDO);
} catch (Exception e) {
throw new PasswordEncryptionException("Error encrypting password", e);
}
}
private String encryptPassword(String password, String salt)
throws PasswordEncryptionException {
try {
return AESEncryptionUtil.encrypt(password, AESEncryptionUtil.getBytesFromString(salt));
} catch (Exception e) {
throw new PasswordEncryptionException("Error encrypting password", e);
}
}
public static class PasswordEncryptionException extends Exception {
public PasswordEncryptionException(String message, Throwable cause) {
super(message, cause);
}
}
private UserWithPassword getUserWithPassword(UserReq userReq) {
UserDO userDO = getUser(userReq.getName());
if (userDO == null) {
@@ -136,4 +202,70 @@ public class DefaultUserAdaptor implements UserAdaptor {
public Set<String> getUserAllOrgId(String userName) {
return Sets.newHashSet();
}
@Override
public UserToken generateToken(String name, String userName, long expireTime) {
TokenService tokenService = ContextUtils.getBean(TokenService.class);
UserDO userDO = getUser(userName);
if (userDO == null) {
throw new RuntimeException("user not exist,please register");
}
UserWithPassword userWithPassword =
new UserWithPassword(userDO.getId(), userDO.getName(), userDO.getDisplayName(),
userDO.getEmail(), userDO.getPassword(), userDO.getIsAdmin());
String token =
tokenService.generateToken(UserWithPassword.convert(userWithPassword), expireTime);
UserTokenDO userTokenDO = saveUserToken(name, userName, token, expireTime);
return convertUserToken(userTokenDO);
}
@Override
public void deleteUserToken(Long id) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
userRepository.deleteUserToken(id);
}
@Override
public UserToken getUserToken(Long id) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
return convertUserToken(userRepository.getUserToken(id));
}
@Override
public List<UserToken> getUserTokens(String userName) {
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
List<UserToken> userTokens = userRepository.getUserTokenListByName(userName).stream()
.map(this::convertUserToken).collect(Collectors.toList());
return userTokens;
}
private UserTokenDO saveUserToken(String tokenName, String userName, String token,
long expireTime) {
UserTokenDO userTokenDO = new UserTokenDO();
userTokenDO.setName(tokenName);
userTokenDO.setUserName(userName);
userTokenDO.setToken(token);
userTokenDO.setExpireTime(expireTime);
userTokenDO.setCreateTime(new java.util.Date());
userTokenDO.setCreateBy(userName);
userTokenDO.setUpdateBy(userName);
userTokenDO.setExpireDateTime(new java.util.Date(System.currentTimeMillis() + expireTime));
UserRepository userRepository = ContextUtils.getBean(UserRepository.class);
userRepository.addUserToken(userTokenDO);
return userTokenDO;
}
private UserToken convertUserToken(UserTokenDO userTokenDO) {
UserToken userToken = new UserToken();
userToken.setId(userTokenDO.getId());
userToken.setName(userTokenDO.getName());
userToken.setUserName(userTokenDO.getUserName());
userToken.setToken(userTokenDO.getToken());
userToken.setExpireTime(userTokenDO.getExpireTime());
userToken.setCreateDate(userTokenDO.getCreateTime());
userToken.setExpireDate(userTokenDO.getExpireDateTime());
return userToken;
}
}

View File

@@ -1,5 +1,7 @@
package com.tencent.supersonic.auth.authentication.interceptor;
import javax.servlet.http.HttpServletRequest;
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
import com.tencent.supersonic.auth.api.authentication.constant.UserConstants;
import com.tencent.supersonic.auth.authentication.service.UserServiceImpl;
@@ -13,7 +15,6 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

View File

@@ -1,5 +1,8 @@
package com.tencent.supersonic.auth.authentication.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.supersonic.auth.api.authentication.annotation.AuthenticationIgnore;
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
@@ -14,8 +17,6 @@ import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Optional;

View File

@@ -0,0 +1,25 @@
package com.tencent.supersonic.auth.authentication.persistence.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@Data
@TableName("s2_user_token")
public class UserTokenDO {
@TableId(type = IdType.AUTO)
Integer id;
String name;
String userName;
Long expireTime;
String token;
String salt;
Date createTime;
Date updateTime;
String createBy;
String updateBy;
Date expireDateTime;
}

View File

@@ -14,4 +14,6 @@ public interface UserDOMapper {
/** @mbg.generated */
List<UserDO> selectByExample(UserDOExample example);
void updateByPrimaryKey(UserDO userDO);
}

View File

@@ -0,0 +1,11 @@
package com.tencent.supersonic.auth.authentication.persistence.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserTokenDOMapper extends BaseMapper<UserTokenDO> {
}

View File

@@ -1,6 +1,7 @@
package com.tencent.supersonic.auth.authentication.persistence.repository;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import java.util.List;
@@ -10,5 +11,17 @@ public interface UserRepository {
void addUser(UserDO userDO);
List<UserTokenDO> getUserTokenListByName(String userName);
UserDO getUser(String name);
void updateUser(UserDO userDO);
void addUserToken(UserTokenDO userTokenDO);
UserTokenDO getUserToken(Long tokenId);
void deleteUserTokenByName(String userName);
void deleteUserToken(Long tokenId);
}

View File

@@ -1,8 +1,11 @@
package com.tencent.supersonic.auth.authentication.persistence.repository.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDOExample;
import com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO;
import com.tencent.supersonic.auth.authentication.persistence.mapper.UserDOMapper;
import com.tencent.supersonic.auth.authentication.persistence.mapper.UserTokenDOMapper;
import com.tencent.supersonic.auth.authentication.persistence.repository.UserRepository;
import org.springframework.stereotype.Component;
@@ -14,8 +17,11 @@ public class UserRepositoryImpl implements UserRepository {
private UserDOMapper userDOMapper;
public UserRepositoryImpl(UserDOMapper userDOMapper) {
private UserTokenDOMapper userTokenDOMapper;
public UserRepositoryImpl(UserDOMapper userDOMapper, UserTokenDOMapper userTokenDOMapper) {
this.userDOMapper = userDOMapper;
this.userTokenDOMapper = userTokenDOMapper;
}
@Override
@@ -23,6 +29,11 @@ public class UserRepositoryImpl implements UserRepository {
return userDOMapper.selectByExample(new UserDOExample());
}
@Override
public void updateUser(UserDO userDO) {
userDOMapper.updateByPrimaryKey(userDO);
}
@Override
public void addUser(UserDO userDO) {
userDOMapper.insert(userDO);
@@ -36,4 +47,33 @@ public class UserRepositoryImpl implements UserRepository {
Optional<UserDO> userDOOptional = userDOS.stream().findFirst();
return userDOOptional.orElse(null);
}
@Override
public List<UserTokenDO> getUserTokenListByName(String userName) {
QueryWrapper<UserTokenDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_name", userName);
return userTokenDOMapper.selectList(queryWrapper);
}
@Override
public void addUserToken(UserTokenDO userTokenDO) {
userTokenDOMapper.insert(userTokenDO);
}
@Override
public UserTokenDO getUserToken(Long tokenId) {
return userTokenDOMapper.selectById(tokenId);
}
@Override
public void deleteUserTokenByName(String userName) {
QueryWrapper<UserTokenDO> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_name", userName);
userTokenDOMapper.delete(queryWrapper);
}
@Override
public void deleteUserToken(Long tokenId) {
userTokenDOMapper.deleteById(tokenId);
}
}

View File

@@ -4,7 +4,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
import com.tencent.supersonic.auth.api.authentication.request.UserTokenReq;
import com.tencent.supersonic.auth.api.authentication.service.UserService;
import com.tencent.supersonic.common.pojo.User;
import lombok.extern.slf4j.Slf4j;
@@ -13,6 +15,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@@ -69,4 +72,28 @@ public class UserController {
public String login(@RequestBody UserReq userCmd, HttpServletRequest request) {
return userService.login(userCmd, request);
}
@PostMapping("/generateToken")
public UserToken generateToken(@RequestBody UserTokenReq userTokenReq,
HttpServletRequest request, HttpServletResponse response) {
User user = userService.getCurrentUser(request, response);
return userService.generateToken(userTokenReq.getName(), user.getName(),
userTokenReq.getExpireTime());
}
@GetMapping("/getUserTokens")
public List<UserToken> getUserTokens(HttpServletRequest request, HttpServletResponse response) {
User user = userService.getCurrentUser(request, response);
return userService.getUserTokens(user.getName());
}
@GetMapping("/getUserToken")
public UserToken getUserToken(@RequestParam(name = "tokenId") Long tokenId) {
return userService.getUserToken(tokenId);
}
@PostMapping("/deleteUserToken")
public void deleteUserToken(@RequestParam(name = "tokenId") Long tokenId) {
userService.deleteUserToken(tokenId);
}
}

View File

@@ -4,6 +4,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.supersonic.auth.api.authentication.pojo.Organization;
import com.tencent.supersonic.auth.api.authentication.pojo.UserToken;
import com.tencent.supersonic.auth.api.authentication.request.UserReq;
import com.tencent.supersonic.auth.api.authentication.service.UserService;
import com.tencent.supersonic.auth.api.authentication.utils.UserHolder;
@@ -79,4 +80,34 @@ public class UserServiceImpl implements UserService {
public String login(UserReq userReq, String appKey) {
return ComponentFactory.getUserAdaptor().login(userReq, appKey);
}
@Override
public String getPassword(String userName) {
return ComponentFactory.getUserAdaptor().getPassword(userName);
}
@Override
public void resetPassword(String userName, String password, String newPassword) {
ComponentFactory.getUserAdaptor().resetPassword(userName, password, newPassword);
}
@Override
public UserToken generateToken(String name, String userName, long expireTime) {
return ComponentFactory.getUserAdaptor().generateToken(name, userName, expireTime);
}
@Override
public List<UserToken> getUserTokens(String userName) {
return ComponentFactory.getUserAdaptor().getUserTokens(userName);
}
@Override
public UserToken getUserToken(Long id) {
return ComponentFactory.getUserAdaptor().getUserToken(id);
}
@Override
public void deleteUserToken(Long id) {
ComponentFactory.getUserAdaptor().deleteUserToken(id);
}
}

View File

@@ -1,5 +1,8 @@
package com.tencent.supersonic.auth.authentication.strategy;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.tencent.supersonic.auth.api.authentication.constant.UserConstants;
import com.tencent.supersonic.auth.api.authentication.service.UserStrategy;
import com.tencent.supersonic.auth.authentication.utils.TokenService;
@@ -7,8 +10,6 @@ import com.tencent.supersonic.common.pojo.User;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Optional;
@Service

View File

@@ -1,6 +1,10 @@
package com.tencent.supersonic.auth.authentication.utils;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import com.tencent.supersonic.auth.api.authentication.config.AuthenticationConfig;
import com.tencent.supersonic.auth.api.authentication.pojo.UserWithPassword;
import com.tencent.supersonic.common.pojo.exception.AccessException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
@@ -9,14 +13,11 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_CREATE_TIME;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_PREFIX;
import static com.tencent.supersonic.auth.api.authentication.constant.UserConstants.TOKEN_USER_NAME;
@@ -32,33 +33,40 @@ public class TokenService {
public String generateToken(Map<String, Object> claims, HttpServletRequest request) {
String appKey = getAppKey(request);
return generateToken(claims, appKey);
long expiration = System.currentTimeMillis() + authenticationConfig.getTokenTimeout();
return generateToken(claims, appKey, expiration);
}
public String generateToken(Map<String, Object> claims, long expiration) {
String appKey = authenticationConfig.getTokenDefaultAppKey();
long exp = System.currentTimeMillis() + expiration;
return generateToken(claims, appKey, exp);
}
public String generateToken(Map<String, Object> claims, String appKey) {
return toTokenString(claims, appKey);
long expiration = System.currentTimeMillis() + authenticationConfig.getTokenTimeout();
return toTokenString(claims, appKey, expiration);
}
private String toTokenString(Map<String, Object> claims, String appKey) {
Long tokenTimeout = authenticationConfig.getTokenTimeout();
long expiration = Long.parseLong(claims.get(TOKEN_CREATE_TIME) + "") + tokenTimeout;
Date expirationDate = new Date(expiration);
String tokenSecret = getTokenSecret(appKey);
return Jwts.builder().setClaims(claims).setSubject(claims.get(TOKEN_USER_NAME).toString())
.setExpiration(expirationDate)
.signWith(new SecretKeySpec(tokenSecret.getBytes(StandardCharsets.UTF_8),
SignatureAlgorithm.HS512.getJcaName()), SignatureAlgorithm.HS512)
.compact();
public String generateToken(Map<String, Object> claims, String appKey, long expiration) {
return toTokenString(claims, appKey, expiration);
}
private String getTokenSecret(String appKey) {
Map<String, String> appKeyToSecretMap = authenticationConfig.getAppKeyToSecretMap();
String secret = appKeyToSecretMap.get(appKey);
if (StringUtils.isBlank(secret)) {
throw new AccessException("get secret from appKey failed :" + appKey);
public String generateAppUserToken(HttpServletRequest request) {
String appName = request.getHeader("AppId");
if (StringUtils.isBlank(appName)) {
String message = "AppId is blank, get app_user failed";
log.warn("{}, uri: {}", message, request.getServletPath());
throw new AccessException(message);
}
return secret;
UserWithPassword appUser = new UserWithPassword(appName);
appUser.setId(1L);
appUser.setName(appName);
appUser.setPassword("c3VwZXJzb25pY0BiaWNvbdktJJYWw6A3rEmBUPzbn/6DNeYnD+y3mAwDKEMS3KVT");
appUser.setDisplayName(appName);
appUser.setIsAdmin(0);
return generateToken(UserWithPassword.convert(appUser), request);
}
public Optional<Claims> getClaims(HttpServletRequest request) {
@@ -67,6 +75,17 @@ public class TokenService {
return getClaims(token, appKey);
}
private Optional<Claims> getClaims(String token, HttpServletRequest request) {
Optional<Claims> claims;
try {
String appKey = getAppKey(request);
claims = getClaims(token, appKey);
} catch (Exception e) {
throw new AccessException("parse user info from token failed :" + token);
}
return claims;
}
public Optional<Claims> getClaims(String token, String appKey) {
try {
String tokenSecret = getTokenSecret(appKey);
@@ -86,6 +105,26 @@ public class TokenService {
: token.trim();
}
private String toTokenString(Map<String, Object> claims, String appKey, long expiration) {
Date expirationDate = new Date(expiration);
String tokenSecret = getTokenSecret(appKey);
return Jwts.builder().setClaims(claims).setSubject(claims.get(TOKEN_USER_NAME).toString())
.setExpiration(expirationDate)
.signWith(new SecretKeySpec(tokenSecret.getBytes(StandardCharsets.UTF_8),
SignatureAlgorithm.HS512.getJcaName()), SignatureAlgorithm.HS512)
.compact();
}
private String getTokenSecret(String appKey) {
Map<String, String> appKeyToSecretMap = authenticationConfig.getAppKeyToSecretMap();
String secret = appKeyToSecretMap.get(appKey);
if (StringUtils.isBlank(secret)) {
throw new AccessException("get secret from appKey failed :" + appKey);
}
return secret;
}
public String getAppKey(HttpServletRequest request) {
String appKey = request.getHeader(authenticationConfig.getTokenHttpHeaderAppKey());
if (StringUtils.isBlank(appKey)) {
@@ -93,4 +132,8 @@ public class TokenService {
}
return appKey;
}
public String getDefaultAppKey() {
return authenticationConfig.getTokenDefaultAppKey();
}
}

View File

@@ -122,4 +122,29 @@
<include refid="Example_Where_Clause" />
</if>
</select>
<update id="updateByPrimaryKey" parameterType="com.tencent.supersonic.auth.authentication.persistence.dataobject.UserDO">
update s2_user
<set>
<if test="name != null">
name = #{name,jdbcType=VARCHAR},
</if>
<if test="password != null">
password = #{password,jdbcType=VARCHAR},
</if>
<if test="salt != null">
salt = #{salt,jdbcType=VARCHAR},
</if>
<if test="displayName != null">
display_name = #{displayName,jdbcType=VARCHAR},
</if>
<if test="email != null">
email = #{email,jdbcType=VARCHAR},
</if>
<if test="isAdmin != null">
is_admin = #{isAdmin,jdbcType=INTEGER},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
</mapper>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tencent.supersonic.auth.authentication.persistence.mapper.UserTokenDOMapper">
<resultMap id="BaseResultMap" type="com.tencent.supersonic.auth.authentication.persistence.dataobject.UserTokenDO">
<result column="id" jdbcType="BIGINT" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="token" jdbcType="VARCHAR" property="token" />
<result column="expire_time" jdbcType="TIMESTAMP" property="expireTime" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
</resultMap>
</mapper>

View File

@@ -2,8 +2,15 @@ package com.tencent.supersonic.common.pojo;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.tencent.supersonic.common.pojo.Parameter;
import dev.langchain4j.provider.*;
import dev.langchain4j.provider.AzureModelFactory;
import dev.langchain4j.provider.DashscopeModelFactory;
import dev.langchain4j.provider.DifyModelFactory;
import dev.langchain4j.provider.LocalAiModelFactory;
import dev.langchain4j.provider.ModelProvider;
import dev.langchain4j.provider.OllamaModelFactory;
import dev.langchain4j.provider.OpenAiModelFactory;
import dev.langchain4j.provider.QianfanModelFactory;
import dev.langchain4j.provider.ZhipuModelFactory;
import java.util.ArrayList;
import java.util.List;
@@ -52,7 +59,7 @@ public class ChatModelParameters {
return Lists.newArrayList(OpenAiModelFactory.PROVIDER, OllamaModelFactory.PROVIDER,
QianfanModelFactory.PROVIDER, ZhipuModelFactory.PROVIDER,
LocalAiModelFactory.PROVIDER, DashscopeModelFactory.PROVIDER,
AzureModelFactory.PROVIDER);
AzureModelFactory.PROVIDER, DifyModelFactory.PROVIDER);
}
private static List<Parameter.Dependency> getBaseUrlDependency() {
@@ -63,20 +70,23 @@ public class ChatModelParameters {
QianfanModelFactory.PROVIDER, QianfanModelFactory.DEFAULT_BASE_URL,
ZhipuModelFactory.PROVIDER, ZhipuModelFactory.DEFAULT_BASE_URL,
LocalAiModelFactory.PROVIDER, LocalAiModelFactory.DEFAULT_BASE_URL,
DashscopeModelFactory.PROVIDER, DashscopeModelFactory.DEFAULT_BASE_URL));
DashscopeModelFactory.PROVIDER, DashscopeModelFactory.DEFAULT_BASE_URL,
DifyModelFactory.PROVIDER, DifyModelFactory.DEFAULT_BASE_URL));
}
private static List<Parameter.Dependency> getApiKeyDependency() {
return getDependency(CHAT_MODEL_PROVIDER.getName(),
Lists.newArrayList(OpenAiModelFactory.PROVIDER, QianfanModelFactory.PROVIDER,
ZhipuModelFactory.PROVIDER, LocalAiModelFactory.PROVIDER,
AzureModelFactory.PROVIDER, DashscopeModelFactory.PROVIDER),
AzureModelFactory.PROVIDER, DashscopeModelFactory.PROVIDER,
DifyModelFactory.PROVIDER),
ImmutableMap.of(OpenAiModelFactory.PROVIDER,
ModelProvider.DEMO_CHAT_MODEL.getApiKey(), QianfanModelFactory.PROVIDER,
ModelProvider.DEMO_CHAT_MODEL.getApiKey(), ZhipuModelFactory.PROVIDER,
ModelProvider.DEMO_CHAT_MODEL.getApiKey(), LocalAiModelFactory.PROVIDER,
ModelProvider.DEMO_CHAT_MODEL.getApiKey(), AzureModelFactory.PROVIDER,
ModelProvider.DEMO_CHAT_MODEL.getApiKey(), DashscopeModelFactory.PROVIDER,
ModelProvider.DEMO_CHAT_MODEL.getApiKey(), DifyModelFactory.PROVIDER,
ModelProvider.DEMO_CHAT_MODEL.getApiKey()));
}
@@ -88,7 +98,8 @@ public class ChatModelParameters {
ZhipuModelFactory.PROVIDER, ZhipuModelFactory.DEFAULT_MODEL_NAME,
LocalAiModelFactory.PROVIDER, LocalAiModelFactory.DEFAULT_MODEL_NAME,
AzureModelFactory.PROVIDER, AzureModelFactory.DEFAULT_MODEL_NAME,
DashscopeModelFactory.PROVIDER, DashscopeModelFactory.DEFAULT_MODEL_NAME));
DashscopeModelFactory.PROVIDER, DashscopeModelFactory.DEFAULT_MODEL_NAME,
DifyModelFactory.PROVIDER, DifyModelFactory.DEFAULT_MODEL_NAME));
}
private static List<Parameter.Dependency> getEndpointDependency() {

View File

@@ -0,0 +1,85 @@
package com.tencent.supersonic.common.util;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class DifyClient {
private static final String DEFAULT_USER = "zhaodongsheng";
private static final String CONTENT_TYPE_JSON = "application/json";
private String difyURL;
private String difyKey;
public DifyClient(String difyURL, String difyKey) {
this.difyURL = difyURL;
this.difyKey = difyKey;
}
public DifyResult generate(String prompt) {
Map<String, String> headers = defaultHeaders();
DifyRequest request = new DifyRequest();
request.setQuery(prompt);
request.setUser(DEFAULT_USER);
return sendRequest(request, headers);
}
public DifyResult generate(String prompt, String user) {
Map<String, String> headers = defaultHeaders();
DifyRequest request = new DifyRequest();
request.setQuery(prompt);
request.setUser(user);
return sendRequest(request, headers);
}
public DifyResult generate(Map<String, String> inputs, String queryText, String user,
String conversationId) {
Map<String, String> headers = defaultHeaders();
DifyRequest request = new DifyRequest();
request.setInputs(inputs);
request.setQuery(queryText);
request.setUser(user);
if (conversationId != null && !conversationId.isEmpty()) {
request.setConversationId(conversationId);
}
return sendRequest(request, headers);
}
public DifyResult sendRequest(DifyRequest request, Map<String, String> headers) {
try {
log.debug("请求dify- header--->" + JsonUtil.toString(headers));
log.debug("请求dify- conversionId--->" + JsonUtil.toString(request));
return HttpUtils.post(difyURL, JsonUtil.toString(request), headers, DifyResult.class);
} catch (Exception e) {
log.error("请求dify失败---->" + e.getMessage());
throw new RuntimeException(e);
}
}
public String parseSQLResult(String sql) {
Pattern pattern = Pattern.compile("```(sql)?(.*)```", Pattern.DOTALL);
Matcher matcher = pattern.matcher(sql);
if (!matcher.find()) {
return sql.trim();
} else {
return matcher.group(2).trim();
}
}
private Map<String, String> defaultHeaders() {
Map<String, String> headers = new HashMap<>();
if (difyKey.contains("Bearer")) {
headers.put("Authorization", difyKey);
} else {
headers.put("Authorization", "Bearer " + difyKey);
}
headers.put("Content-Type", CONTENT_TYPE_JSON);
return headers;
}
}

View File

@@ -0,0 +1,19 @@
package com.tencent.supersonic.common.util;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class DifyRequest {
private String query;
private Map<String, String> inputs = new HashMap<>();
private String responseMode = "blocking";
private String user;
@JsonProperty("conversation_id")
private String conversationId;
@JsonProperty("auto_generate_name")
private Boolean autoGenerateName = false;
}

View File

@@ -0,0 +1,13 @@
package com.tencent.supersonic.common.util;
import lombok.Data;
@Data
public class DifyResult {
private String event = "";
private String taskId = "";
private String conversationId = "";
private String id = "";
private String messageId = "";
private String answer = "";
}

View File

@@ -0,0 +1,170 @@
package com.tencent.supersonic.common.util;
import okhttp3.Dispatcher;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class HttpUtils {
private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);
// 重试参考okhttp3.RealCall.getResponseWithInterceptorChain
private static final OkHttpClient client = new OkHttpClient.Builder()
.readTimeout(3, TimeUnit.MINUTES).retryOnConnectionFailure(true).build();
static {
Dispatcher dispatcher = client.dispatcher();
dispatcher.setMaxRequestsPerHost(300);
dispatcher.setMaxRequests(200);
}
public static Response execute(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
return client.newCall(request).execute();
}
public static String get(String url) throws IOException {
return doRequest(builder(url).build());
}
public static String get(String url, Map<String, String> headers) throws IOException {
return doRequest(headerBuilder(url, headers).build());
}
public static String get(String url, Map<String, String> headers, Map<String, Object> params)
throws IOException {
return doRequest(headerBuilder(url + buildUrlParams(params), headers).build());
}
public static <T> T get(String url, Class<T> classOfT) throws IOException {
return doRequest(builder(url).build(), classOfT);
}
public static <T> T get(String url, Map<String, String> headers, Class<T> classOfT)
throws IOException {
return doRequest(headerBuilder(url, headers).build(), classOfT);
}
public static <T> T get(String url, Map<String, String> headers, Map<String, Object> params,
Class<T> classOfT) throws IOException {
return doRequest(headerBuilder(url + buildUrlParams(params), headers).build(), classOfT);
}
// public static <T> T get(String url, TypeReference<T> type) throws IOException {
// return doRequest(builder(url).build(), type);
// }
// public static <T> T get(String url, Map<String, String> headers, TypeReference<T> type)
// throws IOException {
// return doRequest(headerBuilder(url, headers).build(), type);
// }
// public static <T> T get(String url, Map<String, String> headers, Map<String, Object> params,
// TypeReference<T> type) throws IOException {
// return doRequest(headerBuilder(url + buildUrlParams(params), headers).build(), type);
// }
public static String post(String url, Object body) throws IOException {
return doRequest(postRequest(url, body));
}
public static String post(String url, Object body, Map<String, String> headers)
throws IOException {
return doRequest(postRequest(url, body, headers));
}
public static <T> T post(String url, Object body, Class<T> classOfT) throws IOException {
return doRequest(postRequest(url, body), classOfT);
}
// public static <T> T post(String url, Object body, TypeReference<T> type) throws IOException {
// return doRequest(postRequest(url, body), type);
// }
public static <T> T post(String url, Object body, Map<String, String> headers,
Class<T> classOfT) throws IOException {
return doRequest(postRequest(url, body, headers), classOfT);
}
// public static <T> T post(String url, Object body, Map<String, String> headers,
// TypeReference<T> type) throws IOException {
// return doRequest(postRequest(url, body, headers), type);
// }
private static Request postRequest(String url, Object body) {
return builder(url).post(buildRequestBody(body, null)).build();
}
private static Request postRequest(String url, Object body, Map<String, String> headers) {
return headerBuilder(url, headers).post(buildRequestBody(body, headers)).build();
}
private static Request.Builder builder(String url) {
return new Request.Builder().url(url);
}
private static Request.Builder headerBuilder(String url, Map<String, String> headers) {
Request.Builder builder = new Request.Builder().url(url);
headers.forEach(builder::addHeader);
return builder;
}
private static <T> T doRequest(Request request, Class<T> classOfT) throws IOException {
return JsonUtil.toObject(doRequest(request), classOfT);
}
// private static <T> T doRequest(Request request, TypeReference<T> type) throws IOException {
// return JsonUtil.toObject(doRequest(request), type);
// }
private static String doRequest(Request request) throws IOException {
long beginTime = System.currentTimeMillis();
try {
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
return response.body().string();
} else {
throw new RuntimeException(
"Http请求失败[" + response.code() + "]:" + response.body().string() + "...");
}
} finally {
logger.info("begin to request : {}, execute costs(ms) : {}", request.url(),
System.currentTimeMillis() - beginTime);
}
}
private static RequestBody buildRequestBody(Object body, Map<String, String> headers) {
if (headers != null && headers.containsKey("Content-Type")) {
String contentType = headers.get("Content-Type");
return RequestBody.create(MediaType.parse(contentType), body.toString());
}
if (body instanceof String && ((String) body).contains("=")) {
return RequestBody.create(MediaType.parse("application/x-www-form-urlencoded"),
(String) body);
}
return RequestBody.create(MediaType.parse("application/json"), JsonUtil.toString(body));
}
private static String buildUrlParams(Map<String, Object> params) {
if (params.isEmpty()) {
return "";
}
return "?" + params.entrySet().stream().map(it -> it.getKey() + "=" + it.getValue())
.collect(Collectors.joining("&"));
}
}

View File

@@ -0,0 +1,95 @@
package dev.langchain4j.model.dify;
import com.tencent.supersonic.common.util.AESEncryptionUtil;
import com.tencent.supersonic.common.util.DifyClient;
import com.tencent.supersonic.common.util.DifyResult;
import dev.langchain4j.agent.tool.ToolSpecification;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.output.Response;
import lombok.Builder;
import java.util.List;
import static dev.langchain4j.internal.Utils.getOrDefault;
import static dev.langchain4j.internal.Utils.isNullOrEmpty;
import static dev.langchain4j.internal.ValidationUtils.ensureNotEmpty;
import static java.util.Collections.singletonList;
public class DifyAiChatModel implements ChatLanguageModel {
private static final String CONTENT_TYPE_JSON = "application/json";
private final String baseUrl;
private final String apiKey;
private final DifyClient difyClient;
private final Integer maxRetries;
private final Integer maxToken;
private final String appName;
private final Double temperature;
private final Long timeOut;
private String userName;
@Builder
public DifyAiChatModel(String baseUrl, String apiKey, Integer maxRetries, Integer maxToken,
String modelName, Double temperature, Long timeOut) {
this.baseUrl = baseUrl;
this.maxRetries = getOrDefault(maxRetries, 3);
this.maxToken = getOrDefault(maxToken, 512);
try {
this.apiKey = AESEncryptionUtil.aesDecryptECB(apiKey);
} catch (Exception e) {
throw new RuntimeException(e);
}
this.appName = modelName;
this.temperature = temperature;
this.timeOut = timeOut;
this.difyClient = new DifyClient(this.baseUrl, this.apiKey);
}
@Override
public String generate(String message) {
DifyResult difyResult = this.difyClient.generate(message, this.getUserName());
return difyResult.getAnswer().toString();
}
@Override
public Response<AiMessage> generate(List<ChatMessage> messages) {
return generate(messages, (ToolSpecification) null);
}
@Override
public Response<AiMessage> generate(List<ChatMessage> messages,
List<ToolSpecification> toolSpecifications) {
ensureNotEmpty(messages, "messages");
DifyResult difyResult =
this.difyClient.generate(messages.get(0).text(), this.getUserName());
System.out.println(difyResult.toString());
if (!isNullOrEmpty(toolSpecifications)) {
// TODO
}
return Response.from(AiMessage.from(difyResult.getAnswer()));
}
@Override
public Response<AiMessage> generate(List<ChatMessage> messages,
ToolSpecification toolSpecification) {
return generate(messages,
toolSpecification != null ? singletonList(toolSpecification) : null);
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserName() {
return null == userName ? "zhaodongsheng" : userName;
}
}

View File

@@ -0,0 +1,41 @@
package dev.langchain4j.provider;
import com.tencent.supersonic.common.pojo.ChatModelConfig;
import com.tencent.supersonic.common.pojo.EmbeddingModelConfig;
import com.tencent.supersonic.common.util.AESEncryptionUtil;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.dify.DifyAiChatModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.zhipu.ZhipuAiEmbeddingModel;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
@Service
public class DifyModelFactory implements ModelFactory, InitializingBean {
public static final String PROVIDER = "DIFY";
public static final String DEFAULT_BASE_URL = "https://dify.com/v1/chat-messages";
public static final String DEFAULT_MODEL_NAME = "demo-预留-可不填写";
public static final String DEFAULT_EMBEDDING_MODEL_NAME = "all-minilm";
@Override
public ChatLanguageModel createChatModel(ChatModelConfig modelConfig) {
return DifyAiChatModel.builder().baseUrl(modelConfig.getBaseUrl())
.apiKey(AESEncryptionUtil.aesDecryptECB(modelConfig.getApiKey()))
.modelName(modelConfig.getModelName()).timeOut(modelConfig.getTimeOut()).build();
}
@Override
public EmbeddingModel createEmbeddingModel(EmbeddingModelConfig embeddingModelConfig) {
return ZhipuAiEmbeddingModel.builder().baseUrl(embeddingModelConfig.getBaseUrl())
.apiKey(embeddingModelConfig.getApiKey()).model(embeddingModelConfig.getModelName())
.maxRetries(embeddingModelConfig.getMaxRetries())
.logRequests(embeddingModelConfig.getLogRequests())
.logResponses(embeddingModelConfig.getLogResponses()).build();
}
@Override
public void afterPropertiesSet() {
ModelProvider.add(PROVIDER, this);
}
}

View File

@@ -682,3 +682,19 @@ CREATE TABLE IF NOT EXISTS `s2_term` (
PRIMARY KEY (`id`)
);
COMMENT ON TABLE s2_term IS 'term info';
CREATE TABLE IF NOT EXISTS `s2_user_token` (
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`user_name` VARCHAR(255) NOT NULL,
`expire_time` INT NOT NULL,
`token` text NOT NULL,
`salt` VARCHAR(255) default NULL,
`create_time` DATETIME NOT NULL,
`create_by` VARCHAR(255) NOT NULL,
`update_time` DATETIME default NULL,
`update_by` VARCHAR(255) NOT NULL,
`expire_date_time` DATETIME NOT NULL,
PRIMARY KEY (`id`)
);
COMMENT ON TABLE s2_user_token IS 'user token info';

View File

@@ -603,3 +603,19 @@ CREATE TABLE IF NOT EXISTS `s2_term` (
`updated_by` varchar(100) DEFAULT NULL ,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8 COMMENT ='术语表';
CREATE TABLE `s2_user_token` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`user_name` VARCHAR(255) NOT NULL,
`expire_time` BIGINT(20) NOT NULL,
`token` text NOT NULL,
`salt` VARCHAR(255) default NULL,
`create_time` DATETIME NOT NULL,
`create_by` VARCHAR(255) NOT NULL,
`update_time` DATETIME default NULL,
`update_by` VARCHAR(255) NOT NULL,
`expire_date_time` DATETIME NOT NULL,
unique key name_username (`name`, `user_name`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin comment='用户令牌信息表';

View File

@@ -1,7 +1,7 @@
export default {
dev: {
'/api/': {
target: 'http://10.91.217.39:9080',
target: 'http://127.0.0.1:9080',
changeOrigin: true,
},
},

View File

@@ -0,0 +1,195 @@
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { Button, Form, Input, message, Modal, Table } from 'antd';
import { useBoolean, useDynamicList, useRequest } from 'ahooks';
import {
changePassword,
generateAccessToken,
getUserAccessTokens,
removeAccessToken,
} from '@/services/user';
import { encryptPassword, encryptKey } from '@/utils/utils';
import { API } from '@/services/API';
import { EditableProTable, ProColumns } from '@ant-design/pro-components';
import { CopyOutlined } from '@ant-design/icons';
type DataSourceType = {
id: React.Key;
name?: string;
token?: string;
expireDate?: string;
createDate?: string;
toBeSaved?: boolean;
};
export interface IRef {
open: () => void;
close: () => void;
}
const ChangePasswordModal = forwardRef<IRef>((_, ref) => {
const [open, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
const [dataSource, setDataSource] = useState<readonly DataSourceType[]>([]);
const getAccessTokens = async () => {
try {
const res = await getUserAccessTokens();
if (res && res.code === 200) {
return res.data;
} else {
message.error(res.msg);
return [];
}
} catch (error) {
message.error('获取数据失败,原因:' + error);
return [];
}
};
useImperativeHandle(ref, () => ({
open: () => {
openModal();
},
close: () => {
closeModal();
},
}));
const columns: ProColumns<DataSourceType>[] = [
{
title: '名称',
dataIndex: 'name',
width: '15%',
formItemProps: {
rules: [{ required: true, message: '此项为必填项' }],
},
editable: (text, record, index) => !!record.toBeSaved,
},
{
title: '访问令牌',
dataIndex: 'token',
width: '25%',
formItemProps: (form, { rowIndex }) => {
return {
rules: [{ required: true, message: '此项为必填项' }],
};
},
render: (text, record, index) => {
// 脱敏处理, 点击图标可完整token
return record.toBeSaved ? (
text
) : (
<>
{record.token ? record.token.slice(0, 5) + '********' + record.token.slice(-5) : ''}
<Button
type="link"
size="small"
onClick={() => {
navigator.clipboard.writeText(record.token || '');
message.info('已复制到剪贴板');
}}
>
<CopyOutlined />
</Button>
</>
);
},
editable: false,
},
{
title: '过期时间',
dataIndex: 'expireDate',
valueType: 'date',
width: '20%',
formItemProps: {
rules: [{ required: true, message: '此项为必填项' }],
},
editable: (text, record, index) => !!record.toBeSaved,
},
{
title: '创建时间',
dataIndex: 'createDate',
valueType: 'date',
editable: false,
width: '20%',
},
{
title: '操作',
valueType: 'option',
width: '15%',
render: (text, record, _, action) => [
<a
key="delete"
onClick={() => {
Modal.confirm({
title: '删除访问令牌',
content: '确定删除此访问令牌吗?',
onOk: async () => {
const res = await removeAccessToken(record.id as number);
if (res && res.code !== 200) {
message.error('删除失败,原因:' + res.msg);
return;
}
setDataSource(dataSource.filter((item) => item.id !== record.id));
message.success('删除成功');
},
});
}}
>
</a>,
],
},
];
return (
<Modal
title="访问令牌"
open={open}
onClose={closeModal}
onCancel={closeModal}
width={1200}
footer={false}
destroyOnClose
>
<EditableProTable<DataSourceType>
rowKey="id"
recordCreatorProps={{
position: 'bottom',
creatorButtonText: '新增访问令牌',
record: () => ({ id: (Math.random() * 1000000).toFixed(0), toBeSaved: true }),
}}
loading={false}
columns={columns}
request={async () => {
const data = await getAccessTokens();
return {
data,
total: data.length,
success: true,
};
}}
value={dataSource}
onChange={setDataSource}
editable={{
type: 'single',
onSave: async (rowKey, data, row) => {
console.log(rowKey, data, row);
await generateAccessToken({
name: data.name!,
expireTime: new Date(data.expireDate!).getTime() - new Date().getTime(),
});
const newTokens = await getAccessTokens();
setTimeout(() => {
setDataSource(newTokens);
}, 100);
},
}}
/>
</Modal>
);
});
export default ChangePasswordModal;

View File

@@ -1,10 +1,12 @@
import React from 'react';
import { LogoutOutlined } from '@ant-design/icons';
import React, { useRef } from 'react';
import { LogoutOutlined, KeyOutlined, UnlockOutlined } from '@ant-design/icons';
import { useModel } from 'umi';
import HeaderDropdown from '../HeaderDropdown';
import styles from './index.less';
import TMEAvatar from '../TMEAvatar';
import { AUTH_TOKEN_KEY } from '@/common/constants';
import ChangePasswordModal, { IRef as IRefChangePasswordModal } from './ChangePasswordModal';
import AccessTokensModal, { IRef as IAccessTokensModalRef } from './AccessTokensModal';
import { history } from 'umi';
export type GlobalHeaderRightProps = {
@@ -27,7 +29,38 @@ const { APP_TARGET } = process.env;
const AvatarDropdown: React.FC<GlobalHeaderRightProps> = () => {
const { initialState = {}, setInitialState } = useModel('@@initialState');
const { currentUser = {} } = initialState as any;
const changePasswordModalRef = useRef<IRefChangePasswordModal>(null);
const accessTokensModalRef = useRef<IAccessTokensModalRef>(null);
const handleAccessToken = () => {
accessTokensModalRef.current?.open();
};
const handleChangePassword = () => {
changePasswordModalRef.current?.open();
};
const items = [
{
label: (
<>
<KeyOutlined />
访
</>
),
key: 'accessToken',
onClick: handleAccessToken,
},
{
label: (
<>
<UnlockOutlined />
</>
),
key: 'changePassword',
onClick: handleChangePassword,
},
{
label: (
<>
@@ -48,12 +81,21 @@ const AvatarDropdown: React.FC<GlobalHeaderRightProps> = () => {
},
];
return (
<HeaderDropdown menu={{ items }} disabled={APP_TARGET === 'inner'}>
<span className={`${styles.action} ${styles.account}`}>
<TMEAvatar className={styles.avatar} size="small" staffName={currentUser.staffName} />
<span className={styles.userName}>{currentUser.staffName}</span>
</span>
</HeaderDropdown>
<>
<HeaderDropdown
menu={{
items,
}}
disabled={APP_TARGET === 'inner'}
>
<span className={`${styles.action} ${styles.account}`}>
<TMEAvatar className={styles.avatar} size="small" staffName={currentUser.staffName} />
<span className={styles.userName}>{currentUser.staffName}</span>
</span>
</HeaderDropdown>
<ChangePasswordModal ref={changePasswordModalRef} />
<AccessTokensModal ref={accessTokensModalRef} />
</>
);
};

View File

@@ -0,0 +1,121 @@
import React, { forwardRef, useImperativeHandle } from 'react';
import { Form, Input, message, Modal } from 'antd';
import { useBoolean } from 'ahooks';
import { changePassword } from '@/services/user';
import { pick } from 'lodash';
import { encryptPassword, encryptKey } from '@/utils/utils';
export interface IRef {
open: () => void;
close: () => void;
}
const ChangePasswordModal = forwardRef<IRef>((_, ref) => {
const [form] = Form.useForm();
const [open, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
const [confirmLoading, { set: setConfirmLoading }] = useBoolean(false);
useImperativeHandle(ref, () => ({
open: () => {
openModal();
form.resetFields();
},
close: () => {
closeModal();
form.resetFields();
},
}));
const handleOk = async () => {
try {
const values = await form.validateFields();
console.log(values);
setConfirmLoading(true);
// Call API to change password
const res = await changePassword({
oldPassword: encryptPassword(values.oldPassword, encryptKey),
newPassword: encryptPassword(values.newPassword, encryptKey),
});
if (res && res.code !== 200) {
return message.warning(res.msg);
}
closeModal();
} catch (error) {
console.log('Failed:', error);
} finally {
setConfirmLoading(false);
}
};
return (
<Modal
title="修改密码"
open={open}
onOk={handleOk}
onClose={closeModal}
onCancel={closeModal}
confirmLoading={confirmLoading}
>
<Form form={form}>
<Form.Item
name="oldPassword"
label="原密码"
rules={[
{
required: true,
message: '请输入原密码!',
},
]}
hasFeedback
>
<Input.Password />
</Form.Item>
<Form.Item
name="newPassword"
label="新密码"
rules={[
{
required: true,
message: '请输入新密码!',
},
{
min: 6,
max: 10,
message: '密码须在6-10字符之间!',
},
]}
hasFeedback
>
<Input.Password />
</Form.Item>
<Form.Item
name="confirm"
label="确认密码"
dependencies={['newPassword']}
hasFeedback
rules={[
{
required: true,
message: '请确认密码!',
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次输入的密码不一致!'));
},
}),
]}
>
<Input.Password />
</Form.Item>
</Form>
</Modal>
);
});
export default ChangePasswordModal;

View File

@@ -23,6 +23,23 @@ declare namespace API {
access?: 'user' | 'guest' | 'admin';
};
export interface UserItem {
id: number;
name: string;
displayName: string;
email: string;
}
export interface UserAccessToken {
createDate: string;
expireDate: string;
expireTime: number;
id: number;
name: string;
token: string;
userName: string;
}
export type LoginStateType = {
status?: 'ok' | 'error';
type?: string;

View File

@@ -20,3 +20,31 @@ export function saveSystemConfig(data: any): Promise<any> {
data,
});
}
export function changePassword(data: { newPassword: string; oldPassword: string }): Promise<any> {
return request(`${process.env.AUTH_API_BASE_URL}user/resetPassword`, {
method: 'post',
data: {
newPassword: data.newPassword,
password: data.oldPassword,
},
});
}
// 获取用户accessTokens
export async function getUserAccessTokens(): Promise<Result<API.UserItem[]>> {
return request.get(`${process.env.AUTH_API_BASE_URL}user/getUserTokens`);
}
export function generateAccessToken(data: { expireTime: number; name: string }): Promise<any> {
return request(`${process.env.AUTH_API_BASE_URL}user/generateToken`, {
method: 'post',
data,
});
}
export function removeAccessToken(id: number): Promise<any> {
return request(`${process.env.AUTH_API_BASE_URL}user/deleteUserToken?tokenId=${id}`, {
method: 'post',
});
}

View File

@@ -472,7 +472,7 @@ export const objToArray = (_obj: ObjToArrayParams, keyType: string = 'string') =
});
};
const encryptKey = CryptoJS.enc.Hex.parse(
export const encryptKey = CryptoJS.enc.Hex.parse(
'9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08',
);