[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操作的,如下:
功能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}]}
至此,我们的广告投放系统简单功能已经全部实现完毕,并且可以通过网关进行转发。
[Spring cloud 一步步实现广告系统] 6. Service实现&Zuul配置&Test的更多相关文章
- [Spring cloud 一步步实现广告系统] 19. 监控Hystrix Dashboard
在之前的18次文章中,我们实现了广告系统的广告投放,广告检索业务功能,中间使用到了 服务发现Eureka,服务调用Feign,网关路由Zuul以及错误熔断Hystrix等Spring Cloud组件. ...
- [Spring cloud 一步步实现广告系统] 21. 系统错误汇总
广告系统学习过程中问题答疑 博客园 Eureka集群启动报错 Answer 因为Eureka在集群启动过程中,会连接集群中其他的机器进行数据同步,在这个过程中,如果别的服务还没有启动完成,就会出现Co ...
- [Spring cloud 一步步实现广告系统] 2. 配置&Eureka服务
父项目管理 首先,我们在创建投放系统之前,先看一下我们的工程结构: mscx-ad-sponsor就是我们的广告投放系统.如上结构,我们需要首先创建一个Parent Project mscx-ad 来 ...
- [Spring cloud 一步步实现广告系统] 22. 广告系统回顾总结
到目前为止,我们整个初级广告检索系统就初步开发完成了,我们来整体回顾一下我们的广告系统. 整个广告系统编码结构如下: mscx-ad 父模块 主要是为了方便我们项目的统一管理 mscx-ad-db 这 ...
- [Spring cloud 一步步实现广告系统] 7. 中期总结回顾
在前面的过程中,我们创建了4个project: 服务发现 我们使用Eureka 作为服务发现组件,学习了Eureka Server,Eureka Client的使用. Eureka Server 加依 ...
- [Spring cloud 一步步实现广告系统] 1. 业务架构分析
什么是广告系统? 主要包含: 广告主投放广告的<广告投放系统> 媒体方(广告展示媒介-)检索广告用的<广告检索系统> 广告计费系统(按次,曝光量等等) 报表系统 Etc. 使用 ...
- [Spring cloud 一步步实现广告系统] 13. 索引服务编码实现
上一节我们分析了广告索引的维护有2种,全量索引加载和增量索引维护.因为广告检索是广告系统中最为重要的环节,大家一定要认真理解我们索引设计的思路,接下来我们来编码实现索引维护功能. 我们来定义一个接口, ...
- [Spring cloud 一步步实现广告系统] 12. 广告索引介绍
索引设计介绍 在我们广告系统中,为了我们能更快的拿到我们想要的广告数据,我们需要对广告数据添加类似于数据库index一样的索引结构,分两大类:正向索引和倒排索引. 正向索引 通过唯一键/主键生成与对象 ...
- [Spring cloud 一步步实现广告系统] 11. 使用Feign实现微服务调用
上一节我们使用了Ribbon(基于Http/Tcp)进行微服务的调用,Ribbon的调用比较简单,通过Ribbon组件对请求的服务进行拦截,通过Eureka Server 获取到服务实例的IP:Por ...
随机推荐
- CMAKE同时编译C++和CUDA文件
1. 首先是运行环境 Ubuntu 16.04 G++ 5.4.0 CUDA 8.0 2. 文件结构 cv@cv:~/myproject$ tree src src/ |-- CMakeLists.t ...
- IPFS学习-哈希
Hashes 哈希函数是接受一些任意输入并返回固定长度值的函数.具体值取决于所使用的给定哈希算法,例如SHA-1(GIT在使用),SHA-256,或者是BLAKE2,但是给予一个输入使用哈希算法总是返 ...
- AOP框架Dora.Interception 3.0 [5]: 基于策略的拦截器注册方式
注册拦截器旨在解决如何将拦截器应用到目标方法的问题.在我看来,针对拦截器的注册应该是明确而精准的,也就是我们提供的注册方式应该让拦截器准确地应用到期望的目标方法上,不能多也不能少.如果注册的方式过于模 ...
- 【CV现状-2】三维感知
#磨染的初心--计算机视觉的现状 [这一系列文章是关于计算机视觉的反思,希望能引起一些人的共鸣.可以随意传播,随意喷.所涉及的内容过多,将按如下内容划分章节.已经完成的会逐渐加上链接.] 缘起 三维感 ...
- Python使用openpyxl操作excel表格
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取 ht ...
- 【30天自制操作系统】day05:结构体、文字显示与 GDT/IDT 初始化
输出一个 16 行 8 列的点阵字符 void putfont8(char *vram, int xsize, int x, int y, char c, char *font) { int i; c ...
- React Native-ios环境的搭建
要在mac系统上搭建RN环境: 一.安装Homebrew.它是一款Mac OS平台下的软件包管理工具. 详见Homebrew的安装这篇文章. 二.安装node和watchman,用Homebrew在命 ...
- 关于InterruptedException的两篇博文的转载
博文一:https://www.jianshu.com/p/a8abe097d4ed InterruptedException异常 在了解InterruptedException异常之前应该了解以下的 ...
- Ajax之处理不同格式的JSON数据
JSON是一种网络中的数据格式,主要用于网络间的数据传输,它比XML格式的数据传输速度快,使用更广. 1.Ajax处理对象格式的JSON数据: <script src="../JS/j ...
- Class文件结构-练习题1
package org.fenixsoft.clazz; public class TestClass { private int m; public int inc() { return m + 1 ...