package
com.cucpay.tradeportal.util;
import
java.io.UnsupportedEncodingException;
import
java.lang.reflect.Field;
import
java.net.URLDecoder;
import
java.net.URLEncoder;
import
java.nio.charset.CharacterCodingException;
import
java.nio.charset.Charset;
import
java.security.MessageDigest;
import
java.security.NoSuchAlgorithmException;
import
java.text.SimpleDateFormat;
import
java.util.ArrayList;
import
java.util.Arrays;
import
java.util.Calendar;
import
java.util.Collections;
import
java.util.Date;
import
java.util.List;
import
java.util.Map;
import
java.util.UUID;
import
org.apache.commons.lang.StringUtils;
import
org.apache.mina.core.buffer.IoBuffer;
/**
* 交易前置系统专用工具类
* @create Aug 15, 2012 12:16:49 PM
* @update Sep 27, 2012 3:07:09 PM
* @author 玄玉<http://blog.youkuaiyun.com/jadyer>
* @version v2.0
* @history v1.7.2-->新增<code>getHexSign()</code>通过指定算法签名字符串方法
* @history v1.7.2-->新增<code>getString()</code>字节数组转为字符串方法
* @history v1.7.3-->修改<code>getSysJournalNo()</code>实现细节为<code>java.util.UUID.randomUUID()</code>
* @history v1.7.4-->新增<code>getHexSign()</code>根据指定的签名密钥和算法签名Map<String,String>
* @history v1.7.5-->新增<code>getStringSimple()</code>获取一个字符串的简明效果,返回的字符串格式类似于"abcd***hijk"
* @history v2.0-->局部的StringBuffer一律StringBuilder之(本思路提示自坦克<captmjc@gmail.com>)
*/
public
class TradePortalUtil {
private
TradePortalUtil(){}
/**
* 获取系统流水号
* @see 若欲指定返回值的长度or是否全由数字组成等,you can call {@link TradePortalUtil#getSysJournalNo(int, boolean)}
* @return 长度为20的全数字
*/
public
static String getSysJournalNo(){
return
getSysJournalNo(20,
true);
}
/**
* 获取系统流水号
* @param length 指定流水号长度
* @param toNumber 指定流水号是否全由数字组成
*/
public
static String getSysJournalNo(int
length, boolean
isNumber){
String uuid = UUID.randomUUID().toString().replaceAll("-",
"");
if(uuid.length() > length){
uuid = uuid.substring(0, length);
}else
if(uuid.length() < length){
for(int
i=0; i<length-uuid.length(); i++){
uuid = uuid + Math.round(Math.random()*9);
}
}
if(isNumber){
return
uuid.replaceAll("a",
"1").replaceAll("b",
"2").replaceAll("c",
"3").replaceAll("d",
"4").replaceAll("e",
"5").replaceAll("f",
"6");
}else{
return
uuid;
}
}
/**
* 判断输入的字符串参数是否为空
* @return boolean 空则返回true,非空则flase
*/
public
static boolean
isEmpty(String input) {
return
null==input ||
0==input.length() ||
0==input.replaceAll("\\s",
"").length();
}
/**
* 判断输入的字节数组是否为空
* @return boolean 空则返回true,非空则flase
*/
public
static boolean
isEmpty(byte[] bytes){
return
null==bytes ||
0==bytes.length;
}
/**
* 从org.apache.mina.core.buffer.IoBuffer中读取字符串
* @see 该方法默认以GBK解码
* @see 若想自己指定字符集,可以使用<code>getStringFromIoBuffer(IoBuffer buffer, int size, String charset)</code>方法
* @param size 所要读取的字节数
*/
public
static String getStringFromIoBuffer(IoBuffer buffer,
int size){
return
getStringFromIoBuffer(buffer, size, "GBK");
}
/**
* 从org.apache.mina.core.buffer.IoBuffer中读取字符串
* @param size 所要读取的字节数
* @param charset 解码的字符集
*/
public
static String getStringFromIoBuffer(IoBuffer buffer,
int size, String charset){
String result =
null;
try
{
result = buffer.getString(size, Charset.forName(charset).newDecoder());
}
catch (CharacterCodingException e) {
LogUtil.getLogger().error("字符解码异常,自动切换第二种解码方式,本次的堆栈信息如下", e);
try
{
result =
new String(buffer.array(), charset);
}
catch (UnsupportedEncodingException ee) {
LogUtil.getLogger().error("字符解码异常,系统不支持该字符集["
+ charset + "],本次的堆栈信息如下", ee);
}
}
return
result;
}
/**
* 获取实体类中的属性
* @see 本方法用到了反射,其适用于所有的属性类型均为byte[]的JavaBean
* @return String field11=value11 field22=value22 field33=value33
*/
public
static String getStringFromObjectForByte(Object obj){
StringBuilder sb =
new StringBuilder();
sb.append(obj.getClass().getName()).append("@").append(obj.hashCode()).append("[");
for(Field field : obj.getClass().getDeclaredFields()){
String methodName =
"get" + StringUtils.capitalize(field.getName());
Object fieldValue =
null;
try{
fieldValue = obj.getClass().getDeclaredMethod(methodName).invoke(obj);
}catch(Exception e){
sb.append("\n").append(field.getName()).append("=UnKnown");
continue;
}
if(fieldValue ==
null){
sb.append("\n").append(field.getName()).append("=null");
}else{
sb.append("\n").append(field.getName()).append("=").append(new
String((byte[])fieldValue));
}
}
return
sb.append("\n]").toString();
}
/**
* 获取Map中的属性
* @see 由于Map.toString()打印出来的参数值对,是横着一排的...参数多的时候,不便于查看各参数值
* @see 故此仿照commons-lang.jar中的ReflectionToStringBuilder.toString()编写了本方法
* @return String key11=value11 \n key22=value22 \n key33=value33 \n......
*/
public
static String getStringFromMap(Map<String, String> map){
StringBuilder sb =
new StringBuilder();
sb.append(map.getClass().getName()).append("@").append(map.hashCode()).append("[");
for(Map.Entry<String,String> entry : map.entrySet()){
sb.append("\n").append(entry.getKey()).append("=").append(entry.getValue());
}
return
sb.append("\n]").toString();
}
/**
* 获取Map中的属性
* @see 该方法的参数适用于打印Map<String, byte[]>的情况
* @return String key11=value11 \n key22=value22 \n key33=value33 \n......
*/
public
static String getStringFromMapForByte(Map<String,
byte[]> map){
StringBuilder sb =
new StringBuilder();
sb.append(map.getClass().getName()).append("@").append(map.hashCode()).append("[");
for(Map.Entry<String,byte[]> entry : map.entrySet()){
sb.append("\n").append(entry.getKey()).append("=").append(new
String(entry.getValue()));
}
return
sb.append("\n]").toString();
}
/**
* 获取Map中的属性
* @see 该方法的参数适用于打印Map<String, Object>的情况
* @return String key11=value11 \n key22=value22 \n key33=value33 \n......
*/
public
static String getStringFromMapForObject(Map<String, Object> map){
StringBuilder sb =
new StringBuilder();
sb.append(map.getClass().getName()).append("@").append(map.hashCode()).append("[");
for(Map.Entry<String,Object> entry : map.entrySet()){
sb.append("\n").append(entry.getKey()).append("=").append(entry.getValue().toString());
}
return
sb.append("\n]").toString();
}
/**
* 金额元转分
* @see 注意:该方法可处理贰仟万以内的金额,且若有小数位,则不限小数位的长度
* @see 注意:如果你的金额达到了贰仟万以上,则不推荐使用该方法,否则计算出来的结果会令人大吃一惊
* @param amount 金额的元进制字符串
* @return String 金额的分进制字符串
*/
public
static String moneyYuanToFen(String amount){
if(isEmpty(amount)){
return
amount;
}
if(-1
== amount.indexOf(".")){
return
Integer.parseInt(amount) * 100
+ "";
}
int
money_fen = Integer.parseInt(amount.substring(0, amount.indexOf("."))) *
100;
String pointBehind = (amount.substring(amount.indexOf(".") +
1));
if(pointBehind.length() ==
1){
return
money_fen + Integer.parseInt(pointBehind)*10
+ "";
}
int
pointString_1 = Integer.parseInt(pointBehind.substring(0,
1));
int
pointString_2 = Integer.parseInt(pointBehind.substring(1,
2));
if(pointString_1 ==
0){
return
money_fen + pointString_2 + "";
}else{
return
money_fen + pointString_1*10
+ pointString_2 + "";
}
}
/**
* 金额元转分
* @see 该方法会将金额中小数点后面的数值,四舍五入后只保留两位....如12.345-->12.35
* @see 注意:该方法可处理贰仟万以内的金额
* @see 注意:如果你的金额达到了贰仟万以上,则非常不建议使用该方法,否则计算出来的结果会令人大吃一惊
* @param amount 金额的元进制字符串
* @return String 金额的分进制字符串
*/
public
static String moneyYuanToFenByRound(String amount){
if(isEmpty(amount)){
return
amount;
}
if(-1
== amount.indexOf(".")){
return
Integer.parseInt(amount) * 100
+ "";
}
int
money_fen = Integer.parseInt(amount.substring(0, amount.indexOf("."))) *
100;
String pointBehind = (amount.substring(amount.indexOf(".") +
1));
if(pointBehind.length() ==
1){
return
money_fen + Integer.parseInt(pointBehind)*10
+ "";
}
int
pointString_1 = Integer.parseInt(pointBehind.substring(0,
1));
int
pointString_2 = Integer.parseInt(pointBehind.substring(1,
2));
if(pointBehind.length() >
2){
int
pointString_3 = Integer.parseInt(pointBehind.substring(2,
3));
if(pointString_3 >=
5){
if(pointString_2 ==
9){
if(pointString_1 ==
9){
money_fen = money_fen +
100;
pointString_1 =
0;
pointString_2 =
0;
}else{
pointString_1 = pointString_1 +
1;
pointString_2 =
0;
}
}else{
pointString_2 = pointString_2 +
1;
}
}
}
if(pointString_1 ==
0){
return
money_fen + pointString_2 + "";
}else{
return
money_fen + pointString_1*10
+ pointString_2 + "";
}
}
/**
* 金额分转元
* @see 注意:如果传入的参数中含小数点,则直接原样返回
* @see 该方法返回的金额字符串格式为<code>00.00</code>,其整数位有且至少有一个,小数位有且长度固定为2
* @param amount 金额的分进制字符串
* @return String 金额的元进制字符串
*/
public
static String moneyFenToYuan(String amount){
if(isEmpty(amount)){
return
amount;
}
if(amount.indexOf(".") > -1){
return
amount;
}
if(amount.length() ==
1){
return
"0.0" + amount;
}else
if(amount.length() ==
2){
return
"0." + amount;
}else{
return
amount.substring(0, amount.length()-2) +
"." + amount.substring(amount.length()-2);
}
}
/**
* 字节数组转为字符串
* @see 该方法默认以ISO-8859-1转码
* @see 若想自己指定字符集,可以使用<code>getString(byte[] data, String charset)</code>方法
*/
public
static String getString(byte[] data){
return
getString(data, "ISO-8859-1");
}
/**
* 字节数组转为字符串
* @see 如果系统不支持所传入的<code>charset</code>字符集,则按照系统默认字符集进行转换
*/
public
static String getString(byte[] data, String charset){
if(isEmpty(data)){
return
"";
}
if(isEmpty(charset)){
return
new String(data);
}
try
{
return
new String(data, charset);
}
catch (UnsupportedEncodingException e) {
LogUtil.getLogger().error("将byte数组["
+ data + "]转为String时发生异常:系统不支持该字符集["
+ charset + "]");
return
new String(data);
}
}
/**
* 获取一个字符串的简明效果
* @return String 返回的字符串格式类似于"abcd***hijk"
*/
public
static String getStringSimple(String data){
return
data.substring(0,4) +
"***" + data.substring(data.length()-4);
}
/**
* 字符串转为字节数组
* @see 该方法默认以ISO-8859-1转码
* @see 若想自己指定字符集,可以使用<code>getBytes(String str, String charset)</code>方法
*/
public
static byte[] getBytes(String data){
return
getBytes(data, "ISO-8859-1");
}
/**
* 字符串转为字节数组
* @see 如果系统不支持所传入的<code>charset</code>字符集,则按照系统默认字符集进行转换
*/
public
static byte[] getBytes(String data, String charset){
data = (data==null
? ""
: data);
if(isEmpty(charset)){
return
data.getBytes();
}
try
{
return
data.getBytes(charset);
}
catch (UnsupportedEncodingException e) {
LogUtil.getLogger().error("将字符串["
+ data + "]转为byte[]时发生异常:系统不支持该字符集["
+ charset + "]");
return
data.getBytes();
}
}
/**
* 根据指定的签名密钥和算法签名Map<String,String>
* @see 方法内部首先会过滤Map<String,String>参数中的部分键值对
* @see 过滤规则:移除键名为"cert","hmac","signMsg"或者键值为null或者键值长度为零的键值对
* @see 过滤结果:过滤完Map<String,String>后会产生一个字符串,其格式为[key11=value11|key22=value22|key33=value33]
* @see And the calls {@link TradePortalUtil#getHexSign(String,String,String,boolean)}进行签名
* @param param 待签名的Map<String,String>
* @param charset 签名时转码用到的字符集
* @param algorithm 签名时所使用的算法,从业务上看,目前其可传入两个值:MD5,SHA-1
* @param signKey 签名用到的密钥
* @return String algorithm digest as a lowerCase hex string
*/
public
static String getHexSign(Map<String, String> param, String charset, String algorithm, String signKey){
StringBuilder sb =
new StringBuilder();
List<String> keys =
new ArrayList<String>(param.keySet());
Collections.sort(keys);
for(int
i=0; i<keys.size(); i++){
String key = keys.get(i);
String value = param.get(key);
if(key.equalsIgnoreCase("cert") || key.equalsIgnoreCase("hmac")
|| key.equalsIgnoreCase("signMsg") || value==null
|| value.length()==0){
continue;
}
sb.append(key).append("=").append(value).append("|");
}
sb.append("key=").append(signKey);
return
getHexSign(sb.toString(), charset, algorithm, true);
}
/**
* 通过指定算法签名字符串
* @see Calculates the algorithm digest and returns the value as a hex string
* @see If system dosen't support this <code>algorithm</code>, return "" not null
* @see It will Calls {@link TradePortalUtil#getBytes(String str, String charset)}
* @see 若系统不支持<code>charset</code>字符集,则按照系统默认字符集进行转换
* @see 若系统不支持<code>algorithm</code>算法,则直接返回""空字符串
* @see 另外,commons-codec.jar提供的DigestUtils.md5Hex(String data)与本方法getHexSign(data, "UTF-8", "MD5", false)效果相同
* @param data Data to digest
* @param charset 字符串转码为byte[]时使用的字符集
* @param algorithm 目前其有效值为<code>MD5,SHA,SHA1,SHA-1,SHA-256,SHA-384,SHA-512</code>
* @param toLowerCase 指定是否返回小写形式的十六进制字符串
* @return String algorithm digest as a lowerCase hex string
*/
public
static String getHexSign(String data, String charset, String algorithm,
boolean toLowerCase){
char[] DIGITS_LOWER = {'0',
'1', '2',
'3', '4',
'5', '6',
'7', '8',
'9', 'a',
'b', 'c',
'd', 'e',
'f'};
char[] DIGITS_UPPER = {'0',
'1', '2',
'3', '4',
'5', '6',
'7', '8',
'9', 'A',
'B', 'C',
'D', 'E',
'F'};
char[] DIGITS = toLowerCase ? DIGITS_LOWER : DIGITS_UPPER;
byte[] dataBytes = getBytes(data, charset);
byte[] algorithmData =
null;
try
{
algorithmData = MessageDigest.getInstance(algorithm).digest(dataBytes);
}
catch (NoSuchAlgorithmException e) {
LogUtil.getLogger().error("签名字符串["
+ data + "]时发生异常:System doesn't support this algorithm["
+ algorithm + "]");
return
"";
}
char[] respData =
new char[algorithmData.length <<
1];
for(int
i=0,j=0; i<algorithmData.length; i++){
respData[j++] = DIGITS[(0xF0
& algorithmData[i]) >>> 4];
respData[j++] = DIGITS[0x0F
& algorithmData[i]];
}
return
new String(respData);
}
/**
* 字符编码
* @see 该方法默认会以UTF-8编码字符串
* @see 若想自己指定字符集,可以使用<code>encode(String chinese, String charset)</code>方法
*/
public
static String encode(String chinese){
return
encode(chinese, "UTF-8");
}
/**
* 字符编码
* @see 该方法通常用于对中文进行编码
* @see 若系统不支持指定的编码字符集,则直接将<code>chinese</code>原样返回
*/
public
static String encode(String chinese, String charset){
chinese = (chinese==null
? ""
: chinese);
try
{
return
URLEncoder.encode(chinese, charset);
}
catch (UnsupportedEncodingException e) {
LogUtil.getLogger().error("编码字符串["
+ chinese + "]时发生异常:系统不支持该字符集["
+ charset + "]");
return
chinese;
}
}
/**
* 字符解码
* @see 该方法默认会以UTF-8解码字符串
* @see 若想自己指定字符集,可以使用<code>decode(String chinese, String charset)</code>方法
*/
public
static String decode(String chinese){
return
decode(chinese, "UTF-8");
}
/**
* 字符解码
* @see 该方法通常用于对中文进行解码
* @see 若系统不支持指定的解码字符集,则直接将<code>chinese</code>原样返回
*/
public
static String decode(String chinese, String charset){
chinese = (chinese==null
? ""
: chinese);
try
{
return
URLDecoder.decode(chinese, charset);
}
catch (UnsupportedEncodingException e) {
LogUtil.getLogger().error("解码字符串["
+ chinese + "]时发生异常:系统不支持该字符集["
+ charset + "]");
return
chinese;
}
}
/**
* 字符串右补空格
* @see 该方法默认采用空格(其ASCII码为32)来右补字符
* @see 若想自己指定所补字符,可以使用<code>rightPadForByte(String str, int size, int padStrByASCII)</code>方法
*/
public
static String rightPadForByte(String str,
int size){
return
rightPadForByte(str, size, 32);
}
/**
* 字符串右补字符
* @see 若str对应的byte[]长度不小于size,则按照size截取str对应的byte[],而非原样返回str
* @see 所以size参数很关键..事实上之所以这么处理,是由于支付处理系统接口文档规定了字段的最大长度
* @see 若对普通字符串进行右补字符,建议org.apache.commons.lang.StringUtils.rightPad(...)
* @param size 该参数指的不是字符串长度,而是字符串所对应的byte[]长度
* @param padStrByASCII 该值为所补字符的ASCII码,如32表示空格,48表示0,64表示@等
*/
public
static String rightPadForByte(String str,
int size,
int padStrByASCII){
byte[] srcByte = str.getBytes();
byte[] destByte =
null;
if(srcByte.length >= size){
destByte = Arrays.copyOf(srcByte, size);
}else{
destByte = Arrays.copyOf(srcByte, size);
Arrays.fill(destByte, srcByte.length, size, (byte)padStrByASCII);
}
return
new String(destByte);
}
/**
* 字符串左补空格
* @see 该方法默认采用空格(其ASCII码为32)来左补字符
* @see 若想自己指定所补字符,可以使用<code>leftPadForByte(String str, int size, int padStrByASCII)</code>方法
*/
public
static String leftPadForByte(String str,
int size){
return
leftPadForByte(str, size, 32);
}
/**
* 字符串左补字符
* @see 若str对应的byte[]长度不小于size,则按照size截取str对应的byte[],而非原样返回str
* @see 所以size参数很关键..事实上之所以这么处理,是由于支付处理系统接口文档规定了字段的最大长度
* @param padStrByASCII 该值为所补字符的ASCII码,如32表示空格,48表示0,64表示@等
*/
public
static String leftPadForByte(String str,
int size,
int padStrByASCII){
byte[] srcByte = str.getBytes();
byte[] destByte =
new byte[size];
Arrays.fill(destByte, (byte)padStrByASCII);
if(srcByte.length >= size){
System.arraycopy(srcByte,
0, destByte,
0, size);
}else{
System.arraycopy(srcByte,
0, destByte, size-srcByte.length, srcByte.length);
}
return
new String(destByte);
}
/**
* 获取前一天日期yyyyMMdd
* @see 经测试,针对闰年02月份或跨年等情况,该代码仍有效。测试代码如下
* @see calendar.set(Calendar.YEAR, 2013);
* @see calendar.set(Calendar.MONTH, 0);
* @see calendar.set(Calendar.DATE, 1);
* @see 测试时,将其放到<code>calendar.add(Calendar.DATE, -1);</code>前面即可
* @return 返回的日期格式为yyyyMMdd
*/
public
static String getYestoday(){
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DATE, -1);
return
new SimpleDateFormat("yyyyMMdd").format(calendar.getTime());
}
/**
* 获取当前的日期yyyyMMdd
*/
public
static String getCurrentDate(){
return
new SimpleDateFormat("yyyyMMdd").format(new
Date());
}
/**
* 获取当前的时间yyyyMMddHHmmss
*/
public
static String getCurrentTime(){
return
new SimpleDateFormat("yyyyMMddHHmmss").format(new
Date());
}
/**
* HTML字符转义
* @see 对输入参数中的敏感字符进行过滤替换,防止用户利用JavaScript等方式输入恶意代码
* @see String input = <img src='http://t1.baidu.com/it/fm=0&gp=0.jpg'/>
* @see HtmlUtils.htmlEscape(input); //from spring.jar
* @see StringEscapeUtils.escapeHtml(input); //from commons-lang.jar
* @see 尽管Spring和Apache都提供了字符转义的方法,但Apache的StringEscapeUtils功能要更强大一些
* @see StringEscapeUtils提供了对HTML,Java,JavaScript,SQL,XML等字符的转义和反转义
* @see 但二者在转义HTML字符时,都不会对单引号和空格进行转义,而本方法则提供了对它们的转义
* @return String 过滤后的字符串
*/
public
static String htmlEscape(String input) {
if(isEmpty(input)){
return
input;
}
input = input.replaceAll("&",
"&");
input = input.replaceAll("<",
"<");
input = input.replaceAll(">",
">");
input = input.replaceAll(" ",
" ");
input = input.replaceAll("'",
"'");
input = input.replaceAll("\"",
""");
input = input.replaceAll("\n",
"<br/>");
return
input;
}
}