Exception处理改进,使用Clock
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
<orika.version>1.4.6</orika.version>
|
||||
<commons-codec.version>1.10</commons-codec.version>
|
||||
<assertj.version>2.2.0</assertj.version>
|
||||
<h2.version>1.3.176</h2.version>
|
||||
|
||||
<java.version>1.7</java.version>
|
||||
</properties>
|
||||
@@ -124,6 +125,17 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-maven-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<driver>org.h2.Driver</driver>
|
||||
<url>jdbc:h2:file:~/.h2/bootapi;AUTO_SERVER=TRUE;DB_CLOSE_DELAY=-1;</url>
|
||||
<user>sa</user>
|
||||
<password></password>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
+12
-7
@@ -1,15 +1,18 @@
|
||||
package org.springside.examples.bootapi.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springside.examples.bootapi.api.support.RestException;
|
||||
import org.springside.examples.bootapi.service.AccountService;
|
||||
import org.springside.examples.bootapi.service.ServiceException;
|
||||
import org.springside.examples.bootapi.service.exception.ErrorCode;
|
||||
import org.springside.modules.web.MediaTypes;
|
||||
|
||||
// Spring Restful MVC Controller的标识, 直接输出内容,不调用template引擎.
|
||||
@@ -21,14 +24,16 @@ public class AccountEndPoint {
|
||||
@Autowired
|
||||
private AccountService accountServcie;
|
||||
|
||||
@RequestMapping(value = "/api/accounts/login", produces = MediaTypes.TEXT_PLAIN_UTF_8)
|
||||
public String login(@RequestParam("email") String email, @RequestParam("password") String password) {
|
||||
@RequestMapping(value = "/api/accounts/login", produces = MediaTypes.JSON_UTF_8)
|
||||
public Map<String, String> login(@RequestParam("email") String email, @RequestParam("password") String password) {
|
||||
|
||||
if (StringUtils.isEmpty(email) || StringUtils.isEmpty(password)) {
|
||||
throw new RestException("User or password empty", HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("User or password empty", ErrorCode.BAD_REQUEST);
|
||||
}
|
||||
|
||||
return accountServcie.login(email, password);
|
||||
String token = accountServcie.login(email, password);
|
||||
|
||||
return Collections.singletonMap("token", token);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/api/accounts/logout")
|
||||
@@ -41,7 +46,7 @@ public class AccountEndPoint {
|
||||
@RequestParam("password") String password) {
|
||||
|
||||
if (StringUtils.isEmpty(email) || StringUtils.isEmpty(name) || StringUtils.isEmpty(password)) {
|
||||
throw new RestException("User or name or password empty", HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("User or name or password empty", ErrorCode.BAD_REQUEST);
|
||||
}
|
||||
|
||||
accountServcie.register(email, name, password);
|
||||
|
||||
+18
-1
@@ -18,6 +18,8 @@ import org.springside.examples.bootapi.dto.BookDto;
|
||||
import org.springside.examples.bootapi.service.AccountService;
|
||||
import org.springside.examples.bootapi.service.BookAdminService;
|
||||
import org.springside.examples.bootapi.service.BookBorrowService;
|
||||
import org.springside.examples.bootapi.service.ServiceException;
|
||||
import org.springside.examples.bootapi.service.exception.ErrorCode;
|
||||
import org.springside.modules.mapper.BeanMapper;
|
||||
import org.springside.modules.web.MediaTypes;
|
||||
|
||||
@@ -53,7 +55,7 @@ public class BookEndpoint {
|
||||
@RequestMapping(value = "/api/books", method = RequestMethod.POST, consumes = MediaTypes.JSON_UTF_8)
|
||||
public void createBook(@RequestBody BookDto bookDto,
|
||||
@RequestParam(value = "token", required = false) String token) {
|
||||
|
||||
checkToken(token);
|
||||
// 使用Header中的Token,查找登录用户
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
|
||||
@@ -67,6 +69,7 @@ public class BookEndpoint {
|
||||
@RequestMapping(value = "/api/books/{id}/modify", method = RequestMethod.POST, consumes = MediaTypes.JSON_UTF_8)
|
||||
public void modifyBook(@RequestBody BookDto bookDto,
|
||||
@RequestParam(value = "token", required = false) String token) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
Book book = BeanMapper.map(bookDto, Book.class);
|
||||
adminService.modifyBook(book, currentUser.id);
|
||||
@@ -74,6 +77,7 @@ public class BookEndpoint {
|
||||
|
||||
@RequestMapping(value = "/api/books/{id}/delete")
|
||||
public void deleteBook(@PathVariable("id") Long id, @RequestParam(value = "token", required = false) String token) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
adminService.deleteBook(id, currentUser.id);
|
||||
}
|
||||
@@ -81,6 +85,7 @@ public class BookEndpoint {
|
||||
@RequestMapping(value = "/api/books/{id}/request")
|
||||
public void applyBorrowRequest(@PathVariable("id") Long id,
|
||||
@RequestParam(value = "token", required = false) String token) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
borrowService.applyBorrowRequest(id, currentUser);
|
||||
}
|
||||
@@ -88,6 +93,7 @@ public class BookEndpoint {
|
||||
@RequestMapping(value = "/api/books/{id}/cancel")
|
||||
public void cancelBorrowRequest(@PathVariable("id") Long id,
|
||||
@RequestParam(value = "token", required = false) String token) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
borrowService.cancelBorrowRequest(id, currentUser);
|
||||
}
|
||||
@@ -95,6 +101,7 @@ public class BookEndpoint {
|
||||
@RequestMapping(value = "/api/books/{id}/confirm")
|
||||
public void markBookBorrowed(@PathVariable("id") Long id,
|
||||
@RequestParam(value = "token", required = false) String token) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
borrowService.markBookBorrowed(id, currentUser);
|
||||
}
|
||||
@@ -102,6 +109,7 @@ public class BookEndpoint {
|
||||
@RequestMapping(value = "/api/books/{id}/reject")
|
||||
public void rejectBorrowRequest(@PathVariable("id") Long id,
|
||||
@RequestParam(value = "token", required = false) String token) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
borrowService.rejectBorrowRequest(id, currentUser);
|
||||
}
|
||||
@@ -109,12 +117,14 @@ public class BookEndpoint {
|
||||
@RequestMapping(value = "/api/books/{id}/return")
|
||||
public void markBookReturned(@PathVariable("id") Long id,
|
||||
@RequestParam(value = "token", required = false) String token) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
borrowService.markBookReturned(id, currentUser);
|
||||
}
|
||||
|
||||
@RequestMapping(value = "/api/mybook", produces = MediaTypes.JSON_UTF_8)
|
||||
public List<BookDto> listMyBook(@RequestParam(value = "token", required = false) String token, Pageable pageable) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
List<Book> books = adminService.listMyBook(currentUser.id, pageable);
|
||||
return BeanMapper.mapList(books, BookDto.class);
|
||||
@@ -123,8 +133,15 @@ public class BookEndpoint {
|
||||
@RequestMapping(value = "/api/myborrowedbook", produces = MediaTypes.JSON_UTF_8)
|
||||
public List<BookDto> listMyBorrowedBook(@RequestParam(value = "token", required = false) String token,
|
||||
Pageable pageable) {
|
||||
checkToken(token);
|
||||
Account currentUser = accountService.getLoginUser(token);
|
||||
List<Book> books = borrowService.listMyBorrowedBook(currentUser.id, pageable);
|
||||
return BeanMapper.mapList(books, BookDto.class);
|
||||
}
|
||||
|
||||
private void checkToken(String token) {
|
||||
if (token == null) {
|
||||
throw new ServiceException("No token in request", ErrorCode.NO_TOKEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package org.springside.examples.bootapi.api.support;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
public class RestException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -8634700792767837033L;
|
||||
|
||||
public HttpStatus status;
|
||||
|
||||
public RestException(String message, HttpStatus status) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package org.springside.examples.bootapi.api.support;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
import org.springside.modules.web.MediaTypes;
|
||||
|
||||
@ControllerAdvice
|
||||
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
|
||||
@ExceptionHandler(value = { RestException.class })
|
||||
public final ResponseEntity<?> handleException(RestException ex, WebRequest request) {
|
||||
logger.error(ex.getMessage(), ex);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType(MediaTypes.TEXT_PLAIN_UTF_8));
|
||||
return new ResponseEntity<Object>(ex.getMessage(), headers, ex.status);
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package org.springside.examples.bootapi.api.support;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
import org.springside.examples.bootapi.service.ServiceException;
|
||||
import org.springside.modules.web.MediaTypes;
|
||||
|
||||
@ControllerAdvice
|
||||
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
|
||||
@ExceptionHandler(value = { ServiceException.class })
|
||||
public final ResponseEntity<?> handleException(ServiceException ex, HttpServletRequest request) {
|
||||
logger.error(ex.getMessage() + ", request from " + request.getRemoteAddr(), ex);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType(MediaTypes.JSON_UTF_8));
|
||||
ErrorResult result = new ErrorResult(ex.code.code, ex.getMessage());
|
||||
return new ResponseEntity<Object>(result, headers, HttpStatus.valueOf(ex.code.httpStatus));
|
||||
}
|
||||
|
||||
public static class ErrorResult {
|
||||
|
||||
public int code;
|
||||
public String message;
|
||||
|
||||
public ErrorResult() {
|
||||
|
||||
}
|
||||
|
||||
public ErrorResult(int code, String message) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -32,10 +32,10 @@ public class Message {
|
||||
|
||||
}
|
||||
|
||||
public Message(Account receiver, String message) {
|
||||
public Message(Account receiver, String message, Date receiveDate) {
|
||||
this.receiver = receiver;
|
||||
this.message = message;
|
||||
this.receiveDate = new Date();
|
||||
this.receiveDate = receiveDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-10
@@ -9,12 +9,11 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.actuate.metrics.CounterService;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springside.examples.bootapi.api.support.RestException;
|
||||
import org.springside.examples.bootapi.domain.Account;
|
||||
import org.springside.examples.bootapi.repository.AccountDao;
|
||||
import org.springside.examples.bootapi.service.exception.ErrorCode;
|
||||
import org.springside.modules.utils.Digests;
|
||||
import org.springside.modules.utils.Encodes;
|
||||
import org.springside.modules.utils.Ids;
|
||||
@@ -53,11 +52,11 @@ public class AccountService {
|
||||
Account account = accountDao.findByEmail(email);
|
||||
|
||||
if (account == null) {
|
||||
throw new RestException("User not exist", HttpStatus.UNAUTHORIZED);
|
||||
throw new ServiceException("User not exist", ErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (!account.hashPassword.equals(hashPassword(password))) {
|
||||
throw new RestException("Password wrong", HttpStatus.UNAUTHORIZED);
|
||||
throw new ServiceException("Password wrong", ErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
String token = Ids.uuid2();
|
||||
@@ -78,15 +77,10 @@ public class AccountService {
|
||||
|
||||
public Account getLoginUser(String token) {
|
||||
|
||||
if (token == null) {
|
||||
logger.error("no token");
|
||||
throw new RestException("User doesn't login", HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
Account account = loginUsers.getIfPresent(token);
|
||||
|
||||
if (account == null) {
|
||||
throw new RestException("User doesn't login", HttpStatus.UNAUTHORIZED);
|
||||
throw new ServiceException("User doesn't login", ErrorCode.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
return account;
|
||||
|
||||
+8
-6
@@ -1,19 +1,18 @@
|
||||
package org.springside.examples.bootapi.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springside.examples.bootapi.api.support.RestException;
|
||||
import org.springside.examples.bootapi.domain.Account;
|
||||
import org.springside.examples.bootapi.domain.Book;
|
||||
import org.springside.examples.bootapi.repository.BookDao;
|
||||
import org.springside.examples.bootapi.service.exception.ErrorCode;
|
||||
import org.springside.modules.utils.Clock;
|
||||
|
||||
// Spring Bean的标识.
|
||||
@Service
|
||||
@@ -24,6 +23,9 @@ public class BookAdminService {
|
||||
@Autowired
|
||||
private BookDao bookDao;
|
||||
|
||||
// 可注入的Clock,方便测试时控制日期
|
||||
protected Clock clock = Clock.DEFAULT;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Iterable<Book> findAll(Pageable pageable) {
|
||||
return bookDao.findAll(pageable);
|
||||
@@ -44,7 +46,7 @@ public class BookAdminService {
|
||||
|
||||
book.owner = owner;
|
||||
book.status = Book.STATUS_IDLE;
|
||||
book.onboardDate = new Date();
|
||||
book.onboardDate = clock.getCurrentDate();
|
||||
|
||||
bookDao.save(book);
|
||||
}
|
||||
@@ -52,7 +54,7 @@ public class BookAdminService {
|
||||
@Transactional
|
||||
public void modifyBook(Book book, Long currentAccountId) {
|
||||
if (!currentAccountId.equals(book.owner.id)) {
|
||||
throw new RestException("User can't modify others book", HttpStatus.FORBIDDEN);
|
||||
throw new ServiceException("User can't modify others book", ErrorCode.BOOK_OWNERSHIP_WRONG);
|
||||
}
|
||||
|
||||
Book orginalBook = bookDao.findOne(book.id);
|
||||
@@ -66,7 +68,7 @@ public class BookAdminService {
|
||||
Book book = bookDao.findOne(id);
|
||||
|
||||
if (!currentAccountId.equals(book.owner.id)) {
|
||||
throw new RestException("User can't delete others book", HttpStatus.FORBIDDEN);
|
||||
throw new ServiceException("User can't delete others book", ErrorCode.BOOK_OWNERSHIP_WRONG);
|
||||
}
|
||||
|
||||
bookDao.delete(id);
|
||||
|
||||
+22
-19
@@ -1,21 +1,20 @@
|
||||
package org.springside.examples.bootapi.service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springside.examples.bootapi.api.support.RestException;
|
||||
import org.springside.examples.bootapi.domain.Account;
|
||||
import org.springside.examples.bootapi.domain.Book;
|
||||
import org.springside.examples.bootapi.domain.Message;
|
||||
import org.springside.examples.bootapi.repository.BookDao;
|
||||
import org.springside.examples.bootapi.repository.MessageDao;
|
||||
import org.springside.examples.bootapi.service.exception.ErrorCode;
|
||||
import org.springside.modules.utils.Clock;
|
||||
|
||||
// Spring Bean的标识.
|
||||
@Service
|
||||
@@ -29,6 +28,9 @@ public class BookBorrowService {
|
||||
@Autowired
|
||||
protected MessageDao messageDao;
|
||||
|
||||
// 可注入的Clock,方便测试时控制日期
|
||||
protected Clock clock = Clock.DEFAULT;
|
||||
|
||||
@Transactional
|
||||
public void applyBorrowRequest(Long id, Account borrower) {
|
||||
Book book = bookDao.findOne(id);
|
||||
@@ -36,12 +38,13 @@ public class BookBorrowService {
|
||||
if (!book.status.equals(Book.STATUS_IDLE)) {
|
||||
logger.error("User request the book not idle, user id:" + borrower.id + ",book id:" + id + ",status:"
|
||||
+ book.status);
|
||||
throw new RestException("The book is not idle", HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("The book is not idle", ErrorCode.BOOK_STATUS_WRONG);
|
||||
}
|
||||
|
||||
if (borrower.id.equals(book.owner.id)) {
|
||||
logger.error("User borrow the book himself, user id:" + borrower.id + ",book id:" + id);
|
||||
throw new RestException("User shouldn't borrower the book which is himeself", HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("User shouldn't borrower the book which is himeself",
|
||||
ErrorCode.BOOK_OWNERSHIP_WRONG);
|
||||
}
|
||||
|
||||
book.status = Book.STATUS_REQUEST;
|
||||
@@ -49,7 +52,7 @@ public class BookBorrowService {
|
||||
bookDao.save(book);
|
||||
|
||||
Message message = new Message(book.owner,
|
||||
String.format("Apply book <%s> request by %s", book.title, borrower.name));
|
||||
String.format("Apply book <%s> request by %s", book.title, borrower.name), clock.getCurrentDate());
|
||||
|
||||
messageDao.save(message);
|
||||
}
|
||||
@@ -61,13 +64,13 @@ public class BookBorrowService {
|
||||
if (!book.status.equals(Book.STATUS_REQUEST)) {
|
||||
logger.error("User cancel the book not reqesting, user id:" + borrower.id + ",book id:" + id + ",status:"
|
||||
+ book.status);
|
||||
throw new RestException("The book is not requesting", HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG);
|
||||
}
|
||||
|
||||
if (!borrower.id.equals(book.borrower.id)) {
|
||||
logger.error("User cancel the book not request by him, user id:" + borrower.id + ",book id:" + id
|
||||
+ ",borrower id" + book.borrower.id);
|
||||
throw new RestException("User can't cancel other ones request", HttpStatus.FORBIDDEN);
|
||||
throw new ServiceException("User can't cancel other ones request", ErrorCode.BOOK_OWNERSHIP_WRONG);
|
||||
}
|
||||
|
||||
book.status = Book.STATUS_IDLE;
|
||||
@@ -75,7 +78,7 @@ public class BookBorrowService {
|
||||
bookDao.save(book);
|
||||
|
||||
Message message = new Message(book.owner,
|
||||
String.format("Cancel book <%s> request by %s", book.title, borrower.name));
|
||||
String.format("Cancel book <%s> request by %s", book.title, borrower.name), clock.getCurrentDate());
|
||||
|
||||
messageDao.save(message);
|
||||
}
|
||||
@@ -87,21 +90,21 @@ public class BookBorrowService {
|
||||
if (!book.status.equals(Book.STATUS_REQUEST)) {
|
||||
logger.error("User confirm the book not reqesting, user id:" + owner.id + ",book id:" + id + ",status:"
|
||||
+ book.status);
|
||||
throw new RestException("The book is not requesting", HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG);
|
||||
}
|
||||
|
||||
if (!owner.id.equals(book.owner.id)) {
|
||||
logger.error("User confirm the book not himself, user id:" + owner.id + ",book id:" + id + ",owner id"
|
||||
+ book.owner.id);
|
||||
throw new RestException("User can't cofirm others book", HttpStatus.FORBIDDEN);
|
||||
throw new ServiceException("User can't cofirm others book", ErrorCode.BOOK_OWNERSHIP_WRONG);
|
||||
}
|
||||
|
||||
book.status = Book.STATUS_OUT;
|
||||
book.borrowDate = new Date();
|
||||
book.borrowDate = clock.getCurrentDate();
|
||||
bookDao.save(book);
|
||||
|
||||
Message message = new Message(book.borrower,
|
||||
String.format("Confirm book <%s> request by %s", book.title, owner.name));
|
||||
String.format("Confirm book <%s> request by %s", book.title, owner.name), clock.getCurrentDate());
|
||||
messageDao.save(message);
|
||||
}
|
||||
|
||||
@@ -112,14 +115,14 @@ public class BookBorrowService {
|
||||
if (!book.status.equals(Book.STATUS_REQUEST)) {
|
||||
logger.error("User reject the book not reqesting, user id:" + owner.id + ",book id:" + id + ",status:"
|
||||
+ book.status);
|
||||
throw new RestException("The book is not requesting", HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("The book is not requesting", ErrorCode.BOOK_STATUS_WRONG);
|
||||
}
|
||||
|
||||
if (!owner.id.equals(book.owner.id)) {
|
||||
|
||||
logger.error("User reject the book not himself, user id:" + owner.id + ",book id:" + id + ",owener id"
|
||||
+ book.owner.id);
|
||||
throw new RestException("User can't reject others book", HttpStatus.FORBIDDEN);
|
||||
throw new ServiceException("User can't reject others book", ErrorCode.BOOK_OWNERSHIP_WRONG);
|
||||
}
|
||||
|
||||
book.status = Book.STATUS_IDLE;
|
||||
@@ -128,7 +131,7 @@ public class BookBorrowService {
|
||||
bookDao.save(book);
|
||||
|
||||
Message message = new Message(book.borrower,
|
||||
String.format("Reject book <%s> request by %s", book.title, owner.name));
|
||||
String.format("Reject book <%s> request by %s", book.title, owner.name), clock.getCurrentDate());
|
||||
messageDao.save(message);
|
||||
}
|
||||
|
||||
@@ -139,13 +142,13 @@ public class BookBorrowService {
|
||||
if (!book.status.equals(Book.STATUS_OUT)) {
|
||||
logger.error(
|
||||
"User return the book not out, user id:" + owner.id + ",book id:" + id + ",status:" + book.status);
|
||||
throw new RestException("The book is not borrowing", HttpStatus.BAD_REQUEST);
|
||||
throw new ServiceException("The book is not borrowing", ErrorCode.BOOK_STATUS_WRONG);
|
||||
}
|
||||
|
||||
if (!owner.id.equals(book.owner.id)) {
|
||||
logger.error("User return the book not himself, user id:" + owner.id + ",book id:" + id + ",owner id"
|
||||
+ book.owner.id);
|
||||
throw new RestException("User can't make others book returned", HttpStatus.FORBIDDEN);
|
||||
throw new ServiceException("User can't make others book returned", ErrorCode.BOOK_OWNERSHIP_WRONG);
|
||||
}
|
||||
|
||||
book.status = Book.STATUS_IDLE;
|
||||
@@ -154,7 +157,7 @@ public class BookBorrowService {
|
||||
bookDao.save(book);
|
||||
|
||||
Message message = new Message(book.borrower,
|
||||
String.format("Mark book <%s> returned by %s", book.title, owner.name));
|
||||
String.format("Mark book <%s> returned by %s", book.title, owner.name), clock.getCurrentDate());
|
||||
messageDao.save(message);
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package org.springside.examples.bootapi.service;
|
||||
|
||||
import org.springside.examples.bootapi.service.exception.ErrorCode;
|
||||
|
||||
public class ServiceException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -8634700792767837033L;
|
||||
|
||||
public ErrorCode code;
|
||||
|
||||
public ServiceException(String message, ErrorCode code) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package org.springside.examples.bootapi.service.exception;
|
||||
|
||||
public enum ErrorCode {
|
||||
|
||||
BAD_REQUEST(1000, 400), UNAUTHORIZED(1001, 401), FORBIDDEN(1002, 403),
|
||||
|
||||
BOOK_STATUS_WRONG(1100, 400), BOOK_OWNERSHIP_WRONG(1101, 403), NO_TOKEN(1102, 401);
|
||||
|
||||
public int code;
|
||||
public int httpStatus;
|
||||
|
||||
ErrorCode(int code, int httpStatus) {
|
||||
this.code = code;
|
||||
this.httpStatus = httpStatus;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@ spring.datasource.password=
|
||||
#disable automatic initialize for embedded H2
|
||||
spring.jpa.hibernate.ddl-auto=none
|
||||
spring.datasource.initialize=false
|
||||
flyway.enabled=true
|
||||
|
||||
#connection pool settings
|
||||
spring.datasource.initial-size=10
|
||||
|
||||
@@ -9,6 +9,8 @@ app.loginTimeoutSecs=600
|
||||
spring.datasource.sqlScriptEncoding=UTF-8
|
||||
spring.jpa.hibernate.ddl-auto=validate
|
||||
spring.jpa.showsql=false
|
||||
spring.datasource.initialize=true
|
||||
flyway.enabled=false
|
||||
|
||||
# other settings
|
||||
spring.main.show-banner=false
|
||||
|
||||
+19
-6
@@ -32,9 +32,12 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springside.examples.bootapi.BootApiApplication;
|
||||
import org.springside.examples.bootapi.api.support.ServiceExceptionHandler.ErrorResult;
|
||||
import org.springside.examples.bootapi.domain.Book;
|
||||
import org.springside.examples.bootapi.dto.BookDto;
|
||||
import org.springside.examples.bootapi.repository.BookDao;
|
||||
import org.springside.examples.bootapi.service.exception.ErrorCode;
|
||||
import org.springside.modules.mapper.JsonMapper;
|
||||
import org.springside.modules.test.data.RandomData;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
@@ -53,6 +56,8 @@ public class BookEndpointTest {
|
||||
private BookDao bookDao;
|
||||
|
||||
private RestTemplate restTemplate;
|
||||
private JsonMapper jsonMapper = new JsonMapper();
|
||||
|
||||
private String resourceUrl;
|
||||
private String loginUrl;
|
||||
private String logoutUrl;
|
||||
@@ -101,6 +106,8 @@ public class BookEndpointTest {
|
||||
// 未设置token
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(resourceUrl + "/{id}/request", String.class, 1L);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
ErrorResult errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class);
|
||||
assertThat(errorResult.code).isEqualTo(ErrorCode.NO_TOKEN.code);
|
||||
|
||||
Book book = bookDao.findOne(1L);
|
||||
assertThat(book.borrower).isNull();
|
||||
@@ -108,6 +115,8 @@ public class BookEndpointTest {
|
||||
// 设置错误token
|
||||
response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 1L, "abc");
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
|
||||
errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class);
|
||||
assertThat(errorResult.code).isEqualTo(ErrorCode.UNAUTHORIZED.code);
|
||||
|
||||
book = bookDao.findOne(1L);
|
||||
assertThat(book.borrower).isNull();
|
||||
@@ -116,7 +125,9 @@ public class BookEndpointTest {
|
||||
String token = login("calvin.xiao@vipshop.com");
|
||||
|
||||
response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 1L, token);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
|
||||
errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class);
|
||||
assertThat(errorResult.code).isEqualTo(ErrorCode.BOOK_OWNERSHIP_WRONG.code);
|
||||
|
||||
book = bookDao.findOne(1L);
|
||||
assertThat(book.borrower).isNull();
|
||||
@@ -131,6 +142,8 @@ public class BookEndpointTest {
|
||||
|
||||
response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class, 3L, token);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
errorResult = jsonMapper.fromJson(response.getBody(), ErrorResult.class);
|
||||
assertThat(errorResult.code).isEqualTo(ErrorCode.BOOK_STATUS_WRONG.code);
|
||||
|
||||
// 回退操作
|
||||
response = restTemplate.getForEntity(resourceUrl + "/{id}/cancel?token={token}", String.class, 3L, token);
|
||||
@@ -145,8 +158,8 @@ public class BookEndpointTest {
|
||||
// 发起请求
|
||||
String token = login("david02.wang@vipshop.com");
|
||||
|
||||
ResponseEntity response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}", String.class,
|
||||
1L, token);
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(resourceUrl + "/{id}/request?token={token}",
|
||||
String.class, 1L, token);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
|
||||
logout(token);
|
||||
@@ -167,10 +180,10 @@ public class BookEndpointTest {
|
||||
map.put("email", user);
|
||||
map.put("password", "springside");
|
||||
|
||||
ResponseEntity<String> response = restTemplate.getForEntity(loginUrl + "?email={email}&password={password}",
|
||||
String.class, map);
|
||||
ResponseEntity<Map> response = restTemplate.getForEntity(loginUrl + "?email={email}&password={password}",
|
||||
Map.class, map);
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
return response.getBody();
|
||||
return (String) response.getBody().get("token");
|
||||
}
|
||||
|
||||
public void logout(String token) {
|
||||
|
||||
@@ -34,24 +34,12 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-jaxb-annotations</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ma.glasnost.orika</groupId>
|
||||
<artifactId>orika-core</artifactId>
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.util.JSONPObject;
|
||||
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
|
||||
|
||||
/**
|
||||
* 简单封装Jackson,实现JSON String<->Java Object的Mapper.
|
||||
@@ -64,9 +63,7 @@ public class JsonMapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Object可以是POJO,也可以是Collection或数组。
|
||||
* 如果对象为Null, 返回"null".
|
||||
* 如果集合为空集合, 返回"[]".
|
||||
* Object可以是POJO,也可以是Collection或数组。 如果对象为Null, 返回"null". 如果集合为空集合, 返回"[]".
|
||||
*/
|
||||
public String toJson(Object object) {
|
||||
|
||||
@@ -81,8 +78,7 @@ public class JsonMapper {
|
||||
/**
|
||||
* 反序列化POJO或简单Collection如List<String>.
|
||||
*
|
||||
* 如果JSON字符串为Null或"null"字符串, 返回Null.
|
||||
* 如果JSON字符串为"[]", 返回空集合.
|
||||
* 如果JSON字符串为Null或"null"字符串, 返回Null. 如果JSON字符串为"[]", 返回空集合.
|
||||
*
|
||||
* 如需反序列化复杂Collection如List<MyBean>, 请使用fromJson(String, JavaType)
|
||||
*
|
||||
@@ -154,24 +150,13 @@ public class JsonMapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* 設定是否使用Enum的toString函數來讀寫Enum,
|
||||
* 為False時時使用Enum的name()函數來讀寫Enum, 默認為False.
|
||||
* 注意本函數一定要在Mapper創建後, 所有的讀寫動作之前調用.
|
||||
* 設定是否使用Enum的toString函數來讀寫Enum, 為False時時使用Enum的name()函數來讀寫Enum, 默認為False. 注意本函數一定要在Mapper創建後, 所有的讀寫動作之前調用.
|
||||
*/
|
||||
public void enableEnumUseToString() {
|
||||
mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
|
||||
mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
|
||||
}
|
||||
|
||||
/**
|
||||
* 支持使用Jaxb的Annotation,使得POJO上的annotation不用与Jackson耦合。
|
||||
* 默认会先查找jaxb的annotation,如果找不到再找jackson的。
|
||||
*/
|
||||
public void enableJaxbAnnotation() {
|
||||
JaxbAnnotationModule module = new JaxbAnnotationModule();
|
||||
mapper.registerModule(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出Mapper做进一步的设置或使用其他序列化API.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user