refactor(common-satoken): 收敛登录助手、权限解析与 DAO 缓存逻辑
- LoginHelper 改成更稳的空值/未登录处理,isLogin() 直接走 StpUtil.isLogin() - SaPermissionImpl 把菜单/角色权限提取收敛成一套逻辑 - PlusSaTokenDao 抽了公共写入、读取、TTL 转换逻辑,并修了 searchData 的本地缓存键 - SaTokenExceptionHandler 合并了角色/权限异常处理
This commit is contained in:
+66
-40
@@ -37,8 +37,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String get(String key) {
|
public String get(String key) {
|
||||||
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
return getCacheValue(key);
|
||||||
return (String) o;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,16 +45,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void set(String key, String value, long timeout) {
|
public void set(String key, String value, long timeout) {
|
||||||
if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
|
writeValue(key, value, timeout);
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 判断是否为永不过期
|
|
||||||
if (timeout == NEVER_EXPIRE) {
|
|
||||||
RedisUtils.setCacheObject(key, value);
|
|
||||||
} else {
|
|
||||||
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
|
||||||
}
|
|
||||||
CAFFEINE.invalidate(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +55,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
public void update(String key, String value) {
|
public void update(String key, String value) {
|
||||||
if (RedisUtils.hasKey(key)) {
|
if (RedisUtils.hasKey(key)) {
|
||||||
RedisUtils.setCacheObject(key, value, true);
|
RedisUtils.setCacheObject(key, value, true);
|
||||||
CAFFEINE.invalidate(key);
|
invalidate(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +65,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
@Override
|
@Override
|
||||||
public void delete(String key) {
|
public void delete(String key) {
|
||||||
if (RedisUtils.deleteObject(key)) {
|
if (RedisUtils.deleteObject(key)) {
|
||||||
CAFFEINE.invalidate(key);
|
invalidate(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +74,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public long getTimeout(String key) {
|
public long getTimeout(String key) {
|
||||||
long timeout = RedisUtils.getTimeToLive(key);
|
return toTimeoutSeconds(RedisUtils.getTimeToLive(key));
|
||||||
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
|
|
||||||
return timeout < 0 ? timeout : timeout / 1000 + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -103,8 +91,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Object getObject(String key) {
|
public Object getObject(String key) {
|
||||||
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
return getCacheValue(key);
|
||||||
return o;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,11 +100,9 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
* @param key 键名称
|
* @param key 键名称
|
||||||
* @return object
|
* @return object
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T getObject(String key, Class<T> classType) {
|
public <T> T getObject(String key, Class<T> classType) {
|
||||||
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
|
return classType.cast(getCacheValue(key));
|
||||||
return (T) o;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,16 +110,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setObject(String key, Object object, long timeout) {
|
public void setObject(String key, Object object, long timeout) {
|
||||||
if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
|
writeValue(key, object, timeout);
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 判断是否为永不过期
|
|
||||||
if (timeout == NEVER_EXPIRE) {
|
|
||||||
RedisUtils.setCacheObject(key, object);
|
|
||||||
} else {
|
|
||||||
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
|
|
||||||
}
|
|
||||||
CAFFEINE.invalidate(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -144,7 +120,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
public void updateObject(String key, Object object) {
|
public void updateObject(String key, Object object) {
|
||||||
if (RedisUtils.hasKey(key)) {
|
if (RedisUtils.hasKey(key)) {
|
||||||
RedisUtils.setCacheObject(key, object, true);
|
RedisUtils.setCacheObject(key, object, true);
|
||||||
CAFFEINE.invalidate(key);
|
invalidate(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +130,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
@Override
|
@Override
|
||||||
public void deleteObject(String key) {
|
public void deleteObject(String key) {
|
||||||
if (RedisUtils.deleteObject(key)) {
|
if (RedisUtils.deleteObject(key)) {
|
||||||
CAFFEINE.invalidate(key);
|
invalidate(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,9 +139,7 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public long getObjectTimeout(String key) {
|
public long getObjectTimeout(String key) {
|
||||||
long timeout = RedisUtils.getTimeToLive(key);
|
return toTimeoutSeconds(RedisUtils.getTimeToLive(key));
|
||||||
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
|
|
||||||
return timeout < 0 ? timeout : timeout / 1000 + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,11 +156,63 @@ public class PlusSaTokenDao implements SaTokenDaoBySessionFollowObject {
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
|
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
|
||||||
String keyStr = prefix + "*" + keyword + "*";
|
String pattern = prefix + "*" + keyword + "*";
|
||||||
return (List<String>) CAFFEINE.get(keyStr, k -> {
|
String cacheKey = pattern + start + ":" + size + ":" + sortType;
|
||||||
Collection<String> keys = RedisUtils.keys(keyStr);
|
return (List<String>) CAFFEINE.get(cacheKey, k -> {
|
||||||
|
Collection<String> keys = RedisUtils.keys(pattern);
|
||||||
List<String> list = new ArrayList<>(keys);
|
List<String> list = new ArrayList<>(keys);
|
||||||
return SaFoxUtil.searchList(list, start, size, sortType);
|
return SaFoxUtil.searchList(list, start, size, sortType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从缓存读取对象。
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @return 缓存值
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> T getCacheValue(String key) {
|
||||||
|
return (T) CAFFEINE.get(key, RedisUtils::getCacheObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入缓存值并刷新本地缓存。
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
* @param value 缓存值
|
||||||
|
* @param timeout 超时时间
|
||||||
|
*/
|
||||||
|
private void writeValue(String key, Object value, long timeout) {
|
||||||
|
if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (timeout == NEVER_EXPIRE) {
|
||||||
|
RedisUtils.setCacheObject(key, value);
|
||||||
|
} else {
|
||||||
|
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
|
||||||
|
}
|
||||||
|
invalidate(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除本地缓存。
|
||||||
|
*
|
||||||
|
* @param key 缓存键
|
||||||
|
*/
|
||||||
|
private void invalidate(String key) {
|
||||||
|
CAFFEINE.invalidate(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 Redis TTL 转为秒。
|
||||||
|
*
|
||||||
|
* @param timeoutRedis Redis TTL 毫秒值
|
||||||
|
* @return Sa-Token 需要的秒值
|
||||||
|
*/
|
||||||
|
private long toTimeoutSeconds(long timeoutRedis) {
|
||||||
|
// 加1的目的 解决sa-token使用秒 redis是毫秒导致1秒的精度问题 手动补偿
|
||||||
|
return timeoutRedis < 0 ? timeoutRedis : timeoutRedis / 1000 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-35
@@ -3,16 +3,16 @@ package org.dromara.common.satoken.core.service;
|
|||||||
import cn.dev33.satoken.stp.StpInterface;
|
import cn.dev33.satoken.stp.StpInterface;
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import org.dromara.common.core.enums.UserType;
|
|
||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
import org.dromara.common.core.service.PermissionService;
|
import org.dromara.common.core.service.PermissionService;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
|
||||||
import org.dromara.common.satoken.utils.LoginHelper;
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
import org.dromara.system.api.model.LoginUser;
|
import org.dromara.system.api.model.LoginUser;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sa-token 权限管理实现类
|
* sa-token 权限管理实现类
|
||||||
@@ -30,26 +30,7 @@ public class SaPermissionImpl implements StpInterface {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
return resolvePermissionList(loginId, LoginUser::getMenuPermission, PermissionService::getMenuPermission);
|
||||||
if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
|
|
||||||
PermissionService permissionService = getPermissionService();
|
|
||||||
if (ObjectUtil.isNotNull(permissionService)) {
|
|
||||||
List<String> list = StringUtils.splitList(loginId.toString(), ":");
|
|
||||||
return new ArrayList<>(permissionService.getMenuPermission(Long.parseLong(list.get(1))));
|
|
||||||
} else {
|
|
||||||
throw new ServiceException("PermissionService 实现类不存在");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UserType userType = UserType.getUserType(loginUser.getUserType());
|
|
||||||
if (userType == UserType.APP_USER) {
|
|
||||||
// 其他端 自行根据业务编写
|
|
||||||
}
|
|
||||||
if (CollUtil.isNotEmpty(loginUser.getMenuPermission())) {
|
|
||||||
// SYS_USER 默认返回权限
|
|
||||||
return new ArrayList<>(loginUser.getMenuPermission());
|
|
||||||
} else {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,26 +42,33 @@ public class SaPermissionImpl implements StpInterface {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<String> getRoleList(Object loginId, String loginType) {
|
public List<String> getRoleList(Object loginId, String loginType) {
|
||||||
|
return resolvePermissionList(loginId, LoginUser::getRolePermission, PermissionService::getRolePermission);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析当前登录对象的权限列表。
|
||||||
|
*
|
||||||
|
* @param loginId 登录ID
|
||||||
|
* @param localPermissionExtractor 当前登录用户权限提取器
|
||||||
|
* @param remotePermissionExtractor 远程权限提取器
|
||||||
|
* @return 权限列表
|
||||||
|
*/
|
||||||
|
private List<String> resolvePermissionList(Object loginId,
|
||||||
|
java.util.function.Function<LoginUser, Collection<String>> localPermissionExtractor,
|
||||||
|
BiFunction<PermissionService, Long, Collection<String>> remotePermissionExtractor) {
|
||||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||||
if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
|
if (ObjectUtil.isNull(loginUser) || !loginUser.getLoginId().equals(loginId)) {
|
||||||
PermissionService permissionService = getPermissionService();
|
PermissionService permissionService = getPermissionService();
|
||||||
if (ObjectUtil.isNotNull(permissionService)) {
|
if (ObjectUtil.isNotNull(permissionService)) {
|
||||||
List<String> list = StringUtils.splitList(loginId.toString(), ":");
|
return new ArrayList<>(remotePermissionExtractor.apply(permissionService, resolveUserId(loginId)));
|
||||||
return new ArrayList<>(permissionService.getRolePermission(Long.parseLong(list.get(1))));
|
|
||||||
} else {
|
|
||||||
throw new ServiceException("PermissionService 实现类不存在");
|
|
||||||
}
|
}
|
||||||
|
throw new ServiceException("PermissionService 实现类不存在");
|
||||||
}
|
}
|
||||||
UserType userType = UserType.getUserType(loginUser.getUserType());
|
Collection<String> permissionList = localPermissionExtractor.apply(loginUser);
|
||||||
if (userType == UserType.APP_USER) {
|
if (CollUtil.isNotEmpty(permissionList)) {
|
||||||
// 其他端 自行根据业务编写
|
return new ArrayList<>(permissionList);
|
||||||
}
|
|
||||||
if (CollUtil.isNotEmpty(loginUser.getRolePermission())) {
|
|
||||||
// SYS_USER 默认返回权限
|
|
||||||
return new ArrayList<>(loginUser.getRolePermission());
|
|
||||||
} else {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -96,4 +84,19 @@ public class SaPermissionImpl implements StpInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从登录ID中提取用户ID。
|
||||||
|
*
|
||||||
|
* @param loginId 登录ID
|
||||||
|
* @return 用户ID
|
||||||
|
*/
|
||||||
|
private Long resolveUserId(Object loginId) {
|
||||||
|
String loginIdStr = loginId.toString();
|
||||||
|
int separatorIndex = loginIdStr.indexOf(':');
|
||||||
|
if (separatorIndex < 0 || separatorIndex == loginIdStr.length() - 1) {
|
||||||
|
throw new ServiceException("登录ID格式错误");
|
||||||
|
}
|
||||||
|
return Long.parseLong(loginIdStr.substring(separatorIndex + 1));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-17
@@ -26,24 +26,11 @@ public class SaTokenExceptionHandler {
|
|||||||
* @param request 当前请求
|
* @param request 当前请求
|
||||||
* @return 统一失败响应
|
* @return 统一失败响应
|
||||||
*/
|
*/
|
||||||
@ExceptionHandler(NotPermissionException.class)
|
@ExceptionHandler({NotPermissionException.class, NotRoleException.class})
|
||||||
public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
|
public R<Void> handleNotAccessException(RuntimeException e, HttpServletRequest request) {
|
||||||
String requestURI = request.getRequestURI();
|
String requestURI = request.getRequestURI();
|
||||||
log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
|
String reason = e instanceof NotRoleException ? "角色权限校验失败" : "权限码校验失败";
|
||||||
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
|
log.error("请求地址'{}',{}'{}'", requestURI, reason, e.getMessage());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理角色权限校验失败异常。
|
|
||||||
*
|
|
||||||
* @param e 异常信息
|
|
||||||
* @param request 当前请求
|
|
||||||
* @return 统一失败响应
|
|
||||||
*/
|
|
||||||
@ExceptionHandler(NotRoleException.class)
|
|
||||||
public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
|
|
||||||
String requestURI = request.getRequestURI();
|
|
||||||
log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
|
|
||||||
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
|
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+25
-12
@@ -3,6 +3,7 @@ package org.dromara.common.satoken.utils;
|
|||||||
import cn.dev33.satoken.session.SaSession;
|
import cn.dev33.satoken.session.SaSession;
|
||||||
import cn.dev33.satoken.stp.StpUtil;
|
import cn.dev33.satoken.stp.StpUtil;
|
||||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||||
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.http.useragent.UserAgent;
|
import cn.hutool.http.useragent.UserAgent;
|
||||||
@@ -98,13 +99,12 @@ public class LoginHelper {
|
|||||||
*
|
*
|
||||||
* @return 登录用户
|
* @return 登录用户
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T extends LoginUser> T getLoginUser() {
|
public static <T extends LoginUser> T getLoginUser() {
|
||||||
SaSession session = StpUtil.getTokenSession();
|
try {
|
||||||
if (ObjectUtil.isNull(session)) {
|
return getLoginUser(StpUtil.getTokenSession());
|
||||||
|
} catch (NotLoginException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (T) session.get(LOGIN_USER_KEY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -113,9 +113,27 @@ public class LoginHelper {
|
|||||||
* @param token Token
|
* @param token Token
|
||||||
* @return 登录用户
|
* @return 登录用户
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <T extends LoginUser> T getLoginUser(String token) {
|
public static <T extends LoginUser> T getLoginUser(String token) {
|
||||||
SaSession session = StpUtil.getTokenSessionByToken(token);
|
if (StringUtils.isBlank(token)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
SaSession session;
|
||||||
|
try {
|
||||||
|
session = StpUtil.getTokenSessionByToken(token);
|
||||||
|
} catch (NotLoginException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getLoginUser(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从会话中读取登录用户。
|
||||||
|
*
|
||||||
|
* @param session 登录会话
|
||||||
|
* @return 登录用户
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T extends LoginUser> T getLoginUser(SaSession session) {
|
||||||
if (ObjectUtil.isNull(session)) {
|
if (ObjectUtil.isNull(session)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -225,12 +243,7 @@ public class LoginHelper {
|
|||||||
* @return 是否已登录
|
* @return 是否已登录
|
||||||
*/
|
*/
|
||||||
public static boolean isLogin() {
|
public static boolean isLogin() {
|
||||||
try {
|
return StpUtil.isLogin();
|
||||||
StpUtil.checkLogin();
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user