一人撸前后端后台管理系统(vue+springboot)——后端篇

分析

针对项目的一些背景和环境的分析:

  • 该项目采用的是oracle数据,生产上的数据采用专线物理隔离的形式隔离开。
  • 系统的使用方为内部工作人员,是小范围的操作,使用用户量不高于3000人。
  • 目前不考虑权限的问题,但是后面功能扩展可能需要用到。
  • 每次登录token的有效时间在1小时左右,并不需要控制单点登录功能

因此:

  1. 决定采用前后端分离,后端采用springboot+maven;
  2. 利用mybatis plus连接oralce,数据库连接池采用druid;
  3. 鉴权采用springSecurity+jwtToken的形式,json解析用fastjson;
  4. 本来也就我一个人开发,但是还是撸上了swagger对接口进行说明,便于后期管理与维护;
  5. 并不会与外部系统做交互,所以不用考虑暴露接口问题。

预备:

  1. 如果后面访问量上去,可以撸上redis;
  2. 利用redis可以做单点登录控制。

正式工作

一、初始化springboot项目

1.在idea里面初始化spingboot项目非常方便,网上的参考资料很多,不懂的读者随便百度一下都可以初始化一个maven管理的springboot项目。

  1. 根据前面提到的引入相应的maven配置
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.14</version>

        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.1.0</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.7.0</version>
        </dependency>

关于maven的介绍,不清楚的读者可以参考我之前的博客:https://www.jianshu.com/p/477ad2e14150

二、搭建数据库连接

DataSourceConfig

@Configuration
@Slf4j
@Order(1)
public class DataSourceConfig {
    @Autowired
    private DruidConfig druidConfig;

    // 如果不是 druid 的 datasource, 直接用下面这个方式就可以, 但是 SpringBoot 默认还不支持 druid
    //    @Bean(name = "hkb2bDataSource")
    //    @ConfigurationProperties(prefix = "datasource.hkb2b")
    //    public DataSource hkb2bDataSource() {
    //                return DataSourceBuilder.create().build();
    //    }

    @Bean(name = "demoDataSource")
    public DataSource demoDataSource() {
        DruidDataSource datasource = new DruidDataSource();

        datasource.setUrl(druidConfig.getUrl());
        datasource.setUsername(druidConfig.getUsername());
        datasource.setPassword(druidConfig.getPassword());
        datasource.setDriverClassName(druidConfig.getDriverClassName());

        datasource.setInitialSize(druidConfig.getInitialSize());
        datasource.setMinIdle(druidConfig.getMinIdle());
        datasource.setMaxActive(druidConfig.getMaxActive());
        datasource.setMaxWait(druidConfig.getMaxWait());
        datasource.setTimeBetweenEvictionRunsMillis(druidConfig.getTimeBetweenEvictionRunsMillis());
        datasource.setMinEvictableIdleTimeMillis(druidConfig.getMinEvictableIdleTimeMillis());
        datasource.setValidationQuery(druidConfig.getValidationQuery());
        datasource.setTestWhileIdle(druidConfig.isTestWhileIdle());
        datasource.setTestOnBorrow(druidConfig.isTestOnBorrow());
        datasource.setTestOnReturn(druidConfig.isTestOnReturn());
        datasource.setPoolPreparedStatements(druidConfig.isPoolPreparedStatements());
        datasource.setMaxPoolPreparedStatementPerConnectionSize(
                druidConfig.getMaxPoolPreparedStatementPerConnectionSize());

        try {
            datasource.setFilters(druidConfig.getFilters());
        } catch (SQLException e) {
            log.error("druid configuration initialization filter", e);
        }
        datasource.setConnectionProperties(druidConfig.getConnectionProperties());

        return datasource;

    }

//    @Primary
//    @Bean(name = "demoJdbcTemplate")
//    public NamedParameterJdbcTemplate demoJdbcTemplate() {
//        return new NamedParameterJdbcTemplate(demoDataSource());
//    }

    @Configuration
    @ConfigurationProperties(prefix = "datasource.demo")
    @Data
    public class DruidConfig {
        // http://www.voidcn.com/blog/blueheart20/article/p-6181465.html
        // http://my.oschina.net/letao/blog/518012
        // https://github.com/alibaba/druid/wiki/配置_DruidDataSource参考配置
        private String url;
        private String username;
        private String password;
        private String driverClassName;
        private int initialSize;
        private int minIdle;
        private int maxActive;
        private int maxWait;
        private int timeBetweenEvictionRunsMillis;
        private int minEvictableIdleTimeMillis;
        private String validationQuery;
        private boolean testWhileIdle;
        private boolean testOnBorrow;
        private boolean testOnReturn;
        private boolean poolPreparedStatements;
        private int maxPoolPreparedStatementPerConnectionSize;
        private String filters;
        private String connectionProperties;
    }
}

MybatisPlusConfig

/**
 * @author wyk on 2019/02/28
 */
@Configuration
@Slf4j
public class MybatisPlusConfig {

    //@Qualifier(value = "demoDataSource")
    @Autowired
    DataSource dataSource;

    @Value("${mybatis-plus.global-config.db-config.capital-mode}")
    private boolean capitalMode;
    @Value("${mybatis-plus.mapper-locations}")
    private String mapperLocations;
    @Value("${mybatis-plus.type-aliases-package}")
    private String typeAliasesPackage;
    @Value("${mybatis-plus.global-config.db-config.logic-delete-value}")
    private String delete;
    @Value("${mybatis-plus.global-config.db-config.logic-not-delete-value}")
    private String notDelete;


    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory createSqlSessionFactoryBean() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage(typeAliasesPackage);
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sqlSessionFactoryBean.setMapperLocations(resolver.getResources(mapperLocations));
        sqlSessionFactoryBean.setPlugins(new PaginationInterceptor[]{this.paginationInterceptor()});

        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        configuration.setJdbcTypeForNull(JdbcType.NULL);

        sqlSessionFactoryBean.setConfiguration(configuration);
        GlobalConfig globalConfig = new GlobalConfig();
        globalConfig.setDbConfig(this.globalConfiguration());
        globalConfig.setSqlInjector(new LogicSqlInjector());
        sqlSessionFactoryBean.setGlobalConfig(globalConfig);

        return sqlSessionFactoryBean.getObject();
    }

    @Bean(name = "transactionManager")
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }


    @Bean
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        /*<!-- SQL 执行性能分析,开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长 -->*/
        performanceInterceptor.setMaxTime(1000);
        /*<!--SQL是否格式化 默认false-->*/
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    }

    /**
     * mybatis-plus分页插件
     */
    private PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor page = new PaginationInterceptor();
        page.setDialectType("oracle");
        return page;
    }



    private GlobalConfig.DbConfig globalConfiguration() {

        GlobalConfig.DbConfig configuration = new GlobalConfig.DbConfig();
        //主键策略
        configuration.setIdType(IdType.INPUT);
        //字段策略
        configuration.setFieldStrategy(FieldStrategy.NOT_EMPTY);
        //数据库大写 下划线转换
        configuration.setCapitalMode(capitalMode);
        configuration.setLogicDeleteValue(delete);
        configuration.setLogicNotDeleteValue(notDelete);
        configuration.setKeyGenerator(new OracleKeyGenerator());
        return configuration;
    }
}

三、搭建springSecurity权限拦截

SecurityConfig

@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //@Autowired
    //UserDetailService userDetailService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//      http.httpBasic()// httpBasic 登录

        http.formLogin()// 表单登录  来身份认证
                .loginPage("/login") //
                .failureUrl("/login?error")
                .permitAll()// 自定义登录页面
                .and()
                .authorizeRequests()// 对请求授权
                .antMatchers(
                        "/login/*","/test/*").permitAll()// 这些页面不需要身份认证,其他请求需要认证
                .antMatchers("/admin/*").hasAuthority(TYPE_ADMIN)
                .antMatchers("/normal/*").hasAnyAuthority(TYPE_ADMIN,TYPE_NORMAL)
                .anyRequest() // 任何请求
                .authenticated()//; // 都需要身份认证
                .and()
                .csrf().disable()// 禁用跨站攻击
        .logout().logoutUrl("/logout").permitAll();

        http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
            String jwtToken = request.getHeader("jwtToken");
            Assert.notNull(jwtToken,CodeDefault.LOGIN);
        });

        // 关闭跨域拦截
        http.csrf().disable();
        // 关闭不允许在frame中访问的拦截
        http.headers().frameOptions().disable();
        // 单用户最大登陆数
        http.sessionManagement().maximumSessions(1);

        TokenLoginFilter tokenLoginFilter = new TokenLoginFilter();
        Map<String, TokenLoginStrategy> tokenLoginStrategys = this.getApplicationContext()
                .getBeansOfType(TokenLoginStrategy.class);
        ArrayList<TokenLoginStrategy> tokenLoginStrategysList = new ArrayList<>(tokenLoginStrategys.values());
        tokenLoginFilter.setTokenLoginStrategys(tokenLoginStrategysList);
        http.addFilterBefore(tokenLoginFilter, UsernamePasswordAuthenticationFilter.class);
    }

}

四、增加swagger

/**
 * @author wyk on 2019/02/28
 */
@Configuration
@EnableSwagger2
@Profile({"dev","sit"})//生产和预发布环境不开启
public class SwaggerConfig {

//    @Value("${spring.profiles.active}")
//    private String active;

    @Bean
    public Docket createRestApi() {
        //生产和预发布环境不开启
//        if (Constant.ACTIVE_PROD.equals(active) || Constant.ACTIVE_PROP.equals(active)) {
//            return null;
//        }
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                // 指定 package,避免展现 spring-boot-starter-actuator 引入的 log-file-mvc-endpoint、health-mvc-endpoint 等
                .apis(RequestHandlerSelectors.basePackage("com.psbc.wyk.eirs"))
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("SpringBoot 示例框架")
                .description("SpringBoot 示例框架")
                .contact(new Contact("文宇坤", "yk.wen", "530016380@qq.com"))
                .version("1.0").build();
    }
}

五、完成

一个基本的框架就完成了,读者可以从github上下载我搭建的一个demo:
https://github.com/YukunWen/dangjian
后续工作只需要往里面添加业务代码即可。

参考网址:
  1. jwtToken官网:https://jwt.io/
    请翻墙后访问,国内和国外看到的界面不完全一致
  2. springSecurity:http://blog.didispace.com/springbootsecurity/
  3. mybatis-plus:https://mp.baomidou.com/
  4. swagger:https://swagger.io/tools/swagger-ui/
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容