gitweixin
  • 首页
  • 小程序代码
    • 资讯读书
    • 工具类
    • O2O
    • 地图定位
    • 社交
    • 行业软件
    • 电商类
    • 互联网类
    • 企业类
    • UI控件
  • 大数据开发
    • Hadoop
    • Spark
    • Hbase
    • Elasticsearch
    • Kafka
    • Flink
    • 数据仓库
    • 数据挖掘
    • flume
    • Kafka
    • Hive
    • shardingsphere
    • solr
  • 开发博客
    • Android
    • php
    • python
    • 运维
    • 技术架构
    • 数据库
  • 程序员网赚
  • bug清单
  • 量化投资
  • 在线查询工具
    • 去行号
    • 在线时间戳转换工具
    • 免费图片批量修改尺寸在线工具
    • SVG转JPG在线工具
    • SVG转PDF/Word
    • SVG转Draw.io可二次编辑格式
    • js代码混淆

Android绘制股票k线图系列2-绘制分时图

精品微信小程序开发门户,代码全部亲测可用

  • 首页   /  
  • 作者: east
  • ( 页面71 )
Android 12月 8,2020

Android绘制股票k线图系列2-绘制分时图



import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.graphics.Shader;
import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;



import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TimeChartView extends GridChartView {

    /**
     * 分时数据
     */
    private Vector<TimeData> data;
    /**
     * 时间段数据
     */
    private List<TRADETIME> tradetimes;
    /**
     * 昨收价
     */
    private float preClose;

    /**
     * 记录列表中设置到上表中的数据的最大值
     */
    private float upMaxData;
    /**
     * 记录列表中设置到上表中的数据的最小值
     */
    private float upMinData;
    /**
     * 记录列表中设置到下表中的数据最大值
     */
    private float downMaxData;
    /**
     * 记录列表中设置到下表中的数据最小值
     */
    private float downMinData;
    /**
     * 上表与下表中间的日期时间文字的默认大小
     */
    protected final float DEFAULT_textSize = 9;
    /**
     * 文字大小
     */
    protected float textSize;
    /**
     * 上表与下表间距
     */
    protected float spacing;

    /**
     * 默认经线条数
     */
    private final int DEFAULT_LONGITUDE_NUM = 3;
    /**
     * 默认经线类型(24小时)
     */
    private final int DEFAULT_THEME = LongitudesTheme.theme0;
    /**
     * 默认上表纬线条数
     */
    private final int DEFAULT_LATITUDE_UP_NUM = 4;
    /**
     * 默认下表纬线条数
     */
    private final int DEFAULT_LATITUDE_DOWN_NUM = 2;
    /**
     * 默认经线颜色
     */
    private final int DEFAULT_LONGITUDE_COLOR = 0xffb7b7b7;

    /**
     * 默认纬线颜色
     */
    private final int MIDDLE_LONGITUDE_LATITUDE_COLOR = 0xffff4700;
    /**
     * 中心的经纬线颜色
     */
    private final int DEFAULT_LATITUDE_COLOR = 0xffeeeeee;
    /**
     * 默认经、纬线宽
     */
    private final float DEFAULT_LATITUDE_LONGITUDE_WIDTH = 0.8f;


    /**
     * 经线间距
     */
    protected float longitudesSpacing;
    /**
     * 纬线间距
     */
    protected float latitudesSpacing;
    /**
     * 经线条数
     */
    private int longitudesNum = DEFAULT_LONGITUDE_NUM;
    /**
     * 经线类型
     */
    private int longitudesTheme = DEFAULT_THEME;
    /**
     * 上表纬线条数
     */
    protected int latitudesUpNum = DEFAULT_LATITUDE_UP_NUM;
    /**
     * 下表纬线条数
     */
    protected int latitudesDownNum = DEFAULT_LATITUDE_DOWN_NUM;
    /**
     * 经线颜色
     */
    private int longitudesColor = DEFAULT_LONGITUDE_COLOR;
    private int midLongitudesColor = 0xffff4700;
    /**
     * 纬线颜色
     */
    private int latitudesColor = DEFAULT_LATITUDE_COLOR;
    /**
     * 经、纬线宽
     */
    private float latLongWidth = DEFAULT_LATITUDE_LONGITUDE_WIDTH;
    /**
     * 是否绘制经线
     */
    private boolean isDrawLongitudes;
    /**
     * 是否绘制纬线
     */
    private boolean isDrawLatitudes;

    /**
     * 上表底部
     */
    protected float upChartBottom;
    /**
     * 下表底部
     */
    protected float downChartBottom;
    /**
     * 上表高度
     */
    protected float upChartHeight;
    /**
     * 下表高度
     */
    protected float downChartHeight;

    /**
     * 分时默认最大时间
     */
    private final int MAX_DATE = 60 * 4 + 1;
    /**
     * 分时最大时间(默认24个小时)
     */
    private int maxDate = MAX_DATE;

    /**
     * 各个数据点的间距
     */
    private float dataSpacing;

    /**
     * 上表的尺寸与数据比例
     */
    private float upRatio;
    /**
     * 下表的尺寸与数据比例
     */
    private float downRatio;
    /**
     * 下表柱形图宽
     */
    private float columnWidth;

    /**
     * 是否显示十字光标
     */
    private boolean isShowCross;
    /**
     * 十字光标X坐标
     */
    private float crossX;
    /**
     * 十字光标Y坐标
     */
    private float crossY;

    /**
     * 长按事件的Runnable
     */
    private Runnable mRunnable;
    /**
     * 手指是否离开屏幕
     */
    private boolean isReleased;
    /**
     * 手指是否移动了
     */
    private boolean isMoved;
    /**
     * 是否可以执行OnTouch中的Move事件了
     */
    private boolean isStartMoved;
    /**
     * 按下时的X坐标
     */
    private float touchDownX;
    /**
     * 按下时的Y坐标
     */
    private float touchDownY;
    /**
     * 触摸点 X
     */
    private float movedX;
    /**
     * 触摸点 Y
     */
    private float movedY;

    private float moveRawX;// 触摸中的X坐标
    private float moveRawY;// 触摸中的Y坐标
    /**
     * 设置是否将两边坐标值绘制在边框线外(注:如果设置成true,必须设置strokeLeft和strokeRight)
     */
    private boolean isDrawOutside;

    /**
     * 传递数据的接口
     */
    private OnTimeListener l;
    /**
     * 单击事件接口
     */
    private OnTimeChartClickListener mOnTimeChartClickListener;

    /**
     * 左边要显示当前十字光标中心点的数据
     */
    private float leftData;
    /**
     * 右边要显示当前十字光标中心点的数据
     */
    private float rightData;
    /**
     * 底部要显示当前十字光标中心点点数据
     */
    private float bottomData;
    /**
     * 中间时间
     */
    private long time;

    ExecutorService executorService = Executors.newCachedThreadPool();

    /**
     * 小数保留位数
     */
    private int num = 2;// 默认保留两位

    /**
     * 经线类型
     *
     * @author dingrui
     */
    public static class LongitudesTheme {
        public static final int theme0 = 0;// 9:30-11:30(2h) 13:00-15:00(2h)
        public static final int theme1 = 1;// 6:00-次日6:00(24h)
        public static final int theme2 = 2;// 昨日20:00-2:30(6.5h),9:00-11:30(2.5h),13:30-15:30(2h)
        public static final int theme3 = 3;// 8:00-次日2:00(18h)
        public static final int theme4 = 4;// 9:00-10:15(1.5h),10:30-11:30(1h),13:30-15:00(1.5h),21:00-次日2:30(5.5h)
        public static final int theme5 = 5;// 9:00-10:15(1.25h),10:30-11:30(1h),13:30-15:00(1.5h)
        public static final int theme6 = 6;// 9:00-10:15(1.25h),10:30-11:30(1h),13:30-15:00(1.5h),21:00-23:30(2.5h)
        public static final int theme7 = 7;// 9:00-10:15(1.25h),10:30-11:30(1h),13:30-15:00(1.5h),21:00-23:00(2h)
        public static final int theme8 = 8;// 9:00-10:15(1.25h),10:30-11:30(1h),13:30-15:00(1.5h),21:00-次日1:00(4h)
    }



    private boolean isDrawDownChart = true; //是否画下表

    // private TimeListThread mTimeListThread;

    Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            switch (msg.what) {
                case 0x1:
                    postInvalidate();
                    break;
                case 0x2:
                    Bundle bundle = msg.getData();
                    data = (Vector<TimeData>) bundle.getSerializable("data");
                    tradetimes = (List<TRADETIME>) bundle.getSerializable("tradetimes");
                    upMaxData = bundle.getFloat("upMaxData");
                    upMinData = bundle.getFloat("upMinData");
                    downMinData = bundle.getFloat("downMinData");
                    downMaxData = bundle.getFloat("downMaxData");
                    preClose = bundle.getFloat("preClose");
                    if (!isShowCross)
                        postInvalidate();
                    break;
                default:
                    break;
            }
        }
    };

    public TimeChartView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        init();
    }

    public TimeChartView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        init();
    }

    public TimeChartView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        // TODO Auto-generated constructor stub
        init();
    }

    /**
     * 初始化
     */
    private void init() {
        upMaxData = 0;
        downMaxData = 0;
        isShowCross = false;
        textSize = MyUtils.getInstance().dp2px(getContext(), DEFAULT_textSize);
        spacing = textSize * 2;// 比文字高度高一半
        isDrawLongitudes = true;
        isDrawLatitudes = true;
        isDrawOutside = false;

        mRunnable = new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                if (isMoved || isReleased)
                    return;
                isStartMoved = true;
            }
        };
    }


    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        float viewHight = getHeight();
        float viewWidth = getWidth();
        // 文字画笔
        TextPaint tp = new TextPaint();
        tp.setTextSize(textSize);
        //    tp.setTypeface(Typeface.DEFAULT_BOLD);
        int upLength = Parse.getInstance().parse2String(upMaxData, num)
                .length();
        int downLength = Parse.getInstance().parse2CNString(downMaxData)
                .length();
        int defaultLength = "0000.00".length();
        float dataWidth = 0;
        if (upLength > downLength) {
            if (defaultLength > upLength) {
                dataWidth = tp.measureText("0000.00");
            } else {
                dataWidth = tp.measureText(Parse.getInstance().parse2String(
                        upMaxData, num));
            }
        } else {
            if (defaultLength > downLength) {
                dataWidth = tp.measureText("0000.00");
            } else {
                dataWidth = tp.measureText(Parse.getInstance().parse2CNString(
                        downMaxData));
            }
        }
        if (spacing == 0.0f) {
            setStrokeBottom(textSize);
        }
        if(!isDrawDownChart) {
            latitudesDownNum = 0;
        }
        latitudesSpacing = (viewHight - getStrokeWidth() * 2 - spacing
                - getStrokeTop() - getStrokeBottom())
                / (latitudesUpNum + latitudesDownNum);

        upChartHeight = latitudesSpacing * latitudesUpNum;
        upChartBottom = latitudesSpacing * latitudesUpNum + getStrokeTop()
                + getStrokeWidth();

        downChartHeight = latitudesSpacing * latitudesDownNum;
        downChartBottom = viewHight - getStrokeBottom() - getStrokeWidth();
        columnWidth = (getWidth() - getStrokeWidth() * 2 - getStrokeLeft() - getStrokeRight())
                / maxDate;
        if (columnWidth > 2.0f) {
            columnWidth = 2.0f;
        }
        dataSpacing = (getWidth() - getStrokeWidth() * 2 - getStrokeLeft()
                - getStrokeRight() - 2.0f)
                / (maxDate - 1);

        if (upMaxData >= preClose && upMinData <= preClose) {
            if (upMaxData - preClose >= preClose - upMinData) {
                upMinData = preClose - (upMaxData - preClose);
            } else {
                upMaxData = preClose + (preClose - upMinData);
            }
        } else if (upMaxData >= preClose && upMinData >= preClose) {
            upMinData = preClose - (upMaxData - preClose);
        } else {
            upMaxData = preClose + (preClose - upMinData);
        }

        upRatio = upChartHeight / (upMaxData - upMinData);

        downRatio = downChartHeight / (downMaxData - downMinData);

        // 画笔
        Paint paint = new Paint();

        if (isDrawLatitudes) {
            // 绘制纬线
            drawLatitudes(canvas, paint);
        }
        if (isDrawLongitudes)
        // 绘制经线
        {
            if (tradetimes != null && tradetimes.size() > 0) {
                drawLongitudes1(canvas, paint);
            } else {
                drawLongitudes(canvas, paint);
            }
        }

        paint.reset();
        // 绘制上表折线
        drawUpLine(canvas, paint);
//        drawCubicUpLine(canvas, paint);

        // 绘制上表Y坐标上的X数据
        drawUpAxisXTitle(canvas, tp, dataWidth);

        if(isDrawDownChart) {
            // 绘制下表柱形图
            drawDownColumnChart(canvas, paint);

            // 绘制下表刻度
            drawDownAxisXTitle(canvas, tp, dataWidth);
        }

        // 绘制时间
        // drawTime(canvas, tp);

        // 绘制十字线
        drawCrossLine(canvas, paint);

    }

    /**
     * 绘制纬线
     *
     * @param canvas 画布
     * @param paint
     */
    private void drawLatitudes(Canvas canvas, Paint paint) {
        paint.reset();
        PathEffect pe = new DashPathEffect(new float[]{4, 4}, 1);
        paint.setColor(latitudesColor);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(latLongWidth);
        paint.setAntiAlias(true);
        Path path = new Path();
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        for (int i = 1; i <= latitudesUpNum; i++) {
            if(i == (latitudesUpNum + 1) / 2){
                paint.setColor(MIDDLE_LONGITUDE_LATITUDE_COLOR);
                paint.setStrokeWidth(1);
            }else {
                paint.setColor(latitudesColor);
                paint.setStrokeWidth(latLongWidth);
            }
            //         paint1.setColor(MIDDLE_LONGITUDE_LATITUDE_COLOR);
            if (i == latitudesUpNum || i == latitudesUpNum / 2) {
                paint.setPathEffect(pe);

            }
            else {
                paint.setPathEffect(pe);
            }

            canvas.drawLine(getStrokeLeft(), latitudesSpacing * i
                            + getStrokeWidth() + getStrokeTop(), getWidth()
                            -
                            getStrokeRight(),
                    latitudesSpacing * i + getStrokeWidth()
                            + getStrokeTop(), paint);
        /*    path.moveTo(getStrokeLeft(), latitudesSpacing * i
                    + getStrokeWidth() + getStrokeTop());
            path.lineTo(getWidth()
                            -
                            getStrokeRight(),
                    latitudesSpacing * i + getStrokeWidth()
                            + getStrokeTop());
          */
        }
        if(isDrawDownChart) {
            for (int i = 1; i <= latitudesDownNum; i++) {
                if (i == latitudesDownNum)
                    paint.setPathEffect(null);
                else
                    paint.setPathEffect(pe);

                canvas.drawLine(getStrokeLeft(),
                        (getHeight() - getStrokeWidth() - getStrokeBottom())
                                - latitudesSpacing * i, getWidth()
                                - getStrokeRight(),
                        (getHeight() - getStrokeWidth() - getStrokeBottom())
                                - latitudesSpacing * i, paint);

            }
        }

    }

    /**
     * 绘制经线
     *
     * @param canvas 画布
     * @param paint
     */
    private void drawLongitudes(Canvas canvas, Paint paint) {
        Paint paint1 = new Paint();
        PathEffect pe = new DashPathEffect(new float[] { 4, 4 }, 1);
        TextPaint tp = new TextPaint();
        tp.setTextSize(textSize / 4 * 3);
        tp.setTypeface(Typeface.DEFAULT_BOLD);
        paint1.setColor(longitudesColor);
        paint1.setStyle(Paint.Style.STROKE);
        paint1.setStrokeWidth(latLongWidth);
        paint1.setAntiAlias(true);
        paint1.setPathEffect(pe);
        Path path1 = new Path();
        float width = getWidth() - getStrokeWidth() * 2 - getStrokeLeft()
                - getStrokeRight() - 2.0f;
        if (longitudesTheme == LongitudesTheme.theme0) {
            // 4H
            longitudesSpacing = width / (longitudesNum + 1);
            for (int i = 1; i <= longitudesNum; i++) {
          /*      canvas.drawLine(longitudesSpacing * i + getStrokeWidth()
                                + getStrokeLeft(), getStrokeWidth() + getStrokeTop(),
                        longitudesSpacing * i + getStrokeWidth()
                        + getStrokeLeft(), latitudesSpacing
                                           * latitudesUpNum + getStrokeWidth()
                                           + getStrokeTop(), paint); */
                if(i == latitudesUpNum / 2){
                    paint1.setColor(midLongitudesColor);
                }else{
                    paint1.setColor(longitudesColor);
                }
                path1.moveTo(longitudesSpacing * i + getStrokeWidth()
                        + getStrokeLeft(), getStrokeWidth() + getStrokeTop());
                path1.lineTo(longitudesSpacing * i + getStrokeWidth()
                        + getStrokeLeft(), latitudesSpacing
                        * latitudesUpNum + getStrokeWidth()
                        + getStrokeTop());

             /*   canvas.drawLine(longitudesSpacing * i + getStrokeWidth()
                                + getStrokeLeft(), getHeight() - getStrokeWidth()
                                - getStrokeBottom(), longitudesSpacing * i
                                + getStrokeWidth() +
                                getStrokeLeft(),
                        (getHeight()
                                - getStrokeWidth() - getStrokeBottom())
                                - latitudesSpacing * latitudesDownNum, paint); */
                path1.moveTo(longitudesSpacing * i + getStrokeWidth()
                        + getStrokeLeft(), getHeight() - getStrokeWidth()
                        - getStrokeBottom());
                path1.lineTo(longitudesSpacing * i
                                + getStrokeWidth() +
                                getStrokeLeft(),
                        (getHeight()
                                - getStrokeWidth() - getStrokeBottom())
                                - latitudesSpacing * latitudesDownNum);
            }
            canvas.drawPath(path1, paint1);
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("09:30");
            date.add("10:30");
            date.add("11:30/13:00");
            date.add("14:00");
            date.add("15:00");
            drawTime(canvas, tp, date);
        } else if (longitudesTheme == LongitudesTheme.theme1) {
            // 24H
            longitudesSpacing = width / (longitudesNum + 1);
            for (int i = 1; i <= longitudesNum; i++) {
                // if (i == longitudesNum / 2 + 1)
                // paint.setPathEffect(null);
                // else
                // paint.setPathEffect(pe);
                canvas.drawLine(longitudesSpacing * i + getStrokeWidth()
                                + getStrokeLeft(), getStrokeWidth() + getStrokeTop(),
                        longitudesSpacing * i + getStrokeWidth()
                                + getStrokeLeft(), latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(longitudesSpacing * i + getStrokeWidth()
                                    + getStrokeLeft(), getHeight() - getStrokeWidth()
                                    - getStrokeBottom(), longitudesSpacing * i
                                    + getStrokeWidth() +
                                    getStrokeLeft(),
                            (getHeight()
                                    - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }
            }
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("06:00");
            date.add("12:00");
            date.add("18:00");
            date.add("00:00");
            date.add("06:00");
            drawTime(canvas, tp, date);
        } else if (longitudesTheme == LongitudesTheme.theme2) {
            // 昨日20:00-2:30(6.5h),9:00-11:30(2.5h),13:30-15:30(2h)
            float total = 6.5f + 2.5f + 2f;
            float no1 = width / total * 6.5f;
            float no2 = width / total * (6.5f + 2.5f);
            for (int i = 0; i < longitudesNum; i++) {
                float x;
                if (i == 0) {
                    x = no1;
                } else
                    x = no2;
                canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                        getStrokeWidth() + getStrokeTop(), getStrokeLeft()
                                + getStrokeWidth() / 2 + x,
                        latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                            getHeight() - getStrokeWidth() - getStrokeBottom(),
                            getStrokeLeft() + getStrokeWidth() / 2 + x,
                            (getHeight() - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }
            }

            tp.setColor(getResources().getColor(R.color.text_color_5c5f66));
            tp.setAntiAlias(true);
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("20:00");
            date.add("02:30/09:00");
            date.add("13:30");
            date.add("15:30");
            float Y = 0.0f;
            if (spacing != 0.0f) {
                Y = upChartBottom + textSize;
            } else {
                Y = getHeight();
            }
            for (int i = 0; i < date.size(); i++) {
                if (i == date.size() - 1) {
                    canvas.drawText(date.get(i),
                            getWidth() - getStrokeWidth() - getStrokeRight()
                                    - 2 - tp.measureText(date.get(i)), Y, tp);
                } else if (i == 0) {
                    canvas.drawText(date.get(i), getStrokeLeft()
                            + getStrokeWidth() + 2, Y, tp);
                } else {
                    float x;
                    if (i == 1) {
                        x = no1;
                    } else {
                        x = no2;
                    }
                    canvas.drawText(date.get(i), getStrokeWidth() / 2
                            + getStrokeLeft() + x - tp.measureText(date.get(i))
                            / 2, Y, tp);
                }
            }
        } else if (longitudesTheme == LongitudesTheme.theme3) {
            // 8:00-次日2:00(18h)
            longitudesSpacing = width / (longitudesNum + 1);
            for (int i = 1; i <= longitudesNum; i++) {
                canvas.drawLine(longitudesSpacing * i + getStrokeWidth()
                                + getStrokeLeft(), getStrokeWidth() + getStrokeTop(),
                        longitudesSpacing * i + getStrokeWidth()
                                + getStrokeLeft(), latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(longitudesSpacing * i + getStrokeWidth()
                                    + getStrokeLeft(), getHeight() - getStrokeWidth()
                                    - getStrokeBottom(), longitudesSpacing * i
                                    + getStrokeWidth() +
                                    getStrokeLeft(),
                            (getHeight()
                                    - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }
            }
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("08:00");
            date.add("14:00");
            date.add("20:00");
            date.add("02:00");
            drawTime(canvas, tp, date);
        } else if (longitudesTheme == LongitudesTheme.theme4) {
            // 9:00-10:15(1.5h),10:30-11:30(1h),13:30-15:00(1.5h),21:00-次日2:30(5.5h)
            float total = 1.5f + 1f + 1.5f + 5.5f;
            float no1 = width / total * 1.5f;
            float no2 = width / total * (1.5f + 1f);
            float no3 = width / total * (1.5f + 1f + 1.5f);
            for (int i = 0; i < longitudesNum; i++) {
                float x;
                if (i == 0) {
                    x = no1;
                } else if (i == 1)
                    x = no2;
                else
                    x = no3;
                canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                        getStrokeWidth() + getStrokeTop(), getStrokeLeft()
                                + getStrokeWidth() / 2 + x,
                        latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                            getHeight() - getStrokeWidth() - getStrokeBottom(),
                            getStrokeLeft() + getStrokeWidth() / 2 + x,
                            (getHeight() - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }
            }

            tp.setColor(getResources().getColor(R.color.text_color_5c5f66));
            tp.setAntiAlias(true);
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("09:00");
            date.add("10:30");
            date.add("13:30");
            date.add("21:00");
            date.add("02:00");
            float Y = 0.0f;
            if (spacing != 0.0f) {
                Y = upChartBottom + textSize;
            } else {
                Y = getHeight();
            }
            for (int i = 0; i < date.size(); i++) {
                if (i == date.size() - 1) {
                    canvas.drawText(date.get(i),
                            getWidth() - getStrokeWidth() - getStrokeRight()
                                    - 2 - tp.measureText(date.get(i)), Y, tp);
                } else if (i == 0) {
                    canvas.drawText(date.get(i), getStrokeLeft()
                            + getStrokeWidth() + 2, Y, tp);
                } else {
                    float x;
                    if (i == 1) {
                        x = no1;
                    } else if (i == 2) {
                        x = no2;
                    } else {
                        x = no3;
                    }
                    canvas.drawText(date.get(i), getStrokeWidth() / 2
                            + getStrokeLeft() + x - tp.measureText(date.get(i))
                            / 2, Y, tp);
                }
            }
        } else if (longitudesTheme == LongitudesTheme.theme5) {
            // 9:00-10:15(1.25h),10:30-11:30(1h),13:30-15:00(1.5h)
            float total = 1.25f + 1f + 1.5f;
            float no1 = width / total * 1.25f;
            float no2 = width / total * (1.25f + 1f);
            for (int i = 0; i < longitudesNum; i++) {
                float x;
                if (i == 0) {
                    x = no1;
                } else
                    x = no2;
                canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                        getStrokeWidth() + getStrokeTop(), getStrokeLeft()
                                + getStrokeWidth() / 2 + x,
                        latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                            getHeight() - getStrokeWidth() - getStrokeBottom(),
                            getStrokeLeft() + getStrokeWidth() / 2 + x,
                            (getHeight() - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }
            }

            tp.setColor(getResources().getColor(R.color.text_color_5c5f66));
            tp.setAntiAlias(true);
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("09:00");
            date.add("10:30");
            date.add("13:30");
            date.add("15:00");
            float Y = 0.0f;
            if (spacing != 0.0f) {
                Y = upChartBottom + textSize;
            } else {
                Y = getHeight();
            }
            for (int i = 0; i < date.size(); i++) {
                if (i == date.size() - 1) {
                    canvas.drawText(date.get(i),
                            getWidth() - getStrokeWidth() - getStrokeRight()
                                    - 2 - tp.measureText(date.get(i)), Y, tp);
                } else if (i == 0) {
                    canvas.drawText(date.get(i), getStrokeLeft()
                            + getStrokeWidth() + 2, Y, tp);
                } else {
                    float x;
                    if (i == 1) {
                        x = no1;
                    } else {
                        x = no2;
                    }
                    canvas.drawText(date.get(i), getStrokeWidth() / 2
                            + getStrokeLeft() + x - tp.measureText(date.get(i))
                            / 2, Y, tp);
                }
            }
        } else if (longitudesTheme == LongitudesTheme.theme6) {
            // 9:00-10:15(1.25h),10:30-11:30(1h),13:30-15:00(1.5h),21:00-23:30(2.5h)
            float total = 1.25f + 1f + 1.5f + 2.5f;
            float no1 = width / total * 1.25f;
            float no2 = width / total * (1.25f + 1f);
            float no3 = width / total * (1.25f + 1f + 1.5f);
            for (int i = 0; i < longitudesNum; i++) {
                float x;
                if (i == 0) {
                    x = no1;
                } else if (i == 1)
                    x = no2;
                else
                    x = no3;
                canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                        getStrokeWidth() + getStrokeTop(), getStrokeLeft()
                                + getStrokeWidth() / 2 + x,
                        latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                            getHeight() - getStrokeWidth() - getStrokeBottom(),
                            getStrokeLeft() + getStrokeWidth() / 2 + x,
                            (getHeight() - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }
            }

            tp.setColor(getResources().getColor(R.color.text_color_5c5f66));
            tp.setAntiAlias(true);
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("09:00");
            date.add("10:30");
            date.add("13:30");
            date.add("21:00");
            date.add("23:30");
            float Y = 0.0f;
            if (spacing != 0.0f) {
                Y = upChartBottom + textSize;
            } else {
                Y = getHeight();
            }
            for (int i = 0; i < date.size(); i++) {
                if (i == date.size() - 1) {
                    canvas.drawText(date.get(i),
                            getWidth() - getStrokeWidth() - getStrokeRight()
                                    - 2 - tp.measureText(date.get(i)), Y, tp);
                } else if (i == 0) {
                    canvas.drawText(date.get(i), getStrokeLeft()
                            + getStrokeWidth() + 2, Y, tp);
                } else {
                    float x;
                    if (i == 1) {
                        x = no1;
                    } else if (i == 2) {
                        x = no2;
                    } else
                        x = no3;
                    canvas.drawText(date.get(i), getStrokeWidth() / 2
                            + getStrokeLeft() + x - tp.measureText(date.get(i))
                            / 2, Y, tp);
                }
            }
        } else if (longitudesTheme == LongitudesTheme.theme7) {
            // 9:00-10:15(1.25h),10:30-11:30(1h),13:30-15:00(1.5h),21:00-23:00(2h)
            float total = 1.25f + 1f + 1.5f + 2f;
            float no1 = width / total * 1.25f;
            float no2 = width / total * (1.25f + 1f);
            float no3 = width / total * (1.25f + 1f + 1.5f);
            for (int i = 0; i < longitudesNum; i++) {
                float x;
                if (i == 0) {
                    x = no1;
                } else if (i == 1)
                    x = no2;
                else
                    x = no3;
                canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                        getStrokeWidth() + getStrokeTop(), getStrokeLeft()
                                + getStrokeWidth() / 2 + x,
                        latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                            getHeight() - getStrokeWidth() - getStrokeBottom(),
                            getStrokeLeft() + getStrokeWidth() / 2 + x,
                            (getHeight() - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }
            }

            tp.setColor(getResources().getColor(R.color.text_color_5c5f66));
            tp.setAntiAlias(true);
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("09:00");
            date.add("10:30");
            date.add("13:30");
            date.add("21:00");
            date.add("23:00");
            float Y = 0.0f;
            if (spacing != 0.0f) {
                Y = upChartBottom + textSize;
            } else {
                Y = getHeight();
            }
            for (int i = 0; i < date.size(); i++) {
                if (i == date.size() - 1) {
                    canvas.drawText(date.get(i),
                            getWidth() - getStrokeWidth() - getStrokeRight()
                                    - 2 - tp.measureText(date.get(i)), Y, tp);
                } else if (i == 0) {
                    canvas.drawText(date.get(i), getStrokeLeft()
                            + getStrokeWidth() + 2, Y, tp);
                } else {
                    float x;
                    if (i == 1) {
                        x = no1;
                    } else if (i == 2) {
                        x = no2;
                    } else
                        x = no3;
                    canvas.drawText(date.get(i), getStrokeWidth() / 2
                            + getStrokeLeft() + x - tp.measureText(date.get(i))
                            / 2, Y, tp);
                }
            }
        } else if (longitudesTheme == LongitudesTheme.theme8) {
            // 9:00-10:15(1.25h),10:30-11:30(1h),13:30-15:00(1.5h),21:00-次日1:00(4h)
            float total = 1.25f + 1f + 1.5f + 4f;
            float no1 = width / total * 1.25f;
            float no2 = width / total * (1.25f + 1f);
            float no3 = width / total * (1.25f + 1f + 1.5f);
            for (int i = 0; i < longitudesNum; i++) {
                float x;
                if (i == 0) {
                    x = no1;
                } else if (i == 1)
                    x = no2;
                else
                    x = no3;
                canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                        getStrokeWidth() + getStrokeTop(), getStrokeLeft()
                                + getStrokeWidth() / 2 + x,
                        latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                            getHeight() - getStrokeWidth() - getStrokeBottom(),
                            getStrokeLeft() + getStrokeWidth() / 2 + x,
                            (getHeight() - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }
            }

            tp.setColor(getResources().getColor(R.color.text_color_5c5f66));
            tp.setAntiAlias(true);
            ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
            date.add("09:00");
            date.add("10:30");
            date.add("13:30");
            date.add("21:00");
            date.add("01:00");
            float Y = 0.0f;
            if (spacing != 0.0f) {
                Y = upChartBottom + textSize;
            } else {
                Y = getHeight();
            }
            for (int i = 0; i < date.size(); i++) {
                if (i == date.size() - 1) {
                    canvas.drawText(date.get(i),
                            getWidth() - getStrokeWidth() - getStrokeRight()
                                    - 2 - tp.measureText(date.get(i)), Y, tp);
                } else if (i == 0) {
                    canvas.drawText(date.get(i), getStrokeLeft()
                            + getStrokeWidth() + 2, Y, tp);
                } else {
                    float x;
                    if (i == 1) {
                        x = no1;
                    } else if (i == 2) {
                        x = no2;
                    } else
                        x = no3;
                    canvas.drawText(date.get(i), getStrokeWidth() / 2
                            + getStrokeLeft() + x - tp.measureText(date.get(i))
                            / 2, Y, tp);
                }
            }
        }
    }

    /**
     * 绘制经线
     *
     * @param canvas 画布
     * @param paint
     */
    private void drawLongitudes1(Canvas canvas, Paint paint) {
        PathEffect pe = new DashPathEffect(new float[] { 4, 4 }, 1);
        TextPaint tp = new TextPaint();
        tp.setTextSize(textSize / 2);
        tp.setTypeface(Typeface.DEFAULT_BOLD);
        paint.reset();
        paint.setColor(longitudesColor);
        paint.setStrokeWidth(latLongWidth);
        paint.setAntiAlias(true);
        paint.setPathEffect(pe);
        Path path = new Path();
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        float width = getWidth() - getStrokeWidth() * 2 - getStrokeLeft()
                - getStrokeRight() - 2.0f;
        float total = 0.0f;
        ArrayList<String> date = new ArrayList<String>();// 大小必须比经线数多2
        for (int i = 0; i < tradetimes.size() + 1; i++) {
            if (i != tradetimes.size())
                total += tradetimes.get(i).getTimeSlot();
            if (i == 0 && i != tradetimes.size())
                date.add(MyUtils.getInstance().date2String("HH:mm", tradetimes.get(i).getOpenTimeMillis()));
            else if (i == tradetimes.size() && i != 0)
                date.add(MyUtils.getInstance().date2String("HH:mm", tradetimes.get(
                        i - 1).getEndTimeMillis()));
            else if (i != 0) {
                date.add(
                        MyUtils.getInstance().date2String("HH:mm", tradetimes.get(
                                i - 1).getEndTimeMillis()) + "/" +
                                MyUtils.getInstance().date2String("HH:mm", tradetimes.get(i).getOpenTimeMillis()));
            }

        }

        float index = 0.0f;
        for (int i = 0; i < tradetimes.size(); i++) {
            float x = width / total * index;
            if (i != 0) {
                if(i == (tradetimes.size() ) / 2){
                    //    paint.setColor(MIDDLE_LONGITUDE_LATITUDE_COLOR);
                    paint.setColor(longitudesColor);
                }else {
                    paint.setColor(longitudesColor);
                }
                canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                        getStrokeWidth() + getStrokeTop(), getStrokeLeft()
                                + getStrokeWidth() / 2 + x,
                        latitudesSpacing
                                * latitudesUpNum + getStrokeWidth()
                                + getStrokeTop(), paint);
                if(isDrawDownChart) {
                    canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + x,
                            getHeight() - getStrokeWidth() - getStrokeBottom(),
                            getStrokeLeft() + getStrokeWidth() / 2 + x,
                            (getHeight() - getStrokeWidth() - getStrokeBottom())
                                    - latitudesSpacing * latitudesDownNum, paint);
                }

            }
            index += tradetimes.get(i).getTimeSlot();

        }
        canvas.drawPath(path, paint);

        tp.setColor(getResources().getColor(R.color.text_color_5c5f66));
        tp.setAntiAlias(true);
        float Y = 0.0f;
        if (spacing != 0.0f) {
            Y = upChartBottom + textSize;
        } else {
            Y = getHeight();
        }
        index = 0.0f;
        for (int i = 0; i < date.size(); i++) {
            float x = width / total * index;
            if (i == date.size() - 1) {
                canvas.drawText(date.get(i),
                        getWidth() - getStrokeWidth() - getStrokeRight()
                                - 2 - tp.measureText(date.get(i)), Y, tp);
            } else if (i == 0) {
                canvas.drawText(date.get(i), getStrokeLeft()
                        + getStrokeWidth() + 2, Y, tp);
            } else {
                canvas.drawText(date.get(i), getStrokeWidth() / 2
                        + getStrokeLeft() + x - tp.measureText(date.get(i))
                        / 2, Y, tp);
            }
            if (i != date.size() - 1)
                index += tradetimes.get(i).getTimeSlot();
        }
    }

    /**
     * 绘制上表中的折线
     *
     * @param canvas
     */
    private void drawUpLine(Canvas canvas, Paint paint) {
        if (data == null || data.size() == 0)
            return;
        paint.setStrokeWidth(1);
        paint.setAntiAlias(true);
        float startWhiteX = getStrokeWidth() + getStrokeLeft() + 1f;
        float startWhiteY = (float) (upChartBottom - (data.get(0).getClose() - upMinData)
                * upRatio);
        paint.setColor(getContext().getResources().getColor(R.color.timechart_line_yellow));
      /*  */
        int dataSize = data.size();
        if (dataSize == 1) {
            canvas.drawCircle(startWhiteX, startWhiteY, 3, paint);
        } else {
            Path path = new Path();
            Path path1 = new Path();
            for (int i = 1; i < data.size() && i < maxDate; i++) {
                float endWhiteY = (float) (upChartBottom - (data.get(i)
                        .getClose() - upMinData) *
                        upRatio);
                float endWhiteX = getStrokeWidth() + getStrokeLeft() + 1f
                        + dataSpacing * i;
//                canvas.drawLine(startWhiteX, startWhiteY, endWhiteX, endWhiteY,
//                        paint);
                if (i == 1) {
                    path.moveTo(startWhiteX, startWhiteY);
                    path1.moveTo(startWhiteX, startWhiteY);
                } else {
                    path.lineTo(startWhiteX, startWhiteY);
                    path1.lineTo(startWhiteX, startWhiteY);
                }
                if (i == data.size() - 1 || i == maxDate - 1) {
                    path.lineTo(endWhiteX, endWhiteY);
                    path1.lineTo(endWhiteX, endWhiteY);
                    path1.lineTo(endWhiteX, upChartBottom);
                    path1.lineTo(getStrokeWidth() + getStrokeLeft() + 1f,
                            upChartBottom);
                    path1.lineTo(
                            getStrokeWidth() + getStrokeLeft() + 1f,
                            (float) (upChartBottom - (data.get(0).getClose() - upMinData)
                                    * upRatio));
                }
                startWhiteX = endWhiteX;
                startWhiteY = endWhiteY;
            }
            path1.close();
            paint.setStyle(Style.STROKE);
            canvas.drawPath(path, paint);
            Paint LinerPaint = new Paint();
            LinearGradient lg=new LinearGradient(0,0,0,getHeight(),getContext().getResources().getColor(R.color.timechart_line_yellow_start),getContext().getResources().getColor(R.color.timechart_line_yellow_end), Shader.TileMode.MIRROR);
            LinerPaint.setShader(lg);
            LinerPaint.setStyle(Style.FILL);
            LinerPaint.setAlpha(80);
            canvas.drawPath(path1, LinerPaint);
        }
        paint.setColor(getContext().getResources()
                .getColor(R.color.timechart_yellow));
        startWhiteX = getStrokeWidth() + getStrokeLeft() + 1f;
        startWhiteY = (float) (upChartBottom - (data.get(0).getStockMeanLine() - upMinData)
                * upRatio);
        if (dataSize == 1) {
            canvas.drawCircle(startWhiteX, startWhiteY, 2, paint);
        } else {
            Path path = new Path();
            for (int i = 1; i < data.size() && i < maxDate; i++) {
                float endWhiteY = (float) (upChartBottom - (data.get(i)
                        .getStockMeanLine() -
                        upMinData) * upRatio);
                float endWhiteX = getStrokeWidth() + getStrokeLeft() + 1f
                        + dataSpacing * i;
   //             Log.w("TimeChartView", "startWhiteX=" + startWhiteX + "  startWhiteY=" + startWhiteY + "  endWhiteX=" + endWhiteX + "  endWhiteY=" + endWhiteY);
//                canvas.drawLine(startWhiteX, startWhiteY, endWhiteX, endWhiteY,
//                        paint);
                if (i == 1) {
                    path.moveTo(startWhiteX, startWhiteY);
                } else {
                    path.lineTo(startWhiteX, startWhiteY);
                }
                if (i == data.size() - 1 || i == maxDate - 1) {
                    path.lineTo(endWhiteX, endWhiteY);
                }
                startWhiteX = endWhiteX;
                startWhiteY = endWhiteY;
            }
            paint.setStyle(Style.STROKE);
            canvas.drawPath(path, paint);
        }
    }

    Path path = new Path();
    Path fillpath = new Path();

    /**
     * 绘制上表中的折线。 使曲线更加平滑
     *
     * @param canvas
     */
    private void drawCubicUpLine(Canvas canvas, Paint paint) {
        if (data == null)
            return;
        paint.setStrokeWidth(1);
        paint.setAntiAlias(true);
        float startWhiteX = getStrokeWidth() + getStrokeLeft() + 1f;
        float startWhiteY = (float) (upChartBottom - (data.get(0).getClose() - upMinData)
                * upRatio);
        paint.setColor(getContext().getResources().getColor(R.color.line_blue));
        int dataSize = data.size();
        // 画分时线
        if (dataSize == 1) {
            canvas.drawCircle(startWhiteX, startWhiteY, 3, paint);
        } else {
            path.reset();
            float prevDx = 0f;
            float prevDy = 0f;
            float curDx = 0f;
            float curDy = 0f;
            float preWhiteX = startWhiteX;
            float preWhiteY = startWhiteY;
            float endWhiteX = 0f;
            float endWhiteY = 0f;
            path.moveTo(startWhiteX, startWhiteY);
            for (int i = 1; i < data.size() && i < maxDate; i++) {

                endWhiteX = getStrokeWidth() + getStrokeLeft() + 1f
                        + dataSpacing * i;
                endWhiteY = (float) (upChartBottom - (data.get(i).getClose() - upMinData)
                        * upRatio);
                // paint.setColor(getContext().getResources().getColor(
                // R.color.line_blue));

                prevDx = (startWhiteX - preWhiteX) * 0.2f;
                prevDy = (startWhiteY - preWhiteY) * 0.2f;
                curDx = (endWhiteX - startWhiteX) * 0.2f;
                curDy = (endWhiteY - startWhiteY) * 0.2f;

                path.cubicTo(startWhiteX + prevDx, startWhiteY + prevDy,
                        endWhiteX - curDx, endWhiteY - curDy, endWhiteX,
                        endWhiteY);
                // if (i == 1)
                // path.moveTo(startWhiteX, startWhiteY);
                // path.lineTo(startWhiteX, startWhiteY);
                // if (i == data.size() - 1 || i == maxDate - 1) {
                // path.lineTo(endWhiteX, endWhiteY);
                // path.lineTo(endWhiteX, upChartBottom);
                // path.lineTo(getStrokeWidth() + getStrokeLeft() + 1f,
                // upChartBottom);
                // path.lineTo(getStrokeWidth() + getStrokeLeft() + 1f,
                // (float) (upChartBottom - (data.get(0).getClose() - upMinData)
                // * upRatio));
                // }

                preWhiteX = startWhiteX;
                preWhiteY = startWhiteY;
                startWhiteX = endWhiteX;
                startWhiteY = endWhiteY;
            }
            fillpath.reset();
            fillpath.addPath(path);
            drawFill(canvas, paint, fillpath, getStrokeWidth()
                    + getStrokeLeft() + 1f, endWhiteX, upChartBottom);
            // path.close();
            // paint.setStyle(Style.FILL);
            // paint.setAlpha(50);
            // canvas.drawPath(path, paint);
            paint.setStyle(Style.STROKE);
            canvas.drawPath(path, paint);
        }

        // 画分时均线
        paint.setColor(getContext().getResources()
                .getColor(R.color.line_yellow));
        startWhiteX = getStrokeWidth() + getStrokeLeft() + 1f;
        startWhiteY = (float) (upChartBottom - (data.get(0).getStockMeanLine() - upMinData)
                * upRatio);
        if (dataSize == 1) {
            canvas.drawCircle(startWhiteX, startWhiteY, 2, paint);
        } else {
            for (int i = 1; i < data.size() && i < maxDate; i++) {
                float endWhiteY = (float) (upChartBottom - (data.get(i)
                        .getStockMeanLine() -
                        upMinData) * upRatio);
                float endWhiteX = getStrokeWidth() + getStrokeLeft() + 1f
                        + dataSpacing * i;
                canvas.drawLine(startWhiteX, startWhiteY, endWhiteX, endWhiteY,
                        paint);
                startWhiteX = endWhiteX;
                startWhiteY = endWhiteY;
            }
        }
    }

    protected void drawFill(Canvas canvas, Paint paint, Path spline,
                            float from, float to, float bottom) {

        spline.lineTo(to, bottom);
        spline.lineTo(from, bottom);
        spline.close();

        paint.setStyle(Style.FILL);
        paint.setAlpha(50);
        canvas.drawPath(spline, paint);
        paint.setAlpha(255);
    }

    /**
     * 绘制上表Y轴文字
     *
     * @param canvas
     * @param paint
     */
    private void drawUpAxisXTitle(Canvas canvas, TextPaint paint,
                                  float dataWidth) {
        paint.setColor(getResources().getColor(R.color.text_color_5c5f66));
        paint.setAntiAlias(true);
        if (!isDrawOutside) {
            float YData = upMaxData;
            float zDF = 0;
            if (preClose != 0)
                zDF = ((YData - preClose) / preClose) * 100;
            float spacing = (upMaxData - upMinData) / (latitudesUpNum);
            for (int i = 0; i < latitudesUpNum + 1; i++) {
                String zdf = Parse.getInstance().parse2String(zDF) + "%";
                if ("-0.00%".equals(zdf))
                    zdf = "0.00%";
                if (i > 2)
                    paint.setColor(getContext().getResources().getColor(
                            R.color.green1));
                else if (i < 2)
                    paint.setColor(getContext().getResources().getColor(
                            R.color.red));
                else
                    paint.setColor(getContext().getResources().getColor(
                            R.color.text_color_5c5f66));
                if (i == 0) {
                    canvas.drawText(
                            Parse.getInstance().parse2String(upMaxData, num),
                            getStrokeWidth() + getStrokeLeft() + 2,
                            getStrokeWidth() + getStrokeTop() + textSize, paint);
                    // 绘制右边刻度
                    canvas.drawText(zdf,
                            getWidth() - getStrokeRight() - getStrokeWidth()
                                    / 2 - paint.measureText(zdf) - 2,
                            getStrokeWidth() + getStrokeTop() + textSize
                                    + latitudesSpacing * i, paint);
                } else if (i == latitudesUpNum) {
                    canvas.drawText(
                            Parse.getInstance().parse2String(upMinData, num),
                            getStrokeWidth() + getStrokeLeft() + 2,
                            (getStrokeWidth() + getStrokeTop() + latitudesSpacing
                                    * latitudesUpNum) - 2, paint);
                    // 绘制右边的
                    canvas.drawText(
                            zdf,
                            getWidth() - getStrokeRight() - getStrokeWidth()
                                    / 2 - paint.measureText(zdf) - 2,
                            (getStrokeWidth() + getStrokeTop() + latitudesSpacing
                                    * i) - 2, paint);
                } else {
                    canvas.drawText(Parse.getInstance()
                            .parse2String(YData, num), getStrokeWidth()
                            + getStrokeLeft() + 2, getStrokeWidth()
                            + getStrokeTop() +
                            textSize / 2 +
                            latitudesSpacing
                                    * i, paint);
                    // 绘制右边的
                    canvas.drawText(zdf,
                            getWidth() - getStrokeRight() - getStrokeWidth()
                                    / 2 - paint.measureText(zdf) - 2,
                            getStrokeWidth() + getStrokeTop() + textSize / 2
                                    + latitudesSpacing * i, paint);
                }
                YData -= spacing;
                if (preClose != 0)
                    zDF = ((YData - preClose) / preClose) * 100;
            }

        } else {
            float YData = upMaxData;
            float zDF = 0;
            if (preClose != 0)
                zDF = ((YData - preClose) / preClose) * 100;
            float spacing = (upMaxData - upMinData) / (latitudesUpNum);
            for (int i = 0; i < latitudesUpNum + 1; i++) {
                String zdf = Parse.getInstance().parse2String(zDF) + "%";
                if ("-0.00%".equals(zdf))
                    zdf = "0.00%";
                if (i > 2)
                    paint.setColor(getContext().getResources().getColor(
                            R.color.green1));
                else if (i < 2)
                    paint.setColor(getContext().getResources().getColor(
                            R.color.red));
                else
                    paint.setColor(getContext().getResources().getColor(
                            R.color.text_color_5c5f66));
                if (i == 0) {
                    canvas.drawText(
                            Parse.getInstance().parse2String(YData, num),
                            getStrokeLeft()
                                    - getStrokeWidth()
                                    / 2
                                    - 2
                                    - paint.measureText(Parse.getInstance()
                                    .parse2String(YData, num)),
                            getStrokeWidth() + getStrokeTop() + textSize
                                    + latitudesSpacing * i, paint);
                    // 绘制右边刻度
                    canvas.drawText(zdf, getWidth() - getStrokeRight() + 2,
                            getStrokeWidth() + getStrokeTop() + textSize
                                    + latitudesSpacing * i, paint);
                } else if (i == latitudesUpNum) {
                    canvas.drawText(
                            Parse.getInstance().parse2String(YData, num),
                            getStrokeLeft()
                                    - getStrokeWidth()
                                    / 2
                                    - 2
                                    - paint.measureText(Parse.getInstance()
                                    .parse2String(YData, num)),
                            (getStrokeWidth() + getStrokeTop() + latitudesSpacing
                                    * i) - 2, paint);
                    // 绘制右边的
                    canvas.drawText(
                            zdf,
                            getWidth() - getStrokeRight() + 2,
                            (getStrokeWidth() + getStrokeTop() + latitudesSpacing
                                    * i) - 2, paint);
                } else {
                    canvas.drawText(
                            Parse.getInstance().parse2String(YData, num),
                            getStrokeLeft()
                                    - getStrokeWidth()
                                    / 2
                                    - 2
                                    - paint.measureText(Parse.getInstance()
                                    .parse2String(YData, num)),
                            getStrokeWidth() + getStrokeTop() + textSize / 2
                                    + latitudesSpacing * i, paint);
                    // 绘制右边的
                    canvas.drawText(zdf, getWidth() - getStrokeRight() + 2,
                            getStrokeWidth() + getStrokeTop() + textSize / 2
                                    + latitudesSpacing * i, paint);
                }
                YData -= spacing;
                if (preClose != 0)
                    zDF = ((YData - preClose) / preClose) * 100;
            }
        }
    }

    /**
     * 绘制下表柱形图
     *
     * @param canvas 画布
     */
    private void drawDownColumnChart(Canvas canvas, Paint paint) {
        if (data == null)
            return;
        paint.setStrokeWidth(columnWidth);
        paint.setAntiAlias(true);
        // Map<String, Object> map = timesList.get(0);
        // float zuoShow = Float.parseFloat(map.get("zuoShow").toString());
        for (int i = 0; i < data.size() && i < maxDate; i++) {
            float chengJiaoLiang = (float) data.get(i).getCjl();
            paint.setColor(data.get(i).getColor());
            float x = getStrokeWidth() + getStrokeLeft() + 1.0f + dataSpacing
                    * i;
            canvas.drawLine(x, downChartBottom, x, downChartBottom
                    - chengJiaoLiang * downRatio, paint);
        }
    }

    /**
     * 绘制下表Y轴X坐标
     *
     * @param canvas
     * @param paint
     * @param dataWidth
     */
    private void drawDownAxisXTitle(Canvas canvas, TextPaint paint,
                                    float dataWidth) {
        paint.setColor(getResources().getColor(R.color.text_color_5c5f66));
        paint.setAntiAlias(true);
        if (!isDrawOutside) {
            float YData = downMinData;
            float spacing = (downMaxData - downMinData) / (latitudesDownNum);
            for (int i = 0; i < latitudesDownNum + 1; i++) {
                if (i == latitudesDownNum) {
                    canvas.drawText(
                            Parse.getInstance().parse2CNStringWan(YData, 2,
                                    false), getStrokeWidth() + getStrokeLeft()
                                    + 2, downChartBottom + textSize
                                    - latitudesSpacing * i, paint);
                } else if (i == 0) {
                    canvas.drawText("0"
                    /* Parse.getInstance().parse2CNString(YData) */,
                            getStrokeWidth() + getStrokeLeft() + 2,
                            downChartBottom - latitudesSpacing * i - 2, paint);
                } else {
                    canvas.drawText(
                            Parse.getInstance().parse2CNStringWan(YData, 2,
                                    false), getStrokeWidth() + getStrokeLeft()
                                    + 2, downChartBottom + textSize / 2
                                    - latitudesSpacing * i, paint);
                }
                YData += spacing;
            }

        } else {
            float YData = downMinData;
            float spacing = (downMaxData - downMinData) / (latitudesDownNum);
            for (int i = 0; i < latitudesDownNum + 1; i++) {
                if (i == latitudesDownNum) {
                    canvas.drawText(
                            Parse.getInstance().parse2CNStringWan(YData, 2,
                                    false),
                            getStrokeLeft()
                                    - getStrokeWidth()
                                    / 2
                                    - 2
                                    - paint.measureText(Parse.getInstance()
                                    .parse2CNStringWan(YData, 2, false)),
                            downChartBottom + textSize - latitudesSpacing * i,
                            paint);
                } else if (i == 0) {
                    canvas.drawText(
                            "0"
                            /* Parse.getInstance().parse2CNString(YData) */,
                            getStrokeLeft() - getStrokeWidth() / 2 - 2
                                    - paint.measureText("0"/*
                                                             * Parse.getInstance(
															 * )
															 * .parse2CNString(
															 * YData)
															 */),
                            downChartBottom - latitudesSpacing * i - 2, paint);
                } else {
                    canvas.drawText(
                            Parse.getInstance().parse2CNStringWan(YData, 2,
                                    false),
                            getStrokeLeft()
                                    - getStrokeWidth()
                                    / 2
                                    - 2
                                    - paint.measureText(Parse.getInstance()
                                    .parse2CNStringWan(YData, 2, false)),
                            downChartBottom + textSize / 2 - latitudesSpacing
                                    * i, paint);
                }
                YData += spacing;
            }
        }
    }

    /**
     * 绘制时间
     *
     * @param canvas
     * @param paint
     */
    private void drawTime(Canvas canvas, TextPaint paint, ArrayList<String> date) {
        paint.setColor(getResources().getColor(R.color.text_color_5c5f66));
        paint.setAntiAlias(true);
        float Y = 0.0f;
        if (spacing != 0.0f) {
            Y = upChartBottom + textSize;
        } else {
            Y = getHeight();
        }
        for (int i = 0; i < date.size(); i++) {
            if (i == date.size() - 1) {
                canvas.drawText(date.get(i),
                        getWidth() - getStrokeWidth() - getStrokeRight() - 2
                                - paint.measureText(date.get(i)), Y, paint);
            } else if (i == 0) {
                canvas.drawText(date.get(i), getStrokeLeft() + getStrokeWidth()
                        + 2, Y, paint);
            } else {
                // if ("18:00".equals(date.get(i))) {
                // canvas.drawText(
                // date.get(i),
                // getStrokeWidth() + getStrokeLeft()
                // + longitudesSpacing
                // * ((longitudesNum + 2 - 1) / 2)
                // - paint.measureText(date.get(i)) / 2, Y,
                // paint);
                // } else {
                canvas.drawText(date.get(i),
                        getStrokeWidth() + getStrokeLeft() + longitudesSpacing
                                * i -
                                paint.measureText(date.get(i)) / 2, Y,
                        paint);
                // }
            }
        }
    }

    /**
     * 绘制十字光标
     *
     * @param canvas 画布
     */
    private void drawCrossLine(Canvas canvas, Paint paint) {
        if (!isShowCross)
            return;
        paint.setStrokeWidth(1);
        paint.setColor(getContext().getResources().getColor(
                R.color.cross_line_color));
        paint.setAntiAlias(true);
        int rSpacing = 12;
        // 绘制十字光标横线
        canvas.drawLine(getStrokeLeft() + getStrokeWidth(), crossY, crossX
                - rSpacing >= getStrokeLeft() +
                getStrokeWidth() ?
                crossX
                        - rSpacing : getStrokeLeft() +
                getStrokeWidth(), crossY, paint);
        // 绘制十字光标横线
        canvas.drawLine(crossX + rSpacing <= getWidth() - getStrokeWidth()
                        - getStrokeRight() ? crossX + rSpacing : getWidth()
                        -
                        getStrokeWidth() -
                        getStrokeRight(), crossY,
                getWidth()
                        - getStrokeWidth() - getStrokeRight(), crossY, paint);
        // 绘制上表十字光标竖线
        canvas.drawLine(crossX, getStrokeWidth() + getStrokeTop(), crossX,
                crossY - rSpacing >= getStrokeWidth() + getStrokeTop() ? crossY
                        - rSpacing
                        : getStrokeWidth() +
                        getStrokeTop(), paint);
        // 绘制上表十字光标竖线
        canvas.drawLine(crossX, crossY + rSpacing <= upChartBottom ? crossY
                + rSpacing
                : upChartBottom, crossX, upChartBottom, paint);
        if(isDrawDownChart) {
            // 绘制下表十字光标竖线
            canvas.drawLine(crossX, getHeight() - getStrokeWidth()
                            - getStrokeBottom(), crossX, downChartBottom - downChartHeight,

                    paint);
        }
        // 绘制十字光标交叉小圆点
        Paint p = new Paint();
        p.setColor(getContext().getResources().getColor(R.color.cross_line_color));
        p.setAntiAlias(true);
        p.setAlpha(150);
        canvas.drawCircle(crossX, crossY, 12, p);
        p.setAlpha(255);
        canvas.drawCircle(crossX, crossY, 6, p);

        // 绘制十字光标中心数据
        TextPaint tp = new TextPaint();
        tp.setColor(Color.WHITE);
        tp.setTextSize(textSize);
        tp.setAntiAlias(true);
        tp.setTypeface(Typeface.DEFAULT_BOLD);

        paint.setColor(getResources().getColor(R.color.cross_line_color));
        paint.setStrokeWidth(textSize + 6);
        String leftDatas = Parse.getInstance().parse2String(leftData, num);
        String rightDatas = Parse.getInstance().parse2String(rightData) + "%";
        if (!isDrawOutside) {
            float topS = crossY - (textSize + 6) / 2 <= getStrokeTop()
                    + getStrokeWidth() ? getStrokeTop() +
                    getStrokeWidth()
                    + (textSize + 6) / 2
                    : crossY;
            topS = topS + (textSize + 6) / 2 >= upChartBottom ? upChartBottom
                    - (textSize + 6) / 2 : topS;
            float size = MyUtils.getInstance().dp2px(getContext(), 10);
            float dataWidth = tp.measureText(leftDatas);
            canvas.drawLine(getStrokeLeft() + getStrokeWidth() / 2 + dataWidth
                            + size, topS, getStrokeLeft() + getStrokeWidth() / 2, topS,
                    paint);
            canvas.drawText(leftDatas, getStrokeLeft() + getStrokeWidth() / 2
                    + size / 2, topS + textSize / 2 - 3, tp);

            if (rightData > 0.0f) {
                rightDatas = "+" + rightDatas;
            }
            dataWidth = tp.measureText(rightDatas);
            canvas.drawLine(getWidth() - getStrokeRight() - getStrokeWidth()
                            / 2 - dataWidth - size, topS,
                    getWidth() - getStrokeWidth()
                            / 2 - getStrokeRight(), topS, paint);
            canvas.drawText(rightDatas, getWidth() - getStrokeRight()
                    - getStrokeWidth() / 2 - dataWidth - size / 2, topS
                    + textSize /
                    2 - 3, tp);

            String bottomDatas = Parse.getInstance().parse2CNStringWan(
                    bottomData, 2, false);
            dataWidth = tp.measureText(bottomDatas);
            float Y = downChartBottom - textSize / 2 - 3;
            float drawX = crossX - (dataWidth + size) / 2;
            if (drawX <= getStrokeLeft() + getStrokeWidth() / 2) {
                drawX = getStrokeLeft() + getStrokeWidth() / 2;
            } else if (drawX >= getWidth() - getStrokeRight()
                    - getStrokeWidth() / 2 - dataWidth - size) {
                drawX = getWidth() - getStrokeRight() - getStrokeWidth() / 2
                        - dataWidth - size;
            }
            canvas.drawLine(drawX, Y, drawX + dataWidth + size, Y, paint);
            canvas.drawText(bottomDatas, drawX + size / 2, Y + textSize / 2, tp);

            /**
             * 绘制时间
             */
            String timeDatas = MyUtils.getInstance().date2String("HH:mm", time);
            dataWidth = tp.measureText(timeDatas);
            float timeY = upChartBottom + textSize / 2;
            float drawTimeX = crossX - (dataWidth + size) / 2;
            if (drawTimeX <= getStrokeLeft() + getStrokeWidth() / 2) {
                drawTimeX = getStrokeLeft() + getStrokeWidth() / 2;
            } else if (drawTimeX >= getWidth() - getStrokeRight()
                    - getStrokeWidth() / 2 - dataWidth - size) {
                drawTimeX = getWidth() - getStrokeRight() - getStrokeWidth() / 2
                        - dataWidth - size;
            }
            canvas.drawLine(drawTimeX, timeY, drawTimeX + dataWidth + size, timeY, paint);
            canvas.drawText(timeDatas, drawTimeX + size / 2, timeY + textSize / 2, tp);
        } else {
            float topS = crossY - (textSize + 6) / 2 <= getStrokeTop()
                    + getStrokeWidth() ? getStrokeTop() +
                    getStrokeWidth()
                    + (textSize + 6) / 2
                    : crossY;
            topS = topS + (textSize + 6) / 2 >= upChartBottom ? upChartBottom
                    - (textSize + 6) / 2 : topS;
            float size = MyUtils.getInstance().dp2px(getContext(), 1);
            float dataWidth = tp.measureText(leftDatas);
            canvas.drawLine(getStrokeLeft() - getStrokeWidth() / 2 - dataWidth
                            - size, topS, getStrokeLeft() - getStrokeWidth() / 2, topS,
                    paint);
            canvas.drawText(leftDatas, getStrokeLeft() - getStrokeWidth() / 2
                    - dataWidth - size / 2, topS + textSize / 2 - 3, tp);

            if (rightData > 0.0f) {
                rightDatas = "+" + rightDatas;
            }
            dataWidth = tp.measureText(rightDatas);
            canvas.drawLine(getWidth() - getStrokeRight(), topS, getWidth()
                    - getStrokeRight() + dataWidth +
                    size, topS, paint);
            canvas.drawText(rightDatas, getWidth() - getStrokeRight() + size
                            / 2,
                    topS + textSize / 2 - 3, tp);


            String bottomDatas = Parse.getInstance().parse2CNStringWan(
                    bottomData, 2, false);
            dataWidth = tp.measureText(bottomDatas);
            float Y = downChartBottom - textSize / 2 - 3;
            float drawX = crossX - (dataWidth + size) / 2;
            if (drawX <= getStrokeLeft() + getStrokeWidth() / 2) {
                drawX = getStrokeLeft() + getStrokeWidth() / 2;
            } else if (drawX >= getWidth() - getStrokeRight()
                    - getStrokeWidth() / 2 - dataWidth - size) {
                drawX = getWidth() - getStrokeRight() - getStrokeWidth() / 2
                        - dataWidth - size;
            }
            canvas.drawLine(drawX, Y, drawX + dataWidth + size, Y, paint);
            canvas.drawText(bottomDatas, drawX + size / 2, Y + textSize / 2, tp);

            /**
             * 绘制时间
             */
            String timeDatas = MyUtils.getInstance().date2String("HH:mm", time);
            dataWidth = tp.measureText(timeDatas);
            float timeY = upChartBottom + textSize / 2;
            float drawTimeX = crossX - (dataWidth + size) / 2;
            if (drawTimeX <= getStrokeLeft() + getStrokeWidth() / 2) {
                drawTimeX = getStrokeLeft() + getStrokeWidth() / 2;
            } else if (drawTimeX >= getWidth() - getStrokeRight()
                    - getStrokeWidth() / 2 - dataWidth - size) {
                drawTimeX = getWidth() - getStrokeRight() - getStrokeWidth() / 2
                        - dataWidth - size;
            }
            canvas.drawLine(drawTimeX, timeY, drawTimeX + dataWidth + size, timeY, paint);
            canvas.drawText(timeDatas, drawTimeX + size / 2, timeY + textSize / 2, tp);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        // TODO Auto-generated method stub
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 按下
                touchDownX = event.getX();
                touchDownY = event.getY();
                if (touchDownX < getStrokeLeft()
                        || touchDownX > getWidth() - getStrokeRight()) {
                    return super.onTouchEvent(event);
                }
                movedX = touchDownX;
                movedY = touchDownY;
                moveRawX = event.getRawX();
                moveRawY = event.getRawY();
                isMoved = false;
                isReleased = false;
                isStartMoved = false;
                if (mRunnable != null) {
                    removeCallbacks(mRunnable);
                    postDelayed(mRunnable, 200);
                }
                getParent().requestDisallowInterceptTouchEvent(true);
                return true;

            case MotionEvent.ACTION_POINTER_DOWN:
                // 多点
                if (touchDownX < getStrokeLeft()
                        || touchDownX > getWidth() - getStrokeRight()) {
                    return super.onTouchEvent(event);
                }
                return true;

            case MotionEvent.ACTION_MOVE:
                // 触摸中
                if (touchDownX < getStrokeLeft()
                        || touchDownX > getWidth() - getStrokeRight()) {
                    return super.onTouchEvent(event);
                }
                // 子控件相对于父布局若达到滚动条件,则让父布局拦截触摸事件


             /*   if (Math.abs(event.getRawY() - moveRawY) > 50 && Math.abs(event.getRawX() - moveRawX) < 150) {
                    isMoved = true;
                    getParent().requestDisallowInterceptTouchEvent(false);
                }*/
                isMoved = false;
                if (isStartMoved && !isMoved) {
                    movedX = event.getX();
                    movedY = event.getY();
                    viewCrossLine();
                }
                break;

            case MotionEvent.ACTION_UP:
                // 拿起
                if (touchDownX < getStrokeLeft()
                        || touchDownX > getWidth() - getStrokeRight()) {
                    return super.onTouchEvent(event);
                }
                if (!isStartMoved && !isMoved) {
                    if (Math.abs(Math.abs(event.getX()) - Math.abs(touchDownX)) < 30
                            && Math.abs(Math.abs(event.getY())
                            - Math.abs(touchDownY)) < 30) {
                        if (mOnTimeChartClickListener != null)
                            mOnTimeChartClickListener.click(this);
                    }
                }
                isReleased = true;
                isShowCross = false;
                isStartMoved = false;
                if (l != null)
                    l.isMove(this, isShowCross, crossX);
                invalidate();
                return true;

            default:
                break;
        }
        return super.onTouchEvent(event);
    }

    /**
     * 设置数据
     *
     * @param data        分时数据
     * @param upMaxData   最大价
     * @param upMinData   最小价
     * @param downMinData 成交量数据
     * @param downMinData 最小成交量
     * @param preClose    昨收价
     * @param isHighLow   是否将最高最低价作为基准,否则取收盘价中的最高最低
     */
    public void setTimesList(Vector<TimeData> data, List<TRADETIME> tradetimes, float upMaxData,
                             float upMinData, float downMinData, float preClose,
                             boolean isHighLow) {
        // handler.removeCallbacks(mTimeListThread);
        // mTimeListThread.setData(data, upMaxData, upMinData, downMinData,
        // preClose, isHighLow);
        // handler.post(mTimeListThread);
        /*
         * if (!isAlive(mTimeListThread)) { mTimeListThread = new
		 * TimeListThread(data, upMaxData, upMinData, downMinData, preClose,
		 * isHighLow); mTimeListThread.start(); }
		 */
        executorService.execute(new TimeListThread(data, tradetimes, upMaxData, upMinData,
                downMinData, preClose, isHighLow));
    }

    /**
     * 计算
     *
     * @author dingrui
     */
    boolean running = false;

    class TimeListThread implements Runnable {

        private Vector<TimeData> dataOld;
        private List<TRADETIME> tradetimesOld;// 时间段
        private float upMaxData;
        private float upMinData;
        private float downMinData;
        private float downMaxData;
        private float preClose;
        private boolean isHighLow;// 是否直接取最高最低

        public TimeListThread(Vector<TimeData> data, List<TRADETIME> tradetimes, float upMaxData,
                              float upMinData, float downMinData, float preClose,
                              boolean isHighLow) {
            this.dataOld = data;
            this.tradetimesOld = tradetimes;
            this.upMaxData = upMaxData;
            this.upMinData = upMinData;
            this.downMinData = downMinData;
            this.preClose = preClose;
            this.isHighLow = isHighLow;
        }

        @Override
        public void run() {
            if (running)
                return;
            synchronized (this) {
            if (tradetimesOld == null || tradetimesOld.size() <= 0) {
                return;
            }
                running = true;
                List<Long> timeMillis = new ArrayList<>();
                float timeS = 0.0f;
                List<TRADETIME> tradetimes = new ArrayList<>();
                for (int i = 0; i < tradetimesOld.size(); i++) {
                    TRADETIME oldItem = tradetimesOld.get(i);
                    TRADETIME item = new TRADETIME();
                    item.setOpenTime(oldItem.getOpenTime());
                    item.setEndTime(oldItem.getEndTime());
                    tradetimes.add(item);
                }
                for (int i = 0; i < tradetimes.size(); i++) {
                    String openTime = Parse.getInstance().isNull(tradetimes.get(i).getOpenTime());
                    String endTime = Parse.getInstance().isNull(tradetimes.get(i).getEndTime());
                    int openH, openM;
                    int endH, endM;
                    if (openTime.length() == 1) {
                        openH = 0;
                        openM = 0;
                    } else if (openTime.length() == 3) {
                        openH = Parse.getInstance().parseInt(openTime.substring(0, 1));
                        openM = Parse.getInstance().parseInt(openTime.substring(1, openTime.length()));
                    } else {
                        openH = Parse.getInstance().parseInt(openTime.substring(0, 2));
                        openM = Parse.getInstance().parseInt(openTime.substring(2, openTime.length()));
                    }
                    if (endTime.length() == 1) {
                        endH = 0;
                        endM = 0;
                    } else if (endTime.length() == 3) {
                        endH = Parse.getInstance().parseInt(endTime.substring(0, 1));
                        endM = Parse.getInstance().parseInt(endTime.substring(1, endTime.length()));
                    } else {
                        endH = Parse.getInstance().parseInt(endTime.substring(0, 2));
                        endM = Parse.getInstance().parseInt(endTime.substring(2, endTime.length()));
                    }

                    float min;
                    int hour;
                    int openH1 = openH, openM1 = openM;
                    int endH1 = endH, endM1 = endM;
                    if (endM1 < openM1) {
                        min = (endM1 + 60 - openM1) / 60.0f;
                        endH1 = endH1 - 1;
                    } else {
                        min = (endM1 - openM1) / 60.0f;
                    }
                    if (endH1 <= openH1) {
                        hour = endH1 + 24 - openH1;
                        tradetimes.get(i).setOpenTimes(String.format("2016-01-01 %02d:%02d:00", openH, openM));
                        tradetimes.get(i).setEndTimes(String.format("2016-01-02 %02d:%02d:00", endH, endM));
                    } else {
                        hour = endH1 - openH1;
                        tradetimes.get(i).setOpenTimes(String.format("2016-01-02 %02d:%02d:00", openH, openM));
                        tradetimes.get(i).setEndTimes(String.format("2016-01-02 %02d:%02d:00", endH, endM));
                    }
                    String minS = String.format("%.2f", hour + min);
                    tradetimes.get(i).setTimeSlot(Parse.getInstance().parseFloat(minS));
                    timeS += tradetimes.get(i).getTimeSlot();
                    for (long j = tradetimes.get(i).getOpenTimeMillis();
                         j <= tradetimes.get(i).getEndTimeMillis(); j += 60000) {// 60000毫秒=1分钟
                        if (i == 0) {
                            timeMillis.add(j);
                        } else {
                            if (j != tradetimes.get(i).getOpenTimeMillis()) {
                                timeMillis.add(j);
                            }
                        }
                    }
                }
                maxDate = (int) (timeS * 60) + 1;
                Vector<TimeData> data = new Vector<>();
                try {
                    if (dataOld != null && dataOld.size() > 0 && timeMillis.size() > 0) {
                        for (int i = 0; i < dataOld.size() && i < timeMillis.size(); i++) {
                            TimeData oldItem = dataOld.get(i);
                            double close;
                            if (i > 0) {
                                close = data.get(i - 1).getClose();
                            } else {
                                close = preClose;
                            }
                            TimeData item = new TimeData();
                            item.setTime(timeMillis.get(i));
                            item.setClose(oldItem.getClose());
                            item.setCjlYS(oldItem.getCjlYS());
                            item.setCjeYS(oldItem.getCjeYS());
                            item.setCjl(oldItem.getCjl());
                            item.setCje(oldItem.getCje());
                            item.setStockMeanLine(oldItem.getStockMeanLine());
                            double zde = oldItem.getClose() - preClose;
                            double zdf = zde / preClose * 100;
                            item.setZde(zde);
                            item.setZdf(zdf);
                            if (item.getClose() > close) {
                                item.setColor(getResources().getColor(R.color.red));
                            } else if (item.getClose() < close) {
                                item.setColor(getResources().getColor(R.color.green));
                            } else {
                                item.setColor(Color.WHITE);
                            }
                            data.add(item);
                        }
                        if (!isHighLow) {
                            upMaxData = (float) data.get(0).getClose();
                            upMinData = upMaxData;
                        }
                        downMaxData = (float) data.get(0).getCjl();
                        for (int i = 1; i < data.size() && i < maxDate; i++) {
                            if (!isHighLow) {
                                upMaxData = (float) (data.get(i).getClose() < upMaxData ? upMaxData
                                        : data.get(i).getClose());
                                upMinData = (float) (data.get(i).getClose() > upMinData ? upMinData
                                        : data.get(i).getClose());

                                upMaxData = (float) (data.get(i).getStockMeanLine() < upMaxData ? upMaxData
                                        : data.get(i).getStockMeanLine());
                                upMinData = (float) (data.get(i).getStockMeanLine() > upMinData ? upMinData
                                        : data.get(i).getStockMeanLine());
                            }
                            // 下表相关
                            if (data.size() == 0) {
                                return;
                            }
                            downMaxData = (float) (data.get(i).getCjl() < downMaxData ? downMaxData
                                    : data.get(i).getCjl());
                        }
                    } else {
                        downMaxData = 0;
                    }
                } catch (ArrayIndexOutOfBoundsException ex) {
                    ex.printStackTrace();
                }

                Bundle bundle = new Bundle();
                bundle.putSerializable("data",
                        data);
                bundle.putSerializable("tradetimes", (Serializable) tradetimes);
                bundle.putFloat("upMaxData", upMaxData);
                bundle.putFloat("upMinData", upMinData);
                bundle.putFloat("downMinData", downMinData);
                bundle.putFloat("downMaxData", downMaxData);
                bundle.putFloat("preClose", preClose);


//            TimeChartView.this.data = data;
//            TimeChartView.this.upMaxData = upMaxData;
//            TimeChartView.this.upMinData = upMinData;
//            TimeChartView.this.downMinData = downMinData;
//            TimeChartView.this.downMaxData = downMaxData;
//            TimeChartView.this.preClose = preClose;
//            if (!isShowCross)
//                postInvalidate();
                Message msg = handler.obtainMessage();
                msg.what = 0x2;
                msg.setData(bundle);
                handler.sendMessage(msg);
                running = false;
            }
        }
    }

    /**
     * 设置上表与下表中间的日期与时间的文字大小
     *
     * @param textSize 像素单位
     * @return
     */
    public GridChartView settextSize(float textSize) {
        this.textSize = textSize;
        return this;
    }

    /**
     * 获取上表与下表中间的日期与时间的文字大小
     *
     * @return 像素单位
     */
    public float gettextSize() {
        return textSize;
    }

    /**
     * 设置上表纬线条数
     *
     * @param latitudesUpNum
     * @return
     */
    public GridChartView setLatitudesUpNum(int latitudesUpNum) {
        if (latitudesUpNum < 3) {
            latitudesUpNum = DEFAULT_LATITUDE_UP_NUM;
        }
        this.latitudesUpNum = latitudesUpNum;
        return this;
    }

    /**
     * 设置经纬线颜色
     *
     * @param longitudesColor
     * @return
     */
    public GridChartView setLongitudesColor(int longitudesColor) {
        this.longitudesColor = longitudesColor;
        return this;
    }

    /**
     * 设置经、纬线宽度
     *
     * @param latLongWidth
     * @return
     */
    public GridChartView setLatLongWidth(float latLongWidth) {
        if (latLongWidth < DEFAULT_LATITUDE_LONGITUDE_WIDTH) {
            latLongWidth = DEFAULT_LATITUDE_LONGITUDE_WIDTH;
        }
        this.latLongWidth = latLongWidth;
        return this;
    }

    /**
     * 返回是否绘制经线
     *
     * @return
     */
    public boolean isDrawLongitudes() {
        return isDrawLongitudes;
    }

    /**
     * 获取上表高度
     *
     * @return
     */
    public float getUpChartHeight() {
        return Math.abs(upChartHeight);
    }

    /**
     * 设置是否绘制经线
     *
     * @param isDrawLongitudes true绘制,false不绘制
     * @return
     */
    public GridChartView setDrawLongitudes(boolean isDrawLongitudes) {
        this.isDrawLongitudes = isDrawLongitudes;
        return this;
    }

    /**
     * 返回是否绘制纬线
     *
     * @return
     */
    public boolean isDrawLatitudes() {
        return isDrawLatitudes;
    }

    /**
     * 设置是否显示纬线
     *
     * @param isDrawLatitudes true绘制,false不绘制
     * @return
     */
    public GridChartView setDrawLatitudes(boolean isDrawLatitudes) {
        this.isDrawLatitudes = isDrawLatitudes;
        return this;
    }

    /**
     * 设置上表与下表间距(如果设置值小于默认字体大小,则设置成默认字体大小大5像素;如果设置值大于默认字体大小+5大小,那么字体也随之变化;
     * 如果设置值等于0.0f,那么时间文字将绘制则底部,并且上表与下表间距为0,底部高度为默认字体高度)
     *
     * @param spacing
     */
    public void setSpacing(float spacing) {
        if (spacing < DEFAULT_textSize && spacing > 0) {
            spacing = DEFAULT_textSize * 2;
        } else if (spacing > DEFAULT_textSize * 2) {
            this.spacing = spacing;
            textSize = spacing / 2;
        } else {
            this.spacing = spacing;
        }
    }

    /**
     * 设置经线类型
     *
     * @param longitudesTheme
     */
  /*  public void setLongitudesTheme(int longitudesTheme) {
        if (longitudesTheme < DEFAULT_THEME
                && longitudesTheme > LongitudesTheme.theme8) {
            longitudesTheme = DEFAULT_THEME;
        }
        if (longitudesTheme == LongitudesTheme.theme0) {
            longitudesNum = DEFAULT_LONGITUDE_NUM;
            maxDate = MAX_DATE;
        } else if (longitudesTheme == LongitudesTheme.theme1) {
            longitudesNum = DEFAULT_LONGITUDE_NUM;
            maxDate = 60 * 24;
        } else if (longitudesTheme == LongitudesTheme.theme2) {
            longitudesNum = 2;
            maxDate = 11 * 60;
        } else if (longitudesTheme == LongitudesTheme.theme3) {
            longitudesNum = 2;
            maxDate = 18 * 60;
        } else if (longitudesTheme == LongitudesTheme.theme4) {
            longitudesNum = DEFAULT_LONGITUDE_NUM;
            maxDate = (int) (9.5 * 60);
        } else if (longitudesTheme == LongitudesTheme.theme5) {
            longitudesNum = 2;
            maxDate = (int) (3.75 * 60);
        } else if (longitudesTheme == LongitudesTheme.theme6) {
            longitudesNum = DEFAULT_LONGITUDE_NUM;
            maxDate = (int) (6.25 * 60);
        } else if (longitudesTheme == LongitudesTheme.theme7) {
            longitudesNum = DEFAULT_LONGITUDE_NUM;
            maxDate = (int) (5.75 * 60);
        } else if (longitudesTheme == LongitudesTheme.theme8) {
            longitudesNum = DEFAULT_LONGITUDE_NUM;
            maxDate = (int) (7.75 * 60);
        }
        this.longitudesTheme = longitudesTheme;
    } */

    /**
     * 设置是否将两边坐标值绘制在边框线外(注:如果设置成true,必须设置strokeLeft和strokeRight)
     *
     * @param isDrawOutside
     * @param strokeLeft    左边距
     * @param strokeRight   右边距
     */
    public void setDrawOutside(boolean isDrawOutside, float strokeLeft,
                               float strokeRight) {
        this.isDrawOutside = isDrawOutside;
        if (isDrawOutside == false) {
            setStrokeLeft(1.0f);
            setStrokeRight(1.0f);
        } else {
            setStrokeLeft(strokeLeft);
            setStrokeRight(strokeRight);
        }
        invalidate();
    }

    /**
     * 设置小数保留位数
     *
     * @param num
     */
    public void setNum(int num) {
        this.num = num;
        invalidate();
    }

    /**
     * 设置接口
     */
    public void setOnTimeListener(OnTimeListener l) {
        this.l = l;
    }

    /**
     * 通知外部接口
     *
     * @author 锐
     */
    public interface OnTimeListener extends OnTimeDataChangeListener {

        /**
         * 是否显示指标详情
         */
        void isMove(View view, boolean isMove, float TouchX);
    }

    public interface OnTimeDataChangeListener {
        /**
         * 传递数据
         *
         * @param view
         * @param data
         * @param position
         * @param preClose 昨收价
         */
        void listener(View view, Vector<TimeData> data, int position, double preClose, int num);
    }


    /**
     * 计算十字光标位置
     */
    private void viewCrossLine() {
        // TODO Auto-generated method stub
        if (data != null && data.size() > 0) {
            getParent().requestDisallowInterceptTouchEvent(true);

            movedX = movedX - (getStrokeWidth() + getStrokeLeft() + 1f);
            if (movedX < 0f) {
                movedX = 0f;
            } else if (movedX >
                    getWidth() - (getStrokeWidth() + getStrokeLeft() + getStrokeRight() + 2f)) {
                movedX = getWidth() - (getStrokeWidth() + getStrokeLeft() + getStrokeRight() + 2f);
            }
            int index = (int) (movedX / dataSpacing);

            if (data == null || data.size() == 0) {
                return;
            } else if (index > data.size() - 1) {
                index = data.size() - 1;
            }
            crossX = getStrokeWidth() + getStrokeLeft() + 1f + dataSpacing
                    * index;
            crossY = (float) (upChartBottom - (data.get(index).getClose() - upMinData)
                    * upRatio);
            leftData = (float) data.get(index).getClose();
            rightData = (float) data.get(index).getZdf();
            bottomData = (float) data.get(index).getCjl();
            time = data.get(index).getTime();
            isShowCross = true;
            invalidate();
            if (l != null) {
                l.listener(this, data, index, preClose, num);
                l.isMove(this, isShowCross, crossX);
            }

//            for (int i = 0; i < data.size() - 1 && i < maxDate - 1; i++) {
//                float Y = (float) (upChartBottom - (data.get(i).getClose() - upMinData)
//                                                   * upRatio);
//                float endY = (float) (upChartBottom - (data.get(i + 1)
//                                                               .getClose() - upMinData) * upRatio);
//                float X = getStrokeWidth() + getStrokeLeft() + 1f + dataSpacing
//                                                                    * i;
//                float endX = getStrokeWidth() + getStrokeLeft() + 1f
//                             + dataSpacing * (i + 1);
//                float spacing = endX - X;
//                float positionX = movedX - X;
//                if (movedX >= X && movedX < endX) {
//                    isShowCross = true;
//                    if (positionX <= spacing / 2) {
//                        crossX = X;
//                        crossY = Y;
//                        leftData = (float) data.get(i).getClose();
//                        rightData = (float) data.get(i).getZdf();
//                        bottomData = (float) data.get(i).getCjl();
//                        time = data.get(i).getTime();
//                        if (l != null) {
//                            l.listener(this, data, i, preClose);
//                            l.isMove(this, isShowCross, crossX);
//                        }
//                    } else {
//                        if (data.size() == 0) {
//                            return;
//                        }
//                        crossX = endX;
//                        crossY = endY;
//                        leftData = (float) data.get(i + 1).getClose();
//                        rightData = (float) data.get(i + 1).getZdf();
//                        bottomData = (float) data.get(i + 1).getCjl();
//                        time = data.get(i + 1).getTime();
//                        if (l != null) {
//                            l.listener(this, data, i + 1, preClose);
//                            l.isMove(this, isShowCross, crossX);
//                        }
//                    }
//                    invalidate();
//                    return;
//                } else if (movedX >= endX
//                           && (i + 1 == data.size() - 1 || i + 1 == maxDate - 1)) {
//                    isShowCross = true;
//                    crossX = endX;
//                    crossY = endY;
//                    leftData = (float) data.get(i + 1).getClose();
//                    rightData = (float) data.get(i + 1).getZdf();
//                    bottomData = (float) data.get(i + 1).getCjl();
//                    time = data.get(i + 1).getTime();
//                    if (l != null) {
//                        l.listener(this, data, i + 1, preClose);
//                        l.isMove(this, isShowCross, crossX);
//                    }
//                    invalidate();
//                    return;
//                }
//            }
        }
    }

    public void setOnTimeChartClickListener(OnTimeChartClickListener l) {
        this.mOnTimeChartClickListener = l;
    }

    /**
     * 单击事件
     *
     * @author dingrui
     */
    public interface OnTimeChartClickListener {
        public void click(View v);
    }

    /**
     * 判断线程是否活动
     *
     * @param thread
     * @return
     */
    private boolean isAlive(Thread thread) {
        if (thread == null)
            return false;
        else {
            if (thread.isAlive())
                return true;
            else
                return false;
        }
    }

    public boolean isDrawDownChart() {
        return isDrawDownChart;
    }

    public void setDrawDownChart(boolean drawDownChart) {
        isDrawDownChart = drawDownChart;
    }

}
作者 east
Android 12月 8,2020

Android绘制股票k线图系列1-绘制网格线背景

public class GridChartView extends View {

	/** 当前控件高度 */
	private float viewHight;
	/** 当前控件宽度 */
	private float viewWidth;
	/** 边框宽度 */
	private float strokeWidth;
	/** 默认边框宽度 */
	private final float DEFAULT_STROKEWIDTH = 0.8f;
	/** 边框颜色 */
	private int strokeColor;
	/** 默认边框颜色 */
	private final int DEFAULT_STROKECOLOR = 0xffeeeeee;

	/** 是否显示边框(左边框、右边框、顶部边框、底部边框) */
	private boolean isLeftVisible;// 左
	private boolean isRightVisible;// 右
	private boolean isTopVisible;// 顶部
	private boolean isBottomVisible;// 底部

	/** 边框边距 */
	private float strokeLeft;// 左边距
	private float strokeRight;// 右边距
	private float strokeTop;// 顶边距
	private float strokeBottom;// 底边距
	private final float DEFAULT_MARGIN = 1.0f;// 默认边距
	protected SurfaceHolder mHolder;
	protected Canvas canvas;

	public GridChartView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	public GridChartView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		init();
	}

	public GridChartView(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		// TODO Auto-generated constructor stub
		init();
	}

	private void init() {
		strokeWidth = DEFAULT_STROKEWIDTH;
		strokeColor = DEFAULT_STROKECOLOR;
		isLeftVisible = true;
		isRightVisible = true;
		isTopVisible = true;
		isBottomVisible = true;
		strokeLeft = DEFAULT_MARGIN;
		strokeRight = DEFAULT_MARGIN;
		strokeTop = DEFAULT_MARGIN;
		strokeBottom = DEFAULT_MARGIN;



	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		super.dispatchDraw(canvas);
		viewHight = getHeight();
		viewWidth = getWidth();

		// 绘制边框
		drawFrame(canvas);

	}



	//
	// @Override
	// protected void onDraw(Canvas canvas) {
	// // TODO Auto-generated method stub
	// super.onDraw(canvas);
	// viewHight = getHeight();
	// viewWidth = getWidth();
	//
	// // 绘制边框
	// drawFrame(canvas);
	//
	// }

	/**
	 * 绘制边框
	 * 
	 * @param canvas
	 *            画布
	 */
	protected void drawFrame(Canvas canvas) {
		Paint paint = new Paint();
		paint.setStrokeWidth(strokeWidth);
		paint.setColor(strokeColor);
		paint.setAntiAlias(true);

		if (isLeftVisible)
			canvas.drawLine(strokeLeft + strokeWidth / 2, strokeTop, strokeLeft
					+ strokeWidth / 2, viewHight - strokeBottom, paint);// 绘制左边竖线
		if (isRightVisible)
			canvas.drawLine(viewWidth - strokeRight - strokeWidth / 2,
					strokeTop, viewWidth - strokeRight - strokeWidth / 2,
					viewHight - strokeBottom, paint);// 绘制右边竖线
		if (isTopVisible)
			canvas.drawLine(strokeLeft, strokeTop + strokeWidth / 2, viewWidth
					- strokeRight, strokeTop + strokeWidth / 2, paint);// 绘制顶部横线
		if (isBottomVisible)
			canvas.drawLine(strokeLeft, viewHight - strokeBottom - strokeWidth
					/ 2, viewWidth - strokeRight, viewHight - strokeBottom
					- strokeWidth / 2, paint);// 绘制底部横线
	}

	/**
	 * 设置边框宽度
	 * 
	 * @param strokeWidth
	 */
	public GridChartView setStrokeWidth(float strokeWidth) {
		if (strokeWidth < 1) {
			strokeWidth = DEFAULT_STROKEWIDTH;
		}
		this.strokeWidth = strokeWidth;
		return this;
	}

	/**
	 * 获取边框宽度
	 * 
	 * @return strokeWidth
	 */
	public float getStrokeWidth() {
		return strokeWidth;
	}

	/**
	 * 设置边框颜色
	 * 
	 * @param strokeColor
	 */
	public GridChartView setStrokeColor(int strokeColor) {
		this.strokeColor = strokeColor;
		return this;
	}

	/**
	 * 获取边框颜色
	 * 
	 * @return strokeColor
	 */
	public int getStrokeColor() {
		return strokeColor;
	}

	/**
	 * 设置左边框是否显示
	 * 
	 * @param isVisible
	 *            true显示,false不显示
	 * @return
	 */
	public GridChartView setStrokeLeftVisibility(boolean isVisible) {
		this.isLeftVisible = isVisible;
		return this;
	}

	/**
	 * 设置右边框是否显示
	 * 
	 * @param isVisible
	 *            true显示,false不显示
	 * @return
	 */
	public GridChartView setStrokeRightVisibility(boolean isVisible) {
		this.isRightVisible = isVisible;
		return this;
	}

	/**
	 * 设置顶部边框是否显示
	 * 
	 * @param isVisible
	 *            true显示,false不显示
	 * @return
	 */
	public GridChartView setStrokeTopVisibility(boolean isVisible) {
		this.isTopVisible = isVisible;
		return this;
	}

	/**
	 * 设置底部边框是否显示
	 * 
	 * @param isVisible
	 *            true显示,false不显示
	 * @return
	 */
	public GridChartView setStrokeBottomVisibility(boolean isVisible) {
		this.isBottomVisible = isVisible;
		return this;
	}

	/**
	 * 设置边框左边距
	 * 
	 * @param margin
	 * @return
	 */
	public GridChartView setStrokeLeft(float margin) {
		if (margin < 1) {
			margin = DEFAULT_MARGIN;
		}
		this.strokeLeft = margin;
		return this;
	}

	/**
	 * 获取边框左边距
	 * 
	 * @return
	 */
	public float getStrokeLeft() {
		return strokeLeft;
	}

	/**
	 * 设置边框右边距
	 * 
	 * @param margin
	 * @return
	 */
	public GridChartView setStrokeRight(float margin) {
		if (margin < 1) {
			margin = DEFAULT_MARGIN;
		}
		this.strokeRight = margin;
		return this;
	}

	/**
	 * 获取边框右边距
	 * 
	 * @return
	 */
	public float getStrokeRight() {
		return strokeRight;
	}

	/**
	 * 设置边框顶边距
	 * 
	 * @param margin
	 * @return
	 */
	public GridChartView setStrokeTop(float margin) {
		if (margin < 1) {
			margin = DEFAULT_MARGIN;
		}
		this.strokeTop = margin;
		return this;
	}

	/**
	 * 获取边框顶边距
	 * 
	 * @return
	 */
	public float getStrokeTop() {
		return strokeTop;
	}

	/**
	 * 设置边框底边距
	 * 
	 * @param margin
	 * @return
	 */
	public GridChartView setStrokeBottom(float margin) {
		if (margin < 1) {
			margin = DEFAULT_MARGIN;
		}
		this.strokeBottom = margin;
		return this;
	}

	/**
	 * 获取边框底边距
	 * 
	 * @return
	 */
	public float getStrokeBottom() {
		return strokeBottom;
	}

}
作者 east
Java 12月 7,2020

汉字转换位汉语拼音工具类

public final class Cn2Spell {

private Cn2Spell() {

}

/**
* 汉字转换位为语拼音首字母,英文字符不变
*
*
@param chines 汉字
*
@return 拼音
*/
public static String converterToFirstSpell(String chines) {
if (StringUtils.isBlank(chines)) {
return chines;
}
String pinyinName = "";
char[] nameChar = chines.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < nameChar.length; i++) {
if (nameChar[i] > 128) {
try {
pinyinName += PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0].charAt(0);
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
pinyinName += nameChar[i];
}
}
return pinyinName.toUpperCase();
}

/**
* 汉字转换为汉语拼音,英文字符不变
*
*
@param chines 汉字
*
@return 拼音
*/
public static String converterToSpell(String chines) {
if (StringUtils.isBlank(chines)) {
return chines;
}
String pinyinName = "";
int index = 0;
char[] nameChar = chines.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < nameChar.length; i++) {
if (nameChar[i] > 128) {
try {
if (index == 0 && i != 0) {
pinyinName += "_" + PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0];
} else {
pinyinName += PinyinHelper.toHanyuPinyinStringArray(nameChar[i], defaultFormat)[0];
}
index++;
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
} else {
if (index > 0) {
pinyinName += "_" + nameChar[i];
index = 0;
} else {
pinyinName += nameChar[i];
}

}
}
return pinyinName.toUpperCase();
}

/**
* 判断是否有中文
*
*
@param str
*
@return
*/
public static boolean isChineseChar(String str) {
boolean temp = false;
// 即[\\u4e00-\\u9fa5]字符,因代码检查不通过,需如下处理
StringBuilder sb = new StringBuilder();
sb.append("[\\u").append("4e00-\\u").append("9fa5]");

Pattern p = Pattern.compile(sb.toString());
Matcher m = p.matcher(str);

if (m.find()) {
temp = true;
}
return temp;
}
}
作者 east
Java 12月 7,2020

中文转数字Java工具类


import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;


public final class CnNumericToArabicUtil {

/**
* 无参构造函数
*/
private CnNumericToArabicUtil() {
}

/**
* 中文数字数组
*/
private static final Character[] CN_NUMERIC = { '一', '二', '三', '四', '五', '六', '七', '八', '九', '壹', '贰', '叁', '肆',
'伍', '陆', '柒', '捌', '玖', '○', 'O', '零', '十', '百', '千', '拾', '佰', '仟', '万', '亿' };

private static Map<Character, Integer> cnNumeric = null;

static {
cnNumeric = new HashMap<Character, Integer>(40, 0.85f);
for (int j = 0; j < 9; j++) {
cnNumeric.put(CN_NUMERIC[j], j + 1);
}
for (int j = 9; j < 18; j++) {
cnNumeric.put(CN_NUMERIC[j], j - 8);
}
for (int j = 18; j < 21; j++) {
cnNumeric.put(CN_NUMERIC[j], 0);
}

cnNumeric.put(' ', 0);
cnNumeric.put('两', 2);
cnNumeric.put('十', 10);
cnNumeric.put('拾', 10);
cnNumeric.put('百', 100);
cnNumeric.put('佰', 100);
cnNumeric.put('千', 1000);
cnNumeric.put('仟', 1000);
cnNumeric.put('万', 10000);
cnNumeric.put('亿', 100000000);
}

/**
* 中文数字转换为阿拉伯数字<br>
* TODO 该方法不完善的地方为只能纯的中文数字或者纯的阿拉伯数字,如果互相掺杂,得到的结果不准确,后期再完善<br>
*
*
@param cn 中文数字
*
@return int
*/
public static int cnNumericToArabic(String cn) {

// 中文数字参数为空判断,为空时返回0
if (StringUtils.isEmpty(cn)) {
return 0;
}

cn = cn.trim();

// 阿拉伯数字,类型转换后,直接返回结果
if (NumberUtils.isNumber(cn)) {
return Integer.parseInt(cn);
}

if (cn.length() == 1) {
return isCnNumeric(cn.charAt(0));
}

cn = cn.replace('佰', '百').replace('仟', '千').replace('拾', '十').replace('零', ' ');

// 结果值
int val = 0;

// 根据中文单位,将中文数字转换为阿拉伯数字,得到结果数组
// 亿
String[] cnNumericToArabicByCnUnitResults = cnNumericToArabicByCnUnit(cn, '亿', 100000000);
cn = cnNumericToArabicByCnUnitResults[0];
val += Integer.parseInt(cnNumericToArabicByCnUnitResults[1]);

// 万
cnNumericToArabicByCnUnitResults = cnNumericToArabicByCnUnit(cn, '万', 10000);
cn = cnNumericToArabicByCnUnitResults[0];
val += Integer.parseInt(cnNumericToArabicByCnUnitResults[1]);

// 千
cnNumericToArabicByCnUnitResults = cnNumericToArabicByCnUnit(cn, '千', 1000);
cn = cnNumericToArabicByCnUnitResults[0];
val += Integer.parseInt(cnNumericToArabicByCnUnitResults[1]);

// 百
cnNumericToArabicByCnUnitResults = cnNumericToArabicByCnUnit(cn, '百', 100);
cn = cnNumericToArabicByCnUnitResults[0];
val += Integer.parseInt(cnNumericToArabicByCnUnitResults[1]);

// 十
int ten = -1;
ten = cn.lastIndexOf('十');
if (ten > -1) {
if (ten == 0) {
val += 1 * 10;
} else {
val += cnNumericToArabic(cn.substring(0, ten)) * 10;
}
if (ten < cn.length() - 1) {
cn = cn.substring(ten + 1, cn.length());
} else {
cn = "";
}
}

cn = cn.trim();
for (int j = 0; j < cn.length(); j++) {
val += isCnNumeric(cn.charAt(j));
}
return val;
}

/**
* 中文数字转阿拉伯数字<BR>
* 如果字符串中文数字表达不全或错误,则只会解析部分(例如三十二百五六返回32)
*
*
@param numStr 待转换的字符串
*
@param numArrays 阿拉伯数值数组
*
@return
*/
public static String cnNumericToArabic(String numStr, char[] numArrays) {

if (StringUtils.isEmpty(numStr)) {
return "";
}

StringBuffer strRs = new StringBuffer();
boolean isFirst = (null == numArrays || numArrays.length == 0);
numStr = filterStr(numStr);
Pattern pattern = Pattern.compile("^\\d+$");

if (pattern.matcher(numStr).find()) {
return numStr;
}
char[] args = numStr.toCharArray();
int index = 0;
for (int i = UNITS_2.length - 1; i > 0; i--) {
index = numStr.indexOf(UNITS_2[i]);

if (index > 0) {
if (null == numArrays || numArrays.length == 0) {
numArrays = new char[Arrays.binarySearch(UNITS_2, args[index]) * 4];
Arrays.fill(numArrays, '0');
}
cnNumericToArabic(numStr.substring(index), numArrays);
break;
}
}

try {
process(numStr, numArrays, strRs, isFirst, index);

} catch (Exception e) {
return "";
}
// 避免只有单位没数字 比如十三这个“十”单位前没数字,根据习惯默认为一
return strRs.toString().replaceAll("[十,百,千,万,亿]", "1");
}

/**
* 检查是否为中文字符
*
*
@param ch 中文字符
*
@return boolean true:是中文数字;false:不是中文数字
*/
private static int isCnNumeric(char ch) {
Integer i = cnNumeric.get(ch);
if (i == null) {
return 0;
}
return i.intValue();
}

/**
* 根据中文单位,将中文数字转换为阿拉伯数字
*
*
@param cn 中文数字
*
@param cnUnit 中文单位,如:亿、万、千、百、十
*
@param unitVal 单位值,如:亿-100000000
*
@return String[] 字符串数组,格式为:{处理后的中文数字、结果值}
*/
private static String[] cnNumericToArabicByCnUnit(String cn, char cnUnit, int unitVal) {
// 中文数字为空判断,当为空时直接返回空字符串和结果值为0的数组
if (StringUtils.isEmpty(cn)) {
return new String[] { "", "0" };
}

// 结果值
int val = 0;
// 中文单位所在位置
int unitPos = cn.lastIndexOf(cnUnit);
if (unitPos > -1) {
// 中文数字转换为阿拉伯数字
val += cnNumericToArabic(cn.substring(0, unitPos)) * unitVal;
if (unitPos < cn.length() - 1) {
cn = cn.substring(unitPos + 1, cn.length());
} else {
cn = "";
}

if (cn.length() == 1) {
int arbic = isCnNumeric(cn.charAt(0));
if (arbic <= 10) {
val += arbic * unitVal * 0.1;
}
cn = "";
}
}
return new String[] { cn, "" + val };
}

/** 中文数值 */
private static final Character[] CN_NUMBER_1 = { 'O', '一', '二', '三', '四', '五', '六', '七', '八', '九' };

/** 中文数值 */
private static final Character[] CN_NUMBER_2 = { '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' };

/** 阿拉伯数值 */
private static final Character[] ARABIC_NUMBER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };

/** 用于辅助定位单位 */
private static final String UNITS_1_STR = " 十百千";

/** 单位一 */
private static final Character[] UNITS_1 = { ' ', '十', '百', '千' };

/** 单位一的同义词 */
private static final Character[] UNITS_1_T = { ' ', '拾', '佰', '仟' };

/** 单位2 */
private static final Character[] UNITS_2 = { ' ', '万', '亿' };

/**
* 从字符串中提取满足当前可转换为阿拉伯数字的字符串
*
*
@param str 待转换的字符串
*
@return
*/
public static String getCnNumericStr(String str) {
StringBuffer regx = new StringBuffer("([");
for (Character c : CN_NUMBER_1) {
regx.append(c.charValue());
}
for (Character c : CN_NUMBER_2) {
regx.append(c.charValue());
}
for (Character c : ARABIC_NUMBER) {
regx.append(c.charValue());
}
for (Character c : UNITS_1) {
regx.append(String.valueOf(c.charValue()).trim());
}
for (Character c : UNITS_1_T) {
regx.append(String.valueOf(c.charValue()).trim());
}
for (Character c : UNITS_2) {
regx.append(String.valueOf(c.charValue()).trim());
}
regx.append("两Oo○");
regx.append("]+)");
Pattern pattern = Pattern.compile(regx.toString());
Matcher matcher = pattern.matcher(str);
if (matcher.find()) {
return matcher.group();
}
return "";
}

/**
* 从字符串中提取满足当前可转换为阿拉伯数字的字符串,并将转换后的中文数字用阿拉伯数字替换掉原来字符串中的数值,并且返回
*
*
@param str 待转换的字符串
*
@return
*/
public static String transCnNumericStr(String str) {
if (StringUtils.isEmpty(str)) {
return "";
}
StringBuffer regx = new StringBuffer("([");
for (Character c : CN_NUMBER_1) {
regx.append(c.charValue());
}
for (Character c : CN_NUMBER_2) {
regx.append(c.charValue());
}
for (Character c : ARABIC_NUMBER) {
regx.append(c.charValue());
}
for (Character c : UNITS_1) {
regx.append(String.valueOf(c.charValue()).trim());
}
for (Character c : UNITS_1_T) {
regx.append(String.valueOf(c.charValue()).trim());
}
for (Character c : UNITS_2) {
regx.append(String.valueOf(c.charValue()).trim());
}
regx.append("两Oo○");
regx.append("]+)");
Pattern pattern = Pattern.compile(regx.toString());

Matcher matcher = pattern.matcher(str);
String value = "";
if (matcher.find()) {
value = matcher.group();
}
return str.replaceFirst(regx.toString(), CnNumericToArabicUtil.cnNumericToArabic(value, null));
}

/**
* 进行解析处理
*
*
@param numStr 需要解析的数值字符串
*
@param numArrays 数值对应的数组
*
@param strRs 结果
*
@param isFirst 是否第一次遍历
*
@param index 分隔索引
*
@throws Exception
*/
private static void process(String numStr, char[] numArrays, StringBuffer strRs, boolean isFirst, int index)
throws Exception {
char[] args;

if (isFirst) {
firstParse(numStr, numArrays, strRs, index);
} else {
// 大于万
if (index > 0) {
args = numStr.substring(0, index).toCharArray();
numStr = numStr.substring(0, index);
} else {
args = numStr.toCharArray();
}
// 找到起始位置
int start = numArrays.length - Arrays.binarySearch(UNITS_2, args[0]) * 4;

for (int i = 1, j = 0; i < UNITS_1.length; i++) {
j = numStr.indexOf(UNITS_1[i]);
// 包含十, 百, 千单位
if (j > 0) {
numArrays[start + (3 - i)] = args[j - 1];

if (i == 1 && j + 1 < args.length) {
numArrays[start + (3 - i) + 1] = args[j + 1];
}
} else {
// 找上级(比如十没找到,则找百)
for (int ii = 1 + i; ii < UNITS_1.length; ii++) {
j = numStr.indexOf(UNITS_1[ii]);
// 找到上级
if (j > 0) {
numArrays[start + (3 - ii)] = args[j - 1];
break;
}
}
// 如果没有任何上级,且当前索引必须小于千,则按十-千填入数字
if (j < 0 && args.length - 1 - i >= 0 && i < 3) {
// 填入当前位置的数值
if (NumberUtils.isNumber(String.valueOf(args[args.length - 1 - i]))) {
numArrays[start + (3 - i)] = args[args.length - 1 - i];
}
// 填入当前位置后面的数值
if (NumberUtils.isNumber(String.valueOf(args[args.length - i]))) {
numArrays[start + (3 - i) + 1] = args[args.length - i];
}
}
}
}
}
}

/**
* 第一次解析
*
*
@param numStr 需要解析的数值字符串
*
@param numArrays 数值对应的数组
*
@param strRs 结果
*
@param index 为万级别的索引
*/
private static void firstParse(String numStr, char[] numArrays, StringBuffer strRs, int index) {
char[] args;
// 数值不超过5位
if (index <= 0) {
index = numStr.length();
}
args = numStr.substring(0, index).toCharArray();

if (null != args && args.length > 1) {
// 第二位为单位(十百千)
int k = UNITS_1_STR.indexOf(args[1]);
// 此位为单位(十百千),则创建此段空数组,准备填入数值
if (k > 0) {
char[] arrays = new char[k + 1];
// 默认为0
Arrays.fill(arrays, '0');
// 从十百千依次开始
for (int i = 1, j = 0; k > 0 && i < UNITS_1.length; i++, k--) {
j = numStr.substring(0, index).indexOf(UNITS_1[i]);
// 此字符串包含十百千,j的前一位肯定是数字
if (j > 0) {
// 在对应的数组位置填上其数值
arrays[arrays.length - 1 - i] = args[j - 1];
// i为1说明当前处于十位,则需要补全个位数
if (i == 1 && j + 1 < args.length) {
arrays[arrays.length - i] = args[j + 1];
}
// 没找到单位
} else {
// 找上级(比如十没找到,则找百)
for (int ii = 1 + i; ii < UNITS_1.length; ii++) {
j = numStr.substring(0, index).indexOf(UNITS_1[ii]);
// 找到上级
if (j > 0) {
// 如果上级索引位置后面还有数字个数大于等于理论上的数字减一,则填入当前索引位的最后一位
if ((args.length - j - 1) >= (ii - 1)) {
arrays[arrays.length - i] = args[args.length - i];
}
}

}
}
}

strRs.append(String.valueOf(arrays));

if (null != numArrays) {
strRs.append(String.valueOf(numArrays));
}
} else {
// 不规则的情况(单位前没数字) 十二、百三十
strRs.append(numStr.substring(0, index));
if (null != numArrays) {
strRs.append(String.valueOf(numArrays));
}
}
} else {
// 解析位只有1位,则判断其是否为单位属性
if (null != args && args.length == 1) {
int k = UNITS_1_STR.indexOf(args[0]);
if (k > 0) {
char[] arrays = new char[k + 1];
Arrays.fill(arrays, '0');
arrays[0] = '1';
strRs.append(String.valueOf(arrays));
}
}
// 如果不包含单位,则直接添加此数值
if (!UNITS_1_STR.contains(numStr.substring(0, index))) {
strRs.append(numStr.substring(0, index));
}

if (null != numArrays) {
strRs.append(String.valueOf(numArrays));
}
}
}

/**
* 将中文数值转换为阿拉伯数字
*
*
@param numStr 待过滤的字符串
*
@return
*/
private static String filterStr(String numStr) {
numStr = numStr.replace('O', ARABIC_NUMBER[0].charValue());
numStr = numStr.replace('○', ARABIC_NUMBER[0].charValue());
numStr = numStr.replace('两', ARABIC_NUMBER[2].charValue());
for (int i = 0; i < ARABIC_NUMBER.length; i++) {
numStr = numStr.replace(CN_NUMBER_1[i].charValue(), ARABIC_NUMBER[i].charValue());
numStr = numStr.replace(CN_NUMBER_2[i].charValue(), ARABIC_NUMBER[i].charValue());
}
for (int i = 1; i < UNITS_1.length; i++) {
numStr = numStr.replace(UNITS_1_T[i].charValue(), UNITS_1[i].charValue());
}
return numStr;
}
}
作者 east
solr 12月 7,2020

Solr封装的Java客户端工具类




import com.finest.ms.address.util.AddrMatchConstant;
import com.finest.ms.address.util.ConfigUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.client.HttpClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpClientUtil;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.*;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;


public class SolrClient {


private Logger log = LoggerFactory.getLogger(SolrClient.class);

/** ID唯一标识 */
public static final String ID = "ID";

/**
* solr服务哈希表,key为(格式为“http://ip:端口/core”)路径名称,value为HttpSolrServer对象
*/
private HashMap<String, SolrServer> solrServers = new HashMap<String, SolrServer>();

/** UTF-8编码 */
public static final String UTF8_CHARSET_NAME = "UTF-8";

/** 拆分词组URL,用于拆分词组 */
public static final String URL_SPLIT_WORDS = "splitwords";

/** 代码词组的URL,用于根据代码得到标准词组 */
public static final String URL_CODE_WORDS = "codewords";

/** 字段通用名称--冒号 */
public static final String FIELD_COLON = ":";

/** 字段通用名称--双引号 */
public static final String FIELD_DOUBLE_QUOTATION_MARKS = "\"";

/** 注解字段常量--otherFields */
public static final String ANNOTATION_OTHERFIELDS = "otherFields";

/** solr的URL地址,目前只有两种,标准地址的solrurl地址和业务数据的solrurl地址,在config.properties配置 */
private String solrUrl;

private HttpClient client;

/** solr的循环次数集合,key为solr的url地址, value为循环次数 */
private Map<String, Integer> solrLoopNumMap = new HashMap<String, Integer>();

/** solr网络重连次数,默认为5 */
private int pingLoopNum;

/** solr网络重连睡眠时间,以毫秒为单位,默认为1000毫秒 */
private long pingSleepTime;

// /** debug模式下 - 统计solr访问次数 ,多线程下此值会存在不准确情况 */public static long total = 0;

/**
* 构造函数
*
*
@param solrUrl solr的URL,目前只有两种,标准地址的solrurl地址和业务数据的solrurl地址,在config.properties配置
*/
public SolrClient(String solrUrl) {
this.solrUrl = solrUrl;
}

public SolrClient(String solrUrl, HttpClient client){
this.solrUrl = solrUrl;
this.client = client;
}

/**
* 根据core名称得到SolrServer对象,如果不存在,就创建
*
*
@param core solr的core名称
*
@return
*/
public SolrServer getSolrServer(String core) throws Exception {
// 对solrUrl和core为空判断,当为空时,直接返回SolrHttpException异常
if (StringUtils.isEmpty(solrUrl) || StringUtils.isEmpty(core)) {
String error = "solr工具类-根据core名称得到SolrServer对象方法出现异常!solrUrl或core名称为空!";
log.warn(error);
throw new SolrHttpException(error); // 异常自定义的异常,可以很清楚知道该异常是http连接出现问题导致
}
SolrServer solrServer = null;
// 根据格式为“http://ip:端口/core”组成的key,存放至solr服务哈希表。
String solrCoreUrl = solrUrl + "/" + core;
boolean isNeedAuth = "true".equalsIgnoreCase(ConfigUtil.getPropsValueByKey("ac.solr.isNeedAuth"));
if (solrServers.containsKey(solrCoreUrl)&&!isNeedAuth) {
solrServer = solrServers.get(solrCoreUrl);
} else {
// 判断是否需要验证用户名密码
if (isNeedAuth) {
String solrUser = ConfigUtil.getPropsValueByKey("ac.solr.username");
String solrPass = ConfigUtil.getPropsValueByKey("ac.solr.password");
ModifiableSolrParams params = new ModifiableSolrParams();
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS, 128);
params.set(HttpClientUtil.PROP_MAX_CONNECTIONS_PER_HOST, 32);
params.set(HttpClientUtil.PROP_FOLLOW_REDIRECTS, false);
params.set(HttpClientUtil.PROP_BASIC_AUTH_USER, solrUser);
params.set(HttpClientUtil.PROP_BASIC_AUTH_PASS, solrPass);
HttpClient httpClient = HttpClientUtil.createClient(params);
solrServer = new HttpSolrExServer(solrCoreUrl, httpClient);
} else {
solrServer = new HttpSolrExServer(solrCoreUrl);
}
// solrServer的ping调用
pingSolrServer(solrServer);
//solrServers.put(solrCoreUrl, solrServer);
}
return solrServer;
}

/**
* 获取得到SolrDocument的数据列表
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@return
*/
public List<SolrDocument> getDocs(String criteria, String coreName, int rows, int start, String sortParams)
throws Exception {
return getDocs(criteria, coreName, rows, start, sortParams, null, null, null);
}

/**
* 获取得到SolrDocument的数据列表<br>
* 使用场景:<br>
* 1.当没有过滤显示字段数组,直接返回所有solr原始字段的SolrDocument数据。<br>
* 2.当有过滤显示字段数组并且没有过滤显示字段别名数组,返回过滤字段后的solr原始字段的SolrDocument数据。<br>
* 3.当有过滤显示字段数组并且有过滤显示字段别名数组,长度顺序一致,返回过滤字段后的以别名为key的SolrDocument数据。<br>
* 4.当有过滤显示字段数组并且有过滤显示字段别名数组,顺序一致,但长度不一致,即有部分设置别名,有部分没有设置别名,返回过滤字段后,有别名的以别名为key、没有别名的以字段名为key的SolrDocument数据。<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@param coreUrls core Url数组,用于跨core查询
*
@return
*/
public List<SolrDocument> getDocs(String criteria, String coreName, int rows, int start, String sortParams,
String[] fields, String[] aliasFields, String[] coreUrls) throws Exception {
List<SolrDocument> docs = new ArrayList<SolrDocument>();
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, rows, start, sortParams, fields, aliasFields,
null, null, coreUrls);

if (response.getResults() != null) {
docs.addAll(response.getResults());
}
}
return docs;
}

/**
* 得到分组统计后的文档列表<br>
* 与数据库group by分组返回结果集一致,支持多个字段的分组<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param facetParams 分组参数字符串,格式以逗号隔开,如name,age
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与facetParams分组参数字符串拆分后得到数组的顺序一一对应,长度可不相同,<br>
* 可短于facetParams分组参数字符串拆分后得到数组,也可长于facetParams分组参数字符串拆分后得到数组。<br>
* 当短时,那些没有设置别名的,将取回facetParams分组对应的字段名;当长时,除了全部用别名之外,还将对统计别名的设置。
*
@return
* @throws Exception
*/
public List<SolrDocument> getFacetDocs(String criteria, String coreName, String facetParams, String[] aliasFields)
throws Exception {
// 当查询条件,或者core名称,或者分组参数为空时,直接返回空列表
if (StringUtils.isEmpty(criteria) || StringUtils.isEmpty(coreName) || StringUtils.isEmpty(facetParams)) {
return Collections.emptyList();
}

// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, 0, 0, null, null, aliasFields, facetParams, null,
null);

List<PivotField> pivotFieldList = null;
// 根据分组结果,设置返回值
NamedList<List<PivotField>> facetPivot = response.getFacetPivot();
if (facetPivot != null) {
pivotFieldList = facetPivot.get(facetParams);
}
// 调用得到分组的solr文档对象列表方法,返回结果
return getFacetSolrDocumentList(pivotFieldList, aliasFields);
}

/**
* 获取得到对象化的数据列表
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> List<T> getBeans(String criteria, String coreName, int rows, int start, String sortParams,
Class<T> clazz) throws Exception {
return getBeans(criteria, coreName, rows, start, sortParams, clazz, null);
}

/**
* 获取得到对象化的数据列表<br>
* 使用场景:<br>
* 1.当没有过滤显示字段数组,转换对象的Class注解有@Field关联字段值进行映射,返回对象数据列表。<br>
* 2.当有过滤显示字段数组并且没有过滤显示字段别名数组,过滤字段后,转换对象的Class注解有@Field关联字段值进行映射,返回对象数据列表。<br>
* 3.当有过滤显示字段数组并且有过滤显示字段别名数组,不需要转换对象的Class注解有@Field字段:<br>
* 1)长度顺序一致,即都设置有别名情况下,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值。<br>
* 2)顺序一致,但长度不一致,即有部分设置别名,有部分没有设置别名,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值,找不到相同的属性名则不做赋值处理。<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名,并且返回对象的属性名与别名一致。
*
@param coreUrls core Url数组,用于跨core查询
*
@return
*/
public <T extends Object> List<T> getBeans(String criteria, String coreName, int rows, int start, String sortParams,
Class<T> clazz, String[] fields, String[] aliasFields, String[] coreUrls) throws Exception {
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, rows, start, sortParams, fields, aliasFields,
null, null, coreUrls);

if (response.getResults() != null) {
// 如果设置有别名,根据转换对象的Class,通过反射设置得到对应的转换对象列表,并返回
// 如果没有设置别名,返回solr原始getBeans方法得到的对象列表值。该getBeans方法是通过注解的方式设置映射关系
if (fields != null && fields.length > 0 && aliasFields != null && aliasFields.length > 0) {
return this.getBeansByDocsAndClazz(response.getResults(), clazz);
} else {
try {
return response.getBeans(clazz);
} catch (Exception e) {
String error = "获取得到对象化的数据列表方法,在执行QueryResponse的getBeans时出现异常!";
log.error(error, e);
throw new SolrBeanBindingException(error);
}
}
}
}
return Collections.emptyList();
}

/**
* 获取得到对象化的数据列表<br>
* 使用场景:<br>
* 1.当fieldsMap字段Map为空,以转换对象的Class注解有@Field关联字段值进行映射,返回对象数据列表。<br>
* 2.当fieldsMap字段Map不为空情况下,不需要转换对象的Class注解有@Field字段:<br>
* 1)所有键与值都有值,即都设置有别名情况下,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值。<br>
* 2)转换对象的Class注解有@Field("otherFields")的Map属性,所有找不到别名名称相同的转换对象Class的属性名,把那些没有设置到属性里的值,全部加入到注解有@Field(
* "otherFields")的Map对象中<br>
* 3)如果没有找到注解有@Field("otherFields")的Map,所有找不到别名名称相同的转换对象Class的属性名,把那些没有设置到属性里的值全部丢弃掉<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@param fieldsMap 字段Map,key为字段名,value为字段别名,别名可以为空,取回key对应的字段名为别名
*
@return
*/
public <T extends Object> List<T> getBeans(String criteria, String coreName, int rows, int start, String sortParams,
Class<T> clazz, Map<String, String> fieldsMap) throws Exception {
// 过滤显示字段数组
String[] fields = null;
// 过滤显示字段别名数组
String[] aliasFields = null;

// 通过字段map,得到字段名数组和别名数组,并设置
if (fieldsMap != null) {
// 字段名拼接字符串,字段名之间以逗号隔开
StringBuilder fieldsSb = new StringBuilder();
// 字段别名拼接字符串,字段别名之间以逗号隔开
StringBuilder aliasFieldsSb = new StringBuilder();

for (Entry<String, String> entry : fieldsMap.entrySet()) {
fieldsSb.append(entry.getKey()).append(",");
if (StringUtils.isNotEmpty(entry.getValue())) {
aliasFieldsSb.append(entry.getValue().trim()).append(",");
} else {
aliasFieldsSb.append(",");
}
}
if (fieldsSb.length() > 0) {
fieldsSb.delete(fieldsSb.length() - 1, fieldsSb.length());
}
if (aliasFieldsSb.length() > 0) {
aliasFieldsSb.delete(aliasFieldsSb.length() - 1, aliasFieldsSb.length());
}

fields = fieldsSb.toString().split(" *, *");
aliasFields = aliasFieldsSb.toString().split(" *, *");
}

return getBeans(criteria, coreName, rows, start, sortParams, clazz, fields, aliasFields, null);
}

/**
* 获取得到对象化的数据对象<br>
* 使用场景:<br>
* 1.当没有过滤显示字段数组,转换对象的Class注解有@Field关联字段值进行映射,返回对象数据。<br>
* 2.当有过滤显示字段数组并且没有过滤显示字段别名数组,过滤字段后,转换对象的Class注解有@Field关联字段值进行映射,返回对象数据。<br>
* 3.当有过滤显示字段数组并且有过滤显示字段别名数组,不需要转换对象的Class注解有@Field字段:<br>
* 1)长度顺序一致,即都设置有别名情况下,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值。<br>
* 2)顺序一致,但长度不一致,即有部分设置别名,有部分没有设置别名,设置对应的别名,并且能找到与别名名称相同的转换对象Class的属性名,通过反射直接赋值,找不到相同的属性名则不做赋值处理。<br>
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名,并且返回对象的属性名与别名一致。
*
@param coreUrls core Url数组,用于跨core查询
*
@return
*/
public <T extends Object> T getBean(String criteria, String coreName, String sortParams, Class<T> clazz,
String[] fields, String[] aliasFields, String[] coreUrls) throws Exception {
List<T> list = getBeans(criteria, coreName, 1, 0, sortParams, clazz, fields, aliasFields, coreUrls);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0);
}
return null;
}

/**
* 获取得到对象化的数据对象
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> T getBean(String criteria, String coreName, String sortParams, Class<T> clazz)
throws Exception {
return getBean(criteria, coreName, sortParams, clazz, null, null, null);
}

/**
* 根据ID获取得到对象化的数据对象
*
*
@param id ID唯一标识
*
@param coreName core的名称
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> T getBeanById(String id, String coreName, Class<T> clazz) throws Exception {
StringBuilder querySb = new StringBuilder();
querySb.append("ID:").append(id);
return getBean(querySb.toString(), coreName, null, clazz);
}

/**
* 获取得到对象化的数据Solr文档
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@return
*/
public SolrDocument getDoc(String criteria, String coreName, String[] fields, String[] aliasFields)
throws Exception {
List<SolrDocument> list = getDocs(criteria, coreName, 1, 0, null, fields, aliasFields, null);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0);
}
return null;
}

/**
* 获取得到对象化的数据Solr文档
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@return
*/
public SolrDocument getDoc(String criteria, String coreName) throws Exception {
return getDoc(criteria, coreName, null, null);
}

/**
* 获取总条数
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@return
*/
public long getNumberFound(String criteria, String coreName) throws Exception {
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, 0, 0, null, null, null, null, null, null);
return response.getResults().getNumFound();
}
return 0;
}

/**
* 添加文档
*
*
@param document solr的输入文档对象
*
@param coreName core的名称
*/
public void addDoc(SolrInputDocument document, String coreName) throws Exception {
addDocumentIntoSolr(document, coreName, false);
}

/**
* 添加文档并马上提交
*
*
@param document solr的输入文档对象
*
@param coreName core的名称
*/
public void addDocByHardCommit(SolrInputDocument document, String coreName) throws Exception {
addDocumentIntoSolr(document, coreName, true);
}

/**
* 添加Bean
*
*
@param document Bean对象
*
@param coreName core的名称
*/
public void addBean(Object document, String coreName) throws Exception {
addDocumentIntoSolr(document, coreName, false);
}

/**
* 添加Bean并马上提交
*
*
@param document Bean对象
*
@param coreName core的名称
*/
public void addBeanByHardCommit(Object document, String coreName) throws Exception {
addDocumentIntoSolr(document, coreName, true);
}

/**
* 添加文档至solr
*
*
@param object object对象,可能是SolrInputDocument、也可能是bean对象
*
@param coreName core名称
*
@param isHardCommit 是否马上提交
*
@throws Exception
*/
private void addDocumentIntoSolr(Object object, String coreName, boolean isHardCommit) throws Exception {
if (object != null && StringUtils.isNotEmpty(coreName)) {
SolrServer solrServer = getSolrServer(coreName);
try {
if (object instanceof SolrInputDocument) {
solrServer.add((SolrInputDocument) object);
} else {
solrServer.addBean(object);
}
if (isHardCommit) {
solrServer.commit();
}
} catch (SolrServerException | IOException e) {
String error = "solr工具类-添加文档至solr方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
}
}
}

/**
* 添加bean列表
*
*
@param documents
*
@param coreName
*/
@SuppressWarnings("rawtypes")
public void addBeans(List documents, String coreName) throws Exception {
addDocumentsIntoSolr(documents, coreName, false);
}

/**
* 添加bean列表并马上提交
*
*
@param documents
*
@param coreName
*/
@SuppressWarnings("rawtypes")
public void addBeansByHardCommit(List documents, String coreName) throws Exception {
addDocumentsIntoSolr(documents, coreName, true);
}

/**
* 添加文档列表
*
*
@param documents
*
@param coreName
*/
public void addDocs(List<SolrInputDocument> documents, String coreName) throws Exception {
addDocumentsIntoSolr(documents, coreName, false);
}

/**
* 添加文档列表并马上提交
*
*
@param documents
*
@param coreName
*/
public void addDocsByHardCommit(List<SolrInputDocument> documents, String coreName) throws Exception {
addDocumentsIntoSolr(documents, coreName, true);
}

/**
* 添加文档(对象)列表到solr
*
*
@param documents
*
@param coreName
*
@param isHardCommit
*
@throws Exception
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private void addDocumentsIntoSolr(List documents, String coreName, boolean isHardCommit) throws Exception {
if (documents != null && documents.size() > 0 && StringUtils.isNotEmpty(coreName)) {
try {
SolrServer solrServer = getSolrServer(coreName);
if (documents.get(0) instanceof SolrInputDocument) {
solrServer.add(documents);
} else {
solrServer.addBeans(documents);
}
if (isHardCommit) {
solrServer.commit();
}
} catch (SolrServerException | IOException e) {
String error = "solr工具类-添加文档至solr方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
}
}
}

/**
* 根据ID更新solr字段值
*
*
@param inputDoc
*
@param id
*
@param fields
*
@param values
*
@return
*/
public SolrInputDocument updateSolrFieldsWithId(SolrInputDocument inputDoc, Object id, List<String> fields,
Object[] values) {
inputDoc = this.updateSolrFields(inputDoc, fields, values);
inputDoc.setField(ID, id);
return inputDoc;
}

/**
* 更新solr字段值
*
*
@param inputDoc
*
@param fields
*
@param values
*
@return
*/
public SolrInputDocument updateSolrFields(SolrInputDocument inputDoc, List<String> fields, Object[] values) {
if (fields != null && values != null && fields.size() <= values.length) {
for (int i = 0; i < fields.size(); i++) {
Map<String, Object> updater = new HashMap<String, Object>();
updater.put("set", values[i]);
inputDoc.setField(fields.get(i), updater);
}
}

return inputDoc;
}

/**
* 更新solr字段值
*
*
@param inputDoc
*
@param fields
*
@param values
*
@return
*/
public SolrInputDocument updateSolrFields(SolrInputDocument inputDoc, List<String> fields, SolrDocument values) {
if (fields != null && values != null) {
for (int i = 0; i < fields.size(); i++) {
Map<String, Object> updater = new HashMap<String, Object>();
updater.put("set", values.getFieldValue(fields.get(i)));
inputDoc.setField(fields.get(i), updater);
}
}

return inputDoc;
}

/**
* 根据查询条件删除solr数据
*
*
@param query 查询条件
*
@param coreName core名称
*/
public void deleteByQuery(String query, String coreName) throws Exception {
try {
SolrServer solrServer = getSolrServer(coreName);
solrServer.deleteByQuery(query);
solrServer.commit();
} catch (SolrServerException | IOException e) {
String error = "solr工具类-根据查询条件删除solr数据方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
}
}

/**
*
*
@param coreName
*
@param termsField
*
@param prefix
*
@param fetchRows
*
@return
*/
public List<TermsResponse.Term> suggest(String coreName, String termsField, String prefix, int fetchRows)
throws Exception {
SolrQuery solrQuery = new SolrQuery();
solrQuery.addTermsField(termsField);
solrQuery.setTerms(true);
solrQuery.setTermsLimit(fetchRows);
solrQuery.setTermsSortString("index");
solrQuery.setTermsPrefix(prefix);
solrQuery.setRequestHandler("/terms");
QueryResponse queryResponse = getSolrServer(coreName).query(solrQuery);
return queryResponse.getTermsResponse().getTerms(termsField);
}

/**
* 拼写
*
*
@param coreName
*
@param queryString
*
@param fetchRows
*
@param fetchStartAt
*
@param sortParams
*
@param params
*
@return
*/
public <T extends Object> SolrDocumentList spell(String coreName, String queryString, int fetchRows,
int fetchStartAt, String sortParams, String params) throws Exception {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(queryString, coreName, fetchRows, fetchStartAt, sortParams, null,
null, null, "spell", null);

SolrDocumentList results = response.getResults();
if (null == results && null != response.getGroupResponse()) {
results = new SolrDocumentList();
for (GroupCommand groupCommand : response.getGroupResponse().getValues()) {
for (Group group : groupCommand.getValues()) {
results.addAll(group.getResult());
}
}
}
return results;
}

/**
* 拆分词组,返回匹配到的词组
*
*
@param criteria 查询条件
*
@param coreName core名称
*
@param requestHandler 请求句柄
*
@param rows 条数,可用于分页
*
@param start 开始查询位置,可用于分页
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@return
*/
public <T extends Object> List<T> splitwWordsBeans(String criteria, String coreName, String requestHandler,
int rows, int start, String sortParams, Class<T> clazz) throws Exception {
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)
&& StringUtils.isNotEmpty(requestHandler)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, rows, start, sortParams, null, null, null,
requestHandler, null);

if (response.getResults() != null) {
try {
return response.getBeans(clazz);
} catch (Exception e) {
String error = "拆分词组返回匹配到的词组方法,在执行QueryResponse的getBeans操作时出现异常!";
log.error(error, e);
throw new SolrBeanBindingException(error);
}
}
}
return Collections.emptyList();
}

/**
* 拆分词组,返回匹配到SolrDocument的词组
*
*
@param criteria 查询条件
*
@param coreName core名称
*
@param requestHandler 请求句柄
*
@param rows 条数,可用于分页
*
@param start 开始查询位置,可用于分页
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@return
*/
public List<SolrDocument> splitwWordsDocs(String criteria, String coreName, String requestHandler, int rows,
int start, String sortParams) throws Exception {
List<SolrDocument> docs = new ArrayList<SolrDocument>();
if (StringUtils.isNotEmpty(criteria) && StringUtils.isNotEmpty(coreName)
&& StringUtils.isNotEmpty(requestHandler)) {
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(criteria, coreName, rows, start, sortParams, null, null, null,
requestHandler, null);

if (response.getResults() != null) {
docs.addAll(response.getResults());
}
}
return docs;
}

/**
* 反向匹配,返回转换后的结果对象
*
*
@param coreName core名称
*
@param lon 经度
*
@param lat 纬度
*
@param radius 搜索半径,单位km
*
@param returnDistanceName 返回时距离的名称,与tClass距离属性名称要一致,如不填,tClass要有distance距离属性,不然将无法得到距离值
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> T reverseMatchBean(String coreName, double lon, double lat, double radius, Class<T> clazz,
String returnDistanceName) throws Exception {
List<T> list = this.reverseMatchBeans(coreName, lon, lat, radius, 1, 0, clazz, returnDistanceName);
if (CollectionUtils.isNotEmpty(list)) {
return list.get(0);
}
return null;
}

/**
* 反向匹配,返回转换后的结果对象列表
*
*
@param coreName core名称
*
@param lon 经度
*
@param lat 纬度
*
@param radius 搜索半径,单位km
*
@param start 开始位置
*
@param rows 查询的行数
*
@param returnDistanceName 返回时距离的名称,与tClass距离属性名称要一致,如不填,tClass要有distance距离属性,不然将无法得到距离值
*
@param clazz 要转换对象的Class
*
@return
*/
public <T extends Object> List<T> reverseMatchBeans(String coreName, double lon, double lat, double radius,
int rows, int start, Class<T> clazz, String returnDistanceName) throws Exception {
if (StringUtils.isNotEmpty(coreName)) {
// 得到反向solr查询对象
SolrQuery solrQuery = getReverseSolrQuery(coreName, lon, lat, radius, rows, start, returnDistanceName);
// 得到solr的查询返回结果对象
if(!coreName.equals(AddrMatchConstant.CORE_NAME_STANDARDADDRESS)) {
coreName = AddrMatchConstant.CORE_NAME_STANDARDADDRESS;
}
QueryResponse response = getQueryResponse(coreName, solrQuery);

if (response.getResults() != null) {
try {
return response.getBeans(clazz);
} catch (Exception e) {
String error = "反向匹配返回转换后的结果对象列表方法,在执行QueryResponse的getBeans时出现异常!";
log.error(error, e);
throw new SolrBeanBindingException(error);
}
}
}
return Collections.emptyList();
}

/**
* 反向匹配,返回SolrDocument列表
*
*
@param coreName core名称
*
@param lon 经度
*
@param lat 纬度
*
@param radius 搜索半径,单位km
*
@param start 开始位置
*
@param rows 查询的行数
*
@param returnDistanceName 返回时距离的名称
*
@return
*/
public List<SolrDocument> reverseMatchDocs(String coreName, double lon, double lat, double radius, int rows,
int start, String returnDistanceName) throws Exception {
List<SolrDocument> docs = new ArrayList<SolrDocument>();
if (StringUtils.isNotEmpty(coreName)) {
// 得到反向solr查询对象
SolrQuery solrQuery = getReverseSolrQuery(coreName, lon, lat, radius, rows, start, returnDistanceName);
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(coreName, solrQuery);

if (response.getResults() != null) {
docs.addAll(response.getResults());
}
}
return docs;
}

/**
* 得到反向solr查询对象
*
*
@param coreName core名称
*
@param lon 经度
*
@param lat 纬度
*
@param radius 搜索半径,单位km
*
@param start 开始位置
*
@param rows 查询的行数
*
@param returnDistanceName 返回时距离的名称
*
@return
*/
private SolrQuery getReverseSolrQuery(String coreName, double lon, double lat, double radius, int rows, int start,
String returnDistanceName) {
SolrQuery solrQuery = new SolrQuery();
if(coreName.equals(AddrMatchConstant.CORE_NAME_INFOPOINT)) {
System.out.println("信息点请求");
solrQuery.set("q", "YSLXDM:12");
}else if(coreName.equals(AddrMatchConstant.CORE_NAME_AEINCLUSIONRELATION)) {
System.out.println("街路巷查询");
solrQuery.set("q", "YSLXDM:6");
}else {
solrQuery.set("q", "*:*");
}
solrQuery.set("fq", "{!geofilt}"); // 距离过滤函数
solrQuery.set("pt", lat + "," + lon); // 当前坐标
solrQuery.set("sfield", "GEO"); // 经纬度字段,默认约定geo名称
solrQuery.set("d", "" + radius); // 搜索半径,单位km
solrQuery.set("sort", "geodist() asc"); // 根据距离排序:由近到远
solrQuery.set("start", start); // 记录开始位置
solrQuery.set("rows", rows); // 查询的行数
if (StringUtils.isEmpty(returnDistanceName)) {
returnDistanceName = "distance";
}
solrQuery.set("fl", "*," + returnDistanceName + ":geodist()"); // 查询的结果中添加距离
return solrQuery;
}


/**
* 得到街路巷查询对象
*
*
@return
*/
private SolrQuery getStreetSolrQuery(String coreName, int rows, int start,
String ssqx) {
String q = "(SJDM:"+ssqx+" OR SJMC:"+ssqx+" OR SJQHDM:"+ssqx+" OR SJQHMC:"+ssqx+" OR QXDM:"+ssqx+" OR QXMC:"+ssqx+") AND " +
"(YSLXMC:"+coreName+" OR JLXDM:"+coreName+")";

SolrQuery solrQuery = new SolrQuery();
solrQuery.set("q", q);
// solrQuery.set("sort", "geodist() asc"); // 根据距离排序:由近到远
solrQuery.set("start", start); // 记录开始位置
solrQuery.set("rows", rows); // 查询的行数

return solrQuery;
}

/**
* solr导入数据操作,返回solr的查询返回结果对象<br>
* 根据参数不同,该方法不一定就是进行solr同步数据。 比如获取solr导入数据状态<br>
*
*
@param coreName core的名称
*
@param paramsDataMap 参数数据Map
*
@return
*/
private QueryResponse importData(String coreName, Map<String, String> paramsDataMap) throws Exception {
// 得到反向solr查询对象
SolrQuery solrQuery = new SolrQuery();
solrQuery.setRequestHandler("/dataimport");
if (paramsDataMap != null) { // 设置相关参数
for (Map.Entry<String, String> entry : paramsDataMap.entrySet()) {
solrQuery.setParam(entry.getKey(), entry.getValue());
}
}
// 得到solr的查询返回结果对象
QueryResponse response = getQueryResponse(coreName, solrQuery);
return response;
}

/**
* 压缩
*
*
@param content
*
@param name
*
@return
* @throws IOException
*/
public static byte[] zip(String content, String name) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ZipOutputStream zipOutputStream = null;
DataOutputStream dataOutputStream = null;
try {
zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
zipOutputStream.putNextEntry(new ZipEntry(name));
dataOutputStream = new DataOutputStream(zipOutputStream);
dataOutputStream.write(content.getBytes(UTF8_CHARSET_NAME));
dataOutputStream.flush();
zipOutputStream.flush();
zipOutputStream.finish();
} finally {
dataOutputStream.close();
zipOutputStream.close();
}
return byteArrayOutputStream.toByteArray();
}

/**
* 解压
*
*
@param value
*
@return
*/
public String unZip(byte[] value) throws Exception {
if (null == value) {
return null;
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(value);
ZipInputStream zipInputStream = null;
String sourceText = null;
try {
zipInputStream = new ZipInputStream(byteArrayInputStream);
zipInputStream.getNextEntry();
final int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int n;
while ((n = zipInputStream.read(buffer, 0, bufferSize)) != -1) {
byteArrayOutputStream.write(buffer, 0, n);
}
sourceText = byteArrayOutputStream.toString("UTF-8");
} catch (IOException e) {
String error = "solr工具类-解压方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
} finally {
try {
if (null != zipInputStream) {
zipInputStream.close();
zipInputStream = null;
}
if (null != byteArrayInputStream) {
byteArrayInputStream.close();
byteArrayInputStream = null;
}
if (null != byteArrayOutputStream) {
byteArrayOutputStream.close();
byteArrayOutputStream = null;
}
} catch (IOException e) {
String error = "solr工具类-解压方法出现异常!";
log.error(error, e);
throw new SolrServerIoException(error);
}
}
return sourceText;
}

/**
* 设置排序参数,并返回SolrQuery对象
*
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param solrQuery solr查询对象
*
@return
*/
private SolrQuery setSortParams(String sortParams, SolrQuery solrQuery) {
SolrQuery returnSolrQuery = solrQuery;
if (StringUtils.isNotEmpty(sortParams)) {
String[] sortFields = sortParams.trim().split(" *, *");
for (String sortField : sortFields) {
String[] sortFieldAndOrder = sortField.split(" +");
if (2 == sortFieldAndOrder.length) {
returnSolrQuery.addSort(sortFieldAndOrder[0],
SolrQuery.ORDER.valueOf(sortFieldAndOrder[1].toLowerCase()));
} else if (1 == sortFieldAndOrder.length) {
returnSolrQuery.addSort(sortFieldAndOrder[0], SolrQuery.ORDER.asc);
}
}
}
return returnSolrQuery;
}

/**
* 得到设置过虑字段参数,如果有别名,则设置别名
*
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*/
private String[] getAliasFieldsArray(String[] fields, String[] aliasFields) {
if (fields == null || fields.length <= 0) {
return null;
}

// 判断字段别名数组不为空且长度与字段数组相等
if (aliasFields != null && aliasFields.length > 0) {
StringBuilder fieldSb = new StringBuilder();
// 别名的格式是: 别名:真实名
for (int i = 0; i < fields.length; i++) {
fieldSb.delete(0, fieldSb.length());
if (aliasFields.length - 1 >= i) {
if (StringUtils.isEmpty(aliasFields[i])) {
aliasFields[i] = fields[i];
}
fieldSb.append(aliasFields[i]).append(":").append(fields[i]);
} else {
fieldSb.append(fields[i]).append(":").append(fields[i]);
}
fields[i] = fieldSb.toString();
}
}
return fields;
}

/**
* 设置分组参数,并返回SolrQuery对象<br>
* 主要用于数据统计,对应数据库中的group by
*
*
@param facetParams 分组参数字符串,格式以逗号隔开,如name,age
*
@param solrQuery solr查询类
*/
private SolrQuery setFacetParams(String facetParams, SolrQuery solrQuery) {
SolrQuery returnSolrQuery = solrQuery;
if (StringUtils.isNotEmpty(facetParams)) {
returnSolrQuery.setFacet(true);
returnSolrQuery.addFacetPivotField(facetParams);
returnSolrQuery.setFacetMissing(false); // 不统计null的值
}
return returnSolrQuery;
}

/**
* 根据solr文档列表和转换对象的Class,通过反射设置得到对应的转换对象列表<br>
* 1.主要用于当已经设置过滤返回对象和别名后得到的docs文档列表,根据转换对象的Class,设置与文档key一致的属性值<br>
* 2.当转换对象的Class注解有@Field("otherFields"),把那些没有设置到属性里的值,全部加入到注解有@Field("otherFields")的Map对象中<br>
* 3.如果没有找到注解有@Field("otherFields")的Map,那些没有设置到属性里的值全部丢弃掉<br>
*
*
@param docs solr文档对象列表
*
@param clazz 要转换对象的Class
*
@return
*/
private <T extends Object> List<T> getBeansByDocsAndClazz(List<SolrDocument> docs, Class<T> clazz) {
// solr文档对象列表为空,直接返回空列表
if (docs == null || docs.size() <= 0) {
return Collections.emptyList();
}

// 得到所有属性列表
Field[] declaredFields = clazz.getDeclaredFields();
// 对象实例
T obj = null;
// 其他字段值map
Map<String, String> otherFieldValueMap = null;
// solr字段Object对象变量
Object fieldValueObj = null;
// solr字段字符串变量
String fieldValueStr = null;
// 返回列表
List<T> rtnList = new ArrayList<T>();
// 是否有相同的字段名称
boolean hasSameFieldName = false;
// 日期格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

for (SolrDocument doc : docs) {
// fieldValueMap = doc.getFieldValueMap();
try {
hasSameFieldName = false;
otherFieldValueMap = new HashMap<String, String>();
// 创建实例
obj = clazz.newInstance();
// 循环反射得到的字段列表,比较字段名是否一致,一致的话则赋值给对象。
for (Entry<String, Object> entry : doc.entrySet()) {
fieldValueObj = entry.getValue();

for (Field field : declaredFields) {
// 字段名一致
if (field.getName().equals(entry.getKey())) {
field.setAccessible(true);

// 类型转换,如果是solr文档对象是日期类型,并且与clazz属性类型不一致,则做日期格式转换
if (fieldValueObj instanceof Date) {
if (field.getType() == Date.class) {
field.set(obj, fieldValueObj);
} else {
field.set(obj, dateFormat.format(fieldValueObj));
}
} else {
// 除了日期类型之外,其他类型solr对象与bean对象属性类型一致,按原类型设置值
if (fieldValueObj.getClass() == field.getType()) {
field.set(obj, fieldValueObj);
} else {
field.set(obj, fieldValueObj.toString());
}
}
hasSameFieldName = true;
break;
}
}

if (!hasSameFieldName) {
// 那些没有找到相同属性名的值,全部加入Map对象中
if (fieldValueObj instanceof Date) {
fieldValueStr = dateFormat.format(fieldValueObj);
} else {
// 除了日期类型之外,其他类型按字符串类型设置值
fieldValueStr = fieldValueObj.toString();
}
otherFieldValueMap.put(entry.getKey(), fieldValueStr);
}

} // end-for (Entry<String, Object> entry : doc.entrySet())

// 通过反射,设置其他字段值map到对象实例
setOtherFieldValueMap(declaredFields, obj, otherFieldValueMap);

rtnList.add(obj);
} catch (InstantiationException | IllegalAccessException e) {
// 出现异常,记录日志,不直接抛出中断流程
log.error("通过转换得到对应的转换对象列表方法时,出现异常!", e);
}
}
return rtnList;
}

/**
* 通过反射,设置其他字段值map到对象实例
*
*
@param declaredFields 所有属性字段的列表
*
@param obj 要转换对象Class的对象实例
*
@param otherFieldValueMap 其他字段值map
*
@return
*/
private <T extends Object> T setOtherFieldValueMap(Field[] declaredFields, T obj,
Map<String, String> otherFieldValueMap) {

for (Field field : declaredFields) {
if (field.isAnnotationPresent(org.apache.solr.client.solrj.beans.Field.class)
&& field.getType() == Map.class) {

org.apache.solr.client.solrj.beans.Field annotationField = field
.getAnnotation(org.apache.solr.client.solrj.beans.Field.class);

// 注解的字段名是否为otherFields,则把除了有设置别名之外的需要返回的字段值,赋值给该字段值上
if (ANNOTATION_OTHERFIELDS.equals(annotationField.value())) {
try {
field.setAccessible(true);
field.set(obj, otherFieldValueMap);
} catch (IllegalArgumentException | IllegalAccessException e) {
// 出现异常,记录日志,不直接抛出中断流程
log.error("通过反射设置其他字段值map到对象实例方法时,出现异常!", e);
}
break;
}
}
}
return obj;
}

/**
* 触发批量更新,针对批量<br>
* 全库导入,同步的速度是最快,增量导入,需要逐条比对,比较慢。<br>
*
*
@param coreName core的名称
*
@param hasFullImport 是否全库导入,第一次抽取匹配时设置为true,增量抽取匹配时设置为false
*
@return Long 更新条数
*/
@SuppressWarnings("unchecked")
public Long triggerBatchUpdate(String coreName, Boolean hasFullImport) throws Exception {
// 命令参数
String commandParam = (hasFullImport != null && hasFullImport) ? "full-import" : "delta-import";
// 同步参数设置
Map<String, String> paramsDataMap = new HashMap<String, String>();
paramsDataMap.put("command", commandParam);
paramsDataMap.put("clean", "false");
paramsDataMap.put("commit", "true");

// 根据参数执行solr导入同步数据操作
importData(coreName, paramsDataMap);

// 状态参数设置
Map<String, String> statusParamsDataMap = new HashMap<String, String>();
statusParamsDataMap.put("command", "status");

int num = 0;
QueryResponse response; // 查询结果对象
NamedList<Object> resMap; // 查询结果Map
String status; // 状态值,有两种,idle和busy。
LinkedHashMap<String, String> statusMessages; // 状态消息对象

while (true) {
// solr导入数据操作
response = importData(coreName, statusParamsDataMap);

// 获取得到更新条数。
resMap = response.getResponse();
status = resMap.get("status") == null ? "" : resMap.get("status").toString();
log.debug("~~~~~获取数据同步至solr状态!状态为:" + status);

if ("idle".equalsIgnoreCase(status)) { // 状态已完成
if (resMap.get("statusMessages") != null) { // 更新条数是在statusMessages对象里
try {
statusMessages = (LinkedHashMap<String, String>) resMap.get("statusMessages");
num = Integer.parseInt(statusMessages.get("Total Documents Processed"));
} catch (Exception e1) {
num = 0;
}
}
break;
}
TimeUnit.MILLISECONDS.sleep(1000); // 睡眠1秒
}

return (long) num;
}

/**
* 得到分组的solr文档对象列表
*
*
@param pivotFieldList 枢轴字段对象列表
*
@param aliasFields 过滤显示字段别名数组
*
@return
*/
private List<SolrDocument> getFacetSolrDocumentList(List<PivotField> pivotFieldList, String[] aliasFields) {
List<Map<String, Object>> results = new ArrayList<Map<String, Object>>();
List<SolrDocument> returnSolrDocList = new ArrayList<SolrDocument>();
// 调用递归枢轴字段方法
recursiveFacetPivotField(pivotFieldList, null, results);
if (CollectionUtils.isEmpty(results)) {
return returnSolrDocList;
}

// 将List<Map<String, Object>> 转换为List<SolrDocument>结果
SolrDocument solrDoc = null;
int aliasFieldIndex;
String pivotFieldKey;
for (Map<String, Object> pivotFieldAndValueMap : results) {
solrDoc = new SolrDocument();
aliasFieldIndex = 0;
for (Entry<String, Object> entry : pivotFieldAndValueMap.entrySet()) {
// 别名处理
if (aliasFields != null && aliasFields.length > 0 && aliasFieldIndex < aliasFields.length) {
pivotFieldKey = aliasFields[aliasFieldIndex];
aliasFieldIndex++;
} else {
pivotFieldKey = entry.getKey();
}
solrDoc.setField(pivotFieldKey, entry.getValue());
}
returnSolrDocList.add(solrDoc);
}
return returnSolrDocList;
}

/**
* 递归分组的枢轴字段方法<br>
* PivotField为solr特殊类型<br>
*
*
@param pivotFieldList 枢轴字段对象列表
*
@param pivotFieldAndValueMap 枢轴字段和值的Map对象
*
@param results 结果集,类似于数据库group by后,得到的结果集
*/
private void recursiveFacetPivotField(List<PivotField> pivotFieldList, Map<String, Object> pivotFieldAndValueMap,
List<Map<String, Object>> results) {
if (pivotFieldList == null || pivotFieldList.size() <= 0) {
return;
}
if (pivotFieldAndValueMap == null) {
pivotFieldAndValueMap = new LinkedHashMap<String, Object>();
}

for (PivotField pivotField : pivotFieldList) {
pivotFieldAndValueMap.put(pivotField.getField(), pivotField.getValue());
if (pivotField.getPivot() == null || pivotField.getPivot().size() <= 0) {
// TODO 设定统计值key和值,key暂时未设置为常量
pivotFieldAndValueMap.put("count", pivotField.getCount());
results.add(new LinkedHashMap<String, Object>(pivotFieldAndValueMap));
} else {
recursiveFacetPivotField(pivotField.getPivot(), pivotFieldAndValueMap, results);
}
}
}

/**
* 得到solr的查询返回结果对象
*
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@param facetParams 分组参数字符串,格式以逗号隔开,如name,age
*
@param requestHandler 请求句柄,如分词为splitwords
*
@param coreUrls core Url数组,用于跨core查询
*
@return
* @throws Exception
*/
private QueryResponse getQueryResponse(String criteria, String coreName, Integer rows, Integer start,
String sortParams, String[] fields, String[] aliasFields, String facetParams, String requestHandler,
String[] coreUrls) throws Exception {
// 跨core查询
if (coreUrls != null && coreUrls.length > 1) {
return getMoreCoreQueryResponse(coreUrls, criteria, coreName, rows, start, sortParams, fields, aliasFields);
}

SolrQuery solrQuery = new SolrQuery().setQuery(criteria).setRows(rows).setStart(start);
solrQuery = setSortParams(sortParams, solrQuery);
solrQuery = setFacetParams(facetParams, solrQuery);
fields = this.getAliasFieldsArray(fields, aliasFields);
solrQuery.setFields(fields);
if (StringUtils.isNotEmpty(requestHandler)) {
solrQuery.setRequestHandler("/" + requestHandler);
}

return this.getQueryResponse(coreName, solrQuery);
}

/**
* 得到solr的查询返回结果对象
*
*
@param coreName core名称
*
@param solrQuery solr查询对象
*
@return
* @throws Exception
*/
public QueryResponse getQueryResponse(String coreName, SolrQuery solrQuery) throws Exception {
SolrServer solrServer = getSolrServer(coreName);
// 设置分组参数
QueryRequest queryRequest = new QueryRequest(solrQuery);
queryRequest.setMethod(SolrRequest.METHOD.POST);
try {
// if (log.isDebugEnabled()) {total++;}
QueryResponse response = queryRequest.process(solrServer);
return response;
} catch (SolrServerException | HttpSolrServer.RemoteSolrException e) {
String error = "solr工具类-执行得到solr的查询返回结果对象方法出现异常!";
Throwable throwable = e.getCause();
if (throwable instanceof SocketException || throwable instanceof IOException) {
try {
// solrServer的ping调用
pingSolrServer(solrServer);
} catch (Exception ex) {
throw ex;
}
return getQueryResponse(coreName, solrQuery);
}
log.error(error, e);
throw new SolrServerIoException(error);
}
}

/**
* 得到跨core查询返回的结果对象
*
*
@param coreUrls core Url数组,用于跨core查询
*
@param criteria 查询条件
*
@param coreName core的名称
*
@param rows 查询条数
*
@param start 开始查询的位置
*
@param sortParams 排序参数,以逗号隔开。如id desc,name desc
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@return
* @throws Exception
*/
private QueryResponse getMoreCoreQueryResponse(String[] coreUrls, String criteria, String coreName, int rows,
int start, String sortParams, String[] fields, String[] aliasFields) throws Exception {
SolrServer solrServer = getSolrServer(coreName);
// 获取多core查询参数
ModifiableSolrParams solrParams = getMoreCoreModifiableSolrParams(coreUrls, criteria, rows, start, sortParams,
fields, aliasFields);
QueryRequest queryRequest = new QueryRequest(solrParams);
queryRequest.setMethod(SolrRequest.METHOD.POST);
try {
// if (log.isDebugEnabled()) {total++;}
QueryResponse response = queryRequest.process(solrServer);
return response;
} catch (SolrServerException | HttpSolrServer.RemoteSolrException e) {
String error = "solr工具类-执行得到solr的查询返回结果对象方法出现异常!";
Throwable throwable = e.getCause();
if (throwable instanceof SocketException || throwable instanceof IOException) {
try {
// solrServer的ping调用
pingSolrServer(solrServer);
} catch (Exception ex) {
throw ex;
}
return getMoreCoreQueryResponse(coreUrls, criteria, coreName, rows, start, sortParams, fields,
aliasFields);
}
log.error(error, e);
throw new SolrServerIoException(error);
}
}

/**
* 获取多core查询参数
*
*
@param coreUrls core Url数组,用于跨core查询
*
@param criteria 查询条件
*
@param rows 查询的行数
*
@param start 开始位置
*
@param sortParams 排序参数
*
@param fields 过滤显示字段数组,如果设置,只显示数组内的字段,数组为空,显示全部
*
@param aliasFields 过滤显示字段别名数组,数组的顺序与fields字段数组的顺序一一对应,长度可不相同,当不相同时,取回fields对应的字段名为别名
*
@return
*/
private ModifiableSolrParams getMoreCoreModifiableSolrParams(String[] coreUrls, String criteria, int rows,
int start, String sortParams, String[] fields, String[] aliasFields) {
ModifiableSolrParams solrParams = new ModifiableSolrParams();
solrParams.set("q", criteria);
solrParams.set("sort", sortParams); // 排序
solrParams.set("start", start); // 记录开始位置
solrParams.set("rows", rows); // 查询的行数
solrParams.set("shards.info", true);
solrParams.set("shards", StringUtils.join(coreUrls, ","));
fields = this.getAliasFieldsArray(fields, aliasFields);
if (fields != null && fields.length > 0) {
solrParams.set("fl", StringUtils.join(fields, ",")); // 设置过滤字段
}

return solrParams;
}

/**
* 得到solrClient的solrUrl
*
*
@return
*/
public String getSolrUrl() {
return solrUrl;
}

/**
* solrServer的ping调用<br>
* 网络断开连接不上时,调用ping方法检查网络情况。在设置的次数范围之内能ping通网络,则继续进行往下执行地址匹配流程,否则抛出异常。
*
*
@param solrServer solr服务对象
*
@return
* @throws Exception
*/
private synchronized SolrServer pingSolrServer(SolrServer solrServer) throws Exception {
try {
solrServer.ping();
} catch (SolrServerException | IOException | HttpSolrServer.RemoteSolrException e) {
// 循环次数,网络出现异常情况下,循环设置的次数回调ping方法。超过设置的次数,将直接抛出异常。
int loopNum = 0;
if (solrLoopNumMap.containsKey(solrUrl)) {
loopNum = solrLoopNumMap.get(solrUrl) + 1;
} else {
loopNum++;
}
solrLoopNumMap.put(solrUrl, loopNum);

// TODO 由于类代码长度限制1500行,目前暂时以简单方式读取配置值,后期再优化
// solr的ping循环次数,默认为5
if (pingLoopNum <= 0) {
try {
pingLoopNum = Integer.parseInt(ConfigUtil.getPropsValueByKey("ac.httpSolr.pingLoopNum"));
//pingLoopNum = Integer.parseInt(CommonString.pingLoopNum);
pingLoopNum = pingLoopNum <= 0 ? 5 : pingLoopNum;
} catch (NumberFormatException ex) {
ex.printStackTrace();
pingLoopNum = 5;
}
}
// solr的ping睡眠时间,以毫秒为单位,默认为10秒
if (pingSleepTime <= 0) {
try {
pingSleepTime = Long.parseLong(ConfigUtil.getPropsValueByKey("ac.httpSolr.pingSleepTime"));
//pingSleepTime = Long.parseLong(CommonString.pingSleepTime);
pingSleepTime = pingSleepTime <= 0 ? 10000 : pingSleepTime;
} catch (NumberFormatException ex) {
ex.printStackTrace();
pingSleepTime = 10000;
}
}
// sol的ping循环次数超过设置的值,抛出SolrHttpException异常
if (loopNum > pingLoopNum) {
log.warn("执行solrServer的ping方法超过" + pingLoopNum + "次仍然出现网络异常!调用URL为:" + solrUrl);
String error = String.format("solr工具类-执行solrServer的ping方法出现异常!原因是solr【%s】没有启动或网络不通", solrUrl + "/");
log.error(error, e);
throw new SolrHttpException(error);
}
// 睡眠,单位为毫秒
TimeUnit.MILLISECONDS.sleep(pingSleepTime);

log.info("网络出现异常!再次执行solrServer的ping方法,调用次数为:" + loopNum + ",调用URL为:" + solrUrl);
return pingSolrServer(solrServer);
}
solrLoopNumMap.put(solrUrl, 0);
return solrServer;
}

/**
* 空间检索
*
@param coreName
*
@param solrQuery
*
@return
* @throws Exception
*/
public QueryResponse spaceSearch(String coreName, SolrQuery solrQuery) throws Exception {
if (StringUtils.isNotEmpty(coreName)) {
try {
QueryResponse response = getQueryResponse(coreName, solrQuery);
return response;
} catch (SolrServerException | HttpSolrServer.RemoteSolrException e) {
String error = "solr工具类-执行得到solr的查询返回结果对象方法出现异常!";
Throwable throwable = e.getCause();
if (throwable instanceof SocketException || throwable instanceof IOException) {
try {
SolrServer solrServer = getSolrServer(coreName);
// solrServer的ping调用
pingSolrServer(solrServer);
} catch (Exception ex) {
throw ex;
}
return spaceSearch(coreName, solrQuery);
}
log.error(error, e);
throw new SolrServerIoException(error);
}
}
return null;
}




}
作者 east
Java 12月 7,2020

非spring容器下的配置工具类

1、按照spring boot的约定,配置文件名称默认是classpath根路径下的application.properties,并按其约定加载多个配置文件
2、按指定配置文件的路径及名称读取配置,一般是兼容旧工程没有按spring boot约定命名的配置文件 <br>
3、新增读取yml配置文件的功能,默认先读取properties文件,如果不存在再读取yml文件

public final class ConfigUtil {

    private static Logger log = LoggerFactory.getLogger(DataBaseUtil.class);

    /**
     * spring boot默认的配置文件名称
     */
    private static final String CONFIG_FILENAME = "application";

    private static final String DEFAULT_SUFFIX = ".properties";

    private static final String YML_SUFFIX = ".yml";

    /**
     * spring boot通过application.properties配置文件中的spring.profiles.active配置项值指定生效的配置文件名称<br>
     * 如spring.profiles.active=business,表示application-business.properties配置文件生效
     */
    private static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";

    /**
     * spring boot约定命名格式"application-{profile}.properties"来定义多个配置文件,其中{profile}是可变部分
     */
    private static final String SPRING_BOOT_PROFILE_TEMPLATE = "application-{profile}";

    /**
     * 属性对象集合,注意这里是线程安全的
     */
    private static Map<String, Properties> props = new ConcurrentHashMap<>();

    /** 私有的构造函数 */
    private ConfigUtil() {

    }

    /**
     * 加载指定配置文件中的配置
     * 
     * @param fileName
     * @return
     */
    private static Properties loadPropFromFile(String fileName) {
        Properties prop = new Properties();

        try {
            prop.load(ConfigUtil.class.getClassLoader().getResourceAsStream(fileName));
        } catch (IOException e) {
            e.printStackTrace();
            String error = "配置文件工具类加载配置文件" + fileName + "出现异常!";
            log.error(e.getMessage()+":===:"+error);
            throw new RuntimeException(error);
        }
        return prop;
    }

    /**
     * 将yml格式文件加载到Properties中
     * 
     * @param fileName 文件名
     * @return
     */
    private static Properties loadYmlFromFile(String fileName) {
        Properties prop = new Properties();
        LoaderOptions options = new LoaderOptions();
        // 是否允许读取重复key值,默认为true
        options.setAllowDuplicateKeys(false);
        Yaml yaml = new Yaml(options);

        // 加载yml文件流
        InputStream inStream = ConfigUtil.class.getClassLoader().getResourceAsStream(fileName);
        // 按照文件编码读取流
        Reader reader = new UnicodeReader(inStream);
        Iterable<Object> it = yaml.loadAll(reader);
        Map<String, Object> result = new LinkedHashMap<>();

        for (Object object : it) {
            // 构建map类型数据
            buildFlattenedMap(result, asMap(object), null);
        }
        prop.putAll(result);
        return prop;
    }

    /**
     * 递归转换map源数据,将递归的key值用"."拼接成最终目标map的key值,最终的value为目标map的value值
     * 
     * @param result
     * @param source
     * @param path
     */
    @SuppressWarnings("unchecked")
    private static void buildFlattenedMap(Map<String, Object> result, Map<String, Object> source, String path) {
        source.forEach((key, value) -> {

            if (hasText(path)) {
                if (key.startsWith("[")) {
                    key = path + key;
                } else {
                    // 分层读取key值,使用"."拼接
                    key = path + '.' + key;
                }
            }

            if (value instanceof String) {
                result.put(key, value);
            } else if (value instanceof Map) {
                // Need a compound key
                Map<String, Object> map = (Map<String, Object>) value;
                // 递归构造map
                buildFlattenedMap(result, map, key);
            } else if (value instanceof Collection) {
                // Need a compound key
                Collection<Object> collection = (Collection<Object>) value;

                if (collection.isEmpty()) {
                    result.put(key, "");
                } else {
                    int count = 0;
                    for (Object object : collection) {
                        buildFlattenedMap(result, Collections.singletonMap("[" + (count++) + "]", object), key);
                    }
                }
            } else {
                result.put(key, (value != null ? value : ""));
            }
        });
    }

    /**
     * 将object数据转换为Map
     * 
     * @param object
     * @return
     */
    @SuppressWarnings("unchecked")
    private static Map<String, Object> asMap(Object object) {
        // YAML can have numbers as keys
        Map<String, Object> result = new LinkedHashMap<>();

        if (!(object instanceof Map)) {
            // A document can be a text literal
            result.put("document", object);
            return result;
        }
        Map<Object, Object> map = (Map<Object, Object>) object;

        map.forEach((key, value) -> {

            if (value instanceof Map) {
                value = asMap(value);
            }

            if (key instanceof CharSequence) {
                result.put(key.toString(), value);
            } else {
                // It has to be a map key in this case
                result.put("[" + key.toString() + "]", value);
            }
        });
        return result;
    }

    // 判断字符串是否有值(不为空、不为null、必须包含字符串)
    private static boolean hasText(@Nullable String str) {
        return (str != null && !str.isEmpty() && containsText(str));
    }

    // 是否包含字符
    private static boolean containsText(CharSequence str) {
        int strLen = str.length();

        for (int i = 0; i < strLen; i++) {

            if (!Character.isWhitespace(str.charAt(i))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 根据属性key得到对应的属性值<br>
     * 配置文件按spring boot的约定的配置文件命名规则进行加载<br>
     * <font color=red>注意: 暂不支持yml文件属性读取</font>
     * 
     * @param key 键名称
     * @return
     */
    public static String getPropsValueByKey(String key) {
        // TODO 暂时不支持读取yml格式文件,yml文件支持map和list格式数据,需要另写方法支持
        if (!props.containsKey(CONFIG_FILENAME)) {
            Properties prop = new Properties();
            prop = getPropertiesByFileName(CONFIG_FILENAME);

            if (prop.get(SPRING_PROFILES_ACTIVE) != null) {
                // 依次读取指定的配置文件
                for (String partName : prop.get(SPRING_PROFILES_ACTIVE).toString().split(",")) {
                    prop.putAll(getPropertiesByFileName(SPRING_BOOT_PROFILE_TEMPLATE.replace("{profile}", partName)));
                }
            }
            props.put(CONFIG_FILENAME, prop);
        }
        Object obj = props.get(CONFIG_FILENAME).get(key);
        if (obj == null) {
            return null;
        } else {
            return obj.toString();
        }
    }

    /**
     * 根据配置文件名称和属性key得到对应的属性值.<br>
     * 一般用于非spring boot约定的配置文件名称,比如集成其它系统的功能时,有固定名称的配置文件,或兼容旧系统<br>
     * <font color=red>注意: 暂不支持yml文件属性读取</font>
     * 
     * @param configFileName 配置文件名称
     * @param key 属性key值
     * @return
     * 
     */
    public static String getPropsValueByKey(String configFileName, String key) {
        // TODO 暂时不支持读取yml格式文件,yml文件支持map和list格式数据,需要另写方法支持
        if (!props.containsKey(configFileName)) {
            props.put(configFileName, loadPropFromFile(configFileName));
        }
        return props.get(configFileName).getProperty(key);
    }

    /**
     * 根据文件名称获取Properties
     * 
     * @param fileName 文件名
     * @return
     */
    private static Properties getPropertiesByFileName(String fileName) {
        String fileFullName = fileName + DEFAULT_SUFFIX;
        InputStream inputStream = ConfigUtil.class.getClassLoader().getResourceAsStream(fileFullName);

        if (inputStream == null) {
            fileFullName = fileName + YML_SUFFIX;
            // 读取yml配置文件
            inputStream = ConfigUtil.class.getClassLoader().getResourceAsStream(fileFullName);
            return loadYmlFromFile(fileFullName);
        } else {
            // 读取properties文件
            return loadPropFromFile(fileFullName);
        }
    }

    public static String getBody(String bodyName,JSONObject jsonObject) {
        String body = (String) jsonObject.get(bodyName);
        String reqBody = body;
        String pattern = "\\$\\{.*?\\}";
        Pattern r = Pattern.compile(pattern);
        Matcher matcher = r.matcher(body);
        while (matcher.find()) {
            String substring = body.substring(matcher.start(), matcher.end());
            String s = getPropsValueByKey(substring.substring(2, substring.length() - 1));
            reqBody = reqBody.replace(substring, s);
        }
        return reqBody;
    }

}
作者 east
Android 12月 7,2020

Android绘制饼图例子

public class MeFragment extends Fragment {
private View view;
private PieChartView pieChart;
PieChartData pieChardata;
List<SliceValue> values = new ArrayList<SliceValue>();
private int[] data = {21, 20, 9, 2, 8};
private int[] colorData = {Color.parseColor("#ec063d"),
Color.parseColor("#f1c704"),
Color.parseColor("#c9c9c9"),
Color.parseColor("#2bc208"),
Color.parseColor("#333333")};
private String[] stateChar = {"提示", "故障", "离线", "正常", "未激活"};

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_me, null);
setPieChartData();
initPieChart();
pieChart.setOnValueTouchListener(selectListener);//设置点击事件监听
return view;
}

public void initPieChart() {
pieChart = (PieChartView) view.findViewById(R.id.chart);
pieChardata = new PieChartData();
pieChardata.setHasLabels(false);//显示表情
pieChardata.setHasLabelsOnlyForSelected(false);//不用点击显示占的百分比
pieChardata.setHasLabelsOutside(false);//占的百分比是否显示在饼图外面
pieChardata.setHasCenterCircle(true);//是否是环形显示
pieChardata.setValues(values);//填充数据
pieChardata.setCenterCircleColor(Color.WHITE);//设置环形中间的颜色
pieChardata.setCenterCircleScale(0.7f);//设置环形的大小级别
pieChart.setPieChartData(pieChardata);
pieChart.setValueSelectionEnabled(true);//选择饼图某一块变大
pieChart.setAlpha(0.9f);//设置透明度
pieChart.setCircleFillRatio(1f);//设置饼图大小
}

private void setPieChartData() {
for (int i = 0; i < data.length; ++i) {
SliceValue sliceValue = new SliceValue((float) data[i], colorData[i]);
values.add(sliceValue);
}
}

private PieChartOnValueSelectListener selectListener = new PieChartOnValueSelectListener() {

@Override
public void onValueDeselected() {
// TODO Auto-generated method stub

}

@Override
public void onValueSelected(int arg0, SliceValue value) {
//选择对应图形后,在中间部分显示相应信息
pieChardata.setCenterText1(stateChar[arg0]);
pieChardata.setCenterText1Color(colorData[arg0]);
pieChardata.setCenterText1FontSize(10);
pieChardata.setCenterText2(value.getValue() + "(" + calPercent(arg0) + ")");
pieChardata.setCenterText2Color(colorData[arg0]);
pieChardata.setCenterText2FontSize(12);
Toast.makeText(getContext(), stateChar[arg0] + ":" + value.getValue(), Toast.LENGTH_SHORT).show();
}
};

private String calPercent(int i) {
String result = "";
int sum = 0;
for (int i1 = 0; i1 < data.length; i1++) {
sum += data[i1];
}
result = String.format("%.2f", (float) data[i] * 100 / sum) + "%";
return result;
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/base"
android:gravity="center"
android:padding="10dp">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/me_title"
android:textColor="@color/white"
android:textSize="@dimen/title" />
</LinearLayout>

<LinearLayout
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<lecho.lib.hellocharts.view.PieChartView
android:id="@+id/chart"
android:layout_width="200dp"
android:layout_height="200dp" />
</LinearLayout>

</LinearLayout>
作者 east
Android 12月 7,2020

Android获取用户的地理位置工具类

public class GPSUtils {

private static GPSUtils instance;
private Context mContext;
private LocationManager locationManager;

private GPSUtils(Context context) {
this.mContext = context;
}

public static GPSUtils getInstance(Context context) {
if (instance == null) {
instance = new GPSUtils(context);
}
return instance;
}

/**
* 获取经纬度
*
* @return
*/
public String getLngAndLat(OnLocationResultListener onLocationResultListener) {
double latitude = 0.0;
double longitude = 0.0;

mOnLocationListener = onLocationResultListener;

String locationProvider = null;
locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
//获取所有可用的位置提供器
List<String> providers = locationManager.getProviders(true);

if (providers.contains(LocationManager.GPS_PROVIDER)) {
//如果是GPS
locationProvider = LocationManager.GPS_PROVIDER;
} else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
//如果是Network
locationProvider = LocationManager.NETWORK_PROVIDER;
} else {
Intent i = new Intent();
i.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
mContext.startActivity(i);
return null;
}

//获取Location
Location location = locationManager.getLastKnownLocation(locationProvider);
if (location != null) {
//不为空,显示地理位置经纬度
if (mOnLocationListener != null) {
mOnLocationListener.onLocationResult(location);
}

}
//监视地理位置变化
locationManager.requestLocationUpdates(locationProvider, 3000, 1, locationListener);
return null;
}


public LocationListener locationListener = new LocationListener() {

// Provider的状态在可用、暂时不可用和无服务三个状态直接切换时触发此函数
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {

}

// Provider被enable时触发此函数,比如GPS被打开
@Override
public void onProviderEnabled(String provider) {

}

// Provider被disable时触发此函数,比如GPS被关闭
@Override
public void onProviderDisabled(String provider) {

}

//当坐标改变时触发此函数,如果Provider传进相同的坐标,它就不会被触发
@Override
public void onLocationChanged(Location location) {
if (mOnLocationListener != null) {
mOnLocationListener.OnLocationChange(location);
}
}
};

public void removeListener() {
locationManager.removeUpdates(locationListener);
}

private OnLocationResultListener mOnLocationListener;

public interface OnLocationResultListener {
void onLocationResult(Location location);

void OnLocationChange(Location location);
}
}
作者 east
Android 12月 7,2020

Android下载工具类

如果要下载在外部存储空间,要看外部存储空间是否可用。

public class DownloadUtil {
private static DownloadUtil downloadUtil;
private final OkHttpClient okHttpClient;

public static DownloadUtil get() {
if (downloadUtil == null) {
downloadUtil = new DownloadUtil();
}
return downloadUtil;
}

private DownloadUtil() {
okHttpClient = new OkHttpClient();
}

/**
* @param url 下载连接
* @param destFileDir 下载的文件储存目录
* @param destFileName 下载文件名称
* @param listener 下载监听
*/
public void download(final String url, final String destFileDir, final String destFileName, final OnDownloadListener listener) {
Request request = new Request.Builder().url(url).build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
// 下载失败监听回调
listener.onDownloadFailed(e);
}

@Override
public void onResponse(Call call, Response response) throws IOException {
InputStream is = null;
byte[] buf = new byte[2048];
int len = 0;
FileOutputStream fos = null;
// 储存下载文件的目录
File dir = new File(getSDPath(destFileDir));
if (!dir.exists()) {
boolean b= dir.mkdirs();
}
File file = new File(dir, destFileName);
try {
is = response.body().byteStream();
long total = response.body().contentLength();
fos = new FileOutputStream(file);
long sum = 0;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
sum += len;
int progress = (int) (sum * 1.0f / total * 100);
// 下载中更新进度条
listener.onDownloading(progress);
}
fos.flush();
// 下载完成
listener.onDownloadSuccess(file);
} catch (Exception e) {
listener.onDownloadFailed(e);
} finally {
try {
if (is != null)
is.close();
} catch (IOException e) {
}
try {
if (fos != null)
fos.close();
} catch (IOException e) {
}
}
}
});
}

public interface OnDownloadListener {
/**
* @param file 下载成功后的文件
*/
void onDownloadSuccess(File file);

/**
* @param progress 下载进度
*/
void onDownloading(int progress);

/**
* @param e 下载异常信息
*/
void onDownloadFailed(Exception e);
}

public String getSDPath(String dirName) {
File sdDir = null;
boolean sdCardExist = Environment.getExternalStorageState()
.equals(android.os.Environment.MEDIA_MOUNTED);
if (sdCardExist) {
sdDir = Environment.getExternalStorageDirectory();//获取根目录
Log.e("Test", "外部存储可用..." + sdDir.toString());
}
return sdDir.toString()+"/"+dirName;
}
}
作者 east
Java 12月 7,2020

Excel快速导入导出工具(仅提供excel2007以及更高版本的支持)

public class ExcelWriter {

/** 日志 */
private static Logger LOG = LoggerFactory.getLogger(ExcelWriter.class);

/** 原生HttpServletResponse对象 */
private HttpServletResponse response = null;

/**
* 用于导入excel的构造函数
*/
private ExcelWriter() {

}

/**
* 用于导出excel的构造函数 <br>
* List(Bean)格式
*
* @param clazz
* @param response
*/
private ExcelWriter(Class<?> clazz, HttpServletResponse response) {
// this.clazz == clazz;
this.response = response;
}

/**
* 用于导出excel的构造函数 <br>
* List(String[]) 格式
*
* @param response
*/
private ExcelWriter(HttpServletResponse response) {
this.response = response;
}

/**
* 用于浏览器导出excel的实例化
*
* @param clazz 实体Class对象
* @param response 原生HttpServletResponse对象
* @return ExcelUtils
*/
public static ExcelWriter exportInit(HttpServletResponse response) {
return new ExcelWriter(response);
}

/**
* 用于浏览器导出excel的实例化
*
* @param clazz 实体Class对象
* @param response 原生HttpServletResponse对象
* @return ExcelUtils
*/
public static ExcelWriter exportInit(Class<?> clazz, HttpServletResponse response) {
return new ExcelWriter(clazz, response);
}

/**
* 导出Excel,响应到浏览器下载(List<String[]>格式)
*
* @param headers 表头
* @param data 数据
* @param fileName 文件名称
* @param sheetName 工作表名
* @return
*/
public boolean writeExcel(List<String> headers, List<String[]> datas, String fileName, String sheetName) {
if (response == null) {
throw new IllegalArgumentException("请先使用exportInit(HttpServletResponse)初始化参数。");
}
long begin = System.currentTimeMillis();

if (ListUtils.isEmpty(datas)) {
LOG.error("没有检测到数据,不执行导出操作。");
return false;
}
int dataSize = datas.size();
LOG.info(String.format("即将导出excel数据 : %s条,请稍后...", dataSize));
try {
// 输出流
OutputStream outputStream = response.getOutputStream();
// 创建新的工作薄
SXSSFWorkbook wb = PoiUtils.newSXSSFWorkbook();
// 表头的样式
CellStyle cellStyle = wb.createCellStyle();
Font font = wb.createFont();
// 对齐
cellStyle.setAlignment(CellStyle.ALIGN_LEFT);
font.setBoldweight(Font.BOLDWEIGHT_NORMAL);
// 字体大小
font.setFontHeightInPoints((short) 12);
font.setFontName("宋体");
// 应用标题字体到标题样式
cellStyle.setFont(font);
// 设置单元格文本形式
DataFormat dataFormat = wb.createDataFormat();
cellStyle.setDataFormat(dataFormat.getFormat("@"));

// 表头的值
String headerValue = null;
// 单元格的值
String cellValue = null;
// 创建sheet
SXSSFSheet sheet = PoiUtils.newSXSSFSheet(wb, sheetName);
// 创建行表头
SXSSFRow headerRow = PoiUtils.newSXSSFRow(sheet, 0);
for (int i = 0, size = headers.size(); i < size; i++) {
headerValue = headers.get(i);
SXSSFCell cell = PoiUtils.newSXSSFCell(headerRow, i);
cell.setCellValue(headerValue);
cell.setCellStyle(cellStyle);
}

// 明细行数组
String[] bodys = null;
for (int i = 0; i < dataSize; i++) {
// 创建行数据
SXSSFRow bodyRow = PoiUtils.newSXSSFRow(sheet, i + 1);
bodys = datas.get(i);
for (int j = 0, len = bodys.length; j < len; j++) {
cellValue = bodys[j];
if (StringUtils.isBlank(cellValue)) {
// FIXME: 当值为""时,当前index的cell会失效,这里设置为null
cellValue = null;
}

// 创建列
SXSSFCell cell = PoiUtils.newSXSSFCell(bodyRow, j);
cell.setCellValue(cellValue);
cell.setCellType(SXSSFCell.CELL_TYPE_STRING);
}
}

writeByBrowser(response, fileName, wb, outputStream);
LOG.info(String.format("excel处理完成,共生成数据 : %s行,耗时 : %s seconds.", dataSize,
(System.currentTimeMillis() - begin) / 1000.0));
} catch (Exception e) {
LOG.error("导出excel失败 : " + e.getMessage(), e);
}
return true;
}

/**
* 响应到浏览器下载
*
* @param response
* @param fileName
* @param wb
* @param out
* @throws Exception
*/
private static void writeByBrowser(HttpServletResponse response, String fileName, SXSSFWorkbook wb,
OutputStream out) throws Exception {
// response对象不为空,响应到浏览器下载
if (response != null) {
response.setContentType(ConstantUtils.CONTENT_TYPE);
response.setCharacterEncoding("UTF-8");
fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1") + ConstantUtils.XLSX_SUFFIX;
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
if (out == null) {
out = response.getOutputStream();
}
}
wb.write(out);
out.flush();
out.close();
}

/**
* 保存文件到本地
*
* @param headers 表头
* @param datas 转换好的数据
* @param filePath 路径
* @param sheetName 工作表名
* @param type 类型 0-转换成功 1-转换失败
*/
public static void saveByLocal(List<String> headers, List<String[]> datas, String filePath, int type) {
long begin = System.currentTimeMillis();

if (ListUtils.isEmpty(datas)) {
LOG.error("没有检测到数据,不执行导出操作。");
return;
}
int dataSize = datas.size();
LOG.info(String.format("即将保存excel数据 : %s条,请稍后...", dataSize));
// 输出流
FileOutputStream fileOutputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
fileOutputStream = new FileOutputStream(filePath);
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
String sheetName = "";
if (type == 0) {
sheetName = PropertyUtils.getProperty("successConversion");
} else if (type == 1) {
sheetName = PropertyUtils.getProperty("failConversion");
}
// 创建新的工作薄
SXSSFWorkbook wb = PoiUtils.newSXSSFWorkbook();
// 表头的样式
CellStyle cellStyle = wb.createCellStyle();
Font font = wb.createFont();
// 对齐
cellStyle.setAlignment(CellStyle.ALIGN_LEFT);
font.setBoldweight(Font.BOLDWEIGHT_NORMAL);
// 字体大小
font.setFontHeightInPoints((short) 12);
font.setFontName("宋体");
// 应用标题字体到标题样式
cellStyle.setFont(font);
// 设置单元格文本形式
DataFormat dataFormat = wb.createDataFormat();
cellStyle.setDataFormat(dataFormat.getFormat("@"));

// 表头的值
String headerValue = null;
// 单元格的值
String cellValue = null;
// 创建sheet
SXSSFSheet sheet = PoiUtils.newSXSSFSheet(wb, sheetName);
// 创建行表头
SXSSFRow headerRow = PoiUtils.newSXSSFRow(sheet, 0);
for (int i = 0, size = headers.size(); i < size; i++) {
headerValue = headers.get(i);
SXSSFCell cell = PoiUtils.newSXSSFCell(headerRow, i);
cell.setCellValue(headerValue);
cell.setCellStyle(cellStyle);
}

// 明细行数组
String[] bodys = null;
for (int i = 0; i < dataSize; i++) {
// 创建行数据
SXSSFRow bodyRow = PoiUtils.newSXSSFRow(sheet, i + 1);
bodys = datas.get(i);
for (int j = 0, len = bodys.length; j < len; j++) {
cellValue = bodys[j];
if (StringUtils.isBlank(cellValue)) {
// FIXME: 当值为""时,当前index的cell会失效,这里设置为null
cellValue = null;
}

// 创建列
SXSSFCell cell = PoiUtils.newSXSSFCell(bodyRow, j);
cell.setCellValue(cellValue);
cell.setCellType(SXSSFCell.CELL_TYPE_STRING);
}
}

wb.write(bufferedOutputStream);
LOG.info(String.format("excel处理完成,共生成数据 : %s行,耗时 : %s seconds.", dataSize,
(System.currentTimeMillis() - begin) / 1000.0));
} catch (Exception e) {
LOG.error("保存excel失败 : " + e.getMessage(), e);
} finally {
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
LOG.error("文件流关闭异常");
}
}
}
}

}
作者 east
Java 12月 7,2020

GAT1400视图库没有视图库账户对接遇到的坑

没有视图库账号对接视图库,把DeviceId没有写自己的视图库账号,错误写成要对接的视图库账号,一直返回“用户名错误”。后来才知道,只要按照GAT1400标准,20位的视图库账号可以自己按照标准进行编写一个,然后让要对接的视图库厂商后台配置一下。

作者 east
Java 12月 7,2020

用AOP写的日志管理工具

 使用AOP,将日志记录,异常报告等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。 
import com.alibaba.fastjson.JSONObject;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

@Component
@Aspect
public class LogManageUtil {
private static Logger logger = LoggerFactory.getLogger(LogManageUtil.class);
private static final String serverName = "cloudsearch";
private static final Integer showRows = 5;

@Pointcut("execution(* com.funny.services.web..*.*(..))")
public void controllerLogProxy() {

}

@Before("controllerLogProxy()")
public void beforeLogInfo(JoinPoint joinPoint) {
Object[] params = joinPoint.getArgs();
String paramStr = "";
for (int i = 0; i < params.length; i++) {
Object param = params[i];
if (i == 0){
logger.info(param.getClass().getSimpleName());
if(param.getClass().getSimpleName().equals("SessionRepositoryRequestWrapper")) {
paramStr = "该接口无参数";
}else{
paramStr += JSONObject.toJSONString(param);
}
}

else
paramStr += "," + JSONObject.toJSONString(param);
}
logger.info("service:[" + serverName + "] -- API:[" + getApiUrl() + "] 请求参数:" + paramStr);
}

@AfterReturning(pointcut = "controllerLogProxy()", returning = "result")
public void afterLogInfo(Object result) {
if (result instanceof ResultData) {
ResultData tmpRs = (ResultData) result;
ResultData rs = new ResultData();
BeanUtils.copyProperties(tmpRs, rs);
if (rs.getCode()==ResultStatus.SUCCESS_CODE) {
if (rs.getData() instanceof List) {
List<Object> list = (List) rs.getData();
List<Object> tmpList = new ArrayList<>();
if (list != null && (list.size() > showRows)) {
for (int i = 0; i < showRows; i++) {
tmpList.add(list.get(i));
}
Object more = "more...";
tmpList.add(more);
rs.setData(tmpList);
}
}
}
logger.info("service:[" + serverName + "] -- API:[" + getApiUrl() + "] 返回结果:" + JSONObject.toJSONString(rs));
}
}

public static String getApiUrl() {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String url = request.getRequestURL().toString();
int startIndex = url.indexOf(serverName);
String ApiUrl = url.substring(startIndex + serverName.length(), url.length());
return ApiUrl;
}

public static void serviceErrorPrint(Exception e) {
logger.error("service:[" + serverName + "] -- API:[" + getApiUrl() + "] 错误信息:" + e.getMessage());
}


}
作者 east

上一 1 … 70 71 72 … 93 下一个

关注公众号“大模型全栈程序员”回复“小程序”获取1000个小程序打包源码。回复”chatgpt”获取免注册可用chatgpt。回复“大数据”获取多本大数据电子书

标签

AIGC AI创作 bert chatgpt github GPT-3 gpt3 GTP-3 hive mysql O2O tensorflow UI控件 不含后台 交流 共享经济 出行 图像 地图定位 外卖 多媒体 娱乐 小程序 布局 带后台完整项目 开源项目 搜索 支付 效率 教育 日历 机器学习 深度学习 物流 用户系统 电商 画图 画布(canvas) 社交 签到 联网 读书 资讯 阅读 预订

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 解决gitlab配置Webhooks,提示 Invalid url given的问题
  • 如何在Chrome中设置启动时自动打开多个默认网页
  • spark内存溢出怎样区分是软件还是代码原因
  • MQTT完全解析和实践
  • 解决运行Selenium报错:self.driver = webdriver.Chrome(service=service) TypeError: __init__() got an unexpected keyword argument ‘service’
  • python 3.6使用mysql-connector-python报错:SyntaxError: future feature annotations is not defined
  • 详解Python当中的pip常用命令
  • AUTOSAR如何在多个供应商交付的配置中避免ARXML不兼容?
  • C++thread pool(线程池)设计应关注哪些扩展性问题?
  • 各类MCAL(Microcontroller Abstraction Layer)如何与AUTOSAR工具链解耦?

文章归档

  • 2025年10月
  • 2025年8月
  • 2025年7月
  • 2025年6月
  • 2025年5月
  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年1月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年7月
  • 2018年6月

分类目录

  • Android (73)
  • bug清单 (79)
  • C++ (34)
  • Fuchsia (15)
  • php (4)
  • python (45)
  • sklearn (1)
  • 云计算 (20)
  • 人工智能 (61)
    • chatgpt (21)
      • 提示词 (6)
    • Keras (1)
    • Tensorflow (3)
    • 大模型 (1)
    • 智能体 (4)
    • 深度学习 (14)
  • 储能 (44)
  • 前端 (5)
  • 大数据开发 (494)
    • CDH (6)
    • datax (4)
    • doris (31)
    • Elasticsearch (15)
    • Flink (79)
    • flume (7)
    • Hadoop (19)
    • Hbase (23)
    • Hive (41)
    • Impala (2)
    • Java (71)
    • Kafka (10)
    • neo4j (5)
    • shardingsphere (6)
    • solr (5)
    • Spark (100)
    • spring (11)
    • 数据仓库 (9)
    • 数据挖掘 (7)
    • 海豚调度器 (10)
    • 运维 (36)
      • Docker (3)
  • 小游戏代码 (1)
  • 小程序代码 (139)
    • O2O (16)
    • UI控件 (5)
    • 互联网类 (23)
    • 企业类 (6)
    • 地图定位 (9)
    • 多媒体 (6)
    • 工具类 (25)
    • 电商类 (22)
    • 社交 (7)
    • 行业软件 (7)
    • 资讯读书 (11)
  • 嵌入式 (71)
    • autosar (63)
    • RTOS (1)
    • 总线 (1)
  • 开发博客 (16)
    • Harmony (9)
  • 技术架构 (6)
  • 数据库 (32)
    • mongodb (1)
    • mysql (13)
    • pgsql (2)
    • redis (1)
    • tdengine (4)
  • 未分类 (8)
  • 程序员网赚 (20)
    • 广告联盟 (3)
    • 私域流量 (5)
    • 自媒体 (5)
  • 量化投资 (4)
  • 面试 (14)

功能

  • 登录
  • 文章RSS
  • 评论RSS
  • WordPress.org

All Rights Reserved by Gitweixin.本站收集网友上传代码, 如有侵犯版权,请发邮件联系yiyuyos@gmail.com删除.