Refactor project structure and enhance functionality

- Renamed XML mapper files for consistency.
- Added new fields `is_admin` and `is_banned` to the `Reader` entity and updated the database schema.
- Introduced `AuthCheck` annotation for method-level authorization.
- Implemented JWT utility for token generation and validation.
- Enhanced service and controller layers for `Book`, `Reader`, and `Orders` with new methods and improved error handling.
- Added global exception handling for better API response management.
- Updated `pom.xml` to include new dependencies for JWT and Spring Security.
This commit is contained in:
grtsinry43 2025-05-19 19:05:43 +08:00
parent 81e6212acd
commit 0f5defb2d0
Signed by: grtsinry43
GPG Key ID: F3305FB3A978C934
44 changed files with 3395 additions and 48 deletions

File diff suppressed because it is too large Load Diff

19
pom.xml
View File

@ -63,11 +63,11 @@
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter --> <!-- https://mvnrepository.com/artifact/com.github.xingfudeshi/knife4j-openapi3-jakarta-spring-boot-starter -->
<dependency> <dependency>
<groupId>com.github.xiaoymin</groupId> <groupId>com.github.xingfudeshi</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version> <version>4.6.0</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/io.swagger/swagger-annotations --> <!-- https://mvnrepository.com/artifact/io.swagger/swagger-annotations -->
@ -88,6 +88,19 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.5.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>

View File

@ -0,0 +1,17 @@
package com.grtsinry43.bookmanagement.annotation;
import com.grtsinry43.bookmanagement.common.UserRole;
import java.lang.annotation.*;
/**
* @author grtsinry43
* @date 2024/9/8 14:41
* @description 少年负壮气奋烈自有时
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthCheck {
UserRole requiredRole() default UserRole.NOT_LOGIN;
}

View File

@ -0,0 +1,114 @@
package com.grtsinry43.bookmanagement.aop;
import com.grtsinry43.bookmanagement.annotation.AuthCheck;
import com.grtsinry43.bookmanagement.common.BusinessException;
import com.grtsinry43.bookmanagement.common.ErrorCode;
import com.grtsinry43.bookmanagement.common.UserRole;
import com.grtsinry43.bookmanagement.entity.Reader;
import com.grtsinry43.bookmanagement.service.ReaderService;
import com.grtsinry43.bookmanagement.util.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
/**
* @author grtsinry43
* @date 2024/9/8 14:48
* @description 少年负壮气奋烈自有时
*/
@Component
public class AuthInterceptor implements HandlerInterceptor {
private final ReaderService readerService;
public AuthInterceptor(ReaderService readerService) {
this.readerService = readerService;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod method = (HandlerMethod) handler;
AuthCheck authCheck = method.getMethodAnnotation(AuthCheck.class);
if (authCheck != null) {
// 根据传入的注解获取所需的权限
UserRole requiredRole = authCheck.requiredRole();
String token = request.getHeader("Authorization");
if (requiredRole == UserRole.NOT_LOGIN) {
// 未登录可以访问无需登录的接口
return true;
}
if (token == null || token.isEmpty() || !token.startsWith("Bearer ")) {
// 未登录访问需要登录的接口返回未授权
System.out.println("Token missing or invalid format for: " + request.getRequestURI());
throw new BusinessException(ErrorCode.NOT_LOGIN);
}
token = token.substring(7);
// 先判断是否过期catch 异常说明 token 无效
try {
if (JwtUtil.isTokenExpired(token)) {
throw new BusinessException(ErrorCode.NOT_LOGIN);
}
} catch (Exception e) {
throw new BusinessException(ErrorCode.NOT_LOGIN);
}
String userIdStr = JwtUtil.getUserFromToken(token);
if (userIdStr == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN);
}
Integer userId;
try {
userId = Integer.parseInt(userIdStr);
} catch (NumberFormatException e) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
System.out.println("User ID: " + userId + " accessing: " + request.getRequestURI());
Reader user = readerService.getReaderById(userId);
if (user == null) {
// This case might mean the user was deleted after token issuance
throw new BusinessException(ErrorCode.NOT_FOUND);
}
// Check if user is banned first, unless they are an admin trying to access an admin-only route.
// An admin should be able to access admin routes even if banned, to potentially unban themselves or others.
// However, a banned admin should not access general user routes.
if (user.getIsBanned() != null && user.getIsBanned()) {
// If user is banned, they cannot access any route unless it's an admin route and they are an admin.
if (!(user.getIsAdmin() != null && user.getIsAdmin() && requiredRole == UserRole.ADMIN)) {
System.out.println("Banned user ID: " + userId + " attempted to access: " + request.getRequestURI());
throw new BusinessException(ErrorCode.UNAUTHORIZED);
}
}
request.setAttribute("userId", userId);
if (user.getIsAdmin() != null && user.getIsAdmin()) {
// 管理员可以访问所有接口 (already set attribute, so just return true)
System.out.println("Admin user ID: " + userId + " accessed: " + request.getRequestURI());
return true;
}
// At this point, user is not an admin.
if (requiredRole == UserRole.ADMIN) {
// 非管理员访问管理员接口返回无权限
System.out.println("Non-admin user ID: " + userId + " attempted to access admin route: " + request.getRequestURI());
throw new BusinessException(ErrorCode.UNAUTHORIZED);
}
// For regular users, already checked for ban status above.
// If not banned and not admin, and route doesn't require admin, access is granted.
return true;
}
}
return true;
}
}

View File

@ -0,0 +1,50 @@
package com.grtsinry43.bookmanagement.common;
import lombok.Data;
import java.io.Serializable;
/**
* @Author grtsinry43
* @Date 2024/7/13 上午 12:27
* 这里规定返回消息格式所有的正确响应 HTTP 状态码都是 200只会根据 code 字段和 msg 来提供错误信息
*/
@Data
public class ApiResponse<T> implements Serializable {
private Integer code;
private String msg = "";
private T data;
/**
* 含有数据的成功响应
*
* @param data 返回的数据
* @param <T> 数据类型
*/
public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(0);
response.setData(data);
return response;
}
/**
* 不含数据的成功响应
*/
public static <T> ApiResponse<T> success() {
return success(null);
}
/**
* 失败响应包含错误码和错误信息
*
* @param code 错误码
* @param msg 错误信息
*/
public static <T> ApiResponse<T> error(Integer code, String msg) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMsg(msg);
return response;
}
}

View File

@ -0,0 +1,19 @@
package com.grtsinry43.bookmanagement.common;
import lombok.Getter;
/**
* @author grtsinry43
* @date 2024/9/8 14:55
* @description 少年负壮气奋烈自有时
*/
@Getter
public class BusinessException extends RuntimeException {
private ErrorCode errorCode;
private String message;
public BusinessException(ErrorCode errorCode) {
this.errorCode = errorCode;
this.message = errorCode.getMessage();
}
}

View File

@ -0,0 +1,33 @@
package com.grtsinry43.bookmanagement.common;
import lombok.Getter;
/**
* @author grtsinry43
* @date 2024/9/1 15:44
* @description 少年负壮气奋烈自有时
*/
@Getter
public enum ErrorCode {
PARAMS_ERROR(400, "参数错误"),
NOT_LOGIN(401, "未登录或登录已过期"),
INVALID_DATA(402, "无效数据"),
UNAUTHORIZED(403, "你没有访问该资源的权限"),
BANNED(40301, "你没有访问该资源的权限"),
NOT_FOUND(404, "请求的资源不存在"),
METHOD_NOT_ALLOWED(405, "请求方法不支持"),
INTERNAL_SERVER_ERROR(500, "服务器内部错误");
private final int code;
private final String msg;
ErrorCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public String getMessage() {
return msg;
}
}

View File

@ -0,0 +1,54 @@
package com.grtsinry43.bookmanagement.common;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import lombok.Getter;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 用户角色枚举
*
* @author grtsinry43
* @date 2024/9/1 16:14
*/
@Getter
public enum UserRole {
NOT_LOGIN("未登录", "not_login"),
USER("默认角色", "user"),
ADMIN("管理员角色", "admin"),
BAN("被封禁用户", "ban");
private final String text;
private final String value;
UserRole(String text, String value) {
this.text = text;
this.value = value;
}
/**
* 获取值列表
*/
public static List<String> getValues() {
return Arrays.stream(values()).map(item -> item.value).collect(Collectors.toList());
}
/**
* 根据 value 获取枚举
*/
public static UserRole getEnumByValue(String value) {
if (ObjectUtils.isEmpty(value)) {
return null;
}
for (UserRole anEnum : UserRole.values()) {
if (anEnum.value.equals(value)) {
return anEnum;
}
}
return null;
}
}

View File

@ -1,7 +1,16 @@
package com.grtsinry43.bookmanagement.controller; package com.grtsinry43.bookmanagement.controller;
import org.springframework.web.bind.annotation.RequestMapping; import com.grtsinry43.bookmanagement.annotation.AuthCheck;
import org.springframework.web.bind.annotation.RestController; import com.grtsinry43.bookmanagement.common.ApiResponse;
import com.grtsinry43.bookmanagement.common.BusinessException;
import com.grtsinry43.bookmanagement.common.ErrorCode;
import com.grtsinry43.bookmanagement.common.UserRole;
import com.grtsinry43.bookmanagement.entity.Book;
import com.grtsinry43.bookmanagement.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/** /**
* <p> * <p>
@ -15,4 +24,69 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/book") @RequestMapping("/book")
public class BookController { public class BookController {
@Autowired
private BookService bookService;
@GetMapping("/all")
public ApiResponse<List<Book>> getAllBooks() {
List<Book> books = bookService.getAllBooks();
return ApiResponse.success(books);
}
@GetMapping("/{id}")
public ApiResponse<Book> getBookById(@PathVariable Integer id) {
Book book = bookService.getBookById(id);
if (book != null) {
return ApiResponse.success(book);
} else {
throw new BusinessException(ErrorCode.NOT_FOUND);
}
}
@GetMapping("/search/title")
@AuthCheck(requiredRole = UserRole.USER)
public ApiResponse<List<Book>> searchBooksByTitle(@RequestParam String title) {
List<Book> books = bookService.getBooksByTitle(title);
return ApiResponse.success(books);
}
@GetMapping("/search/author")
@AuthCheck(requiredRole = UserRole.USER)
public ApiResponse<List<Book>> searchBooksByAuthor(@RequestParam String authorName) {
List<Book> books = bookService.getBooksByAuthor(authorName);
return ApiResponse.success(books);
}
@GetMapping("/search/publisher")
@AuthCheck(requiredRole = UserRole.USER)
public ApiResponse<List<Book>> searchBooksByPublisher(@RequestParam String publisherName) {
List<Book> books = bookService.getBooksByPublisher(publisherName);
return ApiResponse.success(books);
}
@PostMapping("/admin/add")
@AuthCheck(requiredRole = UserRole.ADMIN)
public ApiResponse<Book> addBook(@RequestBody Book book) {
bookService.addBook(book);
return ApiResponse.success(book);
}
@PutMapping("/admin/update")
@AuthCheck(requiredRole = UserRole.ADMIN)
public ApiResponse<Book> updateBook(@RequestBody Book book) {
bookService.updateBook(book);
return ApiResponse.success(book);
}
@DeleteMapping("/admin/delete/{id}")
@AuthCheck(requiredRole = UserRole.ADMIN)
public ApiResponse<Void> deleteBook(@PathVariable Integer id) {
bookService.deleteBook(id);
return ApiResponse.success(null);
}
} }

View File

@ -0,0 +1,71 @@
package com.grtsinry43.bookmanagement.controller;
import com.grtsinry43.bookmanagement.common.ApiResponse;
import com.grtsinry43.bookmanagement.common.BusinessException;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
/**
* @author grtsinry43
* @date 2024/7/13 上午12:29
* @description 规定触发异常时的返回格式依然是HTTP状态码200只会根据code字段和msg来提供错误信息
*/
@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理缺少请求参数异常
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<ApiResponse<Object>> handleMissingServletRequestParameterException(Exception ex) {
String errorMessage = "缺少请求参数:" + ex.getMessage();
ApiResponse<Object> apiResponse = ApiResponse.error(400, errorMessage);
return new ResponseEntity<>(apiResponse, HttpStatus.OK);
}
/**
* 处理参数校验异常注释声明即可
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiResponse<Object>> handleConstraintViolationException(ConstraintViolationException ex) {
String errorMessage = ex.getMessage();
ApiResponse<Object> apiResponse = ApiResponse.error(400, errorMessage);
return new ResponseEntity<>(apiResponse, HttpStatus.OK);
}
/**
* 处理参数校验异常注释声明即可
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Object>> handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
ApiResponse<Object> apiResponse = ApiResponse.error(400, "参数传递有误或者缺少参数");
return new ResponseEntity<>(apiResponse, HttpStatus.OK);
}
/**
* 处理业务逻辑异常注释声明即可
*/
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ApiResponse<Object>> handleBusinessException(BusinessException ex) {
String errorMessage = ex.getMessage();
ApiResponse<Object> apiResponse = ApiResponse.error(ex.getErrorCode().getCode(), errorMessage);
return new ResponseEntity<>(apiResponse, HttpStatus.OK);
}
/**
* 处理其他异常注释声明即可
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Object>> handleException(Exception ex) {
String errorMessage = ex.getMessage();
log.error("系统异常: {}", errorMessage, ex); // 使用日志框架记录异常包含完整堆栈
ApiResponse<Object> apiResponse = ApiResponse.error(500, "服务器发生内部错误,您可以尝试刷新,如果问题依旧,请联系我们");
return new ResponseEntity<>(apiResponse, HttpStatus.OK);
}
}

View File

@ -1,11 +1,24 @@
package com.grtsinry43.bookmanagement.controller; package com.grtsinry43.bookmanagement.controller;
import org.springframework.web.bind.annotation.RequestMapping; import com.grtsinry43.bookmanagement.annotation.AuthCheck;
import org.springframework.web.bind.annotation.RestController; import com.grtsinry43.bookmanagement.common.ApiResponse;
import com.grtsinry43.bookmanagement.common.BusinessException;
import com.grtsinry43.bookmanagement.common.ErrorCode;
import com.grtsinry43.bookmanagement.common.UserRole;
import com.grtsinry43.bookmanagement.dto.CreateOrderRequest;
import com.grtsinry43.bookmanagement.entity.OrderItem;
import com.grtsinry43.bookmanagement.entity.Orders;
import com.grtsinry43.bookmanagement.service.OrderItemService; // Keep for future use if OrderItem specific endpoints are added
import com.grtsinry43.bookmanagement.service.OrdersService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/** /**
* <p> * <p>
* 前端控制器 * 订单前端控制器
* </p> * </p>
* *
* @author grtsinry43 * @author grtsinry43
@ -15,4 +28,121 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/orders") @RequestMapping("/orders")
public class OrdersController { public class OrdersController {
@Autowired
private OrdersService ordersService;
// @Autowired
// private OrderItemService orderItemService; // 如果需要单独操作订单项则保留
/**
* 顾客创建新订单
* @param createOrderRequest 包含订单信息和订单项列表的请求体
* @param request HTTP请求对象用于获取用户ID
* @return 包含创建的订单信息的API响应
*/
@PostMapping("/create")
@AuthCheck(requiredRole = UserRole.USER) // 需要用户登录才能创建订单
public ApiResponse<Orders> createOrder(@RequestBody CreateOrderRequest createOrderRequest, HttpServletRequest request) {
// 从HttpServletRequest中获取用户ID
Object userIdObject = request.getAttribute("userId");
if (userIdObject == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN);
}
Integer userId = (Integer) userIdObject;
// 基本验证订单项不能为空
if (createOrderRequest.getItems() == null || createOrderRequest.getItems().isEmpty()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Orders orderToCreate = createOrderRequest.getOrder();
if (orderToCreate == null) {
// 如果请求中没有order部分或者需要初始化一个
orderToCreate = new Orders();
}
// 设置订单的读者ID为当前登录用户的ID
orderToCreate.setReaderId(userId);
// 调用服务创建订单
// 服务层中的 createOrder 方法应该处理库存检查等业务逻辑并在发生错误时抛出 BusinessException
Orders createdOrder = ordersService.createOrder(orderToCreate, createOrderRequest.getItems());
return ApiResponse.success(createdOrder);
}
/**
* 顾客查看自己的订单列表
* @param request HTTP请求对象用于获取用户ID
* @return 包含用户订单列表的API响应
*/
@GetMapping("/my-orders") // 移除了{readerId}路径参数
@AuthCheck(requiredRole = UserRole.USER) // 需要用户登录才能查看订单
public ApiResponse<List<Orders>> getMyOrders(HttpServletRequest request) {
// 从HttpServletRequest中获取用户ID
Object userIdObject = request.getAttribute("userId");
if (userIdObject == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN);
}
Integer userId = (Integer) userIdObject;
List<Orders> orders = ordersService.getOrdersByReaderId(userId);
return ApiResponse.success(orders);
}
/**
* 顾客和管理员查看特定订单详情
* (对于顾客应在服务层或此处增加逻辑校验订单是否属于该顾客除非拦截器已处理)
* @param orderId 订单ID
* @param request HTTP请求对象 (如果需要根据用户角色或ID进行权限校验)
* @return 包含订单详情的API响应
*/
@GetMapping("/{orderId}")
@AuthCheck(requiredRole = UserRole.USER) // 需要用户登录才能查看订单详情
public ApiResponse<Orders> getOrderDetails(@PathVariable Integer orderId, HttpServletRequest request) {
// 示例如果需要基于用户ID的权限检查可以从request中获取userId
Object userIdObject = request.getAttribute("userId");
Integer currentUserId = (userIdObject != null) ? (Integer) userIdObject : null;
// TODO: 根据业务需求添加权限校验逻辑例如检查订单是否属于当前用户如果非管理员
Orders order = ordersService.getOrderById(orderId);
if (order == null) {
throw new BusinessException(ErrorCode.NOT_FOUND);
}
if (order.getReaderId() != null && !order.getReaderId().equals(currentUserId)) {
// 如果订单属于其他用户且当前用户不是管理员则抛出权限异常
throw new BusinessException(ErrorCode.UNAUTHORIZED);
}
// 考虑如果用户不是管理员且订单不属于该用户也应抛出权限异常
return ApiResponse.success(order);
}
/**
* 管理员查看所有订单列表
* (假设已有管理员权限拦截器)
* @return 包含所有订单列表的API响应
*/
@GetMapping("/admin/all")
@AuthCheck(requiredRole = UserRole.ADMIN) // 需要管理员权限才能查看所有订单
public ApiResponse<List<Orders>> getAllOrders() {
List<Orders> orders = ordersService.getAllOrders();
return ApiResponse.success(orders);
}
/**
* 管理员更新订单状态
* (假设已有管理员权限拦截器)
* @param orderId 订单ID
* @param status 新的订单状态
* @return 操作结果的API响应
*/
@PutMapping("/admin/update-status/{orderId}")
@AuthCheck(requiredRole = UserRole.ADMIN) // 需要管理员权限才能更新订单状态
public ApiResponse<String> updateOrderStatus(@PathVariable Integer orderId, @RequestParam String status) {
// TODO: 可以增加对status参数有效性的校验
if (status == null || status.trim().isEmpty()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
ordersService.updateOrderStatus(orderId, status);
// 更新成功通常返回成功的消息data部分可以为null或包含更新后的对象如果服务层返回
return ApiResponse.success(null);
}
} }

View File

@ -1,7 +1,24 @@
package com.grtsinry43.bookmanagement.controller; package com.grtsinry43.bookmanagement.controller;
import org.springframework.web.bind.annotation.RequestMapping; import com.grtsinry43.bookmanagement.annotation.AuthCheck;
import org.springframework.web.bind.annotation.RestController; import com.grtsinry43.bookmanagement.common.ApiResponse;
import com.grtsinry43.bookmanagement.common.BusinessException;
import com.grtsinry43.bookmanagement.common.ErrorCode;
import com.grtsinry43.bookmanagement.common.UserRole;
import com.grtsinry43.bookmanagement.dto.LoginRequest;
import com.grtsinry43.bookmanagement.dto.RegisterRequest;
import com.grtsinry43.bookmanagement.entity.Reader;
import com.grtsinry43.bookmanagement.service.ReaderService;
import com.grtsinry43.bookmanagement.util.JwtUtil;
import com.grtsinry43.bookmanagement.vo.ReaderVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/** /**
* <p> * <p>
@ -15,4 +32,102 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/reader") @RequestMapping("/reader")
public class ReaderController { public class ReaderController {
private final ReaderService readerService;
@Autowired
public ReaderController(ReaderService readerService) {
this.readerService = readerService;
}
private ReaderVO convertToReaderVO(Reader reader) {
if (reader == null) {
return null;
}
ReaderVO readerVO = new ReaderVO();
BeanUtils.copyProperties(reader, readerVO);
return readerVO;
}
@PostMapping("/register")
@AuthCheck(requiredRole = UserRole.NOT_LOGIN)
public ApiResponse<ReaderVO> registerReader(@RequestBody RegisterRequest registerRequest) {
if (registerRequest.getUsername() == null || registerRequest.getPassword() == null ||
registerRequest.getUsername().trim().isEmpty() || registerRequest.getPassword().isEmpty()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Reader reader = new Reader();
BeanUtils.copyProperties(registerRequest, reader);
readerService.registerReader(reader);
return ApiResponse.success(convertToReaderVO(reader));
}
@PostMapping("/login")
@AuthCheck(requiredRole = UserRole.NOT_LOGIN)
public ApiResponse<Map<String, Object>> login(@RequestBody LoginRequest loginRequest) {
if (loginRequest.getUsername() == null || loginRequest.getPassword() == null ||
loginRequest.getUsername().trim().isEmpty() || loginRequest.getPassword().isEmpty()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Reader loggedInReader = readerService.login(loginRequest.getUsername(), loginRequest.getPassword());
String token = JwtUtil.generateToken(loggedInReader.getReaderId().toString());
Map<String, Object> responseData = new HashMap<>();
responseData.put("token", token);
responseData.put("user", convertToReaderVO(loggedInReader));
return ApiResponse.success(responseData);
}
@GetMapping("/admin/all")
@AuthCheck(requiredRole = UserRole.ADMIN)
public ApiResponse<List<ReaderVO>> getAllReaders() {
List<Reader> readers = readerService.getAllReaders();
List<ReaderVO> readerVOs = readers.stream()
.map(this::convertToReaderVO)
.collect(Collectors.toList());
return ApiResponse.success(readerVOs);
}
@GetMapping("/{id}")
@AuthCheck(requiredRole = UserRole.USER)
public ApiResponse<ReaderVO> getReaderById(@PathVariable Integer id) {
Reader reader = readerService.getReaderById(id);
if (reader == null) {
throw new BusinessException(ErrorCode.NOT_FOUND);
}
return ApiResponse.success(convertToReaderVO(reader));
}
@PutMapping("/admin/{id}/make-admin")
@AuthCheck(requiredRole = UserRole.ADMIN)
public ApiResponse<ReaderVO> makeAdmin(@PathVariable Integer id) {
readerService.updateUserAdminStatus(id, true);
Reader updatedReader = readerService.getReaderById(id);
return ApiResponse.success(convertToReaderVO(updatedReader));
}
@PutMapping("/admin/{id}/remove-admin")
@AuthCheck(requiredRole = UserRole.ADMIN)
public ApiResponse<ReaderVO> removeAdmin(@PathVariable Integer id) {
readerService.updateUserAdminStatus(id, false);
Reader updatedReader = readerService.getReaderById(id);
return ApiResponse.success(convertToReaderVO(updatedReader));
}
@PutMapping("/admin/{id}/ban")
@AuthCheck(requiredRole = UserRole.ADMIN)
public ApiResponse<ReaderVO> banUser(@PathVariable Integer id) {
readerService.updateUserBanStatus(id, true);
Reader updatedReader = readerService.getReaderById(id);
return ApiResponse.success(convertToReaderVO(updatedReader));
}
@PutMapping("/admin/{id}/unban")
@AuthCheck(requiredRole = UserRole.ADMIN)
public ApiResponse<ReaderVO> unbanUser(@PathVariable Integer id) {
readerService.updateUserBanStatus(id, false);
Reader updatedReader = readerService.getReaderById(id);
return ApiResponse.success(convertToReaderVO(updatedReader));
}
} }

View File

@ -0,0 +1,20 @@
package com.grtsinry43.bookmanagement.dto;
import com.grtsinry43.bookmanagement.entity.OrderItem;
import com.grtsinry43.bookmanagement.entity.Orders;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 创建订单请求体
*/
@Data
public class CreateOrderRequest implements Serializable {
private Orders order;
private List<OrderItem> items;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,29 @@
package com.grtsinry43.bookmanagement.dto;
import java.io.Serializable;
/**
* 登录请求体
*/
public class LoginRequest implements Serializable {
private String username;
private String password;
private static final long serialVersionUID = 1L;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,18 @@
package com.grtsinry43.bookmanagement.dto;
import lombok.Data;
import java.io.Serializable;
/**
* 注册请求体
*/
@Data
public class RegisterRequest implements Serializable {
private String username;
private String password;
private String email;
private String phone;
private static final long serialVersionUID = 1L;
}

View File

@ -21,19 +21,30 @@ import java.io.Serializable;
@Getter @Getter
@Setter @Setter
@ToString @ToString
@ApiModel(value = "Reader对象", description = "") @ApiModel(value = "Reader对象", description = "读者实体类")
public class Reader implements Serializable { public class Reader implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ApiModelProperty("读者ID")
@TableId(value = "reader_id", type = IdType.AUTO) @TableId(value = "reader_id", type = IdType.AUTO)
private Integer readerId; private Integer readerId;
@ApiModelProperty("用户名")
private String username; private String username;
@ApiModelProperty("密码")
private String password; private String password;
@ApiModelProperty("邮箱")
private String email; private String email;
@ApiModelProperty("电话")
private String phone; private String phone;
@ApiModelProperty("是否为管理员")
private Boolean isAdmin = false;
@ApiModelProperty("是否被封禁")
private Boolean isBanned = false;
} }

View File

@ -2,6 +2,7 @@ package com.grtsinry43.bookmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grtsinry43.bookmanagement.entity.Author; import com.grtsinry43.bookmanagement.entity.Author;
import org.apache.ibatis.annotations.Mapper;
/** /**
* <p> * <p>
@ -11,6 +12,7 @@ import com.grtsinry43.bookmanagement.entity.Author;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
@Mapper
public interface AuthorMapper extends BaseMapper<Author> { public interface AuthorMapper extends BaseMapper<Author> {
} }

View File

@ -2,6 +2,7 @@ package com.grtsinry43.bookmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grtsinry43.bookmanagement.entity.BookAuthor; import com.grtsinry43.bookmanagement.entity.BookAuthor;
import org.apache.ibatis.annotations.Mapper;
/** /**
* <p> * <p>
@ -11,6 +12,7 @@ import com.grtsinry43.bookmanagement.entity.BookAuthor;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
@Mapper
public interface BookAuthorMapper extends BaseMapper<BookAuthor> { public interface BookAuthorMapper extends BaseMapper<BookAuthor> {
} }

View File

@ -2,6 +2,10 @@ package com.grtsinry43.bookmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grtsinry43.bookmanagement.entity.Book; import com.grtsinry43.bookmanagement.entity.Book;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/** /**
* <p> * <p>
@ -11,6 +15,21 @@ import com.grtsinry43.bookmanagement.entity.Book;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
@Mapper
public interface BookMapper extends BaseMapper<Book> { public interface BookMapper extends BaseMapper<Book> {
Book findById(@Param("bookId") Integer bookId);
List<Book> findAll();
List<Book> findByTitle(@Param("title") String title);
List<Book> findByAuthor(@Param("authorName") String authorName);
List<Book> findByPublisher(@Param("publisherName") String publisherName);
int insert(Book book);
int update(Book book);
int delete(@Param("bookId") Integer bookId);
} }

View File

@ -2,6 +2,10 @@ package com.grtsinry43.bookmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grtsinry43.bookmanagement.entity.OrderItem; import com.grtsinry43.bookmanagement.entity.OrderItem;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/** /**
* <p> * <p>
@ -11,6 +15,12 @@ import com.grtsinry43.bookmanagement.entity.OrderItem;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
@Mapper
public interface OrderItemMapper extends BaseMapper<OrderItem> { public interface OrderItemMapper extends BaseMapper<OrderItem> {
List<OrderItem> findByOrderId(@Param("orderId") Integer orderId);
int insert(OrderItem orderItem);
// For batch insert if needed
// int insertBatch(@Param("list") List<OrderItem> orderItems);
} }

View File

@ -2,6 +2,10 @@ package com.grtsinry43.bookmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grtsinry43.bookmanagement.entity.Orders; import com.grtsinry43.bookmanagement.entity.Orders;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/** /**
* <p> * <p>
@ -11,6 +15,16 @@ import com.grtsinry43.bookmanagement.entity.Orders;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
@Mapper
public interface OrdersMapper extends BaseMapper<Orders> { public interface OrdersMapper extends BaseMapper<Orders> {
Orders findById(@Param("orderId") Integer orderId);
List<Orders> findAll(); // For admin
List<Orders> findByReaderId(@Param("readerId") Integer readerId);
// Consider adding findByDateRange, findByStatus etc. if needed
int insert(Orders order);
int updateStatus(@Param("orderId") Integer orderId, @Param("status") String status); // Simplified
} }

View File

@ -2,6 +2,7 @@ package com.grtsinry43.bookmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grtsinry43.bookmanagement.entity.Publisher; import com.grtsinry43.bookmanagement.entity.Publisher;
import org.apache.ibatis.annotations.Mapper;
/** /**
* <p> * <p>
@ -11,6 +12,7 @@ import com.grtsinry43.bookmanagement.entity.Publisher;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
@Mapper
public interface PublisherMapper extends BaseMapper<Publisher> { public interface PublisherMapper extends BaseMapper<Publisher> {
} }

View File

@ -2,6 +2,10 @@ package com.grtsinry43.bookmanagement.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.grtsinry43.bookmanagement.entity.Reader; import com.grtsinry43.bookmanagement.entity.Reader;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/** /**
* <p> * <p>
@ -11,6 +15,21 @@ import com.grtsinry43.bookmanagement.entity.Reader;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
public interface ReaderMapper extends BaseMapper<Reader> { @Mapper
public interface ReaderMapper extends BaseMapper<Reader> { // 移除了 BaseMapper<Reader>
Reader findById(@Param("readerId") Integer readerId);
Reader findByUsername(@Param("username") String username);
List<Reader> findAll();
int insert(Reader reader);
int updateReaderAdminStatus(@Param("readerId") Integer readerId, @Param("isAdmin") boolean isAdmin);
int updateReaderBanStatus(@Param("readerId") Integer readerId, @Param("isBanned") boolean isBanned);
// update and delete methods can be added if needed for admin functionality
// 例如: int update(Reader reader); (如果需要更新Reader所有信息)
// 例如: int deleteById(@Param("readerId") Integer readerId);
} }

View File

@ -1,7 +1,7 @@
package com.grtsinry43.bookmanagement.service; package com.grtsinry43.bookmanagement.service;
import com.grtsinry43.bookmanagement.entity.Book; import com.grtsinry43.bookmanagement.entity.Book;
import com.baomidou.mybatisplus.extension.service.IService; import java.util.List;
/** /**
* <p> * <p>
@ -11,6 +11,20 @@ import com.baomidou.mybatisplus.extension.service.IService;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
public interface BookService extends IService<Book> { public interface BookService {
Book getBookById(Integer bookId);
List<Book> getAllBooks();
List<Book> getBooksByTitle(String title);
List<Book> getBooksByAuthor(String authorName);
List<Book> getBooksByPublisher(String publisherName);
void addBook(Book book);
void updateBook(Book book);
void deleteBook(Integer bookId);
} }

View File

@ -1,7 +1,9 @@
package com.grtsinry43.bookmanagement.service; package com.grtsinry43.bookmanagement.service;
import com.grtsinry43.bookmanagement.entity.Orders; import com.grtsinry43.bookmanagement.entity.Orders;
import com.baomidou.mybatisplus.extension.service.IService; import com.grtsinry43.bookmanagement.entity.OrderItem;
import java.util.List;
/** /**
* <p> * <p>
@ -11,6 +13,14 @@ import com.baomidou.mybatisplus.extension.service.IService;
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
public interface OrdersService extends IService<Orders> { public interface OrdersService {
Orders getOrderById(Integer orderId);
List<Orders> getAllOrders(); // For admin
List<Orders> getOrdersByReaderId(Integer readerId);
Orders createOrder(Orders order, List<OrderItem> items); // Simplified: pass items directly
void updateOrderStatus(Integer orderId, String status); // Simplified
} }

View File

@ -1,16 +1,71 @@
package com.grtsinry43.bookmanagement.service; package com.grtsinry43.bookmanagement.service;
import com.grtsinry43.bookmanagement.entity.Reader; import com.grtsinry43.bookmanagement.entity.Reader;
import com.baomidou.mybatisplus.extension.service.IService; import com.grtsinry43.bookmanagement.vo.ReaderVO; // 导入ReaderVO
import java.util.List;
/** /**
* <p> * <p>
* 服务类 * 读者服务类接口
* </p> * </p>
* *
* @author grtsinry43 * @author grtsinry43
* @since 2025-05-19 * @since 2025-05-19
*/ */
public interface ReaderService extends IService<Reader> { public interface ReaderService {
/**
* 根据ID获取读者信息 (通常返回实体Controller层转换为VO)
* @param readerId 读者ID
* @return 读者实体
*/
Reader getReaderById(Integer readerId);
/**
* 根据用户名获取读者信息 (通常返回实体)
* @param username 用户名
* @return 读者实体
*/
Reader getReaderByUsername(String username);
/**
* 获取所有读者列表 (通常返回实体列表Controller层转换为VO列表)
* @return 读者实体列表
*/
List<Reader> getAllReaders();
/**
* 注册新读者
* @param reader 读者实体 (密码应在服务层处理哈希)
*/
void registerReader(Reader reader);
/**
* 更新用户管理员状态 (管理员操作)
* @param readerId 用户ID
* @param isAdmin 是否为管理员
*/
void updateUserAdminStatus(Integer readerId, boolean isAdmin);
/**
* 更新用户封禁状态 (管理员操作)
* @param readerId 用户ID
* @param isBanned 是否被封禁
*/
void updateUserBanStatus(Integer readerId, boolean isBanned);
/**
* 检查用户是否被封禁 (用于登录或权限校验)
* @param readerId 用户ID
* @return true 如果被封禁, false otherwise
*/
boolean isUserBanned(Integer readerId); // 参数类型改为Integer
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @return 登录成功后的用户实体如果登录失败则抛出 BusinessException
*/
Reader login(String username, String password);
} }

View File

@ -1,11 +1,14 @@
package com.grtsinry43.bookmanagement.service.impl; package com.grtsinry43.bookmanagement.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.grtsinry43.bookmanagement.entity.Book; import com.grtsinry43.bookmanagement.entity.Book;
import com.grtsinry43.bookmanagement.mapper.BookMapper; import com.grtsinry43.bookmanagement.mapper.BookMapper;
import com.grtsinry43.bookmanagement.service.BookService; import com.grtsinry43.bookmanagement.service.BookService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
/** /**
* <p> * <p>
* 服务实现类 * 服务实现类
@ -17,4 +20,46 @@ import org.springframework.stereotype.Service;
@Service @Service
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService { public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements BookService {
@Autowired
private BookMapper bookMapper;
@Override
public Book getBookById(Integer bookId) {
return bookMapper.findById(bookId);
}
@Override
public List<Book> getAllBooks() {
return bookMapper.findAll();
}
@Override
public List<Book> getBooksByTitle(String title) {
return bookMapper.findByTitle(title);
}
@Override
public List<Book> getBooksByAuthor(String authorName) {
return bookMapper.findByAuthor(authorName);
}
@Override
public List<Book> getBooksByPublisher(String publisherName) {
return bookMapper.findByPublisher(publisherName);
}
@Override
public void addBook(Book book) {
bookMapper.insert(book);
}
@Override
public void updateBook(Book book) {
bookMapper.update(book);
}
@Override
public void deleteBook(Integer bookId) {
bookMapper.delete(bookId);
}
} }

View File

@ -1,10 +1,20 @@
package com.grtsinry43.bookmanagement.service.impl; package com.grtsinry43.bookmanagement.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.grtsinry43.bookmanagement.entity.Book;
import com.grtsinry43.bookmanagement.entity.OrderItem;
import com.grtsinry43.bookmanagement.entity.Orders; import com.grtsinry43.bookmanagement.entity.Orders;
import com.grtsinry43.bookmanagement.mapper.BookMapper;
import com.grtsinry43.bookmanagement.mapper.OrderItemMapper;
import com.grtsinry43.bookmanagement.mapper.OrdersMapper; import com.grtsinry43.bookmanagement.mapper.OrdersMapper;
import com.grtsinry43.bookmanagement.service.OrdersService; import com.grtsinry43.bookmanagement.service.OrdersService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
/** /**
* <p> * <p>
@ -17,4 +27,62 @@ import org.springframework.stereotype.Service;
@Service @Service
public class OrdersServiceImpl extends ServiceImpl<OrdersMapper,Orders> implements OrdersService { public class OrdersServiceImpl extends ServiceImpl<OrdersMapper,Orders> implements OrdersService {
@Autowired
private OrdersMapper ordersMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private BookMapper bookMapper; // To update stock and get price
@Override
public Orders getOrderById(Integer orderId) {
return ordersMapper.findById(orderId);
}
@Override
public List<Orders> getAllOrders() {
return ordersMapper.findAll();
}
@Override
public List<Orders> getOrdersByReaderId(Integer readerId) {
return ordersMapper.findByReaderId(readerId);
}
@Override
@Transactional // Ensure atomicity
public Orders createOrder(Orders order, List<OrderItem> items) {
order.setOrderDate(LocalDateTime.now());
order.setStatus("PENDING");
BigDecimal totalAmount = BigDecimal.ZERO;
for (OrderItem item : items) {
Book book = bookMapper.findById(item.getBookId());
if (book == null || book.getStock() < item.getQuantity()) {
throw new RuntimeException("Book not found or insufficient stock for: " + (book != null ? book.getTitle() : item.getBookId()));
}
item.setUnitPrice(book.getPrice());
totalAmount = totalAmount.add(book.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())));
}
order.setTotalAmount(totalAmount);
ordersMapper.insert(order);
for (OrderItem item : items) {
item.setOrderId(order.getOrderId());
orderItemMapper.insert(item);
Book book = bookMapper.findById(item.getBookId());
book.setStock(book.getStock() - item.getQuantity());
bookMapper.update(book);
}
return order;
}
@Override
public void updateOrderStatus(Integer orderId, String status) {
ordersMapper.updateStatus(orderId, status);
}
} }

View File

@ -1,14 +1,20 @@
package com.grtsinry43.bookmanagement.service.impl; package com.grtsinry43.bookmanagement.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.grtsinry43.bookmanagement.common.BusinessException;
import com.grtsinry43.bookmanagement.common.ErrorCode;
import com.grtsinry43.bookmanagement.entity.Reader; import com.grtsinry43.bookmanagement.entity.Reader;
import com.grtsinry43.bookmanagement.mapper.ReaderMapper; import com.grtsinry43.bookmanagement.mapper.ReaderMapper;
import com.grtsinry43.bookmanagement.service.ReaderService; import com.grtsinry43.bookmanagement.service.ReaderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List;
/** /**
* <p> * <p>
* 服务实现类 * 读者服务实现类
* </p> * </p>
* *
* @author grtsinry43 * @author grtsinry43
@ -17,4 +23,93 @@ import org.springframework.stereotype.Service;
@Service @Service
public class ReaderServiceImpl extends ServiceImpl<ReaderMapper, Reader> implements ReaderService { public class ReaderServiceImpl extends ServiceImpl<ReaderMapper, Reader> implements ReaderService {
private final ReaderMapper readerMapper;
private final BCryptPasswordEncoder passwordEncoder;
@Autowired
public ReaderServiceImpl(ReaderMapper readerMapper) {
this.readerMapper = readerMapper;
this.passwordEncoder = new BCryptPasswordEncoder();
}
@Override
public Reader getReaderById(Integer readerId) {
if (readerId == null) {
return null;
}
return readerMapper.findById(readerId);
}
@Override
public Reader getReaderByUsername(String username) {
if (username == null || username.trim().isEmpty()) {
return null;
}
return readerMapper.findByUsername(username);
}
@Override
public List<Reader> getAllReaders() {
return readerMapper.findAll();
}
@Override
public void registerReader(Reader reader) {
if (reader == null || reader.getUsername() == null || reader.getPassword() == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
if (readerMapper.findByUsername(reader.getUsername()) != null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
reader.setPassword(passwordEncoder.encode(reader.getPassword()));
readerMapper.insert(reader);
}
@Override
public void updateUserAdminStatus(Integer readerId, boolean isAdmin) {
Reader reader = readerMapper.findById(readerId);
if (reader == null) {
throw new BusinessException(ErrorCode.NOT_FOUND);
}
readerMapper.updateReaderAdminStatus(readerId, isAdmin);
}
@Override
public void updateUserBanStatus(Integer readerId, boolean isBanned) {
Reader reader = readerMapper.findById(readerId);
if (reader == null) {
throw new BusinessException(ErrorCode.NOT_FOUND);
}
readerMapper.updateReaderBanStatus(readerId, isBanned);
}
@Override
public boolean isUserBanned(Integer readerId) {
if (readerId == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Reader reader = readerMapper.findById(readerId);
if (reader == null) {
throw new BusinessException(ErrorCode.NOT_FOUND);
}
return reader.getIsBanned() != null && reader.getIsBanned();
}
@Override
public Reader login(String username, String password) {
if (username == null || username.trim().isEmpty() || password == null || password.isEmpty()) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
Reader reader = readerMapper.findByUsername(username);
if (reader == null) {
throw new BusinessException(ErrorCode.NOT_FOUND);
}
if (!passwordEncoder.matches(password, reader.getPassword())) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
if (reader.getIsBanned() != null && reader.getIsBanned()) {
throw new BusinessException(ErrorCode.UNAUTHORIZED);
}
return reader;
}
} }

View File

@ -0,0 +1,69 @@
package com.grtsinry43.bookmanagement.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author grtsinry43
* @date 2024/8/11 21:23
* @description 少年负壮气奋烈自有时
*/
@Slf4j
@Component
public class JwtUtil {
private static String SECRET_KEY;
@Value("${com.grtsinry43.secret_key}")
public void setSecretKey(String secretKey) {
SECRET_KEY = secretKey;
}
public static final long EXPIRATION_TIME = 86400000; // 1 day
public static final long REFRESH_EXPIRATION_TIME = 2592000000L; // 30 days
public static String generateToken(String userid) {
log.info(SECRET_KEY);
Algorithm algorithm = Algorithm.HMAC512(SECRET_KEY);
return JWT.create()
.withSubject(userid)
.withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.sign(algorithm);
}
public static String generateRefreshToken(String userid) {
Algorithm algorithm = Algorithm.HMAC512(SECRET_KEY);
return JWT.create()
.withSubject(userid)
.withExpiresAt(new Date(System.currentTimeMillis() + REFRESH_EXPIRATION_TIME))
.sign(algorithm);
}
public static DecodedJWT getDecodedJWT(String token) {
try {
log.info("Decoding token: {}", token);
Algorithm algorithm = Algorithm.HMAC512(SECRET_KEY);
JWTVerifier verifier = JWT.require(algorithm).build();
return verifier.verify(token);
} catch (JWTDecodeException e) {
log.error("Invalid token: {}", token, e);
throw e;
}
}
public static boolean isTokenExpired(String token) {
return getDecodedJWT(token).getExpiresAt().before(new Date());
}
public static String getUserFromToken(String token) {
return getDecodedJWT(token).getSubject();
}
}

View File

@ -0,0 +1,78 @@
package com.grtsinry43.bookmanagement.vo;
import java.io.Serializable;
/**
* 读者视图对象 (安全)
*/
public class ReaderVO implements Serializable {
private Integer readerId;
private String username;
private String email;
private String phone;
private Boolean isAdmin;
private Boolean isBanned; // 添加是否被封禁字段
private static final long serialVersionUID = 1L;
// 构造函数Getters Setters
public ReaderVO() {
}
public ReaderVO(Integer readerId, String username, String email, String phone, Boolean isAdmin, Boolean isBanned) {
this.readerId = readerId;
this.username = username;
this.email = email;
this.phone = phone;
this.isAdmin = isAdmin;
this.isBanned = isBanned;
}
public Integer getReaderId() {
return readerId;
}
public void setReaderId(Integer readerId) {
this.readerId = readerId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Boolean getIsAdmin() {
return isAdmin;
}
public void setIsAdmin(Boolean admin) {
isAdmin = admin;
}
public Boolean getIsBanned() {
return isBanned;
}
public void setIsBanned(Boolean banned) {
isBanned = banned;
}
}

View File

@ -7,3 +7,7 @@ spring.datasource.driver-class-name=org.postgresql.Driver
logging.config=classpath:logback-spring.xml logging.config=classpath:logback-spring.xml
server.shutdown=graceful server.shutdown=graceful
server.port=8080 server.port=8080
mybatis.type-aliases-package=com.grtsinry43.bookmanagement.entity
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.com.grtsinry43.bookmanagement.mapper=DEBUG

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.grtsinry43.bookmanagement.mapper.BookMapper">
<select id="findById" resultType="com.grtsinry43.bookmanagement.entity.Book">
SELECT * FROM book WHERE book_id = #{bookId}
</select>
<select id="findAll" resultType="com.grtsinry43.bookmanagement.entity.Book">
SELECT * FROM book
</select>
<select id="findByTitle" resultType="com.grtsinry43.bookmanagement.entity.Book">
SELECT * FROM book WHERE title LIKE CONCAT('%', #{title}, '%')
</select>
<select id="findByAuthor" resultType="com.grtsinry43.bookmanagement.entity.Book">
SELECT b.* FROM book b
JOIN book_author ba ON b.book_id = ba.book_id
JOIN author a ON ba.author_id = a.author_id
WHERE a.name LIKE CONCAT('%', #{authorName}, '%')
</select>
<select id="findByPublisher" resultType="com.grtsinry43.bookmanagement.entity.Book">
SELECT b.* FROM book b
JOIN publisher p ON b.publisher_id = p.publisher_id
WHERE p.name LIKE CONCAT('%', #{publisherName}, '%')
</select>
<insert id="insert" parameterType="com.grtsinry43.bookmanagement.entity.Book" useGeneratedKeys="true" keyProperty="bookId">
INSERT INTO book (title, isbn, price, stock, publish_date, publisher_id)
VALUES (#{title}, #{isbn}, #{price}, #{stock}, #{publishDate}, #{publisherId})
</insert>
<update id="update" parameterType="com.grtsinry43.bookmanagement.entity.Book">
UPDATE book
SET title = #{title},
isbn = #{isbn},
price = #{price},
stock = #{stock},
publish_date = #{publishDate},
publisher_id = #{publisherId}
WHERE book_id = #{bookId}
</update>
<delete id="delete">
DELETE FROM book WHERE book_id = #{bookId}
</delete>
</mapper>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.grtsinry43.bookmanagement.mapper.OrderItemMapper">
<select id="findByOrderId" resultType="com.grtsinry43.bookmanagement.entity.OrderItem">
SELECT * FROM order_item WHERE order_id = #{orderId}
</select>
<insert id="insert" parameterType="com.grtsinry43.bookmanagement.entity.OrderItem" useGeneratedKeys="true" keyProperty="orderItemId">
INSERT INTO order_item (order_id, book_id, quantity, unit_price)
VALUES (#{orderId}, #{bookId}, #{quantity}, #{unitPrice})
</insert>
<!-- Example for batch insert -->
<!--
<insert id="insertBatch" parameterType="java.util.List">
INSERT INTO order_item (order_id, book_id, quantity, unit_price) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.orderId}, #{item.bookId}, #{item.quantity}, #{item.unitPrice})
</foreach>
</insert>
-->
</mapper>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.grtsinry43.bookmanagement.mapper.OrdersMapper">
<select id="findById" resultType="com.grtsinry43.bookmanagement.entity.Orders">
SELECT * FROM orders WHERE order_id = #{orderId}
</select>
<select id="findAll" resultType="com.grtsinry43.bookmanagement.entity.Orders">
SELECT * FROM orders ORDER BY order_date DESC
</select>
<select id="findByReaderId" resultType="com.grtsinry43.bookmanagement.entity.Orders">
SELECT * FROM orders WHERE reader_id = #{readerId} ORDER BY order_date DESC
</select>
<insert id="insert" parameterType="com.grtsinry43.bookmanagement.entity.Orders" useGeneratedKeys="true" keyProperty="orderId">
INSERT INTO orders (reader_id, order_date, total_amount, status)
VALUES (#{readerId}, #{orderDate}, #{totalAmount}, #{status})
</insert>
<update id="updateStatus">
UPDATE orders SET status = #{status} WHERE order_id = #{orderId}
</update>
</mapper>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.grtsinry43.bookmanagement.mapper.ReaderMapper">
<select id="findById" resultType="com.grtsinry43.bookmanagement.entity.Reader">
SELECT * FROM reader WHERE reader_id = #{readerId}
</select>
<select id="findByUsername" resultType="com.grtsinry43.bookmanagement.entity.Reader">
SELECT * FROM reader WHERE username = #{username}
</select>
<select id="findAll" resultType="com.grtsinry43.bookmanagement.entity.Reader">
SELECT * FROM reader
</select>
<insert id="insert" parameterType="com.grtsinry43.bookmanagement.entity.Reader" useGeneratedKeys="true" keyProperty="readerId">
INSERT INTO reader (username, password, email, phone, is_admin, is_banned)
VALUES (#{username}, #{password}, #{email}, #{phone}, #{isAdmin,jdbcType=BOOLEAN}, #{isBanned,jdbcType=BOOLEAN})
</insert>
<update id="updateReaderAdminStatus">
UPDATE reader SET is_admin = #{isAdmin} WHERE reader_id = #{readerId}
</update>
<update id="updateReaderBanStatus">
UPDATE reader SET is_banned = #{isBanned} WHERE reader_id = #{readerId}
</update>
</mapper>

View File

@ -47,6 +47,8 @@ CREATE TABLE reader
username VARCHAR(50) NOT NULL UNIQUE, username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL, password VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE,
is_admin BOOLEAN NOT NULL DEFAULT FALSE,
is_banned BOOLEAN NOT NULL DEFAULT FALSE,
phone VARCHAR(20) phone VARCHAR(20)
); );

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.grtsinry43.bookmanagement.mapper.BookMapper">
</mapper>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.grtsinry43.bookmanagement.mapper.OrderItemMapper">
</mapper>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.grtsinry43.bookmanagement.mapper.OrdersMapper">
</mapper>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.grtsinry43.bookmanagement.mapper.ReaderMapper">
</mapper>