同一账户在不同地方登录问题

news/2024/11/9 0:04:22/

同一个账户在在不同地方登录,别的地方提示下线了。

在这里插入图片描述

概述

其实就是以用户最后一次登录的为准。其他登录的地方全部提示:你已经下线,是否重新登录。从而保护你的操作信息是安全的。

实现原理

随机产生一个uuid,然后设置一个键key,比如下面两个key中的一个,然后再设置UserBo,也要将这个uuid回传会前端
在这里插入图片描述
在这里插入图片描述
然后编写前端传给后端所要经过的拦截器
如果该用户在另一处登录会覆盖redis中的tokenuuid,如果你还是从前端向后端传原来的tokenuuid,拦截器中和redis中的tokenuuid比较,发现不相同,报出异常在异地登录。
在这里插入图片描述

在这里插入图片描述

只能一个地方登录

key = sys:login:+userid

如果同设备互斥

key = sys:login:“+pc+”:“+userid

具体实现

AdminRedisKeyManager

package com.pug.zixun.config.redis;
public interface AdminRedisKeyManager {// 登录token续期使用的keyString USER_LOGIN_TOKEN_KEY = "pug:user:login:token:";// 下线使用的rediskeyString USER_LOGIN_LOGOUT_KEY = "pug:user:logout:";// 续期返回的新的token的keyString RESPONSE_AUTH_TOKEN = "x-auth-token";//指定token的claim的key的名字String PUG_USER_ID = "pug_user_id";// header中userid 接口校验和线下使用String TOKEN_USERID_NAME = "token_userid";// header中token 接口校验使用String TOKEN_NAME = "token";// header中token_uuid 下线使用String TOKEN_UUID_NAME = "token_uuid";
}

JwtService

package com.pug.zixun.config.jwt;import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.pug.zixun.common.enums.AdminUserResultEnum;
import com.pug.zixun.common.ex.PugValidatorException;
import com.pug.zixun.common.utils.date.TmDateUtil;
import com.pug.zixun.common.utils.fn.asserts.Vsserts;
import com.pug.zixun.config.redis.AdminRedisKeyManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.concurrent.TimeUnit;
// 问题1:别人工具类 static方法,方便进行操作和获取。
// 问题2:为什么要让上面容器管理,因为我要考虑把这里常量用配置文件管理,甚至用统一配置中心。所以
@Component
public class JwtService  implements AdminRedisKeyManager {//jwt 私钥 注意这里一定要保密,不能泄露,否则就会被别人通过程序伪造@Value("${pug.jwt.key}")private String KEY = "pugadmin123456";//指定作者@Value("${pug.jwt.author}")private  String AUTHOR = "xiexiangban";// token的私有前缀@Value("${pug.jwt.prefix}")private String PUG_TOKEN_PREFIX = "pugbear ";// 续期时间@Value("${pug.jwt.period}")private Long period =  30L;// 1 秒private Long ONE_SECOND = 1000L;// 1 分钟private Long ONE_MINIUTE = ONE_SECOND * 60;// token 30分钟过期private Long TOKEN_EXPIRE_TIME = ONE_MINIUTE * period;@Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 创建token** @param userId* @return*/public String createToken(Long userId) {// 1:确定token加密签名的算法和密钥Algorithm algorithm = Algorithm.HMAC256(KEY);// 2: 创建tokenString token = JWT.create()// 指定作者.withIssuer(AUTHOR)// 指定用户id即可,不要去放完整的用户对象信息。因为生成token太长,// 为什么就放一个id,因为后续开发我们会把解析的用户id,去db或redis查一遍。保证实时性。// 方便以后对平台的一些恶意分子直接拉黑, 就会生效。//PUG_USER_ID="pug_user_id".withClaim(PUG_USER_ID, userId)// 签发时间.withIssuedAt(new Date())// 指定token的过期时间.withExpiresAt(new Date(System.currentTimeMillis() + TOKEN_EXPIRE_TIME))// 签名返回.sign(algorithm);return token;}/*** 校验 token** @param token* @return*/public boolean verify(String token) {try {// 1:确定token加密签名的算法和密钥Algorithm algorithm = Algorithm.HMAC256(KEY);// 2 : 获取token的校验对象JWTVerifier verifier = JWT.require(algorithm).withIssuer(AUTHOR).build();// 3: 开始校验,如果校验通过DecodedJWT.如果token是伪造或者失效的,就会出现异常。DecodedJWT jwt = verifier.verify(token);return true;} catch (Exception ex) {return false;}}/*** token 自动续期* @param token* @param userId* @return*/public boolean refreshTokenRedis(String token, String userId, HttpServletResponse response){// Redis双倍缓存keyString tokenKey = USER_LOGIN_TOKEN_KEY + token;String cacheToken = stringRedisTemplate.opsForValue().get(tokenKey);if(Vsserts.isEmpty(cacheToken)){return false;}try {// 把自己校验一次,如果自己能通过,说明token还没有过期// 1:确定token加密签名的算法和密钥Algorithm algorithm = Algorithm.HMAC256(KEY);// 2 : 获取token的校验对象JWTVerifier verifier = JWT.require(algorithm).withIssuer(AUTHOR).build();// 3: 开始校验,如果校验通过DecodedJWT.如果token是伪造或者失效的,就会出现异常。verifier.verify(token);}catch (TokenExpiredException tokenExpiredException){// 如果过期了。redis还能找到。说明还可以继续激活使用if (stringRedisTemplate.hasKey(tokenKey)) {// 生成新的tokenString newToken  = this.createToken(new Long(userId));stringRedisTemplate.opsForValue().set(tokenKey,newToken,TOKEN_EXPIRE_TIME * 2, TimeUnit.MILLISECONDS);return true;}}catch ( Exception ex){throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR_STATUS);}return true;}/*** 签发时间续期** @param token* @param tokenUserId* @param response*/public void refreshToken(String token, Long tokenUserId, HttpServletResponse response) {// token续期// 获取token的签发时间 --------第一种写法Date signTokenTime = this.getTokenIssuedTime(token);int diffminutes = TmDateUtil.diffminutes(signTokenTime,new Date());// 开始刷新token   10的含义是:旧的token还剩下10分钟,在最后的这10分钟范围内去续期,// 假设你的token存活时间(TOKEN_EXPIRE_TIME=30)。那么久是久的token存活20分钟,在20分钟以后时间内都是续期时间点。Long period = TOKEN_EXPIRE_TIME - 10;if(diffminutes >= period ){// 续期,重新生成一个新的tokenString newToken = this.createToken(tokenUserId);// 通过response的头部输出token,然后前台通过reponse获取response.setHeader(RESPONSE_AUTH_TOKEN, newToken);}}/*** 过期时间续期** @param token* @param tokenUserId* @param response*/public void refreshToken2(String token, Long tokenUserId, HttpServletResponse response) {// token续期// 获取token的签发时间 --------第一种写法Date expireTime = this.getTokenExpireTime(token);// 假设过期时间是 30分钟,用过期时间减去当前时间:30 29 28 27 20...10int diffminutes = TmDateUtil.diffminutes(new Date(),expireTime);// 如果时间以及过去了20分钟,到最后十分钟的时候就开始续期 10 9 8 7if(diffminutes <= 10 ){// 续期,重新生成一个新的tokenString newToken = this.createToken(tokenUserId);// 通过response的头部输出token,然后前台通过reponse获取response.setHeader(RESPONSE_AUTH_TOKEN, newToken);}}/*** @param token* @return*/public Long getTokenUserId(String token) {try {// 1:确定token加密签名的算法和密钥Algorithm algorithm = Algorithm.HMAC256(KEY);// 2 : 获取token的校验对象JWTVerifier verifier = JWT.require(algorithm).withIssuer(AUTHOR).build(); //Reusable verifier instance// 3: 开始校验,如果校验通过DecodedJWT.如果token是伪造或者失效的,就会出现异常。DecodedJWT jwt = verifier.verify(token);return jwt.getClaim(PUG_USER_ID).asLong();} catch (Exception ex) {throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR);}}/*** 根据token 获取签发时间* @param token* @return*/public Date getTokenIssuedTime(String token){try {// 1:确定token加密签名的算法和密钥Algorithm algorithm = Algorithm.HMAC256(KEY);// 2 : 获取token的校验对象JWTVerifier verifier = JWT.require(algorithm).withIssuer(AUTHOR).build(); //Reusable verifier instance// 3: 开始校验,如果校验通过DecodedJWT.如果token是伪造或者失效的,就会出现异常。DecodedJWT jwt = verifier.verify(token);return jwt.getIssuedAt();} catch (Exception ex) {throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR);}}/*** 获取过期时间* @param token* @return*/public Date getTokenExpireTime(String token){try {// 1:确定token加密签名的算法和密钥Algorithm algorithm = Algorithm.HMAC256(KEY);// 2 : 获取token的校验对象JWTVerifier verifier = JWT.require(algorithm).withIssuer(AUTHOR).build(); //Reusable verifier instance// 3: 开始校验,如果校验通过DecodedJWT.如果token是伪造或者失效的,就会出现异常。DecodedJWT jwt = verifier.verify(token);return jwt.getExpiresAt();} catch (Exception ex) {throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR);}}/*** 获取请求头的token** @param request* @return*/public String getToken(HttpServletRequest request) {String token = request.getHeader(TOKEN_NAME);if (Vsserts.isEmpty(token)) {return null;}if (!token.startsWith(PUG_TOKEN_PREFIX)) {throw new PugValidatorException(AdminUserResultEnum.TOKEN_ERROR_STATUS);}// 截取前缀token = token.substring(PUG_TOKEN_PREFIX.length());// 返回return token;}/*** 获取请求头的token的用户ID** @param request* @return*/public String getTokenUserId(HttpServletRequest request) {String tokenUserId = request.getHeader(TOKEN_USERID_NAME);if (Vsserts.isEmpty(tokenUserId)) {return null;}// 返回return tokenUserId;}/*** 登录使用,双倍时间* @param token*/public void redisToken(String token){// Jwt和redis的续期双倍时间String tokenKey = USER_LOGIN_TOKEN_KEY + token;// 记住,在redis的单位默认是 秒,也就是说这个tokenkey是双倍,时间是30分钟,双倍也就是60分钟  转换成秒 3600秒stringRedisTemplate.opsForValue().set(tokenKey, token, TOKEN_EXPIRE_TIME * 2, TimeUnit.MILLISECONDS);}
}

IJwtBlackService

package com.pug.zixun.config.redis;
public interface IJwtBlackService {String BLACK_STRING_KEY = "blacklist:string";Long BLACK_EXPIRE_TIME = 30L;String BLACK_LIST_KEY = "blacklist:set";//添加黑白名单void addBlackList(String token);// 2: 判断当前用户是否在黑名单中boolean isBlackList(String token);// 4: 删除黑名单boolean removeBlackList(String token);
}

AdminUserResultEnum

采用枚举

package com.pug.zixun.common.enums;public interface AdminResultInterface {Integer getCode();String getMsg();}
package com.pug.zixun.common.enums;
public enum AdminUserResultEnum implements AdminResultInterface{USER_NULL_ERROR(100601, "用户不存在"),USER_SERVER_ERROR(100602, "服务出现故障"),USER_INPUT_USERNAME_ERROR(100603, "用户名或密码输入有误"),TOKEN_ERROR(100604, "token expired"),TOKEN_NOT_FOUND(100605, "token not found"),USER_FORBIDDEN_ERROR(100606, "用户异常,请联系管理员"),USER_NAME_NOT_EMPTY(100607, "账号不能是空"),USER_PWD_NOT_EMPTY(100607, "密码不能为空"),USER_LOGIN_UUID_EMPTY(100608, "会话过期了..."),TOKEN_ERROR_STATUS(100609, "token无效"),USER_LOGIN_SAME(100610, "你已经在别地方登录了");private Integer code;private String msg;AdminUserResultEnum(Integer code, String msg) {this.code = code;this.msg = msg;}@Overridepublic Integer getCode() {return code;}@Overridepublic String getMsg() {return msg;}
}

UserVo

package com.pug.zixun.vo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserVo extends ParentVo implements java.io.Serializable {// idprivate Long id;// 用户姓名private String username;// 密码private String password;// 验证码private String code;// 验证码的UUIDprivate String uuid;// tokenprivate String token;
}

UserServiceImpl,IUserService

package com.pug.zixun.service.user;import com.baomidou.mybatisplus.extension.service.IService;
import com.pug.zixun.domain.User;
import com.pug.zixun.vo.UserVo;
public interface IUserService extends IService<User> {/*** 登录* @param userVo* @return*/User login(UserVo userVo);}
package com.pug.zixun.service.user;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pug.zixun.domain.User;
import com.pug.zixun.mapper.UserMapper;
import com.pug.zixun.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {/*** 登录** @param userVo* @return*/@Overridepublic User login(UserVo userVo) {LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(User::getUsername, userVo.getUsername());User user = this.getOne(lambdaQueryWrapper);return user;}}

MD5Utils

package com.pug.zixun.common.utils.pwd;import java.math.BigInteger;
import java.security.MessageDigest;public class MD5Util {public MD5Util() {}public static String md5(String str) {try {MessageDigest md5 = MessageDigest.getInstance("MD5");md5.update(str.getBytes("UTF-8"));return bytesToHex(md5.digest());} catch (Exception var2) {throw new RuntimeException(var2);}}public static String md5slat(String str) {//MD5加盐return MD5Util.md5(MD5Util.md5("kuangstudy" + str + "202102170318!!!"));}public static String bytesToHex(byte[] bytes) {BigInteger bigInt = new BigInteger(1, bytes);String hashtext;for (hashtext = bigInt.toString(16); hashtext.length() < 32; hashtext = "0" + hashtext) {}return hashtext;}public static void main(String[] args) {System.out.println(md5slat("123456"));}
}

UserBo

返回给前端的格式

package com.pug.zixun.bo;
import com.pug.zixun.domain.User;
import lombok.Data;
@Data
public class UserBo implements java.io.Serializable {// 接口校验使用private String token;// 下线使用private String tokenUuid;//  登录的用户信息private User user;
}

* PassportLoginController

1 : 在登录产生一个随机的uuid放入缓存中

package com.pug.zixun.controller.login;import com.pug.zixun.bo.UserBo;
import com.pug.zixun.common.enums.AdminUserResultEnum;
import com.pug.zixun.common.ex.PugValidatorException;
import com.pug.zixun.common.utils.pwd.MD5Util;
import com.pug.zixun.config.BaseController;
import com.pug.zixun.config.jwt.JwtService;
import com.pug.zixun.config.validator.PugAssert;
import com.pug.zixun.domain.User;
import com.pug.zixun.service.user.IUserService;
import com.pug.zixun.vo.UserVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import java.util.UUID;
@RestController
@Slf4j
public class PassportLoginController extends BaseController {@Autowiredprivate IUserService userService;@Autowiredprivate JwtService jwtService;@Autowiredprivate RedisTemplate redisTemplate;/*** 登录** @param userVo* @return*/@PostMapping("/login/toLogin")public UserBo logined(@RequestBody UserVo userVo) {// 这里有校验,spring-validator框架来完成 或者用断言 或者用自己封装的PugAssert.isEmptyEx(userVo.getUsername(), AdminUserResultEnum.USER_NAME_NOT_EMPTY);PugAssert.isEmptyEx(userVo.getPassword(), AdminUserResultEnum.USER_PWD_NOT_EMPTY);// 根据用户名称查询用户信息User dbLoginUser = userService.login(userVo);PugAssert.isNullEx(dbLoginUser, AdminUserResultEnum.USER_NULL_ERROR);// 用户输入的密码,加盐处理不容易被破解String inputPwd = MD5Util.md5slat(userVo.getPassword());// 如果输入密码和数据库密码不一致boolean isLogin = dbLoginUser.getPassword().equalsIgnoreCase(inputPwd);// 如果输入的账号和有误,isLogin=false.注意isFalseEx在里面取反的,所以会抛出异常PugAssert.isFalseEx(isLogin,AdminUserResultEnum.USER_INPUT_USERNAME_ERROR);UserBo userBo = new UserBo();// 根据用户生成tokenString token = jwtService.createToken(dbLoginUser.getId());userBo.setToken(token);// 注意把一些敏感信息全部清空返回dbLoginUser.setPassword(null);userBo.setUser(dbLoginUser);// 登录挤下线String tokenUuid = UUID.randomUUID().toString();//获取用户id设置keyString tokenUuidKey = "pug:user:login:"+dbLoginUser.getId();redisTemplate.opsForValue().set(tokenUuidKey,tokenUuid);userBo.setTokenUuid(tokenUuid);return userBo;}}

* PassportLoginInterceptor

2:编写下线拦截器

package com.pug.zixun.config.interceptor;import com.pug.zixun.common.anno.IgnoreToken;
import com.pug.zixun.common.enums.AdminUserResultEnum;
import com.pug.zixun.common.ex.PugValidatorException;
import com.pug.zixun.common.utils.fn.asserts.Vsserts;
import com.pug.zixun.config.jwt.JwtService;
import com.pug.zixun.config.validator.PugAssert;
import com.pug.zixun.domain.User;
import com.pug.zixun.local.UserThreadLocal;
import com.pug.zixun.service.user.IUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.UUID;/*** 挤下线使用*/
@Component
@Slf4j
public class PassportLogoutInterceptor implements HandlerInterceptor {@Autowiredprivate StringRedisTemplate  stringRedisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {/*********************这里是用户输入的信息********************/// 获取用户传递过来的tokenuuidString tokenUuid = request.getHeader("token_uuid");// 如果没有获取到,说明没有登录PugAssert.isEmptyEx(tokenUuid,AdminUserResultEnum.USER_LOGIN_UUID_EMPTY);// *******************从redis获取uuid********************/String tokenUserId = request.getHeader("token_userid");String tokenUuidKey = "pug:user:login:"+tokenUserId;String cacheUuid =  stringRedisTemplate.opsForValue().get(tokenUuidKey);// 如果没有获取到,说明没有登录PugAssert.isEmptyEx(tokenUuid,AdminUserResultEnum.USER_LOGIN_UUID_EMPTY);// *******************比较********************/// 如果你当前访问的uuid和缓存的uuid不同,就说明你在别的地方登录了。if(!tokenUuid.equalsIgnoreCase(cacheUuid)){throw new PugValidatorException(AdminUserResultEnum.USER_LOGIN_SAME);}return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {UserThreadLocal.remove();}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserThreadLocal.remove();}
}

WebMvcConfiguration

3.注册和设置规则

package com.pug.zixun.config.mvc;import com.pug.zixun.config.interceptor.PassportLoginInterceptor;
import com.pug.zixun.config.interceptor.PassportLogoutInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {@Autowiredprivate PassportLogoutInterceptor passportLogoutInterceptor;@Autowiredprivate PassportLoginInterceptor passportLoginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 下线拦截器registry.addInterceptor(passportLogoutInterceptor).addPathPatterns("/admin/**").excludePathPatterns("/admin/login/**");// 设置passportlogin的规则。以/admin开头的所有请求都要进行token校验registry.addInterceptor(passportLoginInterceptor).addPathPatterns("/admin/**").excludePathPatterns("/admin/login/**");}
}

测试

先登录,返回前端userbo。获得token,tokenUuid,user属性
在这里插入图片描述
然后通过得到的属性开始测试
在这里插入图片描述
debug发现cacheUuid和tokenUuid相等
在这里插入图片描述
返回成功
在这里插入图片描述
重新登录后,更新了redis的uuid,所以抛出异常
在这里插入图片描述
在这里插入图片描述


http://www.ppmy.cn/news/869502.html

相关文章

下单账号与支付账号不一致_【支付宝】如何申请支付账号

简单说明:1.支付宝支付功能开通相对较快,一般1天就可以通过了 2.以下的网址是方便您直接跳转到对应页面的,点击网址后先登录哦 添加关联账户(为了财务分离)(如果已经有一个想要做APP支付的支付宝账户,请用原有的公司的账户登陆后访问以下网址) https://uemprod.alipay.…

亚马逊店铺同站点关联了应该怎么操作方法?

亚马逊店铺同站点关联了应该怎么操作方法&#xff1f; 一般陌生关联都会收到这样的邮件&#xff1a; 尊敬的 ***&#xff1a; 您好&#xff01; 我们发现您与以 L***** 开头的账户有关。 我们收到了您提交的信息&#xff0c;但目前这些信息不足以重新激活您的账户。要重新激活您…

亚马逊店铺品牌关联怎么办有什么好的解决方法?

亚马逊店铺品牌关联怎么办有什么好的解决方法&#xff1f; 关于亚马逊店铺不懂得问题都可以联系我..... 诉信的内容一定要强调你只有一个卖家账号&#xff0c;没有其他的亚马逊卖家账号&#xff0c;并且账号的行为表现良好&#xff0c;并没有违规的几率&#xff0c;而且还是优质…

亚马逊卖家问题-01.注册了品牌,但是仍然无法使用Vine和品牌旗舰店功能

这个问题困扰了我5天,开了3个case,其实很好解决,根本不用开case,下面直接上干货: 直接进入卖家后台的Vine菜单里面 其实已经通过了品牌注册,但是这个功能仍然不能使用 这时候需要点击最下方的"了解有关Amazon Vine计划的更多信息" 点开之后,会看到官方对于Vine的…

亚马逊自营店铺被跟卖,我们该如何保护我方的listing?

亚马逊自营店铺是让无数卖家又爱又恨的存在。近日就有一位亚马逊欧洲站卖家发现自己已上线半年的产品莫名其妙被亚马逊其他站点的自营店铺跟卖了&#xff0c;更加荒唐的是跟卖产品的ASIN和listing都与这位卖家的店铺一模一样&#xff0c;属实让这位卖家冒出许多的问号。 虽然…

edge浏览器登录谷歌账号显示此浏览器或应用可能不安全

1.关闭edge浏览器 2. 打开C:\Program Files (x86)\Microsoft\Edge\Application 3.双击msedge_proxy.exe

java中json和对象之间相互转换的运用

1.目录结构 2.配置相关文件 2.1.引入相关的pom文件 pom.xml <dependencies><!-- JSON --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.12.3</vers…

绝美的ArcGIS Pro的制图样式

乐高地图 这种 ArcGIS Pro 风格使任何矢量点、线或多边形图层看起来像一个由小塑料高贵螺柱组成的网格,随时可以捕捉眼球并为拟物制图激起无限的兴奋!此外,当您放大和缩小时,它总是会重新排序,总是看起来不错且块状。 毛毡风格 这个是我最喜欢的风格,他真是太棒了真的,…