系统间通信问题

数据同步

dubbo 与 Ribbon的区别_ide


性能有影响。有新的系统,代码需要更新,耦合太高。

方案二:引入消息中间件(MQ),可解耦,异步化。

dubbo 与 Ribbon的区别_dubbo 与 Ribbon的区别_02

RabbitMQ

1、simple模式:一对一

dubbo 与 Ribbon的区别_redis_03


2、work模式:消息是共享模式

dubbo 与 Ribbon的区别_ide_04


限流:对于消费者说,哪个干的快,哪个就干的多。给1个,处理完后再给下一个。

用限流+手工确认。

3、发布订阅模式

dubbo 与 Ribbon的区别_rabbitmq_05


X:交换机,有两个队列,每个消费者都有队列。

生产者面向的是交换机。

4、路由模式:可以指定推送消息给消费者。打标记。

5、通配符模式:

dubbo 与 Ribbon的区别_dubbo 与 Ribbon的区别_06


*

采用MQ的场景

dubbo 与 Ribbon的区别_redis_07

dubbo 与 Ribbon的区别_rabbitmq_08

dubbo 与 Ribbon的区别_java_09

邮件服务

redis实战

首页使用redis优化

第一次没有查数据库,第二次就不用查数据库,可以直接查缓存。

dubbo 与 Ribbon的区别_java_10


dubbo 与 Ribbon的区别_rabbitmq_11

  • 配置application.yml
redis:
    host: 192.168.142.137
    port: 6379
    password: java1907
  • 配置类redisConfig
@Configuration
public class RedisConfig {

    @Bean(name = "myStringRedisTemplate")
    public RedisTemplate<String,Object> getRedisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        return redisTemplate;
    }
}

建一个RedisTemplate的对象

  • service实现
@Service
public class ProductTypeServiceImpl extends BaseServiceImpl<TProductType> implements IProductTypeService {

    @Autowired
    private TProductTypeMapper productTypeMapper;

    @Resource(name = "myStringRedisTemplate")
    private RedisTemplate<String,Object> redisTemplate;

    @Override
    public IBaseDao<TProductType> getBaseDao() {
        return productTypeMapper;
    }

    /**
     * 重写获取列表的方法,加入缓存的逻辑
     * 硬件
     * 内存读取速度》磁盘读取速度
     * @return
     */
    @Override
    public List<TProductType> list() {
        //1.查询当前缓存是否存在分类信息
        List<TProductType> list = (List<TProductType>) redisTemplate.opsForValue().get("productType:list");
        if (list == null || list.size()==0) {
            //2.缓存不存在,则查询数据库
            list = super.list();
            //3.将查询结果保存到缓存中
            redisTemplate.opsForValue().set("productType:list",list);
        }
        return list;
    }
}
  • Controller层调用
@Controller
@RequestMapping("index")
public class IndexController {

    @Reference
    private IProductTypeService productTypeService;

    @RequestMapping("show")
    public String showIndex(Model model) {
        //1.获取到数据
        List<TProductType> list = productTypeService.list();
        //2.传递到前端进行展示
        model.addAttribute("list", list);
        //
        return "index";
    }

    @RequestMapping("listType")
    @ResponseBody
    public ResultBean listType() {
        //1.获取到数据
        List<TProductType> list = productTypeService.list();
        //2.封装返回
        return new ResultBean("200", list);
    }
}

首页展示是远程调用了商品服务

注册系统

注册系统是远程调用了用户的服务

dubbo 与 Ribbon的区别_redis_12


通过MQ去异步通知邮件服务进行邮件发送。Mq应用场景:发送邮件

dubbo 与 Ribbon的区别_rabbitmq_13


dubbo 与 Ribbon的区别_ide_14


发送失败怎么处理?

dubbo 与 Ribbon的区别_redis_15

登录系统

表结构设计

表名:t-user

dubbo 与 Ribbon的区别_redis_16


phone和email加唯一索引

dubbo 与 Ribbon的区别_redis_17


统一定义返回:ResultBean

dubbo 与 Ribbon的区别_dubbo 与 Ribbon的区别_18

@Data
@AllArgsConstructor
public class ResultBean<T> implements Serializable{

    private String statusCode;
    private T data;

}

用户类:

public class TUser implements Serializable{
    private Long id;

    private String username;

    private String password;

    private String phone;

    private String email;

    private Boolean flag;

    private Date createTime;

    private Date updateTime;
    }
  • 接口层
    IUserService:
public interface IUserService extends IBaseService<TUser>{
    public ResultBean checkUserNameIsExists(String username);
    public ResultBean checkPhoneIsExists(String phone);
    public ResultBean checkEmailIsExists(String email);
    public ResultBean generateCode(String identification);
    ResultBean checkLogin(TUser user);
    ResultBean checkIsLogin(String uuid);
  • service层:
@Service
public class UserServiceImpl extends BaseServiceImpl<TUser> implements IUserService{

    @Autowired
    private TUserMapper userMapper;

    @Resource(name = "myStringRedisTemplate")
    private RedisTemplate<String,Object> redisTemplate;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Override
    public ResultBean checkUserNameIsExists(String username) {
        return null;
    }

    @Override
    public ResultBean checkPhoneIsExists(String phone) {
        return null;
    }

    @Override
    public ResultBean checkEmailIsExists(String email) {
        return null;
    }

    @Override
    public ResultBean generateCode(String identification) {
        //1.生成验证码
        String code = CodeUtils.generateCode(6);
        //2.往redis保存一个凭证跟验证码的对应关系 key-value
        redisTemplate.opsForValue().set(identification,code,2, TimeUnit.MINUTES);
        //3.发送消息,给手机发送验证码
        //3.1 调通阿里云提供的短信Demo
        //3.2 发送短信这个功能,整个体系很多系统都可能会用上,变成一个公共的服务
        //3.3 发送消息,异步处理发送短信
        Map<String,String> params = new HashMap<>();
        params.put("identification",identification);
        params.put("code",code);
        rabbitTemplate.convertAndSend("sms-exchange","sms.code",params);

        //此处是不需要发送任何邮件,仅做测试使用
        Map<String,String> params2 = new HashMap<>();
        params2.put("to","2678383176@qq.com");
        params2.put("username","马老师");

        rabbitTemplate.convertAndSend("email-exchange","email.birthday",params2);

        return new ResultBean("200","OK");
    }

    @Override
    public ResultBean checkLogin(TUser user) {
        //1.根据用户输入的账号(手机/邮箱)信息,去查询
        TUser currentUser = userMapper.selectByIdentification(user.getUsername());
        //2.根据查询出来的密码信息,进行比较
        if(currentUser != null){
            //if(user.getPassword().equals(currentUser.getPassword())){
            if(passwordEncoder.matches(user.getPassword(),currentUser.getPassword())){
                //1.生成uuid
                //String uuid = UUID.randomUUID().toString();
                //2.保存到redis中,并设置有效期为30分钟,代替原先的session
                //redisTemplate.opsForValue().set("user:token:"+uuid,currentUser.getUsername(),30,TimeUnit.MINUTES);

                //生成令牌
                JwtUtils jwtUtils = new JwtUtils();
                jwtUtils.setSecretKey("java1907");
                jwtUtils.setTtl(30*60*1000);

                String jwtToken = jwtUtils.createJwtToken(currentUser.getId().toString(), currentUser.getUsername());

                //TODO 构建一个map,返回令牌和唯一标识
                Map<String,String> params = new HashMap<>();
                params.put("jwttoken",jwtToken);
                params.put("username",currentUser.getUsername());

                return new ResultBean("200",params);
            }
        }
        return new ResultBean("404",null);
    }

    @Override
    public ResultBean checkIsLogin(String uuid) {
        //1.拼接key
        //StringBuilder key = new StringBuilder("user:token:").append(uuid);
        //2.查询redis
        //String username = (String) redisTemplate.opsForValue().get(key.toString());
        //3.返回结果
        /*if(username != null){
            //刷新凭证的有效期
            redisTemplate.expire(key.toString(),30,TimeUnit.MINUTES);
            //
            return new ResultBean("200",username);
        }*/
        JwtUtils jwtUtils = new JwtUtils();
        jwtUtils.setSecretKey("java1907");

        //ExpiredJwtException
        //RuntimeException
        //解析令牌
        try {
            Claims claims = jwtUtils.parseJwtToken(uuid);
            String username = claims.getSubject();
            return new ResultBean("200",username);
        }catch (RuntimeException ex){
            //如果针对不同的异常,我们需要区分对待,那么就应该写多个catch,分别处理
            //如果是一样的处理方式,那么直接统一就行
            return new ResultBean("404",null);
        }
    }

    @Override
    public IBaseDao<TUser> getBaseDao() {
        return userMapper;
    }
}
  • Controller层
@Controller
@RequestMapping("user")
public class UserController {

    @Reference
    private IUserService userService;

    @GetMapping("checkUserNameIsExists/{username}")
    @ResponseBody
    public ResultBean checkUserNameIsExists(@PathVariable("username") String username){
        return userService.checkUserNameIsExists(username);
    }

    @GetMapping("checkPhoneIsExists/{phone}")
    @ResponseBody
    public ResultBean checkPhoneIsExists(@PathVariable("phone") String phone){
        return userService.checkPhoneIsExists(phone);
    }

    @GetMapping("checkEmailIsExists/{email}")
    @ResponseBody
    public ResultBean checkEmailIsExists(@PathVariable("email") String email){
        return userService.checkEmailIsExists(email);
    }

    @PostMapping("generateCode/{identification}")
    @ResponseBody
    public ResultBean generateCode(@PathVariable("identification") String identification){
        return userService.generateCode(identification);
    }

    /**
     * 适合处理异步请求
     * @return
     */
    @PostMapping("register")
    @ResponseBody
    public ResultBean register(TUser user){
        return null;
    }

    /**
     * 适合处理同步请求,跳转到相关页面
     * @return
     */
    @PostMapping("register4PC")
    public String register4PC(TUser user){
        return null;
    }

    @GetMapping("activating")
    public String activating(String token){
        return null;
    }

dubbo 与 Ribbon的区别_java_19

  1. 需要设计的接口

dubbo 与 Ribbon的区别_java_20

dubbo 与 Ribbon的区别_java_21


dubbo 与 Ribbon的区别_redis_22

关键点,系统是如何为用户保存登录凭证的?

dubbo 与 Ribbon的区别_rabbitmq_23

基于session操作

简单的登录验证:

dubbo 与 Ribbon的区别_redis_24


简单验证用户是否登录:

dubbo 与 Ribbon的区别_dubbo 与 Ribbon的区别_25

简单的注销:

dubbo 与 Ribbon的区别_java_26


问题:微服务架构下还能适用吗?

分布式拆分,集群保证高可用,session不能共享

dubbo 与 Ribbon的区别_rabbitmq_27


探讨解决方案:

通过cookie解决

dubbo 与 Ribbon的区别_dubbo 与 Ribbon的区别_28


jsessionId:凭证信息

2.是http协议的特性,1.是服务器自动的行为

dubbo 与 Ribbon的区别_dubbo 与 Ribbon的区别_29

有状态的解决方案:

dubbo 与 Ribbon的区别_redis_30

代码层面

登录校验

  • 增加redis依赖
    1、登录验证,生成uuid,并保存在redis中

service层

public ResultBean checkLogin(TUser user) {
        //1.根据用户输入的账号(手机/邮箱)信息,去查询
        TUser currentUser = userMapper.selectByIdentification(user.getUsername());
        //2.根据查询出来的密码信息,进行比较
        if(currentUser != null){
            //if(user.getPassword().equals(currentUser.getPassword())){
            if(passwordEncoder.matches(user.getPassword(),currentUser.getPassword())){
                //1.生成uuid
                String uuid = UUID.randomUUID().toString();
                //2.保存到redis中,并设置有效期为30分钟,代替原先的session
 redisTemplate.opsForValue().set("user:token:"+uuid,currentUser.getUsername(),30,TimeUnit.MINUTES);
                   return new ResultBean("200",uuid);
            }
        }
        return new ResultBean("404",null);
    }

redis的key值:“user:token:”+uuid,保证唯一性
redis的value值:currentUser.getUsername(),当前用户的名字

  • SSOController层
@PostMapping("checkLogin")
    @ResponseBody
    public ResultBean checkLogin(TUser user,HttpServletResponse response){
        ResultBean resultBean = userService.checkLogin(user);
        //2.如果正确,则在服务端保存凭证信息
        if("200".equals(resultBean.getStatusCode())){
            //TODO 写cookie给客户端,保存凭证
            //1.获取uuid
            String uuid = (String) resultBean.getData();
            //2.创建cookie对象
            Cookie cookie = new Cookie("user_token",uuid);
            cookie.setPath("/");
            //设置cookie的域名为父域名,这样所有子域名系统都可以访问该cookie,解决cookie的跨域问题
            cookie.setDomain("qf.com");
            cookie.setHttpOnly(true);
            //3.写cookie到客户端
            response.addCookie(cookie);
        }
        return resultBean;
    }

resultBean里返回了uuid,并且在userservice服务中这个值,已经被写入了redis。
如何将cookie写入到客户端,使用返回对象,response.addCookie();

校验是否在登录状态
1、获取cookie,获取user_token的值,在请求头里,所以用request去取
2、去redis中查询,是否存在这个凭证
SSOController层

dubbo 与 Ribbon的区别_ide_31

@GetMapping("checkIsLogin")
    @CrossOrigin(origins = "*",allowCredentials = "true")
    @ResponseBody
    public ResultBean checkIsLogin(@CookieValue(name = "user_token",required = false) String uuid){
        //1.获取cookie,获取user_token的值
        if(uuid != null){
            //2.去redis中查询,是否存在该凭证信息
            ResultBean resultBean = userService.checkIsLogin(uuid);
            return resultBean;
        }
        return new ResultBean("404",null);
    }

service层

@Override
    public ResultBean checkIsLogin(String uuid) {
        //1.拼接key
        StringBuilder key = new StringBuilder("user:token:").append(uuid);
        //2.查询redis
        String username = (String) redisTemplate.opsForValue().get(key.toString());
        ///3.返回结果
        if(username != null){
            //刷新凭证的有效期
            redisTemplate.expire(key.toString(),30,TimeUnit.MINUTES);
            
            return new ResultBean("200",username);
        }
       
    }

一旦有操作,要刷新redis凭证的有效期,如果有,那就刷新继续持续30min

redisTemplate.expire(key.toString(),30,TimeUnit.MINUTES)

具体的实现,是远程调用service层下面的userservice服务

回顾梳理:

dubbo 与 Ribbon的区别_java_32


在service层中通过checkLogin方法,去生成一个cookie凭证,并保存在redis中,设置超时时间30min;

然后Controller层中,将cookie写入到客户端。

当做登录验证时,调用checkLogin,返回一个resultBean,Bean里有redis中存入的凭证,如果验证通过了,将凭证(cookie),通过response.addCookie(cookie),写入到客户端。

**

全文索引的实现

**

搭建solar服务器

安装IK分词解释器

dubbo 与 Ribbon的区别_ide_33

springboot整合solr

dubbo 与 Ribbon的区别_dubbo 与 Ribbon的区别_34


与自定义域的索引保持一致。

dubbo 与 Ribbon的区别_java_35


dubbo 与 Ribbon的区别_rabbitmq_36


dubbo 与 Ribbon的区别_rabbitmq_37

查询和删除

dubbo 与 Ribbon的区别_rabbitmq_38


dubbo 与 Ribbon的区别_java_39


精确匹配用id;

全量同步数据

dubbo 与 Ribbon的区别_redis_40


dubbo 与 Ribbon的区别_dubbo 与 Ribbon的区别_41