這里我們使用Spring DATA JPA
來實現(xiàn)數(shù)據(jù)庫操作,當然大家也可以使用Mybatis
,都是一樣的,我們依然以用戶表操作為例:
讓客戶滿意是我們工作的目標,不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領域值得信任、有價值的長期合作伙伴,公司提供的服務項目有:國際域名空間、虛擬主機、營銷軟件、網(wǎng)站建設、新干網(wǎng)站維護、網(wǎng)站推廣。
/**
* AdUserRepository for 用戶數(shù)據(jù)庫操作接口
* 繼承自JpaRepository,第一個參數(shù)AdUser代表當前要操作的實體類的class定義,第二個參數(shù)Long表示該類的主鍵類型
*
* @author Isaac.Zhang
*/
public interface AdUserRepository extends JpaRepository {
/**
* 根據(jù)用戶名稱獲取用戶
*
* @param username 名稱
* @return 用戶對象
*/
AdUser findByUserName(String username);
List findAllByUserName(String userName);
}
JPARepository 的默認實現(xiàn)方法,如果我們只是繼承了JpaRepository
而沒有實現(xiàn)具體的操作方法,我們也是可以通過使用它的默認方法來做CRUD
操作的,如下:
創(chuàng)建service package,依然以用戶操作為例,創(chuàng)建com.sxzhongf.ad.service.IUserService
和com.sxzhongf.ad.service.impl.UserServiceImpl
,UserServiceImpl
實現(xiàn)了IUserService
。
IUserService
接口/**
* IUserService for 用戶service
*
* @author Isaac.Zhang | 若初
*/
public interface IUserService {
/**
* 創(chuàng)建用戶接口
*
* @param userRequestVO {@link UserRequestVO}
* @return {@link UserResponseVO}
* @throws AdException 錯誤
*/
UserResponseVO createUser(UserRequestVO userRequestVO) throws AdException;
List findAllByUserName(String userName);
}
IUserService
接口/**
* UserServiceImpl for 用戶service
*
* @author Isaac.Zhang | 若初
*/
@Slf4j
@Service
public class UserServiceImpl implements IUserService {
private final AdUserRepository userRepository;
@Autowired
public UserServiceImpl(AdUserRepository userRepository) {
this.userRepository = userRepository;
}
/**
* 創(chuàng)建用戶
*
* @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 findAllByUserName(String userName) {
return userRepository.findAllByUserName(userName);
}
}
創(chuàng)建數(shù)據(jù)傳輸對象(dto/vo)
其實好多人在這里都會特別郁悶,搞不清楚這些命名有什么區(qū)別,個人建議是大家不用糾結,dto(data transfer object),就是表示我們在各個層傳遞的對象,vo在展示層操作的對象。但是這個只是個命名,它的本質就是一個object, 你傳遞到DAO層可以嗎?當然可以,你傳單獨字段都是可以的。所以,沒必要過分糾結這種信息,咬文嚼字有時候反而會適得其反。
/**
* UserRequestVO for 創(chuàng)建用戶請求對象VO
*
* @author Isaac.Zhang | 若初
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRequestVO {
private String userName;
public boolean validate() {
return !StringUtils.isEmpty(userName);
}
}
---
/**
* UserResponseVO for 用戶響應VO
*
* @author Isaac.Zhang | 若初
*/
@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 Isaac.Zhang | 若初
*/
public class Constants {
/**
* 通用錯誤信息異常類
*/
public static class ErrorMessage {
public static final String REQUEST_PARAM_ERROR = "請求參數(shù)異常";
public static final String USER_EXIST = "用戶已存在";
public static final String USER_NOT_EXIST = "用戶不存在";
}
}
com.sxzhongf.ad.common.utils.CommonUtils
,用來對用戶username進行md5加密來獲取token信息。/**
* CommonUtils for 工具類
*
* @author Isaac.Zhang | 若初
*/
@Slf4j
public class CommonUtils {
/**
* md5 加密
*/
public static String md5(String value) {
return DigestUtils.md5Hex(value).toUpperCase();
}
}
參考創(chuàng)建用戶的實現(xiàn),依次實現(xiàn)其他表操作。
依然以用戶功能實現(xiàn)為例:
/**
* UserController for 用戶controller
*
* @author Isaac.Zhang | 若初
*/
@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));
}
}
我們在投放系統(tǒng)的配置中,配置了server.servlet.context-path:/ad-sponsor
這么一個路徑,意味著所有請求當前系統(tǒng)的路徑都需要帶有ad-sponsor, 例如:http://xxx/ad-sponsor/user/get?username=yyy
,這是網(wǎng)關請求所必需的。根據(jù)上述,我們在網(wǎng)關服務中配置我們當前的投放系統(tǒng):
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中聲明過的服務
# 配置網(wǎng)關路由規(guī)則
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 設置的路徑進行截取,默認轉發(fā)會截取掉配置的前綴
直接訪問投放系統(tǒng)
調用curl -G http://localhost:7000/ad-sponsor/user/get?username=Isaac%20Zhang
,返回結果:
{
code: 0, // 統(tǒng)一成功標示
message: "success", // 統(tǒng)一處理結果message
data: [ // 具體的對象信息
{
userId: 10,
userName: "Isaac Zhang",
token: "2D3ABB6F2434109A105170FB21D00453",
userStatus: 1,
createTime: 1561118873000,
updateTime: 1561118873000
}
]
}
通過網(wǎng)關調用
因為我在網(wǎng)關配置中加了前綴prefix: /gateway/api
,因此,我們訪問的時候需要添加上這個前綴信息,否則會報404錯誤。
curl -G http://localhost:1111/gateway/api/ad-sponsor/user/get?username=Isaac%20Zhang
,我們發(fā)現(xiàn)結果并沒有按照我們想象的展示出來。
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
,為什么會有這么一個報錯呢?那是因為我在配置網(wǎng)關的時候,添加了一次攔截:
/**
* ValidateTokenFilter for 服務token校驗
*
* @author Isaac.Zhang
*/
@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;
}
}
觀察代碼我們發(fā)現(xiàn),會從RequestHeader
中獲取accessToken
參數(shù),我們沒有提供,當然就會報錯了呀。接下來,我們提供上該參數(shù)再試:
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}]}
至此,我們的廣告投放系統(tǒng)簡單功能已經全部實現(xiàn)完畢,并且可以通過網(wǎng)關進行轉發(fā)。