Commit 09e200c364a05a572120f29273c30dc76edf7c63
1 parent
b9246d15
mp:完成公众号统计的用户增减数据
Showing
15 changed files
with
591 additions
and
12 deletions
yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java
| @@ -14,6 +14,9 @@ public interface ErrorCodeConstants { | @@ -14,6 +14,9 @@ public interface ErrorCodeConstants { | ||
| 14 | ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1006000001, "生成公众号二维码失败,原因:{}"); | 14 | ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1006000001, "生成公众号二维码失败,原因:{}"); |
| 15 | ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000001, "清空公众号的 API 配额失败,原因:{}"); | 15 | ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000001, "清空公众号的 API 配额失败,原因:{}"); |
| 16 | 16 | ||
| 17 | + // ========== 公众号账号 1006001000============ | ||
| 18 | + ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1006001000, "获取用户增减数据失败,原因:{}"); | ||
| 19 | + | ||
| 17 | // TODO 要处理下 | 20 | // TODO 要处理下 |
| 18 | ErrorCode COMMON_NOT_EXISTS = new ErrorCode(1006001002, "用户不存在"); | 21 | ErrorCode COMMON_NOT_EXISTS = new ErrorCode(1006001002, "用户不存在"); |
| 19 | 22 |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/MpAccountController.java
| @@ -33,14 +33,14 @@ public class MpAccountController { | @@ -33,14 +33,14 @@ public class MpAccountController { | ||
| 33 | @PostMapping("/create") | 33 | @PostMapping("/create") |
| 34 | @ApiOperation("创建公众号账号") | 34 | @ApiOperation("创建公众号账号") |
| 35 | @PreAuthorize("@ss.hasPermission('mp:account:create')") | 35 | @PreAuthorize("@ss.hasPermission('mp:account:create')") |
| 36 | - public CommonResult<Long> createWxAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) { | 36 | + public CommonResult<Long> createAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) { |
| 37 | return success(mpAccountService.createAccount(createReqVO)); | 37 | return success(mpAccountService.createAccount(createReqVO)); |
| 38 | } | 38 | } |
| 39 | 39 | ||
| 40 | @PutMapping("/update") | 40 | @PutMapping("/update") |
| 41 | @ApiOperation("更新公众号账号") | 41 | @ApiOperation("更新公众号账号") |
| 42 | @PreAuthorize("@ss.hasPermission('mp:account:update')") | 42 | @PreAuthorize("@ss.hasPermission('mp:account:update')") |
| 43 | - public CommonResult<Boolean> updateWxAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) { | 43 | + public CommonResult<Boolean> updateAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) { |
| 44 | mpAccountService.updateAccount(updateReqVO); | 44 | mpAccountService.updateAccount(updateReqVO); |
| 45 | return success(true); | 45 | return success(true); |
| 46 | } | 46 | } |
| @@ -49,7 +49,7 @@ public class MpAccountController { | @@ -49,7 +49,7 @@ public class MpAccountController { | ||
| 49 | @ApiOperation("删除公众号账号") | 49 | @ApiOperation("删除公众号账号") |
| 50 | @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) | 50 | @ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class) |
| 51 | @PreAuthorize("@ss.hasPermission('mp:account:delete')") | 51 | @PreAuthorize("@ss.hasPermission('mp:account:delete')") |
| 52 | - public CommonResult<Boolean> deleteWxAccount(@RequestParam("id") Long id) { | 52 | + public CommonResult<Boolean> deleteAccount(@RequestParam("id") Long id) { |
| 53 | mpAccountService.deleteAccount(id); | 53 | mpAccountService.deleteAccount(id); |
| 54 | return success(true); | 54 | return success(true); |
| 55 | } | 55 | } |
| @@ -58,7 +58,7 @@ public class MpAccountController { | @@ -58,7 +58,7 @@ public class MpAccountController { | ||
| 58 | @ApiOperation("获得公众号账号") | 58 | @ApiOperation("获得公众号账号") |
| 59 | @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) | 59 | @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class) |
| 60 | @PreAuthorize("@ss.hasPermission('mp:account:query')") | 60 | @PreAuthorize("@ss.hasPermission('mp:account:query')") |
| 61 | - public CommonResult<MpAccountRespVO> getWxAccount(@RequestParam("id") Long id) { | 61 | + public CommonResult<MpAccountRespVO> getAccount(@RequestParam("id") Long id) { |
| 62 | MpAccountDO wxAccount = mpAccountService.getAccount(id); | 62 | MpAccountDO wxAccount = mpAccountService.getAccount(id); |
| 63 | return success(MpAccountConvert.INSTANCE.convert(wxAccount)); | 63 | return success(MpAccountConvert.INSTANCE.convert(wxAccount)); |
| 64 | } | 64 | } |
| @@ -66,7 +66,7 @@ public class MpAccountController { | @@ -66,7 +66,7 @@ public class MpAccountController { | ||
| 66 | @GetMapping("/page") | 66 | @GetMapping("/page") |
| 67 | @ApiOperation("获得公众号账号分页") | 67 | @ApiOperation("获得公众号账号分页") |
| 68 | @PreAuthorize("@ss.hasPermission('mp:account:query')") | 68 | @PreAuthorize("@ss.hasPermission('mp:account:query')") |
| 69 | - public CommonResult<PageResult<MpAccountRespVO>> getWxAccountPage(@Valid MpAccountPageReqVO pageVO) { | 69 | + public CommonResult<PageResult<MpAccountRespVO>> getAccountPage(@Valid MpAccountPageReqVO pageVO) { |
| 70 | PageResult<MpAccountDO> pageResult = mpAccountService.getAccountPage(pageVO); | 70 | PageResult<MpAccountDO> pageResult = mpAccountService.getAccountPage(pageVO); |
| 71 | return success(MpAccountConvert.INSTANCE.convertPage(pageResult)); | 71 | return success(MpAccountConvert.INSTANCE.convertPage(pageResult)); |
| 72 | } | 72 | } |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java
| @@ -13,7 +13,7 @@ import javax.validation.constraints.*; | @@ -13,7 +13,7 @@ import javax.validation.constraints.*; | ||
| 13 | @ToString(callSuper = true) | 13 | @ToString(callSuper = true) |
| 14 | public class MpAccountUpdateReqVO extends MpAccountBaseVO { | 14 | public class MpAccountUpdateReqVO extends MpAccountBaseVO { |
| 15 | 15 | ||
| 16 | - @ApiModelProperty(value = "编号", required = true) | 16 | + @ApiModelProperty(value = "编号", required = true, example = "1024") |
| 17 | @NotNull(message = "编号不能为空") | 17 | @NotNull(message = "编号不能为空") |
| 18 | private Long id; | 18 | private Long id; |
| 19 | 19 |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/MpStatisticsController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.controller.admin.statistics; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | ||
| 4 | +import cn.iocoder.yudao.module.mp.convert.statistics.MpStatisticsConvert; | ||
| 5 | +import cn.iocoder.yudao.module.mp.service.statistics.MpStatisticsService; | ||
| 6 | +import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsGetReqVO; | ||
| 7 | +import io.swagger.annotations.Api; | ||
| 8 | +import io.swagger.annotations.ApiOperation; | ||
| 9 | +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; | ||
| 10 | +import org.springframework.security.access.prepost.PreAuthorize; | ||
| 11 | +import org.springframework.validation.annotation.Validated; | ||
| 12 | +import org.springframework.web.bind.annotation.GetMapping; | ||
| 13 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 14 | +import org.springframework.web.bind.annotation.RestController; | ||
| 15 | + | ||
| 16 | +import javax.annotation.Resource; | ||
| 17 | +import java.util.List; | ||
| 18 | + | ||
| 19 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | ||
| 20 | + | ||
| 21 | +@Api(tags = "管理后台 - 公众号统计") | ||
| 22 | +@RestController | ||
| 23 | +@RequestMapping("/mp/statistics") | ||
| 24 | +@Validated | ||
| 25 | +public class MpStatisticsController { | ||
| 26 | + | ||
| 27 | + @Resource | ||
| 28 | + private MpStatisticsService mpStatisticsService; | ||
| 29 | + | ||
| 30 | + @GetMapping("/user-summary") | ||
| 31 | + @ApiOperation("获得用户增减数据") | ||
| 32 | + @PreAuthorize("@ss.hasPermission('mp:statistics:query')") | ||
| 33 | + public CommonResult<List<WxDataCubeUserSummary>> getAccount(MpStatisticsGetReqVO getReqVO) { | ||
| 34 | + List<WxDataCubeUserSummary> list = mpStatisticsService.getUserSummary(getReqVO.getId(), getReqVO.getDate()); | ||
| 35 | + return success(MpStatisticsConvert.INSTANCE.convertList01(list)); | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | +} |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModel; | ||
| 4 | +import io.swagger.annotations.ApiModelProperty; | ||
| 5 | +import lombok.Data; | ||
| 6 | +import org.springframework.format.annotation.DateTimeFormat; | ||
| 7 | + | ||
| 8 | +import javax.validation.constraints.NotNull; | ||
| 9 | +import java.time.LocalDateTime; | ||
| 10 | + | ||
| 11 | +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; | ||
| 12 | + | ||
| 13 | +@ApiModel("管理后台 - 获得统计数据 Request VO") | ||
| 14 | +@Data | ||
| 15 | +public class MpStatisticsGetReqVO { | ||
| 16 | + | ||
| 17 | + @ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024") | ||
| 18 | + @NotNull(message = "公众号账号的编号不能为空") | ||
| 19 | + private Long id; | ||
| 20 | + | ||
| 21 | + @ApiModelProperty(value = "查询时间范围") | ||
| 22 | + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ||
| 23 | + @NotNull(message = "查询时间范围不能为空") | ||
| 24 | + private LocalDateTime[] date; | ||
| 25 | + | ||
| 26 | +} |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo; | ||
| 2 | + | ||
| 3 | +import io.swagger.annotations.ApiModel; | ||
| 4 | +import io.swagger.annotations.ApiModelProperty; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +import java.util.Date; | ||
| 8 | + | ||
| 9 | +@ApiModel("管理后台 - 某一天的用户增减数据 Response VO") | ||
| 10 | +@Data | ||
| 11 | +public class MpStatisticsUserSummaryRespVO { | ||
| 12 | + | ||
| 13 | + @ApiModelProperty(value = "日期", required = true) | ||
| 14 | + private Date refDate; | ||
| 15 | + | ||
| 16 | + @ApiModelProperty(value = "用户来源", required = true, example = "0") | ||
| 17 | + private Integer userSource; | ||
| 18 | + | ||
| 19 | + @ApiModelProperty(value = "新关注的用户数量", required = true, example = "10") | ||
| 20 | + private Integer newUser; | ||
| 21 | + | ||
| 22 | + @ApiModelProperty(value = "取消关注的用户数量", required = true, example = "20") | ||
| 23 | + private Integer cancelUser; | ||
| 24 | + | ||
| 25 | +} |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/statistics/MpStatisticsConvert.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.convert.statistics; | ||
| 2 | + | ||
| 3 | +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; | ||
| 4 | +import org.mapstruct.Mapper; | ||
| 5 | +import org.mapstruct.factory.Mappers; | ||
| 6 | + | ||
| 7 | +import java.util.List; | ||
| 8 | + | ||
| 9 | +@Mapper | ||
| 10 | +public interface MpStatisticsConvert { | ||
| 11 | + | ||
| 12 | + MpStatisticsConvert INSTANCE = Mappers.getMapper(MpStatisticsConvert.class); | ||
| 13 | + | ||
| 14 | + List<WxDataCubeUserSummary> convertList01(List<WxDataCubeUserSummary> list); | ||
| 15 | + | ||
| 16 | +} |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/DefaultMpServiceFactory.java
| @@ -38,7 +38,11 @@ public class DefaultMpServiceFactory implements MpServiceFactory { | @@ -38,7 +38,11 @@ public class DefaultMpServiceFactory implements MpServiceFactory { | ||
| 38 | /** | 38 | /** |
| 39 | * 微信 appId 与 WxMpService 的映射 | 39 | * 微信 appId 与 WxMpService 的映射 |
| 40 | */ | 40 | */ |
| 41 | - private volatile Map<String, WxMpService> mpServices; | 41 | + private volatile Map<String, WxMpService> appId2MpServices; |
| 42 | + /** | ||
| 43 | + * 公众号账号 id 与 WxMpService 的映射 | ||
| 44 | + */ | ||
| 45 | + private volatile Map<Long, WxMpService> id2MpServices; | ||
| 42 | /** | 46 | /** |
| 43 | * 微信 appId 与 WxMpMessageRouter 的映射 | 47 | * 微信 appId 与 WxMpMessageRouter 的映射 |
| 44 | */ | 48 | */ |
| @@ -62,26 +66,34 @@ public class DefaultMpServiceFactory implements MpServiceFactory { | @@ -62,26 +66,34 @@ public class DefaultMpServiceFactory implements MpServiceFactory { | ||
| 62 | 66 | ||
| 63 | @Override | 67 | @Override |
| 64 | public void init(List<MpAccountDO> list) { | 68 | public void init(List<MpAccountDO> list) { |
| 65 | - Map<String, WxMpService> mpServices = Maps.newHashMap(); | 69 | + Map<String, WxMpService> appId2MpServices = Maps.newHashMap(); |
| 70 | + Map<Long, WxMpService> id2MpServices = Maps.newHashMap(); | ||
| 66 | Map<String, WxMpMessageRouter> mpMessageRouters = Maps.newHashMap(); | 71 | Map<String, WxMpMessageRouter> mpMessageRouters = Maps.newHashMap(); |
| 67 | // 处理 list | 72 | // 处理 list |
| 68 | list.forEach(account -> { | 73 | list.forEach(account -> { |
| 69 | // 构建 WxMpService 对象 | 74 | // 构建 WxMpService 对象 |
| 70 | WxMpService mpService = buildMpService(account); | 75 | WxMpService mpService = buildMpService(account); |
| 71 | - mpServices.put(account.getAppId(), mpService); | 76 | + appId2MpServices.put(account.getAppId(), mpService); |
| 77 | + id2MpServices.put(account.getId(), mpService); | ||
| 72 | // 构建 WxMpMessageRouter 对象 | 78 | // 构建 WxMpMessageRouter 对象 |
| 73 | WxMpMessageRouter mpMessageRouter = buildMpMessageRouter(mpService); | 79 | WxMpMessageRouter mpMessageRouter = buildMpMessageRouter(mpService); |
| 74 | mpMessageRouters.put(account.getAppId(), mpMessageRouter); | 80 | mpMessageRouters.put(account.getAppId(), mpMessageRouter); |
| 75 | }); | 81 | }); |
| 76 | 82 | ||
| 77 | // 设置到缓存 | 83 | // 设置到缓存 |
| 78 | - this.mpServices = mpServices; | 84 | + this.appId2MpServices = appId2MpServices; |
| 85 | + this.id2MpServices = id2MpServices; | ||
| 79 | this.mpMessageRouters = mpMessageRouters; | 86 | this.mpMessageRouters = mpMessageRouters; |
| 80 | } | 87 | } |
| 81 | 88 | ||
| 82 | @Override | 89 | @Override |
| 90 | + public WxMpService getMpService(Long id) { | ||
| 91 | + return id2MpServices.get(id); | ||
| 92 | + } | ||
| 93 | + | ||
| 94 | + @Override | ||
| 83 | public WxMpService getMpService(String appId) { | 95 | public WxMpService getMpService(String appId) { |
| 84 | - return mpServices.get(appId); | 96 | + return appId2MpServices.get(appId); |
| 85 | } | 97 | } |
| 86 | 98 | ||
| 87 | @Override | 99 | @Override |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/MpServiceFactory.java
| @@ -22,6 +22,20 @@ public interface MpServiceFactory { | @@ -22,6 +22,20 @@ public interface MpServiceFactory { | ||
| 22 | void init(List<MpAccountDO> list); | 22 | void init(List<MpAccountDO> list); |
| 23 | 23 | ||
| 24 | /** | 24 | /** |
| 25 | + * 获得 id 对应的 WxMpService 实例 | ||
| 26 | + * | ||
| 27 | + * @param id 微信公众号的编号 | ||
| 28 | + * @return WxMpService 实例 | ||
| 29 | + */ | ||
| 30 | + WxMpService getMpService(Long id); | ||
| 31 | + | ||
| 32 | + default WxMpService getRequiredMpService(Long id) { | ||
| 33 | + WxMpService wxMpService = getMpService(id); | ||
| 34 | + Assert.notNull(wxMpService, "找到对应 id({}) 的 WxMpService,请核实!", id); | ||
| 35 | + return wxMpService; | ||
| 36 | + } | ||
| 37 | + | ||
| 38 | + /** | ||
| 25 | * 获得 appId 对应的 WxMpService 实例 | 39 | * 获得 appId 对应的 WxMpService 实例 |
| 26 | * | 40 | * |
| 27 | * @param appId 微信公众号 appId | 41 | * @param appId 微信公众号 appId |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.service.statistics; | ||
| 2 | + | ||
| 3 | +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; | ||
| 4 | + | ||
| 5 | +import java.time.LocalDateTime; | ||
| 6 | +import java.util.List; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 公众号统计 Service 接口 | ||
| 10 | + * | ||
| 11 | + * @author 芋道源码 | ||
| 12 | + */ | ||
| 13 | +public interface MpStatisticsService { | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 获取用户增减数据 | ||
| 17 | + * | ||
| 18 | + * @param id 公众号账号编号 | ||
| 19 | + * @param date 时间区间 | ||
| 20 | + * @return 用户增减数据 | ||
| 21 | + */ | ||
| 22 | + List<WxDataCubeUserSummary> getUserSummary(Long id, LocalDateTime[] date); | ||
| 23 | + | ||
| 24 | +} |
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/statistics/MpStatisticsServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.mp.service.statistics; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.date.DateUtil; | ||
| 4 | +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; | ||
| 5 | +import me.chanjar.weixin.common.error.WxErrorException; | ||
| 6 | +import me.chanjar.weixin.mp.api.WxMpService; | ||
| 7 | +import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary; | ||
| 8 | +import org.springframework.context.annotation.Lazy; | ||
| 9 | +import org.springframework.stereotype.Service; | ||
| 10 | + | ||
| 11 | +import javax.annotation.Resource; | ||
| 12 | +import java.time.LocalDateTime; | ||
| 13 | +import java.util.List; | ||
| 14 | + | ||
| 15 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 16 | +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.STATISTICS_GET_USER_SUMMARY_FAIL; | ||
| 17 | + | ||
| 18 | +/** | ||
| 19 | + * 公众号统计 Service 实现类 | ||
| 20 | + * | ||
| 21 | + * @author 芋道源码 | ||
| 22 | + */ | ||
| 23 | +@Service | ||
| 24 | +public class MpStatisticsServiceImpl implements MpStatisticsService { | ||
| 25 | + | ||
| 26 | + @Resource | ||
| 27 | + @Lazy // 延迟加载,解决循环依赖的问题 | ||
| 28 | + private MpServiceFactory mpServiceFactory; | ||
| 29 | + | ||
| 30 | + @Override | ||
| 31 | + public List<WxDataCubeUserSummary> getUserSummary(Long id, LocalDateTime[] date) { | ||
| 32 | + WxMpService mpService = mpServiceFactory.getRequiredMpService(id); | ||
| 33 | + try { | ||
| 34 | + return mpService.getDataCubeService().getUserSummary( | ||
| 35 | + DateUtil.date(date[0]), DateUtil.date(date[1])); | ||
| 36 | + } catch (WxErrorException e) { | ||
| 37 | + throw exception(STATISTICS_GET_USER_SUMMARY_FAIL, e.getError().getErrorMsg()); | ||
| 38 | + } | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + | ||
| 42 | +} |
yudao-ui-admin/src/api/mp/statistics.js
0 → 100644
| 1 | +import request from '@/utils/request' | ||
| 2 | + | ||
| 3 | +// TODO 获得公众号账号分页 | ||
| 4 | +export function getInterfaceSummary(query) { | ||
| 5 | + return request({ | ||
| 6 | + url: '/mp/account/page', | ||
| 7 | + method: 'get', | ||
| 8 | + params: query | ||
| 9 | + }) | ||
| 10 | +} | ||
| 11 | + | ||
| 12 | +// TODO 获得公众号账号分页 | ||
| 13 | +export function getUserSummary(query) { | ||
| 14 | + return request({ | ||
| 15 | + url: '/mp/statistics/user-summary', | ||
| 16 | + method: 'get', | ||
| 17 | + params: query | ||
| 18 | + }) | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +// TODO 获得公众号账号分页 | ||
| 22 | +export function getUserCumulate(query) { | ||
| 23 | + return request({ | ||
| 24 | + url: '/mp/account/page', | ||
| 25 | + method: 'get', | ||
| 26 | + params: query | ||
| 27 | + }) | ||
| 28 | +} |
yudao-ui-admin/src/utils/dateUtils.js
| @@ -32,3 +32,35 @@ export function beginOfDay(date) { | @@ -32,3 +32,35 @@ export function beginOfDay(date) { | ||
| 32 | export function endOfDay(date) { | 32 | export function endOfDay(date) { |
| 33 | return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999); | 33 | return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999); |
| 34 | } | 34 | } |
| 35 | + | ||
| 36 | +export function betweenDay(date1, date2) { | ||
| 37 | + // 适配 string 字符串的日期 | ||
| 38 | + if (typeof date1 === 'string') { | ||
| 39 | + date1 = new Date(date1); | ||
| 40 | + } | ||
| 41 | + if (typeof date2 === 'string') { | ||
| 42 | + date2 = new Date(date2); | ||
| 43 | + } | ||
| 44 | + return Math.floor((date2.getTime() - date1.getTime()) / (24 * 3600 * 1000)); | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +export function formatDate(date, fmt) { | ||
| 48 | + const o = { | ||
| 49 | + "M+": date.getMonth() + 1, //月份 | ||
| 50 | + "d+": date.getDate(), //日 | ||
| 51 | + "H+": date.getHours(), //小时 | ||
| 52 | + "m+": date.getMinutes(), //分 | ||
| 53 | + "s+": date.getSeconds(), //秒 | ||
| 54 | + "q+": Math.floor((date.getMonth() + 3) / 3), //季度 | ||
| 55 | + "S": date.getMilliseconds() //毫秒 | ||
| 56 | + }; | ||
| 57 | + if (/(y+)/.test(fmt)) { // 年份 | ||
| 58 | + fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); | ||
| 59 | + } | ||
| 60 | + for (const k in o) { | ||
| 61 | + if (new RegExp("(" + k + ")").test(fmt)) { | ||
| 62 | + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | + return fmt; | ||
| 66 | +} |
yudao-ui-admin/src/views/mp/account/index.vue
| @@ -33,7 +33,7 @@ | @@ -33,7 +33,7 @@ | ||
| 33 | <!-- <el-table-column label="消息加解密密钥" align="center" prop="aesKey"/>--> | 33 | <!-- <el-table-column label="消息加解密密钥" align="center" prop="aesKey"/>--> |
| 34 | <el-table-column label="服务器地址(URL)" align="center" prop="appId" width="360"> | 34 | <el-table-column label="服务器地址(URL)" align="center" prop="appId" width="360"> |
| 35 | <template v-slot="scope"> | 35 | <template v-slot="scope"> |
| 36 | - {{ 'http://服务端地址/mp/open/' + scope.row.appId }} | 36 | + {{ 'http://服务端地址/mp/open/' + scope.row.appId }} |
| 37 | </template> | 37 | </template> |
| 38 | </el-table-column> | 38 | </el-table-column> |
| 39 | <el-table-column label="二维码" align="center" prop="qrCodeUrl"> | 39 | <el-table-column label="二维码" align="center" prop="qrCodeUrl"> |
yudao-ui-admin/src/views/mp/statistics/index.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <div class="app-container"> | ||
| 3 | + <el-card class="box-card"> | ||
| 4 | + <div slot="header" class="clearfix"> | ||
| 5 | + <span class="demonstration">时间范围</span> | ||
| 6 | + <el-date-picker v-model="date" style="width: 260px" value-format="yyyy-MM-dd HH:mm:ss" type="daterange" | ||
| 7 | + range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" | ||
| 8 | + :picker-options="datePickerOptions" :default-time="['00:00:00', '23:59:59']" | ||
| 9 | + @change="changeDate" > | ||
| 10 | + </el-date-picker> | ||
| 11 | + </div> | ||
| 12 | + <el-row> | ||
| 13 | + <el-col :span="12"> | ||
| 14 | + <div id="userCumulateChart" :style="{width: '80%', height: '500px'}"></div> | ||
| 15 | + </el-col> | ||
| 16 | + <el-col :span="12"> | ||
| 17 | + <div id="interfaceSummaryChart" :style="{width: '80%', height: '500px'}"></div> | ||
| 18 | + </el-col> | ||
| 19 | + <el-col :span="12"> | ||
| 20 | + <div id="interfaceSummaryChart2" :style="{width: '80%', height: '500px'}"></div> | ||
| 21 | + </el-col> | ||
| 22 | + </el-row> | ||
| 23 | + | ||
| 24 | + <el-row> | ||
| 25 | + <el-col :span="12" class="card-box"> | ||
| 26 | + <el-card> | ||
| 27 | + <div slot="header"> | ||
| 28 | + <span>用户增减数据</span> | ||
| 29 | + </div> | ||
| 30 | + <div class="el-table el-table--enable-row-hover el-table--medium"> | ||
| 31 | + <div ref="userSummary" style="height: 420px" /> | ||
| 32 | + </div> | ||
| 33 | + </el-card> | ||
| 34 | + </el-col> | ||
| 35 | + | ||
| 36 | + <el-col :span="12" class="card-box"> | ||
| 37 | + <el-card> | ||
| 38 | + <div slot="header"> | ||
| 39 | + <span>内存信息</span> | ||
| 40 | + </div> | ||
| 41 | + <div class="el-table el-table--enable-row-hover el-table--medium"> | ||
| 42 | + <div ref="usedmemory" style="height: 420px" /> | ||
| 43 | + </div> | ||
| 44 | + </el-card> | ||
| 45 | + </el-col> | ||
| 46 | + </el-row> | ||
| 47 | + </el-card> | ||
| 48 | + </div> | ||
| 49 | +</template> | ||
| 50 | + | ||
| 51 | +<script> | ||
| 52 | +// 引入基本模板 | ||
| 53 | +import * as echarts from 'echarts' | ||
| 54 | + | ||
| 55 | +// 引入柱状图组件 | ||
| 56 | +require('echarts/lib/chart/bar') | ||
| 57 | +// 引入柱拆线组件 | ||
| 58 | +require('echarts/lib/chart/line') | ||
| 59 | +// 引入提示框和title组件 | ||
| 60 | +require('echarts/lib/component/tooltip') | ||
| 61 | +require('echarts/lib/component/title') | ||
| 62 | +require('echarts/lib/component/legend') | ||
| 63 | + | ||
| 64 | +import { getInterfaceSummary, getUserSummary, getUserCumulate } from '@/api/mp/statistics' | ||
| 65 | +import { datePickerOptions } from "@/utils/constants"; | ||
| 66 | +import {beginOfDay, betweenDay, endOfDay, formatDate} from "@/utils/dateUtils"; | ||
| 67 | + | ||
| 68 | +export default { | ||
| 69 | + name: 'mpStatistics', | ||
| 70 | + data() { | ||
| 71 | + return { | ||
| 72 | + date : [beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)), // -7 天 | ||
| 73 | + endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24))], // -1 天 | ||
| 74 | + | ||
| 75 | + xAxisDate: [], // X 轴的日期范围 | ||
| 76 | + seriesData1: [], | ||
| 77 | + seriesData2: [], | ||
| 78 | + seriesData3: [], | ||
| 79 | + seriesData4: [], | ||
| 80 | + seriesData5: [], | ||
| 81 | + seriesData6: [], | ||
| 82 | + seriesData7: [], | ||
| 83 | + | ||
| 84 | + userSummaryOption: { // 用户增减数据 | ||
| 85 | + color: ['#67C23A', '#e5323e'], | ||
| 86 | + legend: { | ||
| 87 | + data: ['新增用户','取消关注的用户'] | ||
| 88 | + }, | ||
| 89 | + tooltip: {}, | ||
| 90 | + xAxis: { | ||
| 91 | + data: this.xAxisDate | ||
| 92 | + }, | ||
| 93 | + yAxis: { | ||
| 94 | + minInterval: 1 | ||
| 95 | + }, | ||
| 96 | + series: [{ | ||
| 97 | + name: '新增用户', | ||
| 98 | + type: 'bar', | ||
| 99 | + label: { | ||
| 100 | + normal: { | ||
| 101 | + show: true | ||
| 102 | + } | ||
| 103 | + }, | ||
| 104 | + barGap: 0, | ||
| 105 | + data: [] | ||
| 106 | + }, { | ||
| 107 | + name: '取消关注的用户', | ||
| 108 | + type: 'bar', | ||
| 109 | + label: { | ||
| 110 | + normal: { | ||
| 111 | + show: true | ||
| 112 | + } | ||
| 113 | + }, | ||
| 114 | + data: [] | ||
| 115 | + }] | ||
| 116 | + }, | ||
| 117 | + | ||
| 118 | + // 静态变量 | ||
| 119 | + datePickerOptions: datePickerOptions, | ||
| 120 | + } | ||
| 121 | + }, | ||
| 122 | + created() { | ||
| 123 | + | ||
| 124 | + }, | ||
| 125 | + mounted: function() { | ||
| 126 | + this.getSummary() | ||
| 127 | + }, | ||
| 128 | + computed: { | ||
| 129 | + | ||
| 130 | + }, | ||
| 131 | + methods: { | ||
| 132 | + changeDate() { | ||
| 133 | + | ||
| 134 | + | ||
| 135 | + | ||
| 136 | + this.seriesData1 = [] | ||
| 137 | + this.seriesData2 = [] | ||
| 138 | + this.seriesData5 = [] | ||
| 139 | + this.seriesData6 = [] | ||
| 140 | + | ||
| 141 | + this.getSummary() | ||
| 142 | + }, | ||
| 143 | + getSummary() { | ||
| 144 | + // 必须选择 7 天内,因为公众号有时间跨度限制为 7 | ||
| 145 | + if (betweenDay(this.date[0], this.date[1]) >= 7) { | ||
| 146 | + this.$message.error('时间间隔 7 天以内,请重新选择') | ||
| 147 | + return false | ||
| 148 | + } | ||
| 149 | + this.xAxisDate = [] | ||
| 150 | + const days = betweenDay(this.date[0], this.date[1]) // 相差天数 | ||
| 151 | + for(let i = 0; i <= days; i++){ | ||
| 152 | + this.xAxisDate.push(formatDate(new Date(this.date[0].getTime() + 3600 * 1000 * 24 * i), 'yyyy-MM-dd')); | ||
| 153 | + this.seriesData2.push(0) | ||
| 154 | + this.seriesData5.push(0) | ||
| 155 | + this.seriesData6.push(0) | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | + // 用户增减数据 | ||
| 159 | + this.userSummaryOption.xAxis.data = []; | ||
| 160 | + this.userSummaryOption.series[0].data = []; | ||
| 161 | + this.userSummaryOption.series[1].data = []; | ||
| 162 | + getUserSummary({ | ||
| 163 | + id: 1, | ||
| 164 | + date: [formatDate(this.date[0], 'yyyy-MM-dd HH:mm:ss'), formatDate(this.date[1], 'yyyy-MM-dd HH:mm:ss'),] | ||
| 165 | + }).then(response => { | ||
| 166 | + this.userSummaryOption.xAxis.data = this.xAxisDate; | ||
| 167 | + // 处理数据 | ||
| 168 | + this.xAxisDate.forEach((date, index) => { | ||
| 169 | + response.data.forEach((item) => { | ||
| 170 | + // 匹配日期 | ||
| 171 | + const refDate = formatDate(new Date(item.refDate), 'yyyy-MM-dd'); | ||
| 172 | + if (refDate.indexOf(date) === -1) { | ||
| 173 | + return; | ||
| 174 | + } | ||
| 175 | + // 设置数据到对应的位置 | ||
| 176 | + this.userSummaryOption.series[0].data[index] = item.newUser; | ||
| 177 | + this.userSummaryOption.series[1].data[index] = item.cancelUser; | ||
| 178 | + }) | ||
| 179 | + }) | ||
| 180 | + // 绘制图表 | ||
| 181 | + let userSummaryChart = echarts.init(this.$refs.userSummary); | ||
| 182 | + userSummaryChart.setOption(this.userSummaryOption) | ||
| 183 | + }) | ||
| 184 | + // .catch(() => {}) | ||
| 185 | + | ||
| 186 | + // getUserCumulate({ | ||
| 187 | + // startDate: this.startDate, | ||
| 188 | + // endDate: this.endDate | ||
| 189 | + // }).then(response => { | ||
| 190 | + // response.data.forEach((item, index, arr) => { | ||
| 191 | + // this.$set(this.seriesData7, index, item.cumulateUser) | ||
| 192 | + // }) | ||
| 193 | + // // 基于准备好的dom,初始化echarts实例 | ||
| 194 | + // let userCumulateChart = echarts.init(document.getElementById('userCumulateChart')) | ||
| 195 | + // // 绘制图表 | ||
| 196 | + // userCumulateChart.setOption({ | ||
| 197 | + // title: { text: '累计用户数据' }, | ||
| 198 | + // legend: { | ||
| 199 | + // data: ['累计用户量'] | ||
| 200 | + // }, | ||
| 201 | + // xAxis: { | ||
| 202 | + // type: 'category', | ||
| 203 | + // data: this.xAxisData | ||
| 204 | + // }, | ||
| 205 | + // yAxis: { | ||
| 206 | + // type: 'value' | ||
| 207 | + // }, | ||
| 208 | + // series: [{ | ||
| 209 | + // name:'累计用户量', | ||
| 210 | + // data: this.seriesData7, | ||
| 211 | + // type: 'line', | ||
| 212 | + // smooth: true, | ||
| 213 | + // label: { | ||
| 214 | + // normal: { | ||
| 215 | + // show: true | ||
| 216 | + // } | ||
| 217 | + // } | ||
| 218 | + // }] | ||
| 219 | + // }) | ||
| 220 | + // }).catch(() => { | ||
| 221 | + // }) | ||
| 222 | + // | ||
| 223 | + // //获取接口数据 | ||
| 224 | + // getInterfaceSummary({ | ||
| 225 | + // startDate: this.startDate, | ||
| 226 | + // endDate: this.endDate | ||
| 227 | + // }).then(response => { | ||
| 228 | + // response.data.forEach((item, index, arr) => { | ||
| 229 | + // this.$set(this.seriesData1, index, item.callbackCount) | ||
| 230 | + // this.$set(this.seriesData2, index, item.maxTimeCost) | ||
| 231 | + // this.$set(this.seriesData3, index, item.totalTimeCost) | ||
| 232 | + // this.$set(this.seriesData4, index, item.failCount) | ||
| 233 | + // }) | ||
| 234 | + // // 基于准备好的dom,初始化echarts实例 | ||
| 235 | + // let interfaceSummaryChart = echarts.init(document.getElementById('interfaceSummaryChart')) | ||
| 236 | + // // 绘制图表 | ||
| 237 | + // interfaceSummaryChart.setOption({ | ||
| 238 | + // title: { text: '接口分析数据' }, | ||
| 239 | + // color: ['#67C23A', '#e5323e'], | ||
| 240 | + // legend: { | ||
| 241 | + // data: ['被动回复用户消息的次数','失败次数'] | ||
| 242 | + // }, | ||
| 243 | + // tooltip: {}, | ||
| 244 | + // xAxis: { | ||
| 245 | + // data: this.xAxisData | ||
| 246 | + // }, | ||
| 247 | + // yAxis: {}, | ||
| 248 | + // series: [{ | ||
| 249 | + // name: '被动回复用户消息的次数', | ||
| 250 | + // type: 'bar', | ||
| 251 | + // label: { | ||
| 252 | + // normal: { | ||
| 253 | + // show: true | ||
| 254 | + // } | ||
| 255 | + // }, | ||
| 256 | + // barGap: 0, | ||
| 257 | + // data: this.seriesData1 | ||
| 258 | + // }, | ||
| 259 | + // { | ||
| 260 | + // name: '失败次数', | ||
| 261 | + // type: 'bar', | ||
| 262 | + // label: { | ||
| 263 | + // normal: { | ||
| 264 | + // show: true | ||
| 265 | + // } | ||
| 266 | + // }, | ||
| 267 | + // data: this.seriesData4 | ||
| 268 | + // }] | ||
| 269 | + // }) | ||
| 270 | + // | ||
| 271 | + // // 基于准备好的dom,初始化echarts实例 | ||
| 272 | + // let interfaceSummaryChart2 = echarts.init(document.getElementById('interfaceSummaryChart2')) | ||
| 273 | + // // 绘制图表 | ||
| 274 | + // interfaceSummaryChart2.setOption({ | ||
| 275 | + // title: { text: '接口分析数据' }, | ||
| 276 | + // color: ['#E6A23C', '#409EFF'], | ||
| 277 | + // legend: { | ||
| 278 | + // data: ['最大耗时','总耗时'] | ||
| 279 | + // }, | ||
| 280 | + // tooltip: {}, | ||
| 281 | + // xAxis: { | ||
| 282 | + // data: this.xAxisData | ||
| 283 | + // }, | ||
| 284 | + // yAxis: {}, | ||
| 285 | + // series: [ | ||
| 286 | + // { | ||
| 287 | + // name: '最大耗时', | ||
| 288 | + // type: 'bar', | ||
| 289 | + // label: { | ||
| 290 | + // normal: { | ||
| 291 | + // show: true | ||
| 292 | + // } | ||
| 293 | + // }, | ||
| 294 | + // data: this.seriesData2 | ||
| 295 | + // }, | ||
| 296 | + // { | ||
| 297 | + // name: '总耗时', | ||
| 298 | + // type: 'bar', | ||
| 299 | + // label: { | ||
| 300 | + // normal: { | ||
| 301 | + // show: true | ||
| 302 | + // } | ||
| 303 | + // }, | ||
| 304 | + // data: this.seriesData3 | ||
| 305 | + // }] | ||
| 306 | + // }) | ||
| 307 | + // }).catch(() => { | ||
| 308 | + // }) | ||
| 309 | + } | ||
| 310 | + } | ||
| 311 | +} | ||
| 312 | +</script> | ||
| 313 | + | ||
| 314 | +<style lang="scss" scoped> | ||
| 315 | +.demonstration{ | ||
| 316 | + font-size: 15px; | ||
| 317 | + margin-right: 10px; | ||
| 318 | +} | ||
| 319 | +</style> |