瀏覽代碼

Merge remote-tracking branch 'origin/master' into Payment-Configuration

# Conflicts:
#	fs-service/src/main/resources/mapper/MerchantAppConfigMapper.xml
yfh 9 小時之前
父節點
當前提交
ae46d79674
共有 46 個文件被更改,包括 1187 次插入71 次删除
  1. 96 17
      fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java
  2. 45 4
      fs-admin/src/main/java/com/fs/his/task/Task.java
  3. 3 0
      fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java
  4. 9 0
      fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java
  5. 2 0
      fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java
  6. 4 4
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  7. 129 7
      fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java
  8. 10 0
      fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java
  9. 33 0
      fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java
  10. 51 4
      fs-company/src/main/java/com/fs/company/controller/company/CompanyLoginController.java
  11. 10 4
      fs-company/src/main/java/com/fs/company/controller/live/LiveController.java
  12. 6 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java
  13. 10 0
      fs-company/src/main/java/com/fs/company/controller/qw/QwTagGroupController.java
  14. 1 1
      fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java
  15. 257 0
      fs-company/src/main/java/com/fs/framework/service/CompanyLoginService.java
  16. 1 1
      fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java
  17. 196 0
      fs-framework/src/main/java/com/fs/framework/web/service/SysLoginService.java
  18. 49 0
      fs-service/src/main/java/com/fs/common/service/WechatLoginService.java
  19. 10 0
      fs-service/src/main/java/com/fs/company/domain/CompanyUser.java
  20. 75 10
      fs-service/src/main/java/com/fs/erp/service/impl/FsJstAftersalePushScrmServiceImpl.java
  21. 13 0
      fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java
  22. 39 1
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  23. 3 0
      fs-service/src/main/java/com/fs/his/param/FsIntegralOrderParam.java
  24. 1 0
      fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java
  25. 1 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java
  26. 1 0
      fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java
  27. 8 0
      fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java
  28. 10 2
      fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java
  29. 1 0
      fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java
  30. 3 2
      fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  31. 12 0
      fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java
  32. 6 0
      fs-service/src/main/java/com/fs/qw/param/QwExternalContactAddTagParam.java
  33. 8 0
      fs-service/src/main/java/com/fs/qw/param/QwTagParam.java
  34. 2 0
      fs-service/src/main/java/com/fs/qw/service/IQwTagGroupService.java
  35. 14 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java
  36. 2 0
      fs-service/src/main/java/com/fs/system/mapper/SysUserMapper.java
  37. 2 0
      fs-service/src/main/java/com/fs/system/service/ISysUserService.java
  38. 4 0
      fs-service/src/main/java/com/fs/system/service/impl/SysUserServiceImpl.java
  39. 9 0
      fs-service/src/main/resources/application-common.yml
  40. 3 1
      fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml
  41. 8 1
      fs-service/src/main/resources/mapper/fastGpt/FastGptRoleMapper.xml
  42. 6 0
      fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml
  43. 21 9
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  44. 3 0
      fs-service/src/main/resources/mapper/live/LiveOrderMapper.xml
  45. 9 2
      fs-service/src/main/resources/mapper/system/SysUserMapper.xml
  46. 1 0
      fs-user-app/src/main/java/com/fs/app/controller/live/LiveAfterSalesController.java

+ 96 - 17
fs-admin/src/main/java/com/fs/his/controller/FsIntegralOrderController.java

@@ -7,6 +7,8 @@ import com.fs.common.annotation.Log;
 import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
 import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.enums.BusinessType;
 import com.fs.common.utils.CloudHostUtils;
@@ -22,6 +24,7 @@ import com.fs.his.param.BatchSetErpOrderParam;
 import com.fs.his.vo.*;
 import com.fs.his.param.FsIntegralOrderParam;
 import com.fs.his.service.*;
+import com.fs.system.service.ISysRoleService;
 import com.fs.utils.OrderContextHolder;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -29,6 +32,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
+import lombok.extern.slf4j.Slf4j;
 
 import java.text.ParseException;
 import java.time.LocalDateTime;
@@ -45,6 +49,7 @@ import static com.fs.his.utils.PhoneUtil.decryptPhone;
  * @author fs
  * @date 2023-11-02
  */
+@Slf4j
 @RestController
 @RequestMapping("/his/integralOrder")
 public class FsIntegralOrderController extends BaseController
@@ -78,44 +83,118 @@ public class FsIntegralOrderController extends BaseController
     public TableDataInfo list(FsIntegralOrderParam fsIntegralOrder)
     {
         startPage();
+        List<FsIntegralOrderListVO> list = new ArrayList<>();
         if (CloudHostUtils.hasCloudHostName("金牛明医")){
             /*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*/
             if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
                 fsIntegralOrder.setStatus("1");
                 fsIntegralOrder.setIsPush(0);
             }
-            List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+
+            // 金牛明医项目:支持多个订单ID查询
+//            if (fsIntegralOrder.getOrderCodes() != null && !fsIntegralOrder.getOrderCodes().isEmpty()) {
+//                // 如果传了orderIds参数,使用新的查询逻辑
+//                List<FsIntegralOrder> orders = fsIntegralOrderService.selectFsIntegralOrderByOrderIdsv2(fsIntegralOrder.getOrderCodes());
+//                List<FsIntegralOrderListVO> list = orders.stream()
+//                    .map(this::convertOrderToListVO)
+//                    .collect(Collectors.toList());
+//                for (FsIntegralOrderListVO vo : list) {
+//                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+//                }
+//                return getDataTable(list);
+//            } else {
+//                // 原有逻辑:单个orderId或其他条件查询
+//                List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+//                for (FsIntegralOrderListVO vo : list) {
+//                    vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+//                }
+//                return getDataTable(list);
+//            }
+            list = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        if (sysRole != null && !(sysRole.getIsCheckPhone()==1)) {
             for (FsIntegralOrderListVO vo : list) {
                 vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
             }
-            return getDataTable(list);
-        }
-        List<FsIntegralOrderListVO> list = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
-        for (FsIntegralOrderListVO vo : list) {
-            vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
         }
         return getDataTable(list);
     }
 
+    @Autowired
+    private ISysRoleService sysRoleService;
+    private SysRole isCheckPermission() {
+        SysRole sysRole = new SysRole();
+        SysUser user = getLoginUser().getUser();
+        boolean flag = user.isAdmin();
+        if (flag) {
+            sysRole.setIsCheckPhone(1);
+            sysRole.setIsCheckAddress(1);
+        } else {
+            List<SysRole> roles = user.getRoles();
+            if (roles != null && !roles.isEmpty()) {
+                Long[] roleIds = roles.stream().map(SysRole::getRoleId).toArray(Long[]::new);
+                return sysRoleService.getIsCheckPermission(roleIds);
+            }
+        }
+        return sysRole;
+    }
+
     /**
      * 导出积分商品订单列表
      */
     @PreAuthorize("@ss.hasPermi('his:integralOrder:export')")
     @Log(title = "积分商品订单", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
-    public AjaxResult export(FsIntegralOrder fsIntegralOrder)
-    {
-        if (CloudHostUtils.hasCloudHostName("金牛明医")&&fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals(6)) {
+    public AjaxResult export(FsIntegralOrderParam fsIntegralOrder) {
+        List<FsIntegralOrderListVO> fsIntegralOrderListVOS = new ArrayList<>();
+        if (CloudHostUtils.hasCloudHostName("金牛明医")){
             /*目前只有金牛有状态为6的查询,其他项目避免使用6状态码*/
-            FsIntegralOrderParam param = new FsIntegralOrderParam();
-            BeanUtil.copyProperties(fsIntegralOrder, param);
-            param.setStatus("1");
-            param.setIsPush(0);
-            List<FsIntegralOrderListVO> fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(param);
-            ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
-            return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
+            if (fsIntegralOrder.getStatus() != null && fsIntegralOrder.getStatus().equals("6")) {
+                fsIntegralOrder.setStatus("1");
+                fsIntegralOrder.setIsPush(0);
+            }
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListByJn(fsIntegralOrder);
+        } else {
+            fsIntegralOrderListVOS = fsIntegralOrderService.selectFsIntegralOrderListVO(fsIntegralOrder);
+        }
+        SysRole sysRole = isCheckPermission();
+        // 处理商品名称:解析item_json并设置goodsName
+        for (FsIntegralOrderListVO vo : fsIntegralOrderListVOS) {
+            if (StringUtils.isNotEmpty(vo.getItemJson())) {
+                try {
+                    // 尝试解析JSON格式的商品信息
+                    if (vo.getItemJson().startsWith("[")) {
+                        // 数组格式,取第一个商品
+                        com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(vo.getItemJson());
+                        if (jsonArray != null && !jsonArray.isEmpty()) {
+                            com.alibaba.fastjson.JSONObject goods = jsonArray.getJSONObject(0);
+                            if (goods != null && goods.getString("goodsName") != null) {
+                                vo.setGoodsName(goods.getString("goodsName"));
+                            }
+                        }
+                    } else if (vo.getItemJson().startsWith("{")) {
+                        // 对象格式
+                        com.alibaba.fastjson.JSONObject goods = com.alibaba.fastjson.JSONObject.parseObject(vo.getItemJson());
+                        if (goods != null && goods.getString("goodsName") != null) {
+                            vo.setGoodsName(goods.getString("goodsName"));
+                        }
+                    }
+                } catch (Exception e) {
+                    // 解析失败时保持goodsName为空,避免导出出错
+                    log.warn("解析商品信息失败,订单编号:{}, 商品信息:{}", vo.getOrderCode(), vo.getItemJson());
+                }
+            }
+            if (!(sysRole.getIsCheckPhone()==1)){
+                // 加密手机号
+                vo.setUserPhone(decryptAutoPhoneMk(vo.getUserPhone()));
+            }
+
         }
-        return fsIntegralOrderService.export(fsIntegralOrder);
+        ExcelUtil<FsIntegralOrderListVO> util = new ExcelUtil<>(FsIntegralOrderListVO.class);
+        return util.exportExcel(new ArrayList<>(fsIntegralOrderListVOS), "积分商品订单数据");
     }
     /**
      * 发货

+ 45 - 4
fs-admin/src/main/java/com/fs/his/task/Task.java

@@ -3,6 +3,7 @@ package com.fs.his.task;
 import cn.hutool.core.date.DateTime;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -33,12 +34,10 @@ import com.fs.erp.dto.ErpOrderQueryResponse;
 import com.fs.erp.dto.ErpOrderResponse;
 import com.fs.erp.mapper.FsErpFinishPushMapper;
 import com.fs.erp.service.IErpOrderService;
-import com.fs.fastGpt.domain.FastGptEventTokenLog;
-import com.fs.fastGpt.domain.FastGptPushTokenTotal;
-import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
-import com.fs.fastGpt.domain.FastgptEventLogTotal;
+import com.fs.fastGpt.domain.*;
 import com.fs.fastGpt.mapper.FastGptChatSessionMapper;
 import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
+import com.fs.fastGpt.service.AiHookService;
 import com.fs.fastGpt.service.IFastgptEventLogTotalService;
 import com.fs.fastgptApi.util.AudioUtils;
 import com.fs.fastgptApi.vo.AudioVO;
@@ -67,7 +66,9 @@ import com.fs.im.dto.*;
 import com.fs.im.service.IImService;
 import com.fs.im.service.OpenIMService;
 import com.fs.qw.domain.QwCompany;
+import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwRestrictionPushRecordMapper;
+import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.service.*;
 import com.fs.qwApi.service.QwApiService;
 import com.fs.sop.domain.QwSopTempVoice;
@@ -225,6 +226,46 @@ public class Task {
     @Autowired
     private FsIntegralOrderMapper fsIntegralOrderMapper;
 
+    private final String DELAY_MSG = "delayMsg";
+
+    @Autowired
+    private QwUserMapper qwUserMapper;
+
+    @Autowired
+    private AiHookService aiHookService;
+
+
+    /**
+     * 定时任务,处理ai禁止回复之后的消息
+     */
+    public void forbidTimeMsgTask() {
+        Map<String, Object> cacheMap = redisCache.hGetAll(DELAY_MSG);
+        for (String key : cacheMap.keySet()) {
+            String value = (String) cacheMap.get(key);
+            //获取sessionId
+            Long sessionId = Long.parseLong(key);
+            try {
+                if (value != null && !value.isEmpty()) {
+                    FastGptChatSession chatSession = fastGptChatSessionMapper.selectFastGptChatSessionBySessionId(sessionId);
+                    Long qwUserId = chatSession.getQwUserId();
+                    QwUser qwUser = qwUserMapper.selectQwUserById(qwUserId);
+                    String uid = qwUser.getUid();
+
+                    JSONObject jsonObject = JSONObject.parseObject(value);
+                    String content = jsonObject.getString("content");
+                    Long sender = jsonObject.getLong("sender");
+                    Integer type = jsonObject.getInteger("type");
+
+                    aiHookService.qwHookNotifyAiReply(qwUserId,sender,content,uid,type);
+                    //删除已经处理的缓存
+                    redisCache.hDel(DELAY_MSG,key);
+                }
+            } catch (Exception e) {
+                log.error("个人定时消息处理异常,会话id:{},文本:{}",sessionId,value,e);
+            }
+        }
+    }
+
     /**
      * sop任务token消耗统计
      */

+ 3 - 0
fs-admin/src/main/java/com/fs/hisStore/controller/FsStoreAfterSalesScrmController.java

@@ -133,6 +133,9 @@ public class FsStoreAfterSalesScrmController extends BaseController
 //                            zmvo.setAfterSalesNumber
                             zmvo.setRefundMoney(vo.getRefundAmount());
                             zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
+
                         } catch (Exception e) {
                             // 处理异常
                             e.printStackTrace();

+ 9 - 0
fs-admin/src/main/java/com/fs/hisStore/task/LiveTask.java

@@ -12,6 +12,7 @@ import com.fs.erp.domain.ErpDeliverys;
 import com.fs.erp.domain.ErpOrderQuery;
 import com.fs.erp.dto.ErpOrderQueryRequert;
 import com.fs.erp.dto.ErpOrderQueryResponse;
+import com.fs.erp.service.FsJstAftersalePushScrmService;
 import com.fs.erp.service.IErpOrderService;
 import com.fs.his.config.FsSysConfig;
 import com.fs.his.dto.ExpressInfoDTO;
@@ -160,6 +161,14 @@ public class LiveTask {
     @Autowired
     private IFsStoreOrderScrmService orderService;
 
+    @Autowired
+    private FsJstAftersalePushScrmService fsJstAftersalePushScrmService;
+
+    // 聚水潭 推送售后信息
+    public void pushJst(){
+        fsJstAftersalePushScrmService.pushJst();
+    }
+
 
     // 订单银行回调数据丢失补偿
     public void recoveryBankOrder() {

+ 2 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveAfterSalesController.java

@@ -143,6 +143,8 @@ public class LiveAfterSalesController extends BaseController
 //                            zmvo.setAfterSalesNumber
                             zmvo.setRefundMoney(vo.getRefundAmount());
                             zmvo.setBankTransactionId(vo.getBankTransactionId());
+                            zmvo.setReasons(vo.getReasons());
+                            zmvo.setExplains(vo.getExplains());
                         } catch (Exception e) {
                             // 处理异常
                             e.printStackTrace();

+ 4 - 4
fs-admin/src/main/java/com/fs/live/controller/LiveController.java

@@ -93,7 +93,7 @@ public class LiveController extends BaseController {
     @PutMapping
     public AjaxResult edit(@RequestBody Live live) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( live));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( live));
         return toAjax(liveService.updateLive(live));
     }
 
@@ -105,7 +105,7 @@ public class LiveController extends BaseController {
     @DeleteMapping("/{liveIds}")
     public AjaxResult remove(@PathVariable Long[] liveIds) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( liveIds));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( liveIds));
         return toAjax(liveService.deleteLiveByLiveIds(liveIds, new Live()));
     }
 
@@ -130,7 +130,7 @@ public class LiveController extends BaseController {
     @PostMapping("/handleShelfOrUn")
     public R handleShelfOrUn(@RequestBody LiveListVo listVo) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleShelfOrUnAdmin(listVo);
     }
 
@@ -141,7 +141,7 @@ public class LiveController extends BaseController {
     @PostMapping("/handleDeleteSelected")
     public R handleDeleteSelected(@RequestBody LiveListVo listVo) {
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        log.warn("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
+        log.info("loginUser:{},update:{}", loginUser.getUserId(), JSON.toJSONString( listVo));
         return liveService.handleDeleteSelectedAdmin(listVo);
     }
     /**

+ 129 - 7
fs-admin/src/main/java/com/fs/web/controller/system/SysLoginController.java

@@ -1,22 +1,28 @@
 package com.fs.web.controller.system;
 
-import java.util.List;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import com.alibaba.fastjson.JSONObject;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.PatternUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.framework.web.service.TokenService;
 import com.fs.his.utils.ConfigUtil;
 import com.fs.hisStore.config.MedicalMallConfig;
 import com.fs.system.service.ISysRoleService;
+import com.fs.system.service.ISysUserService;
 import lombok.Synchronized;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.web.bind.annotation.*;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.domain.entity.SysMenu;
@@ -33,7 +39,7 @@ import com.fs.system.service.ISysMenuService;
 
  */
 @RestController
-
+@Slf4j
 public class SysLoginController
 {
     @Autowired
@@ -54,6 +60,18 @@ public class SysLoginController
     @Autowired
     private MedicalMallConfig medicalMallConfig;
 
+    @Autowired
+    RedisCache redisCache;
+
+    @Autowired
+    private ISysUserService userService;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private UserDetailsService userDetailsService;
+
     /**
      * 登录方法
      *
@@ -141,4 +159,108 @@ public class SysLoginController
         List<SysMenu> menus = menuService.selectMenuTreeByUserId(userId);
         return AjaxResult.success(menuService.buildMenus(menus));
     }
+
+    @PostMapping("/checkIsNeedCheck")
+    public boolean checkIsNeedCheck(@RequestBody LoginBody loginBody)
+    {
+//        return  false;
+        return loginService.checkIsNeedCheck(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());
+    }
+
+    @PostMapping("/checkCode")
+    public AjaxResult checkCode(@RequestBody Map<String,String> map)
+    {
+        String phone = map.get("phone");
+        String code = map.get("code");
+        String smsKey = "doctorLogin:sms:" + map.get("phone");
+        String smsCode = redisCache.getCacheObject(smsKey);
+
+        if (smsCode == null) {
+            throw new ServiceException("验证码已过期,请重新发送");
+        } else {
+            String string = redisCache.getCacheObject("doctorLogin:sms:" + phone).toString();
+            if (!string.equals(code)){
+                throw new ServiceException("验证码错误");
+            }else{
+                redisCache.deleteObject("doctorLogin:sms:" + phone);
+                List<SysUser> sysUsers = userService.selectUserByPhone(phone);
+                if(sysUsers.size()>1){
+                    throw new ServiceException("此电话号码绑定了多个医生,请核实");
+                }
+                SysUser sysUser = sysUsers.get(0);
+                String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+                String username = sysUser.getUserName();
+
+                // 调 UserDetailsServiceImpl.loadUserByUsername 获取完整 LoginUser
+                sysUser.setLoginIp(ipAddr);
+                sysUser.setLoginDate(new Date());
+                userService.updateUserProfile(sysUser);
+                LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
+                String token = tokenService.createToken(loginUser);
+                AjaxResult ajax = AjaxResult.success();
+                ajax.put(Constants.TOKEN, token);
+                return ajax;
+            }
+        }
+    }
+
+    @GetMapping("/checkWechatScan")
+    public AjaxResult checkWechatScan(@RequestParam String ticket)
+    {
+        String status = redisCache.getCacheObject("wechat:scan:" + ticket);
+        if ("ok".equals(status)) {
+            String username = redisCache.getCacheObject("login:ticket:" + ticket);
+            redisCache.deleteObject("login:ticket:" + ticket);
+            redisCache.deleteObject("wechat:scan:" + ticket);
+            SysUser sysUser = userService.selectUserByUserName(username);
+
+            String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+            String loginIp = sysUser.getLoginIp();
+            if (com.fs.common.utils.StringUtils.isEmpty(loginIp)) {
+                sysUser.setLoginIp(ipAddr.trim());
+            } else {
+                List<String> ipList = Arrays.stream(loginIp.split(","))
+                        .map(String::trim)       // 去掉前后空格
+                        .filter(s -> !s.isEmpty())
+                        .distinct()              // 去重
+                        .collect(Collectors.toList());
+
+                String newIp = ipAddr.trim();
+                if (!ipList.contains(newIp)) {
+                    ipList.add(newIp);
+                }
+
+                sysUser.setLoginIp(String.join(",", ipList));
+            }
+
+            sysUser.setLoginDate(new Date());
+            userService.updateUserProfile(sysUser);
+            LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
+            String token = tokenService.createToken(loginUser);
+            if (token != null){
+                return AjaxResult.success(Constants.TOKEN, token);
+            }
+            return AjaxResult.success("waiting");
+        }else if (com.fs.common.utils.StringUtils.isNotEmpty(status)&&status.startsWith("error:")) {
+            // 把错误返回给前端
+            throw new ServiceException(status);
+        }
+        return null;
+    }
+
+    @PostMapping("/getWechatQrCode")
+    public AjaxResult getWechatQrCode(@RequestBody Map<String,String> params) throws Exception {
+        Map<String,String> qr = loginService.getWechatQrCode(params.get("username"));
+        return AjaxResult.success(qr);
+    }
+    @GetMapping("/callback")
+    public String wechatCallback(@RequestParam String code, @RequestParam String state) {
+        try {
+            log.info("触发回调");
+            loginService.handleCallback(code, state);
+            return "success"; // 微信要求返回内容,显示给用户即可
+        } catch (Exception e) {
+            return "error";
+        }
+    }
 }

+ 10 - 0
fs-common/src/main/java/com/fs/common/core/domain/entity/SysUser.java

@@ -103,6 +103,16 @@ public class SysUser extends BaseEntity
     @Excel(name = "角色名称")
     private List<String> roleName;
 
+    private String unionId;
+
+    public String getUnionId() {
+        return unionId;
+    }
+
+    public void setUnionId(String unionId) {
+        this.unionId = unionId;
+    }
+
     public SysUser()
     {
 

+ 33 - 0
fs-common/src/main/java/com/fs/common/core/redis/RedisCache.java

@@ -451,4 +451,37 @@ public class RedisCache
     public Long size(String key) {
         return redisTemplate.opsForHash().size(key);
     }
+
+    // ========== 新增Hash操作方法 ==========
+    /**
+     * 存储Hash结构:field-value
+     */
+    public <T> void hPut(String mainKey, String field, T value) {
+        redisTemplate.opsForHash().put(mainKey, field, value);
+    }
+
+    /**
+     * 获取Hash中指定field的值
+     */
+    public <T> T hGet(String mainKey, String field) {
+        return (T) redisTemplate.opsForHash().get(mainKey, field);
+    }
+
+    /**
+     * 获取Hash中所有field-value(方便批量读取)
+     */
+    public Map<String, Object> hGetAll(String mainKey) {
+        return (Map<String, Object>) redisTemplate.opsForHash().entries(mainKey);
+    }
+
+    /**
+     * 删除Hash中指定field
+     */
+    public void hDel(String mainKey, Object... fields) {
+        redisTemplate.opsForHash().delete(mainKey, (Object[]) fields);
+    }
+
+    public boolean hasKey(String key) {
+        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
+    }
 }

+ 51 - 4
fs-company/src/main/java/com/fs/company/controller/company/CompanyLoginController.java

@@ -4,11 +4,18 @@ import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.exception.user.CaptchaException;
+import com.fs.common.exception.user.CaptchaExpireException;
+import com.fs.common.exception.user.UserPasswordNotMatchException;
+import com.fs.common.utils.MessageUtils;
 import com.fs.common.utils.PatternUtils;
 import com.fs.common.utils.ServletUtils;
 import com.fs.company.domain.CompanyMenu;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyMenuService;
+import com.fs.framework.manager.AsyncManager;
+import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.framework.security.LoginBody;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.service.CompanyLoginService;
@@ -16,13 +23,16 @@ import com.fs.framework.service.CompanyPermissionService;
 import com.fs.framework.service.TokenService;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -31,6 +41,7 @@ import java.util.Set;
 
  */
 @RestController
+@Slf4j
 public class CompanyLoginController
 {
     @Autowired
@@ -133,5 +144,41 @@ public class CompanyLoginController
         return AjaxResult.success(false);
     }
 
+    @PostMapping("/checkIsNeedCheck")
+    public boolean checkIsNeedCheck(@RequestBody LoginBody loginBody)
+    {
+//        return false;
+        return loginService.checkIsNeedCheck(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), loginBody.getUuid());
+    }
+
+    @PostMapping("/getWechatQrCode")
+    public AjaxResult getWechatQrCode(@RequestBody Map<String,String> params) throws Exception {
+        Map<String,String> qr = loginService.getWechatQrCode(params.get("username"));
+        return AjaxResult.success(qr);
+    }
+
+    @GetMapping("/checkWechatScan")
+    public AjaxResult checkWechatScan(@RequestParam String ticket) {
+        //log.info("触发轮询");
+        String token = loginService.checkWechatScan(ticket);
+        Map<String, String> stringStringMap = Collections.singletonMap(Constants.TOKEN, token);
+        if (token != null){
+            return AjaxResult.success(Constants.TOKEN, token);
+        }
+        return AjaxResult.success("waiting");
+    }
+
+    @GetMapping("/callback")
+    public String wechatCallback(@RequestParam String code, @RequestParam String state) {
+        try {
+            log.info("触发回调");
+            loginService.handleCallback(code, state);
+            return "success"; // 微信要求返回内容,显示给用户即可
+        } catch (Exception e) {
+            return "error";
+        }
+    }
+
+
 }
 

+ 10 - 4
fs-company/src/main/java/com/fs/company/controller/live/LiveController.java

@@ -15,9 +15,17 @@ import com.fs.company.domain.CompanyUser;
 import com.fs.framework.security.LoginUser;
 import com.fs.framework.security.SecurityUtils;
 import com.fs.framework.service.TokenService;
+import com.fs.huifuPay.domain.HuiFuQueryOrderResult;
+import com.fs.huifuPay.sdk.opps.core.request.V2TradePaymentScanpayQueryRequest;
+import com.fs.huifuPay.service.HuiFuService;
 import com.fs.live.domain.Live;
 import com.fs.live.domain.LiveCompanyCode;
+import com.fs.live.domain.LiveOrder;
+import com.fs.live.domain.LiveOrderPayment;
+import com.fs.live.mapper.LiveOrderMapper;
+import com.fs.live.mapper.LiveOrderPaymentMapper;
 import com.fs.live.service.ILiveCompanyCodeService;
+import com.fs.live.service.ILiveOrderService;
 import com.fs.live.service.ILiveService;
 import com.fs.live.vo.LiveListVo;
 import com.fs.system.oss.OSSFactory;
@@ -29,10 +37,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 
 import java.nio.charset.StandardCharsets;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.text.SimpleDateFormat;
+import java.util.*;
 
 /**
  * 直播Controller

+ 6 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwExternalContactController.java

@@ -462,7 +462,12 @@ public class QwExternalContactController extends BaseController
     @Log(title = "添加标签", businessType = BusinessType.UPDATE)
     @PostMapping("/addTag")
     public R addTag(@RequestBody QwExternalContactAddTagParam Param) throws JSONException {
-
+        if(Param.isFilter()){
+            Param.setUserIds(getList(Param.getAddType(), Param.getParam()));
+        }
+        if(Param.getUserIds() == null || Param.getUserIds().isEmpty()){
+            return R.error("修改用户为空");
+        }
         return qwExternalContactService.addUserTag(Param);
     }
 

+ 10 - 0
fs-company/src/main/java/com/fs/company/controller/qw/QwTagGroupController.java

@@ -59,6 +59,16 @@ public class QwTagGroupController extends BaseController
         List<QwTagGroupListVO> list = qwTagGroupService.selectQwTagGroupListVO(qwTagGroup);
         return getDataTable(list);
     }
+    /**
+     * 所有标签列表 模糊查询标签名称
+     */
+    @GetMapping("/allListPage")
+    public TableDataInfo allListPage(QwTagGroup qwTagGroup)
+    {
+        startPage();
+        List<QwTagGroupListVO> list = qwTagGroupService.selectQwTagGroupListVOPage(qwTagGroup);
+        return getDataTable(list);
+    }
     @PreAuthorize("@ss.hasPermi('qw:tagGroup:sync')")
     @Log(title = "同步标签", businessType = BusinessType.INSERT)
     @PostMapping("/syncTag/{corpId}")

+ 1 - 1
fs-company/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -100,7 +100,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 // 过滤请求
                 .authorizeRequests()
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/chat/upload/**","/login", "/register", "/captchaImage").anonymous()
+                .antMatchers("/chat/upload/**","/login", "/register", "/captchaImage","/checkIsNeedCheck","/getWechatQrCode","/checkWechatScan","/callback").anonymous()
                 .antMatchers(
                         HttpMethod.GET,
                         "/",

+ 257 - 0
fs-company/src/main/java/com/fs/framework/service/CompanyLoginService.java

@@ -1,26 +1,41 @@
 package com.fs.framework.service;
 
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.HttpUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.exception.user.CaptchaException;
 import com.fs.common.exception.user.CaptchaExpireException;
 import com.fs.common.exception.user.UserPasswordNotMatchException;
+import com.fs.common.service.WechatLoginService;
 import com.fs.common.utils.MessageUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.company.domain.CompanyUser;
+import com.fs.company.service.ICompanyUserService;
 import com.fs.framework.manager.AsyncManager;
 import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.framework.security.LoginUser;
 import com.fs.his.domain.StoreLoginUser;
 import com.fs.system.service.ISysConfigService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * 登录校验方法
@@ -28,6 +43,7 @@ import java.util.concurrent.TimeUnit;
  
  */
 @Component
+@Slf4j
 public class CompanyLoginService
 {
     @Autowired
@@ -39,6 +55,24 @@ public class CompanyLoginService
     @Autowired
     private RedisCache redisCache;
 
+    @Autowired
+    private ICompanyUserService companyUserService;
+
+    @Value("${wechat.company.appid}")
+    private String appId;
+    @Value("${wechat.company.secret}")
+    private String secret;
+    @Value("${wechat.company.redirectUri}")
+    private String redirectUri;
+    @Value("${wechat.isNeedScan}")
+    private Boolean isNeedScan;
+
+    @Autowired
+    private WechatLoginService wechatLoginService;
+
+    @Autowired
+    private UserDetailsService userDetailsService;
+
     /**
      * 登录验证
      *
@@ -91,4 +125,227 @@ public class CompanyLoginService
         return tokenService.createToken(loginUser);
     }
 
+
+    public boolean checkIsNeedCheck(String username, String password, String code, String uuid)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String captcha = redisCache.getCacheObject(verifyKey);
+        //redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+            throw new CaptchaException();
+        }
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(0l,username, Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        //查询当前登录用户信息
+        CompanyUser companyUser = companyUserService.selectCompanyUserById(loginUser.getUser().getUserId());
+
+        Long[] userIds = new Long[]{2020L};
+        for (Long userId : userIds) {
+            if (userId.equals(companyUser.getUserId())) {
+                return false;
+            }
+        }
+
+        // 判断是否开启了扫码配置
+        if (ObjectUtil.isEmpty(isNeedScan) || !isNeedScan){
+            return false;
+        }
+
+        //true → 要发短信验证码再登录
+        //false → 直接登录
+        return needCheck(companyUser);
+    }
+
+    public boolean needCheck(CompanyUser companyUser) {
+        // 1. 校验 IP
+        if (!checkIp(companyUser)) {
+            // IP 不一致
+            return true;
+        }
+
+        // 2. 校验是否首次登录
+        if (checkIsFirstLogin(companyUser)) {
+            return true;
+        }
+
+        // 3. 校验上次登录时间是否在五天前
+        if (checkIsLoginTime(companyUser)) {
+            return true;
+        }
+
+        // 4. 检查是否在设置的某一天
+        /*if (checkIsSpecialDay(new Date())) {
+            return true;
+        }*/
+        if (haveUnionId(companyUser)){
+            return true;
+        }
+
+        return false;
+    }
+
+    public boolean checkIp(CompanyUser companyUser) {
+        // 获取当前 IP
+        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()).split(",")[0].trim();
+
+        // 获取已记录的登录 IP
+        String lastLoginIp = companyUser.getLoginIp();
+
+        if (StringUtils.isNotEmpty(lastLoginIp)) {
+            List<String> ipList = Arrays.stream(lastLoginIp.split(","))
+                    .map(String::trim)
+                    .filter(s -> !s.isEmpty())
+                    .distinct()
+                    .collect(Collectors.toList());
+
+            return ipList.contains(ipAddr);
+        }
+        return false;
+    }
+
+    //检查是否第一次登录
+    public boolean checkIsFirstLogin(CompanyUser companyUser){
+        // 获取上次登录 IP
+        String lastLoginIp = companyUser.getLoginIp();
+        if (StringUtils.isEmpty(lastLoginIp)||companyUser.getLoginDate()==null){
+            return true;
+        }
+        return false;
+    }
+    public boolean checkIsLoginTime(CompanyUser companyUser) {
+        // 获取上次登录时间
+        Date loginDate = companyUser.getLoginDate();
+        if (loginDate == null) {
+            // 没有登录记录,直接返回 true(需要处理)
+            return true;
+        }
+
+        // 当前时间
+        Date now = new Date();
+
+        // 计算两个时间的毫秒差
+        long diff = now.getTime() - loginDate.getTime();
+
+        // 5天 = 5 * 24 * 60 * 60 * 1000 毫秒
+        long fiveDays = 5L * 24 * 60 * 60 * 1000;
+
+        return diff >= fiveDays;
+    }
+
+    public boolean haveUnionId( CompanyUser companyUser){
+        if (StringUtils.isEmpty(companyUser.getUnionId())){
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 获取微信登录二维码参数
+     * @param username 当前登录用户名
+     * @return 二维码参数
+     */
+    public Map<String, String> getWechatQrCode(String username) throws Exception {
+        // 生成 loginTicket
+        String ticket = UUID.randomUUID().toString();
+        redisCache.setCacheObject("login:ticket:" + ticket, username, 60, TimeUnit.SECONDS);
+
+        return wechatLoginService.getQrCode(ticket,appId,secret,redirectUri); // 返回二维码参数
+    }
+
+    public String checkWechatScan(String ticket) {
+        String status = redisCache.getCacheObject("wechat:scan:" + ticket);
+        if ("ok".equals(status)) {
+            String username = redisCache.getCacheObject("login:ticket:" + ticket);
+            redisCache.deleteObject("login:ticket:" + ticket);
+            redisCache.deleteObject("wechat:scan:" + ticket);
+            CompanyUser companyUser = companyUserService.selectUserByUserName(username);
+
+            // 调 UserDetailsServiceImpl.loadUserByUsername 获取完整 LoginUser
+            LoginUser loginUser = (LoginUser) userDetailsService.loadUserByUsername(username);
+            companyUser.setUserId(loginUser.getUser().getUserId());
+            String loginIp = companyUser.getLoginIp();
+            String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest()).split(",")[0].trim();
+            log.info("销售用户{}扫码验证过后登录获取到的ip地址{}", loginUser.getUser().getUserId(), ipAddr);
+            List<String> ipList = new ArrayList<>();
+            if (StringUtils.isNotEmpty(loginIp)) {
+                String[] ips = loginIp.split(",");
+                for (String ip : ips) {
+                    ip = ip.trim();
+                    if (!ip.isEmpty()) {
+                        ipList.add(ip);
+                    }
+                }
+            }
+            ipList.add(ipAddr);
+            List<String> distinctList = ipList.stream()
+                    .map(String::trim)       // 再次确保去掉空格
+                    .distinct()
+                    .collect(Collectors.toList());
+            companyUser.setLoginIp(String.join(",", distinctList));
+            companyUser.setLoginDate(new Date());
+            companyUserService.updateCompanyUser(companyUser);
+            return tokenService.createToken(loginUser);
+        }else if (StringUtils.isNotEmpty(status)&&status.startsWith("error:")) {
+            // 把错误返回给前端
+            throw new ServiceException(status);
+        }
+        return null;
+    }
+    /**
+     * 微信扫码回调
+     */
+    public void handleCallback(String code, String ticket) {
+        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId
+                + "&secret=" + secret
+                + "&code=" + code
+                + "&grant_type=authorization_code";
+
+        JSONObject json = JSON.parseObject(HttpUtil.get(url));
+        String unionid = json.getString("unionid");
+        if (unionid == null) throw new ServiceException("微信授权失败");
+
+        String username = redisCache.getCacheObject("login:ticket:" + ticket);
+        if (username == null) throw new ServiceException("ticket无效或过期");
+        CompanyUser companyUser = companyUserService.selectUserByUserName(username);
+        if (companyUser == null) throw new ServiceException("用户不存在");
+        if (companyUser.getUnionId() == null || companyUser.getUnionId().isEmpty()) {
+            // 如果用户没有绑定 unionid,则绑定当前扫码用户的 unionid
+            companyUser.setUnionId(unionid);
+            companyUserService.updateCompanyUser(companyUser);
+        } else if (!companyUser.getUnionId().equals(unionid)) {
+            // 如果用户已绑定 unionid,但与扫码用户不一致,则拒绝登录
+            redisCache.setCacheObject("wechat:scan:" + ticket, "error:账号与绑定用户不匹配", 30, TimeUnit.SECONDS);
+            return;
+        }
+
+        redisCache.setCacheObject("wechat:scan:" + ticket, "ok", 30, TimeUnit.SECONDS);
+    }
+
 }

+ 1 - 1
fs-framework/src/main/java/com/fs/framework/config/SecurityConfig.java

@@ -97,7 +97,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
                 // 过滤请求
                 .authorizeRequests()
                 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
-                .antMatchers("/login", "/register", "/captchaImage").anonymous()
+                .antMatchers("/login", "/register", "/captchaImage","/getWechatQrCode","/checkWechatScan","/callback","/checkIsNeedCheck").anonymous()
                 .antMatchers("/app/common/test").anonymous()
                 .antMatchers("/ad/adDyApi/authorized").anonymous()
                 .antMatchers(

+ 196 - 0
fs-framework/src/main/java/com/fs/framework/web/service/SysLoginService.java

@@ -1,7 +1,14 @@
 package com.fs.framework.web.service;
 
 import javax.annotation.Resource;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.service.WechatLoginService;
+import com.fs.common.utils.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -24,6 +31,9 @@ import com.fs.framework.manager.factory.AsyncFactory;
 import com.fs.system.service.ISysConfigService;
 import com.fs.system.service.ISysUserService;
 
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
 /**
  * 登录校验方法
  * 
@@ -46,6 +56,17 @@ public class SysLoginService
 
     @Autowired
     private ISysConfigService configService;
+    @Autowired
+    private WechatLoginService wechatLoginService;
+
+    @Value("${wechat.admin.appid}")
+    private String appId;
+    @Value("${wechat.admin.secret}")
+    private String secret;
+    @Value("${wechat.admin.redirectUri}")
+    private String redirectUri;
+    @Value("${wechat.isNeedScan}")
+    private Boolean isNeedScan;
 
     /**
      * 登录验证
@@ -126,4 +147,179 @@ public class SysLoginService
         user.setLoginDate(DateUtils.getNowDate());
         userService.updateUserProfile(user);
     }
+
+
+    public boolean checkIsNeedCheck(String username, String password, String code, String uuid)
+    {
+        String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
+        String captcha = redisCache.getCacheObject(verifyKey);
+        //redisCache.deleteObject(verifyKey);
+        if (captcha == null)
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
+            throw new CaptchaExpireException();
+        }
+        if (!code.equalsIgnoreCase(captcha))
+        {
+            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
+            throw new CaptchaException();
+        }
+        // 用户验证
+        Authentication authentication = null;
+        try
+        {
+            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
+            authentication = authenticationManager
+                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
+        }
+        catch (Exception e)
+        {
+            if (e instanceof BadCredentialsException)
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
+                throw new UserPasswordNotMatchException();
+            }
+            else
+            {
+                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
+                throw new ServiceException(e.getMessage());
+            }
+        }
+        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
+        //查询当前登录用户信息
+        SysUser sysUser = userService.selectUserById(loginUser.getUserId());
+        Long[] userIds = new Long[]{236L, 246L, 247L, 253L,119L};
+        for (Long userId : userIds) {
+            if (userId.equals(sysUser.getUserId())){
+                return false;
+            }
+        }
+
+        // 判断是否开启了扫码配置
+        if (ObjectUtil.isEmpty(isNeedScan) || !isNeedScan){
+            return false;
+        }
+
+        //true → 要发短信验证码再登录
+        //false → 直接登录
+        return needCheck(sysUser);
+    }
+    public boolean needCheck(SysUser sysUser) {
+
+
+        // 1. 校验 IP
+        if (!checkIp(sysUser)) {
+            // IP 不一致
+            return true;
+        }
+
+        // 2. 校验是否首次登录
+        if (checkIsFirstLogin(sysUser)) {
+            return true;
+        }
+
+        // 3. 校验上次登录时间是否在五天前
+        if (checkIsLoginTime(sysUser)) {
+            return true;
+        }
+
+        // 4. 检查是否在设置的某一天
+//        if (checkIsSpecialDay(new Date())) {
+//            return true;
+//        }
+        if (haveUnionId(sysUser)){
+            return true;
+        }
+
+        return false;
+    }
+    public boolean haveUnionId( SysUser sysUser){
+        if (StringUtils.isEmpty(sysUser.getUnionId())){
+            return true;
+        }
+        return false;
+    }
+    public boolean checkIp(SysUser sysUser){
+        // 获取当前 IP
+        String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
+        // 获取已记录的登录 IP
+        String lastLoginIp = sysUser.getLoginIp();
+
+        if (StringUtils.isNotEmpty(lastLoginIp)) {
+            List<String> ipList = Arrays.asList(lastLoginIp.split(","));
+            return ipList.contains(ipAddr);
+        }
+        return false;
+    }
+    //检查是否第一次登录
+    public boolean checkIsFirstLogin(SysUser sysUser){
+        // 获取上次登录 IP
+        String lastLoginIp = sysUser.getLoginIp();
+        if (StringUtils.isEmpty(lastLoginIp)||sysUser.getLoginDate()==null){
+            return true;
+        }
+        return false;
+    }
+    public boolean checkIsLoginTime(SysUser sysUser) {
+        // 获取上次登录时间
+        Date loginDate = sysUser.getLoginDate();
+        if (loginDate == null) {
+            // 没有登录记录,直接返回 true(需要处理)
+            return true;
+        }
+
+        // 当前时间
+        Date now = new Date();
+
+        // 计算两个时间的毫秒差
+        long diff = now.getTime() - loginDate.getTime();
+
+        // 5天 = 5 * 24 * 60 * 60 * 1000 毫秒
+        long fiveDays = 5L * 24 * 60 * 60 * 1000;
+
+        return diff >= fiveDays;
+    }
+
+    /**
+     * 获取微信登录二维码参数
+     * @param account 当前登录用户名
+     * @return 二维码参数
+     */
+    public Map<String, String> getWechatQrCode(String account) throws Exception {
+        // 生成 loginTicket
+        String ticket = UUID.randomUUID().toString();
+        redisCache.setCacheObject("login:ticket:" + ticket, account, 60, TimeUnit.SECONDS);
+
+        return wechatLoginService.getQrCode(ticket,appId,secret,redirectUri); // 返回二维码参数
+    }
+
+    /**
+     * 微信扫码回调
+     */
+    public void handleCallback(String code, String ticket) {
+        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + appId
+                + "&secret=" + secret
+                + "&code=" + code
+                + "&grant_type=authorization_code";
+
+        JSONObject json = JSON.parseObject(cn.hutool.http.HttpUtil.get(url));
+        String unionid = json.getString("unionid");
+        if (unionid == null) throw new ServiceException("微信授权失败");
+
+        String username = redisCache.getCacheObject("login:ticket:" + ticket);
+        if (username == null) throw new ServiceException("ticket无效或过期");
+        SysUser sysUser = userService.selectUserByUserName(username);
+        if (sysUser == null) throw new ServiceException("用户不存在");
+        if (sysUser.getUnionId() == null || sysUser.getUnionId().isEmpty()) {
+            // 如果用户没有绑定 unionid,则绑定当前扫码用户的 unionid
+            sysUser.setUnionId(unionid);
+            userService.updateUserProfile(sysUser);
+        } else if (!sysUser.getUnionId().equals(unionid)) {
+            // 如果用户已绑定 unionid,但与扫码用户不一致,则拒绝登录
+            redisCache.setCacheObject("wechat:scan:" + ticket, "error:账号与绑定用户不匹配", 30, TimeUnit.SECONDS);
+            return;
+        }
+
+        redisCache.setCacheObject("wechat:scan:" + ticket, "ok", 30, TimeUnit.SECONDS);
+    }
 }

+ 49 - 0
fs-service/src/main/java/com/fs/common/service/WechatLoginService.java

@@ -0,0 +1,49 @@
+package com.fs.common.service;
+
+import com.fs.common.core.redis.RedisCache;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class WechatLoginService {
+
+    @Autowired
+    private RedisCache redisCache;
+    /**
+     * 获取二维码参数
+     */
+    public Map<String, String> getQrCode(String ticket,String appId,String secret,String redirectUri) throws UnsupportedEncodingException {
+        String username = redisCache.getCacheObject("login:ticket:" + ticket);
+        if (username == null) throw new RuntimeException("ticket无效或过期");
+
+        Map<String, String> data = new HashMap<>();
+        data.put("appid", appId);
+        data.put("scope", "snsapi_login");
+        data.put("state", ticket);
+        data.put("redirect_uri", redirectUri);
+
+        // 拼接完整的微信扫码 URL
+        String url = "https://open.weixin.qq.com/connect/qrconnect?appid=" + appId
+                + "&redirect_uri=" + URLEncoder.encode(redirectUri, StandardCharsets.UTF_8.name())
+                + "&response_type=code"
+                + "&scope=snsapi_login"
+                + "&state=" + ticket
+                + "#wechat_redirect";
+        data.put("url", url);
+        log.info("url{}",url);
+        return data;
+    }
+
+
+
+
+
+}

+ 10 - 0
fs-service/src/main/java/com/fs/company/domain/CompanyUser.java

@@ -94,6 +94,16 @@ public class CompanyUser extends BaseEntity
     /** 医生id */
     private Long doctorId;
 
+    private String unionId;
+
+    public String getUnionId() {
+        return unionId;
+    }
+
+    public void setUnionId(String unionId) {
+        this.unionId = unionId;
+    }
+
     public String getIdCard() {
         return idCard;
     }

+ 75 - 10
fs-service/src/main/java/com/fs/erp/service/impl/FsJstAftersalePushScrmServiceImpl.java

@@ -22,6 +22,12 @@ import com.fs.hisStore.domain.FsStoreOrderScrm;
 import com.fs.hisStore.mapper.FsStoreAfterSalesScrmMapper;
 import com.fs.hisStore.mapper.FsStoreOrderScrmMapper;
 import com.fs.hisStore.service.IFsStoreOrderItemScrmService;
+import com.fs.live.domain.LiveOrder;
+import com.fs.live.domain.LiveOrderItem;
+import com.fs.live.mapper.LiveOrderMapper;
+import com.fs.live.mapper.LiveOrderPaymentMapper;
+import com.fs.live.service.ILiveOrderItemService;
+import com.fs.live.service.ILiveOrderPaymentService;
 import com.fs.ybPay.dto.RefundOrderDTO;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.util.Asserts;
@@ -45,6 +51,13 @@ public class FsJstAftersalePushScrmServiceImpl implements FsJstAftersalePushScrm
 
     @Autowired
     private FsStoreOrderScrmMapper fsStoreOrderMapper;
+    @Autowired
+    private LiveOrderMapper liveOrderMapper;
+
+    @Autowired
+    private ILiveOrderItemService liveOrderItemService;
+    @Autowired
+    private LiveOrderPaymentMapper liveOrderPaymentMapper;
 
     @Autowired
     private IFsStoreOrderItemScrmService storeOrderItemService;
@@ -65,21 +78,37 @@ public class FsJstAftersalePushScrmServiceImpl implements FsJstAftersalePushScrm
                 log.info("获取记录{} 锁失败!",item.getId());
                 continue;
             }
-
+            item.setRetryCount(item.getRetryCount()+1);
             FsStoreOrderScrm fsStoreOrder = fsStoreOrderMapper.selectFsStoreOrderByOrderCode(item.getOrderId());
+            LiveOrder liveOrder = null;
+            // 拆开,现在分为商城订单和直播订单
+            if (fsStoreOrder == null) {
+                liveOrder = liveOrderMapper.selectLiveOrderByOrderCode(item.getOrderId());
+                if (liveOrder == null) {
+                    item.setErrorMessage("该订单未找到!");
+                    item.setTaskStatus(TaskStatusEnum.FAILED.getCode());
+                    log.info("该订单未找到!");
+                    fsJstAftersalePushMapper.update(item);
+                    continue;
+                }
+            }
 
-            item.setRetryCount(item.getRetryCount()+1);
 
-            if(fsStoreOrder == null){
-                item.setErrorMessage("该订单未找到!");
-                item.setTaskStatus(TaskStatusEnum.FAILED.getCode());
-                log.info("该订单未找到!");
-                fsJstAftersalePushMapper.update(item);
+            RefundOrderDTO dto;
+            try {
+                if(fsStoreOrder != null){
+                    dto = getAfterSaleDTO(item, fsStoreOrder);
+                }else {
+                    dto = getAfterSaleLiveDTO(item, liveOrder);
+                }
+            } catch (Exception e) {
+                log.error("订单售后创建售后信息失败:{}" ,e.getMessage());
                 continue;
             }
-            Asserts.notNull(fsStoreOrder,"该订单未找到!");
-            RefundOrderDTO dto;
-            dto = getAfterSaleDTO(item, fsStoreOrder);
+
+
+
+
             // 买家已经申请,等待卖家同意
             if(StringUtils.equals(AfterSalesOrderStatusEnum.WAIT_SELLER_AGREE.getIndex().toString()
                     ,item.getType())){
@@ -173,4 +202,40 @@ public class FsJstAftersalePushScrmServiceImpl implements FsJstAftersalePushScrm
         dto.setItems(refundItemDTOS);
         return dto;
     }
+
+    private RefundOrderDTO getAfterSaleLiveDTO(FsJstAftersalePush item, LiveOrder fsStoreOrder) {
+        RefundOrderDTO dto = new RefundOrderDTO();
+        AfterSalesOrderStatusEnum statusEnum = AfterSalesOrderStatusEnum.getByIndex(Integer.valueOf(item.getType()));
+
+        dto.setShopStatus(statusEnum.getCode());
+        dto.setQuestionType("可更新");
+        dto.setOuterAsId(item.getAfterSaleId());
+        dto.setRemark("用户退款");
+        dto.setType("仅退款");
+
+        dto.setShopId(Long.parseLong(shopId));
+        dto.setTotalAmount(fsStoreOrder.getTotalPrice());
+        dto.setSoId(item.getOrderId());
+        dto.setRefund(fsStoreOrder.getPayPrice());
+
+
+        FsStoreOrderItemScrm itemMap=new FsStoreOrderItemScrm();
+//        itemMap.setOrderId(fsStoreOrder.getId());
+        itemMap.setOrderId(fsStoreOrder.getOrderId());
+        List<LiveOrderItem> orderItems=liveOrderItemService.selectCheckedByOrderId(fsStoreOrder.getOrderId());
+        List<RefundItemDTO> refundItemDTOS=new ArrayList<>();
+
+        for(LiveOrderItem orderItem: orderItems) {
+            FsStoreCartDTO cartDTO = JSONUtil.toBean(orderItem.getJsonInfo(), FsStoreCartDTO.class);
+
+            RefundItemDTO itemDTO = new RefundItemDTO();
+            itemDTO.setSkuId(cartDTO.getBarCode());
+            itemDTO.setQty(cartDTO.getNum());
+            itemDTO.setAmount(cartDTO.getPrice());
+            itemDTO.setType("退货");
+            refundItemDTOS.add(itemDTO);
+        }
+        dto.setItems(refundItemDTOS);
+        return dto;
+    }
 }

+ 13 - 0
fs-service/src/main/java/com/fs/fastGpt/domain/FastGptRole.java

@@ -4,6 +4,8 @@ import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
 
+import java.sql.Time;
+
 /**
  * 应用对象 fastgpt_role
  *
@@ -65,4 +67,15 @@ public class FastGptRole extends BaseEntity
     private String channelType;
 
     private Integer logistics;
+
+    //回复禁止起始时间
+    private Time forbidSendStart;
+
+    //回复禁止结束时间
+    private Time forbidSendEnd;
+
+    /**
+     * 是否禁止时段回复 0是不开启禁止  1是开启禁止 默认为1
+     */
+    private Integer forbidStatus;
 }

+ 39 - 1
fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java

@@ -66,14 +66,17 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.ObjectUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.jetbrains.annotations.Nullable;
+import org.json.JSONObject;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import java.lang.reflect.Field;
+import java.sql.Time;
 import java.time.DayOfWeek;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -169,6 +172,8 @@ public class AiHookServiceImpl implements AiHookService {
     private static final String AI_REPLY = "AI_REPLY:";
     private static final String AI_REPLY_TAG = "AI_REPLY_TAG:";
 
+    private final String DELAY_MSG = "delayMsg";
+
 
     /** Ai半小时未回复提醒 **/
     /**
@@ -479,6 +484,39 @@ public class AiHookServiceImpl implements AiHookService {
                 }
             }
 
+            //规定时间不发送内容
+            if(role.getForbidStatus() == null){
+                role.setForbidStatus(1);
+            }
+
+            if(role.getForbidStatus() == 1){
+                Time forbidSendStart = role.getForbidSendStart()==null? Time.valueOf("00:00:00") :role.getForbidSendStart();
+                Time forbidSendEnd = role.getForbidSendEnd()==null? Time.valueOf("04:59:59") :role.getForbidSendEnd();
+                //设置规定时间不发送消息
+                Time now = Time.valueOf(LocalDateTime.now().toLocalTime());
+                // 判断当前时间是否在禁止发送时间段内
+                if (now.after(forbidSendStart) && now.before(forbidSendEnd)) {
+                    String sessionId = String.valueOf(fastGptChatSession.getSessionId());
+                    log.info("当前时间 {} 在禁止发送时间段内 ({} - {}),会话id:{},跳过回复", now, forbidSendStart, forbidSendEnd,fastGptChatSession.getSessionId());
+
+                    // HSET命令是原子的,多个线程同时写不同field不会互相覆盖;同field会覆盖(符合需求)
+                    JSONObject jsonObject = new JSONObject();
+                    jsonObject.put("content",contentEmj);
+                    jsonObject.put("sender",sender);
+                    jsonObject.put("type",type);
+                    String objectString = jsonObject.toString();
+
+                    redisCache.hPut(DELAY_MSG, sessionId, objectString);
+
+                    // 4. 确保主Key有8小时过期时间(只在首次设置时生效,避免重复刷新)
+                    if (!redisCache.hasKey(DELAY_MSG)) {
+                        redisCache.expire(DELAY_MSG, 8, TimeUnit.HOURS);
+                    }
+
+                    return R.ok();
+                }
+            }
+
 
             //判断是否转人工
             if (fastGptChatSession.getIsArtificial()==1){
@@ -549,7 +587,7 @@ public class AiHookServiceImpl implements AiHookService {
                 //从fastgpt_chat_artificial_words表中查询所有转人工文本
                 List<FastgptChatArtificialWords> chatArtificialWords = qwExternalContactMapper.selectChatGptChatArtificialWords();
                 List<String> collect = chatArtificialWords.stream().map(m -> m.getContent()).collect(Collectors.toList());
-                if (collect.stream().anyMatch(contentKh::contains)){
+                if (collect.stream().anyMatch(content::contains)){
                     log.info("触发关键词:"+role.getRoleId()+":"+qwExternalContacts.getName());
                     notifyArtificial(fastGptChatSession.getSessionId(),user,qwExternalContacts.getName()," 触发关键词",qwExternalContacts.getId(),sender);
                     return R.ok();

+ 3 - 0
fs-service/src/main/java/com/fs/his/param/FsIntegralOrderParam.java

@@ -7,6 +7,7 @@ import lombok.Data;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.Date;
+import java.util.List;
 
 @Data
 public class FsIntegralOrderParam {
@@ -17,6 +18,8 @@ public class FsIntegralOrderParam {
     @Excel(name = "订单编号")
     private String orderCode;
 
+    private List<String> orderCodes;
+
     /** 用户id */
     @Excel(name = "用户id")
     private Long userId;

+ 1 - 0
fs-service/src/main/java/com/fs/his/vo/FsIntegralOrderListVO.java

@@ -84,4 +84,5 @@ public class FsIntegralOrderListVO {
     private String erpPhone;
 
     private String loginAccount;
+    private String goodsName;
 }

+ 1 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreAfterSalesScrmServiceImpl.java

@@ -401,6 +401,7 @@ public class FsStoreAfterSalesScrmServiceImpl implements IFsStoreAfterSalesScrmS
         request.setOid(order.getOrderCode());
         request.setRefund_state(1);
         request.setStoreAfterSalesId(storeAfterSales.getId());
+        request.setOrderStatus(orderStatus);
         if (StringUtils.isNotBlank(order.getExtendOrderId())){
             BaseResponse response=erpOrderService.refundUpdateScrm(request);
             if(response.getSuccess()){

+ 1 - 0
fs-service/src/main/java/com/fs/hisStore/service/impl/FsStoreOrderScrmServiceImpl.java

@@ -2399,6 +2399,7 @@ public class FsStoreOrderScrmServiceImpl implements IFsStoreOrderScrmService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
+            request.setOrderStatus(order.getStatus());
             //BaseResponse response=erpOrderService.refundUpdate(request);
 //            if(response.getSuccess()){
 //            }

+ 8 - 0
fs-service/src/main/java/com/fs/hisStore/vo/FsStoreOrderItemExportRefundZMVO.java

@@ -130,4 +130,12 @@ public class FsStoreOrderItemExportRefundZMVO implements Serializable  {
     @Excel(name = "退款金额" ,sort = 240)
     private BigDecimal refundMoney;
 
+    /** 申请原因 */
+    @Excel(name = "申请原因",sort = 250)
+    private String reasons;
+
+    /** 说明 */
+    @Excel(name = "说明",sort = 260)
+    private String explains;
+
 }

+ 10 - 2
fs-service/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -138,6 +138,7 @@ public interface LiveMapper
             "select * from live where 1=1 " +
             " <if test='param.companyId!=null' > and company_id = #{param.companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1 " +
             " <if test='param.liveName!=null' > and live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and start_time between #{param.startTime} and  #{param.endTime}  </if> " +
             " UNION " +
             "select l.* from live l " +
             "LEFT JOIN ( " +
@@ -153,6 +154,7 @@ public interface LiveMapper
             "and TIMESTAMPDIFF(SECOND, l.start_time, NOW()) > COALESCE(video_duration.total_duration, 0) " +
             "and COALESCE(video_duration.total_duration, 0) > 0 " +
             " <if test='param.liveName!=null' > and l.live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and l.start_time between #{param.startTime} and  #{param.endTime}  </if> " +
             "order by create_time desc" +
             " </script>"})
     List<Live> listLiveData(@Param("param") LiveDataParam param);
@@ -162,6 +164,7 @@ public interface LiveMapper
             "select * from live where 1=1 " +
             " <if test='param.companyId!=null' > and company_id = #{param.companyId} </if> and live_type IN (1,2, 3) AND status IN (3, 4) AND is_del = 0 and is_audit=1 " +
             " <if test='param.liveName!=null' > and live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and start_time between #{param.startTime} and  #{param.endTime}  </if> " +
             " UNION " +
             "select l.* from live l " +
             "LEFT JOIN ( " +
@@ -177,6 +180,7 @@ public interface LiveMapper
             "and TIMESTAMPDIFF(SECOND, l.start_time, NOW()) > COALESCE(video_duration.total_duration, 0) " +
             "and COALESCE(video_duration.total_duration, 0) > 0 " +
             " <if test='param.liveName!=null' > and l.live_name like concat('%' ,#{param.liveName},'%') </if> " +
+            " <if test='param.startTime!=null and param.endTime!=null' > and l.start_time between #{param.startTime} and  #{param.endTime}  </if> " +
             ") as temp " +
             " </script>"})
     int listLiveDataCount(@Param("param") LiveDataParam param);
@@ -207,6 +211,10 @@ public interface LiveMapper
             "GROUP BY l.live_id, l.start_time")
     Integer selectLiveFlagByLiveId(@Param("liveId") Long liveId);
 
-    @Select("SELECT * FROM live WHERE is_audit = 1 and is_del = 0 and status in (1,2,4) and live_type in (2,3) order by create_time desc")
-    List<Live> listToLiveNoEnd(Live live);
+    @Select({"<script>" +
+            " SELECT * FROM live WHERE is_audit = 1 and is_del = 0 and status in (1,2,4) and live_type in (2,3) " +
+            "  <if test='live.liveName!=null' > and live_name like concat('%',#{live.liveName},'%') </if> " +
+            " order by create_time desc" +
+            " </script>"})
+    List<Live> listToLiveNoEnd(@Param("live") Live live);
 }

+ 1 - 0
fs-service/src/main/java/com/fs/live/service/impl/LiveAfterSalesServiceImpl.java

@@ -461,6 +461,7 @@ public class LiveAfterSalesServiceImpl implements ILiveAfterSalesService {
         request.setOid(order.getOrderCode());
         request.setRefund_state(1);
         request.setStoreAfterSalesId(storeAfterSales.getId());
+        request.setOrderStatus(orderStatus);
         if (StringUtils.isNotBlank(order.getExtendOrderId())){
             BaseResponse response=erpOrderService.refundUpdateLive(request);
             if(response.getSuccess()){

+ 3 - 2
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -1177,6 +1177,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
+            request.setOrderStatus(order.getStatus());
 
             if (ObjectUtils.equals(order.getStatus(), 2)) {
                 LiveAfterSalesParam param = new LiveAfterSalesParam();
@@ -1373,7 +1374,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             request.setTid(order.getOrderCode());
             request.setOid(order.getOrderCode());
             request.setRefund_state(1);
-
+            request.setOrderStatus(order.getStatus());
             if (ObjectUtils.equals(order.getStatus(), 2)) {
                 LiveAfterSalesParam param = new LiveAfterSalesParam();
                 param.setOrderCode(order.getOrderCode());
@@ -2982,7 +2983,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         }
         String orderId=redisCache.getCacheObject("isPaying:"+order.getOrderId());
         if(StringUtils.isNotEmpty(orderId)&&order.getOrderId().toString().equals(orderId)){
-            return R.error("正在支付中...");
+            return R.error(501,"正在支付中...");
         }
         FsUserScrm user=userMapper.selectFsUserById(Long.valueOf(order.getUserId()));
         if(user == null){

+ 12 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwTagGroupMapper.java

@@ -78,6 +78,18 @@ public interface QwTagGroupMapper
             "</script>"})
     List<QwTagGroupListVO> selectQwTagGroupListVO(QwTagGroup qwTagGroup);
 
+    @Select({"<script> " +
+            "select * from qw_tag_group "+
+            "<where>\n" +
+            "            <if test=\"groupId != null  and groupId != ''\"> and group_id = #{groupId}</if>\n" +
+            "            <if test=\"name != null  and name != ''\"> and name like concat( '%',#{name}, '%')</if>\n" +
+            "            <if test=\"order != null  and order != ''\"> and order = #{order}</if>\n" +
+            "            <if test=\"corpId != null  and corpId != ''\"> and corp_id = #{corpId}</if>\n" +
+            "            <if test=\"companyId != null \"> and company_id = #{companyId}</if>\n" +
+            "        </where> order by `order` desc ,id desc"+
+            "</script>"})
+    List<QwTagGroupListVO> selectQwTagGroupListVOPage(QwTagGroup qwTagGroup);
+
     @Select("select * from qw_tag_group where id=#{id}")
     QwTagGroupVO selectQwTagGroupByIdVO(Long id);
 

+ 6 - 0
fs-service/src/main/java/com/fs/qw/param/QwExternalContactAddTagParam.java

@@ -9,4 +9,10 @@ public class QwExternalContactAddTagParam {
     List<Long> userIds;
     List<String> tagIds;
     String corpId;
+    /**
+     * 指筛选条件  我的,部门,还是全部
+     */
+    private Integer addType;
+    private boolean filter;
+    private QwExternalContactParam param;
 }

+ 8 - 0
fs-service/src/main/java/com/fs/qw/param/QwTagParam.java

@@ -1,5 +1,6 @@
 package com.fs.qw.param;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fs.common.annotation.Excel;
 import lombok.Data;
 
@@ -17,4 +18,11 @@ public class QwTagParam  {
      * 公司id
      */
     private Long companyId;
+
+    @TableField(exist = false)
+    private Integer pageNum = 1;
+
+    @TableField(exist = false)
+    private Integer pageSize = 10;
+
 }

+ 2 - 0
fs-service/src/main/java/com/fs/qw/service/IQwTagGroupService.java

@@ -87,4 +87,6 @@ public interface IQwTagGroupService
     void addQwTagByAi(String trimTag, Long extId);
 
     void delQwTagByAi(String trimTag, Long extId);
+
+    List<QwTagGroupListVO> selectQwTagGroupListVOPage(QwTagGroup qwTagGroup);
 }

+ 14 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwTagGroupServiceImpl.java

@@ -636,4 +636,18 @@ public class QwTagGroupServiceImpl implements IQwTagGroupService {
         }
 
     }
+
+    @Override
+    public List<QwTagGroupListVO> selectQwTagGroupListVOPage(QwTagGroup qwTagGroup) {
+        List<QwTagGroupListVO> vo = qwTagGroupMapper.selectQwTagGroupListVOPage(qwTagGroup);
+
+        for (QwTagGroupListVO qwTagGroupListVO : vo) {
+            QwTag qwTag = new QwTag();
+            qwTag.setGroupId(qwTagGroupListVO.getGroupId());
+            qwTag.setCompanyId(qwTagGroupListVO.getCompanyId());
+            List<QwTagVO> qwTags = qwTagMapper.selectQwTagListVO(qwTag);
+            qwTagGroupListVO.setTag(qwTags);
+        }
+        return vo;
+    }
 }

+ 2 - 0
fs-service/src/main/java/com/fs/system/mapper/SysUserMapper.java

@@ -130,4 +130,6 @@ public interface SysUserMapper
      * @return 结果
      */
     public SysUser checkEmailUnique(String email);
+
+    List<SysUser> selectUserByPhone(String phone);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/system/service/ISysUserService.java

@@ -214,4 +214,6 @@ public interface ISysUserService
     public String importUser(List<SysUser> userList, Boolean isUpdateSupport, String operName);
 
     int updateUserInfo(SysUser sysuser);
+
+    List<SysUser> selectUserByPhone(String phone);
 }

+ 4 - 0
fs-service/src/main/java/com/fs/system/service/impl/SysUserServiceImpl.java

@@ -586,4 +586,8 @@ public class SysUserServiceImpl implements ISysUserService
     public int updateUserInfo(SysUser sysuser) {
         return userMapper.updateUser(sysuser);
     }
+
+    public List<SysUser> selectUserByPhone(String phone) {
+        return userMapper.selectUserByPhone(phone);
+    }
 }

+ 9 - 0
fs-service/src/main/resources/application-common.yml

@@ -143,3 +143,12 @@ wechat:
   api:
     base-url: https://api.weixin.qq.com
     upload-shipping-info: /wxa/sec/order/upload_shipping_info
+  company:
+    appid: wxd7c1e221622a0ccf
+    secret: 70d3ed4f8eb68cca0cf525b8ce07405d
+    redirectUri: http://rfa96c48.natappfree.cc/callback
+  admin:
+    appid: wxd7c1e221622a0ccf
+    secret: 70d3ed4f8eb68cca0cf525b8ce07405d
+    redirectUri: http://rfa96c48.natappfree.cc/callback
+  isNeedScan: false

+ 3 - 1
fs-service/src/main/resources/mapper/company/CompanyUserMapper.xml

@@ -44,6 +44,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="isAllowedAllRegister"    column="is_allowed_all_register"    />
         <result property="doctorId"    column="doctor_id"    />
         <result property="bindCompanyUserId"    column="bind_company_user_id"    />
+        <result property="unionId"    column="union_id"    />
         <association property="dept"    column="dept_id" javaType="CompanyDept" resultMap="deptResult" />
         <collection  property="roles"   javaType="java.util.List"        resultMap="RoleResult" />
     </resultMap>
@@ -315,6 +316,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="domain != null">domain = #{domain},</if>
             <if test="isAudit != null">`is_audit` = #{isAudit},</if>
             <if test="doctorId != null">`doctor_id` = #{doctorId},</if>
+            <if test="unionId != null">`union_id` = #{unionId},</if>
         </trim>
         where user_id = #{userId}
     </update>
@@ -443,7 +445,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                u.create_time,u.id_card, u.remark,u.user_type,u.open_id,u.qr_code_weixin,u.qr_code_wecom,u.jpush_id,u.domain,u.is_audit,u.address_id,
                d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,
                r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,
-               u.is_need_register_member, u.is_allowed_all_register,u.doctor_id
+               u.is_need_register_member, u.is_allowed_all_register,u.doctor_id,u.union_id
         from company_user u
                  left join company_dept d on u.dept_id = d.dept_id
                  left join company_user_role ur on u.user_id = ur.user_id

+ 8 - 1
fs-service/src/main/resources/mapper/fastGpt/FastGptRoleMapper.xml

@@ -22,10 +22,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="contactInfo"    column="contact_info"    />
         <result property="channelType"    column="channel_type"    />
         <result property="logistics"    column="logistics"    />
+        <result property="forbidSendStart"    column="forbid_send_start"    />
+        <result property="forbidSendEnd"    column="forbid_send_end"    />
+        <result property="forbidStatus"    column="forbid_status"    />
     </resultMap>
 
     <sql id="selectFastGptRoleVo">
-        select role_id, role_name,contact_info,company_id, create_time, update_time, role_type, mode_config_json, mode, kf_id, kf_url, avatar, kf_media_id,reminder_words, bind_corp_id,channel_type,logistics from fastgpt_role
+        select role_id, role_name,contact_info,company_id, create_time, update_time, role_type, mode_config_json,
+               mode, kf_id, kf_url, avatar, kf_media_id,reminder_words, bind_corp_id,channel_type,logistics,forbid_send_start,
+               forbid_send_end,forbid_status
+        from fastgpt_role
     </sql>
 
     <select id="selectFastGptRoleList" parameterType="FastGptRole" resultMap="FastGptRoleResult">
@@ -136,6 +142,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="contactInfo != null">contact_info = #{contactInfo},</if>
             <if test="channelType != null">channel_type = #{channelType},</if>
             <if test="logistics != null">logistics = #{logistics},</if>
+            <if test="forbidStatus != null">forbid_status = #{forbidStatus},</if>
         </trim>
         where role_id = #{roleId}
     </update>

+ 6 - 0
fs-service/src/main/resources/mapper/his/FsIntegralOrderMapper.xml

@@ -97,6 +97,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         1 = 1
         <!-- 订单编码精确查询 -->
         <if test="orderCode != null and orderCode != ''"> AND fio.order_code = #{orderCode} </if>
+        <if test="orderCodes != null and orderCodes.size >0">
+            AND fio.order_code in
+            <foreach collection="orderCodes" item="orderCode" open="(" close=")" separator=",">
+                #{orderCode}
+            </foreach>
+        </if>
         <!-- 用户名模糊查询 -->
         <if test="userName != null and userName != ''"> AND fio.user_name LIKE CONCAT('%', #{userName}, '%') </if>
         <!-- 用户手机号精确查询 -->

+ 21 - 9
fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml

@@ -1309,7 +1309,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             left join fs_store_order_item_scrm oi on o.id = oi.order_id
             left join fs_store_product_scrm fsp on fsp.product_id = oi.product_id
         </if>
-        <if test="maps.appId != null and maps.appId != ''">
+        <if test="(maps.appId != null and maps.appId != '') or (maps.bankTransactionId != null and  maps.bankTransactionId !='')">
             LEFT JOIN (
             SELECT
             sp.*,
@@ -1456,7 +1456,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         LEFT JOIN fs_user us ON us.user_id=o.user_id
         LEFT JOIN company_user cu on cu.user_id=o.company_user_id
         LEFT JOIN fs_store_order_df df on df.order_id=o.id
-        <if test="maps.appId != null and maps.appId != ''">
+        <if test="(maps.appId != null and maps.appId != '') or (maps.bankTransactionId != null and  maps.bankTransactionId !='')">
             LEFT JOIN (
             SELECT
             sp.*,
@@ -1466,6 +1466,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             ) sp_latest ON sp_latest.business_code = o.order_code AND sp_latest.rn = 1
             LEFT JOIN fs_course_play_source_config csc ON csc.appid = sp_latest.app_id
         </if>
+        <if test = "maps.productName != null and  maps.productName !=  '' ">
+            left join fs_store_product_scrm fsp on fsp.product_id = soi.product_id
+        </if>
         <where>
             <if test="maps.appId != null and maps.appId != ''">
                 and csc.appid = #{maps.appId}
@@ -1730,9 +1733,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
         </where>
         ${maps.params.dataScope}
-        <if test="maps.productName != null and  maps.productName !=  ''   ">
-            group by o.id
-        </if>
+<!--        <if test="maps.productName != null and  maps.productName !=  ''   ">-->
+<!--            group by o.id-->
+<!--        </if>-->
             order by
         <if test="maps.sortField == 'companyUserName'">
             cu.nick_name
@@ -1771,7 +1774,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             left join fs_store_order_item_scrm oi on o.id = oi.order_id
             left join fs_store_product_scrm fsp on fsp.product_id = oi.product_id
         </if>
-        <if test="maps.appId != null and maps.appId != ''">
+        <if test="(maps.appId != null and maps.appId != '') or (maps.bankTransactionId != null and  maps.bankTransactionId !='')">
             LEFT JOIN (
             SELECT
             sp.*,
@@ -1781,6 +1784,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             ) sp_latest ON sp_latest.business_code = o.order_code AND sp_latest.rn = 1
             LEFT JOIN fs_course_play_source_config csc ON csc.appid = sp_latest.app_id
         </if>
+<!--        <if  test="maps.bankTransactionId != null and  maps.bankTransactionId !=''">-->
+<!--            LEFT JOIN (-->
+<!--            SELECT-->
+<!--            sp.*,-->
+<!--            ROW_NUMBER() OVER (PARTITION BY sp.business_code ORDER BY sp.create_time DESC) as rn-->
+<!--            FROM fs_store_payment_scrm sp-->
+<!--            WHERE sp.business_code IS NOT NULL-->
+<!--            ) sp_latest ON sp_latest.business_order_id = o.id AND sp_latest.rn = 1-->
+<!--        </if>-->
 
         <where>
             <if test="maps.appId != null and map.appId != ''">
@@ -1904,9 +1916,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             </if>
         </where>
         ${maps.params.dataScope}
-        <if test="maps.productName != null and  maps.productName !=  ''   ">
-            group by o.id
-        </if>
+<!--        <if test="maps.productName != null and  maps.productName !=  ''   ">-->
+<!--            group by o.id-->
+<!--        </if>-->
         order by o.id desc
     </select>
     <select id="selectAddTuiMoney" resultType="java.lang.Long">

+ 3 - 0
fs-service/src/main/resources/mapper/live/LiveOrderMapper.xml

@@ -1026,6 +1026,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
         <where>
             o.is_del = 0 and p.product_id IS NOT NULL
+            <if test="bankTransactionId != null and  bankTransactionId !=''">
+                and lop.bank_transaction_id like CONCAT('%',#{bankTransactionId},'%')
+            </if>
             <if test="orderId != null">
                 AND o.order_id = #{orderId}
             </if>

+ 9 - 2
fs-service/src/main/resources/mapper/system/SysUserMapper.xml

@@ -50,6 +50,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 		<result property="updateTime"   column="update_time"  />
 		<result property="remark"       column="remark"       />
 		<result property="companyId"       column="company_id"       />
+		<result property="unionId"       column="union_id"       />
 		<association property="dept"    column="dept_id" javaType="SysDept" resultMap="deptResult" />
 		<collection  property="roles"   javaType="java.util.List"        resultMap="RoleResult" />
 	</resultMap>
@@ -75,7 +76,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 	<sql id="selectUserVo">
         select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
         d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,
-        r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,u.company_id
+        r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status,u.company_id,u.union_id
         from sys_user u
 		    left join sys_dept d on u.dept_id = d.dept_id
 		    left join sys_user_role ur on u.user_id = ur.user_id
@@ -247,7 +248,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 		select user_id, email from sys_user where email = #{email} limit 1
 	</select>
 
-	<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
+    <select id="selectUserByPhone" resultType="com.fs.common.core.domain.entity.SysUser">
+		<include refid="selectUserVo"/>
+		where u.phonenumber = #{phone}
+	</select>
+
+    <insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
  		insert into sys_user(
  			<if test="userId != null and userId != 0">user_id,</if>
  			<if test="deptId != null and deptId != 0">dept_id,</if>
@@ -298,6 +304,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
  			<if test="remark != null">remark = #{remark},</if>
  			<if test="companyId != null">company_id = #{companyId},</if>
+ 			<if test="unionId != null">union_id = #{unionId},</if>
  			update_time = sysdate()
  		</set>
  		where user_id = #{userId}

+ 1 - 0
fs-user-app/src/main/java/com/fs/app/controller/live/LiveAfterSalesController.java

@@ -6,6 +6,7 @@ import com.fs.app.controller.AppBaseController;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.R;
 import com.fs.common.utils.ParseUtils;
+import com.fs.erp.service.FsJstAftersalePushScrmService;
 import com.fs.live.domain.LiveAfterSales;
 import com.fs.live.domain.LiveAfterSalesItem;
 import com.fs.live.domain.LiveOrder;