Merge remote-tracking branch 'origin/master' into feature/springdoc

# Conflicts:
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSimpleRespVO.java
This commit is contained in:
xingyu 2023-01-18 15:39:58 +08:00
commit f2adb441cd
193 changed files with 15055 additions and 676 deletions

View File

@ -68,6 +68,7 @@
* 会员中心
* 数据报表
* 商城系统
* 微信公众号
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
>
@ -152,6 +153,21 @@ ps核心功能已经实现正在对接微信小程序中...
| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 |
| 🚀 | 大屏设计器 | 建设中... 拖拽式实现可视化数据大屏 |
### 微信公众号
| | 功能 | 描述 |
|-----|--------|-------------------------------|
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
### 商城系统
建设中...
@ -182,6 +198,8 @@ ps核心功能已经实现正在对接微信小程序中...
| `yudao-module-infra` | 基础设施的 Module 模块 |
| `yudao-module-bpm` | 工作流程的 Module 模块 |
| `yudao-module-pay` | 支付系统的 Module 模块 |
| `yudao-module-mall` | 商城系统的 Module 模块 |
| `yudao-module-mp` | 微信公众号的 Module 模块 |
| `yudao-module-visualization` | 大屏报表 Module 模块 |
### 后端

View File

@ -15,6 +15,6 @@
"appApi": "http://127.0.0.1:8888/app-api",
"appToken": "test1",
"appTenentId": "1"
"appTenantId": "1"
}
}

View File

@ -18,8 +18,9 @@
<module>yudao-module-infra</module>
<module>yudao-module-pay</module>
<!-- <module>yudao-module-bpm</module>-->
<module>yudao-module-visualization</module>
<!-- <module>yudao-module-mall</module>-->
<!-- <module>yudao-module-visualization</module>-->
<!-- <module>yudao-module-mp</module>-->
<!-- <module>yudao-module-mall</module>-->
<!-- 示例项目 -->
<module>yudao-example</module>
</modules>

274
sql/mysql/optional/mp.sql Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +0,0 @@
DROP TABLE IF EXISTS `coupon`;
CREATE TABLE `coupon`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券类型 reward-满减 discount-折扣 random-随机',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券名称',
`coupon_type_id` bigint UNSIGNED DEFAULT 0 COMMENT '优惠券类型id',
`coupon_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券编码',
`member_id` bigint UNSIGNED NOT NULL DEFAULT 0 COMMENT '领用人',
`use_order_id` bigint UNSIGNED NOT NULL DEFAULT 0 COMMENT '优惠券使用订单id',
`goods_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '适用商品类型1-全部商品可用2-指定商品可用3-指定商品不可用',
`goods_ids` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '适用商品id',
`at_least` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最小金额',
`money` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '面额',
`discount` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '1 =< 折扣 <= 9.9 当type为discount时需要添加',
`discount_limit` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最多折扣金额 当type为discount时可选择性添加',
`whether_forbid_preference` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用',
`whether_expire_notice` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启过期提醒0-不开启 1-开启',
`expire_notice_fixed_term` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '过期前N天提醒',
`whether_noticed` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否已提醒',
`state` tinyint(4) UNSIGNED NOT NULL DEFAULT 0 COMMENT '优惠券状态 1已领用未使用 2已使用 3已过期',
`get_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '获取方式1订单2.直接领取3.活动领取 4转赠 5分享获取',
`fetch_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '领取时间',
`use_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '使用时间',
`start_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '可使用的开始时间',
`end_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '有效期结束时间',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 119
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT = '优惠券';
DROP TABLE IF EXISTS `coupon_templete`;
CREATE TABLE `coupon_templete`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`type` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券类型 reward-满减 discount-折扣 random-随机',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '优惠券名称',
`coupon_name_remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '名称备注',
`image` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '优惠券图片',
`count` int(11) NOT NULL DEFAULT 0 COMMENT '发放数量',
`lead_count` int(11) NOT NULL DEFAULT 0 COMMENT '已领取数量',
`used_count` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '已使用数量',
`goods_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '适用商品类型1-全部商品可用2-指定商品可用3-指定商品不可用',
`product_ids` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '适用商品id',
`has_use_limit` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '使用门槛0-无门槛 1-有门槛',
`at_least` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '满多少元使用 0代表无限制',
`money` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '发放面额 当type为reward时需要添加',
`discount` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '1 =< 折扣 <= 9.9 当type为discount时需要添加',
`discount_limit` decimal(10, 2) NOT NULL DEFAULT 0 COMMENT '最多折扣金额 当type为discount时可选择性添加',
`min_money` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最低金额 当type为radom时需要添加',
`max_money` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '最大金额 当type为radom时需要添加',
`validity_type` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '过期类型1-时间范围过期 2-领取之日固定日期后过期 3-领取次日固定日期后过期',
`start_use_time` datetime COMMENT '使用开始日期 过期类型1时必填',
`end_use_time` datetime COMMENT '使用结束日期 过期类型1时必填',
`fixed_term` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '当validity_type为2或者3时需要添加 领取之日起或者次日N天内有效',
`whether_limitless` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否无限制0- 1是',
`max_fetch` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '每人最大领取个数',
`whether_expire_notice` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否开启过期提醒0-不开启 1-开启',
`expire_notice_fixed_term` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '过期前N天提醒',
`whether_forbid_preference` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '优惠叠加 0-不限制 1- 优惠券仅原价购买商品时可用',
`whether_show` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否显示',
`discount_order_money` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '订单的优惠总金额',
`order_money` decimal(10, 2) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用券总成交额',
`whether_forbidden` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT '是否禁止发放0- 1-',
`order_goods_num` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '使用优惠券购买的商品数量',
`status` tinyint(11) NOT NULL DEFAULT 0 COMMENT '状态1进行中2已结束-1已关闭',
`end_time` datetime COMMENT '有效日期结束时间',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 119
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT = '优惠券模板';

View File

@ -1,315 +0,0 @@
/*
Navicat Premium Data Transfer
Source Server : 127.0.0.1 MySQL
Source Server Type : MySQL
Source Server Version : 80026
Source Host : localhost:3306
Source Schema : ruoyi-vue-pro
Target Server Type : MySQL
Target Server Version : 80026
File Encoding : 65001
Date: 01/08/2022 23:01:36
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for market_activity
-- ----------------------------
DROP TABLE IF EXISTS `market_activity`;
CREATE TABLE `market_activity` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '活动编号',
`title` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '活动标题',
`activity_type` tinyint NOT NULL COMMENT '活动类型',
`status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`invalid_time` datetime NULL DEFAULT NULL COMMENT '失效时间',
`delete_time` datetime NULL DEFAULT NULL COMMENT '删除时间',
`time_limited_discount` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '限制折扣字符串使用 JSON 序列化成字符串存储',
`full_privilege` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '限制折扣字符串使用 JSON 序列化成字符串存储',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '促销活动';
-- ----------------------------
-- Records of market_activity
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for market_banner
-- ----------------------------
DROP TABLE IF EXISTS `market_banner`;
CREATE TABLE `market_banner` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'Banner编号',
`title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Banner标题',
`pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图片URL',
`status` tinyint NOT NULL DEFAULT -1 COMMENT '活动状态',
`url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '跳转地址',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
`sort` tinyint NULL DEFAULT NULL COMMENT '排序',
`memo` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'Banner管理';
-- ----------------------------
-- Records of market_banner
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for member_address
-- ----------------------------
DROP TABLE IF EXISTS `member_address`;
CREATE TABLE `member_address` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '收件地址编号',
`user_id` bigint NOT NULL COMMENT '用户编号',
`name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收件人名称',
`mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号',
`area_id` bigint NOT NULL COMMENT '地区编码',
`post_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮编',
`detail_address` varchar(250) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '收件详细地址',
`defaulted` bit(1) NOT NULL COMMENT '是否默认',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_userId`(`user_id` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 21 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户收件地址';
-- ----------------------------
-- Records of member_address
-- ----------------------------
BEGIN;
INSERT INTO `member_address` (`id`, `user_id`, `name`, `mobile`, `area_id`, `post_code`, `detail_address`, `defaulted`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (21, 1, 'yunai', '15601691300', 610632, '200000', '芋道源码 233 666 ', b'1', '1', '2022-08-01 22:46:35', '1', '2022-08-01 22:46:35', b'0', 1);
COMMIT;
-- ----------------------------
-- Table structure for product_brand
-- ----------------------------
DROP TABLE IF EXISTS `product_brand`;
CREATE TABLE `product_brand` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '品牌编号',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌名称',
`pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌图片',
`sort` int NULL DEFAULT 0 COMMENT '品牌排序',
`description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '品牌描述',
`status` tinyint NOT NULL COMMENT '状态',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品品牌';
-- ----------------------------
-- Records of product_brand
-- ----------------------------
BEGIN;
INSERT INTO `product_brand` (`id`, `name`, `pic_url`, `sort`, `description`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, '苹果', 'http://test.yudao.iocoder.cn/e3726713fa56db5717c78c011762fcc7a251db12735c3581470638b8e1fa17e2.jpeg', 0, '是上市', 0, '1', '2022-07-30 22:12:18', '1', '2022-07-30 22:13:55', b'0', 1);
COMMIT;
-- ----------------------------
-- Table structure for product_category
-- ----------------------------
DROP TABLE IF EXISTS `product_category`;
CREATE TABLE `product_category` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '分类编号',
`parent_id` bigint NOT NULL COMMENT '父分类编号',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类名称',
`pic_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '分类图片',
`sort` int NULL DEFAULT 0 COMMENT '分类排序',
`description` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '分类描述',
`status` tinyint NOT NULL COMMENT '开启状态',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '商品分类';
-- ----------------------------
-- Records of product_category
-- ----------------------------
BEGIN;
INSERT INTO `product_category` (`id`, `parent_id`, `name`, `pic_url`, `sort`, `description`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 0, '电脑办公', 'http://test.yudao.iocoder.cn/122d548e1b3cd5dec72fe8075c6977a70f9cc13541a684ab3685f1b5df42f6bd.jpeg', 1, '1234', 0, '1', '2022-07-30 16:36:35', '1', '2022-07-30 20:27:16', b'0', 1), (2, 1, '笔记本', 'http://test.yudao.iocoder.cn/72713ac7b947600a019a18786ed0e6562e8692e253dbd35110a0a85c2469bbec.jpg', 1, '<p>测试一下</p>', 0, '1', '2022-07-30 16:38:09', '1', '2022-07-30 16:38:09', b'0', 1), (3, 1, '游戏本', 'http://test.yudao.iocoder.cn/287c50dd9f5f575f57329a0c57b2095be6d1aeba83867b905fe549f54a296feb.jpg', 2, '<p>测试一下</p>', 0, '1', '2022-07-30 16:39:09', '1', '2022-07-30 20:26:59', b'0', 1), (4, 0, '手机', 'http://test.yudao.iocoder.cn/e1b63900c78dbb661b3e383960cee5cfea7e1dd2fb22cff2e317ff025faaf8b2.jpeg', 2, '<p>123</p>', 0, '1', '2022-07-30 16:40:00', '1', '2022-07-30 16:40:09', b'0', 1), (5, 4, '5G手机', 'http://test.yudao.iocoder.cn/3af6557ac7def6423f046f5b2e920b644793420b466959aaa996a2e19068bbde.jpeg', 1, '<p><br></p>', 0, '1', '2022-07-30 16:43:00', '1', '2022-07-30 16:43:00', b'0', 1), (6, 4, '游戏手机', 'http://test.yudao.iocoder.cn/964fe9ccd1710d64ede261dc36d231918a017641986c15293c367f9f66d94d05.jpeg', 2, NULL, 0, '1', '2022-07-30 16:43:44', '1', '2022-07-30 16:43:44', b'0', 1), (7, 5, '厉害的 5G 手机', 'http://test.yudao.iocoder.cn/b287122f277838e8de368769b96217918605743bc45f3a29bda3cc7359dc66e1.png', 0, '123', 0, '1', '2022-07-30 20:38:09', '1', '2022-07-30 20:38:09', b'0', 1);
COMMIT;
-- ----------------------------
-- Table structure for product_property
-- ----------------------------
DROP TABLE IF EXISTS `product_property`;
CREATE TABLE `product_property` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格名称',
`status` tinyint NULL DEFAULT NULL COMMENT '状态 0 开启 1 禁用',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_name`(`name`(32) ASC) USING BTREE COMMENT '规格名称索引'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格名称';
-- ----------------------------
-- Records of product_property
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for product_property_value
-- ----------------------------
DROP TABLE IF EXISTS `product_property_value`;
CREATE TABLE `product_property_value` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`property_id` bigint NULL DEFAULT NULL COMMENT '规格键id',
`name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '规格值名字',
`status` tinyint NULL DEFAULT NULL COMMENT '状态 1 开启 2 禁用',
`create_time` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新人',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '规格值';
-- ----------------------------
-- Records of product_property_value
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for product_sku
-- ----------------------------
DROP TABLE IF EXISTS `product_sku`;
CREATE TABLE `product_sku` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`spu_id` bigint NOT NULL COMMENT 'spu编号',
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
`name` varchar(128) DEFAULT NULL COMMENT '商品 SKU 名字',
`properties` varchar(128) DEFAULT NULL COMMENT '规格值数组-json格式 [{propertId: , valueId: }, {propertId: , valueId: }]',
`price` int NOT NULL DEFAULT '-1' COMMENT '销售价格单位',
`market_price` int DEFAULT NULL COMMENT '市场价',
`cost_price` int NOT NULL DEFAULT '-1' COMMENT '成本价单位 ',
`pic_url` varchar(128) NOT NULL COMMENT '图片地址',
`stock` int DEFAULT NULL COMMENT '库存',
`warn_stock` int DEFAULT NULL COMMENT '预警库存',
`volume` double DEFAULT NULL COMMENT '商品体积',
`weight` double DEFAULT NULL COMMENT '商品重量',
`bar_code` varchar(64) DEFAULT NULL COMMENT '条形码',
`status` tinyint DEFAULT NULL COMMENT '状态 0-正常 1-禁用',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` varchar(64) DEFAULT NULL COMMENT '创建人',
`updater` double(64,0) DEFAULT NULL COMMENT '更新人',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='商品sku';
-- ----------------------------
-- Records of product_sku
-- ----------------------------
BEGIN;
COMMIT;
-- ----------------------------
-- Table structure for product_spu
-- ----------------------------
DROP TABLE IF EXISTS `product_spu`;
CREATE TABLE `product_spu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`tenant_id` bigint NOT NULL DEFAULT '0' COMMENT '租户编号',
`brand_id` bigint DEFAULT NULL COMMENT '商品品牌编号',
`category_id` bigint NOT NULL COMMENT '分类id',
`spec_type` int NOT NULL COMMENT '规格类型0 单规格 1 多规格',
`code` varchar(128) DEFAULT NULL COMMENT '商品编码',
`name` varchar(128) NOT NULL COMMENT '商品名称',
`sell_point` varchar(128) DEFAULT NULL COMMENT '卖点',
`description` text COMMENT '描述',
`pic_urls` varchar(1024) DEFAULT '' COMMENT '商品轮播图地址\n 数组以逗号分隔\n 最多上传15张',
`video_url` varchar(128) DEFAULT NULL COMMENT '商品视频',
`market_price` int DEFAULT NULL COMMENT '市场价单位使用',
`min_price` int DEFAULT NULL COMMENT '最小价格单位使用',
`max_price` int DEFAULT NULL COMMENT '最大价格单位使用',
`total_stock` int NOT NULL DEFAULT '0' COMMENT '总库存',
`show_stock` int DEFAULT '0' COMMENT '是否展示库存',
`sales_count` int DEFAULT '0' COMMENT '商品销量',
`virtual_sales_count` int DEFAULT '0' COMMENT '虚拟销量',
`click_count` int DEFAULT '0' COMMENT '商品点击量',
`status` bit(1) DEFAULT NULL COMMENT '上下架状态 0 上架开启 1 下架禁用-1 回收',
`sort` int NOT NULL DEFAULT '0' COMMENT '排序字段',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`creator` varchar(64) DEFAULT NULL COMMENT '创建人',
`updater` varchar(64) DEFAULT NULL COMMENT '更新人',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB COMMENT='商品spu';
-- ----------------------------
-- Records of product_spu
-- ----------------------------
BEGIN;
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2000, '商品中心', '', 1, 60, 0, '/product', 'merchant', NULL, 0, b'1', b'1', '', '2022-07-29 15:53:53', '1', '2022-07-30 22:26:19', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2002, '商品分类', '', 2, 2, 2000, 'category', 'dict', 'mall/product/category/index', 0, b'1', b'1', '', '2022-07-29 15:53:53', '1', '2022-07-30 22:23:37', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2003, '分类查询', 'product:category:query', 3, 1, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2004, '分类创建', 'product:category:create', 3, 2, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2005, '分类更新', 'product:category:update', 3, 3, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2006, '分类删除', 'product:category:delete', 3, 4, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-29 15:53:53', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2007, '分类导出', 'product:category:export', 3, 5, 2002, '', '', '', 0, b'1', b'1', '', '2022-07-29 15:53:53', '', '2022-07-30 13:52:13', b'1');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2008, '商品品牌', '', 2, 1, 2000, 'brand', 'dashboard', 'mall/product/brand/index', 0, b'1', b'1', '', '2022-07-30 13:52:44', '1', '2022-07-30 22:23:43', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2009, '品牌查询', 'product:brand:query', 3, 1, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2010, '品牌创建', 'product:brand:create', 3, 2, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2011, '品牌更新', 'product:brand:update', 3, 3, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2012, '品牌删除', 'product:brand:delete', 3, 4, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 13:52:44', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2013, '品牌导出', 'product:brand:export', 3, 5, 2008, '', '', '', 0, b'1', b'1', '', '2022-07-30 13:52:44', '', '2022-07-30 14:15:00', b'1');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2014, '商品管理', '', 2, 0, 2000, 'spu', 'link', 'mall/product/spu/index', 0, b'1', b'1', '', '2022-07-30 14:22:58', '1', '2022-07-30 22:26:35', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2015, '商品查询', 'product:spu:query', 3, 1, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2016, '商品创建', 'product:spu:create', 3, 2, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2017, '商品更新', 'product:spu:update', 3, 3, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2018, '商品删除', 'product:spu:delete', 3, 4, 2014, '', '', '', 0, b'1', b'1', '', '2022-07-30 14:22:58', '', '2022-07-30 14:22:58', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2019, '规格管理', '', 2, 3, 2000, 'property', '', 'mall/product/property/index', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2020, '规格查询', 'product:property:query', 3, 1, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2021, '规格创建', 'product:property:create', 3, 2, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2022, '规格更新', 'product:property:update', 3, 3, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2023, '规格删除', 'product:property:delete', 3, 4, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2024, '规格导出', 'product:property:export', 3, 5, 2019, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:55:35', '', '2022-08-01 14:55:35', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2025, 'Banner管理', '', 2, 1, 2000, 'banner', '', 'mall/market/banner/index', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2026, 'Banner查询', 'market:banner:query', 3, 1, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2027, 'Banner创建', 'market:banner:create', 3, 2, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2028, 'Banner更新', 'market:banner:update', 3, 3, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');
INSERT INTO `ruoyi-vue-pro`.`system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `status`, `visible`, `keep_alive`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES (2029, 'Banner删除', 'market:banner:delete', 3, 4, 2025, '', '', '', 0, b'1', b'1', '', '2022-08-01 14:56:14', '', '2022-08-01 14:56:14', b'0');

View File

@ -1,78 +0,0 @@
/**todo cancelType 设置默认值 0?*/
DROP TABLE IF EXISTS `trade_order`;
CREATE TABLE `trade_order`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`sn` varchar(32) NOT NULL COMMENT '订单流水号',
`type` int NOT NULL DEFAULT '0' COMMENT '订单类型[0:普通订单 1:秒杀订单 2:拼团订单 3:砍价订单]',
`terminal` int NOT NULL COMMENT '订单来源终端[1:小程序 2:H5 3:iOS 4:安卓]',
`user_id` bigint unsigned NOT NULL COMMENT '用户编号',
`user_ip` varchar(30) NOT NULL DEFAULT '' COMMENT '用户 IP',
`user_remark` varchar(200) DEFAULT NULL COMMENT '用户备注',
`status` int NOT NULL DEFAULT '0' COMMENT '订单状态[0:待付款 1:待发货 2:待收货 3:已完成 4:已关闭]',
`product_count` int NOT NULL COMMENT '购买的商品数量',
`cancel_type` int DEFAULT NULL COMMENT '取消类型[10:超时未支付 20:退款关闭 30:买家取消 40:已通过货到付款交易]',
`remark` varchar(200) DEFAULT NULL COMMENT '商家备注',
`payed` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已支付[0:未支付 1:已经支付过]',
`pay_time` datetime DEFAULT NULL COMMENT '订单支付时间',
`finish_time` datetime DEFAULT NULL COMMENT '订单完成时间',
`cancel_time` datetime DEFAULT NULL COMMENT '订单取消时间',
`sku_original_price` int NOT NULL DEFAULT '0' COMMENT '商品原价单位',
`sku_promotion_price` int NOT NULL DEFAULT '0' COMMENT '商品优惠单位',
`order_promotion_price` int NOT NULL DEFAULT '0' COMMENT '订单优惠单位',
`delivery_price` int NOT NULL DEFAULT '0' COMMENT '运费金额单位',
`pay_price` int NOT NULL DEFAULT '0' COMMENT '应付金额单位',
`pay_order_id` int DEFAULT NULL COMMENT '支付订单编号',
`pay_channel` int DEFAULT NULL COMMENT '支付成功的支付渠道',
`delivery_type` int DEFAULT NULL DEFAULT '1' COMMENT '配送方式:[1:快递发货 2:自提]',
`actual_delivery_type` int DEFAULT NULL DEFAULT '1' COMMENT '实际的配送方式:[1:快递发货 2:自提]',
`delivery_template_id` int DEFAULT NULL COMMENT '配置模板的编号',
`express_no` int DEFAULT NULL COMMENT '物流公司单号',
`delivery_status` bit(1) NOT NULL DEFAULT b'0' COMMENT '发货状态[0:未发货 1:已发货]',
`delivery_time` datetime DEFAULT NULL COMMENT '发货时间',
`receive_time` datetime DEFAULT NULL COMMENT '收货时间',
`receiver_name` varchar(20) NOT NULL COMMENT '收件人名称',
`receiver_mobile` varchar(20) NOT NULL COMMENT '收件人手机',
`receiver_area_id` int NOT NULL COMMENT '收件人地区编号',
`receiver_post_code` int DEFAULT NULL COMMENT '收件人邮编',
`receiver_detail_address` varchar(255) NOT NULL COMMENT '收件人详细地址',
`refund_status` int NOT NULL DEFAULT '0' COMMENT '订单状态[0:未退款 1:部分退款 2:全部退款]',
`refund_price` int NOT NULL DEFAULT '0' COMMENT '退款金额单位',
`coupon_id` bigint unsigned NOT NULL COMMENT '优惠劵编号',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT ='交易订单表';
DROP TABLE IF EXISTS `trade_order_item`;
CREATE TABLE `trade_order_item`
(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`user_id` bigint unsigned NOT NULL COMMENT '用户编号',
`order_Id` bigint unsigned NOT NULL COMMENT '订单编号',
`spu_id` bigint unsigned NOT NULL COMMENT '商品 SPU 编号',
`sku_id` bigint unsigned NOT NULL COMMENT '商品 SKU 编号',
`properties` json DEFAULT NULL COMMENT '规格值数组JSON 格式',
`name` varchar(128) NOT NULL DEFAULT '' COMMENT '商品名称',
`pic_url` varchar(200) DEFAULT NULL COMMENT '商品图片',
`count` int NOT NULL COMMENT '购买数量',
`commented` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否评论[0:未评论 1:已评论]',
`original_price` int NOT NULL DEFAULT '0' COMMENT '商品原价单位',
`total_original_price` int NOT NULL DEFAULT '0' COMMENT '商品原价单位',
`total_promotion_price` int NOT NULL DEFAULT '0' COMMENT '商品级优惠单位',
`present_price` int NOT NULL DEFAULT '0' COMMENT '最终购买金额单位',
`total_present_price` int NOT NULL DEFAULT '0' COMMENT '最终购买金额单位',
`total_pay_price` int NOT NULL DEFAULT '0' COMMENT '应付金额单位',
`refund_status` int NOT NULL DEFAULT '0' COMMENT '退款状态:[0:未申请退款 1:申请退款 2:等待退款 3:退款成功]',
`refund_total` int NOT NULL DEFAULT '0' COMMENT '退款总金额单位',
`creator` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updater` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB COMMENT ='交易订单明细表';

View File

@ -68,6 +68,7 @@
<justauth.version>1.4.0</justauth.version>
<jimureport.version>1.5.6</jimureport.version>
<xercesImpl.version>2.12.2</xercesImpl.version>
<wx-java-mp.version>4.3.0</wx-java-mp.version>
</properties>
<dependencyManagement>
@ -571,6 +572,12 @@
<version>${justauth.version}</version>
</dependency>
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>${wx-java-mp.version}</version>
</dependency>
<!-- 积木报表-->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.tenant.core.util;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import java.util.Map;
import java.util.concurrent.Callable;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
@ -36,6 +37,31 @@ public class TenantUtils {
}
}
/**
* 使用指定租户执行对应的逻辑
*
* 注意如果当前是忽略租户的情况下会被强制设置成不忽略租户
* 当然执行完成后还是会恢复回去
*
* @param tenantId 租户编号
* @param callable 逻辑
*/
public static <V> V execute(Long tenantId, Callable<V> callable) {
Long oldTenantId = TenantContextHolder.getTenantId();
Boolean oldIgnore = TenantContextHolder.isIgnore();
try {
TenantContextHolder.setTenantId(tenantId);
TenantContextHolder.setIgnore(false);
// 执行逻辑
return callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
TenantContextHolder.setTenantId(oldTenantId);
TenantContextHolder.setIgnore(oldIgnore);
}
}
/**
* 忽略租户执行对应的逻辑
*

View File

@ -34,6 +34,7 @@
<!-- 三方云服务相关 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<!-- <artifactId>weixin-java-mp</artifactId>-->
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>

24
yudao-module-mp/pom.xml Normal file
View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-mp</artifactId>
<packaging>pom</packaging>
<description>
wechat 模块,主要实现微信平台的相关业务。
例如:微信公众号、企业微信 SCRM 等
</description>
<modules>
<module>yudao-module-mp-api</module>
<module>yudao-module-mp-biz</module>
</modules>
</project>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao-module-mp</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-mp-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
mp 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.mp.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* Mp 错误码枚举类
*
* mp 系统使用 1-006-000-000
*/
public interface ErrorCodeConstants {
// ========== 公众号账号 1006000000============
ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1006000000, "公众号账号不存在");
ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1006000001, "生成公众号二维码失败,原因:{}");
ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1006000001, "清空公众号的 API 配额失败,原因:{}");
// ========== 公众号统计 1006001000============
ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1006001000, "获取粉丝增减数据失败,原因:{}");
ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1006001001, "获得粉丝累计数据失败,原因:{}");
ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1006001002, "获得消息发送概况数据失败,原因:{}");
ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1006001003, "获得接口分析数据失败,原因:{}");
// ========== 公众号标签 1006002000============
ErrorCode TAG_NOT_EXISTS = new ErrorCode(1006002000, "标签不存在");
ErrorCode TAG_CREATE_FAIL = new ErrorCode(1006002001, "创建标签失败,原因:{}");
ErrorCode TAG_UPDATE_FAIL = new ErrorCode(1006002001, "更新标签失败,原因:{}");
ErrorCode TAG_DELETE_FAIL = new ErrorCode(1006002001, "删除标签失败,原因:{}");
ErrorCode TAG_GET_FAIL = new ErrorCode(1006002001, "获得标签失败,原因:{}");
// ========== 公众号粉丝 1006003000============
ErrorCode USER_NOT_EXISTS = new ErrorCode(1006003000, "粉丝不存在");
ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1006003001, "更新粉丝标签失败,原因:{}");
// ========== 公众号素材 1006004000============
ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1006004000, "素材不存在");
ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1006004001, "上传素材失败,原因:{}");
ErrorCode MATERIAL_IMAGE_UPLOAD_FAIL = new ErrorCode(1006004002, "上传图片失败,原因:{}");
ErrorCode MATERIAL_DELETE_FAIL = new ErrorCode(1006004003, "删除素材失败,原因:{}");
// ========== 公众号消息 1006005000============
ErrorCode MESSAGE_SEND_FAIL = new ErrorCode(1006005000, "发送消息失败,原因:{}");
// ========== 公众号发布能力 1006006000============
ErrorCode FREE_PUBLISH_LIST_FAIL = new ErrorCode(1006006000, "获得已成功发布列表失败,原因:{}");
ErrorCode FREE_PUBLISH_SUBMIT_FAIL = new ErrorCode(1006006001, "提交发布失败,原因:{}");
ErrorCode FREE_PUBLISH_DELETE_FAIL = new ErrorCode(1006006001, "删除发布失败,原因:{}");
// ========== 公众号草稿 1006007000============
ErrorCode DRAFT_LIST_FAIL = new ErrorCode(1006007000, "获得草稿列表失败,原因:{}");
ErrorCode DRAFT_CREATE_FAIL = new ErrorCode(1006007001, "创建草稿失败,原因:{}");
ErrorCode DRAFT_UPDATE_FAIL = new ErrorCode(1006007002, "更新草稿失败,原因:{}");
ErrorCode DRAFT_DELETE_FAIL = new ErrorCode(1006007002, "删除草稿失败,原因:{}");
// ========== 公众号菜单 1006008000============
ErrorCode MENU_SAVE_FAIL = new ErrorCode(1006008000, "创建菜单失败,原因:{}");
ErrorCode MENU_DELETE_FAIL = new ErrorCode(1006008001, "删除菜单失败,原因:{}");
// ========== 公众号自动回复 1006009000============
ErrorCode AUTO_REPLY_NOT_EXISTS = new ErrorCode(1006009000, "自动回复不存在");
ErrorCode AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS = new ErrorCode(1006009001, "操作失败,原因:已存在关注时的回复");
ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1006009002, "操作失败,原因:已存在该消息类型的回复");
ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1006009003, "操作失败,原因:已关在该关键字的回复");
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mp.enums.message;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 公众号消息自动回复的匹配模式
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum MpAutoReplyMatchEnum {
ALL(1, "完全匹配"),
LIKE(2, "半匹配"),
;
/**
* 匹配
*/
private final Integer match;
/**
* 匹配的名字
*/
private final String name;
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.mp.enums.message;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 公众号消息自动回复的类型
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum MpAutoReplyTypeEnum {
SUBSCRIBE(1, "关注时回复"),
MESSAGE(2, "收到消息回复"),
KEYWORD(3, "关键词回复"),
;
/**
* 来源
*/
private final Integer type;
/**
* 类型的名字
*/
private final String name;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mp.enums.message;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 微信公众号消息的发送来源
*
* @author 芋道源码
*/
@Getter
@AllArgsConstructor
public enum MpMessageSendFromEnum {
USER_TO_MP(1, "粉丝发送给公众号"),
MP_TO_USER(2, "公众号发给粉丝"),
;
/**
* 来源
*/
private final Integer from;
/**
* 来源的名字
*/
private final String name;
}

View File

@ -0,0 +1,8 @@
/**
* mp 模块我们放微信微信公众号
* 例如说提供微信公众号的账号菜单粉丝标签消息自动回复素材模板通知运营数据等功能
*
* 1. Controller URL /mp/ 开头避免和其它 Module 冲突
* 2. DataObject 表名 mp_ 开头方便在数据库中区分
*/
package cn.iocoder.yudao.module.mp;

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>yudao-module-mp</artifactId>
<groupId>cn.iocoder.boot</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-mp-biz</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
mp 模块,我们放微信微信公众号。
例如说:提供微信公众号的账号、菜单、粉丝、标签、消息、自动回复、素材、模板通知、运营数据等功能
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-mp-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-infra-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-weixin</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mq</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,98 @@
package cn.iocoder.yudao.module.mp.controller.admin.account;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.*;
import cn.iocoder.yudao.module.mp.convert.account.MpAccountConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 公众号账号")
@RestController
@RequestMapping("/mp/account")
@Validated
public class MpAccountController {
@Resource
private MpAccountService mpAccountService;
@PostMapping("/create")
@ApiOperation("创建公众号账号")
@PreAuthorize("@ss.hasPermission('mp:account:create')")
public CommonResult<Long> createAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) {
return success(mpAccountService.createAccount(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新公众号账号")
@PreAuthorize("@ss.hasPermission('mp:account:update')")
public CommonResult<Boolean> updateAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) {
mpAccountService.updateAccount(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除公众号账号")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:account:delete')")
public CommonResult<Boolean> deleteAccount(@RequestParam("id") Long id) {
mpAccountService.deleteAccount(id);
return success(true);
}
@GetMapping("/get")
@ApiOperation("获得公众号账号")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:account:query')")
public CommonResult<MpAccountRespVO> getAccount(@RequestParam("id") Long id) {
MpAccountDO wxAccount = mpAccountService.getAccount(id);
return success(MpAccountConvert.INSTANCE.convert(wxAccount));
}
@GetMapping("/page")
@ApiOperation("获得公众号账号分页")
@PreAuthorize("@ss.hasPermission('mp:account:query')")
public CommonResult<PageResult<MpAccountRespVO>> getAccountPage(@Valid MpAccountPageReqVO pageVO) {
PageResult<MpAccountDO> pageResult = mpAccountService.getAccountPage(pageVO);
return success(MpAccountConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/list-all-simple")
@ApiOperation(value = "获取公众号账号精简信息列表")
@PreAuthorize("@ss.hasPermission('mp:account:query')")
public CommonResult<List<MpAccountSimpleRespVO>> getSimpleAccounts() {
List<MpAccountDO> list = mpAccountService.getAccountList();
return success(MpAccountConvert.INSTANCE.convertList02(list));
}
@PutMapping("/generate-qr-code")
@ApiOperation("生成公众号二维码")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:account:qr-code')")
public CommonResult<Boolean> generateAccountQrCode(@RequestParam("id") Long id) {
mpAccountService.generateAccountQrCode(id);
return success(true);
}
@PutMapping("/clear-quota")
@ApiOperation("清空公众号 API 配额")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:account:clear-quota')")
public CommonResult<Boolean> clearAccountQuota(@RequestParam("id") Long id) {
mpAccountService.clearAccountQuota(id);
return success(true);
}
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.mp.controller.admin.account.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
/**
* 公众号账号 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*
* @author fengdan
*/
@Data
public class MpAccountBaseVO {
@ApiModelProperty(value = "公众号名称", required = true, example = "芋道源码")
@NotEmpty(message = "公众号名称不能为空")
private String name;
@ApiModelProperty(value = "公众号微信号", required = true, example = "yudaoyuanma")
@NotEmpty(message = "公众号微信号不能为空")
private String account;
@ApiModelProperty(value = "公众号 appId", required = true, example = "wx5b23ba7a5589ecbb")
@NotEmpty(message = "公众号 appId 不能为空")
private String appId;
@ApiModelProperty(value = "公众号密钥", required = true, example = "3a7b3b20c537e52e74afd395eb85f61f")
@NotEmpty(message = "公众号密钥不能为空")
private String appSecret;
@ApiModelProperty(value = "公众号 token", required = true, example = "kangdayuzhen")
@NotEmpty(message = "公众号 token 不能为空")
private String token;
@ApiModelProperty(value = "加密密钥", example = "gjN+Ksei")
private String aesKey;
@ApiModelProperty(value = "备注", example = "请关注芋道源码,学习技术")
private String remark;
}

View File

@ -0,0 +1,14 @@
package cn.iocoder.yudao.module.mp.controller.admin.account.vo;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ApiModel("管理后台 - 公众号账号创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAccountCreateReqVO extends MpAccountBaseVO {
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mp.controller.admin.account.vo;
import lombok.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
@ApiModel("管理后台 - 公众号账号分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAccountPageReqVO extends PageParam {
@ApiModelProperty(value = "公众号名称", notes = "模糊匹配")
private String name;
@ApiModelProperty(value = "公众号账号", notes = "模糊匹配")
private String account;
@ApiModelProperty(value = "公众号 appid", notes = "模糊匹配")
private String appId;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.mp.controller.admin.account.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
@ApiModel("管理后台 - 公众号账号 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAccountRespVO extends MpAccountBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "二维码图片URL", example = "https://www.iocoder.cn/1024.png")
private String qrCodeUrl;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.mp.controller.admin.account.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("管理后台 - 公众号账号精简信息 Response VO")
@Data
public class MpAccountSimpleRespVO {
@ApiModelProperty(value = "编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "公众号名称", required = true, example = "芋道源码")
private String name;
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.mp.controller.admin.account.vo;
import lombok.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@ApiModel("管理后台 - 公众号账号更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAccountUpdateReqVO extends MpAccountBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1024")
@NotNull(message = "编号不能为空")
private Long id;
}

View File

@ -0,0 +1,5 @@
### 请求 /mp/material/page 接口 => 成功
GET {{baseUrl}}/mp/material/page?permanent=true&pageNo=1&pageSize=10
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.mp.controller.admin.material;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.material.vo.*;
import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 公众号素材")
@RestController
@RequestMapping("/mp/material")
@Validated
public class MpMaterialController {
@Resource
private MpMaterialService mpMaterialService;
@ApiOperation("上传临时素材")
@PostMapping("/upload-temporary")
@PreAuthorize("@ss.hasPermission('mp:material:upload-temporary')")
public CommonResult<MpMaterialUploadRespVO> uploadTemporaryMaterial(
@Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException {
MpMaterialDO material = mpMaterialService.uploadTemporaryMaterial(reqVO);
return success(MpMaterialConvert.INSTANCE.convert(material));
}
@ApiOperation("上传永久素材")
@PostMapping("/upload-permanent")
@PreAuthorize("@ss.hasPermission('mp:material:upload-permanent')")
public CommonResult<MpMaterialUploadRespVO> uploadPermanentMaterial(
@Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException {
MpMaterialDO material = mpMaterialService.uploadPermanentMaterial(reqVO);
return success(MpMaterialConvert.INSTANCE.convert(material));
}
@ApiOperation("删除素材")
@DeleteMapping("/delete-permanent")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:material:delete')")
public CommonResult<Boolean> deleteMaterial(@RequestParam("id") Long id) {
mpMaterialService.deleteMaterial(id);
return success(true);
}
@ApiOperation("上传图文内容中的图片")
@PostMapping("/upload-news-image")
@PreAuthorize("@ss.hasPermission('mp:material:upload-news-image')")
public CommonResult<String> uploadNewsImage(@Valid MpMaterialUploadNewsImageReqVO reqVO)
throws IOException {
return success(mpMaterialService.uploadNewsImage(reqVO));
}
@ApiOperation("获得素材分页")
@GetMapping("/page")
@PreAuthorize("@ss.hasPermission('mp:material:query')")
public CommonResult<PageResult<MpMaterialRespVO>> getMaterialPage(@Valid MpMaterialPageReqVO pageReqVO) {
PageResult<MpMaterialDO> pageResult = mpMaterialService.getMaterialPage(pageReqVO);
return success(MpMaterialConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号素材的分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpMaterialPageReqVO extends PageParam {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@ApiModelProperty(value = "是否永久", example = "true")
private Boolean permanent;
@ApiModelProperty(value = "文件类型", example = "image", notes = "参见 WxConsts.MediaFileType 枚举")
private String type;
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@ApiModel("管理后台 - 公众号素材 Response VO")
@Data
public class MpMaterialRespVO {
@ApiModelProperty(value = "主键", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1")
private Long accountId;
@ApiModelProperty(value = "公众号账号的 appId", required = true, example = "wx1234567890")
private String appId;
@ApiModelProperty(value = "素材的 media_id", required = true, example = "123")
private String mediaId;
@ApiModelProperty(value = "文件类型", required = true, example = "image", notes = "参见 WxConsts.MediaFileType 枚举")
private String type;
@ApiModelProperty(value = "是否永久", required = true, example = "true", notes = "true - 永久false - 临时")
private Boolean permanent;
@ApiModelProperty(value = "素材的 URL", required = true, example = "https://www.iocoder.cn/1.png")
private String url;
@ApiModelProperty(value = "名字", example = "yunai.png")
private String name;
@ApiModelProperty(value = "公众号文件 URL", example = "https://mmbiz.qpic.cn/xxx.mp3", notes = "只有【永久素材】使用")
private String mpUrl;
@ApiModelProperty(value = "视频素材的标题", example = "我是标题", notes = "只有【永久素材】使用")
private String title;
@ApiModelProperty(value = "视频素材的描述", example = "我是介绍", notes = "只有【永久素材】使用")
private String introduction;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号素材上传图文内容中的图片 Request VO")
@Data
public class MpMaterialUploadNewsImageReqVO {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@ApiModelProperty(value = "文件附件", required = true)
@NotNull(message = "文件不能为空")
@JsonIgnore // 避免被操作日志进行序列化导致报错
private MultipartFile file;
}

View File

@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import me.chanjar.weixin.common.api.WxConsts;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号素材上传永久 Request VO")
@Data
public class MpMaterialUploadPermanentReqVO {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@ApiModelProperty(value = "文件类型", required = true, example = "image", notes = "参见 WxConsts.MediaFileType 枚举")
@NotEmpty(message = "文件类型不能为空")
private String type;
@ApiModelProperty(value = "文件附件", required = true)
@NotNull(message = "文件不能为空")
@JsonIgnore // 避免被操作日志进行序列化导致报错
private MultipartFile file;
@ApiModelProperty(value = "名字", example = "wechat.mp", notes = "如果 name 为空,则使用 file 文件名")
private String name;
@ApiModelProperty(value = "视频素材的标题", example = "视频素材的标题", notes = "文件类型为 video 时,必填")
private String title;
@ApiModelProperty(value = "视频素材的描述", example = "视频素材的描述", notes = "文件类型为 video 时,必填")
private String introduction;
@AssertTrue(message = "标题不能为空")
public boolean isTitleValid() {
// 生成场景为管理后台时必须设置上级菜单不然生成的菜单 SQL 是无父级菜单的
return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO)
|| title != null;
}
@AssertTrue(message = "描述不能为空")
public boolean isIntroductionValid() {
// 生成场景为管理后台时必须设置上级菜单不然生成的菜单 SQL 是无父级菜单的
return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO)
|| introduction != null;
}
}

View File

@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("管理后台 - 公众号素材上传结果 Response VO")
@Data
public class MpMaterialUploadRespVO {
@ApiModelProperty(value = "素材的 media_id", required = true, example = "123")
private String mediaId;
@ApiModelProperty(value = "素材的 URL", required = true, example = "https://www.iocoder.cn/1.png")
private String url;
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号素材上传临时 Request VO")
@Data
public class MpMaterialUploadTemporaryReqVO {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@ApiModelProperty(value = "文件类型", required = true, example = "image", notes = "参见 WxConsts.MediaFileType 枚举")
@NotEmpty(message = "文件类型不能为空")
private String type;
@ApiModelProperty(value = "文件附件", required = true)
@NotNull(message = "文件不能为空")
@JsonIgnore // 避免被操作日志进行序列化导致报错
private MultipartFile file;
}

View File

@ -0,0 +1,50 @@
### 请求 /mp/menu/save 接口 => 成功
POST {{baseUrl}}/mp/menu/save
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"accountId": "1",
"menus": [
{
"type":"click",
"name":"今日歌曲",
"menuKey":"V1001_TODAY_MUSIC"
},
{
"name":"搜索",
"type":"view",
"url":"https://www.soso.com/"
},
{
"name": "父按钮",
"children": [
{
"type":"click",
"name":"归去来兮",
"menuKey":"MUSIC"
},
{
"name":"不说",
"type":"view",
"url":"https://www.soso.com/"
}]
}]
}
### 请求 /mp/menu/save 接口 => 成功(清空)
POST {{baseUrl}}/mp/menu/save
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"accountId": "1",
"menus": []
}
### 请求 /mp/menu/list 接口 => 成功
GET {{baseUrl}}/mp/menu/list?accountId=1
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.mp.controller.admin.menu;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;
import cn.iocoder.yudao.module.mp.convert.menu.MpMenuConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
import cn.iocoder.yudao.module.mp.service.menu.MpMenuService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 公众号菜单")
@RestController
@RequestMapping("/mp/menu")
@Validated
public class MpMenuController {
@Resource
private MpMenuService mpMenuService;
@PostMapping("/save")
@ApiOperation("保存公众号菜单")
@PreAuthorize("@ss.hasPermission('mp:menu:save')")
public CommonResult<Boolean> saveMenu(@Valid @RequestBody MpMenuSaveReqVO createReqVO) {
mpMenuService.saveMenu(createReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除公众号菜单")
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true, example = "10", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:menu:delete')")
public CommonResult<Boolean> deleteMenu(@RequestParam("accountId") Long accountId) {
mpMenuService.deleteMenuByAccountId(accountId);
return success(true);
}
@GetMapping("/list")
@ApiOperation("获得公众号菜单列表")
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true, example = "10", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:menu:query')")
public CommonResult<List<MpMenuRespVO>> getMenuList(@RequestParam("accountId") Long accountId) {
List<MpMenuDO> list = mpMenuService.getMenuListByAccountId(accountId);
return success(MpMenuConvert.INSTANCE.convertList(list));
}
}

View File

@ -0,0 +1,116 @@
package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import me.chanjar.weixin.common.api.WxConsts;
import org.hibernate.validator.constraints.URL;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
import static cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
/**
* 公众号菜单 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class MpMenuBaseVO {
/**
* 菜单名称
*/
private String name;
/**
* 菜单标识
*
* 支持多 DB 类型时无法直接使用 key + @TableField("menuKey") 来实现转换原因是 "menuKey" AS key 而存在报错
*/
private String menuKey;
/**
* 父菜单编号
*/
private Long parentId;
// ========== 按钮操作 ==========
/**
* 按钮类型
*
* 枚举 {@link WxConsts.MenuButtonType}
*/
private String type;
@ApiModelProperty(value = "网页链接", example = "https://www.iocoder.cn/")
@NotEmpty(message = "网页链接不能为空", groups = {ViewButtonGroup.class, MiniProgramButtonGroup.class})
@URL(message = "网页链接必须是 URL 格式")
private String url;
@ApiModelProperty(value = "小程序的 appId", example = "wx1234567890")
@NotEmpty(message = "小程序的 appId 不能为空", groups = MiniProgramButtonGroup.class)
private String miniProgramAppId;
@ApiModelProperty(value = "小程序的页面路径", example = "pages/index/index")
@NotEmpty(message = "小程序的页面路径不能为空", groups = MiniProgramButtonGroup.class)
private String miniProgramPagePath;
@ApiModelProperty(value ="跳转图文的媒体编号", example = "jCQk93AIIgp8ixClWcW_NXXqBKInNWNmq2XnPeDZl7IMVqWiNeL4FfELtggRXd83")
@NotEmpty(message = "跳转图文的媒体编号不能为空", groups = ViewLimitedButtonGroup.class)
private String articleId;
// ========== 消息内容 ==========
@ApiModelProperty(value = "回复的消息类型", example = "text",
notes = "枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC")
@NotEmpty(message = "回复的消息类型不能为空", groups = {ClickButtonGroup.class, ScanCodeWaitMsgButtonGroup.class})
private String replyMessageType;
@ApiModelProperty(value = "回复的消息内容", example = "欢迎关注")
@NotEmpty(message = "回复的消息内容不能为空", groups = TextMessageGroup.class)
private String replyContent;
@ApiModelProperty(value = "回复的媒体 id", example = "123456")
@NotEmpty(message = "回复的消息 mediaId 不能为空",
groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
private String replyMediaId;
@ApiModelProperty(value = "回复的媒体 URL", example = "https://www.iocoder.cn/xxx.jpg")
@NotEmpty(message = "回复的消息 mediaId 不能为空",
groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
private String replyMediaUrl;
@ApiModelProperty(value = "缩略图的媒体 id", example = "123456")
@NotEmpty(message = "回复的消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class})
private String replyThumbMediaId;
@ApiModelProperty(value = "缩略图的媒体 URL",example = "https://www.iocoder.cn/xxx.jpg")
@NotEmpty(message = "回复的消息 thumbMedia 地址不能为空", groups = {MusicMessageGroup.class})
private String replyThumbMediaUrl;
@ApiModelProperty(value = "回复的标题", example = "视频标题")
@NotEmpty(message = "回复的消息标题不能为空", groups = VideoMessageGroup.class)
private String replyTitle;
@ApiModelProperty(value = "回复的描述", example = "视频描述")
@NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class)
private String replyDescription;
/**
* 回复的图文消息数组
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@NotNull(message = "回复的图文消息不能为空", groups = {NewsMessageGroup.class, ViewLimitedButtonGroup.class})
@Valid
private List<MpMessageDO.Article> replyArticles;
@ApiModelProperty(value = "回复的音乐链接", example = "https://www.iocoder.cn/xxx.mp3")
@NotEmpty(message = "回复的音乐链接不能为空", groups = MusicMessageGroup.class)
@URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class)
private String replyMusicUrl;
@ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3")
@NotEmpty(message = "回复的高质量音乐链接不能为空", groups = MusicMessageGroup.class)
@URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class)
private String replyHqMusicUrl;
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
@ApiModel("管理后台 - 公众号菜单 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpMenuRespVO extends MpMenuBaseVO {
@ApiModelProperty(value = "主键", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
private Long accountId;
@ApiModelProperty(value = "公众号 appId", required = true, example = "wx1234567890ox")
private String appId;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.mp.controller.admin.menu.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@ApiModel("管理后台 - 公众号菜单保存 Request VO")
@Data
public class MpMenuSaveReqVO {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@NotEmpty(message = "菜单不能为空")
@Valid
private List<Menu> menus;
@ApiModel("管理后台 - 公众号菜单保存时的每个菜单")
@Data
public static class Menu extends MpMenuBaseVO {
/**
* 子菜单数组
*/
private List<Menu> children;
}
}

View File

@ -0,0 +1,5 @@
### 请求 /mp/message/page 接口 => 成功
GET {{baseUrl}}/mp/auto-reply/page?accountId=1&pageNo=1&pageSize=10
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.mp.controller.admin.message;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;
import cn.iocoder.yudao.module.mp.convert.message.MpAutoReplyConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
import cn.iocoder.yudao.module.mp.service.message.MpAutoReplyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 公众号自动回复")
@RestController
@RequestMapping("/mp/auto-reply")
@Validated
public class MpAutoReplyController {
@Resource
private MpAutoReplyService mpAutoReplyService;
@GetMapping("/page")
@ApiOperation("获得公众号自动回复分页")
@PreAuthorize("@ss.hasPermission('mp:auto-reply:query')")
public CommonResult<PageResult<MpAutoReplyRespVO>> getAutoReplyPage(@Valid MpMessagePageReqVO pageVO) {
PageResult<MpAutoReplyDO> pageResult = mpAutoReplyService.getAutoReplyPage(pageVO);
return success(MpAutoReplyConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/get")
@ApiOperation("获得公众号自动回复")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:auto-reply:query')")
public CommonResult<MpAutoReplyRespVO> getAutoReply(@RequestParam("id") Long id) {
MpAutoReplyDO autoReply = mpAutoReplyService.getAutoReply(id);
return success(MpAutoReplyConvert.INSTANCE.convert(autoReply));
}
@PostMapping("/create")
@ApiOperation("创建公众号自动回复")
@PreAuthorize("@ss.hasPermission('mp:auto-reply:create')")
public CommonResult<Long> createAutoReply(@Valid @RequestBody MpAutoReplyCreateReqVO createReqVO) {
return success(mpAutoReplyService.createAutoReply(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新公众号自动回复")
@PreAuthorize("@ss.hasPermission('mp:auto-reply:update')")
public CommonResult<Boolean> updateAutoReply(@Valid @RequestBody MpAutoReplyUpdateReqVO updateReqVO) {
mpAutoReplyService.updateAutoReply(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除公众号自动回复")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:auto-reply:delete')")
public CommonResult<Boolean> deleteAutoReply(@RequestParam("id") Long id) {
mpAutoReplyService.deleteAutoReply(id);
return success(true);
}
}

View File

@ -0,0 +1,33 @@
### 请求 /mp/message/page 接口 => 成功
GET {{baseUrl}}/mp/message/page?accountId=1&pageNo=1&pageSize=10
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 请求 /mp/message/send 接口 => 成功(文本)
POST {{baseUrl}}/mp/message/send
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"userId": 3,
"type": "text",
"content": "测试消息"
}
### 请求 /mp/message/send 接口 => 成功(音乐)
POST {{baseUrl}}/mp/message/send
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"userId": 3,
"type": "music",
"title": "测试音乐标题",
"description": "测试音乐内容",
"musicUrl": "https://www.iocoder.cn/xx.mp3",
"hqMusicUrl": "https://www.iocoder.cn/xx_high.mp3",
"thumbMediaId": "s98Iveeg9vDVFwa9q0u8-zSfdKe3xIzAm7wCrFE4WKGPIo4d9qAhtC-n6qvnyWyH"
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.mp.controller.admin.message;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;
import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import cn.iocoder.yudao.module.mp.service.message.MpMessageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 公众号消息")
@RestController
@RequestMapping("/mp/message")
@Validated
public class MpMessageController {
@Resource
private MpMessageService mpMessageService;
@GetMapping("/page")
@ApiOperation("获得公众号消息分页")
@PreAuthorize("@ss.hasPermission('mp:message:query')")
public CommonResult<PageResult<MpMessageRespVO>> getMessagePage(@Valid MpMessagePageReqVO pageVO) {
PageResult<MpMessageDO> pageResult = mpMessageService.getMessagePage(pageVO);
return success(MpMessageConvert.INSTANCE.convertPage(pageResult));
}
@PostMapping("/send")
@ApiOperation("给粉丝发送消息")
@PreAuthorize("@ss.hasPermission('mp:message:send')")
public CommonResult<MpMessageRespVO> sendMessage(@Valid @RequestBody MpMessageSendReqVO reqVO) {
MpMessageDO message = mpMessageService.sendKefuMessage(reqVO);
return success(MpMessageConvert.INSTANCE.convert(message));
}
}

View File

@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum;
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import me.chanjar.weixin.common.api.WxConsts;
import org.hibernate.validator.constraints.URL;
import javax.validation.Valid;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 公众号自动回复 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*/
@Data
public class MpAutoReplyBaseVO {
@ApiModelProperty(value = "回复类型", example = "1", notes = "参见 MpAutoReplyTypeEnum 枚举")
@NotNull(message = "回复类型不能为空")
private Integer type;
// ==================== 请求消息 ====================
@ApiModelProperty(value = "请求的关键字", example = "关键字", notes = "当 type 为 MpAutoReplyTypeEnum#KEYWORD 时,必填")
private String requestKeyword;
@ApiModelProperty(value = "请求的匹配方式", example = "1", notes = "当 type 为 MpAutoReplyTypeEnum#KEYWORD 时,必填")
private Integer requestMatch;
@ApiModelProperty(value = "请求的消息类型", example = "text", notes = "当 type 为 MpAutoReplyTypeEnum#MESSAGE 时,必填")
private String requestMessageType;
// ==================== 响应消息 ====================
@ApiModelProperty(value = "回复的消息类型", example = "text",
notes = "枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC")
@NotEmpty(message = "回复的消息类型不能为空")
private String responseMessageType;
@ApiModelProperty(value = "回复的消息内容", example = "欢迎关注")
@NotEmpty(message = "回复的消息内容不能为空", groups = TextMessageGroup.class)
private String responseContent;
@ApiModelProperty(value = "回复的媒体 id", example = "123456")
@NotEmpty(message = "回复的消息 mediaId 不能为空",
groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
private String responseMediaId;
@ApiModelProperty(value = "回复的媒体 URL", example = "https://www.iocoder.cn/xxx.jpg")
@NotEmpty(message = "回复的消息 mediaId 不能为空",
groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
private String responseMediaUrl;
@ApiModelProperty(value = "缩略图的媒体 id", example = "123456")
@NotEmpty(message = "回复的消息 thumbMediaId 不能为空", groups = {MusicMessageGroup.class})
private String responseThumbMediaId;
@ApiModelProperty(value = "缩略图的媒体 URL",example = "https://www.iocoder.cn/xxx.jpg")
@NotEmpty(message = "回复的消息 thumbMedia 地址不能为空", groups = {MusicMessageGroup.class})
private String responseThumbMediaUrl;
@ApiModelProperty(value = "回复的标题", example = "视频标题")
@NotEmpty(message = "回复的消息标题不能为空", groups = VideoMessageGroup.class)
private String responseTitle;
@ApiModelProperty(value = "回复的描述", example = "视频描述")
@NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class)
private String responseDescription;
/**
* 回复的图文消息
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@NotNull(message = "回复的图文消息不能为空", groups = {NewsMessageGroup.class, ViewLimitedButtonGroup.class})
@Valid
private List<MpMessageDO.Article> responseArticles;
@ApiModelProperty(value = "回复的音乐链接", example = "https://www.iocoder.cn/xxx.mp3")
@NotEmpty(message = "回复的音乐链接不能为空", groups = MusicMessageGroup.class)
@URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class)
private String responseMusicUrl;
@ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3")
@NotEmpty(message = "回复的高质量音乐链接不能为空", groups = MusicMessageGroup.class)
@URL(message = "回复的高质量音乐链接格式不正确", groups = MusicMessageGroup.class)
private String responseHqMusicUrl;
@AssertTrue(message = "请求的关键字不能为空")
public boolean isRequestKeywordValid() {
return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.KEYWORD)
|| requestKeyword != null;
}
@AssertTrue(message = "请求的关键字的匹配不能为空")
public boolean isRequestMatchValid() {
return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.KEYWORD)
|| requestMatch != null;
}
@AssertTrue(message = "请求的消息类型不能为空")
public boolean isRequestMessageTypeValid() {
return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.MESSAGE)
|| requestMessageType != null;
}
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号自动回复的创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAutoReplyCreateReqVO extends MpAutoReplyBaseVO {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号自动回复的分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAutoReplyPageReqVO extends PageParam {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
@ApiModel("管理后台 - 公众号自动回复 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAutoReplyRespVO extends MpAutoReplyBaseVO {
@ApiModelProperty(value = "主键", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
private Long accountId;
@ApiModelProperty(value = "公众号 appId", required = true, example = "wx1234567890")
private String appId;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号自动回复的更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAutoReplyUpdateReqVO extends MpAutoReplyBaseVO {
@ApiModelProperty(value = "主键", required = true, example = "1024")
@NotNull(message = "主键不能为空")
private Long id;
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
import lombok.*;
import io.swagger.annotations.*;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("管理后台 - 公众号消息分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpMessagePageReqVO extends PageParam {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@ApiModelProperty(value = "消息类型", example = "text", notes = "参见 WxConsts.XmlMsgType 枚举")
private String type;
@ApiModelProperty(value = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
private String openid;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@ApiModelProperty(value = "创建时间")
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,104 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import me.chanjar.weixin.common.api.WxConsts;
import java.util.Date;
import java.util.List;
@ApiModel("管理后台 - 公众号消息 Response VO")
@Data
public class MpMessageRespVO {
@ApiModelProperty(value = "主键", required = true, example = "1024")
private Integer id;
@ApiModelProperty(value = "微信公众号消息 id", required = true, example = "23953173569869169")
private Long msgId;
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1")
private Long accountId;
@ApiModelProperty(value = "公众号账号的 appid", required = true, example = "wx1234567890")
private String appId;
@ApiModelProperty(value = "公众号粉丝编号", required = true, example = "2048")
private Long userId;
@ApiModelProperty(value = "公众号粉丝标志", required = true, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
private String openid;
@ApiModelProperty(value = "消息类型", required = true, example = "text", notes = "参见 WxConsts.XmlMsgType 枚举")
private String type;
@ApiModelProperty(value = "消息来源", required = true, example = "1", notes = "参见 MpMessageSendFromEnum 枚举")
private Integer sendFrom;
// ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
@ApiModelProperty(value = "消息内容", example = "你好呀", notes = "消息类型为 text 时,才有值")
private String content;
@ApiModelProperty(value = "媒体素材的编号", example = "1234567890", notes = "消息类型为 image、voice、video 时,才有值")
private String mediaId;
@ApiModelProperty(value = "媒体文件的 URL", example = "https://www.iocoder.cn/xxx.png",
notes = "消息类型为 image、voice、video 时,才有值")
private String mediaUrl;
@ApiModelProperty(value = "语音识别后文本", example = "语音识别后文本", notes = "消息类型为 voice 时,才有值")
private String recognition;
@ApiModelProperty(value = "语音格式", example = "amr", notes = "消息类型为 voice 时,才有值")
private String format;
@ApiModelProperty(value = "标题", example = "我是标题", notes = "消息类型为 video、music、link 时,才有值")
private String title;
@ApiModelProperty(value = "描述", example = "我是描述", notes = "消息类型为 video、music 时,才有值")
private String description;
@ApiModelProperty(value = "缩略图的媒体 id", example = "1234567890", notes = "消息类型为 video、music 时,才有值")
private String thumbMediaId;
@ApiModelProperty(value = "缩略图的媒体 URL", example = "https://www.iocoder.cn/xxx.png",
notes = "消息类型为 video、music 时,才有值")
private String thumbMediaUrl;
@ApiModelProperty(value = "点击图文消息跳转链接", example = "https://www.iocoder.cn", notes = "消息类型为 link 时,才有值")
private String url;
@ApiModelProperty(value = "地理位置维度", example = "23.137466", notes = "消息类型为 location 时,才有值")
private Double locationX;
@ApiModelProperty(value = "地理位置经度", example = "113.352425", notes = "消息类型为 location 时,才有值")
private Double locationY;
@ApiModelProperty(value = "地图缩放大小", example = "13", notes = "消息类型为 location 时,才有值")
private Double scale;
@ApiModelProperty(value = "详细地址", example = "杨浦区黄兴路 221-4 号临", notes = "消息类型为 location 时,才有值")
private String label;
/**
* 图文消息数组
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
private List<MpMessageDO.Article> articles;
@ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/xxx.mp3", notes = "消息类型为 music 时,才有值")
private String musicUrl;
@ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/xxx.mp3", notes = "消息类型为 music 时,才有值")
private String hqMusicUrl;
// ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
@ApiModelProperty(value = "事件类型", example = "subscribe", notes = "参见 WxConsts.EventType 枚举")
private String event;
@ApiModelProperty(value = "事件 Key", example = "qrscene_123456", notes = "参见 WxConsts.EventType 枚举")
private String eventKey;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.mp.controller.admin.message.vo.message;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@ApiModel("管理后台 - 公众号消息发送 Request VO")
@Data
public class MpMessageSendReqVO {
@ApiModelProperty(value = "公众号粉丝的编号", required = true, example = "1024")
@NotNull(message = "公众号粉丝的编号不能为空")
private Long userId;
// ========== 消息内容 ==========
@ApiModelProperty(value = "消息类型", required = true, example = "text", notes = "TEXT/IMAGE/VOICE/VIDEO/NEWS")
@NotEmpty(message = "消息类型不能为空")
public String type;
@ApiModelProperty(value = "消息内容", required = true, example = "你好呀")
@NotEmpty(message = "消息内容不能为空", groups = TextMessageGroup.class)
private String content;
@ApiModelProperty(value = "媒体 ID", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
@NotEmpty(message = "消息内容不能为空", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})
private String mediaId;
@ApiModelProperty(value = "标题", required = true, example = "没有标题")
@NotEmpty(message = "消息内容不能为空", groups = VideoMessageGroup.class)
private String title;
@ApiModelProperty(value = "描述", required = true, example = "你猜")
@NotEmpty(message = "消息描述不能为空", groups = VideoMessageGroup.class)
private String description;
@ApiModelProperty(value = "缩略图的媒体 id", required = true, example = "qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP")
@NotEmpty(message = "缩略图的媒体 id 不能为空", groups = MusicMessageGroup.class)
private String thumbMediaId;
@ApiModelProperty(value = "图文消息", required = true)
@Valid
@NotNull(message = "图文消息不能为空", groups = NewsMessageGroup.class)
private List<MpMessageDO.Article> articles;
@ApiModelProperty(value = "音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时")
private String musicUrl;
@ApiModelProperty(value = "高质量音乐链接", example = "https://www.iocoder.cn/music.mp3", notes = "消息类型为 MUSIC 时")
private String hqMusicUrl;
}

View File

@ -0,0 +1,54 @@
### 请求 /mp/draft/page 接口 => 成功
GET {{baseUrl}}/mp/draft/page?accountId=1&pageNo=1&pageSize=10
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 请求 /mp/draft/create 接口 => 成功
POST {{baseUrl}}/mp/draft/create?accountId=1
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"articles": [
{
"title": "我是标题",
"author": "我是作者",
"digest": "我是摘要",
"content": "我是内容",
"contentSourceUrl": "https://www.iocoder.cn",
"thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn"
},
{
"title": "我是标题 2",
"author": "我是作者 2",
"digest": "我是摘要 2",
"content": "我是内容 2",
"contentSourceUrl": "https://www.iocoder.cn",
"thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn"
}
]
}
### 请求 /mp/draft/create 接口 => 成功
PUT {{baseUrl}}/mp/draft/update?accountId=1&mediaId=r6ryvl6LrxBU0miaST4Y-q-G9pdsmZw0OYG4FzHQkKfpLfEwIH51wy2bxisx8PvW
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
[{
"title": "我是标题OOO",
"author": "我是作者",
"digest": "我是摘要",
"content": "我是内容",
"contentSourceUrl": "https://www.iocoder.cn",
"thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn"
}, {
"title": "我是标题XXX",
"author": "我是作者",
"digest": "我是摘要",
"content": "我是内容",
"contentSourceUrl": "https://www.iocoder.cn",
"thumbMediaId": "r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn"
}]

View File

@ -0,0 +1,141 @@
package cn.iocoder.yudao.module.mp.controller.admin.news;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.mp.controller.admin.news.vo.MpDraftPageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.draft.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
@Api(tags = "管理后台 - 公众号草稿")
@RestController
@RequestMapping("/mp/draft")
@Validated
public class MpDraftController {
@Resource
private MpServiceFactory mpServiceFactory;
@Resource
private MpMaterialService mpMaterialService;
@GetMapping("/page")
@ApiOperation("获得草稿分页")
@PreAuthorize("@ss.hasPermission('mp:draft:query')")
public CommonResult<PageResult<WxMpDraftItem>> getDraftPage(MpDraftPageReqVO reqVO) {
// 从公众号查询草稿箱
WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());
WxMpDraftList draftList;
try {
draftList = mpService.getDraftService().listDraft(PageUtils.getStart(reqVO), reqVO.getPageSize());
} catch (WxErrorException e) {
throw exception(DRAFT_LIST_FAIL, e.getError().getErrorMsg());
}
// 查询对应的图片地址目的解决公众号的图片链接无法在我们后台展示
setDraftThumbUrl(draftList.getItems());
// 返回分页
return success(new PageResult<>(draftList.getItems(), draftList.getTotalCount().longValue()));
}
private void setDraftThumbUrl(List<WxMpDraftItem> items) {
// 1.1 获得 mediaId 数组
Set<String> mediaIds = new HashSet<>();
items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> mediaIds.add(newsItem.getThumbMediaId())));
if (CollUtil.isEmpty(mediaIds)) {
return;
}
// 1.2 批量查询对应的 Media 素材
Map<String, MpMaterialDO> materials = CollectionUtils.convertMap(mpMaterialService.getMaterialListByMediaId(mediaIds),
MpMaterialDO::getMediaId);
// 2. 设置回 WxMpDraftItem 记录
items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem ->
findAndThen(materials, newsItem.getThumbMediaId(), material -> newsItem.setThumbUrl(material.getUrl()))));
}
@PostMapping("/create")
@ApiOperation("创建草稿")
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true,
example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:draft:create')")
public CommonResult<String> deleteDraft(@RequestParam("accountId") Long accountId,
@RequestBody WxMpAddDraft draft) {
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
try {
String mediaId = mpService.getDraftService().addDraft(draft);
return success(mediaId);
} catch (WxErrorException e) {
throw exception(DRAFT_CREATE_FAIL, e.getError().getErrorMsg());
}
}
@PutMapping("/update")
@ApiOperation("更新草稿")
@ApiImplicitParams({
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true,
example = "1024", dataTypeClass = Long.class),
@ApiImplicitParam(name = "mediaId", value = "草稿素材的编号", required = true,
example = "xxx", dataTypeClass = String.class),
})
@PreAuthorize("@ss.hasPermission('mp:draft:update')")
public CommonResult<Boolean> deleteDraft(@RequestParam("accountId") Long accountId,
@RequestParam("mediaId") String mediaId,
@RequestBody List<WxMpDraftArticles> articles) {
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
try {
for (int i = 0; i < articles.size(); i++) {
WxMpDraftArticles article = articles.get(i);
mpService.getDraftService().updateDraft(new WxMpUpdateDraft(mediaId, i, article));
}
return success(true);
} catch (WxErrorException e) {
throw exception(DRAFT_UPDATE_FAIL, e.getError().getErrorMsg());
}
}
@DeleteMapping("/delete")
@ApiOperation("删除草稿")
@ApiImplicitParams({
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true,
example = "1024", dataTypeClass = Long.class),
@ApiImplicitParam(name = "mediaId", value = "草稿素材的编号", required = true,
example = "xxx", dataTypeClass = String.class),
})
@PreAuthorize("@ss.hasPermission('mp:draft:delete')")
public CommonResult<Boolean> deleteDraft(@RequestParam("accountId") Long accountId,
@RequestParam("mediaId") String mediaId) {
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
try {
mpService.getDraftService().delDraft(mediaId);
return success(true);
} catch (WxErrorException e) {
throw exception(DRAFT_DELETE_FAIL, e.getError().getErrorMsg());
}
}
}

View File

@ -0,0 +1,13 @@
### 请求 /mp/free-publish/page 接口 => 成功
GET {{baseUrl}}/mp/free-publish/page?accountId=1&pageNo=1&pageSize=10
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 请求 /mp/free-publish/submit 接口 => 成功
POST {{baseUrl}}/mp/free-publish/submit?accountId=1&mediaId=r6ryvl6LrxBU0miaST4Y-vilmd7iS51D8IPddxflWrau0hIQ2ovY8YanO5jlgUcM
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{}

View File

@ -0,0 +1,123 @@
package cn.iocoder.yudao.module.mp.controller.admin.news;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.module.mp.controller.admin.news.vo.MpFreePublishPageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishItem;
import me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishList;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
@Api(tags = "管理后台 - 公众号发布能力")
@RestController
@RequestMapping("/mp/free-publish")
@Validated
public class MpFreePublishController {
@Resource
private MpServiceFactory mpServiceFactory;
@Resource
private MpMaterialService mpMaterialService;
@GetMapping("/page")
@ApiOperation("获得已发布的图文分页")
@PreAuthorize("@ss.hasPermission('mp:free-publish:query')")
public CommonResult<PageResult<WxMpFreePublishItem>> getFreePublishPage(MpFreePublishPageReqVO reqVO) {
// 从公众号查询已发布的图文列表
WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());
WxMpFreePublishList publicationRecords;
try {
publicationRecords = mpService.getFreePublishService().getPublicationRecords(
PageUtils.getStart(reqVO), reqVO.getPageSize());
} catch (WxErrorException e) {
throw exception(FREE_PUBLISH_LIST_FAIL, e.getError().getErrorMsg());
}
// 查询对应的图片地址目的解决公众号的图片链接无法在我们后台展示
setFreePublishThumbUrl(publicationRecords.getItems());
// 返回分页
return success(new PageResult<>(publicationRecords.getItems(), publicationRecords.getTotalCount().longValue()));
}
private void setFreePublishThumbUrl(List<WxMpFreePublishItem> items) {
// 1.1 获得 mediaId 数组
Set<String> mediaIds = new HashSet<>();
items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> mediaIds.add(newsItem.getThumbMediaId())));
if (CollUtil.isEmpty(mediaIds)) {
return;
}
// 1.2 批量查询对应的 Media 素材
Map<String, MpMaterialDO> materials = CollectionUtils.convertMap(mpMaterialService.getMaterialListByMediaId(mediaIds),
MpMaterialDO::getMediaId);
// 2. 设置回 WxMpFreePublishItem 记录
items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem ->
findAndThen(materials, newsItem.getThumbMediaId(), material -> newsItem.setThumbUrl(material.getUrl()))));
}
@PostMapping("/submit")
@ApiOperation("发布草稿")
@ApiImplicitParams({
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true,
example = "1024", dataTypeClass = Long.class),
@ApiImplicitParam(name = "mediaId", value = "要发布的草稿的 media_id", required = true,
example = "2048", dataTypeClass = String.class)
})
@PreAuthorize("@ss.hasPermission('mp:free-publish:submit')")
public CommonResult<String> submitFreePublish(@RequestParam("accountId") Long accountId,
@RequestParam("mediaId") String mediaId) {
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
try {
String publishId = mpService.getFreePublishService().submit(mediaId);
return success(publishId);
} catch (WxErrorException e) {
throw exception(FREE_PUBLISH_SUBMIT_FAIL, e.getError().getErrorMsg());
}
}
@DeleteMapping("/delete")
@ApiOperation("删除草稿")
@ApiImplicitParams({
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true,
example = "1024", dataTypeClass = Long.class),
@ApiImplicitParam(name = "articleId", value = "发布记录的编号", required = true,
example = "2048", dataTypeClass = String.class)
})
@PreAuthorize("@ss.hasPermission('mp:free-publish:delete')")
public CommonResult<Boolean> deleteFreePublish(@RequestParam("accountId") Long accountId,
@RequestParam("articleId") String articleId) {
WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);
try {
mpService.getFreePublishService().deletePushAllArticle(articleId);
return success(true);
} catch (WxErrorException e) {
throw exception(FREE_PUBLISH_DELETE_FAIL, e.getError().getErrorMsg());
}
}
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mp.controller.admin.news.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号草稿的分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpDraftPageReqVO extends PageParam {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mp.controller.admin.news.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号已发布列表的分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpFreePublishPageReqVO extends PageParam {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
}

View File

@ -0,0 +1,116 @@
package cn.iocoder.yudao.module.mp.controller.admin.open;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenCheckSignatureReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
import cn.iocoder.yudao.module.mp.framework.mp.core.context.MpContextHolder;
import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Objects;
@Api(tags = "管理后台 - 公众号回调")
@RestController
@RequestMapping("/mp/open")
@Validated
@Slf4j
public class MpOpenController {
@Resource
private MpServiceFactory mpServiceFactory;
@Resource
private MpAccountService mpAccountService;
/**
* 接收微信公众号的校验签名
*
* 对应 <a href="https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html">文档</a>
*/
@ApiOperation("校验签名") // 参见
@GetMapping(value = "/{appId}", produces = "text/plain;charset=utf-8")
public String checkSignature(@PathVariable("appId") String appId,
MpOpenCheckSignatureReqVO reqVO) {
log.info("[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]", appId, reqVO);
// 校验请求签名
WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);
// 校验通过
if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) {
return reqVO.getEchostr();
}
// 校验不通过
return "非法请求";
}
/**
* 接收微信公众号的消息推送
*
* <a href="https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html">文档</a>
*/
@ApiOperation("处理消息")
@PostMapping(value = "/{appId}", produces = "application/xml; charset=UTF-8")
@OperateLog(enable = false) // 回调地址无需记录操作日志
public String handleMessage(@PathVariable("appId") String appId,
@RequestBody String content,
MpOpenHandleMessageReqVO reqVO) {
log.info("[handleMessage][appId({}) 推送消息,参数({}) 内容({})]", appId, reqVO, content);
// 处理 appId + 多租户的上下文
MpAccountDO account = mpAccountService.getAccountFromCache(appId);
Assert.notNull(account, "公众号 appId({}) 不存在", appId);
try {
MpContextHolder.setAppId(appId);
return TenantUtils.execute(account.getTenantId(),
() -> handleMessage0(appId, content, reqVO));
} finally {
MpContextHolder.clear();
}
}
private String handleMessage0(String appId, String content, MpOpenHandleMessageReqVO reqVO) {
// 校验请求签名
WxMpService mppService = mpServiceFactory.getRequiredMpService(appId);
Assert.isTrue(mppService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature()),
"非法请求");
// 第一步解析消息
WxMpXmlMessage inMessage = null;
if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式
inMessage = WxMpXmlMessage.fromXml(content);
} else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式
inMessage = WxMpXmlMessage.fromEncryptedXml(content, mppService.getWxMpConfigStorage(),
reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getMsg_signature());
}
Assert.notNull(inMessage, "消息解析失败,原因:消息为空");
// 第二步处理消息
WxMpMessageRouter mpMessageRouter = mpServiceFactory.getRequiredMpMessageRouter(appId);
WxMpXmlOutMessage outMessage = mpMessageRouter.route(inMessage);
if (outMessage == null) {
return "";
}
// 第三步返回消息
if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式
return outMessage.toXml();
} else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式
return outMessage.toEncryptedXml(mppService.getWxMpConfigStorage());
}
return "";
}
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.mp.controller.admin.open.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotEmpty;
@ApiModel("管理后台 - 公众号校验签名 Request VO")
@Data
public class MpOpenCheckSignatureReqVO {
@ApiModelProperty(value = "微信加密签名", required = true, example = "490eb57f448b87bd5f20ccef58aa4de46aa1908e")
@NotEmpty(message = "微信加密签名不能为空")
private String signature;
@ApiModelProperty(value = "时间戳", required = true, example = "1672587863")
@NotEmpty(message = "时间戳不能为空")
private String timestamp;
@ApiModelProperty(value = "随机数", required = true, example = "1827365808")
@NotEmpty(message = "随机数不能为空")
private String nonce;
@ApiModelProperty(value = "随机字符串", required = true, example = "2721154047828672511")
@NotEmpty(message = "随机字符串不能为空")
@SuppressWarnings("SpellCheckingInspection")
private String echostr;
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.mp.controller.admin.open.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@ApiModel("管理后台 - 公众号处理消息 Request VO")
@Data
public class MpOpenHandleMessageReqVO {
public static final String ENCRYPT_TYPE_AES = "aes";
@ApiModelProperty(value = "微信加密签名", required = true, example = "490eb57f448b87bd5f20ccef58aa4de46aa1908e")
@NotEmpty(message = "微信加密签名不能为空")
private String signature;
@ApiModelProperty(value = "时间戳", required = true, example = "1672587863")
@NotEmpty(message = "时间戳不能为空")
private String timestamp;
@ApiModelProperty(value = "随机数", required = true, example = "1827365808")
@NotEmpty(message = "随机数不能为空")
private String nonce;
@ApiModelProperty(value = "粉丝 openid", required = true, example = "oz-Jdtyn-WGm4C4I5Z-nvBMO_ZfY")
@NotEmpty(message = "粉丝 openid 不能为空")
private String openid;
@ApiModelProperty(value = "消息加密类型", example = "aes")
private String encrypt_type;
@ApiModelProperty(value = "微信签名", example = "QW5kcm9pZCBUaGUgQmFzZTY0IGlzIGEgZ2VuZXJhdGVkIHN0cmluZw==")
private String msg_signature;
}

View File

@ -0,0 +1,68 @@
package cn.iocoder.yudao.module.mp.controller.admin.statistics;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.*;
import cn.iocoder.yudao.module.mp.convert.statistics.MpStatisticsConvert;
import cn.iocoder.yudao.module.mp.service.statistics.MpStatisticsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult;
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult;
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate;
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 公众号统计")
@RestController
@RequestMapping("/mp/statistics")
@Validated
public class MpStatisticsController {
@Resource
private MpStatisticsService mpStatisticsService;
@GetMapping("/user-summary")
@ApiOperation("获得粉丝增减数据")
@PreAuthorize("@ss.hasPermission('mp:statistics:query')")
public CommonResult<List<MpStatisticsUserSummaryRespVO>> getUserSummary(MpStatisticsGetReqVO getReqVO) {
List<WxDataCubeUserSummary> list = mpStatisticsService.getUserSummary(
getReqVO.getAccountId(), getReqVO.getDate());
return success(MpStatisticsConvert.INSTANCE.convertList01(list));
}
@GetMapping("/user-cumulate")
@ApiOperation("获得粉丝累计数据")
@PreAuthorize("@ss.hasPermission('mp:statistics:query')")
public CommonResult<List<MpStatisticsUserCumulateRespVO>> getUserCumulate(MpStatisticsGetReqVO getReqVO) {
List<WxDataCubeUserCumulate> list = mpStatisticsService.getUserCumulate(
getReqVO.getAccountId(), getReqVO.getDate());
return success(MpStatisticsConvert.INSTANCE.convertList02(list));
}
@GetMapping("/upstream-message")
@ApiOperation("获取消息发送概况数据")
@PreAuthorize("@ss.hasPermission('mp:statistics:query')")
public CommonResult<List<MpStatisticsUpstreamMessageRespVO>> getUpstreamMessage(MpStatisticsGetReqVO getReqVO) {
List<WxDataCubeMsgResult> list = mpStatisticsService.getUpstreamMessage(
getReqVO.getAccountId(), getReqVO.getDate());
return success(MpStatisticsConvert.INSTANCE.convertList03(list));
}
@GetMapping("/interface-summary")
@ApiOperation("获取消息发送概况数据")
@PreAuthorize("@ss.hasPermission('mp:statistics:query')")
public CommonResult<List<MpStatisticsInterfaceSummaryRespVO>> getInterfaceSummary(MpStatisticsGetReqVO getReqVO) {
List<WxDataCubeInterfaceResult> list = mpStatisticsService.getInterfaceSummary(
getReqVO.getAccountId(), getReqVO.getDate());
return success(MpStatisticsConvert.INSTANCE.convertList04(list));
}
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@ApiModel("管理后台 - 获得统计数据 Request VO")
@Data
public class MpStatisticsGetReqVO {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1024")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@ApiModelProperty(value = "查询时间范围")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@NotNull(message = "查询时间范围不能为空")
private LocalDateTime[] date;
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@ApiModel("管理后台 - 某一天的接口分析数据 Response VO")
@Data
public class MpStatisticsInterfaceSummaryRespVO {
@ApiModelProperty(value = "日期", required = true)
private Date refDate;
@ApiModelProperty(value = "通过服务器配置地址获得消息后,被动回复粉丝消息的次数", required = true, example = "10")
private Integer callbackCount;
@ApiModelProperty(value = "上述动作的失败次数", required = true, example = "20")
private Integer failCount;
@ApiModelProperty(value = "总耗时,除以 callback_count 即为平均耗时", required = true, example = "30")
private Integer totalTimeCost;
@ApiModelProperty(value = "最大耗时", required = true, example = "40")
private Integer maxTimeCost;
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@ApiModel("管理后台 - 某一天的粉丝增减数据 Response VO")
@Data
public class MpStatisticsUpstreamMessageRespVO {
@ApiModelProperty(value = "日期", required = true)
private Date refDate;
@ApiModelProperty(value = "上行发送了(向公众号发送了)消息的粉丝数", required = true, example = "10")
private Integer messageUser;
@ApiModelProperty(value = "上行发送了消息的消息总数", required = true, example = "20")
private Integer messageCount;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@ApiModel("管理后台 - 某一天的消息发送概况数据 Response VO")
@Data
public class MpStatisticsUserCumulateRespVO {
@ApiModelProperty(value = "日期", required = true)
private Date refDate;
@ApiModelProperty(value = "累计粉丝量", required = true, example = "10")
private Integer cumulateUser;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.mp.controller.admin.statistics.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
@ApiModel("管理后台 - 某一天的粉丝增减数据 Response VO")
@Data
public class MpStatisticsUserSummaryRespVO {
@ApiModelProperty(value = "日期", required = true)
private Date refDate;
@ApiModelProperty(value = "粉丝来源", required = true, example = "0")
private Integer userSource;
@ApiModelProperty(value = "新关注的粉丝数量", required = true, example = "10")
private Integer newUser;
@ApiModelProperty(value = "取消关注的粉丝数量", required = true, example = "20")
private Integer cancelUser;
}

View File

@ -0,0 +1,39 @@
### 请求 /mp/tag/create 接口 => 成功
POST {{baseUrl}}/mp/tag/create
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"accountId": "1",
"name": "测试"
}
### 请求 /mp/tag/update 接口 => 成功
PUT {{baseUrl}}/mp/tag/update
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"id": "3",
"name": "测试标签啦"
}
### 请求 /mp/tag/delete 接口 => 成功
DELETE {{baseUrl}}/mp/tag/delete?id=3
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 请求 /mp/tag/page 接口 => 成功
GET {{baseUrl}}/mp/tag/page?accountId=1&pageNo=1&pageSize=10
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 请求 /mp/tag/sync 接口 => 成功
POST {{baseUrl}}/mp/tag/sync?accountId=1
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -0,0 +1,80 @@
package cn.iocoder.yudao.module.mp.controller.admin.tag;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.*;
import cn.iocoder.yudao.module.mp.convert.tag.MpTagConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO;
import cn.iocoder.yudao.module.mp.service.tag.MpTagService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 公众号标签")
@RestController
@RequestMapping("/mp/tag")
@Validated
public class MpTagController {
@Resource
private MpTagService mpTagService;
@PostMapping("/create")
@ApiOperation("创建公众号标签")
@PreAuthorize("@ss.hasPermission('mp:tag:create')")
public CommonResult<Long> createTag(@Valid @RequestBody MpTagCreateReqVO createReqVO) {
return success(mpTagService.createTag(createReqVO));
}
@PutMapping("/update")
@ApiOperation("更新公众号标签")
@PreAuthorize("@ss.hasPermission('mp:tag:update')")
public CommonResult<Boolean> updateTag(@Valid @RequestBody MpTagUpdateReqVO updateReqVO) {
mpTagService.updateTag(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@ApiOperation("删除公众号标签")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:tag:delete')")
public CommonResult<Boolean> deleteTag(@RequestParam("id") Long id) {
mpTagService.deleteTag(id);
return success(true);
}
@GetMapping("/page")
@ApiOperation("获取公众号标签分页")
@PreAuthorize("@ss.hasPermission('mp:tag:query')")
public CommonResult<PageResult<MpTagRespVO>> getTagPage(MpTagPageReqVO pageReqVO) {
PageResult<MpTagDO> pageResult = mpTagService.getTagPage(pageReqVO);
return success(MpTagConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/list-all-simple")
@ApiOperation(value = "获取公众号账号精简信息列表")
@PreAuthorize("@ss.hasPermission('mp:account:query')")
public CommonResult<List<MpTagSimpleRespVO>> getSimpleTags() {
List<MpTagDO> list = mpTagService.getTagList();
return success(MpTagConvert.INSTANCE.convertList02(list));
}
@PostMapping("/sync")
@ApiOperation("同步公众号标签")
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:tag:sync')")
public CommonResult<Boolean> syncTag(@RequestParam("accountId") Long accountId) {
mpTagService.syncTag(accountId);
return success(true);
}
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.mp.controller.admin.tag.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
/**
* 公众号标签 Base VO提供给添加修改详细的子 VO 使用
* 如果子 VO 存在差异的字段请不要添加到这里影响 Swagger 文档生成
*
* @author fengdan
*/
@Data
public class MpTagBaseVO {
@ApiModelProperty(value = "标签名", required = true, example = "土豆")
@NotEmpty(message = "标签名不能为空")
private String name;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.mp.controller.admin.tag.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号标签创建 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpTagCreateReqVO extends MpTagBaseVO {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.mp.controller.admin.tag.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotEmpty;
@ApiModel("管理后台 - 公众号标签分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpTagPageReqVO extends PageParam {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotEmpty(message = "公众号账号的编号不能为空")
private Long accountId;
@ApiModelProperty(value = "标签名", example = "哈哈", notes = "模糊匹配")
private String name;
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.mp.controller.admin.tag.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
@ApiModel("管理后台 - 公众号标签 Response VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpTagRespVO extends MpTagBaseVO {
@ApiModelProperty(value = "编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "此标签下粉丝数量", required = true, example = "0")
private Integer count;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.mp.controller.admin.tag.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("管理后台 - 公众号标签精简信息 Response VO")
@Data
public class MpTagSimpleRespVO {
@ApiModelProperty(value = "编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "公众号的标签编号", required = true, example = "2048")
private Long tagId;
@ApiModelProperty(value = "标签名称", required = true, example = "快乐")
private String name;
}

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.mp.controller.admin.tag.vo;
import lombok.*;
import io.swagger.annotations.*;
import javax.validation.constraints.*;
@ApiModel("管理后台 - 公众号标签更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpTagUpdateReqVO extends MpTagBaseVO {
@ApiModelProperty(value = "编号", required = true)
@NotNull(message = "编号不能为空")
private Long id;
}

View File

@ -0,0 +1,18 @@
### 请求 /mp/user/sync 接口 => 成功
POST {{baseUrl}}/mp/user/sync?accountId=1
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 请求 /mp/user/update 接口 => 成功
PUT {{baseUrl}}/mp/user/update
Content-Type: application/json
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
{
"id": "3",
"nickname": "test",
"remark": "测试备注",
"tagIds": [103, 104]
}

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.mp.controller.admin.user;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserPageReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO;
import cn.iocoder.yudao.module.mp.convert.user.MpUserConvert;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Api(tags = "管理后台 - 公众号粉丝")
@RestController
@RequestMapping("/mp/user")
@Validated
public class MpUserController {
@Resource
private MpUserService mpUserService;
@GetMapping("/page")
@ApiOperation("获得公众号粉丝分页")
@PreAuthorize("@ss.hasPermission('mp:user:query')")
public CommonResult<PageResult<MpUserRespVO>> getUserPage(@Valid MpUserPageReqVO pageVO) {
PageResult<MpUserDO> pageResult = mpUserService.getUserPage(pageVO);
return success(MpUserConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/get")
@ApiOperation("获得公众号粉丝")
@ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:user:query')")
public CommonResult<MpUserRespVO> getUser(@RequestParam("id") Long id) {
return success(MpUserConvert.INSTANCE.convert(mpUserService.getUser(id)));
}
@PutMapping("/update")
@ApiOperation("更新公众号粉丝")
@PreAuthorize("@ss.hasPermission('mp:user:update')")
public CommonResult<Boolean> updateUser(@Valid @RequestBody MpUserUpdateReqVO updateReqVO) {
mpUserService.updateUser(updateReqVO);
return success(true);
}
@PostMapping("/sync")
@ApiOperation("同步公众号粉丝")
@ApiImplicitParam(name = "accountId", value = "公众号账号的编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('mp:user:sync')")
public CommonResult<Boolean> syncUser(@RequestParam("accountId") Long accountId) {
mpUserService.syncUser(accountId);
return success(true);
}
}

View File

@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mp.controller.admin.user.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@ApiModel("管理后台 - 公众号粉丝分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpUserPageReqVO extends PageParam {
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
@NotNull(message = "公众号账号的编号不能为空")
private Long accountId;
@ApiModelProperty(value = "公众号粉丝标识", example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M", notes = "模糊匹配")
private String openid;
@ApiModelProperty(value = "公众号粉丝昵称", example = "芋艿", notes = "模糊匹配")
private String nickname;
}

View File

@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.mp.controller.admin.user.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@ApiModel("管理后台 - 公众号粉丝 Response VO")
@Data
public class MpUserRespVO {
@ApiModelProperty(value = "编号", required = true, example = "1024")
private Long id;
@ApiModelProperty(value = "公众号粉丝标识", required = true, example = "o6_bmjrPTlm6_2sgVt7hMZOPfL2M")
private String openid;
@ApiModelProperty(value = "关注状态", required = true, example = "1", notes = "参见 CommonStatusEnum 枚举")
private Integer subscribeStatus;
@ApiModelProperty(value = "关注时间", required = true)
private LocalDateTime subscribeTime;
@ApiModelProperty(value = "取消关注时间")
private LocalDateTime unsubscribeTime;
@ApiModelProperty(value = "昵称", example = "芋道")
private String nickname;
@ApiModelProperty(value = "头像地址", example = "https://www.iocoder.cn/1.png")
private String headImageUrl;
@ApiModelProperty(value = "语言", example = "zh_CN")
private String language;
@ApiModelProperty(value = "国家", example = "中国")
private String country;
@ApiModelProperty(value = "省份", example = "广东省")
private String province;
@ApiModelProperty(value = "城市", example = "广州市")
private String city;
@ApiModelProperty(value = "备注", example = "你是一个芋头嘛")
private String remark;
@ApiModelProperty(value = "标签编号数组", example = "1,2,3")
private List<Long> tagIds;
@ApiModelProperty(value = "公众号账号的编号", required = true, example = "1")
private Long accountId;
@ApiModelProperty(value = "公众号账号的 appId", required = true, example = "wx1234567890")
private String appId;
@ApiModelProperty(value = "创建时间", required = true)
private Date createTime;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.mp.controller.admin.user.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
@ApiModel("管理后台 - 公众号粉丝更新 Request VO")
@Data
public class MpUserUpdateReqVO {
@ApiModelProperty(value = "编号", required = true, example = "1024")
@NotNull(message = "编号不能为空")
private Long id;
@ApiModelProperty(value = "昵称", example = "芋道")
private String nickname;
@ApiModelProperty(value = "备注", example = "你是一个芋头嘛")
private String remark;
@ApiModelProperty(value = "标签编号数组", example = "1,2,3")
private List<Long> tagIds;
}

View File

@ -0,0 +1,6 @@
/**
* 提供 RESTful API 给前端
* 1. admin 提供给管理后台 yudao-ui-admin 前端项目
* 2. app 提供给用户 APP yudao-ui-app 前端项目它的 Controller VO 都要添加 App 前缀用于和管理后台进行区分
*/
package cn.iocoder.yudao.module.mp.controller;

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.mp.convert.account;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountSimpleRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface MpAccountConvert {
MpAccountConvert INSTANCE = Mappers.getMapper(MpAccountConvert.class);
MpAccountDO convert(MpAccountCreateReqVO bean);
MpAccountDO convert(MpAccountUpdateReqVO bean);
MpAccountRespVO convert(MpAccountDO bean);
List<MpAccountRespVO> convertList(List<MpAccountDO> list);
PageResult<MpAccountRespVO> convertPage(PageResult<MpAccountDO> page);
List<MpAccountSimpleRespVO> convertList02(List<MpAccountDO> list);
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.mp.convert.material;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadRespVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
import me.chanjar.weixin.mp.bean.material.WxMpMaterial;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.io.File;
@Mapper
public interface MpMaterialConvert {
MpMaterialConvert INSTANCE = Mappers.getMapper(MpMaterialConvert.class);
@Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(source = "account.id", target = "accountId"),
@Mapping(source = "account.appId", target = "appId"),
@Mapping(source = "name", target = "name")
})
MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account,
String name);
@Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(source = "account.id", target = "accountId"),
@Mapping(source = "account.appId", target = "appId"),
@Mapping(source = "name", target = "name")
})
MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account,
String name, String title, String introduction, String mpUrl);
MpMaterialUploadRespVO convert(MpMaterialDO bean);
default WxMpMaterial convert(String name, File file, String title, String introduction) {
return new WxMpMaterial(name, file, title, introduction);
}
PageResult<MpMaterialRespVO> convertPage(PageResult<MpMaterialDO> page);
}

View File

@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.mp.convert.menu;
import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import me.chanjar.weixin.common.bean.menu.WxMenuButton;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface MpMenuConvert {
MpMenuConvert INSTANCE = Mappers.getMapper(MpMenuConvert.class);
MpMenuRespVO convert(MpMenuDO bean);
List<MpMenuRespVO> convertList(List<MpMenuDO> list);
@Mappings({
@Mapping(source = "menu.appId", target = "appId"),
@Mapping(source = "menu.replyMessageType", target = "type"),
@Mapping(source = "menu.replyContent", target = "content"),
@Mapping(source = "menu.replyMediaId", target = "mediaId"),
@Mapping(source = "menu.replyThumbMediaId", target = "thumbMediaId"),
@Mapping(source = "menu.replyTitle", target = "title"),
@Mapping(source = "menu.replyDescription", target = "description"),
@Mapping(source = "menu.replyArticles", target = "articles"),
@Mapping(source = "menu.replyMusicUrl", target = "musicUrl"),
@Mapping(source = "menu.replyHqMusicUrl", target = "hqMusicUrl"),
})
MpMessageSendOutReqBO convert(String openid, MpMenuDO menu);
List<WxMenuButton> convert(List<MpMenuSaveReqVO.Menu> list);
@Mappings({
@Mapping(source = "menuKey", target = "key"),
@Mapping(source = "children", target = "subButtons"),
})
WxMenuButton convert(MpMenuSaveReqVO.Menu bean);
MpMenuDO convert02(MpMenuSaveReqVO.Menu menu);
}

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.mp.convert.message;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface MpAutoReplyConvert {
MpAutoReplyConvert INSTANCE = Mappers.getMapper(MpAutoReplyConvert.class);
@Mappings({
@Mapping(source = "reply.appId", target = "appId"),
@Mapping(source = "reply.responseMessageType", target = "type"),
@Mapping(source = "reply.responseContent", target = "content"),
@Mapping(source = "reply.responseMediaId", target = "mediaId"),
@Mapping(source = "reply.responseTitle", target = "title"),
@Mapping(source = "reply.responseDescription", target = "description"),
@Mapping(source = "reply.responseArticles", target = "articles"),
})
MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply);
PageResult<MpAutoReplyRespVO> convertPage(PageResult<MpAutoReplyDO> page);
MpAutoReplyRespVO convert(MpAutoReplyDO bean);
MpAutoReplyDO convert(MpAutoReplyCreateReqVO bean);
MpAutoReplyDO convert(MpAutoReplyUpdateReqVO bean);
}

View File

@ -0,0 +1,172 @@
package cn.iocoder.yudao.module.mp.convert.message;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;
import me.chanjar.weixin.mp.builder.outxml.BaseBuilder;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface MpMessageConvert {
MpMessageConvert INSTANCE = Mappers.getMapper(MpMessageConvert.class);
MpMessageRespVO convert(MpMessageDO bean);
List<MpMessageRespVO> convertList(List<MpMessageDO> list);
PageResult<MpMessageRespVO> convertPage(PageResult<MpMessageDO> page);
default MpMessageDO convert(WxMpXmlMessage wxMessage, MpAccountDO account, MpUserDO user) {
MpMessageDO message = convert(wxMessage);
if (account != null) {
message.setAccountId(account.getId()).setAppId(account.getAppId());
}
if (user != null) {
message.setUserId(user.getId()).setOpenid(user.getOpenid());
}
return message;
}
@Mappings(value = {
@Mapping(source = "msgType", target = "type"),
@Mapping(target = "createTime", ignore = true),
})
MpMessageDO convert(WxMpXmlMessage bean);
default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) {
// 构建消息
MpMessageDO message = new MpMessageDO();
message.setType(sendReqBO.getType());
switch (sendReqBO.getType()) {
case WxConsts.XmlMsgType.TEXT: // 1. 文本
message.setContent(sendReqBO.getContent());
break;
case WxConsts.XmlMsgType.IMAGE: // 2. 图片
case WxConsts.XmlMsgType.VOICE: // 3. 语音
message.setMediaId(sendReqBO.getMediaId());
break;
case WxConsts.XmlMsgType.VIDEO: // 4. 视频
message.setMediaId(sendReqBO.getMediaId())
.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription());
break;
case WxConsts.XmlMsgType.NEWS: // 5. 图文
message.setArticles(sendReqBO.getArticles());
case WxConsts.XmlMsgType.MUSIC: // 6. 音乐
message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription())
.setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl())
.setThumbMediaId(sendReqBO.getThumbMediaId());
break;
default:
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
}
// 其它字段
if (account != null) {
message.setAccountId(account.getId()).setAppId(account.getAppId());
}
if (user != null) {
message.setUserId(user.getId()).setOpenid(user.getOpenid());
}
return message;
}
default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) {
BaseBuilder<?, ? extends WxMpXmlOutMessage> builder;
// 个性化字段
switch (message.getType()) {
case WxConsts.XmlMsgType.TEXT:
builder = WxMpXmlOutMessage.TEXT().content(message.getContent());
break;
case WxConsts.XmlMsgType.IMAGE:
builder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId());
break;
case WxConsts.XmlMsgType.VOICE:
builder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId());
break;
case WxConsts.XmlMsgType.VIDEO:
builder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId())
.title(message.getTitle()).description(message.getDescription());
break;
case WxConsts.XmlMsgType.NEWS:
builder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles()));
break;
case WxConsts.XmlMsgType.MUSIC:
builder = WxMpXmlOutMessage.MUSIC().title(message.getTitle()).description(message.getDescription())
.musicUrl(message.getMusicUrl()).hqMusicUrl(message.getHqMusicUrl())
.thumbMediaId(message.getThumbMediaId());
break;
default:
throw new IllegalArgumentException("不支持的消息类型:" + message.getType());
}
// 通用字段
builder.fromUser(account.getAccount());
builder.toUser(message.getOpenid());
return builder.build();
}
List<WxMpXmlOutNewsMessage.Item> convertList02(List<MpMessageDO.Article> list);
default WxMpKefuMessage convert(MpMessageSendReqVO sendReqVO, MpUserDO user) {
me.chanjar.weixin.mp.builder.kefu.BaseBuilder<?> builder;
// 个性化字段
switch (sendReqVO.getType()) {
case WxConsts.KefuMsgType.TEXT:
builder = WxMpKefuMessage.TEXT().content(sendReqVO.getContent());
break;
case WxConsts.KefuMsgType.IMAGE:
builder = WxMpKefuMessage.IMAGE().mediaId(sendReqVO.getMediaId());
break;
case WxConsts.KefuMsgType.VOICE:
builder = WxMpKefuMessage.VOICE().mediaId(sendReqVO.getMediaId());
break;
case WxConsts.KefuMsgType.VIDEO:
builder = WxMpKefuMessage.VIDEO().mediaId(sendReqVO.getMediaId())
.title(sendReqVO.getTitle()).description(sendReqVO.getDescription());
break;
case WxConsts.KefuMsgType.NEWS:
builder = WxMpKefuMessage.NEWS().articles(convertList03(sendReqVO.getArticles()));
break;
case WxConsts.KefuMsgType.MUSIC:
builder = WxMpKefuMessage.MUSIC().title(sendReqVO.getTitle()).description(sendReqVO.getDescription())
.thumbMediaId(sendReqVO.getThumbMediaId())
.musicUrl(sendReqVO.getMusicUrl()).hqMusicUrl(sendReqVO.getHqMusicUrl());
break;
default:
throw new IllegalArgumentException("不支持的消息类型:" + sendReqVO.getType());
}
// 通用字段
builder.toUser(user.getOpenid());
return builder.build();
}
List<WxMpKefuMessage.WxArticle> convertList03(List<MpMessageDO.Article> list);
default MpMessageDO convert(WxMpKefuMessage wxMessage, MpAccountDO account, MpUserDO user) {
MpMessageDO message = convert(wxMessage);
if (account != null) {
message.setAccountId(account.getId()).setAppId(account.getAppId());
}
if (user != null) {
message.setUserId(user.getId()).setOpenid(user.getOpenid());
}
return message;
}
@Mappings(value = {
@Mapping(source = "msgType", target = "type"),
@Mapping(target = "createTime", ignore = true),
})
MpMessageDO convert(WxMpKefuMessage bean);
}

View File

@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.mp.convert.statistics;
import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsInterfaceSummaryRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUpstreamMessageRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUserCumulateRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.statistics.vo.MpStatisticsUserSummaryRespVO;
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult;
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult;
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate;
import me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface MpStatisticsConvert {
MpStatisticsConvert INSTANCE = Mappers.getMapper(MpStatisticsConvert.class);
List<MpStatisticsUserSummaryRespVO> convertList01(List<WxDataCubeUserSummary> list);
List<MpStatisticsUserCumulateRespVO> convertList02(List<WxDataCubeUserCumulate> list);
List<MpStatisticsUpstreamMessageRespVO> convertList03(List<WxDataCubeMsgResult> list);
@Mappings({
@Mapping(source = "refDate", target = "refDate", dateFormat = "yyyy-MM-dd"),
@Mapping(source = "msgUser", target = "messageUser"),
@Mapping(source = "msgCount", target = "messageCount"),
})
MpStatisticsUpstreamMessageRespVO convert(WxDataCubeMsgResult bean);
List<MpStatisticsInterfaceSummaryRespVO> convertList04(List<WxDataCubeInterfaceResult> list);
@Mapping(source = "refDate", target = "refDate", dateFormat = "yyyy-MM-dd")
MpStatisticsInterfaceSummaryRespVO convert(WxDataCubeInterfaceResult bean);
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.mp.convert.tag;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagSimpleRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO;
import me.chanjar.weixin.mp.bean.tag.WxUserTag;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface MpTagConvert {
MpTagConvert INSTANCE = Mappers.getMapper(MpTagConvert.class);
WxUserTag convert(MpTagUpdateReqVO bean);
MpTagRespVO convert(WxUserTag bean);
List<MpTagRespVO> convertList(List<WxUserTag> list);
PageResult<MpTagRespVO> convertPage(PageResult<MpTagDO> page);
@Mappings({
@Mapping(target = "id", ignore = true),
@Mapping(source = "tag.id", target = "tagId"),
@Mapping(source = "tag.name", target = "name"),
@Mapping(source = "tag.count", target = "count"),
@Mapping(source = "account.id", target = "accountId"),
@Mapping(source = "account.appId", target = "appId"),
})
MpTagDO convert(WxUserTag tag, MpAccountDO account);
List<MpTagSimpleRespVO> convertList02(List<MpTagDO> list);
}

View File

@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.mp.convert.user;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserRespVO;
import cn.iocoder.yudao.module.mp.controller.admin.user.vo.MpUserUpdateReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface MpUserConvert {
MpUserConvert INSTANCE = Mappers.getMapper(MpUserConvert.class);
MpUserRespVO convert(MpUserDO bean);
List<MpUserRespVO> convertList(List<MpUserDO> list);
PageResult<MpUserRespVO> convertPage(PageResult<MpUserDO> page);
@Mappings(value = {
@Mapping(source = "openId", target = "openid"),
@Mapping(source = "headImgUrl", target = "headImageUrl"),
@Mapping(target = "subscribeTime", ignore = true), // 单独转换
})
MpUserDO convert(WxMpUser wxMpUser);
default MpUserDO convert(MpAccountDO account, WxMpUser wxMpUser) {
MpUserDO user = convert(wxMpUser);
user.setSubscribeStatus(wxMpUser.getSubscribe() ? CommonStatusEnum.ENABLE.getStatus()
: CommonStatusEnum.DISABLE.getStatus());
user.setSubscribeTime(LocalDateTimeUtil.of(wxMpUser.getSubscribeTime() * 1000L));
if (account != null) {
user.setAccountId(account.getId());
user.setAppId(account.getAppId());
}
return user;
}
default List<MpUserDO> convertList(MpAccountDO account, List<WxMpUser> wxUsers) {
return CollectionUtils.convertList(wxUsers, wxUser -> convert(account, wxUser));
}
MpUserDO convert(MpUserUpdateReqVO bean);
}

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.account;
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* 公众号账号 DO
*
* @author 芋道源码
*/
@TableName("mp_account")
@KeySequence("mp_account_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MpAccountDO extends TenantBaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 公众号名称
*/
private String name;
/**
* 公众号账号
*/
private String account;
/**
* 公众号 appid
*/
private String appId;
/**
* 公众号密钥
*/
private String appSecret;
/**
* 公众号token
*/
private String token;
/**
* 消息加解密密钥
*/
private String aesKey;
/**
* 二维码图片 URL
*/
private String qrCodeUrl;
/**
* 备注
*/
private String remark;
}

View File

@ -0,0 +1,99 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.material;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import me.chanjar.weixin.common.api.WxConsts;
/**
* 公众号素材 DO
*
* 1. <a href="https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html">临时素材</a>
* 2. <a href="https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Adding_Permanent_Assets.html">永久素材</a>
*
* @author 芋道源码
*/
@TableName("mp_material")
@KeySequence("mp_material_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MpMaterialDO extends BaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 公众号账号的编号
*
* 关联 {@link MpAccountDO#getId()}
*/
private Long accountId;
/**
* 公众号 appId
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
/**
* 公众号素材 id
*/
private String mediaId;
/**
* 文件类型
*
* 枚举 {@link WxConsts.MediaFileType}
*/
private String type;
/**
* 是否永久
*
* true - 永久素材
* false - 临时素材
*/
private Boolean permanent;
/**
* 文件服务器的 URL
*/
private String url;
/**
* 名字
*
* 永久素材非空
* 临时素材可能为空
* 1. 为空的情况粉丝主动发送的图片语音等
* 2. 非空的情况主动发送给粉丝的图片语音等
*/
private String name;
/**
* 公众号文件 URL
*
* 只有永久素材使用
*/
private String mpUrl;
/**
* 视频素材的标题
*
* 只有永久素材使用
*/
private String title;
/**
* 视频素材的描述
*
* 只有永久素材使用
*/
private String introduction;
}

View File

@ -0,0 +1,184 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.menu;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpMessageDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.api.WxConsts.MenuButtonType;
import java.util.List;
/**
* 公众号菜单 DO
*
* @author 芋道源码
*/
@TableName(value = "mp_menu", autoResultMap = true)
@KeySequence("mp_menu_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpMenuDO extends BaseDO {
/**
* 编号 - 顶级菜单
*/
public static final Long ID_ROOT = 0L;
/**
* 编号
*/
@TableId
private Long id;
/**
* 公众号账号的编号
*
* 关联 {@link MpAccountDO#getId()}
*/
private Long accountId;
/**
* 公众号 appId
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
/**
* 菜单名称
*/
private String name;
/**
* 菜单标识
*
* 支持多 DB 类型时无法直接使用 key + @TableField("menuKey") 来实现转换原因是 "menuKey" AS key 而存在报错
*/
private String menuKey;
/**
* 父菜单编号
*/
private Long parentId;
// ========== 按钮操作 ==========
/**
* 按钮类型
*
* 枚举 {@link MenuButtonType}
*/
private String type;
/**
* 网页链接
*
* 粉丝点击菜单可打开链接不超过 1024 字节
*
* 类型为 {@link WxConsts.XmlMsgType} VIEWMINIPROGRAM
*/
private String url;
/**
* 小程序的 appId
*
* 类型为 {@link MenuButtonType} MINIPROGRAM
*/
private String miniProgramAppId;
/**
* 小程序的页面路径
*
* 类型为 {@link MenuButtonType} MINIPROGRAM
*/
private String miniProgramPagePath;
/**
* 跳转图文的媒体编号
*/
private String articleId;
// ========== 消息内容 ==========
/**
* 消息类型
*
* {@link #type} CLICKSCANCODE_WAITMSG
*
* 枚举 {@link WxConsts.XmlMsgType} 中的 TEXTIMAGEVOICEVIDEONEWSMUSIC
*/
private String replyMessageType;
/**
* 回复的消息内容
*
* 消息类型为 {@link WxConsts.XmlMsgType} TEXT
*/
private String replyContent;
/**
* 回复的媒体 id
*
* 消息类型为 {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/
private String replyMediaId;
/**
* 回复的媒体 URL
*
* 消息类型为 {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/
private String replyMediaUrl;
/**
* 回复的标题
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEO
*/
private String replyTitle;
/**
* 回复的描述
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEO
*/
private String replyDescription;
/**
* 回复的缩略图的媒体 id通过素材管理中的接口上传多媒体文件得到的 id
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSICVIDEO
*/
private String replyThumbMediaId;
/**
* 回复的缩略图的媒体 URL
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSICVIDEO
*/
private String replyThumbMediaUrl;
/**
* 回复的图文消息数组
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
private List<MpMessageDO.Article> replyArticles;
/**
* 回复的音乐链接
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC
*/
private String replyMusicUrl;
/**
* 回复的高质量音乐链接
*
* WIFI 环境优先使用该链接播放音乐
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC
*/
private String replyHqMusicUrl;
}

View File

@ -0,0 +1,164 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.message;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
import java.util.List;
import java.util.Set;
/**
* 公众号消息自动回复 DO
*
* @author 芋道源码
*/
@TableName(value = "mp_auto_reply", autoResultMap = true)
@KeySequence("mp_auto_reply_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpAutoReplyDO extends BaseDO {
public static Set<String> REQUEST_MESSAGE_TYPE = SetUtils.asSet(WxConsts.XmlMsgType.TEXT, WxConsts.XmlMsgType.IMAGE,
WxConsts.XmlMsgType.VOICE, WxConsts.XmlMsgType.VIDEO, WxConsts.XmlMsgType.SHORTVIDEO,
WxConsts.XmlMsgType.LOCATION, WxConsts.XmlMsgType.LINK);
/**
* 主键
*/
@TableId
private Long id;
/**
* 公众号账号的编号
*
* 关联 {@link MpAccountDO#getId()}
*/
private Long accountId;
/**
* 公众号 appId
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
/**
* 回复类型
*
* 枚举 {@link MpAutoReplyTypeEnum}
*/
private Integer type;
// ==================== 请求消息 ====================
/**
* 请求的关键字
*
* {@link #type} {@link MpAutoReplyTypeEnum#KEYWORD}
*/
private String requestKeyword;
/**
* 请求的关键字的匹配
*
* {@link #type} {@link MpAutoReplyTypeEnum#KEYWORD}
*
* 枚举 {@link MpAutoReplyMatchEnum}
*/
private Integer requestMatch;
/**
* 请求的消息类型
*
* {@link #type} {@link MpAutoReplyTypeEnum#MESSAGE}
*
* 枚举 {@link XmlMsgType} 中的 {@link #REQUEST_MESSAGE_TYPE}
*/
private String requestMessageType;
// ==================== 响应消息 ====================
/**
* 回复的消息类型
*
* 枚举 {@link XmlMsgType} 中的 TEXTIMAGEVOICEVIDEONEWS
*/
private String responseMessageType;
/**
* 回复的消息内容
*
* 消息类型为 {@link WxConsts.XmlMsgType} TEXT
*/
private String responseContent;
/**
* 回复的媒体 id
*
* 消息类型为 {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/
private String responseMediaId;
/**
* 回复的媒体 URL
*/
private String responseMediaUrl;
/**
* 回复的标题
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEO
*/
private String responseTitle;
/**
* 回复的描述
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEO
*/
private String responseDescription;
/**
* 回复的缩略图的媒体 id通过素材管理中的接口上传多媒体文件得到的 id
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSICVIDEO
*/
private String responseThumbMediaId;
/**
* 回复的缩略图的媒体 URL
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSICVIDEO
*/
private String responseThumbMediaUrl;
/**
* 回复的图文消息
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)
private List<MpMessageDO.Article> responseArticles;
/**
* 回复的音乐链接
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC
*/
private String responseMusicUrl;
/**
* 回复的高质量音乐链接
*
* WIFI 环境优先使用该链接播放音乐
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC
*/
private String responseHqMusicUrl;
}

View File

@ -0,0 +1,255 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.message;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.user.MpUserDO;
import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;
import lombok.*;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.builder.kefu.NewsBuilder;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.List;
/**
* 公众号消息 DO
*
* @author 芋道源码
*/
@TableName(value = "mp_message", autoResultMap = true)
@KeySequence("mp_message_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class MpMessageDO extends BaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 微信公众号消息 id
*/
private Long msgId;
/**
* 公众号账号的 ID
*
* 关联 {@link MpAccountDO#getId()}
*/
private Long accountId;
/**
* 公众号 appid
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
/**
* 公众号粉丝的编号
*
* 关联 {@link MpUserDO#getId()}
*/
private Long userId;
/**
* 公众号粉丝标志
*
* 冗余 {@link MpUserDO#getOpenid()}
*/
private String openid;
/**
* 消息类型
*
* 枚举 {@link WxConsts.XmlMsgType}
*/
private String type;
/**
* 消息来源
*
* 枚举 {@link MpMessageSendFromEnum}
*/
private Integer sendFrom;
// ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
/**
* 消息内容
*
* 消息类型为 {@link WxConsts.XmlMsgType} TEXT
*/
private String content;
/**
* 媒体文件的编号
*
* 消息类型为 {@link WxConsts.XmlMsgType} IMAGEVOICEVIDEO
*/
private String mediaId;
/**
* 媒体文件的 URL
*/
private String mediaUrl;
/**
* 语音识别后文本
*
* 消息类型为 {@link WxConsts.XmlMsgType} VOICE
*/
private String recognition;
/**
* 语音格式 amrspeex
*
* 消息类型为 {@link WxConsts.XmlMsgType} VOICE
*/
private String format;
/**
* 标题
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEOMUSICLINK
*/
private String title;
/**
* 描述
*
* 消息类型为 {@link WxConsts.XmlMsgType} VIDEOMUSIC
*/
private String description;
/**
* 缩略图的媒体 id通过素材管理中的接口上传多媒体文件得到的 id
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSICVIDEO
*/
private String thumbMediaId;
/**
* 缩略图的媒体 URL
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSICVIDEO
*/
private String thumbMediaUrl;
/**
* 点击图文消息跳转链接
*
* 消息类型为 {@link WxConsts.XmlMsgType} LINK
*/
private String url;
/**
* 地理位置维度
*
* 消息类型为 {@link WxConsts.XmlMsgType} LOCATION
*/
private Double locationX;
/**
* 地理位置经度
*
* 消息类型为 {@link WxConsts.XmlMsgType} LOCATION
*/
private Double locationY;
/**
* 地图缩放大小
*
* 消息类型为 {@link WxConsts.XmlMsgType} LOCATION
*/
private Double scale;
/**
* 详细地址
*
* 消息类型为 {@link WxConsts.XmlMsgType} LOCATION
*
* 例如说杨浦区黄兴路 221-4 号临
*/
private String label;
/**
* 图文消息数组
*
* 消息类型为 {@link WxConsts.XmlMsgType} NEWS
*/
@TableField(typeHandler = ArticleTypeHandler.class)
private List<Article> articles;
/**
* 音乐链接
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC
*/
private String musicUrl;
/**
* 高质量音乐链接
*
* WIFI 环境优先使用该链接播放音乐
*
* 消息类型为 {@link WxConsts.XmlMsgType} MUSIC
*/
private String hqMusicUrl;
// ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
/**
* 事件类型
*
* 枚举 {@link WxConsts.EventType}
*/
private String event;
/**
* 事件 Key
*
* 1. {@link WxConsts.EventType} SCANqrscene_ 为前缀后面为二维码的参数值
* 2. {@link WxConsts.EventType} CLICK与自定义菜单接口中 KEY 值对应
*/
private String eventKey;
/**
* 文章
*/
@Data
public static class Article implements Serializable {
/**
* 图文消息标题
*/
@NotEmpty(message = "图文消息标题不能为空", groups = NewsBuilder.class)
private String title;
/**
* 图文消息描述
*/
@NotEmpty(message = "图文消息描述不能为空", groups = NewsBuilder.class)
private String description;
/**
* 图片链接
*
* 支持 JPGPNG 格式较好的效果为大图 360*200小图 200*200
*/
@NotEmpty(message = "图片链接不能为空", groups = NewsBuilder.class)
private String picUrl;
/**
* 点击图文消息跳转链接
*/
@NotEmpty(message = "点击图文消息跳转链接不能为空", groups = NewsBuilder.class)
private String url;
}
// TODO @芋艿可以找一些新的思路
public static class ArticleTypeHandler extends AbstractJsonTypeHandler<List<Article>> {
@Override
protected List<Article> parse(String json) {
return JsonUtils.parseArray(json, Article.class);
}
@Override
protected String toJson(List<Article> obj) {
return JsonUtils.toJsonString(obj);
}
}
}

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.tag;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import lombok.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import me.chanjar.weixin.mp.bean.tag.WxUserTag;
/**
* 公众号标签 DO
*
* @author 芋道源码
*/
@TableName("mp_tag")
@KeySequence("mp_tag_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MpTagDO extends BaseDO {
/**
* 主键
*/
@TableId(type = IdType.INPUT)
private Long id;
/**
* 公众号标签 id
*/
private Long tagId;
/**
* 标签名
*/
private String name;
/**
* 此标签下粉丝数
*
* 冗余{@link WxUserTag#getCount()} 字段需要管理员点击同步更新该字段
*/
private Integer count;
/**
* 公众号账号的编号
*
* 关联 {@link MpAccountDO#getId()}
*/
private Long accountId;
/**
* 公众号 appId
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
}

View File

@ -0,0 +1,110 @@
package cn.iocoder.yudao.module.mp.dal.dataobject.user;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import cn.iocoder.yudao.module.mp.dal.dataobject.tag.MpTagDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 微信公众号粉丝 DO
*
* @author 芋道源码
*/
@TableName(value = "mp_user", autoResultMap = true)
@KeySequence("mp_user_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MpUserDO extends BaseDO {
/**
* 编号
*/
@TableId
private Long id;
/**
* 粉丝标识
*/
private String openid;
/**
* 关注状态
*
* 枚举 {@link CommonStatusEnum}
* 1. 开启 - 已关注
* 2. 禁用 - 取消关注
*/
private Integer subscribeStatus;
/**
* 关注时间
*/
private LocalDateTime subscribeTime;
/**
* 取消关注时间
*/
private LocalDateTime unsubscribeTime;
/**
* 昵称
*
* 注意2021-12-27 公众号接口不再返回头像和昵称只能通过微信公众号的网页登录获取
*/
private String nickname;
/**
* 头像地址
*
* 注意2021-12-27 公众号接口不再返回头像和昵称只能通过微信公众号的网页登录获取
*/
private String headImageUrl;
/**
* 语言
*/
private String language;
/**
* 国家
*/
private String country;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 备注
*/
private String remark;
/**
* 标签编号数组
*
* 注意对应的是 {@link MpTagDO#getTagId()} 字段
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> tagIds;
/**
* 公众号账号的编号
*
* 关联 {@link MpAccountDO#getId()}
*/
private Long accountId;
/**
* 公众号 appId
*
* 冗余 {@link MpAccountDO#getAppId()}
*/
private String appId;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.mp.dal.mysql.account;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.controller.admin.account.vo.MpAccountPageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MpAccountMapper extends BaseMapperX<MpAccountDO> {
default PageResult<MpAccountDO> selectPage(MpAccountPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<MpAccountDO>()
.likeIfPresent(MpAccountDO::getName, reqVO.getName())
.likeIfPresent(MpAccountDO::getAccount, reqVO.getAccount())
.likeIfPresent(MpAccountDO::getAppId, reqVO.getAppId())
.orderByDesc(MpAccountDO::getId));
}
default MpAccountDO selectByAppId(String appId) {
return selectOne(MpAccountDO::getAppId, appId);
}
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.mp.dal.mysql.material;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialPageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
public interface MpMaterialMapper extends BaseMapperX<MpMaterialDO> {
default MpMaterialDO selectByAccountIdAndMediaId(Long accountId, String mediaId) {
return selectOne(MpMaterialDO::getAccountId, accountId,
MpMaterialDO::getMediaId, mediaId);
}
default PageResult<MpMaterialDO> selectPage(MpMaterialPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<MpMaterialDO>()
.eq(MpMaterialDO::getAccountId, pageReqVO.getAccountId())
.eqIfPresent(MpMaterialDO::getPermanent, pageReqVO.getPermanent())
.eqIfPresent(MpMaterialDO::getType, pageReqVO.getType())
.orderByDesc(MpMaterialDO::getId));
}
default List<MpMaterialDO> selectListByMediaId(Collection<String> mediaIds) {
return selectList(MpMaterialDO::getMediaId, mediaIds);
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.mp.dal.mysql.menu;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.dal.dataobject.menu.MpMenuDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MpMenuMapper extends BaseMapperX<MpMenuDO> {
default MpMenuDO selectByAppIdAndMenuKey(String appId, String menuKey) {
return selectOne(MpMenuDO::getAppId, appId,
MpMenuDO::getMenuKey, menuKey);
}
default List<MpMenuDO> selectListByAccountId(Long accountId) {
return selectList(MpMenuDO::getAccountId, accountId);
}
default void deleteByAccountId(Long accountId) {
delete(new LambdaQueryWrapperX<MpMenuDO>().eq(MpMenuDO::getAccountId, accountId));
}
}

View File

@ -0,0 +1,70 @@
package cn.iocoder.yudao.module.mp.dal.mysql.message;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;
import cn.iocoder.yudao.module.mp.dal.dataobject.message.MpAutoReplyDO;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyMatchEnum;
import cn.iocoder.yudao.module.mp.enums.message.MpAutoReplyTypeEnum;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MpAutoReplyMapper extends BaseMapperX<MpAutoReplyDO> {
default PageResult<MpAutoReplyDO> selectPage(MpMessagePageReqVO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<MpAutoReplyDO>()
.eq(MpAutoReplyDO::getAccountId, pageVO.getAccountId())
.eqIfPresent(MpAutoReplyDO::getType, pageVO.getType()));
}
default List<MpAutoReplyDO> selectListByAppIdAndKeywordAll(String appId, String requestKeyword) {
return selectList(new LambdaQueryWrapperX<MpAutoReplyDO>()
.eq(MpAutoReplyDO::getAppId, appId)
.eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType())
.eq(MpAutoReplyDO::getRequestMatch, MpAutoReplyMatchEnum.ALL.getMatch())
.eq(MpAutoReplyDO::getRequestKeyword, requestKeyword));
}
default List<MpAutoReplyDO> selectListByAppIdAndKeywordLike(String appId, String requestKeyword) {
return selectList(new LambdaQueryWrapperX<MpAutoReplyDO>()
.eq(MpAutoReplyDO::getAppId, appId)
.eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType())
.eq(MpAutoReplyDO::getRequestMatch, MpAutoReplyMatchEnum.LIKE.getMatch())
.like(MpAutoReplyDO::getRequestKeyword, requestKeyword));
}
default List<MpAutoReplyDO> selectListByAppIdAndMessage(String appId, String requestMessageType) {
return selectList(new LambdaQueryWrapperX<MpAutoReplyDO>()
.eq(MpAutoReplyDO::getAppId, appId)
.eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.MESSAGE.getType())
.eq(MpAutoReplyDO::getRequestMessageType, requestMessageType));
}
default List<MpAutoReplyDO> selectListByAppIdAndSubscribe(String appId) {
return selectList(new LambdaQueryWrapperX<MpAutoReplyDO>()
.eq(MpAutoReplyDO::getAppId, appId)
.eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.SUBSCRIBE.getType()));
}
default MpAutoReplyDO selectByAccountIdAndSubscribe(Long accountId) {
return selectOne(MpAutoReplyDO::getAccountId, accountId,
MpAutoReplyDO::getType, MpAutoReplyTypeEnum.SUBSCRIBE.getType());
}
default MpAutoReplyDO selectByAccountIdAndMessage(Long accountId, String requestMessageType) {
return selectOne(new LambdaQueryWrapperX<MpAutoReplyDO>()
.eq(MpAutoReplyDO::getAccountId, accountId)
.eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.MESSAGE.getType())
.eq(MpAutoReplyDO::getRequestMessageType, requestMessageType));
}
default MpAutoReplyDO selectByAccountIdAndKeyword(Long accountId, String requestKeyword) {
return selectOne(new LambdaQueryWrapperX<MpAutoReplyDO>()
.eq(MpAutoReplyDO::getAccountId, accountId)
.eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType())
.eq(MpAutoReplyDO::getRequestKeyword, requestKeyword));
}
}

Some files were not shown because too many files have changed in this diff Show More