欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

[Spring cloud 一步步实现广告系统] 6. Service实现&Zuul配置&Test

程序员文章站 2022-05-25 12:52:46
DAO层设计实现 这里我们使用 来实现数据库操作,当然大家也可以使用 ,都是一样的,我们依然以用户表操作为例: JPARepository 的默认实现方法,如果我们只是继承了 而没有实现具体的操作方法,我们也是可以通过使用它的默认方法来做 操作的,如下: 功能Service实现 创建service ......
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操作的,如下:

    [Spring cloud 一步步实现广告系统] 6. Service实现&Zuul配置&Test

功能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}]}

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