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 values = new ArrayList<>(columns.size()); + columns.forEach(each -> values.add(map.get(each))); + + // 记录入库创建时间 + columns.add(GMT_CREATE_COLUMN); + columns.add(GMT_MODIFIED_COLUMN); + values.add(new Date()); + values.add(new Date()); + + String columnStr = StrUtil.join(", ", columns); + String valueStr = StrUtil.repeat("?, ", values.size()); + + String insertSql = String.format("INSERT INTO %s(%s) VALUES (%s)", + tableName, columnStr, valueStr.substring(0, valueStr.length() - 2)); + log.info("SQL: " + insertSql); + + Query query = entityManager.createNativeQuery(insertSql); + for (int i = 0; i < values.size(); i++) { + query.setParameter(i + 1, values.get(i)); + } + result = query.executeUpdate(); + return result; + } + + // update + public Integer updateMoreFiled(String tableName, Map map, Long recId, String appId) { + int result; + StringBuilder sqlSb = new StringBuilder("UPDATE " + tableName + " SET "); + + map.put(GMT_MODIFIED_COLUMN, new Date()); + Set set = map.keySet(); + List updateColumnList = new ArrayList<>(set); + for (int i = 0; i <= updateColumnList.size() - 1 ; i++){ + sqlSb.append(updateColumnList.get(i) + "=:" + updateColumnList.get(i) + ", "); + } + String sql = sqlSb.toString().substring(0, sqlSb.toString().length() - 2); + sql += " WHERE rec_id = :recId AND app_id = :appId"; + log.info("SQL: " + sql); + + Query query = entityManager.createNativeQuery(sql); + for (int i = 0; i <= updateColumnList.size() - 1; i++) { + query.setParameter(updateColumnList.get(i), map.get(updateColumnList.get(i))); + } + query.setParameter("recId", recId); + query.setParameter("appId", appId); + result = query.executeUpdate(); + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/yuyou/openapi/openapi/utils/ConvertHelp.java b/src/main/java/com/yuyou/openapi/openapi/utils/ConvertHelp.java new file mode 100644 index 0000000..7d6d9cd --- /dev/null +++ b/src/main/java/com/yuyou/openapi/openapi/utils/ConvertHelp.java @@ -0,0 +1,54 @@ +package com.yuyou.openapi.openapi.utils; + +import com.alibaba.fastjson.JSONObject; +import com.yuyou.openapi.openapi.common.security.SecurityConstants; +import com.yuyou.openapi.openapi.common.security.SecurityService; +import com.yuyou.openapi.openapi.exception.SecretException; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; + +public final class ConvertHelp implements SecurityConstants { + + /** + * 表单通用接口的值类型转换 + * @param columnRules 列转换规则 + * @param filedValues + * @return + * @throws SecretException + */ + public static Map convertFields(JSONObject columnRules, JSONObject filedValues) throws SecretException { + Map resultMap = new HashMap<>(columnRules.size()); + for (String column : columnRules.keySet()) { + Object rule = columnRules.get(column); + // 特殊列名在匹配时进行转换 + column = StringUtils.isNotEmpty(column) && "pnum" == column ? "mobile" : column; + String value = filedValues.getString(column); + + if (rule instanceof Integer) { + Integer tag = (Integer) rule; + switch (tag) { + case PHONE_ENCRYPT_TAG: + String pnum = SecurityService.encrypt(value, SecurityConstants.PHONE); + resultMap.put("pnum", pnum); + break; + case SIMPLE_ENCRYPT_TAG: + String encryptedValue = SecurityService.encrypt(value, SecurityConstants.SIMPLE); + resultMap.put(column, encryptedValue); + break; + default: + resultMap.put(column, value); + break; + } + } else if (rule instanceof JSONObject) { + // 基于规则进行映射, 规则统一有一个"未知"映射 + JSONObject map = (JSONObject) rule; + Object mapValue = map.keySet().contains(value) ? map.get(value) : map.get("未知"); + resultMap.put(column, mapValue); + } else { + continue; + } + } + return resultMap; + } +}