Browse Source

Merge branch 'master' of https://github.com/7-idiot/weChat-Operation

周杰伦 4 years ago
parent
commit
d059fa72c4
38 changed files with 1952 additions and 67 deletions
  1. 1 1
      README.md
  2. 0 8
      operation-backend/pom.xml
  3. 2 0
      operation-backend/src/main/java/com/idiot/operationbackend/OperationBackendApplication.java
  4. 36 0
      operation-backend/src/main/java/com/idiot/operationbackend/config/AsyncPoolConfig.java
  5. 6 10
      operation-backend/src/main/java/com/idiot/operationbackend/controller/AccountController.java
  6. 0 2
      operation-backend/src/main/java/com/idiot/operationbackend/controller/AuthController.java
  7. 343 0
      operation-backend/src/main/java/com/idiot/operationbackend/controller/IndexController.java
  8. 32 1
      operation-backend/src/main/java/com/idiot/operationbackend/controller/WeChatController.java
  9. 41 0
      operation-backend/src/main/java/com/idiot/operationbackend/entity/AccountFans.java
  10. 222 0
      operation-backend/src/main/java/com/idiot/operationbackend/entity/AccountFansStat.java
  11. 64 0
      operation-backend/src/main/java/com/idiot/operationbackend/entity/FansActionStat.java
  12. 41 0
      operation-backend/src/main/java/com/idiot/operationbackend/entity/SubscribeScene.java
  13. 48 0
      operation-backend/src/main/java/com/idiot/operationbackend/handler/ScheduledHandler.java
  14. 33 0
      operation-backend/src/main/java/com/idiot/operationbackend/handler/SyncUserTask.java
  15. 72 0
      operation-backend/src/main/java/com/idiot/operationbackend/mappers/AccountFansStatMapper.java
  16. 11 0
      operation-backend/src/main/java/com/idiot/operationbackend/mappers/AccountTagMapper.java
  17. 13 0
      operation-backend/src/main/java/com/idiot/operationbackend/mappers/FansActionStatMapper.java
  18. 11 0
      operation-backend/src/main/java/com/idiot/operationbackend/mappers/SubscribeSceneMapper.java
  19. 35 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/facade/AccountFansService.java
  20. 56 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/facade/AccountFansStatService.java
  21. 8 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/facade/AccountService.java
  22. 53 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/facade/AccountTagService.java
  23. 67 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/facade/FansActionStatService.java
  24. 11 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/facade/SubscribeSceneService.java
  25. 81 10
      operation-backend/src/main/java/com/idiot/operationbackend/service/facade/WeChatService.java
  26. 29 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/impl/AccountFansServiceImpl.java
  27. 118 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/impl/AccountFansStatServiceImpl.java
  28. 12 1
      operation-backend/src/main/java/com/idiot/operationbackend/service/impl/AccountServiceImpl.java
  29. 75 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/impl/AccountTagServiceImpl.java
  30. 63 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/impl/FansActionStatServiceImpl.java
  31. 16 0
      operation-backend/src/main/java/com/idiot/operationbackend/service/impl/SubscribeSceneServiceImpl.java
  32. 163 3
      operation-backend/src/main/java/com/idiot/operationbackend/service/impl/WeChatServiceImpl.java
  33. 38 0
      operation-backend/src/main/java/com/idiot/operationbackend/support/Constants.java
  34. 60 0
      operation-backend/src/main/java/com/idiot/operationbackend/vo/GeneralStatData.java
  35. 23 26
      operation-backend/src/main/resources/application-test.yml
  36. 4 2
      operation-frontend/package.json
  37. 0 2
      operation-frontend/src/main.js
  38. 64 1
      sql/dataBase.sql

+ 1 - 1
README.md

@@ -3,7 +3,7 @@
 ## [Features](#features)
 * 一键授权公众号,帮助管理微信公众号.
 * 实时同步粉丝信息,及时统计分析用户数据。
-* 便签同步功能使用户管理更加便捷
+* 标签、备注同步功能使用户管理更加便捷
 * 一键智能群发,随时设置关注回复,特殊回复
 * 自定义菜单,方便更改公众号功能,让推送更加及时
 * 在线文案媒体编辑

+ 0 - 8
operation-backend/pom.xml

@@ -51,14 +51,6 @@
         <!-- swagger2  start -->
 
 
-        <!--kaptcha start-->
-        <dependency>
-            <groupId>com.baomidou</groupId>
-            <artifactId>kaptcha-spring-boot-starter</artifactId>
-            <version>1.1.0</version>
-        </dependency>
-        <!--kaptcha end-->
-
         <!--JWT start-->
         <dependency>
             <groupId>com.auth0</groupId>

+ 2 - 0
operation-backend/src/main/java/com/idiot/operationbackend/OperationBackendApplication.java

@@ -4,12 +4,14 @@ import org.mybatis.spring.annotation.MapperScan;
 import org.mybatis.spring.annotation.MapperScans;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
 
 /**
  * 微信公众号运营平台
  * @author wangxiao
  * @date 2020-09-09
  */
+@EnableScheduling
 @SpringBootApplication
 @MapperScans(value = {@MapperScan("com.idiot.operationbackend.mappers")})
 public class OperationBackendApplication {

+ 36 - 0
operation-backend/src/main/java/com/idiot/operationbackend/config/AsyncPoolConfig.java

@@ -0,0 +1,36 @@
+package com.idiot.operationbackend.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.ThreadPoolExecutor;
+
+
+/**
+ * 异步线程 但是丢弃任务会使得用户信息同步失败
+ * @author wang xiao
+ * @date Created in 18:23 2020/9/14
+ */
+@EnableAsync
+@Configuration
+public class AsyncPoolConfig {
+
+
+    @Bean(name = "asyncExecutor")
+    public Executor  asyncExecutor () {
+        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+        executor.setCorePoolSize(5);
+        executor.setMaxPoolSize(10);
+        executor.setQueueCapacity(500);
+        executor.setKeepAliveSeconds(60);
+        executor.setThreadNamePrefix("asyncExecutor-");
+        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+        executor.setWaitForTasksToCompleteOnShutdown(true);
+        executor.setAwaitTerminationSeconds(60);
+        executor.initialize();
+        return executor;
+    }
+}

+ 6 - 10
operation-backend/src/main/java/com/idiot/operationbackend/controller/AccountController.java

@@ -2,8 +2,10 @@ package com.idiot.operationbackend.controller;
 
 import com.idiot.operationbackend.entity.Account;
 import com.idiot.operationbackend.entity.AuthUser;
+import com.idiot.operationbackend.handler.SyncUserTask;
 import com.idiot.operationbackend.service.facade.AccountService;
 import com.idiot.operationbackend.service.facade.AuthUserService;
+import com.idiot.operationbackend.service.facade.WeChatService;
 import com.idiot.operationbackend.support.JsonResult;
 import com.idiot.operationbackend.util.JwtTokenUtil;
 import io.swagger.annotations.Api;
@@ -37,10 +39,9 @@ public class AccountController {
     private AuthUserService userService;
 
 
-
     @GetMapping
     @ApiOperation(value = "查询公众号列表")
-    public ResponseEntity<JsonResult<List<Account>>> list (@RequestHeader(value = "AUTH_TOKEN") String token) {
+    public ResponseEntity<JsonResult<List<Account>>> list (@RequestHeader String token) {
         String userId = JwtTokenUtil.getUserId(token);
         logger.info("用户:{}查询公众号列表",userId);
         List<Account> accounts  = accountService.queryAccountByUserId(userId);;
@@ -57,14 +58,6 @@ public class AccountController {
     }
 
 
-    @GetMapping("confirmAccount")
-    @ApiOperation(value = "确认授权的微信公众号")
-    public ResponseEntity<JsonResult<Boolean>> confirmAccount (@RequestHeader String token,
-                                                               @RequestParam String accountId) {
-        String userId = JwtTokenUtil.getUserId(token);
-        boolean ifUpdate = accountService.updateUserIdByAccount(accountId,userId);
-        return ResponseEntity.ok(JsonResult.success(ifUpdate));
-    }
 
     @GetMapping("/{id}")
     @ApiOperation(value = "根据id查询微信公众号")
@@ -76,4 +69,7 @@ public class AccountController {
         return ResponseEntity.ok(JsonResult.success(account));
     }
 
+
+
+
 }

+ 0 - 2
operation-backend/src/main/java/com/idiot/operationbackend/controller/AuthController.java

@@ -44,8 +44,6 @@ public class AuthController  {
     @ApiOperation(value = "账号登录")
     public ResponseEntity<JsonResult<AuthUser>> login(@RequestBody @Valid AuthUser authUser) {
         logger.info("用户:{}账号密码登录",authUser.getUserCode());
-
-
         authUser =userService.queryByUserCodeAndPassword(authUser.getPassword(),authUser.getUserCode());
         if (Objects.isNull(authUser)) {
             throw new CustomException(500,"账号或者密码错误!请检查大小写");

+ 343 - 0
operation-backend/src/main/java/com/idiot/operationbackend/controller/IndexController.java

@@ -0,0 +1,343 @@
+package com.idiot.operationbackend.controller;
+
+import com.idiot.operationbackend.entity.Account;
+import com.idiot.operationbackend.entity.AccountFans;
+import com.idiot.operationbackend.entity.AccountFansStat;
+import com.idiot.operationbackend.entity.FansActionStat;
+import com.idiot.operationbackend.service.facade.AccountFansService;
+import com.idiot.operationbackend.service.facade.AccountFansStatService;
+import com.idiot.operationbackend.service.facade.AccountService;
+import com.idiot.operationbackend.service.facade.FansActionStatService;
+import com.idiot.operationbackend.support.Constants;
+import com.idiot.operationbackend.support.CustomException;
+import com.idiot.operationbackend.support.JsonResult;
+import com.idiot.operationbackend.util.JwtTokenUtil;
+import com.idiot.operationbackend.vo.GeneralStatData;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.*;
+
+import java.math.BigDecimal;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static com.idiot.operationbackend.support.Constants.calcRate;
+
+/**
+ * 运营星 首页
+ * @author wang xiao
+ * @date Created in 10:55 2020/9/15
+ */
+@RestController
+@RequestMapping("/index")
+@Api(value = "IndexController", tags ="首页")
+public class IndexController {
+
+
+    private final Logger logger = LoggerFactory.getLogger(IndexController.class);
+
+    private final String HOUR = "H";
+
+    private final String WEEK = "W";
+
+    private final String DAY = "D";
+
+    private final String MONTH = "M";
+
+    @Autowired
+    private AccountService accountService;
+
+    @Autowired
+    private AccountFansStatService fansStatService;
+
+    @Autowired
+    private AccountFansService fansService;
+
+    @Autowired
+    private FansActionStatService actionStatService;
+
+
+
+
+    @GetMapping("/summary")
+    @ApiOperation(value = "查询公众号昨日统计数据概览")
+    public ResponseEntity<JsonResult<AccountFansStat>> getFansStat (@RequestHeader String token) {
+        String userId = JwtTokenUtil.getUserId(token);
+        logger.info("用户:{}查询首页数据统计概览---start",userId);
+        List<Account> accounts = accountService.queryAccountByUserId(userId);
+        List<String> accountIds =  accounts.stream().map(Account::getId).collect(Collectors.toList());
+        // 查询数据  不知道怎么优化
+        String yesterdayStr = Constants.DATE_FORMATTER.format(LocalDate.now().plusDays(-1));
+        List<AccountFansStat> ydStatData = fansStatService.queryByDateAndAccIds(accountIds,yesterdayStr);
+        String beforeYesterdayStr = Constants.DATE_FORMATTER.format(LocalDate.now().plusDays(-2));
+        List<AccountFansStat> beforeYdStat = fansStatService.queryByDateAndAccIds(accountIds,beforeYesterdayStr);
+
+        logger.info("用户:{}首页概览数据计算中---ing",userId);
+        // 所有公众号昨日数据
+        long totalNum = ydStatData.stream().mapToLong(AccountFansStat::getTotalFansNum).sum();
+        long addNum = ydStatData.stream().mapToLong(AccountFansStat::getAddNum).sum();
+        long cancelNum = ydStatData.stream().mapToLong(AccountFansStat::getCancelNum).sum();
+        long newNum = ydStatData.stream().mapToLong(AccountFansStat::getNewNum).sum();
+        long inactiveNum = ydStatData.stream().mapToLong(AccountFansStat::getInactiveNum).sum();
+        // 所有公众号前日数据
+        long bfCancelNum = beforeYdStat.stream().mapToLong(AccountFansStat::getCancelNum).sum();
+        long bfAddNum = beforeYdStat.stream().mapToLong(AccountFansStat::getAddNum).sum();
+        long bfNewNum = beforeYdStat.stream().mapToLong(AccountFansStat::getNewNum).sum();
+        long bfInactiveNum = beforeYdStat.stream().mapToLong(AccountFansStat::getInactiveNum).sum();
+        long bfTotalNum = beforeYdStat.stream().mapToLong(AccountFansStat::getTotalFansNum).sum();
+
+        AccountFansStat result = new AccountFansStat();
+        result.setAddNum(addNum);
+        result.setNewNum(newNum);
+        result.setCancelNum(cancelNum);
+        result.setInactiveNum(inactiveNum);
+        result.setTotalFansNum(totalNum);
+        result.setAddRate(calcRate(addNum,bfAddNum));
+        result.setCancelRate(calcRate(cancelNum,bfCancelNum));
+        result.setNewRate(calcRate(newNum,bfNewNum));
+        result.setInactiveRate(calcRate(inactiveNum,bfInactiveNum));
+        result.setTotalFansRate(calcRate(totalNum,bfTotalNum));
+        logger.info("用户:{}查询首页数据统计概览---end",userId);
+        return ResponseEntity.ok(JsonResult.success(result));
+    }
+
+    @GetMapping("/single/{accountId}")
+    @ApiOperation(value = "查询单个公众号昨日统计数据概览")
+    public ResponseEntity<JsonResult<AccountFansStat>> getSingleFansStat (@RequestHeader String token,
+                                                                          @PathVariable String accountId) {
+        String userId = JwtTokenUtil.getUserId(token);
+        logger.info("用户:{}查询首页单个公众号:{}数据统计概览---start",userId,accountId);
+        String yesterdayStr = Constants.DATE_FORMATTER.format(LocalDate.now().plusDays(-1));
+        AccountFansStat fansStat = fansStatService.queryByDateAndAccountId(accountId,yesterdayStr);
+        logger.info("用户:{}查询首页单个公众号:{}数据统计概览---end",userId,accountId);
+        return ResponseEntity.ok(JsonResult.success(fansStat));
+    }
+
+
+    @GetMapping("/fansGrowth/{accountId}")
+    @ApiOperation(value = "查询单个公众号--粉丝增长")
+    public ResponseEntity<JsonResult<Map<String,Object>>> getFansGrowthStat (@PathVariable String accountId,
+                                                              @RequestHeader String token,
+                                                              @RequestParam String type,
+                                                              @RequestParam String startDate,
+                                                              @RequestParam String endDate) {
+        String userId = JwtTokenUtil.getUserId(token);
+        if (!checkDate(startDate,endDate)){
+            throw new CustomException(500,"请选择开始和结束时间!");
+        }
+        Map<String,Object> result = new HashMap<>(8);
+        logger.info("用户:{}查询首页单个公众号:{}粉丝增长,type:{},startDate:{},endDate:{} --->start",
+                userId,accountId,type,startDate,endDate);
+        LocalDate start = LocalDate.parse(startDate);
+        LocalDate end= LocalDate.parse(endDate);
+        long days = end.toEpochDay() - start.toEpochDay();
+        List<GeneralStatData> statDataList = queryFansGrowthStatData(accountId, type, start, end);
+        long newNum =0;
+        long addNum =0;
+        long inactiveNum =0;
+        long cancelNum =0;
+        BigDecimal ave =BigDecimal.ZERO;
+        if (CollectionUtils.isEmpty(statDataList)) {
+            newNum = statDataList.parallelStream().mapToLong(GeneralStatData::getNewNum).sum();
+            addNum = statDataList.parallelStream().mapToLong(GeneralStatData::getAddNum).sum();
+            inactiveNum = statDataList.parallelStream().mapToLong(GeneralStatData::getInactiveNum).sum();
+            cancelNum = statDataList.parallelStream().mapToLong(GeneralStatData::getCancelNum).sum();
+            if (days == 0){
+                days = 1;
+            }
+            ave = new BigDecimal(newNum/days).setScale(0);
+        }
+        result.put("newNum",newNum);
+        result.put("addNum",addNum);
+        result.put("inactiveNum",inactiveNum);
+        result.put("cancelNum",cancelNum);
+        result.put("aveNum",ave);
+        result.put("tableData",statDataList);
+        logger.info("用户:{}查询首页单个公众号:{}粉丝增长,type:{},startDate:{},endDate:{} --->end",
+                userId,accountId,type,startDate,endDate);
+        return ResponseEntity.ok(JsonResult.success(result));
+    }
+
+
+    @GetMapping("/fansProperty/{accountId}")
+    @ApiOperation(value = "查询单个公众号--粉丝属性")
+    public ResponseEntity<JsonResult<Map<String,Object>>> getFansPropertyStat(
+                                                                @RequestHeader String token,
+                                                                @PathVariable String accountId,
+                                                                @RequestParam(required = false) String startDate,
+                                                                @RequestParam(required = false) String endDate) {
+        String userId = JwtTokenUtil.getUserId(token);
+        logger.info("用户:{}查询首页单个公众号:{}属性,startDate:{},endDate:{} --->start",userId,accountId,startDate,endDate);
+        List<AccountFans> accountFans;
+        Map<String,Object> result = new HashMap<>(4);
+        if (!StringUtils.isEmpty(startDate) && !StringUtils.isEmpty(endDate) ){
+           accountFans = fansService.queryFansByDateAndDate(accountId,startDate,endDate);
+        }else {
+
+        }
+
+        logger.info("用户:{}查询首页单个公众号:{}属性,startDate:{},endDate:{} --->end",userId,accountId,startDate,endDate);
+        return null;
+
+    }
+
+
+
+    /**
+     *  统计粉丝数据  好难受写不出完美的代码
+     * @author wangxiao
+     * @date 16:00 2020/9/16 
+     * @param accountId
+     * @param type
+     * @param startDate
+     * @param endDate
+     * @return java.util.List<com.idiot.operationbackend.vo.StatData>
+     */
+    private List<GeneralStatData> queryFansGrowthStatData(String accountId, String type, LocalDate startDate, LocalDate endDate) {
+
+        // 粉丝增长数据 因为能选择今天 需要在 用户动作分析里面取数据。或者一小时分析一次数据
+        long disValue = 0;
+        if (HOUR.equals(type)) {
+            disValue = 7200;
+        }else if (DAY.equals(type)){
+            disValue = 86400;
+        }else if (WEEK.equals(type)){
+            disValue = 604800;
+        }else if (MONTH.equals(type)){
+            // 默认 30 天
+            disValue = 2592000;
+        }else {
+            throw new CustomException(500,"请你选择正确的时间统计类型!");
+        }
+        LocalDateTime startLocalDate =  startDate.atTime(LocalTime.of(0,0,0));
+        LocalDateTime endLocalDate =  endDate.plusDays(1).atTime(LocalTime.of(0,0,0));
+        List<FansActionStat> fansActionStats = actionStatService.queryFansActionStat(accountId,startLocalDate,endLocalDate);
+        if (CollectionUtils.isEmpty(fansActionStats)) {
+            throw new CustomException(500,"暂无数据!");
+        }
+        long start = startLocalDate.toEpochSecond(Constants.DEFAULT_ZONE);
+        long end = endLocalDate.toEpochSecond(Constants.DEFAULT_ZONE);
+        List<GeneralStatData> statDataList = new ArrayList<>();
+        GeneralStatData statData =  null;
+        List<FansActionStat> tempStatList = null;
+        while (start<=end){
+            long finalStart = start;
+            long finalDisValue = disValue;
+            tempStatList = fansActionStats.stream()
+                    .filter(e->e.getCreateTime() >= finalStart && e.getCreateTime()<=  finalStart + finalDisValue)
+                    .sorted(Comparator.comparingLong(FansActionStat::getCreateTime))
+                    .collect(Collectors.toList());
+            statData = countStatData(tempStatList,finalStart,finalStart+finalDisValue,type);
+            statDataList.add(statData);
+            start += finalDisValue;
+        }
+        return statDataList;
+    }
+
+    private boolean checkDate (String start, String end) {
+        return !(StringUtils.isEmpty(start) | StringUtils.isEmpty(end));
+    }
+
+    /**
+     *  统计 粉丝增长 时间段
+     * @author wangxiao
+     * @date 16:45 2020/9/16
+     * @param fansActionStats
+     * @param start
+     * @param end
+     * @param type
+     * @return com.idiot.operationbackend.vo.StatData
+     */
+    private GeneralStatData countStatData (List<FansActionStat> fansActionStats, long start, long end, String type) {
+
+        LocalDateTime startDateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(start),Constants.DEFAULT_ZONE);
+        LocalDateTime endDateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(end),Constants.DEFAULT_ZONE);
+        GeneralStatData statData = new GeneralStatData();
+        long newNum = 0;
+        long cancelNum = 0;
+        long inactiveNum = 0;
+        if (!CollectionUtils.isEmpty(fansActionStats)) {
+            newNum = fansActionStats.parallelStream().filter(e->Constants.NEW == e.getAction()).count();
+            cancelNum = fansActionStats.parallelStream().filter(e->Constants.CANCEL == e.getAction()).count();
+            inactiveNum = fansActionStats.parallelStream().filter(e->Constants.NEW != e.getAction() && Constants.CANCEL != e.getAction()).count();
+        }
+        String label = "";
+
+        if (HOUR.equals(type)) {
+            label = String.format("%s %s:%s-%s:%s",startDateTime.toLocalDate(),startDateTime.getHour(),"00",endDateTime.getHour(),"00");
+        }else if (DAY.equals(type)){
+            label = String.format("%s",startDateTime.toLocalDate());
+        }else if (WEEK.equals(type)){
+            label = String.format("%s/%s-%s/%s",startDateTime.getMonthValue(),startDateTime.getDayOfMonth()
+                    ,endDateTime.getMonthValue(),endDateTime.getDayOfMonth());
+        }else if (MONTH.equals(type)){
+            label =  String.format("%s-%s",startDateTime.getYear(),startDateTime.getMonthValue());
+        }
+        statData.setDateLabel(label);
+        statData.setAddNum(newNum-cancelNum);
+        statData.setCancelNum(cancelNum);
+        statData.setNewNum(newNum);
+        statData.setInactiveNum(inactiveNum);
+        return statData;
+    }
+
+
+    /**
+     *  统计粉丝属性
+     * @author wangxiao
+     * @date 18:57 2020/9/16
+     * @param fans
+     * @param map
+     * @return void
+     */
+    private void statFansProperty(List<AccountFans> fans,Map<String,Object> map) {
+        long totalFansNum = 0;
+        long  manNum = 0;
+        BigDecimal manRate = BigDecimal.ZERO;
+        long  womanNum = 0;
+        BigDecimal womanRate = BigDecimal.ZERO;
+        Map<String,Long> subscribeScene = null;
+        if (!CollectionUtils.isEmpty(fans)) {
+            totalFansNum = fans.size();
+            manNum = fans.parallelStream().filter(e->1==e.getSex()).count();
+            womanNum = fans.parallelStream().filter(e->2==e.getSex()).count();
+            manRate = BigDecimal.valueOf((manNum*100)/totalFansNum).setScale(2);
+            womanRate = BigDecimal.valueOf((womanNum*100)/totalFansNum).setScale(2);
+            subscribeScene = fans.stream().collect(Collectors.groupingBy(AccountFans::getSubscribeScene,Collectors.reducing(0L,accountFans -> 1L,Long::sum)));
+        }
+        Map<String,Object> base = new HashMap<>(6);
+        base.put("totalFansNum",totalFansNum);
+        base.put("manNum",manNum);
+        base.put("womanNum",womanNum);
+        base.put("manRate",manRate);
+        base.put("womanRate",womanRate);
+        map.put("base",base);
+        Map<String,Object> sex = new HashMap<>(4);
+        sex.put("manNum",manNum);
+        sex.put("womanNum",womanNum);
+        sex.put("manRate",manRate);
+        sex.put("womanRate",womanRate);
+        map.put("sex",sex);
+        map.put("subscribeScene",subscribeScene);
+
+    }
+
+
+
+
+
+
+
+}

+ 32 - 1
operation-backend/src/main/java/com/idiot/operationbackend/controller/WeChatController.java

@@ -2,11 +2,14 @@ package com.idiot.operationbackend.controller;
 
 import com.alibaba.fastjson.JSONObject;
 import com.idiot.operationbackend.entity.Account;
+import com.idiot.operationbackend.entity.SubscribeScene;
 import com.idiot.operationbackend.service.facade.AccountService;
+import com.idiot.operationbackend.service.facade.SubscribeSceneService;
 import com.idiot.operationbackend.service.facade.WeChatService;
 import com.idiot.operationbackend.support.AccountState;
 import com.idiot.operationbackend.support.Constants;
 import com.idiot.operationbackend.support.JsonResult;
+import com.idiot.operationbackend.util.JwtTokenUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.slf4j.Logger;
@@ -21,6 +24,7 @@ import javax.servlet.http.HttpServletResponse;
 import java.io.BufferedInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.List;
 
 
 /**
@@ -42,6 +46,9 @@ public class WeChatController {
     @Autowired
     private WeChatService weChatService;
 
+    @Autowired
+    private SubscribeSceneService sceneService;
+
 
     @GetMapping("preAuth")
     @ApiOperation(value = "获取预授权码地址,微信认证公众号")
@@ -118,13 +125,37 @@ public class WeChatController {
             boolean ifResult = weChatService.saveOrUpdateWechatAcc(account);
             weChatService.cacheAuthorizerAccessToken(account.getId(),authorizerAccessToken);
             // 重定向到 用户确认中介页面
-
             response.sendRedirect(confirmDomain+"?accountId="+account.getId());
         }catch (Exception e) {
             response.sendRedirect(confirmDomain+"?accountId=");
             logger.error("微信授权回调 ------->失败,authCode is :{},expiresIn:{},error message is :{}",authCode,expiresIn,e.getMessage());
         }
+    }
+
+
+    @GetMapping("confirmAccount")
+    @ApiOperation(value = "确认授权的微信公众号")
+    public ResponseEntity<JsonResult<Boolean>> confirmAccount (@RequestHeader String token,
+                                                               @RequestParam String accountId) {
+
+        String userId = JwtTokenUtil.getUserId(token);
+        logger.error("微信公众号:{}授权,用户:{}确认 ,",accountId,userId);
+        boolean ifUpdate = weChatService.confirmAccount(accountId,userId);
+        if (ifUpdate){
+            logger.error("微信公众号:{}授权,用户:{} 成功确认 ,开始同步用户标签数据",accountId,userId);
+            weChatService.syncAccountUser(accountId);
+            weChatService.syncTag(accountId);
+        }
+
+        return ResponseEntity.ok(JsonResult.success(ifUpdate));
+    }
+
 
+    @GetMapping("/subscribeScene")
+    @ApiOperation(value = "查询用户关注公众号来源类型")
+    public ResponseEntity<JsonResult<List<SubscribeScene>>> getSubscribeScene (@RequestHeader String token) {
+        String userId = JwtTokenUtil.getUserId(token);
+        return ResponseEntity.ok(JsonResult.success(sceneService.list()));
     }
 
 

+ 41 - 0
operation-backend/src/main/java/com/idiot/operationbackend/entity/AccountFans.java

@@ -24,6 +24,8 @@ public class AccountFans {
 
     private String city;
 
+    private String province;
+
     private String country;
 
     private String headImgUrl;
@@ -38,6 +40,12 @@ public class AccountFans {
 
     private String tagIdList;
 
+    private String remark;
+
+    private String unionId;
+
+    private Integer groupId;
+
     private Long lastInactiveTime;
 
     private String updateTime;
@@ -91,6 +99,14 @@ public class AccountFans {
         this.city = city;
     }
 
+    public String getProvince() {
+        return province;
+    }
+
+    public void setProvince(String province) {
+        this.province = province;
+    }
+
     public String getCountry() {
         return country;
     }
@@ -162,4 +178,29 @@ public class AccountFans {
     public void setUpdateTime(String updateTime) {
         this.updateTime = updateTime;
     }
+
+
+    public String getRemark() {
+        return remark;
+    }
+
+    public void setRemark(String remark) {
+        this.remark = remark;
+    }
+
+    public String getUnionId() {
+        return unionId;
+    }
+
+    public void setUnionId(String unionId) {
+        this.unionId = unionId;
+    }
+
+    public Integer getGroupId() {
+        return groupId;
+    }
+
+    public void setGroupId(Integer groupId) {
+        this.groupId = groupId;
+    }
 }

+ 222 - 0
operation-backend/src/main/java/com/idiot/operationbackend/entity/AccountFansStat.java

@@ -0,0 +1,222 @@
+package com.idiot.operationbackend.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+import java.math.BigDecimal;
+
+/**
+ * 公众号粉丝数据统计
+ * @author wang xiao
+ * @date Created in 13:57 2020/9/15
+ */
+@TableName("t_account_fans_stat")
+public class AccountFansStat {
+
+    @TableId
+    private String id;
+
+    private String accountId;
+
+    /**
+     * 新增
+     */
+    private  Long newNum;
+
+    /**
+     * 取关
+     */
+    private  Long cancelNum;
+
+    /**
+     * 活跃
+     */
+    private  Long inactiveNum;
+
+    /**
+     * 总粉丝数
+     */
+    private  Long totalFansNum;
+
+    /**
+     * 净增
+     */
+    private  Long addNum;
+
+    /**
+     * 阅读数量
+     */
+    private  Long pageReadNum;
+
+
+    /**
+     * 新增比例
+     */
+    private BigDecimal newRate;
+
+
+    /**
+     * 取关比例比例
+     */
+    private BigDecimal cancelRate;
+
+
+    /**
+     * 活跃比例
+     */
+    private BigDecimal inactiveRate;
+
+    /**
+     * 总粉丝
+     */
+    private BigDecimal totalFansRate;
+
+    /**
+     * 净增比例
+     */
+    private BigDecimal addRate;
+
+
+    /**
+     * 阅读比例
+     */
+    private BigDecimal pageReadRate;
+
+    /**
+     * 统计日期 yyyy-MM-dd
+     */
+    private String statDate;
+
+    /**
+     * 创建时间
+     */
+    private String createTime;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(String accountId) {
+        this.accountId = accountId;
+    }
+
+    public Long getNewNum() {
+        return newNum;
+    }
+
+    public void setNewNum(Long newNum) {
+        this.newNum = newNum;
+    }
+
+    public Long getCancelNum() {
+        return cancelNum;
+    }
+
+    public void setCancelNum(Long cancelNum) {
+        this.cancelNum = cancelNum;
+    }
+
+    public Long getInactiveNum() {
+        return inactiveNum;
+    }
+
+    public void setInactiveNum(Long inactiveNum) {
+        this.inactiveNum = inactiveNum;
+    }
+
+    public Long getTotalFansNum() {
+        return totalFansNum;
+    }
+
+    public void setTotalFansNum(Long totalFansNum) {
+        this.totalFansNum = totalFansNum;
+    }
+
+    public Long getAddNum() {
+        return addNum;
+    }
+
+    public void setAddNum(Long addNum) {
+        this.addNum = addNum;
+    }
+
+    public Long getPageReadNum() {
+        return pageReadNum;
+    }
+
+    public void setPageReadNum(Long pageReadNum) {
+        this.pageReadNum = pageReadNum;
+    }
+
+    public BigDecimal getNewRate() {
+        return newRate;
+    }
+
+    public void setNewRate(BigDecimal newRate) {
+        this.newRate = newRate;
+    }
+
+    public BigDecimal getCancelRate() {
+        return cancelRate;
+    }
+
+    public void setCancelRate(BigDecimal cancelRate) {
+        this.cancelRate = cancelRate;
+    }
+
+    public BigDecimal getInactiveRate() {
+        return inactiveRate;
+    }
+
+    public void setInactiveRate(BigDecimal inactiveRate) {
+        this.inactiveRate = inactiveRate;
+    }
+
+    public BigDecimal getTotalFansRate() {
+        return totalFansRate;
+    }
+
+    public void setTotalFansRate(BigDecimal totalFansRate) {
+        this.totalFansRate = totalFansRate;
+    }
+
+    public BigDecimal getAddRate() {
+        return addRate;
+    }
+
+    public void setAddRate(BigDecimal addRate) {
+        this.addRate = addRate;
+    }
+
+    public BigDecimal getPageReadRate() {
+        return pageReadRate;
+    }
+
+    public void setPageReadRate(BigDecimal pageReadRate) {
+        this.pageReadRate = pageReadRate;
+    }
+
+    public String getStatDate() {
+        return statDate;
+    }
+
+    public void setStatDate(String statDate) {
+        this.statDate = statDate;
+    }
+
+    public String getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(String createTime) {
+        this.createTime = createTime;
+    }
+}

+ 64 - 0
operation-backend/src/main/java/com/idiot/operationbackend/entity/FansActionStat.java

@@ -0,0 +1,64 @@
+package com.idiot.operationbackend.entity;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+
+/**
+ * 粉丝动作统计
+ * @author wang xiao
+ * @date Created in 11:17 2020/9/16
+ */
+@TableName("t_fans_action_stat")
+public class FansActionStat {
+
+    @TableId
+    private String id;
+
+    private String accountId;
+
+    private String openId;
+
+    private Integer action;
+
+    private Long createTime;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getAccountId() {
+        return accountId;
+    }
+
+    public void setAccountId(String accountId) {
+        this.accountId = accountId;
+    }
+
+    public String getOpenId() {
+        return openId;
+    }
+
+    public void setOpenId(String openId) {
+        this.openId = openId;
+    }
+
+    public Integer getAction() {
+        return action;
+    }
+
+    public void setAction(Integer action) {
+        this.action = action;
+    }
+
+    public Long getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Long createTime) {
+        this.createTime = createTime;
+    }
+}

+ 41 - 0
operation-backend/src/main/java/com/idiot/operationbackend/entity/SubscribeScene.java

@@ -0,0 +1,41 @@
+package com.idiot.operationbackend.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+
+/**
+ * @author wang xiao
+ * @date Created in 17:40 2020/9/16
+ */
+@TableName("t_subscribe_scene")
+public class SubscribeScene {
+
+    private Integer id;
+
+    private String key;
+
+    private String label;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public void setKey(String key) {
+        this.key = key;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+}

+ 48 - 0
operation-backend/src/main/java/com/idiot/operationbackend/handler/ScheduledHandler.java

@@ -0,0 +1,48 @@
+package com.idiot.operationbackend.handler;
+
+
+import com.idiot.operationbackend.entity.Account;
+import com.idiot.operationbackend.service.facade.AccountFansStatService;
+import com.idiot.operationbackend.service.facade.AccountService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * @author wang xiao
+ * @date Created in 17:31 2020/9/15
+ */
+@Component
+public class ScheduledHandler {
+
+
+    @Autowired
+    private AccountFansStatService fansStatService;
+
+
+    @Autowired
+    private AccountService accountService;
+
+    private final Logger logger = LoggerFactory.getLogger(ScheduledHandler.class);
+
+    @Scheduled(cron = "0 0 1 * * ?")
+    public void  scheduled () {
+        logger.info("开始同步微信用户分析数据----->start,时间:{}", LocalDateTime.now().toString());
+        List<Account> accounts = accountService.queryAccount();
+        if (null == accounts || accounts.isEmpty()) {
+            logger.info("开始同步微信用户分析数据----->认证公众号信息为空,时间:{}", LocalDateTime.now().toString());
+            return;
+        }
+        for (Account account : accounts) {
+            logger.info("同步微信公众号:{}用户分析数据----->,时间:{}", account.getId(),LocalDateTime.now().toString());
+            fansStatService.statAccountFansData(account.getId());
+        }
+        logger.info("开始同步微信用户分析数据----->end,时间:{}", LocalDateTime.now().toString());
+    }
+
+}

+ 33 - 0
operation-backend/src/main/java/com/idiot/operationbackend/handler/SyncUserTask.java

@@ -0,0 +1,33 @@
+package com.idiot.operationbackend.handler;
+
+import com.idiot.operationbackend.service.facade.WeChatService;
+
+import java.util.List;
+
+/**
+ * @author wang xiao
+ * @date Created in 18:41 2020/9/14
+ */
+public class SyncUserTask {
+
+    private String accountId;
+
+    private  List<String> openIds;
+
+    private WeChatService weChatService;
+
+
+
+    /**
+     * 卸载这里原因是 jdk 代理 异步方法不生效
+     */
+    public void  doSyncUserTask () {
+        weChatService.syncUserTask(accountId,openIds);
+    }
+
+    public SyncUserTask(String accountId, List<String> openIds, WeChatService weChatService) {
+        this.accountId = accountId;
+        this.openIds = openIds;
+        this.weChatService = weChatService;
+    }
+}

+ 72 - 0
operation-backend/src/main/java/com/idiot/operationbackend/mappers/AccountFansStatMapper.java

@@ -0,0 +1,72 @@
+package com.idiot.operationbackend.mappers;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.idiot.operationbackend.entity.AccountFansStat;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Result;
+import org.apache.ibatis.annotations.Results;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * @author wang xiao
+ * @date Created in 14:52 2020/9/15
+ */
+public interface AccountFansStatMapper extends BaseMapper<AccountFansStat> {
+
+    /**
+     *  查询 两个日期的统计数据
+     * @author wangxiao
+     * @date 10:33 2020/9/16
+     * @param accountId
+     * @param stateDate
+     * @param beforeDate
+     * @return java.util.List<com.idiot.operationbackend.entity.AccountFansStat>
+     */
+    @Select("SELECT " +
+            " t.id, " +
+            " t.account_id, " +
+            " t.stat_date, " +
+            " t.new_num, " +
+            " t.cancel_num, " +
+            " t.inactive_num, " +
+            " t.total_fans_num, " +
+            " t.add_num  " +
+            "FROM " +
+            " t_account_fans_stat t  " +
+            "WHERE " +
+            " t.account_id =  #{accountId}  " +
+            " AND t.stat_date = #{stateDate} " +
+            " UNION " +
+            "SELECT " +
+            " t.id, " +
+            " t.account_id, " +
+            " t.stat_date, " +
+            " t.new_num, " +
+            " t.cancel_num, " +
+            " t.inactive_num, " +
+            " t.total_fans_num, " +
+            " t.add_num  " +
+            "FROM " +
+            " t_account_fans_stat t  " +
+            "WHERE " +
+            " t.account_id = #{accountId}  " +
+            " AND t.stat_date = #{beforeDate} ")
+    @Results(id = "AccountFansStatMap",value = {
+            @Result(column = "id",id = true,property = "id"),
+            @Result(column = "account_id",property = "accountId"),
+            @Result(column = "new_num",property = "newNum"),
+            @Result(column = "cancel_num",property = "cancelNum"),
+            @Result(column = "inactive_num",property = "inactiveNum"),
+            @Result(column = "total_fans_num",property = "totalFansNum"),
+            @Result(column = "add_num",property = "addNum"),
+            @Result(column = "stat_date",property = "statDate"),
+    })
+    List<AccountFansStat> selectByDateAndAccId(@Param("accountId") String accountId, @Param("stateDate") String stateDate,
+                                               @Param("beforeDate")  String beforeDate);
+
+
+
+
+}

+ 11 - 0
operation-backend/src/main/java/com/idiot/operationbackend/mappers/AccountTagMapper.java

@@ -0,0 +1,11 @@
+package com.idiot.operationbackend.mappers;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.idiot.operationbackend.entity.AccountTag;
+
+
+/**
+ *   @author wang xiao
+ */
+public interface AccountTagMapper extends BaseMapper<AccountTag> {
+}

+ 13 - 0
operation-backend/src/main/java/com/idiot/operationbackend/mappers/FansActionStatMapper.java

@@ -0,0 +1,13 @@
+package com.idiot.operationbackend.mappers;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.idiot.operationbackend.entity.FansActionStat;
+
+
+/**
+ * @author wang xiao
+ * @date Created in 11:32 2020/9/16
+ */
+public interface FansActionStatMapper extends BaseMapper<FansActionStat> {
+
+}

+ 11 - 0
operation-backend/src/main/java/com/idiot/operationbackend/mappers/SubscribeSceneMapper.java

@@ -0,0 +1,11 @@
+package com.idiot.operationbackend.mappers;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.idiot.operationbackend.entity.SubscribeScene;
+
+/**
+ * @author wang xiao
+ * @date Created in 17:48 2020/9/16
+ */
+public interface SubscribeSceneMapper extends BaseMapper<SubscribeScene> {
+}

+ 35 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/facade/AccountFansService.java

@@ -3,9 +3,44 @@ package com.idiot.operationbackend.service.facade;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.idiot.operationbackend.entity.AccountFans;
 
+import java.util.List;
+
 /**
  * @author wang xiao
  * @date Created in 19:24 2020/9/10
  */
 public interface AccountFansService extends IService<AccountFans> {
+
+
+    /**
+     *  openId和公众号id  查询用户
+     * @author wangxiao
+     * @date 18:59 2020/9/14
+     * @param accountId
+     * @param openId
+     * @return com.idiot.operationbackend.entity.AccountFans
+     */
+    AccountFans queryByAccountIdAndOpenId (String accountId,String openId);
+
+
+    /**
+     *  统计粉丝总数目
+     * @author wangxiao
+     * @date 14:26 2020/9/16
+     * @param accountId
+     * @return int
+     */
+    int countFansByAccountId(String accountId);
+
+
+    /**
+     *  查询一段时间内的粉丝
+     * @author wangxiao
+     * @date 18:39 2020/9/16
+     * @param accountId
+     * @param startDate
+     * @param endDate
+     * @return java.util.List<com.idiot.operationbackend.entity.AccountFans>
+     */
+    List<AccountFans> queryFansByDateAndDate (String accountId,String startDate,String endDate);
 }

+ 56 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/facade/AccountFansStatService.java

@@ -0,0 +1,56 @@
+package com.idiot.operationbackend.service.facade;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.idiot.operationbackend.entity.AccountFansStat;
+
+import java.util.List;
+
+/**
+ * @author wang xiao
+ * @date Created in 14:53 2020/9/15
+ */
+public interface AccountFansStatService extends IService<AccountFansStat> {
+
+    /**
+     *  查询 公众号列表下的 粉丝统计
+     * @author wangxiao
+     * @date 15:18 2020/9/15
+     * @param accountIds
+     * @param statDate
+     * @return java.util.List<com.idiot.operationbackend.entity.AccountFansStat>
+     */
+    List<AccountFansStat> queryByDateAndAccIds(List<String> accountIds,String statDate);
+
+
+    /**
+     *  单独查询公众号列表下粉丝统计
+     * @author wangxiao
+     * @date 15:21 2020/9/15
+     * @param accountId
+     * @param statDate
+     * @return com.idiot.operationbackend.entity.AccountFansStat
+     */
+    AccountFansStat queryByDateAndAccountId(String accountId,String statDate);
+
+
+
+    /**
+     *  统计粉丝数据
+     * @author wangxiao
+     * @date 17:19 2020/9/15
+     * @param accountId
+     * @return void
+     */
+    void  statAccountFansData (String accountId);
+
+
+    /**
+     * 昨日活跃数量
+     * @author wangxiao
+     * @date 10:28 2020/9/16
+     * @param accountId
+     * @param statDate
+     * @return int
+     */
+    int inactiveFansNum (String accountId,String statDate);
+}

+ 8 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/facade/AccountService.java

@@ -51,4 +51,12 @@ public interface AccountService extends IService<Account> {
      * @return java.util.List<com.idiot.operationbackend.entity.Account>
      */
     List<Account> queryAccountBuSubUserIds(List<String> userIds);
+
+    /**
+     *  查询所有已经认证成功的公众号
+     * @author wangxiao
+     * @date 17:43 2020/9/15
+     * @return java.util.List<com.idiot.operationbackend.entity.Account>
+     */
+    List<Account> queryAccount();
 }

+ 53 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/facade/AccountTagService.java

@@ -0,0 +1,53 @@
+package com.idiot.operationbackend.service.facade;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.idiot.operationbackend.entity.AccountTag;
+
+import java.util.List;
+
+/**
+ * 用户标签
+ * @author wangxiao
+ */
+public interface AccountTagService extends IService<AccountTag> {
+
+    /**
+     *  覆盖之前的标签
+     * @author wangxiao
+     * @date 10:06 2020/9/15
+     * @param accountId
+     * @param newTags
+     * @return boolean
+     */
+    boolean overlayAccountTag(String accountId, List<AccountTag> newTags);
+
+
+    /**
+     *  查询标签
+     * @author wangxiao
+     * @date 10:10 2020/9/15
+     * @param accountId
+     * @return java.util.List<com.idiot.operationbackend.entity.AccountTag>
+     */
+    List<AccountTag> queryAccountTag(String accountId);
+
+
+    /**
+     *  新增
+     * @author wangxiao
+     * @date 10:29 2020/9/15
+     * @param accountTags
+     * @return boolean
+     */
+    boolean addAccountTag (List<AccountTag> accountTags);
+
+
+    /**
+     *  删除
+     * @author wangxiao
+     * @date 10:29 2020/9/15
+     * @param accountTags
+     * @return boolean
+     */
+    boolean delAccountTag (List<AccountTag> accountTags);
+}

+ 67 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/facade/FansActionStatService.java

@@ -0,0 +1,67 @@
+package com.idiot.operationbackend.service.facade;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.idiot.operationbackend.entity.FansActionStat;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+
+/**
+ * @author wang xiao
+ * @date Created in 11:33 2020/9/16
+ */
+public interface FansActionStatService extends IService<FansActionStat> {
+
+    /**
+     * 统计区间内 粉丝活跃数量 (秒级时间戳)
+     *
+     * @param accountId
+     * @param startDate
+     * @param endDate
+     * @return int
+     * @author wangxiao
+     * @date 11:39 2020/9/16
+     */
+    int countInactiveFansNum(String accountId, long startDate, long endDate);
+
+
+    /**
+     * 查询区间内 粉丝活跃数量 (秒级时间戳)
+     *
+     * @param accountId
+     * @param startDate
+     * @param endDate
+     * @return int
+     * @author wangxiao
+     * @date 11:39 2020/9/16
+     */
+    List<FansActionStat> queryFansActionStat(String accountId, String startDate, String endDate);
+
+
+    /**
+     * 查询区间内 粉丝活跃数量 (秒级时间戳)
+     *
+     * @param accountId
+     * @param startDate
+     * @param endDate
+     * @return int
+     * @author wangxiao
+     * @date 11:39 2020/9/16
+     */
+    List<FansActionStat> queryFansActionStat(String accountId, LocalDateTime startDate, LocalDateTime endDate);
+
+
+
+    /**
+     * 查询区间内 粉丝活跃数量 (秒级时间戳)
+     *
+     * @param accountId
+     * @param start
+     * @param end
+     * @return int
+     * @author wangxiao
+     * @date 11:39 2020/9/16
+     */
+    List<FansActionStat> queryFansActionStat(String accountId, long start, long end);
+}

+ 11 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/facade/SubscribeSceneService.java

@@ -0,0 +1,11 @@
+package com.idiot.operationbackend.service.facade;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.idiot.operationbackend.entity.SubscribeScene;
+
+/**
+ * @author wang xiao
+ * @date Created in 17:48 2020/9/16
+ */
+public interface SubscribeSceneService extends IService<SubscribeScene> {
+}

+ 81 - 10
operation-backend/src/main/java/com/idiot/operationbackend/service/facade/WeChatService.java

@@ -29,16 +29,7 @@ public interface WeChatService {
     String notice(Map<String,String> param);
 
 
-  /**
-   * 不支持的消息
-   * @author wangxiao
-   * @date 15:27 2020/7/1
-   * @param param
-   * @return java.lang.String
-   */
-  default String unSupportedMessage(Map<String,String> param) {
-    return Constants.SUCCESS;
-  }
+
 
 
 
@@ -151,6 +142,18 @@ public interface WeChatService {
   void cacheAuthorizerAccessToken(String accountId,String authorizerAccessToken);
 
 
+  /**
+   *  同步用户数据
+   * @author wangxiao
+   * @date 18:51 2020/9/14
+   * @param accountId
+   * @param openIds
+   * @return void
+   */
+  void syncUserTask(String accountId,List<String> openIds);
+
+
+
   /**
    *  同步用户
    * @author wangxiao
@@ -160,6 +163,74 @@ public interface WeChatService {
    */
   int syncAccountUser(String accountId);
 
+  /**
+   *  同步标签信息
+   * @author wangxiao
+   * @date 20:16 2020/9/14
+   * @param accountId
+   * @return void
+   */
+  void syncTag(String accountId);
+
+
+  /**
+   * 获取粉丝信息
+   * @author wangxiao
+   * @date 19:09 2020/9/14
+   * @param accountId
+   * @param openId
+   * @return java.lang.String
+   */
+  String getFansInfo(String accountId,String openId);
+
+
+  /**
+   *  微信授权后确认公众号
+   * @author wangxiao
+   * @date 20:12 2020/9/14
+   * @param accountId
+   * @param userId
+   * @return boolean
+   */
+  boolean confirmAccount(String accountId,String userId);
+
+
+  /**
+   *  获取微信用户增减数据
+   * @author wangxiao
+   * @date 17:57 2020/9/15
+   * @param accountId
+   * @param endDate  结束日期
+   * @param startDate  开始日期
+   * @return java.lang.String
+   */
+  String getFansSunmmary(String accountId,String startDate,String endDate);
+
+
+  /**
+   *  查询汇总数据
+   * @author wangxiao
+   * @date 20:20 2020/9/15
+   * @param accountId
+   * @param startDate
+   * @param endDate
+   * @return java.lang.String
+   */
+  String getFansCumulate(String accountId,String startDate,String endDate);
+
+
+  /**
+   * 不支持的消息
+   * @author wangxiao
+   * @date 15:27 2020/7/1
+   * @param param
+   * @return java.lang.String
+   */
+  default String unSupportedMessage(Map<String,String> param) {
+    return Constants.SUCCESS;
+  }
+
+
 
   /**
    *  微信消息转换成map(非加密)

+ 29 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/impl/AccountFansServiceImpl.java

@@ -1,11 +1,16 @@
 package com.idiot.operationbackend.service.impl;
 
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.idiot.operationbackend.entity.AccountFans;
 import com.idiot.operationbackend.mappers.AccountFansMapper;
 import com.idiot.operationbackend.service.facade.AccountFansService;
+import com.idiot.operationbackend.support.Constants;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDate;
+import java.util.List;
+
 /**
  * @author wang xiao
  * @date Created in 19:24 2020/9/10
@@ -13,4 +18,28 @@ import org.springframework.stereotype.Service;
 @Service
 public class AccountFansServiceImpl extends ServiceImpl<AccountFansMapper, AccountFans>
         implements AccountFansService {
+
+
+    @Override
+    public AccountFans queryByAccountIdAndOpenId(String accountId, String openId) {
+        return getOne(Wrappers.<AccountFans>lambdaQuery().eq(AccountFans::getAccountId,accountId)
+                .eq(AccountFans::getOpenId,openId),false);
+    }
+
+
+    @Override
+    public int countFansByAccountId(String accountId) {
+        return count(Wrappers.<AccountFans>lambdaQuery().eq(AccountFans::getAccountId,accountId));
+    }
+
+
+    @Override
+    public List<AccountFans> queryFansByDateAndDate(String accountId, String startDate, String endDate) {
+        LocalDate end = LocalDate.parse(endDate).plusDays(1);
+        String endStr = end.format(Constants.DATE_FORMATTER);
+        return list(Wrappers.<AccountFans>lambdaQuery()
+                .eq(AccountFans::getAccountId,accountId)
+                .ge(AccountFans::getUpdateTime,startDate)
+                .le(AccountFans::getUpdateTime,endStr));
+    }
 }

+ 118 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/impl/AccountFansStatServiceImpl.java

@@ -0,0 +1,118 @@
+package com.idiot.operationbackend.service.impl;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.idiot.operationbackend.entity.AccountFansStat;
+import com.idiot.operationbackend.mappers.AccountFansStatMapper;
+import com.idiot.operationbackend.service.facade.AccountFansStatService;
+import com.idiot.operationbackend.service.facade.FansActionStatService;
+import com.idiot.operationbackend.service.facade.WeChatService;
+import com.idiot.operationbackend.support.Constants;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.util.List;
+
+/**
+ * 公众号数据统计
+ * @author wang xiao
+ * @date Created in 14:54 2020/9/15
+ */
+@Service
+public class AccountFansStatServiceImpl extends ServiceImpl<AccountFansStatMapper, AccountFansStat>
+        implements AccountFansStatService {
+
+
+
+    @Autowired
+    private WeChatService weChatService;
+
+
+    @Autowired
+    private FansActionStatService actionStatService;
+
+
+    @Override
+    public List<AccountFansStat> queryByDateAndAccIds(List<String> accountIds, String statDate) {
+        return list(Wrappers.<AccountFansStat>lambdaQuery().eq(AccountFansStat::getStatDate,statDate)
+                .in(AccountFansStat::getAccountId,accountIds));
+    }
+
+
+    @Override
+    public AccountFansStat queryByDateAndAccountId(String accountId, String statDate) {
+        return getOne(Wrappers.<AccountFansStat>lambdaQuery()
+                .eq(AccountFansStat::getAccountId,accountId)
+                .eq(AccountFansStat::getStatDate,statDate));
+    }
+
+
+    @Override
+    @Async("asyncExecutor")
+    public void statAccountFansData(String accountId) {
+        String yesterday = Constants.DATE_FORMATTER.format(LocalDate.now().plusDays(-1));
+        String beforeDay = Constants.DATE_FORMATTER.format(LocalDate.now().plusDays(-2));
+        String jsonStr = weChatService.getFansSunmmary(accountId,yesterday,yesterday);
+        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
+        JSONArray jsonArray = jsonObject.getJSONArray("list");
+        int size  = jsonArray.size();
+        long newNum = 0;
+        long cancelNum = 0;
+        long addNum = 0;
+        JSONObject tempObject = null;
+        // 昨日增减
+        for (int i = 0; i < size; i++) {
+            tempObject = jsonArray.getJSONObject(i);
+            newNum += tempObject.getInteger("new_user");
+            cancelNum += tempObject.getInteger("cancel_user");
+        }
+        long totalNum = 0;
+        // 昨日汇总
+        String jsonStrTotal = weChatService.getFansCumulate(accountId,yesterday,yesterday);
+        JSONObject jsonObjectTotal = JSONObject.parseObject(jsonStrTotal);
+        JSONArray jsonArrayTotal = jsonObjectTotal.getJSONArray("list");
+        totalNum = jsonArrayTotal.getJSONObject(0).getInteger("cumulate_user");
+
+        // 昨日和前日 数据
+        List<AccountFansStat> statData = baseMapper.selectByDateAndAccId(accountId,yesterday,beforeDay);
+        // 昨日活跃数量
+        long inactiveNum =  inactiveFansNum(accountId,yesterday);
+        AccountFansStat ydData = statData.stream().filter(e->yesterday.equals(e.getStatDate())).findFirst().orElseGet(AccountFansStat::new);
+        addNum = newNum-cancelNum;
+        ydData.setTotalFansNum(totalNum);
+        ydData.setNewNum(newNum);
+        ydData.setCancelNum(cancelNum);
+        ydData.setStatDate(yesterday);
+        ydData.setAccountId(accountId);
+        ydData.setAddNum(addNum);
+        ydData.setInactiveNum(inactiveNum);
+
+        statData.stream().filter(e->beforeDay.equals(e.getStatDate())).findFirst().map(e->{
+            ydData.setAddRate(Constants.calcRate(ydData.getAddNum(), e.getAddNum()));
+            ydData.setNewRate(Constants.calcRate(ydData.getNewNum(), e.getNewNum()));
+            ydData.setCancelRate(Constants.calcRate(ydData.getCancelNum(), e.getCancelNum()));
+            ydData.setInactiveRate(Constants.calcRate(ydData.getInactiveNum(), e.getInactiveNum()));
+            ydData.setPageReadRate(Constants.calcRate(ydData.getPageReadNum(),e.getPageReadNum()));
+            ydData.setInactiveRate(Constants.calcRate(ydData.getInactiveNum(),e.getInactiveNum()));
+            return e;
+        });
+        saveOrUpdate(ydData);
+    }
+
+
+    @Override
+    public int inactiveFansNum(String accountId, String statDate) {
+        LocalDate localDate = LocalDate.parse(statDate,Constants.DATE_FORMATTER);
+        long start =  localDate.atTime(LocalTime.of(0,0,0)).toInstant(Constants.DEFAULT_ZONE).getEpochSecond();
+        long end = localDate.atTime(LocalTime.of(23,59,59)).toInstant(Constants.DEFAULT_ZONE).getEpochSecond();
+        return actionStatService.countInactiveFansNum(accountId,start,end);
+    }
+
+
+
+}

+ 12 - 1
operation-backend/src/main/java/com/idiot/operationbackend/service/impl/AccountServiceImpl.java

@@ -1,10 +1,12 @@
 package com.idiot.operationbackend.service.impl;
 
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.idiot.operationbackend.entity.Account;
 import com.idiot.operationbackend.mappers.AccountMapper;
 import com.idiot.operationbackend.service.facade.AccountService;
+import com.idiot.operationbackend.support.CustomException;
 import org.springframework.stereotype.Service;
 
 import java.util.List;
@@ -40,7 +42,11 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account>
 
     @Override
     public List<Account> queryAccountByUserId(String userId) {
-        return list(Wrappers.<Account>lambdaQuery().eq(Account::getCreateUserId,userId));
+        List<Account> list =  list(Wrappers.<Account>lambdaQuery().eq(Account::getCreateUserId,userId));
+        if (CollectionUtils.isEmpty(list)) {
+            throw new CustomException(501,"当前暂无认证公众号,或无权查看!请前往公众号管理授权认证");
+        }
+        return list;
     }
 
 
@@ -48,4 +54,9 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account>
     public List<Account> queryAccountBuSubUserIds(List<String> userIds) {
         return list(Wrappers.<Account>lambdaQuery().in(Account::getCreateUserId,userIds));
     }
+
+    @Override
+    public List<Account> queryAccount() {
+        return list(Wrappers.<Account>lambdaQuery().eq(Account::getState,"1"));
+    }
 }

+ 75 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/impl/AccountTagServiceImpl.java

@@ -0,0 +1,75 @@
+package com.idiot.operationbackend.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.idiot.operationbackend.entity.Account;
+import com.idiot.operationbackend.entity.AccountTag;
+import com.idiot.operationbackend.mappers.AccountTagMapper;
+import com.idiot.operationbackend.service.facade.AccountTagService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+/**
+ * @author wang xiao
+ */
+
+@Service
+public class AccountTagServiceImpl  extends ServiceImpl<AccountTagMapper,AccountTag>
+        implements AccountTagService {
+
+    private final Logger logger = LoggerFactory.getLogger(AccountTagServiceImpl.class);
+
+
+    @Override
+    public boolean overlayAccountTag(String accountId, List<AccountTag> newTags) {
+        logger.info("同步粉丝标签 保存/删除数据库 --------start");
+        List<AccountTag> preData = queryAccountTag(accountId);
+        List<AccountTag> addTag = newTags.stream().filter(e->!ifHave(preData,e.getWxId())).collect(Collectors.toList());
+        List<AccountTag> delTag = preData.stream().filter(e->!ifHave(newTags,e.getWxId())).collect(Collectors.toList());
+        boolean ifAdd = addAccountTag(addTag);
+        boolean ifDel = delAccountTag(delTag);
+        logger.info("同步粉丝标签 保存/删除数据库 ---------end,add 结果:{},del 结果:{}",ifAdd,ifDel);
+        return ifAdd & ifDel;
+    }
+
+
+    @Override
+    public List<AccountTag> queryAccountTag(String accountId) {
+        return list(Wrappers.<AccountTag>lambdaQuery().eq(AccountTag::getAccountId,accountId));
+    }
+
+
+    @Override
+    public boolean addAccountTag(List<AccountTag> accountTags) {
+        if (CollectionUtils.isEmpty(accountTags)) {
+            return false;
+        }
+        return saveBatch(accountTags);
+    }
+
+    @Override
+    public boolean delAccountTag(List<AccountTag> accountTags) {
+        if (CollectionUtils.isEmpty(accountTags)) {
+            return false;
+        }
+        List<String> ids = accountTags.stream().map(AccountTag::getId).collect(Collectors.toList());
+        return removeByIds(ids);
+    }
+
+    private boolean ifHave(List<AccountTag> accountTags, Integer wxId) {
+        if (CollectionUtils.isEmpty(accountTags)) {
+            return false;
+        }
+        return accountTags.stream().anyMatch(e->wxId.equals(e.getWxId()));
+    }
+
+
+
+
+}

+ 63 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/impl/FansActionStatServiceImpl.java

@@ -0,0 +1,63 @@
+package com.idiot.operationbackend.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.idiot.operationbackend.entity.FansActionStat;
+import com.idiot.operationbackend.mappers.FansActionStatMapper;
+import com.idiot.operationbackend.service.facade.FansActionStatService;
+import com.idiot.operationbackend.support.Constants;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.List;
+
+/**
+ * @author wang xiao
+ * @date Created in 11:33 2020/9/16
+ */
+@Service
+public class FansActionStatServiceImpl extends ServiceImpl<FansActionStatMapper, FansActionStat>
+        implements FansActionStatService {
+
+    @Override
+    public int countInactiveFansNum(String accountId, long startDate, long endDate) {
+        return count(Wrappers.<FansActionStat>lambdaQuery().eq(FansActionStat::getAccountId,accountId)
+                .ne(FansActionStat::getAction,"3")
+                .ge(FansActionStat::getCreateTime,startDate)
+                .le(FansActionStat::getCreateTime,endDate));
+    }
+
+
+    @Override
+    public List<FansActionStat> queryFansActionStat(String accountId, String startDate, String endDate) {
+        LocalDate var1 =LocalDate.parse(startDate, Constants.DATE_FORMATTER);
+        LocalDate var2 =LocalDate.parse(endDate, Constants.DATE_FORMATTER);
+        long start =  var1.atTime(LocalTime.of(0,0,0)).toInstant(Constants.DEFAULT_ZONE).getEpochSecond();
+        long end = var2.atTime(LocalTime.of(23,59,59)).toInstant(Constants.DEFAULT_ZONE).getEpochSecond();
+        return list(Wrappers.<FansActionStat>lambdaQuery().eq(FansActionStat::getAccountId,accountId)
+                .ge(FansActionStat::getCreateTime,start)
+                .le(FansActionStat::getCreateTime,end)
+                .orderByAsc(FansActionStat::getCreateTime));
+    }
+
+
+    @Override
+    public List<FansActionStat> queryFansActionStat(String accountId, LocalDateTime startDate, LocalDateTime endDate) {
+        long start =  startDate.toInstant(Constants.DEFAULT_ZONE).getEpochSecond();
+        long end = endDate.toInstant(Constants.DEFAULT_ZONE).getEpochSecond();
+        return list(Wrappers.<FansActionStat>lambdaQuery().eq(FansActionStat::getAccountId,accountId)
+                .ge(FansActionStat::getCreateTime,start)
+                .le(FansActionStat::getCreateTime,end)
+                .orderByAsc(FansActionStat::getCreateTime));
+    }
+
+    @Override
+    public List<FansActionStat> queryFansActionStat(String accountId, long start, long end) {
+          return list(Wrappers.<FansActionStat>lambdaQuery().eq(FansActionStat::getAccountId,accountId)
+                .ge(FansActionStat::getCreateTime,start)
+                .le(FansActionStat::getCreateTime,end)
+                .orderByAsc(FansActionStat::getCreateTime));
+    }
+}

+ 16 - 0
operation-backend/src/main/java/com/idiot/operationbackend/service/impl/SubscribeSceneServiceImpl.java

@@ -0,0 +1,16 @@
+package com.idiot.operationbackend.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.idiot.operationbackend.entity.SubscribeScene;
+import com.idiot.operationbackend.mappers.SubscribeSceneMapper;
+import com.idiot.operationbackend.service.facade.SubscribeSceneService;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author wang xiao
+ * @date Created in 17:49 2020/9/16
+ */
+@Service
+public class SubscribeSceneServiceImpl extends ServiceImpl<SubscribeSceneMapper, SubscribeScene>
+        implements SubscribeSceneService {
+}

+ 163 - 3
operation-backend/src/main/java/com/idiot/operationbackend/service/impl/WeChatServiceImpl.java

@@ -1,10 +1,18 @@
 package com.idiot.operationbackend.service.impl;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.google.common.cache.*;
 import com.idiot.operationbackend.config.PlatformProperties;
 import com.idiot.operationbackend.entity.Account;
+import com.idiot.operationbackend.entity.AccountFans;
+import com.idiot.operationbackend.entity.AccountTag;
+import com.idiot.operationbackend.handler.SyncUserTask;
+import com.idiot.operationbackend.service.facade.AccountFansService;
 import com.idiot.operationbackend.service.facade.AccountService;
+import com.idiot.operationbackend.service.facade.AccountTagService;
 import com.idiot.operationbackend.service.facade.WeChatService;
 import com.idiot.operationbackend.support.Constants;
 import com.idiot.operationbackend.support.CustomException;
@@ -15,6 +23,7 @@ import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.*;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
@@ -23,6 +32,8 @@ import org.springframework.web.client.RestTemplate;
 
 import javax.annotation.Resource;
 import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
@@ -59,6 +70,12 @@ public class WeChatServiceImpl implements WeChatService, InitializingBean {
     @Autowired
     private AccountService accountService;
 
+    @Autowired
+    private AccountFansService fansService;
+
+    @Autowired
+    private AccountTagService tagService;
+
 
     @Override
     public String notice(Map<String, String> param) {
@@ -256,11 +273,11 @@ public class WeChatServiceImpl implements WeChatService, InitializingBean {
 
         String lockKey = String.format(Constants.LOCK_SYNC_USER,accountId);
         String lockValue = cache.getIfPresent(lockKey);
-        if (StringUtils.isEmpty(lockValue)) {
+        if (!StringUtils.isEmpty(lockValue)) {
             throw new CustomException(500,"当前公众号正在后台同步粉丝数据,请您稍等一会!");
         }
-        cache.put(lockKey,lockValue);
-        logger.info("公众号:{}同步粉丝数据,时间:{}",accountId, LocalDateTime.now().toString());
+        cache.put(lockKey,lockKey);
+        logger.info("公众号:{}同步粉丝数据---- start,时间:{}",accountId, LocalDateTime.now().toString());
         String requestUrl = "https://api.weixin.qq.com/cgi-bin/user/get?access_token=%s&next_openid=%s";
         String nextOpenId = "";
         String accessToken = getAuthorizerAccessToken(accountId);
@@ -282,10 +299,153 @@ public class WeChatServiceImpl implements WeChatService, InitializingBean {
             total = respJsonBody.getInteger("total");
             count += respJsonBody.getInteger("count");
             nextOpenId = respJsonBody.getString("next_openid");
+            JSONArray openIdArray = respJsonBody.getJSONObject("data").getJSONArray("openid");
+            List<String> openIds = JSONArray.parseArray(openIdArray.toJSONString(),String.class);
+            new SyncUserTask(accountId,openIds,this);
         }while (count < total);
         cache.invalidate(lockKey);
+        logger.info("公众号:{}同步粉丝数据----end,时间:{}",accountId, LocalDateTime.now().toString());
         return count;
     }
 
 
+    @Override
+    @Async("asyncExecutor")
+    public void syncUserTask(String accountId, List<String> openIds) {
+
+        if (CollectionUtils.isEmpty(openIds)) {
+            return;
+        }
+        int size = openIds.size();
+        logger.info("公众号:{}同步粉丝请求粉丝信息,openId 大小:{}---- start,时间:{}",accountId,size,LocalDateTime.now().toString());
+        List<AccountFans> accountFans =  new ArrayList<>(size);
+
+        for (String openId : openIds) {
+            AccountFans fans = fansService.queryByAccountIdAndOpenId(accountId,openId);
+            if (fans == null) {
+                fans = new AccountFans();
+            }
+            JSONObject fansObject = JSON.parseObject(getFansInfo(accountId,openId));
+            fans.setAccountId(accountId);
+            fans.setOpenId(openId);
+            fans.setNickName(fansObject.getString("nickname"));
+            fans.setHeadImgUrl(fansObject.getString("headimgurl"));
+            fans.setSex(fansObject.getInteger("sex"));
+            fans.setSubscribe(fansObject.getInteger("subscribe"));
+            fans.setCity(fansObject.getString("city"));
+            fans.setProvince(fansObject.getString("province"));
+            fans.setSubscribeTime(fansObject.getLong("subscribe_time"));
+            fans.setSubscribeScene(fansObject.getString("subscribe_scene"));
+            fans.setUnionId(fansObject.getString("unionid"));
+            fans.setRemark(fansObject.getString("remark"));
+            fans.setGroupId(fansObject.getInteger("groupid"));
+            fans.setTagIdList(JSONObject.toJSONString(fansObject.getJSONObject("tagid_list")));
+            accountFans.add(fans);
+        }
+        logger.info("公众号:{}同步粉丝请求粉丝信息,openId 大小:{}---- end,时间:{}",accountId,size,LocalDateTime.now().toString());
+        //  保存用户
+        fansService.saveOrUpdateBatch(accountFans,1000);
+    }
+
+
+    @Override
+    @Async("asyncExecutor")
+    public void syncTag(String accountId) {
+
+        String lockKey = String.format(Constants.LOCK_SYNC_TAG,accountId);
+        String cacheValue = cache.getIfPresent(lockKey);
+        if (!StringUtils.isEmpty(cacheValue)) {
+            throw new CustomException(500,"");
+        }
+        cache.put(lockKey,lockKey);
+        logger.info("公众号:{}同步标签 ---- start,时间:{}",accountId,LocalDateTime.now().toString());
+        String requestUrl = "https://api.weixin.qq.com/cgi-bin/tags/get?access_token=%s";
+        String accessToken =  getAuthorizerAccessToken(accountId);
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        requestUrl = String.format(requestUrl,accessToken);
+        HttpEntity<MultiValueMap<String, String>>  entity = new HttpEntity<> (headers);
+        ResponseEntity<String> respStr = restTemplate.exchange(requestUrl,HttpMethod.GET,entity,String.class);
+        JSONArray jsonArray = JSONObject.parseObject(respStr.getBody()).getJSONArray("tags");
+        int size = jsonArray.size();
+        List<AccountTag> accountTags = new ArrayList<>(size);
+        JSONObject temp = null;
+        AccountTag tempTag = null;
+        String createTime  = Constants.DATE_TIME_FORMATTER.format(LocalDateTime.now());
+        for (int i = 0; i < size; i++) {
+            temp = jsonArray.getJSONObject(i);
+            tempTag = new AccountTag();
+            tempTag.setAccountId(accountId);
+            tempTag.setFansCount(temp.getInteger("count"));
+            tempTag.setName(temp.getString("name"));
+            tempTag.setWxId(temp.getInteger("id"));
+            tempTag.setCreateTime(createTime);
+            accountTags.add(tempTag);
+        }
+        logger.info("公众号:{}同步标签 ---- end,时间:{}",accountId,LocalDateTime.now().toString());
+        // 新增 多余的 删掉缺少的 俗称覆盖
+        boolean ifOverlay = tagService.overlayAccountTag(accountId,accountTags);
+        cache.invalidate(lockKey);
+
+    }
+
+    @Override
+    public String getFansInfo(String accountId, String openId) {
+        logger.info("查询粉丝信息,openId:{}----start,时间:{}",accountId, LocalDateTime.now().toString());
+        String requestUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=OPENID&lang=zh_CN";
+        String accessToken =  getAuthorizerAccessToken(accountId);
+        requestUrl = String.format(requestUrl,accessToken);
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        HttpEntity<MultiValueMap<String, String>>  entity = new HttpEntity<> (headers);
+        ResponseEntity<String> respStr = restTemplate.exchange(requestUrl,HttpMethod.GET,entity,String.class);
+        String jsonStr = respStr.getBody();
+        logger.info("查询粉丝信息,openId:{}----end,时间:{},微信返回:{}",accountId, LocalDateTime.now().toString(),jsonStr);
+        return jsonStr;
+    }
+
+
+    @Override
+    public boolean confirmAccount(String accountId, String userId) {
+        return accountService.updateUserIdByAccount(accountId,userId);
+    }
+
+    @Override
+    public String getFansSunmmary(String accountId,String startDate,String endDate) {
+        String requestUrl = "https://api.weixin.qq.com/datacube/getusersummary?access_token=%s";
+        String accessToken = getAuthorizerAccessToken(accountId);
+        requestUrl = String.format(requestUrl,accessToken);
+        logger.info("获取微信用户增减数据,accountId:{}----start,时间:{}",accountId, LocalDateTime.now().toString());
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        MultiValueMap<String,String> params = new LinkedMultiValueMap<>(2);
+        params.add("begin_date",startDate);
+        params.add("end_date",endDate);
+        HttpEntity<MultiValueMap<String, String>>  entity = new HttpEntity<> (params,headers);
+        ResponseEntity<String> respStr = restTemplate.exchange(requestUrl,HttpMethod.POST,entity,String.class);
+        String jsonStr = respStr.getBody();
+        logger.info("获取微信用户增减数据,accountId:{}----end,时间:{},微信返回{}",accountId,
+                LocalDateTime.now().toString(),jsonStr);
+        return jsonStr;
+    }
+
+
+    @Override
+    public String getFansCumulate(String accountId, String startDate, String endDate) {
+        String requestUrl = "https://api.weixin.qq.com/datacube/getusercumulate?access_token=%s";
+        String accessToken = getAuthorizerAccessToken(accountId);
+        requestUrl = String.format(requestUrl,accessToken);
+        logger.info("获取微信用户汇总数据,accountId:{}----start,时间:{}",accountId, LocalDateTime.now().toString());
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+        MultiValueMap<String,String> params = new LinkedMultiValueMap<>(2);
+        params.add("begin_date",startDate);
+        params.add("end_date",endDate);
+        HttpEntity<MultiValueMap<String, String>>  entity = new HttpEntity<> (params,headers);
+        ResponseEntity<String> respStr = restTemplate.exchange(requestUrl,HttpMethod.POST,entity,String.class);
+        String jsonStr = respStr.getBody();
+        logger.info("获取微信用户汇总数据,accountId:{}----end,时间:{},微信返回{}",accountId,
+                LocalDateTime.now().toString(),jsonStr);
+        return null;
+    }
 }

+ 38 - 0
operation-backend/src/main/java/com/idiot/operationbackend/support/Constants.java

@@ -1,6 +1,9 @@
 package com.idiot.operationbackend.support;
 
+import java.math.BigDecimal;
 import java.net.URLDecoder;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.util.Locale;
 
@@ -14,8 +17,12 @@ public class Constants {
 
     public static final Locale DEFAULT_LOCALE = Locale.CHINA;
 
+    public static final ZoneOffset DEFAULT_ZONE= ZoneOffset.of("+8");
+
     public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss",DEFAULT_LOCALE);
 
+    public static final DateTimeFormatter DATE_TIME_FORMATTER1 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH",DEFAULT_LOCALE);
+
     public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd",DEFAULT_LOCALE);
 
     public static final String DEFAULT_HEAD_IMG = "http://pic.51yuansu.com/pic3/cover/01/69/80/595f67c2aff1e_610.jpg";
@@ -37,4 +44,35 @@ public class Constants {
      * 同步用户锁
      */
     public static final String LOCK_SYNC_USER = "LOCK_SYNC_USER_%s";
+
+    public static final String LOCK_SYNC_TAG = "LOCK_SYNC_TAG_%s";
+
+
+    public static final BigDecimal ZERO_RATE = BigDecimal.valueOf(0.00);
+
+    /**
+     * 粉丝动作 0-粉丝消息,1-新关注,2-关注,3-取关,4-扫描二维码,5-菜单点击
+     */
+    public static final int NEW = 1;
+
+    public static final int MSG = 0;
+
+    public static final int SUBSCRIBE = 2;
+
+    public static final int CANCEL = 3;
+
+    public static final int SCAN = 4;
+
+    public static final int MENU = 5;
+
+
+    public static BigDecimal calcRate(long now, long before) {
+        if (0 == before) {
+            return BigDecimal.valueOf((now*100.0)/1.0);
+        } else if (0 == now) {
+            return Constants.ZERO_RATE;
+        }else {
+            return BigDecimal.valueOf(((now - before) * 100) / before).setScale(2);
+        }
+    }
 }

+ 60 - 0
operation-backend/src/main/java/com/idiot/operationbackend/vo/GeneralStatData.java

@@ -0,0 +1,60 @@
+package com.idiot.operationbackend.vo;
+
+
+/**
+ * 粉丝 数据统计
+ * @author wang xiao
+ * @date Created in 17:01 2020/9/15
+ */
+public class GeneralStatData {
+
+    private String dateLabel;
+
+    private Long newNum;
+
+    private Long cancelNum;
+
+    private Long addNum;
+
+    private Long inactiveNum;
+
+    public String getDateLabel() {
+        return dateLabel;
+    }
+
+    public void setDateLabel(String dateLabel) {
+        this.dateLabel = dateLabel;
+    }
+
+    public Long getNewNum() {
+        return newNum;
+    }
+
+    public void setNewNum(Long newNum) {
+        this.newNum = newNum;
+    }
+
+    public Long getCancelNum() {
+        return cancelNum;
+    }
+
+    public void setCancelNum(Long cancelNum) {
+        this.cancelNum = cancelNum;
+    }
+
+    public Long getAddNum() {
+        return addNum;
+    }
+
+    public void setAddNum(Long addNum) {
+        this.addNum = addNum;
+    }
+
+    public Long getInactiveNum() {
+        return inactiveNum;
+    }
+
+    public void setInactiveNum(Long inactiveNum) {
+        this.inactiveNum = inactiveNum;
+    }
+}

+ 23 - 26
operation-backend/src/main/resources/application-test.yml

@@ -2,7 +2,7 @@ spring:
   #  datasource
   datasource:
     driver-class-name: com.mysql.cj.jdbc.Driver
-    url: jdbc:mysql://192.168.1.142:3307/db_operation?serverTimezone=UTC&characterEncoding=utf8
+    url: jdbc:mysql://192.168.1.141:3307/db_operation?serverTimezone=UTC&characterEncoding=utf8
     username: root
     password: asdfg12345
     type: com.zaxxer.hikari.HikariDataSource
@@ -10,11 +10,12 @@ spring:
       pool-name: mysqlDataSourcePool
       maximum-pool-size: 20
       minimum-idle: 10
-      connection-timeout: 120000
-      validation-timeout: 6000
-      idle-timeout: 60000
+      connection-timeout: 30000
+      validation-timeout: 5000
+      idle-timeout: 600000
       login-timeout: 5
-      max-lifetime: 60000
+      max-lifetime: 1800000
+      connection-test-query:  select  1
 #  mybatis-plus
 mybatis-plus:
   global-config:
@@ -30,26 +31,22 @@ logging:
   level:
     root: info
     org.mybatis: debug
-#kaptcha 图形验证码配置
-kaptcha:
-  height: 50
-  width: 150
-  content:
-    length: 4
-    source: 0123456789abcdefghijklmnopqrstuvwxyz
-    space: 10
-  font:
-    color: blue
-    name: 宋体,楷体,微软雅黑
-    size: 40
-  background-color:
-    from: lightGray
-    to: white
-  border:
-    enabled: false
-weChat:
+wechat:
   # 微信第三方平台 (独立部署需要自主申请)
   platform:
-    appid: xxxxxxxxxxxx
-
-
+    # appid
+    appId: wxbc8cf6d177029077
+    # appsecret
+    appSecret: cad9594114c9473b5d9a697cfe154b33
+    # 授权事件接收URL
+    ticketUrl: http://luojigou.vip/wxoperate/wechat/notice
+    # 加解密key
+    secret: FDl8GfVXfGWwKs9LKc11xE6N2f8DM6MB8cyMm6xYsac
+    # 开发平台配置token
+    token: eNoUNRR4e7V85KLb
+    # authCallBackUrl 授权回调地址
+    authCallBack: http://luojigou.vip/wxoperate/wechat/authCallBack
+    # 消息接受地址
+    msgCallBack: http://luojigou.vip/wxoperate/wechat/msgCallBack
+# 授权确认界面地址 前端界面路由地址
+confirm-domain: https://www.xxxx

+ 4 - 2
operation-frontend/package.json

@@ -14,8 +14,9 @@
     "@ant-design-vue/pro-layout": "^0.3.12",
     "@antv/data-set": "^0.10.2",
     "ant-design-vue": "^1.6.2",
-    "axios": "^0.19.0",
+    "axios": "^0.19.2",
     "core-js": "^3.1.2",
+    "cors": "^2.8.5",
     "enquire.js": "^2.1.6",
     "lodash.clonedeep": "^4.5.0",
     "lodash.get": "^4.4.2",
@@ -35,7 +36,8 @@
     "vue-router": "^3.1.2",
     "vue-svg-component-runtime": "^1.0.1",
     "vuex": "^3.1.1",
-    "wangeditor": "^3.1.1"
+    "wangeditor": "^3.1.1",
+    "yarn": "^1.22.5"
   },
   "devDependencies": {
     "@ant-design/colors": "^3.2.1",

+ 0 - 2
operation-frontend/src/main.js

@@ -22,8 +22,6 @@ import './utils/filter' // global filter
 import './global.less'
 Vue.use(SlideVerify)
 
-Vue.config.productionTip = false
-
 // mount axios to `Vue.$http` and `this.$http`
 Vue.use(VueAxios)
 Vue.component('pro-layout', ProLayout)

+ 64 - 1
sql/dataBase.sql

@@ -55,14 +55,18 @@ CREATE TABLE `t_account_fans`  (
   `nick_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT 'nickname',
   `sex` tinyint(1) NULL DEFAULT NULL COMMENT '用户的性别,值为1时是男性,值为2时是女性,值为0时是未知',
   `city` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '城市',
+  `province` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '省份',
   `country` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '国家',
   `head_img_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT 'headimgurl',
+  `union_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
   `subscribe` tinyint(1) NULL DEFAULT NULL COMMENT '1是关注',
   `subscribe_time` bigint(0) NULL DEFAULT NULL COMMENT '关注时间',
   `subscribe_scene` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '关注的渠道来源',
-  `subscribe_scene_label` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL  COMMENT '关注的渠道来源zh',
+  `subscribe_scene_label` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '关注的渠道来源zh',
   `tag_id_list` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '标签id集合tagid_list',
   `last_inactive_time` bigint(0) NULL DEFAULT NULL COMMENT '最后互动时间',
+  `group_id` int(0) NULL DEFAULT NULL COMMENT '分组',
+  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '备注',
   `update_time` datetime(0) NULL DEFAULT NULL,
   PRIMARY KEY (`id`) USING BTREE
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '公众号粉丝' ROW_FORMAT = Dynamic;
@@ -84,5 +88,64 @@ CREATE TABLE `t_account_tag`  (
 ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '公众号标签' ROW_FORMAT = Dynamic;
 
 
+-- ----------------------------
+-- Table structure for 公众号粉丝统计
+-- ----------------------------
+DROP TABLE IF EXISTS `t_account_fans_stat`;
+CREATE TABLE `t_account_fans_stat`  (
+  `id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'id',
+  `account_id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '公众号id',
+  `new_num` int(0) NULL DEFAULT 0 COMMENT '新增数量',
+  `cancel_num` int(0) NULL DEFAULT 0 COMMENT '取关数量',
+  `inactive_num` int(0) NULL DEFAULT 0 COMMENT '活跃数量',
+  `total_fans_num` int(0) NULL DEFAULT 0 COMMENT '总粉丝数',
+  `add_num` int(0) NULL DEFAULT 0 COMMENT '净增数量',
+  `page_read_num` int(0) NULL DEFAULT 0 COMMENT '阅读数量',
+  `new_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '新增比例',
+  `cancel_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '取关比例',
+  `inactive_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '活跃数量',
+  `total_fans_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '总粉丝比例',
+  `add_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '净增比例',
+  `page_read_rate` decimal(5, 2) NULL DEFAULT NULL COMMENT '阅读比例',
+  `stat_date` date NULL DEFAULT NULL COMMENT '统计日期',
+  `create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '公众号粉丝统计' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Table structure for 粉丝动作统计
+-- ----------------------------
+DROP TABLE IF EXISTS `t_fans_action_stat`;
+CREATE TABLE `t_fans_action_stat`  (
+  `id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'id',
+  `account_id` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '公众号id',
+  `opend_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT 'openid',
+  `action` int(0) NULL DEFAULT NULL COMMENT '粉丝动作 0-粉丝消息,1-新关注,2-关注,3-取关,4-扫描二维码,5-菜单点击',
+  `create_time` bigint(0) NULL DEFAULT NULL COMMENT '创建时间 秒级',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '粉丝动作统计' ROW_FORMAT = Dynamic;
 
 
+-- ----------------------------
+-- Table structure for t_subscribe_scene
+-- ----------------------------
+DROP TABLE IF EXISTS `t_subscribe_scene`;
+CREATE TABLE `t_subscribe_scene`  (
+  `id` int(0) NOT NULL AUTO_INCREMENT,
+  `key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT 'key',
+  `label` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT 'value',
+  PRIMARY KEY (`id`) USING BTREE
+) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '粉丝关注类型' ROW_FORMAT = Dynamic;
+
+-- ----------------------------
+-- Records of 微信关注来源
+-- ----------------------------
+INSERT INTO `t_subscribe_scene` VALUES (1, 'ADD_SCENE_SEARCH', '公众号搜索');
+INSERT INTO `t_subscribe_scene` VALUES (2, 'ADD_SCENE_ACCOUNT_MIGRATION', '公众号迁移');
+INSERT INTO `t_subscribe_scene` VALUES (3, 'ADD_SCENE_PROFILE_CARD', '名片分享');
+INSERT INTO `t_subscribe_scene` VALUES (4, 'ADD_SCENE_QR_CODE', '扫描二维码');
+INSERT INTO `t_subscribe_scene` VALUES (5, 'ADD_SCENE_PROFILE_LINK', '图文页内名称点击');
+INSERT INTO `t_subscribe_scene` VALUES (6, 'ADD_SCENE_PROFILE_ITEM', '图文页右上角菜单');
+INSERT INTO `t_subscribe_scene` VALUES (7, 'ADD_SCENE_PAID', '支付后关注');
+INSERT INTO `t_subscribe_scene` VALUES (8, 'ADD_SCENE_WECHAT_ADVERTISEMENT', '微信广告');
+INSERT INTO `t_subscribe_scene` VALUES (9, 'ADD_SCENE_OTHERS', '其他');