购物车渲染
如上图所示,用户每次将商品加入购物车,或者点击购物车列表的时候,先经过订单购物车后端渲染服务,再通过feign调用购物车订单微服务来实现购物车的操作,例如:加入购物车、购物车列表。
购物车渲染服务搭建
在changgou_web中搭建订单购物车微服务工程changgou_web_order
,该工程主要实现购物车和订单的渲染操作。
(1) pom.xml依赖
<dependencies> <dependency> <groupId>com.changgou</groupId> <artifactId>changgou_service_order_api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.changgou</groupId> <artifactId>changgou_common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies>
(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)创建启动类
创建com.changgou.OrderWebApplication启动类,代码如下:
@SpringBootApplication @EnableEurekaClient public class OrderWebApplication { public static void main(String[] args) { SpringApplication.run(OrderWebApplication.class,args); } }
(4)静态资源拷贝
资源\成品页面\cart.html页面拷贝到工程中,如下图:
购物车列表渲染
Feign创建
在changgou_service_order_api中添加CartFeign接口,并在接口中创建添加购物车和查询购物车列表,代码如下:
@FeignClient(name="order") public interface CartFeign { /** * 添加购物车 * @param skuId * @param num * @return */ @GetMapping("/cart/add") public Result add(@RequestParam("skuId") String skuId, @RequestParam("num") Integer num); /*** * 查询用户购物车列表 * @return */ @GetMapping(value = "/cart/list") public Map list(); }
后台代码
在changgou_web_order中创建com.changgou.order.controller.CartController,并添加查询购物车集合方法和添加购物车方法,代码如下:
@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.add(id, num); Map map = cartFeign.list(); return new Result<>(true, StatusCode.OK,"添加购物车成功",map); } }
前端页面
(1)加载列表数据
我们可以先在网页下面添加一个js脚本,用于加载购物车数据,并用一个对象存储,代码如下:
<!-- vue loadlist --> <div class="cart-list" v-for="item in items.orderItemList" :key="item.index"> <ul class="goods-list yui3-g"> <li class="yui3-u-1-24"> <input type="checkbox" name="chk_list" id="" value="" /> </li> <li class="yui3-u-6-24"> <div class="good-item"> <div class="item-img"> <img :src="item.image" /> </div> <div class="item-msg"></div> </div> </li> <li class="yui3-u-5-24"> <div class="item-txt">{{item.name}}</div> </li> <li class="yui3-u-1-8"> <span class="price">{{item.price}}</span> </li> <li class="yui3-u-1-8"> <a href="javascript:void(0)" @click="add(item.skuId,-1)" class="increment mins">-</a> <input autocomplete="off" type="text" v-model="item.num" @blur="add(item.skuId,item.num)" value="1" minnum="1" class="itxt" /> <a href="javascript:void(0)" @click="add(item.skuId,1)" class="increment plus">+</a> </li> <li class="yui3-u-1-8"> <span class="sum">{{item.num*item.price}}</span> </li> <li class="yui3-u-1-8"> <a href="#none">删除</a> <br /> <a href="#none">移到收藏</a> </li> </ul> </div> </div> </div> </div> <div class="cart-tool"> <div class="select-all"> <input class="chooseAll" type="checkbox" name="" id="" value="" /> <span>全选</span> </div> <div class="option"> <a href="#none">删除选中的商品</a> <a href="#none">移到我的关注</a> <a href="#none">清除下柜商品</a> </div> <div class="money-box"> <div class="chosed">已选择 <span>{{items.totalNum}}</span>件商品</div> <div class="sumprice"> <span> <em>总价(不含运费) :</em> <i class="summoney">¥{{items.totalMoney}}</i> </span> <span> <em>已节省:</em> <i>-¥20.00</i> </span> </div> <div class="sumbtn"> <a class="sum-btn" href="getOrderInfo.html" target="_blank">结算</a> </div> </div> </div>
<script th:inline="javascript"> var app = new Vue({ el: '#app', data() { return { items: [[${items}]] } }, methods:{ add:function (skuId, num) { axios.get("/wcart/add?skuId="+skuId+"&num="+num).then(function (response) { if (response.data.flag){ app.items=response.data.data } }) } } }) </script>
购物车渲染服务、订单服务对接网关
1) 修改微服务网关changgou-gateway-web
的application.yml配置文件,添加order的路由过滤配置,配置如下:
#订单微服务 - id: changgou_order_route uri: lb://order predicates: - Path=/api/cart/**,/api/categoryReport/**,/api/orderConfig/**,/api/order/**,/api/orderItem/**,/api/orderLog/**,/api/preferential/**,/api/returnCause/**,/api/returnOrder/**,/api/returnOrderItem/** filters: - StripPrefix=1 #购物车订单渲染微服务 - id: changgou_order_web_route uri: lb://order-web predicates: - Path=/api/wcart/**,/api/worder/** filters: - StripPrefix=1
运行之后,效果如下:
商品数量变更
用户可以点击+号或者-号,或者手动输入一个数字,然后更新购物车列表,我们可以给-+号一个点击事件,给数字框一个失去焦点事件,然后调用后台,实现购物车的更新。
请求后台方法:
在js里面创建一个请求后台的方法,代码如下:
添加事件:
在+-号和数字框那里添加点击事件和失去焦点事件,然后调用上面的add方法,代码如下:
删除商品购物车
我们发现个问题,就是用户将商品加入购物车,无论数量是正负,都会执行添加购物车,如果数量如果<=0,应该移除该商品的。
修改changgou-service-order的com.changgou.order.service.impl.CartServiceImpl的add方法,添加如下代码:
订单服务对接oauth
1)配置公钥
2)oauth依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency>
3) 添加配置类
@Configuration @EnableResourceServer //开启方法上的PreAuthorize注解 @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class ResourceServerConfig extends ResourceServerConfigurerAdapter { //公钥 private static final String PUBLIC_KEY = "public.key"; /*** * 定义JwtTokenStore * @param jwtAccessTokenConverter * @return */ @Bean public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) { return new JwtTokenStore(jwtAccessTokenConverter); } /*** * 定义JJwtAccessTokenConverter * @return */ @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setVerifierKey(getPubKey()); return converter; } /** * 获取非对称加密公钥 Key * @return 公钥 Key */ private String getPubKey() { Resource resource = new ClassPathResource(PUBLIC_KEY); try { InputStreamReader inputStreamReader = new InputStreamReader(resource.getInputStream()); BufferedReader br = new BufferedReader(inputStreamReader); return br.lines().collect(Collectors.joining("\n")); } catch (IOException ioe) { return null; } } /*** * Http安全配置,对每个到达系统的http请求链接进行校验 * @param http * @throws Exception */ @Override public void configure(HttpSecurity http) throws Exception { //所有请求必须认证通过 http.authorizeRequests() .anyRequest(). authenticated(); //其他地址需要认证授权 } }
微服务间认证
如上图:因为微服务之间并没有传递头文件,所以我们可以定义一个拦截器,每次微服务调用之前都先检查下头文件,将请求的头文件中的令牌数据再放入到header中,再调用其他微服务即可。
测试访问购物车,产生如下效果:
feign拦截器实现微服务间认证
(1)创建拦截器
在changgou_common服务中创建一个com.changgou.interceptor.FeignInterceptor拦截器,并将所有头文件数据再次加入到Feign请求的微服务头文件中,代码如下:
@Component public class FeignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate requestTemplate) { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes!=null){ HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest(); if (request!=null){ Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames!=null){ while (headerNames.hasMoreElements()){ String headerName = headerNames.nextElement(); if (headerName.equals("authorization")){ String headerValue = request.getHeader(headerName); requestTemplate.header(headerName,headerValue); } } } } } } }
2) 更改changgou_order_web启动类,添加拦截器声明
@Bean public FeignInterceptor feignInterceptor(){ return new FeignInterceptor(); }
动态获取当前登陆人
数据分析
用户登录后,数据会封装到SecurityContextHolder.getContext().getAuthentication()
里面,我们可以将数据从这里面取出,然后转换成OAuth2AuthenticationDetails
,在这里面可以获取到令牌信息、令牌类型等,代码如下:
这里的tokenValue是加密之后的令牌数据,remoteAddress是用户的IP信息,tokenType是令牌类型。
我们可以获取令牌加密数据后,使用公钥对它进行解密,如果能解密说明数据无误,如果不能解密用户也没法执行到这一步。解密后可以从明文中获取用户信息。
代码实现
因为该类在很多微服务中都会被使用到,所以需要将该类添加到common工程中
(1)在changgou-common工程中引入鉴权包
<!--鉴权--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> <scope>provided</scope> </dependency>
(2)添加资源中的TokenDecode工具类到changgou-service-order微服务config包下,用于解密令牌信息
代码如下:
(3)将该工具类以bean的形式声明到order服务中
(4)控制层获取用户数据
在CartController中注入TokenDecode,并调用TokenDecode的getUserInfo方法获取用户信息,代码如下:
注入TokenDecode:
@Autowired private TokenDecode tokenDecode;
获取用户名:
页面配置
未登录时登录跳转
在用户没有登录的情况下,直接访问购物车页面,效果如下:
我们可以发现,返回的只是个错误状态码,这个毫无意义,我们应该重定向到登录页面,让用户登录,我们可以修改网关的头文件,让用户每次没登录的时候,都跳转到登录页面。
修改changgou-gateway-web的com.changgou.filter.AuthorizeFilter
,代码如下:
此时再测试,就可以跳转到登录页面了。
访问:http://localhost:8001/api/wcart/list
登录成功跳转原地址
刚才已经实现了未登录时跳转登录页,但是当登录成功后,并没有跳转到用户本来要访问的页面。
要实现这个功能的话,可以将用户要访问的页面作为参数传递到登录控制器,由登录控制器根据参数完成路径跳转
1) 修改网关携带当前访问URI
修改changgou-gateway-web的com.changgou.filter.AuthorizeFilter
,在之前的URL后面添加FROM参数以及FROM参数的值为request.getURI()
,代码如下:
2)登录控制器获取参数
修改changgou-user-oauth的com.changgou.oauth.controller.LoginRedirect
记录访问来源页,代码如下:
3) 修改页面,获取来源页信息,并存到from变量中,登录成功后跳转到该地址。
此时再测试,就可以识别未登录用户,跳转到登录页,然后根据登录状态,如果登录成功,则跳转到来源页。