传统分页
PageHelper插件
是国内优秀的基于,mybatis的分页查询。支持主流数据库(mysql),自动的生成查询总记录数和分页语句,PageHelper内置了PageBean对象(PageInfo)
使用步骤(简化service和dao层代码开发)
export_dao
工程中applicationContext-dao.xml中
添加PageHelper的配置PageHelper的执行过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WA3BYmco-1605668991333)(assets\14.png)]
多租户的数据库设计方法(saas模式的数据库选型)
数据库类型
三范式
反三范式
数据库建模
<script>
//1.ztree的设置
var setting = {
check: {
enable: true
},
data: {
simpleData: {
enable: true
}
}
};
//2.ztree的数据
var zNodes =[
{
id:11, pId:1, name:"随意勾选 1-1"},
{
id:111, pId:11, name:"随意勾选 1-1-1"},
{
id:112, pId:11, name:"随意勾选 1-1-2"},
{
id:12, pId:1, name:"随意勾选 1-2"},
{
id:121, pId:12, name:"随意勾选 1-2-1"},
{
id:122, pId:12, name:"随意勾选 1-2-2"},
{
id:1, pId:0, name:"随意勾选 1"}
];
//3.页面初始化的时候,加载ztree树
var zTreeObj;
$(document).ready(function(){
//init方法,初始化ztree树
zTreeObj = $.fn.zTree.init($("#treeDemo"), setting, zNodes); //1.dom域,2、ztree的设置,3、数据
//可以调用zTreeObj对象的方法
zTreeObj.expandAll(true); //true:全部展开,false:折叠
});
/**
* 调用Ztree的API获取到选中节点的数据
*/
function getNodesId() {
//获取所有被勾选节点的集合
var nodes = zTreeObj.getCheckedNodes(true); //true:被勾选数据,false:未被勾选的
for(var i=0;i<nodes.length;i++) {
var node = nodes[i];
console.log(node.id);
}
}
script>
//展示的位置
<div>
<ul id="treeDemo" class="ztree">ul>
<button onclick="getNodesId()">保存button>
div>
进入分配页面
执行权限分配
new Md5Hash(password,salt,2).toString()
加密当用户登录成功之后,根据用户的角色,权限等数据查询出此用户可操作的所有模块(菜单),在页面端构造菜单数据
数据分析
代码实现
页面处理
<c:forEach items="${sessionScope.modules}" var="item">
<c:if test="${item.ctype==0}">
<li class="treeview">
<a href="#">
<i class="fa fa-cube">i> <span>${item.name}span>
<span class="pull-right-container"><i class="fa fa-angle-left pull-right">i>span>
a>
<ul class="treeview-menu">
<c:forEach items="${sessionScope.modules}" var="item2">
<c:if test="${item2.ctype==1 && item2.parentId == item.id}">
<li id="${item2.id}">
<a onclick="setSidebarActive(this)" href="${ctx}/${item2.curl}" target="iframe">
<i class="fa fa-circle-o">i>${item2.name}
a>
li>
c:if>
c:forEach>
ul>
li>
c:if>
c:forEach>
将用户的所有请求(执行的controller中的方法),保存到数据库中。
通过spring中的AOP的形式对所有controller中的方法进行增强
AOP的使用方式(xml + 注解)
开启AOP注解的支持
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
配置切面类
package cn.itcast.web.aspect;//web模块
@Component
@Aspect
public class LogAspect {
@Around(value="execution(* cn.itcast.web.controller.*.*.*(..))")
public Object aroud(ProceedingJoinPoint pjp){
//通过反射获取标记对象,内部封装了方法对象Method
MethodSignature ms =(MethodSignature)pjp.getSignature();
//获取方法
Method method = ms.getMethod();
//从方法的注解上获取name
if(method.isAnnotationPresent(RequestMapping.class)){
//有指定注解
RequestMapping annotation = method.getAnnotation(RequestMapping.class);
String name = annotation.name();
log.setAction(name);
}
//执行被代理对象的方法
return pjp.proceed();
}
}
resource/spring/applicationContext-shiro.xml
cn.itcast.web.shiro.AuthRealm
cn.itcast.web.shiro.CustomCredentialsMatcher
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvbAHqp0-1605668991334)(assets\17.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oM8gY21J-1605668991335)(assets\15.png)]
更改LoginController
在AuthRealm域对象中补充认证方法
/**
* 认证方法
* 参数:AuthenticationToken(UsernamePasswordToken)
* 用户登录时封装的用户名(邮箱)和密码
* 返回值:
* AuthenticationInfo :认证数据
* 1、安全数据(用户对象)
* 2、密码(非必须):为了方便操作数据库密码
* 3、realm域名称:当前类名
* 业务:
* 1、获取登录输入的用户名和密码
* 2、调用service查询
* 3、判断用户是否存在
* 3.1 用户存在,构造info返回值
* 3.2 用户不存在,返回null,自动抛出异常
*
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1、获取登录输入的用户名和密码
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
String email = upToken.getUsername();
String password = new String(upToken.getPassword());
//2、调用service查询(根据邮箱查询)
User user = userService.findByEmail(email);
//3、判断用户是否存在
if(user != null) {
//3.1 用户存在,构造info返回值
return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
}else {
//3.2 用户不存在,返回null,自动抛出异常
return null;
}
}
在密码比较器中实现密码比较方法doCredentialsMatch
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//1、获取用户登录输入的邮箱密码
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String email = upToken.getUsername();
String password = new String(upToken.getPassword());
//2、获取数据库密码
String dbPassword = (String)info.getCredentials();
//3、对用户输入的密码加密
password = Encrypt.md5(password,email);
//4、比较两个密码
return dbPassword.equals(password);
}
}
修改web.xml的servlet配置
修改Logincontroller中logout方法
//退出
@RequestMapping(value = "/logout",name="用户登出")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout(); //shiro的用户退出登录
return "forward:login.jsp";
}
过程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vYQyw8ga-1605668991337)(assets\16.png)]
授权:
步骤:
完成Realm域的授权方法
/**
* 授权方法
* 本质:提供操作用户的权限数据(权限名称集合)
* 参数:principalCollection(安全数据集合),只能获取唯一的安全数据(User用户对象)
* 返回值:
* AuthorizationInfo :授权数据(权限名称集合)
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1、获取当前登录用户(安全数据)
User user = (User) principalCollection.getPrimaryPrincipal();
//2、根据当前登录用户,查询此用户的所有模块权限 List
List<Module> modules = moduleService.findByUser(user);
//3、提取所有的模块名称:set集合(自动去重)
Set<String> permissions = new HashSet<>();
for (Module module : modules) {
permissions.add(module.getName());
}
//4、构造返回值
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setStringPermissions(permissions);
return info;
}
基于XML的权限配置
基于注解的权限配置
通过注解的形式替代xml的配置
页面标签库控制页面资源
根据当前登录用户的权限,动态控制按钮的隐藏与展示
在需要控制的资源(按钮,超链接)页面上引入shiro标签库
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
通过shiro:hasPermission控制显示对应的按钮
自定义异常处理器
shiro内部会根据用户的权限数据和所需权限自动判断
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O6RSBBsD-1605668991338)(assets\18.png)]
缓存优化
自定义过滤器
前身是阿里巴巴开源的框架,现在是apache提供的一个RPC框架(远程调用框架),解决分布式系统中web层和service层的调用。
dubbo的结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dZKTnYXL-1605668991339)(assets\19.png)]
相关软件的安装
注册中心zookeeper
windows系统:双击zkServer.cmd运行
Linux虚拟机
第一步:安装 jdk(略)
第二步:把 zookeeper 的压缩包(zookeeper-3.4.6.tar.gz)上传到 linux 系统
第三步:解压缩压缩包
tar -zxvf zookeeper-3.4.6.tar.gz
第四步:进入zookeeper-3.4.6目录,创建data目录
mkdir data
第五步:进入conf目录 ,把zoo_sample.cfg 改名为zoo.cfg
cd conf
mv zoo_sample.cfg zoo.cfg
第六步:打开zoo.cfg文件, 修改data属性:
dataDir=/root/zookeeper-3.4.6/data
进入bin目录,启动服务命令
./zkServer.sh start
停止服务命令
./zkServer.sh stop
查看服务状态:
./zkServer.sh status
防火墙开2181端口
* firewall-cmd --zone=public --add-port=2181/tcp --permanent
* firewall-cmd --reload
zookeeper安装成功之后暴露一个请求的地址:zookeeper://ip:2181
监控中心dubbo-admin
是dubbo官方提供的一个dubbo框架的监控中心,本质是一个war包。将此war包修改配置之后,部署到tomcat中即可
只支持jdk1.8
解压war包修改WEB-INF下的dubbo.properties
dubbo.registry.address=zookeeper://ip:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest
将文件夹拖拽到tomcat中启动
@Service:配置到提供者service实现类上(dubbo包下的)
@Reference:配置到需要注入的对象上(替换@Autowired)
传递数据时,对象需要实现序列化接口
服务提供者:
服务消费者:
启动方式:
将工程部署到tomcat中运行。优势:安全可靠,缺点:不利于开发
通过java的启动类。缺点:不安全,不可靠,优点:适用于开发阶段
不需要配置web.xml
/**
* 启动类:
* 加载spring配置文件,引导dubbo服务提供者启动
*/
public class HelloProvider {
public static void main(String[] args) throws IOException {
//1.加载spring配置文件
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext-provider.xml");
//2.启动spring容器
ac.start();
//3.输入任一项关闭
System.in.read();
}
}
dubbo的maven结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FnNMFr4u-1605668991340)(assets\20.png)]
提供者配置文件
spring和dubbo整合提供者配置
serivce事务的配置文件
web.xml(启动方式:如果tomcat启动配置,通过监听加载spring配置文件)
消费者配置文件
springmvc的配置文件
web.xml的配置文件
(1) 操作数据传输的对象,必须实现序列化接口
(2) 消费者的启动检查
<dubbo:consumer check="false">dubbo:consumer>
(3) 错误重试和超时
在消费者调用提供者过程中,由于网络波动,可能造成暂时的链接失败
通过在消费者端配置重试次数=0
<dubbo:consumer check="false" retries="0">dubbo:consumer>
(4) 集群和负载均衡
集群:部署多个提供者,共同承担系统压力
负载均衡 :消费者通过负载均衡算法自动的从集群中找到合适的提供者调用
Mybatis Generator: mybatis官方提供的一套工具(jar包),可以方便的根据数据库表创建实体类、dao接口、映射文件(只能生成最基础的CRUD操作的代码)
在企业中开发的时候
注意
public interface FactoryDao {
// 根据id删除
int deleteByPrimaryKey(String id);
//根据id查询
Factory selectByPrimaryKey(String id);
/**
* 可选属性保存
* 保存实体类的时候,将实体类中的非空数据构造为SQL语句,发送到mysql数据库
*/
int insertSelective(Factory record);
/**
* 可选属性更新(非空属性更新)
*/
int updateByPrimaryKeySelective(Factory record);
//条件查询
List<Factory> selectByExample(FactoryExample example);
}
条件查询的特点:
public void testSelectByExample() {
/**
* 条件查询:
* 1、创建example对象
* 2、通过example创建条件封装对象criteria
* 3、向criteria设置查询条件
* 4、调用selectByExample发起查询
* 案例:根据工厂名称查询
* SQL:SELECT * FROM co_factory WHERE factory_name = "升华"
* SELECT * FROM co_factory WHERE factory_name LIKE "升%"
* SELECT * FROM co_factory WHERE factory_name LIKE "升%" AND contacts='刘生'
*/
//1、创建example对象
FactoryExample example = new FactoryExample();
//2、通过example创建条件封装对象criteria
FactoryExample.Criteria criteria = example.createCriteria();
//3、向criteria设置查询条件 (查询字段,查询方法,数据)
//criteria中方法的语法:and + 属性名 + 查询方式(参数数据)
//criteria.andFactoryNameEqualTo("升华");
criteria.andFactoryNameLike("升%");
criteria.andContactsEqualTo("刘生");
//4、调用selectByExample发起查询
//Example如果是空,查询所有
List<Factory> list = factoryDao.selectByExample(example);
for (Factory factory : list) {
System.out.println(factory);
}
}
}
搭建环境:
multipart/form-data
细粒度权限:根据不同用户的分类,展示不同的数据列表
细粒度权限控制:数据库表的设计
管理本部门
管理下属部门
原生POI框架
EasyPOI
excel
搭建环境
创建Excel文件
public class PoiTest01 {
public static void main(String[] args) throws Exception {
//1、创建工作表(工作簿)
/**
* HSSFWorkbook : 处理2003版本EXCEL
* XSSFWorkbook : 处理2007版本EXCEL
* SXSSFWorkbook : 处理2007版本大数据量Excel
*/
Workbook wb = new XSSFWorkbook();
//2、创建第一页sheet
Sheet sheet = wb.createSheet("heima31");
//3、创建行对象(第四行)
Row row = sheet.createRow(3);//行索引,从0开始
//4、创建单元格对象(第五个单元格)
Cell cell = row.createCell(4);//单元格索引,从0开始
//5、向单元格写入数据(传智播客)
cell.setCellValue("传智播客");
//补充单元格和字体样式(封装样式对象)
CellStyle style = wb.createCellStyle();
//设置边框
style.setBorderLeft(BorderStyle.THIN); //左边框细线
//设置字体(字体对象)
Font font = wb.createFont();
font.setFontName("华文楷体");
font.setFontHeightInPoints((short)16);
style.setFont(font);
//单元格设置样式
cell.setCellStyle(style);
//6、通过API方法,生成Excel文件
wb.write(new FileOutputStream("D:\\demo.xlsx"));
}
}
解析Excel文件
public static void main(String[] args) throws Exception {
//1.根据Excel文件,创建工作簿
Workbook wb = new XSSFWorkbook("E:\\demo.xlsx");
//2.获取excel中的第一页
Sheet sheetAt = wb.getSheetAt(0);//页索引,从0开始
//3.循环所有的数据行
// sheetAt.getLastRowNum() :获取最后一个数据行的索引
for(int i=1 ;i<sheetAt.getLastRowNum() + 1;i++) {
//4.获取每一行
Row row = sheetAt.getRow(i);
//5.获取每行中的第4个单元格
Cell cell = row.getCell(3);
//6.获取单元格中的数据内容
String name = cell.getStringCellValue();
System.out.println(name);
}
}
EasyExcel:阿里巴巴开源的操作Excel的框架,底层封装的POI
EasyPOI:国内开源的一个操作Excel的框架,底层也是POI
基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
创建实体类对象
在实体类对象上通过注解配置 :标题,行高,列宽等数据
/**
* 在EasyExcel中,通过java代码生成或者解析Excel文件很简单。不需要创建繁琐的表头
* @ExcelProperty : 在创建Excel的时候,自动的读取实体类中的注解配置,生成表头
*/
@ContentRowHeight(20) //数据行高数
@HeadRowHeight(20) //表头高度
@ColumnWidth(15) //列宽
public class ContractProductVo implements Serializable {
@ExcelProperty("客户名称")
private String customName; //客户名称
......
@ExcelProperty("船期")
@DateTimeFormat("yyyy-MM-dd")
private Date shipTime; //船期
@ExcelProperty("贸易条款")
private String tradeTerms; //贸易条款
//省略get,set
}
java代码
在java代码中通过EasyExcel工具类完成excel文件的生成和下载
/**
* 使用EasyExcel完成excel的生成和下载
* 1、数据查询
* 2、设置下载信息
* 3、调用EasyExcel的工具类完成生成下载
*/
@RequestMapping("/printEasyExcel")
public void printEasyExcel(String inputDate) throws IOException {
//1、数据查询
List<ContractProductVo> list = contractService.findByShipTime(inputDate+"%");
//2、设置下载信息
response.setContentType("application/vnd.ms-excel"); //下载excel
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("出货表", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
//3、调用EasyExcel的工具类完成生成下载
EasyExcel.write(response.getOutputStream())
.head(ContractProductVo.class) //设置表头
.sheet("heima127") //指定页名称
.doWrite(list); //设置数据
}
模板打印
定义模板
java代码填充模板并下载Excel
/**
* 模板打印
*
*/
@RequestMapping("/printTemplate")
public void printTemplate(String inputDate) throws IOException {
//1.准备数据
List<ContractProductVo> list = contractService.findByShipTime(inputDate+"%");
Map map = new HashMap<>();
inputDate = inputDate.replaceAll("-0","-").replaceAll("-","年");
map.put("time",inputDate);
map.put("title1","客户名称");
//2.设置下载信息
response.setContentType("application/vnd.ms-excel"); //下载excel
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("出货表", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
//3.加载excel模板
String path = session.getServletContext().getRealPath("/")+"/make/tOUTPRODUCT.xlsx";
//4.创建EasyExcel的excelWtire对象( 用于数据填充)
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
.head(ContractProductVo.class) //设置表头
.withTemplate(path) //加载模板
.build();
//获取sheet对象
WriteSheet sheet = EasyExcel.writerSheet().build();
//5.调用方法完成填充map数据
excelWriter.fill(map,sheet);
//6.调用方法完成填充list数据
excelWriter.fill(list,sheet);
//7.属性资源,完成下载
excelWriter.finish(); //下载excel文件,释放内存资源
}
创建实体类对象(用于解析封装对象)
在实体类上配置注解(EasyExcel自动的根据注解配置获取文件中对应行的内容)
package cn.itcast.domain.cargo;
import cn.itcast.domain.BaseEntity;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import java.io.Serializable;
import java.util.List;
/**
* 合同下货物的实体类
* 通过EasyExcel完成文件解析,在实体类上通过注解配置表头
* 如果实体类中的属性,未配置@ExcelProperty
* 默认按照属性的编写顺序,从excel中获取数据
* 默认情况下,为配置注解的属性会影响数据封装
* 在类上配置一个注解:忽略未配置注解的属性
*
* @ExcelIgnoreUnannotated 忽略默认表头配置
* @ExcelProperty配置表头
*/
@ExcelIgnoreUnannotated
public class ContractProduct extends BaseEntity implements Serializable {
private String id;
@ExcelProperty("货号")
......
@ExcelProperty("生产厂家")
private String factoryName; //厂家名称,冗余字段
private String factoryId;
private List<ExtCproduct> extCproducts ; //货物和附件,一对多
//省略,get,set方法
}
调用EasyExcel工具类完成文件解析
/**
* 通过EasyExcel完成文件上传解析,批量货物保存
* 参数:购销合同id
* 参数:上传的excel文件对象
*/
@RequestMapping("/import")
public String importExcel(String contractId,MultipartFile file) throws Exception {
List<ContractProduct> list = EasyExcel.read(file.getInputStream())
.head(ContractProduct.class) //设置表头,将数据转化为目标对象
.sheet(0) //读取第一页数据
.doReadSync(); //解析excel,获取所有的数据
for (ContractProduct contractProduct : list) {
System.out.println(contractProduct);
contractProduct.setContractId(contractId);
contractProduct.setCompanyId(getLoginCompanyId());
contractProduct.setCompanyName(getLoginCompanyName());
}
contractProductService.saveAll(list);
return "redirect:/cargo/contractProduct/list.do?contractId="+contractId;
}
购销合同–> 提交—>合同管理—>勾选合同,添加出口报运单(向报运单表添加数据)–>展示出口报运单---->勾选出口报运单,完成海关保运工作(电子保运)
合同管理:展示出所有已上报的购销合同(查询购销合同中,状态=1)
将数据保存到数据库表(出口报运单)
将数据发送到海关(电子报运单)
报运单创建为PDF文件
搭建环境
数据准备
新增报运单页面进入代码
/**
* 进入到新增页面
* /cargo/export/toExport.do
* 参数:
* 同名参数id,多个id
* 通过springmvc可以将多个id,自动转化为数组 : String [] id
* 在web工程中,如果有多个同名参数请求,自动的转化为字符串(多个参数间通过“,”隔开)
* 业务逻辑
* 1、将多个id,转化为字符串,多个id间通过“,”隔开。存入request即可
* 2、页面跳转
*/
@RequestMapping("/toExport")
public String toExport(String id) {
request.setAttribute("id",id);
return "cargo/export/export-toExport";
}
新增报运单业务层逻辑
保存报运单数据
根据购销合同货物生成报运单商品数据
根据购销合同附件生成报运单附件数据
根据某一个货物查询所有货物的附件
每个附件构造一个报运单附件
BeanUtils.copyProperties(ecp, eep);//属性复制
报运单附件的属性赋值
修改报运单
提供者
创建工程导入依赖
@WebService //当写了此接口时,表明当前接口是一个webservice接口
public interface WeatherService {
String getWeatherByCityName(String cityName);
}
创建service接口和实现类
创建spring整合配置文件applicationContext-cxf-server.xml
配置web.xml
消费者
服务端
客户端 (重点)
和服务端开发商索要开发文档(word文档)
规定请求接口的信息
根据文档配置实体类
通过WebClient工具类发送请求即可
public class ClientTest {
public static void main(String[] args) {
//根据id调用提供者的方法 :http://localhost:8084/ws/us/userService/user/1,get
WebClient wc = WebClient.create("http://.../user/1");
User user = wc.get(User.class);
System.out.println(user);
WebClient wc = WebClient.create("http://.../user/1");
wc.delete();
}
}
模拟海关平台
海关保运业务
保运结果查询
发送报运数据
定时查询保运结果
定时任务:根据一定的时间规则自动的执行java方法。
定时任务框架:spring task ,quartz
quart介绍:
job (任务):定时执行的java类对象 (类)
jobdetail (任务描述):定时执行的类和方法(类.方法)
trigger (触发器):定义时间规则和待执行的任务描述
调度容器:协调管理所有的触发器
quartz入门
定义一个定时执行的java类和方法
配置spring和quartz整合
在web工程创建applicationContext-job.xml的
整合配置文件
<bean id="exportJob" class="cn.itcast.web.task.ExportJob">bean>
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject" ref="exportJob">property>
<property name="targetMethod" value="execute">property>
bean>
<bean id="trigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="cronExpression" value="0/5 * * * * ? *">property>
<property name="jobDetail" ref="jobDetail">property>
bean>
<bean id="startQuartz" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="trigger">ref>
list>
property>
bean>
quartz时间表达式
cron 七子表达式
秒 分 时 日 月 周 年
0-59 0-59 0-23 1-31 1-12 1-7 1970-2099
常用的符号
* 任意
? (忽略)任意(日 和 周 同时只能出现一个* 和一个?)年的配置(可以省略)
m/n 从M开始,每间隔N执行
m-n 从M开始,到N执行,整数执行
q,w,e,r,t,y 指定时间
L=Last
m#n 第n个m
页面跳转
@RequestMapping("/toCharts")
public String toCharts(String chartsType) {
return "stat/stat-"+chartsType;
}
搭建环境
概述
生命周期
JaperSoft-studio:模板设计工具
制作模板
修改页面
java代码填充数据
@RequestMapping("/exportPdf")
public void exportPdf(String id) throws Exception {
//根据id查询报运单对象
Export export = exportService.findById(id);
//根据报运单id查询报运单商品
ExportProductExample example = new ExportProductExample();
ExportProductExample.Criteria criteria = example.createCriteria();
criteria.andExportIdEqualTo(id);
List<ExportProduct> list = exportProductService.findAll(example);
//将报运单对象转化为map集合
Map<String, Object> map = BeanMapUtils.beanToMap(export);
//获取模板路径
String path = session.getServletContext().getRealPath("/")+"/jasper/export.jasper";
//加载模板,数据填充
JasperPrint print = JasperFillManager.fillReport(path, map, new JRBeanCollectionDataSource(list));
//预览pdf
//JasperExportManager.exportReportToPdfStream(print,response.getOutputStream());
byte[] bytes = JasperExportManager.exportReportToPdf(print); //获取到pdf文件的byte数组
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //创建ByteArrayOutputStream输出流
// 把pdf字节数组写入到缓存流中
byteArrayOutputStream.write(bytes);
new DownloadUtil().download(byteArrayOutputStream,response,"报运单.pdf");
}
中文处理
发送邮件包含两种协议:
发送邮件准备工作
发送邮件的工具类
import javax.mail.Address;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;
public class MailUtil {
/**
* 发送邮件工具类
* @param to : 邮件的接收人地址
* @param subject : 邮件的主体
* @param content : 邮件的正文
*/
public static void sendMsg(String to ,String subject ,String content) throws Exception{
//1.邮件服务器设置
Properties props = new Properties();
props.setProperty("mail.smtp.host", "smtp.sina.com"); //设置主机地址 smtp.qq.com smtp.sina.com
props.setProperty("mail.smtp.auth", "true");//认证
//2.产生一个用于邮件发送的Session对象
Session session = Session.getInstance(props);
//3.产生一个邮件的消息对象
MimeMessage message = new MimeMessage(session);
//4.设置消息的发送者
Address fromAddr = new InternetAddress("[email protected]");
message.setFrom(fromAddr);
//5.设置消息的接收者
Address toAddr = new InternetAddress(to);
//TO 直接发送 CC抄送 BCC密送
message.setRecipient(MimeMessage.RecipientType.TO, toAddr);
//6.设置主题
message.setSubject(subject);
//7.设置正文
message.setText(content);
//8.准备发送,得到火箭
Transport transport = session.getTransport("smtp");
//9.设置火箭的发射目标
transport.connect("smtp.sina.com", "[email protected]", "3b8823dd9d424f6f");
//10.发送
transport.sendMessage(message, message.getAllRecipients());
//11.关闭
transport.close();
}
public static void main(String[] args) throws Exception {
MailUtil.sendMsg("[email protected]","我们一起来画龙","左手和我一起画个龙,右手画一道彩虹,走起");
}
}
发送邮件
常见的消息中间件
应用场景
消息中间件的角色
RabbitMQ的准备:
管理后台:
消息类型:
RoutingKey
(路由key)RoutingKey
。Routing Key
进行判断,只有队列的Routingkey
与消息的 Routing key
完全一致,才会接收到消息Routingkey
一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
#
:匹配一个或多个词*
:匹配不多不少恰好1个词item.#
:能够匹配item.spu.insert
或者 item.spu
:只能匹配
item.spu`创建export_email工程配置依赖
搭建生产者环境
搭建消费者环境
配置spring整合消息消费者
配置启动类
day2:git
整体业务逻辑
海关保运(将数据发送到海关平台):webservice中的jax-rs的客户端 webclient
获取保运结果:为了及时获取结果,通过定时任务扫描查询
定时任务框架quartz(4个元素,cron表达式)
Spring initializr 介绍
SpringBoot程序搭建:
@SpringBootApplication
SpringApplication.run(类.class,args);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EUxd4iD7-1605668991341)(assets/1576124211841.png)]
pom文件(父工程,jdk版本,起步依赖)
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.3.RELEASEversion>
parent>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
在resoure/application.properties(空)
配置启动类(类上配置注解@SpringBootApplication, 配置main方法)
/**
* 引导类:
* 命名规则: xxxApplication
* 类上:@SpringBootApplication
* main:引导启动
* 业务代码:
* 需要放到引导类所在包及其子包下
*/
@SpringBootApplication
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class,args);
}
}
定义Controller
介绍
基本语法
数据格式
参数引用
回顾spring注解
@Configuration
:声明一个类作为配置类,代替xml文件@Bean
:声明在方法上,将方法的返回值加入Bean容器,代替
标签@Value
:属性注入读取配置文件内容
@Value
Environment工具类
@ConfigurationPropetties
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Asp3DdvJ-1605668991342)(C:/Users/skping/Desktop/assets/1575947723407.png)]
日志控制
springboot中内置了日志的处理。
需要在类上:@Slf4J (开启日志支持)
调用log.xxx方法,输出日志
在application.yml中配置输出的日志级别
#logging.level:固定
#error,warn,info,debug 输出范围,从右向左包含
logging:
level:
cn.itcast: info
静态资源
IP:端口/静态资源名称
访问拦截器
自定义拦截器
配置拦截器(注册拦截器)
定义一个配置类,继承webmvcconfigurer。重写方法
/**
* 注册拦截器
* 实现 WebMvcConfigurer 接口
* 重写 addInterceptors 方法
*/
@Configuration
public class MvcConfig implements WebMvcConfigurer {
/**
* registry : 注册器
* 所有的拦截器添加到注册器中即可
* 指定拦截的URL
* 不拦截的URL
*/
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/user/**")
.excludePathPatterns("/hello/**");
}
}
整合jdbc和事务
@Transactional
来控制事务整合连接池
SpringBoot中内置了HikariCP数据库连接池
只需要在SpringBoot配置文件中加入配置
#配置数据库连接池
spring:
datasource:
username: root
password: root
url: jdbc:mysql:///springbootdb
driver-class-name: com.mysql.jdbc.Driver
整合Mybatis
dao接口
dao映射文件
配置文件
#配置mybatis
mybatis:
mapper-locations: /mappers/**
type-aliases-package: cn.itcast.user.domain
configuration:
map-underscore-to-camel-case: true #自动识别驼峰标识 user_name -- userName
自动扫描(在引导类指定dao接口的包)
@MapperScan(cn.itcast.user.dao)
service实现类,注入dao查询
单元测试
起步依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
dependency>
创建一个单元测试类
指定单元测试环境和引导类
//指定单元测试环境
@RunWith(SpringRunner.class)
//指定引导类
@SpringBootTest(classes = BootMapperApplication.class)
public class UserDaoTest {
@Autowired
private UserDao userDao;
@Test
public void test() {
User user = userDao.findByUsername("wangwu");
System.out.println(user);
}
}
通用Mapper
导入依赖mapper-spring-boot-starter
配置实体类
@Table(name="tb_user")
配置到类上@ID
配置在主键属性上Column
配置在属性上,可省略,遵循驼峰标志规则配置dao接口
配置service和controller
配置SpringBoot
server:
port: 8088
#配置输出的日志级别
#error,warn,info,debug
logging:
level:
cn.itcast: debug
#配置数据库连接池
spring:
datasource:
username: root
password: root
url: jdbc:mysql:///springbootdb
driver-class-name: com.mysql.jdbc.Driver
@SpringBootApplication
@MapperScan("cn.itcast.user.dao")
public class MapperApplication {
public static void main(String[] args) {
SpringApplication.run(MapperApplication.class, args);
}
}
Thymeleaf简介
操作步骤:
基础语法
语法配置到html标签上 : th:
页面名称解析器
<html lang="en" xmlns:th="http://www.thymeleaf.org">
使用规则
Thymeleaf通过${}
来获取model中的变量,注意这不是el表达式,而是ognl表达式,但是语法非常像。
我们在页面获取user数据:
<h1>
欢迎您:<span th:text="${user.name}">请登录span>
h1>
感觉跟el表达式几乎是一样的。不过区别在于,我们的表达式写在一个名为:th:text
的标签属性中,这个叫做指令
循环
<tr th:each="user : ${users}">
<td th:text="${user.name}">Onionstd>
<td th:text="${user.age}">2.41td>
tr>
${users} 是要遍历的集合,可以是以下类型:
在迭代的同时,我们也可以获取迭代的状态对象:
<tr th:each="user,stat : ${users}">
<td th:text="${user.name}">Onionstd>
<td th:text="${user.age}">2.41td>
tr>
stat对象包含以下属性:
消息生产者
搭建springboot环境
配置rabbitmq的访问信息
spring:
rabbitmq:
virtual-host: saas #虚拟主机
username: saas
password: saas
host: 127.0.0.1
port: 5672
通过注入RabbitTemplate对象发送消息
rabbitTemplate.convertAndSend("springboot-rabbi2","a.b","802不见不散");
消息消费者
搭建springboot环境
配置rabbitmq的访问信息
配置一个消息监听器
@RabbitListener(
bindings = @QueueBinding(
value=@Queue(value="queue2",durable = "true"),
exchange=@Exchange(value="springboot-rabbi2",type= ExchangeTypes.TOPIC),
key="*.*"
)
)
public void message(String message) {
System.out.println("获取到消息:"+message);
}
@RabbitListener:
注解
导入起步依赖
spring-boot-starter-data-redis
在application.yml 配置redis访问信息
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
测试(通过注入RedisTemplate,操作redis数据库)
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest {
/**
* 此对象默认调用java的序列化接口对传输的数据进行序列化操作
* java底层的序列化操作效率低
* private RedisTemplate redisTemplate;
*/
/**
* 专门用于操作String类型数据的工具类
* private StringRedisTemplate redisTemplate;
* 需要将传输的对象,转化为json字符串的形式发送
*/
@Autowired
private StringRedisTemplate redisTemplate;
@Test
public void test() {
//存入数据
//redisTemplate.opsForValue().set("key1","heima115");
//取出基本数据
//String value = redisTemplate.opsForValue().get("key1");
//System.out.println(value);
//redisTemplate.opsForValue().set("key2","heima115",10, TimeUnit.SECONDS);
// Map map = new HashMap();
// map.put("name","zhangsan");
// map.put("age","12");
// redisTemplate.opsForHash().putAll("user",map);
//System.out.println(redisTemplate.opsForHash().get("user","name"));
Map<Object, Object> map = redisTemplate.opsForHash().entries("user");
map.forEach((key,value)-> System.out.println(key+"--"+value));
}
@Conditional注解
Spring4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建Bean操作
自定义条件
定义条件类:自定义类实现Condition接口,重写 matches 方法,在 matches 方法中进行逻辑判断,返回boolean值 。 matches 方法两个参数:
判断条件: 在初始化Bean时,使用 @Conditional(条件类.class)注解
2、条件注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKK2GqgO-1605668991343)(C:/Users/skping/Desktop/assets/2-condition注解.png)]
@Import注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wM6Djrks-1605668991343)(C:\Users\Skping\Desktop\assets\3-enable注解.png)]
@EnableAutoConfiguration注解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uOOVlFzk-1605668991344)(assets/1576124211841.png)]
Spring Boot Admin是一个开源社区项目,用于管理和监控SpringBoot应用程序。
Spring Boot Admin 有两个角色,客户端(Client)和服务端(Server)。
应用程序作为Spring Boot Admin Client向为Spring Boot Admin Server注册
Spring Boot Admin Server 的界面将Boot Admin ClientActuatorEndpoint
jar包发布
配置打包插件
通过maven命令package完成项目打包。
找到jar包位置,以命令行窗口执行
java -jar xxx.jar
war包发布
前端框架三巨头:Vue.js、React.js、AngularJS,vue.js以期轻量易用著称,vue.js和React.js发展速度最快,AngularJS还是老大。
npm:前端世界的maven,自动的下载前端依赖的js组件
npm是nodejs附带的功能,需要安装nodejs
npm默认从国外下载,需要切换国内镜像(nrm镜像切换工具)
安装nrm切换工具
下载安装解压,得到vue.js文件。
直接使用公共的CDN服务
推荐npm安装
npm init -y
进行初始化npm install vue --save
v-
前缀的特殊特性。插值表达式
花括号
{
{表达式}}
v-text和v-html
<div id="app">
v-text:<span v-text="hello">span> <br/>
v-html:<span v-html="hello">span>
div>
{ {}}
,在html标签上使用v-on
v-on指令用于给页面元素绑定事件。
事件绑定可以简写,例如v-on:click='add'
可以简写为@click='add'
<div id="app">
<button v-on:click="func">查看button><br/>
div>
v-model
双向绑定,视图(View)和模型(Model)之间会互相影响。
<div id="app">
用户名 <input v-model="name"> <br>
{
{name}}
div>
目前v-model的可使用视图元素有:
v-for
遍历数组
<div id="app">
<ul>
<li v-for="item in items">
{
{item.name}} : {
{item.gender}} : {
{item.age}}
li>
ul>
div>
在遍历的过程中,如果我们需要知道数组角标,可以指定第二个参数
index:迭代到的当前元素索引,从0开始。
<div id="app">
<ul>
<li v-for="(item,index) in items">
{
{item.name}} : {
{item.gender}} - {
{index}}
li>
ul>
div>
v-if和v-show
v-if:当得到结果为true时,所在的元素才会被渲染。
<div id="app">
{
{age}}
<div v-if="age<18">未成年人div>
<div v-else-if="age>=60">老年人div>
<div v-else>成年人div>
div>
v-show
只是简单地切换元素的 CSS 属性 display
。
<div v-show="flag">
我是v-show
div>
v-bind
动态渲染页面标签中属性值
v-bind:class
可以简写为:class
<div id="app">
<font v-bind:color="ys1">刘备font><br/>
<font :color="ys2">关羽font>
div>
watch
可以让我们监控一个值的变化。从而做出相应的反应。
<div id="app">
<input type="text" v-model="message">
div>
<script src="./node_modules/vue/dist/vue.js">script>
<script type="text/javascript">
var vm = new Vue({
el:"#app",
data:{
message:""
},
watch:{
message(newVal, oldVal){
console.log(newVal, oldVal);
}
}
})
script>
定义全局组件
组件其实也是一个Vue实例,因此它在定义时也会接收:data、methods、生命周期函数等
不同的是组件不会与页面的元素绑定,否则就无法复用了,因此没有el属性。
但是组件渲染需要html模板,所以增加了template属性,值就是HTML模板
全局组件定义完毕,任何vue实例都可以直接在HTML中通过组件名称来使用组件了。
data的定义方式比较特殊,必须是一个函数。
定义好的组件,可以任意复用多次
执行代码流程时,无论是否使用都会加载
<div id="app">
<counter>counter>
div>
<script src="./node_modules/vue/dist/vue.js">script>
<script type="text/javascript">
// 定义全局组件,两个参数:1,组件名称。2,组件参数
Vue.component("counter",{
template:`
`,
data(){
return {
count:0
}
}
})
var app = new Vue({
el:"#app"
})
script>
定义局部组件
-
在声明时不知道自己是组件,只是一个普通的js对象
-
注册给根对象后才能使用
-
执行代码流程时,只有注册了才会加载,因此用的更多
<div id="app">
<heima>heima>
div>
<script src="./node_modules/vue/dist/vue.js">script>
<script type="text/javascript">
// 定义全局组件,两个参数:1,组件名称。2,组件参数
var heima = {
template:
<div>
<button @click="num++">我是按钮,值为{
{
num}}</button>
</div>,
data(){
return{
num:18
}
}
}
var app = new Vue({
el:"#app",
compontents:{
//abc:heima 此时标签只能写abc
//heima:heima 一般写成一样的
heima //key和value一致时,可以简写
}
})
script>
组件通讯
-
父向子通讯使用子的props属性,子需要什么就传什么
-
子向父通讯需要父向子传递一套方法,被子调用
-
执行步骤
- 点击按钮,触发的是子的add方法
- 然后引用了父向子传递的jia方法
- 最后执行了对应的increment方法
-
一般实际使用时,三个方法名取一致的一个
19.7生命周期和钩子函数
生命周期
- 每个 Vue 实例在被创建时都要经过一系列的初始化过程 :创建实例,装载模板,渲染模板等等
- Vue为生命周期中的每个状态都设置了钩子函数(监听函数)
- 每当Vue实例处于不同的生命周期时,对应的函数就会被触发调用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rCGOX3Ew-1605668991345)(assets/生命周期详解.jpg)]
this
- 当前vue对象
钩子函数
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
name:"abc"
},
beforeCreate:function(){
console.log("beforeCreate:Vue实例创建之前");
console.log(this.$el);//undefind
console.log(this.$data);//undefind
},
created:function () {
//非常重要
console.log("created:Vue实例创建之后");
console.log(this.$el);//undefind
console.log(this.$data);//有数据
},
//渲染数据
beforeMount:function () {
console.log("beforeMount:VUE的data中数据挂载到html之前");
},
//渲染之后
mounted:function () {
//非常重要
console.log("mounted:VUE的data中数据挂载到html之后");
},
beforeUpdate:function () {
console.log("数据变化之前");
},
updated:function () {
console.log("数据变化之后");
},
beforeDestroy:function () {
console.log("VUE实例销毁前");
},
destroyed:function () {
console.log("VUE实例销毁后");
}
})
script>
19.8Ajax异步请求数据
//可以通过Terminal中通过npm install axios --save
//参数1是请求的地址 参数2是请求的内容
axios.post('company',this.dept).then(resp=>{
console.log(resp)
this.companyList = resp.data;
})
//原生ajax请求对比
$.ajax({
type: "post", //数据提交方式(post/get)
url: "/company", //提交到的url
data: {
"dept.deptName": this.dept.deptName, "dept.companyName":
this.dept.companyName},//提交的数据
dataType: "json",//返回的数据类型格式
success: function (msg) {
if (msg.success) {
app.findAll();
} else {
alert(response.data.message);
}
}
});
19.9解构
let arr = [1,2,3,4,5];
let [a,b,c,d...others] = arr;//others是一个数组
{User}如果写成{name:n,id:i,sex}
意思是直接把User对象中的三个属性取出来,并且可以选择性的赋给一个新的变量
19.10属性
- 属性只有三个地方可以声明
- data
- computed
- properties
传递的jia方法
-
最后执行了对应的increment方法
-
一般实际使用时,三个方法名取一致的一个
19.7生命周期和钩子函数
生命周期
- 每个 Vue 实例在被创建时都要经过一系列的初始化过程 :创建实例,装载模板,渲染模板等等
- Vue为生命周期中的每个状态都设置了钩子函数(监听函数)
- 每当Vue实例处于不同的生命周期时,对应的函数就会被触发调用。
[外链图片转存中…(img-rCGOX3Ew-1605668991345)]
this
- 当前vue对象
钩子函数
<script type="text/javascript">
var app = new Vue({
el:"#app",
data:{
name:"abc"
},
beforeCreate:function(){
console.log("beforeCreate:Vue实例创建之前");
console.log(this.$el);//undefind
console.log(this.$data);//undefind
},
created:function () {
//非常重要
console.log("created:Vue实例创建之后");
console.log(this.$el);//undefind
console.log(this.$data);//有数据
},
//渲染数据
beforeMount:function () {
console.log("beforeMount:VUE的data中数据挂载到html之前");
},
//渲染之后
mounted:function () {
//非常重要
console.log("mounted:VUE的data中数据挂载到html之后");
},
beforeUpdate:function () {
console.log("数据变化之前");
},
updated:function () {
console.log("数据变化之后");
},
beforeDestroy:function () {
console.log("VUE实例销毁前");
},
destroyed:function () {
console.log("VUE实例销毁后");
}
})
script>
19.8Ajax异步请求数据
//可以通过Terminal中通过npm install axios --save
//参数1是请求的地址 参数2是请求的内容
axios.post('company',this.dept).then(resp=>{
console.log(resp)
this.companyList = resp.data;
})
//原生ajax请求对比
$.ajax({
type: "post", //数据提交方式(post/get)
url: "/company", //提交到的url
data: {
"dept.deptName": this.dept.deptName, "dept.companyName":
this.dept.companyName},//提交的数据
dataType: "json",//返回的数据类型格式
success: function (msg) {
if (msg.success) {
app.findAll();
} else {
alert(response.data.message);
}
}
});
19.9解构
let arr = [1,2,3,4,5];
let [a,b,c,d...others] = arr;//others是一个数组
{User}如果写成{name:n,id:i,sex}
意思是直接把User对象中的三个属性取出来,并且可以选择性的赋给一个新的变量
19.10属性
- 属性只有三个地方可以声明
- data
- computed
- properties