zoukankan      html  css  js  c++  java
  • [Spring cloud 一步步实现广告系统] 6. Service实现&Zuul配置&Test

    DAO层设计实现

    这里我们使用Spring DATA JPA来实现数据库操作,当然大家也可以使用Mybatis,都是一样的,我们依然以用户表操作为例:

    /**
     * AdUserRepository for 用户数据库操作接口
     * 继承自JpaRepository<AdUser, Long>,第一个参数AdUser代表当前要操作的实体类的class定义,第二个参数Long表示该类的主键类型
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang</a>
     */
    
    public interface AdUserRepository extends JpaRepository<AdUser, Long> { 
        /**
         * 根据用户名称获取用户
         *
         * @param username 名称
         * @return 用户对象
         */
        AdUser findByUserName(String username);
    
        List<AdUser> findAllByUserName(String userName);
    }
    
    • JPARepository 的默认实现方法,如果我们只是继承了JpaRepository而没有实现具体的操作方法,我们也是可以通过使用它的默认方法来做CRUD操作的,如下:

      UTOOLS1564192818939.png

    功能Service实现

    创建service package,依然以用户操作为例,创建com.sxzhongf.ad.service.IUserServicecom.sxzhongf.ad.service.impl.UserServiceImpl,UserServiceImpl实现了IUserService

    1. 创建 IUserService 接口
    /**
     * IUserService for 用户service
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     */
    public interface IUserService {
        /**
         * 创建用户接口
         *
         * @param userRequestVO {@link UserRequestVO}
         * @return {@link UserResponseVO}
         * @throws AdException 错误
         */
        UserResponseVO createUser(UserRequestVO userRequestVO) throws AdException;
    
        List<AdUser> findAllByUserName(String userName);
    }
    
    1. 使用IUserService接口
    /**
     * UserServiceImpl for 用户service
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     */
    @Slf4j
    @Service
    public class UserServiceImpl implements IUserService {
    
        private final AdUserRepository userRepository;
    
        @Autowired
        public UserServiceImpl(AdUserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        /**
         * 创建用户
         *
         * @param userRequestVO {@link UserRequestVO}
         * @return result {@link UserResponseVO}
         */
        @Override
        @Transactional
        public UserResponseVO createUser(UserRequestVO userRequestVO) throws AdException {
            if (!userRequestVO.validate()) {
              	log.error("Request params error: {}", userRequestVO);
                throw new AdException(Constants.ErrorMessage.REQUEST_PARAM_ERROR);
            }
            //查重
            AdUser existUser = userRepository.findByUserName(userRequestVO.getUserName());
            if (existUser != null) {
                log.error("{} user is not exist.", userRequestVO.getUserName());
                throw new AdException(Constants.ErrorMessage.USER_EXIST);
            }
            AdUser user = userRepository.save(new AdUser(userRequestVO.getUserName(), CommonUtils.md5(userRequestVO.getUserName())));
            log.info("current user is : {}", user);
            return new UserResponseVO(user.getUserId(), user.getUserName(), user.getToken(),
                    user.getCreateTime(), user.getUpdateTime());
        }
    
        @Override
        public List<AdUser> findAllByUserName(String userName) {
            return userRepository.findAllByUserName(userName);
        }
    }
    
    1. 创建数据传输对象(dto/vo)

      其实好多人在这里都会特别郁闷,搞不清楚这些命名有什么区别,个人建议是大家不用纠结,dto(data transfer object),就是表示我们在各个层传递的对象,vo在展示层操作的对象。但是这个只是个命名,它的本质就是一个object, 你传递到DAO层可以吗?当然可以,你传单独字段都是可以的。所以,没必要过分纠结这种信息,咬文嚼字有时候反而会适得其反。

    /**
     * UserRequestVO for 创建用户请求对象VO
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UserRequestVO {
        private String userName;
        public boolean validate() {
            return !StringUtils.isEmpty(userName);
        }
    }
    
    ---
      
    /**
     * UserResponseVO for 用户响应VO
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class UserResponseVO {
        private Long userId;
        private String userName;
        private String token;
        private Date createTime;
        private Date updateTime;
    }
    
    1. 因为报错信息有可能是相同的,那我们抽取一个常量类来封装。
    /**
     * Constants for TODO
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     */
    public class Constants {
        /**
         * 通用错误信息异常类
         */
        public static class ErrorMessage {
            public static final String REQUEST_PARAM_ERROR = "请求参数异常";
            public static final String USER_EXIST = "用户已存在";
            public static final String USER_NOT_EXIST = "用户不存在";
        }
    }
    
    1. 在Common Project 下面创建一个工具类com.sxzhongf.ad.common.utils.CommonUtils,用来对用户username进行md5加密来获取token信息。
    /**
     * CommonUtils for 工具类
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     */
    @Slf4j
    public class CommonUtils {
        /**
         * md5 加密
         */
        public static String md5(String value) {
            return DigestUtils.md5Hex(value).toUpperCase();
        }
    }
    

    参考创建用户的实现,依次实现其他表操作。

    Controller实现

    依然以用户功能实现为例:

    /**
     * UserController for 用户controller
     *
     * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang | 若初</a>
     */
    @RestController
    @Slf4j
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        private IUserService userService;
    
        @PostMapping(path = "/create")
        public UserResponseVO createUser(@RequestBody UserRequestVO requestVO) throws AdException {
            log.info("ad-sponsor: createUser -> {}", JSON.toJSONString(requestVO));
            return userService.createUser(requestVO);
        }
    
        @GetMapping(path = "/get")
        public CommonResponse getUserList(@Param(value = "username") String username) throws AdException {
            log.info("ad-sponsor: getUserList -> {}", JSON.toJSONString(username));
            return new CommonResponse(userService.findAllByUserName(username));
        }
    }
    
    
    在网关中配置广告投放系统

    我们在投放系统的配置中,配置了server.servlet.context-path:/ad-sponsor这么一个路径,意味着所有请求当前系统的路径都需要带有ad-sponsor, 例如:http://xxx/ad-sponsor/user/get?username=yyy,这是网关请求所必需的。根据上述,我们在网关服务中配置我们当前的投放系统:

    spring:
      application:
        name: ad-gateway-zuul
    server:
      port: 1111
    eureka:
      client:
        service-url:
          defaultZone: http://server1:7777/eureka/,http://server2:8888/eureka/,http://server3:9999/eureka/
      instance:
        hostname: ad-gateway-zuul
    ##############################################
    # 以下为重要信息
    zuul:
      ignored-services: '*' # 过滤所有请求,除了下面routes中声明过的服务
      # 配置网关路由规则
      routes:
        sponsor: #在路由中自定义服务路由名称
          path: /ad-sponsor/**
          serviceId: mscx-ad-sponsor #微服务name
          strip-prefix: false
        search: #在路由中自定义服务路由名称
          path: /ad-search/**
          serviceId: mscx-ad-search #微服务name
          strip-prefix: false
      prefix: /gateway/api
      strip-prefix: false #不对 prefix: /gateway/api 设置的路径进行截取,默认转发会截取掉配置的前缀
    
    
    Test
    • 直接访问投放系统

      调用curl -G http://localhost:7000/ad-sponsor/user/get?username=Isaac%20Zhang,返回结果:

    {
      code: 0,  // 统一成功标示
      message: "success", // 统一处理结果message
      data: [  // 具体的对象信息
        {
          userId: 10,
          userName: "Isaac Zhang",
          token: "2D3ABB6F2434109A105170FB21D00453",
          userStatus: 1,
          createTime: 1561118873000,
          updateTime: 1561118873000
        }
      ]
    }
    
    • 通过网关调用

      因为我在网关配置中加了前缀prefix: /gateway/api,因此,我们访问的时候需要添加上这个前缀信息,否则会报404错误。

      curl -G http://localhost:1111/gateway/api/ad-sponsor/user/get?username=Isaac%20Zhang,我们发现结果并没有按照我们想象的展示出来。

      bogon:~ zhangpan$ http://localhost:1111/gateway/api/ad-sponsor/user/get?username=Isaac%20Zhang
      -bash: http://localhost:1111/gateway/api/ad-sponsor/user/get?username=Isaac%20Zhang: No such file or directory
      

      为什么呢?我们来查看一下日志:

      2019-07-27 20:44:19.093  INFO 4766 --- [nio-1111-exec-4] c.s.a.g.filter.ValidateTokenFilter       : GET request to http://localhost:1111/gateway/api/ad-sponsor/user/get
      2019-07-27 20:44:19.093  WARN 4766 --- [nio-1111-exec-4] c.s.a.g.filter.ValidateTokenFilter       : access token is empty
      2019-07-27 20:44:19.098  INFO 4766 --- [nio-1111-exec-4] c.s.ad.gateway.filter.AccessLogFilter    : Request "/gateway/api/ad-sponsor/user/get" spent : 0 seconds.
      2019-07-27 20:48:37.801  INFO 4766 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver      : Resolving eureka endpoints via configuration
      

      我们可以清晰的看到,ValidateTokenFilter : access token is empty,为什么会有这么一个报错呢?那是因为我在配置网关的时候,添加了一次拦截:

      /**
       * ValidateTokenFilter for 服务token校验
       *
       * @author <a href="mailto:magicianisaac@gmail.com">Isaac.Zhang</a>
       */
      @Slf4j
      @Component
      public class ValidateTokenFilter extends ZuulFilter {
      ...
          @Override
          public Object run() throws ZuulException {
              RequestContext ctx = RequestContext.getCurrentContext();
              HttpServletRequest request = ctx.getRequest();
              log.info(String.format("%s request to %s", request.getMethod(), request.getRequestURL().toString()));
      
              Object accessToken = request.getHeader("accessToken"); //.getParameter("accessToken");
              if (accessToken == null) {
                  log.warn("access token is empty");
                  ctx.setSendZuulResponse(false);
                  ctx.setResponseStatusCode(401);
      //            ctx.setResponseBody(body)对返回body内容进行编辑
                  return null;
              }
              log.info("access token ok");
              return null;
          }
      }
      

      观察代码我们发现,会从RequestHeader中获取accessToken参数,我们没有提供,当然就会报错了呀。接下来,我们提供上该参数再试:

      bogon:~ zhangpan$ curl -H "accessToken:true" http://localhost:1111/gateway/api/ad-sponsor/user/get?username=Isaac%20Zhang
      ---返回
      {"code":0,"message":"success","data":[{"userId":10,"userName":"Isaac Zhang","token":"2D3ABB6F2434109A105170FB21D00453","userStatus":1,"createTime":1561118873000,"updateTime":1561118873000}]}
      

    至此,我们的广告投放系统简单功能已经全部实现完毕,并且可以通过网关进行转发。

  • 相关阅读:
    Backtrader中文笔记之Renko Bricks
    Renko Charts介绍
    Backtrader中文笔记之Cerebro(大脑)。
    Backtrader中文笔记之Operating the platform(操作平台)。
    Backtrader中文笔记之Platform Concepts(平台介绍)。
    Backtrader中文笔记_Quickstart。
    PyAlgoTrade 0.20中文笔记
    浅谈JS中 reduce() 的用法(转帖)
    Python websocket的示例(转帖)
    硬盘显示有容量,但无法放入文件,还有一个查看机器端口是否开放。
  • 原文地址:https://www.cnblogs.com/zhangpan1244/p/11266075.html
Copyright © 2011-2022 走看看