package com.mzh.library.service; import com.mzh.library.dao.BookDao; import com.mzh.library.entity.AuthenticatedUser; import com.mzh.library.entity.Book; import com.mzh.library.entity.BookCategory; import com.mzh.library.entity.BookSearchCriteria; import com.mzh.library.entity.BookStatus; import com.mzh.library.entity.Permission; import com.mzh.library.entity.Role; import com.mzh.library.exception.DaoException; import com.mzh.library.service.impl.BookServiceImpl; import java.util.ArrayList; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; public final class BookServiceCheck { private static final String UNAVAILABLE_MESSAGE = "图书服务暂时不可用,请稍后重试。"; private BookServiceCheck() { } public static void main(String[] args) { Logger.getLogger(BookServiceImpl.class.getName()).setLevel(Level.OFF); InMemoryBookDao dao = new InMemoryBookDao(); BookService service = new BookServiceImpl(dao); AuthenticatedUser librarian = user(10L, Role.LIBRARIAN); AuthenticatedUser reader = user(20L, Role.READER); ServiceResult invalidInventory = service.createBook(librarian, book(0L, "BK-1000", "Invalid Copies", "Test Author", 1L, 2, 3, BookStatus.AVAILABLE)); require(!invalidInventory.isSuccessful(), "invalid inventory should fail"); require(invalidInventory.getErrors().containsKey("availableCopies"), "available copies greater than total should be rejected"); ServiceResult denied = service.createBook(reader, book(0L, "BK-1001", "Reader Write", "Test Author", 1L, 1, 1, BookStatus.AVAILABLE)); require(!denied.isSuccessful(), "reader write should fail"); require("您无权管理图书。".equals(denied.getMessage()), "reader write should return permission message"); ServiceResult deniedCategory = service.createCategory(reader, category(0L, "Reader Category", "Denied category")); require(!deniedCategory.isSuccessful(), "reader category create should fail"); require("您无权管理图书。".equals(deniedCategory.getMessage()), "reader category write should return permission message"); ServiceResult created = service.createBook(librarian, book(0L, "BK-1002", "Service Test", "Test Author", 1L, 2, 1, BookStatus.AVAILABLE)); require(created.isSuccessful(), "librarian should create a valid book"); long createdId = created.getData(); ServiceResult duplicate = service.createBook(librarian, book(0L, "BK-1002", "Duplicate", "Test Author", 1L, 1, 1, BookStatus.AVAILABLE)); require(!duplicate.isSuccessful(), "duplicate identifier should fail"); require(duplicate.getErrors().containsKey("identifier"), "duplicate should target identifier field"); ServiceResult updated = service.updateBook(librarian, book(createdId, "BK-1003", "Service Test Updated", "Test Author", 1L, 3, 3, BookStatus.AVAILABLE)); require(updated.isSuccessful(), "librarian should update a valid book"); require(dao.findById(createdId).get().getAvailableCopies() == 3, "update should persist available copies"); ServiceResult deleteUsedCategory = service.deleteCategory(librarian, 1L); require(!deleteUsedCategory.isSuccessful(), "used category delete should fail"); require("该分类已被现有图书使用,不能删除。".equals(deleteUsedCategory.getMessage()), "used category delete should return a safe specific message"); require(deleteUsedCategory.getErrors().containsKey("category"), "used category delete should return a category-level field error"); ServiceResult> search = service.searchBooks(new BookSearchCriteria("BK-1003", "", "", null)); require(search.isSuccessful(), "search should succeed"); require(search.getData().size() == 1, "search should find updated identifier"); ServiceResult deleted = service.deleteBook(librarian, createdId); require(deleted.isSuccessful(), "librarian should delete a book"); require(!dao.findById(createdId).isPresent(), "delete should remove the record"); ServiceResult createdCategory = service.createCategory(librarian, category(0L, "Architecture", "Design and systems")); require(createdCategory.isSuccessful(), "librarian should create a category"); long categoryId = createdCategory.getData(); ServiceResult duplicateCategory = service.createCategory(librarian, category(0L, "Architecture", "Duplicate category")); require(!duplicateCategory.isSuccessful(), "duplicate category should fail"); require(duplicateCategory.getErrors().containsKey("name"), "duplicate category should target name field"); ServiceResult updatedCategory = service.updateCategory(librarian, category(categoryId, "Software Architecture", "Updated category")); require(updatedCategory.isSuccessful(), "librarian should update a category"); require("Software Architecture".equals(dao.findCategoryById(categoryId).get().getName()), "category update should persist the new name"); ServiceResult deletedCategory = service.deleteCategory(librarian, categoryId); require(deletedCategory.isSuccessful(), "unused category should be deleted"); require(!dao.findCategoryById(categoryId).isPresent(), "category delete should remove unused category"); BookService failingService = new BookServiceImpl(new FailingBookDao()); ServiceResult> unavailable = failingService.searchBooks(new BookSearchCriteria()); require(!unavailable.isSuccessful(), "DAO failure should not escape service"); require(UNAVAILABLE_MESSAGE.equals(unavailable.getMessage()), "DAO failure should map to safe message"); } private static AuthenticatedUser user(long id, Role role) { return new AuthenticatedUser(id, role.getCode(), role.getDisplayName(), role, role == Role.READER ? EnumSet.of(Permission.VIEW_CATALOG, Permission.BORROW_BOOKS) : EnumSet.of(Permission.MANAGE_BOOKS, Permission.VIEW_CATALOG)); } private static Book book(long id, String identifier, String title, String author, long categoryId, int totalCopies, int availableCopies, BookStatus status) { Book book = new Book(); book.setId(id); book.setIdentifier(identifier); book.setTitle(title); book.setAuthor(author); book.setCategoryId(categoryId); book.setTotalCopies(totalCopies); book.setAvailableCopies(availableCopies); book.setStatus(status); return book; } private static BookCategory category(long id, String name, String description) { BookCategory category = new BookCategory(); category.setId(id); category.setName(name); category.setDescription(description); return category; } private static void require(boolean condition, String message) { if (!condition) { throw new AssertionError(message); } } private static final class InMemoryBookDao implements BookDao { private final Map books = new LinkedHashMap<>(); private final Map categories = new LinkedHashMap<>(); private long nextId = 1L; private long nextCategoryId = 2L; private InMemoryBookDao() { categories.put(1L, category(1L, "Computer Science", "Programming books")); } @Override public List findAllCategories() { List results = new ArrayList<>(); for (BookCategory category : categories.values()) { results.add(copy(category)); } return results; } @Override public Optional findCategoryById(long id) { return Optional.ofNullable(categories.get(id)).map(this::copy); } @Override public Optional findCategoryByName(String name) { for (BookCategory category : categories.values()) { if (category.getName().equals(name)) { return Optional.of(copy(category)); } } return Optional.empty(); } @Override public long createCategory(BookCategory category) { long id = nextCategoryId++; BookCategory stored = copy(category); stored.setId(id); categories.put(id, stored); return id; } @Override public boolean updateCategory(BookCategory category) { if (!categories.containsKey(category.getId())) { return false; } categories.put(category.getId(), copy(category)); return true; } @Override public boolean deleteCategory(long id) { return categories.remove(id) != null; } @Override public int countBooksByCategoryId(long categoryId) { int count = 0; for (Book book : books.values()) { if (book.getCategoryId() == categoryId) { count++; } } return count; } @Override public List search(BookSearchCriteria criteria) { List matches = new ArrayList<>(); for (Book book : books.values()) { if (matches(criteria.getIdentifier(), book.getIdentifier()) && matches(criteria.getTitle(), book.getTitle()) && matches(criteria.getAuthor(), book.getAuthor()) && (criteria.getCategoryId() == null || criteria.getCategoryId() == book.getCategoryId())) { matches.add(copy(book)); } } return matches; } @Override public Optional findById(long id) { return Optional.ofNullable(books.get(id)).map(this::copy); } @Override public Optional findByIdentifier(String identifier) { for (Book book : books.values()) { if (book.getIdentifier().equals(identifier)) { return Optional.of(copy(book)); } } return Optional.empty(); } @Override public long create(Book book) { long id = nextId++; Book stored = copy(book); stored.setId(id); books.put(id, stored); return id; } @Override public boolean update(Book book) { if (!books.containsKey(book.getId())) { return false; } books.put(book.getId(), copy(book)); return true; } @Override public boolean delete(long id) { return books.remove(id) != null; } private boolean matches(String filter, String value) { return filter == null || filter.isEmpty() || value.contains(filter); } private Book copy(Book source) { Book copy = book(source.getId(), source.getIdentifier(), source.getTitle(), source.getAuthor(), source.getCategoryId(), source.getTotalCopies(), source.getAvailableCopies(), source.getStatus()); copy.setCategoryName(source.getCategoryName()); return copy; } private BookCategory copy(BookCategory source) { return category(source.getId(), source.getName(), source.getDescription()); } } private static final class FailingBookDao implements BookDao { @Override public List findAllCategories() { throw new DaoException("Simulated category failure", null); } @Override public Optional findCategoryById(long id) { throw new DaoException("Simulated category find failure", null); } @Override public Optional findCategoryByName(String name) { throw new DaoException("Simulated category find failure", null); } @Override public long createCategory(BookCategory category) { throw new DaoException("Simulated category create failure", null); } @Override public boolean updateCategory(BookCategory category) { throw new DaoException("Simulated category update failure", null); } @Override public boolean deleteCategory(long id) { throw new DaoException("Simulated category delete failure", null); } @Override public int countBooksByCategoryId(long categoryId) { throw new DaoException("Simulated category count failure", null); } @Override public List search(BookSearchCriteria criteria) { throw new DaoException("Simulated search failure", null); } @Override public Optional findById(long id) { throw new DaoException("Simulated find failure", null); } @Override public Optional findByIdentifier(String identifier) { throw new DaoException("Simulated find failure", null); } @Override public long create(Book book) { throw new DaoException("Simulated create failure", null); } @Override public boolean update(Book book) { throw new DaoException("Simulated update failure", null); } @Override public boolean delete(long id) { throw new DaoException("Simulated delete failure", null); } } }