Ver código fonte

Merge remote-tracking branch 'origin/master_feat_ysy_20250929' into master_feat_ysy_20250929

三七 1 mês atrás
pai
commit
bee7046268
26 arquivos alterados com 1365 adições e 49 exclusões
  1. 104 0
      fs-admin/src/main/java/com/fs/his/controller/OpenApiFsUserInformationController.java
  2. 10 0
      fs-admin/src/main/java/com/fs/his/task/Task.java
  3. 0 22
      fs-company/src/main/java/com/fs/company/controller/company/OpenApiFsUserInformationController.java
  4. 3 0
      fs-service/src/main/java/com/fs/his/domain/FsUser.java
  5. 17 0
      fs-service/src/main/java/com/fs/his/dto/CPWUserAndAddressAddDTO.java
  6. 23 0
      fs-service/src/main/java/com/fs/his/dto/CPWUserBaseInfoAddDTO.java
  7. 31 0
      fs-service/src/main/java/com/fs/his/dto/CPWUserInfoCollectionAddDTO.java
  8. 70 4
      fs-service/src/main/java/com/fs/his/service/impl/FsQuestionAndAnswerServiceImpl.java
  9. 129 0
      fs-service/src/main/java/com/fs/his/utils/IdCardUtil.java
  10. 147 0
      fs-service/src/main/java/com/fs/his/validator/cpw/AddressValidator.java
  11. 136 0
      fs-service/src/main/java/com/fs/his/validator/cpw/AnswerValidator.java
  12. 82 0
      fs-service/src/main/java/com/fs/his/validator/cpw/BaseInfoValidator.java
  13. 105 0
      fs-service/src/main/java/com/fs/his/validator/cpw/CollectionInfoFixer.java
  14. 100 0
      fs-service/src/main/java/com/fs/his/validator/cpw/CollectionInfoValidator.java
  15. 59 0
      fs-service/src/main/java/com/fs/his/validator/cpw/OptionValidator.java
  16. 56 0
      fs-service/src/main/java/com/fs/his/validator/cpw/UserInformationValidator.java
  17. 8 0
      fs-service/src/main/java/com/fs/his/validator/cpw/Validator.java
  18. 38 0
      fs-service/src/main/java/com/fs/his/vo/SubmitUserInformationVO.java
  19. 6 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsUserInformationCollection.java
  20. 5 0
      fs-service/src/main/java/com/fs/hisStore/mapper/FsUserInformationCollectionMapper.java
  21. 31 0
      fs-service/src/main/java/com/fs/hisStore/service/IOpenApiCPWUserInformationService.java
  22. 0 11
      fs-service/src/main/java/com/fs/hisStore/service/IOpenApiFsUserInformationService.java
  23. 176 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/OpenApiCPWUserInformationServiceImpl.java
  24. 0 10
      fs-service/src/main/java/com/fs/hisStore/service/impl/OpenApiFsUserInformationServiceImpl.java
  25. 6 1
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  26. 23 1
      fs-service/src/main/resources/mapper/hisStore/FsUserInformationCollectionMapper.xml

+ 104 - 0
fs-admin/src/main/java/com/fs/his/controller/OpenApiFsUserInformationController.java

@@ -0,0 +1,104 @@
+package com.fs.his.controller;
+
+import com.fs.common.core.controller.BaseController;
+import com.fs.common.core.domain.AjaxResult;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.dto.CPWUserAndAddressAddDTO;
+import com.fs.his.dto.CPWUserInfoCollectionAddDTO;
+import com.fs.his.validator.cpw.UserInformationValidator;
+import com.fs.his.vo.FsQuestionAndAnswerVO;
+import com.fs.his.vo.SubmitUserInformationVO;
+import com.fs.hisStore.service.IOpenApiCPWUserInformationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+/**
+ * 用户信息采集接口(第三方使用)---超拼网
+ * */
+@RestController
+@RequestMapping("/ysy-api/template")
+public class OpenApiFsUserInformationController extends BaseController {
+
+    @Autowired
+    private IOpenApiCPWUserInformationService openApiFsUserInformationService;
+
+    @Autowired
+    private UserInformationValidator userInformationValidator;
+    /**
+     * 获取信息采集问题模板
+     * */
+    @GetMapping("/getInformationTemplate")
+    public AjaxResult getInformationTemplate(){
+        try {
+            FsQuestionAndAnswerVO informationTemplate = openApiFsUserInformationService.getInformationTemplate();
+            if (informationTemplate == null) {
+                return AjaxResult.error("未找到信息采集模板");
+            }
+            return AjaxResult.success(informationTemplate);
+        } catch (CustomException e) {
+            logger.error("获取信息采集模板业务异常:{}", e.getMessage());
+            if (e.getCode() != null) {
+                return AjaxResult.error(e.getCode(), e.getMessage());
+            }
+            return AjaxResult.error(e.getMessage());
+        } catch (Exception e) {
+            logger.error("获取信息采集模板系统异常:", e);
+            return AjaxResult.error("系统处理异常,请稍后重试");
+        }
+    }
+
+    /**
+     * 创建用户信息与地址--超拼网
+     * */
+    @PostMapping("/createUserInformation")
+    public AjaxResult createUserInformationCPW(@RequestBody CPWUserAndAddressAddDTO cpwUserAndAddressAddDTO) {
+        try {
+            // 参数校验
+            userInformationValidator.validateBaseInfo(cpwUserAndAddressAddDTO.getUserBaseInfo());
+            userInformationValidator.validateUserAndAddress(cpwUserAndAddressAddDTO.getAddress());
+            SubmitUserInformationVO result = openApiFsUserInformationService.createUserInformationCPW(cpwUserAndAddressAddDTO);
+            return AjaxResult.success(result);
+        } catch (CustomException e) {
+            logger.error("创建用户信息与地址业务异常:{}", e.getMessage());
+            return AjaxResult.error("系统处理异常,请稍后重试");
+        }
+    }
+    /**
+     * 提交用户采集信息
+     * */
+    @PostMapping("/submitUserInformation")
+    public AjaxResult submitUserInformation(@RequestBody CPWUserInfoCollectionAddDTO collectionAddDTO) {
+        try {
+            // 参数校验
+            userInformationValidator.validateCollection(collectionAddDTO);
+            SubmitUserInformationVO result = openApiFsUserInformationService.submitUserInformation(collectionAddDTO);
+            return AjaxResult.success(result);
+        } catch (CustomException e) {
+            logger.error("提交用户采集信息业务异常:{}", e.getMessage());
+            Map<String, Object> errorDetail = new HashMap<>();
+            errorDetail.put("errorCode", e.getCode() != null ? e.getCode() : 500);
+            errorDetail.put("errorMessage", e.getMessage());
+            errorDetail.put("timestamp", DateUtils.getTime());
+            errorDetail.put("path", "/ysy-api/template/submitUserInformation");
+
+            AjaxResult result = AjaxResult.error(e.getCode() != null ? e.getCode() : 500, e.getMessage());
+            result.put("data", errorDetail);
+
+            return result;
+
+        } catch (Exception e) {
+            logger.error("提交用户采集信息系统异常:", e);
+            return AjaxResult.error("系统繁忙,请稍后重试");
+        }
+    }
+
+    /**
+     * 推送处方信息给第三方
+     * */
+    @PostMapping("/pushPrescription")
+    public AjaxResult pushPrescription() {
+        return AjaxResult.success();
+    }
+}

+ 10 - 0
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -2393,4 +2393,14 @@ public class Task {
 
     }
 
+    /**
+     * 定时修复用户采集信息电话号码与性别存储异常的问题
+     * */
+    public void resolvePhoneNumberGenderDataStoreIssue(){
+        int i = userInformationCollectionMapper.resolvePhoneNumberGenderDataStoreIssue();
+        if (i > 0){
+            log.info("修复用户采集信息电话号码与性别存储异常的问题成功,数量:{}",i);
+        }
+    }
+
 }

+ 0 - 22
fs-company/src/main/java/com/fs/company/controller/company/OpenApiFsUserInformationController.java

@@ -1,22 +0,0 @@
-package com.fs.company.controller.company;
-
-import com.fs.hisStore.service.IOpenApiFsUserInformationService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/index/statistics")
-public class OpenApiFsUserInformationController {
-
-    @Autowired
-    private IOpenApiFsUserInformationService openApiFsUserInformationService;
-
-    /**
-     * 获取信息采集问题模板
-     * */
-
-    /**
-     * 提交用户采集信息
-     * */
-}

+ 3 - 0
fs-service/src/main/java/com/fs/his/domain/FsUser.java

@@ -168,6 +168,9 @@ public class FsUser extends BaseEntity
 
     /** app登录后不为null(表示是否下载app) */
     private String historyApp;
+
+    /** 用户来源;2:超拼网,null或者空为系统客户*/
+    private Integer userSource;
     public void setNickName(String nickname)
     {
         if(StringUtils.isNotEmpty(nickname)){

+ 17 - 0
fs-service/src/main/java/com/fs/his/dto/CPWUserAndAddressAddDTO.java

@@ -0,0 +1,17 @@
+package com.fs.his.dto;
+
+import com.fs.his.domain.FsUserAddress;
+import lombok.Data;
+
+@Data
+public class CPWUserAndAddressAddDTO {
+    /**
+     * 用户基础信息
+     */
+    private CPWUserBaseInfoAddDTO userBaseInfo;
+
+    /**
+     * 用户地址信息
+     * */
+    private FsUserAddress address;
+}

+ 23 - 0
fs-service/src/main/java/com/fs/his/dto/CPWUserBaseInfoAddDTO.java

@@ -0,0 +1,23 @@
+package com.fs.his.dto;
+
+import lombok.Data;
+/**
+ * 新增用户基础信息---超拼网
+ * */
+@Data
+public class CPWUserBaseInfoAddDTO {
+    /**
+     * 用户姓名
+     */
+    private String realName;
+
+    /**
+     * 身份证号码
+     */
+    private String idCard;
+
+    /**
+     * 用户电话
+     */
+    private String userPhone;
+}

+ 31 - 0
fs-service/src/main/java/com/fs/his/dto/CPWUserInfoCollectionAddDTO.java

@@ -0,0 +1,31 @@
+package com.fs.his.dto;
+
+
+import com.fs.his.vo.AnswerVO;
+import lombok.Data;
+
+import java.util.List;
+/**
+ * 新增用户采集信息---超拼网
+ * */
+@Data
+public class CPWUserInfoCollectionAddDTO {
+    /**
+     * 用户主键
+     * */
+    private Long userId;
+    /**
+     * 模板问题以及答案
+     */
+    private List<AnswerVO> answers;
+
+    /**
+     * 过敏症状描述,默认无
+     */
+    private String allergy;
+
+    /**
+     * 备注(非必填)
+     */
+    private String remark;
+}

+ 70 - 4
fs-service/src/main/java/com/fs/his/service/impl/FsQuestionAndAnswerServiceImpl.java

@@ -3,16 +3,19 @@ package com.fs.his.service.impl;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.DateUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.fs.his.vo.AnswerVO;
 import com.fs.his.vo.FsQuestionAndAnswerVO;
 import com.fs.his.vo.OptionsVO;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import com.fs.his.mapper.FsQuestionAndAnswerMapper;
@@ -26,12 +29,30 @@ import org.springframework.util.CollectionUtils;
  * @author fs
  * @date 2025-09-29
  */
+@Slf4j
 @Service
 public class FsQuestionAndAnswerServiceImpl extends ServiceImpl<FsQuestionAndAnswerMapper, FsQuestionAndAnswer> implements IFsQuestionAndAnswerService {
 
 
     @Autowired
     private FsQuestionAndAnswerMapper fsQuestionAndAnswerMapper;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    /**
+     * 问答模板缓存key前缀
+     * 格式:fs:question:template:{id}
+     */
+    public static final String QUESTION_TEMPLATE_KEY = "fs:question:template:";
+
+    /**
+     * 缓存过期时间:60天(单位:秒)
+     * 60天 = 60 * 24 * 60 * 60 = 5,184,000秒
+     * 因为模板很少修改,设置较长的过期时间
+     */
+    private static final long CACHE_EXPIRE_TIME = 5184000L;
+
     /**
      * 查询问答
      * 
@@ -41,12 +62,22 @@ public class FsQuestionAndAnswerServiceImpl extends ServiceImpl<FsQuestionAndAns
     @Override
     public FsQuestionAndAnswerVO selectFsQuestionAndAnswerById(Long id)
     {
+        String cacheKey = getTemplateKey(id);
+        FsQuestionAndAnswerVO cachedVo = redisCache.getCacheObject(cacheKey);
+        if (cachedVo != null) {
+            return cachedVo;
+        }
+        log.info("缓存未命中,从数据库查询问答模板,id: {}", id);
         FsQuestionAndAnswer fsQuestionAndAnswer = baseMapper.selectFsQuestionAndAnswerById(id);
+        if (fsQuestionAndAnswer == null) {
+            return null;
+        }
         FsQuestionAndAnswerVO vo = new FsQuestionAndAnswerVO();
         vo.setQuestionName(fsQuestionAndAnswer.getQuestionName());
         List<AnswerVO> answerVOS = JSON.parseArray(fsQuestionAndAnswer.getJsonInfo(), AnswerVO.class);
         vo.setAnswers(answerVOS);
         vo.setId(fsQuestionAndAnswer.getId());
+        redisCache.setCacheObject(cacheKey, vo, (int) CACHE_EXPIRE_TIME, TimeUnit.SECONDS);
         return vo;
     }
 
@@ -72,7 +103,13 @@ public class FsQuestionAndAnswerServiceImpl extends ServiceImpl<FsQuestionAndAns
     public int insertFsQuestionAndAnswer(FsQuestionAndAnswer fsQuestionAndAnswer)
     {
         fsQuestionAndAnswer.setCreateTime(DateUtils.getNowDate());
-        return baseMapper.insertFsQuestionAndAnswer(fsQuestionAndAnswer);
+        int result  = baseMapper.insertFsQuestionAndAnswer(fsQuestionAndAnswer);
+        if (result > 0 && fsQuestionAndAnswer.getId() != null) {
+            String cacheKey = getTemplateKey(fsQuestionAndAnswer.getId());
+            redisCache.deleteObject(cacheKey);
+            log.info("新增问答模板,清除缓存,id: {}, key: {}", fsQuestionAndAnswer.getId(), cacheKey);
+        }
+        return result;
     }
 
     /**
@@ -85,7 +122,13 @@ public class FsQuestionAndAnswerServiceImpl extends ServiceImpl<FsQuestionAndAns
     public int updateFsQuestionAndAnswer(FsQuestionAndAnswer fsQuestionAndAnswer)
     {
         fsQuestionAndAnswer.setUpdateTime(DateUtils.getNowDate());
-        return baseMapper.updateFsQuestionAndAnswer(fsQuestionAndAnswer);
+        int result=baseMapper.updateFsQuestionAndAnswer(fsQuestionAndAnswer);
+        if (result > 0 && fsQuestionAndAnswer.getId() != null) {
+            String cacheKey = getTemplateKey(fsQuestionAndAnswer.getId());
+            redisCache.deleteObject(cacheKey);
+            log.info("修改问答模板,清除缓存,id: {}, key: {}", fsQuestionAndAnswer.getId(), cacheKey);
+        }
+        return result;
     }
 
     /**
@@ -97,7 +140,15 @@ public class FsQuestionAndAnswerServiceImpl extends ServiceImpl<FsQuestionAndAns
     @Override
     public int deleteFsQuestionAndAnswerByIds(Long[] ids)
     {
-        return baseMapper.deleteFsQuestionAndAnswerByIds(ids);
+        int result=baseMapper.deleteFsQuestionAndAnswerByIds(ids);
+        if (result > 0 && ids != null && ids.length > 0) {
+            for (Long id : ids) {
+                String cacheKey = getTemplateKey(id);
+                redisCache.deleteObject(cacheKey);
+                log.info("批量删除问答模板,清除缓存,id: {}, key: {}", id, cacheKey);
+            }
+        }
+        return result;
     }
 
     /**
@@ -109,7 +160,13 @@ public class FsQuestionAndAnswerServiceImpl extends ServiceImpl<FsQuestionAndAns
     @Override
     public int deleteFsQuestionAndAnswerById(Long id)
     {
-        return baseMapper.deleteFsQuestionAndAnswerById(id);
+        int result=baseMapper.deleteFsQuestionAndAnswerById(id);
+        if (result > 0) {
+            String cacheKey = getTemplateKey(id);
+            redisCache.deleteObject(cacheKey);
+            log.info("删除问答模板,清除缓存,id: {}, key: {}", id, cacheKey);
+        }
+        return result;
     }
 
     @Override
@@ -133,4 +190,13 @@ public class FsQuestionAndAnswerServiceImpl extends ServiceImpl<FsQuestionAndAns
         }
         return vos;
     }
+
+    /**
+     * 获取完整的模板缓存key
+     * @param id 模板ID
+     * @return 完整的key
+     */
+    private String getTemplateKey(Long id) {
+        return QUESTION_TEMPLATE_KEY + id;
+    }
 }

+ 129 - 0
fs-service/src/main/java/com/fs/his/utils/IdCardUtil.java

@@ -14,7 +14,11 @@ import org.springframework.stereotype.Service;
 
 import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
 import java.util.Date;
+import java.util.regex.Pattern;
 
 /**
  * 三方身份验证
@@ -24,6 +28,13 @@ public class IdCardUtil {
     @Autowired
     private static ISysConfigService configService;
 
+    // 加权因子
+    private static final int[] WEIGHT_FACTORS = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
+    // 校验码
+    private static final char[] CHECK_CODES = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
+    // 正则表达式,用于初步验证格式
+    private static final Pattern ID_CARD_PATTERN = Pattern.compile("^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$");
+
     /**
      * 身份证信息隐藏 中间
      * @param idCard   身份证信息
@@ -140,4 +151,122 @@ public class IdCardUtil {
             throw new RuntimeException("匹配请求异常", e);
         }
     }
+
+    /**
+     * 校验身份证号码是否合规
+     *
+     * @param idCard 身份证号码(18位)
+     * @return 合规返回 true,否则返回 false
+     */
+    public static boolean isValidIdCard(String idCard) {
+        if (idCard == null || idCard.length() != 18) {
+            return false;
+        }
+
+        // 转换为大写,方便后续校验
+        idCard = idCard.toUpperCase();
+
+        // 1. 使用正则表达式进行初步格式校验
+        if (!ID_CARD_PATTERN.matcher(idCard).matches()) {
+            return false;
+        }
+
+        try {
+            // 2. 校验出生日期的有效性
+            String birthPart = idCard.substring(6, 14);
+            LocalDate birthDate = LocalDate.parse(birthPart, DateTimeFormatter.ofPattern("yyyyMMdd"));
+            //检查出生日期是否在未来
+            if (birthDate.isAfter(LocalDate.now())) {
+                return false;
+            }
+
+            // 3. 校验最后一位校验码
+            String body = idCard.substring(0, 17);
+            char lastChar = idCard.charAt(17);
+
+            int sum = 0;
+            for (int i = 0; i < 17; i++) {
+                sum += (body.charAt(i) - '0') * WEIGHT_FACTORS[i];
+            }
+            int remainder = sum % 11;
+            char expectedCheckCode = CHECK_CODES[remainder];
+
+            return lastChar == expectedCheckCode;
+
+        } catch (NumberFormatException | DateTimeParseException e) {
+            // 在解析数字或日期时发生错误,说明身份证号码无效
+            return false;
+        }
+    }
+
+    /**
+     * 根据身份证号码提取年龄
+     *
+     * @param idCard 身份证号码(18位)
+     * @return 年龄(int类型),如果身份证号码无效则返回 -1
+     */
+    public static int getAgeByIdCard(String idCard) {
+        if (!isValidIdCard(idCard)) {
+            // 身份证无效,返回-1表示无法计算
+            return -1;
+        }
+
+        try {
+            String birthPart = idCard.substring(6, 14);
+            LocalDate birthDate = LocalDate.parse(birthPart, DateTimeFormatter.ofPattern("yyyyMMdd"));
+            LocalDate now = LocalDate.now();
+
+            // 使用Period.between计算年份差
+            return java.time.Period.between(birthDate, now).getYears();
+        } catch (Exception e) {
+            return -1;
+        }
+    }
+
+    /**
+     * 根据身份证号码提取出生日期
+     *
+     * @param idCard 身份证号码(18位)
+     * @return 出生日期,格式为 "yyyy-MM-dd",如果身份证号码无效则返回 null
+     */
+    public static String getDateOfBirthByIdCard(String idCard) {
+        if (!isValidIdCard(idCard)) {
+            // 身份证无效
+            return null;
+        }
+
+        try {
+            String birthPart = idCard.substring(6, 14);
+            LocalDate birthDate = LocalDate.parse(birthPart, DateTimeFormatter.ofPattern("yyyyMMdd"));
+            // 格式化为 "yyyy-MM-dd"
+            return birthDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 根据身份证号码获取性别
+     *
+     * @param idCard 身份证号码(18位)
+     * @return 性别,0表示女,1表示男,如果身份证号码无效则返回 -1
+     */
+    public static int getGenderByIdCard(String idCard) {
+        if (!isValidIdCard(idCard)) {
+            // 身份证无效,返回-1表示无法获取
+            return -1;
+        }
+
+        try {
+            // 18位身份证号码的第17位是性别位
+            String genderStr = idCard.substring(16, 17);
+            int genderNum = Integer.parseInt(genderStr);
+
+            // 根据国标,奇数为男性,偶数为女性
+            return (genderNum % 2 == 1) ? 1 : 0;
+        } catch (Exception e) {
+            // 理论上,能通过上面的校验,这里就不会出错。但为了安全起见,捕获异常
+            return -1;
+        }
+    }
 }

+ 147 - 0
fs-service/src/main/java/com/fs/his/validator/cpw/AddressValidator.java

@@ -0,0 +1,147 @@
+package com.fs.his.validator.cpw;
+
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.domain.FsUserAddress;
+import org.springframework.stereotype.Component;
+
+/**
+ * 用户地址校验器
+ */
+@Component
+public class AddressValidator implements Validator<FsUserAddress> {
+
+    @Override
+    public void validate(FsUserAddress address) {
+        if (address == null) {
+            throw new CustomException("用户地址信息不能为空", 400);
+        }
+
+        // 收货人姓名校验
+        validateReceiverName(address.getRealName());
+
+        // 收货人电话校验
+        validatePhone(address.getPhone());
+
+        // 省份校验
+        validateProvince(address.getProvince());
+
+        // 城市校验
+        validateCity(address.getCity());
+
+        // 区/县校验
+        validateDistrict(address.getDistrict());
+
+        // 详细地址校验
+        validateDetail(address.getDetail());
+    }
+
+    /**
+     * 校验收货人姓名
+     */
+    private void validateReceiverName(String realName) {
+        if (StringUtils.isEmpty(realName)) {
+            throw new CustomException("收货人姓名不能为空", 400);
+        }
+
+        if (realName.length() < 2 || realName.length() > 20) {
+            throw new CustomException("收货人姓名长度必须在2-20个字符之间", 400);
+        }
+
+        if (realName.matches(".*\\d.*")) {
+            throw new CustomException("收货人姓名不能包含数字", 400);
+        }
+
+        if (!realName.matches("^[\\u4e00-\\u9fa5a-zA-Z\\s]+$")) {
+            throw new CustomException("收货人姓名只能包含中文、英文和空格", 400);
+        }
+    }
+
+    /**
+     * 校验收货人电话
+     */
+    private void validatePhone(String phone) {
+        if (StringUtils.isEmpty(phone)) {
+            throw new CustomException("收货人电话不能为空", 400);
+        }
+
+        // 支持手机号或固话
+        if (!phone.matches("^1[3-9]\\d{9}$") && !phone.matches("^\\d{3,4}-?\\d{7,8}$")) {
+            throw new CustomException("收货人电话格式不正确,请输入正确的手机号或固定电话", 400);
+        }
+    }
+
+    /**
+     * 校验省份
+     */
+    private void validateProvince(String province) {
+        if (StringUtils.isEmpty(province)) {
+            throw new CustomException("所在省份不能为空", 400);
+        }
+
+        if (province.length() > 50) {
+            throw new CustomException("省份名称不能超过50个字符", 400);
+        }
+    }
+
+    /**
+     * 校验城市
+     */
+    private void validateCity(String city) {
+        if (StringUtils.isEmpty(city)) {
+            throw new CustomException("所在城市不能为空", 400);
+        }
+
+        if (city.length() > 50) {
+            throw new CustomException("城市名称不能超过50个字符", 400);
+        }
+    }
+
+    /**
+     * 校验区/县
+     */
+    private void validateDistrict(String district) {
+        if (StringUtils.isEmpty(district)) {
+            throw new CustomException("所在区/县不能为空", 400);
+        }
+
+        if (district.length() > 50) {
+            throw new CustomException("区/县名称不能超过50个字符", 400);
+        }
+    }
+
+    /**
+     * 校验详细地址
+     */
+    private void validateDetail(String detail) {
+        if (StringUtils.isEmpty(detail)) {
+            throw new CustomException("详细地址不能为空", 400);
+        }
+
+        if (detail.length() < 5 || detail.length() > 200) {
+            throw new CustomException("详细地址长度必须在5-200个字符之间", 400);
+        }
+
+        // 去除前后空格后校验
+        String trimmedDetail = detail.trim();
+        if (trimmedDetail.length() < 5) {
+            throw new CustomException("详细地址不能少于5个有效字符", 400);
+        }
+    }
+
+    /**
+     * 校验城市ID串
+     */
+    public void validateCityIds(String cityIds) {
+        if (StringUtils.isNotEmpty(cityIds)) {
+            String[] ids = cityIds.split(",");
+            for (String id : ids) {
+                try {
+                    Long.parseLong(id.trim());
+                } catch (NumberFormatException e) {
+                    throw new CustomException("城市ID格式不正确", 400);
+                }
+            }
+        }
+    }
+}

+ 136 - 0
fs-service/src/main/java/com/fs/his/validator/cpw/AnswerValidator.java

@@ -0,0 +1,136 @@
+package com.fs.his.validator.cpw;
+
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.vo.AnswerVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 问题答案校验器
+ */
+@Component
+public class AnswerValidator implements Validator<AnswerVO> {
+
+    @Autowired
+    private OptionValidator optionValidator;
+
+    @Override
+    public void validate(AnswerVO answer) {
+        validateAnswer(answer, 0);
+    }
+
+    /**
+     * 带索引的校验方法
+     */
+    public void validate(AnswerVO answer, int index) {
+        if (answer == null) {
+            throw new CustomException(String.format("第%d个问题的答案不能为空", index + 1), 400);
+        }
+        validateAnswer(answer, index);
+    }
+
+    private void validateAnswer(AnswerVO answer, int index) {
+        // 1. 问题标题校验
+        validateTitle(answer.getTitle(), index);
+
+        // 2. 选项列表校验
+        validateOptions(answer);
+
+        // 3. 已选择的值校验
+        validateSelectedValues(answer);
+
+        // 4. 排序字段校验
+        validateSort(answer.getSort(), answer.getTitle());
+
+        // 5. flag字段处理
+        handleFlag(answer);
+    }
+
+    /**
+     * 校验问题标题
+     */
+    private void validateTitle(String title, int index) {
+        if (StringUtils.isEmpty(title)) {
+            throw new CustomException(String.format("第%d个问题的问题名称不能为空", index + 1), 400);
+        }
+        
+        if (title.length() > 200) {
+            throw new CustomException(String.format("第%d个问题的问题名称不能超过200个字符", index + 1), 400);
+        }
+    }
+
+    /**
+     * 校验选项列表
+     */
+    private void validateOptions(AnswerVO answer) {
+        if (answer.getOptions() == null || answer.getOptions().isEmpty()) {
+            throw new CustomException(String.format("问题【%s】的选项列表不能为空", answer.getTitle()), 400);
+        }
+
+        // 检查选项值是否有重复
+        Set<Integer> valueSet = answer.getOptions().stream()
+                .map(AnswerVO.Options::getValue)
+                .collect(Collectors.toSet());
+        
+        if (valueSet.size() != answer.getOptions().size()) {
+            throw new CustomException(String.format("问题【%s】的选项值不能重复", answer.getTitle()), 400);
+        }
+
+        for (int j = 0; j < answer.getOptions().size(); j++) {
+            optionValidator.validate(answer.getOptions().get(j), answer.getTitle(), j);
+        }
+    }
+
+    /**
+     * 校验已选择的值
+     */
+    private void validateSelectedValues(AnswerVO answer) {
+        if (answer.getValue() == null) {
+            throw new CustomException(String.format("问题【%s】的已选择值不能为null", answer.getTitle()), 400);
+        }
+
+        if (!answer.getValue().isEmpty()) {
+            Set<Integer> validOptionValues = answer.getOptions().stream()
+                    .map(AnswerVO.Options::getValue)
+                    .collect(Collectors.toSet());
+
+            for (Integer selectedValue : answer.getValue()) {
+                if (selectedValue == null) {
+                    throw new CustomException(String.format("问题【%s】的选择值不能为null", answer.getTitle()), 400);
+                }
+                if (!validOptionValues.contains(selectedValue)) {
+                    throw new CustomException(
+                            String.format("问题【%s】的选择值 %d 不在有效选项范围内", answer.getTitle(), selectedValue),
+                            400
+                    );
+                }
+            }
+        }
+    }
+
+    /**
+     * 校验排序字段
+     */
+    private void validateSort(Integer sort, String questionTitle) {
+        if (sort == null) {
+            throw new CustomException(String.format("问题【%s】的排序字段不能为空", questionTitle), 400);
+        }
+        
+        if (sort < 0) {
+            throw new CustomException(String.format("问题【%s】的排序字段不能为负数", questionTitle), 400);
+        }
+    }
+
+    /**
+     * 处理flag字段
+     */
+    private void handleFlag(AnswerVO answer) {
+        if (answer.getFlag() == null) {
+            answer.setFlag(false);
+        }
+    }
+}

+ 82 - 0
fs-service/src/main/java/com/fs/his/validator/cpw/BaseInfoValidator.java

@@ -0,0 +1,82 @@
+package com.fs.his.validator.cpw;
+
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.dto.CPWUserBaseInfoAddDTO;
+import com.fs.his.utils.IdCardUtil;
+import org.springframework.stereotype.Component;
+
+/**
+ * 用户基本信息校验器
+ */
+@Component
+public class BaseInfoValidator implements Validator<CPWUserBaseInfoAddDTO> {
+
+    @Override
+    public void validate(CPWUserBaseInfoAddDTO userBaseInfo) {
+        if (userBaseInfo == null) {
+            throw new CustomException("用户基本信息不能为空", 400);
+        }
+
+        // 姓名校验
+        validateRealName(userBaseInfo.getRealName());
+        
+        // 手机号校验
+        validateUserPhone(userBaseInfo.getUserPhone());
+        
+        // 身份证校验
+        validateIdCard(userBaseInfo.getIdCard());
+    }
+
+    /**
+     * 校验用户姓名
+     */
+    private void validateRealName(String realName) {
+        if (StringUtils.isEmpty(realName)) {
+            throw new CustomException("用户姓名不能为空", 400);
+        }
+
+        if (realName.length() < 2 || realName.length() > 20) {
+            throw new CustomException("用户姓名长度必须在2-20个字符之间", 400);
+        }
+
+        if (realName.matches(".*\\d.*")) {
+            throw new CustomException("用户姓名不能包含数字", 400);
+        }
+
+        if (realName.matches(".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?].*")) {
+            throw new CustomException("用户姓名不能包含特殊字符", 400);
+        }
+
+        if (!realName.matches("^[\\u4e00-\\u9fa5a-zA-Z\\s]+$")) {
+            throw new CustomException("用户姓名只能包含中文、英文和空格", 400);
+        }
+    }
+
+    /**
+     * 校验用户手机号
+     */
+    private void validateUserPhone(String userPhone) {
+        if (StringUtils.isEmpty(userPhone)) {
+            throw new CustomException("用户手机号不能为空", 400);
+        }
+        
+        // 手机号必须是11位数字
+        if (!userPhone.matches("^1[3-9]\\d{9}$")) {
+            throw new CustomException("用户手机号格式不正确,必须是11位手机号", 400);
+        }
+    }
+
+    /**
+     * 校验身份证
+     */
+    private void validateIdCard(String idCard) {
+        if (StringUtils.isEmpty(idCard)) {
+            throw new CustomException("用户身份证不能为空", 400);
+        }
+
+        if (!IdCardUtil.isValidIdCard(idCard)) {
+            throw new CustomException("用户身份证格式不正确", 400);
+        }
+    }
+}

+ 105 - 0
fs-service/src/main/java/com/fs/his/validator/cpw/CollectionInfoFixer.java

@@ -0,0 +1,105 @@
+package com.fs.his.validator.cpw;
+
+import com.fs.his.dto.CPWUserInfoCollectionAddDTO;
+import com.fs.his.vo.AnswerVO;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * 采集信息格式修复器
+ * 用于修复第三方提交的格式问题
+ */
+@Component
+public class CollectionInfoFixer {
+
+    /**
+     * 修复采集信息格式
+     */
+    public void fix(CPWUserInfoCollectionAddDTO collectionAddDTO) {
+        if (collectionAddDTO == null || collectionAddDTO.getAnswers() == null) {
+            return;
+        }
+
+        // 修复过敏情况
+        fixAllergy(collectionAddDTO);
+
+        // 修复答案列表
+        for (AnswerVO answer : collectionAddDTO.getAnswers()) {
+            fixAnswer(answer);
+        }
+    }
+
+    /**
+     * 修复过敏情况
+     */
+    private void fixAllergy(CPWUserInfoCollectionAddDTO collectionAddDTO) {
+        String allergy = collectionAddDTO.getAllergy();
+        if (allergy == null || allergy.trim().isEmpty()) {
+            collectionAddDTO.setAllergy("无");
+        } else {
+            collectionAddDTO.setAllergy(allergy.trim());
+        }
+    }
+
+    /**
+     * 修复单个答案的格式
+     */
+    private void fixAnswer(AnswerVO answer) {
+        if (answer == null) {
+            return;
+        }
+
+        // 修复title
+        if (answer.getTitle() != null) {
+            answer.setTitle(answer.getTitle().trim());
+        }
+
+        // 修复value字段
+        if (answer.getValue() == null) {
+            answer.setValue(new ArrayList<>());
+        }
+
+        // 修复flag字段
+        if (answer.getFlag() == null) {
+            answer.setFlag(false);
+        }
+
+        // 修复options
+        fixOptions(answer);
+    }
+
+    /**
+     * 修复选项列表
+     */
+    private void fixOptions(AnswerVO answer) {
+        if (answer.getOptions() == null) {
+            answer.setOptions(new ArrayList<>());
+            return;
+        }
+
+        // 去重
+        Set<Integer> seenValues = new HashSet<>();
+        List<AnswerVO.Options> uniqueOptions = new ArrayList<>();
+
+        for (AnswerVO.Options option : answer.getOptions()) {
+            if (option == null) {
+                continue;
+            }
+            
+            // 修复option
+            if (option.getName() != null) {
+                option.setName(option.getName().trim());
+            }
+            
+            if (option.getValue() != null && !seenValues.contains(option.getValue())) {
+                seenValues.add(option.getValue());
+                uniqueOptions.add(option);
+            }
+        }
+
+        // 按value排序
+        uniqueOptions.sort(Comparator.comparing(AnswerVO.Options::getValue));
+        answer.setOptions(uniqueOptions);
+    }
+}

+ 100 - 0
fs-service/src/main/java/com/fs/his/validator/cpw/CollectionInfoValidator.java

@@ -0,0 +1,100 @@
+package com.fs.his.validator.cpw;
+
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.dto.CPWUserInfoCollectionAddDTO;
+import com.fs.his.vo.AnswerVO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 采集信息校验器
+ */
+@Component
+public class CollectionInfoValidator implements Validator<CPWUserInfoCollectionAddDTO> {
+
+    @Autowired
+    private AnswerValidator answerValidator;
+
+    @Override
+    public void validate(CPWUserInfoCollectionAddDTO collectionAddDTO) {
+        if (collectionAddDTO == null) {
+            throw new CustomException("采集信息不能为空", 400);
+        }
+
+        // 校验用户ID(如果是提交采集信息时需要)
+        validateUserId(collectionAddDTO.getUserId());
+
+        // 校验答案列表
+        validateAnswers(collectionAddDTO.getAnswers());
+
+        // 校验过敏情况
+        validateAllergy(collectionAddDTO.getAllergy());
+
+        // 校验备注
+        validateRemark(collectionAddDTO.getRemark());
+    }
+
+    /**
+     * 校验用户ID
+     */
+    private void validateUserId(Long userId) {
+        if (userId == null) {
+            throw new CustomException("用户ID不能为空", 400);
+        }
+        
+        if (userId <= 0) {
+            throw new CustomException("用户ID格式不正确", 400);
+        }
+    }
+
+    /**
+     * 校验答案列表
+     */
+    private void validateAnswers(List<AnswerVO> answers) {
+        if (answers == null || answers.isEmpty()) {
+            throw new CustomException("用户采集信息不能为空", 400);
+        }
+
+        // 检查是否有重复的排序
+        long distinctSortCount = answers.stream()
+                .map(AnswerVO::getSort)
+                .filter(sort -> sort != null)
+                .distinct()
+                .count();
+        
+        if (distinctSortCount != answers.size()) {
+            throw new CustomException("问题的排序字段不能重复", 400);
+        }
+
+        for (int i = 0; i < answers.size(); i++) {
+            answerValidator.validate(answers.get(i), i);
+        }
+    }
+
+    /**
+     * 校验过敏情况
+     */
+    private void validateAllergy(String allergy) {
+        if (allergy != null) {
+            if (allergy.length() > 500) {
+                throw new CustomException("过敏情况描述不能超过500个字符", 400);
+            }
+            // 过敏情况默认为"无",如果是空字符串也设置为"无"
+            if (allergy.trim().isEmpty()) {
+                allergy = "无";
+            }
+        }
+    }
+
+    /**
+     * 校验备注
+     */
+    private void validateRemark(String remark) {
+        if (remark != null && remark.length() > 500) {
+            throw new CustomException("备注不能超过500个字符", 400);
+        }
+    }
+}

+ 59 - 0
fs-service/src/main/java/com/fs/his/validator/cpw/OptionValidator.java

@@ -0,0 +1,59 @@
+package com.fs.his.validator.cpw;
+
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.StringUtils;
+import com.fs.his.vo.AnswerVO;
+import org.springframework.stereotype.Component;
+
+/**
+ * 选项校验器
+ */
+@Component
+public class OptionValidator {
+
+    /**
+     * 校验单个选项
+     */
+    public void validate(AnswerVO.Options option, String questionTitle, int optionIndex) {
+        if (option == null) {
+            throw new CustomException(
+                    String.format("问题【%s】的第%d个选项不能为null", questionTitle, optionIndex + 1),
+                    400
+            );
+        }
+
+        validateOptionName(option.getName(), questionTitle, optionIndex);
+        validateOptionValue(option.getValue(), questionTitle, optionIndex);
+    }
+
+    /**
+     * 校验选项名称
+     */
+    private void validateOptionName(String name, String questionTitle, int optionIndex) {
+        if (StringUtils.isEmpty(name)) {
+            throw new CustomException(
+                    String.format("问题【%s】的第%d个选项的名称不能为空", questionTitle, optionIndex + 1),
+                    400
+            );
+        }
+
+        if (name.length() > 100) {
+            throw new CustomException(
+                    String.format("问题【%s】的第%d个选项名称不能超过100个字符", questionTitle, optionIndex + 1),
+                    400
+            );
+        }
+    }
+
+    /**
+     * 校验选项值
+     */
+    private void validateOptionValue(Integer value, String questionTitle, int optionIndex) {
+        if (value == null) {
+            throw new CustomException(
+                    String.format("问题【%s】的第%d个选项的值不能为空", questionTitle, optionIndex + 1),
+                    400
+            );
+        }
+    }
+}

+ 56 - 0
fs-service/src/main/java/com/fs/his/validator/cpw/UserInformationValidator.java

@@ -0,0 +1,56 @@
+package com.fs.his.validator.cpw;
+
+import com.fs.common.exception.CustomException;
+import com.fs.his.domain.FsUserAddress;
+import com.fs.his.dto.CPWUserBaseInfoAddDTO;
+import com.fs.his.dto.CPWUserInfoCollectionAddDTO;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 用户信息主校验器---超拼网
+ * 整合所有校验器
+ */
+@Component
+public class UserInformationValidator {
+
+    @Autowired
+    private BaseInfoValidator baseInfoValidator;
+
+    @Autowired
+    private CollectionInfoValidator collectionInfoValidator;
+
+    @Autowired
+    private AddressValidator addressValidator;
+
+    @Autowired(required = false)
+    private CollectionInfoFixer collectionInfoFixer;
+
+    /**
+     * 只校验采集信息(用于 submitUserInformation 接口)
+     */
+    public void validateCollection(CPWUserInfoCollectionAddDTO collectionAddDTO) {
+        // 先尝试修复格式问题
+        if (collectionInfoFixer != null) {
+            collectionInfoFixer.fix(collectionAddDTO);
+        }
+        collectionInfoValidator.validate(collectionAddDTO);
+    }
+
+    /**
+     * 只校验用户基本信息(用于 createUserInformation 接口)
+     */
+    public void validateBaseInfo(CPWUserBaseInfoAddDTO baseInfoAddDTO) {
+        baseInfoValidator.validate(baseInfoAddDTO);
+    }
+
+    /**
+     * 同时校验用户基本信息和地址(可根据需要扩展)
+     */
+    public void validateUserAndAddress(FsUserAddress address) {
+        if (address==null){
+            throw new CustomException("请填写地址信息");
+        }
+        addressValidator.validate(address);
+    }
+}

+ 8 - 0
fs-service/src/main/java/com/fs/his/validator/cpw/Validator.java

@@ -0,0 +1,8 @@
+package com.fs.his.validator.cpw;
+
+/**
+ * 校验器接口
+ */
+public interface Validator<T> {
+    void validate(T t);
+}

+ 38 - 0
fs-service/src/main/java/com/fs/his/vo/SubmitUserInformationVO.java

@@ -0,0 +1,38 @@
+package com.fs.his.vo;
+
+import lombok.Data;
+import java.io.Serializable;
+
+/**
+ * 提交用户采集信息返回VO--超拼网
+ */
+@Data
+public class SubmitUserInformationVO implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 用户ID
+     */
+    private Long userId;
+    
+    /**
+     * 采集信息ID
+     */
+    private Long collectionId;
+    
+    /**
+     * 提交状态:0-失败 1-成功
+     */
+    private Integer status;
+    
+    /**
+     * 提示信息
+     */
+    private String message;
+    
+    /**
+     * 提交时间
+     */
+    private String submitTime;
+}

+ 6 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsUserInformationCollection.java

@@ -97,4 +97,10 @@ public class FsUserInformationCollection extends BaseEntity{
     //用户年龄
     private Integer age;
 
+    /**
+     * 采集信息来源(null或空为来源于系统本身用户)
+     * 2-超拼网
+     * */
+    private Integer infoSource;
+
 }

+ 5 - 0
fs-service/src/main/java/com/fs/hisStore/mapper/FsUserInformationCollectionMapper.java

@@ -96,4 +96,9 @@ public interface FsUserInformationCollectionMapper extends BaseMapper<FsUserInfo
      * 根据fs_user_id集合批量查询用户信息采集
      * */
     List<FsUserInformationCollection> selectFsUserInformationCollectionListByUserIds(List<Long> fsUserIds);
+
+    /**
+     * 修复手机号码和性别数据存储问题
+     * */
+    int resolvePhoneNumberGenderDataStoreIssue();
 }

+ 31 - 0
fs-service/src/main/java/com/fs/hisStore/service/IOpenApiCPWUserInformationService.java

@@ -0,0 +1,31 @@
+package com.fs.hisStore.service;
+
+import com.fs.his.dto.CPWUserAndAddressAddDTO;
+import com.fs.his.dto.CPWUserInfoCollectionAddDTO;
+import com.fs.his.vo.FsQuestionAndAnswerVO;
+import com.fs.his.vo.SubmitUserInformationVO;
+
+/**
+ * 对接-超拼网-信息采集服务
+ * */
+public interface IOpenApiCPWUserInformationService {
+    /**
+     * 获取信息采集问题模板--超拼网
+     * */
+    public FsQuestionAndAnswerVO getInformationTemplate();
+
+    /**
+     * 创建用户信息与地址--超拼网
+     * */
+    public SubmitUserInformationVO createUserInformationCPW(CPWUserAndAddressAddDTO cpwUserAndAddressAddDTO);
+
+    /**
+     * 提交用户采集信息--超拼网
+     * */
+    public SubmitUserInformationVO submitUserInformation(CPWUserInfoCollectionAddDTO collectionAddDTO);
+
+    /**
+     * 推送处方信息给第三方
+     * */
+    public Object pushPrescription(String userId, String prescriptionId);
+}

+ 0 - 11
fs-service/src/main/java/com/fs/hisStore/service/IOpenApiFsUserInformationService.java

@@ -1,11 +0,0 @@
-package com.fs.hisStore.service;
-
-public interface IOpenApiFsUserInformationService {
-    /**
-     * 获取信息采集问题模板
-     * */
-
-    /**
-     * 提交用户采集信息
-     * */
-}

+ 176 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/OpenApiCPWUserInformationServiceImpl.java

@@ -0,0 +1,176 @@
+package com.fs.hisStore.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.exception.CustomException;
+import com.fs.common.utils.DateUtils;
+import com.fs.his.domain.FsUser;
+import com.fs.his.domain.FsUserAddress;
+import com.fs.his.dto.CPWUserAndAddressAddDTO;
+import com.fs.his.dto.CPWUserBaseInfoAddDTO;
+import com.fs.his.dto.CPWUserInfoCollectionAddDTO;
+import com.fs.his.mapper.FsUserAddressMapper;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.service.IFsQuestionAndAnswerService;
+import com.fs.his.utils.IdCardUtil;
+import com.fs.his.vo.FsQuestionAndAnswerVO;
+import com.fs.his.vo.SubmitUserInformationVO;
+import com.fs.hisStore.domain.FsUserInformationCollection;
+import com.fs.hisStore.mapper.FsUserInformationCollectionMapper;
+import com.fs.hisStore.service.IOpenApiCPWUserInformationService;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import static com.fs.his.utils.PhoneUtil.decryptPhone;
+
+/**
+ * 对接-超拼网-信息采集服务实现类
+ * */
+@Slf4j
+@Service
+public class OpenApiCPWUserInformationServiceImpl implements IOpenApiCPWUserInformationService {
+
+    /** 默认患者问题模板id */
+    private static final Long QUESTION_ID = 10L;
+
+    @Autowired
+    private IFsQuestionAndAnswerService questionAndAnswerService;
+
+    @Autowired
+    private FsUserMapper fsUserMapper;
+
+    @Autowired
+    private FsUserAddressMapper fsUserAddressMapper;
+
+    @Autowired
+    private FsUserInformationCollectionMapper fsUserInformationCollectionMapper;
+
+    @Override
+    public FsQuestionAndAnswerVO getInformationTemplate() {
+        try {
+            FsQuestionAndAnswerVO questionAndAnswerVo = questionAndAnswerService.selectFsQuestionAndAnswerById(QUESTION_ID);
+            if (questionAndAnswerVo == null) {
+                log.error("未找到患者问题模板,模板id:{}", QUESTION_ID);
+                throw new CustomException("未找到信息采集模板", 404);
+            }
+            return questionAndAnswerVo;
+        } catch (Exception e) {
+            log.error("获取信息采集模板失败", e);
+            throw new CustomException("获取信息采集模板失败,请稍后重试", 500);
+        }
+    }
+
+    @Override
+    public SubmitUserInformationVO createUserInformationCPW(CPWUserAndAddressAddDTO cpwUserAndAddressAddDTO) {
+        SubmitUserInformationVO resultVO=new SubmitUserInformationVO();
+        //创建fs_user用户信息
+        CPWUserBaseInfoAddDTO userBaseInfo = cpwUserAndAddressAddDTO.getUserBaseInfo();
+        FsUser addFsUser = transformUserDtoToFsUser(userBaseInfo);
+        int addResult1 = fsUserMapper.insertFsUser(addFsUser);
+        if (addResult1 <= 0) {
+            throw new CustomException("用户信息保存失败", 500);
+        }
+        //新增fs_user_address用户地址信息
+        FsUserAddress address=buildUserAddress(cpwUserAndAddressAddDTO.getAddress(),addFsUser);
+        int addResult2 = fsUserAddressMapper.insertFsUserAddress(address);
+        if (addResult2 <= 0) {
+            throw new CustomException("用户地址信息保存失败", 500);
+        }
+        resultVO.setUserId(addFsUser.getUserId());
+        resultVO.setStatus(BigDecimal.ONE.intValue());
+        resultVO.setMessage("提交成功");
+        resultVO.setSubmitTime(DateUtils.getTime());
+        return resultVO;
+    }
+
+    @Override
+    //@Transactional(rollbackFor = Exception.class)
+    public SubmitUserInformationVO submitUserInformation(CPWUserInfoCollectionAddDTO collectionAddDTO) {
+        //新增fs_user_information_collection用户采集信息
+        Long userId = collectionAddDTO.getUserId();
+        FsUser fsUserQueryCondition=new FsUser();
+        fsUserQueryCondition.setUserId(userId);
+        fsUserQueryCondition.setUserSource(2);//超拼网用户
+        List<FsUser> fsUsers = fsUserMapper.selectFsUserList(fsUserQueryCondition);
+        if (CollectionUtils.isEmpty(fsUsers)) {
+            log.error("超拼网提交用户采集信息,用户不存在,用户id:{}", userId);
+            throw new CustomException("用户不存在", 500);
+        }
+        FsUserInformationCollection fsUserInformationCollection = transformUserDtoToFsUserCollection(collectionAddDTO,fsUsers.get(0));
+        int addResult = fsUserInformationCollectionMapper.insertFsUserInformationCollection(fsUserInformationCollection);
+        if (addResult <= 0) {
+            throw new CustomException("采集信息保存失败", 500);
+        }
+        //构建成功返回结果
+        SubmitUserInformationVO resultVO = new SubmitUserInformationVO();
+        resultVO.setUserId(fsUserInformationCollection.getUserId());
+        resultVO.setCollectionId(fsUserInformationCollection.getId());
+        resultVO.setStatus(BigDecimal.ONE.intValue());
+        resultVO.setMessage("提交成功");
+        resultVO.setSubmitTime(DateUtils.getTime());
+        log.info("超拼网---用户采集信息提交成功,userId: {}, collectionId: {}",
+                userId, fsUserInformationCollection.getId());
+        return resultVO;
+    }
+
+    @Override
+    public Object pushPrescription(String userId, String prescriptionId) {
+        return null;
+    }
+
+    /**
+     * 转换用户信息fs_user
+     * */
+    private FsUser transformUserDtoToFsUser(CPWUserBaseInfoAddDTO userBaseInfo) {
+        FsUser fsUser=new FsUser();
+        fsUser.setRealName(userBaseInfo.getRealName());
+        fsUser.setNickname("超拼网用户"+userBaseInfo.getUserPhone().substring(userBaseInfo.getUserPhone().length()-4));
+        fsUser.setIdCard(userBaseInfo.getIdCard());
+        fsUser.setVipLevel(BigDecimal.ZERO.intValue());
+        fsUser.setUserSource(2);//超拼网用户
+        fsUser.setIsDel(BigDecimal.ZERO.intValue());
+        fsUser.setStatus(BigDecimal.ONE.intValue());
+        fsUser.setCreateTime(DateUtils.getNowDate());
+        fsUser.setPhone(userBaseInfo.getUserPhone());
+        return fsUser;
+    }
+
+    /**
+     *  构建用户地址信息
+     * */
+    private FsUserAddress buildUserAddress(FsUserAddress fsUserAddress,FsUser addFsUser) {
+        fsUserAddress.setUserId(addFsUser.getUserId());
+        fsUserAddress.setPhone(decryptPhone(addFsUser.getPhone()));
+        fsUserAddress.setDetail(fsUserAddress.getDetail().trim());
+        fsUserAddress.setCreateTime(DateUtils.getNowDate());
+        fsUserAddress.setIsDefault(BigDecimal.ONE.intValue());
+        fsUserAddress.setIsDel(BigDecimal.ZERO.intValue());
+        return fsUserAddress;
+    }
+
+    /**
+     * 转换用户采集信息fs_user_information_collection
+     * */
+    private FsUserInformationCollection transformUserDtoToFsUserCollection(CPWUserInfoCollectionAddDTO collectionAddDTO,FsUser fsUser) {
+        FsUserInformationCollection fsUserInformationCollection=new FsUserInformationCollection();
+        fsUserInformationCollection.setUserId(collectionAddDTO.getUserId());
+        fsUserInformationCollection.setQuestionId(QUESTION_ID);
+        fsUserInformationCollection.setUserName(fsUser.getRealName());
+        fsUserInformationCollection.setUserPhoneFour(fsUser.getPhone().substring(fsUser.getPhone().length()-4));//手机号后四位
+        fsUserInformationCollection.setSex((long) IdCardUtil.getGenderByIdCard(fsUser.getIdCard()));
+        fsUserInformationCollection.setAge(IdCardUtil.getAgeByIdCard(fsUser.getIdCard()));//年龄
+        fsUserInformationCollection.setAllergy(collectionAddDTO.getAllergy());
+        fsUserInformationCollection.setRemark(collectionAddDTO.getRemark());
+        fsUserInformationCollection.setInfoSource(2);//超拼网用户采集信息
+        fsUserInformationCollection.setCreateTime(DateUtils.getNowDate());
+        fsUserInformationCollection.setUserConfirm(BigDecimal.ZERO.intValue());//用户第一次确认 0-未确认 1-已确认
+        fsUserInformationCollection.setUserConfirm2(BigDecimal.ZERO.intValue());//用户第二次确认 0-未确认 1-已确认
+        fsUserInformationCollection.setDoctorConfirm(BigDecimal.ZERO.intValue());//医生确认开方 0-未确认 1-已确认
+        fsUserInformationCollection.setJsonInfo(JSON.toJSONString(collectionAddDTO.getAnswers()));//采集信息
+        return fsUserInformationCollection;
+    }
+}

+ 0 - 10
fs-service/src/main/java/com/fs/hisStore/service/impl/OpenApiFsUserInformationServiceImpl.java

@@ -1,10 +0,0 @@
-package com.fs.hisStore.service.impl;
-
-import com.fs.hisStore.service.IOpenApiFsUserInformationService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-@Slf4j
-@Service
-public class OpenApiFsUserInformationServiceImpl implements IOpenApiFsUserInformationService {
-}

+ 6 - 1
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -52,10 +52,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="firstLoginTime"    column="first_login_time"    />
         <result property="appRoles"    column="app_roles"    />
         <result property="appRewardsViewedDays"    column="app_rewards_viewed_days"    />
+        <result property="userSource"    column="user_source"    />
     </resultMap>
 
     <sql id="selectFsUserVo">
-        select user_id,qw_ext_id,sex,is_buy,course_ma_open_id,is_push,is_add_qw,source,login_device,is_individuation_push,store_open_id,password,jpush_id, is_vip,vip_start_date,vip_end_date,vip_level,vip_status,nick_name,integral_status, avatar, phone, integral,sign_num, status, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id,app_open_id, union_id, is_del, user_code, remark, create_time, update_time, last_ip, balance,is_weixin_auth,parent_id,qw_user_id,company_id,company_user_id,first_login_time,app_roles,app_rewards_viewed_days from fs_user
+        select user_id,qw_ext_id,sex,is_buy,course_ma_open_id,is_push,is_add_qw,source,login_device,is_individuation_push,store_open_id,password,jpush_id, is_vip,vip_start_date,vip_end_date,vip_level,vip_status,nick_name,integral_status, avatar, phone, integral,sign_num, status, tui_user_id, tui_time, tui_user_count, ma_open_id, mp_open_id,app_open_id, union_id, is_del, user_code, remark, create_time, update_time, last_ip, balance,is_weixin_auth,parent_id,qw_user_id,company_id,company_user_id,first_login_time,app_roles,app_rewards_viewed_days,user_source from fs_user
     </sql>
 
     <select id="selectFsUserList" parameterType="FsUser" resultMap="FsUserResult">
@@ -80,6 +81,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="lastIp != null  and lastIp != ''"> and last_ip = #{lastIp}</if>
             <if test="balance != null "> and balance = #{balance}</if>
             <if test="appRewardsViewedDays != null "> and app_rewards_viewed_days = #{appRewardsViewedDays}</if>
+            <if test="userSource != null "> and user_source = #{userSource}</if>
         </where>
     </select>
 
@@ -616,6 +618,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="firstLoginTime != null">first_login_time,</if>
             <if test="appRoles != null">app_roles,</if>
             <if test="appRewardsViewedDays != null">app_rewards_viewed_days,</if>
+            <if test="userSource != null">user_source,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="nickName != null">#{nickName},</if>
@@ -663,6 +666,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="firstLoginTime != null">#{firstLoginTime},</if>
             <if test="appRoles != null">#{appRoles},</if>
             <if test="appRewardsViewedDays != null">#{appRewardsViewedDays},</if>
+            <if test="userSource != null">#{userSource},</if>
          </trim>
     </insert>
 
@@ -716,6 +720,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="firstLoginTime != null">first_login_time = #{firstLoginTime},</if>
             <if test="appRoles != null">app_roles = #{appRoles},</if>
             <if test="appRewardsViewedDays != null">app_rewards_viewed_days = #{appRewardsViewedDays},</if>
+            <if test="userSource != null">user_source = #{userSource},</if>
         </trim>
         where user_id = #{userId}
     </update>

+ 23 - 1
fs-service/src/main/resources/mapper/hisStore/FsUserInformationCollectionMapper.xml

@@ -31,6 +31,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="allergy"    column="allergy"    />
         <result property="remark"    column="remark"    />
         <result property="age"    column="age"    />
+        <result property="infoSource"    column="info_source"    />
     </resultMap>
 
     <sql id="selectFsUserInformationCollectionVo">
@@ -38,7 +39,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              , doctor_confirm, create_time, update_time,doctor_id,company_user_id
              ,package_id,pay_type,amount,is_package,user_confirm2,package_order_code
              ,status,user_advice,doctor_advice,doctor_confirm_time,sex,user_name,user_phone_four
-             ,allergy,remark,age  from fs_user_information_collection
+             ,allergy,remark,age,info_source  from fs_user_information_collection
     </sql>
 
     <select id="selectFsUserInformationCollectionList" parameterType="FsUserInformationCollection" resultMap="FsUserInformationCollectionResult">
@@ -50,6 +51,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userConfirm != null "> and user_confirm = #{userConfirm}</if>
             <if test="doctorConfirm != null "> and doctor_confirm = #{doctorConfirm}</if>
             <if test="packageOrderCode != null "> and package_order_code = #{packageOrderCode}</if>
+            <if test="infoSource != null "> and info_source = #{infoSource}</if>
         </where>
     </select>
     
@@ -111,6 +113,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="allergy != null">allergy,</if>
             <if test="remark != null">remark,</if>
             <if test="age != null">age,</if>
+            <if test="infoSource != null">info_source,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
             <if test="questionId != null">#{questionId},</if>
@@ -138,6 +141,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="allergy != null">#{allergy},</if>
             <if test="remark != null">#{remark},</if>
             <if test="age != null">#{age},</if>
+            <if test="infoSource != null">#{infoSource},</if>
          </trim>
     </insert>
 
@@ -169,6 +173,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="allergy != null">allergy = #{allergy},</if>
             <if test="remark != null">remark = #{remark},</if>
             <if test="age != null">age = #{age},</if>
+            <if test="infoSource != null">info_source = #{infoSource},</if>
         </trim>
         where id = #{id}
     </update>
@@ -183,4 +188,21 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             #{id}
         </foreach>
     </delete>
+
+    <update id="resolvePhoneNumberGenderDataStoreIssue">
+        UPDATE fs_user_information_collection
+        SET
+            -- 1. 先备份原始的 user_phone_four 到 back_phone
+            back_phone = user_phone_four,
+            -- 2. 先备份原始的 sex 到 back_sex
+            back_sex = sex,
+            -- 3. 将原始的 sex 值(现在在 back_sex 中)赋给 user_phone_four
+            user_phone_four = CAST(back_sex AS CHAR),
+            -- 4. 将原始的 user_phone_four 值(现在在 back_phone 中)赋给 sex
+            sex = CAST(back_phone AS SIGNED)
+        WHERE
+            user_phone_four IN ('0', '1')
+          AND sex > 1
+          AND sex IS NOT NULL;
+    </update>
 </mapper>