CRM项目开发【实操篇----市场活动模块】

前言:本项目来源于B站动力节点视频,CRM项目开发
使用的后端技术栈主要是SSM框架,不涉及boot,老师讲的非常细致,推荐
关于流程图部分,由于是老师创作的,就不在这里贴出了,可以B站搜索动力节点的CRM项目,有下载链接,流程图的分析很到位,非常适合用于巩固SSM框架和熟悉业务流程。

01-项目配置文件

  1. 最重要的配置文件——web.xml

    Web项目的核心配置文件,在服务器启动的时候加载,在该文件中导入SSM的配置文件,可以认为是所有其他配置文件的父加载器。

  2. Spring配置文件——applicationContext.xml

    扫描service包下面的注解,导入MyBatis的配置文件

  3. SpringMVC配置文件——applicationContext-mvc.xml

    扫描controller包下面的注解,配置视图解析器,DispatcherServlet分发器

  4. MyBatis配置文件——applicationContext-datasource.xml

    扫描mapper包下面的注解,配置对应的数据源,与数据库连接

web.xml配置applicationContext-mvc.xmlapplicationContext.xml

``applicationContext.xml配置applicationContext-datasource.xml`

  1. pom.xml配置文件——指定maven想配置的文件。

    maven默认情况下只会配置src/main/java下的文件,通过配置maven可以指定目录下的配置文件

02-部署过程及目录结构

  1. 将静态网页放在webapp下面

  2. src\main\javasrc\main\resources两个文件夹中的内容在部署时会被idea生成为classes文件放到webapp\WEB-INF下面

  3. pom.xml的配置文件会被编译为lib文件夹放到webapp\WEB-INF下面

  4. 文件目录树

    webapps

    ​ |–crm(本项目)

    ​ |–WEB-INF

    ​ |–web.xml(web基础配置文件)

    ​ |–classes(项目文件)

    ​ |–lib(经由pom.xml编译得到的一些依赖的jar包)

    ​ |–pages(同样的也是HTML等页面资源,但是外界不可以直接访问)

    ​ |–HTML页面等资源(该目录下的页面外界可以直接访问,存在安全问题,通常存放一些css和img)

    ​ |–stucrm(其他的项目)

  5. webapps下的内容除了WEB-INF下的内容都是可以直接访问的,因此将一些图片和格式文件直接存放在这个目录下;

    webapps/WEB-INF下的内容都是要经过controller处理的,所以要经过一定的保护。

03-首页功能分析与设计

开发基本过程:

分析需求

分析与设计

编码实现

测试

  1. 分析需求

  2. 分析与设计

    controller的创建原则:

    如果两个页面不在同一个目录,新创建一个新的Controller

    一个独立的资源访问目录就代表一个新的Controller

  3. 编码实现

    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"; // 路径没有/ ,因为视图解析器中有了
    }
    
  4. 测试

  5. 基本流程图

    客户端发起请求,访问默认路径,默认路径要重定向到登陆页面。

先写底层,再写上层,先完成被调用的对象。

04-登录功能分析与设计

  1. 分析需求

    同步请求与异步请求

    同步请求:整个页面都刷新

    异步请求:局部页面刷新,也可以整个页面都刷新

    所以看情况决定是使用同步请求还是异步请求。

    局部刷新只能用异步,整个页面的刷新优先用同步。

  2. 分析与设计

    service类和Mappar的创建原则:

    与上述的Controller层不同,这两个类的创建原则是根据数据库中的表,如果查的表不同,那么使用的就不一样。

    前台Controller看资源访问路径

    后台serviceMappar看数据库中的表

  3. 编码实现

    流程:

    1. myBatis逆向工程

      导入pom.xml的插件

      添加配置文件

      执行逆向工程

    2. 根据已经生成的Mapper编写Service层

    3. 再反写Controller层

    想把控制层代码中处理好的数据,发送给页面上显示给用户看,一定考虑作用域

    作用域:

    pageContext:一个页面的不同标签之间传递数据,从一个标签中传数据到另一个标签中

    request:一个请求中有效(一次请求)

    session:一个会话中有效(同一个浏览器窗口中的不同请求之间传递数据)

    application:所有的用户都共享的数据,并且频繁使用的

    需要注意的是,逆向工程在执行时不能执行多次,针对同一个表仅仅执行一次,逆向工程默认的是覆盖方法,所以之前写的内容会被覆盖掉。

    BUGThe 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

  4. 测试

  5. 基本流程图

05-记录密码功能实现和安全退出

  1. 记录密码的实现逻辑

    第一次决定记录密码,向浏览器写Cookie;决定不记录密码,将本地Cookie删除

    第二次后再访问时,判断有没有Cookie,有的话将Cookie一起传到服务器。

  2. 安全退出

06-登陆验证

  1. 路径问题

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对象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; // false表示拦截,true表示放行}
}
  1. 排除拦截在拦截器中优先级更高,拦截器和排除拦截同时生效的时候,一定是排除拦截优先生效并放行。

07-页面切割技术

  1. 早期技术:<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>
    
  2. 较新技术:<div><iframe>

    <div>:切割页面。<div style="height:10%;width=20%">高度与宽度占据的屏幕比例

    <iframe>:显示页面。

    <div style="height:10%;width=20%"><iframe href="url">
    </div>
    

    效率高的轻量级标签。

08-市场活动模块----创建市场活动

  1. 模态窗口:模拟的窗口,时间发生后的弹出的小窗口,能够独立显示网页的一个小窗口。

    • 旧技术实现:

    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="",点击了添加该属性的标签,点击了对象就会使模态窗口关闭。

  2. 创建市场活动

    为什么步骤18是个异步请求,因为这是一个局部更新的问题,将列表更新就可以了。

  3. 用户登录的时候,是一种查询操作,所以查出来看看是不是空,做个比较就可以。

    但是当插入修改时,这是一种会变动数据库的操作,所以看看会不会产生什么异常,要考虑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进行二次封装,将一些没有填充的属性加上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;
    }
    
  4. 【前端】获取对象

    // DOM方式
    document.getElementById("#..")
    // jquery方式
    $("#id")
    
  5. 【前端】日期格式输入

    该类东西都有现成的模板,不要浪费时间,如下是一个示例,使用的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>
    
  6. 为多个容器加上工具属性

    // 写多个
    <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>
    
  7. 附:正则表达式语法大全:https://www.runoob.com/regexp/regexp-tutorial.html

09-市场活动模块----查询市场活动

  1. 查询市场活动的三个基本大需求,什么时候需要用到查询

    分页显示全部结果的查询(刚进入页面的时候,需要进行一个查询)

    特定条件检索结果的查询(用户使用特定的检索,进行一次查询)

    具有翻页功能结果的查询(翻页功能)

  2. 分页显示流程图

  3. 分页查询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也要查询出来
    
  4. 返回的不是一个完整页面时,我们需要做什么?

    • 返回的对象要使用@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进行二次封装,将一些没有填充的属性加上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; // 1成功 0 失败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字符串的格式是一致的。

  5. JSP中针对返回的对象有两种遍历方式,一种是直接在javascript代码中,一种是jquery方式

    遍历jsp对象使用,遍历的是js变量
    $.each()
    作用域对象:jstl标签,与el标签一起使用
    <c:foreach></c:foreach>
    
  6. 在指定的标签中显示jsp页面片段:

    选择器.html(jsp页面片段的字符串);//覆盖显示
    选择器.append(jsp页面片段的字符串);//追加显示
    选择器.after(jsp页面片段的字符串);
    选择器.before(jsp页面片段的字符串);
    选择器.text(jsp页面片段的字符串);
    
  7. 分页插件【类似日历插件】

    • 引入开发包

    • 创建容器

    • 当容器加载完后,对容器调用工具函数

10-删除市场活动

  1. 删除功能流程分析

  2. 在页面中给元素添加事件的语法【jquery】

    • 使用元素的事件属性:在标签上加上onclick属性【不推荐,代码和页面混在一起】

      <οnclick="f()">
      
    • 使用jquery对象添加:使用jquery选择器筛出来对应的对象,再调事件函数【推荐,早期版本】

      【不足:只能给固有元素添加属性】

      【固有元素:当调用事件函数给元素添加事件的时候,如果元素已经生成,则这些元素叫固有元素】

      【动态元素:当调用事件函数给元素添加事件的时候,如果元素还没有生成,则这些元素叫动态元素】

      【考虑到异步请求,有的元素实际上会成为动态元素】

      <script>
      $("#xxx").onclick(function(){// js代码
      });
      </script>
      
    • 使用juery对象添加:使用on函数,可以针对动态对象进行绑定【动态静态都可以】

      先找父元素:父元素必须是固有元素,可以是直接父元素,也可以是非直接父元素,理论上任意父元素都可以,但是理论上范围越小越好

      事件类型:与事件属性和事件函数一一对应。

      子选择器:目标元素,自动建立在父选择器的基础上

      <script>父选择器.on("事件类型", 子选择器, function(){// js代码});
      </script>
      
  3. mybatis中的update、insert、delete语句没有定义resultMap属性,写了反而错了。

  4. 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);
        // formdata.append("userName", "张三");
        // 发送异步请求
        $.ajax({url:'workbench/activity/importActivity.do',data: formData,type: 'post',dataType:'json',success:function(data){if (data.code == "1"){alert("导入成功!")}}
        });
        

11-修改市场活动和批量导出市场活动、选择导出市场活动

  1. 修改流程图分析【先查出来】

  2. 使用jquery获取或者设置指定元素的value属性值:

    获取:选择器.val();

    设置:选择器.val(属性值);

  3. 批量导出市场活动分析

    • 应用场景:不方便在线查看的场景,都可以使用批量导出的方式

    • 分析流程:

      • 给“批量导出”按钮添加事件,发送导出请求
      • 查询所有的市场活动
      • 创建一个excel文件,将所有文件写入一个excel文件中
      • 把生成的excel输出到浏览器
    • 技术准备:

      • iText插件:收费

      • poi插件:免费,效率不如iText

        本项目采用poi

  4. 使用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>
    
    • 使用封装类生成excel文件
    public class CreateExcelTest {public static void main(String[] args) throws Exception{// 创建文件对象HSSFWorkbook sheets = new HSSFWorkbook();// 创建工作表HSSFSheet sheet = sheets.createSheet("学生列表");// 创建一行,行号从0开始HSSFRow row = sheet.createRow(0);// 行号// 创建列,列也是从0开始HSSFCell cell = row.createCell(0);// 写入内容,标题cell.setCellValue("学号");// 写入内容,对0的不会失去引用的,内部有一个记录的,再使用cell作为变量名cell = row.createCell(1); cell.setCellValue("姓名");cell = row.createCell(2);cell.setCellValue("年龄");// 创建10个row对象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();}
    }
    
  5. 用户文件下载功能注意事项

    是一个web功能,有前端和后端。

    所有文件下载的请求只能是同步请求,不能是异步请求,因为响应信息是个文件。

  6. Controller层面的书写

    SpringMVC不擅长使用返回值返回文件类型,所以我们要自己手动编写。

    @RequestMapping("workbench/activity/fileDown.do")
    public void fileDown(HttpServletResponse response) throws Exception { // 文件下载的异常没有必要捕获,直接抛出就可以// 返回excel文件,设置文件返回信息,指明返回的是一个二进制文件response.setContentType("application/octet-stream;charset=UTF-8");// 获取输出流,只能写字符流,写二进制文件不能用这个response.getWriter();OutputStream outputStream = response.getOutputStream();// 浏览器接收到相应信息后,默认在浏览器中直接打开,即使浏览器自己打不开,也会调用电脑自带的软件,实在打不开才会激活文件下载窗口// 可以设置响应头信息,使浏览器接收到响应信息后,直接激活文件下载窗口,即使能打开也不打开// 设置文件名response.addHeader("Content-Disposition","attachment;filename=myStudentList.xls");// 读取excel文件到内存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会由Tomcat关闭outputStream.flush();
    }
    
  7. 批量导出市场活动

  8. 选择导出功能实现

    带信息的同步请求:

    window.location.href = "workbench/activity/exportActivityByIds.do" + "?" + ids;
    

12-文件上传功能和批量导入功能

  • SpringMVC对文件上传插件的依赖
    <!-- 配置文件上传解析器 id:必须是multipartResolver--><bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"><property name="maxUploadSize" value="#{1024*1024*5}"/> <!--最大文件大小--><property name="defaultEncoding" value="utf-8"/> <!--编码--></bean>
  • 文件上传的前端表单

文件上传的表单三个条件:

  1. 表单组件标签必须用:<input type="file">

    <input type="text|password|radio|checkbox|hidden|button|submit|reset|file"><select><textarea>

  2. 请求方式只能用:post
    get:参数通过请求头提交到后台,参数放在URL后边;只能向后台提交文本数据;对参数长度有限制;数据不安全;效率高
    post:参数通过请求体提交到后台;既能能提交文件数据,又能够提交二进制数据;理论上对参数长度没有限制;相对安全;效率较低

  3. 表单的编码格式只能用: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的格式$.ajax({url:'workbench/activity/importActivity.do',data: formData,processData: false, // 设置ajax提交参数之前,是否把参数统一转成字符串(默认方法是true)contentType: false, // 设置ajax向后台提交参数之前,是否按照urlencoded编码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");}}});
  • Controller层面的内容
@RequestMapping("workbench/activity/fileUp.do")
@ResponseBody
public Object fileUp(String userName, MultipartFile myFile) throws Exception{ // 定义一个形参,这是myFile,要与前端的命名一致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; // 浏览器是不能解析JSON的,这只是一个示例
}
  • 导入市场活动的流程分析

  • Mapper

    <!--遍历集合,采用list     List<Activity>        -->
    <!--遍历数组,采用array    Activity[]            -->
    <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 (){// 收集参数,下面这种方法只能获取文件名,不能获取文件对象// $("#activityFile").val();// 可以根据文件名做一个表单验证var FileName = $("#activityFile").val();// js截取字符串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);// formdata.append("userName", "张三");// 发送异步请求$.ajax({url:'workbench/activity/importActivity.do',data: formData,processData: false, // 设置ajax提交参数之前,是否把参数统一转成字符串(默认方法是true)contentType: false, // 设置ajax向后台提交参数之前,是否按照urlencoded编码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-查看市场活动详细信息

  1. 流程图分析

  2. 【前端】在jsp这个页面上显示遍历的内容有两种方式

    • 使用jquery

      $.each()
      

      【适用场景】遍历js变量,不是作用域里面的对象,可以使用它进行遍历

    • 使用jstl标签库,el表达式

      <c:forEach></c:forEach>
      

      【适用场景】遍历作用域里面的数据

  3. 使用标签保存数据,以便在需要的时候能够获取到这些数据

    • 给标签添加属性:
      如果是表单组件标签,优先使用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-添加、删除、修改市场活动评论

  1. 流程分析

  2. 【前端】获取jquery对象的值和设定值

    var myval = $("#id").val(); // 获取值
    $("#id").val("myString"); // 设定值
    

    【前端】把页面片段动态显示在页面中:

      选择器.html(htmlStr):覆盖显示在标签的内部选择器.text(htmlStr):覆盖显示在标签的内部选择器.append(htmlStr):追加显示在指定标签的内部的后边选择器.after(htmlStr):追加显示在指定标签的外部的后面选择器.before(htmlStr):追加显示在指定标签的外部的前边
    
  3. 删除市场活动备注

  4. 修改市场活动评论

  5. 修改的SQL语句,自动返回影响记录的条数,不用使用resultMap和resultType,直接写SQL语句就行了。

CRM项目开发【实操篇----市场活动模块】相关推荐

  1. 动力节点CRM项目开发【准备篇】

    CRM项目开发[准备篇] (参考B站视频CRM项目) 01-基本技术框架 视图层(View):展示数据,负责跟用户交互 html,css,js,jquery,bootstrap 控制层(Control ...

  2. 郝健: github多人协作项目开发实操笔记

    作者简介: 郝健(Artist),目前就职于赛尔网络CERNET技术开发部,研发项目经理:以前在天融信Topsec软件平台部做防火墙核心系统开发. 本文简介: 这是网友Artist在看完王玉成老师的g ...

  3. Linux入门笔记-尚硅谷韩顺平-基础篇实操篇

    文章目录 课程导论 基础篇 Linux入门 Linux介绍 Linux和Unix的关系 Linux和Windows比较 基础篇 Linux的目录结构 基本介绍 具体的目录结构 实操篇 vi和vim的使 ...

  4. Linux实操篇笔记

    Linux实操篇 远程登陆Linux 先检查一下sshd服务打开没有( " * " 表示打开): setup 选择系统设置,进入下面页面: Xshell 是一个强大的安全终端模拟软 ...

  5. frp(内网穿透)实操篇--映射远程端口(二)

    frp(内网穿透)实操篇–远程电脑(二) 内网穿透的前提需要:一个拥有固定的ip地址的主机: 常规家用网络运营商都采用的是动态ip,ip随时在变化 我这里用到的是 腾讯云:大家有需要也可以购买. 新用 ...

  6. 第五章-Linux实操篇

    title: 第五章 Linux实操篇 categories: Linux tags: linux typora-root-url: - abbrlink: 93414991 date: 2019-0 ...

  7. Android实训内容及过程,Android项目开发实训大纲.doc

    Android项目开发实训大纲Android项目开发实训大纲 黎明职业大学信息与电子工程学院 <Android项目开发> 实训指导书 2014年6月 <Android项目开发> ...

  8. RecyclerView的超强辅助Graywater——基础实操篇

    关于Graywater的系列文章 RecyclerView的超强辅助Graywater--理论篇 RecyclerView的超强辅助Graywater--基础实操篇 RecyclerView的超强辅助 ...

  9. Linux笔记总结 - linux实操篇 - 用户管理

    Linux笔记总结 - linux实操篇 - 用户管理 1 基本介绍 Linux系统是一个多用户多任务的操作系统,任何一个要使用的系统资源的用户,都首先向系统管理员申请一个账号,然后以这个账号身份进入 ...

最新文章

  1. Linux_《Linux命令行与shell脚本编程大全》第十八章学习总结
  2. 查看Linux的磁盘使用情况
  3. mysql迁移之后读取速度变慢_如何解决数据库迁移之后变慢的问题
  4. 无法读取项目文件 .csproj
  5. symfony3 使用命令行工具生成Entity实体所踩的坑
  6. SparkSession对象
  7. Numpy数据二进制化
  8. model 字段参数 choice
  9. 怎么把sql文件导入MySQL数据库中_《sql基础教程》书里的sql文件如何导入数据库?...
  10. 小新pro13 archlinux 显卡 声卡 驱动安装
  11. Android计算器横屏,如何将华为手机的计算器横屏转换成竖屏
  12. php json输出后 u6563,肉肉's Blog
  13. 【HTML5入门指北】第二篇 网页相关的标签
  14. activity劫持学习与复现
  15. 经典文章:一位营销总监的辞职信及回复
  16. 测试工程师, 入职以后如何开展工作?
  17. java 三边求面积_已知三角形的三边长如何求面积?
  18. R语言实战应用精讲50篇(十二)-正态分布与方差齐性的检验方法与SPSS操作
  19. Bonfire: Slasher Flick
  20. 【转帖】姑娘不是你想追,想追就能追。

热门文章

  1. java获取当前时间秒单位的时间
  2. 阿里内部资料,10W字总结JAVA面试题-Maven篇
  3. 将oracle中数据转化为汉字,将Oracle数据库中的“数字”对应成“汉字”
  4. 《救时宰相于谦》 郦波
  5. 503 service temporarily unavailable的解决
  6. GET http://localhost:8080/***.png net::ERR_BLOCKED_BY_CLIENT 无法加载图片
  7. 广播信道及CSMA/CD协议
  8. 研发一个机器人需要什么详细的步骤?
  9. ensp rip 实验2
  10. 挖掘机技术哪家强(20)