keys, boolean fragment) {
+ UriComponentsBuilder template = UriComponentsBuilder.newInstance();
+ UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
+ URI redirectUri;
+ try {
+ // assume it's encoded to start with (if it came in over the wire)
+ redirectUri = builder.build(true).toUri();
+ } catch (Exception e) {
+ // ... but allow client registrations to contain hard-coded non-encoded values
+ redirectUri = builder.build().toUri();
+ builder = UriComponentsBuilder.fromUri(redirectUri);
+ }
+ template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
+ .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());
+
+ if (fragment) {
+ StringBuilder values = new StringBuilder();
+ if (redirectUri.getFragment() != null) {
+ String append = redirectUri.getFragment();
+ values.append(append);
+ }
+ for (String key : query.keySet()) {
+ if (values.length() > 0) {
+ values.append("&");
+ }
+ String name = key;
+ if (keys != null && keys.containsKey(key)) {
+ name = keys.get(key);
+ }
+ values.append(name).append("={").append(key).append("}");
+ }
+ if (values.length() > 0) {
+ template.fragment(values.toString());
+ }
+ UriComponents encoded = template.build().expand(query).encode();
+ builder.fragment(encoded.getFragment());
+ } else {
+ for (String key : query.keySet()) {
+ String name = key;
+ if (keys != null && keys.containsKey(key)) {
+ name = keys.get(key);
+ }
+ template.queryParam(name, "{" + key + "}");
+ }
+ template.fragment(redirectUri.getFragment());
+ UriComponents encoded = template.build().expand(query).encode();
+ builder.query(encoded.getQuery());
+ }
+ return builder.build().toUriString();
+ }
+
+ public static String[] obtainBasicAuthorization(HttpServletRequest request) {
+ String clientId;
+ String clientSecret;
+ // 先从 Header 中获取
+ String authorization = request.getHeader("Authorization");
+ authorization = StrUtil.subAfter(authorization, "Basic ", true);
+ if (StringUtils.hasText(authorization)) {
+ authorization = Base64.decodeStr(authorization);
+ clientId = StrUtil.subBefore(authorization, ":", false);
+ clientSecret = StrUtil.subAfter(authorization, ":", false);
+ // 再从 Param 中获取
+ } else {
+ clientId = request.getParameter("client_id");
+ clientSecret = request.getParameter("client_secret");
+ }
+
+ // 如果两者非空,则返回
+ if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) {
+ return new String[]{clientId, clientSecret};
+ }
+ return null;
+ }
+
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/http/SoapUtil.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/http/SoapUtil.java
new file mode 100644
index 0000000..09b7aae
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/http/SoapUtil.java
@@ -0,0 +1,67 @@
+package com.jojubanking.boot.framework.common.util.http;
+
+import com.jojubanking.boot.framework.common.exception.enums.GlobalErrorCodeConstants;
+import com.jojubanking.boot.framework.common.exception.util.ServiceExceptionUtil;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.methods.PostMethod;
+
+
+/**
+ * 访问远程SOAP Web Service 协议接口
+ */
+public class SoapUtil {
+ //外网测试地址
+// private static String url = "http://222.128.90.115:8010/BtGhYytWebService.asmx";
+ //测试库地址
+// private static String url = "http://192.168.150.17:8002/BtGhYytWebService.asmx";
+
+ //生成库地址
+// private static String url = "http://192.168.150.15:8001/BtGhYytWebService.asmx";
+ private static String url = "http://101.132.179.117/webservice/WebService.asmx";
+
+ /**
+ * @param xml
+ * @return
+ * @throws Exception
+ */
+ public static String soapMethod(String xml) throws Exception {
+ HttpClient client = new HttpClient();
+ PostMethod method = new PostMethod(url);
+ // 设置请求头头
+ method.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
+ // 设置请求体
+ method.setRequestBody(xml);
+ // 获取响应状态码
+ int code = client.executeMethod(method);
+ if (code == 200) {
+ return method.getResponseBodyAsString();
+ } else {
+ throw ServiceExceptionUtil.exception(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR);
+// throw new LogicException("异常,请联系管理员。");
+ }
+ }
+
+ /**
+ * 定时任务发送请求
+ *
+ * @param xml
+ * @return
+ * @throws Exception
+ */
+ public static String taskSoapMethod(String xml) throws Exception {
+ HttpClient client = new HttpClient();
+ PostMethod method = new PostMethod(url);
+ // 设置请求头头
+ method.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
+ // 设置请求体
+ method.setRequestBody(xml);
+ // 获取响应状态码
+ int code = client.executeMethod(method);
+ if (code == 200) {
+ return method.getResponseBodyAsString();
+ } else {
+ return "";
+ }
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/io/FileUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/io/FileUtils.java
new file mode 100644
index 0000000..b6ccf4f
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/io/FileUtils.java
@@ -0,0 +1,84 @@
+package com.jojubanking.boot.framework.common.util.io;
+
+import cn.hutool.core.io.FileTypeUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.IdUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
+import lombok.SneakyThrows;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+
+/**
+ * 文件工具类
+ *
+ * @author TW
+ */
+public class FileUtils {
+
+ /**
+ * 创建临时文件
+ * 该文件会在 JVM 退出时,进行删除
+ *
+ * @param data 文件内容
+ * @return 文件
+ */
+ @SneakyThrows
+ public static File createTempFile(String data) {
+ File file = createTempFile();
+ // 写入内容
+ FileUtil.writeUtf8String(data, file);
+ return file;
+ }
+
+ /**
+ * 创建临时文件
+ * 该文件会在 JVM 退出时,进行删除
+ *
+ * @param data 文件内容
+ * @return 文件
+ */
+ @SneakyThrows
+ public static File createTempFile(byte[] data) {
+ File file = createTempFile();
+ // 写入内容
+ FileUtil.writeBytes(data, file);
+ return file;
+ }
+
+ /**
+ * 创建临时文件,无内容
+ * 该文件会在 JVM 退出时,进行删除
+ *
+ * @return 文件
+ */
+ @SneakyThrows
+ public static File createTempFile() {
+ // 创建文件,通过 UUID 保证唯一
+ File file = File.createTempFile(IdUtil.simpleUUID(), null);
+ // 标记 JVM 退出时,自动删除
+ file.deleteOnExit();
+ return file;
+ }
+
+ /**
+ * 生成文件路径
+ *
+ * @param content 文件内容
+ * @param originalName 原始文件名
+ * @return path,唯一不可重复
+ */
+ public static String generatePath(byte[] content, String originalName) {
+ String sha256Hex = DigestUtil.sha256Hex(content);
+ // 情况一:如果存在 name,则优先使用 name 的后缀
+ if (StrUtil.isNotBlank(originalName)) {
+ String extName = FileNameUtil.extName(originalName);
+ return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + "." + extName;
+ }
+ // 情况二:基于 content 计算
+ return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/io/IoUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/io/IoUtils.java
new file mode 100644
index 0000000..25db556
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/io/IoUtils.java
@@ -0,0 +1,28 @@
+package com.jojubanking.boot.framework.common.util.io;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.io.InputStream;
+
+/**
+ * IO 工具类,用于 {@link cn.hutool.core.io.IoUtil} 缺失的方法
+ *
+ * @author TW
+ */
+public class IoUtils {
+
+ /**
+ * 从流中读取 UTF8 编码的内容
+ *
+ * @param in 输入流
+ * @param isClose 是否关闭
+ * @return 内容
+ * @throws IORuntimeException IO 异常
+ */
+ public static String readUtf8(InputStream in, boolean isClose) throws IORuntimeException {
+ return StrUtil.utf8Str(IoUtil.read(in, isClose));
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/json/JsonUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/json/JsonUtils.java
new file mode 100644
index 0000000..5187277
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/json/JsonUtils.java
@@ -0,0 +1,142 @@
+package com.jojubanking.boot.framework.common.util.json;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import lombok.SneakyThrows;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * JSON 工具类
+ *
+ * @author TW
+ */
+@UtilityClass
+@Slf4j
+public class JsonUtils {
+
+ private static ObjectMapper objectMapper = new ObjectMapper();
+
+ static {
+ objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+ }
+
+ /**
+ * 初始化 objectMapper 属性
+ *
+ * 通过这样的方式,使用 Spring 创建的 ObjectMapper Bean
+ *
+ * @param objectMapper ObjectMapper 对象
+ */
+ public static void init(ObjectMapper objectMapper) {
+ JsonUtils.objectMapper = objectMapper;
+ }
+
+ @SneakyThrows
+ public static String toJsonString(Object object) {
+ return objectMapper.writeValueAsString(object);
+ }
+
+ @SneakyThrows
+ public static byte[] toJsonByte(Object object) {
+ return objectMapper.writeValueAsBytes(object);
+ }
+
+ @SneakyThrows
+ public static String toJsonPrettyString(Object object) {
+ return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
+ }
+
+ public static T parseObject(String text, Class clazz) {
+ if (StrUtil.isEmpty(text)) {
+ return null;
+ }
+ try {
+ return objectMapper.readValue(text, clazz);
+ } catch (IOException e) {
+ log.error("json parse err,json:{}", text, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 将字符串解析成指定类型的对象
+ * 使用 {@link #parseObject(String, Class)} 时,在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下,
+ * 如果 text 没有 class 属性,则会报错。此时,使用这个方法,可以解决。
+ *
+ * @param text 字符串
+ * @param clazz 类型
+ * @return 对象
+ */
+ public static T parseObject2(String text, Class clazz) {
+ if (StrUtil.isEmpty(text)) {
+ return null;
+ }
+ return JSONUtil.toBean(text, clazz);
+ }
+
+ public static T parseObject(byte[] bytes, Class clazz) {
+ if (ArrayUtil.isEmpty(bytes)) {
+ return null;
+ }
+ try {
+ return objectMapper.readValue(bytes, clazz);
+ } catch (IOException e) {
+ log.error("json parse err,json:{}", bytes, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static T parseObject(String text, TypeReference typeReference) {
+ try {
+ return objectMapper.readValue(text, typeReference);
+ } catch (IOException e) {
+ log.error("json parse err,json:{}", text, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static List parseArray(String text, Class clazz) {
+ if (StrUtil.isEmpty(text)) {
+ return new ArrayList<>();
+ }
+ try {
+ return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));
+ } catch (IOException e) {
+ log.error("json parse err,json:{}", text, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static JsonNode parseTree(String text) {
+ try {
+ return objectMapper.readTree(text);
+ } catch (IOException e) {
+ log.error("json parse err,json:{}", text, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static JsonNode parseTree(byte[] text) {
+ try {
+ return objectMapper.readTree(text);
+ } catch (IOException e) {
+ log.error("json parse err,json:{}", text, e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static boolean isJson(String text) {
+ return JSONUtil.isTypeJSON(text);
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/monitor/TracerUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/monitor/TracerUtils.java
new file mode 100644
index 0000000..32eda4a
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/monitor/TracerUtils.java
@@ -0,0 +1,30 @@
+package com.jojubanking.boot.framework.common.util.monitor;
+
+import org.apache.skywalking.apm.toolkit.trace.TraceContext;
+
+/**
+ * 链路追踪工具类
+ *
+ * 考虑到每个 starter 都需要用到该工具类,所以放到 common 模块下的 util 包下
+ *
+ * @author TW
+ */
+public class TracerUtils {
+
+ /**
+ * 私有化构造方法
+ */
+ private TracerUtils() {
+ }
+
+ /**
+ * 获得链路追踪编号,直接返回 SkyWalking 的 TraceId。
+ * 如果不存在的话为空字符串!!!
+ *
+ * @return 链路追踪编号
+ */
+ public static String getTraceId() {
+ return TraceContext.traceId();
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/number/NumberUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/number/NumberUtils.java
new file mode 100644
index 0000000..0b4f318
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/number/NumberUtils.java
@@ -0,0 +1,16 @@
+package com.jojubanking.boot.framework.common.util.number;
+
+import cn.hutool.core.util.StrUtil;
+
+/**
+ * 数字的工具类,补全 {@link cn.hutool.core.util.NumberUtil} 的功能
+ *
+ * @author TW
+ */
+public class NumberUtils {
+
+ public static Long parseLong(String str) {
+ return StrUtil.isNotEmpty(str) ? Long.valueOf(str) : null;
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/object/ObjectUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/object/ObjectUtils.java
new file mode 100644
index 0000000..f1af84a
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/object/ObjectUtils.java
@@ -0,0 +1,63 @@
+package com.jojubanking.boot.framework.common.util.object;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ReflectUtil;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * Object 工具类
+ *
+ * @author TW
+ */
+public class ObjectUtils {
+
+ /**
+ * 复制对象,并忽略 Id 编号
+ *
+ * @param object 被复制对象
+ * @param consumer 消费者,可以二次编辑被复制对象
+ * @return 复制后的对象
+ */
+ public static T cloneIgnoreId(T object, Consumer consumer) {
+ T result = ObjectUtil.clone(object);
+ // 忽略 id 编号
+ Field field = ReflectUtil.getField(object.getClass(), "id");
+ if (field != null) {
+ ReflectUtil.setFieldValue(result, field, null);
+ }
+ // 二次编辑
+ if (result != null) {
+ consumer.accept(result);
+ }
+ return result;
+ }
+
+ public static > T max(T obj1, T obj2) {
+ if (obj1 == null) {
+ return obj2;
+ }
+ if (obj2 == null) {
+ return obj1;
+ }
+ return obj1.compareTo(obj2) > 0 ? obj1 : obj2;
+ }
+
+ public static T defaultIfNull(T... array) {
+ for (T item : array) {
+ if (item != null) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ public static boolean equalsAny(T obj, T... array) {
+ return Arrays.asList(array).contains(obj);
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/object/PageUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/object/PageUtils.java
new file mode 100644
index 0000000..b48278d
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/object/PageUtils.java
@@ -0,0 +1,16 @@
+package com.jojubanking.boot.framework.common.util.object;
+
+import com.jojubanking.boot.framework.common.pojo.PageParam;
+
+/**
+ * {@link PageParam} 工具类
+ *
+ * @author TW
+ */
+public class PageUtils {
+
+ public static int getStart(PageParam pageParam) {
+ return (pageParam.getPageNo() - 1) * pageParam.getPageSize();
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/package-info.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/package-info.java
new file mode 100644
index 0000000..1d1ec7c
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * 对于工具类的选择,优先查找 Hutool 中有没对应的方法
+ * 如果没有,则自己封装对应的工具类,以 Utils 结尾,用于区分
+ *
+ * ps:如果担心 Hutool 存在坑的问题,可以阅读 Hutool 的实现源码,以确保可靠性。并且,可以补充相关的单元测试。
+ */
+package com.jojubanking.boot.framework.common.util;
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/servlet/ServletUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/servlet/ServletUtils.java
new file mode 100644
index 0000000..2317d1e
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/servlet/ServletUtils.java
@@ -0,0 +1,95 @@
+package com.jojubanking.boot.framework.common.util.servlet;
+
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.servlet.ServletUtil;
+import com.jojubanking.boot.framework.common.util.json.JsonUtils;
+import org.springframework.http.MediaType;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.net.URLEncoder;
+
+/**
+ * 客户端工具类
+ *
+ * @author TW
+ */
+public class ServletUtils {
+
+ /**
+ * 返回 JSON 字符串
+ *
+ * @param response 响应
+ * @param object 对象,会序列化成 JSON 字符串
+ */
+ @SuppressWarnings("deprecation") // 必须使用 APPLICATION_JSON_UTF8_VALUE,否则会乱码
+ public static void writeJSON(HttpServletResponse response, Object object) {
+ String content = JsonUtils.toJsonString(object);
+ ServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);
+ }
+
+ /**
+ * 返回附件
+ *
+ * @param response 响应
+ * @param filename 文件名
+ * @param content 附件内容
+ * @throws IOException
+ */
+ public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
+ // 设置 header 和 contentType
+ response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
+ response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
+ // 输出附件
+ IoUtil.write(response.getOutputStream(), false, content);
+ }
+
+ /**
+ * @param request 请求
+ * @return ua
+ */
+ public static String getUserAgent(HttpServletRequest request) {
+ String ua = request.getHeader("User-Agent");
+ return ua != null ? ua : "";
+ }
+
+ /**
+ * 获得请求
+ *
+ * @return HttpServletRequest
+ */
+ public static HttpServletRequest getRequest() {
+ RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
+ if (!(requestAttributes instanceof ServletRequestAttributes)) {
+ return null;
+ }
+ return ((ServletRequestAttributes) requestAttributes).getRequest();
+ }
+
+ public static String getUserAgent() {
+ HttpServletRequest request = getRequest();
+ if (request == null) {
+ return null;
+ }
+ return getUserAgent(request);
+ }
+
+ public static String getClientIP() {
+ HttpServletRequest request = getRequest();
+ if (request == null) {
+ return null;
+ }
+ return ServletUtil.getClientIP(request);
+ }
+
+ public static boolean isJsonRequest(ServletRequest request) {
+ return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/spring/SpringAopUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/spring/SpringAopUtils.java
new file mode 100644
index 0000000..a164593
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/spring/SpringAopUtils.java
@@ -0,0 +1,46 @@
+package com.jojubanking.boot.framework.common.util.spring;
+
+import cn.hutool.core.bean.BeanUtil;
+import org.springframework.aop.framework.AdvisedSupport;
+import org.springframework.aop.framework.AopProxy;
+import org.springframework.aop.support.AopUtils;
+
+/**
+ * Spring AOP 工具类
+ *
+ * 参考波克尔 http://www.bubuko.com/infodetail-3471885.html 实现
+ */
+public class SpringAopUtils {
+
+ /**
+ * 获取代理的目标对象
+ *
+ * @param proxy 代理对象
+ * @return 目标对象
+ */
+ public static Object getTarget(Object proxy) throws Exception {
+ // 不是代理对象
+ if (!AopUtils.isAopProxy(proxy)) {
+ return proxy;
+ }
+ // Jdk 代理
+ if (AopUtils.isJdkDynamicProxy(proxy)) {
+ return getJdkDynamicProxyTargetObject(proxy);
+ }
+ // Cglib 代理
+ return getCglibProxyTargetObject(proxy);
+ }
+
+ private static Object getCglibProxyTargetObject(Object proxy) throws Exception {
+ Object dynamicAdvisedInterceptor = BeanUtil.getFieldValue(proxy, "CGLIB$CALLBACK_0");
+ AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(dynamicAdvisedInterceptor, "advised");
+ return advisedSupport.getTargetSource().getTarget();
+ }
+
+ private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {
+ AopProxy aopProxy = (AopProxy) BeanUtil.getFieldValue(proxy, "h");
+ AdvisedSupport advisedSupport = (AdvisedSupport) BeanUtil.getFieldValue(aopProxy, "advised");
+ return advisedSupport.getTargetSource().getTarget();
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/spring/SpringExpressionUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/spring/SpringExpressionUtils.java
new file mode 100644
index 0000000..5a8cf4f
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/spring/SpringExpressionUtils.java
@@ -0,0 +1,82 @@
+package com.jojubanking.boot.framework.common.util.spring;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ArrayUtil;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Spring EL 表达式的工具类
+ *
+ * @author mashu
+ */
+public class SpringExpressionUtils {
+
+ private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
+ private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
+
+ private SpringExpressionUtils() {
+ }
+
+ /**
+ * 从切面中,单个解析 EL 表达式的结果
+ *
+ * @param joinPoint 切面点
+ * @param expressionString EL 表达式数组
+ * @return 执行界面
+ */
+ public static Object parseExpression(ProceedingJoinPoint joinPoint, String expressionString) {
+ Map result = parseExpressions(joinPoint, Collections.singletonList(expressionString));
+ return result.get(expressionString);
+ }
+
+ /**
+ * 从切面中,批量解析 EL 表达式的结果
+ *
+ * @param joinPoint 切面点
+ * @param expressionStrings EL 表达式数组
+ * @return 结果,key 为表达式,value 为对应值
+ */
+ public static Map parseExpressions(ProceedingJoinPoint joinPoint, List expressionStrings) {
+ // 如果为空,则不进行解析
+ if (CollUtil.isEmpty(expressionStrings)) {
+ return MapUtil.newHashMap();
+ }
+
+ // 第一步,构建解析的上下文 EvaluationContext
+ // 通过 joinPoint 获取被注解方法
+ MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
+ Method method = methodSignature.getMethod();
+ // 使用 spring 的 ParameterNameDiscoverer 获取方法形参名数组
+ String[] paramNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
+ // Spring 的表达式上下文对象
+ EvaluationContext context = new StandardEvaluationContext();
+ // 给上下文赋值
+ if (ArrayUtil.isNotEmpty(paramNames)) {
+ Object[] args = joinPoint.getArgs();
+ for (int i = 0; i < paramNames.length; i++) {
+ context.setVariable(paramNames[i], args[i]);
+ }
+ }
+
+ // 第二步,逐个参数解析
+ Map result = MapUtil.newHashMap(expressionStrings.size(), true);
+ expressionStrings.forEach(key -> {
+ Object value = EXPRESSION_PARSER.parseExpression(key).getValue(context);
+ result.put(key, value);
+ });
+ return result;
+ }
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/string/StrUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/string/StrUtils.java
new file mode 100644
index 0000000..8a4a8e2
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/string/StrUtils.java
@@ -0,0 +1,40 @@
+package com.jojubanking.boot.framework.common.util.string;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.util.Collection;
+
+/**
+ * 字符串工具类
+ *
+ * @author TW
+ */
+public class StrUtils {
+
+ public static String maxLength(CharSequence str, int maxLength) {
+ return StrUtil.maxLength(str, maxLength - 3); // -3 的原因,是该方法会补充 ... 恰好
+ }
+
+ /**
+ * 给定字符串是否以任何一个字符串开始
+ * 给定字符串和数组为空都返回 false
+ *
+ * @param str 给定字符串
+ * @param prefixes 需要检测的开始字符串
+ * @since 3.0.6
+ */
+ public static boolean startWithAny(String str, Collection prefixes) {
+ if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {
+ return false;
+ }
+
+ for (CharSequence suffix : prefixes) {
+ if (StrUtil.startWith(str, suffix, false)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/validation/ValidationUtils.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/validation/ValidationUtils.java
new file mode 100644
index 0000000..930b1e3
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/validation/ValidationUtils.java
@@ -0,0 +1,49 @@
+package com.jojubanking.boot.framework.common.util.validation;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import org.springframework.util.StringUtils;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ConstraintViolationException;
+import javax.validation.Validator;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * 校验工具类
+ *
+ * @author TW
+ */
+public class ValidationUtils {
+
+ private static final Pattern PATTERN_URL = Pattern.compile("^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]");
+
+ private static final Pattern PATTERN_XML_NCNAME = Pattern.compile("[a-zA-Z_][\\-_.0-9_a-zA-Z$]*");
+
+ public static boolean isMobile(String mobile) {
+ if (StrUtil.length(mobile) != 11) {
+ return false;
+ }
+ // TODO TW,后面完善手机校验
+ return true;
+ }
+
+ public static boolean isURL(String url) {
+ return StringUtils.hasText(url)
+ && PATTERN_URL.matcher(url).matches();
+ }
+
+ public static boolean isXmlNCName(String str) {
+ return StringUtils.hasText(str)
+ && PATTERN_XML_NCNAME.matcher(str).matches();
+ }
+
+ public static void validate(Validator validator, Object object, Class>... groups) {
+ Set> constraintViolations = validator.validate(object, groups);
+ if (CollUtil.isNotEmpty(constraintViolations)) {
+ throw new ConstraintViolationException(constraintViolations);
+ }
+ }
+
+}
diff --git a/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/xml/XmlUtil.java b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/xml/XmlUtil.java
new file mode 100644
index 0000000..78703e2
--- /dev/null
+++ b/joju-framework/joju-common/src/main/java/com/jojubanking/boot/framework/common/util/xml/XmlUtil.java
@@ -0,0 +1,283 @@
+package com.jojubanking.boot.framework.common.util.xml;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.dom4j.Document;
+import org.dom4j.DocumentException;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.List;
+import java.util.*;
+
+public class XmlUtil {
+// public static void main(String[] args) {
+// Reserve8Vo vo = new Reserve8Vo();
+// System.out.println(getOpAppAppoint(vo));
+// }
+
+ /**
+ * 省份证的正则表达式^(\d{15}|\d{17}[\dx])$
+ *
+ * @param id 省份证号
+ * @return 生日(yyyy-MM-dd)
+ */
+ public static String extractYearMonthDayOfIdCard(String id) {
+ String year = null;
+ String month = null;
+ String day = null;
+ //正则匹配身份证号是否是正确的,15位或者17位数字+数字/x/X
+ if (id.matches("^\\d{15}|\\d{17}[\\dxX]$")) {
+ year = id.substring(6, 10);
+ month = id.substring(10, 12);
+ day = id.substring(12, 14);
+ } else {
+ System.out.println("身份证号码不匹配!");
+ return null;
+ }
+ return year + "-" + month + "-" + day;
+ }
+
+ // 获取返回的报文统一处理
+// public static Map map = new HashMap();
+
+ public static Map parse(String soap) throws DocumentException {
+ //map.clear();
+ String soapWeb = StringEscapeUtils.unescapeXml(soap);
+ soapWeb = soapWeb.replace("", "");
+ Document doc = DocumentHelper.parseText(soapWeb);// 报文 转成 xml
+ Element root = doc.getRootElement();//获取根元素,准备递归解析这个XML树
+
+ Map map = getCodeNC(root);
+ return map;
+ }
+
+ // 非递归遍历所有的 节点 和 数据
+ public static Map getCodeNC(Element root) {
+ Map map = new HashMap();
+
+ Stack a = null;
+ a = new Stack<>();
+ a.push(root);
+
+ if (root.elements().size() == 0 || root.elements() == null) {
+ return map;
+ }
+ //top 是栈顶元素
+ Element top = null;
+ while (!a.empty()) {
+ top = (Element) a.peek();
+ a.pop();
+
+ //如果当前跟节点有子节点,找到子节点
+ List list = top.elements();
+ int size = top.elements().size();
+
+ for (Element e : list) {
+ //遍历每个节点
+ if (e.elements().size() > 0) {
+ //对栈顶元素进行反向遍历,入栈
+ a.push((Element) e);
+ }
+ if (e.elements().size() == 0) {
+ map.put(e.getName(), e.getTextTrim());
+ }//如果为叶子节点,那么直接把名称和值放入map
+ }
+ }
+
+ return map;
+ }
+
+// // 递归遍历所有的 节点 和 数据
+// public static Map getCode(Element root) {
+// // Map map = new HashMap();
+//
+// if (root.elements() != null) {
+// List list = root.elements();//如果当前跟节点有子节点,找到子节点
+// for (Element e : list) {//遍历每个节点
+// if (e.elements().size() > 0) {
+// getCode(e);//当前节点不为空的话,递归遍历子节点;
+// }
+// if (e.elements().size() == 0) {
+// map.put(e.getName(), e.getTextTrim());
+// }//如果为叶子节点,那么直接把名称和值放入map
+// }
+// }
+// return map;
+// }
+//
+ public static List