基于spring cloud实现订单服务框架demo(一)

springcloud是目前比较热门的微服务管理框架, 最近基于springcloud完成了订单服务框架的demo,在此记录一下框架的整体设计点和一些关键的技术点。
整体的架构如下所示:
在这里插入图片描述
限于时间关系,只编写了后端rest的代码框架,整体的模块分为:
1) 注册中心eureka:提供模块的注册、发现服务
2) seata server:提供分布式TCC服务,当用户服务发起购买商品流程时,由于需要保证扣减库存、生成订单在一个分布式事物中,通过该服务来保证
3) 网关服务gateway service:基于zuul完成用户操作的拦截、权限校验、登录等服务
4) 库存服务warehouse service:提供商品库存查询、库存扣减服务
5) 订单服务order service:提供生成订单、查询订单等服务
6) 支付服务pay service:提供订单支付服务
7) 用户服务user service:提供购买商品、支付商品等业务化操作
8) 物流服务:负责调用第三方物流接口生成物流订单、查询物流信息,该模块与支付模块之间通过MQ进行解耦

mysql相关的表:

#用户表
create table if not exists `user_table`(
   id bigint(20) primary key not null auto_increment,
   name varchar(100) not null,
   password varchar(100) not null,
   money bigint(20) not null,
   version int(11) not null
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

#订单信息表
create table if not exists `order_info`(
  id varchar(50) primary key not null,
  userid bigint(20) not null,
  commodity_id varchar(50) not null,
  `count` int(11) not null,
  total_price bigint(20) not null,
  status varchar(20) not null,
  create_time varchar(20),
  delivery_time varchar(20),
  complete_time varchar(20)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

#mq事务表
create table if not exists `mq_transaction`(
  id varchar(50) primary key not null,
  business varchar(50) primary key not null,
  orderId varchar(50) primary key not null
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

为了保证商品信息查询、库存扣减的实时性,采用redis缓存商品库存信息,用户下单时直接更新redis中的库存,以保证高并发性能。

下面梳理一下关键的技术点:

基于zuul实现网关服务
网关服务的主要目的是实现用户权限校验与登录服务

添加@EnableZuulProxy注解开启网关服务:

@EnableFeignClients
@EnableZuulProxy
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

zuul配置文件:

zuul:
  host:
    connect-timeout-millis: 15000
    socket-timeout-millis: 60000
  sensitiveHeaders:
  ignoredPatterns: /**/sso/**  #以sso开头的路径忽略
  routes:
    user-service:
      path: /user/**
      serviceId: user-service  #校验通过的流量重定向到user-service中
      strip-prefix: false

自定义一个zuul过滤器,对请求进行拦截,校验用户权限,校验通过则放行,否则重定向到登录页面:

@Component
public class AccessFilter extends ZuulFilter {

    @Value("${token.expire-time}")
    private int tokenExpireTime;

    @Autowired
    private UserService userService;

    //前置过滤器
    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        HttpServletResponse response = ctx.getResponse();

        String url = request.getRequestURL().toString();
        String accessToken = null;
        String authorization = request.getHeader("authorization");
        if (StringUtils.isNotEmpty(authorization)) {
            accessToken = StringUtils.substringAfter(authorization, "Bearer ");
        }
        //从cookie中获取token
        Cookie[] cookies = request.getCookies();
        Cookie tokenCookie = null;
        if (null != cookies) {
            for (Cookie cookie : cookies) {
                if ("accessToken".equals(cookie.getName())) {
                    accessToken = cookie.getValue();
                    tokenCookie = cookie;
                }
            }
        }

        //是登录请求或者权限校验通过,则放行
        if (url.contains("sso/loginPage") || url.contains("sso/login") || checkToken(accessToken)) {
            ctx.setSendZuulResponse(true);
            if (null != accessToken) {
                ctx.addZuulRequestHeader("authorization", "Bearer " + accessToken);
            }
            //刷新token
            if (null != tokenCookie) {
                Long remainTime = JwtUtil.getRemainTimeInToken(accessToken);
                //token过期前30秒内刷新token
                if (remainTime > 0 && remainTime <= 30000) {
                    tokenCookie.setValue(JwtUtil.refreshToken(accessToken, tokenExpireTime));
                    //设置token过期时间
                    tokenCookie.setMaxAge(tokenExpireTime);
                    tokenCookie.setPath("/");
                    response.addCookie(tokenCookie);
                }
            }
            ctx.setResponseStatusCode(200);
            return null;
        } else {
            //没有权限,重定向到登录页面
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try {
                response.sendRedirect("/sso/loginPage?url=" + resolveGetUrl(request));
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
}

通过get请求访问时,请求参数包含在HttpServletRequest中,重定向url时,需要将请求参数解析出来重新拼接:

private String resolveGetUrl(HttpServletRequest request) {
        String method = request.getMethod();
        String url = request.getRequestURL().toString();
        if ("GET".equals(method)) {
            Map<String, String[]> parameterMap = request.getParameterMap();
            List<String> list = new ArrayList<>();
            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
                String key = entry.getKey();
                String[] values = entry.getValue();
                for (String value : values) {
                    list.add(key + "=" + value);
                }
            }
            if (!list.isEmpty()) {
                url = url + "?" + StringUtils.join(list, "&");
            }
        }
        return url;
    }

实现的效果:
在浏览器中访问商品信息的接口
在这里插入图片描述
带着原始url重定向到登录页面:
在这里插入图片描述
登录成功之后,返回商品信息:
在这里插入图片描述
cookie中带有token信息,下次访问直接放行
在这里插入图片描述
设定cookie 30分钟过期,过期之后需要重新登录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值