SpringBoot与Apache Ignite整合,实现广告实时竞拍系统

114 阅读7分钟

Apache Ignite的优势

1. 高性能

  • 内存计算:Apache Ignite是一个内存数据库,数据存放在内存中,提供了极低的延迟访问速度。
  • 分布式架构:支持水平扩展,通过集群化部署,可以显著提升系统的吞吐量和处理能力。

2. 灵活性

  • 多种数据模型:支持键值存储、SQL查询、流处理等多种数据模型,能够满足不同场景下的需求。
  • ACID事务支持:确保数据的一致性和完整性,适合需要强一致性的应用。

3. 易用性

  • 丰富的API:提供Java、C++、.NET、Python等多种语言的API接口,方便开发者快速集成。
  • 易于部署:可以通过简单的配置文件进行设置,并且支持自动发现和负载均衡。

4. 强大的功能特性

  • 缓存与持久化:结合了内存缓存的优势和磁盘持久化的稳定性,能够在性能和耐久性之间取得平衡。
  • 实时分析:内置的数据网格技术允许在内存中进行复杂的分析操作,而无需额外的数据移动或转换。
  • 机器学习加速:与MLlib等机器学习库集成良好,可以加速训练过程并提高预测准确性。

代码实操

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>ad-auction-system</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
    </parent>

    <properties>
        <java.version>11</java.version>
        <ignite.version>2.15.0</ignite.version>
    </properties>

    <dependencies>
        <!-- Spring Boot Starter Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- Apache Ignite -->
        <dependency>
            <groupId>org.apache.ignite</groupId>
            <artifactId>ignite-core</artifactId>
            <version>${ignite.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.ignite</groupId>
            <artifactId>ignite-spring-data_2.3</artifactId>
            <version>${ignite.version}</version>
        </dependency>

        <!-- Lombok for concise code -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Spring Boot Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Spring Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
            <scope>runtime</scope>
        </dependency>

        <!-- AspectJ for AOP -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

application.yml

server:
  port:8080

logging:
level:
    root:INFO
    org.springframework.web:DEBUG
    com.example.adauctionsystem:DEBUG

spring:
security:
    user:
      name:admin
      password:admin

app:
jwtSecret:yourJwtSecretKey
jwtExpirationMs:86400000# 24 hours

data:
sql:
    init-mode:always

ignite-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="ignite.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
        <property name="cacheConfiguration">
            <list>
                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="UserProfiles"/>
                    <property name="cacheMode" value="REPLICATED"/>
                </bean>
                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="BidRequests"/>
                    <property name="cacheMode" value="PARTITIONED"/>
                </bean>
                <bean class="org.apache.ignite.configuration.CacheConfiguration">
                    <property name="name" value="WinningBids"/>
                    <property name="cacheMode" value="PARTITIONED"/>
                </bean>
            </list>
        </property>
    </bean>
</beans>

AdAuctionApplication.java

package com.example.adauctionsystem;

import org.apache.ignite.Ignition;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
publicclass AdAuctionApplication {

    public static void main(String[] args) {
        SpringApplication.run(AdAuctionApplication.class, args);
    }

    @Bean
    public void igniteInstance() {
        Ignition.start("classpath:ignite-config.xml");
    }
}

IgniteConfig.java

package com.example.adauctionsystem.config;

import org.apache.ignite.Ignite;
import org.apache.ignite.Ignition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
publicclass IgniteConfig {

    @Bean
    public Ignite igniteInstance() {
        return Ignition.start("classpath:ignite-config.xml");
    }
}

SecurityConfig.java

package com.example.adauctionsystem.config;

import com.example.adauctionsystem.security.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
publicclass SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        returnsuper.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        returnnew BCryptPasswordEncoder();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        returnnew JwtAuthenticationFilter();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

WebConfig.java

package com.example.adauctionsystem.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
publicclass WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

UserProfile.java

package com.example.adauctionsystem.model;

import lombok.Data;

@Data
public class UserProfile {
    private Integer id; // 用户ID
    private String name; // 用户名
    private String interests; // 用户兴趣
}

BidRequest.java

package com.example.adauctionsystem.model;

import lombok.Data;

@Data
public class BidRequest {
    private String requestId; // 竞价请求ID
    private Double bidAmount; // 竞价金额
    private String adContent; // 广告内容
    private Integer userId; // 用户ID
}

WinningBid.java

package com.example.adauctionsystem.model;

import lombok.Data;

@Data
public class WinningBid {
    private String requestId; // 竞价请求ID
    private Double bidAmount; // 胜出的竞价金额
    private String adContent; // 胜出的广告内容
    private Integer winningUserId; // 胜出的用户ID
}

UserRepository.java

package com.example.adauctionsystem.repository;

import com.example.adauctionsystem.model.UserProfile;
import org.apache.ignite.springdata.repository.IgniteRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends IgniteRepository<UserProfile, Integer> {
}

BidRepository.java

package com.example.adauctionsystem.repository;

import com.example.adauctionsystem.model.BidRequest;
import org.apache.ignite.springdata.repository.IgniteRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BidRepository extends IgniteRepository<BidRequest, String> {
}

UserProfileService.java

package com.example.adauctionsystem.service;

import com.example.adauctionsystem.model.UserProfile;
import com.example.adauctionsystem.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
publicclass UserProfileService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 保存用户画像
     *
     * @param userProfile 用户画像对象
     */
    public void saveUserProfile(UserProfile userProfile) {
        userRepository.save(userProfile);
    }

    /**
     * 根据用户ID获取用户画像
     *
     * @param userId 用户ID
     * @return 用户画像对象
     */
    public UserProfile getUserProfileById(int userId) {
        return userRepository.findById(userId).orElse(null);
    }
}

AdBiddingService.java

package com.example.adauctionsystem.service;

import com.example.adauctionsystem.model.BidRequest;
import com.example.adauctionsystem.model.WinningBid;
import com.example.adauctionsystem.repository.BidRepository;
import com.example.adauctionsystem.util.AuctionUtils;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.List;

@Service
publicclass AdBiddingService {

    @Autowired
    private Ignite ignite;

    @Autowired
    private BidRepository bidRepository;

    private IgniteCache<String, BidRequest> bidCache;
    private IgniteCache<String, WinningBid> winningBidCache;

    @PostConstruct
    public void init() {
        bidCache = ignite.getOrCreateCache("BidRequests");
        winningBidCache = ignite.getOrCreateCache("WinningBids");
    }

    /**
     * 提交竞价请求
     *
     * @param bidRequest 竞价请求对象
     */
    public void placeBid(BidRequest bidRequest) {
        bidCache.put(bidRequest.getRequestId(), bidRequest);
        processAuction(bidRequest.getRequestId());
    }

    /**
     * 处理拍卖过程
     *
     * @param requestId 竞价请求ID
     */
    private void processAuction(String requestId) {
        List<BidRequest> bids = bidCache.values().stream()
                .filter(b -> b.getRequestId().equals(requestId))
                .toList();

        if (bids.isEmpty()) return;

        BidRequest highestBid = AuctionUtils.findHighestBid(bids);

        WinningBid winningBid = new WinningBid();
        winningBid.setRequestId(highestBid.getRequestId());
        winningBid.setBidAmount(highestBid.getBidAmount());
        winningBid.setAdContent(highestBid.getAdContent());
        winningBid.setWinningUserId(highestBid.getUserId());

        winningBidCache.put(winningBid.getRequestId(), winningBid);
    }

    /**
     * 获取胜出的竞价
     *
     * @param requestId 竞价请求ID
     * @return 胜出的竞价对象
     */
    public WinningBid getWinningBidForRequest(String requestId) {
        return winningBidCache.get(requestId);
    }
}

AuthService.java

package com.example.adauctionsystem.service;

import com.example.adauctionsystem.dto.AuthResponseDTO;
import com.example.adauctionsystem.dto.LoginRequestDTO;
import com.example.adauctionsystem.dto.SignUpRequestDTO;
import com.example.adauctionsystem.security.JwtTokenProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
publicclass AuthService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * 认证用户并生成JWT令牌
     *
     * @param loginRequest 登录请求对象
     * @return 包含JWT令牌的响应对象
     */
    public AuthResponseDTO authenticateUser(LoginRequestDTO loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = tokenProvider.generateToken(authentication);
        returnnew AuthResponseDTO(jwt);
    }

    /**
     * 注册新用户
     *
     * @param signUpRequest 注册请求对象
     */
    public void registerUser(SignUpRequestDTO signUpRequest) {
        // 这里通常会在数据库中创建一个新的用户
        // 为了简化示例,我们仅模拟注册过程
    }
}

AuctionUtils.java

package com.example.adauctionsystem.util;

import com.example.adauctionsystem.model.BidRequest;

import java.util.Comparator;
import java.util.List;

publicclass AuctionUtils {

    /**
     * 查找最高竞价
     *
     * @param bids 竞价列表
     * @return 最高竞价对象
     */
    public static BidRequest findHighestBid(List<BidRequest> bids) {
        return bids.stream()
                .max(Comparator.comparingDouble(BidRequest::getBidAmount))
                .orElse(null);
    }
}

LoggingAspect.java

package com.example.adauctionsystem.util;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
publicclass LoggingAspect {

    privatefinal Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 方法执行前的日志记录
     *
     * @param joinPoint 连接点对象
     */
    @Before("execution(* com.example.adauctionsystem.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        logger.info("Entering method {} with arguments {}", joinPoint.getSignature().getName(), joinPoint.getArgs());
    }

    /**
     * 方法执行后的日志记录
     *
     * @param joinPoint 连接点对象
     * @param result 返回结果
     */
    @AfterReturning(pointcut = "execution(* com.example.adauctionsystem.service.*.*(..))", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        logger.info("Exiting method {} with result {}", joinPoint.getSignature().getName(), result);
    }
}

UserProfileController.java

package com.example.adauctionsystem.controller;

import com.example.adauctionsystem.model.UserProfile;
import com.example.adauctionsystem.service.UserProfileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
publicclass UserProfileController {

    @Autowired
    private UserProfileService userProfileService;

    /**
     * 创建用户画像
     *
     * @param userProfile 用户画像对象
     * @return 成功响应
     */
    @PostMapping("/")
    public ResponseEntity<Void> createUserProfile(@RequestBody UserProfile userProfile) {
        userProfileService.saveUserProfile(userProfile);
        return ResponseEntity.ok().build();
    }

    /**
     * 根据用户ID获取用户画像
     *
     * @param userId 用户ID
     * @return 用户画像对象或404错误
     */
    @GetMapping("/{userId}")
    public ResponseEntity<UserProfile> getUserProfile(@PathVariable int userId) {
        UserProfile userProfile = userProfileService.getUserProfileById(userId);
        if (userProfile == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(userProfile);
    }
}

AdController.java

package com.example.adauctionsystem.controller;

import com.example.adauctionsystem.model.BidRequest;
import com.example.adauctionsystem.model.WinningBid;
import com.example.adauctionsystem.service.AdBiddingService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/bids")
publicclass AdController {

    @Autowired
    private AdBiddingService adBiddingService;

    /**
     * 提交竞价请求
     *
     * @param bidRequest 竞价请求对象
     * @return 成功响应
     */
    @PostMapping("/")
    public ResponseEntity<Void> placeBid(@RequestBody BidRequest bidRequest) {
        adBiddingService.placeBid(bidRequest);
        return ResponseEntity.ok().build();
    }

    /**
     * 获取胜出的竞价
     *
     * @param requestId 竞价请求ID
     * @return 胜出的竞价对象或404错误
     */
    @GetMapping("/{requestId}/winning")
    public ResponseEntity<WinningBid> getWinningBid(@PathVariable String requestId) {
        WinningBid winningBid = adBiddingService.getWinningBidForRequest(requestId);
        if (winningBid == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(winningBid);
    }
}

AuthController.java

package com.example.adauctionsystem.controller;

import com.example.adauctionsystem.dto.AuthResponseDTO;
import com.example.adauctionsystem.dto.LoginRequestDTO;
import com.example.adauctionsystem.dto.SignUpRequestDTO;
import com.example.adauctionsystem.service.AuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/auth")
publicclass AuthController {

    @Autowired
    private AuthService authService;

    /**
     * 用户登录并获取JWT令牌
     *
     * @param loginRequest 登录请求对象
     * @return 包含JWT令牌的响应对象
     */
    @PostMapping("/signin")
    public ResponseEntity<AuthResponseDTO> authenticateUser(@RequestBody LoginRequestDTO loginRequest) {
        AuthResponseDTO response = authService.authenticateUser(loginRequest);
        return ResponseEntity.ok(response);
    }

    /**
     * 用户注册
     *
     * @param signUpRequest 注册请求对象
     * @return 成功响应
     */
    @PostMapping("/signup")
    public ResponseEntity<Void> registerUser(@RequestBody SignUpRequestDTO signUpRequest) {
        authService.registerUser(signUpRequest);
        return ResponseEntity.ok().build();
    }
}

GlobalExceptionHandler.java

package com.example.adauctionsystem.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

@ControllerAdvice
publicclass GlobalExceptionHandler {

    /**
     * 处理资源未找到异常
     *
     * @param ex 异常对象
     * @return 404错误响应
     */
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
        returnnew ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }

    /**
     * 处理通用异常
     *
     * @param ex 异常对象
     * @return 500错误响应
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResponseEntity<String> handleException(Exception ex) {
        returnnew ResponseEntity<>("Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

ResourceNotFoundException.java

package com.example.adauctionsystem.exception;

/**
 * 自定义资源未找到异常类
 */
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

AuthResponseDTO.java

package com.example.adauctionsystem.dto;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 认证响应数据传输对象
 */
@Data
@AllArgsConstructor
public class AuthResponseDTO {
    private String accessToken; // JWT访问令牌
}

LoginRequestDTO.java

package com.example.adauctionsystem.dto;

import lombok.Data;

/**
 * 登录请求数据传输对象
 */
@Data
public class LoginRequestDTO {
    private String username; // 用户名
    private String password; // 密码
}

SignUpRequestDTO.java

package com.example.adauctionsystem.dto;

import lombok.Data;

/**
 * 注册请求数据传输对象
 */
@Data
public class SignUpRequestDTO {
    private String username; // 用户名
    private String password; // 密码
}

JwtAuthenticationFilter.java

package com.example.adauctionsystem.security;

import com.example.adauctionsystem.service.AuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * JWT认证过滤器
 */
publicclass JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private AuthService authService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(request);

            if (jwt != null && tokenProvider.validateToken(jwt)) {
                Long userId = tokenProvider.getUserIdFromJWT(jwt);

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userId, null, authService.loadUserById(userId).getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    /**
     * 从请求头中提取JWT令牌
     *
     * @param request HTTP请求对象
     * @return JWT令牌字符串
     */
    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7, bearerToken.length());
        }
        returnnull;
    }
}

JwtTokenProvider.java

package com.example.adauctionsystem.security;

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * JWT令牌提供者
 */
@Component
publicclass JwtTokenProvider {

    @Value("${app.jwtSecret}")
    private String jwtSecret;

    @Value("${app.jwtExpirationMs}")
    privateint jwtExpirationMs;

    /**
     * 生成JWT令牌
     *
     * @param authentication 认证对象
     * @return JWT令牌字符串
     */
    public String generateToken(Authentication authentication) {
        UserDetails userDetails = (UserDetails) authentication.getPrincipal();

        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationMs);

        return Jwts.builder()
                .setSubject(Long.toString(((UserDetailsImpl) userDetails).getId()))
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    /**
     * 从JWT令牌中提取用户ID
     *
     * @param token JWT令牌字符串
     * @return 用户ID
     */
    public Long getUserIdFromJWT(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();

        return Long.parseLong(claims.getSubject());
    }

    /**
     * 验证JWT令牌有效性
     *
     * @param authToken JWT令牌字符串
     * @return 是否有效
     */
    public boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            returntrue;
        } catch (MalformedJwtException ex) {
            logger.error("Invalid JWT token");
        } catch (ExpiredJwtException ex) {
            logger.error("Expired JWT token");
        } catch (UnsupportedJwtException ex) {
            logger.error("Unsupported JWT token");
        } catch (IllegalArgumentException ex) {
            logger.error("JWT claims string is empty.");
        }
        returnfalse;
    }
}

UserDetailsServiceImpl.java

package com.example.adauctionsystem.security;

import com.example.adauctionsystem.model.UserProfile;
import com.example.adauctionsystem.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * 用户详细信息服务实现类
 */
@Service
publicclass UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 根据用户名加载用户详情
     *
     * @param username 用户名
     * @return 用户详情对象
     * @throws UsernameNotFoundException 用户未找到异常
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserProfile user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));

        return UserDetailsImpl.build(user);
    }

    /**
     * 根据用户ID加载用户详情
     *
     * @param id 用户ID
     * @return 用户详情对象
     * @throws UsernameNotFoundException 用户未找到异常
     */
    public UserDetails loadUserById(Long id) {
        UserProfile user = userRepository.findById(id.intValue())
                .orElseThrow(() -> new UsernameNotFoundException("User Not Found with id: " + id));

        return UserDetailsImpl.build(user);
    }
}

data.sql

INSERT INTO UserProfiles (id, name, interests) VALUES (1, 'John Doe', 'sports,travel');
INSERT INTO UserProfiles (id, name, interests) VALUES (2, 'Jane Smith', 'music,gaming');
1.2.

测试

创建用户画像

POST http://localhost:8080/api/users/request Body:

{
    "id": 1,
    "name": "John Doe",
    "interests": "sports,travel"
}

Respons Code: 200 OK。

获取用户画像

GET http://localhost:8080/api/users/1Respons:

{
    "id": 1,
    "name": "John Doe",
    "interests": "sports,travel"
}

提交竞价请求

POST  http://localhost:8080/api/bids/

request Body:

{
    "requestId": "req1",
    "bidAmount": 10.0,
    "adContent": "Ad Content 1",
    "userId": 1
}

Respons Code: 200 OK

获取胜出竞价

GET http://localhost:8080/api/bids/req1/winning

Respons:

{
    "requestId": "req1",
    "bidAmount": 10.0,
    "adContent": "Ad Content 1",
    "winningUserId": 1
}