CRM项目开发【实操篇----市场活动模块】
前言:本项目来源于B站动力节点视频,CRM项目开发 使用的后端技术栈主要是SSM框架,不涉及boot,老师讲的非常细致,推荐 关于流程图部分,由于是老师创作的,就不在这里贴出了,可以B站搜索动力节点的CRM项目,有下载链接,流程图的分析很到位,非常适合用于巩固SSM框架和熟悉业务流程。
01-项目配置文件
-
最重要的配置文件——web.xml 是Web 项目的核心配置文件,在服务器启动的时候加载,在该文件中导入SSM 的配置文件,可以认为是所有其他配置文件的父加载器。 -
Spring 配置文件——applicationContext.xml 扫描service 包下面的注解,导入MyBatis 的配置文件 -
SpringMVC 配置文件——applicationContext-mvc.xml 扫描controller 包下面的注解,配置视图解析器,DispatcherServlet 分发器 -
MyBatis 配置文件——applicationContext-datasource.xml 扫描mapper 包下面的注解,配置对应的数据源,与数据库连接
web.xml 配置applicationContext-mvc.xml 和applicationContext.xml
``applicationContext.xml配置 applicationContext-datasource.xml`
-
pom.xml 配置文件——指定maven 想配置的文件。 maven 默认情况下只会配置src/main/java 下的文件,通过配置maven 可以指定目录下的配置文件
02-部署过程及目录结构
-
将静态网页放在webapp 下面 -
src\main\java 和src\main\resources 两个文件夹中的内容在部署时会被idea 生成为classes 文件放到webapp\WEB-INF 下面 -
pom.xml 的配置文件会被编译为lib 文件夹放到webapp\WEB-INF 下面 -
文件目录树
webapps
? |–crm(本项目)
? |–WEB-INF
? |–web.xml(web基础配置文件)
? |–classes(项目文件)
? |–lib(经由pom.xml编译得到的一些依赖的jar包)
? |–pages(同样的也是HTML等页面资源,但是外界不可以直接访问)
? |–HTML页面等资源(该目录下的页面外界可以直接访问,存在安全问题,通常存放一些css和img)
? |–stucrm(其他的项目)
-
webapps 下的内容除了WEB-INF 下的内容都是可以直接访问的,因此将一些图片和格式文件直接存放在这个目录下; webapps/WEB-INF 下的内容都是要经过controller 处理的,所以要经过一定的保护。
03-首页功能分析与设计
开发基本过程:
分析需求
分析与设计
编码实现
测试
-
分析需求 -
分析与设计
controller 的创建原则:
如果两个页面不在同一个目录,新创建一个新的Controller
一个独立的资源访问目录就代表一个新的Controller
-
编码实现
controller 中的方法为什么是public :如果不是最高级别,分发器无法访问。
@RequestMapping("/") 中的路径在理论上应该写全,但是为了简便统一规定必须省略。
实现的时候使用请求转发?因为WEB-INF 中的内容对用户不可见。
idea对HTML和JSP的默认网页编码格式是不同的,HTML(ISO8859-1)、JSP(UTF-8),在HTML中要显式地指明。
return的字段是字符串,交给视图解析器处理,视图解析器中已经配置了/ ,不需要再带/ 。
<!--将HTML直接转换为JSP文件,要注意将前面的编码格式替换掉-->
<!DOCTYPE html> <!--被替换-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
@RequestMapping("/settings/qx/user/toLogin.do")
public String toLogin(){
return "settings/qx/user/login";
}
-
测试 -
基本流程图 客户端发起请求,访问默认路径,默认路径要重定向到登陆页面。
先写底层,再写上层,先完成被调用的对象。
04-登录功能分析与设计
-
分析需求
同步请求与异步请求
同步请求:整个页面都刷新
异步请求:局部页面刷新,也可以整个页面都刷新
所以看情况决定是使用同步请求还是异步请求。
局部刷新只能用异步,整个页面的刷新优先用同步。
-
分析与设计
service 类和Mappar 的创建原则:
与上述的Controller 层不同,这两个类的创建原则是根据数据库中的表,如果查的表不同,那么使用的就不一样。
前台Controller 看资源访问路径
后台service 和Mappar 看数据库中的表
-
编码实现
流程:
-
myBatis逆向工程 导入pom.xml的插件 添加配置文件 执行逆向工程 -
根据已经生成的Mapper编写Service层 -
再反写Controller层
想把控制层代码中处理好的数据,发送给页面上显示给用户看,一定考虑作用域。
作用域:
pageContext:一个页面的不同标签之间传递数据,从一个标签中传数据到另一个标签中
request:一个请求中有效(一次请求)
session:一个会话中有效(同一个浏览器窗口中的不同请求之间传递数据)
application:所有的用户都共享的数据,并且频繁使用的
需要注意的是,逆向工程在执行时不能执行多次,针对同一个表仅仅执行一次,逆向工程默认的是覆盖方法,所以之前写的内容会被覆盖掉。
BUG:The last packet successfully received from the server was x milliseconds ago
问题描述:获取到的数据库连接超时
产生原因: 程序启动时,在跟数据库首次交互时,获得了相应的数据库连接资源,从而进行正常的数据库读写操作。但是在下次进行数据库读写时(我的定时任务本身设置的时间间隔是24小时),应用程序认为这个连接是可以正常使用的(程序执行过一次之后没有退出,这个连接从来并没有被释放掉),但实际上,这个连接已经坏掉了,因为Mysql本身已经把这个连接标记为timeout了。于是,应用程序“傻乎乎”的在这个已经坏掉的数据通道上发起对数据库的读写请求,但是Mysql已经对这些请求不买账了。
解决方案:在配置数据库连接的url 后面加上
?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false
-
测试 -
基本流程图
05-记录密码功能实现和安全退出
-
记录密码的实现逻辑 第一次决定记录密码,向浏览器写Cookie;决定不记录密码,将本地Cookie删除 第二次后再访问时,判断有没有Cookie,有的话将Cookie一起传到服务器。 -
安全退出
06-登陆验证
- 路径问题
Controller 的方法中,重定向不需要写全路径,这里的重定向借助SpringMVC 框架,经过翻译后变成了response.sendRedirect("/crm/")
@RequestMapping("/settings/qx/user/logout.do")
public String logout(HttpServletResponse response, HttpSession session){
Cookie cookie = new Cookie("loginAct", "1");
cookie.setMaxAge(0);
response.addCookie(cookie);
Cookie cookie1 = new Cookie("loginPwd", "1");
cookie1.setMaxAge(0);
response.addCookie(cookie1);
session.invalidate();
return "redirect:/";
}
Interceptor (拦截器)中,重定向路径要写全,这里跟框架没关系了,要自己实现全部内容
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
HttpSession session = httpServletRequest.getSession();
User user = (User) session.getAttribute(Constant.SESSION_USER);
if (user == null){
httpServletResponse.sendRedirect(httpServletRequest.getContextPath());
return false;
} else {
return true;
}
}
- 排除拦截在拦截器中优先级更高,拦截器和排除拦截同时生效的时候,一定是排除拦截优先生效并放行。
07-页面切割技术
-
早期技术:<frameset> 和<frame> 进行切割 <frameset cols="20%,60%,20%"> :用于切割界面,按照列进行切割,左边20%,中间60%,右边20%;行切割使用rows 。 <frame> :显示页面,<frame src="url"> 使用url 对内容进行填充,显示的是其他页面的内容 <frameset cols="20% 60% 20%">
<frame src="null" name="f1">
<frame src="null" name="f2">
<frame src="null" name="f3">
</frameset>
每一个<frame> 都是一个独立的浏览器窗口,只不过面积小一点,通过命名name 属性,我们可以在其他超链接中指明在哪个浏览器中打开,是一种重量级的标签。
<a href="url" target="f3">test</a>
-
较新技术:<div> 和<iframe> <div> :切割页面。<div style="height:10%;width=20%"> 高度与宽度占据的屏幕比例 <iframe> :显示页面。 <div style="height:10%;width=20%">
<iframe href="url">
</div>
效率高的轻量级标签。
08-市场活动模块----创建市场活动
-
模态窗口:模拟的窗口,时间发生后的弹出的小窗口,能够独立显示网页的一个小窗口。
window.open("url","_blank") 在新窗口中弹出一个以url 为内容的小窗口,难点在于两个网页之间的信息交互
本质上弹出一个<div> ,通过设置z-index 的大小来实现的。 初始时,z-index 初始参数是<0 的,需要显示的时候,将该值变为大于0的,就实现了显示。(bootstrap 实现的) 使用新技术实现,模态窗口就是一个<div> 标签,所以整个模态窗口只是大窗口的一个标签,实现信息交互非常简单。
-
控制模态窗口的显示与隐藏
-
方法一:通过data-toggle="modal" data-target="id" 实现,id 是窗口的id ,这个方法是要在标签上加的,所以无法进行一些代码的初始化 <div class="btn-toolbar" role="toolbar" style="background-color: #F7F7F7; height: 50px; position: relative;top: 5px;">
<div class="btn-group" style="position: relative; top: 18%;">
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#importActivityModal"><span class="glyphicon glyphicon-plus"></span> 创建</button>
</div>
</div>
-
方法二(常用):通过js 函数控制,选择器选择标签,使用[标签对象].modal("show") 和[标签对象].modal("hide") 实现显示和隐藏。这种方法的好处就在于可以进行一些初始化。 <script type="text/javascript">
$(function (){
$("#createActivityBtn").click(function (){
// 做一些初始化工作
// 弹出窗口
$("#createActivityModal").modal("show");
});
});
</script>
<div class="btn-toolbar" role="toolbar" style="background-color: #F7F7F7; height: 50px; position: relative;top: 5px;">
<div class="btn-group" style="position: relative; top: 18%;">
<button type="button" class="btn btn-primary" id="createActivityBtn"><span class="glyphicon glyphicon-plus"></span> 创建</button>
</div>
</div>
-
方式三:只能关闭不能显示,通过标签的属性实现data-dismiss="" ,点击了添加该属性的标签,点击了对象就会使模态窗口关闭。 -
创建市场活动 为什么步骤18是个异步请求,因为这是一个局部更新的问题,将列表更新就可以了。 -
用户登录的时候,是一种查询操作,所以查出来看看是不是空,做个比较就可以。 但是当插入修改时,这是一种会变动数据库的操作,所以看看会不会产生什么异常,要考虑Service 层报异常的问题,要使用try-catch 。 @RequestMapping("/workbench/activity/saveCreateActivity.do")
@ResponseBody
public Object saveCreateActivity(Activity activity, HttpSession session) {
User user = (User) session.getAttribute(Constant.SESSION_USER);
activity.setId(UUIDUtils.getUUID());
activity.setCreateTime(DateUtils.formatDateTime(new Date()));
activity.setCreateBy(user.getId());
ReturnObject returnObject = new ReturnObject();
try {
int i = activityService.saveCreateActivity(activity);
if (i > 0){
returnObject.setCode(Constant.RETURN_OBJECT_CODE_SUCCESS);
} else {
returnObject.setCode(Constant.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("系统忙,请稍后重试...");
}
} catch (Exception e) {
e.printStackTrace();
returnObject.setCode(Constant.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("系统忙,请稍后重试...");
}
return returnObject;
}
-
【前端】获取对象 // DOM方式
document.getElementById("#..")
// jquery方式
$("#id")
-
【前端】日期格式输入 该类东西都有现成的模板,不要浪费时间,如下是一个示例,使用的bootstrap 框架。 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
String basePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort() + request.getContextPath() +"/";
%>
<html>
<head>
<base href="<%=basePath%>">
<!--引入jquery-->
<script type="text/javascript" src="jquery/jquery-1.11.1-min.js"></script>
<!--引入框架-->
<link rel="stylesheet" href="jquery/bootstrap_3.3.0/css/bootstrap.min.css">
<script type="text/javascript" src="jquery/bootstrap_3.3.0/js/bootstrap.min.js"></script>
<!--bootstrap插件,要依赖于框架-->
<link rel="stylesheet" href="jquery/bootstrap-datetimepicker-master/css/bootstrap-datetimepicker.min.css">
<script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/js/bootstrap-datetimepicker.js"></script>
<script type="text/javascript" src="jquery/bootstrap-datetimepicker-master/locale/bootstrap-datetimepicker.zh-CN.js"></script>
<title>演示</title>
<script type="text/javascript">
$(function (){
// 当容器加载完成,对容器调用工具函数
$("#myDate").datetimepicker({
language:'zh-CN',
format:'yyyy-mm-dd',
minView:'month', // 可以选择的最小视图
initialDate:new Date(),
autoclose:true, // 选择完是否自动关闭
todayBtn:true // 设置是否显示今天的
});
});
</script>
</head>
<body>
<input type="text" id="myDate">
</body>
</html>
防止用户手动修改日期格式,可以为容器标签加上readonly='true' <div class="form-group">
<label for="create-startDate" class="col-sm-2 control-label">开始日期</label>
<div class="col-sm-10" style="width: 300px;">
<input type="text" class="form-control" id="create-startDate" readonly="true">
</div>
<label for="create-endDate" class="col-sm-2 control-label">结束日期</label>
<div class="col-sm-10" style="width: 300px;">
<input type="text" class="form-control" id="create-endDate" readonly="true">
</div>
</div>
加上该readonly 属性,用户没办法手动清空,可以加一个清空按钮,是在jsp语句中配置的。 <script type="text/javascript">
$(function (){
// 当容器加载完成,对容器调用工具函数
$("#myDate").datetimepicker({
language:'zh-CN',
format:'yyyy-mm-dd',
minView:'month', // 可以选择的最小视图
initialDate:new Date(),
autoclose:true, // 选择完是否自动关闭
todayBtn:true, // 设置是否显示今天的
clearBtn:true // 显示清空按钮
});
});
</script>
-
为多个容器加上工具属性 // 写多个
<head>
<script>
$(function (){
// 添加日历start
$("#create-startDate").datetimepicker({
language:'zh-CN',
format:'yyyy-mm-dd',
minView:'month', // 可以选择的最小视图
initialDate:new Date(),
autoclose:true, // 选择完是否自动关闭
todayBtn:true, // 设置是否显示今天的
clearBtn:true
});
// 添加日历end
$("#create-endDate").datetimepicker({
language:'zh-CN',
format:'yyyy-mm-dd',
minView:'month', // 可以选择的最小视图
initialDate:new Date(),
autoclose:true, // 选择完是否自动关闭
todayBtn:true, // 设置是否显示今天的
clearBtn:true
});
});
</script>
</head>
// 使用类选择器
<head>
<script>
$(function (){
// 添加日历start
$(".mydate").datetimepicker({
language:'zh-CN',
format:'yyyy-mm-dd',
minView:'month', // 可以选择的最小视图
initialDate:new Date(),
autoclose:true, // 选择完是否自动关闭
todayBtn:true, // 设置是否显示今天的
clearBtn:true
});
});
</script>
</head>
<div class="form-group">
<label for="create-startDate" class="col-sm-2 control-label">开始日期</label>
<div class="col-sm-10" style="width: 300px;">
<input type="text" class="form-control mydate" id="create-startDate" readonly="true">
<!--上面我们能看到,加了一个mydate属性,class可以有多个值-->
</div>
<label for="create-endDate" class="col-sm-2 control-label">结束日期</label>
<div class="col-sm-10" style="width: 300px;">
<input type="text" class="form-control mydate" id="create-endDate" readonly="true">
<!--上面我们能看到,加了一个mydate属性,class可以有多个值-->
</div>
</div>
-
附:正则表达式语法大全:https://www.runoob.com/regexp/regexp-tutorial.html
09-市场活动模块----查询市场活动
-
查询市场活动的三个基本大需求,什么时候需要用到查询 分页显示全部结果的查询(刚进入页面的时候,需要进行一个查询) 特定条件检索结果的查询(用户使用特定的检索,进行一次查询) 具有翻页功能结果的查询(翻页功能) -
分页显示流程图 -
分页查询SQL语句的细节【一个表可以被链接多次,而且每个表之间的连接方式要仔细考虑】 select a.id, b.name as owner, b.name, a.start_date, a.end_date, a.cost, a.description, a.create_time,
c.name as create_by, a.edit_time, d.id as edit_by
from crm2022.tbl_activity a
inner join crm2022.tbl_user b on a.owner = b.id # 注意,使用内外链接都可以
inner join crm2022.tbl_user c on a.create_by = c.id # 注意,连接条件不同了,需要重新链接
left join crm2022.tbl_user d on a.create_by = c.id # 注意,这里是左外连接,左边即便是null也要查询出来
-
返回的不是一个完整页面时,我们需要做什么?
-
返回的对象要使用@ResponseBody 注解对内容进行注释 -
返回的对象有两种选择,一个是封装成为一个Map直接变成JSON字符串,另一个是自己定义一个返回类型。
-
自己定义一个返回类型: @RequestMapping("/workbench/activity/saveCreateActivity.do")
@ResponseBody
public Object saveCreateActivity(Activity activity, HttpSession session) {
User user = (User) session.getAttribute(Constant.SESSION_USER);
activity.setId(UUIDUtils.getUUID());
activity.setCreateTime(DateUtils.formatDateTime(new Date()));
activity.setCreateBy(user.getId());
ReturnObject returnObject = new ReturnObject();
try {
int i = activityService.saveCreateActivity(activity);
if (i > 0) {
returnObject.setCode(Constant.RETURN_OBJECT_CODE_SUCCESS);
} else {
returnObject.setCode(Constant.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("系统忙,请稍后重试...");
}
} catch (Exception e) {
e.printStackTrace();
returnObject.setCode(Constant.RETURN_OBJECT_CODE_FAIL);
returnObject.setMessage("系统忙,请稍后重试...");
}
return returnObject;
}
public class ReturnObject {
private String code;
private String message;
private Object retData;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getRetData() {
return retData;
}
public void setRetData(Object retData) {
this.retData = retData;
}
}
-
直接封装成一个Map @RequestMapping("/workbench/activity/queryActivityByConditionForPage.do")
@ResponseBody
public Object queryActivityByConditionForPage(String name, String owner, String startDate, String endDate, int pageNo, int pageSize) {
Map<String, Object> map = new HashMap<>();
map.put("name", name);
map.put("owner", owner);
map.put("startDate", startDate);
map.put("endDate", endDate);
map.put("beginNo", (pageNo - 1) * pageSize);
map.put("pageSize", pageSize);
List<Activity> activities = activityService.queryActivityByConditionForPage(map);
int i = activityService.queryCountOfActivityByCondition(map);
Map<String, Object> retMap = new HashMap<>();
retMap.put("activityList",activities);
retMap.put("totalRows", i);
return retMap;
}
-
简单来说,就是看看有没有已经定义好的实体类满足自己的返回需求,有的话直接用,没有的话就返回一个Map对象,因为它与JSON字符串的格式是一致的。 -
JSP中针对返回的对象有两种遍历方式,一种是直接在javascript代码中,一种是jquery方式 遍历jsp对象使用,遍历的是js变量
$.each()
作用域对象:jstl标签,与el标签一起使用
<c:foreach></c:foreach>
-
在指定的标签中显示jsp页面片段: 选择器.html(jsp页面片段的字符串);
选择器.append(jsp页面片段的字符串);
选择器.after(jsp页面片段的字符串);
选择器.before(jsp页面片段的字符串);
选择器.text(jsp页面片段的字符串);
-
分页插件【类似日历插件】
-
引入开发包 -
创建容器 -
当容器加载完后,对容器调用工具函数
10-删除市场活动
-
删除功能流程分析 -
在页面中给元素添加事件的语法【jquery】
-
使用元素的事件属性:在标签上加上onclick属性【不推荐,代码和页面混在一起】 <οnclick="f()">
-
使用jquery对象添加:使用jquery选择器筛出来对应的对象,再调事件函数【推荐,早期版本】 【不足:只能给固有元素添加属性】 【固有元素:当调用事件函数给元素添加事件的时候,如果元素已经生成,则这些元素叫固有元素】 【动态元素:当调用事件函数给元素添加事件的时候,如果元素还没有生成,则这些元素叫动态元素】 【考虑到异步请求,有的元素实际上会成为动态元素】 <script>
$("#xxx").onclick(function(){
// js代码
});
</script>
-
使用juery对象添加:使用on函数,可以针对动态对象进行绑定【动态静态都可以】 先找父元素:父元素必须是固有元素,可以是直接父元素,也可以是非直接父元素,理论上任意父元素都可以,但是理论上范围越小越好 事件类型:与事件属性和事件函数一一对应。 子选择器:目标元素,自动建立在父选择器的基础上 <script>
父选择器.on("事件类型", 子选择器, function(){
// js代码
});
</script>
-
mybatis中的update、insert、delete语句没有定义resultMap属性,写了反而错了。 -
ajax向后台发送请求时,可以通过data提交参数,data的数据格式有三种格式:
-
第一种 data:{
k1:v1,
k2:v2,
....
}
劣势:只能向后台提交一个参数名对应一个参数值的数据,不能向后台提交一个参数名对应多个参数值的数据。只能向后台提交字符串数据。 优势:操作简单 -
第二种 data:k1=v1&k2:v2&....
优势:不但能够向后台提交一个参数名对应一个参数值的数据,还能向后台提交一个参数名对应多个参数值的数据。 劣势:操作麻烦,只能向后台提交字符串数据 -
第三种 data:FormData对象
优势:不但能提交字符串数据,还能提交二进制数据,主要是上传文件的时候使用的 劣势:操作更复杂
-
FormData 对象的使用:创建对象,使用append() 添加键值对。 var formdata = new FormData();
formdata.append("activityFile", actFile);
$.ajax({
url:'workbench/activity/importActivity.do',
data: formData,
type: 'post',
dataType:'json',
success:function(data){
if (data.code == "1"){
alert("导入成功!");
}
}
});
11-修改市场活动和批量导出市场活动、选择导出市场活动
-
修改流程图分析【先查出来】 -
使用jquery获取或者设置指定元素的value属性值: 获取:选择器.val() ; 设置:选择器.val(属性值) ; -
批量导出市场活动分析
-
使用poi 插件操作文档 【插件的基本思想】:将办公文档的所有元素封装成普通的Java类,进而实现操作办公文档的目的。 【类对象】:Excel文件对象(HSSFWorkbook ),工作表对象(HSSFSheet ),行对象(HSSFRow ),列对象(HSSFCell ),样式对象(HSSFCellStyle ) 【使用】:
- 添加依赖:在
pom.xml 文件中添加依赖,3.15版本比较经典,3.17进行了很多的改进。 <dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.15</version>
</dependency>
public class CreateExcelTest {
public static void main(String[] args) throws Exception{
HSSFWorkbook sheets = new HSSFWorkbook();
HSSFSheet sheet = sheets.createSheet("学生列表");
HSSFRow row = sheet.createRow(0);
HSSFCell cell = row.createCell(0);
cell.setCellValue("学号");
cell = row.createCell(1);
cell.setCellValue("姓名");
cell = row.createCell(2);
cell.setCellValue("年龄");
for (int i = 1; i <= 10; i++){
row = sheet.createRow(i);
cell = row.createCell(0);
cell.setCellValue(100 + i);
cell = row.createCell(1);
cell.setCellValue("name" + i);
cell = row.createCell(2);
cell.setCellValue(20 + i);
}
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\10727\\Desktop\\my.xls");
sheets.write(fileOutputStream);
fileOutputStream.close();
sheets.close();
}
}
-
用户文件下载功能注意事项 是一个web功能,有前端和后端。 所有文件下载的请求只能是同步请求,不能是异步请求,因为响应信息是个文件。 -
Controller 层面的书写 SpringMVC 不擅长使用返回值返回文件类型,所以我们要自己手动编写。 @RequestMapping("workbench/activity/fileDown.do")
public void fileDown(HttpServletResponse response) throws Exception {
response.setContentType("application/octet-stream;charset=UTF-8");
OutputStream outputStream = response.getOutputStream();
response.addHeader("Content-Disposition","attachment;filename=myStudentList.xls");
InputStream fileInputStream = new FileInputStream("C:\\Users\\10727\\Desktop\\my.xls");
byte[] buff = new byte[256];
int len = 0;
while ((len = fileInputStream.read(buff)) != -1) {
outputStream.write(buff, 0, len);
}
fileInputStream.close();
outputStream.flush();
}
-
批量导出市场活动 -
选择导出功能实现 带信息的同步请求: window.location.href = "workbench/activity/exportActivityByIds.do" + "?" + ids;
12-文件上传功能和批量导入功能
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="#{1024*1024*5}"/>
<property name="defaultEncoding" value="utf-8"/>
</bean>
文件上传的表单三个条件:
-
表单组件标签必须用:<input type="file"> <input type="text|password|radio|checkbox|hidden|button|submit|reset|file"> , <select> ,<textarea> 等 -
请求方式只能用:post get:参数通过请求头提交到后台,参数放在URL后边;只能向后台提交文本数据;对参数长度有限制;数据不安全;效率高 post:参数通过请求体提交到后台;既能能提交文件数据,又能够提交二进制数据;理论上对参数长度没有限制;相对安全;效率较低 -
表单的编码格式只能用:multipart/form-data 根据HTTP协议的规定,浏览器每次向后台提交参数,都会对参数进行统一编码;默认采用的编码格式是urlencoded,这种编码格式只能对文本数据进行编码; 浏览器每次向后台提交参数,都会首先把所有的参数转换成字符串,然后对这些数据统一进行urlencoded编码; 文件上传的表单编码格式只能用multipart/form-data:enctype=“multipart/form-data” 使用ajax的话有另一种格式。
<form action="url" method="post" enctype="multipart/form-data">
<input type="file" name="myFile"><br>
<input type="submit" value="提交">
</form>
$.ajax({
url:'workbench/activity/importActivity.do',
data: formData,
processData: false,
contentType: false,
type: 'post',
dataType:'json',
success:function(data){
if (data.code == "1"){
alert("导入成功!");
$("#importActivityModal").modal("hide");
queryActivityByConditionForPage(1, $("#demo_pag1").bs_pagination('getOption', 'rowsPerPage'));
} else {
alert(data.message);
$("#importActivityModal").modal("show");
}
}
});
@RequestMapping("workbench/activity/fileUp.do")
@ResponseBody
public Object fileUp(String userName, MultipartFile myFile) throws Exception{
System.out.println("userName" + userName);
String originalFilename = myFile.getOriginalFilename();
File file = new File("C:\\Users\\10727\\Desktop\\", originalFilename);
myFile.transferTo(file);
ReturnObject object = new ReturnObject();
object.setCode(Constant.RETURN_OBJECT_CODE_SUCCESS);
object.setMessage("上传成功");
return object;
}
-
导入市场活动的流程分析 -
Mapper 层
<insert id="insertActivityByList" parameterType="com.powernode.crm.workbench.domain.Activity">
insert into crm2022.tbl_activity (id, owner, name)
values
<foreach collection="list" item="obj" separator=",">
(#{obj.id}, #{obj.owner}, #{obj.name})
</foreach>
</insert>
-
前台页面(注意ajax发送地方,有额外的字段) $("#importActivityBtn").click(function (){
var FileName = $("#activityFile").val();
var type = FileName.substr(FileName.lastIndexOf(".")).toLowerCase();
if (type != "xls"){
alert("请导入.xls格式的文件进行导入!");
return;
}
var actFile = $("#activityFile")[0].files[0];
if (actFile.size > 5 * 1024 * 1024){
alert("所选的文件过大!请重新上传5M大小以内的数据!")
}
var formdata = new FormData();
formdata.append("activityFile", actFile);
$.ajax({
url:'workbench/activity/importActivity.do',
data: formData,
processData: false,
contentType: false,
type: 'post',
dataType:'json',
success:function(data){
if (data.code == "1"){
alert("导入成功!");
$("#importActivityModal").modal("hide");
queryActivityByConditionForPage(1, $("#demo_pag1").bs_pagination('getOption', 'rowsPerPage'));
} else {
alert(data.message);
$("#importActivityModal").modal("show");
}
}
});
});
13-查看市场活动详细信息
-
流程图分析 -
【前端】在jsp这个页面上显示遍历的内容有两种方式
-
使用标签保存数据,以便在需要的时候能够获取到这些数据
-
给标签添加属性: 如果是表单组件标签,优先使用value属性,只有value不方便使用时,使用自定义属性; 如果不是表单组件标签,不推荐使用value,推荐使用自定义属性。
添加自定义属性——比如下面的remarkId 就是自己设置的
<a class="myHref" remarkId="${remark.id}" href="javascript:void(0);"><span class="glyphicon glyphicon-edit" style="font-size: 20px; color: #E6E6E6;"></span></a>
-
获取属性值时:【优先考虑id,其次是name属性,这两个属性都不合适,采用自定义】 如果获取表单组件标签的value属性值:dom对象.value,jquery对象.val() 如果自定义的属性,不管是什么标签,只能用:jquery对象.attr(“属性名”);
获取自定义属性——
$("#remarkDivList").on("click", "a[name='deleteA']", function (){
var activityRemarkId = $(this).attr("remarkId");
$.ajax({
url:'workbench/activity/deleteActivityRemarkById.do',
data:{
activityRemarkId:activityRemarkId
},
type:'post',
dataType:'json',
success:function (data){
if (data.code == "1"){
}
}
});
});
14-添加、删除、修改市场活动评论
-
流程分析 -
【前端】获取jquery对象的值和设定值 var myval = $("#id").val();
$("#id").val("myString");
【前端】把页面片段动态显示在页面中: 选择器.html(htmlStr):覆盖显示在标签的内部
选择器.text(htmlStr):覆盖显示在标签的内部
选择器.append(htmlStr):追加显示在指定标签的内部的后边
选择器.after(htmlStr):追加显示在指定标签的外部的后面
选择器.before(htmlStr):追加显示在指定标签的外部的前边
-
删除市场活动备注 -
修改市场活动评论 -
修改的SQL语句,自动返回影响记录的条数,不用使用resultMap和resultType,直接写SQL语句就行了。
|