SpringBoot项目实战(39)--Beetl网页HTML文件中静态图片及CSS、JS文件的引用和展示

使用Beetl开发网页时,在网页中使用的CSS、JS、图片等静态资源需要进行适当的配置才可以展示。大致的过程如下:

(1)首先Spring Security框架需要允许js、css、图片资源免授权访问。

(2)网站开发时,如果网页文件不放在SpringBoot工程内部打包,单独指定了文件目录,需要在Config文件中加载文件系统中的网页目录。

(3)网页文件中引用静态文件的路径不能使用相对路径,需要使用绝对路径。

(4)如果使用了Nginx,静态资源文件路径还要加上 Nginx反向代理的路径。

下面是实现过程:

首先贴一下之前openjweb-sys工程的WebSecurityConfig文件,增加了js目录、css目录、images目录的免授权访问:

java 复制代码
package opackage org.openjweb.sys.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.openjweb.core.service.CommUserService;
import org.openjweb.sys.auth.security.AESPasswordEncoder;
import org.openjweb.sys.auth.security.MD5PasswordEncoder;
import org.openjweb.sys.auth.security.MyAccessDecisionManager;
import org.openjweb.sys.auth.security.MyFilterInvocationSecurityMetadataSource;
import org.openjweb.sys.entry.JwtAuthenticationEntryPoint;
import org.openjweb.sys.filter.JwtAuthenticationFilter;
import org.openjweb.sys.handler.JWTLogoutSuccessHandler;
import org.openjweb.sys.handler.JwtAccessDeniedHandler;
import org.openjweb.sys.handler.LoginFailureHandler;
import org.openjweb.sys.handler.LoginSuccessHandler;
import org.openjweb.sys.provider.MyAuthenticationProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

@Slf4j
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CommUserService userDetailService;

    @Bean
    public PasswordEncoder passwordEncoder()
    {
        return new AESPasswordEncoder();
    }

    @Autowired
    LoginSuccessHandler loginSuccessHandler;

    @Autowired
    LoginFailureHandler loginFailureHandler;

    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Autowired
    JWTLogoutSuccessHandler jwtLogoutSuccessHandler;

    @Value("${oauth2.server}")
    private boolean isOAuth2Server = false;

    private static final String[] ALLOW_URL_LIST = {
            //
            "/login",
            "/logout",
            "/captcha",
            "/favicon.ico",
            //"/api/jwt/**",
            "/api/cms/**",
            "/api/b2c/**",
            "/api/b2b2c/**",
            "/api/sns/**",
            "/api/comm/**",
            "/api/cms1/**",
            "/api/store/**",
            "/demo/**",
            "/oauth/**", //允许oauth认证的路径
            "/webjars/**", //webjars js允许的路径
            "/testduoyu",
            "/i18n/**",
            "/**/*.html", //swagger
            "/api/comm/user/login",
            "/api/weixin/login",
            "/api/weixin/getVueMenu",
            "/api/comm/user/getUserInfo2",
            "/front/**",
            "/**/js/**",
            "/**/images/**",
            "/**/css/**"

    };

    //作用???暴露AuthenticationManager给其他Bean使用
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
        //return super.authenticationManagerBean();
    }

    //这个和上面的是什么区别?能一起用吗?

    /*@Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }*/


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //下面注释掉的是第一阶段的示例
      /*  http.cors().and().csrf().disable()//登录表单
         .formLogin()
                .and()
                .authorizeRequests()
                .antMatchers(ALLOW_URL_LIST).permitAll()
                .anyRequest().authenticated();
       */
        //下面是第二阶段整合了数据库权限控制的示例
        log.info("是否配置了oauth2 server:::::");
        log.info(String.valueOf(this.isOAuth2Server));
        if(this.isOAuth2Server){
            log.info("OAUTH2模式...........");

            http.formLogin()
                    //.loginPage("/login.html")
                    .loginProcessingUrl("/login")
                    .and()
                    .authorizeRequests()
                    .antMatchers("/login.html", "/img/**","/demo/**","/webjars/**",  "/testduoyu","/i18n/**","/api/b2c/b2carea/**","/api/store/**","/**/*.html", "/api/comm/user/login","/api/weixin/login","/api/weixin/getVueMenu","/api/comm/user/getUserInfo2", "/front/**"
                                    ,"/**/js/**",  "/**/images/**",  "/**/css/**"
                    ).permitAll()
                    .anyRequest().authenticated()
                    .and()
                    .csrf().disable();
        }
        else {
            log.info("非OAUTH2模式............");
            http.authorizeRequests()
                    .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                        @Override
                        public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                            object.setSecurityMetadataSource(cfisms());
                            object.setAccessDecisionManager(cadm());

                            return object;
                        }
                    })
                    .and().formLogin()
                    //先注掉这个检查oauth认证
                    //.successHandler(loginSuccessHandler) //登录成功处理
                    .failureHandler(loginFailureHandler) //登录失败处理
                    .loginProcessingUrl("/login").permitAll()
                    //.loginProcessingUrl("/demo/jwt/login").permitAll()

                    .and()
                    .logout()
                    .logoutSuccessHandler(jwtLogoutSuccessHandler)

                    // 禁用session
                    .and()
                    .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)

                    // 配置拦截规则
                    .and()
                    .authorizeRequests()
                    .antMatchers(ALLOW_URL_LIST).permitAll()
                    .anyRequest().authenticated()


                    // 异常处理器
                    .and()
                    .exceptionHandling()
                    //接口登录模式打开这个
                    //.authenticationEntryPoint(jwtAuthenticationEntryPoint) //这个影响登录,会导致/login登录蔬菜
                    .accessDeniedHandler(jwtAccessDeniedHandler)

                    // 配置自定义的过滤器
                    //这个jwtAuthenticationFilter 不加也执行了,是否增加了会调整多个过滤器的执行顺序
                    .and()
                    .addFilter(jwtAuthenticationFilter())
                    .logout().permitAll().and().csrf().disable();
        }
    }

    /*@Bean
    PasswordEncoder PasswordEncoder() {
        //return md5PasswordEncoder;
        //return aesPasswordEncoder;//这个不行
        return new AESPasswordEncoder();
        //return new BCryptPasswordEncoder();
        //return new Md5PasswordEncoder();

    }*/
   /*
    @Autowired
    MD5PasswordEncoder md5PasswordEncoder;

    @Autowired
    AESPasswordEncoder aesPasswordEncoder;
    */

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        if(true){
            //如果自定义AuthenticationProvider 则不使用这个
            //auth.userDetailsService(userDetailService).passwordEncoder(aesPasswordEncoder);
            //auth.userDetailsService(userDetailService).passwordEncoder(new BCryptPasswordEncoder());
            DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
            provider.setUserDetailsService(userDetailService);
            provider.setPasswordEncoder(passwordEncoder());
            auth.authenticationProvider(provider);
        }
        else{
            //自定义AuthenticationProvider
            auth.authenticationProvider(new MyAuthenticationProvider(userDetailService));
        }
    }

    @Bean
    MyAccessDecisionManager cadm() {
        //System.out.println("加载角色权限设置。。。。。。。。。。。。");
        return new MyAccessDecisionManager();
    }

    @Bean
    MyFilterInvocationSecurityMetadataSource cfisms() {
        //System.out.println("加载权限设置。。。。。。。。。。。。");
        return new MyFilterInvocationSecurityMetadataSource();
    }


    @Bean
    @ConditionalOnExpression("'${oauth2.server}'=='false'")
    JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
        return jwtAuthenticationFilter;
    }
}

然后在openjweb-core工程的WebMvcConfig中增加文件系统中静态资源目录:

java 复制代码
package org.openjweb.core.config;

import lombok.extern.slf4j.Slf4j;
import org.beetl.core.GroupTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

import java.util.HashMap;
import java.util.Map;

@Configuration
@Slf4j
public class WebMvcConfig implements WebMvcConfigurer {

    @Value("${beetl.fileTemplatesPath:}") String fileTemplatesPath;

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/testduoyu").setViewName("testduoyu");//不能命名testlocale 可能locale有冲突
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("fileTemplatePath::");
        log.info(fileTemplatesPath);
        if(!fileTemplatesPath.endsWith("/"))fileTemplatesPath+="/";
        //D:/tmp/beetl/templates
        registry.addResourceHandler("/**")
                .addResourceLocations("file:"+fileTemplatesPath)
                .addResourceLocations("classpath:/static/");
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        log.info("设置国际化参数lang...........");
        //默认拦截器 其中lang表示切换语言的参数名 LocaleChangeInterceptor 指定切换国际化语言的参数名。
        // 例如?lang=zh_CN 表示读取国际化文件messages_zh_CN.properties。
        //System.out.println("增加国际化拦截器...........");
        LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
        localeInterceptor.setParamName("lang");// 指定设置国际化的参数
        registry.addInterceptor(localeInterceptor);

    }
}

在上面的代码中,增加了addResourceHandlers方法,此方法加载了文件系统fileTemplatePath目录的静态资源,在开发环境中,application-dev.yml中指定的fileTemplatePath是d:\tmp\beetl\templates。

项目的静态网页:

假设项目的静态网页路径是D:\tmp\beetl\templates\cms\site\wenhua,在此路径下有js、css、images目录以及html文件,下面截取了局部的HTML文件:

html 复制代码
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
<title>中国文化网</title>
<meta name="keywords" content="中国文化网">
<meta name="description" content="中国文化网">
<link href="/clouds/cms/site/wenhua/css/common.css" rel="stylesheet" type="text/css" />
<link href="/clouds/cms/site/wenhua/css/font-awesome/css/font-awesome.min.css" rel="stylesheet"/>
<link href="/clouds/cms/site/wenhua/css/index.css" rel="stylesheet" type="text/css" />
<link href="/clouds/cms/site/wenhua/css/focus1.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="/clouds/cms/site/wenhua/js/jquery.1.7.2.min.js"></script>
<script type="text/javascript" src="/clouds/cms/site/wenhua/js/koala.min.1.5.js"></script>
<script type="text/javascript">
    $(function(){ 
        var pw = $('#po-pic').width();
        var sw = $(window).width();
        var lw = (pw - sw)/2;
        $('#po-pic').attr('style','margin-left:-'+lw+'px');
    })
</script>
</head>
<body>
<div class="top">
    <div class="layout">
        <div class="fl logo"><a href="#"><img src="/clouds/cms/site/wenhua/images/logo.jpg" width="257" height="70" border="0" /></a></div>
        <div class="fr top-nav">
            <div class="fr search">
                <input type="text" name="search" class="fr" /><span>高级搜索&nbsp;&nbsp;<a href="#"><i class="fa fa-search fa-lg"></i></a></span>
            </div>
            <a href="#">邮箱登陆1</a><span>&emsp;|&emsp;</span>
        </div>
        <div class="clear"></div>
    </div>
</div>

注意静态资源文件的引用:/clouds/cms/site/wenhua/css/common.css、/clouds/cms/site/wenhua/images/logo.jpg,其中clouds是使用Nginx反向代理将8001端口转发到了SpringBoot,如果没使用nginx,而是使用了localhost:8001,则不需要使用/clouds,后面/cms开头的路径则是针对模版文件跟路径下的相对路径,根路径为D:\tmp\beetl\templates。

现在我们开发一个控制层类,用于展示静态页面。在openjweb-cms内容管理模块增加一个控制层类:

java 复制代码
package org.openjweb.cms.controller;

import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.openjweb.cms.service.CmsInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.HashMap;
import java.util.Map;

@Api(tags = "内容管理-前端查询")
@Slf4j
@Controller
@RequestMapping("/front") //

public class CmsInfoController {
    @Autowired
    private CmsInfoService cmsInfoService;

    @RequestMapping(value="/index")

    public String index( Model model) {

        return "cms/site/wenhua/index.html";//返回页面名
    }

}

测试访问:http://localhost:8001/front/index (不使用nginx则html中静态资源不要加/clouds)

返回的静态页效果:

相关推荐
瘦的可以下饭了19 分钟前
Day01-API
javascript
Nan_Shu_61440 分钟前
学习:Vue (2)
javascript·vue.js·学习
一水鉴天1 小时前
整体设计 定稿 之24 dashboard.html 增加三层次动态记录体系仪表盘 之2 程序 (Q208 之1)
前端·html
亮子AI1 小时前
【css】列表的标号怎么实现居中对齐?
前端·css
一水鉴天2 小时前
整体设计 定稿 之22 dashboard.html 增加三层次动态记录体系仪表盘 之1
前端·html
一水鉴天3 小时前
整体设计 定稿 之24+ dashboard.html 增加三层次动态记录体系仪表盘 之2 程序 (Q208 之2)
开发语言·前端·javascript
二狗哈3 小时前
Cesium快速入门17:与entity和primitive交互
开发语言·前端·javascript·3d·webgl·cesium·地图可视化
GISer_Jing3 小时前
AI驱动营销增长:7大核心场景与前端实现
前端·javascript·人工智能
星光不问赶路人4 小时前
new Array() 与 Array.from() 的差异与陷阱
javascript·面试
T___T4 小时前
Vue 3 做 todos , ref 能看懂,computed 终于也懂了
前端·javascript·面试