系统间通信问题
数据同步
性能有影响。有新的系统,代码需要更新,耦合太高。
方案二:引入消息中间件(MQ),可解耦,异步化。
RabbitMQ
1、simple模式:一对一
2、work模式:消息是共享模式
限流:对于消费者说,哪个干的快,哪个就干的多。给1个,处理完后再给下一个。
用限流+手工确认。
3、发布订阅模式
X:交换机,有两个队列,每个消费者都有队列。
生产者面向的是交换机。
4、路由模式:可以指定推送消息给消费者。打标记。
5、通配符模式:
*
采用MQ的场景
邮件服务
redis实战
首页使用redis优化
第一次没有查数据库,第二次就不用查数据库,可以直接查缓存。
- 配置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);
}
}
首页展示是远程调用了商品服务
注册系统
注册系统是远程调用了用户的服务
通过MQ去异步通知邮件服务进行邮件发送。Mq应用场景:发送邮件
发送失败怎么处理?
登录系统
表结构设计
表名:t-user
phone和email加唯一索引
统一定义返回:ResultBean
@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;
}
- 需要设计的接口
关键点,系统是如何为用户保存登录凭证的?
基于session操作
简单的登录验证:
简单验证用户是否登录:
简单的注销:
问题:微服务架构下还能适用吗?
分布式拆分,集群保证高可用,session不能共享
探讨解决方案:
通过cookie解决
jsessionId:凭证信息
2.是http协议的特性,1.是服务器自动的行为
有状态的解决方案:
代码层面
登录校验
- 增加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层
@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服务
回顾梳理:
在service层中通过checkLogin方法,去生成一个cookie凭证,并保存在redis中,设置超时时间30min;
然后Controller层中,将cookie写入到客户端。
当做登录验证时,调用checkLogin,返回一个resultBean,Bean里有redis中存入的凭证,如果验证通过了,将凭证(cookie),通过response.addCookie(cookie),写入到客户端。
**
全文索引的实现
**
搭建solar服务器
安装IK分词解释器
springboot整合solr
与自定义域的索引保持一致。
查询和删除
精确匹配用id;
全量同步数据