1.不是微服务的架构做登录是浏览器登录发一个请求到tomcat,tomcat会去数据库验证你输入的用户和密码是否是正确的,查出来是正确会把信息存入到session中,并登录成功的时候返回一个页面,存入session有一个好处是hhtp协议是无状态协议的,当第二次登录的时候会去session里面查看信息。这里有一个问题是tomcat是怎么判断不同的session对应的浏览器发送请求。他们是有不同的sessionid并且他们是放在cookie中的。
微服务的登录事件你要考虑到一个问题,每个微服务就像一个单体架构,如果密码还是放在session里面的,用户浏览器访问别的微服务就后变成未登录状态。而且把密码放在session微服务就会变成有状态的服务。但是我们可以状态转移,把密码保存在客户端的cookie里,客户端发请求是无状态的但是带上了cookie。这样就实现了无论客户端访问哪个微服务,都会带上cookie的信息。
但是通过读cookie会窃取cookie的信息,可以去伪造信息,发送请求。但是我们可以在客户端采用jwt的验证。但是jwt又很容易解密。我们可以在验签的时候在加上设备号,这样也可以达到保密的效果。
在验证的时候,用户发任何请求都会带上秘钥的值,然后微服务都要做简单的校验就能够得到用户是否登录,登录了做登录的事情,未登录做为登录的事情。理论上说有n个请求和n个服务是不是都要做n个校验,和登录了做登录的事情,未登录做为登录的事情。这个时候我们就可以统一处理一下,服务器判断登录无非有三种需求,1.未登录也可以执行的操作2.可登录也可不登录可以执行的操作.他们的区别就比如说爱奇艺的vip有VIP和没有vip都可以看视频,但是没vip我当你是游客给你放广告,3.必须登录才能执行操作(1) @IsLogin,比如支付,积分。在可登录也可不登录也可以执行的操作我自定义一个注解,在注解里面可以调用一个方法实现默认值的设置,一旦被打上这个注解就会判断有没有登录。在写了自定义注解的方法里面获得当前用户的信息,只要在这里做一个判断,没有登录做没有登录的逻辑,登录了做登录的逻辑。如果你要实现强制登录就在自定义的注解你把他属性就设置为true。一旦没有登录方法就会强制你调到登录页面。那在判断有没有登录是用户服务发了请求发给哪个服务(城市服务)就在这个城市服务做判断做aop的拦截,(2)LoginAop 。但是在业务代码是会用到业务信息的,比不只是单纯的知道有没有登录,还要知道谁登录,在aop里面的user是知道谁登录的,但是并没有用,还要让开发者知道是谁登录这个时候我们在创建一个(3)UserUtil 类,写静态变量,里面放user对象给一个get和set方法,并在aop里面调用目标方法的之前设置登录的信息,但是这样也有一个问题,业务代码是在controller里面假如有多个同时请求,可能会出现线程的互抢,这个我们可以在类里面加一个ThreadLocal他的作用是哪个线程设置的东西只有在同一个线程才能拿出来
注解的作用仅仅只是标识位的作用,但是可以在注解里面设置一个方法mustLogin为true这个告诉框架这个方法必须要登录的,具体登录,登录有没有成功还是靠spring框架去实现。
(1). @IsLogin 自定义的注解
/**
* javadoc他可以把你源码写的注释生成api文档
* @Documented,在生成javadoc文档的时候这个注解要不要被记录,没什么太大意义
* @Inherited,一旦IsLogin加上这个注解之后你的IsLogin就有了继承性
* @Target,当前注解的作用范围
* ElementType.CONSTRUCTOR -只能够标注在构造方法上
* ElementType.FIELD -能够标注在属性(全局变量)上
* ElementType.LOCAL_VARIABLE -能够标注在局部变量上(很少用)
* ElementType.METHOD -能够标注在方法上
* ElementType.PACKAGE -能够标注在方法参数上
* ElementType.TYPE -作用在类,接口,内部类上
* @Retention -当前注解有效范围
* RetentionPolicy.SOURCE -表示当前注解在源码中有效,一旦编译成class文件,注解会丢失
* RetentionPolicy.CLASS - 表示当前注解在源码、class文件中有效,一旦运行时,就会丢失
* RetentionPolicy.RUNTIME - 表示当前注解运行时有效,如果需要配合反射使用,必须是Runtime范围因为反射是运行时动态解析你的类
*注解里的方法语法: 返回值类型 方法名() [default 默认值];
*/
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface IsLogin {
boolean mustLogin() default false;
}
(2).aop切面实现环绕增强
/**
* 切面类
* @Aspect表示当前类是切面类
* @Component你的切面类一定要被当前类给扫到
*/
@Aspect
@Component
public class LoginAop {
/**
* 环绕增强 必须要这个参数ProceedingJoinPoint
* @Around("@annotation(IsLogin)")表示写了@IsLogin这个注解会被增强
* 拦截器的底层就是靠Aop实现的,但是Aop比拦截器更加灵活,拦截器只能拦截Controller,aop理论上说可以增强spring里面的所有东西包括service,dao
* @param joinPoint
* @return
*/
@Around("@annotation(IsLogin)")
public Object isLogin(ProceedingJoinPoint joinPoint){
//前置增强
//通过请求获得jwtToken和devId类似对象
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
//通过参数获取jwtToken和devId
String jwtToken=request.getParameter("jwtToken");
String devId = request.getParameter("devId");
User user=null;
//判断
if (jwtToken!=null && devId !=null){
//有可能登录
User toke = Jwtutil.isToke(jwtToken, devId);
}
if (user==null){
//未登录
//获取方法上的IsLogin注解,判断mustLogin方法的返回值
//方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//拦截的方法
Method method = signature.getMethod();
//拿到注解,注解也是对象
IsLogin isLogin = method.getAnnotation(IsLogin.class);
boolean flag = isLogin.mustLogin();
if (flag){
//强制登录
//通知前端
return new ResultData().setCode(ResultData.Code.TOLOGIN);
}
}
//设置登录的信息
Object result = null;
try {
//目标方法的调用
result= joinPoint.proceed();
//后置增强
} catch (Throwable throwable) {
throwable.printStackTrace();
//异常增强
}finally {
//后置完成增强
}
return result;
}
}
(3).UserUtil 实现Aop里面知道了谁登录的,这里要告诉业务代码是谁登录的
/**
* Aop里面知道了谁登录的,这里要告诉业务代码是谁登录的
*/
public class UserUtil {
private static ThreadLocal<User> user=new ThreadLocal<>();
public static User getUser() {
return UserUtil.user.get();
}
public static void setUser(User user) {
UserUtil.user.set(user);
}
/**
* 当controllwe的代码调这里,有可能会同时两个请求他们会抢线程
* 哪个线程的设置的值,只有在当前线程才能拿出来,干扰也不行
* @param args
*/
public static void main(String[] args){
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Hello");
new Thread(){
@Override
public void run() {
threadLocal.set("World!");
System.out.println("子线程:" + threadLocal.get());
}
}.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String s = threadLocal.get();
System.out.println(s);
}
}
3.在es中城市服务是内嵌于酒店服务如果我们要对城市服务的新增客房信息和新增客房价格每日信息要同步es,但是城市服务是依附于酒店服务新增城市服务其实是在修改酒店服务,对于内嵌es只能修改脚步完成操作
1.监听客房信息
2.新增客房价格每日信息
@Override
public boolean insertRoomPrice(Integer hid,List<RoomPrice> roomPrices) {
List<UpdateQuery> updateQueries=new ArrayList<>();
//循环价格集合 ->map集合
roomPrices.stream().forEach(roomPrice -> {
//将RoomRices ->Map集合
Map<String , Object> priceMap=new HashMap<>();
priceMap.put("id",roomPrice.getId());
priceMap.put("rid",roomPrice.getRid());
priceMap.put("date",new SimpleDateFormat("yyyy-MM-dd").format(roomPrice.getDate()));
priceMap.put("hasnumber",roomPrice.getHasNumber());
priceMap.put("number",roomPrice.getNumber());
priceMap.put("price",roomPrice.getPrice().doubleValue());
//脚步参数集合
HashMap<String, Object> params = new HashMap<>();
params.put("rid",roomPrice.getId());
params.put("priceInfo",priceMap);
UpdateQuery build = UpdateQuery.builder(hid + "")
.withScript("for(r in ctx._source.rooms){if(r.id == params.id){r.prices.add(params.priceInfo);}}")
.withParams(params)
.build();
updateQueries.add(build);
});
//批量修改
restTemplate.bulkUpdate(updateQueries, IndexCoordinates.of("hotal_index"));
return true;
}