DAO 层设计实现
这里我们使用 Spring DATA JPA 来实现数据库操作, 当然大家也可以使用 Mybatis, 都是一样的, 我们依然以用户表操作为例:
- /**
- * AdUserRepository for 用户数据库操作接口
- * 继承自 JpaRepository<AdUser, Long>, 第一个参数 AdUser 代表当前要操作的实体类的 class 定义, 第二个参数 Long 表示该类的主键类型
- *
- * @author <a href="mailto:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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:[email protected]">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 Zhang, 返回结果:
- {
- 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 Zhang, 我们发现结果并没有按照我们想象的展示出来.
- bogon:~ zhangpan$ http://localhost:1111/gateway/API/ad-sponsor/user/get?username=Isaac Zhang
- -bash: http://localhost:1111/gateway/API/ad-sponsor/user/get?username=Isaac Zhang: 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:[email protected]">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 Zhang
--- 返回
{"code":0,"message":"success","data":[{"userId":10,"userName":"Isaac Zhang","token":"2D3ABB6F2434109A105170FB21D00453","userStatus":1,"createTime":1561118873000,"updateTime":1561118873000}]}
至此, 我们的广告投放系统简单功能已经全部实现完毕, 并且可以通过网关进行转发.
[Spring cloud 一步步实现广告系统] 6. Service 实现 & Zuul 配置 & Test
来源: http://www.bubuko.com/infodetail-3138261.html