p193 异步 异步复习
多线程几种方式
1.继承Thread
2.实现runable接口3.实现callable接口
4.线程池
public class ThreadTest {
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getId());
});
System.out.println("main结束");
}
}
p194 线程池详解
原始构造方法
快速构建线程池
p195 completableFuture异步编排
p196 completableFuture启动异步任务
无返回值
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
System.out.println("main start---");
CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
}, executorService);
System.out.println("main end---");
}
有返回值
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main start---");
//可以获取结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executorService);
Integer result = completableFuture.get();
System.out.println(result);
System.out.println("main end---");
}
p197completableFuture完成回调与异常感知
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main start---");
//可以获取结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 0;
System.out.println("运行结果:" + i);
return i;
}, executorService).whenComplete(new BiConsumer<Integer, Throwable>() {
//可以获取到结果 或者异常信息
@Override
public void accept(Integer integer, Throwable throwable) {
System.out.println("异步任务完成,结果为:"+integer+",异常为:"+throwable);
}
}).exceptionally( (throwable)->{//如果出现异常,可以自定义返回结果
return 10;
});
Integer result = completableFuture.get();
System.out.println(result);
System.out.println("main end---");
}
结果:
p198 handler最终处理
public static ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("main start---");
//可以获取结果
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println("当前线程:" + Thread.currentThread().getId());
int i = 10 / 2;
System.out.println("运行结果:" + i);
return i;
}, executorService).handle((res,throwable)->{
if (res!=null){
return res*2;
}
if (throwable!=null){
return 0;
}
return 0;
});
System.out.println("结果:"+completableFuture.get());
System.out.println("main end---");
}
p199线程串行化方法
p200 两任务组合
p201 两任务组合 一个完成
p202多任务组合
等待多个任务全部结束
只需要任意一个任务执行结束
p203商品详情
p204模型抽取
p205商品规格参数
p206 销售属性组合
p207 详情页面渲染
p208 商品详情 销售属性渲染
属性设置边框 回显当前sku的属性
p209sku组合切换
点击属性时,会跳转到具有该属性的sku商品页面
p210 异步编排优化
yml配置
gulimall:
thread:
core-size: 20 # 20-50
max-size: 200
keep-alive-time: 10 # 10s
建立配置属性类(使用@Component会加入容器)
@ConfigurationProperties(prefix = "gulimall.thread")
//@Component
@Data
public class ThreadPoolConfigProperties {
private Integer coreSize;
private Integer maxSize;
private Integer keepAliveTime;
}
有了@EnableConfigurationProperties,上面配置属性类的注解@Component取消掉
// 开启这个属性配置
@EnableConfigurationProperties(ThreadPoolConfigProperties.class)
@Configuration
public class MyThreadConfig {
@Bean
public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties threadPoolConfigProperties){
return new ThreadPoolExecutor(threadPoolConfigProperties.getCoreSize(),
threadPoolConfigProperties.getMaxSize(),
threadPoolConfigProperties.getKeepAliveTime() ,TimeUnit.SECONDS,
new LinkedBlockingDeque<>(10000), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}
}
优化商品详情为异步编排
@Override
public SkuItemVo item(Long skuId) throws ExecutionException, InterruptedException {
SkuItemVo skuItemVo = new SkuItemVo();
//sku基本信息 开启一个异步任务
CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
SkuInfoEntity skuInfoEntity = getById(skuId);
skuItemVo.setInfo(skuInfoEntity);
return skuInfoEntity;
}, executor);
//Long spuId = skuInfoEntity.getSpuId();
//图片信息 开启另一个异步任务
CompletableFuture<Void> imgFuture = CompletableFuture.runAsync(() -> {
List<SkuImagesEntity> images = imagesService.getImagesBySkuId(skuId);
skuItemVo.setImages(images);
}, executor);
//spu销售属性信息 res就是infoFuture任务的返回结果 infoFuture完成后就行下面的任务
CompletableFuture<Void> future1 = infoFuture.thenAcceptAsync((res) -> {
List<ItemSaleAttrVo> saleAttrVos = skuSaleAttrValueService.getSaleAttrsBuSpuId(res.getSpuId());
skuItemVo.setSaleAttr(saleAttrVos);
}, executor);
//spu介绍
CompletableFuture<Void> future2 = infoFuture.thenAcceptAsync((res)->{
SpuInfoDescEntity spuInfo = spuInfoDescService.getById(res.getSpuId());
skuItemVo.setDesc(spuInfo);
},executor);
//获取spu规格参数信息
CompletableFuture<Void> future3 =infoFuture.thenAcceptAsync((res)->{
List<SpuItemAttrGroup> attrGroups = attrGroupService.getAttrGroupWithAttrsBySpuId(res.getSpuId(),res.getCatalogId());
skuItemVo.setGroupAttrs(attrGroups);
},executor);
//等待所有任务完成
CompletableFuture.allOf(imgFuture,future1,future2,future3).get();
return skuItemVo;
}
p211 认证服务环境搭建
输入域名时,先通过ip到linux服务器,找到nginx,nginx再路由到网关,网关再路由到具体的服务
网关配置
p212 验证码倒计时
p213 短信验证码
@Data
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Component
public class SmsComponent {
private String host;
private String path;
private String appCode;
public void sendSmsCode(String phone, String code){
// String host = "https://dfsns.market.alicloudapi.com";
// String path = "/data/send_sms";
String method = "POST";
// String appcode = "764d82d28f354f028122ca6450114cb6";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appCode);
//根据API的要求,定义相对应的Content-Type
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
Map<String, String> querys = new HashMap<String, String>();
Map<String, String> bodys = new HashMap<String, String>();
bodys.put("content", "code:"+code);
bodys.put("phone_number", phone);
bodys.put("template_id", "TPL_0000");
try {
/**
* 重要提示如下:
* HttpUtils请从
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/src/main/java/com/aliyun/api/gateway/demo/util/HttpUtils.java
* 下载
*
* 相应的依赖请参照
* https://github.com/aliyun/api-gateway-demo-sign-java/blob/master/pom.xml
*/
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
//获取response的body
//System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
p214 验证码防刷校验
@ResponseBody
@RequestMapping("/sms/sendcode")
public R sendCode(@RequestParam("param")String phone){
//1接口防刷
String oldCode = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
//如果之前有验证码
if (StringUtils.isNotEmpty(oldCode)){
String lastTime = oldCode.split("_")[1];
//60s内防刷
if(System.currentTimeMillis()-Long.valueOf(lastTime)<60000){
return R.error(BizCodeEnum.SMS_CODE_EXCEPTION.getCode(),BizCodeEnum.SMS_CODE_EXCEPTION.getMsg());
}
}
//2存储验证码后面注册再次校验
String code = UUID.randomUUID().toString().substring(0, 5)+"_"+System.currentTimeMillis();
//缓存验证码
redisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX+phone,code,10,TimeUnit.MINUTES);
service.sendCode(phone,code);
return R.ok();
}
p215 注册页
@PostMapping("register")
public String register(@Valid UserRegisterVo userRegisterVo,
BindingResult result,
// Model model
RedirectAttributes redirectAttributes){
if (result.hasErrors()){
//获取错误信息
Map<String, String> errors = result.getFieldErrors().stream().collect(
Collectors.toMap(FieldError::getField, fieldError -> fieldError.getDefaultMessage())
);
// model.addAttribute("errors",errors);
//视图转发会有浏览器刷新重复提交的问题(因为浏览器地址栏没有变化) 改用重定向
// return "reg";
//但是重定向会有问题,使用model无法在重定向使用,解决:用RedirectAttributes(原理session)
//todo 用RedirectAttributes有分布式session问题
redirectAttributes.addFlashAttribute("errors",errors);//Flash代表取了就删除了
return "redirect:http://auth.gulimall.com/reg.html";//这里加上域名 不加的时候有个坑 会使用ip
}
return "redirect:/login.html";
}
p216 注册逻辑和远程注册
远程会员服务注册
p217 认证服务 MD5&盐值&BCrypt
不可逆加密算法
直接使用md5容易被彩虹表破解,加盐能提高安全性
p218 注册完成
@PostMapping("register")
public String register(@Valid UserRegisterVo userRegisterVo,
BindingResult result,
// Model model
RedirectAttributes redirectAttributes){
//jsr303校验
if (result.hasErrors()){
//获取错误信息
Map<String, String> errors = result.getFieldErrors().stream().collect(
Collectors.toMap(FieldError::getField, fieldError -> fieldError.getDefaultMessage())
);
// model.addAttribute("errors",errors);
//视图转发会有浏览器刷新重复提交的问题(因为浏览器地址栏没有变化) 改用重定向
// return "reg";
//但是重定向会有问题,使用model无法在重定向使用,解决:用RedirectAttributes(原理session)
//todo 用RedirectAttributes有分布式session问题
redirectAttributes.addFlashAttribute("errors",errors);//Flash代表取了就删除了
return "redirect:http://auth.gulimall.com/reg.html";//这里加上域名 不加的时候有个坑 会使用ip
}
//远程调用注册
// 1.校验验证码
String code = userRegisterVo.getCode();
String redis_code = redisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegisterVo.getPhone());
//如果验证码不为空
if(!org.springframework.util.StringUtils.isEmpty(redis_code)){
// 验证码通过
if(code.equals(redis_code.split("_")[0])){
// 删除验证码
redisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX + userRegisterVo.getPhone());
// 调用远程服务进行注册
R r = memberFeignService.register(userRegisterVo);
if(r.getCode() == 0){
// 注册成功,去登录
return "redirect:http://auth.gulimall.com/login.html";
}else{
Map<String, String> errors = new HashMap<>();
errors.put("msg",r.getData("msg",new TypeReference<String>(){}));
// 数据只需要取一次
redirectAttributes.addFlashAttribute("errors",errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}else{//验证码不通过
Map<String, String> errors = new HashMap<>();
errors.put("code", "验证码错误");
// addFlashAttribute 这个数据只取一次
redirectAttributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
}else{//如果验证码为空
Map<String, String> errors = new HashMap<>();
errors.put("code", "验证码错误");
// addFlashAttribute 这个数据只取一次
redirectAttributes.addFlashAttribute("errors", errors);
return "redirect:http://auth.gulimall.com/reg.html";
}
//注册成功跳转到登录页
}
p219 登录功能 账号密码登录
调用远程用户服务 登录
p220 认证服务 oauth2简介
p221微博登录
拿code换取token
拿到token可以去访问微博用户信息
p222 社交登陆回调
拿到code去换取token,跳转到首页
p223 社交登陆完成
@Override // 已经用code生成了token
public MemberEntity login(SocialUser socialUser) {
// 微博的uid
String uid = socialUser.getUid();
// 1.判断社交用户登录过系统
MemberDao dao = this.baseMapper;
MemberEntity entity = dao.selectOne(new QueryWrapper<MemberEntity>().eq("social_uid", uid));
MemberEntity memberEntity = new MemberEntity();
if(entity != null){ // 注册过
// 说明这个用户注册过, 修改它的资料
// 更新令牌
memberEntity.setId(entity.getId());
memberEntity.setAccessToken(socialUser.getAccessToken());
memberEntity.setExpiresIn(socialUser.getExpiresIn());
// 更新
dao.updateById(memberEntity);
entity.setAccessToken(socialUser.getAccessToken());
entity.setExpiresIn(socialUser.getExpiresIn());
entity.setPassword(null);
return entity;
}else{ // 没有注册过
// 2. 没有查到当前社交用户对应的记录 我们就需要注册一个
HashMap<String, String> map = new HashMap<>();
map.put("access_token", socialUser.getAccessToken());
map.put("uid", socialUser.getUid());
try {
// 3. 查询当前社交用户账号信息(昵称、性别、头像等)
HttpResponse response = HttpUtils.doGet("https://api.weibo.com", "/2/users/show.json", "get", new HashMap<>(), map);
if(response.getStatusLine().getStatusCode() == 200){
// 查询成功
String json = EntityUtils.toString(response.getEntity());
// 这个JSON对象什么样的数据都可以直接获取
JSONObject jsonObject = JSON.parseObject(json);
memberEntity.setNickname(jsonObject.getString("name"));
memberEntity.setUsername(jsonObject.getString("name"));
memberEntity.setGender("m".equals(jsonObject.getString("gender"))?1:0);
memberEntity.setCity(jsonObject.getString("location"));
memberEntity.setJob("自媒体");
memberEntity.setEmail(jsonObject.getString("email"));
}
} catch (Exception e) {
log.warn("社交登录时远程调用出错 [尝试修复]");
}
memberEntity.setStatus(0);
memberEntity.setCreateTime(new Date());
memberEntity.setBirth(new Date());
memberEntity.setLevelId(1L);
memberEntity.setSocialUid(socialUser.getUid());
memberEntity.setAccessToken(socialUser.getAccessToken());
memberEntity.setExpiresIn(socialUser.getExpiresIn());
// 注册 -- 登录成功
dao.insert(memberEntity);
memberEntity.setPassword(null);
return memberEntity;
}
}
p224社交登陆测试
p225 分布式session
2个问题
p226分布式session解决方案原理
多个相同服务分布式下解决办法
采用redis存储方案
不同服务session同步办法
解决了多个相同服务的问题,还有不同服务之间域名不同导致的cookie的domain不一样,session不能共享问题,修改cookie作用域名
p227整合springsession
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
配置
#分布式session
spring.session.store-type=redis
server.servlet.session.timeout=30m
注解
p228完成session子域共享
// TODO 1.默认发的当前域的session (需要解决子域session共享问题)
// TODO 2.使用JSON序列化后保存到redis 自动完成
@Configuration
public class AuthSessionConfig {
@Bean
public CookieSerializer cookieSerializer(){
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
// 明确的指定Cookie的作用域
cookieSerializer.setDomainName("gulimall.com");
cookieSerializer.setCookieName(AuthServerConstant.SESSION);
return cookieSerializer;
}
/**
* 自定义序列化机制
* 这里方法名必须是:springSessionDefaultRedisSerializer
*/
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer(){
return new GenericJackson2JsonRedisSerializer();
}
}
p229springsession原理
p230页面效果完成
搜索服务也引入springsession
各个服务修改前端页面
p231单点登录
p232 xxl-sso 单点登录
在一个服务器上登录.其他服务器都不用再登录
p233-234 单点登录流程
登录client1
p235登录流程
登录client2
上一步登录client1和ssoserver了,但是在登录client2还不能直接免登录,需要修改之前的流程
client1登陆时 即第一次访问sso时,添加相应的cookie
后面浏览器只要没关闭,访问ssoserver就会携带相关的cookie
p236 购物车服务 环境搭建
搭建购物车
p237购物车 数据模型分析
p238 编写vo
p239 购物车 ThreadLocal 用户身份识别
springsession配置
引入依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
p240 页面环境修改
主要是修改html相关链接的跳转路径