1 添加购物车接口开发
1.1Feign接口声明
1). 定义Feign远程 调用的接口
@FeignClient(name = “goods”)
public interface SkuFeign {
@GetMapping("/sku/spu/{spuId}")
public List findSkuListBySpuId(@PathVariable(“spuId”) String spuId);
@GetMapping("/sku/{id}")
public Result<Sku> findById(@PathVariable("id") String id);
}
2). 订单微服务中开启Feign调用开关
@EnableFeignClients(basePackages = {“com.changgou.goods.feign”})
1.2 Service层逻辑
1). Redis数据结构
数据结构: Hash
cart_Tom 100001 OrderItem
100002 OrderItem
…
2). 加入购物车逻辑
A. 查询redis中的购物车数据
B. 判定redis中是否包含当前添加的sku信息 , 如果已经存在, 更新数量, 计算总金额
C. 如果不存在, 将当前商品组装成购物车对象 , 加入Redis
3). 代码实现
public void addCart(String skuId, Integer num, String username) {
//1.查询redis中相对应的商品信息
OrderItem orderItem = (OrderItem) redisTemplate.boundHashOps(CART+username).get(skuId);
if (orderItem != null){
//2.如果当前商品在redis中的存在,则更新商品的数量与价钱
orderItem.setNum(orderItem.getNum()+num);
orderItem.setMoney(orderItem.getNum()*orderItem.getPrice());
orderItem.setPayMoney(orderItem.getNum()*orderItem.getPrice());
}else {
//3.如果当前商品在redis中不存在,将商品添加到redis中
Sku sku = skuFeign.findById(skuId).getData();
Spu spu = spuFeign.findSpuById(sku.getSpuId()).getData();
//封装orderItem
orderItem = this.sku2OrderItem(sku,spu,num);
}
//3.将orderitem添加到redis中
redisTemplate.boundHashOps(CART+username).put(skuId,orderItem);
}
1.3 Controller 层逻辑
@GetMapping("/addCart")
public Result addCart(@RequestParam(“skuId”) String skuId, @RequestParam(“num”) Integer num){
//动态获取当前人信息,暂时静态
String username = “itcast”;
cartService.addCart(skuId,num,username);
return new Result(true, StatusCode.OK,“加入购物车成功”);
}
2 购物车列表查询接口
1). Service
A. 查询当前用户对应的redis中购物车列表数据 ;
B. 计算购物车中商品总数量, 总金额 ;
@Override
public Map list(String username) {
Map map = new HashMap();
/*
A. 查询当前用户对应的redis中购物车列表数据 ;
B. 计算购物车中商品总数量, 总金额 ;
*/
// 根据用户名查询所对应的value
List orderItemList = redisTemplate.boundHashOps(CART + username).values();
// 1.使用map封装redis中的购物车列表数据
map.put(“orderItemList”,orderItemList);
// 2.定义变量接受商品的总数量。以及总金额
Integer totalMoney = 0;
Integer totalNum = 0;
if (orderItemList != null){
for (OrderItem orderItem : orderItemList) {
totalMoney += orderItem.getMoney();
totalNum += orderItem.getNum();
}
map.put(“totalMoney”,totalMoney);
map.put(“totalNum”,totalNum);
}
return map;
}
2). Controller
@GetMapping("/list")
public Map list(){
//动态获取当前人信息,暂时静态
String username = “itcast”;
Map map = cartService.list(username);
return map;
}
- 购物车渲染
3.1 实现思路
3.2 购物车渲染服务搭建
1). pom.xml
com.changgou
changgou_service_order_api
1.0-SNAPSHOT
com.changgou
changgou_common
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-thymeleaf
2). application.yml
server:
port: 9011
spring:
application:
name: order-web
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
thymeleaf:
cache: false
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true
feign:
hystrix:
enabled: true
client:
config:
default: #配置全局的feign的调用超时时间 如果 有指定的服务配置 默认的配置不会生效
connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接 单位是毫秒
readTimeout: 80000 # 指定的是调用服务提供者的 服务 的超时时间() 单位是毫秒
#hystrix 配置
hystrix:
command:
default:
execution:
timeout:
#如果enabled设置为false,则请求超时交给ribbon控制
enabled: true
isolation:
strategy: SEMAPHORE
thread:
# 熔断器超时时间,默认:1000/毫秒
timeoutInMilliseconds: 80000
#请求处理的超时时间
ribbon:
ReadTimeout: 4000
#请求连接的超时时间
ConnectTimeout: 3000
3). 引导类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {“com.changgou.order.feign”})
public class WebOrderApplication {
public static void main(String[] args) {
SpringApplication.run(WebOrderApplication.class,args);
}
}
3.3 Feign接口定义
@FeignClient(name = “order”)
public interface CartFeign {
@GetMapping("/cart/addCart")
public Result addCart(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num);
@GetMapping("/cart/list")
public Map list();
}
3.4 Controller层
@Controller
@RequestMapping("/wcart")
public class CartController {
@Autowired
private CartFeign cartFeign;
//查询
@GetMapping("/list")
public String list(Model model){
Map map = cartFeign.list();
model.addAttribute("items",map);
return "cart";
}
//添加
@GetMapping("/add")
@ResponseBody
public Result<Map> add(String id,Integer num){
cartFeign.addCart(id,num);
Map map = cartFeign.list();
return new Result<>(true, StatusCode.OK,"添加购物车成功",map);
}
}
3.5 购物车列表页面渲染
1). 需要在服务网关中增加路由配置
#购物车订单渲染微服务
- id: changgou_order_web_route
uri: lb://order-web
predicates:
- Path=/api/wcart/,/api/worder/
filters:
- StripPrefix=1
2). 遍历展示数据
TIP :
th:inline —> 内联JavaScript , 如果需要在js中获取Thymeleaf数据模型中的数据, 需要加该属性 ;
3). 页面遍历展示数据
-
-
{{item.name}}
- {{item.price}}
4). + - 事件绑定
5). 后台处理
4.购物车渲染
4.1 订单微服务接入认证
1). pom.xml
org.springframework.cloud
spring-cloud-starter-oauth2
2). 引入公钥 public.key
3). 引入配置 ResourceServerConfig
4.2 微服务之间的认证
1). 当前问题
2). 解决方案
通过Feign拦截器, 在通过Feign进行远程调用时, 对请求增加头信息, 让头信息 Authorization 继续往下传递 ;
3). 代码实现
A. 在Common中定义FeignInterceptor
@Component
public class FeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
// 传递令牌
// 1.获取request请求
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null){
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 2.获取所有的请求头
if (request != null){
Enumeration headerNames = request.getHeaderNames();
// 3.遍历所有的请求头名称
while (headerNames.hasMoreElements()){
String headerName = headerNames.nextElement();
// 4.判断如果有Authorization 的请求头,则传递令牌
if (“authorization”.equals(headerName)){
String headerValue = request.getHeader(headerName);
requestTemplate.header(headerName,headerValue);
}
}
}
}
}
}
B. 在Feign远程调用发起微服务中声明该Bean
@Bean
public FeignInterceptor feignInterceptor(){
return new FeignInterceptor();
}
4.3 动态获取当前登录用户
1). common引入依赖
io.jsonwebtoken
jjwt
0.9.0
provided
2). common依赖工具类
public class TokenDecode {
//公钥
private static final String PUBLIC_KEY = “public.key”;
private static String publickey="";
/***
* 获取用户信息
* @return
*/
public Map<String,String> getUserInfo(){
//获取授权信息
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) SecurityContextHolder.getContext().getAuthentication().getDetails();
//令牌解码
return dcodeToken(details.getTokenValue());
}
/***
* 读取令牌数据
*/
public Map<String,String> dcodeToken(String token){
//校验Jwt
Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(getPubKey()));
//获取Jwt原始内容
String claims = jwt.getClaims();
return JSON.parseObject(claims,Map.class);
}
/**
* 获取非对称加密公钥 Key
* @return 公钥 Key
*/
public String getPubKey() {
if(!StringUtils.isEmpty(publickey)){
return publickey;
}
Resource resource = new ClassPathResource(PUBLIC_KEY);
try {
InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream());
BufferedReader br = new BufferedReader(inputStreamReader);
publickey = br.lines().collect(Collectors.joining("\n"));
return publickey;
} catch (IOException ioe) {
return null;
}
}
}
核心 : 读写公钥内容, 解析jwt令牌 ;
3). 需要获取当前登录用户微服务中配置
@Bean
public TokenDecode tokenDecode(){
return new TokenDecode();
}
4). 获取登录用户
String username = tokenDecode.getUserInfo().get(“username”);
4.4 页面跳转设置
1). 需求
A. 如果用户没有登录, 访问业务系统, 直接跳转登录页面 ;
B. 从哪儿来到哪儿去 ;
2). 流程
3). 代码实现
A. 服务网关中进行改造
B. 认证微服务 /oauth/toLogin改造
C. login.html登录成功的回调函数中改造