JAVA代码中日周月季年的数据查询的统计通用封装

目录

背景

​编辑

实现思路

包结构

规则引擎调用入口

调用执行策略

规则引擎详细代码

TimeConditionExpression.java

TimeConditionType.java

 TimeConditionRule.java

TimeConditionRuleEngine.java

MergeRank.java

TimeConditionInvocationHandler.java

接下来是5个规则实现类

DayConditionRule.java

WeekConditionRule.java

MonthConditionRule.java

QuarterConditionRule.java

YearConditionRule.java

最终输出的数据结构如下:


背景

        工作中常常需要对某一个或者多个指标进行日期维度的统计分析。通常简单的思路是从sql中实现,直接查询当前维度所有日期的指标值,这种写法sql会比较复杂,扩展性不强。

        现在介绍一种JAVA代码中基于规则引擎的设计思路进行封装的 年、季、月、周、日五个维度的统计查询实现思路,整体代码简洁易读,易于扩展。分享给各位参考,一起学习!

常见的场景如下:

实现思路

        接下来会直接贴代码,并简单描述。

包结构

规则引擎调用入口

        参数即时间维度,这是策略中定义的枚举值。

@GetMapping("/clean-data-trend")
    @ApiOperation(value = "已清洗数据表/已清洗覆盖率", notes = "day week month season year")
    public R<Object> cleanDataTrend(@ApiParam(value = "day week month season year", required = true) String dataType) {
        Map<String, Object> resultMap = dataGovernService.cleanDataTrend(dataType);
        return R.data(resultMap);
    }

调用执行策略

        最终经过规则处理后返回一组当前维度的指标值。

// 统计清洗数据趋势
TimeConditionExpression ex = new TimeConditionExpression(
                TimeConditionType.getCode(dataType),
                DataGovernMapper.class.getName(),
                new String[]{"cleanDataTableTrend"},
                null,
                null,
                sqlSession);
TimeConditionRuleEngine engine = new TimeConditionRuleEngine();
List<MergeRank> tableTrendList = engine1.process(ex);

规则引擎详细代码

接下来是重点

TimeConditionExpression.java

        维度规则表达式,可以理解成引擎中执行的入参。

range            时间范围
className   引擎最终需要执行的 Mapper 类名

method         需要执行的方法名字符串

paramTypes 参数类型 (额外参数的类型)

params         参数对象(与参数类型对应)

sqlSession    对应连接源的sqlSession,用于执行单粒度统计sql

package com.dsj.prod.backend.biz.utils.tcr;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.ibatis.session.SqlSession;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TimeConditionExpression {

        private TimeConditionType range;
        private String className;
        private String[] method;
        private Class<?>[] paramTypes;
        private Object[] params;
        private SqlSession sqlSession;


}

TimeConditionType.java

        时间维度枚举

package com.dsj.prod.backend.biz.utils.tcr;

import com.baomidou.mybatisplus.annotation.EnumValue;

public enum TimeConditionType {
    DAY(1, "day"),
    WEEK(2, "week"),
    MONTH(3, "month"),
    SEASON(4, "season"),
    YEAR(5, "year"),
    ;

    TimeConditionType(int code, String descp){
        this.code = code;
        this.descp = descp;
    }

    @EnumValue
    private final int code;
    private final String descp;

    public static TimeConditionType getEnum(int value){
        for (TimeConditionType e:TimeConditionType.values()) {
            if(e.ordinal() == value) {
                return e;
            }
        }
        return null;
    }

    public static TimeConditionType getCode(String descp) {
        for (TimeConditionType e : TimeConditionType.values()) {
            if (e.getDescp().equalsIgnoreCase(descp)) {
                return e;
            }
        }
        return TimeConditionType.DAY;
    }

    public int getCode() {
        return code;
    }

    public String getDescp() {
        return descp;
    }
}

 TimeConditionRule.java

        时间维度规则配置.

import com.dsj.prod.backend.api.vo.MergeRank;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Locale;

/**
 * 融合数据总量
 */
public abstract class TimeConditionRule {
    private static final Logger logger = LoggerFactory.getLogger(TimeConditionRule.class);

    public static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static Calendar startCalendar = Calendar.getInstance(Locale.CHINA);
    public static Calendar endCalendar = Calendar.getInstance(Locale.CHINA);

    protected void initCalendar() {
        startCalendar = Calendar.getInstance(Locale.CHINA);
        endCalendar = Calendar.getInstance(Locale.CHINA);
        startCalendar.set(Calendar.MILLISECOND, 0);
        startCalendar.set(Calendar.SECOND, 0);
        startCalendar.set(Calendar.MINUTE, 0);
        startCalendar.set(Calendar.HOUR_OF_DAY, 0);
        endCalendar.set(Calendar.MILLISECOND, 999);
        endCalendar.set(Calendar.SECOND,59);
        endCalendar.set(Calendar.MINUTE, 59);
        endCalendar.set(Calendar.HOUR_OF_DAY, 23);
    }

    abstract boolean evaluate(TimeConditionExpression expression);

    abstract List<MergeRank> getResult();

    public Object getObject(String className, String methon, SqlSession sqlSession, Class<?>[] parameterTypes, Object[] args) {
        try {
            Class interfaceImpl = Class.forName(className);
            Object instance = Proxy.newProxyInstance(
                    interfaceImpl.getClassLoader(),
                    new Class[]{interfaceImpl},
                    new TimeConditionInvocationHandler(sqlSession.getMapper(interfaceImpl))
            );

            Method method = instance.getClass().getMethod(methon, parameterTypes);

            return method.invoke(instance, args);
        } catch (Exception e) {
            logger.error("getObject error is : ", e);
        }
        return null;
    }

    /**
     * 修复 jdk 的 WEEK_OF_YEAR 跨年陷阱
     *
     * @param date
     * @return {@link Integer}
     * @author chentl
     * @version v1.0.0
     * @since 3:21 下午 2022/1/6
     **/
    public static Integer getWeekOfYear(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setFirstDayOfWeek(Calendar.MONDAY);
        calendar.setTime(date);
        int week = calendar.get(Calendar.WEEK_OF_YEAR);
        // JDK think 2021-12-31 as 2022 1th week
        int mouth = calendar.get(Calendar.MONTH);
        if (mouth >= 11 && week <= 1) {
            week += 52;
        }
        return week;
    }
}

TimeConditionRuleEngine.java

        时间维度规则引擎对象,由它执行具体的规则计算。

package com.dsj.prod.backend.biz.utils.tcr;

import com.dsj.prod.backend.api.vo.MergeRank;
import java.util.ArrayList;
import java.util.List;

public class TimeConditionRuleEngine {

    private static List<TimeConditionRule> rules = new ArrayList<>();

    static {
        rules.add(new DayConditionRule());
        rules.add(new WeekConditionRule());
        rules.add(new MonthConditionRule());
        rules.add(new QuarterConditionRule());
        rules.add(new YearConditionRule());
    }

    public List<MergeRank> process(TimeConditionExpression expression) {

        TimeConditionRule rule = rules.stream()
            .filter(r -> r.evaluate(expression))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("Expression does not matches any Rule"));
        return rule.getResult();
    }
}

MergeRank.java

        合并统计结果对象,用于接收细粒度统计结果。最终将当前时间维度的所有日期数据按顺序封装成集合返回。

package com.dsj.prod.backend.api.vo;

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.Date;

@Data
public class MergeRank {

    private String dayCount;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM")
    @JSONField(format = "yyyy-MM")
    @ApiModelProperty(value = "单个接口上次调用时间", required = true)
    private String time;
    private long millionTime;

    public static int compare(MergeRank o1,MergeRank o2){
        if (o1.getMillionTime() > o2.getMillionTime()) {
            return 1;
        }
        if (o1.getMillionTime() < o2.getMillionTime()) {
            return -1;
        }
        return 0;
    }
}

TimeConditionInvocationHandler.java

        SQLMapper的动态代理类

/**
 * 动态代理类
 */
public class TimeConditionInvocationHandler implements InvocationHandler {

    private Object target;

    public TimeConditionInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }
}

XXXXMapper.xml

        这个是底层最细粒度的统计sql实现,由自己根据业务编写。查询出时间维度下,某个时间节点的指标值即可。是不是非常简单!

  <sql id="timeRangeConditions">
        <if test="startDate!=null and endDate!=null">
            <![CDATA[
                    AND create_time >= #{startDate}
                    AND create_time <= #{endDate}
                ]]>
        </if>
    </sql>



<select id="cleanDataTableTrend" resultType="com.dsj.prod.backend.api.vo.MergeRank">
        SELECT
        count( DISTINCT t1.source_table_id ) dayCount
        FROM
        t_inspect_clean_task_table t1
        WHERE
        t1.is_deleted = 0
        <include refid="timeRangeConditions"/>

    </select>

接下来是5个规则实现类

DayConditionRule.java

        按天维度,默认近7天

package com.dsj.prod.backend.biz.utils.tcr;

import cn.hutool.core.util.ObjectUtil;
import com.dsj.prod.backend.api.vo.MergeRank;
import com.dsj.prod.common.utils.DateUtils;

import java.util.*;
import java.util.stream.Stream;

/**
 * 一个星期每天统计
 */
public class DayConditionRule extends TimeConditionRule {
    private List<MergeRank> mergeRankList = new ArrayList<>();

    @Override
    public boolean evaluate(TimeConditionExpression expression) {
        if (expression.getRange() == TimeConditionType.DAY) {
            initCalendar();

            List<MergeRank> mergeRankList = new ArrayList<>();

            for (int i = 0; i < 7; i++) {
                Class<?>[] defaultParamTypes = new Class<?>[]{String.class, String.class};
                Object[] defaultParams = new Object[]{format.format(startCalendar.getTime()), format.format(endCalendar.getTime())};

                Class<?>[] paramTypes = Stream.concat(
                        Arrays.stream(defaultParamTypes),
                        Objects.isNull(expression.getParamTypes()) ? Stream.empty() : Arrays.stream(expression.getParamTypes())
                ).toArray(Class[]::new);

                Object[] combinedParams = Stream.concat(
                        Arrays.stream(defaultParams),
                        Objects.isNull(expression.getParams()) ? Stream.empty() : Arrays.stream(expression.getParams())
                ).toArray();

                MergeRank mergeRank = (MergeRank) getObject(
                        expression.getClassName(),
                        expression.getMethod()[0],
                        expression.getSqlSession(),
                        paramTypes,
                        combinedParams
                );


                startCalendar.add(Calendar.DATE, -1);
                endCalendar.add(Calendar.DATE, -1);

                if (ObjectUtil.isNull(mergeRank)) {
                    mergeRank = new MergeRank();
                    mergeRank.setDayCount("0");
                }
                mergeRank.setTime(DateUtils.format(endCalendar.getTime(), "yyyy-MM-dd"));
                mergeRankList.add(mergeRank);
            }
            this.mergeRankList = mergeRankList;

            return true;
        }
        return false;
    }

    @Override
    public List<MergeRank> getResult() {
        return mergeRankList;
    }
}
WeekConditionRule.java

        按周维度,默认近4周

package com.dsj.prod.backend.biz.utils.tcr;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.dsj.prod.backend.api.vo.MergeRank;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

/**
 * 一个月每周统计
 */
public class WeekConditionRule extends TimeConditionRule{
    private List<MergeRank> mergeRankList = new ArrayList<MergeRank>();

	public static void main(String[] args) {
		System.out.println(DateUtil.beginOfWeek(new Date()));
		System.out.println(DateUtil.endOfWeek(new Date()));
	}

    @Override
    public boolean evaluate(TimeConditionExpression expression) {

        if (expression.getRange() == TimeConditionType.WEEK) {
            initCalendar();

            List<MergeRank> mergeRankList = new ArrayList<MergeRank>();
            startCalendar.setTime(DateUtil.beginOfWeek(new Date()));
            endCalendar.setTime(DateUtil.endOfWeek(new Date()));

            for (int i = 0; i < 4; i++) {
				System.out.println("startCalendar: " + format.format(startCalendar.getTime()));
				System.out.println("endCalendar: " + format.format(endCalendar.getTime()));

                MergeRank mergeRank = (MergeRank) getObject(expression.getClassName(), expression.getMethod()[0], expression.getSqlSession(),
                        ArrayUtil.addAll( expression.getParamTypes(),new Class[]{String.class, String.class}),
                        ArrayUtil.addAll(expression.getParams(),new Object[]{format.format(startCalendar.getTime()), format.format(endCalendar.getTime())}));

                if (ObjectUtil.isNull(mergeRank)) {
                    mergeRank = new MergeRank();
                    mergeRank.setDayCount("0");
                }
                mergeRank.setTime(MessageFormat.format("{0}年第{1}周", String.valueOf(endCalendar.get(Calendar.YEAR)), getWeekOfYear(endCalendar.getTime())));
                mergeRank.setMillionTime(endCalendar.getTime().getTime());
				startCalendar.add(Calendar.DATE, -7);
                endCalendar.add(Calendar.DATE, -7);
                mergeRankList.add(mergeRank);
            }

            //排序
            mergeRankList.sort(MergeRank::compare);
            this.mergeRankList = mergeRankList;

            return true;
        }
        return false;
    }

    @Override
    public List<MergeRank> getResult() {
        return mergeRankList;
    }

}
MonthConditionRule.java

        按月统计,默认12月

package com.dsj.prod.backend.biz.utils.tcr;

import cn.hutool.core.util.ObjectUtil;
import com.dsj.prod.backend.api.enums.ProcessType;
import com.dsj.prod.backend.api.vo.MergeRank;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * 一个季度每月周统计
 */
public class MonthConditionRule extends TimeConditionRule{
    private List<MergeRank> mergeRankList = new ArrayList<MergeRank>();

    @Override
    public boolean evaluate(TimeConditionExpression expression) {

        if (expression.getRange() == TimeConditionType.MONTH) {
            initCalendar();

            List<MergeRank> mergeRankList = new ArrayList<MergeRank>();

            for (int i = 0; i < 12; i++) {
                startCalendar.add(Calendar.MONTH, -1);

                MergeRank mergeRank = (MergeRank) getObject(expression.getClassName(), expression.getMethod()[0], expression.getSqlSession(),
                        new Class[] {String.class, String.class},
                        new Object[] {format.format(startCalendar.getTime()), format.format(endCalendar.getTime())});


                if (ObjectUtil.isNull(mergeRank)) {
                    mergeRank = new MergeRank();
                    mergeRank.setDayCount("0");
                }
                mergeRank.setTime(MessageFormat.format("{0}年{1}月", String.valueOf(endCalendar.get(Calendar.YEAR)), endCalendar.get(Calendar.MONTH) + 1));
                endCalendar.add(Calendar.MONTH, -1);

                mergeRankList.add(mergeRank);
            }


            this.mergeRankList = mergeRankList;

            return true;
        }
        return false;
    }

    @Override
    public List<MergeRank> getResult() {
        return mergeRankList;
    }
}
QuarterConditionRule.java

        按季统计,默认近4季度

package com.dsj.prod.backend.biz.utils.tcr;

import cn.hutool.core.util.ObjectUtil;
import com.dsj.prod.backend.api.enums.ProcessType;
import com.dsj.prod.backend.api.vo.MergeRank;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * 一年每个季度统计
 */
public class QuarterConditionRule extends TimeConditionRule{
    private List<MergeRank> mergeRankList = new ArrayList<MergeRank>();

    @Override
    public boolean evaluate(TimeConditionExpression expression) {

        if (expression.getRange() == TimeConditionType.SEASON) {
            initCalendar();

            List<MergeRank> mergeRankList = new ArrayList<MergeRank>();

            for (int i = 0; i < 4; i++) {
                startCalendar.add(Calendar.MONTH, -3);

                MergeRank mergeRank = (MergeRank) getObject(expression.getClassName(), expression.getMethod()[0], expression.getSqlSession(),
                        new Class[] {String.class, String.class},
                        new Object[] {format.format(startCalendar.getTime()), format.format(endCalendar.getTime())});


                if (ObjectUtil.isNull(mergeRank)) {
                    mergeRank = new MergeRank();
                    mergeRank.setDayCount("0");
                }

                //计算季度
                int currentMonth = endCalendar.get(Calendar.MONTH);
                int currentQuarter = (currentMonth % 4) + 1;
                mergeRank.setTime(MessageFormat.format("{0}年第{1}季度", String.valueOf(endCalendar.get(Calendar.YEAR)), currentQuarter));


                endCalendar.add(Calendar.MONTH, -3);
                mergeRankList.add(mergeRank);
            }


            this.mergeRankList = mergeRankList;

            return true;
        }
        return false;
    }

    @Override
    public List<MergeRank> getResult() {
        return mergeRankList;
    }
}
YearConditionRule.java

        按年维度,默认近7年。

package com.dsj.prod.backend.biz.utils.tcr;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import com.dsj.prod.backend.api.enums.ProcessType;
import com.dsj.prod.backend.api.vo.MergeRank;
import org.apache.commons.collections.ListUtils;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

/**
 * 最近7年统计
 */
public class YearConditionRule extends TimeConditionRule{
    private List<MergeRank> mergeRankList = new ArrayList<MergeRank>();

    @Override
    public boolean evaluate(TimeConditionExpression expression) {

        if (expression.getRange() == TimeConditionType.YEAR) {
            initCalendar();

            List<MergeRank> mergeRankList = new ArrayList<MergeRank>();

            for (int i = 0; i < 7; i++) {
                startCalendar.add(Calendar.YEAR, -1);



                MergeRank mergeRank = (MergeRank) getObject(expression.getClassName(), expression.getMethod()[0], expression.getSqlSession(),
                        ArrayUtil.addAll(expression.getParamTypes(),new Class[]{String.class, String.class}),
                        ArrayUtil.addAll(expression.getParams(),new Object[]{format.format(startCalendar.getTime()), format.format(endCalendar.getTime())}));

                if (ObjectUtil.isNull(mergeRank)) {
                    mergeRank = new MergeRank();
                    mergeRank.setDayCount("0");
                }
                mergeRank.setTime(MessageFormat.format("{0}年", String.valueOf(endCalendar.get(Calendar.YEAR))));
                mergeRank.setMillionTime(endCalendar.getTime().getTime());
                endCalendar.add(Calendar.YEAR, -1);
                mergeRankList.add(mergeRank);
            }
            //排序
            mergeRankList.sort(MergeRank::compare);
            this.mergeRankList = mergeRankList;

            return true;
        }
        return false;
    }

    @Override
    public List<MergeRank> getResult() {
        return mergeRankList;
    }
}

最终输出的数据结构如下:

{
  "code": 200,
  "success": true,
  "data": {
    "apiDataSumTrend": [
      {
        "dayCount": "177246",
        "time": "2023年第46周"
      },
      {
        "dayCount": "177036",
        "time": "2023年第45周"
      },
      {
        "dayCount": "991",
        "time": "2023年第44周"
      },
      {
        "dayCount": "381",
        "time": "2023年第43周"
      }
    ]
  },
  "msg": "操作成功"
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Java统计学生成绩可以通过以下步骤实现: 1. 创建一个包含学生成绩的数组或集合,可以使用数组、ArrayList等数据结构来存储学生成绩。 2. 遍历数组或集合,计算总分、平均分、最高分和最低分等统计指标。 3. 可以使用循环结构和条件判断语句来实现遍历和统计操作。 4. 可以使用Java的数学库(如Math类)来进行数值计算,例如计算平均分。 5. 可以使用Java的排序算法(如Arrays.sort()方法)对成绩进行排序,以便找出最高分和最低分。 6. 可以使用条件判断语句来判断学生是否及格或优秀等。 下面是一个简单的示例代码,用于统计学生成绩: ```java import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class GradeStatistics { public static void main(String[] args) { // 创建学生成绩列表 List<Integer> grades = new ArrayList<>(Arrays.asList(80, 90, 70, 85, 95)); // 统计总分 int total = 0; for (int grade : grades) { total += grade; } // 计算平均分 double average = (double) total / grades.size(); // 找出最高分和最低分 int maxGrade = Integer.MIN_VALUE; int minGrade = Integer.MAX_VALUE; for (int grade : grades) { if (grade > maxGrade) { maxGrade = grade; } if (grade < minGrade) { minGrade = grade; } } // 输出统计结果 System.out.println("总分:" + total); System.out.println("平均分:" + average); System.out.println("最高分:" + maxGrade); System.out.println("最低分:" + minGrade); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值