投诉受理

一:投诉受理模块

1.1:投诉受理列表

  • 原型图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a56S0wwT-1590204145755)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1590137023809.png)]

  • 功能说明:
1、可以根据投诉标题、投诉时间、状态,查询用户的投诉信息
2、其中操作栏内容要展示,投诉标题、被投诉部门、被投诉人、状态(待受理,已受理,已失效)
3、点击处理,即可跳转到处理受理页面,查看更具体的投诉信息,并且可以多次回复
4、一旦回复,就说明已受理到投诉,应该将投诉信息的状态更新为已受理!

1.2:投诉详细信息

  • 原型图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPJmS4G4-1590204145758)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1590138156799.png)]

  • 功能说明
1、在本页面中首先要明显地展示出当前投诉是否已经受理;然后再显示投诉人信息、被投诉信息、受理信息(历史受理信息)三部分内容,并且在页面中可以无限次的对本次受理进行回复
2、投诉人信息包括:是否匿名投诉、投诉人单位、投诉人姓名、投诉人手机,如果是匿名投诉,则不显示投诉人单位、姓名并对手机号中间4位号码使用*号代替。
3、被投诉信息包括:投诉时间、被投诉部门、被投诉人、投诉标题、投诉内容。
4、如果有多次回复则将多次的回复信息显示,显示内容包括回复时间、回复部门、回复人、受理回复内容;可以再次回复。
  • 分析
1、首先拿到需求应该分析一下,这个需求中需要创建哪些表,表中有哪些字段,表与表之间的关系
    a.需要创建的表:通过需求描述,我们知道要创建complain(投诉表)complain_replay(投诉回复表)
    b.表中字段,根据原型中的“投诉详情信息”,可以得出投诉表和投诉回复表所需要的字段
    c.通过需求分析:我们知道一个投诉,可以回复多次,所以“投诉表”和“投诉回复表”的关系是一对多的关系!
使用powerDesigner --> 概念模型 --> 物理模型 --> 建表语句 --> 逆向工程(生成实体类和映射文件) 
概要模型:实体和实体之间的关系
物理模型:表与表之间的关系

注:有些公司会跳过概念模型,直接从物理模型开始,是因为他们已经确定好了数据库,但是我推荐从概要模型开始到    时候无论用哪个数据库都可以随意切换生成物理模型。
注:只要把概念模型设计准确了,无论映射文件多么复杂都不需要你去写了
1.看完需求之后我们应该透过表面看到本质,先清楚大概一个方向,其实它这些在页面上操作,不就是相当于操作我们数据库中的表吗,这其实就可以看做是封装的思想,用户只管点击保存,至于我们后台里面怎么实现的,根本就不需要用户去关心,

比如:从投诉受理管理页面,我们应该可以看出它是对“投诉表”的查询操作
查询操作:不就是上面选择条件,内容就显示对应的数据
写出伪sql
select
	投诉标题,被投诉部门,被投诉人,投诉时间,状态
from
	投诉表
where
	投诉标题 = xxx,投诉时间 = xxx,状态 = xxx; 
    
2.程序执行流程
前台:点击"投诉受理管理"超链接(用户发送请求) --> 
后台:action-->service-->dao(查询数据中的数据) -->
通过action转发到相对应的试图jsp

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SayiM2fH-1590204145759)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1590142541261.png)]

  • 投诉受理列表的三层处理

一:controller层

//查询列表
public String listUI() {
    //加载特殊受理状态
    try {
        ActionContext.getContext().put("complainStateMap", Complain.COMPLAIN_STATE_MAP);
        QueryHelper helper = new QueryHelper(Complain.class,"c");

        //为什么把时间,状态的筛选条件放在前面呢?因为like模糊查询比较耗时间
        //不用between and, 因为用户只传startTime呢? 或者只传endTime呢?
        if(StringUtils.isNotBlank(startTime)) {
            //时间需要解码一下,因为它中间有一个空格会影响到
            startTime = URLDecoder.decode(startTime, "utf-8");
            helper.addCondition("c.compTime >= ?", DateUtils.parseDate(startTime+":00", "yyyy-MM-dd HH:mm:ss"));
        }

        if(StringUtils.isNotBlank(endTime)) {
            endTime = URLDecoder.decode(endTime, "utf-8");
            helper.addCondition("c.compTime <= ?", DateUtils.parseDate(endTime+":59", "yyyy-MM-dd HH:mm:ss"));
        }

        if(complain != null) {
            //前台传的投诉状态
            if(StringUtils.isNotBlank(complain.getState())) {
                // 因为状态值是(0,1) 所以不必url解码
                helper.addCondition("c.state = ?", complain.getState());
            }

            //前台传的投诉标题
            if(StringUtils.isNotBlank(complain.getCompTitle())) {
                complain.setCompTitle(decode(complain.getCompTitle()));
                helper.addCondition("c.compTitle like ?", "%"+ complain.getCompTitle() +"%");
            }
        }

        // 升序:先处理没有受理的
        helper.addOrderByProperty("c.state", QueryHelper.ORDER_BY_ASC);
        // 升序:先处理先投诉的
        // 根据时间降序,让最新的投诉在前面,那就有点不公平,假如我比你晚投诉,那么还最先显示在前面,所以得升序 
        helper.addOrderByProperty("c.compTime", QueryHelper.ORDER_BY_ASC);

        pageResult = complainService.getPageResult(helper, getPageNo(), getPageSize());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "listUI";
}

二:service层

@Override
public PageResult getPageResult(QueryHelper helper, int pageNo, int pageSize) {
    return baseDao.getPageResult(helper, pageNo, pageSize);
}

注:service层没有什么特殊处理,因为它这里没有什么业务需要处理,所以直接调用dao的方法即可!

三:dao层

@Override
public PageResult getPageResult(QueryHelper queryHelper, int pageNo, int pageSize) {
    Query query = getSession().createQuery(queryHelper.getQueryListHql());
    List<Object> parameters = queryHelper.getParameters();
    if(parameters != null && parameters.size()>0) {
        for(int i=0; i<parameters.size(); i++) {
            query.setParameter(i, parameters.get(i));
        }
    }

    if(pageNo < 1) pageNo = 1;
    //limit ?,? --->(当前页码-1)*每页记录数, 为了防止当前页码为0,所以得判断一下
    query.setFirstResult((pageNo-1)*pageSize); //设置数据起始索引号,
    query.setMaxResults(pageSize);
    List items = query.list();

    //获取总记录数:不能使用上面的query,如果用上面的query.list(), 返回就是分页的记录数了
    Query queryCount = getSession().createQuery(queryHelper.getQueryCountSql());
    if(parameters != null && parameters.size()>0) {
        for(int i=0; i<parameters.size(); i++) {
            queryCount.setParameter(i, parameters.get(i));
        }
    }

    long totalCount = (long) queryCount.uniqueResult();
    return new PageResult(pageNo, totalCount, pageSize, items);
}
  • 投诉详细信息中点击保存的三层处理
当我们点击“受理”超链接的时候,受理页面的“投诉人信息”,“投诉信息”部分,我们可以根据超链接附带给我们的id去投诉表中将信息查询出来,那么受理操作中的,“回复部门”,“回复人”是怎么显示出值的呢?他们是属于User表中的信息,因为我们在登录成功的时候,就将用户信息存入到了Session, 所以此时直接从session域中取出来用即可!

一:controller层

// 保存受理页面
public String deal() {
    if(complain != null) {
        Complain temp = complainService.findObjectById(complain.getCompId());
        // 1、更新投诉状态为:已受理
        if(!Complain.COMPLAIN_STATE_DONE.equals(complain.getState())) { //如果不是已受理,那就更新成已经受理
            temp.setState(Complain.COMPLAIN_STATE_DONE);
        }

        // 2、保存受理信息
        if(reply != null) {
            //由于回复者,回复部门,回复内容是由前台传过来的所以不用设置
            reply.setComplain(complain); // 设置一下回复哪个投诉人!
            reply.setReplyTime(new Timestamp(new Date().getTime()));
            temp.getComplainReplies().add(reply); //投诉和回复是一对多的关系,这里由于都有映射文件可以级联保存!cascade
        }

        complainService.update(temp);
    }

    return "list";
}

二:service层

@Override
public void update(T entity) {
    baseDao.update(entity);
}

注:没有多余的业务操作

三:dao层

@Override
public T findObjectById(Serializable id) {
    return getHibernateTemplate().get(clazz, id);
}
第一次进去受理信息是空的,其他投诉信息,受理操作这部分的信息却显示出来了,
你记住一点,我们发送一个请求,然后就看到了一个页面显示在我们面前,其实都是去数据库查询数据,然后渲染到页面上的,也就是说,页面上看到的数据其实数据库查询出来的,而数据库中的数据是从哪里来的呢?其实也是用户在页面上提交过去的,

这里我们就可以清楚的知道,为什么第一次进来“受理操作”的内容是空的,因为页面上填的数据还没到添加数据库中呢,等回复完之后,后台就会将回复内容设置给“投诉人”,到时候根据“id”就可以将回复信息查询出来显示,这也就是为什么第二次就有的原因

需求分析的一些套路

1.需求分析之后,如果复杂的话,应该画一个流程图
2.根据需求中的原型图分析出对应的表,表中的字段,表和表之间的关系
3.使用powerDesigner进行数据库建模,通过逆向工程生成实体类及其映射文件
4.然后进行dao --> service --> controller或者controller--> service--> dao的开发顺序
注:数据库层方面尽量少写点约束,可以让前台或者后台来判断

1.3:我要投诉

  • 原型图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-al1TTNPG-1590204145760)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1590137953101.png)]

  • 功能说明
1、在“工作主页”中点击“我要投诉”进入页面,
   添加内容包括:投诉标题、被投诉部门(部门A/B)、被投诉人、投诉详情、是否匿名投诉
  • 分析
经过之前的分析,我们也知道这个页面其实也就是个保存操作,将用户填写的数据添加到数据库“投诉表”中
但是其实它这里还涉及到一个“条件查询”的操作,因为被投诉部门,被投诉人要做成一个二级联动的效果
例如:
部门A: 张三, 李四
部门B: tom, jerry
效果应该是,你选中部门A,那么被投诉人应该只有张三,李四等人!
    
应该根据前台传的“部门”的参数值,去用户表中,进行条件查询 select * from user where dept = ?;

一:前台js

为“被投诉人部门”绑定一个onchange()事件,每当改变值时,都会通过ajax异步的方式向后台发送一个请求,
返回一个json数据后,然后将数据拼接到页面的指定位置上!
function doSelectDept(){
    // 1.获取部门的值
    var toCompDept = $("#toCompDept option:selected").val();
    if(toCompDept != ""){
        // 2.发送ajax请求, 后台根据部门值,去user表筛选出对应的人
        $.ajax({
            /* url:"${basePath}sys/home_getUserJson.action", */
            url:"${basePath}sys/home_getUserJson2.action",
            type:"post", //因为有中文
            data:{"toCompDept":toCompDept},
            dataType:"json", //返回数据类型
            success:function(data){
                debugger;
                if(data == "" || data == null && data == undefined){
                    alert("获取被投诉人列表失败!");
                }

                var $toCompName = $("#toCompName");
                if(data.msg == "success"){
                    //避免叠加
                    $toCompName.empty();
                    $.each(data.userList, function(index, user){
                        $toCompName.append("<option value='" + user.name + "'>" + user.name + "</option>");
                    });
                    console.log("完");
                }else{
                    alert("获取被投诉人列表失败!");
                }
            },
            error:function(){
                alert("获取被投诉人列表失败!");
            }
        });
    }else{
        //因为假设我先选择了A部门,然后再选择了,“请选择”选项,这时如果你还发送给后台没什么必要,因为没有是所属空的部门
        //这时会进入ajax的error函数,弹出获取被投诉人列表失败, 并且被投诉人列表却还是原来的值
        //因此需要对被投诉部门的值,进行判断!
        $("#toCompName").empty();
    }
}

二:后台返回json

后台返回json,一种是不用struts框架处理的,一种是借助struts2框架的
  • 不用框架处理的
// 根据部门名称,获取被投诉人列表,也就是所谓的二级联动!
	public void getUserJson() {
        try {
            String toCompDept = ServletActionContext.getRequest().getParameter("toCompDept");
            if(StringUtils.isNotBlank(toCompDept)) {
                QueryHelper helper = new QueryHelper(User.class, "u"); // 要明确最终去哪个表去查!,所以需要userService
                helper.addCondition("u.dept = ?", toCompDept);
                List<User> userList = userService.findObjects(helper);

                JSONObject jsonObject = new JSONObject();
                jsonObject.put("msg", "success");
                jsonObject.put("userList", userList);

                HttpServletResponse response = ServletActionContext.getResponse();
                response.setContentType("text/html");
                ServletOutputStream outputStream = response.getOutputStream();
                outputStream.write(jsonObject.toString().getBytes("utf-8"));
                outputStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 用框架处理的
public String getUserJson2() {
		try {
			String toCompDept = ServletActionContext.getRequest().getParameter("toCompDept");
			if(StringUtils.isNotBlank(toCompDept)) {
				QueryHelper helper = new QueryHelper(User.class, "u"); // 要明确最终去哪个表去查!,所以需要userService
				helper.addCondition("u.dept = ?", toCompDept);
				List<User> userList = userService.findObjects(helper);
				
				return_map = new HashMap<String, Object>();
				return_map.put("msg", "success");
				return_map.put("userList", userList);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return SUCCESS;
	}

还得去配置文件做如下配置:

  • 继承 json-default
  • 指定返回结果为json,root参数指定的就是要返回的属性,在action中必须为该属性提供get方法
  • 注:如果不提供root = “return_map”,那么在action中所以具有get方法的属性,都会返回json格式到前台
<package name="sysHomeJson-action" namespace="/sys" extends="json-default">
		<action name="home_getUserJson2" class="cn.itcast.home.action.HomeAction" method="getUserJson2">
			<result type="json">
				<param name="root">return_map</param>
			</result>
		</action>
	</package>

三:在"我要投诉页面"点击保存之后,前台js的处理

function doSubmit(){
    var formData = $("#form").serialize();
    //1.保存投诉
    $.ajax({
        url:"${basePath}sys/home_complainAdd.action",
        type:"post", //使用post,因为投诉内容比较多,get可能不够!
        data:formData, //序列化表单
        success:function(msg){
            debugger;
            if(msg == "success"){
                //2.提示用户保存成功
                alert("投诉保存成功!");
                //3.刷新父窗口(主页)
                window.opener.parent.location.reload(true);
                //4.关闭当前窗口(我要投诉页面)
                window.close();
            }else{
                alert("投诉保存失败!");
            }
        },
        error:function(){
            alert("投诉保存失败!");
        }
    });
}

四:后台就是一个保存操作

1.4:自动投诉处理

需求:
在每个月月底最后一天对本月之前的投诉进行自动处理;将投诉信息的状态改为 已失效。
在后台管理中不能对该类型投诉进行回复。

注:比如2020/5/31(5月底),在这一天,需要对5/1之前的投诉状态改为已失效
  • 分析
看到这个需求之后,我们可能会想到在javase阶段,学过的定时器Timer,
通过上面的描述,我们可以知道应该还有一个数据库的更新操作
sql: update 投诉表 set state = '已失效' where state = '未处理' and comp_time < x月/1
public class MyTimer {

	public static void main(String[] args) {
		Timer timer = new Timer();
		//参数: task(任务), delay(延迟), period(期间)
		//延迟1秒,间隔2秒,去执行我的任务
		//可以用一个监听器监听服务启动时,去执行我的定时任务,那么定时任务会随着容器启动而启动,但是不可控制,假如我想停止的话,还必须停止容器
		timer.scheduleAtFixedRate(new MyTimerTask(), 1000 , 2000);
	}
}

public class MyTimerTask extends TimerTask{

	@Override
	public void run() {
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		System.out.println("当前时间:"+format.format(new Date()));
	}

}

注:Timer只是按照一定的频率去执行我们的任务,并不能在精确到某一个时间点让它自动执行!,这时就需要请出我们的开源任务调度框架quartz,spring已经集成了它,它有简单调度功能和复杂调度功能,简单调度和Timer差不多,所以这里就不去演示了

使用quartz
1.导入jar包:org.springframework.context.support-3.0.2.RELEASE,quartz-1.8.6

2.简单触发器其实跟TimerTask差不多,Java.util.TimerTask其实也可以配置在spring中,按道理来说任何java代
码都可以配置applicationContext.xml

3.xml中配置:我总结成了一句话:哪个类的哪个方法(任务),在什么时机,被scheduler工厂去执行!!!

<!--1.指定任务信息  -->
<bean id="complainJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
    <!--设置执行对象:任务放在complainService来处理,因为我是更新状态  -->
    <property name="targetObject" ref="complainService"></property>
    <!--设置执行对象中执行方法名  -->
    <property name="targetMethod" value="autoDeal"></property>
    <!--是否可以同步执行: false:不可同步执行  -->
    <property name="concurrent" value="false"></property>
</bean>

<!--2.制定执行时机  -->
<bean id="complainCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
    <!--设置任务详细信息  -->
    <property name="jobDetail" ref="complainJobDetail"></property>
    <!-- 设置任务执行时间点,cronExpression: 在每月的月底下午的3点每分钟的第10秒执行任务 -->
    <!--表达式: 秒 分 时 日 月 周 年(可选) 注:日和周只能两者只能出现其中之一,另一个得是?  -->
    <!--以下表达式的意思是:每月最后一天(L),23时59分59秒  -->
    <property name="cronExpression" value="20 10 22 17 5 ?"></property>
</bean>

<!--3.设置调度工厂   -->
<bean id="complainScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
    <property name="triggers">
        <list>
            <ref bean="complainCronTrigger"/>
        </list>
    </property>
</bean>
  • quartz的表达式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GCiC7CG4-1590204145762)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1590200370715.png)]

注:更详细的使用,请看官方文档****

二:service层

@Override
public void autoDeal() {
    //本月1号: 也可以获取当前月份,弄成一个字符串,然后使用DateUtils来根据字符串来解析
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.DAY_OF_MONTH, 1);//1号
    calendar.set(calendar.HOUR_OF_DAY, 0); //0时
    calendar.set(Calendar.MINUTE, 0); //0分
    calendar.set(Calendar.SECOND, 0); //0秒

    // update complain set state = "已失效" where state = "受理" and compTime <
    complainDao.updateStateByStateBeforeCompTime(Complain.COMPLAIN_STATE_INVALID,Complain.COMPLAIN_STATE_UNDONE,calendar.getTime());
}

三:dao层

@Override
public void updateStateByStateBeforeCompTime(String updateState, String whereState,Date compTime) {
    // HQL 
    Query query = getSession().createQuery("update Complain set state = ? where state = ? and compTime < ?");
    query.setParameter(0, updateState);
    query.setParameter(1, whereState);
    query.setParameter(2, compTime);
}

1.5:将投诉统计数显示为柱状图

需求:
根据年度将相应年度的每个月的投诉数进行统计,并以图表的形式展示在页面中;
在页面中可以选择查看当前年度及其前4年的投诉数。
在页面中可以选择不同的年度,然后页面展示该年度的曲线统计图。

一:前台:需要引入FusionCharts.js图表组件

<!--我这里直接用的官方的例子,cdn引用js类库 -->
    <!--因为不是引用本地的,所以无法去除水印,如果js下载到了本地,则可以去进入到js中覆盖FusionCharts Trial这个水印  -->
    <!-- Include fusioncharts core library -->
	<script type="text/javascript" src="https://cdn.fusioncharts.com/fusioncharts/latest/fusioncharts.js"></script>
	<!-- Include fusion theme -->
	<script type="text/javascript" src="https://cdn.fusioncharts.com/fusioncharts/latest/themes/fusioncharts.theme.fusion.js"></script>
	<script type="text/javascript">
    //Fusioncharts.js最终需要的json格式如下,所以我们后台最终要传这个格式给前台,让它自己去渲染!
    /*
    const chartData = [{
        "label": "Venezuela",
        "value": "290"
    }, {
        "label": "Saudi",
        "value": "260"
    }]; */

    //加载完Dom元素后,就执行
    $(document).ready(doAnnualStatistic()); //所以默认就先让当前年份传给后台!
    
    //根据年份统计投诉数
    function doAnnualStatistic(){
    	var year = $("#year option:selected").val();
    	if(year == "" || year == undefined){
    		year = ${curYear}; // el表达式
    	}
    	
    	$.ajax({
    		url:"${basePath}nsfw/complain_annualStatistic.action",
    		type:"get",
    		data:{"year":year},
    		dataType:"json",
    		success:function(data){
    			if(data.msg == "success"){
    				 const chartData = data.chartData;
    				 const chartConfig = {
    						    type: 'column2d', //类型:折线图,饼图,2D,3D
    						    renderAt: 'chart-container', //要渲染的div的id
    						    width: '100%',
    						    height: '500',
    						    dataFormat: 'json',
    						    dataSource: {
    						        // Chart Configuration
    						        "chart": {
    						            "caption": year+"年度投诉数统计图", //标题
    						            //"subCaption": "In MMbbl = One Million barrels", //子标题
    						            "xAxisName": "月 份", //x轴
    						            "yAxisName": "统 计 数", //y轴
    						            //"numberSuffix": "K",//y轴的单位 
    						            "theme": "fusion", //主题
    						            },
    						        // Chart Data
    						        "data": chartData //显示的数据
    						        }
    						    };
    				 var fusioncharts = new FusionCharts(chartConfig);
    				 fusioncharts.render();
    			}else{
    				alert("失败");
    			}
    		},
    		error:function(){
    			alert("失败");
    		}
    	});
    }
</script>
  • controller层
//统计投诉数
public String annualStatistic() {
    //获取年份
    HttpServletRequest request = ServletActionContext.getRequest();

    int year = 0;
    if(StringUtils.isBlank(request.getParameter("year"))) {
        year = Calendar.getInstance().get(Calendar.YEAR);
    }else {
        year = Integer.parseInt(request.getParameter("year"));
    }

    //根据年份,查询年度统计数据
    List<Map<String, Object>> list = complainService.getAnnualStatisticDataByYear(year);

    statisticMap = new HashMap<String, Object>();
    statisticMap.put("msg", "success");
    statisticMap.put("chartData", list);
    return "annualStatisticData";
}
  • service层:这时就不是单纯的去调用dao层的方法,这时是有业务需要处理的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hJ2rZ28f-1590204145764)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1590201647837.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pv4Y7b2J-1590204145766)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1590201794576.png)]

@Override
	public List<Map<String,Object>> getAnnualStatisticDataByYear(int year) {
		/**
		   *  根据前台ajax所需要的数据是:chartData = [{"label": "Venezuela","value": "290"},{},{}];
		 *  []可以是一个数组-->list
		 *  {}可以是一个map集合
		  *     组合起来: List<Map<String,String>> 它解析成json,就是对应上面的格式!
		  *  [{label=1, value=0}, {label=1, value=0}, {label=1, value=0}, {label=1, value=0}]
		 */
		List<Map<String,Object>> resList = new ArrayList<Map<String,Object>>();
		
		List<Object[]> list = complainDao.getAnnualStatisticDataByYear(year);
		System.out.println(list);
		Calendar calendar  = Calendar.getInstance();
		boolean  isCurYear = calendar.get(Calendar.YEAR) == year;
		int      curMonth  = calendar.get(Calendar.MONTH)+1; //注意月份要加一!
		Map<String,Object> map = null;
		for(int i=0; i<list.size(); i++) {
			Object[] objects = list.get(i);
			String month = String.valueOf(objects[0]);
			Object obj   = (objects[1] == null) ? 0 : objects[1];
			
			map = new HashMap<String, Object>();
			map.put("label", month);
			
			if(isCurYear) {
				//已经超过当前年的月份, 将value置为 " "
				if(Integer.parseInt(month) > curMonth) {
					map.put("value", "");
				}else {
					map.put("value", obj);
				}
			}else {
				map.put("value", obj);
			}
			
			resList.add(map);
		}
		
		return resList;
	}
  • dao层
@Override
	public List<Object[]> getAnnualStatisticDataByYear(int year) {
	    // 使用不了HQL语句,因为我没有月份表对应的实体类!
		/**
		 *  select
				m.imonth, count(*)
			from
				t_month m
			left join 
				complain c
			on 
				m.imonth = month(c.comp_time) and year(comp_time) = 2020
			group by
				m.imonth
			order by 
				m.imonth asc
				
			这样不好,效率不高,我得用一个子查询,而且因为月份没有对应的实体类,所以我不能使用HQL语句
			只能去使用原生的sql
			select
				m.imonth, t.cnt
			from 
				t_month m 
			left join 
				(select month(c.comp_time) as imonth, count(*) as cnt from complain c where year(c.comp_time) = 2020 group by month(c.comp_time)) t
			on 
				m.imonth = t.imonth;
		 */
		// HQL
		StringBuffer sql = new StringBuffer();
		sql.append("select m.imonth, t.cnt ").
			append("from t_month m ").
			append("left join(select month(c.comp_time) as imonth, count(*) as cnt from complain c where year(c.comp_time) = ? group by month(c.comp_time)) t ").
			append("on ").
			append("m.imonth = t.imonth");
			SQLQuery sqlQuery = getSession().createSQLQuery(sql.toString());
			sqlQuery.setParameter(0, year);
			return sqlQuery.list();
	}
投诉统计图:需求:获取某年的每个月的投诉数
我前台只传了一个参数“年”,怎么根据参数“年”通过后台去数据库中将这一年的每个月的投诉数查询出来,然后返回给前台渲染

根据上面这一句你应该写出一条sql:
字段: 月份, 每月统计数
表: 投诉表
条件: 某年

select
	month(comp_time),count(*),group_concat(month(comp_time))
from 
	complain c 
where 
	year(comp_time) = 2020
group by
	month(comp_time);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tXiwCX5T-1590204145766)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1589979439946.png)]

如上图所示:把2020年有投诉数的月份和个数都统计统计出来了,感觉挺好,但是我要的是每一个月投诉的个数,就是这个月投诉数是0,你也给我显示出来,这么分析的话,你可能会说我先拿到这个月份的值,没有的我去后台判断再补充,那样就太麻烦了,有没有就是无论这个月投诉数是多少,你都得给我显示出来,所以我们想到了新建一张月份表,里面1-12份都有,然后左连接(把月份表放左表),这样到时候不管满不满足条件,这12个月始终会显示出来的!

-- 让投诉表和月份表联合查询,怎么去除笛卡尔积?, 投诉时间可以得到月份让它等于月份表中的月份就行了
select 
	m.imonth,count(c.comp_id)
from 
	t_month m left join complain c
on 
	m.imonth = month(c.comp_time) and year(c.comp_time) = 2020
group by
	m.imonth
order by 
	m.imonth asc
注意:year(c.comp_time) = 2020 这里必须使用and才有效果,我使用where居然还没反应!左表都不显示12个月

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-erZRAj4N-1590204145767)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1589981597790.png)]

就得到了我们想要的效果!

虽然达到了效果,但是你想想,要是有10w条投诉,和month表连接查询就会产生120w条垃圾数据,然后再从其中筛选条件,sql语句的效率比较低下,所以我们可以优化一下sql

问题在于连接时数据过于庞大,我们可以先写一个子查询,将10w投诉数筛选一下,然后在去和month进行左连接

-- 把它当成一个临时表去和month表进行左外连接, 临时表筛选后最多剩12个记录,和month表关联产生的笛卡尔积最多144条,比起之前的120w条少太多了
select month(c.comp_time),count(*) from complain c where year(c.comp_time) = 2020 group by month(c.comp_time);

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3UkjKfQs-1590204145767)(C:\Users\wzj\AppData\Roaming\Typora\typora-user-images\1589982648241.png)]

select
	m.*,t.cnt
from 
	t_month m 
left join 
	(select month(comp_time) t1,count(*) cnt from complain where year(comp_time) = 2020 group by month(comp_time)) t
on 
	m.imonth = t1
order by
	m.imonth asc;
注:左连接时必须加上on条件不然还会报错,不知道为什么!,如下语句会报错
select
	m.*,t.cnt
from 
	t_month m 
left join 
	(select month(comp_time) t1,count(*) cnt from complain where year(comp_time) = 2020 group by month(comp_time)) t
	
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 6
注:说第六行有语法错误

左外连接, 临时表筛选后最多剩12个记录,和month表关联产生的笛卡尔积最多144条,比起之前的120w条少太多了
select month(c.comp_time),count(*) from complain c where year(c.comp_time) = 2020 group by month(c.comp_time);


[外链图片转存中...(img-3UkjKfQs-1590204145767)]

```sql
select
	m.*,t.cnt
from 
	t_month m 
left join 
	(select month(comp_time) t1,count(*) cnt from complain where year(comp_time) = 2020 group by month(comp_time)) t
on 
	m.imonth = t1
order by
	m.imonth asc;
注:左连接时必须加上on条件不然还会报错,不知道为什么!,如下语句会报错
select
	m.*,t.cnt
from 
	t_month m 
left join 
	(select month(comp_time) t1,count(*) cnt from complain where year(comp_time) = 2020 group by month(comp_time)) t
	
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 6
注:说第六行有语法错误
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术工厂 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读