SpringBoot與Apache Ignite整合,實(shí)現(xiàn)廣告實(shí)時(shí)競(jìng)拍系統(tǒng)
傳統(tǒng)的廣告投放方式往往依賴于人工操作和簡(jiǎn)單的規(guī)則引擎,無(wú)法高效地處理大規(guī)模的廣告競(jìng)拍請(qǐng)求。因此,我們需要一個(gè)智能高效的廣告競(jìng)拍系統(tǒng)來(lái)解決這個(gè)問(wèn)題。
哪些公司使用Apache Ignite?
- AdColony:移動(dòng)廣告平臺(tái),使用 Ignite 實(shí)現(xiàn)廣告投放的高效管理和優(yōu)化。
- eBay:在線拍賣(mài)網(wǎng)站,使用 Ignite 加速搜索和推薦系統(tǒng)。
- US Department of Energy (DOE):美國(guó)能源部,使用 Ignite 進(jìn)行科學(xué)研究和大數(shù)據(jù)分析。
- NASA Jet Propulsion Laboratory:美國(guó)國(guó)家航空航天局噴氣推進(jìn)實(shí)驗(yàn)室,利用 Ignite 支持航天任務(wù)的數(shù)據(jù)處理。
- Coinbase:加密貨幣交易所,使用 Ignite 提高交易速度和安全性。
- Blockchain.com:區(qū)塊鏈技術(shù)和金融服務(wù)提供商,利用 Ignite 實(shí)現(xiàn)高效的數(shù)據(jù)存儲(chǔ)和處理。
- Wayfair:家居用品零售商,利用 Ignite 提供高效的庫(kù)存管理和個(gè)性化推薦。
- The Trade Desk:廣告交易平臺(tái),利用 Ignite 支持實(shí)時(shí)競(jìng)價(jià)和廣告效果分析。
- Santander Bank:西班牙第二大銀行,利用 Ignite 提升支付系統(tǒng)的性能和可靠性。
- Barclays Bank:英國(guó)大型銀行,采用 Ignite 實(shí)現(xiàn)低延遲的市場(chǎng)數(shù)據(jù)分析。
Apache Ignite的優(yōu)勢(shì)
1. 高性能
- 內(nèi)存計(jì)算:Apache Ignite是一個(gè)內(nèi)存數(shù)據(jù)庫(kù),數(shù)據(jù)存放在內(nèi)存中,提供了極低的延遲訪問(wèn)速度。
- 分布式架構(gòu):支持水平擴(kuò)展,通過(guò)集群化部署,可以顯著提升系統(tǒng)的吞吐量和處理能力。
2. 靈活性
- 多種數(shù)據(jù)模型:支持鍵值存儲(chǔ)、SQL查詢、流處理等多種數(shù)據(jù)模型,能夠滿足不同場(chǎng)景下的需求。
- ACID事務(wù)支持:確保數(shù)據(jù)的一致性和完整性,適合需要強(qiáng)一致性的應(yīng)用。
3. 易用性
- 豐富的API:提供Java、C++、.NET、Python等多種語(yǔ)言的API接口,方便開(kāi)發(fā)者快速集成。
- 易于部署:可以通過(guò)簡(jiǎn)單的配置文件進(jìn)行設(shè)置,并且支持自動(dòng)發(fā)現(xiàn)和負(fù)載均衡。
4. 強(qiáng)大的功能特性
- 緩存與持久化:結(jié)合了內(nèi)存緩存的優(yōu)勢(shì)和磁盤(pán)持久化的穩(wěn)定性,能夠在性能和耐久性之間取得平衡。
- 實(shí)時(shí)分析:內(nèi)置的數(shù)據(jù)網(wǎng)格技術(shù)允許在內(nèi)存中進(jìn)行復(fù)雜的分析操作,而無(wú)需額外的數(shù)據(jù)移動(dòng)或轉(zhuǎn)換。
- 機(jī)器學(xué)習(xí)加速:與MLlib等機(jī)器學(xué)習(xí)庫(kù)集成良好,可以加速訓(xùn)練過(guò)程并提高預(yù)測(cè)準(zhǔn)確性。
代碼實(shí)操
<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; // 競(jìng)價(jià)請(qǐng)求ID
private Double bidAmount; // 競(jìng)價(jià)金額
private String adContent; // 廣告內(nèi)容
private Integer userId; // 用戶ID
}
WinningBid.java
package com.example.adauctionsystem.model;
import lombok.Data;
@Data
public class WinningBid {
private String requestId; // 競(jìng)價(jià)請(qǐng)求ID
private Double bidAmount; // 勝出的競(jìng)價(jià)金額
private String adContent; // 勝出的廣告內(nèi)容
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;
/**
* 保存用戶畫(huà)像
*
* @param userProfile 用戶畫(huà)像對(duì)象
*/
public void saveUserProfile(UserProfile userProfile) {
userRepository.save(userProfile);
}
/**
* 根據(jù)用戶ID獲取用戶畫(huà)像
*
* @param userId 用戶ID
* @return 用戶畫(huà)像對(duì)象
*/
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");
}
/**
* 提交競(jìng)價(jià)請(qǐng)求
*
* @param bidRequest 競(jìng)價(jià)請(qǐng)求對(duì)象
*/
public void placeBid(BidRequest bidRequest) {
bidCache.put(bidRequest.getRequestId(), bidRequest);
processAuction(bidRequest.getRequestId());
}
/**
* 處理拍賣(mài)過(guò)程
*
* @param requestId 競(jìng)價(jià)請(qǐng)求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);
}
/**
* 獲取勝出的競(jìng)價(jià)
*
* @param requestId 競(jìng)價(jià)請(qǐng)求ID
* @return 勝出的競(jìng)價(jià)對(duì)象
*/
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;
/**
* 認(rèn)證用戶并生成JWT令牌
*
* @param loginRequest 登錄請(qǐng)求對(duì)象
* @return 包含JWT令牌的響應(yīng)對(duì)象
*/
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);
}
/**
* 注冊(cè)新用戶
*
* @param signUpRequest 注冊(cè)請(qǐng)求對(duì)象
*/
public void registerUser(SignUpRequestDTO signUpRequest) {
// 這里通常會(huì)在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)新的用戶
// 為了簡(jiǎn)化示例,我們僅模擬注冊(cè)過(guò)程
}
}
AuctionUtils.java
package com.example.adauctionsystem.util;
import com.example.adauctionsystem.model.BidRequest;
import java.util.Comparator;
import java.util.List;
publicclass AuctionUtils {
/**
* 查找最高競(jìng)價(jià)
*
* @param bids 競(jìng)價(jià)列表
* @return 最高競(jìng)價(jià)對(duì)象
*/
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());
/**
* 方法執(zhí)行前的日志記錄
*
* @param joinPoint 連接點(diǎn)對(duì)象
*/
@Before("execution(* com.example.adauctionsystem.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
logger.info("Entering method {} with arguments {}", joinPoint.getSignature().getName(), joinPoint.getArgs());
}
/**
* 方法執(zhí)行后的日志記錄
*
* @param joinPoint 連接點(diǎn)對(duì)象
* @param result 返回結(jié)果
*/
@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;
/**
* 創(chuàng)建用戶畫(huà)像
*
* @param userProfile 用戶畫(huà)像對(duì)象
* @return 成功響應(yīng)
*/
@PostMapping("/")
public ResponseEntity<Void> createUserProfile(@RequestBody UserProfile userProfile) {
userProfileService.saveUserProfile(userProfile);
return ResponseEntity.ok().build();
}
/**
* 根據(jù)用戶ID獲取用戶畫(huà)像
*
* @param userId 用戶ID
* @return 用戶畫(huà)像對(duì)象或404錯(cuò)誤
*/
@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;
/**
* 提交競(jìng)價(jià)請(qǐng)求
*
* @param bidRequest 競(jìng)價(jià)請(qǐng)求對(duì)象
* @return 成功響應(yīng)
*/
@PostMapping("/")
public ResponseEntity<Void> placeBid(@RequestBody BidRequest bidRequest) {
adBiddingService.placeBid(bidRequest);
return ResponseEntity.ok().build();
}
/**
* 獲取勝出的競(jìng)價(jià)
*
* @param requestId 競(jìng)價(jià)請(qǐng)求ID
* @return 勝出的競(jìng)價(jià)對(duì)象或404錯(cuò)誤
*/
@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 登錄請(qǐng)求對(duì)象
* @return 包含JWT令牌的響應(yīng)對(duì)象
*/
@PostMapping("/signin")
public ResponseEntity<AuthResponseDTO> authenticateUser(@RequestBody LoginRequestDTO loginRequest) {
AuthResponseDTO response = authService.authenticateUser(loginRequest);
return ResponseEntity.ok(response);
}
/**
* 用戶注冊(cè)
*
* @param signUpRequest 注冊(cè)請(qǐng)求對(duì)象
* @return 成功響應(yīng)
*/
@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 異常對(duì)象
* @return 404錯(cuò)誤響應(yīng)
*/
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleResourceNotFoundException(ResourceNotFoundException ex) {
returnnew ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
}
/**
* 處理通用異常
*
* @param ex 異常對(duì)象
* @return 500錯(cuò)誤響應(yīng)
*/
@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;
/**
* 認(rèn)證響應(yīng)數(shù)據(jù)傳輸對(duì)象
*/
@Data
@AllArgsConstructor
public class AuthResponseDTO {
private String accessToken; // JWT訪問(wèn)令牌
}
LoginRequestDTO.java
package com.example.adauctionsystem.dto;
import lombok.Data;
/**
* 登錄請(qǐng)求數(shù)據(jù)傳輸對(duì)象
*/
@Data
public class LoginRequestDTO {
private String username; // 用戶名
private String password; // 密碼
}
SignUpRequestDTO.java
package com.example.adauctionsystem.dto;
import lombok.Data;
/**
* 注冊(cè)請(qǐng)求數(shù)據(jù)傳輸對(duì)象
*/
@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認(rèn)證過(guò)濾器
*/
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);
}
/**
* 從請(qǐng)求頭中提取JWT令牌
*
* @param request HTTP請(qǐng)求對(duì)象
* @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 認(rèn)證對(duì)象
* @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());
}
/**
* 驗(yàn)證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;
/**
* 用戶詳細(xì)信息服務(wù)實(shí)現(xiàn)類
*/
@Service
publicclass UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
/**
* 根據(jù)用戶名加載用戶詳情
*
* @param username 用戶名
* @return 用戶詳情對(duì)象
* @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);
}
/**
* 根據(jù)用戶ID加載用戶詳情
*
* @param id 用戶ID
* @return 用戶詳情對(duì)象
* @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');
測(cè)試
創(chuàng)建用戶畫(huà)像
POST http://localhost:8080/api/users/request Body:
{
"id": 1,
"name": "John Doe",
"interests": "sports,travel"
}
Respons Code: 200 OK。
獲取用戶畫(huà)像
GET http://localhost:8080/api/users/1Respons:
{
"id": 1,
"name": "John Doe",
"interests": "sports,travel"
}
提交競(jìng)價(jià)請(qǐng)求
POST http://localhost:8080/api/bids/
request Body:
{
"requestId": "req1",
"bidAmount": 10.0,
"adContent": "Ad Content 1",
"userId": 1
}
Respons Code: 200 OK
獲取勝出競(jìng)價(jià)
GET http://localhost:8080/api/bids/req1/winning
Respons:
{
"requestId": "req1",
"bidAmount": 10.0,
"adContent": "Ad Content 1",
"winningUserId": 1
}