Procházet zdrojové kódy

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_java

# Conflicts:
#	fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
#	fs-company-app/src/main/resources/application.yml
#	fs-service/src/main/resources/application-druid-qdtst-test.yml
#	fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
15376779826 před 1 dnem
rodič
revize
7ed418feae
47 změnil soubory, kde provedl 1600 přidání a 240 odebrání
  1. 6 6
      fs-admin/src/main/java/com/fs/his/controller/FsArticleController.java
  2. 37 0
      fs-company-app/src/main/java/com/fs/app/controller/AppBaseController.java
  3. 214 6
      fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java
  4. 28 32
      fs-company-app/src/main/java/com/fs/core/aspectj/DataScopeAspect.java
  5. 73 0
      fs-company-app/src/main/java/com/fs/core/aspectj/DataSourceAspect.java
  6. 244 0
      fs-company-app/src/main/java/com/fs/core/aspectj/LogAspect.java
  7. 94 0
      fs-company-app/src/main/java/com/fs/core/config/DataSourceConfig.java
  8. 123 123
      fs-company-app/src/main/java/com/fs/core/config/DruidConfig.java
  9. 3 3
      fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSource.java
  10. 4 5
      fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java
  11. 56 0
      fs-company-app/src/main/java/com/fs/core/manager/AsyncManager.java
  12. 40 0
      fs-company-app/src/main/java/com/fs/core/manager/ShutdownManager.java
  13. 103 0
      fs-company-app/src/main/java/com/fs/core/manager/factory/AsyncFactory.java
  14. 2 2
      fs-company-app/src/main/resources/application.yml
  15. 3 1
      fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java
  16. 43 2
      fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwSopController.java
  17. 6 1
      fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java
  18. 2 2
      fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java
  19. 6 3
      fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java
  20. 6 5
      fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java
  21. 3 0
      fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java
  22. 1 0
      fs-service/src/main/java/com/fs/fastGpt/service/impl/AiHookServiceImpl.java
  23. 14 0
      fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java
  24. 2 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsPrescribeScrm.java
  25. 10 0
      fs-service/src/main/java/com/fs/hisStore/domain/FsUserScrm.java
  26. 4 0
      fs-service/src/main/java/com/fs/qw/domain/QwUser.java
  27. 6 0
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java
  28. 2 1
      fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactTransferLogMapper.java
  29. 26 0
      fs-service/src/main/java/com/fs/qw/param/CourseQuizRedEnvelopeStatsParam.java
  30. 17 0
      fs-service/src/main/java/com/fs/qw/param/ExternalContactParam.java
  31. 37 0
      fs-service/src/main/java/com/fs/qw/param/QwSidebarStatsParam.java
  32. 0 1
      fs-service/src/main/java/com/fs/qw/param/SopMsgParam.java
  33. 6 0
      fs-service/src/main/java/com/fs/qw/service/IQwExternalContactService.java
  34. 5 0
      fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java
  35. 14 0
      fs-service/src/main/java/com/fs/sop/service/IQwSopService.java
  36. 125 3
      fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java
  37. 57 22
      fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java
  38. 15 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalAnswerStatsVO.java
  39. 16 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalRedPacketStatsVO.java
  40. 35 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalUserStatsVO.java
  41. 15 0
      fs-service/src/main/java/com/fs/store/vo/h5/ExternalWatchStatsVO.java
  42. 3 9
      fs-service/src/main/resources/application-druid-qdtst-test.yml
  43. 38 3
      fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml
  44. 4 5
      fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml
  45. 44 0
      fs-service/src/main/resources/mapper/his/FsUserMapper.xml
  46. 4 4
      fs-service/src/main/resources/mapper/hisStore/FsStoreOrderScrmMapper.xml
  47. 4 1
      fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

+ 6 - 6
fs-admin/src/main/java/com/fs/his/controller/FsArticleController.java

@@ -42,7 +42,7 @@ public class FsArticleController extends BaseController
     /**
      * 查询文章列表
      */
-    @PreAuthorize("@ss.hasPermi('his:article:list')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:list')")
     @GetMapping("/list")
     public TableDataInfo list(FsArticle fsArticle)
     {
@@ -54,7 +54,7 @@ public class FsArticleController extends BaseController
     /**
      * 导出文章列表
      */
-    @PreAuthorize("@ss.hasPermi('his:article:export')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:export')")
     @Log(title = "文章", businessType = BusinessType.EXPORT)
     @GetMapping("/export")
     public AjaxResult export(FsArticle fsArticle)
@@ -67,7 +67,7 @@ public class FsArticleController extends BaseController
     /**
      * 获取文章详细信息
      */
-    @PreAuthorize("@ss.hasPermi('his:article:query')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:query')")
     @GetMapping(value = "/{articleId}")
     public AjaxResult getInfo(@PathVariable("articleId") Long articleId)
     {
@@ -77,7 +77,7 @@ public class FsArticleController extends BaseController
     /**
      * 新增文章
      */
-    @PreAuthorize("@ss.hasPermi('his:article:add')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:add')")
     @Log(title = "文章", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody FsArticle fsArticle)
@@ -94,7 +94,7 @@ public class FsArticleController extends BaseController
     /**
      * 修改文章
      */
-    @PreAuthorize("@ss.hasPermi('his:article:edit')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:edit')")
     @Log(title = "文章", businessType = BusinessType.UPDATE)
     @PutMapping
     public AjaxResult edit(@RequestBody FsArticle fsArticle)
@@ -110,7 +110,7 @@ public class FsArticleController extends BaseController
     /**
      * 删除文章
      */
-    @PreAuthorize("@ss.hasPermi('his:article:remove')")
+    @PreAuthorize("@ss.hasPermi('his:healthArticle:remove')")
     @Log(title = "文章", businessType = BusinessType.DELETE)
 	@DeleteMapping("/{articleIds}")
     public AjaxResult remove(@PathVariable Long[] articleIds)

+ 37 - 0
fs-company-app/src/main/java/com/fs/app/controller/AppBaseController.java

@@ -4,15 +4,24 @@ package com.fs.app.controller;
 import cn.hutool.core.util.ObjectUtil;
 import com.fs.app.exception.FSException;
 import com.fs.app.utils.JwtUtils;
+import com.fs.common.constant.HttpStatus;
+import com.fs.common.core.page.PageDomain;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.core.page.TableSupport;
 import com.fs.common.core.redis.RedisCache;
 import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.sql.SqlUtil;
 import com.fs.company.domain.CompanyUser;
 import com.fs.company.service.ICompanyUserService;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
+import com.github.pagehelper.PageHelper;
+import com.github.pagehelper.PageInfo;
 import io.jsonwebtoken.Claims;
 import org.springframework.beans.factory.annotation.Autowired;
 
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 
@@ -67,5 +76,33 @@ public class AppBaseController {
 		}
 		return user.getUserId();
 	}
+    /**
+     * 设置请求分页数据
+     */
+    protected void startPage()
+    {
+        PageDomain pageDomain = TableSupport.buildPageRequest();
+        Integer pageNum = pageDomain.getPageNum();
+        Integer pageSize = pageDomain.getPageSize();
+        if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
+        {
+            String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
+            Boolean reasonable = pageDomain.getReasonable();
+            PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
+        }
+    }
 
+    /**
+     * 响应请求分页数据
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    protected TableDataInfo getDataTable(List<?> list)
+    {
+        TableDataInfo rspData = new TableDataInfo();
+        rspData.setCode(HttpStatus.SUCCESS);
+        rspData.setMsg("查询成功");
+        rspData.setRows(list);
+        rspData.setTotal(new PageInfo(list).getTotal());
+        return rspData;
+    }
 }

+ 214 - 6
fs-company-app/src/main/java/com/fs/app/controller/CompanyUserController.java

@@ -2,6 +2,7 @@ package com.fs.app.controller;
 
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.fs.app.annotation.Login;
@@ -11,19 +12,32 @@ import com.fs.app.vo.CompanySubUserVO;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.constant.UserConstants;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.page.TableDataInfo;
+import com.fs.common.core.redis.RedisCache;
 import com.fs.common.exception.ServiceException;
 import com.fs.common.utils.PatternUtils;
 import com.fs.common.utils.bean.BeanUtils;
 import com.fs.company.domain.*;
 import com.fs.company.mapper.CompanyRoleMapper;
+import com.fs.company.mapper.CompanyUserMapper;
+import com.fs.company.param.companyUserAddPrintParam;
 import com.fs.company.service.*;
 import com.fs.company.vo.CompanyTagUserVO;
 import com.fs.company.vo.CompanyUserChangeApplyVO;
+import com.fs.config.ai.AiHostProper;
 import com.fs.core.security.SecurityUtils;
 import com.fs.course.service.IFsCourseRedPacketLogService;
 import com.fs.course.service.IFsCourseWatchLogService;
 import com.fs.course.service.IFsUserCompanyUserService;
+import com.fs.fastGpt.domain.FastgptChatVoiceHomo;
+import com.fs.fastGpt.mapper.FastgptChatVoiceHomoMapper;
+import com.fs.fastgptApi.util.AudioUtils;
+import com.fs.fastgptApi.vo.AudioVO;
 import com.fs.qw.dto.UserProjectDTO;
+import com.fs.sop.domain.QwSopTempVoice;
+import com.fs.sop.service.IQwSopTempVoiceService;
+import com.fs.system.oss.CloudStorageService;
+import com.fs.system.oss.OSSFactory;
 import com.fs.system.service.ISysDictDataService;
 import com.fs.system.vo.DictVO;
 import com.github.pagehelper.PageHelper;
@@ -34,10 +48,19 @@ import io.swagger.annotations.ApiParam;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.ibatis.annotations.Param;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
+import java.io.File;
+import java.io.FileInputStream;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDate;
@@ -59,12 +82,15 @@ public class CompanyUserController extends AppBaseController {
     private final ICompanyUserChangeApplyService companyUserChangeApplyService;
     private final CompanyRoleMapper companyRoleMapper;
     private final IAppService appService;
-    @Autowired
-    private ISysDictDataService dictDataService;
-    @Autowired
-    private IFsUserCompanyUserService fsUserCompanyUserService;
-    @Autowired
-    private ICompanyTagUserService companyTagUserService;
+    private final RedisCache redisCache;
+    private final CompanyUserMapper companyUserMapper;
+    private final IQwSopTempVoiceService voiceService;
+    private final AiHostProper aiHostProper;
+    private final ISysDictDataService dictDataService;
+    private final IFsUserCompanyUserService fsUserCompanyUserService;
+    private final ICompanyTagUserService companyTagUserService;
+    private final FastgptChatVoiceHomoMapper fastgptChatVoiceHomoMapper;
+    public static final String SOP_TEMP_VOICE_KEY = "sop:tempVoice";
 
     @Login
     @ApiOperation("查询用户列表")
@@ -402,4 +428,186 @@ public class CompanyUserController extends AppBaseController {
         return R.ok().put("data",dictVOS);
     }
 
+    /**
+     * 当只有模板文字text时,生成表中对应条的voice_url和user_voice_url
+     * @param id            qw_sop_temp_voice的id
+     * @return
+     */
+    @GetMapping("/companyUserVoice")
+    public R companyUserVoice(@RequestParam("id") Long id){
+        AudioVO audioVO = new AudioVO();
+        Long companyUserId = getCompanyUserId();
+        List<QwSopTempVoice> sopTempVoices = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY + ":" + companyUserId);
+        if(sopTempVoices != null && !sopTempVoices.isEmpty()){
+            List<Long> collect = sopTempVoices.stream().map(QwSopTempVoice::getId).collect(Collectors.toList());
+            if (collect.contains(id)){
+                return R.ok().put("code",202).put("msg","该语音已进入转换,请完成后再试。");
+            }
+        }
+
+        if(companyUserId != null){
+            CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(companyUserId);
+            if(companyUser != null && companyUser.getVoicePrintUrl() == null){
+                return R.ok().put("code",201).put("msg","账号未录制声纹,请录制后再试!");
+            }
+        }
+
+        QwSopTempVoice qwSopTempVoice = voiceService.selectQwSopTempVoiceById(id);
+        if(qwSopTempVoice != null && qwSopTempVoice.getCompanyUserId() != null){
+            List<FastgptChatVoiceHomo> homos = fastgptChatVoiceHomoMapper.selectFastgptChatVoiceHomoList(new FastgptChatVoiceHomo());
+            audioVO = AudioUtils.createUserUrlAndUrl(homos,qwSopTempVoice.getCompanyUserId(), qwSopTempVoice.getVoiceTxt().replace(" ",""));
+            if(audioVO != null && audioVO.getWavUrl() != null &&  audioVO.getUrl() != null){
+                qwSopTempVoice.setVoiceUrl(audioVO.getUrl());
+                qwSopTempVoice.setUserVoiceUrl(audioVO.getWavUrl());
+                qwSopTempVoice.setDuration(audioVO.getDuration());
+                qwSopTempVoice.setRecordType(1);
+                voiceService.updateQwSopTempVoice(qwSopTempVoice);
+            }
+        }
+        return R.ok().put("data", audioVO);
+    }
+    @Login
+    @ApiOperation("上传声纹")
+    @PostMapping("/addVoicePrintUrl")
+    public R addVoicePrintUrl(@RequestBody companyUserAddPrintParam param) throws Exception {
+        Long userId=getCompanyUserId();
+        if(userId==null){
+            return R.error(403,"用户失效");
+        }
+        CompanyUser companyUser = new CompanyUser();
+        companyUser.setUserId(userId);
+        companyUser.setVoicePrintUrl(param.getVoicePrintUrl());
+
+        //转换音频格式 mp3-wav
+        String s = AudioUtils.audioWAVFromUrl(param.getVoicePrintUrl());
+
+        //保存文件并且上传存储桶
+        System.out.println(s);
+        File file = new File(s);
+        FileInputStream fileInputStream = new FileInputStream(file);
+        CloudStorageService storage = OSSFactory.build();
+        String wavUrl = storage.uploadSuffix(fileInputStream, ".wav");
+
+        //更新销售员工声纹
+        companyUser.setVoicePrintUrl(wavUrl);
+        companyUserMapper.updateCompanyUser(companyUser);
+
+        try {
+            CloseableHttpClient httpClient = HttpClients.createDefault();
+            HttpPost httpPost = new HttpPost(aiHostProper.getCommonApi()+"/app/common/addCompanyAudio");
+            String json = "{\"url\":\""+wavUrl+"\",\"id\":\""+userId+"\"}";
+            StringEntity entity = new StringEntity(json);
+            httpPost.setEntity(entity);
+            httpPost.setHeader("Content-type", "application/json");
+            HttpResponse response = httpClient.execute(httpPost);
+
+            if (response.getStatusLine().getStatusCode() == 200) {
+                String responseBody = EntityUtils.toString(response.getEntity());
+                JSONObject jsonObject = JSON.parseObject(responseBody);
+                Integer code = (Integer)jsonObject.get("code");
+                if (code==200){
+                    voiceService.insertQwSopTempVoiceModel(userId);
+                    return R.ok();
+                }
+            } else {
+                return R.error();
+            }
+
+            httpClient.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return R.error();
+
+    }
+
+    /**
+     * 当只有user_voice_url时,生成表中对应条的voice_url
+     * @param userVoiceUrl  wav格式的语音文件
+     * @param id            qw_sop_temp_voice的id
+     * @return
+     */
+    @GetMapping("/companyUserVoiceNew")
+    public R companyUserVoiceNew( @RequestParam("id") Long id,@RequestParam("userVoiceUrl") String userVoiceUrl){
+
+        AudioVO audioVO = new AudioVO();
+        Long companyUserId = getCompanyUserId();
+        List<QwSopTempVoice> sopTempVoices = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY + ":" + companyUserId);
+        if(sopTempVoices != null && !sopTempVoices.isEmpty()){
+            List<Long> collect = sopTempVoices.stream().map(QwSopTempVoice::getId).collect(Collectors.toList());
+            if (collect.contains(id)){
+                return R.ok().put("code",202).put("msg","该语音已进入转换,请完成后再试。");
+            }
+        }
+
+        QwSopTempVoice qwSopTempVoice = voiceService.selectQwSopTempVoiceByIdAndUserVoiceUrl(id);
+        if(qwSopTempVoice != null && qwSopTempVoice.getId() != null){
+            audioVO = AudioUtils.createVoiceUrl(qwSopTempVoice.getCompanyUserId(), userVoiceUrl);
+            if(audioVO != null && audioVO.getUrl() != null){
+                qwSopTempVoice.setVoiceUrl(audioVO.getUrl());
+                qwSopTempVoice.setUserVoiceUrl(userVoiceUrl);
+                qwSopTempVoice.setDuration(audioVO.getDuration());
+                qwSopTempVoice.setRecordType(1);
+                voiceService.updateQwSopTempVoice(qwSopTempVoice);
+            }
+        }
+        return R.ok().put("data", audioVO);
+    }
+    @GetMapping("/querySopVoiceList")
+    public TableDataInfo querySopVoiceList(@Param("recordType") Integer recordType){
+        startPage();
+        QwSopTempVoice sopTempVoice = new QwSopTempVoice();
+        sopTempVoice.setRecordType(recordType);
+        sopTempVoice.setCompanyUserId(getCompanyUserId());
+        List<QwSopTempVoice> sopTempVoices = voiceService.selectQwSopTempVoiceNewList(sopTempVoice);
+        return getDataTable(sopTempVoices);
+    }
+    @GetMapping("/query/{id}")
+    public R querySopVoiceById(@PathVariable("id") Long id){
+        QwSopTempVoice tempVoice = voiceService.selectQwSopTempVoiceById(id);
+        AudioVO audioVO = new AudioVO();
+        if(tempVoice != null){
+            audioVO.setId(tempVoice.getId());
+            audioVO.setVoiceTxt(tempVoice.getVoiceTxt());
+            audioVO.setUrl(tempVoice.getVoiceUrl());
+            audioVO.setWavUrl(tempVoice.getUserVoiceUrl());
+            audioVO.setDuration(tempVoice.getDuration());
+            audioVO.setRecordType(tempVoice.getRecordType());
+        }
+        return R.ok().put("data", audioVO);
+    }
+    /**
+     * 一键转换
+     * @return
+     */
+    @GetMapping("/createUserAllVoice")
+    public R createUserAllVoice(){
+        QwSopTempVoice sopTempVoice = new QwSopTempVoice();
+        sopTempVoice.setRecordType(0);
+        Long companyUserId = getCompanyUserId();
+
+
+        if(companyUserId != null){
+            CompanyUser companyUser = companyUserMapper.selectCompanyUserByCompanyUserId(companyUserId);
+            if(companyUser != null && companyUser.getVoicePrintUrl() == null){
+                return R.ok().put("code",201).put("msg","账号未录制声纹,请录制后再试!");
+            }
+        }
+
+        sopTempVoice.setCompanyUserId(companyUserId);
+        List<QwSopTempVoice> sopTempVoices = voiceService.selectQwSopTempVoiceNewList(sopTempVoice);
+        if(sopTempVoices != null && !sopTempVoices.isEmpty()){
+            List<Long> newCompanyUserId = redisCache.getVoiceAllList(SOP_TEMP_VOICE_KEY);
+            if(newCompanyUserId != null && newCompanyUserId.contains(companyUserId)){
+                return R.error().put("code",202).put("msg","语音还未转换完成,请完成后再添加!");
+            }else{
+                redisCache.setVoice(SOP_TEMP_VOICE_KEY,companyUserId);
+                sopTempVoices.forEach(m -> m.setVoiceTxt(m.getVoiceTxt().replace(" ","")));
+                redisCache.setVoiceList(SOP_TEMP_VOICE_KEY + ":" + companyUserId, sopTempVoices);
+                return R.ok().put("msg","语音已加入队列进行转换,请耐心等待!");
+            }
+        }
+        return null;
+    }
 }

+ 28 - 32
fs-company-app/src/main/java/com/fs/core/aspectj/DataScopeAspect.java

@@ -1,27 +1,20 @@
 package com.fs.core.aspectj;
 
-import com.fs.app.utils.JwtUtils;
 import com.fs.common.annotation.DataScope;
 import com.fs.common.core.domain.BaseEntity;
+import com.fs.common.core.domain.entity.SysRole;
+import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.StringUtils;
-import com.fs.common.utils.spring.SpringUtils;
-import com.fs.company.domain.CompanyRole;
-import com.fs.company.domain.CompanyUser;
-import com.fs.company.service.ICompanyUserService;
-import io.jsonwebtoken.Claims;
 import org.aspectj.lang.JoinPoint;
 import org.aspectj.lang.Signature;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Before;
 import org.aspectj.lang.annotation.Pointcut;
 import org.aspectj.lang.reflect.MethodSignature;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
-import org.springframework.web.context.request.RequestAttributes;
-import org.springframework.web.context.request.RequestContextHolder;
-import org.springframework.web.context.request.ServletRequestAttributes;
 
-import javax.servlet.http.HttpServletRequest;
 import java.lang.reflect.Method;
 
 /**
@@ -33,9 +26,6 @@ import java.lang.reflect.Method;
 @Component
 public class DataScopeAspect
 {
-    @Autowired
-    JwtUtils jwtUtils;
-
     /**
      * 全部数据权限
      */
@@ -75,6 +65,7 @@ public class DataScopeAspect
     @Before("dataScopePointCut()")
     public void doBefore(JoinPoint point) throws Throwable
     {
+        clearDataScope(point);
         handleDataScope(point);
     }
 
@@ -86,22 +77,15 @@ public class DataScopeAspect
         {
             return;
         }
-        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
-        ServletRequestAttributes sra = (ServletRequestAttributes)ra;
-        HttpServletRequest request = sra.getRequest();
-
-        String headValue = request.getHeader("APPToken");
-        Claims claims = jwtUtils.getClaimByToken(headValue);
-        Long userId =Long.parseLong( claims.getSubject().toString());
-
         // 获取当前的用户
-        CompanyUser user = SpringUtils.getBean(ICompanyUserService.class).selectCompanyUserById(userId);
-        if (StringUtils.isNotNull(user))
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        if (StringUtils.isNotNull(loginUser))
         {
+            SysUser currentUser = loginUser.getUser();
             // 如果是超级管理员,则不过滤数据
-            if (StringUtils.isNotNull(user) && !user.isAdmin())
+            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
             {
-                dataScopeFilter(joinPoint, user, controllerDataScope.deptAlias(),
+                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                         controllerDataScope.userAlias());
             }
         }
@@ -114,11 +98,11 @@ public class DataScopeAspect
      * @param user 用户
      * @param userAlias 别名
      */
-    public static void dataScopeFilter(JoinPoint joinPoint, CompanyUser user, String deptAlias, String userAlias)
+    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
     {
         StringBuilder sqlString = new StringBuilder();
 
-        for (CompanyRole role : user.getRoles())
+        for (SysRole role : user.getRoles())
         {
             String dataScope = role.getDataScope();
             if (DATA_SCOPE_ALL.equals(dataScope))
@@ -129,7 +113,7 @@ public class DataScopeAspect
             else if (DATA_SCOPE_CUSTOM.equals(dataScope))
             {
                 sqlString.append(StringUtils.format(
-                        " OR {}.dept_id IN ( SELECT dept_id FROM company_role_dept WHERE role_id = {} ) ", deptAlias,
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                         role.getRoleId()));
             }
             else if (DATA_SCOPE_DEPT.equals(dataScope))
@@ -139,7 +123,7 @@ public class DataScopeAspect
             else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
             {
                 sqlString.append(StringUtils.format(
-                        " OR {}.dept_id IN ( SELECT dept_id FROM company_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
+                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                         deptAlias, user.getDeptId(), user.getDeptId()));
             }
             else if (DATA_SCOPE_SELF.equals(dataScope))
@@ -151,8 +135,7 @@ public class DataScopeAspect
                 else
                 {
                     // 数据权限为仅本人且没有userAlias别名不查询任何数据
-//                    sqlString.append(" OR 1=0 ");
-                    sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
+                    sqlString.append(" OR 1=0 ");
                 }
             }
         }
@@ -183,4 +166,17 @@ public class DataScopeAspect
         }
         return null;
     }
+
+    /**
+     * 拼接权限sql前先清空params.dataScope参数防止注入
+     */
+    private void clearDataScope(final JoinPoint joinPoint)
+    {
+        Object params = joinPoint.getArgs()[0];
+        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
+        {
+            BaseEntity baseEntity = (BaseEntity) params;
+            baseEntity.getParams().put(DATA_SCOPE, "");
+        }
+    }
 }

+ 73 - 0
fs-company-app/src/main/java/com/fs/core/aspectj/DataSourceAspect.java

@@ -0,0 +1,73 @@
+package com.fs.core.aspectj;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.utils.StringUtils;
+import com.fs.core.datasource.DynamicDataSourceContextHolder;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.AnnotationUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+
+/**
+ * 多数据源处理
+ * 
+
+ */
+@Aspect
+@Order(1)
+@Component
+public class DataSourceAspect
+{
+    protected Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Pointcut("@annotation(com.fs.common.annotation.DataSource)"
+            + "|| @within(com.fs.common.annotation.DataSource)")
+    public void dsPointCut()
+    {
+
+    }
+
+    @Around("dsPointCut()")
+    public Object around(ProceedingJoinPoint point) throws Throwable
+    {
+        DataSource dataSource = getDataSource(point);
+
+        if (StringUtils.isNotNull(dataSource))
+        {
+            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
+        }
+
+        try
+        {
+            return point.proceed();
+        }
+        finally
+        {
+            // 销毁数据源 在执行方法之后
+            DynamicDataSourceContextHolder.clearDataSourceType();
+        }
+    }
+
+    /**
+     * 获取需要切换的数据源
+     */
+    public DataSource getDataSource(ProceedingJoinPoint point)
+    {
+        MethodSignature signature = (MethodSignature) point.getSignature();
+        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
+        if (Objects.nonNull(dataSource))
+        {
+            return dataSource;
+        }
+
+        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
+    }
+}

+ 244 - 0
fs-company-app/src/main/java/com/fs/core/aspectj/LogAspect.java

@@ -0,0 +1,244 @@
+package com.fs.core.aspectj;
+
+import com.alibaba.fastjson.JSON;
+import com.fs.common.annotation.Log;
+import com.fs.common.core.domain.model.LoginUser;
+import com.fs.common.enums.BusinessStatus;
+import com.fs.common.enums.HttpMethod;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.core.manager.AsyncManager;
+import com.fs.core.manager.factory.AsyncFactory;
+import com.fs.system.domain.SysOperLog;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.AfterReturning;
+import org.aspectj.lang.annotation.AfterThrowing;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+import org.springframework.validation.BindingResult;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.servlet.HandlerMapping;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 操作日志记录处理
+ * 
+
+ */
+@Aspect
+@Component
+public class LogAspect
+{
+    private static final Logger log = LoggerFactory.getLogger(LogAspect.class);
+
+    // 配置织入点
+    @Pointcut("@annotation(com.fs.common.annotation.Log)")
+    public void logPointCut()
+    {
+    }
+
+    /**
+     * 处理完请求后执行
+     *
+     * @param joinPoint 切点
+     */
+    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
+    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult)
+    {
+        handleLog(joinPoint, null, jsonResult);
+    }
+
+    /**
+     * 拦截异常操作
+     * 
+     * @param joinPoint 切点
+     * @param e 异常
+     */
+    @AfterThrowing(value = "logPointCut()", throwing = "e")
+    public void doAfterThrowing(JoinPoint joinPoint, Exception e)
+    {
+        handleLog(joinPoint, e, null);
+    }
+
+    protected void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult)
+    {
+        try
+        {
+            // 获得注解
+            Log controllerLog = getAnnotationLog(joinPoint);
+            if (controllerLog == null)
+            {
+                return;
+            }
+
+            // 获取当前的用户
+            LoginUser loginUser = SecurityUtils.getLoginUser();
+
+            // *========数据库日志=========*//
+            SysOperLog operLog = new SysOperLog();
+            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
+            // 请求的地址
+            String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+            operLog.setOperIp(ip);
+            // 返回参数
+            operLog.setJsonResult(JSON.toJSONString(jsonResult));
+
+            operLog.setOperUrl(ServletUtils.getRequest().getRequestURI());
+            if (loginUser != null)
+            {
+                operLog.setOperName(loginUser.getUsername());
+            }
+
+            if (e != null)
+            {
+                operLog.setStatus(BusinessStatus.FAIL.ordinal());
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+            }
+            // 设置方法名称
+            String className = joinPoint.getTarget().getClass().getName();
+            String methodName = joinPoint.getSignature().getName();
+            operLog.setMethod(className + "." + methodName + "()");
+            // 设置请求方式
+            operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
+            // 处理设置注解上的参数
+            getControllerMethodDescription(joinPoint, controllerLog, operLog);
+            // 保存数据库
+            AsyncManager.me().execute(AsyncFactory.recordOper(operLog));
+        }
+        catch (Exception exp)
+        {
+            // 记录本地异常日志
+            log.error("==前置通知异常==");
+            log.error("异常信息:{}", exp.getMessage());
+            exp.printStackTrace();
+        }
+    }
+
+    /**
+     * 获取注解中对方法的描述信息 用于Controller层注解
+     * 
+     * @param log 日志
+     * @param operLog 操作日志
+     * @throws Exception
+     */
+    public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog) throws Exception
+    {
+        // 设置action动作
+        operLog.setBusinessType(log.businessType().ordinal());
+        // 设置标题
+        operLog.setTitle(log.title());
+        // 设置操作人类别
+        operLog.setOperatorType(log.operatorType().ordinal());
+        // 是否需要保存request,参数和值
+        if (log.isSaveRequestData())
+        {
+            // 获取参数的信息,传入到数据库中。
+            setRequestValue(joinPoint, operLog);
+        }
+    }
+
+    /**
+     * 获取请求的参数,放到log中
+     * 
+     * @param operLog 操作日志
+     * @throws Exception 异常
+     */
+    private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception
+    {
+        String requestMethod = operLog.getRequestMethod();
+        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
+        {
+            String params = argsArrayToString(joinPoint.getArgs());
+            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+        }
+        else
+        {
+            Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
+            operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private Log getAnnotationLog(JoinPoint joinPoint) throws Exception
+    {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null)
+        {
+            return method.getAnnotation(Log.class);
+        }
+        return null;
+    }
+
+    /**
+     * 参数拼装
+     */
+    private String argsArrayToString(Object[] paramsArray)
+    {
+        String params = "";
+        if (paramsArray != null && paramsArray.length > 0)
+        {
+            for (int i = 0; i < paramsArray.length; i++)
+            {
+                if (StringUtils.isNotNull(paramsArray[i]) && !isFilterObject(paramsArray[i]))
+                {
+                    Object jsonObj = JSON.toJSON(paramsArray[i]);
+                    params += jsonObj.toString() + " ";
+                }
+            }
+        }
+        return params.trim();
+    }
+
+    /**
+     * 判断是否需要过滤的对象。
+     * 
+     * @param o 对象信息。
+     * @return 如果是需要过滤的对象,则返回true;否则返回false。
+     */
+    @SuppressWarnings("rawtypes")
+    public boolean isFilterObject(final Object o)
+    {
+        Class<?> clazz = o.getClass();
+        if (clazz.isArray())
+        {
+            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
+        }
+        else if (Collection.class.isAssignableFrom(clazz))
+        {
+            Collection collection = (Collection) o;
+            for (Iterator iter = collection.iterator(); iter.hasNext();)
+            {
+                return iter.next() instanceof MultipartFile;
+            }
+        }
+        else if (Map.class.isAssignableFrom(clazz))
+        {
+            Map map = (Map) o;
+            for (Iterator iter = map.entrySet().iterator(); iter.hasNext();)
+            {
+                Map.Entry entry = (Map.Entry) iter.next();
+                return entry.getValue() instanceof MultipartFile;
+            }
+        }
+        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
+                || o instanceof BindingResult;
+    }
+}

+ 94 - 0
fs-company-app/src/main/java/com/fs/core/config/DataSourceConfig.java

@@ -0,0 +1,94 @@
+package com.fs.core.config;
+
+import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+import com.alibaba.druid.util.Utils;
+import com.fs.common.enums.DataSourceType;
+import com.fs.core.datasource.DynamicDataSource;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+
+import javax.servlet.*;
+import javax.servlet.FilterConfig;
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class DataSourceConfig {
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.sop.druid.master")
+    public DataSource sopDataSource() {
+        return new DruidDataSource();
+    }
+
+    @Bean
+    @ConfigurationProperties(prefix = "spring.datasource.mysql.druid.master")
+    public DataSource masterDataSource() {
+        return new DruidDataSource();
+    }
+
+
+
+    @Bean
+    @Primary
+    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource, @Qualifier("sopDataSource") DataSource sopDataSource) {
+        Map<Object, Object> targetDataSources = new HashMap<>();
+        targetDataSources.put(DataSourceType.MASTER, masterDataSource);
+        targetDataSources.put(DataSourceType.SOP.name(), sopDataSource);
+        return new DynamicDataSource(masterDataSource, targetDataSources);
+    }
+
+    /**
+     * 去除监控页面底部的广告
+     */
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Bean
+    @ConditionalOnProperty(name = "spring.datasource.mysql.druid.statViewServlet.enabled", havingValue = "true")
+    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
+    {
+        // 获取web监控页面的参数
+        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+        // 提取common.js的配置路径
+        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+        final String filePath = "support/http/resources/js/common.js";
+        // 创建filter进行过滤
+        Filter filter = new Filter()
+        {
+            @Override
+            public void init(FilterConfig filterConfig) throws ServletException
+            {
+            }
+            @Override
+            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+                    throws IOException, ServletException
+            {
+                chain.doFilter(request, response);
+                // 重置缓冲区,响应头不会被重置
+                response.resetBuffer();
+                // 获取common.js
+                String text = Utils.readFromResource(filePath);
+                // 正则替换banner, 除去底部的广告信息
+                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
+                text = text.replaceAll("powered.*?shrek.wang</a>", "");
+                response.getWriter().write(text);
+            }
+            @Override
+            public void destroy()
+            {
+            }
+        };
+        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
+        registrationBean.setFilter(filter);
+        registrationBean.addUrlPatterns(commonJsPattern);
+        return registrationBean;
+    }
+}

+ 123 - 123
fs-company-app/src/main/java/com/fs/core/config/DruidConfig.java

@@ -1,123 +1,123 @@
-package com.fs.core.config;
-
-import com.alibaba.druid.pool.DruidDataSource;
-import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
-import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
-import com.alibaba.druid.util.Utils;
-import com.fs.common.enums.DataSourceType;
-import com.fs.common.utils.spring.SpringUtils;
-import com.fs.core.config.properties.DruidProperties;
-import com.fs.core.datasource.DynamicDataSource;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.web.servlet.FilterRegistrationBean;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-
-import javax.servlet.*;
-import javax.sql.DataSource;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * druid 配置多数据源
- * 
-
- */
-@Configuration
-public class DruidConfig
-{
-    @Bean
-    @ConfigurationProperties("spring.datasource.mysql.druid.master")
-    public DataSource masterDataSource(DruidProperties druidProperties)
-    {
-        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
-        return druidProperties.dataSource(dataSource);
-    }
-
-    @Bean
-    @ConfigurationProperties("spring.datasource.mysql.druid.slave")
-    @ConditionalOnProperty(prefix = "spring.datasource.mysql.druid.slave", name = "enabled", havingValue = "true")
-    public DataSource slaveDataSource(DruidProperties druidProperties)
-    {
-        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
-        return druidProperties.dataSource(dataSource);
-    }
-
-    @Bean(name = "dynamicDataSource")
-    @Primary
-    public DynamicDataSource dataSource(DataSource masterDataSource)
-    {
-        Map<Object, Object> targetDataSources = new HashMap<>();
-        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
-        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
-        return new DynamicDataSource(masterDataSource, targetDataSources);
-    }
-    
-    /**
-     * 设置数据源
-     * 
-     * @param targetDataSources 备选数据源集合
-     * @param sourceName 数据源名称
-     * @param beanName bean名称
-     */
-    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
-    {
-        try
-        {
-            DataSource dataSource = SpringUtils.getBean(beanName);
-            targetDataSources.put(sourceName, dataSource);
-        }
-        catch (Exception e)
-        {
-        }
-    }
-
-    /**
-     * 去除监控页面底部的广告
-     */
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    @Bean
-    @ConditionalOnProperty(name = "spring.datasource.mysql.druid.statViewServlet.enabled", havingValue = "true")
-    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
-    {
-        // 获取web监控页面的参数
-        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
-        // 提取common.js的配置路径
-        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
-        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
-        final String filePath = "support/http/resources/js/common.js";
-        // 创建filter进行过滤
-        Filter filter = new Filter()
-        {
-            @Override
-            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
-            {
-            }
-            @Override
-            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
-                    throws IOException, ServletException
-            {
-                chain.doFilter(request, response);
-                // 重置缓冲区,响应头不会被重置
-                response.resetBuffer();
-                // 获取common.js
-                String text = Utils.readFromResource(filePath);
-                // 正则替换banner, 除去底部的广告信息
-                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
-                text = text.replaceAll("powered.*?shrek.wang</a>", "");
-                response.getWriter().write(text);
-            }
-            @Override
-            public void destroy()
-            {
-            }
-        };
-        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
-        registrationBean.setFilter(filter);
-        registrationBean.addUrlPatterns(commonJsPattern);
-        return registrationBean;
-    }
-}
+//package com.fs.core.config;
+//
+//import com.alibaba.druid.pool.DruidDataSource;
+//import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
+//import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
+//import com.alibaba.druid.util.Utils;
+//import com.fs.common.enums.DataSourceType;
+//import com.fs.common.utils.spring.SpringUtils;
+//import com.fs.core.config.properties.DruidProperties;
+//import com.fs.core.datasource.DynamicDataSource;
+//import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+//import org.springframework.boot.context.properties.ConfigurationProperties;
+//import org.springframework.boot.web.servlet.FilterRegistrationBean;
+//import org.springframework.context.annotation.Bean;
+//import org.springframework.context.annotation.Configuration;
+//import org.springframework.context.annotation.Primary;
+//
+//import javax.servlet.*;
+//import javax.sql.DataSource;
+//import java.io.IOException;
+//import java.util.HashMap;
+//import java.util.Map;
+//
+///**
+// * druid 配置多数据源
+// *
+//
+// */
+//@Configuration
+//public class DruidConfig
+//{
+//    @Bean
+//    @ConfigurationProperties("spring.datasource.mysql.druid.master")
+//    public DataSource masterDataSource(DruidProperties druidProperties)
+//    {
+//        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
+//        return druidProperties.dataSource(dataSource);
+//    }
+//
+//    @Bean
+//    @ConfigurationProperties("spring.datasource.mysql.druid.slave")
+//    @ConditionalOnProperty(prefix = "spring.datasource.mysql.druid.slave", name = "enabled", havingValue = "true")
+//    public DataSource slaveDataSource(DruidProperties druidProperties)
+//    {
+//        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
+//        return druidProperties.dataSource(dataSource);
+//    }
+//
+//    @Bean(name = "dynamicDataSource")
+//    @Primary
+//    public DynamicDataSource dataSource(DataSource masterDataSource)
+//    {
+//        Map<Object, Object> targetDataSources = new HashMap<>();
+//        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
+//        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
+//        return new DynamicDataSource(masterDataSource, targetDataSources);
+//    }
+//
+//    /**
+//     * 设置数据源
+//     *
+//     * @param targetDataSources 备选数据源集合
+//     * @param sourceName 数据源名称
+//     * @param beanName bean名称
+//     */
+//    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
+//    {
+//        try
+//        {
+//            DataSource dataSource = SpringUtils.getBean(beanName);
+//            targetDataSources.put(sourceName, dataSource);
+//        }
+//        catch (Exception e)
+//        {
+//        }
+//    }
+//
+//    /**
+//     * 去除监控页面底部的广告
+//     */
+//    @SuppressWarnings({ "rawtypes", "unchecked" })
+//    @Bean
+//    @ConditionalOnProperty(name = "spring.datasource.mysql.druid.statViewServlet.enabled", havingValue = "true")
+//    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
+//    {
+//        // 获取web监控页面的参数
+//        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
+//        // 提取common.js的配置路径
+//        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
+//        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
+//        final String filePath = "support/http/resources/js/common.js";
+//        // 创建filter进行过滤
+//        Filter filter = new Filter()
+//        {
+//            @Override
+//            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
+//            {
+//            }
+//            @Override
+//            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+//                    throws IOException, ServletException
+//            {
+//                chain.doFilter(request, response);
+//                // 重置缓冲区,响应头不会被重置
+//                response.resetBuffer();
+//                // 获取common.js
+//                String text = Utils.readFromResource(filePath);
+//                // 正则替换banner, 除去底部的广告信息
+//                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
+//                text = text.replaceAll("powered.*?shrek.wang</a>", "");
+//                response.getWriter().write(text);
+//            }
+//            @Override
+//            public void destroy()
+//            {
+//            }
+//        };
+//        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
+//        registrationBean.setFilter(filter);
+//        registrationBean.addUrlPatterns(commonJsPattern);
+//        return registrationBean;
+//    }
+//}

+ 3 - 3
fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSource.java

@@ -7,8 +7,8 @@ import java.util.Map;
 
 /**
  * 动态数据源
- * 
- 
+ *
+
  */
 public class DynamicDataSource extends AbstractRoutingDataSource
 {
@@ -24,4 +24,4 @@ public class DynamicDataSource extends AbstractRoutingDataSource
     {
         return DynamicDataSourceContextHolder.getDataSourceType();
     }
-}
+}

+ 4 - 5
fs-company-app/src/main/java/com/fs/core/datasource/DynamicDataSourceContextHolder.java

@@ -5,11 +5,10 @@ import org.slf4j.LoggerFactory;
 
 /**
  * 数据源切换处理
- * 
- 
+ *
+
  */
-public class DynamicDataSourceContextHolder
-{
+public class DynamicDataSourceContextHolder {
     public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
 
     /**
@@ -23,7 +22,7 @@ public class DynamicDataSourceContextHolder
      */
     public static void setDataSourceType(String dsType)
     {
-        log.info("切换到{}数据源", dsType);
+//        log.info("切换到{}数据源", dsType);
         CONTEXT_HOLDER.set(dsType);
     }
 

+ 56 - 0
fs-company-app/src/main/java/com/fs/core/manager/AsyncManager.java

@@ -0,0 +1,56 @@
+package com.fs.core.manager;
+
+import com.fs.common.utils.Threads;
+import com.fs.common.utils.spring.SpringUtils;
+
+import java.util.TimerTask;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 异步任务管理器
+ * 
+
+ */
+public class AsyncManager
+{
+    /**
+     * 操作延迟10毫秒
+     */
+    private final int OPERATE_DELAY_TIME = 10;
+
+    /**
+     * 异步操作任务调度线程池
+     */
+    private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
+
+    /**
+     * 单例模式
+     */
+    private AsyncManager(){}
+
+    private static AsyncManager me = new AsyncManager();
+
+    public static AsyncManager me()
+    {
+        return me;
+    }
+
+    /**
+     * 执行任务
+     * 
+     * @param task 任务
+     */
+    public void execute(TimerTask task)
+    {
+        executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * 停止任务线程池
+     */
+    public void shutdown()
+    {
+        Threads.shutdownAndAwaitTermination(executor);
+    }
+}

+ 40 - 0
fs-company-app/src/main/java/com/fs/core/manager/ShutdownManager.java

@@ -0,0 +1,40 @@
+package com.fs.core.manager;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PreDestroy;
+
+/**
+ * 确保应用退出时能关闭后台线程
+ *
+
+ */
+@Component
+public class ShutdownManager
+{
+    private static final Logger logger = LoggerFactory.getLogger("sys-user");
+
+    @PreDestroy
+    public void destroy()
+    {
+        shutdownAsyncManager();
+    }
+
+    /**
+     * 停止异步执行任务
+     */
+    private void shutdownAsyncManager()
+    {
+        try
+        {
+            logger.info("====关闭后台任务任务线程池====");
+            AsyncManager.me().shutdown();
+        }
+        catch (Exception e)
+        {
+            logger.error(e.getMessage(), e);
+        }
+    }
+}

+ 103 - 0
fs-company-app/src/main/java/com/fs/core/manager/factory/AsyncFactory.java

@@ -0,0 +1,103 @@
+package com.fs.core.manager.factory;
+
+import com.fs.common.constant.Constants;
+import com.fs.common.utils.LogUtils;
+import com.fs.common.utils.ServletUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.common.utils.ip.AddressUtils;
+import com.fs.common.utils.ip.IpUtils;
+import com.fs.common.utils.spring.SpringUtils;
+import com.fs.system.domain.SysLogininfor;
+import com.fs.system.domain.SysOperLog;
+import com.fs.system.service.ISysLogininforService;
+import com.fs.system.service.ISysOperLogService;
+import eu.bitwalker.useragentutils.UserAgent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.TimerTask;
+
+/**
+ * 异步工厂(产生任务用)
+ * 
+
+ */
+public class AsyncFactory
+{
+    private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");
+
+    /**
+     * 记录登录信息
+     * 
+     * @param username 用户名
+     * @param status 状态
+     * @param message 消息
+     * @param args 列表
+     * @return 任务task
+     */
+    public static TimerTask recordLogininfor(final String username, final String status, final String message,
+            final Object... args)
+    {
+        final UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
+        final String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                String address = AddressUtils.getRealAddressByIP(ip);
+                StringBuilder s = new StringBuilder();
+                s.append(LogUtils.getBlock(ip));
+                s.append(address);
+                s.append(LogUtils.getBlock(username));
+                s.append(LogUtils.getBlock(status));
+                s.append(LogUtils.getBlock(message));
+                // 打印信息到日志
+                sys_user_logger.info(s.toString(), args);
+                // 获取客户端操作系统
+                String os = userAgent.getOperatingSystem().getName();
+                // 获取客户端浏览器
+                String browser = userAgent.getBrowser().getName();
+                // 封装对象
+                SysLogininfor logininfor = new SysLogininfor();
+                logininfor.setUserName(username);
+                logininfor.setIpaddr(ip);
+                logininfor.setLoginLocation(address);
+                logininfor.setBrowser(browser);
+                logininfor.setOs(os);
+                logininfor.setMsg(message);
+                // 日志状态
+                if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
+                {
+                    logininfor.setStatus(Constants.SUCCESS);
+                }
+                else if (Constants.LOGIN_FAIL.equals(status))
+                {
+                    logininfor.setStatus(Constants.FAIL);
+                }
+                // 插入数据
+                SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor);
+            }
+        };
+    }
+
+    /**
+     * 操作日志记录
+     * 
+     * @param operLog 操作日志信息
+     * @return 任务task
+     */
+    public static TimerTask recordOper(final SysOperLog operLog)
+    {
+        return new TimerTask()
+        {
+            @Override
+            public void run()
+            {
+                // 远程查询操作地点
+                operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp()));
+                SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog);
+            }
+        };
+    }
+}

+ 2 - 2
fs-company-app/src/main/resources/application.yml

@@ -5,5 +5,5 @@ server:
 # Spring配置
 spring:
   profiles:
-    active: druid-hyt-test
-#    active: dev
+#    active: druid-fcky-test
+    active: dev-jnlzjk

+ 3 - 1
fs-company/src/main/java/com/fs/company/controller/qw/QwUserController.java

@@ -459,8 +459,10 @@ public class QwUserController extends BaseController
         startPage();
 
         LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
-        qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
 
+        if(ObjectUtil.notEqual(qwUser.getDisableCompanyId(),1)){
+            qwUser.setCompanyId(loginUser.getCompany().getCompanyId());
+        }
         List<QwUser> list = qwUserService.selectQwUserList(qwUser);
         return getDataTable(list);
     }

+ 43 - 2
fs-qwhook-sop/src/main/java/com/fs/app/controller/ApisQwSopController.java

@@ -2,12 +2,17 @@ package com.fs.app.controller;
 
 import com.fs.app.params.SopLogsEditParam;
 import com.fs.common.BeanCopyUtils;
+import com.fs.common.core.controller.BaseController;
 import com.fs.common.core.domain.R;
+import com.fs.common.core.domain.ResponseResult;
+import com.fs.common.core.page.TableDataInfo;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.utils.StringUtils;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.fastGpt.param.FastGptChatSessionParam;
 import com.fs.fastGpt.service.IFastGptChatSessionService;
 import com.fs.qw.domain.QwTagGroup;
-import com.fs.qw.param.SopMsgParam;
+import com.fs.qw.param.*;
 import com.fs.qw.param.sidebar.ExternalContactInfoParam;
 import com.fs.qw.param.sidebar.TagGroupListParam;
 import com.fs.qw.param.sidebar.TagGroupUpdateParam;
@@ -21,6 +26,9 @@ import com.fs.sop.domain.QwSopLogs;
 import com.fs.sop.params.GetQwSopLogsByJsApiParam;
 import com.fs.sop.params.SendSopParamDetailsC;
 import com.fs.sop.service.IQwSopLogsService;
+import com.fs.sop.service.IQwSopService;
+import com.fs.store.vo.h5.ExternalUserStatsVO;
+import com.fs.store.vo.h5.FsUserStatisticsVO;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.ApiOperation;
@@ -28,13 +36,14 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
 import java.text.SimpleDateFormat;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
 
 @RestController
 @RequestMapping("/apis/app/qwSop")
-public class ApisQwSopController {
+public class ApisQwSopController  extends BaseController {
 
     @Autowired
     RedisCache redisCache;
@@ -49,6 +58,8 @@ public class ApisQwSopController {
     @Autowired
     private IQwTagGroupService qwTagGroupService;
 
+    @Autowired
+    private IQwSopService qwSopService;
     /**
      * 更新AI发送状态
      */
@@ -162,4 +173,34 @@ public class ApisQwSopController {
         return R.error();
     }
 
+    //员工看板 课程/答题/红包统计--侧边栏
+    @GetMapping("/boardCourseQuizRedEnvelopeStats")
+    @ApiOperation("员工看板 课程/答题/红包统计")
+    public ResponseResult<FsUserStatisticsVO> boardCourseQuizRedEnvelopeStats(CourseQuizRedEnvelopeStatsParam qwParam) {
+        if (StringUtils.isBlank(qwParam.getStartTime()) || StringUtils.isBlank(qwParam.getEndTime())) {
+            return ResponseResult.ok(new FsUserStatisticsVO());
+        }
+        FsUserStatisticsVO resultVo = qwSopService.boardCourseQuizRedEnvelopeStats(qwParam);
+        return ResponseResult.ok(resultVo);
+    }
+
+    //外部联系人答题/红包/看课统计--侧边栏
+    @GetMapping("/externalStatsList")
+    @ApiOperation("外部联系人答题/红包/看课统计")
+    public ResponseResult<ExternalUserStatsVO> externalStatsList(QwSidebarStatsParam qwParam) {
+        ExternalUserStatsVO vo = qwSopService.externalStatsList(qwParam);
+        return ResponseResult.ok(vo);
+    }
+
+    //外部联系人看课轨迹--侧边栏
+    @GetMapping("/externalWatchRecordStatsList")
+    @ApiOperation("用户看课轨迹")
+    public TableDataInfo externalWatchRecordStatsList(QwSidebarStatsParam qwParam) {
+        if (qwParam.getStartTime() == null || qwParam.getEndTime() == null) {
+            return getDataTable(Collections.emptyList());
+        }
+        List<FsCourseWatchLogStatisticsListVO> list = qwSopService.externalWatchRecordStatsList(qwParam);
+        return getDataTable(list);
+    }
+
 }

+ 6 - 1
fs-service/src/main/java/com/fs/course/mapper/FsCourseWatchLogMapper.java

@@ -7,12 +7,12 @@ import com.fs.course.param.*;
 import com.fs.course.vo.*;
 import com.fs.im.dto.OpenImBatchResponseDataDTO;
 import com.fs.qw.domain.QwExternalContact;
+import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.sop.vo.QwRatingVO;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 
-import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
@@ -534,4 +534,9 @@ public interface FsCourseWatchLogMapper extends BaseMapper<FsCourseWatchLog> {
     Integer getUserCountByCampId(@Param("trainingCampId") Long trainingCampId);
 
     List<FsCourseWatchLogStatisticsListByCompanyVO> selectFsCourseWatchLogStatisticsListByCompanyVO(FsCourseWatchLogStatisticsListParam param);
+
+    /**
+     * 看课统计
+     * */
+    List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param);
 }

+ 2 - 2
fs-service/src/main/java/com/fs/course/param/FsCourseWatchLogListParam.java

@@ -34,10 +34,10 @@ public class FsCourseWatchLogListParam implements Serializable {
     private Integer sendType;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Date eTime;
+    private String eTime;
 
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private Date sTime;
+    private String sTime;
 
     @JsonFormat(pattern = "yyyy-MM-dd")
     private String upSTime;

+ 6 - 3
fs-service/src/main/java/com/fs/course/service/IFsCourseWatchLogService.java

@@ -4,11 +4,9 @@ import com.baomidou.mybatisplus.extension.service.IService;
 import com.fs.course.domain.FsCourseWatchLog;
 import com.fs.course.param.*;
 import com.fs.course.vo.*;
-import com.fs.qw.param.QwWatchLogStatisticsListParam;
-import com.fs.qw.vo.QwWatchLogStatisticsListVO;
+import com.fs.qw.param.QwSidebarStatsParam;
 
 import java.time.LocalDateTime;
-import java.time.LocalTime;
 import java.util.List;
 import java.util.Map;
 
@@ -135,4 +133,9 @@ public interface IFsCourseWatchLogService extends IService<FsCourseWatchLog> {
     void scheduleBatchUpdateToDatabaseIsOpen();
 
     List<FsCourseWatchLogListVO> selectFsCourseWatchLogListVOexport(FsCourseWatchLogListParam param);
+
+    /**
+     * 看课统计
+     * */
+    List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param);
 }

+ 6 - 5
fs-service/src/main/java/com/fs/course/service/impl/FsCourseWatchLogServiceImpl.java

@@ -29,25 +29,21 @@ import com.fs.his.config.FsSysConfig;
 import com.fs.his.domain.FsUser;
 import com.fs.his.service.IFsUserService;
 import com.fs.his.utils.ConfigUtil;
-import com.fs.his.vo.OptionsVO;
 import com.fs.qw.Bean.MsgBean;
 import com.fs.qw.cache.IQwExternalContactCacheService;
 import com.fs.qw.cache.IQwUserCacheService;
 import com.fs.qw.domain.QwExternalContact;
-import com.fs.qw.domain.QwExternalContactInfo;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.domain.QwWatchLog;
 import com.fs.qw.mapper.QwExternalContactMapper;
 import com.fs.qw.mapper.QwUserMapper;
 import com.fs.qw.mapper.QwWatchLogMapper;
+import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.qw.param.SendSopParamDetails;
-import com.fs.qw.param.SopUserLogsVO;
 import com.fs.qw.vo.QwSopCourseFinishTempSetting;
 import com.fs.qw.vo.QwSopTempSetting;
 import com.fs.sop.domain.QwSopLogs;
-import com.fs.sop.mapper.QwSopLogsMapper;
 import com.fs.sop.mapper.SopUserLogsMapper;
-import com.fs.sop.service.IQwSopLogsService;
 import com.fs.store.service.cache.IFsUserCacheService;
 import com.fs.store.service.cache.IFsUserCourseCacheService;
 import com.fs.system.service.ISysConfigService;
@@ -1240,4 +1236,9 @@ public class FsCourseWatchLogServiceImpl extends ServiceImpl<FsCourseWatchLogMap
         return list;
     }
 
+    @Override
+    public List<FsCourseWatchLogStatisticsListVO> selectQwFsCourseWatchLogStatisticsListVO(QwSidebarStatsParam param) {
+        return fsCourseWatchLogMapper.selectQwFsCourseWatchLogStatisticsListVO(param);
+    }
+
 }

+ 3 - 0
fs-service/src/main/java/com/fs/course/vo/FsCourseWatchLogListVO.java

@@ -63,6 +63,9 @@ public class FsCourseWatchLogListVO extends BaseEntity
     @Excel(name = "记录类型" ,dictType = "sys_course_watch_log_type")
     private Integer logType;
 
+    @Excel(name = "奖励类型 1红包 2积分")
+    private Integer rewardType;
+
 //    @Excel(name = "企微外部联系人id")
     private String qwExternalContactId;
 

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

@@ -974,6 +974,7 @@ public class AiHookServiceImpl implements AiHookService {
         wxwUploadCdnLinkFileDTO.setFilename(data.getUrl());
         wxwUploadCdnLinkFileDTO.setUuid(uuid);
         WxWorkResponseDTO<WxwUploadCdnLinkFileRespDTO> dto = wxWorkService.uploadCdnLinkFile(wxwUploadCdnLinkFileDTO, serverId);
+        log.info("语音数据:{}", JSON.toJSONString(dto));
         WxwUploadCdnLinkFileRespDTO voice = dto.getData();
         WxwSendCDNVoiceMsgDTO wxwSendCDNVoiceMsgDTO = new WxwSendCDNVoiceMsgDTO();
         wxwSendCDNVoiceMsgDTO.setSend_userid(sendId);

+ 14 - 0
fs-service/src/main/java/com/fs/his/mapper/FsUserMapper.java

@@ -410,4 +410,18 @@ public interface FsUserMapper
     Map<String, Object> countUserStats(
             UserStatisticsCommonParam param);
 
+    /**
+     * 统计用户领取红包数据(红包个数、红包金额)
+     * */
+    ExternalRedPacketStatsVO countExternalRedPacketStats(UserStatisticsCommonParam param);
+
+    /**
+     * 统计用户答题数据(答题次数、正确次数)
+     * */
+    ExternalAnswerStatsVO countExternalAnswerStats(UserStatisticsCommonParam param);
+
+    /**
+     * 统计用户看课数据(课程观看次数、课程完播次数)
+     * */
+    ExternalWatchStatsVO countExternalWatchStats(UserStatisticsCommonParam param);
 }

+ 2 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsPrescribeScrm.java

@@ -2,6 +2,7 @@ package com.fs.hisStore.domain;
 
 import java.util.Date;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fs.common.annotation.Excel;
@@ -31,6 +32,7 @@ public class FsPrescribeScrm extends BaseEntity
 
     /** 订单ID */
     @Excel(name = "订单ID")
+    @TableField (exist = false)
     private Long orderId;
 
     /** 用户ID */

+ 10 - 0
fs-service/src/main/java/com/fs/hisStore/domain/FsUserScrm.java

@@ -135,6 +135,9 @@ public class FsUserScrm extends BaseEntity
     @Excel(name = "连续签到天数")
     private Long signNum;
 
+    /** 订单数 */
+    private Integer orderCount;
+
     private Integer integralStatus;
 
     private Integer isBuy;
@@ -806,4 +809,11 @@ public class FsUserScrm extends BaseEntity
         return this;
     }
 
+    public Integer getOrderCount() {
+        return orderCount;
+    }
+
+    public void setOrderCount(Integer orderCount) {
+        this.orderCount = orderCount;
+    }
 }

+ 4 - 0
fs-service/src/main/java/com/fs/qw/domain/QwUser.java

@@ -1,6 +1,7 @@
 package com.fs.qw.domain;
 
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fs.common.annotation.Excel;
 import com.fs.common.core.domain.BaseEntity;
 import lombok.Data;
@@ -109,4 +110,7 @@ public class QwUser extends BaseEntity
      */
     private String isAuto;
     private Integer videoGetStatus;
+
+    @TableField(exist = false)
+    private Integer disableCompanyId;
 }

+ 6 - 0
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactMapper.java

@@ -14,6 +14,7 @@ import com.fs.qw.result.QwExternalContactVo;
 import com.fs.qw.vo.*;
 import com.fs.qw.vo.newvo.ExternalContactListVO;
 import com.fs.qw.vo.newvo.ExternalContactNumVO;
+import com.fs.qw.vo.sidebar.ExternalContactQwUserVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
@@ -523,4 +524,9 @@ public interface QwExternalContactMapper extends BaseMapper<QwExternalContact> {
     void updateQwExternalContactStatusById(QwExternalContact qwExternalContact);
 
     List<QwExternalContactVO> selectQwExternalContactListVONewSys(QwExternalContactParam qwExternalContact);
+
+    /**
+     * 根据qw_user_id+crop_id+external_user_id查询外部联系人信息
+     * */
+    QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam);
 }

+ 2 - 1
fs-service/src/main/java/com/fs/qw/mapper/QwExternalContactTransferLogMapper.java

@@ -96,10 +96,11 @@ public interface QwExternalContactTransferLogMapper extends BaseMapper<QwExterna
             "LEFT JOIN company_user cu on cu.user_id=qu.company_user_id " +
             "<where>  \n" +
             "            <if test=\"corpId != null  and corpId != ''\"> and l.corp_id = #{corpId}</if>\n" +
+            "            <if test=\"name != null  and name != ''\"> and c.name like concat( #{name}, '%')</if>\n" +
             "            <if test=\"companyId != null \"> and l.company_id = #{companyId}</if>\n" +
             "            <if test=\"companyUserId != null \"> and l.company_user_id = #{companyUserId}</if>\n" +
             "            <if test=\"externalUserId != null  and externalUserId != ''\"> and l.external_user_id = #{externalUserId}</if>\n" +
-            "            <if test=\"companyUserNickName != null  and companyUserNickName != ''\"> and qu.qw_user_name = #{companyUserNickName}</if>\n" +
+            "            <if test=\"companyUserNickName != null  and companyUserNickName != ''\"> and qu.qw_user_name like concat( #{companyUserNickName}, '%')</if>\n" +
             "            <if test=\"customerId != null \"> and l.customer_id = #{customerId}</if>\n" +
             "            <if test=\"externalContactId != null \"> and l.external_contact_id = #{externalContactId}</if>\n" +
             "            <if test=\"status != null  and status != ''\"> and l.status = #{status}</if>\n" +

+ 26 - 0
fs-service/src/main/java/com/fs/qw/param/CourseQuizRedEnvelopeStatsParam.java

@@ -0,0 +1,26 @@
+package com.fs.qw.param;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+
+@Data
+public class CourseQuizRedEnvelopeStatsParam  implements Serializable {
+    @ApiModelProperty(value = "企业id", required = true)
+    @NotBlank(message = "corpId 不能为空")
+    private String corpId;
+
+    @ApiModelProperty(value = "用户id", required = true)
+    @NotBlank(message = "qwUserId 不能为空")
+    private String qwUserId;
+
+    @ApiModelProperty(value = "开始日期", required = true)
+    @NotBlank(message = "startTime 不能为空")
+    private String startTime;
+
+    @ApiModelProperty(value = "截止日期", required = true)
+    @NotBlank(message = "endTime 不能为空")
+    private String endTime;
+}

+ 17 - 0
fs-service/src/main/java/com/fs/qw/param/ExternalContactParam.java

@@ -0,0 +1,17 @@
+package com.fs.qw.param;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+@Data
+public class ExternalContactParam  implements Serializable {
+    //企业id
+    private String corpId;
+    //登录用户id
+    private String userId;
+
+    //外部联系人id
+    private String externalUserId;
+
+}

+ 37 - 0
fs-service/src/main/java/com/fs/qw/param/QwSidebarStatsParam.java

@@ -0,0 +1,37 @@
+package com.fs.qw.param;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+@Data
+@ApiModel
+public class QwSidebarStatsParam implements Serializable {
+    @NotBlank(message = "corpId 不能为空")
+    @ApiModelProperty(value = "企业id", required = true)
+    private String corpId;
+
+    @ApiModelProperty(value = "用户id", required = true)
+    @NotBlank(message = "qwUserId 不能为空")
+    private String qwUserId;
+
+    @ApiModelProperty(value = "外部联系人id", required = true)
+    @NotBlank(message = "externalUserId 不能为空")
+    private String externalUserId;
+
+    @ApiModelProperty(value = "页码,默认为1", required = false)
+    private Integer pageNum = 1;
+
+    @ApiModelProperty(value = "页大小,默认为10", required = false)
+    private Integer pageSize = 10;
+
+    private String startTime;
+
+    private String endTime;
+
+    //外部联系人主键Id
+    private Long qwExternalContactId;
+
+}

+ 0 - 1
fs-service/src/main/java/com/fs/qw/param/SopMsgParam.java

@@ -17,7 +17,6 @@ public class SopMsgParam {
     private String qwUserid;
 
     /** 企微外部联系人id */
-
     @NotBlank(message = "externalUserId 不能为空")
     private String externalUserId;
 

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

@@ -16,8 +16,10 @@ import com.fs.qw.vo.*;
 import com.fs.qw.vo.newvo.ExternalContactListVO;
 import com.fs.qw.vo.newvo.ExternalContactPageVO;
 import com.fs.qw.vo.sidebar.ExternalContactInfoVO;
+import com.fs.qw.vo.sidebar.ExternalContactQwUserVO;
 import com.fs.qw.vo.sidebar.ExternalContactTagVO;
 import com.fs.qwApi.param.QwExternalContactHParam;
+import org.apache.ibatis.annotations.Param;
 import org.codehaus.jettison.json.JSONException;
 
 import java.io.IOException;
@@ -251,4 +253,8 @@ public interface IQwExternalContactService extends IService<QwExternalContact> {
 
     R getRepeat(RepeatParam param);
     List<QwExternalContactVO> selectQwExternalContactListVONewSys(QwExternalContactParam qwExternalContact);
+    /**
+     * 根据qw_user_id+crop_id+external_user_id查询外部联系人信息
+     * */
+    QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam);
 }

+ 5 - 0
fs-service/src/main/java/com/fs/qw/service/impl/QwExternalContactServiceImpl.java

@@ -5899,6 +5899,11 @@ public class QwExternalContactServiceImpl extends ServiceImpl<QwExternalContactM
         return qwExternalContactMapper.selectQwExternalContactListVONewSys(qwExternalContact);
     }
 
+    @Override
+    public QwExternalContact selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(ExternalContactParam externalContactParam) {
+        return qwExternalContactMapper.selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(externalContactParam);
+    }
+
 
     public Boolean getSopAiChatByRedis(String qwUserId,String corpId,String externalUserId){
 

+ 14 - 0
fs-service/src/main/java/com/fs/sop/service/IQwSopService.java

@@ -1,13 +1,18 @@
 package com.fs.sop.service;
 
 import com.fs.common.core.domain.R;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
 import com.fs.qw.domain.QwSopUpdateStatus;
+import com.fs.qw.param.CourseQuizRedEnvelopeStatsParam;
+import com.fs.qw.param.QwSidebarStatsParam;
 import com.fs.sop.domain.QwSop;
 import com.fs.sop.params.GetSOPTaskDataParam;
 import com.fs.sop.params.QwSopAutoTime;
 import com.fs.sop.params.QwSopEditQwUserParam;
 import com.fs.sop.vo.QwSopTask;
 import com.fs.sop.vo.SopVoiceListVo;
+import com.fs.store.vo.h5.ExternalUserStatsVO;
+import com.fs.store.vo.h5.FsUserStatisticsVO;
 
 import java.io.IOException;
 import java.util.List;
@@ -100,4 +105,13 @@ public interface IQwSopService
     List<QwSop> selectAllQwSopInfo(QwSop qwSop);
 
     int deleteQwSopLogsBySopIds(String[] ids);
+
+    //员工看板 课程/答题/红包统计--侧边栏
+    FsUserStatisticsVO boardCourseQuizRedEnvelopeStats(CourseQuizRedEnvelopeStatsParam qwParam);
+
+    //红包/看课统计--侧边栏
+    ExternalUserStatsVO externalStatsList(QwSidebarStatsParam qwParam);
+
+    //看课足迹--侧边栏
+    List<FsCourseWatchLogStatisticsListVO> externalWatchRecordStatsList (QwSidebarStatsParam qwParam);
 }

+ 125 - 3
fs-service/src/main/java/com/fs/sop/service/impl/QwSopServiceImpl.java

@@ -13,13 +13,18 @@ import com.fs.company.mapper.CompanyUserMapper;
 import com.fs.company.vo.CompanyQwUserByIdsVo;
 import com.fs.course.mapper.FsCourseWatchLogMapper;
 import com.fs.course.service.IFsCourseLinkService;
+import com.fs.course.service.IFsCourseWatchLogService;
+import com.fs.course.vo.FsCourseWatchLogStatisticsListVO;
+import com.fs.his.mapper.FsUserMapper;
+import com.fs.his.service.IFsUserService;
+import com.fs.qw.domain.QwExternalContact;
 import com.fs.qw.domain.QwSopUpdateStatus;
 import com.fs.qw.domain.QwUser;
 import com.fs.qw.mapper.QwUserMapper;
-import com.fs.qw.param.QwAutoSopTimeParam;
+import com.fs.qw.param.*;
 import com.fs.qw.result.QwFilterSopCustomersResult;
+import com.fs.qw.service.IQwExternalContactService;
 import com.fs.qw.service.impl.AsyncChatSopService;
-import com.fs.qw.service.impl.AsyncSopService;
 import com.fs.qw.service.impl.AsyncSopTestService;
 import com.fs.qw.service.impl.AsyncWxSopService;
 import com.fs.qw.vo.QwSopRuleTimeVO;
@@ -32,9 +37,12 @@ import com.fs.sop.service.*;
 import com.fs.sop.vo.QwSopTask;
 import com.fs.sop.vo.SopVoiceListVo;
 import com.fs.sop.vo.VoiceVo;
+import com.fs.store.param.h5.UserStatisticsCommonParam;
+import com.fs.store.vo.h5.*;
 import com.fs.voice.utils.StringUtil;
 import com.fs.wxUser.mapper.CompanyWxUserMapper;
 import com.fs.wxUser.param.CompanyWxUserSopParam;
+import com.github.pagehelper.PageHelper;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.rocketmq.spring.core.RocketMQTemplate;
 import org.slf4j.Logger;
@@ -122,6 +130,17 @@ public class QwSopServiceImpl implements IQwSopService
     @Autowired
     private RocketMQTemplate rocketMQTemplate;
 
+    @Autowired
+    private IQwExternalContactService qwExternalContactService;
+
+    @Autowired
+    private IFsUserService fsUserService;
+
+    @Autowired
+    private IFsCourseWatchLogService fsCourseWatchLogService;
+
+    @Autowired
+    private FsUserMapper fsUserMapper;
     /**
      * 查询企微sop
      *
@@ -1100,7 +1119,6 @@ public class QwSopServiceImpl implements IQwSopService
         }
         return qwSopLogsMapper.delete(new QueryWrapper<QwSopLogs>().in("sop_id", Arrays.asList(ids)));
     }
-
     /**
      *新增员工执行SOP
      */
@@ -1253,4 +1271,108 @@ public class QwSopServiceImpl implements IQwSopService
     }
 
 
+    @Override
+    public FsUserStatisticsVO boardCourseQuizRedEnvelopeStats(CourseQuizRedEnvelopeStatsParam qwParam) {
+        String startTime = qwParam.getStartTime();
+        String endTime = qwParam.getEndTime();
+        //1:获取当前登录企微用户信息
+        QwUser qwUser = qwExternalContactService.getQwUserByRedis(qwParam.getCorpId().trim(),qwParam.getQwUserId().trim());
+        if (qwUser==null||qwUser.getCompanyUserId()==null){
+            return new FsUserStatisticsVO();
+        }
+        Long userId = qwUser.getCompanyUserId();
+        UserStatisticsCommonParam param = new UserStatisticsCommonParam();
+        param.setUserId(userId).setStartTime(startTime).setEndTime(endTime);
+        //2:获取当前用户统计数据
+        FsUserStatisticsVO vo = fsUserService.userStatistics(param);
+        DateTimeFormatter dfm = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        LocalDate today = LocalDate.now();
+        LocalDateTime startDateTime = LocalDateTime.parse(startTime, dfm);
+        LocalDateTime endDateTime = LocalDateTime.parse(endTime, dfm);
+        if (startDateTime.toLocalDate().equals(today) && endDateTime.toLocalDate().equals(today)) {
+            String yesterday = LocalDate.now().minusDays(1).toString();
+            UserStatisticsCommonParam paramYes = new UserStatisticsCommonParam();
+            paramYes.setUserId(userId).setStartTime(yesterday + " 00:00:00").setEndTime(yesterday + " 23:59:59");
+            FsUserStatisticsVO fsUserStatisticsVO = fsUserService.userStatistics(paramYes);
+            vo.setYesterdayVO(fsUserStatisticsVO);
+        } else {
+            vo.setYesterdayVO(new FsUserStatisticsVO());
+        }
+        return vo;
+    }
+
+
+    @Override
+    public ExternalUserStatsVO externalStatsList(QwSidebarStatsParam qwParam) {
+        ExternalUserStatsVO resultVo=new ExternalUserStatsVO();
+        //外部联系人入参对象
+        QwExternalContact externalContact = getExternalContact(
+                qwParam.getQwUserId(),
+                qwParam.getCorpId(),
+                qwParam.getExternalUserId()
+        );
+        if (externalContact == null) return new ExternalUserStatsVO();
+        Long fsUserId = externalContact.getFsUserId();
+        if (fsUserId == null || fsUserId <= 0) {
+            log.info("外部联系人没有绑定fs用户:{}",externalContact.getExternalUserId());
+            return resultVo;
+        }
+        Long companyUserId = externalContact.getCompanyUserId();
+        if (companyUserId == null || companyUserId <= 0){
+            log.info("外部联系人没有绑定销售人员:{}",externalContact.getExternalUserId());
+            return resultVo;
+        }
+        UserStatisticsCommonParam queryParam = new UserStatisticsCommonParam();
+        queryParam.setUserId(fsUserId).setCompanyUserId(String.valueOf(companyUserId)).setStartTime(qwParam.getStartTime()).setEndTime(qwParam.getEndTime());
+        //获取用户领取红包数据(红包个数、红包金额)
+        ExternalRedPacketStatsVO redPacketStatsVo = fsUserMapper.countExternalRedPacketStats(queryParam);
+        resultVo.setRedPacketNum(redPacketStatsVo.getRedPacketNum());
+        resultVo.setRedPacketAmount(redPacketStatsVo.getRedPacketAmount());
+        //获取用户答题数据(答题次数、正确次数)
+        ExternalAnswerStatsVO externalAnswerStatsVO = fsUserMapper.countExternalAnswerStats(queryParam);
+        resultVo.setAnswerNum(externalAnswerStatsVO.getAnswerNum());
+        resultVo.setAnswerRightNum(externalAnswerStatsVO.getAnswerRightNum());
+        //获取用户看课数据(观看次数、完播次数)
+        ExternalWatchStatsVO externalWatchStatsVO = fsUserMapper.countExternalWatchStats(queryParam);
+        resultVo.setCourseWatchNum(externalWatchStatsVO.getCourseWatchNum());
+        resultVo.setCourseCompleteNum(externalWatchStatsVO.getCourseCompleteNum());
+        return resultVo;
+    }
+
+    @Override
+    public List<FsCourseWatchLogStatisticsListVO> externalWatchRecordStatsList(QwSidebarStatsParam qwParam) {
+        QwExternalContact externalContact = getExternalContact(
+                qwParam.getQwUserId(),
+                qwParam.getCorpId(),
+                qwParam.getExternalUserId()
+        );
+        if (externalContact == null) return Collections.emptyList();
+        //根据外部联系人主键id获取用户的课程观看记录
+        qwParam.setQwExternalContactId(externalContact.getId());
+        PageHelper.startPage(qwParam.getPageNum(), qwParam.getPageSize());
+        List<FsCourseWatchLogStatisticsListVO> list = fsCourseWatchLogService.selectQwFsCourseWatchLogStatisticsListVO(qwParam);
+        if (CollectionUtils.isEmpty(list)){
+            return Collections.emptyList();
+        }
+        return list;
+    }
+
+    /**
+     *  获取外部联系人信息
+     * */
+    private QwExternalContact getExternalContact(String qwUserId, String corpId, String externalUserId) {
+        ExternalContactParam param = new ExternalContactParam();
+        param.setUserId(qwUserId);
+        param.setCorpId(corpId);
+        param.setExternalUserId(externalUserId);
+
+        QwExternalContact contact = qwExternalContactService.selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId(param);
+
+        if (contact == null) {
+            log.info("未查询到关联的外部联系人信息: userId={}, corpId={}, externalUserId={}",
+                    qwUserId, corpId, externalUserId);
+        }
+        return contact;
+    }
+
 }

+ 57 - 22
fs-service/src/main/java/com/fs/sop/service/impl/SopUserLogsInfoServiceImpl.java

@@ -482,6 +482,15 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
             return R.error().put("msg","企业编号为空,不能创建一键群发");
         }
 
+
+        // 查询公司关联小程序数据
+        List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
+
+        Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
+        QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
+        if (qwCompany == null ) {
+            return  R.error().put("msg","企业不存在,请联系管理员");
+        }
         List<QwSopLogs> sopLogsList;
         if(param.getFilterMode() != null && param.getFilterMode() == 2 && param.getChatIds() != null && param.getChatIds().length > 0){
             List<QwGroupChat> groupList = qwGroupChatMapper.selectQwGroupChatByChatIds(param.getChatIds());
@@ -616,11 +625,31 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
                                         qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config, qwGroupChat.getChatId());
 
-                                if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())) {
-                                    log.error("配置中无小程序id,采用默认的");
-                                    st.setMiniprogramAppid("wxc84c6f789ba7f176");
+                                String miniAppId = null;
+
+                                int listIndex = 1;
+                                if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                    Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(qwUser.getCompanyId());
+                                    if (integerListMap != null) {
+                                        List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                                        if (miniapps != null && !miniapps.isEmpty()) {
+                                            CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                            if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                                miniAppId = companyMiniapp.getAppId();
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                    miniAppId = qwCompany.getMiniAppId();
+                                }
+                                st.setMiniType(listIndex);
+                                if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                    st.setMiniprogramAppid(miniAppId);
                                 } else {
-                                    st.setMiniprogramAppid(config.getMiniprogramAppid());
+                                    log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                                 }
 
                                 st.setMiniprogramPage(linkByMiniApp);
@@ -724,11 +753,31 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                                 String linkByMiniApp = createLinkByMiniApp(st, param.getCorpId(), new Date(), param.getCourseId(), param.getVideoId(),
                                         qwUser.getId(), qwUser.getCompanyUserId().toString(), qwUser.getCompanyId().toString(), null, config, groupChat.getChatId());
 
-                                if (StringUtil.strIsNullOrEmpty(config.getMiniprogramAppid())) {
-                                    log.error("配置中无小程序id,采用默认的");
-                                    st.setMiniprogramAppid("wxc84c6f789ba7f176");
+                                String miniAppId = null;
+
+                                int listIndex = 1;
+                                if (!miniMap.isEmpty() && qwUser.getSendMsgType() == 1) {
+                                    Map<Integer, List<CompanyMiniapp>> integerListMap = miniMap.get(qwUser.getCompanyId());
+                                    if (integerListMap != null) {
+                                        List<CompanyMiniapp> miniapps = integerListMap.get(listIndex);
+
+                                        if (miniapps != null && !miniapps.isEmpty()) {
+                                            CompanyMiniapp companyMiniapp = miniapps.get(0);
+                                            if (companyMiniapp != null && !StringUtil.strIsNullOrEmpty(companyMiniapp.getAppId())) {
+                                                miniAppId = companyMiniapp.getAppId();
+                                            }
+                                        }
+                                    }
+                                }
+
+                                if (StringUtil.strIsNullOrEmpty(miniAppId) && !StringUtil.strIsNullOrEmpty(qwCompany.getMiniAppId())) {
+                                    miniAppId = qwCompany.getMiniAppId();
+                                }
+                                st.setMiniType(listIndex);
+                                if (!StringUtil.strIsNullOrEmpty(miniAppId)) {
+                                    st.setMiniprogramAppid(miniAppId);
                                 } else {
-                                    st.setMiniprogramAppid(config.getMiniprogramAppid());
+                                    log.error("公司的小程序id为空:采用了前端传的固定值" + sopLogs.getSopId());
                                 }
 
                                 st.setMiniprogramPage(linkByMiniApp);
@@ -751,14 +800,6 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
                 }).collect(Collectors.toList());
             }
         }else{
-
-
-            // 查询公司关联小程序数据
-            List<CompanyMiniapp> miniList = companyMiniappService.list(new QueryWrapper<CompanyMiniapp>().orderByAsc("sort_num"));
-
-            Map<Long, Map<Integer, List<CompanyMiniapp>>> miniMap = miniList.stream().collect(Collectors.groupingBy(CompanyMiniapp::getCompanyId, Collectors.groupingBy(CompanyMiniapp::getType)));
-
-
             sopLogsList = new ArrayList<>();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
             List<SopUserLogsInfo> sopUserLogsInfos = sopUserLogsInfoMapper.selectSopUserLogsInfoByIds(param.getIds());
@@ -791,12 +832,6 @@ public class SopUserLogsInfoServiceImpl implements ISopUserLogsInfoService {
 //                domainName = config.getRealLinkDomainName();
 //            }
 
-            QwCompany qwCompany = iQwCompanyService.getQwCompanyByRedis(param.getCorpId());
-
-            if (qwCompany == null ) {
-                return  R.error().put("msg","企业不存在,请联系管理员");
-            }
-
 
             String finalDomainName = "domainName";
 

+ 15 - 0
fs-service/src/main/java/com/fs/store/vo/h5/ExternalAnswerStatsVO.java

@@ -0,0 +1,15 @@
+package com.fs.store.vo.h5;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel
+public class ExternalAnswerStatsVO {
+    @ApiModelProperty(value = "答题次数")
+    private int answerNum;
+
+    @ApiModelProperty(value = "正确次数")
+    private int answerRightNum;
+}

+ 16 - 0
fs-service/src/main/java/com/fs/store/vo/h5/ExternalRedPacketStatsVO.java

@@ -0,0 +1,16 @@
+package com.fs.store.vo.h5;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+@Data
+@ApiModel
+public class ExternalRedPacketStatsVO {
+    @ApiModelProperty(value = "答题红包数")
+    private int redPacketNum;
+
+    @ApiModelProperty(value = "答题红包金额")
+    private BigDecimal redPacketAmount = BigDecimal.ZERO;
+}

+ 35 - 0
fs-service/src/main/java/com/fs/store/vo/h5/ExternalUserStatsVO.java

@@ -0,0 +1,35 @@
+package com.fs.store.vo.h5;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+/*
+*   侧边栏外部联系人统计数据 (课程/答题/红包等数据)
+* */
+@Data
+@ApiModel
+public class ExternalUserStatsVO {
+
+    @ApiModelProperty(value = "课程统计-观看次数")
+    private int courseWatchNum;
+
+    @ApiModelProperty(value = "课程统计-完播次数")
+    private int courseCompleteNum;
+
+
+    @ApiModelProperty(value = "答题次数")
+    private int answerNum;
+
+    @ApiModelProperty(value = "正确次数")
+    private int answerRightNum;
+
+    @ApiModelProperty(value = "答题红包数")
+    private int redPacketNum;
+
+    @ApiModelProperty(value = "答题红包金额")
+    private BigDecimal redPacketAmount = BigDecimal.ZERO;
+
+
+}

+ 15 - 0
fs-service/src/main/java/com/fs/store/vo/h5/ExternalWatchStatsVO.java

@@ -0,0 +1,15 @@
+package com.fs.store.vo.h5;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel
+public class ExternalWatchStatsVO {
+    @ApiModelProperty(value = "看课统计-观看次数")
+    private int courseWatchNum;
+
+    @ApiModelProperty(value = "看课统计-完播次数")
+    private int courseCompleteNum;
+}

+ 3 - 9
fs-service/src/main/resources/application-druid-qdtst-test.yml

@@ -164,13 +164,7 @@ token:
     # 令牌有效期(默认30分钟)
     expireTime: 180
 openIM:
-    secret: openIM123
-    userID: imAdmin
-    url: https://web.im.fbylive.com/api
-#是否使用新im
-im:
-    type: NONE
+    secret:
+    userID:
 #是否为新商户,新商户不走mpOpenId
-isNewWxMerchant: true
-
-
+isNewWxMerchant: false

+ 38 - 3
fs-service/src/main/resources/mapper/course/FsCourseWatchLogMapper.xml

@@ -60,7 +60,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         l.log_type,SEC_TO_TIME(l.duration) as duration,c.company_name,l.camp_period_time,l.finish_time,
         cu.nick_name as company_user_name ,l.send_type,l.create_time,l.update_time,l.last_heartbeat_time,
         qu.qw_user_name,qec.name as external_user_name,c.company_id,u.avatar as fsAvatar,u.nick_name as fsNickName,qec.create_time as qec_create_time,
-        u.is_vip isVip, l.project, l.im_msg_send_detail_id
+        u.is_vip isVip,l.reward_type
          from fs_course_watch_log l
          left join fs_user_course_video v on v.video_id = l.video_id
          left join fs_user_course uc on uc.course_id = l.course_id
@@ -122,10 +122,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                 and DATE(qec.create_time) &lt;= DATE(#{maps.qecETime})
             </if>
             <if test= 'maps.sTime != null '>
-                and DATE(l.create_time) &gt;= DATE(#{maps.sTime})
+                and l.create_time &gt;= #{maps.sTime}
             </if>
             <if test='maps.eTime != null '>
-                and DATE(l.create_time) &lt;= DATE(#{maps.eTime})
+                and l.create_time &lt;= #{maps.eTime}
             </if>
             <if test= 'maps.scheduleStartTime != null '>
                 and DATE(l.camp_period_time) &gt;= DATE(#{maps.scheduleStartTime})
@@ -938,4 +938,39 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         comp.company_id,
             DATE (o.create_time)
     </select>
+    <select id="selectQwFsCourseWatchLogStatisticsListVO"
+            resultType="com.fs.course.vo.FsCourseWatchLogStatisticsListVO" parameterType="com.fs.qw.param.QwSidebarStatsParam">
+        SELECT
+        o.project,
+        o.course_id AS courseId,
+        uc.course_name AS courseName,
+        o.video_id AS videoId,
+        v.title AS videoName,
+        CASE WHEN o.log_type = 1 THEN 1 END AS type1,
+        CASE WHEN o.log_type = 2 THEN 1 END AS type2,
+        CASE WHEN o.log_type = 3 THEN 1 END AS type3,
+        CASE WHEN o.log_type = 4 THEN 1 END AS type4,
+        o.qw_user_id,
+        o.user_id AS userId,
+        o.company_user_id AS companyUserId,
+        o.company_id AS companyId,
+        o.create_time AS createTime
+        FROM
+        fs_course_watch_log o
+        LEFT JOIN fs_user_course_video v ON v.video_id = o.video_id
+        LEFT JOIN fs_user_course uc ON uc.course_id = v.course_id
+        WHERE o.qw_external_contact_id=#{qwExternalContactId}
+        <if test="sendType != null">
+            AND send_type = #{sendType}
+        </if>
+        <if test="startTime != null">
+            AND DATE(o.create_time) &gt;= DATE(#{startTime})
+        </if>
+
+        <if test="endTime != null">
+            AND DATE(o.create_time) &lt;= DATE(#{endTime})
+        </if>
+        o.create_time DESC
+    </select>
+
 </mapper>

+ 4 - 5
fs-service/src/main/resources/mapper/his/FsStoreOrderMapper.xml

@@ -2120,17 +2120,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 
         FROM fs_store_order
         WHERE is_del = 0 AND status > 1
-        <if test="startTime != null">
+        <if test="startTime != null and startTime !=''">
             AND create_time &gt;= #{startTime}
         </if>
-        <if test="endTime != null">
+        <if test="endTime != null and endTime != ''">
             AND create_time &lt;= #{endTime}
         </if>
-
-        <if test="companyId != null">
+        <if test="companyId != null and companyId !=''">
             AND company_id = #{companyId}
         </if>
-        <if test="companyUserId != null">
+        <if test="companyUserId != null and companyUserId !=''">
             AND company_user_id = #{companyUserId}
         </if>
     </select>

+ 44 - 0
fs-service/src/main/resources/mapper/his/FsUserMapper.xml

@@ -2136,4 +2136,48 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </if>
     </select>
 
+    <select id="countExternalRedPacketStats" resultType="com.fs.store.vo.h5.ExternalRedPacketStatsVO">
+        SELECT
+            COUNT(red.log_id) AS redPacketNum,
+            COALESCE(SUM(red.amount), 0) AS redPacketAmount
+        FROM fs_course_red_packet_log log
+        WHERE log.status = 1
+          AND log.user_id = #{userId} AND log.company_user_id = #{companyUserId}
+        <if test="startTime != null  and startTime != ''">
+            AND log.create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null  and endTime != ''">
+            AND log.create_time &lt;= #{endTime}
+        </if>
+    </select>
+
+    <select id="countExternalAnswerStats" resultType="com.fs.store.vo.h5.ExternalAnswerStatsVO">
+        SELECT
+        COUNT(*) AS answerNum,
+        SUM(CASE WHEN is_right = 1 THEN 1 ELSE 0 END) AS answerRightNum
+        FROM fs_course_answer_logs log
+        WHERE log.user_id = #{userId} AND log.company_user_id = #{companyUserId}
+        <if test="startTime != null  and startTime != ''">
+            AND log.create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null  and endTime != ''">
+            AND log.create_time &lt;= #{endTime}
+
+        </if>
+    </select>
+
+    <select id="countExternalWatchStats" resultType="com.fs.store.vo.h5.ExternalWatchStatsVO">
+        SELECT
+        COUNT(CASE WHEN log_type != 3 THEN 1 END) AS courseWatchNum,
+        COUNT(CASE WHEN log_type = 2 THEN 1 END) AS courseCompleteNum
+        FROM fs_course_watch_log log
+        WHERE log.user_id = #{userId} AND log.company_user_id = #{companyUserId}
+        <if test="startTime != null  and startTime != ''">
+            AND log.create_time &gt;= #{startTime}
+        </if>
+        <if test="endTime != null  and endTime != ''">
+            AND log.create_time &lt;= #{endTime}
+        </if>
+    </select>
+
 </mapper>

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

@@ -1877,16 +1877,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         SUM(CASE WHEN pay_type IN ('2', '3') THEN COALESCE(pay_money, 0) ELSE 0 END) AS codAmount
         FROM fs_store_order_scrm
         WHERE is_del = 0 AND status > 0
-        <if test="startTime != null">
+        <if test="startTime != null and startTime !=''">
             AND create_time &gt;= #{startTime}
         </if>
-        <if test="endTime != null">
+        <if test="endTime != null and endTime != ''">
             AND create_time &lt;= #{endTime}
         </if>
-        <if test="companyId != null">
+        <if test="companyId != null and companyId !=''">
             AND company_id = #{companyId}
         </if>
-        <if test="companyUserId != null">
+        <if test="companyUserId != null and companyUserId !=''">
             AND company_user_id = #{companyUserId}
         </if>
     </select>

+ 4 - 1
fs-service/src/main/resources/mapper/qw/QwExternalContactMapper.xml

@@ -782,5 +782,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             order by ec.create_time desc, ec.id desc
     </select>
 
-
+    <select id="selectQwUserListVOByQwUserIdAndCorpIdAndExternalUserId" resultMap="QwExternalContactResult">
+        <include refid="selectQwExternalContactVo"/>
+        where user_id = #{userId} and corp_id = #{corpId} and external_user_id = #{externalUserId}
+    </select>
 </mapper>