简单的智能销售系统

一.框架搭建与集成

sssdj - SpringMvc+Spring+springDataJpa

1.导包

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<!--上下文支持包:支持以后Spring的邮件发送,定时器,模板-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context-support</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<!--事务-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-tx</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-jdbc</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-orm</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test</artifactId>
  <version>${org.springframework.version}</version>
  <!--代表测试只能在test包中使用-->
  <scope>test</scope>
</dependency>
<!-- 引入web前端的支持 -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-webmvc</artifactId>
  <version>${org.springframework.version}</version>
</dependency>
<!-- SpringMVC上传需要用到io包-->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-io</artifactId>
  <version>1.3.2</version>
</dependency>
<!-- 文件上传用到的包 -->
<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.2.2</version>
</dependency>
<!-- SpringMVC的json支持包 -->
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>${com.fasterxml.jackson.version}</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>${com.fasterxml.jackson.version}</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>${com.fasterxml.jackson.version}</version>
</dependency>
<!-- hibernate的支持包 -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>${org.hibernate.version}</version>
</dependency>
<!--hibernate对于jpa的支持包-->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-entitymanager</artifactId>
  <version>${org.hibernate.version}</version>
</dependency>
<!-- SpringData的支持包 -->
<dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-jpa</artifactId>
  <version>${spring-data-jpa.version}</version>
</dependency>
<!-- SpringDataJPA的擴展包 -->
<dependency>
  <groupId>com.github.wenhao</groupId>
  <artifactId>jpa-spec</artifactId>
  <version>3.1.1</version>
  <!-- 把所有的依賴都去掉 -->
  <exclusions>
    <exclusion>
      <groupId>*</groupId>
      <artifactId>*</artifactId>
    </exclusion>
  </exclusions>
</dependency>

<dependency>
  <groupId>commons-dbcp</groupId>
  <artifactId>commons-dbcp</artifactId>
  <version>1.2.2</version>
</dependency>

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.6</version>
</dependency>

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.5</version>
</dependency>
<!-- 測試包 -->
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>4.12</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <!-- 这个scope 只能作用在编译和测试时,同时没有传递性。表示在运行的时候不添加此jar文件 -->
  <scope>provided</scope>
</dependency>
<!-- 日志文件 -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>${org.slf4j.version}</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>${org.slf4j.version}</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.14</version>
</dependency>
<!-- 代码生成器模版技术 -->
<dependency>
  <groupId>org.apache.velocity</groupId>
  <artifactId>velocity</artifactId>
  <version>1.6</version>
</dependency>
<!-- shiro的支持包 -->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-all</artifactId>
  <version>1.4.0</version>
  <type>pom</type>
</dependency>
<!-- shiro与Spring的集成包 -->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring</artifactId>
  <version>1.4.0</version>
</dependency>
<!-- poi支持的jar包 -->
<!-- easypoi的支持 -->
<dependency>
  <groupId>cn.afterturn</groupId>
  <artifactId>easypoi-base</artifactId>
  <version>3.2.0</version>
</dependency>
<dependency>
  <groupId>cn.afterturn</groupId>
  <artifactId>easypoi-web</artifactId>
  <version>3.2.0</version>
</dependency>
<dependency>
  <groupId>cn.afterturn</groupId>
  <artifactId>easypoi-annotation</artifactId>
  <version>3.2.0</version>
</dependency>
<!-- 图片压缩功能 -->
<!-- 缩略图 -->
<dependency>
  <groupId>net.coobird</groupId>
  <artifactId>thumbnailator</artifactId>
  <version>0.4.6</version>
</dependency>
<!-- 定时调度 -->
<dependency>
  <groupId>quartz</groupId>
  <artifactId>quartz</artifactId>
  <version>1.5.2</version>
</dependency>
<!-- 邮件支持 -->
<dependency>
  <groupId>javax.mail</groupId>
  <artifactId>mail</artifactId>
  <version>1.4.1</version>
</dependency>
<!-- JSR 303 规范验证包 -->
<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>5.2.4.Final</version>
</dependency>

xml

<?xml version="1.0" encoding="UTF-8"?>

openEm org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter openEm /* characterEncodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true characterEncodingFilter /* org.springframework.web.context.ContextLoaderListener contextConfigLocation classpath:applicationContext.xml dispatcherServlet org.springframework.web.servlet.DispatcherServlet contextConfigLocation classpath:applicationContext-mvc.xml 1 dispatcherServlet / shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true shiroFilter /*

spring配置

<?xml version="1.0" encoding="UTF-8"?>

<!--Spring进行包的扫描-->
<context:component-scan base-package="cn.liyong.aisell.service" />

<!--引入jdbc.properties文件(必需加classpath) 记不住20个 -->
<context:property-placeholder location="classpath:jdbc.properties" />

<!--配置DataSource-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

<!--
    LocalContainerEntityManagerFactoryBean:就是一个FactoryBean对象,返回EntityManagerFactory对象给我们
    集成JPA:就是把EntityManagerFactory给它搞出来
        四大金刚,方言,建表策略,显示SQL
    Alt+Ins
 -->

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <!--配置连接信息-->
    <property name="dataSource" ref="dataSource"/>
    <!--配置包的扫描(Scan),认识JPA的注解 -->
    <property name="packagesToScan" value="cn.liyong.aisell.domain" />
    <!--
        配置适配器 Spring+JPA(ORM规范) -> 到底用的是哪一个框架来完成的
                JPA是有很多实现(Hibernate,OpenJPA,...)
     -->
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <!--方言(确定数据库)-->
            <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
            <!--建表策略  true:update  false:什么都不做-->
            <property name="generateDdl" value="false" />
            <!--支持SQL显示-->
            <property name="showSql" value="true" />
        </bean>
    </property>
</bean>

<!--配置Spring对于JPA的事务支持-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<!--支持事务的注解-->
<tx:annotation-driven transaction-manager="transactionManager" />

<!--用SpringDataJpa的方案去扫描它
    只要发现接口继承了JpaRepository接口,就自动完成CRUD以及分页等功能
     factory-class:告诉SpringdataJpa,我们要在使用BaseRepositoryFactoryBean
-->
<jpa:repositories base-package="cn.liyong.aisell.repository"
                  entity-manager-factory-ref="entityManagerFactory"
                  transaction-manager-ref="transactionManager"
                  factory-class="cn.liyong.aisell.repository.BaseRepositoryFactoryBean"
/>
<!--读取到shiro的配置文件-->
<import resource="classpath:applicationContext-shiro.xml" />

springmvc配置

<?xml version="1.0" encoding="UTF-8"?>



<context:component-scan base-package=“cn.liyong.aisell.web” />


mvc:annotation-driven
mvc:message-converters



application/json; charset=UTF-8
application/x-www-form-urlencoded; charset=UTF-8







</mvc:message-converters>
</mvc:annotation-driven>

<mvc:default-servlet-handler />









3000000



<context:component-scan base-package=“cn.afterturn.easypoi.view” />
<context:component-scan base-package=“cn.liyong.aisell.common” />

shiro配置

<?xml version="1.0" encoding="UTF-8"?>

<!-- securityManager:Spring创建核心对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="aiSellRealm"/>
</bean>


<bean id="aiSellRealm" class="cn.liyong.aisell.shiro.AiSellRealm">
    <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <property name="hashAlgorithmName" value="MD5"/>
            <property name="hashIterations" value="10" />
        </bean>
    </property>
</bean>

<!-- 建议大家把它留着,它可以支持我们做注解权限判断 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
      depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>

<!--
    Shiro中真正的过滤器:名称必需和web.xml中的那个过滤器名字一样
 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!--如果你没有登录,就会进入这个页面-->
    <property name="loginUrl" value="/s/login.jsp"/>
    <!--如果登录成功,就会进入这个页面-->
    <property name="successUrl" value="/s/main.jsp"/>
    <!--如果没有权限,就会进入这个页面-->
    <property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
    <!--
        anon:不需要权限也可以访问的页面
        authc:需要登录才能访问(有顺序
        路径 = perms[权限]
            /employee/index = perms[employee:index]
    -->
    <!--
    <property name="filterChainDefinitions">
        <value>
            /s/login.jsp = anon
            /login = anon
            /employee/index = perms[employee:index]
            /dept/index = perms[dept:index]
            /** = authc
        </value>
    </property>
    -->
    <property name="filterChainDefinitionMap" ref="filterChainDefinitionMapBuilderMap" />
    <!--引用自定义的权限过滤器-->
    <property name="filters">
        <map>
            <entry key="aiPerms" value-ref="aiSellPermissionsAuthorizationFilter"/>
        </map>
    </property>
</bean>
<bean  id="aiSellPermissionsAuthorizationFilter" class="cn.liyong.aisell.shiro.AiSellPermissionsAuthorizationFilter"/>
<!--最后的结果就是对应方法返回的Map值-->
<bean id="filterChainDefinitionMapBuilderMap" factory-bean="filterChainDefinitionMapBuilder"
      factory-method="createFilterChainDefinitionMap" />
<!--配置相应的bean-->
<bean id="filterChainDefinitionMapBuilder" class="cn.liyong.aisell.shiro.FilterChainDefinitionMapBuilder" />

SpringDataJpa

它是JPA规范的再次封装抽象,底层还是使用了Hibernate的JPA技术实现,引用JPQL的查询语句 ,是属于Spring的生成体系中的一部分。
SpringDataJpa使用起来比较方便,加快了开发的速度,使开发人员不需要关心和配置更多的东西。
SpringDataJpa上手简单,开发效率高,对对象的支持非常好,还十分的灵活。

我们实现了jpaPepositpry接口就可以进行crud,分页和排序

https://img-blog.****img.cn/20190331142651794.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NDgzNzM0MQ==,size_16,color_FFFFFF,t_70)

JpaSpecificationExecutor

我们要使用这个接口中的方法,首先让我们的接口也去继承这个接口
public interface EmployeeRepository extends JpaRepository<Employee,Long>,JpaSpecificationExecutor {}

Spring Data Jpa扩展

简单的智能销售系统applicationContext.xml 中修改配置

<jpa:repositories base-package=“cn.itsource.pss.repository” transaction-manager-ref=“transactionManager”
entity-manager-factory-ref=“entityManagerFactory”
factory-class=“cn.itsource.pss.repository.BaseRepositoryFactoryBean”
/>

继承BaseRepository
public interface EmployeeRepository extends BaseRepository<Employee,Long>{
}

测试扩展功能
//测试分页查询
@Test
public void testFindPageByQuery() {
EmployeeQuery baseQuery = new EmployeeQuery();
baseQuery.setUsername(“1”);
//baseQuery.setAge(20);
//baseQuery.setEmail(“2”);
baseQuery.setOrderByName(“username”);
baseQuery.setOrderByType(“DESC”);
//baseQuery.setCurrentPage(2);
baseQuery.setPageSize(5);
Page page = employeeRepository.findPageByQuery(baseQuery);
for (Employee employee : page) {
System.out.println(employee);
}
}

//测试单独查询
@Test
public void findByQuery() {
EmployeeQuery baseQuery = new EmployeeQuery();
baseQuery.setUsername(“1”);
//baseQuery.setAge(20);
//baseQuery.setEmail(“2”);
baseQuery.setOrderByName(“username”);
baseQuery.setOrderByType(“DESC”);
List emps = employeeRepository.findByQuery(baseQuery);
for (Employee employee : emps) {
System.out.println(employee);
}
}

//测试自定义JPQL
@Test
public void findByJpql() {
List emps = employeeRepository.findByJpql(“select o from Employee o where username = ? and password = ?”,“admin”,“admin”);
for (Employee emp : emps) {
System.out.println(emp);
}
}

*抽取

domain层

/**

  • 在JPA中,如果要让一类做父类:必需加上MappedSuperclass注解
    */
    @MappedSuperclass
    public class BaseDomain{
    @Id
    @GeneratedValue
    private Long id;

    public Long getId() {
    return id;
    }

    public void setId(Long id) {
    this.id = id;
    }
    }
    query层
    package cn.liyong.aisell.query;

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import sun.management.counter.perf.PerfInstrumentation;

/**

  • 作用:

  • 1.公共的代码都放到这里面(子类继承就可以少写代码)

  • 2.规范子类的代码
    */
    public abstract class BaseQuery {
    //当前页
    private int currentPage=1;
    //每页条数
    private int pageSize=10;
    private String order;
    private String sort;

    public String getOrder() {
    return order;
    }

    public void setOrder(String order) {
    this.order = order;
    this.orderByType=“desc”.equals(order);

    }

    public String getSort() {
    return sort;
    }

    public void setSort(String sort) {
    this.sort = sort;
    this.orderByName=sort;
    }

    //排序的类型(升序与降序) ASC/DESC
    private Boolean orderByType = true;
    //排序的字段 age/username/…
    private String orderByName;
    //要求子类必需有一个拿出到查询条件的方法
    public abstract Specification createSpec();
    public Sort createSort(){
    if(StringUtils.isNotBlank(orderByName)){
    //确定是什么方式排序
    Sort sort = new Sort(orderByType ? Sort.Direction.DESC : Sort.Direction.ASC, orderByName);
    return sort;
    }
    return null;
    }

    public int getCurrentPage() {
    return currentPage;
    }
    //专用于SpringDataJpa的分页(从0开始)
    public int getJpaPage(){
    return currentPage-1;
    }

    public void setCurrentPage(int currentPage) {
    this.currentPage = currentPage;
    }

    public int getPageSize() {
    return pageSize;
    }

    public void setPageSize(int pageSize) {
    this.pageSize = pageSize;
    }

    public Boolean getOrderByType() {
    return orderByType;
    }

    public void setOrderByType(Boolean orderByType) {
    this.orderByType = orderByType;
    }

    public String getOrderByName() {
    return orderByName;
    }

    public void setOrderByName(String orderByName) {
    this.orderByName = orderByName;
    }
    //专门加两个方法接收前台(EasyUI)的分页条数
    public void setPage(int page){
    this.currentPage = page;
    }
    public void setRows(int rows) {
    this.pageSize = rows;
    }
    }
    service层
    public interface IBaseService<T,ID extends Serializable> {
    //添加与修改数据
    void save(T t);
    //根据id删除一条数据
    void delete(ID id);
    //根据id查询到一条数据
    T findOne(ID id);
    //查询所有数据
    List findAll();
    //根据Query拿到分页对象(分页)
    Page findPageByQuery(BaseQuery baseQuery);

    //根据Query拿到对应的所有数据(不分页)
    List findByQuery(BaseQuery baseQuery);
    //根据jpql与对应的参数拿到数据
    List findByJpql(String jpql, Object… values);
    }
    service实现

    @Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
    public abstract class BaseServiceImpl<T,ID extends Serializable> implements IBaseService<T,ID> {
    /**

    • 这里咱们注入了BaseRepository这一个实现
    • 咱们系统中的BaseRepository这种类型的对象有很多:
    •      EmployeeRepository<Employee,Long>,DepartmentRepository<Department,Long>,...
      
    •  EmployeeServiceImpl extends BaseServiceImpl<Employee,Long>
      
    •  DepartmentServiceImpl extends BaseServiceImpl<Department,Long>
      

    */
    @Autowired
    private BaseRepository<T,ID> baseRepository;
    @Override
    @Transactional
    public void save(T t){
    baseRepository.save(t);
    }
    @Override
    @Transactional
    public void delete(ID id) {
    baseRepository.delete(id);
    }

    @Override
    public T findOne(ID id) {
    return baseRepository.findOne(id);
    }

    @Override
    public List findAll() {
    return baseRepository.findAll();
    }

    @Override
    public Page findPageByQuery(BaseQuery baseQuery) {
    return baseRepository.findPageByQuery(baseQuery);
    }

    @Override
    public List findByQuery(BaseQuery baseQuery) {
    return baseRepository.findByQuery(baseQuery);

    }

    @Override
    public List findByJpql(String jpql, Object… values) {
    return baseRepository.findByJpql(jpql,values);
    }
    }
    抽取一个head.jsp
    <%@ page contentType=“text/html;charset=UTF-8” language=“java” %>

前端页面进行搭建

用easyui按照客户需要的页面进行设计,然后进行数据的crud及排序功能

数据丢失问题

**方案一:**隐藏要传递的值(只隐藏,但是数据还是需要传递,这和第一个项目是同相同的方案)
这个方案的优点是简单易理解,缺点是如果字段过多,代码量会比较大,另外这种方案的安全性确实是有一些低!
**方案二:**在JPA的相应字段上加标签:
@Column(updatable = false)
private String password;
这个方案也比较简单,但是如果你需要修改这个字段的时候就比较麻烦!

**方案三:**先查询数据库,获取持久状态的对象,然后把页面的数据set到对象里面
(这种方案也是用得比较多的一种方案)
Employee tempEmployee = employeeService.get(employee.getId());
//需要修改的值就从页面里面的employee放入tempEmployee
tempEmployee.setUsername(employee.getUsername());
employeeService.save(tempEmployee);

部门修改的n-to-n

这时候大家继续开发会发现一个问题,在修改部门的时候,会出现n-to-n错误,就是咱们修改的时候也在相应的修改它的部门(这时候部门是一个持久化对象,它的id是不允许进行修改的。)
报错如下:

解决方案(在获到员工的时候把部门设置为空):
/**

  • 特性:在执行相应方法之前都会先执行这个方法
    */

    @ModelAttribute(“editEmployee”)
    public Employee beforeEdit(Long id, String cmd){
    //有id的时候-> 修改功能
    if(id!=null && “update”.equals(cmd)) {
    Employee employee = employeeService.findOne(id);
    //把这个要修改的关联对象设置为null,可以解决n-to-n的问题
    employee.setDepartment(null);
    return employee;
    }
    return null;
    }

代码生成器及权限

模板技术

2种常用的模板技术
velocity-1.6.3.jar 默认模板的后缀vm
freemarker-2.2.19.jar 默认模板的后缀ftl
模板技术和jsp的异同
Jsp是通过java文件编译和作用域结合生成html页面的,他就是一个模板技术
Velocity模板技术可以实现的功能
动态页面静态化:xxx.html
在后台准备数据,在前台准备模板,通过IO把数据与模板合并,真正的生成一个html页面出来
用作发送邮件、短信模板

代码生成器----EasyCode

先定义好模板,然后使用代码自动修改类名,有代码参照模板进行指定内容自动替换
先用一个临时模型Dept,进行测试
注:写代码生成器前,先把项目进行备份,以免写出问题无法挽回!

这次项目运用的是idea 的插件easycold,因为是基于Database Tool开发,所有Database Tool支持的数据库都是支持的。
功能
支持多表同时操作
支持同时生成多个模板
支持自定义模板
支持自定义类型映射(支持正则)
支持自定义附加列
支持列附加属性
所有配置项目支持分组模式,在不同项目(或选择不同数据库时),只需要切换对应的分组,所有配置统一变化。

权限设计—shiro

和Spring security区别
|框架| shiro |Spring security
|易用性|√|×
| 粒度|粗 |细(强大)

shiro的四大基石

身份验证、授权、密码学和会话管理 securityManager:核心对象 realm:获取数据接口
**

shiro的核心api

**
操作之前,先得到securityManager对象
//一.创建我们自己的Realm
MyRealm myRealm = new MyRealm();
//二.搞一个核心对象:
DefaultSecurityManager securityManager = new DefaultSecurityManager();
securityManager.setRealm(myRealm);
//三.把securityManager放到上下文中SecurityUtils.setSecurityManager(securityManager);
我们使用过的方法
//1.拿到当前用户
Subject currentUser = SecurityUtils.getSubject();//2.判断是否登录
currentUser.isAuthenticated();//3.登录(需要令牌的)/**
UnknownAccountException:用户名不存在
IncorrectCredentialsException:密码错误
AuthenticationException:其它错误
/
UsernamePasswordToken token = new UsernamePasswordToken(“admin”, “123456”);
currentUser.login(token);
//4.判断是否是这个角色/权限
currentUser.hasRole(“角色名”)
currentUser.isPermitted(“权限名”)
密码加密功能
/
*

  • String algorithmName, Object source, Object salt, int hashIterations)
  • 第一个参数algorithmName:加密算法名称
  • 第二个参数source:加密原密码
  • 第三个参数salt:盐值
  • 第四个参数hashIterations:加密次数
    */
    SimpleHash hash = new SimpleHash(“MD5”,“123456”,“itsource”,10);System.out.println(hash.toHex());

自定义Realm

继承AuthorizingRealm
实现两个方法:doGetAuthorizationInfo(授权) /doGetAuthenticationInfo(登录认证)
//身份认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.拿用户名与密码
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
String username = token.getUsername();
//2.根据用户名拿对应的密码
String password = getByName(username);
if(password==null){
return null; //返回空代表用户名有问题
}
//返回认证信息
//准备盐值
ByteSource salt = ByteSource.Util.bytes(“itsource”);
//密码是shiro自己进行判断
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,salt,getName());
return authenticationInfo;
}
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//拿到用户名 Principal:主体(用户对象/用户名)
String username = (String)principalCollection.getPrimaryPrincipal();
//拿到角色
Set roles = findRolesBy(username);
//拿到权限
Set permis = findPermsBy(username);
//把角色权限交给用户
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permis);
return authorizationInfo;
}

注意:如果我们的密码加密,应该怎么判断(匹配器)
//一.创建我们自己的Realm
MyRealm myRealm = new MyRealm();//创建一个凭证匹配器(无法设置盐值)
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();// 使用MD5的方式比较密码
matcher.setHashAlgorithmName(“md5”);// 设置编码的迭代次数
matcher.setHashIterations(10);//设置凭证匹配器(加密方式匹配)
myRealm.setCredentialsMatcher(matcher);

集成Spring

去找:shiro-root-1.4.0-RC2\samples\spring
导包

org.apache.shiro shiro-all 1.4.0 pom org.apache.shiro shiro-spring 1.4.0 web.xml 这个过滤器是一个代码(只关注它的名称) shiroFilter org.springframework.web.filter.DelegatingFilterProxy targetFilterLifecycle true shiroFilter /* application-shiro.xml 在咱们的application引入 是从案例中拷备过来,进行了相应的修改 <?xml version="1.0" encoding="UTF-8"?>
<!-- 创建securityManager这个核心对象 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- 设置一个realm进去 -->
    <property name="realm" ref="jpaRealm"/>
</bean>

<!-- 被引用的realm(一定会写一个自定义realm) -->
<bean id="jpaRealm" class="cn.itsource.aisell.shiro.JpaRealm">
    <!-- 为这个realm设置相应的匹配器 -->
    <property name="credentialsMatcher">
        <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <!-- 设置加密方式 -->
            <property name="hashAlgorithmName" value="md5"/>
            <!-- 设置加密次数 -->
            <property name="hashIterations" value="10" />
        </bean>
    </property>
</bean>

<!--  可以让咱们的权限判断支持【注解】方法 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
      depends-on="lifecycleBeanPostProcessor"/>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    <property name="securityManager" ref="securityManager"/>
</bean>


<!--  真正实现权限的过滤器 它的id名称和web.xml中的过滤器名称一样 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!-- 登录路径:如果没有登录,就会跳到这里来 -->
    <property name="loginUrl" value="/s/login.jsp"/>
    <!-- 登录成功后的跳转路径 -->
    <property name="successUrl" value="/s/main.jsp"/>
    <!-- 没有权限跳转的路径 -->
    <property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
    <!--
        anon:这个路径不需要登录也可以访问
        authc:需要登录才可以访问
        perms[depts:index]:做权限拦截
            咱们以后哪些访问有权限拦截,需要从数据库中读取
    -->
    <!--
    <property name="filterChainDefinitions">
        <value>
            /s/login.jsp = anon
            /login = anon
            /s/permission.jsp = perms[user:index]
            /depts/index = perms[depts:index]
            /** = authc
        </value>
    </property>
    -->
    <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap" />
</bean>
<!-- 实例工厂设置 -->
<bean id="filterChainDefinitionMap"
      factory-bean="filterChainDefinitionMapFactory"
      factory-method="createFilterChainDefinitionMap" />
<!-- 创建可以拿到权限map的bean -->
<bean id="filterChainDefinitionMapFactory" class="cn.itsource.aisell.shiro.FilterChainDefinitionMapFactory" /></beans>

获取Map过滤

注意,返回的Map必需是有序的(LinkedHashMap)
public class FilterChainDefinitionMapFactory {

/**
 * 后面这个值会从数据库中来拿
 * /s/login.jsp = anon
 * /login = anon
 * /s/permission.jsp = perms[user:index]
 * /depts/index = perms[depts:index]
 * /** = authc
 */
public Map<String,String> createFilterChainDefinitionMap(){
    //注:LinkedHashMap是有序的
    Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();

    filterChainDefinitionMap.put("/s/login.jsp", "anon");
    filterChainDefinitionMap.put("/login", "anon");
    filterChainDefinitionMap.put("/s/permission.jsp", "perms[user:index]");
    filterChainDefinitionMap.put("/depts/index", "perms[depts:index]");
    filterChainDefinitionMap.put("/**", "authc");

    return filterChainDefinitionMap;
}

}

登录

完成登录页面的设计
完成登录的功能
@RequestMapping(value="/login",method = RequestMethod.POST)
@ResponseBody
public JsonResult login(String username, String password){
//1.拿到当前用户
Subject currentUser = SecurityUtils.getSubject();
//2.如果没有登录,进行登录
if(!currentUser.isAuthenticated()){
//3.准备令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//4.实现登录
try {
currentUser.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
return new JsonResult(false, “用户名不存在!”);
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
return new JsonResult(false, “账号或密码错误!”);
} catch (AuthenticationException e) {
e.printStackTrace();// System.out.println(“就是登录不了(请联系程序员…)”);
return new JsonResult(false, “网络出错(联系管理员)!”);
}
}
//登录成功成功令牌
return new JsonResult();
}

登录细节

数据库的密码设置
要有一套自己的密码规则(md5,10次,盐值:liyong)
a.MD5Util
public class MD5Util {

public static final String SALT = "itsource";
public static final Integer HASHITERATIONS = 10;

//密码加密
public static String changePwd(String password){
    SimpleHash hash = new SimpleHash("MD5",password,SALT,HASHITERATIONS);
    return  hash.toHex();
}

}
添加用户密码加密
controller或者service中都可以进行[我们选择service]
@Overridepublic void save(Employee employee) {
if(employee.getId()==null){
//添加功能就进行密码修改
employee.setPassword(MD5Util.changePwd(employee.getPassword()));
}
employeeRepository.save(employee);
}
加密的判断必需和规则一致
applicationContext-shiro.xml(编码方式与次数)

<!-- 为这个realm设置相应的匹配器 -->
<property name="credentialsMatcher">
    <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!-- 设置加密方式 -->
        <property name="hashAlgorithmName" value="md5"/>
        <!-- 设置加密次数 -->
        <property name="hashIterations" value="10" />
    </bean>
</property></bean>

JpaRealm(加盐一致)
//身份认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.拿用户名与密码
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
String username = token.getUsername();
//2.根据用户名拿到相应的对象
Employee loginUser = employeeService.findByUsername(username);
if(loginUser==null){
return null; //如果用户用空代表用户名不存在
}
String password = loginUser.getPassword();
//返回认证信息
//准备盐值
//传的第一个值就是主体(username名称做的主体)
ByteSource salt = ByteSource.Util.bytes(MD5Util.SALT);
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,salt,getName());
return authenticationInfo;
}

其它细节

静态资源放行
有些地方没有登录也可以直接使用(FilterChainDefinitionMapFactory)
public Map<String,String> createFilterChainDefinitionMap(){
//注:LinkedHashMap是有序的
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();

    filterChainDefinitionMap.put("/s/login.jsp", "anon");
    filterChainDefinitionMap.put("/login", "anon");
    **//把所有静态资源进行放行**
    filterChainDefinitionMap.put("*.js", "anon");
    filterChainDefinitionMap.put("*.css", "anon");
    filterChainDefinitionMap.put("/easyui/**", "anon");
    filterChainDefinitionMap.put("/images/**", "anon");
    filterChainDefinitionMap.put("/js/**", "anon");

    filterChainDefinitionMap.put("/s/permission.jsp", "perms[user:index]");
    filterChainDefinitionMap.put("/**", "authc");

    return filterChainDefinitionMap;

登录过期

login.jsp
// 检查自己是否是顶级页面if (top != window) {// 如果不是顶级
//把子页面的地址,赋值给顶级页面显示
window.top.location.href = window.location.href;
}
回车登录
login.jsp
$(document.documentElement).on(“keyup”, function(event) {
//console.debug(event.keyCode);
var keyCode = event.keyCode;
console.debug(keyCode);
if (keyCode === 13) { // 捕获回车
submitForm(); // 提交表单
}
});
展示用户名与注销
main.jsp
<%@ taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>

欢迎您,亲爱的用户: 注销
LoginController @RequestMapping("/logout")public String logout(){ Subject currentUser = SecurityUtils.getSubject(); currentUser.logout(); return "redirect:/login"; } **角色管理** 先使用代码生成器生成Role,Permission 设置多对多的关系 //[email protected]@JoinTable(name = "employee_role", joinColumns = @JoinColumn(name="employee_id"), inverseJoinColumns = @JoinColumn(name="role_id")) private Set roles = new HashSet<>(); //[email protected]@JoinTable(name="role_permission", joinColumns = @JoinColumn(name="role_id"), inverseJoinColumns = @JoinColumn(name="permission_id")) private List permissions = new ArrayList<>(); 角色中(权限的展示) role.jsp


var rolePermissionGrid = $("#rolePermissionGrid");var allPermissionGrid = KaTeX parse error: Expected 'EOF', got '#' at position 3: ("#̲allPermissionGr…{i}].id`] = row.id;
}
return $(this).form(‘validate’);
},

});
},

//添加一个权限
addPerms(index, row){
//先拿到角色的所有权限
var allRows = rolePermissionGrid.datagrid(“getRows”);
//遍历进行比较
for(let o of allRows){
//如果两个权限相等,就什么都不做了
if(o.id == row.id){
$.messager.show({
title:‘注意事项’,
msg:‘这个权限已经存在,无需再进行添加!’,
timeout:2000,
showType:‘slide’
});
return;
}
}
rolePermissionGrid.datagrid(“appendRow”,row);
},
//删除对应权限
delPerms(index,row){
rolePermissionGrid.datagrid(“deleteRow”,index);
}
};

//创建当前角色对应的权限(grid控件)
rolePermissionGrid.datagrid({
    fit:true,
    fitColumns:true,
    singleSelect:true,
    border:false,
    onDblClickRow:itsource.delPerms
})

//创建拿到所有的权限的grid控件
allPermissionGrid.datagrid({
    fit:true,
    url:'/permission/page',
    fitColumns:true,
    singleSelect:true,
    pagination:true,
    border:false,
    onDblClickRow:itsource.addPerms
})

只增不减的问题

修改的时候只能添加不能减少
@ModelAttribute(“editRole”)public Role beforeEdit(Long id,String cmd){
//修改的时候才查询(只要有id会就进行一次查询,这是不对的)
if(id!=null && “update”.equals(cmd)) {
Role role = roleService.findOne(id);
//把要传过来的关联对象都清空,就可以解决n-to-n的问题
role.getPermissions().clear();
return role;
}
return null;
}

session处理

登录成功后主体为用户
以前登录成功,传的是username,现在传主体Employee对象
//身份认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(loginUser,password,salt,getName());
return authenticationInfo;
}
UserContext
session是从subject获取
存在shiro的session中后,HttpSession也会有值
public class UserContext {

public static final String USER_IN_SESSION ="loginUser";

//把登录成功的用户放到session中
public static void setUser(Employee loginUser){
    Subject subject = SecurityUtils.getSubject();
    //代表登录成功,把当前登录用户放到Session中去(shiro的session)
    //1.拿到session
    Session session = subject.getSession();
    //2.把当前登录成功的用户放到session中去
    session.setAttribute(USER_IN_SESSION, loginUser);

}

//获取到当前登录用户
public static Employee getUser(){
    Subject subject = SecurityUtils.getSubject();
    Session session = subject.getSession();
    Employee employee = (Employee) session.getAttribute(USER_IN_SESSION);
    return employee;
}

}

授权管理

FilterChainDefinitionMapFactory
保存所有权限过滤的数据都是从数据库中获取
@Autowiredprivate IPermissionService permissionService;public Map<String,String> createFilterChainDefinitionMap(){

//拿到所有权限
List perms = permissionService.findAll();
//设置相应的权限
perms.forEach(p -> {
filterChainDefinitionMap.put(p.getUrl(), “perms[”+p.getSn()+"]");
});

filterChainDefinitionMap.put("/**", "authc");

return filterChainDefinitionMap;

}

获取授权

授权部分的数据也是从数据库中获得的
应该拿到当前登录用户的所有权限
PermissionRepository
JPQL关联原则: 1.不写on 2.关联对象的别名.属性
//根据用户拿到他对应的所有权限
@Query(“select distinct p.sn from Employee e join e.roles r join r.permissions p where e.id = ?1”)
Set findPermsByUser(Long userId);
PermissionService(调用)
JpaRealm
拿到当前登录用户,再获取它的权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Employee loginUser = UserContext.getUser();
//根据当前用户拿到对应的权限
Set perms = permissionService.findPermsByUser(loginUser.getId());
//准备并返回AuthorizationInfo这个对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;

}

Ajax请求的权限处理

shiro处理没有权限是跳转页面,而我们如果是ajax请求,我们希望是返回json数据 ajax请求会有一个请求头:X-Requested-With: XMLHttpRequest 需要自定义一个shiro的权限过滤器

自定义权限过滤器

public class AisellPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {

    Subject subject = getSubject(request, response);
    // If the subject isn't identified, redirect to login URL
    if (subject.getPrincipal() == null) {
        saveRequestAndRedirectToLogin(request, response);
    } else {
        //一.拿到请求头
        HttpServletRequest req = (HttpServletRequest)request;
        // 拿到响应头
        HttpServletResponse resp = (HttpServletResponse)response;
        //设置响应头
        resp.setContentType("application/json;charset=UTF-8");
        String xr = req.getHeader("X-Requested-With");
        //二.判断这个请求头是否是Ajax请求
        if(xr!=null && "XMLHttpRequest".equals(xr)){
            //返回一个json {"success":false,"msg":"权限不足,请充值!"}
            resp.getWriter().print("{\"success\":false,\"msg\":\"你的权限不足,请充值!\"}");
        }else {
            //普通请求:拿到没有权限的跳转路径,进行跳转
            String unauthorizedUrl = getUnauthorizedUrl();
            if (StringUtils.hasText(unauthorizedUrl)) {
                WebUtils.issueRedirect(request, response, unauthorizedUrl);
            } else {
                WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }
    }
    return false;
}

}
applicationContext-shiro.xml

配置权限过滤器

entry key=“aisellPerms”:确定权限过滤器的名称









<!-- 配置自定义shiro过滤器 -->
<bean id="aisellPermissionsAuthorizationFilter" class="cn.itsource.aisell.shiro.AisellPermissionsAuthorizationFilter" />

修改过滤器配置
@Autowiredprivate IPermissionService permissionService;public Map<String,String> createFilterChainDefinitionMap(){

//拿到所有权限
List perms = permissionService.findAll();
//设置相应的权限
perms.forEach(p -> {
filterChainDefinitionMap.put(p.getUrl(), “aisellPerms[”+p.getSn()+"]");
});

}

菜单管理

员工 -> 角色 -> 权限 -> 菜单
Menu
菜单domain的自关连配置
需要配置双向,但是不能让JPA去管理一对多(我们自己管理:@Transient)
双向生成JSON会产生死循环,需要一边进行忽略:@JsonIgnore
//让它不再生成JSON
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = “parent_id”)
@JsonIgnore
private Menu parent;

// 临时属性 -> 这个字段JPA就不管它了
@Transient
private List<Menu> children = new ArrayList<>();

MenuRepository
public interface MenuRepository extends BaseRepository<Menu,Long>{
@Query(“select distinct m from Employee e join e.roles r join r.permissions p join p.menu m where e.id = ?1”)
List

findByUser(Long userId);
}
MenuService
根据设计只能通过员工找到子菜单
需要通过子菜单拿到父菜单
判断这个父菜单是否已经存到集合中
如果这个菜单单没有存起来,放到集合中 把当前这个子菜单放到父菜单中去
@Overridepublic List findLoginMenu() {
//1.准备一个装父菜单的容器
List parentMenus = new ArrayList<>();
//2.拿到当前登录用户的所有子菜单
Employee loginUser = UserContext.getUser();
List children = menuRepository.findByUser(loginUser.getId());
//3.遍历子菜单,设置它们的关系
for (Menu child : children) {
//3.1 根据子菜单拿到它对应的父菜单
Menu parent = child.getParent();
//3.2 判断这个父菜单是否在容器中
if(!parentMenus.contains(parent)){
//3.3 如果不在,把父菜单放进去
parentMenus.add(parent);
}
//3.4 为这个父菜单添加对应的子菜单
parent.getChildren().add(child);
}
return parentMenus;
}
UtilController中返回值
@Autowired
private IMenuService menuService;
@RequestMapping("/loginUserMenu")@ResponseBody
public List loginUserMenu(){
return menuService.findLoginMenu();
}
main.jsp修改路径
$(’#menuTree’).tree({
url:’/util/loginUserMenu’,

shiro:hasPermission
没有这个权限,就不展示对应的按键
<%@ taglib prefix=“shiro” uri=“http://shiro.apache.org/tags” %>
…<shiro:hasPermission name=“employee:delete”>
删除</shiro:hasPermission>
**

导入导出的认识

**
操作办公软件(word,excel,ppt) 03,07
JXL(只支持excel,内存优化) POI(功能强大,支持广泛)
POI的基本使用

导包

org.apache.poi
poi
3.11
org.apache.poi
poi-ooxml
3.11

创建 excel文件

//创建一个新的excel,并且写99乘法表:导出
@Test
public void testCreateExcel() throws Exception{
//1.创建一个工作薄(在内存中)
SXSSFWorkbook wb = new SXSSFWorkbook();
//2.创建一张表格
Sheet sheet = wb.createSheet(“99乘法表”);
//3.在表格创建行
for(int i=1;i<=9;i++){
Row row = sheet.createRow(i-1);
//4.在表格中创建列(格子)
for(int j=1;j<=i;j++){
Cell cell = row.createCell(j-1);
//5.格子中写东西
cell.setCellValue(i+""+j+"="+(ij));
}
}
FileOutputStream out = new FileOutputStream(“99.xlsx”);
wb.write(out);
out.close();
}

读取excel文件

//读取excel:导入
@Test
public void testReadExcel() throws Exception{
//读取了emp-poi.xlsx ,创建了Workbook(内存)
InputStream inp = new FileInputStream(“emp-poi.xlsx”);
Workbook wb = WorkbookFactory.create(inp);
//拿到对应的表
Sheet sheet = wb.getSheetAt(0);
//拿到这表的总行数
int lastRowNum = sheet.getLastRowNum();
for (int i = 1; i <= lastRowNum; i++) {
//拿到每一行
Row row = sheet.getRow(i);
//拿到这一行的总列数
short lastCellNum = row.getLastCellNum();
for (int j = 0; j < lastCellNum; j++) {
//拿到这一个格子与它的数据
Cell cell = row.getCell(j);
System.out.print(cell.getStringCellValue()+" ");
}
System.out.println();
}

EasyPOI的使用

文档:http://easypoi.mydoc.io/

导包

注意:今天使用EasyPOI需要把之前的poi导包去掉

cn.afterturn
easypoi-base
3.2.0
cn.afterturn
easypoi-web
3.2.0
cn.afterturn
easypoi-annotation
3.2.0
准备domain
POIEmployee
@ExcelTarget(“emp”)
public class PoiEmployee {
@Excel(name = “名称”)
private String name;
@Excel(name=“邮件”,width = 25)
private String email;
@Excel(name=“年龄”)
private Integer age;
@Excel(name=“性别”,replace = {“男_true”,“女_false”})
private Boolean sex;
@Excel(name=“出生日期”,format = “yyyy-MM-dd”)
private Date bornDate = new Date();
//type=2:代表这是一张图片
@Excel(name = “头像”,type = 2,height = 25)
private String headImage;
@ExcelEntity
private PoiDepartment department;

// 不要忘了加上getter,setter

}
POIDepartment
@ExcelTarget(“dept”)public class PoiDepartment {

@Excel(name="部门名称_emp,名称_dept")
private String name;

@Excel(name="部门地址_emp,地址_dept")
private String address;

@Excel(name="邮件_dept")
private String email;

// 不要忘了加上getter,setter

}

EasyPOI创建excel文件

/**
list的值自己准备一下

  • new ExportParams(title,sheetName):导出的属性设置
    title:表头名称
    sheetName:sheet表的名称

  • PoiEmployee .class:导出的实体类型

  • list:导出的数据(List)
    */
    Workbook workbook = ExcelExportUtil.exportExcel(new ExportParams(“部门名称”,“bbb”),
    PoiEmployee.class, list);
    FileOutputStream fos = new FileOutputStream(“poidept.xlsx”);
    workbook.write(fos);
    fos.close();
    EasyPOI读取excel文件
    ImportParams params = new ImportParams();
    //params.setTitleRows(1);
    params.setHeadRows(1);

    List list = ExcelImportUtil.importExcel(
    new File(“poiemp.xlsx”),
    PoiEmployee.class, params);

    list.forEach(e -> {
    System.out.println(e+","+e.getDepartment());
    });
    EasyPOI集成SpringMVC完成导入导出

导出功能

EasyPOI支持SpringMVC
扫描view
<context:component-scan base-package=“cn.afterturn.easypoi.view” />
配置视图解析器
p:order=“0” 先找这个bean的解析,再找其它的
前台传入相应的查询数据

用户名: 邮件: 部门: 查询 导出 后台接收参数进行导出 注:头像路径必需是真实路径 @RequestMapping("/download")public String download(ModelMap map, HttpServletRequest request,EmployeeQuery query){ //拿到所有员工 List employeeList = employeeService.findByQuery(query); //拿到当前项目的路径 String realPath = request.getServletContext().getRealPath(""); //修改(拼接)员工头像的路径 employeeList.forEach(e -> { e.setHeadImage(realPath+e.getHeadImage()); });
ExportParams params = new ExportParams("员工信息", "列表", ExcelType.XSSF);
params.setFreezeCol(5);

map.put(NormalExcelConstants.DATA_LIST, employeeList); // 数据集合
map.put(NormalExcelConstants.CLASS, Employee.class);//导出实体
map.put(NormalExcelConstants.PARAMS, params);//参数
map.put(NormalExcelConstants.FILE_NAME, "employee");//文件名称
//easypoiExcelView
return NormalExcelConstants.EASYPOI_EXCEL_VIEW;//View名称

}

组合关系

组合就是强聚合 ,聚合就是双向的多对一,一对多

强:最强级联 一方放弃关系维护
单据都会用到组合关系
保存的时候双方都能找到对象
//一方的配置/**
cascade = CascadeType.ALL:包含所有级联(增删改)
orphanRemoval = true:孤儿删除
mappedBy = “bill”:放弃关系维护
*/@OneToMany(cascade = CascadeType.ALL, mappedBy = “bill”, fetch = FetchType.LAZY, orphanRemoval = true)
private List items = new ArrayList();
//多方的配置@ManyToOne(fetch = FetchType.LAZY, optional = false)@JoinColumn(name = “bill_id”)@JsonIgnore
private Purchasebill bill;// 组合关系,非空

日期查询问题

SpringMVC获取与设置日期
get -> @JsonFormat(pattern = “yyyy-MM-dd HH:mm:ss”,timezone = “GMT+8”)
set -> @DateTimeFormat(pattern = “yyyy-MM-dd”)
easyui的日期控件

怎么解决查询有时分秒的问题

结束时间加一/大于等于开始时间,小于结束时间
//准备一个方法,创建相应的条件规则public Specification createSpec(){
//结束时间加一天
Date tempDate = null;
if(endDate!=null){
tempDate = DateUtils.addDays(endDate, 1);
}
//gt:大于 ge:大于等于
//lt:小于 le:小于等于
Specification spec = Specifications.and()
.ge(beginDate!=null,“vdate”,beginDate)
.lt(endDate!=null,“vdate”,tempDate)
.eq(status!=null,“status”,status)
.build();
return spec;
}

明细的问题

控件:http://www.easyui-extlib.com/ ->Datagrid-Edit -单元格编辑
研究控件的代码含义
//设置一些基本数据var dg = $("#dg1"),
defaultRow = { ID: “”, Code: “”, Name: “”, StandardModel: “”, ApplyCount: “”, Remark: “”, Stocks: “” },
insertPosition = “bottom”;//做一些基本的配置(创建datagrid控件)
var dgInit = function () {…//拿到刚添加的行数var getInsertRowIndex = function () { …//绑定相应的事件var buttonBindEvent = function () {//进入页面先执行相应的代码
dgInit(); buttonBindEvent();

数据修改

//完成editgrid的功能//定义定量//dg:拿到编辑的grid defaultRow:默认有哪些数据 insertPosition:插入数据的位置(底部)var dg = $("#itemsGrid"),
defaultRow = { product: “”, productColor: “”, productImg: “”, num: 0, price: 0, amount: 0, descs: “” },
insertPosition = “bottom”;
// 对grid的初始化设置var dgInit = function () {

var getColumns = function () {
    var result = [];
    //商品搞成下拉框,从后台获取到商品数据
    var normal = [
        {
            field: 'product', title: '商品', width: 80,
            editor: {
                type: "combobox",
                options: {
                    required: true,
                    panelHeight:'auto',
                    valueField:'id',
                    textField:'name',
                    url:'/util/findProducts'
                }
            },
            //加上format显示产品的名称
            formatter:function (v,r,i) {
                if(v)return v.name;
            }
        },
        {
            field: 'productColor', title: '颜色', width: 40,
            formatter: function (v, r, i) {
                if(r && r.product){
                    return `<div style="height: 20px;width: 20px;background: ${r.product.color}"></div>`;
                }
            }
        },
        {
            field: 'productImg', title: '图片', width: 100,
            formatter: function (v, r, i) {
                if(r && r.product){
                    return "<img src='"+r.product.smallpic+"' alt='没有图片' />";
                }
            }
        },
        {
            field: 'num', title: '数量', width: 100,
            editor: {
                type: "numberbox", //只允许输入数字
                options: {
                    precision:2, //保留两位小数
                    required: true
                }
            }
        },
        {
            field: 'price', title: '价格', width: 100,
            editor: {
                type: "numberbox",
                options: {
                    precision:2,
                    required: true
                }
            }
        },
        {
            field: 'amount', title: '小计', width: 100,
            formatter: function (v, r, i) {
                if(r.num && r.price){
                    //toFixed:保存几位小数
                    return (r.num * r.price).toFixed(2);
                }
                return 0;
            }
        },
        {
            field: 'descs', title: '备注', width: 100,
            editor: {
                type: "text"
            }
        }
    ];
    result.push(normal);

    return result;
};
var options = {
    idField: "ID",
    rownumbers: true,
    fitColumns: true,
    fit: true,
    border: true,
    title:"明细编辑",
    singleSelect: true,
    toolbar:"#itemsBtns",
    columns: getColumns(),
    //表示开启单元格编辑功能
    enableCellEdit: true
};

dg.datagrid(options);

};
//插入的行的位置(索引)var getInsertRowIndex = function () {
return insertPosition == “top” ? 0 : dg.datagrid(“getRows”).length;
}
//按钮的绑定事件var buttonBindEvent = function () {
//添加一行数据
$("#btnInsert").click(function () {
var targetIndex = getInsertRowIndex(), targetRow = $.extend({}, defaultRow, { ID: $.util.guid() });
//在datagrid中插入一行数据
dg.datagrid(“insertRow”, { index: targetIndex, row: targetRow });
//哪一行的哪一列要进行编辑
dg.datagrid(“editCell”, { index: targetIndex, field: “product” });
});

//删除一行数据
$("#btnRemove").click(function () {
    //获取选中的行
    var row = dg.datagrid("getSelected");
    //获取这一行的索引
    var index = dg.datagrid("getRowIndex",row);
    //根据索引删除这一行
    dg.datagrid("deleteRow",index);
});

};

//调用是相应的方法
dgInit(); buttonBindEvent();

添加与修改

添加时清空明细
dg.datagrid(“loadData”,[]);
修改时回显明细
//回显咱们的明细数据//复制一个明细数据var newItems = […row.items];
dg.datagrid(“loadData”,newItems);
保存时提交明细数据
//items[index].
editForm.form(‘submit’, {
//form提交的路径
url:url,
//提交之前你要做什么事件
onSubmit: function(param){
// 加一些自己的参数过去
//1.拿到编辑明细的所有数据
var rows = dg.datagrid(“getRows”);
//2.遍历所有数据,拼接成咱们需要的格式的参数
//items[0].product.id=1
for(let i=0;i<rows.length;i++){
var json = rows[i];
param[items[${i}].product.id] = json.product.id;
param[items[${i}].num] = json.num;
param[items[${i}].price] = json.price;
param[items[${i}].descs] = json.descs;
}

    // return false to prevent submit; 返回false阻止提交
    return $(this).form('validate');
},

后台保存与计算

双向找到对方,并且进行计算
private JsonResult saveOrUpdate(Purchasebill purchasebill){
JsonResult jsonResult = new JsonResult();
try {
//拿到采购订单的所有明细
List items = purchasebill.getItems();
//System.out.println(“一方获取多方:”+items);
//①.准备总金额与总数量
BigDecimal totalamount = new BigDecimal(0);
BigDecimal totalnum = new BigDecimal(0);
for (Purchasebillitem item : items) {
//System.out.println(“多方拿一方:”+item.getBill());
//设置明细对应的订单
item.setBill(purchasebill);
//计算每一个明细的小计
item.setAmount(item.getNum().multiply(item.getPrice()));
//②.总金额与总数量进行累加
totalamount = totalamount.add(item.getAmount());
totalnum = totalnum.add(item.getNum());
}
//③.把值设置到订单中去
purchasebill.setTotalamount(totalamount);
purchasebill.setTotalnum(totalnum);
purchasebillService.save(purchasebill);
} catch (Exception e) {
e.printStackTrace();
//代表出错
jsonResult.setSuccess(false);
jsonResult.setMsg(e.getMessage());
}
return jsonResult;
}

n-to-n:关连对象清空

@ModelAttribute(“editPurchasebill”)public Purchasebill beforeEdit(Long id,String cmd){
//修改的时候才查询(只要有id会就进行一次查询,这是不对的)
if(id!=null && “update”.equals(cmd)) {
Purchasebill purchasebill = purchasebillService.findOne(id);
//把要传过来的关联对象都清空,就可以解决n-to-n的问题
purchasebill.setSupplier(null);
purchasebill.setBuyer(null);
purchasebill.getItems().clear();
return purchasebill;
}
return null;
}

什么是报表

向上级报告情况的表格。简单的说:报表就是用表格、图表等格式来动态显示数据,可以用公式表示为:“报表 = 多样的格式 + 动态的数据”
表格:详细数据
图表: 直观
表格数据展示
准备了一 vo
报表中需的数据(准备的类)
public class PurchasebillitemVO {
//编号
private Long id;
//供应商
private String supplierName;
//采购员
private String buyerName;
//产品
private String productName;
//产品类型
private String productTypeName;
//日期
private Date vdate;
//数量
private BigDecimal num;
//单价
private BigDecimal price;
//小计
private BigDecimal amount;
//状态
private Integer status;

//分组字段
private String groupField;

public PurchasebillitemVO() {}
//创建时设置值
public PurchasebillitemVO(Purchasebillitem item,Integer groupBy) {
    this.id = item.getId();
    this.supplierName = item.getBill().getSupplier().getName();
    this.buyerName = item.getBill().getBuyer().getUsername();
    this.productName = item.getProduct().getName();
    this.productTypeName = item.getProduct().getTypes().getName();
    this.vdate = item.getBill().getVdate();
    this.num = item.getNum();
    this.price = item.getPrice();
    this.amount = item.getAmount();
    this.status = item.getBill().getStatus();

    //确定分组字段
    switch (groupBy){
        case 1:{
            this.groupField = this.buyerName;
            break;
        }
        case 2:{
            this.groupField = (DateUtils.toCalendar(this.vdate).get(Calendar.MONTH)+1) +"月份";
            break;
        }
        default:
            this.groupField =this.supplierName;
    }
}
...

}

前端展示

easyui的一个扩展控制:groupview
引入相应的js支持

高级查询字段

采购时间: - 状态: --请选择-- 未审 已审 作废 供应商 采购员 月份 查询 3D 2D
案例进行修改 //创建分组grid itemsGrid.datagrid({ //title:'', // width:500, // height:250, fit:true, rownumbers:true, remoteSort:false, nowrap:false, fitColumns:true, //url:'datagrid_data.json', toolbar:"#tb", url:'/purchasebillitem/findItems', columns:[[ {field:'id',title:'编号',width:100,sortable:true}, {field:'supplierName',title:'供应商',width:100,sortable:true}, {field:'buyerName',title:'采购员',width:100,sortable:true}, {field:'productName',title:'产品',width:100,sortable:true}, {field:'productTypeName',title:'产品类型',width:100,sortable:true}, {field:'vdate',title:'采购日期',width:100,sortable:true}, {field:'num',title:'数量',width:100,sortable:true}, {field:'price',title:'价格',width:100,sortable:true}, {field:'amount',title:'小计',width:100,sortable:true}, {field:'status',title:'状态',width:100,sortable:true,formatter:statusFormat} ]], groupField:'groupField', //指示要被分组的字段 view: groupview, groupFormatter:function(value, rows){ //组格式化 let num = 0,amount = 0; for(let r of rows){ num += r.num; amount += r.amount; } return value + ' - ' + rows.length + '条数据 共'+num+'条数据 总金额:'+amount +""; } });

图表展示

两种技术:flash(actionscript),h5(画布)
flash缺点:不安全,容易崩溃
IE的话只能是flash的方式
两个框架:highchart(收费,支持IE),echart(百度,开源免费)