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

分类归档大数据开发

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

  • 首页   /  
  • 分类归档: "大数据开发"
  • ( 页面32 )
bug清单, Java 1月 4,2021

SpringBoot 接口返回的 JSON 数据的时间与数据存储时间有误差

在做一个项目,接入数据存到数据库,在图层上展示今天、昨天的数据。但是发现展示的时间有误差。

 
使用MySQL57,(程序中打印的时间字段)查询出的时间字段总是和数据库存储的相差两个小时。
最后是通过修改数据库连接解决了这个问题。添加了下面这个属性。
&serverTimezone=Asia/Shanghai
接着又出现问题了。
默认情况下使用 @ResponseBody ,项目返回的JSON数据,返回对象会自动转为JSON格式,但是对象中的日期格式Date字段转换的时候相差了八小时,程序内打印时间没有问题,如果将 Date 改为 String 类型的话,也不会出现这种情况了。
所以问题应该出在返回结果格式化为JSON的这个过程中。
原因是spring转json的默认实现jackson中会根据时区去转换时间,而jackson的默认时区跟国内应该是相差8小时,所以在时间换算上自动减去了8小时。
可以通过jackson 的注解 @JsonFormat 解决问题
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss" ,timezone = "GMT+8") private Date createTime; @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss" ,timezone = "GMT+8") private Date updateTime;
也可以在 SpringBoot 配置文件中统一配置,推荐使用这种方式:
  spring.jackson.date-format=yyyy-MM-dd HH:mm:ss   spring.jackson.time-zone=GTM+8
作者 east
bug清单, Java 1月 4,2021

springboot内嵌tomcat文件上传路径不存在bug解决

在线上环境容易出现一些开发环境没遇到的问题。就像这个问题,springboot内嵌tomcat,上传文件时会存放到tomcat临时文件目录(停止时删除/重启时新建),如:/tmp/tomcat.1046709481715876128.17301/work/Tomcat/localhost/cms

可知文件保存在/tmp目录下,/tmp目录在centos下会定时清理,大约10天未使用将会删除目录,(当tomcat未重启,但centos删除相应目录,tomcat获取相应目录却获取不到会报错)

 
解决方案:
一 配置multipartFile上传路径(推荐)
1.application.properties 文件中添加
spring.http.multipart.location=${tmp.file.path} 注意:tmp.file.path 如果不存在,spring会认为是相对路径,对应根路径是tomcat临时文件目录 2
2.配置相应bean
/** * 文件上传临时路径 */ @Bean MultipartConfigElement multipartConfigElement() { MultipartConfigFactory factory = new MultipartConfigFactory(); factory.setLocation("/data/ops/app/cms/cache"); return factor 246810121416
二 修改tomcat临时存放文件位置(不建议)
application.properties 文件中添加 (此方法会讲所有tomcat临时文件放在指定目录,新目录没有定时清理功能,不建议)
server.tomcat.basedir=/data/ops/app/cms/cache 2
三 修改centos定时清理功能(不建议)
vim /etc/cron.daily/tmpwatch
#! /bin/sh flags=-umc /usr/sbin/tmpwatch "$flags" -x /tmp/.X11-unix -x /tmp/.XIM-unix \ -x /tmp/.font-unix -x /tmp/.ICE-unix -x /tmp/.Test-unix \ -X '/tmp/hsperfdata_*' 10d /tmp \ -X '/tmp/tomcat.*' 10d /tmp /usr/sbin/tmpwatch "$flags" 30d /var/tmp for d in /var/{cache/man,catman}/{cat?,X11R6/cat?,local/cat?}; do if [ -d "$d" ]; then /usr/sbin/tmpwatch "$flags" -f 30d "$d" fi done 24681012141618202224
其中添加一行
-X '/tmp/tomcat.*' 10d /tmp
作者 east
Java 1月 4,2021

springboot使用 @scheduled 多任务并发

springboot的@scheduled,并不是默认并发的,想给方法添加@Scheduled注解,实现两个定时任务。可是运行发现,两个task并没有并发执行,而是执行完一个task才会执行另外一个。

要 给类添加注解@EnableAsync,并给方法添加注解@Async。

 
@Component
@Configurable
@EnableScheduling
@EnableAsync
public class DemoTask {
@Async
@Scheduled(cron = "0/5 * *  * * ? ")
public void startSchedule() {
System.out.println("===========1=>");
try {
for(int i=1;i<=10;i++){
System.out.println("=1==>"+i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
 
@Async
@Scheduled(cron = "0/5 * *  * * ? ")
public void startSchedule2() {
for(int i=1;i<=10;i++){
System.out.println("=2==>"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

在这个类或启动类BootApplication添加@EnableScheduling标注

作者 east
bug清单, Java 1月 4,2021

Spring Boot Maven项目使用SystemPath引用线上部署遇到的问题

使用了第三方Jar包,最先考虑的是不使用Maven仓库,便于离线开发。首先采用了方案:

 <dependency>
        <groupId>com.tievd.third</groupId>
        <artifactId>arcvideo</artifactId>
        <version>1.0</version>
        <scope>system</scope>
        <systemPath>${basedir}/lib/face-api-1.0.jar</systemPath>
    </dependency>

但很多人讲到这里就没讲了,你会发现在IDE里会运行的非常好,一旦部署在线上环境,就会出现莫名其妙的问题。比如我遇到的不会抛异常,会一直卡在对象创建上。后来一直找不到问题出现在哪里,就改用了私服,发现问题解决,所以定位在问题肯定出现在打包上:第一步:确认解压之前的Jar包发现确实没有把第三方包打入进去第二步:在build节点加入一下语句使包正确的导入

   <resources>
            <resource>
                <directory>${project.basedir}/lib</directory>
                <targetPath>BOOT-INF/lib/</targetPath>
                        <includes>
                           <include>**/*.jar</include>
                        </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <targetPath>BOOT-INF/classes/</targetPath>
            </resource>
</resources>

  • 重新打包发现可以在线上环境正常部署了。
作者 east
Spark 1月 4,2021

Idea配置Scala开发环境注意事项

使用maven方式,注意切注意spark与scala有版本对应关系, 详情参考Spark官网相关说明:https://spark.apache.org/docs/latest/index.htmlscala版本还要跟工程配置Library添加的Scala版本一致,才不会出现“Cannot find Main Class”在pom.xml中添加maven 依赖包时,我就发现不管是否用了翻墙,下载速度都好慢,就1M的东西能下半天,很是苦恼,于是到网上搜资料,然后让我查到了。说是使用阿里的maven镜像就可以了。我于是亲自试了下,速度快的飞起!!!右键项目选中maven选项,然后选择“open settings.xml”或者 “create settings.xml”,然后把如下代码粘贴进去就可以了。重启IDE,感受速度飞起来的感觉吧!!!

<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <mirrors>
        <!-- mirror
         | Specifies a repository mirror site to use instead of a given repository. The repository that
         | this mirror serves has an ID that matches the mirrorOf element of this mirror. IDs are used
         | for inheritance and direct lookup purposes, and must be unique across the set of mirrors.
         |
        <mirror>
          <id>mirrorId</id>
          <mirrorOf>repositoryId</mirrorOf>
          <name>Human Readable Name for this Mirror.</name>
          <url>http://my.repository.com/repo/path</url>
        </mirror>
         -->

        <mirror>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <mirrorOf>central</mirrorOf>
        </mirror>

        <mirror>
            <id>uk</id>
            <mirrorOf>central</mirrorOf>
            <name>Human Readable Name for this Mirror.</name>
            <url>http://uk.maven.org/maven2/</url>
        </mirror>

        <mirror>
            <id>CN</id>
            <name>OSChina Central</name>
            <url>http://maven.oschina.net/content/groups/public/</url>
            <mirrorOf>central</mirrorOf>
        </mirror>

        <mirror>
            <id>nexus</id>
            <name>internal nexus repository</name>
            <!-- <url>http://192.168.1.100:8081/nexus/content/groups/public/</url>-->
            <url>http://repo.maven.apache.org/maven2</url>
            <mirrorOf>central</mirrorOf>
        </mirror>

    </mirrors>
</settings>
作者 east
数据仓库 1月 3,2021

数据采集与同步经验之谈

根据埋点位置,可分为客户端埋点、服务端埋点,实际各有利弊,比如服务端埋点对后台请求的用户无法捕获,而客户端埋点可能会由于用户的环境问题存在数据丢包,客户端可能无法获取全部的数据等,所以在无特殊情况下,建议采用服务端埋点方案。

埋点要把一切用户操作行为都看做事件,覆盖事件的核心要素,包括人、时间、事、地点、方式。

埋点的数据格式,要确保灵活、可扩展性,上报数据采用json格式,不要太深的嵌套。

作者 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
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

上一 1 … 31 32 33 … 41 下一个

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

标签

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

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 如何在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工具链解耦?
  • 如何设计AUTOSAR中的“域控制器”以支持未来扩展?

文章归档

  • 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)
  • 大数据开发 (491)
    • CDH (6)
    • datax (4)
    • doris (31)
    • Elasticsearch (15)
    • Flink (78)
    • 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)
    • 运维 (34)
      • 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)
  • 未分类 (7)
  • 程序员网赚 (20)
    • 广告联盟 (3)
    • 私域流量 (5)
    • 自媒体 (5)
  • 量化投资 (4)
  • 面试 (14)

功能

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

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