[Spring cloud 一步步实现广告系统] 6. Service&Zuul配置
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
操作的,如下:
功能Service实现
创建service package,依然以用户操作为例,创建com.sxzhongf.ad.service.IUserService
和com.sxzhongf.ad.service.impl.UserServiceImpl
,UserServiceImpl
实现了IUserService
。
- 创建
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); }
- 使用
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); } }
- 创建数据传输对象(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; }
- 因为报错信息有可能是相同的,那我们抽取一个常量类来封装。
/** * 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 = "用户不存在"; } }
- 在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}]}
至此,我们的广告投放系统简单功能已经全部实现完毕,并且可以通过网关进行转发。
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
[Spring cloud 一步步实现广告系统] 4. 通用代码模块设计
一个大的系统,在代码的复用肯定是必不可少的,它能解决: 统一的响应处理(可以对外提供统一的响应对象包装) graph LR HTTP-->|HttpRequest|RestController RestController-->|HttpResponse -> JSON|HTTP 统一的异常处理(可以将业务异常统一收集处理) graph TB subgraph 处理过程 AdCommonException -->|RestControllerAdvice| HttpRequest end subgraph HttpResponse RestControllerAdvice -->|统一返回| JSON end 通用代码定义、配置定义(通用的配置信息放在统一的代码管理中,便于维护和更新) 创建项目 mscx-ad-common POM文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="h...
- 下一篇
[Spring cloud 一步步实现广告系统] 10. Ribbon 服务调用
在使用Ribbon调用广告投放系统API之前,我们需要先创建2个VO对象,AdPlanVO,AdPlanGetRequestVO. //数据请求对象 @Data @NoArgsConstructor @AllArgsConstructor public class AdPlanGetRequestVO { private Long userId; private List<Long> planIds; } ---------------------------------- //API请求响应结果反序列化对象 @Data @AllArgsConstructor @NoArgsConstructor public class AdPlanVO { private Long planId; private Long userId; private String planName; private Integer planStatus; private Date startDate; private Date endDate; private Date createTime; p...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题