简单的智能销售系统
一.框架搭建与集成
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,分页和排序
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
导包
<!-- 创建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” %>
…
…
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
导入导出的认识
**
操作办公软件(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的使用
导包
注意:今天使用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的解析,再找其它的
前台传入相应的查询数据
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支持
高级查询字段
图表展示
两种技术:flash(actionscript),h5(画布)
flash缺点:不安全,容易崩溃
IE的话只能是flash的方式
两个框架:highchart(收费,支持IE),echart(百度,开源免费)