静态资源映射-spring整合

由于我们在做springboot项目时,用的前后端分离

,处理原则就变成了"前端处理托管静态资源,后端仅提供API接口",避免后端直接承载静态资源存储和分发

前后端分离的静态资源分工

  • 前端:负责静态资源的构建、存储和分发

  • 后端:仅提供JSON格式API接口,不处理前端静态资源的渲染和转发

  • 提高静态资源访问速度

  • 静态资源映射是在WebMvcConfigurer这个接口里的addResourceHandlers方法

java 复制代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 本地静态资源目录(如 D:/uploads/ 下的图片)
        String localResourcePath = "file:D:/uploads/";
        // 前端访问路径:http://localhost:8081/static/**
        registry.addResourceHandler("/static/**")
                .addResourceLocations(localResourcePath)
                // 缓存禁用(开发环境避免浏览器缓存)
                .setCachePeriod(0);
    }
  • 前端访问示例:http://localhost:8081/static/2024/05/12/xxx.jpg(对应本地 D:/uploads/2024/05/12/xxx.jpg)。

用一个添加用户的案例,用户有头像上传功能

Controller
java 复制代码
 @PostMapping("/insert")
    public Result addUser(
            @Validated @RequestPart("userDTO")UserDTO userDTO,
            @RequestPart(value = "avatar",required = false)MultipartFile avatar){
        //头像上传,是要用MultipartFile类型的,,用@RequestPart方式
            try {
                Result result = new Result();
                userDTO.setAvatar(avatar);
                User user = userService.addUserByUser(userDTO);
                UserVo userVo = new UserVo();
                BeanUtils.copyProperties(user,userVo);
                String msg = String.format("用户【%s】添加成功,初始密码为手机号后六位:%s",
                        user.getAccount(), user.getTel().substring(user.getTel().length() - 6));
                result.setCode(200);
                result.setMsg(msg);
                result.setData(userVo);
                return result;
            }catch (IllegalArgumentException e){
                return Result.error(e.getMessage());
            } catch (IOException e) {
               return Result.error("头像上传失败");
            }catch (RuntimeException e){
                e.printStackTrace();
                return Result.error("添加用户失败");
            }catch (Exception e){
                log.error("用户添加未知异常",e);
                return Result.error("系统异常,联系管理员");
            }
    }
Impl实现类
java 复制代码
@Override
    public User addUserByUser(UserDTO userDTO) throws IOException {
        boolean exists = this.lambdaQuery().eq(User::getAccount, userDTO.getAccount())
                .exists();
        if(exists){
            throw new IllegalArgumentException("用户名已存在");
        }
        //上传头像,参数传进来之后通过fileUploadUtil工具类来处理上传文件,
        String avatar = fileUploadUtil.uploadAvatar(userDTO.getAvatar());
​
        //处理密码
        String tel = userDTO.getTel();
        if(tel.length()<6){
            throw new IllegalArgumentException("手机号长度不足6位无法生成初始密码");
        }
        String originPwd = tel.substring(tel.length()-6);
        //转换DTD为实体类
        User user = new User();
        BeanUtils.copyProperties(userDTO,user);
        // 设置密码
        user.setPwd(originPwd);
        user.setAvatar(avatar);
        user.setRegisterTime(LocalDateTime.now());
        user.setDelflag("0");
        boolean save = save(user);
        if(save){
            System.out.println("用户["+user.getAccount()+"]初始密码:"+originPwd);
        }else{
            throw new RuntimeException("添加异常");
        }
        //保存用户角色关联关系
        Integer roleId = userDTO.getRoleId();
        if(roleId!=null){
            Role role = roleDao.selectById(roleId);
            if(role==null || "1".equals(role.getDelflag())){
                throw new IllegalArgumentException("选择的角儿不存在或已删除");
            }
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getUserId());
            userRole.setRoleId(roleId);
            userRoleDao.insert(userRole);
        }else {
            throw new IllegalArgumentException("请选择用户角色");
        }
        return user;
    }
​
上传文件工具类

传文件之前可以在yml配置文件里设置路径

bash 复制代码
file:
  upload:
    base-path: D:/images/campus/   # 本地存储路径
    access-path: /images/partners   # 前端访问的URL前缀
    default-avatar: /api/avatar/default.jpg  # 手动放在base-path里的默认头像
java 复制代码
@Component
public class FileUploadUtil {
    @Value("${file.upload.base-path}")
    private String basePath;
    @Value("${file.upload.access-path}")
    private String accessPath;
    @Value("${file.upload.default-avatar}")
    private String defaultAvatar;
​
    public String uploadAvatar(MultipartFile avatarFile) throws IOException {
        if(avatarFile ==null || avatarFile.isEmpty()){
            return defaultAvatar;
        }
        //校验文件类型
        String originalFilename= avatarFile.getOriginalFilename();
        if(originalFilename ==null){
            throw new IllegalArgumentException("上传文件无后缀名,不支持该格式");
        }
        String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")).toLowerCase();
        if(!".jpg,.jpeg,.png,.gif".contains(suffix)){
            throw new IllegalArgumentException("仅支持jpg、jpeg、png、gif格式的头像");
        }
        //生成唯一文件名
        String fileName = UUID.randomUUID().toString() + suffix;
​
        File storageDir  = new File(basePath);
        if(!storageDir.exists()){
            storageDir.mkdirs();
        }
        //保存文件
        File destFile  = new File(basePath+fileName);
        avatarFile.transferTo(destFile);
        //生成访问路径
        return accessPath+fileName;
    }

WebConfig

java 复制代码
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
​
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 虚拟路径 /images/partners/** 映射到本地路径 D:\images\campus
        registry.addResourceHandler("/images/partners/**")
                .addResourceLocations("file:D:/images/campus");
    }
如果有security
java 复制代码
 @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth->
                                auth
                                        .requestMatchers("/user/login").permitAll()
 //给静态资源通过
                                       
                                       .requestMatchers("/images/partners/**").permitAll()
                                        .anyRequest().permitAll()
​
                )
                .cors(cors->cors.configurationSource(corsConfigurationSource()))
                .csrf(csrf->csrf.disable());
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

记得加 前后端的跨域

我是在security里面定义了

复制代码
  
java 复制代码
  // 定义CORS具体规则
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // 允许的前端域名(生产环境需指定具体域名,避免*)
        configuration.setAllowedOrigins(Collections.singletonList("*"));
        // 允许的请求方法(GET、POST等)
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        // 允许的请求头(包括自定义头,如JWT的Authorization)
        configuration.setAllowedHeaders(Collections.singletonList("*"));
        // 允许前端获取的响应头
        configuration.setExposedHeaders(Arrays.asList("Authorization"));
        // 是否允许携带Cookie(跨域请求默认不携带,如需开启需前后端配合)
        configuration.setAllowCredentials(false);
        // 预检请求的缓存时间(避免频繁预检)
        configuration.setMaxAge(3600L);
​
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 对所有接口生效
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
​
相关推荐
大佐不会说日语~1 小时前
基于Spring AI Alibaba的AI聊天系统中,流式输出暂停时出现重复插入问题的分析与解决
java·人工智能·spring
0和1的舞者1 小时前
API交互:前后端分离开发实战指南
java·spring·tomcat·web3·maven·springmvc·springweb
一 乐1 小时前
宠物店管理|基于Java+vue的宠物猫店管理管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
天天摸鱼的小学生1 小时前
【Java泛型一遍过】
java·开发语言·windows
骇客野人1 小时前
JAVA获取一个LIST中的最大值
java·linux·list
JIngJaneIL1 小时前
基于Java失物招领系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·vue
程序员岳焱1 小时前
Java泛型高级玩法:通配符、上下界与类型擦除避坑实战(纯干货,附完整工具类)
java·后端·程序员
期待のcode1 小时前
MyBatis-Plus基本CRUD
java·spring boot·后端·mybatis
❀͜͡傀儡师1 小时前
maven 仓库的Central Portal Namespaces 怎么验证
java·maven·nexus