springboot与vue集成

背景:vue项目一般是单独开发单独部署,但是某些时候我们既想使用vue的各种方便组件与双向数据绑定,又想直接把开发好的vue文件集成到springboot的web项目中集成打包。

先执行npm run build单独打包vue项目,将build的文件内容复制到springboot项目resource下的static文件夹下,文件结构如下图

给index.html一个转发,这样可以在浏览器中输入http://127.0.0.1:8993/ 这种默认首页的时候,直接打开vue项目中的index.html

@Controller
public class IndexController {
    @RequestMapping("/")
    public String index()
    {
        return "forward:/index.html";
    }

}

vue-router histroy刷新404

由于后台中使用了springsecurity作为权限认证框架,因此当直接刷新或输入url访问时,该url是不存在或者无权限的因此增加 error-page的方式解决,在springboot 2.*中是通过实现ErrorPageRegistra接口来实现的,代码如下:

@Component
public class ErrorPageConfig implements ErrorPageRegistrar {

    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        ErrorPage error401Page=new ErrorPage(HttpStatus.UNAUTHORIZED,"/index.html");
        registry.addErrorPages(error401Page);
    }
}


spring-security

spring

参考教程地址

认证过程

  1. 用户使用账号密码登陆
  2. springsecurity将接收到的登陆信息封装成实现了Authentication 接口的UsernamePasswordAuthenticationToken
  3. 将产生的token对象传给AuthenticationManager进行登陆认证
  4. AuthenticationManager认证成功后,会返回一个封装了用户权限信息的Authentication对象
  5. 通过调用 SecurityContextHolder.getContext().setAuthentication(…) 将 AuthenticationManager 返回的 Authentication 对象赋予给当前的 SecurityContext

验证流程

  • ChannelProcessingFilter
  • SecurityContextPersistenceFilter
  • ConcurrentSessionFilter
  • 实现Filter接口的验证类
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • ExceptionTransactionFilter
  • FilterSecurityInterceptor

与springboot集

指定拦截的url

  • antMatchers :configure(HttpSecurity httpSecurity) 中增加antMatchers
  • @PreAuthorize注解 :具体方法上增加PreAuthorize注解如下:
@PreAuthorize("hasAnyRole('ADMIN','MENU_ALL','MENU_SELECT','ROLES_ALL','USER_ALL','USER_SELECT')")

方法级安全使用诸如@PreAuthorize、@PostAuthorize 、@ Secured 注解来实现。

要想使 @PreAuthorize 注解生效,需要继承 WebSecurityConfigurerAdapter 配置类上添加 @EnableGlobalMethodSecurity注解部分代码如下

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

上面的 prePostEnabled :确定 Spring Security 前置注释 [@PreAuthorize,@PostAuthorize,..] 是否应该启用

认证处理机制

  • BasicAuthenticationFilter(basic)
  • UsernamePasswordAuthenticationFilter(用户密码)
  • jwt(自定义jwt的方式)

maven引用

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

security配置

继承WebSecurityConfigurerAdapter,然后重写两个configur

	public void configure(WebSecurity web) throws Exception {
	}
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}

数据库表结构设计

用户角色权限相关表

  • user 用户信息表
字段名字段类型非空注释
idbigint(20)ID
avatarvarchar头像链接
create_timedatetime创建时间
emailvarchar邮箱
enabledbigint是否启用
usernamevarchar用户名
passworvarchar密码
last_password_reset_timedatetime最后修改密码时间
  • users_roles 用户角色关系表
字段名 字段类型 非空 注释
user_idbigint(20)用户ID
role_id bigint(20)角色ID
  • role 角色表
字段名 字段类型 非空 注释
idbigint角色id
create_timedatetime创建时间
namevarchar角色名称
remarkvarchar备注
  • roles_permissions 角色权限关系表
字段名 字段类型 非空 注释
role_idbigint角色ID
permission_idbigint权限ID
  • permission 权限表
字段名 字段类型 非空 注释
idbigint
aliasvarchar别名
create_timedatetime创建时间
namevarchar名称
pidint上级权限
  • 示例数据

菜单设计与展

  • menu 菜单表
字段名 字段名类型是否非空备注
idbigint
create_timedatetime 创建日期
namevarchar菜单名称
componentvarchar组件
pidbigint上级菜单ID
sortbigint排序
iconvarchar图标
pathvarchar链接地址
i_framebit是否外链
menu表 查询结果示例

centos上安装配置jenkins

在centos 7 下安装配置jenkins

背景

红心ERP项目demo已完成,需要联合开发并增加测试环境,因此考虑在阿里云上部署一套测试环境。

使用yum安装

首先到jenkins官网,找到适合centos的安装界面 https://pkg.jenkins.io/redhat-stable/
1. 下载repo 文件

sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo
  1. 导入公钥
sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io.key
  1. 执行Linux 的yum
yum install jenkins -y
  1. 启动jenkins 服务
service jenkins start

重启的命令为:service jenkins restart ,关闭的命令为:service jenkins stop
5. 访问jenkins默认页面
– 页面url :http://127.0.0.1:8080
如果要修改默认端口号则可以用vim 编辑/etc/sysconfig/jenkins文件(默认端口是8080,我这里改成了8999)
blob.jpg
– 首次会进入配置页面,需要输入一个初始密码
初始密码存放位置在 cat /var/lib/jenkins/secrets/initialAdminPassword
6. jenkins用户权限修改
默认用户是jenkins 用户,建议改成root用户,不然用jenkins执行脚本的时候会有权限问题
vim 编辑/etc/sysconfig/jenkins文件修改 JENKINS_USER为root
blob.jpg

springboot+jpa+mybatis 多数据源支持

springboot+jpa+mybatis 多数据源支持


配置dataSource

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

/**
 * com.ehaoyao.paycenter.job.common.config
 * 数据源配置类
 * @author PF
 * @create 2018-05-10 16:17
 **/
@Configuration
public class DataSourceConfig {
    @Bean(name = "payCenterDataSource")
    @Qualifier("payCenterDataSource")
    @Primary
    @ConfigurationProperties(prefix="spring.datasource.paycenter")
    public DataSource paycenterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "erpDataSource")
    @Qualifier("erpDataSource")
    @ConfigurationProperties(prefix="spring.datasource.erp")
    public DataSource erpDataSource() {
        return DataSourceBuilder.create().build();
    }
}

master数据源的sessionFactory、transactionManager等配置

package com.ehaoyao.paycenter.job.common.config;/**
 * 支付中心数据源配置类
 *
 * @author PF
 * Created by dell on 2018-05-04.
 */

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

/**
 * com.ehaoyao.paycenter.job.common.config
 * 支付中心数据源配置类
 *
 * @author PF
 * @create 2018-05-04 10:26
 **/
@Configuration
@MapperScan(basePackages = "com.ehaoyao.paycenter.persistence.pay.mapper.paycenter",
            sqlSessionFactoryRef = "payCenterSqlSessionFactory")
@EnableTransactionManagement
public class PayCenterDataSourceConfig {

    static final String MAPPER_LOCATION = "classpath:mappings/com/ehaoyao/paycenter
/persistence/pay/mapper/paycenter/*.xml";

    @Autowired
    @Qualifier("payCenterDataSource")
    private DataSource payCenterDataSource;


    @Bean(name = "payCenterTransactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        return new DataSourceTransactionManager(payCenterDataSource);
    }

    @Bean(name = "payCenterSqlSessionFactory")
    @Primary
    public SqlSessionFactory payCenterSqlSessionFactory(@Qualifier("payCenterDataSource")
DataSource payCenterDataSource)
            throws Exception {
        final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
        sessionFactory.setDataSource(payCenterDataSource);
        sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources(PayCenterDataSourceConfig.MAPPER_LOCATION));
        return sessionFactory.getObject();
    }
}

配置slave数据源的sessionFactory、transactionManager等配置。

(其中需要注意的是,配置多数据源后,spring.jpa 这些配置,就不要写在application里面了,没法对应多个啊,所以需要写在配置类中,如上getVendorProperties方法)
自己留着备查用的,master数据源用的mybatis,slave用的jpa(虽然用法很搞,但是主要为了记录多数据源,及springboot下jpa配置相关)

package com.ehaoyao.paycenter.job.common.config;/**
 * ERP数据源配置类
 *
 * @author PF
 * Created by dell on 2018-05-04.
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.util.Map;

/**
 * com.ehaoyao.paycenter.job.common.config
 * ERP数据源配置类
 * @author PF
 * @create 2018-05-04 10:27
 **/
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef="erpEntityManagerFactory",
        transactionManagerRef="erpTransactionManager",
        basePackages= { "com.ehaoyao.paycenter.persistence.pay.Repository" })
public class ErpDataSourceConfig {

    @Autowired
    @Qualifier("erpDataSource")
    private DataSource erpDataSource;

    @Bean(name = "entityManager")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder) {
        return erpEntityManagerFactory(builder).getObject().createEntityManager();
    }

    @Bean(name = "erpEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean erpEntityManagerFactory
(EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(erpDataSource)
                .properties(getVendorProperties(erpDataSource))
                .packages("com.ehaoyao.paycenter.persistence.pay.entity.erp")
                .persistenceUnit("erpPersistenceUnit")
                .build();
    }

    @Autowired
    private JpaProperties jpaProperties;

    private Map getVendorProperties(DataSource dataSource) {
     map.put("hibernate.dialect","org.hibernate.dialect.H2Dialect");
     map.put("hibernate.hbm2ddl.auto","update");
     map.put("spring.jpa.show-sql","true");
     jpaProperties.setProperties(map);
        return jpaProperties.getHibernateProperties(dataSource);
    }

    @Bean(name = "erpTransactionManager")
    public PlatformTransactionManager transactionManagerPrimary
(EntityManagerFactoryBuilder builder) {
        return new JpaTransactionManager(erpEntityManagerFactory(builder).getObject());
    }


}

踩坑爬坑

指定实体类包的位置

    @Primary
    @Bean(name = "payCenterEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean
    payCenterEntityManagerFactory(EntityManagerFactoryBuilder builder)
{
        return builder
                .dataSource(payCenterDataSource)
                .properties(getVendorProperties(payCenterDataSource))
                .packages("com.ehaoyao.paycenter.persistence.pay.entity",
                          "com.ehaoyao.pay.common.model")
                .persistenceUnit("payCenterPersistenceUnit")
                .build();
    }

指定多个的话语法为packages(“第一个”,”第二个”) 。其中源码如下: String… 为可变长参数

public EntityManagerFactoryBuilder.Builder packages(String... packagesToScan) {
    this.packagesToScan = packagesToScan;
    return this;
}

JPA常用注解,及注意事项

  • 使用jpa的save新增数据后,有些数据库设置了默认值的字段没有生效,要使其生效可以在对应entity上增加 @DynamicInsert(true)
  • 不想持久化的字段,比如有些字段只是实体类中临时存储,或仅为前端展示用,不需要在自动生成的insert、select语句中包含改字段,可以在实体类的改字段上添加 @Transient注解
    blob.jpg
  • springboot jpa自带的分页起始页为0 ,但是一般前端显示的时候,都是从1开始的。主动-1,解决方法很low,不知道有没有好的办法。
    Pageable pageable = new PageRequest(param.getPageIndex()==0?0:param.getPageIndex()-1, param.getPageSize(), sort); //jpa自带的分页是从第0页开始的,因此这里将传过来的页码-1

     

  • 使用jpa在生产环境需要注意的配置

ddl-auto:create----每次运行该程序,没有表格会新建表格,表内有数据会清空

ddl-auto:create-drop----每次程序结束的时候会清空表

ddl-auto:update----每次运行程序,没有表格会新建表格,表内有数据不会清空,只会更新

ddl-auto:validate----运行程序会校验数据与数据库的字段类型是否相同,不同会报错

jpa支持时间范围,及动态条件分页查询

之前的实例查询虽然可以支持动态条件,而且使用方便,但是对于一个字段需要传入多个参数的就不行了,比如 查询某个时间范
围内的数据。create between beginDate and endDate 。像这种需求,在真实案例中是很常见的。但是在网上找了一圈之
后,大多数是建议用@Query或者specification来实现。但要是这样的话,我还不如用mybatis呢。。。。。(而且基本都是这
篇文章的转载,
严重吐槽,百度前几页全是这个。。。。。原谅我已经不知道原作者是谁了,随便贴了一个链接,反正都一样。。)
之前实例查询代码如下:

    @Override
    public Page<RefundEntity> getDataList(RefundManEntity param) {

        ExampleMatcher exampleMatcher =
            ExampleMatcher.matching().withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING).
                withMatcher("createTime", ExampleMatcher.GenericPropertyMatchers.startsWith())
                .withMatcher("orderNume", ExampleMatcher.GenericPropertyMatchers.contains())
                .withMatcher("refundStatus", ExampleMatcher.GenericPropertyMatchers.contains());
        RefundEntity refundEntity=new RefundEntity();
        BeanUtils.copyProperties(param,refundEntity);
        Example<RefundEntity> example = Example.of(refundEntity, exampleMatcher);
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        Pageable pageable = new PageRequest(param.getPageIndex()==0?0:param.getPageIndex()-1,
param.getPageSize(), sort);   //jpa自带的分页是从第0页开始的,因此这里将传过来的页码-1
        Page<RefundEntity> pages = refundManageRepository.findAll(example, pageable);
        return pages;
    }

我自己的low解决方案,代码如下:

 @Override
    public Page<RefundEntity> getDataList(RefundManEntity param) {
        Specification<RefundEntity> querySpecifi = new Specification<RefundEntity>() {
            @Override
            public Predicate toPredicate(Root<RefundEntity> root, CriteriaQuery<?> criteriaQuery,
CriteriaBuilder criteriaBuilder) {

                List<Predicate> predicates = new ArrayList<>();
                if (StringUtils.isNotBlank(param.getBeginDate())) {
                    //大于或等于传入时间
                    predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createTime").
as(String.class),param.getBeginDate()));
                }
                if (StringUtils.isNotBlank(param.getEndDate())) {
                    //小于或等于传入时间
                    predicates.add(criteriaBuilder.lessThanOrEqualTo(
                        root.get("createTime").as(String.class), param.getEndDate()));
                }
                if (param != null) {
                    Class<? extends RefundManEntity> clazz = param.getClass();
                    Field[] fields = clazz.getDeclaredFields();
                    for (Field tmpField : fields) {
                        tmpField.setAccessible(true);
                        try {
//不为空的查询参数才拼查询条件,并且要去掉额外加上的时间范围条件
                            if (tmpField.get(param) != null &&
!tmpField.getName().equals("beginDate") && !tmpField.getName().equals("endDate"))
                            {
                                 //只拼字符串查询条件的,因为目前只需要按照 订单号、退款状态来查询
                                if (tmpField.getType().equals(String.class) &&
StringUtil.isNotBlank((String)tmpField.get(param)))
                                {
                                    String name = tmpField.getName();
                                    predicates.add(criteriaBuilder.equal(
                                        root.get(name), tmpField.get(param)));
                                }
                            }
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
                // and到一起的话所有条件就是且关系,or就是或关系
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        };
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        Pageable pageable = new PageRequest(param.getPageIndex() == 0 ? 0 : param.getPageIndex() - 1,
param.getPageSize(), sort);   
        //jpa自带的分页是从第0页开始的,因此这里将传过来的页码-1
        return refundManageRepository.findAll(querySpecifi, pageable);
    }
  • repository需要多加个继承
public interface RefundManageRepository extends JpaRepository<RefundEntity,Integer>
,JpaSpecificationExecutor<RefundEntity>{
}
  • 配置主子表,一对多关系
    @OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
    @JoinColumn(name="order_id")
    private List<OrderDetail> orderDetails;
  • 主键id配置自动增长
PS:@GeneratedValue注解的strategy属性提供四种值:

–AUTO: 主键由程序控制,是默认选项,不设置即此项。

–IDENTITY:主键由数据库自动生成,即采用数据库ID自增长的方式,Oracle不支持这种方式。

–SEQUENCE:通过数据库的序列产生主键,通过@SequenceGenerator 注解指定序列名,mysql不支持这种方式。

–TABLE:通过特定的数据库表产生主键,使用该策略可以使应用更易于数据库移植。
  • A different object with the same identifier value was already associated with the session
    实体类中主键id字段配置为@GeneratedValue auto,但是数据库中本身已经有数据了,就会报这个错。
    从源码可以看到,序列生成的方式为在数据库中新增了一个hibernate_sequence 表,这个表中会存储当前最大id
    blob.jpg
    把这个id改的比你已存在数据中最大id还大就可以了。

配置主子表,一对多关系(需要支持级联删除)

    @OneToMany(cascade= CascadeType.ALL,fetch= FetchType.EAGER,
mappedBy = "orderId")
//    @JoinColumn(name="order_id")
    private List<OrderDetail> orderDetails;

开源项目学习

vue与权限系统整合

vue如果集成在java项目中,不单独部署,如何与java项目整合(包含菜单权限管理)?

Vue相关基础内容

  • Vue.extend

由于没法像单独的vue项目那样,直接写component组件,在这里可以使用Vue.extend. 有了这个组建构造器,使用 Vue.component('menuItem',menuItem); 进行注册,就可以在项目中使用了。

springboot下配置多环境配置文件

在springboot项目下,想要区分开发、测试、预发、生产环境如何实现呢?
在application.yml配置文件中增加 spring.profiles.active即可。同时添加对应的配置yml文件如下图:
blob.jpg
在本项目中,默认只有开发环境(dev)和生产环境(prod)
在开发中,application.yml文件中配置成开发环境dev,如下图:
blob.jpg

那么在打成jar包,发布时应该如何指定对应的环境呢?

java -jar web-1.0.0.jar --spring.profiles.active=prod

在使用java -jar启动jar程序的时候,加上spring.profiles.active 即可。

权限验证安全相关

spring-security

可视化quartz定时任务

云存储文件上传

字典管理、系统管理

各种问题杂记

url中带#表示什么意思?

简单来说,对于服务器来说,#后面的被自动忽略了,访问的url还是#号前面的,如:http://localhost:8080/renren-admin/index.html#modules/sys/dept.html 来说,服务器接收到的请求还是http://localhost:8080/renren-admin/index.html ,那#后面的是给到浏览器用的,相当于锚点的概念。
解析参见: https://www.cnblogs.com/kaituorensheng/p/3776527.html#_label0

idea的git插件忽略无意义的项目配置等文件

在FILE->SETTINGS->PLUGINS 中安装.ignore插件,然后新建.ignore文件,如下图
blob.jpg

@ConditionalOnProperty 注解

blob.jpg

shiro部分


2.1.1 Subject
Subject一词是一个安全术语,其基本意思是“当前的操作用户”。称之为“用户”并不准确,因为“用户”一词通常跟人相关。在安全领域,术语“Subject”可以是人,也可以是第三方进程、后台帐户(Daemon Account)、定时作业(Corn Job)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
在程序中你都能轻易的获得Subject,允许在任何需要的地方进行安全操作。每个Subject对象都必须与一个SecurityManager进行绑定,你访问Subject对象其实都是在与SecurityManager里的特定Subject进行交互。
2.1.2 SecurityManager
Subject的“幕后”推手是SecurityManager。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。它是Shiro框架的核心,充当“保护伞”,引用了多个内部嵌套安全组件,它们形成了对象图。但是,一旦SecurityManager及其内部对象图配置好,它就会退居幕后,应用开发人员几乎把他们的所有时间都花在Subject API调用上。
那么,如何设置SecurityManager呢?嗯,这要看应用的环境。例如,Web应用通常会在Web.xml中指定一个Shiro Servlet Filter,这会创建SecurityManager实例,如果你运行的是一个独立应用,你需要用其他配置方式,但有很多配置选项。
一个应用几乎总是只有一个SecurityManager实例。它实际是应用的Singleton(尽管不必是一个静态Singleton)。跟Shiro里的几乎所有组件一样,SecurityManager的缺省实现是POJO,而且可用POJO兼容的任何配置机制进行配置 – 普通的Java代码、Spring XML、YAML、.properties和.ini文件等。基本来讲,能够实例化类和调用JavaBean兼容方法的任何配置形式都可使用。
2.1.3 Realms
Shiro的第三个也是最后一个概念是Realm。Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当与像用户帐户这类安全相关数据进行交互,执行认证(登录)和授权(访问控制)时,Shiro会从应用配置的Realm中查找很多内容。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件 等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
象其他内部组件一样,由SecurityManager来管理如何使用Realms来获取安全的身份数据。


shiro 与thymeleaf整合

添加引用

        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

使用方法如<a shiro:hasPermission="sys:menu:save" class="btn btn-primary" @click="add"><i class="fa fa-plus"></i> 新增</a> 当用户具有menu的保存权限才显示这个a标签

thymeleaf 模板替换路径问题

<header th:replace="../templates/layout/header :: head"></header>
<header th:replace="../templates/layout/header :: head"></header>
使用上面的几个路径一直提示找不到模板 template might not exist or might not be accessible by
因此跟源码看了下,进行替换比对的代码在AbstractConfigurableTemplateResolver 这个类下面(如下图)
blob.jpg
调试发现,前缀用的是classpath:/templates/ 即,这个模板相对路径是相对于templates的
blob.jpg

因此thymeleaf在找模板的时候会自动凑够templates文件夹目录寻找,我们的路径应当写成
<header th:replace="layout/header :: head"></header>
项目结构图如下:
blob.jpg

内置过滤器

blob.jpg
https://www.cnblogs.com/koal/p/5152671.html

idea查看依赖关系

在pom文件中,右键——》show Dependencies
blob.jpg
blob.jpg
像下面这种红色的表示依赖有问题的,。比如说多次引用等,但是只要不影响编译,不发生报错的其实也不一定非得排除例外。不然,太多了。
blob.jpg

验证码

添加kaptcha 的依赖包

        <dependency>
            <groupId>com.github.axet</groupId>
            <artifactId>kaptcha</artifactId>
            <version>${kaptcha.version}</version>
        </dependency>

配置类:

   @Bean
    public DefaultKaptcha producer() {
        Properties properties = new Properties();
        properties.put("kaptcha.border", "no");
        properties.put("kaptcha.textproducer.font.color", "black");
        properties.put("kaptcha.textproducer.char.space", "5");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }

详细使用方式见: https://www.cnblogs.com/java-spring/p/7793149.html

jpa中表关联

如存在实体类SysUserEntity,要体现部门人员关系,需要在SysUserEntity中增加字段 private SysDeptEntity sysDeptEntity;如何配置使得jpa的sql能够自动查询带出来呢?

    @OneToOne() //这里因为一个用户只会属于一个部门,所以用的OneToOne
    @JoinColumn(name="dept_id")     //这里为2个表关联条件字段
    private SysDeptEntity sysDeptEntity;

@OneToMany、@ManyToOne以及@ManyToMany讲解

springboot+spring cloud auth2搭建api接口

基于spring-security-oauth2搭建授权服务器


背景:

  1. 需要API网关控制权限,单点登陆。
  2. 做前后端分离的应用,前端使用vue+elementui实现。

       当前关于这方面的系统资料较少,因此大多是找寻网上零散的示例解析,结合官方文档中的demo再加上源码跟踪调试来进行学习与搭建。但由于涉及的知识点较多,且零散示例中配置或实现方式各有不同,作者经常只会记录关键、核心部分内容,因此会漏掉一些基础配置信息,给初学者带来极大困难。往往按照多个示例拼凑出来的demo无法正常运行,或者不明就里。官方给出的文档又需要具备一定英文功底。综合以上因素,这篇文章横空出世:)
既包含周边理论知识浅析,又包含实际案例demo完整代码(为了保证下载即可正常运行已附上SQL语句)

涉及知识点:

  1. Oauht2基础知识(授权类型,运行流程)
  2. 服务器证书,Keytool工具基本使用(常用命令参数解析及证书类别)
  3. SpringBoot项目增加https支持(https协议简单概述)
  4. Springsecurity知识(spring-security-oauth2是基于springsecurit的,很多配置相关都是沿用springsecurity的)
  5. 具体项目实现(包含服务端及客户端具体实现,鉴权、SSO)

Oauht2基础知识

       既然是要搭建基于spring-security-oauth2的授权服务器,那Oauth2的基础原理和运行流程我们还是需要了解一下的,否则对于授权模式选择和认证授权流程会比较晕。对于Oauth2的介绍这里强烈推荐: 理解OAuth2.0,在这里只简单描述和摘抄部分关键信息.
核心角色介绍
1. 三方客户端
2. 资源所有者
3. 认证服务器
4. 资源服务器

       为了便于理解,这里举一个大家耳熟能详的应用场景来介绍各个角色在实际应用中是如何交互的:在一个愉快的周六午后,小明同学吃完午饭就迫不及待的躺在床上,拿起手机打开吃鸡游戏(三方客户端),为了便于同朋友开黑,小明选择了微信登陆的方式,这时候吃鸡游戏(三方客户端)跳转到一个微信登陆认证页面(认证服务器),在这里小明(资源所有者)输入账号密码(身份认证),登陆成功后界面上显示:是否授权使吃鸡游戏可以访问你的微信头像、昵称、好友资料等。(授权)。授权通过后,剩下是后台处理,用户不可见(返回给客户端一个授权码,客户端拿到授权码结合开始申请授权时的APPKEY申请TOKEN,通过后客户端凭着这个TOKEN去资源服务器获取用户头像、昵称等信息)

客户端的授权模式
1. 授权码模式(authorization code)
2. 简化模式(implicit)
3. 密码模式(resource owner password credentials)
4. 客户端模式(client credentials)

       关于这4种授权模式的区别,详细内容请参见OAuth2.0,在这里我给出了自己比较土的解释:
授权码模式:安全性最高,也是比较常用的方式,但是整个流程最长。运行流程见上面举的“吃鸡”游戏登陆授权例子。
简化模式: 跳过了获取授权码的环节,后续流程同授权码模式。
密码模式:把账号密码给到客户端程序,由客户端程序去请求认证服务器获取token,这种方式客户端是能够获取到用户名、密码信息的。
客户端模式:这种其实就和用户授权没什么关系,需要认证的主体是客户端本身了,客户端本身以自己的名义去找认证服务器要授权。

服务器证书,Keytool工具基本使用

       涉及到鉴权,当然少不了https的应用。在使用https前,需要创建服务器证书,关于证书这部分的详细内容请参见:HTTPS与SSL证书概要
本文中,选择使用keytool工具来自己动手生成一个。由于springboot默认只支持jks和p12格式的。在此我们选择生成jks格式的证书,命令如下(需要注意的是得用管理员权限打开cmd命令行,否则生成keystore文件会失败,报filenotfound错误)
1. 选用JKS的证书,生成语句:
keytool -genkeypair -alias server -keyalg RSA -validity 3650 -keystore server.jks

其中需要特别注意到的是,在输入以上命令后,会要求你写名称、区域等信息,如下图
blob.jpg
这里的姓氏、名称是你的域名,如果域名与证书内容不符,访问时将报SSLHandshakeException: 由此可知是ssl 握手失败!的错误.

  1. 导出cer证书(给到第三步导入用的)
    keytool -export -alias server -keystore server.jks -rfc -file server.cer
  2. 导入到jdk中
    keytool -import -alias server -file server.cer -keystore cacerts
    生成文件如下:
    blob.jpg

踩坑爬坑

Bad Request This combination of host and port requires TLS

使用 http://localhost:8081/oauth/authorize?client_id=erpmanager&response_type=code&redirect_uri=www.forever24.cn 获取code码时返回报错信息
blob.jpg
解决方案:返回信息告诉我们,表示需要通过https来访问,用https://localhost:8081/oauth/authorize?client_id=testcode&response_type=code&redirect_uri=www.forever24.cn访问结果如下:

blob.jpg
选择信任后,转到登陆页面如下,这个登陆页面可以直接替换成自己的(需要自己写登陆页面,否则默认就是这个简陋的)
blob.jpg
client信息读取有in-memory,jdbc等多种方式,这里采用的是jdbc方式,简单来说就是自己从数据库中查询看然后跟用户输入的账号密码比对,需要自己重写UserService部分
blob.jpg
输入账号密码后,满怀期待:blush: ,but:又报错了,奶奶的熊,明明输入的账号密码是正确的为什么还报 Handling ClientRegistrationException error: No client with requested id: erpmanager 的错呢?
blob.jpg blob.jpg
而且,自己写的 SupplierInfo supplierInfo = supplierManRepository.findByuserName(username); 查询user信息中,明明用户名密码填写是对的,也有erpmanager这个用户 根据报错信息,显示的是2019-01-10 11:22:15.974 INFO 9444 --- [nio-8081-exec-4] o.s.s.o.p.e.AuthorizationEndpoint : Handling ClientRegistrationException error: No client with requested id: erpmanager根据关键字搜索可以找到,报错信息是从org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint报出来的,并且访问的是oauth/authorize,可以找到这个url对应的处理方法。
blob.jpg

获取code码,身份验证的代码在:
blob.jpg
获取token的入口代码在:
blob.jpg

Full authentication is required to access this resourceunauthorized
数据库表说明(使用spring-oauth-server的系统表):http://andaily.com/spring-oauth-server/db_table_description.html

关于SecurityConfig 的配置,需要先了解 构造器模式,否则比较难以理解。可以参见 https://www.cnblogs.com/iCanhua/p/8636085.html

    @Override
    protected void configure(HttpSecurity http) throws Exception { // @formatter:off
        http.requestMatchers().antMatchers("/login", "/oauth/authorize")
            .and().authorizeRequests().anyRequest().authenticated()
            .and().formLogin().permitAll();
    } // @formatter:on

豁然开朗,查下数据库的[oauth_client_details]表,发现根本没有设置 erpmanager 这个用户。 blob.jpg

windows下搭建vue开发环境

安装node.js 包管理器

  1. 官网 下载安装包
  2. 直接默认安装,安装完成后验证是否正常安装。打开命令提示符: 输入node -v。 如果显示版本号则证明安装完成。

安装vue

  1. 打开命令提示符输入: npm install vue
  2. 全局安装 vue-cli npm install --global vue-cli
  3. 创建一个基于 webpack 模板的新项目 my-project vue init webpack my-project
  4. 进入项目目录 cd my-project
  5. 运行项目 npm run dev

至此,环境安装完成,接下来就是如何提高全站工程师的自我修养了:yum:
blob.jpg

IDE的使用

因为我最早是.NET阵营的,所以对宇宙第一IDE之称的visual studio系列编辑器情有独钟。因此这里选择了visual studio code作为vue的编辑器,当然需要下载各种插件支持。
blob.jpg

目录结构简介

使用vscode打开刚才的项目文件结构如下图:
blob.jpg
关于,文件结构目录说明如下:
blob.jpg

代码自动生成工具

构建支持多种数据库类型的代码自动生成工具

背景:

一般的业务代码中写来写去,无外乎是先建好model,然后针对这个model做些CRUD的操作。(主要针对单表的业务操作)针对于数据库dao、mapper等的代码自动生成已经有了mybatisGenerator这种工具,但是针对于controller、service这些我们现在的接口api一般遵循的是restful风格,因此这些也是有规则可循的。举例有个goodsInfo 的model,针对于他的操作,肯定有 单个查询、list查询、修改、删除等。而这些代码没必要复制粘贴来一遍,完全可以由工具自动生成,若有特殊业务场景重写即可。本工具就算解决这类问题的。

效果截图

运行生成示例结果:
blob.jpg
blob.jpg

表选择界面:
blob.jpg

思路:

代码自动生成说起来很神秘,其实无外乎两个方面:
1. 从数据库拿到需要自动生成的代码对应表。
2. 从表结构、字段名生成对应的mapper、model、及controller、service等

如何拿到需要自动生成的代码对应表

sqlservr、mysql、oracle等这些主流数据库中都存在系统表结构的表,存储的是所有用户自己建立表的名称、字段等,所以直接查询这些系统表即可罗列出所有业务表。然后做个可视化界面供用户选择即可。(这里做一下更新,我实际项目中没有用sql查询的方式,因为不同数据库对于系统表的存储方式各不相同,查询语句写的太蛋疼了,实际采用的是 conn.getMetaData() 的方式,采用元数据来拿到指定数据库中各种表结构信息)

如何自动生成代码

有了表结构、字段名等如何自动生成代码呢,这个时候就需要模板引擎了。简单来讲可以理解为把固定的地方写死,变化的地方按照规则替换。
可以用我们小时候写作文的例子来说明。我们(作文厉害的请自动忽略 “们” :smiley:)小时候写作文,一般是3段式,开头、结尾、和中间流水账。 开头一般是描写环境心情、中间讲述具体故事,结尾总结赞美。

今天天气不错,风和日丽的,我们早早就来到了学些,大家都很开心。(开头)
    小明,突然在地上捡到了一个钱包……(一顿思想斗争,最后交给了警察叔叔)
最后,这个故事告诉了我们……(结尾)

从上面的示例范文中是不是很熟悉,。基本上都是这个结构,中间基本可以随意替换,最后都能凑成一篇基本合格的小学作文。而我们现在要做的就是把一些表名称、字段名称当做需要填充的内容填充到指定的代码段中去。

具体实现

获取数据库表、字段等信息

好了,上面讲了一大堆废话(背景和思路,个人觉得还是有必要的),下面到具体实现中来。
获取数据库表结构(表、字段)信息关键代码如下


@Service public class DbServiceImpl implements IDbService { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Value("${spring.datasource.driverClassName}") private String driverClassName; @Value("${spring.datasource.url}") private String url; @Value("${spring.datasource.username}") private String user; @Value("${spring.datasource.password}") private String pwd; @Override public List<TableEntity> getTables(String tableName) { List<TableEntity> tables = new ArrayList<>(); try { Class.forName(driverClassName);// 动态加载mysql驱动 Connection connection = DriverManager.getConnection(url, user, pwd); DatabaseMetaData metaData = connection.getMetaData(); ResultSet resultSet = metaData.getTables(null, null, "%", new String[]{"TABLE"}); // metaData.getTables("yjc", "", "%", new String[]{"TABLE"}) while (resultSet.next()) { if (resultSet.getString("TABLE_NAME").contains(tableName)) { TableEntity tmpTable = new TableEntity(); tmpTable.setTbName(resultSet.getString("TABLE_NAME")); tmpTable.setComments(resultSet.getString("REMARKS")); tmpTable.setCatalog(resultSet.getString("TABLE_CAT")); tmpTable.setSchema(resultSet.getString("TABLE_SCHEM")); tables.add(tmpTable); } } } catch (Exception e) { logger.error("获取数据库表列表失败", e); } return tables; } @Override public List<ColumnEntity> getColumns(String tableName) { List<ColumnEntity> columnEntityList = new ArrayList<>(); try { Class.forName(driverClassName);// 动态加载mysql驱动 Connection connection = DriverManager.getConnection(url, user, pwd); DatabaseMetaData metaData = connection.getMetaData(); ResultSet resultSet = metaData.getColumns(null, null, tableName, "%"); while (resultSet.next()) { ColumnEntity tmpColumnEntity = new ColumnEntity(); tmpColumnEntity.setColumnName(resultSet.getString("COLUMN_NAME")); tmpColumnEntity.setBufferLength(resultSet.getInt("BUFFER_LENGTH")); tmpColumnEntity.setColumnSize(resultSet.getInt("COLUMN_SIZE")); tmpColumnEntity.setComments(resultSet.getString("REMARKS")); tmpColumnEntity.setDecimalDigits(resultSet.getInt("DECIMAL_DIGITS")); tmpColumnEntity.setDataType(resultSet.getInt("DATA_TYPE")); tmpColumnEntity.setTypeName(resultSet.getString("TYPE_NAME")); tmpColumnEntity.setIsNullAble(resultSet.getString("IS_NULLABLE")); tmpColumnEntity.setIsAutoIncrement(resultSet.getString("IS_AUTOINCREMENT")); columnEntityList.add(tmpColumnEntity); } } catch (Exception e) { logger.error("查询表的列发生异常", e); } return columnEntityList; } @Override public TableEntity getTableEntity(String tableName) { TableEntity tableEntity = new TableEntity(); try { Class.forName(driverClassName);// 动态加载mysql驱动 Connection connection = DriverManager.getConnection(url, user, pwd); DatabaseMetaData metaData = connection.getMetaData(); ResultSet resultSet = metaData.getTables(null, null, tableName, new String[]{"TABLE"}); while (resultSet.next()) { if(tableName.equals(resultSet.getString("TABLE_NAME"))) { tableEntity.setTbName(resultSet.getString("TABLE_NAME")); tableEntity.setComments(resultSet.getString("REMARKS")); tableEntity.setCatalog(resultSet.getString("TABLE_CAT")); tableEntity.setSchema(resultSet.getString("TABLE_SCHEM")); tableEntity.setPk(this.getPrimaryKeyColumnName(metaData,tableEntity.getCatalog(),tableEntity.getSchema(),tableEntity.getTbName())); } } } catch (Exception e) { logger.error("获取表对象失败", e); } return tableEntity; } private String getPrimaryKeyColumnName(DatabaseMetaData metaData,String catalog,String schema,String tableName) { String primaryKeyColumnName=""; try { ResultSet resultSet = metaData.getPrimaryKeys(catalog, schema, tableName); while (resultSet.next()) { primaryKeyColumnName= resultSet.getString("COLUMN_NAME"); } } catch (SQLException e) { logger.error("获取主键发生异常",e); } return primaryKeyColumnName; } }

另外在使用 DatabaseMetaData获取表、列信息的时候,如

  DatabaseMetaData metaData = connection.getMetaData();
  ResultSet resultSet = metaData.getColumns(null, null, tableName, "%");
  DatabaseMetaData metaData = connection.getMetaData();
  ResultSet resultSet = metaData.getTables(null, null, "%", new String[]{"TABLE"});

获取表格信息、获取列信息都是返回的 ResultSet,这个ResultSet 有点蛋疼,需要按照字段来查询,或者指定索引顺序来获取想要的结果,对照关系如下面截图

blob.jpg

blob.jpg

使用模板生成代码

使用的是velocity引擎(当然也可以使用freemarker等,这个不重要)
模板代码示例如下:

package ${package}.${moduleName}.entity;

import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;

#if(${hasBigDecimal})
import java.math.BigDecimal;
#end
import java.io.Serializable;
import java.util.Date;

/**
 * ${comments}
 * 
 * @author ${author}
 * @email ${email}
 * @date ${datetime}
 */
@TableName("${tableName}")
public class ${className}Entity implements Serializable {
    private static final long serialVersionUID = 1L;

#foreach ($column in $columns)
    /**
     * $column.comments
     */
    #if($column.columnName == $pk.columnName)
@TableId
    #end
private $column.attrType $column.attrname;
#end

#foreach ($column in $columns)
    /**
     * 设置:${column.comments}
     */
    public void set${column.attrName}($column.attrType $column.attrname) {
        this.$column.attrname = $column.attrname;
    }
    /**
     * 获取:${column.comments}
     */
    public $column.attrType get${column.attrName}() {
        return $column.attrname;
    }
#end
}

package ${package}.${moduleName}.controller;

import java.util.Arrays;
import java.util.Map;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import ${package}.${moduleName}.entity.${className}Entity;
import ${package}.${moduleName}.service.${className}Service;
import ${mainPath}.common.utils.PageUtils;
import ${mainPath}.common.utils.R;



/**
 * ${comments}
 *
 * @author ${author}
 * @email ${email}
 * @date ${datetime}
 */
@RestController
@RequestMapping("${moduleName}/${pathName}")
public class ${className}Controller {
    @Autowired
    private ${className}Service ${classname}Service;

    /**
     * 列表
     */
    @GetMapping("/list")
    public  ResponseEntity<BaseResponse<Page<${className}>>>  list(@PageableDefault(value = 15, sort = { "${pk}" }, direction = Sort.Direction.DESC) Pageable pageable)
    {
        BaseResponse<Page<${className}> > baseResponse=new BaseResponse<>();
        Page<${className}> all = ${classname}Repository.findAll(pageable)};
        if(all!=null && !all.isEmpty())
    {
        return  new ResponseEntity<BaseResponse<Page<${className}>>>(BaseResponseFactory.success(all),HttpStatus.OK);
    }
        else
    {
        return  new ResponseEntity<>(HttpStatus.BAD_REQUEST);
    }
}


    /**
    * 单个查询
    */
    @GetMapping("/{${pk}}")
    @ApiOperation(value = "/{${pk}}", httpMethod = "GET", notes = "查询单个${className}信息}")
    public  ResponseEntity<BaseResponse<${className}>> info(@PathVariable long ${pk}) {
        Optional<${className}> optional = ${classname}Repository.findById(${pk});
        if(optional.isPresent())
        {
            return   new ResponseEntity<>(BaseResponseFactory.success(optional.get()), HttpStatus.OK);
        }else
        {
            return new ResponseEntity(HttpStatus.NOT_FOUND);
        }
    }


    /**
    * 保存
    */
    @PostMapping("/add")
    public ResponseEntity<BaseResponse<${className}>> save(@Validated @RequestBody ${className} goodsInfo, BindingResult bindingResult)
    {
        ResponseEntity<BaseResponse<${className}>> responseEntity;
        BaseResponse  baseResponse=new BaseResponse<>();
        if(bindingResult.hasErrors())
        {
            StringBuilder sb=new StringBuilder();
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                sb.append(fieldError.getDefaultMessage());
                sb.append(" ");
            }
            baseResponse.setCode(400);
            baseResponse.setMessage(sb.toString());
            responseEntity=new ResponseEntity<>(baseResponse,HttpStatus.BAD_REQUEST);
        }
        else
        {
            ${className} save = ${classname}Repository.save(${classname});
            baseResponse.setCode(200);
            baseResponse.setMessage("保存成功");
            baseResponse.setData(save);
            responseEntity=new ResponseEntity<>(baseResponse, HttpStatus.OK);
        }
        return  responseEntity;
    }


}

字段对应转换

如何将数据库的字段类型对应到java代码上,比如数据库中的varchar,需要对应到java的String,本例是参考了一个自动生成工具的方式,使用了对应配置表,内容如下。

#代码生成器,配置信息

mainPath=com.
#包名
package=redheart
moduleName=erp
#作者
author=pf
#Email
email=103868365@qq.com
#表前缀(类名不会包含表前缀)
tablePrefix=yjc_

#类型转换,配置信息
TINYINT=Integer
SMALLINT=Integer
MEDIUMINT=Integer
INT=Integer
INTEGER=Integer
BIGINT=Long
FLOAT=Float
DOUBLE=Double
DECIMAL=BigDecimal
BIT=Boolea
CHAR=String
VARCHAR=String
TINYTEXT=String
TEXT=String
MEDIUMTEXT=String
LONGTEXT=String
DATE=Date
DATETIME=Date
TIMESTAMP=Date

java开发常见问题实录

解决springboot默认序列化不支持年月日时分秒格式的问题(yyyy-MM-dd HH:mm:ss)

背景:

数据库中的时间格式是 yyyy-MM-dd HH:mm:ss ,但是springboot默认的json解析用的是jackson,他默认不支持yyyy-MM-dd HH:mm:ss这种格式的

解决方案

在application.yml文件中增加如下配置
blob.jpg

使用注解校验

使用bindingResult与实体类注解实现注解校验

一、引入validatorjar包,并在需要校验的实体类字段上增加校验注解

需要引入hibernate中的 org.hibernate.validator,在springboot2.x中,使用 javax.validation 然后在需要校验的实体类字段上增加校验注解如下:


@Entity @Table(name="sys_goods") public class GoodsInfo extends BaseModel { @Id @GeneratedValue private long goodsNo; @NotBlank private String goodsName; @NotBlank private String manufacturer; //生产厂家 @NotBlank private String specifications; //包装规格 @NotNull private BigDecimal price; //价格 @NotBlank private boolean beactive; //是否活动 public long getGoodsNo() { return goodsNo; } public void setGoodsNo(long goodsNo) { this.goodsNo = goodsNo; } public String getGoodsName() { return goodsName; } public void setGoodsName(String goodsName) { this.goodsName = goodsName; } public String getManufacturer() { return manufacturer; } public void setManufacturer(String manufacturer) { this.manufacturer = manufacturer; } public String getSpecifications() { return specifications; } public void setSpecifications(String specifications) { this.specifications = specifications; } public BigDecimal getPrice() { return price; } public void setPrice(BigDecimal price) { this.price = price; } public boolean isBeactive() { return beactive; } public void setBeactive(boolean beactive) { this.beactive = beactive; } }

附上常见校验注解:

@Null       验证对象是否为null

@NotNull     验证对象是否不为null, 无法查检长度为0的字符串

@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.

@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查

@AssertTrue     验证 Boolean 对象是否为 true  

@AssertFalse    验证 Boolean 对象是否为 false  

长度检查

@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内

@Length(min=, max=) 验证字符串的长度是否在给定的范围之内,包含两端

日期检查

@Past        验证 Date 和 Calendar 对象是否在当前时间之前

@Future     验证 Date 和 Calendar 对象是否在当前时间之后

@Pattern    验证 String 对象是否符合正则表达式的规则

数值检查:建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null

@Min            验证 Number 和 String 对象是否大等于指定的值  

@Max            验证 Number 和 String 对象是否小等于指定的值  

@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度

@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度

@Digits     验证 Number 和 String 的构成是否合法  

@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。


@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.

@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;

@Valid递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)

@CreditCardNumber信用卡验证

@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。

@ScriptAssert(lang= ,script=, alias=)

@URL(protocol=,host=, port=,regexp=, flags=)

二、在需要进行校验的controller中增加校验相关代码

使用示例如下:

 @PostMapping("/dbinfo")
    public @ResponseBody  ResponseEntity<BaseResponse<DbInfo>>  save(@Validated @RequestBody DbInfo newDbInfo, BindingResult bindingResult)
    {
        ResponseEntity<BaseResponse<DbInfo>> response=null;
        BaseResponse<DbInfo> baseResponse=null;
        if(bindingResult.hasErrors())
        {
            StringBuilder sb=new StringBuilder();
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                sb.append(fieldError.getDefaultMessage());
                sb.append(" ");
            }
            //提示错误信息
            baseResponse=new BaseResponse<>();
            baseResponse.setCode(400);
            baseResponse.setMessage(sb.toString());
            response=new  ResponseEntity<BaseResponse<DbInfo>>(baseResponse, HttpStatus.BAD_REQUEST);
        }
        else
        {
//            进行保存
            DbInfo savedResult = dbInfoRepository.save(newDbInfo);
            baseResponse=new BaseResponse<>();
            baseResponse.setData(savedResult);
            baseResponse.setCode(200);
            baseResponse.setMessage("保存成功");
            response=new  ResponseEntity<BaseResponse<DbInfo>>(baseResponse, HttpStatus.OK);
        }
        return  response;
    }