3.1 JAX-RS 基础

3.1.1 JAX-RS 概述

JAX-RS(Java API for RESTful Web Services)是 Jakarta EE 的标准规范,用于构建 RESTful Web 服务。Quarkus 使用 RESTEasy Reactive 作为 JAX-RS 的实现,提供了高性能的响应式 REST 服务支持。

graph TB
    A[JAX-RS 架构] --> B[资源类]
    A --> C[HTTP 方法]
    A --> D[路径映射]
    A --> E[内容协商]
    A --> F[参数绑定]
    A --> G[异常处理]
    
    B --> B1[@Path]
    B --> B2[@ApplicationPath]
    
    C --> C1[@GET]
    C --> C2[@POST]
    C --> C3[@PUT]
    C --> C4[@DELETE]
    C --> C5[@PATCH]
    
    D --> D1[路径参数]
    D --> D2[查询参数]
    D --> D3[矩阵参数]
    
    E --> E1[@Produces]
    E --> E2[@Consumes]
    
    F --> F1[@PathParam]
    F --> F2[@QueryParam]
    F --> F3[@FormParam]
    F --> F4[@HeaderParam]
    
    G --> G1[ExceptionMapper]
    G --> G2[WebApplicationException]

3.1.2 基本资源类

package com.example.resource;

import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;
import java.util.ArrayList;

@Path("/api/books")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class BookResource {
    
    // 模拟数据存储
    private static final List<Book> books = new ArrayList<>();
    
    static {
        books.add(new Book(1L, "Java 编程思想", "Bruce Eckel", "978-0131872486"));
        books.add(new Book(2L, "Effective Java", "Joshua Bloch", "978-0134685991"));
        books.add(new Book(3L, "Spring Boot 实战", "Craig Walls", "978-7115404671"));
    }
    
    @GET
    public List<Book> getAllBooks() {
        return books;
    }
    
    @GET
    @Path("/{id}")
    public Response getBook(@PathParam("id") Long id) {
        Book book = findBookById(id);
        if (book != null) {
            return Response.ok(book).build();
        } else {
            return Response.status(Response.Status.NOT_FOUND)
                          .entity(new ErrorResponse("Book not found", 404))
                          .build();
        }
    }
    
    @POST
    public Response createBook(Book book) {
        if (book.getTitle() == null || book.getTitle().trim().isEmpty()) {
            return Response.status(Response.Status.BAD_REQUEST)
                          .entity(new ErrorResponse("Title is required", 400))
                          .build();
        }
        
        book.setId(generateNextId());
        books.add(book);
        
        return Response.status(Response.Status.CREATED)
                      .entity(book)
                      .build();
    }
    
    @PUT
    @Path("/{id}")
    public Response updateBook(@PathParam("id") Long id, Book updatedBook) {
        Book existingBook = findBookById(id);
        if (existingBook == null) {
            return Response.status(Response.Status.NOT_FOUND)
                          .entity(new ErrorResponse("Book not found", 404))
                          .build();
        }
        
        existingBook.setTitle(updatedBook.getTitle());
        existingBook.setAuthor(updatedBook.getAuthor());
        existingBook.setIsbn(updatedBook.getIsbn());
        
        return Response.ok(existingBook).build();
    }
    
    @DELETE
    @Path("/{id}")
    public Response deleteBook(@PathParam("id") Long id) {
        Book book = findBookById(id);
        if (book == null) {
            return Response.status(Response.Status.NOT_FOUND)
                          .entity(new ErrorResponse("Book not found", 404))
                          .build();
        }
        
        books.remove(book);
        return Response.noContent().build();
    }
    
    // 辅助方法
    private Book findBookById(Long id) {
        return books.stream()
                   .filter(book -> book.getId().equals(id))
                   .findFirst()
                   .orElse(null);
    }
    
    private Long generateNextId() {
        return books.stream()
                   .mapToLong(Book::getId)
                   .max()
                   .orElse(0L) + 1;
    }
}

// 书籍实体类
public class Book {
    private Long id;
    private String title;
    private String author;
    private String isbn;
    
    // 构造器
    public Book() {}
    
    public Book(Long id, String title, String author, String isbn) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.isbn = isbn;
    }
    
    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    
    public String getIsbn() { return isbn; }
    public void setIsbn(String isbn) { this.isbn = isbn; }
}

// 错误响应类
public class ErrorResponse {
    private String message;
    private int code;
    private long timestamp;
    
    public ErrorResponse(String message, int code) {
        this.message = message;
        this.code = code;
        this.timestamp = System.currentTimeMillis();
    }
    
    // Getters and Setters
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    
    public int getCode() { return code; }
    public void setCode(int code) { this.code = code; }
    
    public long getTimestamp() { return timestamp; }
    public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}

3.2 HTTP 方法和路径映射

3.2.1 HTTP 方法注解

@Path("/api/users")
public class UserResource {
    
    @Inject
    UserService userService;
    
    // GET - 获取资源
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> getAllUsers() {
        return userService.findAll();
    }
    
    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public User getUser(@PathParam("id") Long id) {
        return userService.findById(id);
    }
    
    // POST - 创建资源
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response createUser(User user) {
        User createdUser = userService.create(user);
        return Response.status(Response.Status.CREATED)
                      .entity(createdUser)
                      .build();
    }
    
    // PUT - 完整更新资源
    @PUT
    @Path("/{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public User updateUser(@PathParam("id") Long id, User user) {
        user.setId(id);
        return userService.update(user);
    }
    
    // PATCH - 部分更新资源
    @PATCH
    @Path("/{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public User patchUser(@PathParam("id") Long id, UserPatch patch) {
        return userService.patch(id, patch);
    }
    
    // DELETE - 删除资源
    @DELETE
    @Path("/{id}")
    public Response deleteUser(@PathParam("id") Long id) {
        userService.deleteById(id);
        return Response.noContent().build();
    }
    
    // HEAD - 获取资源元信息
    @HEAD
    @Path("/{id}")
    public Response checkUserExists(@PathParam("id") Long id) {
        boolean exists = userService.existsById(id);
        return exists ? Response.ok().build() 
                     : Response.status(Response.Status.NOT_FOUND).build();
    }
    
    // OPTIONS - 获取支持的方法
    @OPTIONS
    public Response getOptions() {
        return Response.ok()
                      .header("Allow", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS")
                      .build();
    }
}

3.2.2 复杂路径映射

@Path("/api/organizations")
public class OrganizationResource {
    
    @Inject
    OrganizationService organizationService;
    
    @Inject
    UserService userService;
    
    // 嵌套资源路径
    @GET
    @Path("/{orgId}/users")
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> getOrganizationUsers(@PathParam("orgId") Long orgId) {
        return userService.findByOrganizationId(orgId);
    }
    
    @POST
    @Path("/{orgId}/users")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public User addUserToOrganization(@PathParam("orgId") Long orgId, User user) {
        user.setOrganizationId(orgId);
        return userService.create(user);
    }
    
    // 多级路径参数
    @GET
    @Path("/{orgId}/departments/{deptId}/users")
    @Produces(MediaType.APPLICATION_JSON)
    public List<User> getDepartmentUsers(@PathParam("orgId") Long orgId,
                                        @PathParam("deptId") Long deptId) {
        return userService.findByOrganizationAndDepartment(orgId, deptId);
    }
    
    // 正则表达式路径
    @GET
    @Path("/code/{code: [A-Z]{2,3}[0-9]{3,6}}")
    @Produces(MediaType.APPLICATION_JSON)
    public Organization getByCode(@PathParam("code") String code) {
        return organizationService.findByCode(code);
    }
    
    // 可选路径段
    @GET
    @Path("/search{path: (/.*)?}")
    @Produces(MediaType.APPLICATION_JSON)
    public List<Organization> search(@PathParam("path") String path,
                                   @QueryParam("q") String query,
                                   @QueryParam("type") String type) {
        // 处理可选的路径段
        String searchPath = path != null ? path.substring(1) : "";
        return organizationService.search(query, type, searchPath);
    }
}

3.3 参数绑定

3.3.1 路径参数(@PathParam)

@Path("/api/products")
public class ProductResource {
    
    @GET
    @Path("/{id}")
    public Product getProduct(@PathParam("id") Long id) {
        return productService.findById(id);
    }
    
    @GET
    @Path("/category/{category}/brand/{brand}")
    public List<Product> getProductsByCategoryAndBrand(
            @PathParam("category") String category,
            @PathParam("brand") String brand) {
        return productService.findByCategoryAndBrand(category, brand);
    }
    
    // 路径参数类型转换
    @GET
    @Path("/price/{min}/{max}")
    public List<Product> getProductsByPriceRange(
            @PathParam("min") @DefaultValue("0") BigDecimal minPrice,
            @PathParam("max") @DefaultValue("999999") BigDecimal maxPrice) {
        return productService.findByPriceRange(minPrice, maxPrice);
    }
}

3.3.2 查询参数(@QueryParam)

@Path("/api/products")
public class ProductSearchResource {
    
    @GET
    @Path("/search")
    @Produces(MediaType.APPLICATION_JSON)
    public Response searchProducts(
            @QueryParam("q") String query,
            @QueryParam("category") String category,
            @QueryParam("minPrice") @DefaultValue("0") BigDecimal minPrice,
            @QueryParam("maxPrice") BigDecimal maxPrice,
            @QueryParam("page") @DefaultValue("0") int page,
            @QueryParam("size") @DefaultValue("20") int size,
            @QueryParam("sort") @DefaultValue("name") String sortBy,
            @QueryParam("order") @DefaultValue("asc") String sortOrder,
            @QueryParam("inStock") Boolean inStock) {
        
        // 构建搜索条件
        ProductSearchCriteria criteria = ProductSearchCriteria.builder()
            .query(query)
            .category(category)
            .minPrice(minPrice)
            .maxPrice(maxPrice)
            .inStock(inStock)
            .build();
        
        // 构建分页信息
        PageRequest pageRequest = PageRequest.of(page, size, sortBy, sortOrder);
        
        // 执行搜索
        PageResult<Product> result = productService.search(criteria, pageRequest);
        
        return Response.ok(result)
                      .header("X-Total-Count", result.getTotalElements())
                      .header("X-Total-Pages", result.getTotalPages())
                      .build();
    }
    
    // 多值查询参数
    @GET
    @Path("/filter")
    public List<Product> filterProducts(
            @QueryParam("tags") List<String> tags,
            @QueryParam("brands") Set<String> brands,
            @QueryParam("colors") String[] colors) {
        
        ProductFilter filter = new ProductFilter();
        filter.setTags(tags);
        filter.setBrands(brands);
        filter.setColors(Arrays.asList(colors));
        
        return productService.filter(filter);
    }
}

3.3.3 表单参数(@FormParam)

@Path("/api/auth")
public class AuthResource {
    
    @POST
    @Path("/login")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    @Produces(MediaType.APPLICATION_JSON)
    public Response login(@FormParam("username") String username,
                         @FormParam("password") String password,
                         @FormParam("rememberMe") @DefaultValue("false") boolean rememberMe) {
        
        try {
            AuthResult result = authService.authenticate(username, password, rememberMe);
            return Response.ok(result).build();
        } catch (AuthenticationException e) {
            return Response.status(Response.Status.UNAUTHORIZED)
                          .entity(new ErrorResponse("Invalid credentials", 401))
                          .build();
        }
    }
    
    @POST
    @Path("/register")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_JSON)
    public Response register(@FormParam("username") String username,
                           @FormParam("email") String email,
                           @FormParam("password") String password,
                           @FormParam("avatar") InputStream avatarStream,
                           @FormParam("avatar") FormDataContentDisposition avatarDetail) {
        
        // 处理用户注册
        User user = new User(username, email, password);
        
        // 处理头像上传
        if (avatarStream != null && avatarDetail != null) {
            String avatarUrl = fileService.uploadAvatar(avatarStream, avatarDetail.getFileName());
            user.setAvatarUrl(avatarUrl);
        }
        
        User createdUser = userService.register(user);
        return Response.status(Response.Status.CREATED).entity(createdUser).build();
    }
}

3.3.4 请求头参数(@HeaderParam)

@Path("/api/secure")
public class SecureResource {
    
    @GET
    @Path("/profile")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getUserProfile(
            @HeaderParam("Authorization") String authHeader,
            @HeaderParam("User-Agent") String userAgent,
            @HeaderParam("Accept-Language") @DefaultValue("en") String language,
            @HeaderParam("X-Request-ID") String requestId) {
        
        // 验证授权头
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return Response.status(Response.Status.UNAUTHORIZED)
                          .entity(new ErrorResponse("Missing or invalid authorization header", 401))
                          .build();
        }
        
        String token = authHeader.substring(7);
        User user = authService.validateToken(token);
        
        if (user == null) {
            return Response.status(Response.Status.UNAUTHORIZED)
                          .entity(new ErrorResponse("Invalid token", 401))
                          .build();
        }
        
        // 记录请求信息
        auditService.logAccess(user.getId(), userAgent, requestId);
        
        // 根据语言返回本地化的用户信息
        UserProfile profile = userService.getLocalizedProfile(user.getId(), language);
        
        return Response.ok(profile)
                      .header("X-Request-ID", requestId)
                      .build();
    }
    
    @POST
    @Path("/upload")
    @Consumes(MediaType.APPLICATION_OCTET_STREAM)
    public Response uploadFile(
            @HeaderParam("Content-Type") String contentType,
            @HeaderParam("Content-Length") long contentLength,
            @HeaderParam("X-File-Name") String fileName,
            @HeaderParam("X-File-Hash") String fileHash,
            InputStream fileStream) {
        
        // 验证文件大小
        if (contentLength > 10 * 1024 * 1024) { // 10MB 限制
            return Response.status(Response.Status.REQUEST_ENTITY_TOO_LARGE)
                          .entity(new ErrorResponse("File too large", 413))
                          .build();
        }
        
        // 验证文件类型
        if (!isAllowedContentType(contentType)) {
            return Response.status(Response.Status.UNSUPPORTED_MEDIA_TYPE)
                          .entity(new ErrorResponse("Unsupported file type", 415))
                          .build();
        }
        
        // 上传文件
        FileUploadResult result = fileService.upload(fileStream, fileName, contentType, fileHash);
        
        return Response.status(Response.Status.CREATED)
                      .entity(result)
                      .build();
    }
    
    private boolean isAllowedContentType(String contentType) {
        return contentType != null && (
            contentType.startsWith("image/") ||
            contentType.equals("application/pdf") ||
            contentType.startsWith("text/")
        );
    }
}

3.3.5 Cookie 参数(@CookieParam)

@Path("/api/preferences")
public class PreferencesResource {
    
    @GET
    @Path("/theme")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getThemePreference(
            @CookieParam("theme") @DefaultValue("light") String theme,
            @CookieParam("language") @DefaultValue("en") String language,
            @CookieParam("sessionId") String sessionId) {
        
        // 验证会话
        if (sessionId != null && sessionService.isValidSession(sessionId)) {
            // 从数据库获取用户偏好
            UserPreferences prefs = preferencesService.getBySessionId(sessionId);
            return Response.ok(prefs).build();
        } else {
            // 使用 Cookie 中的偏好
            UserPreferences prefs = new UserPreferences(theme, language);
            return Response.ok(prefs).build();
        }
    }
    
    @POST
    @Path("/theme")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response setThemePreference(
            UserPreferences preferences,
            @CookieParam("sessionId") String sessionId) {
        
        if (sessionId != null && sessionService.isValidSession(sessionId)) {
            // 保存到数据库
            preferencesService.saveBySessionId(sessionId, preferences);
        }
        
        // 设置 Cookie
        NewCookie themeCookie = new NewCookie.Builder("theme")
            .value(preferences.getTheme())
            .maxAge(30 * 24 * 60 * 60) // 30 天
            .httpOnly(false)
            .secure(false)
            .build();
        
        NewCookie languageCookie = new NewCookie.Builder("language")
            .value(preferences.getLanguage())
            .maxAge(30 * 24 * 60 * 60)
            .httpOnly(false)
            .secure(false)
            .build();
        
        return Response.ok(preferences)
                      .cookie(themeCookie, languageCookie)
                      .build();
    }
}

3.4 内容协商

3.4.1 媒体类型处理

@Path("/api/content")
public class ContentNegotiationResource {
    
    @Inject
    BookService bookService;
    
    // 支持多种输出格式
    @GET
    @Path("/books/{id}")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN})
    public Response getBook(@PathParam("id") Long id, @Context HttpHeaders headers) {
        
        Book book = bookService.findById(id);
        if (book == null) {
            return Response.status(Response.Status.NOT_FOUND).build();
        }
        
        // 根据 Accept 头确定响应格式
        List<MediaType> acceptableTypes = headers.getAcceptableMediaTypes();
        
        for (MediaType mediaType : acceptableTypes) {
            if (mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) {
                return Response.ok(book, MediaType.APPLICATION_JSON).build();
            } else if (mediaType.isCompatible(MediaType.APPLICATION_XML_TYPE)) {
                return Response.ok(book, MediaType.APPLICATION_XML).build();
            } else if (mediaType.isCompatible(MediaType.TEXT_PLAIN_TYPE)) {
                String textRepresentation = String.format(
                    "Book: %s by %s (ISBN: %s)", 
                    book.getTitle(), book.getAuthor(), book.getIsbn());
                return Response.ok(textRepresentation, MediaType.TEXT_PLAIN).build();
            }
        }
        
        // 默认返回 JSON
        return Response.ok(book, MediaType.APPLICATION_JSON).build();
    }
    
    // 支持多种输入格式
    @POST
    @Path("/books")
    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    @Produces(MediaType.APPLICATION_JSON)
    public Response createBook(Book book, @Context HttpHeaders headers) {
        
        // 获取请求的内容类型
        MediaType contentType = headers.getMediaType();
        
        // 根据内容类型进行特殊处理
        if (MediaType.APPLICATION_XML_TYPE.isCompatible(contentType)) {
            // XML 特殊处理逻辑
            book = preprocessXmlBook(book);
        }
        
        Book createdBook = bookService.create(book);
        
        return Response.status(Response.Status.CREATED)
                      .entity(createdBook)
                      .build();
    }
    
    // 条件化内容协商
    @GET
    @Path("/books")
    public Response getBooks(@QueryParam("format") String format,
                           @Context HttpHeaders headers) {
        
        List<Book> books = bookService.findAll();
        
        // 优先使用查询参数指定的格式
        if ("xml".equalsIgnoreCase(format)) {
            return Response.ok(books, MediaType.APPLICATION_XML).build();
        } else if ("json".equalsIgnoreCase(format)) {
            return Response.ok(books, MediaType.APPLICATION_JSON).build();
        } else if ("csv".equalsIgnoreCase(format)) {
            String csv = convertToCsv(books);
            return Response.ok(csv, "text/csv")
                          .header("Content-Disposition", "attachment; filename=books.csv")
                          .build();
        }
        
        // 否则使用内容协商
        List<MediaType> acceptableTypes = headers.getAcceptableMediaTypes();
        
        if (acceptableTypes.contains(MediaType.APPLICATION_XML_TYPE)) {
            return Response.ok(books, MediaType.APPLICATION_XML).build();
        } else {
            return Response.ok(books, MediaType.APPLICATION_JSON).build();
        }
    }
    
    private Book preprocessXmlBook(Book book) {
        // XML 特殊处理逻辑
        if (book.getTitle() != null) {
            book.setTitle(book.getTitle().trim());
        }
        return book;
    }
    
    private String convertToCsv(List<Book> books) {
        StringBuilder csv = new StringBuilder();
        csv.append("ID,Title,Author,ISBN\n");
        
        for (Book book : books) {
            csv.append(String.format("%d,\"%s\",\"%s\",\"%s\"\n",
                book.getId(), 
                book.getTitle().replace("\"", "\"\""), 
                book.getAuthor().replace("\"", "\"\""), 
                book.getIsbn()));
        }
        
        return csv.toString();
    }
}

3.4.2 自定义消息转换器

// 自定义 MessageBodyWriter
@Provider
@Produces("text/csv")
public class CsvMessageBodyWriter implements MessageBodyWriter<List<Book>> {
    
    @Override
    public boolean isWriteable(Class<?> type, Type genericType, 
                              Annotation[] annotations, MediaType mediaType) {
        return List.class.isAssignableFrom(type) && 
               "text/csv".equals(mediaType.toString());
    }
    
    @Override
    public void writeTo(List<Book> books, Class<?> type, Type genericType,
                       Annotation[] annotations, MediaType mediaType,
                       MultivaluedMap<String, Object> httpHeaders,
                       OutputStream entityStream) throws IOException {
        
        try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(entityStream, StandardCharsets.UTF_8))) {
            // 写入 CSV 头
            writer.println("ID,Title,Author,ISBN");
            
            // 写入数据行
            for (Book book : books) {
                writer.printf("%d,\"%s\",\"%s\",\"%s\"\n",
                    book.getId(),
                    escapeCsv(book.getTitle()),
                    escapeCsv(book.getAuthor()),
                    escapeCsv(book.getIsbn()));
            }
        }
    }
    
    private String escapeCsv(String value) {
        if (value == null) return "";
        return value.replace("\"", "\"\"");
    }
}

// 自定义 MessageBodyReader
@Provider
@Consumes("text/csv")
public class CsvMessageBodyReader implements MessageBodyReader<List<Book>> {
    
    @Override
    public boolean isReadable(Class<?> type, Type genericType,
                             Annotation[] annotations, MediaType mediaType) {
        return List.class.isAssignableFrom(type) && 
               "text/csv".equals(mediaType.toString());
    }
    
    @Override
    public List<Book> readFrom(Class<List<Book>> type, Type genericType,
                              Annotation[] annotations, MediaType mediaType,
                              MultivaluedMap<String, String> httpHeaders,
                              InputStream entityStream) throws IOException {
        
        List<Book> books = new ArrayList<>();
        
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(entityStream, StandardCharsets.UTF_8))) {
            
            String line = reader.readLine(); // 跳过头行
            
            while ((line = reader.readLine()) != null) {
                String[] fields = parseCsvLine(line);
                if (fields.length >= 4) {
                    Book book = new Book();
                    book.setId(Long.parseLong(fields[0]));
                    book.setTitle(fields[1]);
                    book.setAuthor(fields[2]);
                    book.setIsbn(fields[3]);
                    books.add(book);
                }
            }
        }
        
        return books;
    }
    
    private String[] parseCsvLine(String line) {
        // 简单的 CSV 解析(生产环境建议使用专业的 CSV 库)
        List<String> fields = new ArrayList<>();
        boolean inQuotes = false;
        StringBuilder field = new StringBuilder();
        
        for (char c : line.toCharArray()) {
            if (c == '"') {
                inQuotes = !inQuotes;
            } else if (c == ',' && !inQuotes) {
                fields.add(field.toString());
                field.setLength(0);
            } else {
                field.append(c);
            }
        }
        fields.add(field.toString());
        
        return fields.toArray(new String[0]);
    }
}

3.5 异常处理

3.5.1 标准异常处理

// 自定义业务异常
public class BookNotFoundException extends RuntimeException {
    private final Long bookId;
    
    public BookNotFoundException(Long bookId) {
        super("Book not found with id: " + bookId);
        this.bookId = bookId;
    }
    
    public Long getBookId() {
        return bookId;
    }
}

public class ValidationException extends RuntimeException {
    private final List<String> errors;
    
    public ValidationException(List<String> errors) {
        super("Validation failed: " + String.join(", ", errors));
        this.errors = errors;
    }
    
    public List<String> getErrors() {
        return errors;
    }
}

// 全局异常映射器
@Provider
public class BookNotFoundExceptionMapper implements ExceptionMapper<BookNotFoundException> {
    
    @Override
    public Response toResponse(BookNotFoundException exception) {
        ErrorResponse error = new ErrorResponse(
            "Book not found",
            404,
            "BOOK_NOT_FOUND",
            Map.of("bookId", exception.getBookId())
        );
        
        return Response.status(Response.Status.NOT_FOUND)
                      .entity(error)
                      .type(MediaType.APPLICATION_JSON)
                      .build();
    }
}

@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
    
    @Override
    public Response toResponse(ValidationException exception) {
        ErrorResponse error = new ErrorResponse(
            "Validation failed",
            400,
            "VALIDATION_ERROR",
            Map.of("errors", exception.getErrors())
        );
        
        return Response.status(Response.Status.BAD_REQUEST)
                      .entity(error)
                      .type(MediaType.APPLICATION_JSON)
                      .build();
    }
}

// 通用异常映射器
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Exception> {
    
    private static final Logger logger = LoggerFactory.getLogger(GenericExceptionMapper.class);
    
    @Override
    public Response toResponse(Exception exception) {
        logger.error("Unhandled exception", exception);
        
        ErrorResponse error = new ErrorResponse(
            "Internal server error",
            500,
            "INTERNAL_ERROR",
            Map.of("timestamp", System.currentTimeMillis())
        );
        
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                      .entity(error)
                      .type(MediaType.APPLICATION_JSON)
                      .build();
    }
}

// 增强的错误响应类
public class ErrorResponse {
    private String message;
    private int status;
    private String code;
    private Map<String, Object> details;
    private long timestamp;
    
    public ErrorResponse(String message, int status) {
        this(message, status, null, null);
    }
    
    public ErrorResponse(String message, int status, String code, Map<String, Object> details) {
        this.message = message;
        this.status = status;
        this.code = code;
        this.details = details;
        this.timestamp = System.currentTimeMillis();
    }
    
    // Getters and Setters
    public String getMessage() { return message; }
    public void setMessage(String message) { this.message = message; }
    
    public int getStatus() { return status; }
    public void setStatus(int status) { this.status = status; }
    
    public String getCode() { return code; }
    public void setCode(String code) { this.code = code; }
    
    public Map<String, Object> getDetails() { return details; }
    public void setDetails(Map<String, Object> details) { this.details = details; }
    
    public long getTimestamp() { return timestamp; }
    public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
}

3.5.2 WebApplicationException 使用

@Path("/api/books")
public class BookResourceWithExceptions {
    
    @Inject
    BookService bookService;
    
    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Book getBook(@PathParam("id") Long id) {
        if (id == null || id <= 0) {
            throw new WebApplicationException(
                "Invalid book ID", 
                Response.Status.BAD_REQUEST
            );
        }
        
        Book book = bookService.findById(id);
        if (book == null) {
            ErrorResponse error = new ErrorResponse("Book not found", 404);
            throw new WebApplicationException(
                Response.status(Response.Status.NOT_FOUND)
                       .entity(error)
                       .type(MediaType.APPLICATION_JSON)
                       .build()
            );
        }
        
        return book;
    }
    
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Book createBook(Book book) {
        // 验证输入
        List<String> errors = validateBook(book);
        if (!errors.isEmpty()) {
            ErrorResponse error = new ErrorResponse(
                "Validation failed", 
                400, 
                "VALIDATION_ERROR", 
                Map.of("errors", errors)
            );
            
            throw new WebApplicationException(
                Response.status(Response.Status.BAD_REQUEST)
                       .entity(error)
                       .type(MediaType.APPLICATION_JSON)
                       .build()
            );
        }
        
        // 检查重复
        if (bookService.existsByIsbn(book.getIsbn())) {
            throw new WebApplicationException(
                "Book with this ISBN already exists",
                Response.Status.CONFLICT
            );
        }
        
        return bookService.create(book);
    }
    
    @DELETE
    @Path("/{id}")
    public Response deleteBook(@PathParam("id") Long id) {
        if (!bookService.existsById(id)) {
            throw new WebApplicationException(
                "Book not found",
                Response.Status.NOT_FOUND
            );
        }
        
        // 检查是否可以删除
        if (bookService.hasActiveLoans(id)) {
            throw new WebApplicationException(
                "Cannot delete book with active loans",
                Response.Status.CONFLICT
            );
        }
        
        bookService.deleteById(id);
        return Response.noContent().build();
    }
    
    private List<String> validateBook(Book book) {
        List<String> errors = new ArrayList<>();
        
        if (book.getTitle() == null || book.getTitle().trim().isEmpty()) {
            errors.add("Title is required");
        }
        
        if (book.getAuthor() == null || book.getAuthor().trim().isEmpty()) {
            errors.add("Author is required");
        }
        
        if (book.getIsbn() == null || !isValidIsbn(book.getIsbn())) {
            errors.add("Valid ISBN is required");
        }
        
        return errors;
    }
    
    private boolean isValidIsbn(String isbn) {
        // 简单的 ISBN 验证
        return isbn != null && isbn.matches("^(978|979)[0-9]{10}$");
    }
}

3.6 响应式编程支持

3.6.1 异步响应

@Path("/api/async")
public class AsyncResource {
    
    @Inject
    BookService bookService;
    
    @Inject
    @ConfigProperty(name = "app.async.timeout", defaultValue = "30")
    int asyncTimeoutSeconds;
    
    // 返回 CompletionStage
    @GET
    @Path("/books/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public CompletionStage<Response> getBookAsync(@PathParam("id") Long id) {
        return CompletableFuture
            .supplyAsync(() -> {
                // 模拟耗时操作
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException(e);
                }
                
                Book book = bookService.findById(id);
                if (book == null) {
                    throw new BookNotFoundException(id);
                }
                return book;
            })
            .thenApply(book -> Response.ok(book).build())
            .exceptionally(throwable -> {
                if (throwable.getCause() instanceof BookNotFoundException) {
                    return Response.status(Response.Status.NOT_FOUND)
                                  .entity(new ErrorResponse("Book not found", 404))
                                  .build();
                }
                return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                              .entity(new ErrorResponse("Internal error", 500))
                              .build();
            });
    }
    
    // 返回 Uni (Mutiny)
    @GET
    @Path("/books")
    @Produces(MediaType.APPLICATION_JSON)
    public Uni<List<Book>> getAllBooksAsync() {
        return Uni.createFrom()
                 .completionStage(() -> CompletableFuture.supplyAsync(() -> {
                     // 模拟异步数据库查询
                     try {
                         Thread.sleep(500);
                     } catch (InterruptedException e) {
                         Thread.currentThread().interrupt();
                         throw new RuntimeException(e);
                     }
                     return bookService.findAll();
                 }))
                 .onFailure().transform(throwable -> 
                     new WebApplicationException("Failed to fetch books", 
                                                Response.Status.INTERNAL_SERVER_ERROR));
    }
    
    // 流式响应
    @GET
    @Path("/books/stream")
    @Produces(MediaType.APPLICATION_JSON)
    public Multi<Book> streamBooks(@QueryParam("delay") @DefaultValue("100") int delayMs) {
        List<Book> books = bookService.findAll();
        
        return Multi.createFrom().iterable(books)
                   .onItem().delayIt().by(Duration.ofMillis(delayMs))
                   .onFailure().recoverWithItem(new Book(-1L, "Error", "Error", "Error"));
    }
    
    // 服务器发送事件 (SSE)
    @GET
    @Path("/books/events")
    @Produces(MediaType.SERVER_SENT_EVENTS)
    public Multi<String> bookEvents() {
        return Multi.createFrom().ticks().every(Duration.ofSeconds(2))
                   .onItem().transform(tick -> {
                       // 模拟实时书籍更新事件
                       Book randomBook = bookService.getRandomBook();
                       return String.format("data: {\"event\": \"book_updated\", \"book\": \"%s\"}\n\n", 
                                           randomBook.getTitle());
                   })
                   .select().first(10); // 限制为前 10 个事件
    }
}

3.6.2 响应式数据处理

@Path("/api/reactive")
public class ReactiveDataResource {
    
    @Inject
    ReactiveBookService reactiveBookService;
    
    // 批量处理
    @POST
    @Path("/books/batch")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Uni<BatchResult> createBooksInBatch(List<Book> books) {
        return Multi.createFrom().iterable(books)
                   .onItem().transformToUniAndMerge(book -> 
                       reactiveBookService.createAsync(book)
                           .onFailure().recoverWithItem(new Book(-1L, "Failed", "Failed", "Failed"))
                   )
                   .collect().asList()
                   .onItem().transform(results -> {
                       long successCount = results.stream()
                           .filter(book -> book.getId() > 0)
                           .count();
                       long failureCount = results.size() - successCount;
                       
                       return new BatchResult(successCount, failureCount, results);
                   });
    }
    
    // 分页异步查询
    @GET
    @Path("/books/page")
    @Produces(MediaType.APPLICATION_JSON)
    public Uni<PageResult<Book>> getBooksPage(
            @QueryParam("page") @DefaultValue("0") int page,
            @QueryParam("size") @DefaultValue("10") int size) {
        
        return reactiveBookService.findAllAsync(page, size)
                   .onItem().transform(books -> {
                       long totalCount = reactiveBookService.countAll();
                       int totalPages = (int) Math.ceil((double) totalCount / size);
                       
                       return new PageResult<>(books, page, size, totalCount, totalPages);
                   });
    }
    
    // 组合多个异步操作
    @GET
    @Path("/books/{id}/details")
    @Produces(MediaType.APPLICATION_JSON)
    public Uni<BookDetails> getBookDetails(@PathParam("id") Long id) {
        Uni<Book> bookUni = reactiveBookService.findByIdAsync(id);
        Uni<List<Review>> reviewsUni = reactiveBookService.getReviewsAsync(id);
        Uni<BookStatistics> statsUni = reactiveBookService.getStatisticsAsync(id);
        
        return Uni.combine().all().unis(bookUni, reviewsUni, statsUni)
                  .asTuple()
                  .onItem().transform(tuple -> {
                      Book book = tuple.getItem1();
                      List<Review> reviews = tuple.getItem2();
                      BookStatistics stats = tuple.getItem3();
                      
                      return new BookDetails(book, reviews, stats);
                  })
                  .onFailure().transform(throwable -> 
                      new WebApplicationException("Failed to fetch book details", 
                                                Response.Status.INTERNAL_SERVER_ERROR));
    }
}

// 支持类
public class BatchResult {
    private final long successCount;
    private final long failureCount;
    private final List<Book> results;
    
    public BatchResult(long successCount, long failureCount, List<Book> results) {
        this.successCount = successCount;
        this.failureCount = failureCount;
        this.results = results;
    }
    
    // Getters
    public long getSuccessCount() { return successCount; }
    public long getFailureCount() { return failureCount; }
    public List<Book> getResults() { return results; }
}

public class PageResult<T> {
    private final List<T> content;
    private final int page;
    private final int size;
    private final long totalElements;
    private final int totalPages;
    
    public PageResult(List<T> content, int page, int size, long totalElements, int totalPages) {
        this.content = content;
        this.page = page;
        this.size = size;
        this.totalElements = totalElements;
        this.totalPages = totalPages;
    }
    
    // Getters
    public List<T> getContent() { return content; }
    public int getPage() { return page; }
    public int getSize() { return size; }
    public long getTotalElements() { return totalElements; }
    public int getTotalPages() { return totalPages; }
}

public class BookDetails {
    private final Book book;
    private final List<Review> reviews;
    private final BookStatistics statistics;
    
    public BookDetails(Book book, List<Review> reviews, BookStatistics statistics) {
        this.book = book;
        this.reviews = reviews;
        this.statistics = statistics;
    }
    
    // Getters
    public Book getBook() { return book; }
    public List<Review> getReviews() { return reviews; }
    public BookStatistics getStatistics() { return statistics; }
}

3.7 实践练习

3.7.1 练习1:构建完整的图书管理 API

创建一个完整的图书管理系统 REST API:

// 图书服务接口
public interface BookService {
    List<Book> findAll();
    Book findById(Long id);
    List<Book> findByCategory(String category);
    List<Book> search(String query);
    Book create(Book book);
    Book update(Book book);
    void deleteById(Long id);
    boolean existsById(Long id);
    boolean existsByIsbn(String isbn);
    boolean hasActiveLoans(Long id);
    Book getRandomBook();
    long countAll();
}

// 图书服务实现
@ApplicationScoped
public class BookServiceImpl implements BookService {
    
    private final Map<Long, Book> books = new ConcurrentHashMap<>();
    private final AtomicLong idGenerator = new AtomicLong(1);
    
    @PostConstruct
    void init() {
        // 初始化示例数据
        create(new Book(null, "Java 编程思想", "Bruce Eckel", "9780131872486", "Programming"));
        create(new Book(null, "Effective Java", "Joshua Bloch", "9780134685991", "Programming"));
        create(new Book(null, "Spring Boot 实战", "Craig Walls", "9787115404671", "Framework"));
        create(new Book(null, "微服务架构设计模式", "Chris Richardson", "9787111627845", "Architecture"));
    }
    
    @Override
    public List<Book> findAll() {
        return new ArrayList<>(books.values());
    }
    
    @Override
    public Book findById(Long id) {
        return books.get(id);
    }
    
    @Override
    public List<Book> findByCategory(String category) {
        return books.values().stream()
                   .filter(book -> category.equalsIgnoreCase(book.getCategory()))
                   .collect(Collectors.toList());
    }
    
    @Override
    public List<Book> search(String query) {
        String lowerQuery = query.toLowerCase();
        return books.values().stream()
                   .filter(book -> 
                       book.getTitle().toLowerCase().contains(lowerQuery) ||
                       book.getAuthor().toLowerCase().contains(lowerQuery) ||
                       book.getIsbn().contains(query))
                   .collect(Collectors.toList());
    }
    
    @Override
    public Book create(Book book) {
        book.setId(idGenerator.getAndIncrement());
        books.put(book.getId(), book);
        return book;
    }
    
    @Override
    public Book update(Book book) {
        books.put(book.getId(), book);
        return book;
    }
    
    @Override
    public void deleteById(Long id) {
        books.remove(id);
    }
    
    @Override
    public boolean existsById(Long id) {
        return books.containsKey(id);
    }
    
    @Override
    public boolean existsByIsbn(String isbn) {
        return books.values().stream()
                   .anyMatch(book -> isbn.equals(book.getIsbn()));
    }
    
    @Override
    public boolean hasActiveLoans(Long id) {
        // 模拟检查是否有活跃借阅
        return false;
    }
    
    @Override
    public Book getRandomBook() {
        List<Book> allBooks = findAll();
        if (allBooks.isEmpty()) {
            return new Book(-1L, "No books available", "System", "N/A", "System");
        }
        return allBooks.get(new Random().nextInt(allBooks.size()));
    }
    
    @Override
    public long countAll() {
        return books.size();
    }
}

// 扩展的 Book 实体
public class Book {
    private Long id;
    private String title;
    private String author;
    private String isbn;
    private String category;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    
    // 构造器
    public Book() {
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }
    
    public Book(Long id, String title, String author, String isbn, String category) {
        this();
        this.id = id;
        this.title = title;
        this.author = author;
        this.isbn = isbn;
        this.category = category;
    }
    
    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { 
        this.id = id;
        this.updatedAt = LocalDateTime.now();
    }
    
    public String getTitle() { return title; }
    public void setTitle(String title) { 
        this.title = title;
        this.updatedAt = LocalDateTime.now();
    }
    
    public String getAuthor() { return author; }
    public void setAuthor(String author) { 
        this.author = author;
        this.updatedAt = LocalDateTime.now();
    }
    
    public String getIsbn() { return isbn; }
    public void setIsbn(String isbn) { 
        this.isbn = isbn;
        this.updatedAt = LocalDateTime.now();
    }
    
    public String getCategory() { return category; }
    public void setCategory(String category) { 
        this.category = category;
        this.updatedAt = LocalDateTime.now();
    }
    
    public LocalDateTime getCreatedAt() { return createdAt; }
    public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
    
    public LocalDateTime getUpdatedAt() { return updatedAt; }
    public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}

3.7.2 练习2:实现搜索和过滤功能

@Path("/api/books")
public class BookSearchResource {
    
    @Inject
    BookService bookService;
    
    @GET
    @Path("/search")
    @Produces(MediaType.APPLICATION_JSON)
    public Response searchBooks(
            @QueryParam("q") String query,
            @QueryParam("category") String category,
            @QueryParam("author") String author,
            @QueryParam("page") @DefaultValue("0") int page,
            @QueryParam("size") @DefaultValue("10") int size,
            @QueryParam("sort") @DefaultValue("title") String sortBy,
            @QueryParam("order") @DefaultValue("asc") String sortOrder) {
        
        List<Book> allBooks = bookService.findAll();
        Stream<Book> bookStream = allBooks.stream();
        
        // 应用过滤条件
        if (query != null && !query.trim().isEmpty()) {
            String lowerQuery = query.toLowerCase();
            bookStream = bookStream.filter(book ->
                book.getTitle().toLowerCase().contains(lowerQuery) ||
                book.getAuthor().toLowerCase().contains(lowerQuery) ||
                book.getIsbn().contains(query)
            );
        }
        
        if (category != null && !category.trim().isEmpty()) {
            bookStream = bookStream.filter(book ->
                category.equalsIgnoreCase(book.getCategory())
            );
        }
        
        if (author != null && !author.trim().isEmpty()) {
            String lowerAuthor = author.toLowerCase();
            bookStream = bookStream.filter(book ->
                book.getAuthor().toLowerCase().contains(lowerAuthor)
            );
        }
        
        // 排序
        Comparator<Book> comparator = getComparator(sortBy, sortOrder);
        bookStream = bookStream.sorted(comparator);
        
        // 收集结果
        List<Book> filteredBooks = bookStream.collect(Collectors.toList());
        
        // 分页
        int totalElements = filteredBooks.size();
        int totalPages = (int) Math.ceil((double) totalElements / size);
        int startIndex = page * size;
        int endIndex = Math.min(startIndex + size, totalElements);
        
        List<Book> pageContent = filteredBooks.subList(startIndex, endIndex);
        
        SearchResult result = new SearchResult(
            pageContent,
            page,
            size,
            totalElements,
            totalPages,
            query,
            category,
            author
        );
        
        return Response.ok(result)
                      .header("X-Total-Count", totalElements)
                      .header("X-Total-Pages", totalPages)
                      .build();
    }
    
    private Comparator<Book> getComparator(String sortBy, String sortOrder) {
        Comparator<Book> comparator;
        
        switch (sortBy.toLowerCase()) {
            case "title":
                comparator = Comparator.comparing(Book::getTitle, String.CASE_INSENSITIVE_ORDER);
                break;
            case "author":
                comparator = Comparator.comparing(Book::getAuthor, String.CASE_INSENSITIVE_ORDER);
                break;
            case "category":
                comparator = Comparator.comparing(Book::getCategory, String.CASE_INSENSITIVE_ORDER);
                break;
            case "created":
                comparator = Comparator.comparing(Book::getCreatedAt);
                break;
            default:
                comparator = Comparator.comparing(Book::getTitle, String.CASE_INSENSITIVE_ORDER);
        }
        
        return "desc".equalsIgnoreCase(sortOrder) ? comparator.reversed() : comparator;
    }
}

// 搜索结果类
public class SearchResult {
    private final List<Book> content;
    private final int page;
    private final int size;
    private final int totalElements;
    private final int totalPages;
    private final String query;
    private final String category;
    private final String author;
    
    public SearchResult(List<Book> content, int page, int size, int totalElements, 
                       int totalPages, String query, String category, String author) {
        this.content = content;
        this.page = page;
        this.size = size;
        this.totalElements = totalElements;
        this.totalPages = totalPages;
        this.query = query;
        this.category = category;
        this.author = author;
    }
    
    // Getters
    public List<Book> getContent() { return content; }
    public int getPage() { return page; }
    public int getSize() { return size; }
    public int getTotalElements() { return totalElements; }
    public int getTotalPages() { return totalPages; }
    public String getQuery() { return query; }
    public String getCategory() { return category; }
    public String getAuthor() { return author; }
}

3.7.3 练习3:实现文件上传和下载

@Path("/api/files")
public class FileResource {
    
    @ConfigProperty(name = "app.upload.directory", defaultValue = "./uploads")
    String uploadDirectory;
    
    @ConfigProperty(name = "app.upload.max-size", defaultValue = "10485760") // 10MB
    long maxFileSize;
    
    @POST
    @Path("/upload")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Produces(MediaType.APPLICATION_JSON)
    public Response uploadFile(@MultipartForm FileUploadForm form) {
        
        try {
            // 验证文件
            validateFile(form);
            
            // 创建上传目录
            Path uploadPath = Paths.get(uploadDirectory);
            if (!Files.exists(uploadPath)) {
                Files.createDirectories(uploadPath);
            }
            
            // 生成唯一文件名
            String originalFileName = form.fileName;
            String fileExtension = getFileExtension(originalFileName);
            String uniqueFileName = UUID.randomUUID().toString() + fileExtension;
            
            // 保存文件
            Path filePath = uploadPath.resolve(uniqueFileName);
            Files.copy(form.file, filePath, StandardCopyOption.REPLACE_EXISTING);
            
            // 创建文件记录
            FileRecord fileRecord = new FileRecord(
                uniqueFileName,
                originalFileName,
                Files.size(filePath),
                form.description,
                LocalDateTime.now()
            );
            
            return Response.status(Response.Status.CREATED)
                          .entity(fileRecord)
                          .build();
            
        } catch (IOException e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                          .entity(new ErrorResponse("File upload failed", 500))
                          .build();
        } catch (IllegalArgumentException e) {
            return Response.status(Response.Status.BAD_REQUEST)
                          .entity(new ErrorResponse(e.getMessage(), 400))
                          .build();
        }
    }
    
    @GET
    @Path("/download/{fileName}")
    public Response downloadFile(@PathParam("fileName") String fileName) {
        
        try {
            Path filePath = Paths.get(uploadDirectory, fileName);
            
            if (!Files.exists(filePath)) {
                return Response.status(Response.Status.NOT_FOUND)
                              .entity(new ErrorResponse("File not found", 404))
                              .build();
            }
            
            // 获取文件信息
            String contentType = Files.probeContentType(filePath);
            if (contentType == null) {
                contentType = MediaType.APPLICATION_OCTET_STREAM;
            }
            
            // 返回文件流
            StreamingOutput stream = output -> {
                try (InputStream input = Files.newInputStream(filePath)) {
                    byte[] buffer = new byte[8192];
                    int bytesRead;
                    while ((bytesRead = input.read(buffer)) != -1) {
                        output.write(buffer, 0, bytesRead);
                    }
                }
            };
            
            return Response.ok(stream, contentType)
                          .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
                          .header("Content-Length", Files.size(filePath))
                          .build();
            
        } catch (IOException e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                          .entity(new ErrorResponse("File download failed", 500))
                          .build();
        }
    }
    
    private void validateFile(FileUploadForm form) {
        if (form.file == null) {
            throw new IllegalArgumentException("File is required");
        }
        
        if (form.fileName == null || form.fileName.trim().isEmpty()) {
            throw new IllegalArgumentException("File name is required");
        }
        
        try {
            long fileSize = form.file.available();
            if (fileSize > maxFileSize) {
                throw new IllegalArgumentException("File size exceeds maximum allowed size");
            }
        } catch (IOException e) {
            throw new IllegalArgumentException("Unable to determine file size");
        }
        
        // 验证文件类型
        String fileExtension = getFileExtension(form.fileName).toLowerCase();
        if (!isAllowedFileType(fileExtension)) {
            throw new IllegalArgumentException("File type not allowed");
        }
    }
    
    private String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf('.');
        return lastDotIndex > 0 ? fileName.substring(lastDotIndex) : "";
    }
    
    private boolean isAllowedFileType(String extension) {
        Set<String> allowedTypes = Set.of(".jpg", ".jpeg", ".png", ".gif", ".pdf", ".txt", ".doc", ".docx");
        return allowedTypes.contains(extension);
    }
}

// 文件上传表单
public static class FileUploadForm {
    
    @FormParam("file")
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    public InputStream file;
    
    @FormParam("fileName")
    @PartType(MediaType.TEXT_PLAIN)
    public String fileName;
    
    @FormParam("description")
    @PartType(MediaType.TEXT_PLAIN)
    public String description;
}

// 文件记录类
public class FileRecord {
    private String id;
    private String originalName;
    private long size;
    private String description;
    private LocalDateTime uploadTime;
    
    public FileRecord(String id, String originalName, long size, String description, LocalDateTime uploadTime) {
        this.id = id;
        this.originalName = originalName;
        this.size = size;
        this.description = description;
        this.uploadTime = uploadTime;
    }
    
    // Getters and Setters
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    
    public String getOriginalName() { return originalName; }
    public void setOriginalName(String originalName) { this.originalName = originalName; }
    
    public long getSize() { return size; }
    public void setSize(long size) { this.size = size; }
    
    public String getDescription() { return description; }
    public void setDescription(String description) { this.description = description; }
    
    public LocalDateTime getUploadTime() { return uploadTime; }
    public void setUploadTime(LocalDateTime uploadTime) { this.uploadTime = uploadTime; }
}

3.8 本章小结

3.8.1 核心概念回顾

本章深入探讨了 Quarkus 中 RESTful Web 服务开发的核心技术:

  1. JAX-RS 基础:掌握了资源类、HTTP 方法注解、路径映射等基本概念
  2. 参数绑定:学习了路径参数、查询参数、表单参数、请求头参数等多种参数绑定方式
  3. 内容协商:了解了媒体类型处理、自定义消息转换器等高级特性
  4. 异常处理:实现了全局异常映射器和标准异常处理机制
  5. 响应式编程:探索了异步响应、流式处理等现代化开发模式

3.8.2 技术要点总结

  • 注解驱动:使用 JAX-RS 注解简化 REST 服务开发
  • 类型安全:利用强类型参数绑定确保 API 的健壮性
  • 内容协商:支持多种媒体类型,提供灵活的数据交换格式
  • 异常处理:统一的异常处理机制,提供一致的错误响应
  • 响应式支持:利用 Mutiny 实现高性能的异步处理

3.8.3 最佳实践

  1. API 设计:遵循 RESTful 设计原则,使用合适的 HTTP 方法和状态码
  2. 参数验证:在服务层进行输入验证,确保数据完整性
  3. 错误处理:提供详细的错误信息,帮助客户端处理异常情况
  4. 性能优化:合理使用异步处理,避免阻塞操作
  5. 安全考虑:验证输入参数,防止安全漏洞

3.8.4 下一章预告

下一章将学习 数据持久化与数据库集成,包括: - Hibernate ORM 与 Panache - 数据库连接配置 - 实体映射和关系管理 - 查询优化和事务处理 - 数据库迁移和版本控制

通过下一章的学习,你将能够构建完整的数据驱动应用程序。