Java后端支付大杂烩之core.common、utils(异常,ID,日期,字符串等工具类)(一)
PayMap
PayMap是一个使用Java语言集成三方支付的小Demo,现已集成支付宝(国内、国际、移动端、PC端)、微信、银联(ACP、UPOP)、光大(网关、网页)、邮政支付,采用的技术栈为:SpringMVC+Spring+MyBatis+Shiro+RabbitMQ+Redis。
特性
- 支持前面提到的各种**支付
- 支付请求调用支持HTTP和异步MQ
- 控制层统一异常处理
- LogBack日志记录
- Redis缓存机制
- Shiro安全机制
- MyBatis代码自动生成
- HTTP请求日志记录
- RESTful APIs
说明
- 1、本文项目来自Martin404,自己只是临摹大佬的项目。
- 2、重要的是学习过程,而不是结果。但,结果同样重要,加油。gogogo。
- 3、框架搭建就略过了。配置文件太多。遇到的时候贴出来。也收藏起来,留着备用。
- 4、Gist、Insight.io for GitHub必备吧,划词翻译不懂的单词划一划。
- 5、在IDEA中我会注重代码规范,但是这里为了节约地方,能省的就省略了。还请谅解。
- 6、代码提交到这里了GitHub。根据提交记录找自己想要的类库。
- 7、重要的在后面,一切都只只是刚刚开始(希望不要被屏蔽)!!gogogo
1、核心包~common
因为没有文档,只能根据自己之前的经验。先把必备的配置文件,包弄好。项目跑不起来,没关系。重要的是学习,先从核心包,common开始学习。配置文件就不贴了。需要的可以到GitHub去找。
我们先整体看一下结构
1、就先从异常开始吧,首先是定义,BaseException。为什么要这么定义呢?不知道大家有没有见过BaseDao、BaseAction。主要原因是为了方便扩展,父类不能实现的,在子类中加强。
回过头来我们在看下整体继承关系图,Throwable应该还有个Error错误。两者的区别在于后者是不可恢复的。这里的是运行时异常。 还有一种区分是受检查和非检查的。比如数据库连接关闭就是受检查异常,数组越界异常是非检查的。还有try{}catch{}finally{}这里不展开了。在Spring框架中可以受检查的包装成非受检查的。而且可明确提出错误信息。
public class BaseException extends RuntimeException { public BaseException(String message) { super(message,new Throwable(message)); } public BaseException(Throwable cause) { super(cause); } public BaseException(String message,Throwable cause) { super(message,cause); } }
2、接下来就是定义它的子类。
/** * Created by guo on 3/2/2018. * 系统类异常 */ public class SystemException extends BaseException { //实现和BaseException一样,构造方法名字换下。为了空间就不展示了。 } --------------------------------------------------------- /** * 业务异常的自定义封装类 */ public class BusinessException extends BaseException { //实现和BaseException一样,为了空间就不展示了。 } --------------------------------------------------------- /** * 数据库异常 */ public class DBException extends BaseException { }
3、在看两个类,验证信息异常。后者估计大家用得着。我不会告诉你们Gist了。什么?你不懂?快去GItHub看看。收藏代码的好地方。
/** * 验证异常,用于封装 */ public class ValidationError { private String objectName; private String fieldName; private String defaultMessage; // Constructor 、Setter 、Getter 、ToString 略。为了节约地方。 } ---------------------------------------------------------------- /** * Created by guo on 3/2/2018. * 异常返回码 */ public enum ResultCode { /** * 成功. ErrorCode : 0 */ SUCCESS("0", "成功"), /** * 未知异常. ErrorCode : 01 */ UnknownException("01", "未知异常"), /** * 系统异常. ErrorCode : 02 */ SystemException("02", "系统异常"), /** * 业务错误. ErrorCode : 03 */ BusinessException("03", "业务错误"), /** * 提示级错误. ErrorCode : 04 */ InfoException("04", "提示级错误"), /** * 数据库操作异常. ErrorCode : 020001 */ DBException("020001", "数据库操作异常"), /** * 参数验证错误. ErrorCode : 040001 */ ParamException("040001", "参数验证错误"), SystemMaintainException("11", "系统正在维护"); private String _code; private String _msg; // Constructor、Getter略 public static ResultCode getByCode(String code) { for (ResultCode ec : ResultCode.values()) { if (ec.getCode().equals(code)) { return ec; } } return null; } }
4、接下来看一些默认设置。
public class ActionConstants { /** * 默认值 - 执行时失败时ReturnContext的ReturnMsg */ public static final String DEFAULT_FAILED_RETURNMSG = "执行失败"; /** * 默认值key - 执行成功时ReturnContext的Returning */ public static final String DEFAULT_SUCCESS_RETURNMSG ="执行成功"; } ------------------------------------------------------------------------- /** * 从SpringApplicationContext中设置的系统参数 */ public class SystemConfig { //系统默认的游客用户名 private static String guestUsername = ""; private SystemConfig() {} //注意这里被私有化了。 public static void setGuestUsername(String guestUsername) { SystemConfig.guestUsername = guestUsername; } } ------------------- -------注意bean------------------------------------ <bean id="systemConfig" class="com.guo.core.common.constant.SystemConfig"> <property name="guestUsername"> <value>${shiro.guest.username}</value> </property> </bean>
5、这个也很重要,局部刷新。首先看下实现了Serializable,为什么呢?因为它要在网络中传输,所以需要序列成二进制格式的。还有当我们需要网络上的一个对象时,可以进行反序列化,在创建对象。或者想把内存中的对象保存在数据库中或者一个文件中。这里涉及ObjectOutputStream类的writeObject()方法、ObjectInputStream类的writeObject()方法。还有需要serialvUID,但不是必须的,最好加上。
/** * AJAX调用返回对象 */ public class AjaxResult implements Serializable { //请求结果是否为成功 private String ErrorCode = ResultCode.SUCCESS.getCode(); //请求返回信息 private String Message = ActionConstants.DEFAULT_SUCCESS_RETURNMSG; //请求结果 private Object Date = null; //Setter、Getter、toString.... /** * 获取正确结果模板 * //标准是这样写的 * @param message 请求返回信息 * @param obj 请求结果 * @return AjaxResult */ public static AjaxResult getOK(String message,Object obj) { AjaxResult result = new AjaxResult(); result.setMessage(message); result.setDate(obj); return result; } /** * 获取正确结果模板 */ public static AjaxResult getOK(Object obj) { AjaxResult result = new AjaxResult(); result.setMessage(ActionConstants.DEFAULT_SUCCESS_RETURNMSG); result.setDate(obj); return result; } /** * 获取正确结果模板 */ public static AjaxResult getOK() { return getOK(ActionConstants.DEFAULT_SUCCESS_RETURNMSG,null); } /** * 获取错误结果模板 */ public static AjaxResult getError(ResultCode errorCode,String message,Object obj) { AjaxResult result = new AjaxResult(); result.setErrorCode(errorCode.getCode()); result.setMessage(message); result.setDate(obj); return result; } /** * 获取错误结果模板 * * @return AjaxResult */ public static final AjaxResult getError(ResultCode resultCode) { AjaxResult result = new AjaxResult(); return getError(resultCode,resultCode.getMsg(),null); } }
2、核心包~util
1、接下来我们看工具类,这个几个非常有用。自己也收集了一些。先看日期吧,会拿出部分作为介绍。
(1)、首先我们看到DateUtils继承了ProperyEditorSuppert。为什么要这么做?看名字叫属性编辑支持。在Spring中我们可以使用属性编辑器来将特定的字符串转为对象:String-->Object.在JDK中用于将XML文件中字符串转为特定的类型,同时提供了一个实现类,是他是他就是PropertEditorSuppert。在Spring注入式,遇到类型不一致,则回去调用相应的属性编辑器进行转换。如:setAsText(String str)、setValue().不然getValue()方法拿不到处理后的对象。
import org.joda.time.format.DateTimeFormat; //注意包名,大佬的付出 public class DateUtils extends PropertyEditorSupport { public static final DateTimeFormatter standard = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss"); public static final DateTimeFormatter yy_MM_dd = DateTimeFormat.forPattern("yyyy-MM-dd"); public static final DateTimeFormatter date_sdf_wz = DateTimeFormat.forPattern("yyyy年MM月dd日"); //还有需要转换格式 ...................................常用的,其实有很多............................................. /** * 字符串转为日期 */ public static Date str2Date(String str,DateTimeFormatter sdf) { if (str == null || "".equals(str)) { return null; } DateTime date; try { date = sdf.parseDateTime(str); return date.toDate(); } catch (IllegalArgumentException e) { String errorMessage = "yyyy-MM-dd HH:mm:ss"; if (sdf.equals(yy_MM_dd)) { errorMessage = "yyyy-MM-dd"; } throw new BusinessException("输入的日期格式有误,因为'" + errorMessage + "'格式"); //异常直接给自定义的。 } } /** * 指定日期的默认显示,具体格式为:年-月-日 * @param date 指定的日期 * @return 指定日期按“年-月-日”显示 */ public static String formatDate(Date date) { return standard.print(date.getTime()); } /** * 返回UNIX时间戳-1970年至今的秒数(注意单位-不是毫秒) * @param str the str * @param format the format * @return the long */ public static long getUnixTimestamp(String str, DateTimeFormatter format) { DateTime dateTime = format.parseDateTime(str); return dateTime.getMillis() / 1000; } /** * 获取给定日期之间的日期 */ public static List<String> getRangeDates(Long startTime, Long endTime) { List<String> dateList = new ArrayList<>(); DateTime startDt = new DateTime(startTime * 1000); DateTime endDt = new DateTime(endTime * 1000); endDt = endDt.withTimeAtStartOfDay(); //因为查询结束时间不包含边界,特殊处理一下 if (endTime * 1000 == endDt.getMillis()) { endDt = endDt.minusDays(1); } dateList.add(getTime(startTime, date_sdf_wz)); while (endDt.isAfter(startDt)) { startDt = startDt.plusDays(1); dateList.add(getTime(startDt.getMillis() / 1000, date_sdf_wz)); } return dateList; } /** * 返回时间间隔天数 */ public static int getDateDiff(Long beginTime, Long endTime) { DateTime dateTime1 = new DateTime(beginTime * 1000); DateTime dateTime2 = new DateTime(endTime * 1000); return Days.daysBetween(dateTime1, dateTime2).getDays(); } }
(2)接下来我们看下ID生产策略,注意静态代码块,还有这里用到了重入锁ReentrantLock(),对资源进行加锁,同一时刻只会有一个线程能够占有锁.当前锁被线程占有时,其他线程会进入挂起状态,直到该锁被释放,其他挂起的线程会被唤醒并开始新的竞争.,AtomicInteger并发条件下原子自增运算。保证一致性。友情提示
/** * ID生成策略 */ public class IDGenerator { private static final DateFormat format = new SimpleDateFormat("yyyyMMddHHmmssSSS"); private static final Random r = new Random(); private static char[] A2Z = null; static { int j = 0; A2Z = new char[26]; for (int i = 65; i < 91; i++) { A2Z[j] = (char)i; j++; } } public static String getTargetId() { char[] temp = new char[5]; for (int i = 0; i < 5; i++) { temp[i] = A2Z[r.nextInt(26)]; } String string = new String(temp); Integer max = 999999; Integer min = 10000; int s = r.nextInt(max) % (max - min + 1) + min; return string + s; } public static String getTranSid() { Lock lock = new ReentrantLock(); lock.lock(); String temp = null; AtomicInteger atomicInteger = new AtomicInteger(); try { String currDate = format.format(new Date()); Integer max = 999; Integer min = 100; int s = r.nextInt(max) % (max - min + 1) + min; temp = currDate + String.valueOf(s); } finally { lock.unlock(); } return temp; } public static String getIcbcTimeStamp() { DateFormat dateFormatStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Lock lock = new ReentrantLock(); lock.lock(); String temp = null; AtomicInteger atomicInteger = new AtomicInteger(); try { String currDate = dateFormatStamp.format(new Date()); Integer max = 999999; Integer min = 100000; int s = r.nextInt(max) % (max - min + 1) + min; temp = currDate + "." + String.valueOf(s); } finally { lock.unlock(); } return temp; } }
(3)、看一个加载配置文件的工具类
需要注意的是这里同样用到了静态代码块,在启动的时候就加载。还有就是反射机制,在这里用的是类的加载器完成的。其他可以用getClass(),Claass.forName();
还有一点就是Properties类,继承自Hashtable,实现Map接口,可以和IO对象组合使用,实现数据的持久存储。存储键值对,根据key找Value。
/** * Properties文件加载工具 */ public class PropertiedsUtil { public static Properties properties = new Properties(); public static List<String> configFile = Arrays.asList( "server_config.properties", "sys_config.properties"); static { try { for (String fileName : configFile) { InputStream in = PropertiedsUtil.class.getClassLoader().getResourceAsStream(fileName); properties.load(in); } }catch (IOException e) { throw new BusinessException("读取配置文件错误"); } } public static String getValue(String key) { return properties.getProperty(key, ""); } }
(4)、SqlSessionFactoryBean工具类
在 MyBatis 中,使用 SqlSessionFactoryBuilder创建SqlSessionFactory ,进而来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射语句,提交或回滚连接,最后,当不再需要它的时候, 你可以关闭 session。
/** * Created by guo on 3/2/2018. * SqlSession工厂bean */ public class SqlSessionFactoryBeanUtil extends SqlSessionFactoryBean { //这里使用了日志记录,方便查看。 private static Logger logger = LoggerFactory.getLogger(SqlSessionFactoryBeanUtil.class); @Override protected SqlSessionFactory buildSqlSessionFactory() throws IOException { try { return super.buildSqlSessionFactory(); //调用父类 }catch (NestedIOException e) { logger.error("ex:{}",e.getMessage()); throw new NestedIOException("Faild to parse mapping resource: " + e); }finally { ErrorContext.instance().reset(); } } }
(5)、最重要的一般压低出现。那就是字符串工具类。这个有些多,挑常用的罗列出来。
/** * Created by guo on 3/2/2018. * 字符串处理及转换工具类 */ public class StringUtil { private static Pattern numericPattern = Pattern.compile("^[0-9\\-]+$"); private static Pattern numericStringPattern = Pattern.compile("^[0-9\\-\\-]+$"); private static Pattern floatNumericPattern = Pattern.compile("^[0-9\\-\\.]+$"); private static Pattern abcPattern = Pattern.compile("^[a-z|A-Z]+$"); public static final String splitStrPattern = ",|,|;|;|、|\\.|。|-|_|\\(|\\)|\\[|\\]|\\{|\\}|\\\\|/| | |\""; private static Logger logger = LoggerFactory.getLogger(StringUtil.class); /** * 判断是否数字表示 * @param src 源字符串 * @return 是否数字的标志 */ public static boolean isNumeric(String src) { boolean return_value = false; if (src != null && src.length() > 0) { Matcher m = numericPattern.matcher(src); if (m.find()) { return_value = true; } } return return_value; } /** * 判断是否纯字母组合 * @param src 源字符串 * @return 是否纯字母组合的标志 */ public static boolean isABC(String src) { boolean return_value = false; if (src != null && src.length() > 0) { Matcher m = abcPattern.matcher(src); if (m.find()) { return_value = true; } } return return_value; } /** * 判断是否浮点数字表示 */ public static boolean isFloatNumeric(String src) {} -------------------------------截取------------------------------------------------------------ /** * 把string array or list用给定的符号symbol连接成一个字符串 */ public static String joinString(List array, String symbol) { String result = ""; if (array != null) { for (int i = 0; i < array.size(); i++) { String temp = array.get(i).toString(); if (temp != null && temp.trim().length() > 0) result += (temp + symbol); } if (result.length() > 1) result = result.substring(0, result.length() - 1); } return result; } /** * 截取字符,不转码 */ public static String subStrNotEncode(String subject, int size) { if (subject.length() > size) { subject = subject.substring(0, size); } return subject; } /** * 取得字符串的实际长度(考虑了汉字的情况) */ public static int getStringLen(String SrcStr) { int return_value = 0; if (SrcStr != null) { char[] theChars = SrcStr.toCharArray(); for (int i = 0; i < theChars.length; i++) { return_value += (theChars[i] <= 255) ? 1 : 2; } } return return_value; } ---------------------------------分割、替换和转换------------------------------------------- /** * 根据指定的字符把源字符串分割成一个数组 */ public static List<String> parseString2ListByCustomerPattern(String pattern, String src) { if (src == null) return null; List<String> list = new ArrayList<String>(); String[] result = src.split(pattern); for (int i = 0; i < result.length; i++) { list.add(result[i]); } return list; } /** * 字符串替换 */ public static String stringReplace(String str, String sr, String sd) { String regEx = sr; Pattern p = Pattern.compile(regEx, Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(str); str = m.replaceAll(sd); return str; } /** * 格式化一个float */ public static String formatFloat(float f, String format) { DecimalFormat df = new DecimalFormat(format); return df.format(f); } /** * 把 名=值 参数表转换成字符串 (a=1,b=2 =>a=1&b=2) */ public static String linkedHashMapToString(LinkedHashMap<String, String> map) { if (map != null && map.size() > 0) { String result = ""; Iterator it = map.keySet().iterator(); while (it.hasNext()) { String name = (String) it.next(); String value = (String) map.get(name); result += (result.equals("")) ? "" : "&"; result += String.format("%s=%s", name, value); } return result; } return null; } /** * 转换编码 */ public static String changCoding(String s, String fencode, String bencode) { String str; try { if (StringUtil.isNotEmpty(s)) { str = new String(s.getBytes(fencode), bencode); } else { str = ""; } return str; } catch (UnsupportedEncodingException e) { return s; } }
下面的是转换,具体的用到了再说。
/** * 将字符串转换成十六进制编码 */ public static String toHexString(String str) throws UnsupportedEncodingException { // 根据默认编码获取字节数组 String hexString = "0123456789ABCDEF"; byte[] bytes = str.getBytes("GB2312"); StringBuilder sb = new StringBuilder(bytes.length * 2); // 将字节数组中每个字节拆解成2位16进制整数 for (byte b : bytes) { sb.append(Integer.toHexString(b + 0x800).substring(1)); } return sb.toString(); } /** * unicode 转字符串 */ public static String unicode2String(String unicode) { StringBuffer string = new StringBuffer(); String[] hex = unicode.split("\\\\u"); for (int i = 1; i < hex.length; i++) { // 转换出每一个代码点 int data = Integer.parseInt(hex[i], 16); // 追加成string string.append((char) data); } return string.toString(); }
gogogo...