ssm+boot+thymeleaf博客系统完成总结

3 篇文章 0 订阅
3 篇文章 0 订阅

1. 基本介绍

在这里插入图片描述

1.1技术栈

后台:

  • SpringBoot
  • Spring
  • Mybatis
  • MybatisPlus
  • SpringSecurity
  • Lombok
  • Maven
  • Druid
  • swagger

前端:

  • thymeleaf模板引擎
  • jquery html css js
  • editormd MarkDown插件
  • font-awesome图标包
  • bootstrap
  • webjar的maven引用

2. 后台实现及问题

2.1 MybatisPlus代码生成

使用MybatisPlus自动生成Controller Service Mapper 与Xml。


/**
 * 代码自动生成 MybatisPlus
 * @author 17566
 * @Date: 2020/8/19 17:28
 */
public class GenerateCode {

    public static void main(String[] args) {
        // 需要构建一个 代码自动生成器 对象
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        // 配置策略
        // 1、全局配置
        GlobalConfig gc = new GlobalConfig();

        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath+"/src/main/java");
        gc.setAuthor("whai");
        gc.setOpen(false);
        // 是否覆盖
        gc.setFileOverride(false);


        // 去Service的I前缀
        gc.setServiceName("%sService");

        //雪花算法加密id
        gc.setIdType(IdType.UUID);
        gc.setDateType(DateType.ONLY_DATE);
        gc.setSwagger2(true);
        mpg.setGlobalConfig(gc);



        //2、设置数据源
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/whaiblog?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        dsc.setDbType(DbType.MYSQL);
        mpg.setDataSource(dsc);


        //3、包的配置
        PackageConfig pc = new PackageConfig();
//        pc.setModuleName("blog");
        pc.setParent("com.whai");
        pc.setEntity("model");
        pc.setMapper("mapper");
        pc.setService("service");
        pc.setServiceImpl("service.impl");
        pc.setController("controller");
//        pc.setXml()

        mpg.setPackageInfo(pc);



        //4、策略配置
        StrategyConfig strategy = new StrategyConfig();

        strategy.setInclude("article","article_tag","comment","message","visit","user");

        // 设置要映射的表名 下划线变驼峰命名
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 自动lombok;
        strategy.setEntityLombokModel(true);


        // 自动填充配置
//        strategy.setLogicDeleteFieldName("deleted");
//        TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
//        TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
//        ArrayList<TableFill> tableFills = new ArrayList<>();
//        tableFills.add(gmtCreate);
//        tableFills.add(gmtModified);
//        strategy.setTableFillList(tableFills);
        // 乐观锁
//        strategy.setVersionFieldName("version");
//        strategy.setRestControllerStyle(true);
//        strategy.setControllerMappingHyphenStyle(true);
        // localhost:8080/hello_id_2
        mpg.setStrategy(strategy);
        mpg.execute();
        //执行
    }
}

2.2 Controller调用MybatisPlus的Service

MybatisPlus的配置分页插件paginationInterceptor

/**
 * @author 17566
 * @Date: 2020/8/19 17:17
 */
@Configuration
public class MybatisPlusConfig  {

    /**
     * 分页插件
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    /**
     * SQL执行效率插件
     * 性能分析拦截器,用于输出每条 SQL 语句及其执行时间
     * // 设置 dev test 环境开启,保证我们的效率
     * SQL执行效率插件
     * */

    @Bean
//    @Profile({"dev","test"})
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
//        performanceInterceptor.setMaxTime(100);
        // ms设置sql执行的最大时间,如果超过了则不 执行
        // performanceInterceptor.setFormat(true);
        // 是否格式化代码
        return performanceInterceptor;
    }
}

page

分页插件

Page<Article> articlePage = new Page(long current, long size);
List<Article> articleList = articlePage.getRecords();

xxxWrapper 的使用

筛选过滤排序

QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("article_create_time");

问题

  • 可优化时间的自动绑定,这个系统的很多时间还是new Date(),并不能满足实际的需要
    可使用MyabtisPlus的自动绑定进行优化
  • 对一些注解的使用还要多看看和使用

2.3 Swagger使用

/**
 * @author 17566
 * @Date: 2020/8/21 16:19
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {

    
    @Value("${swagger.enable}")
    private Boolean enable;
    //在Application.yml中再进行seagger: enable: false的配置

    /**
     * 配置docket以配置Swagger具体参数  api需要关联下面的info
     * @return
     */
    @Bean
    public Docket docket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .enable(enable);
    }

    private ApiInfo apiInfo() {
        Contact contact = new Contact("lkwhai@163.com", "whai", "/");

        ApiInfo apiInfo = new ApiInfo("whaiBlog",
                "whai的简单博客",
                "v1.0",
                "null",
                contact,
                null, null, new ArrayList<>());

        return apiInfo;
    }


}

关闭swagger

@Configuration
@EnableSwagger2
@Profile({"dev", "test"})
//只在tset dev下有效

问题

  • 对一些注解的使用还要多看看和使用

2.4 SpringSecurity的使用

Configration

  • 登陆页面的参数绑定 跳转
  • 注销
  • 验证时采用的加密方式BCryptPasswordEncoder();
/**
 * @author 17566
 * @Date: 2020/8/19 11:17
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    UserServiceImpl userService;

    //请求授权验证
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .antMatchers("/admin*/**").hasRole("admin");

        http.formLogin().loginPage("/toLogin")
                .usernameParameter("username")
                .passwordParameter("password")
                .loginProcessingUrl("/login")
                //login为form请求提交位置
                /*&lt;form action="${loginProcessingUrl}" method="post"&gt;*/
                .defaultSuccessUrl("/admin/index");

//        http.formLogin().usernameParameter("username")
//                .passwordParameter("password").loginPage("/toLogin").loginProcessingUrl("/login").defaultSuccessUrl("/admin/index");

        http.headers().contentTypeOptions().disable();
        http.headers().frameOptions().disable(); // 图片跨域
        http.csrf().disable();//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
        http.logout().logoutSuccessUrl("/");
//
//        // 记住我配置
        http.rememberMe().rememberMeParameter("remember").alwaysRemember(true);
    }

    /**
     * 用户自定义认证规则 登陆验证
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());

//        auth.inMemoryAuthentication()
//               .withUser("user").password(passwordEncoder().encode("123")).roles("admin");
    }

    // 密码加密方式
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

UserService

  • UserDetailsService的接口实现
  • 角色信息获取 (在很多情况下需要到数据库中获取,本站局限于本用户,直接写入ROLE_admin
**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author whai
 * @since 2020-08-20
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService,UserDetailsService{

    @Autowired
    UserService userService;
    @Autowired
    HttpSession session;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {


        User user = userService.getOne(new QueryWrapper<User>().eq("user_name", username));
        session.setAttribute("loginUser",user);

        UserDetails userDetails = null;
        if (null!=user){
            String password = user.getUserPass();
            userDetails = new org.springframework.security.core.userdetails.User(
                    username,password,getAuthorities(user));
        }

        return userDetails;
    }

    /**
     *  获取角色信息
     *
     *  ROLE_前缀
     *  https://blog.csdn.net/universsky2015/article/details/77965567
     * @param user
     * @return
     */
    private Collection<GrantedAuthority> getAuthorities(User user){
        List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
        //注意:这里每个权限前面都要加ROLE_。否在最后验证不会通过
        authList.add(new SimpleGrantedAuthority("ROLE_admin"));
        return authList;
    }
}

管理员用户修改密码的实现

  • SecurityContextHolder.getContext().getAuthentication()获取Authentication

      Authentication extends Principal 
    
  • 获取authentication.getName();

  • 根据username进行查询

  • BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
    boolean matches = bc.matches(currentInputPwd,userPass);验证密码是否正确

  • 验证成功,使用BCryptPasswordEncoder().encode(newPwd)进行加密


    /**
     *https://blog.csdn.net/Mr_LiYyang/article/details/97312327
     * 修改密码
     * @param passwordForm
     * @param model
     * @return
     */
    @PostMapping("/updatePassword")
    public String updatePassword(PasswordForm passwordForm, Model model){

        //用户输入的当前密码
        String currentInputPwd = passwordForm.getCurrentPwd();
        //新密码
        String newPwd = passwordForm.getNewPwd();
        //验证新密码
        String verifyPwd = passwordForm.getVerifyPwd();
        if (currentInputPwd.isEmpty()){
            model.addAttribute("msg","请输入当前密码");
            return "admin/information";
        }else if (newPwd.isEmpty()){
            model.addAttribute("msg","请输入新密码");
            return "admin/information";
        }else if (verifyPwd.isEmpty()){
            model.addAttribute("msg","请再一次输入新密码");
            return "admin/information";
        }else if (!newPwd.equals(verifyPwd)){
            model.addAttribute("msg","两次输入密码验证不一致");
            return "admin/information";
        }


        //获取 Authentication域的用户名
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username = authentication.getName();


        User sysUser = userService.getOne(new QueryWrapper<User>().eq("user_name", username));
        //加密的原密码
        String userPass = sysUser.getUserPass();


        BCryptPasswordEncoder bc = new BCryptPasswordEncoder();
        boolean matches = bc.matches(currentInputPwd,userPass);

        if (matches){
            //验证成功
            sysUser.setUserPass(bc.encode(newPwd));
            userService.updateById(sysUser);
            model.addAttribute("msg", "修改成功");
        }else {
            model.addAttribute("msg","密码验证错误");
        }
        return "admin/information";

    }

问题

对SpringSecurity的很多方法类的了解还不够 本blog仅仅能用,还需要进行巩固加强

2.5 监听器实现启动时自动加载

  • 获取当前的web容器

      //强转为webApplicationContext
      WebApplicationContext applicationContext = (WebApplicationContext) event.getApplicationContext();
      ServletContext context = applicationContext.getServletContext();
    
/**
 * https://blog.csdn.net/m0_37202351/article/details/86180998
 * @author 17566
 * @Date: 2020/8/26 10:34
 */
@Component
public class InitWebListener implements ApplicationListener<ContextRefreshedEvent> {



    @Autowired
    ArticleService articleService;

    @Autowired
    ArticleTagService tagService;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        QueryWrapper<Article> queryWrapper = new QueryWrapper<>();
        //根据时间降序
        queryWrapper.orderByDesc("article_create_time");

        //只需要封装最新的三个文章信息
        Page<Article> page = new Page<>(1,3);
        articleService.page(page, queryWrapper);

        List<Article> records = page.getRecords();



        //标签信息
        List<ArticleTag> tagList = tagService.list(null);


        //强转为webApplicationContext
        WebApplicationContext applicationContext = (WebApplicationContext) event.getApplicationContext();

        ServletContext context = applicationContext.getServletContext();
        //进入直接将全局参数给了  包含三篇基础文章显示 网站统计
        context.setAttribute("article0",records.get(0));
        context.setAttribute("article1",records.get(1));
        context.setAttribute("article2",records.get(2));
        context.setAttribute("tagList",tagList);
        context.setAttribute("defaultAvatar","img/weixin.jpg");
    }
}

问题

只是启动时将最新三篇blog发布到ApplicationContext中,但是一旦发布新文章时,不能实施地显示最新的博客,只能显示原来的那三篇博客,缺少实时性,可以优化。

2.6 Web配置

  • 添加上资源的上传映射路径与真实路径,实现markdown的图片本地文件能够进行上传
  • 发布jar包运行的时候需要在jar包同目录下创建一个upload的文件夹用于存放上传图片文件
**
 * @author 17566
 * @Date: 2020/8/18 17:16
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
//        registry.addViewController("/haha").setViewName("index");
//        registry.addViewController("/haha").setViewName("index.html");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

    }

    /**
     * 文件上传资源的加载
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/upload/**")
                .addResourceLocations("file:"+System.getProperty("user.dir")+"/upload/");
//                .addResourceLocations("/static/**").addResourceLocations("classpath:/static/");
    }


}

问题

  • 可以了解下editorMd插件的功能
  • 对配置类的理解还有加强了解的必要

总结

这个blog还有很多可以优化的点
希望能够把以前所学习的技术栈在这个博客上进行使用实践。

自己瞎写的公众号与博客

普通二本数据科学与大数据技术专业菜鸟一个,望各位大神多多指导!互相学习进步!

whai的个人博客 whaifree.top 欢迎留言!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值