package com.yjs.travel.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {


@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
}
}

package com.yjs.travel.config;

import com.yjs.travel.filter.JwtRequestFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig {


// 统一使用List.of
private static final List<String> ALLOWED_ORIGINS = List.of(
        "http://localhost:5173", "http://127.0.0.1:5173", "https://your-production-domain.com"
);
private static final List<String> ALLOWED_METHODS = List.of("GET", "POST", "PUT", "DELETE", "OPTIONS");
private static final List<String> ALLOWED_HEADERS = List.of("Authorization", "Cache-Control", "Content-Type");

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtRequestFilter jwtRequestFilter) throws Exception {
    return http
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers(
                            "/api/auth/**",
                            "/api/area/**",
                            "/public/**",
                            "/error",
                            "/v3/api-docs/**",
                            "/swagger-ui/**"
                    ).permitAll()
                    .anyRequest().authenticated()
            )
            .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(ALLOWED_ORIGINS);
    config.setAllowedMethods(ALLOWED_METHODS);
    config.setAllowedHeaders(ALLOWED_HEADERS);
    config.setAllowCredentials(true);
    config.setMaxAge(3600L);

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

@Bean
public BCryptPasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
}

// AreaController.java
package com.yjs.travel.controller;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.yjs.travel.entity.Area;
import com.yjs.travel.service.AreaService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.function.Supplier;

@RestController
@RequestMapping("/api/area")
@RequiredArgsConstructor
public class AreaController {
private final AreaService areaService;


// 统一异常处理函数
private <T> ResponseEntity<T> handleRequest(Supplier<ResponseEntity<T>> supplier) {
    try {
        return supplier.get();
    } catch (RuntimeException e) {
        return ResponseEntity.badRequest().build();
    } catch (Exception e) {
        return ResponseEntity.internalServerError().build();
    }
}

// 修改：添加分页参数，返回分页对象
@GetMapping
public ResponseEntity<IPage<Area>> getAllAreas(
        @RequestParam(defaultValue = "1") int pageNum,
        @RequestParam(defaultValue = "10") int pageSize) {
    return handleRequest(() -> ResponseEntity.ok(areaService.getAllAreas(pageNum, pageSize)));
}

@GetMapping("/{id}")
public ResponseEntity<Area> getAreaById(@PathVariable Integer id) {
    return handleRequest(() -> {
        Area area = areaService.getAreaById(id);
        return area != null ? ResponseEntity.ok(area) : ResponseEntity.notFound().build();
    });
}

@PostMapping
public ResponseEntity<Area> addArea(@RequestBody Area area) {
    area.setAreaId(null);
    return handleRequest(() -> ResponseEntity.ok(areaService.saveArea(area)));
}

@PutMapping("/{id}")
public ResponseEntity<Area> updateArea(@PathVariable Integer id, @RequestBody Area area) {
    area.setAreaId(id);
    return handleRequest(() -> {
        Area updatedArea = areaService.updateArea(area);
        return updatedArea != null ?
                ResponseEntity.ok(updatedArea) :
                ResponseEntity.notFound().build();
    });
}

@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteArea(@PathVariable Integer id) {
    return handleRequest(() -> {
        areaService.deleteArea(id);
        return ResponseEntity.noContent().build();
    });
}
}

package com.yjs.travel.controller;

import com.yjs.travel.entity.User;
import com.yjs.travel.service.UserService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final UserService userService;


private static final String REGISTER_SUCCESS = "注册成功";
private static final String PASSWORD_MISMATCH = "两次密码不一致";

@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest request) {
    if (!request.getPassword().equals(request.getConfirmPassword())) {
        return ResponseEntity.badRequest().body(PASSWORD_MISMATCH);
    }

    try {
        userService.registerUser(request.userName, request.password, request.role);
        return ResponseEntity.ok(REGISTER_SUCCESS);
    } catch (RuntimeException e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    }
}

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
    try {
        return ResponseEntity.ok(userService.authenticate(
                request.userName, request.password, request.role));
    } catch (RuntimeException e) {
        return ResponseEntity.badRequest().body(e.getMessage());
    }
}

@Data
public static class RegisterRequest {
    private String userName;
    private String password;
    private String confirmPassword;
    private String role = "user";
}

@Data
public static class LoginRequest {
    private String userName;
    private String password;
    private String role;
}
}

// src/main/java/com/yjs/travel/entity/Area.java
package com.yjs.travel.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

@Data
@TableName("area")
public class Area {
@TableId(type = IdType.AUTO)
private Integer areaId;


private String continent;
private String country;
private String province;
private String city;

@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

// src/main/java/com/yjs/travel/entity/User.java
package com.yjs.travel.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("users")
public class User {
@TableId(type = IdType.AUTO)
private Long userId;


@TableField("user_name")
private String userName;

private String nickName;
private String email;
private String phoneNumber;
private String gender = "0";
private LocalDate birthday;
private String password;
private String role = "user";

@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}

package com.yjs.travel.filter;

import com.yjs.travel.service.UserService;
import com.yjs.travel.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@RequiredArgsConstructor
public class JwtRequestFilter extends OncePerRequestFilter {


private final JwtUtil jwtUtil;
private final UserService userService;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
        throws ServletException, IOException {

    String header = request.getHeader("Authorization");
    if (header != null && header.startsWith("Bearer ")) {
        String jwt = header.substring(7);
        String username = jwtUtil.extractUsername(jwt);

        if (username != null && noExistingAuthentication() && jwtValidForUser(jwt, username)) {
            setSecurityAuthentication(username, request);
        }
    }
    chain.doFilter(request, response);
}

private boolean noExistingAuthentication() {
    return SecurityContextHolder.getContext().getAuthentication() == null;
}

private boolean jwtValidForUser(String jwt, String username) {
    return jwtUtil.validateToken(jwt) && userService.validateToken(username, jwt);
}

private void setSecurityAuthentication(String username, HttpServletRequest request) {
    UserDetails userDetails = userService.loadUserByUsername(username);

    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
            userDetails, null, userDetails.getAuthorities());

    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
    SecurityContextHolder.getContext().setAuthentication(authentication);
}
}

// src/main/java/com/yjs/travel/handler/MyMetaObjectHandler.java
package com.yjs.travel.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {


@Override
public void insertFill(MetaObject metaObject) {
    this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}

@Override
public void updateFill(MetaObject metaObject) {
    this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}

// src/main/java/com/yjs/travel/mapper/AreaMapper.java
package com.yjs.travel.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yjs.travel.entity.Area;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.Optional;

@Mapper
public interface AreaMapper extends BaseMapper {


@Select("SELECT * FROM area " +
        "WHERE continent = #{continent} " +
        "AND country = #{country} " +
        "AND province = #{province} " +
        "AND city = #{city}")
Optional<Area> findByContinentAndCountryAndProvinceAndCity(
        @Param("continent") String continent,
        @Param("country") String country,
        @Param("province") String province,
        @Param("city") String city);
}

// src/main/java/com/yjs/travel/mapper/UserMapper.java
package com.yjs.travel.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yjs.travel.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.Optional;

@Mapper
public interface UserMapper extends BaseMapper


// 添加注解实现方法
@Select("SELECT * FROM users WHERE user_name = #{userName}")
Optional<User> findByUserName(String userName);
}

// AreaService.java
package com.yjs.travel.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yjs.travel.entity.Area;
import com.yjs.travel.mapper.AreaMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Optional;

@Service
@RequiredArgsConstructor
public class AreaService {
private final AreaMapper areaMapper;


// 修改：增加分页查询方法
public Page<Area> getAllAreas(int pageNum, int pageSize) {
    Page<Area> page = new Page<>(pageNum, pageSize);
    return areaMapper.selectPage(page, null);
}

public Area saveArea(Area area) {
    validateAreaUniqueness(area);
    if (area.getAreaId() == null) {
        areaMapper.insert(area);
    } else {
        areaMapper.updateById(area);
    }
    return area;
}

public Area getAreaById(Integer areaId) {
    return Optional.ofNullable(areaMapper.selectById(areaId))
            .orElseThrow(() -> new RuntimeException("地区不存在"));
}

public Area updateArea(Area area) {
    if (areaMapper.selectById(area.getAreaId()) == null) {
        throw new RuntimeException("地区不存在，无法更新");
    }
    return saveArea(area);
}

public void deleteArea(Integer areaId) {
    areaMapper.deleteById(areaId);
}

private void validateAreaUniqueness(Area area) {
    Optional<Area> existing = areaMapper.findByContinentAndCountryAndProvinceAndCity(
            area.getContinent(), area.getCountry(), area.getProvince(), area.getCity()
    );
    if (existing.isPresent() && !sameArea(area, existing.get())) {
        throw new RuntimeException("该地区信息已存在");
    }
}

private boolean sameArea(Area area1, Area area2) {
    return area1.getAreaId() != null && area1.getAreaId().equals(area2.getAreaId());
}
}

// src/main/java/com/yjs/travel/service/UserService.java
package com.yjs.travel.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yjs.travel.entity.User;
import com.yjs.travel.mapper.UserMapper;
import com.yjs.travel.utils.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.concurrent.TimeUnit;

@Service
@RequiredArgsConstructor
public class UserService {


private final UserMapper userMapper;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
private final RedisTemplate<String, String> redisTemplate;

public User registerUser(String userName, String password, String role) {
    if (userMapper.exists(new QueryWrapper<User>().eq("user_name", userName))) {
        throw new RuntimeException("用户名已存在");
    }

    User user = new User();
    user.setUserName(userName);
    user.setNickName(userName);
    user.setPassword(passwordEncoder.encode(password));
    user.setRole(role);

    userMapper.insert(user);
    return user;
}

public String authenticate(String userName, String password, String role) {
    User user = getUserByUserName(userName);

    if (!isPasswordValid(password, user) || !isRoleValid(role, user)) {
        throw new RuntimeException("登录失败,密码或身份错误");
    }

    String token = jwtUtil.generateToken(userName);
    storeTokenInRedis(userName, token);
    return token;
}

public boolean validateToken(String userName, String token) {
    String storedToken = redisTemplate.opsForValue().get(userName);
    return token.equals(storedToken);
}

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = getUserByUserName(username);

    return new org.springframework.security.core.userdetails.User(
            user.getUserName(),
            user.getPassword(),
            Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().toUpperCase()))
    );
}

private User getUserByUserName(String userName) {
    return userMapper.findByUserName(userName)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + userName));
}

private boolean isPasswordValid(String password, User user) {
    return passwordEncoder.matches(password, user.getPassword());
}

private boolean isRoleValid(String role, User user) {
    return role.equals(user.getRole());
}

private void storeTokenInRedis(String userName, String token) {
    redisTemplate.opsForValue().set(userName, token, 2, TimeUnit.HOURS);
}
}

package com.yjs.travel.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Component
public class JwtUtil {


@Value("${jwt.secret}")
private String secret;

@Value("${jwt.expiration}")
private long expiration;

// 生成密钥
private SecretKey getSigningKey() {
    return Keys.hmacShaKeyFor(secret.getBytes());
}

// 生成JWT令牌
public String generateToken(String userName) {
    Map<String, Object> claims = new HashMap<>();
    return createToken(claims, userName);
}

private String createToken(Map<String, Object> claims, String subject) {
    return Jwts.builder()
            .setClaims(claims)
            .setSubject(subject)
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(getSigningKey(), SignatureAlgorithm.HS256)
            .compact();
}

// 从令牌中提取用户名
public String extractUsername(String token) {
    return getClaims(token).getSubject();
}

// 验证令牌有效性
public boolean validateToken(String token) {
    try {
        return !isTokenExpired(token);
    } catch (Exception e) {
        return false;
    }
}

// 检查令牌是否过期
private boolean isTokenExpired(String token) {
    return getClaims(token).getExpiration().before(new Date());
}

// 获取声明
private Claims getClaims(String token) {
    return Jwts.parserBuilder()
            .setSigningKey(getSigningKey())
            .build()
            .parseClaimsJws(token)
            .getBody();
}
}

// src/main/java/com/yjs/travel/TrackTravelApplication.java
package com.yjs.travel;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.yjs.travel.mapper")
public class TrackTravelApplication {
public static void main(String[] args) {
SpringApplication.run(TrackTravelApplication.class, args);
}
}

spring:
datasource:
url: jdbc:mysql://localhost:3306/track?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

jackson:
time-zone: Asia/Shanghai
date-format: yyyy-MM-dd HH:mm:ss

data:
redis:
host: localhost
port: 6379

JWT配置
jwt:
secret: your-strong-secret-key-with-at-least-256-bits # 替换为安全的密钥
expiration: 7200000 # 2小时（单位毫秒）


    <!--mysql依赖-->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- MyBatis Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
        <version>3.5.12</version>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-jsqlparser</artifactId>
        <version>3.5.12</version>
    </dependency>

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

    <!-- BCrypt -->
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-crypto</artifactId>
    </dependency>

    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </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>

    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>jakarta.servlet</groupId>
        <artifactId>jakarta.servlet-api</artifactId>
        <version>5.0.0</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <source>17</source>
                <target>17</target>
                <encoding>UTF-8</encoding>
                <compilerArgs>
                    <arg>-parameters</arg>
                </compilerArgs>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>${spring-boot.version}</version>
            <configuration>
                <mainClass>com.yjs.travel.TrackTravelApplication</mainClass>
                <skip>true</skip>
            </configuration>
            <executions>
                <execution>
                    <id>repackage</id>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
根据上面代码，我想把项目的安全配置Spring Security转化成Sa-Token，保留bc加密，给我完整代码