從零開始搭建博客01----框架搭建,權限控制

FlyBlog

二期90天進階訓練營的課程課後作業,搭建一個blog

20181101更新

摘要

本期主要完成了集成mybatis plus、lombok,Redis,做好全局異常處理,並且把layui社區的頁面集成到項目中,然後就是完成首頁的渲染。

環境

框架 版本
springboot 2.0.1.RELEASE
JDK 1.8
mysql 5.6

項目結構

在這裏插入圖片描述

集成MyBatis plus

說明

MyBatis Plus 是一個持久層框架,是在MyBatis 之上做的一層封裝,通過對Dao層,Servcie層通用代碼的封裝,極大的簡化了開發。
詳情參考:https://github.com/baomidou/mybatis-plus

步驟一 添加依賴

步驟一:在pom文件中添加 MyBatis Plus 的依賴,使用最新版本

<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.0.1</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

步驟二. 數據庫連接配置

步驟二 新建數據fly_blog,並在application.yml 文件中添加數據庫連接配置。

# DataSource Config
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/fly_blog
    username: root
    password: admin

步驟三. 配置Mapper的掃描路徑

在程序的入口類FlyBlogApplication 添加@MapperScan(value = "com.fly.dao") 註解

@SpringBootApplication
@MapperScan(value = "com.fly.dao")
public class FlyBlogApplication {


	public static void main(String[] args) {

		SpringApplication.run(FlyBlogApplication.class, args);
		log.info("系統啓動成功");
	}
}

至此,MyBatis Plus 就集成完畢,在3.0.1 版本的MyBatis Plus中不需要配置xml的位置。
放下以下兩個位置均可
位置一:
在這裏插入圖片描述
位置二
在這裏插入圖片描述

MyBatisPlus 還給我們提供了一個特別實用的功能。自動生成代碼,它可以自動生成dao,model,mapper,service,controller的代碼。

自動生成代碼配置

生成代碼啓動類

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import java.util.ResourceBundle;


/**
 * Created by Lucare.Feng on 2017/2/23.
 */
public class MyGenerator {

    /**
     * <p>
     * MySQL 生成演示
     * </p>
     */
    public static void main(String[] args) {

        //用來獲取Mybatis-Plus.properties文件的配置信息
        ResourceBundle rb = ResourceBundle.getBundle("Mybatis-Plus");
        AutoGenerator mpg = new AutoGenerator();
        String systemDir = System.getProperty("user.dir");
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setOutputDir(systemDir + rb.getString("OutputDir"));
        gc.setFileOverride(true);
        gc.setActiveRecord(true);
        gc.setEnableCache(false);// XML 二級緩存
        gc.setBaseResultMap(true);// XML ResultMap
        gc.setBaseColumnList(false);// XML columList
        gc.setAuthor(rb.getString("author"));

        // 自定義文件命名,注意 %s 會自動填充表實體屬性!
        String classPrefix = "%s";
        gc.setMapperName(classPrefix+"Mapper");
        gc.setXmlName(classPrefix+"Mapper");
        gc.setServiceName(classPrefix+"Service");
        gc.setServiceImplName(classPrefix+"ServiceImpl");
        gc.setControllerName(classPrefix+"Controller");
        mpg.setGlobalConfig(gc);

        // 數據源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setDbType(DbType.MYSQL);
//        dsc.setTypeConvert(new MySqlTypeConvert());
//        dsc.setTypeConvert(new MySqlTypeConvert() {
//            // 自定義數據庫表字段類型轉換【可選】
//            @Override
//            public DbColumnType processTypeConvert(String fieldType) {
//                System.out.println("轉換類型:" + fieldType);
//                return super.processTypeConvert(fieldType);
//            }
//        });
        dsc.setDriverName("com.mysql.jdbc.Driver");
        dsc.setUsername(rb.getString("userName"));
        dsc.setPassword(rb.getString("passWord"));
        dsc.setUrl(rb.getString("url"));
        mpg.setDataSource(dsc);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
//        strategy.setTablePrefix(new String[]{"t_"});// 此處可以修改爲您的表前綴
//        strategy.setNaming(NamingStrategy.remove_prefix_and_camel);// 表名生成策略
//        strategy.setNaming(NamingStrategy.removePrefixAndCamel());// 表名生成策略
//        strategy.setInclude(new String[]{"shop_create_order_record"}); // 需要生成的表
        strategy.setInclude(rb.getString("tableName").split(",")); // 需要生成的表
//        String[] strings = {"mto_users", "shiro_permission"};
//        strategy.setInclude(strings); // 需要生成的表
//        strategy.setExclude(new String[]{"t_rong_bid"}); // 排除生成的表
        // 字段名生成策略
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 自定義實體父類
//         strategy.setSuperEntityClass("hello.entity.BaseEntity");
        // 自定義實體,公共字段
//        strategy.setSuperEntityColumns(new String[]{"id"});
        // 自定義 mapper 父類
        // strategy.setSuperMapperClass("com.fcs.demo.TestMapper");
        // 自定義 service 父類
        // strategy.setSuperServiceClass("com.fcs.demo.TestService");
        // 自定義 service 實現類父類
        // strategy.setSuperServiceImplClass("com.fcs.demo.TestServiceImpl");
        // 自定義 controller 父類
//         strategy.setSuperControllerClass("com.risk.controller.BaseController");
        // 【實體】是否生成字段常量(默認 false)
        // public static final String ID = "test_id";
        // strategy.setEntityColumnConstant(true);
        // 【實體】是否爲構建者模型(默認 false)
        // public User setName(String name) {this.name = name; return this;}
        // strategy.setEntityBuliderModel(true);
        mpg.setStrategy(strategy);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent(rb.getString("parent"));
        pc.setModuleName("");
        pc.setController("controller");// 這裏是控制器包名,默認 web
        pc.setEntity("entity");
        pc.setMapper("dao");
        pc.setXml("mapper");
        pc.setService("service");
        pc.setServiceImpl("service" + ".impl");
        mpg.setPackageInfo(pc);

        // 執行生成
        mpg.execute();
    }

}

對應的配置文件MyBatis-Plus.properties

#此處爲本項目src所在路徑(代碼生成器輸出路徑)
OutputDir=/fly_blog/src/main/java
#數據庫表名(此處切不可爲空,如果爲空,則默認讀取數據庫的所有表名),多個表用","號分割
tableName=user
#生成代碼類名類名
#className=MtoLog
#設置作者
author=jay.xiang
#自定義包路徑
parent=com.fly
#
#
#正常情況下,下面的代碼無需修改!!!!!!!!!!
#
#
#數據庫地址
url=jdbc\:mysql\://localhost\:3306/fly_blog?useUnicode\=true&characterEncoding\=utf-8&useSSL\=false
#數據庫用戶名
userName=root
#數據庫密碼
passWord=admin

至此,MyBatisPlus的配置就完成了。

集成lombok

添加依賴

<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

集成Redis

第一步:導入redis的pom包

`

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

`

第二步:配置redis的連接信息

spring:  redis:
    sentinel:
      master: mymaster
      nodes: 

第三步:爲了讓我們存到redis中的數據更容易看懂,我們需要
換一種序列化方式,默認的是採用jdk的序列化方式,這裏選用Jackson2JsonRedisSerializer,
只需要重寫redisTemplate操作模板的生成方式即可。新建一個config包,放在這個包下。


@Configuration
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);

        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        jackson2JsonRedisSerializer.setObjectMapper(new ObjectMapper());

        template.setKeySerializer(jackson2JsonRedisSerializer);
        template.setValueSerializer(jackson2JsonRedisSerializer);

        return template;
    }
}

全局異常處理

步驟一. 自定義異常類

public class MyException extends RuntimeException {
    public MyException(String message) {
        super(message);
    }
}

步驟二. 定義全局異常處理,使用@ControllerAdvice表示
定義全局控制器異常處理,使用@ExceptionHandler表示
針對性異常處理,可對每種異常針對性處理

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandle {
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) {
        log.error("------------------>捕獲到全局異常", e);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("exception", e);
        modelAndView.addObject("url", req.getRequestURI());
        modelAndView.setViewName("error");
        return modelAndView;
    }

    @ExceptionHandler(value = MyException.class)
    @ResponseBody
    public R jsonErrorHandler(HttpServletRequest req, MyException e) {

        return R.failed(e.getMessage());
    }
}


數據庫設計

sql語句參見: flyblog.sql

以上就是分支1內容[v1-basecode]
----------------------分支二內容[v2-shiro-login]----------------------------------
##集成Shiro
步驟一,引入pom文件

	<!--集成shiro-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>


Realm: 認證與授權
SecurityManager:Shiro架構的核心,協調內部各個安全組件之間的交互。
步驟二:配置Shiro的SecurityManager核心和過濾器

@Slf4j
@Configuration
public class ShiroConfig {

    @Bean("securityManager")
    public SecurityManager securityManager(OAuth2Realm oAuth2Realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(oAuth2Realm);

        log.info("------------->securityManager注入完成");
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {

        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);
        // 配置登錄的url和登錄成功的url
        filterFactoryBean.setLoginUrl("/login");
        filterFactoryBean.setSuccessUrl("/user/center");
        // 配置未授權跳轉頁面
        filterFactoryBean.setUnauthorizedUrl("/error/403");

        Map<String, String> hashMap = new LinkedHashMap<>();
        hashMap.put("/login", "anon");
        hashMap.put("/user*", "user");
        hashMap.put("/user/**", "user");
        hashMap.put("/post/**", "user");
        filterFactoryBean.setFilterChainDefinitionMap(hashMap);

        return filterFactoryBean;
    }
}


在此處我們重寫了認證和授權的Realm,並將Realm配置到SecurityManager中,
然後shiro的過濾器呢,給Shiro配置了登錄的url,登錄成功url和沒有權限提示的url,
有配置了需要攔截的url。

而Realm需要繼承AuthorizingRealm並授權和認證方法。

@Slf4j
@Component
public class OAuth2Realm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    /**
     * 授權
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 認證
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//        注意token.getUsername()是指email!!!
        AccountProfile profile = userService.login(token.getUsername(), String.valueOf(token.getPassword()));
        log.info("-------------------->進入認證步驟");

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(profile, token.getCredentials(), getName());

        return info;
    }
}


這時候shiro已經集成到了項目中,啓動項目後會打印 "securityManager注入完成"的提示。
然後我們可以使用SecurityUtils.getSubject()去操作用戶的權限操作了。

登錄註冊

登錄接口:

 @PostMapping("/login")
    @ResponseBody
    public R doLogin(String email, String password, ModelMap model) {
        if (StringUtils.isAnyBlank(email, password)) {
            return R.failed("用戶名或密碼不能爲空");
        }

        UsernamePasswordToken token = new UsernamePasswordToken(email, SecureUtil.md5(password));

        try {

            //嘗試登陸,將會調用realm的認證方法
            SecurityUtils.getSubject().login(token);

        } catch (AuthenticationException e) {
            if (e instanceof UnknownAccountException) {
                return R.failed("用戶不存在");
            } else if (e instanceof LockedAccountException) {
                return R.failed("用戶被禁用");
            } else if (e instanceof IncorrectCredentialsException) {
                return R.failed("密碼錯誤");
            } else {
                return R.failed("用戶認證失敗");
            }
        }

        return R.ok("登錄成功");

    }


前端調用:

<script>
    layui.use('form',function(){
        var form = layui.form;
//        監聽提交
        form.on('submit(*)',function (data) {
            $.post('/login',data.field,function (res) {
                if (res.code==0) {
                    location.href="/user/center";
                }else {
                    layer.msg(res.msg());
                }
            });
            return false;
        });

        });

</script>

註冊與登錄類似:

博客分類、分頁

1. 首頁分類顯示

由於分類變化較少,所以在項目初始化時候放在上下文猴子那個(ServletContext).
但是這樣也有問題,在web端與後臺管理端不是同一個項目,或者做了負載均衡的項目,
如果改動了分類信息,因爲不同項目不同上下文,所以會出現數據不一致的可能。

現階段我們先把分類信息存放在上下文中,後期如果涉及到負載均衡或者分離時候,
我們可以通過MQ消息隊列的方式來改變上下文內容。

博客分類信息是項目啓動之後初始化到上下文,這裏涉及到ApplicationRunner接口,
ApplicationRunner接口是在容器啓動成功後的最後一步回調(類似於開機自啓)。

package org.springframework.boot;

@FunctionalInterface
public interface ApplicationRunner {
    void run(ApplicationArguments var1) throws Exception;
}

我們需要重寫ApplicationRunner類的run方法,因爲SpringBoot在啓動完成之後
會調用這個run方法。所以我們只需要把博客分類的邏輯在run方法裏面實現就行。

另外,因爲需要用到上下文,所以我們也實現ServletContextAware方法,重寫
serServletContext方法,把serveltContext注入
具體代碼如下:

@Slf4j
@Order(100)
@Component
public class ContextStartup implements ApplicationRunner,ServletContextAware {

    private ServletContext servletContext;

    @Autowired
    CategoryService categoryService;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        servletContext.setAttribute("categorys", categoryService.list(null));

        log.info("ContextStartup------------>加載categorys");

    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }
}


參考代碼:
https://github.com/XWxiaowei/FlyBlog/tree/v4-category

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章