diff --git a/src/main/java/com/yuyou/openapi/openapi/api/FormClient.java b/src/main/java/com/yuyou/openapi/openapi/api/FormClient.java
new file mode 100644
index 0000000..a3351cb
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/api/FormClient.java
@@ -0,0 +1,70 @@
+package com.yuyou.openapi.openapi.api;
+
+import cn.hutool.json.JSONUtil;
+import com.yuyou.openapi.openapi.common.CommonResponse;
+import com.yuyou.openapi.openapi.common.ResponseCode;
+import com.yuyou.openapi.openapi.model.dto.FormMessageDTO;
+import com.yuyou.openapi.openapi.model.vo.FormClientMessageVO;
+import com.yuyou.openapi.openapi.service.FormMessageService;
+import com.yuyou.openapi.openapi.utils.SecurityOperationUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Objects;
+
+
+/**
+ * 接收上游表单推送接口
+ */
+@RestController
+@Slf4j
+public class FormClient {
+
+ @Autowired
+ private FormMessageService formMessageService;
+
+ /**
+ * 获取表单推送数据
+ *
+ * @return 返回调用信息
+ */
+ @PostMapping(value = "/api/req/form/{actName}")
+ @ResponseBody
+ public CommonResponse getFormData(@PathVariable("actName") String actName, @RequestBody FormClientMessageVO formClientMessageVO){
+ if (StringUtils.isEmpty(actName) || Objects.isNull(formClientMessageVO)) {
+ return CommonResponse.createByErrorMessage(ResponseCode.EMPTY_ARGUMENT.getDesc());
+ }
+ log.info("====== [ Receive one request, content is {} ] ======", formClientMessageVO.toString());
+ // 验证参数,并进行解密
+ String callLog = formClientMessageVO.getCallLog();
+ if (StringUtils.isEmpty(callLog)){
+ return CommonResponse.createByErrorMessage("CallLog is empty.");
+ }
+ // 解密获取Json字串
+ String jsonStr = SecurityOperationUtil.decCallLogSecurityInfo(callLog);
+ if (StringUtils.isEmpty(jsonStr)){
+ return CommonResponse.createByErrorMessage(ResponseCode.DECRYPT_ERROR.getDesc());
+ }
+
+ FormMessageDTO formMessageDTO = convertFormMessageDTO(jsonStr, formClientMessageVO);
+
+ // 解析JSON并入库
+ return formMessageService.recordFormMessage(actName, formMessageDTO) ?
+ CommonResponse.createBySuccess() : CommonResponse.createByErrorMessage("调用失败请重试");
+ }
+
+ /**
+ * 转换生成FormMessageDTO
+ * @param jsonStr
+ * @return
+ */
+ private FormMessageDTO convertFormMessageDTO(String jsonStr, FormClientMessageVO formClientMessageVO) {
+ FormMessageDTO formMessageDTO = JSONUtil.toBean(jsonStr, FormMessageDTO.class);
+ BeanUtils.copyProperties(formClientMessageVO, formMessageDTO);
+
+ return formMessageDTO;
+ }
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/common/spi/ErrorCode.java b/src/main/java/com/yuyou/openapi/openapi/common/spi/ErrorCode.java
new file mode 100644
index 0000000..3d93a93
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/common/spi/ErrorCode.java
@@ -0,0 +1,22 @@
+package com.yuyou.openapi.openapi.common.spi;
+
+public interface ErrorCode {
+
+ // 错误码编号
+ String getCode();
+
+ // 错误码描述
+ String getDescription();
+
+ /** 必须提供toString的实现
+ *
+ *
+ * @Override
+ * public String toString() {
+ * return String.format("Code:[%s], Description:[%s]. ", this.code, this.describe);
+ * }
+ *
+ *
+ */
+ String toString();
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/dao/FormActInfoRepository.java b/src/main/java/com/yuyou/openapi/openapi/dao/FormActInfoRepository.java
new file mode 100644
index 0000000..ab83626
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/dao/FormActInfoRepository.java
@@ -0,0 +1,9 @@
+package com.yuyou.openapi.openapi.dao;
+
+import com.yuyou.openapi.openapi.model.dataobject.FormActInfo;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface FormActInfoRepository extends JpaRepository {
+
+ FormActInfo findFirstByActName(String actName);
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/exception/BaiyeException.java b/src/main/java/com/yuyou/openapi/openapi/exception/BaiyeException.java
new file mode 100644
index 0000000..004de4a
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/exception/BaiyeException.java
@@ -0,0 +1,67 @@
+package com.yuyou.openapi.openapi.exception;
+
+import com.yuyou.openapi.openapi.common.spi.ErrorCode;
+
+/**
+ * 业务异常
+ */
+public class BaiyeException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+
+ private ErrorCode errorCode;
+
+ public BaiyeException(ErrorCode errorCode) {
+ super(errorCode.toString());
+ this.errorCode = errorCode;
+ }
+
+ public BaiyeException(ErrorCode errorCode, String errorMessage) {
+ super(errorCode.toString() + " - " + errorMessage);
+ this.errorCode = errorCode;
+ }
+
+ private BaiyeException(ErrorCode errorCode, String errorMessage,
+ Throwable cause) {
+ super(errorCode.toString() + " - " + getMessage(errorMessage)
+ + " - " + getMessage(cause), cause);
+
+ this.errorCode = errorCode;
+ }
+
+ public static BaiyeException asBaiyeException(ErrorCode errorCode, String message) {
+ return new BaiyeException(errorCode, message);
+ }
+
+ public static BaiyeException asBaiyeException(ErrorCode errorCode, String message,
+ Throwable cause) {
+ if (cause instanceof BaiyeException) {
+ return (BaiyeException) cause;
+ }
+ return new BaiyeException(errorCode, message, cause);
+ }
+
+ public static BaiyeException asBaiyeException(ErrorCode errorCode,
+ Throwable cause) {
+ if (cause instanceof BaiyeException) {
+ return (BaiyeException) cause;
+ }
+ return new BaiyeException(errorCode, getMessage(cause), cause);
+ }
+
+ public ErrorCode getErrorCode() {
+ return this.errorCode;
+ }
+
+
+ private static String getMessage(Object obj) {
+ if (obj == null) {
+ return "";
+ }
+
+ if (obj instanceof Throwable) {
+ return ((Throwable) obj).getMessage();
+ } else {
+ return obj.toString();
+ }
+ }
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/exception/CommonErrorCode.java b/src/main/java/com/yuyou/openapi/openapi/exception/CommonErrorCode.java
new file mode 100644
index 0000000..9d16084
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/exception/CommonErrorCode.java
@@ -0,0 +1,34 @@
+package com.yuyou.openapi.openapi.exception;
+
+import com.yuyou.openapi.openapi.common.spi.ErrorCode;
+
+public enum CommonErrorCode implements ErrorCode {
+
+ CONFIG_ERROR(501, "您提供的配置文件存在错误信息,请检查您的作业配置"),
+ RUNTIME_ERROR(500, "运行时内部调用错误"),
+ DATA_NOT_EXIT(502, "数据不存在"),;
+ private final int code;
+
+ private final String describe;
+
+ private CommonErrorCode(int code, String describe) {
+ this.code = code;
+ this.describe = describe;
+ }
+
+ @Override
+ public String getCode() {
+ return null;
+ }
+
+ @Override
+ public String getDescription() {
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Code:[%s], Describe:[%s]", this.code,
+ this.describe);
+ }
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/model/dataobject/FormActInfo.java b/src/main/java/com/yuyou/openapi/openapi/model/dataobject/FormActInfo.java
new file mode 100644
index 0000000..1290c95
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/model/dataobject/FormActInfo.java
@@ -0,0 +1,50 @@
+package com.yuyou.openapi.openapi.model.dataobject;
+
+import lombok.Data;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.annotation.LastModifiedDate;
+
+import javax.persistence.*;
+import java.util.Date;
+
+@Data
+@Entity
+@Table(name = "form_act_info")
+public class FormActInfo {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long actId;
+
+ /**
+ * 记录创建时间
+ */
+ @Column(name = "gmt_create")
+ @CreatedDate
+ private Date gmtCreate;
+
+ /**
+ * 记录修改时间
+ */
+ @Column(name = "gmt_modified")
+ @LastModifiedDate
+ private Date gmtModified;
+
+ /**
+ * 业务名
+ */
+ @Column(name = "act_name")
+ private String actName;
+
+ /**
+ * 业务对应表名
+ */
+ @Column(name = "act_table_name")
+ private String actTableName;
+
+ /**
+ * 所需字段及解析规则
+ */
+ @Column(name = "columns_rule")
+ private String columnsRule;
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/model/dto/FormMessageDTO.java b/src/main/java/com/yuyou/openapi/openapi/model/dto/FormMessageDTO.java
new file mode 100644
index 0000000..3b9fc00
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/model/dto/FormMessageDTO.java
@@ -0,0 +1,29 @@
+package com.yuyou.openapi.openapi.model.dto;
+
+import com.alibaba.fastjson.JSONObject;
+import lombok.Data;
+
+@Data
+public class FormMessageDTO {
+ /**
+ * 推送用户的ID
+ */
+ private String appId;
+
+ /**
+ * 请求时间戳
+ */
+
+ private Long timestamp;
+
+ /**
+ * 标记集合
+ */
+ private JSONObject tag;
+
+ /**
+ * 数据集合
+ */
+ private JSONObject data;
+
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/model/param/FormQueryParam.java b/src/main/java/com/yuyou/openapi/openapi/model/param/FormQueryParam.java
new file mode 100644
index 0000000..2cbd67f
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/model/param/FormQueryParam.java
@@ -0,0 +1,51 @@
+package com.yuyou.openapi.openapi.model.param;
+
+
+import lombok.Data;
+import java.util.List;
+
+/**
+ * 表单查询参数
+ */
+@Data
+public class FormQueryParam {
+
+ /**
+ * 表名
+ */
+ private String tableName;
+
+ /**
+ * uid
+ */
+ private String uid;
+
+ /**
+ * 记录ID
+ */
+ private Long recId;
+
+ /**
+ * 上游推送ID
+ */
+ private String appId;
+
+ /**
+ * 学科
+ */
+ private List stuSubjects;
+
+ /**
+ * 年级
+ */
+ private List stuGrades;
+
+ private Integer gradeStart;
+ private Integer gradeEnd;
+
+ /**
+ * 推送时间
+ */
+ private String pushTimeStart;
+ private String pushTimeEnd;
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/model/vo/FormClientMessageVO.java b/src/main/java/com/yuyou/openapi/openapi/model/vo/FormClientMessageVO.java
new file mode 100644
index 0000000..311e79a
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/model/vo/FormClientMessageVO.java
@@ -0,0 +1,21 @@
+package com.yuyou.openapi.openapi.model.vo;
+
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 表单请求接口表现层映射
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class FormClientMessageVO extends ABClientBaseVO implements Serializable {
+ /**
+ * 需要接收到的加密数据
+ */
+ @JsonProperty(value = "calllog")
+ private String callLog;
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/service/FormMessageService.java b/src/main/java/com/yuyou/openapi/openapi/service/FormMessageService.java
new file mode 100644
index 0000000..8b1fd41
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/service/FormMessageService.java
@@ -0,0 +1,18 @@
+package com.yuyou.openapi.openapi.service;
+
+import com.yuyou.openapi.openapi.model.dto.FormMessageDTO;
+import org.springframework.validation.annotation.Validated;
+
+/**
+ * 表单数据处理服务层
+ */
+@Validated
+public interface FormMessageService {
+
+ /**
+ * 记录表单数据
+ * @param actName 业务名称
+ * @param formMessageDTO
+ */
+ boolean recordFormMessage(String actName, FormMessageDTO formMessageDTO);
+}
diff --git a/src/main/java/com/yuyou/openapi/openapi/service/impl/FormMessageServiceImpl.java b/src/main/java/com/yuyou/openapi/openapi/service/impl/FormMessageServiceImpl.java
new file mode 100644
index 0000000..af00ed3
--- /dev/null
+++ b/src/main/java/com/yuyou/openapi/openapi/service/impl/FormMessageServiceImpl.java
@@ -0,0 +1,211 @@
+package com.yuyou.openapi.openapi.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.yuyou.openapi.openapi.dao.FormActInfoRepository;
+import com.yuyou.openapi.openapi.exception.BaiyeException;
+import com.yuyou.openapi.openapi.exception.CommonErrorCode;
+import com.yuyou.openapi.openapi.model.dataobject.FormActInfo;
+import com.yuyou.openapi.openapi.model.dto.FormMessageDTO;
+import com.yuyou.openapi.openapi.service.FormMessageService;
+import com.yuyou.openapi.openapi.utils.ConvertHelp;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.transaction.Transactional;
+import java.math.BigInteger;
+import java.util.*;
+
+
+@Service
+@Slf4j
+public class FormMessageServiceImpl implements FormMessageService {
+
+ /**
+ * 固定的字段名
+ */
+ private static final String GMT_CREATE_COLUMN = "gmt_create";
+ private static final String GMT_MODIFIED_COLUMN = "gmt_modified";
+ private static final String RECID_COLUMN = "rec_id";
+ private static final String APPID_COLUMN = "app_id";
+ private static final String PUSHTIME_COLUMN = "push_time";
+
+
+ @PersistenceContext
+ private EntityManager entityManager;
+
+ @Autowired
+ private FormActInfoRepository formActInfoRepository;
+
+ @Override
+ @Modifying
+ @Transactional
+ public boolean recordFormMessage(String actName, FormMessageDTO formMessageDTO) {
+ if (StringUtils.isEmpty(actName) || Objects.isNull(formMessageDTO)) {
+ log.error("actName or formMessageDTO can not be null.");
+ throw new BaiyeException(CommonErrorCode.RUNTIME_ERROR);
+ }
+
+ if (Objects.isNull(formMessageDTO.getAppId()) || Objects.isNull(formMessageDTO.getTag()) || Objects.isNull(formMessageDTO.getTimestamp())) {
+ throw new BaiyeException(CommonErrorCode.DATA_NOT_EXIT, "appId/tag/timestamp can not be null");
+ }
+ // 获取业务对应的表单结构
+ FormActInfo formActInfo = formActInfoRepository.findFirstByActName(actName);
+ if (Objects.isNull(formActInfo)) {
+ log.error("no act info match with actName: " + actName);
+ throw new BaiyeException(CommonErrorCode.RUNTIME_ERROR);
+ }
+
+ // 表单参数校验
+ if (StringUtils.isEmpty(formActInfo.getActTableName())) {
+ log.error("act tableName is Empty, actName" + actName);
+ throw new BaiyeException(CommonErrorCode.RUNTIME_ERROR);
+ }
+
+ JSONObject columnsRule = JSON.parseObject(formActInfo.getColumnsRule());
+ if (Objects.isNull(columnsRule)) {
+ log.error("columnsRule is illegal, actName: " + actName);
+ throw new BaiyeException(CommonErrorCode.RUNTIME_ERROR);
+ }
+
+ Long recId = Long.valueOf(formMessageDTO.getTag().get("tag_key_id").toString());
+
+ // 解析入库
+ Map columnMap = new HashMap<>();
+ try {
+ columnMap = ConvertHelp.convertFields(columnsRule, formMessageDTO.getData());
+ }catch (Exception e) {
+ log.error("Parse value raise error, err:", e);
+ }
+
+ if (CollectionUtils.isEmpty(columnMap)) {
+ log.error("Get empty value map, must have some error.");
+ throw new BaiyeException(CommonErrorCode.RUNTIME_ERROR);
+ }
+
+ // 添加固定的字段
+ columnMap.put(RECID_COLUMN, recId);
+ columnMap.put(APPID_COLUMN, formMessageDTO.getAppId());
+ columnMap.put(PUSHTIME_COLUMN, new Date(formMessageDTO.getTimestamp()));
+
+
+ // 是否为已成单客户
+ String pid = String.valueOf(columnMap.get("pnum"));
+ if (countSubmitRecByPid(formActInfo.getActTableName(), pid) > 0) {
+ log.info("uid is exist, recId={}", recId);
+ return true;
+ }
+ try {
+ // 根据推送地址和记录id共同判断是否为重复记录,若重复则覆盖更新
+ if (countExistRec(formActInfo.getActTableName(), recId, formMessageDTO.getAppId()) > 0) {
+ // 重复则更新
+ updateMoreFiled(formActInfo.getActTableName(), columnMap, recId, formMessageDTO.getAppId());
+ } else {
+ // 新记录直接插入
+ insert(formActInfo.getActTableName(), columnMap);
+ }
+ }catch (Exception e) {
+ log.error("Insert/Update rec raise error, error is :", e);
+ throw new BaiyeException(CommonErrorCode.RUNTIME_ERROR);
+ }
+
+ return true;
+ }
+
+ // TODO: 可将以下sql封装为一个EntityManager构建的通用DAO
+
+ /**
+ * 根据mobile查询已提交客户总数
+ * @param tableName
+ * @param pid
+ * @return
+ */
+ private int countSubmitRecByPid(String tableName, String pid) {
+ String sql = String.format("SELECT COUNT(*) FROM %s WHERE pnum = :pid AND submit_status = 1", tableName);
+ Query query = entityManager.createNativeQuery(sql);
+ query.setParameter("pid", pid);
+
+ return ((BigInteger) query.getSingleResult()).intValue();
+ }
+
+ /**
+ * 查询已存在的记录数
+ * @param tableName
+ * @param recId
+ * @param appId
+ * @return
+ */
+ private int countExistRec(String tableName, Long recId, String appId) {
+ String sql = String.format("SELECT COUNT(*) FROM %s WHERE rec_id = :recId AND app_id = :appId", tableName);
+ Query query = entityManager.createNativeQuery(sql);
+ query.setParameter("recId", recId);
+ query.setParameter("appId", appId);
+
+ return ((BigInteger) query.getSingleResult()).intValue();
+ }
+
+ // insert
+ public int insert(String tableName, Map map) {
+ int result;
+
+ Set set = map.keySet();
+ List columns = new ArrayList<>(set);
+
+ List