Merge remote-tracking branch 'origin/master'

# Conflicts:
#	sql/mysql/optional/vue3-menu.sql
#	sql/mysql/ruoyi-vue-pro.sql
#	yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceTest.java
pull/20/head
YunaiV 2023-02-10 23:01:08 +08:00
commit a58dc7b543
285 changed files with 11135 additions and 4677 deletions

View File

@ -34,7 +34,7 @@
* 高效率开发,使用代码生成器可以一键生成前后端代码 + 单元测试 + Swagger 接口文档 + Validator 参数校验
* 集成微信小程序、微信公众号、企业微信、钉钉等三方登陆,集成支付宝、微信等支付与退款
* 集成阿里云、腾讯云等短信渠道,集成 MinIO、阿里云、腾讯云、七牛云等云存储服务
* 集成报表设计器,支持数据报表、图形报表、打印设计等
* 集成报表设计器、大屏设计器,通过拖拽即可生成酷炫的报表与大屏
| 项目名 | 说明 | 传送门 |
|----------------------|------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
@ -55,6 +55,14 @@
③ 代码整洁、架构整洁,遵循《阿里巴巴 Java 开发手册》规范代码注释详细57000 行 Java 代码22000 行代码注释。
## 🤝 项目外包
我们也是接外包滴,如果你有项目想要外包,可以微信联系【**Aix9975**】。
团队包含专业的项目经理、架构师、前端工程师、后端工程师、测试工程师、运维工程师,可以提供全流程的外包服务。
项目可以是商城、SCRM 系统、OA 系统、物流系统、ERP 系统、CMS 系统、HIS 系统、支付系统、IM 聊天、微信公众号、微信小程序等等。
## 🐼 内置功能
系统内置多种多种业务功能,可以用于快速你的业务系统:
@ -91,6 +99,8 @@
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
@ -151,7 +161,7 @@ ps核心功能已经实现正在对接微信小程序中...
| | 功能 | 描述 |
|-----|----------|----------------------------------------------|
| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 |
| 🚀 | 大屏设计器 | 建设中... 拖拽式实现可视化数据大屏 |
| 🚀 | 大屏设计器 | 拖拽生成数据大屏,内置几十种图表组件 |
### 微信公众号
@ -184,21 +194,21 @@ ps核心功能已经实现正在对接微信小程序中...
## 🐨 技术栈
| 项目 | 说明 |
|------------------------------|--------------------|
| `yudao-dependencies` | Maven 依赖版本管理 |
| `yudao-framework` | Java 框架拓展 |
| `yudao-ui-admin` | 管理后台的 Vue2 前端项目 |
| `yudao-ui-admin-vue3` | 管理后台的 Vue3 前端项目 |
| `yudao-ui-admin-uniapp` | 管理后台的 uni-app 多端项目 |
| `yudao-ui-app` | 用户 APP 的 UI 界面 |
| `yudao-gateway` | 服务网关的 Module 模块 |
| `yudao-module-system` | 系统功能的 Module 模块 |
| `yudao-module-member` | 会员中心的 Module 模块 |
| `yudao-module-infra` | 基础设施的 Module 模块 |
| `yudao-module-bpm` | 工作流程的 Module 模块 |
| `yudao-module-pay` | 支付系统的 Module 模块 |
| `yudao-module-visualization` | 大屏报表 Module 模块 |
| 项目 | 说明 |
|-------------------------|--------------------|
| `yudao-dependencies` | Maven 依赖版本管理 |
| `yudao-framework` | Java 框架拓展 |
| `yudao-ui-admin` | 管理后台的 Vue2 前端项目 |
| `yudao-ui-admin-vue3` | 管理后台的 Vue3 前端项目 |
| `yudao-ui-admin-uniapp` | 管理后台的 uni-app 多端项目 |
| `yudao-ui-app` | 用户 APP 的 UI 界面 |
| `yudao-gateway` | 服务网关的 Module 模块 |
| `yudao-module-system` | 系统功能的 Module 模块 |
| `yudao-module-member` | 会员中心的 Module 模块 |
| `yudao-module-infra` | 基础设施的 Module 模块 |
| `yudao-module-bpm` | 工作流程的 Module 模块 |
| `yudao-module-pay` | 支付系统的 Module 模块 |
| `yudao-module-report` | 大屏报表 Module 模块 |
### 后端
@ -306,6 +316,7 @@ ps核心功能已经实现正在对接微信小程序中...
| 模块 | biu | biu | biu |
|---------|------------------------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------------------------------|
| 报表设计器 | ![数据报表](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-数据报表.jpg?imageView2/2/format/webp/w/1280) | ![图形报表](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-图形报表.jpg?imageView2/2/format/webp/w/1280) | ![报表设计器-打印设计](https://static.iocoder.cn/images/ruoyi-vue-pro/报表设计器-打印设计.jpg?imageView2/2/format/webp/w/1280) |
| 大屏设计器 | ![大屏列表](https://static.iocoder.cn/images/ruoyi-vue-pro/大屏设计器-列表.jpg?imageView2/2/format/webp/w/1280) | ![大屏预览](https://static.iocoder.cn/images/ruoyi-vue-pro/大屏设计器-预览.jpg?imageView2/2/format/webp/w/1280) | ![大屏编辑](https://static.iocoder.cn/images/ruoyi-vue-pro/大屏设计器-编辑.jpg?imageView2/2/format/webp/w/1280) |
### 移动端(管理后台)

View File

@ -17,6 +17,7 @@
<module>yudao-module-system</module>
<module>yudao-module-infra</module>
<!-- <module>yudao-module-pay</module>-->
<module>yudao-module-report</module>
</modules>
<name>${project.artifactId}</name>

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 ='交易订单明细表';

File diff suppressed because one or more lines are too long

View File

@ -74,6 +74,8 @@
<aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>
<tencentcloud-sdk-java.version>3.1.676</tencentcloud-sdk-java.version>
<justauth.version>1.4.0</justauth.version>
<jimureport.version>1.5.6</jimureport.version>
<xercesImpl.version>2.12.2</xercesImpl.version>
</properties>
<dependencyManagement>
@ -650,6 +652,24 @@
</exclusions>
</dependency>
<!-- 积木报表-->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot-starter</artifactId>
<version>${jimureport.version}</version>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>${xercesImpl.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -1,8 +1,11 @@
package cn.iocoder.yudao.framework.common.enums;
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
*
*
@ -10,11 +13,13 @@ import lombok.Getter;
*/
@Getter
@AllArgsConstructor
public enum CommonStatusEnum {
public enum CommonStatusEnum implements IntArrayValuable {
ENABLE(0, "开启"),
DISABLE(1, "关闭");
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray();
/**
*
*/
@ -24,4 +29,9 @@ public enum CommonStatusEnum {
*/
private final String name;
@Override
public int[] array() {
return ARRAYS;
}
}

View File

@ -6,7 +6,7 @@ import java.time.Duration;
import java.time.LocalDateTime;
/**
* {@link LocalDateTime}
* {@link java.time.LocalDateTime}
*
* @author
*/
@ -41,6 +41,11 @@ public class LocalDateTimeUtils {
return LocalDateTime.of(year, mouth, day, 0, 0, 0);
}
public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,
int year2, int mouth2, int day2) {
return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};
}
/**
*
*

View File

@ -16,7 +16,7 @@ import java.util.concurrent.TimeUnit;
public class BannerApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
public void run(ApplicationArguments args) {
ThreadUtil.execute(() -> {
ThreadUtil.sleep(1, TimeUnit.SECONDS); // 延迟 1 秒,保证输出到结尾
log.info("\n----------------------------------------------------------\n\t" +
@ -30,6 +30,15 @@ public class BannerApplicationRunner implements ApplicationRunner {
"https://cloud.iocoder.cn",
"https://t.zsxq.com/02Yf6M7Qn",
"https://t.zsxq.com/02B6ujIee");
// 数据报表
System.out.println("[报表模块 yudao-module-report 教程][参考 https://cloud.iocoder.cn/report/ 开启]");
// 工作流
System.out.println("[工作流模块 yudao-module-bpm 教程][参考 https://cloud.iocoder.cn/bpm/ 开启]");
// 微信公众号
System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
// 商城
System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
});
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.framework.datapermission.core.util;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionContextHolder;
import lombok.SneakyThrows;
/**
* Util
*
* @author
*/
public class DataPermissionUtils {
private static DataPermission DATA_PERMISSION_DISABLE;
@DataPermission(enable = false)
@SneakyThrows
private static DataPermission getDisableDataPermissionDisable() {
if (DATA_PERMISSION_DISABLE == null) {
DATA_PERMISSION_DISABLE = DataPermissionUtils.class
.getDeclaredMethod("getDisableDataPermissionDisable")
.getAnnotation(DataPermission.class);
}
return DATA_PERMISSION_DISABLE;
}
/**
*
*
* @param runnable
*/
public static void executeIgnore(Runnable runnable) {
DataPermission dataPermission = getDisableDataPermissionDisable();
DataPermissionContextHolder.add(dataPermission);
try {
// 执行 runnable
runnable.run();
} finally {
DataPermissionContextHolder.remove();
}
}
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.framework.datapermission.core.utils;
import cn.iocoder.yudao.framework.datapermission.core.aop.DataPermissionContextHolder;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class DataPermissionUtilsTest {
@Test
public void testExecuteIgnore() {
DataPermissionUtils.executeIgnore(() -> assertFalse(DataPermissionContextHolder.get().enable()));
}
}

View File

@ -49,7 +49,7 @@ public class ErrorCodeAutoGeneratorImpl implements ErrorCodeAutoGenerator {
log.info("[execute][解析到错误码数量为 ({}) 个]", autoGenerateDTOs.size());
// 第二步,写入到 system 服务
errorCodeApi.autoGenerateErrorCodes(autoGenerateDTOs).checkError();
errorCodeApi.autoGenerateErrorCodeList(autoGenerateDTOs).checkError();
log.info("[execute][写入到 system 组件完成]");
}

View File

@ -49,12 +49,14 @@
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-job</artifactId>
<optional>true</optional>
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-mq</artifactId>
<optional>true</optional>
</dependency>
<!-- Test 测试相关 -->

View File

@ -21,6 +21,7 @@ import com.xxl.job.core.executor.XxlJobExecutor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@ -90,44 +91,10 @@ public class YudaoTenantAutoConfiguration {
return registrationBean;
}
// ========== MQ ==========
@Bean
@GlobalChannelInterceptor // 必须添加在方法上,否则无法生效
public TenantChannelInterceptor tenantChannelInterceptor() {
return new TenantChannelInterceptor();
}
@Bean
public FunctionAroundWrapper functionAroundWrapper() {
return new TenantFunctionAroundWrapper();
}
// ========== Job ==========
@Bean
public BeanPostProcessor jobHandlerBeanPostProcessor(TenantFrameworkService tenantFrameworkService) {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof XxlJobExecutor)) {
return bean;
}
// // 有 TenantJob 注解的情况下,才会进行处理
// if (!AnnotationUtil.hasAnnotation(bean.getClass(), TenantJob.class)) {
// return bean;
// }
//
// // 使用 TenantJobHandlerDecorator 装饰
// return new TenantJobHandlerDecorator(tenantFrameworkService, (JobHandler) bean);
return bean;
}
};
}
@Bean
@ConditionalOnClass(name = "com.xxl.job.core.handler.annotation.XxlJob")
public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) {
return new TenantJobAspect(tenantFrameworkService);
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.framework.tenant.config;
import cn.iocoder.yudao.framework.common.enums.WebFilterOrderEnum;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnoreAspect;
import cn.iocoder.yudao.framework.tenant.core.db.TenantDatabaseInterceptor;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJobAspect;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantChannelInterceptor;
import cn.iocoder.yudao.framework.tenant.core.mq.TenantFunctionAroundWrapper;
import cn.iocoder.yudao.framework.tenant.core.redis.TenantRedisCacheManager;
import cn.iocoder.yudao.framework.tenant.core.security.TenantSecurityWebFilter;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService;
import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkServiceImpl;
import cn.iocoder.yudao.framework.tenant.core.web.TenantContextWebFilter;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.module.system.api.tenant.TenantApi;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.function.context.catalog.FunctionAroundWrapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.integration.config.GlobalChannelInterceptor;
import java.util.Objects;
@AutoConfiguration
@ConditionalOnProperty(prefix = "yudao.tenant", value = "enable", matchIfMissing = true) // 允许使用 yudao.tenant.enable=false 禁用多租户
@ConditionalOnClass(name = {
"org.springframework.messaging.support.ChannelInterceptor",
"org.springframework.cloud.function.context.catalog.FunctionAroundWrapper"
})
@EnableConfigurationProperties(TenantProperties.class)
public class YudaoTenantMQAutoConfiguration {
@Bean
@GlobalChannelInterceptor // 必须添加在方法上,否则无法生效
public TenantChannelInterceptor tenantChannelInterceptor() {
return new TenantChannelInterceptor();
}
@Bean
public FunctionAroundWrapper functionAroundWrapper() {
return new TenantFunctionAroundWrapper();
}
}

View File

@ -30,7 +30,7 @@ public class TenantFrameworkServiceImpl implements TenantFrameworkService {
@Override
public List<Long> load(Object key) {
return tenantApi.getTenantIds().getCheckedData();
return tenantApi.getTenantIdList().getCheckedData();
}
});

View File

@ -1,2 +1,3 @@
cn.iocoder.yudao.framework.tenant.config.YudaoTenantRpcAutoConfiguration
cn.iocoder.yudao.framework.tenant.config.YudaoTenantAutoConfiguration
cn.iocoder.yudao.framework.tenant.config.YudaoTenantMQAutoConfiguration

View File

@ -94,6 +94,19 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
return this;
}
public QueryWrapperX<T> betweenIfPresent(String column, Object[] values) {
if (values!= null && values.length != 0 && values[0] != null && values[1] != null) {
return (QueryWrapperX<T>) super.between(column, values[0], values[1]);
}
if (values!= null && values.length != 0 && values[0] != null) {
return (QueryWrapperX<T>) ge(column, values[0]);
}
if (values!= null && values.length != 0 && values[1] != null) {
return (QueryWrapperX<T>) le(column, values[1]);
}
return this;
}
// ========== 重写父类方法,方便链式调用 ==========
@Override
@ -133,19 +146,19 @@ public class QueryWrapperX<T> extends QueryWrapper<T> {
*
* @return this
*/
public QueryWrapperX<T> limit1() {
public QueryWrapperX<T> limitN(int n) {
Assert.notNull(SqlConstants.DB_TYPE, "获取不到数据库的类型");
switch (SqlConstants.DB_TYPE) {
case ORACLE:
case ORACLE_12C:
super.eq("ROWNUM", 1);
super.eq("ROWNUM", n);
break;
case SQL_SERVER:
case SQL_SERVER2005:
super.select("TOP 1 *"); // 由于 SQL Server 是通过 SELECT TOP 1 实现限制一条,所以只好使用 * 查询剩余字段
super.select("TOP " + n + " *"); // 由于 SQL Server 是通过 SELECT TOP 1 实现限制一条,所以只好使用 * 查询剩余字段
break;
default:
super.last("LIMIT 1");
super.last("LIMIT " + n);
}
return this;
}

View File

@ -7,7 +7,10 @@ import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import uk.co.jemos.podam.api.PodamFactory;
import uk.co.jemos.podam.api.PodamFactoryImpl;
import uk.co.jemos.podam.common.AttributeStrategy;
import javax.validation.constraints.Email;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.util.Arrays;
@ -47,7 +50,7 @@ public class RandomUtils {
}
// 如果是 type、status 结尾的字段,返回 tinyint 范围
if (StrUtil.endWithAnyIgnoreCase(attributeMetadata.getAttributeName(),
"type", "status", "category", "scope")) {
"type", "status", "category", "scope", "result")) {
return RandomUtil.randomInt(0, TINYINT_MAX + 1);
}
return RandomUtil.randomInt();
@ -95,6 +98,10 @@ public class RandomUtils {
return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
}
public static String randomEmail() {
return randomString() + "@qq.com";
}
@SafeVarargs
public static <T> T randomPojo(Class<T> clazz, Consumer<T>... consumers) {
T pojo = PODAM_FACTORY.manufacturePojo(clazz);

View File

@ -116,7 +116,7 @@ public class WebFrameworkUtils {
return (CommonResult<?>) request.getAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT);
}
private static HttpServletRequest getRequest() {
public static HttpServletRequest getRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (!(requestAttributes instanceof ServletRequestAttributes)) {
return null;

View File

@ -37,5 +37,15 @@ spring:
- Path=/admin-api/bpm/**
filters:
- RewritePath=/admin-api/bpm/v2/api-docs, /v2/api-docs
- id: report-admin-api # 路由的编号
uri: grayLb://report-server
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/admin-api/report/**
filters:
- RewritePath=/admin-api/report/v2/api-docs, /v2/api-docs
- id: report-jmreport # 路由的编号(积木报表)
uri: grayLb://report-server
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/jmreport/**
x-forwarded:
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀

View File

@ -213,18 +213,18 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
private void validTaskAssignRuleOptions(Integer type, Set<Long> options) {
if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
roleApi.validRoles(options);
roleApi.validRoleList(options);
} else if (ObjectUtils.equalsAny(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(),
BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
deptApi.validDepts(options);
deptApi.validateDeptList(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
postApi.validPosts(options);
postApi.validPostList(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER.getType())) {
adminUserApi.validUsers(options);
adminUserApi.validUserList(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_GROUP.getType())) {
userGroupService.validUserGroups(options);
} else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {
dictDataApi.validDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
dictDataApi.validateDictDatas(DictTypeConstants.TASK_ASSIGN_SCRIPT,
CollectionUtils.convertSet(options, String::valueOf));
} else {
throw new IllegalArgumentException(format("未知的规则类型({})", type));
@ -288,17 +288,17 @@ public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService {
}
private Set<Long> calculateTaskCandidateUsersByDeptMember(BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByDeptIds(rule.getOptions()).getCheckedData();
List<AdminUserRespDTO> users = adminUserApi.getUserListByDeptIds(rule.getOptions()).getCheckedData();
return convertSet(users, AdminUserRespDTO::getId);
}
private Set<Long> calculateTaskCandidateUsersByDeptLeader(BpmTaskAssignRuleDO rule) {
List<DeptRespDTO> depts = deptApi.getDepts(rule.getOptions()).getCheckedData();
List<DeptRespDTO> depts = deptApi.getDeptList(rule.getOptions()).getCheckedData();
return convertSet(depts, DeptRespDTO::getLeaderUserId);
}
private Set<Long> calculateTaskCandidateUsersByPost(BpmTaskAssignRuleDO rule) {
List<AdminUserRespDTO> users = adminUserApi.getUsersByPostIds(rule.getOptions()).getCheckedData();
List<AdminUserRespDTO> users = adminUserApi.getUserListByPostIds(rule.getOptions()).getCheckedData();
return convertSet(users, AdminUserRespDTO::getId);
}

View File

@ -87,7 +87,7 @@ public class BpmTaskAssignRuleServiceImplTest extends BaseDbUnitTest {
// mock 方法
List<AdminUserRespDTO> users = CollectionUtils.convertList(asSet(11L, 22L),
id -> new AdminUserRespDTO().setId(id));
when(adminUserApi.getUsersByDeptIds(eq(rule.getOptions()))).thenReturn(success(users));
when(adminUserApi.getUserListByDeptIds(eq(rule.getOptions()))).thenReturn(success(users));
mockGetUserMap(asSet(11L, 22L));
// 调用
@ -104,7 +104,7 @@ public class BpmTaskAssignRuleServiceImplTest extends BaseDbUnitTest {
// mock 方法
DeptRespDTO dept1 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(11L));
DeptRespDTO dept2 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(22L));
when(deptApi.getDepts(eq(rule.getOptions()))).thenReturn(success(Arrays.asList(dept1, dept2)));
when(deptApi.getDeptList(eq(rule.getOptions()))).thenReturn(success(Arrays.asList(dept1, dept2)));
mockGetUserMap(asSet(11L, 22L));
// 调用
@ -121,7 +121,7 @@ public class BpmTaskAssignRuleServiceImplTest extends BaseDbUnitTest {
// mock 方法
List<AdminUserRespDTO> users = CollectionUtils.convertList(asSet(11L, 22L),
id -> new AdminUserRespDTO().setId(id));
when(adminUserApi.getUsersByPostIds(eq(rule.getOptions()))).thenReturn(success(users));
when(adminUserApi.getUserListByPostIds(eq(rule.getOptions()))).thenReturn(success(users));
mockGetUserMap(asSet(11L, 22L));
// 调用

View File

@ -114,7 +114,6 @@
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.infra.controller.admin.config;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
@ -23,6 +22,7 @@ import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
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.operatelog.core.enums.OperateTypeEnum.EXPORT;
@ -76,7 +76,7 @@ public class ConfigController {
return null;
}
if (!config.getVisible()) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.CONFIG_GET_VALUE_ERROR_IF_VISIBLE);
throw exception(ErrorCodeConstants.CONFIG_GET_VALUE_ERROR_IF_VISIBLE);
}
return success(config.getValue());
}
@ -93,8 +93,8 @@ public class ConfigController {
@ApiOperation("导出参数配置")
@PreAuthorize("@ss.hasPermission('infra:config:export')")
@OperateLog(type = EXPORT)
public void exportSysConfig(@Valid ConfigExportReqVO reqVO,
HttpServletResponse response) throws IOException {
public void exportConfig(@Valid ConfigExportReqVO reqVO,
HttpServletResponse response) throws IOException {
List<ConfigDO> list = configService.getConfigList(reqVO);
// 拼接数据
List<ConfigExcelVO> datas = ConfigConvert.INSTANCE.convertList(list);

View File

@ -39,27 +39,38 @@ public class CodegenColumnDO extends BaseDO {
/**
*
*
* {@link TableField#getName()}
*/
private String columnName;
/**
*
*
* {@link TableField.MetaInfo#getJdbcType()}
*/
private String dataType;
/**
*
*
* {@link TableField#getComment()}
*/
private String columnComment;
/**
*
*
* {@link TableField.MetaInfo#isNullable()}
*/
private Boolean nullable;
/**
*
*
* {@link TableField#isKeyFlag()}
*/
private Boolean primaryKey;
/**
*
*
* {@link TableField#isKeyIdentityFlag()}
*/
private Boolean autoIncrement;
/**
@ -71,12 +82,16 @@ public class CodegenColumnDO extends BaseDO {
/**
* Java
* <p>
*
* StringBoolean
*
* {@link TableField#getColumnType()}
*/
private String javaType;
/**
* Java
*
* {@link TableField#getPropertyName()}
*/
private String javaField;
/**

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenSceneEnum;
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@ -44,10 +45,14 @@ public class CodegenTableDO extends BaseDO {
/**
*
*
* {@link TableInfo#getName()}
*/
private String tableName;
/**
*
*
* {@link TableInfo#getComment()}
*/
private String tableComment;
/**

View File

@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -75,7 +76,7 @@ public class CodegenServiceImpl implements CodegenService {
private Long createCodegen0(Long userId, Long dataSourceConfigId, TableInfo tableInfo) {
// 校验导入的表和字段非空
checkTableInfo(tableInfo);
validateTableInfo(tableInfo);
// 校验是否已经存在
if (codegenTableMapper.selectByTableNameAndDataSourceConfigId(tableInfo.getName(),
dataSourceConfigId) != null) {
@ -86,7 +87,7 @@ public class CodegenServiceImpl implements CodegenService {
CodegenTableDO table = codegenBuilder.buildTable(tableInfo);
table.setDataSourceConfigId(dataSourceConfigId);
table.setScene(CodegenSceneEnum.ADMIN.getScene()); // 默认配置下,使用管理后台的模板
table.setAuthor(userApi.getUser(userId).getData().getNickname());
table.setAuthor(userApi.getUser(userId).getCheckedData().getNickname());
codegenTableMapper.insert(table);
// 构建 CodegenColumnDO 数组,插入到 DB 中
@ -99,7 +100,7 @@ public class CodegenServiceImpl implements CodegenService {
return table.getId();
}
private void checkTableInfo(TableInfo tableInfo) {
private void validateTableInfo(TableInfo tableInfo) {
if (tableInfo == null) {
throw exception(CODEGEN_IMPORT_TABLE_NULL);
}
@ -148,18 +149,33 @@ public class CodegenServiceImpl implements CodegenService {
private void syncCodegen0(Long tableId, TableInfo tableInfo) {
// 校验导入的表和字段非空
checkTableInfo(tableInfo);
validateTableInfo(tableInfo);
List<TableField> tableFields = tableInfo.getFields();
// 构建 CodegenColumnDO 数组,只同步新增的字段
List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);
Set<String> codegenColumnNames = CollectionUtils.convertSet(codegenColumns, CodegenColumnDO::getColumnName);
//计算需要修改的字段,插入时重新插入,删除时将原来的删除
BiPredicate<TableField, CodegenColumnDO> pr =
(tableField, codegenColumn) -> tableField.getType().equals(codegenColumn.getDataType())
&& tableField.getMetaInfo().isNullable() == codegenColumn.getNullable()
&& tableField.isKeyFlag() == codegenColumn.getPrimaryKey()
&& tableField.getComment().equals(codegenColumn.getColumnComment());
Map<String, CodegenColumnDO> codegenColumnDOMap = CollectionUtils.convertMap(codegenColumns, CodegenColumnDO::getColumnName);
//需要修改的字段
Set<String> modifyFieldNames = tableFields.stream()
.filter(tableField -> codegenColumnDOMap.get(tableField.getColumnName()) != null
&& !pr.test(tableField, codegenColumnDOMap.get(tableField.getColumnName())))
.map(TableField::getColumnName)
.collect(Collectors.toSet());
// 计算需要删除的字段
Set<String> tableFieldNames = CollectionUtils.convertSet(tableFields, TableField::getName);
Set<Long> deleteColumnIds = codegenColumns.stream().filter(column -> !tableFieldNames.contains(column.getColumnName()))
Set<Long> deleteColumnIds = codegenColumns.stream()
.filter(column -> (!tableFieldNames.contains(column.getColumnName())) || modifyFieldNames.contains(column.getColumnName()))
.map(CodegenColumnDO::getId).collect(Collectors.toSet());
// 移除已经存在的字段
tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()));
tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()) && (!modifyFieldNames.contains(column.getColumnName())));
if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {
throw exception(CODEGEN_SYNC_NONE_CHANGE);
}

View File

@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.config.vo.ConfigUpdateReqV
import cn.iocoder.yudao.module.infra.convert.config.ConfigConvert;
import cn.iocoder.yudao.module.infra.dal.dataobject.config.ConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.config.ConfigMapper;
import cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.infra.enums.config.ConfigTypeEnum;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
@ -20,6 +19,7 @@ import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
/**
* Service
@ -35,7 +35,7 @@ public class ConfigServiceImpl implements ConfigService {
@Override
public Long createConfig(ConfigCreateReqVO reqVO) {
// 校验正确性
checkCreateOrUpdate(null, reqVO.getKey());
validateConfigForCreateOrUpdate(null, reqVO.getKey());
// 插入参数配置
ConfigDO config = ConfigConvert.INSTANCE.convert(reqVO);
config.setType(ConfigTypeEnum.CUSTOM.getType());
@ -46,19 +46,19 @@ public class ConfigServiceImpl implements ConfigService {
@Override
public void updateConfig(ConfigUpdateReqVO reqVO) {
// 校验正确性
checkCreateOrUpdate(reqVO.getId(), null); // 不允许更新 key
validateConfigForCreateOrUpdate(reqVO.getId(), null); // 不允许更新 key
// 更新参数配置
ConfigDO updateObj = ConfigConvert.INSTANCE.convert(reqVO);
configMapper.updateById(updateObj);;
configMapper.updateById(updateObj);
}
@Override
public void deleteConfig(Long id) {
// 校验配置存在
ConfigDO config = checkConfigExists(id);
ConfigDO config = validateConfigExists(id);
// 内置配置,不允许删除
if (ConfigTypeEnum.SYSTEM.getType().equals(config.getType())) {
throw exception(ErrorCodeConstants.CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);
throw exception(CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);
}
// 删除
configMapper.deleteById(id);
@ -84,39 +84,39 @@ public class ConfigServiceImpl implements ConfigService {
return configMapper.selectList(reqVO);
}
private void checkCreateOrUpdate(Long id, String key) {
private void validateConfigForCreateOrUpdate(Long id, String key) {
// 校验自己存在
checkConfigExists(id);
validateConfigExists(id);
// 校验参数配置 key 的唯一性
if (StrUtil.isNotEmpty(key)) {
checkConfigKeyUnique(id, key);
validateConfigKeyUnique(id, key);
}
}
@VisibleForTesting
public ConfigDO checkConfigExists(Long id) {
public ConfigDO validateConfigExists(Long id) {
if (id == null) {
return null;
}
ConfigDO config = configMapper.selectById(id);
if (config == null) {
throw exception(ErrorCodeConstants.CONFIG_NOT_EXISTS);
throw exception(CONFIG_NOT_EXISTS);
}
return config;
}
@VisibleForTesting
public void checkConfigKeyUnique(Long id, String key) {
public void validateConfigKeyUnique(Long id, String key) {
ConfigDO config = configMapper.selectByKey(key);
if (config == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的参数配置
if (id == null) {
throw exception(ErrorCodeConstants.CONFIG_KEY_DUPLICATE);
throw exception(CONFIG_KEY_DUPLICATE);
}
if (!config.getId().equals(id)) {
throw exception(ErrorCodeConstants.CONFIG_KEY_DUPLICATE);
throw exception(CONFIG_KEY_DUPLICATE);
}
}

View File

@ -37,7 +37,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
@Override
public Long createDataSourceConfig(DataSourceConfigCreateReqVO createReqVO) {
DataSourceConfigDO dataSourceConfig = DataSourceConfigConvert.INSTANCE.convert(createReqVO);
checkConnectionOK(dataSourceConfig);
validateConnectionOK(dataSourceConfig);
// 插入
dataSourceConfigMapper.insert(dataSourceConfig);
@ -50,7 +50,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
// 校验存在
validateDataSourceConfigExists(updateReqVO.getId());
DataSourceConfigDO updateObj = DataSourceConfigConvert.INSTANCE.convert(updateReqVO);
checkConnectionOK(updateObj);
validateConnectionOK(updateObj);
// 更新
dataSourceConfigMapper.updateById(updateObj);
@ -88,7 +88,7 @@ public class DataSourceConfigServiceImpl implements DataSourceConfigService {
return result;
}
private void checkConnectionOK(DataSourceConfigDO config) {
private void validateConnectionOK(DataSourceConfigDO config) {
boolean success = JdbcUtils.isConnectionOK(config.getUrl(), config.getUsername(), config.getPassword());
if (!success) {
throw exception(DATA_SOURCE_CONFIG_NOT_OK);

View File

@ -41,7 +41,7 @@ public class DatabaseTableServiceImpl implements DatabaseTableService {
return CollUtil.getFirst(getTableList0(dataSourceConfigId, name));
}
public List<TableInfo> getTableList0(Long dataSourceConfigId, String name) {
private List<TableInfo> getTableList0(Long dataSourceConfigId, String name) {
// 获得数据源配置
DataSourceConfigDO config = dataSourceConfigService.getDataSourceConfig(dataSourceConfigId);
Assert.notNull(config, "数据源({}) 不存在!", dataSourceConfigId);

View File

@ -8,8 +8,6 @@ import cn.iocoder.yudao.module.infra.controller.admin.file.vo.config.FileConfigU
import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileConfigDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* Service
@ -60,14 +58,6 @@ public interface FileConfigService {
*/
FileConfigDO getFileConfig(Long id);
/**
*
*
* @param ids
* @return
*/
List<FileConfigDO> getFileConfigList(Collection<Long> ids);
/**
*
*

View File

@ -27,7 +27,6 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.validation.Validator;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -95,7 +94,7 @@ public class FileConfigServiceImpl implements FileConfigService {
@Override
public void updateFileConfig(FileConfigUpdateReqVO updateReqVO) {
// 校验存在
FileConfigDO config = this.validateFileConfigExists(updateReqVO.getId());
FileConfigDO config = validateFileConfigExists(updateReqVO.getId());
// 更新
FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO)
.setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));
@ -108,7 +107,7 @@ public class FileConfigServiceImpl implements FileConfigService {
@Transactional(rollbackFor = Exception.class)
public void updateFileConfigMaster(Long id) {
// 校验存在
this.validateFileConfigExists(id);
validateFileConfigExists(id);
// 更新其它为非 master
fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false));
// 更新
@ -138,9 +137,9 @@ public class FileConfigServiceImpl implements FileConfigService {
@Override
public void deleteFileConfig(Long id) {
// 校验存在
FileConfigDO config = this.validateFileConfigExists(id);
FileConfigDO config = validateFileConfigExists(id);
if (Boolean.TRUE.equals(config.getMaster())) {
throw exception(FILE_CONFIG_DELETE_FAIL_MASTER);
throw exception(FILE_CONFIG_DELETE_FAIL_MASTER);
}
// 删除
fileConfigMapper.deleteById(id);
@ -161,11 +160,6 @@ public class FileConfigServiceImpl implements FileConfigService {
return fileConfigMapper.selectById(id);
}
@Override
public List<FileConfigDO> getFileConfigList(Collection<Long> ids) {
return fileConfigMapper.selectBatchIds(ids);
}
@Override
public PageResult<FileConfigDO> getFileConfigPage(FileConfigPageReqVO pageReqVO) {
return fileConfigMapper.selectPage(pageReqVO);
@ -174,7 +168,7 @@ public class FileConfigServiceImpl implements FileConfigService {
@Override
public String testFileConfig(Long id) throws Exception {
// 校验存在
this.validateFileConfigExists(id);
validateFileConfigExists(id);
// 上传文件
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
return fileClientFactory.getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");

View File

@ -69,7 +69,7 @@ public class FileServiceImpl implements FileService {
@Override
public void deleteFile(Long id) throws Exception {
// 校验存在
FileDO file = this.validateFileExists(id);
FileDO file = validateFileExists(id);
// 从文件存储器中删除
FileClient client = fileConfigService.getFileClient(file.getConfigId());

View File

@ -1,17 +1,13 @@
package cn.iocoder.yudao.module.infra.service.logger;
import cn.iocoder.yudao.framework.apilog.core.service.ApiErrorLog;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogExportReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;
import cn.iocoder.yudao.module.infra.convert.logger.ApiErrorLogConvert;
import cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiErrorLogDO;
import cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.infra.enums.logger.ApiErrorLogProcessStatusEnum;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -19,6 +15,10 @@ import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_PROCESSED;
/**
* API Service
*
@ -33,8 +33,8 @@ public class ApiErrorLogServiceImpl implements ApiErrorLogService {
@Override
public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {
ApiErrorLogDO apiErrorLog = ApiErrorLogConvert.INSTANCE.convert(createDTO);
apiErrorLog.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
ApiErrorLogDO apiErrorLog = ApiErrorLogConvert.INSTANCE.convert(createDTO)
.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
apiErrorLogMapper.insert(apiErrorLog);
}
@ -52,10 +52,10 @@ public class ApiErrorLogServiceImpl implements ApiErrorLogService {
public void updateApiErrorLogProcess(Long id, Integer processStatus, Long processUserId) {
ApiErrorLogDO errorLog = apiErrorLogMapper.selectById(id);
if (errorLog == null) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND);
throw exception(API_ERROR_LOG_NOT_FOUND);
}
if (!ApiErrorLogProcessStatusEnum.INIT.getStatus().equals(errorLog.getProcessStatus())) {
throw ServiceExceptionUtil.exception(ErrorCodeConstants.API_ERROR_LOG_PROCESSED);
throw exception(API_ERROR_LOG_PROCESSED);
}
// 标记处理
apiErrorLogMapper.updateById(ApiErrorLogDO.builder().id(id).processStatus(processStatus)

View File

@ -45,7 +45,7 @@ public class TestDemoServiceImpl implements TestDemoService {
@CacheEvict(value = "test", key = "#updateReqVO.id")
public void updateTestDemo(TestDemoUpdateReqVO updateReqVO) {
// 校验存在
this.validateTestDemoExists(updateReqVO.getId());
validateTestDemoExists(updateReqVO.getId());
// 更新
TestDemoDO updateObj = TestDemoConvert.INSTANCE.convert(updateReqVO);
testDemoMapper.updateById(updateObj);
@ -55,7 +55,7 @@ public class TestDemoServiceImpl implements TestDemoService {
@CacheEvict(value = "test", key = "#id")
public void deleteTestDemo(Long id) {
// 校验存在
this.validateTestDemoExists(id);
validateTestDemoExists(id);
// 删除
testDemoMapper.deleteById(id);
}

View File

@ -39,7 +39,7 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
@Override
public void update${simpleClassName}(${sceneEnum.prefixClass}${table.className}UpdateReqVO updateReqVO) {
// 校验存在
this.validate${simpleClassName}Exists(updateReqVO.getId());
validate${simpleClassName}Exists(updateReqVO.getId());
// 更新
${table.className}DO updateObj = ${table.className}Convert.INSTANCE.convert(updateReqVO);
${classNameVar}Mapper.updateById(updateObj);
@ -48,7 +48,7 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
@Override
public void delete${simpleClassName}(${primaryColumn.javaType} id) {
// 校验存在
this.validate${simpleClassName}Exists(id);
validate${simpleClassName}Exists(id);
// 删除
${classNameVar}Mapper.deleteById(id);
}

View File

@ -52,7 +52,7 @@ import static org.mockito.Mockito.*;
#if (${column.listOperation})
#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写
#if (${column.listOperationCondition} == "BETWEEN")## BETWEEN 的情况
reqVO.set${JavaField}((new LocalDateTime[]{}));
reqVO.set${JavaField}(buildBetweenTime(2023, 2, 1, 2023, 2, 28));
#else
reqVO.set$JavaField(null);
#end

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.config;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
import cn.iocoder.yudao.module.infra.controller.admin.config.vo.ConfigCreateReqVO;
@ -12,27 +11,25 @@ import cn.iocoder.yudao.module.infra.controller.admin.config.vo.ConfigUpdateReqV
import cn.iocoder.yudao.module.infra.dal.dataobject.config.ConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.config.ConfigMapper;
import cn.iocoder.yudao.module.infra.enums.config.ConfigTypeEnum;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.function.Consumer;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildLocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@Import(ConfigServiceImpl.class)
public class ConfigServiceTest extends BaseDbUnitTest {
public class ConfigServiceImplTest extends BaseDbUnitTest {
@Resource
private ConfigServiceImpl configService;
@ -52,7 +49,7 @@ public class ConfigServiceTest extends BaseDbUnitTest {
// 校验记录的属性是否正确
ConfigDO config = configMapper.selectById(configId);
assertPojoEquals(reqVO, config);
Assertions.assertEquals(ConfigTypeEnum.CUSTOM.getType(), config.getType());
assertEquals(ConfigTypeEnum.CUSTOM.getType(), config.getType());
}
@Test
@ -103,40 +100,40 @@ public class ConfigServiceTest extends BaseDbUnitTest {
}
@Test
public void testCheckConfigExists_success() {
public void testValidateConfigExists_success() {
// mock 数据
ConfigDO dbConfigDO = randomConfigDO();
configMapper.insert(dbConfigDO);// @Sql: 先插入出一条存在的数据
// 调用成功
configService.checkConfigExists(dbConfigDO.getId());
configService.validateConfigExists(dbConfigDO.getId());
}
@Test
public void testCheckConfigExist_notExists() {
assertServiceException(() -> configService.checkConfigExists(randomLongId()), CONFIG_NOT_EXISTS);
public void testValidateConfigExist_notExists() {
assertServiceException(() -> configService.validateConfigExists(randomLongId()), CONFIG_NOT_EXISTS);
}
@Test
public void testCheckConfigKeyUnique_success() {
public void testValidateConfigKeyUnique_success() {
// 调用,成功
configService.checkConfigKeyUnique(randomLongId(), randomString());
configService.validateConfigKeyUnique(randomLongId(), randomString());
}
@Test
public void testCheckConfigKeyUnique_keyDuplicateForCreate() {
public void testValidateConfigKeyUnique_keyDuplicateForCreate() {
// 准备参数
String key = randomString();
// mock 数据
configMapper.insert(randomConfigDO(o -> o.setConfigKey(key)));
// 调用,校验异常
assertServiceException(() -> configService.checkConfigKeyUnique(null, key),
assertServiceException(() -> configService.validateConfigKeyUnique(null, key),
CONFIG_KEY_DUPLICATE);
}
@Test
public void testCheckConfigKeyUnique_keyDuplicateForUpdate() {
public void testValidateConfigKeyUnique_keyDuplicateForUpdate() {
// 准备参数
Long id = randomLongId();
String key = randomString();
@ -144,7 +141,7 @@ public class ConfigServiceTest extends BaseDbUnitTest {
configMapper.insert(randomConfigDO(o -> o.setConfigKey(key)));
// 调用,校验异常
assertServiceException(() -> configService.checkConfigKeyUnique(id, key),
assertServiceException(() -> configService.validateConfigKeyUnique(id, key),
CONFIG_KEY_DUPLICATE);
}
@ -155,23 +152,23 @@ public class ConfigServiceTest extends BaseDbUnitTest {
o.setName("芋艿");
o.setConfigKey("yunai");
o.setType(ConfigTypeEnum.SYSTEM.getType());
o.setCreateTime(buildLocalDateTime(2021, 2, 1));
o.setCreateTime(buildTime(2021, 2, 1));
});
configMapper.insert(dbConfig);
// 测试 name 不匹配
configMapper.insert(ObjectUtils.cloneIgnoreId(dbConfig, o -> o.setName("土豆")));
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setName("土豆")));
// 测试 key 不匹配
configMapper.insert(ObjectUtils.cloneIgnoreId(dbConfig, o -> o.setConfigKey("tudou")));
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setConfigKey("tudou")));
// 测试 type 不匹配
configMapper.insert(ObjectUtils.cloneIgnoreId(dbConfig, o -> o.setType(ConfigTypeEnum.CUSTOM.getType())));
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setType(ConfigTypeEnum.CUSTOM.getType())));
// 测试 createTime 不匹配
configMapper.insert(ObjectUtils.cloneIgnoreId(dbConfig, o -> o.setCreateTime(buildLocalDateTime(2021, 1, 1))));
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1))));
// 准备参数
ConfigPageReqVO reqVO = new ConfigPageReqVO();
reqVO.setName("艿");
reqVO.setKey("nai");
reqVO.setType(ConfigTypeEnum.SYSTEM.getType());
reqVO.setCreateTime((new LocalDateTime[]{buildLocalDateTime(2021, 1, 15),buildLocalDateTime(2021, 2, 15)}));
reqVO.setCreateTime(buildBetweenTime(2021, 1, 15, 2021, 2, 15));
// 调用
PageResult<ConfigDO> pageResult = configService.getConfigPage(reqVO);
@ -188,23 +185,23 @@ public class ConfigServiceTest extends BaseDbUnitTest {
o.setName("芋艿");
o.setConfigKey("yunai");
o.setType(ConfigTypeEnum.SYSTEM.getType());
o.setCreateTime(buildLocalDateTime(2021, 2, 1));
o.setCreateTime(buildTime(2021, 2, 1));
});
configMapper.insert(dbConfig);
// 测试 name 不匹配
configMapper.insert(ObjectUtils.cloneIgnoreId(dbConfig, o -> o.setName("土豆")));
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setName("土豆")));
// 测试 key 不匹配
configMapper.insert(ObjectUtils.cloneIgnoreId(dbConfig, o -> o.setConfigKey("tudou")));
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setConfigKey("tudou")));
// 测试 type 不匹配
configMapper.insert(ObjectUtils.cloneIgnoreId(dbConfig, o -> o.setType(ConfigTypeEnum.CUSTOM.getType())));
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setType(ConfigTypeEnum.CUSTOM.getType())));
// 测试 createTime 不匹配
configMapper.insert(ObjectUtils.cloneIgnoreId(dbConfig, o -> o.setCreateTime(buildLocalDateTime(2021, 1, 1))));
configMapper.insert(cloneIgnoreId(dbConfig, o -> o.setCreateTime(buildTime(2021, 1, 1))));
// 准备参数
ConfigExportReqVO reqVO = new ConfigExportReqVO();
reqVO.setName("艿");
reqVO.setKey("nai");
reqVO.setType(ConfigTypeEnum.SYSTEM.getType());
reqVO.setCreateTime((new LocalDateTime[]{buildLocalDateTime(2021, 1, 15),buildLocalDateTime(2021, 2, 15)}));
reqVO.setCreateTime(buildBetweenTime(2021, 1, 15, 2021, 2, 15));
// 调用
List<ConfigDO> list = configService.getConfigList(reqVO);
@ -213,6 +210,21 @@ public class ConfigServiceTest extends BaseDbUnitTest {
assertPojoEquals(dbConfig, list.get(0));
}
@Test
public void testGetConfig() {
// mock 数据
ConfigDO dbConfig = randomConfigDO();
configMapper.insert(dbConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbConfig.getId();
// 调用
ConfigDO config = configService.getConfig(id);
// 断言
assertNotNull(config);
assertPojoEquals(dbConfig, config);
}
@Test
public void testGetConfigByKey() {
// mock 数据

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.infra.service.db;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.iocoder.yudao.framework.mybatis.core.type.EncryptTypeHandler;
@ -9,6 +10,7 @@ import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigCrea
import cn.iocoder.yudao.module.infra.controller.admin.db.vo.DataSourceConfigUpdateReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import cn.iocoder.yudao.module.infra.dal.mysql.db.DataSourceConfigMapper;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DataSourceProperty;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -18,14 +20,14 @@ import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mockStatic;
@ -57,6 +59,11 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
ReflectUtil.setFieldValue(EncryptTypeHandler.class, "aes", aes);
when(aes.encryptBase64(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
when(aes.decryptStr(anyString())).then((Answer<String>) invocation -> invocation.getArgument(0));
// mock DynamicDataSourceProperties
when(dynamicDataSourceProperties.getPrimary()).thenReturn("primary");
when(dynamicDataSourceProperties.getDatasource()).thenReturn(MapUtil.of("primary",
new DataSourceProperty().setUrl("http://localhost:3306").setUsername("yunai").setPassword("tudou")));
}
@Test
@ -89,7 +96,6 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
o.setId(dbDataSourceConfig.getId()); // 设置更新的 ID
});
// mock 方法
// when(stringEncryptor.encrypt(eq(reqVO.getPassword()))).thenReturn("123456");
databaseUtilsMock.when(() -> JdbcUtils.isConnectionOK(eq(reqVO.getUrl()),
eq(reqVO.getUsername()), eq(reqVO.getPassword()))).thenReturn(true);
@ -142,7 +148,58 @@ public class DataSourceConfigServiceImplTest extends BaseDbUnitTest {
// 调用
DataSourceConfigDO result = dataSourceConfigMapper.selectOne(DataSourceConfigDO::getPassword,
EncryptTypeHandler.encrypt(dbDataSourceConfig.getPassword()));
System.out.println(result);
assertPojoEquals(dbDataSourceConfig, result);
}
@Test
public void testGetDataSourceConfig_master() {
// 准备参数
Long id = 0L;
// mock 方法
// 调用
DataSourceConfigDO dataSourceConfig = dataSourceConfigService.getDataSourceConfig(id);
// 断言
assertEquals(id, dataSourceConfig.getId());
assertEquals("primary", dataSourceConfig.getName());
assertEquals("http://localhost:3306", dataSourceConfig.getUrl());
assertEquals("yunai", dataSourceConfig.getUsername());
assertEquals("tudou", dataSourceConfig.getPassword());
}
@Test
public void testGetDataSourceConfig_normal() {
// mock 数据
DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbDataSourceConfig.getId();
// 调用
DataSourceConfigDO dataSourceConfig = dataSourceConfigService.getDataSourceConfig(id);
// 断言
assertPojoEquals(dbDataSourceConfig, dataSourceConfig);
}
@Test
public void testGetDataSourceConfigList() {
// mock 数据
DataSourceConfigDO dbDataSourceConfig = randomPojo(DataSourceConfigDO.class);
dataSourceConfigMapper.insert(dbDataSourceConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
// 调用
List<DataSourceConfigDO> dataSourceConfigList = dataSourceConfigService.getDataSourceConfigList();
// 断言
assertEquals(2, dataSourceConfigList.size());
// master
assertEquals(0L, dataSourceConfigList.get(0).getId());
assertEquals("primary", dataSourceConfigList.get(0).getName());
assertEquals("http://localhost:3306", dataSourceConfigList.get(0).getUrl());
assertEquals("yunai", dataSourceConfigList.get(0).getUsername());
assertEquals("tudou", dataSourceConfigList.get(0).getPassword());
// normal
assertPojoEquals(dbDataSourceConfig, dataSourceConfigList.get(1));
}
}

View File

@ -0,0 +1,89 @@
package cn.iocoder.yudao.module.infra.service.db;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.infra.dal.dataobject.db.DataSourceConfigDO;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import org.apache.ibatis.type.JdbcType;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@Import(DatabaseTableServiceImpl.class)
public class DatabaseTableServiceImplTest extends BaseDbUnitTest {
@Resource
private DatabaseTableServiceImpl databaseTableService;
@MockBean
private DataSourceConfigService dataSourceConfigService;
@Test
public void testGetTableList() {
// 准备参数
Long dataSourceConfigId = randomLongId();
// mock 方法
DataSourceConfigDO dataSourceConfig = new DataSourceConfigDO().setUsername("sa").setPassword("")
.setUrl("jdbc:h2:mem:testdb");
when(dataSourceConfigService.getDataSourceConfig(eq(dataSourceConfigId)))
.thenReturn(dataSourceConfig);
// 调用
List<TableInfo> tables = databaseTableService.getTableList(dataSourceConfigId,
"config", "参数");
// 断言
assertEquals(1, tables.size());
assertTableInfo(tables.get(0));
}
@Test
public void testGetTable() {
// 准备参数
Long dataSourceConfigId = randomLongId();
// mock 方法
DataSourceConfigDO dataSourceConfig = new DataSourceConfigDO().setUsername("sa").setPassword("")
.setUrl("jdbc:h2:mem:testdb");
when(dataSourceConfigService.getDataSourceConfig(eq(dataSourceConfigId)))
.thenReturn(dataSourceConfig);
// 调用
TableInfo tableInfo = databaseTableService.getTable(dataSourceConfigId, "infra_config");
// 断言
assertTableInfo(tableInfo);
}
private void assertTableInfo(TableInfo tableInfo) {
assertEquals("infra_config", tableInfo.getName());
assertEquals("参数配置表", tableInfo.getComment());
assertEquals(13, tableInfo.getFields().size());
// id 字段
TableField idField = tableInfo.getFields().get(0);
assertEquals("id", idField.getName());
assertEquals(JdbcType.BIGINT, idField.getMetaInfo().getJdbcType());
assertEquals("编号", idField.getComment());
assertFalse(idField.getMetaInfo().isNullable());
assertTrue(idField.isKeyFlag());
assertTrue(idField.isKeyIdentityFlag());
assertEquals(DbColumnType.LONG, idField.getColumnType());
assertEquals("id", idField.getPropertyName());
// name 字段
TableField nameField = tableInfo.getFields().get(3);
assertEquals("name", nameField.getName());
assertEquals(JdbcType.VARCHAR, nameField.getMetaInfo().getJdbcType());
assertEquals("名字", nameField.getComment());
assertFalse(nameField.getMetaInfo().isNullable());
assertFalse(nameField.isKeyFlag());
assertFalse(nameField.isKeyIdentityFlag());
assertEquals(DbColumnType.STRING, nameField.getColumnType());
assertEquals("name", nameField.getPropertyName());
}
}

View File

@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.file.core.client.FileClient;
import cn.iocoder.yudao.framework.file.core.client.FileClientConfig;
import cn.iocoder.yudao.framework.file.core.client.FileClientFactory;
import cn.iocoder.yudao.framework.file.core.client.local.LocalFileClient;
import cn.iocoder.yudao.framework.file.core.client.local.LocalFileClientConfig;
import cn.iocoder.yudao.framework.file.core.enums.FileStorageEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
@ -41,10 +42,10 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*;
/**
* {@link FileConfigServiceImpl}
*
* @author
*/
* {@link FileConfigServiceImpl}
*
* @author
*/
@Import(FileConfigServiceImpl.class)
public class FileConfigServiceImplTest extends BaseDbUnitTest {
@ -171,8 +172,8 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
// 调用
fileConfigService.deleteFileConfig(id);
// 校验数据不存在了
assertNull(fileConfigMapper.selectById(id));
// 校验数据不存在了
assertNull(fileConfigMapper.selectById(id));
// verify 调用
verify(fileConfigProducer).sendFileConfigRefreshMessage();
}
@ -200,30 +201,30 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
@Test
public void testGetFileConfigPage() {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setName("芋道源码")
.setStorage(FileStorageEnum.LOCAL.getStorage());
dbFileConfig.setCreateTime(LocalDateTimeUtil.parse("2020-01-23", DatePattern.NORM_DATE_PATTERN));// 等会查询到
fileConfigMapper.insert(dbFileConfig);
// 测试 name 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName("源码")));
// 测试 storage 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(FileStorageEnum.DB.getStorage())));
// 测试 createTime 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(LocalDateTimeUtil.parse("2020-11-23", DatePattern.NORM_DATE_PATTERN))));
// 准备参数
FileConfigPageReqVO reqVO = new FileConfigPageReqVO();
reqVO.setName("芋道");
reqVO.setStorage(FileStorageEnum.LOCAL.getStorage());
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2020, 1, 1),
buildTime(2020, 1, 24)}));
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setName("芋道源码")
.setStorage(FileStorageEnum.LOCAL.getStorage());
dbFileConfig.setCreateTime(LocalDateTimeUtil.parse("2020-01-23", DatePattern.NORM_DATE_PATTERN));// 等会查询到
fileConfigMapper.insert(dbFileConfig);
// 测试 name 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setName("源码")));
// 测试 storage 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setStorage(FileStorageEnum.DB.getStorage())));
// 测试 createTime 不匹配
fileConfigMapper.insert(cloneIgnoreId(dbFileConfig, o -> o.setCreateTime(LocalDateTimeUtil.parse("2020-11-23", DatePattern.NORM_DATE_PATTERN))));
// 准备参数
FileConfigPageReqVO reqVO = new FileConfigPageReqVO();
reqVO.setName("芋道");
reqVO.setStorage(FileStorageEnum.LOCAL.getStorage());
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2020, 1, 1),
buildTime(2020, 1, 24)}));
// 调用
PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbFileConfig, pageResult.getList().get(0));
// 调用
PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(reqVO);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbFileConfig, pageResult.getList().get(0));
}
@Test
@ -242,6 +243,30 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
assertEquals("https://www.iocoder.cn", fileConfigService.testFileConfig(id));
}
@Test
public void testGetFileConfig() {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false);
fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbFileConfig.getId();
// 调用,并断言
assertPojoEquals(dbFileConfig, fileConfigService.getFileConfig(id));
}
@Test
public void testGetFileClient() {
// 准备参数
Long id = randomLongId();
// mock 获得 Client
FileClient fileClient = new LocalFileClient(id, new LocalFileClientConfig());
when(fileClientFactory.getFileClient(eq(id))).thenReturn(fileClient);
// 调用,并断言
assertSame(fileClient, fileConfigService.getFileClient(id));
}
private FileConfigDO randomFileConfigDO() {
return randomPojo(FileConfigDO.class).setStorage(randomEle(FileStorageEnum.values()).getStorage())
.setConfig(new EmptyFileClientConfig());

View File

@ -16,7 +16,7 @@ import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildLocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;
@ -25,7 +25,7 @@ import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.*;
@Import({FileServiceImpl.class})
public class FileServiceTest extends BaseDbUnitTest {
public class FileServiceImplTest extends BaseDbUnitTest {
@Resource
private FileService fileService;
@ -42,7 +42,7 @@ public class FileServiceTest extends BaseDbUnitTest {
FileDO dbFile = randomPojo(FileDO.class, o -> { // 等会查询到
o.setPath("yunai");
o.setType("image/jpg");
o.setCreateTime(buildLocalDateTime(2021, 1, 15));
o.setCreateTime(buildTime(2021, 1, 15));
});
fileMapper.insert(dbFile);
// 测试 path 不匹配
@ -53,13 +53,13 @@ public class FileServiceTest extends BaseDbUnitTest {
}));
// 测试 createTime 不匹配
fileMapper.insert(ObjectUtils.cloneIgnoreId(dbFile, o -> {
o.setCreateTime(buildLocalDateTime(2020, 1, 15));
o.setCreateTime(buildTime(2020, 1, 15));
}));
// 准备参数
FilePageReqVO reqVO = new FilePageReqVO();
reqVO.setPath("yunai");
reqVO.setType("jp");
reqVO.setCreateTime((new LocalDateTime[]{buildLocalDateTime(2021, 1, 10), buildLocalDateTime(2021, 1, 20)}));
reqVO.setCreateTime((new LocalDateTime[]{buildTime(2021, 1, 10), buildTime(2021, 1, 20)}));
// 调用
PageResult<FileDO> pageResult = fileService.getFilePage(reqVO);

View File

@ -1,12 +1,9 @@
package cn.iocoder.yudao.module.infra.service.logger;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogExportReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;
@ -16,149 +13,121 @@ import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildLocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@Import(ApiAccessLogServiceImpl.class)
public class ApiAccessLogServiceImplTest extends BaseDbUnitTest {
@Resource
private ApiAccessLogService apiAccessLogService;
private ApiAccessLogServiceImpl apiAccessLogService;
@Resource
private ApiAccessLogMapper apiAccessLogMapper;
@Test
public void testGetApiAccessLogPage() {
// 构造测试数据
long userId = 2233L;
int userType = UserTypeEnum.ADMIN.getValue();
String applicationName = "yudao-test";
String requestUrl = "foo";
LocalDateTime beginTime = buildLocalDateTime(2021, 3, 13);
int duration = 1000;
int resultCode = GlobalErrorCodeConstants.SUCCESS.getCode();
ApiAccessLogDO infApiAccessLogDO = RandomUtils.randomPojo(ApiAccessLogDO.class, dto -> {
dto.setUserId(userId);
dto.setUserType(userType);
dto.setApplicationName(applicationName);
dto.setRequestUrl(requestUrl);
dto.setBeginTime(beginTime);
dto.setDuration(duration);
dto.setResultCode(resultCode);
ApiAccessLogDO apiAccessLogDO = randomPojo(ApiAccessLogDO.class, o -> {
o.setUserId(2233L);
o.setUserType(UserTypeEnum.ADMIN.getValue());
o.setApplicationName("yudao-test");
o.setRequestUrl("foo");
o.setBeginTime(buildTime(2021, 3, 13));
o.setDuration(1000);
o.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode());
});
apiAccessLogMapper.insert(infApiAccessLogDO);
// 下面几个都是不匹配的数据
// userId 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setUserId(3344L)));
// userType
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setUserType(UserTypeEnum.MEMBER.getValue())));
// applicationName 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setApplicationName("test")));
// requestUrl 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setRequestUrl("bar")));
// 构造一个早期时间 2021-02-06 00:00:00
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setBeginTime(buildLocalDateTime(2021, 2, 6))));
// duration 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setDuration(100)));
// resultCode 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setResultCode(2)));
// 构造调用参数
apiAccessLogMapper.insert(apiAccessLogDO);
// 测试 userId 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setUserId(3344L)));
// 测试 userType 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
// 测试 applicationName 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setApplicationName("test")));
// 测试 requestUrl 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setRequestUrl("bar")));
// 测试 beginTime 不匹配:构造一个早期时间 2021-02-06 00:00:00
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setBeginTime(buildTime(2021, 2, 6))));
// 测试 duration 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setDuration(100)));
// 测试 resultCode 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setResultCode(2)));
// 准备参数
ApiAccessLogPageReqVO reqVO = new ApiAccessLogPageReqVO();
reqVO.setUserId(userId);
reqVO.setUserType(userType);
reqVO.setApplicationName(applicationName);
reqVO.setRequestUrl(requestUrl);
reqVO.setBeginTime((new LocalDateTime[]{buildLocalDateTime(2021, 3, 12),buildLocalDateTime(2021, 3, 14)}));
reqVO.setDuration(duration);
reqVO.setResultCode(resultCode);
reqVO.setUserId(2233L);
reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
reqVO.setApplicationName("yudao-test");
reqVO.setRequestUrl("foo");
reqVO.setBeginTime(buildBetweenTime(2021, 3, 13, 2021, 3, 13));
reqVO.setDuration(1000);
reqVO.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode());
// 调用service方法
// 调用
PageResult<ApiAccessLogDO> pageResult = apiAccessLogService.getApiAccessLogPage(reqVO);
// 断言,只查到了一条符合条件的
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(infApiAccessLogDO, pageResult.getList().get(0));
assertPojoEquals(apiAccessLogDO, pageResult.getList().get(0));
}
@Test
public void testGetApiAccessLogList() {
// 构造测试数据
long userId = 2233L;
int userType = UserTypeEnum.ADMIN.getValue();
String applicationName = "yudao-test";
String requestUrl = "foo";
LocalDateTime beginTime = buildLocalDateTime(2021, 3, 13);
int duration = 1000;
int resultCode = GlobalErrorCodeConstants.SUCCESS.getCode();
ApiAccessLogDO infApiAccessLogDO = RandomUtils.randomPojo(ApiAccessLogDO.class, dto -> {
dto.setUserId(userId);
dto.setUserType(userType);
dto.setApplicationName(applicationName);
dto.setRequestUrl(requestUrl);
dto.setBeginTime(beginTime);
dto.setDuration(duration);
dto.setResultCode(resultCode);
ApiAccessLogDO apiAccessLogDO = randomPojo(ApiAccessLogDO.class, o -> {
o.setUserId(2233L);
o.setUserType(UserTypeEnum.ADMIN.getValue());
o.setApplicationName("yudao-test");
o.setRequestUrl("foo");
o.setBeginTime(buildTime(2021, 3, 13));
o.setDuration(1000);
o.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode());
});
apiAccessLogMapper.insert(infApiAccessLogDO);
// 下面几个都是不匹配的数据
// userId 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setUserId(3344L)));
// userType
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setUserType(UserTypeEnum.MEMBER.getValue())));
// applicationName 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setApplicationName("test")));
// requestUrl 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setRequestUrl("bar")));
// 构造一个早期时间 2021-02-06 00:00:00
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setBeginTime(buildLocalDateTime(2021, 2, 6))));
// duration 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setDuration(100)));
// resultCode 不同的
apiAccessLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiAccessLogDO, logDO -> logDO.setResultCode(2)));
// 构造调用参数
apiAccessLogMapper.insert(apiAccessLogDO);
// 测试 userId 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setUserId(3344L)));
// 测试 userType 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));
// 测试 applicationName 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setApplicationName("test")));
// 测试 requestUrl 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setRequestUrl("bar")));
// 测试 beginTime 不匹配:构造一个早期时间 2021-02-06 00:00:00
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setBeginTime(buildTime(2021, 2, 6))));
// 测试 duration 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setDuration(100)));
// 测试 resultCode 不匹配
apiAccessLogMapper.insert(cloneIgnoreId(apiAccessLogDO, o -> o.setResultCode(2)));
// 准备参数
ApiAccessLogExportReqVO reqVO = new ApiAccessLogExportReqVO();
reqVO.setUserId(userId);
reqVO.setUserType(userType);
reqVO.setApplicationName(applicationName);
reqVO.setRequestUrl(requestUrl);
reqVO.setBeginTime((new LocalDateTime[]{buildLocalDateTime(2021, 3, 12),buildLocalDateTime(2021, 3, 14)}));
reqVO.setDuration(duration);
reqVO.setResultCode(resultCode);
reqVO.setUserId(2233L);
reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
reqVO.setApplicationName("yudao-test");
reqVO.setRequestUrl("foo");
reqVO.setBeginTime(buildBetweenTime(2021, 3, 13, 2021, 3, 13));
reqVO.setDuration(1000);
reqVO.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode());
// 调用service方法
// 调用
List<ApiAccessLogDO> list = apiAccessLogService.getApiAccessLogList(reqVO);
// 断言,只查到了一条符合条件的
assertEquals(1, list.size());
assertPojoEquals(infApiAccessLogDO, list.get(0));
assertPojoEquals(apiAccessLogDO, list.get(0));
}
@Test
public void testCreateApiAccessLogAsync() {
public void testCreateApiAccessLog() {
// 准备参数
ApiAccessLogCreateReqDTO createDTO = RandomUtils.randomPojo(ApiAccessLogCreateReqDTO.class,
dto -> dto.setUserType(RandomUtil.randomEle(UserTypeEnum.values()).getValue()));
ApiAccessLogCreateReqDTO createDTO = randomPojo(ApiAccessLogCreateReqDTO.class);
// 调用
apiAccessLogService.createApiAccessLog(createDTO);
// 断言
ApiAccessLogDO infApiAccessLogDO = apiAccessLogMapper.selectOne(null);
assertNotNull(infApiAccessLogDO);
assertPojoEquals(createDTO, infApiAccessLogDO);
ApiAccessLogDO apiAccessLogDO = apiAccessLogMapper.selectOne(null);
assertPojoEquals(createDTO, apiAccessLogDO);
}
}

View File

@ -1,28 +1,28 @@
package cn.iocoder.yudao.module.infra.service.logger;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
import cn.iocoder.yudao.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogExportReqVO;
import cn.iocoder.yudao.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;
import cn.iocoder.yudao.module.infra.dal.dataobject.logger.ApiErrorLogDO;
import cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper;
import cn.iocoder.yudao.module.infra.enums.logger.ApiErrorLogProcessStatusEnum;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.buildLocalDateTime;
import static cn.hutool.core.util.RandomUtil.randomEle;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildTime;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_PROCESSED;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -35,161 +35,150 @@ public class ApiErrorLogServiceImplTest extends BaseDbUnitTest {
private ApiErrorLogServiceImpl apiErrorLogService;
@Resource
private ApiErrorLogMapper infApiErrorLogMapper;
private ApiErrorLogMapper apiErrorLogMapper;
@Test
public void testGetApiErrorLogPage() {
// 构造测试数据
long userId = 2233L;
int userType = UserTypeEnum.ADMIN.getValue();
String applicationName = "yudao-test";
String requestUrl = "foo";
LocalDateTime beginTime = buildLocalDateTime(2021, 3, 13);
int progressStatus = ApiErrorLogProcessStatusEnum.INIT.getStatus();
ApiErrorLogDO infApiErrorLogDO = RandomUtils.randomPojo(ApiErrorLogDO.class, logDO -> {
logDO.setUserId(userId);
logDO.setUserType(userType);
logDO.setApplicationName(applicationName);
logDO.setRequestUrl(requestUrl);
logDO.setExceptionTime(beginTime);
logDO.setProcessStatus(progressStatus);
// mock 数据
ApiErrorLogDO apiErrorLogDO = randomPojo(ApiErrorLogDO.class, o -> {
o.setUserId(2233L);
o.setUserType(UserTypeEnum.ADMIN.getValue());
o.setApplicationName("yudao-test");
o.setRequestUrl("foo");
o.setExceptionTime(buildTime(2021, 3, 13));
o.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
});
infApiErrorLogMapper.insert(infApiErrorLogDO);
// 下面几个都是不匹配的数据
// userId 不同的
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setUserId(3344L)));
// userType
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setUserType(UserTypeEnum.MEMBER.getValue())));
// applicationName 不同的
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setApplicationName("test")));
// requestUrl 不同的
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setRequestUrl("bar")));
// 构造一个早期时间 2021-02-06 00:00:00
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setExceptionTime(buildLocalDateTime(2021, 2, 6))));
// progressStatus 不同的
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus())));
// 构造调用参数
apiErrorLogMapper.insert(apiErrorLogDO);
// 测试 userId 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setUserId(3344L)));
// 测试 userType 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setUserType(UserTypeEnum.MEMBER.getValue())));
// 测试 applicationName 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setApplicationName("test")));
// 测试 requestUrl 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setRequestUrl("bar")));
// 测试 exceptionTime 不匹配:构造一个早期时间 2021-02-06 00:00:00
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setExceptionTime(buildTime(2021, 2, 6))));
// 测试 progressStatus 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus())));
// 准备参数
ApiErrorLogPageReqVO reqVO = new ApiErrorLogPageReqVO();
reqVO.setUserId(userId);
reqVO.setUserType(userType);
reqVO.setApplicationName(applicationName);
reqVO.setRequestUrl(requestUrl);
reqVO.setExceptionTime((new LocalDateTime[]{buildLocalDateTime(2021, 3, 12),buildLocalDateTime(2021, 3, 14)}));
reqVO.setProcessStatus(progressStatus);
reqVO.setUserId(2233L);
reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
reqVO.setApplicationName("yudao-test");
reqVO.setRequestUrl("foo");
reqVO.setExceptionTime(buildBetweenTime(2021, 3, 1, 2021, 3, 31));
reqVO.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
// 调用service方法
// 调用
PageResult<ApiErrorLogDO> pageResult = apiErrorLogService.getApiErrorLogPage(reqVO);
// 断言,只查到了一条符合条件的
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(infApiErrorLogDO, pageResult.getList().get(0));
assertPojoEquals(apiErrorLogDO, pageResult.getList().get(0));
}
@Test
public void testGetApiErrorLogList() {
// 构造测试数据
long userId = 2233L;
int userType = UserTypeEnum.ADMIN.getValue();
String applicationName = "yudao-test";
String requestUrl = "foo";
LocalDateTime beginTime = buildLocalDateTime(2021, 3, 13);
int progressStatus = ApiErrorLogProcessStatusEnum.INIT.getStatus();
ApiErrorLogDO infApiErrorLogDO = RandomUtils.randomPojo(ApiErrorLogDO.class, logDO -> {
logDO.setUserId(userId);
logDO.setUserType(userType);
logDO.setApplicationName(applicationName);
logDO.setRequestUrl(requestUrl);
logDO.setExceptionTime(beginTime);
logDO.setProcessStatus(progressStatus);
// mock 数据
ApiErrorLogDO apiErrorLogDO = randomPojo(ApiErrorLogDO.class, o -> {
o.setUserId(2233L);
o.setUserType(UserTypeEnum.ADMIN.getValue());
o.setApplicationName("yudao-test");
o.setRequestUrl("foo");
o.setExceptionTime(buildTime(2021, 3, 13));
o.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
});
infApiErrorLogMapper.insert(infApiErrorLogDO);
// 下面几个都是不匹配的数据
// userId 不同的
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setUserId(3344L)));
// userType
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setUserType(UserTypeEnum.MEMBER.getValue())));
// applicationName 不同的
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setApplicationName("test")));
// requestUrl 不同的
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setRequestUrl("bar")));
// 构造一个早期时间 2021-02-06 00:00:00
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setExceptionTime(buildLocalDateTime(2021, 2, 6))));
// progressStatus 不同的
infApiErrorLogMapper.insert(ObjectUtils.cloneIgnoreId(infApiErrorLogDO, logDO -> logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus())));
// 构造调用参数
apiErrorLogMapper.insert(apiErrorLogDO);
// 测试 userId 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, o -> o.setUserId(3344L)));
// 测试 userType 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setUserType(UserTypeEnum.MEMBER.getValue())));
// 测试 applicationName 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setApplicationName("test")));
// 测试 requestUrl 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setRequestUrl("bar")));
// 测试 exceptionTime 不匹配:构造一个早期时间 2021-02-06 00:00:00
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setExceptionTime(buildTime(2021, 2, 6))));
// 测试 progressStatus 不匹配
apiErrorLogMapper.insert(cloneIgnoreId(apiErrorLogDO, logDO -> logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus())));
// 准备参数
ApiErrorLogExportReqVO reqVO = new ApiErrorLogExportReqVO();
reqVO.setUserId(userId);
reqVO.setUserType(userType);
reqVO.setApplicationName(applicationName);
reqVO.setRequestUrl(requestUrl);
reqVO.setExceptionTime((new LocalDateTime[]{buildLocalDateTime(2021, 3, 12),buildLocalDateTime(2021, 3, 14)}));
reqVO.setProcessStatus(progressStatus);
reqVO.setUserId(2233L);
reqVO.setUserType(UserTypeEnum.ADMIN.getValue());
reqVO.setApplicationName("yudao-test");
reqVO.setRequestUrl("foo");
reqVO.setExceptionTime(buildBetweenTime(2021, 3, 1, 2021, 3, 31));
reqVO.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
// 调用service方法
// 调用
List<ApiErrorLogDO> list = apiErrorLogService.getApiErrorLogList(reqVO);
// 断言,只查到了一条符合条件的
assertEquals(1, list.size());
assertPojoEquals(infApiErrorLogDO, list.get(0));
}
// TODO 芋艿:单元测试,可以拆小一点
@Test
public void testUpdateApiErrorLogProcess() {
// 先构造两条数据第一条用于抛出异常第二条用于正常的执行update操作
Long processUserId = 2233L;
ApiErrorLogDO first = RandomUtils.randomPojo(ApiErrorLogDO.class, logDO -> {
logDO.setProcessUserId(processUserId);
logDO.setUserType(UserTypeEnum.ADMIN.getValue());
logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus());
});
infApiErrorLogMapper.insert(first);
ApiErrorLogDO second = RandomUtils.randomPojo(ApiErrorLogDO.class, logDO -> {
logDO.setProcessUserId(1122L);
logDO.setUserType(UserTypeEnum.ADMIN.getValue());
logDO.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());
});
infApiErrorLogMapper.insert(second);
Long firstId = first.getId();
Long secondId = second.getId();
// 执行正常的 update 操作
apiErrorLogService.updateApiErrorLogProcess(secondId, ApiErrorLogProcessStatusEnum.DONE.getStatus(), processUserId);
ApiErrorLogDO secondSelect = infApiErrorLogMapper.selectOne("id", secondId);
// id 为 0 查询不到,应该抛出异常 API_ERROR_LOG_NOT_FOUND
assertServiceException(() -> apiErrorLogService.updateApiErrorLogProcess(0L, ApiErrorLogProcessStatusEnum.DONE.getStatus(), processUserId), API_ERROR_LOG_NOT_FOUND);
// id 为 first 的 progressStatus 为 DONE ,应该抛出 API_ERROR_LOG_PROCESSED
assertServiceException(() -> apiErrorLogService.updateApiErrorLogProcess(firstId, ApiErrorLogProcessStatusEnum.DONE.getStatus(), processUserId), API_ERROR_LOG_PROCESSED);
// 验证 progressStatus 是否修改成功
Assertions.assertEquals(ApiErrorLogProcessStatusEnum.DONE.getStatus(), secondSelect.getProcessStatus());
// 验证 progressUserId 是否修改成功
Assertions.assertEquals(processUserId, secondSelect.getProcessUserId());
assertPojoEquals(apiErrorLogDO, list.get(0));
}
@Test
public void testCreateApiErrorLogAsync() {
public void testCreateApiErrorLog() {
// 准备参数
ApiErrorLogCreateReqDTO createDTO = RandomUtils.randomPojo(ApiErrorLogCreateReqDTO.class,
dto -> dto.setUserType(RandomUtil.randomEle(UserTypeEnum.values()).getValue()));
ApiErrorLogCreateReqDTO createDTO = randomPojo(ApiErrorLogCreateReqDTO.class);
// 调用
apiErrorLogService.createApiErrorLog(createDTO);
// 断言
ApiErrorLogDO infApiErrorLogDO = infApiErrorLogMapper.selectOne(null);
assertNotNull(infApiErrorLogDO);
assertPojoEquals(createDTO, infApiErrorLogDO);
ApiErrorLogDO apiErrorLogDO = apiErrorLogMapper.selectOne(null);
assertPojoEquals(createDTO, apiErrorLogDO);
assertEquals(ApiErrorLogProcessStatusEnum.INIT.getStatus(), apiErrorLogDO.getProcessStatus());
}
@Test
public void testUpdateApiErrorLogProcess_success() {
// 准备参数
ApiErrorLogDO apiErrorLogDO = randomPojo(ApiErrorLogDO.class,
o -> o.setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus()));
apiErrorLogMapper.insert(apiErrorLogDO);
// 准备参数
Long id = apiErrorLogDO.getId();
Integer processStatus = randomEle(ApiErrorLogProcessStatusEnum.values()).getStatus();
Long processUserId = randomLongId();
// 调用
apiErrorLogService.updateApiErrorLogProcess(id, processStatus, processUserId);
// 断言
ApiErrorLogDO dbApiErrorLogDO = apiErrorLogMapper.selectById(apiErrorLogDO.getId());
assertEquals(processStatus, dbApiErrorLogDO.getProcessStatus());
assertEquals(processUserId, dbApiErrorLogDO.getProcessUserId());
assertNotNull(dbApiErrorLogDO.getProcessTime());
}
@Test
public void testUpdateApiErrorLogProcess_processed() {
// 准备参数
ApiErrorLogDO apiErrorLogDO = randomPojo(ApiErrorLogDO.class,
o -> o.setProcessStatus(ApiErrorLogProcessStatusEnum.DONE.getStatus()));
apiErrorLogMapper.insert(apiErrorLogDO);
// 准备参数
Long id = apiErrorLogDO.getId();
Integer processStatus = randomEle(ApiErrorLogProcessStatusEnum.values()).getStatus();
Long processUserId = randomLongId();
// 调用,并断言异常
assertServiceException(() ->
apiErrorLogService.updateApiErrorLogProcess(id, processStatus, processUserId),
API_ERROR_LOG_PROCESSED);
}
@Test
public void testUpdateApiErrorLogProcess_notFound() {
// 准备参数
Long id = randomLongId();
Integer processStatus = randomEle(ApiErrorLogProcessStatusEnum.values()).getStatus();
Long processUserId = randomLongId();
// 调用,并断言异常
assertServiceException(() ->
apiErrorLogService.updateApiErrorLogProcess(id, processStatus, processUserId),
API_ERROR_LOG_NOT_FOUND);
}
}

View File

@ -1,9 +1,9 @@
CREATE TABLE IF NOT EXISTS "infra_config" (
"id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"id" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号',
"category" varchar(50) NOT NULL,
"type" tinyint NOT NULL,
"name" varchar(100) NOT NULL DEFAULT '',
"name" varchar(100) NOT NULL DEFAULT '' COMMENT '名字',
"config_key" varchar(100) NOT NULL DEFAULT '',
"value" varchar(500) NOT NULL DEFAULT '',
"visible" bit NOT NULL,

View File

@ -0,0 +1,23 @@
<?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>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<modules>
<module>yudao-module-report-api</module>
<module>yudao-module-report-biz</module>
</modules>
<artifactId>yudao-module-report</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>
report 模块,主要实现数据可视化报表等功能。
</description>
</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>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-report</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-report-api</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
report 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-common</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,4 @@
/**
* api git
*/
package cn.iocoder.yudao.module.report.api;

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.report.enums;
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
/**
* Report
*
* system 使 1-003-000-000
*/
public interface ErrorCodeConstants {
// ========== AUTH 模块 1003000000 ==========
ErrorCode GO_VIEW_PROJECT_NOT_EXISTS = new ErrorCode(1003000000, "GoView 项目不存在");
}

View File

@ -0,0 +1,135 @@
<?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>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-report</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-report-biz</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
report 模块,主要实现数据可视化报表等功能:
1. 基于「积木报表」实现,打印设计、报表设计、图形设计、大屏设计等。
</description>
<dependencies>
<!-- Spring Cloud 基础 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- 依赖服务 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-report-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-system-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-module-infra-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-banner</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-biz-error-code</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-redis</artifactId>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-rpc</artifactId>
</dependency>
<!-- Registry 注册中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Config 配置中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Job 定时任务相关 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-spring-boot-starter-job</artifactId>-->
<!-- </dependency>-->
<!-- 消息队列相关 -->
<!-- <dependency>-->
<!-- <groupId>cn.iocoder.cloud</groupId>-->
<!-- <artifactId>yudao-spring-boot-starter-mq</artifactId>-->
<!-- </dependency>-->
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 监控相关 -->
<dependency>
<groupId>cn.iocoder.cloud</groupId>
<artifactId>yudao-spring-boot-starter-monitor</artifactId>
</dependency>
<!-- 积木报表-->
<dependency>
<groupId>org.jeecgframework.jimureport</groupId>
<artifactId>jimureport-spring-boot-starter</artifactId>
</dependency>
<!-- 单独依赖升级版本解决低版本validator失败问题 -->
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.report;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
*
*
* https://cloud.iocoder.cn/quick-start/ 文章
* https://cloud.iocoder.cn/quick-start/ 文章
* https://cloud.iocoder.cn/quick-start/ 文章
*
* @author
*/
@SpringBootApplication
public class ReportServerApplication {
public static void main(String[] args) {
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
SpringApplication.run(ReportServerApplication.class, args);
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
// 如果你碰到启动的问题,请认真阅读 https://cloud.iocoder.cn/quick-start/ 文章
}
}

View File

@ -0,0 +1 @@
package cn.iocoder.yudao.module.report.controller.admin.ajreport;

View File

@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.report.controller.admin.goview;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataGetBySqlReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
import cn.iocoder.yudao.module.report.service.goview.GoViewDataService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
//@Tag(name = "管理后台 - GoView 数据", description = "提供 SQL、HTTP 等数据查询的能力")
@RestController
@RequestMapping("/report/go-view/data")
@Validated
public class GoViewDataController {
@Resource
private GoViewDataService goViewDataService;
@RequestMapping("/get-by-sql")
// @Operation(summary = "使用 SQL 查询数据")
@PreAuthorize("@ss.hasPermission('report:go-view-data:get-by-sql')")
@OperateLog(enable = false) // 不记录操作日志,因为不需要
public CommonResult<GoViewDataRespVO> getDataBySQL(@Valid @RequestBody GoViewDataGetBySqlReqVO reqVO) {
return success(goViewDataService.getDataBySQL(reqVO.getSql()));
}
@RequestMapping("/get-by-http")
// @Operation(summary = "使用 HTTP 查询数据", description = "这个只是示例接口,实际应该每个查询,都要写一个接口")
@PreAuthorize("@ss.hasPermission('report:go-view-data:get-by-http')")
@OperateLog(enable = false) // 不记录操作日志,因为不需要
public CommonResult<GoViewDataRespVO> getDataByHttp(
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) { // params、body 按照需要去接收,这里仅仅是示例
GoViewDataRespVO respVO = new GoViewDataRespVO();
// 1. 数据维度
respVO.setDimensions(Arrays.asList("日期", "PV", "UV")); // PV 是每天访问次数UV 是每天访问人数
// 2. 明细数据列表
// 目前通过随机的方式生成。一般来说,这里你可以写逻辑来实现数据的返回
respVO.setSource(new LinkedList<>());
for (int i = 1; i <= 12; i++) {
String date = "2021-" + (i < 10 ? "0" + i : i);
Integer pv = RandomUtil.randomInt(1000, 10000);
Integer uv = RandomUtil.randomInt(100, 1000);
respVO.getSource().add(MapUtil.<String, Object>builder().put("日期", date)
.put("PV", pv).put("UV", uv).build());
}
return success(respVO);
}
}

View File

@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.report.controller.admin.goview;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectRespVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.convert.goview.GoViewProjectConvert;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import cn.iocoder.yudao.module.report.service.goview.GoViewProjectService;
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;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
//@Tag(name = "管理后台 - GoView 项目")
@RestController
@RequestMapping("/report/go-view/project")
@Validated
public class GoViewProjectController {
@Resource
private GoViewProjectService goViewProjectService;
@PostMapping("/create")
// @Operation(summary = "创建项目")
@PreAuthorize("@ss.hasPermission('report:go-view-project:create')")
public CommonResult<Long> createProject(@Valid @RequestBody GoViewProjectCreateReqVO createReqVO) {
return success(goViewProjectService.createProject(createReqVO));
}
@PutMapping("/update")
// @Operation(summary = "更新项目")
@PreAuthorize("@ss.hasPermission('report:go-view-project:update')")
public CommonResult<Boolean> updateProject(@Valid @RequestBody GoViewProjectUpdateReqVO updateReqVO) {
goViewProjectService.updateProject(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
// @Operation(summary = "删除 GoView 项目")
// @Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('report:go-view-project:delete')")
public CommonResult<Boolean> deleteProject(@RequestParam("id") Long id) {
goViewProjectService.deleteProject(id);
return success(true);
}
@GetMapping("/get")
// @Operation(summary = "获得项目")
// @Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('report:go-view-project:query')")
public CommonResult<GoViewProjectRespVO> getProject(@RequestParam("id") Long id) {
GoViewProjectDO project = goViewProjectService.getProject(id);
return success(GoViewProjectConvert.INSTANCE.convert(project));
}
@GetMapping("/my-page")
// @Operation(summary = "获得我的项目分页")
@PreAuthorize("@ss.hasPermission('report:go-view-project:query')")
public CommonResult<PageResult<GoViewProjectRespVO>> getMyProjectPage(@Valid PageParam pageVO) {
PageResult<GoViewProjectDO> pageResult = goViewProjectService.getMyProjectPage(
pageVO, getLoginUserId());
return success(GoViewProjectConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.data;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
//@Schema(description = "管理后台 - GoView 使用 SQL 查询数据 Request VO")
@Data
public class GoViewDataGetBySqlReqVO {
// @Schema(description = "SQL 语句", required = true, example = "SELECT * FROM user")
@NotEmpty(message = "SQL 语句不能为空")
private String sql;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.data;
//import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
import java.util.Map;
//@Schema(description = "管理后台 - GoView 数据 Response VO")
@Data
public class GoViewDataRespVO {
// @Schema(description = "数据维度", required = true, example = "['product', 'data1', 'data2']")
private List<String> dimensions;
// @Schema(description = "数据明细列表", required = true)
private List<Map<String, Object>> source;
}

View File

@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.project;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
//@Schema(description = "管理后台 - GoView 项目创建 Request VO")
@Data
public class GoViewProjectCreateReqVO {
// @Schema(description = "项目名称", required = true, example = "王五")
@NotEmpty(message = "项目名称不能为空")
private String name;
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.project;
import lombok.Data;
import java.time.LocalDateTime;
//@Schema(description = "管理后台 - GoView 项目 Response VO")
@Data
public class GoViewProjectRespVO {
// @Schema(description = "编号", required = true, example = "18993")
private Long id;
// @Schema(description = "项目名称", required = true, example = "王五")
private String name;
// @Schema(description = "发布状态", required = true, example = "1")
private Integer status;
// @Schema(description = "报表内容") // JSON 格式
private String content;
// @Schema(description = "预览图片 URL", example = "https://www.iocoder.cn")
private String picUrl;
// @Schema(description = "项目备注", example = "你猜")
private String remark;
// @Schema(description = "创建人编号", required = true, example = "1024")
private String creator;
// @Schema(description = "创建时间", required = true)
private LocalDateTime createTime;
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.report.controller.admin.goview.vo.project;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import lombok.Data;
import javax.validation.constraints.NotNull;
//@Schema(description = "管理后台 - GoView 项目更新 Request VO")
@Data
public class GoViewProjectUpdateReqVO {
// @Schema(description = "编号", required = true, example = "18993")
@NotNull(message = "编号不能为空")
private Long id;
// @Schema(description = "项目名称", required = true, example = "王五")
private String name;
// @Schema(description = "发布状态", required = true, example = "1")
@InEnum(value = CommonStatusEnum.class, message = "发布状态必须是 {value}")
private Integer status;
// @Schema(description = "报表内容") // JSON 格式
private String content;
// @Schema(description = "预览图片 URL", example = "https://www.iocoder.cn")
private String picUrl;
// @Schema(description = "项目备注", example = "你猜")
private String remark;
}

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.report.controller;

View File

@ -0,0 +1,4 @@
/**
* TODO
*/
package cn.iocoder.yudao.module.report.convert.ajreport;

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.report.convert.goview;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectRespVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface GoViewProjectConvert {
GoViewProjectConvert INSTANCE = Mappers.getMapper(GoViewProjectConvert.class);
GoViewProjectDO convert(GoViewProjectCreateReqVO bean);
GoViewProjectDO convert(GoViewProjectUpdateReqVO bean);
GoViewProjectRespVO convert(GoViewProjectDO bean);
PageResult<GoViewProjectRespVO> convertPage(PageResult<GoViewProjectDO> page);
}

View File

@ -0,0 +1,4 @@
/**
* TODO
*/
package cn.iocoder.yudao.module.report.dal.dataobject.ajreport;

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.report.dal.dataobject.goview;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* GoView
*
*
*
* @author
*/
@TableName(value = "report_go_view_project", autoResultMap = true) // 由于 SQL Server 的 system_user 是关键字,所以使用 system_users
@KeySequence("report_go_view_project_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class GoViewProjectDO extends BaseDO {
/**
*
*/
@TableId
private Long id;
/**
*
*/
private String name;
/**
* URL
*/
private String picUrl;
/**
*
*
* JSON 使
*/
private String content;
/**
*
*
* 0 -
* 1 -
*
* {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
*/
private Integer status;
/**
*
*/
private String remark;
}

View File

@ -0,0 +1,4 @@
/**
* TODO
*/
package cn.iocoder.yudao.module.report.dal.mysql.ajreport;

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.report.dal.mysql.goview;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
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.report.dal.dataobject.goview.GoViewProjectDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface GoViewProjectMapper extends BaseMapperX<GoViewProjectDO> {
default PageResult<GoViewProjectDO> selectPage(PageParam reqVO, Long userId) {
return selectPage(reqVO, new LambdaQueryWrapperX<GoViewProjectDO>()
.eq(GoViewProjectDO::getCreator, userId)
.orderByDesc(GoViewProjectDO::getId));
}
}

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.report.framework.jmreport.config;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.report.framework.jmreport.core.service.JmReportTokenServiceImpl;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
*
*
* @author
*/
@Configuration(proxyBeanMethods = false)
@ComponentScan(basePackages = "org.jeecg.modules.jmreport") // 扫描积木报表的包
public class JmReportConfiguration {
@Bean
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
public JmReportTokenServiceI jmReportTokenService(OAuth2TokenApi oAuth2TokenApi, SecurityProperties securityProperties) {
return new JmReportTokenServiceImpl(oAuth2TokenApi, securityProperties);
}
}

View File

@ -0,0 +1,131 @@
package cn.iocoder.yudao.module.report.framework.jmreport.core.service;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import lombok.RequiredArgsConstructor;
import org.jeecg.modules.jmreport.api.JmReportTokenServiceI;
import org.springframework.http.HttpHeaders;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* {@link JmReportTokenServiceI} Token
*
* @author
*/
@RequiredArgsConstructor
public class JmReportTokenServiceImpl implements JmReportTokenServiceI {
/**
* token head
*/
private static final String JM_TOKEN_HEADER = "X-Access-Token";
/**
* auth
*/
private static final String AUTHORIZATION_FORMAT = SecurityFrameworkUtils.AUTHORIZATION_BEARER + " %s";
private final OAuth2TokenApi oauth2TokenApi;
private final SecurityProperties securityProperties;
/**
* API appian Header Token
* <a href="http://report.jeecg.com/2222224">apitoken</a>
*
* @return head
*/
@Override
public HttpHeaders customApiHeader() {
// 读取积木标标系统的 token
HttpServletRequest request = ServletUtils.getRequest();
String token = request.getHeader(JM_TOKEN_HEADER);
// 设置到 yudao 系统的 token
HttpHeaders headers = new HttpHeaders();
headers.add(securityProperties.getTokenHeader(), String.format(AUTHORIZATION_FORMAT, token));
return headers;
}
/**
* Token
*
* @param token JmReport token
* @return
*/
@Override
public Boolean verifyToken(String token) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (!Objects.isNull(userId)) {
return true;
}
return buildLoginUserByToken(token) != null;
}
/**
*
* <p>
* username
*
* @param token JmReport token
* @return
*/
@Override
public String getUsername(String token) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (ObjectUtil.isNotNull(userId)) {
return String.valueOf(userId);
}
LoginUser user = buildLoginUserByToken(token);
return user == null ? null : String.valueOf(user.getId());
}
/**
* token
*
* @param token token
* @return token
*/
private LoginUser buildLoginUserByToken(String token) {
if (StrUtil.isEmpty(token)) {
return null;
}
// TODO 如下的实现不算特别优雅,主要咱是不想搞的太复杂,所以参考对应的 Filter 先实现了
// ① 参考 TokenAuthenticationFilter 的认证逻辑Security 的上下文清理,交给 Spring Security 完成)
// 目的:实现基于 JmReport 前端传递的 token实现认证
TenantContextHolder.setIgnore(true); // 忽略租户,保证可查询到 token 信息
LoginUser user = null;
try {
OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData();
if (accessToken == null) {
return null;
}
user = new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())
.setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());
} catch (ServiceException ignored) {
// do nothing如果报错说明认证失败则返回 false 即可
}
if (user == null) {
return null;
}
SecurityFrameworkUtils.setLoginUser(user, WebFrameworkUtils.getRequest());
// ② 参考 TenantContextWebFilter 实现Tenant 的上下文清理,交给 TenantContextWebFilter 完成)
// 目的:基于 LoginUser 获得到的租户编号,设置到 Tenant 上下文,避免查询数据库时的报错
TenantContextHolder.setIgnore(false);
TenantContextHolder.setTenantId(user.getTenantId());
return user;
}
}

View File

@ -0,0 +1,4 @@
/**
* Filter {@link cn.iocoder.yudao.module.report.framework.jmreport.core.service.JmReportTokenServiceImpl}
*/
package cn.iocoder.yudao.module.report.framework.jmreport.core.web;

View File

@ -0,0 +1,6 @@
/**
* report framework
*
* @author
*/
package cn.iocoder.yudao.module.report.framework;

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.report.framework.security.config;
import cn.iocoder.yudao.framework.security.config.AuthorizeRequestsCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
/**
* Report Security
*/
@Configuration("reportSecurityConfiguration")
public class SecurityConfiguration {
@Bean("reportAuthorizeRequestsCustomizer")
public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {
return new AuthorizeRequestsCustomizer() {
@Override
public void customize(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry) {
// Swagger 接口文档
registry.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous();
// Spring Boot Actuator 的安全配置
registry.antMatchers("/actuator").anonymous()
.antMatchers("/actuator/**").anonymous();
// Druid 监控
registry.antMatchers("/druid/**").anonymous();
// 积木报表
registry.antMatchers("/jmreport/**").permitAll();
}
};
}
}

View File

@ -0,0 +1,4 @@
/**
*
*/
package cn.iocoder.yudao.module.report.framework.security.core;

View File

@ -0,0 +1,7 @@
/**
* ureport2https://github.com/youseries/ureport
*
* ureport2 jimurepot 2018
* https://juejin.cn/post/6939836480269320200 地址
*/
package cn.iocoder.yudao.module.report.framework.ureport;

View File

@ -0,0 +1,9 @@
/**
* report
* 1. URL /jmreport jimu_
*
*
* 1. Controller URL /report/ Module
* 2. DataObject report_ 便
*/
package cn.iocoder.yudao.module.report;

View File

@ -0,0 +1,4 @@
/**
* TODO
*/
package cn.iocoder.yudao.module.report.service.ajreport;

View File

@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
/**
* GoView Service
*
* @author
*/
public interface GoViewDataService {
/**
* 使 SQL
*
* @param sql SQL
* @return
*/
GoViewDataRespVO getDataBySQL(String sql);
}

View File

@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
import com.google.common.collect.Maps;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Map;
/**
* GoView Service
*
*
* 1. 使 jdbcTemplate jdbcTemplate
* 2. MySQL 使 Click House
*
* @author
*/
@Service
@Validated
public class GoViewDataServiceImpl implements GoViewDataService {
@Resource
private JdbcTemplate jdbcTemplate;
@Override
public GoViewDataRespVO getDataBySQL(String sql) {
// 1. 执行查询
SqlRowSet sqlRowSet = jdbcTemplate.queryForRowSet(sql);
// 2. 构建返回结果
GoViewDataRespVO respVO = new GoViewDataRespVO();
// 2.1 解析元数据
SqlRowSetMetaData metaData = sqlRowSet.getMetaData();
String[] columnNames = metaData.getColumnNames();
respVO.setDimensions(Arrays.asList(columnNames));
// 2.2 解析数据明细
respVO.setSource(new LinkedList<>()); // 由于数据量不确认,使用 LinkedList 虽然内存占用大一点,但是不存在扩容复制的问题
while (sqlRowSet.next()) {
Map<String, Object> data = Maps.newHashMapWithExpectedSize(columnNames.length);
for (String columnName : columnNames) {
data.put(columnName, sqlRowSet.getObject(columnName));
}
respVO.getSource().add(data);
}
return respVO;
}
}

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import javax.validation.Valid;
/**
* GoView Service
*
* @author
*/
public interface GoViewProjectService {
/**
*
*
* @param createReqVO
* @return
*/
Long createProject(@Valid GoViewProjectCreateReqVO createReqVO);
/**
*
*
* @param updateReqVO
*/
void updateProject(@Valid GoViewProjectUpdateReqVO updateReqVO);
/**
*
*
* @param id
*/
void deleteProject(Long id);
/**
*
*
* @param id
* @return
*/
GoViewProjectDO getProject(Long id);
/**
*
*
* @param pageReqVO
* @param userId
* @return GoView
*/
PageResult<GoViewProjectDO> getMyProjectPage(PageParam pageReqVO, Long userId);
}

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.convert.goview.GoViewProjectConvert;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import cn.iocoder.yudao.module.report.dal.mysql.goview.GoViewProjectMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.report.enums.ErrorCodeConstants.GO_VIEW_PROJECT_NOT_EXISTS;
/**
* GoView Service
*
* @author
*/
@Service
@Validated
public class GoViewProjectServiceImpl implements GoViewProjectService {
@Resource
private GoViewProjectMapper goViewProjectMapper;
@Override
public Long createProject(GoViewProjectCreateReqVO createReqVO) {
// 插入
GoViewProjectDO goViewProject = GoViewProjectConvert.INSTANCE.convert(createReqVO)
.setStatus(CommonStatusEnum.DISABLE.getStatus());
goViewProjectMapper.insert(goViewProject);
// 返回
return goViewProject.getId();
}
@Override
public void updateProject(GoViewProjectUpdateReqVO updateReqVO) {
// 校验存在
validateProjectExists(updateReqVO.getId());
// 更新
GoViewProjectDO updateObj = GoViewProjectConvert.INSTANCE.convert(updateReqVO);
goViewProjectMapper.updateById(updateObj);
}
@Override
public void deleteProject(Long id) {
// 校验存在
validateProjectExists(id);
// 删除
goViewProjectMapper.deleteById(id);
}
private void validateProjectExists(Long id) {
if (goViewProjectMapper.selectById(id) == null) {
throw exception(GO_VIEW_PROJECT_NOT_EXISTS);
}
}
@Override
public GoViewProjectDO getProject(Long id) {
return goViewProjectMapper.selectById(id);
}
@Override
public PageResult<GoViewProjectDO> getMyProjectPage(PageParam pageReqVO, Long userId) {
return goViewProjectMapper.selectPage(pageReqVO, userId);
}
}

View File

@ -0,0 +1,101 @@
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
autoconfigure:
exclude:
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
datasource:
druid: # Druid 【监控】相关的全局配置
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
allow: # 设置白名单,不填则允许所有访问
url-pattern: /druid/*
login-username: # 控制台管理用户名和密码
login-password:
filter:
stat:
enabled: true
log-slow-sql: true # 慢 SQL 记录
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 5 # 初始连接数
min-idle: 10 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
primary: master
datasource:
master:
name: ruoyi-vue-pro
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 3WLiVUBEwTbvAfsh
slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
name: ruoyi-vue-pro
url: jdbc:mysql://400-infra.server.iocoder.cn:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT&nullCatalogMeansCurrent=true
driver-class-name: com.mysql.jdbc.Driver
username: root
password: 3WLiVUBEwTbvAfsh
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 400-infra.server.iocoder.cn # 地址
port: 6379 # 端口
database: 1 # 数据库索引
# password: 123456 # 密码,建议生产环境开启
--- #################### MQ 消息队列相关配置 ####################
--- #################### 定时任务相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项
lock4j:
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
web:
base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址
instance:
service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
xss:
enable: false
exclude-urls: # 如下两个 url仅仅是为了演示去掉配置也没关系
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
demo: true # 开启演示模式

View File

@ -0,0 +1,126 @@
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
autoconfigure:
exclude:
- com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure # 排除 Druid 的自动配置,使用 dynamic-datasource-spring-boot-starter 配置多数据源
datasource:
druid: # Druid 【监控】相关的全局配置
web-stat-filter:
enabled: true
stat-view-servlet:
enabled: true
allow: # 设置白名单,不填则允许所有访问
url-pattern: /druid/*
login-username: # 控制台管理用户名和密码
login-password:
filter:
stat:
enabled: true
log-slow-sql: true # 慢 SQL 记录
slow-sql-millis: 100
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic: # 多数据源配置
druid: # Druid 【连接池】相关的全局配置
initial-size: 5 # 初始连接数
min-idle: 10 # 最小连接池数量
max-active: 20 # 最大连接池数量
max-wait: 600000 # 配置获取连接等待超时的时间,单位:毫秒
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒
min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间,单位:毫秒
max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间,单位:毫秒
validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效
test-while-idle: true
test-on-borrow: false
test-on-return: false
primary: master
datasource:
master:
name: ruoyi-vue-pro
url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.master.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.master.name} # SQLServer 连接的示例
username: root
password: 123456
# username: sa
# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
slave: # 模拟从库,可根据自己需要修改
name: ruoyi-vue-pro
url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
# url: jdbc:mysql://127.0.0.1:3306/${spring.datasource.dynamic.datasource.slave.name}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT # MySQL Connector/J 5.X 连接的示例
# url: jdbc:postgresql://127.0.0.1:5432/${spring.datasource.dynamic.datasource.slave.name} # PostgreSQL 连接的示例
# url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例
# url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=${spring.datasource.dynamic.datasource.slave.name} # SQLServer 连接的示例
username: root
password: 123456
# username: sa
# password: JSm:g(*%lU4ZAkz06cd52KqT3)i1?H7W
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
port: 6379 # 端口
database: 0 # 数据库索引
# password: 123456 # 密码,建议生产环境开启
--- #################### MQ 消息队列相关配置 ####################
--- #################### 定时任务相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项
lock4j:
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
--- #################### 监控相关配置 ####################
# Actuator 监控端点的配置项
management:
endpoints:
web:
base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator
exposure:
include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ,可以开放所有端点。
# Spring Boot Admin 配置项
spring:
boot:
admin:
# Spring Boot Admin Client 客户端的相关配置
client:
url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址
instance:
service-host-type: IP # 注册实例时,优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
# 日志文件配置
logging:
level:
# 配置自己写的 MyBatis Mapper 打印日志
cn.iocoder.yudao.module.report.dal.mysql: debug
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
env: # 多环境的配置项
tag: ${HOSTNAME}
security:
mock-enable: true
xss:
enable: false
exclude-urls: # 如下两个 url仅仅是为了演示去掉配置也没关系
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
access-log: # 访问日志的配置项
enable: false
error-code: # 错误码相关配置项
enable: false
demo: false # 关闭演示模式

View File

@ -0,0 +1,83 @@
spring:
main:
allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
allow-bean-definition-overriding: true # 允许 Bean 覆盖,例如说 Dubbo 或者 Feign 等会存在重复定义的服务
# Servlet 配置
servlet:
# 文件上传相关配置项
multipart:
max-file-size: 16MB # 单个文件大小
max-request-size: 32MB # 设置总上传的文件大小
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题,参见 SpringFoxHandlerProviderBeanPostProcessor 类
# Jackson 配置项
jackson:
serialization:
write-dates-as-timestamps: true # 设置 LocalDateTime 的格式,使用时间戳
write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401,而是直接 1611460870401
write-durations-as-timestamps: true # 设置 Duration 的格式,使用时间戳
fail-on-empty-beans: false # 允许序列化无属性的 Bean
# Cache 配置项
cache:
type: REDIS
redis:
time-to-live: 1h # 设置过期时间为 1 小时
# MyBatis Plus 的配置项
mybatis-plus:
configuration:
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
global-config:
db-config:
# 重要说明:如果将配置放到 Nacos 时,请注意将 id-type 设置为对应 DB 的类型,否则会报错;详细见 https://gitee.com/zhijiantianya/yudao-cloud/issues/I5W2N0 讨论
id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
# id-type: AUTO # 自增 ID适合 MySQL 等直接自增的数据库
# id-type: INPUT # 用户输入 ID适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
# id-type: ASSIGN_ID # 分配 ID默认使用雪花算法。注意Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时,需要去除实体类上的 @KeySequence 注解
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
type-aliases-package: ${yudao.info.base-package}.dal.dataobject
encryptor:
password: XDV71a+xqStEA3WH # 加解密的秘钥,可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成
--- #################### RPC 远程调用相关配置 ####################
dubbo:
scan:
base-packages: ${yudao.info.base-package}.api # 指定 Dubbo 服务实现类的扫描基准包
protocol:
name: dubbo # 协议名称
port: -1 # 协议端口,-1 表示自增端口,从 20880 开始
registry:
address: spring-cloud://localhost # 设置使用 Spring Cloud 注册中心
application:
id: report-server # TODO 一定要写么?
--- #################### MQ 消息队列相关配置 ####################
--- #################### 定时任务相关配置 ####################
--- #################### 芋道相关配置 ####################
yudao:
info:
version: 1.0.0
base-package: cn.iocoder.yudao.module.report
web:
admin-ui:
url: http://dashboard.yudao.iocoder.cn # Admin 管理后台 UI 的地址
swagger:
title: 管理后台
description: 提供管理员管理的所有功能
version: ${yudao.info.version}
base-package: ${yudao.info.base-package}
error-code: # 错误码相关配置项
constants-class-list:
- cn.iocoder.yudao.module.report.enums.ErrorCodeConstants
tenant: # 多租户相关配置项
enable: true
debug: false

View File

@ -0,0 +1,23 @@
--- #################### 注册中心相关配置 ####################
spring:
cloud:
nacos:
server-addr: 127.0.0.1:8848
discovery:
namespace: dev # 命名空间。这里使用 dev 开发环境
metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布
--- #################### 配置中心相关配置 ####################
spring:
cloud:
nacos:
# Nacos Config 配置项,对应 NacosConfigProperties 配置属性类
config:
server-addr: 127.0.0.1:8848 # Nacos 服务器地址
namespace: dev # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
name: # 使用的 Nacos 配置集的 dataId默认为 spring.application.name
file-extension: yaml # 使用的 Nacos 配置集的 dataId 的文件拓展名,同时也是 Nacos 配置集的配置格式,默认为 properties

View File

@ -0,0 +1,14 @@
spring:
application:
name: report-server
profiles:
active: local
server:
port: 48084
# 日志文件配置。注意,如果 logging.file.name 不放在 bootstrap.yaml 配置文件,而是放在 application.yaml 中,会导致出现 LOG_FILE_IS_UNDEFINED 文件
logging:
file:
name: ${user.home}/logs/${spring.application.name}.log # 日志文件名,全路径

View File

@ -0,0 +1,76 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- 变量 yudao.info.base-package基础业务包 -->
<springProperty scope="context" name="yudao.info.base-package" source="yudao.info.base-package"/>
<!-- 格式化输出:%d 表示日期,%X{tid} SkWalking 链路追踪编号,%thread 表示线程名,%-5level级别从左显示 5 个字符宽度,%msg日志消息%n是换行符 -->
<property name="PATTERN_DEFAULT" value="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%thread] [%tid] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<!-- 控制台 Appender -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
</appender>
<!-- 文件 Appender -->
<!-- 参考 Spring Boot 的 file-appender.xml 编写 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
<!-- 日志文件名 -->
<file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 滚动后的日志文件名 -->
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<!-- 启动服务时,是否清理历史日志,一般不建议清理 -->
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<!-- 日志文件,到达多少容量,进行滚动 -->
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<!-- 日志文件的总大小0 表示不限制 -->
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<!-- 日志文件的保留天数 -->
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}</maxHistory>
</rollingPolicy>
</appender>
<!-- 异步写入日志,提升性能 -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志。默认的,如果队列的 80% 已满,则会丢弃 TRACT、DEBUG、INFO 级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能。默认值为 256 -->
<queueSize>256</queueSize>
<appender-ref ref="FILE"/>
</appender>
<!-- SkyWalking GRPC 日志收集实现日志中心。注意SkyWalking 8.4.0 版本开始支持 -->
<appender name="GRPC" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout">
<pattern>${PATTERN_DEFAULT}</pattern>
</layout>
</encoder>
</appender>
<!-- 本地环境 -->
<springProfile name="local">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="GRPC"/> <!-- 本地环境下,如果不想接入 SkyWalking 日志服务,可以注释掉本行 -->
<appender-ref ref="ASYNC"/> <!-- 本地环境下,如果不想打印日志,可以注释掉本行 -->
</root>
</springProfile>
<!-- 其它环境 -->
<springProfile name="dev,test,stage,prod,default">
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ASYNC"/>
<appender-ref ref="GRPC"/>
</root>
</springProfile>
</configuration>

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.data.GoViewDataRespVO;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSetMetaData;
import javax.annotation.Resource;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Import(GoViewDataServiceImpl.class)
public class GoViewDataServiceImplTest extends BaseDbUnitTest {
@Resource
private GoViewDataServiceImpl goViewDataService;
@MockBean
private JdbcTemplate jdbcTemplate;
@Test
public void testGetDataBySQL() {
// 准备参数
String sql = "SELECT id, name FROM system_users";
// mock 方法
SqlRowSet sqlRowSet = mock(SqlRowSet.class);
when(jdbcTemplate.queryForRowSet(eq(sql))).thenReturn(sqlRowSet);
// mock 元数据
SqlRowSetMetaData metaData = mock(SqlRowSetMetaData.class);
when(sqlRowSet.getMetaData()).thenReturn(metaData);
when(metaData.getColumnNames()).thenReturn(new String[]{"id", "name"});
// mock 数据明细
when(sqlRowSet.next()).thenReturn(true).thenReturn(true).thenReturn(false);
when(sqlRowSet.getObject("id")).thenReturn(1L).thenReturn(2L);
when(sqlRowSet.getObject("name")).thenReturn("芋道源码").thenReturn("芋道");
// 调用
GoViewDataRespVO dataBySQL = goViewDataService.getDataBySQL(sql);
// 断言
assertEquals(Arrays.asList("id", "name"), dataBySQL.getDimensions());
assertEquals(2, dataBySQL.getDimensions().size());
assertEquals(2, dataBySQL.getSource().get(0).size());
assertEquals(1L, dataBySQL.getSource().get(0).get("id"));
assertEquals("芋道源码", dataBySQL.getSource().get(0).get("name"));
assertEquals(2, dataBySQL.getSource().get(1).size());
assertEquals(2L, dataBySQL.getSource().get(1).get("id"));
assertEquals("芋道", dataBySQL.getSource().get(1).get("name"));
}
}

View File

@ -0,0 +1,135 @@
package cn.iocoder.yudao.module.report.service.goview;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectCreateReqVO;
import cn.iocoder.yudao.module.report.controller.admin.goview.vo.project.GoViewProjectUpdateReqVO;
import cn.iocoder.yudao.module.report.dal.dataobject.goview.GoViewProjectDO;
import cn.iocoder.yudao.module.report.dal.mysql.goview.GoViewProjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.Import;
import javax.annotation.Resource;
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
import static cn.iocoder.yudao.module.report.enums.ErrorCodeConstants.GO_VIEW_PROJECT_NOT_EXISTS;
import static org.junit.jupiter.api.Assertions.*;
/**
* {@link GoViewProjectServiceImpl}
*
* @author
*/
@Import(GoViewProjectServiceImpl.class)
public class GoViewProjectServiceImplTest extends BaseDbUnitTest {
@Resource
private GoViewProjectServiceImpl goViewProjectService;
@Resource
private GoViewProjectMapper goViewProjectMapper;
@Test
public void testCreateProject_success() {
// 准备参数
GoViewProjectCreateReqVO reqVO = randomPojo(GoViewProjectCreateReqVO.class);
// 调用
Long goViewProjectId = goViewProjectService.createProject(reqVO);
// 断言
assertNotNull(goViewProjectId);
// 校验记录的属性是否正确
GoViewProjectDO goViewProject = goViewProjectMapper.selectById(goViewProjectId);
assertPojoEquals(reqVO, goViewProject);
}
@Test
public void testUpdateProject_success() {
// mock 数据
GoViewProjectDO dbGoViewProject = randomPojo(GoViewProjectDO.class);
goViewProjectMapper.insert(dbGoViewProject);// @Sql: 先插入出一条存在的数据
// 准备参数
GoViewProjectUpdateReqVO reqVO = randomPojo(GoViewProjectUpdateReqVO.class, o -> {
o.setId(dbGoViewProject.getId()); // 设置更新的 ID
o.setStatus(randomCommonStatus());
});
// 调用
goViewProjectService.updateProject(reqVO);
// 校验是否更新正确
GoViewProjectDO goViewProject = goViewProjectMapper.selectById(reqVO.getId()); // 获取最新的
assertPojoEquals(reqVO, goViewProject);
}
@Test
public void testUpdateProject_notExists() {
// 准备参数
GoViewProjectUpdateReqVO reqVO = randomPojo(GoViewProjectUpdateReqVO.class);
// 调用, 并断言异常
assertServiceException(() -> goViewProjectService.updateProject(reqVO), GO_VIEW_PROJECT_NOT_EXISTS);
}
@Test
public void testDeleteProject_success() {
// mock 数据
GoViewProjectDO dbGoViewProject = randomPojo(GoViewProjectDO.class);
goViewProjectMapper.insert(dbGoViewProject);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbGoViewProject.getId();
// 调用
goViewProjectService.deleteProject(id);
// 校验数据不存在了
assertNull(goViewProjectMapper.selectById(id));
}
@Test
public void testDeleteProject_notExists() {
// 准备参数
Long id = randomLongId();
// 调用, 并断言异常
assertServiceException(() -> goViewProjectService.deleteProject(id), GO_VIEW_PROJECT_NOT_EXISTS);
}
@Test
public void testGetProject() {
// mock 数据
GoViewProjectDO dbGoViewProject = randomPojo(GoViewProjectDO.class);
goViewProjectMapper.insert(dbGoViewProject);// @Sql: 先插入出一条存在的数据
// 准备参数
Long id = dbGoViewProject.getId();
// 调用
GoViewProjectDO goViewProject = goViewProjectService.getProject(id);
// 断言
assertPojoEquals(dbGoViewProject, goViewProject);
}
@Test
public void testGetMyGoViewProjectPage() {
// mock 数据
GoViewProjectDO dbGoViewProject = randomPojo(GoViewProjectDO.class, o -> { // 等会查询到
o.setCreator("1");
});
goViewProjectMapper.insert(dbGoViewProject);
// 测试 userId 不匹配
goViewProjectMapper.insert(cloneIgnoreId(dbGoViewProject, o -> o.setCreator("2")));
// 准备参数
PageParam reqVO = new PageParam();
Long userId = 1L;
// 调用
PageResult<GoViewProjectDO> pageResult = goViewProjectService.getMyProjectPage(reqVO, userId);
// 断言
assertEquals(1, pageResult.getTotal());
assertEquals(1, pageResult.getList().size());
assertPojoEquals(dbGoViewProject, pageResult.getList().get(0));
}
}

View File

@ -0,0 +1,53 @@
spring:
main:
lazy-initialization: true # 开启懒加载,加快速度
banner-mode: off # 单元测试,禁用 Banner
--- #################### 数据库相关配置 ####################
spring:
# 数据源配置项
datasource:
name: ruoyi-vue-pro
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式DATABASE_TO_UPPER 配置表和字段使用小写
driver-class-name: org.h2.Driver
username: sa
password:
druid:
async-init: true # 单元测试,异步初始化 Druid 连接池,提升启动速度
initial-size: 1 # 单元测试,配置为 1提升启动速度
sql:
init:
schema-locations: classpath:/sql/create_tables.sql
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis:
host: 127.0.0.1 # 地址
port: 16379 # 端口(单元测试,使用 16379 端口)
database: 0 # 数据库索引
mybatis-plus:
lazy-initialization: true # 单元测试,设置 MyBatis Mapper 延迟加载,加速每个单元测试
type-aliases-package: ${yudao.info.base-package}.module.*.dal.dataobject
global-config:
db-config:
id-type: AUTO # H2 主键递增
--- #################### 定时任务相关配置 ####################
--- #################### 配置中心相关配置 ####################
--- #################### 服务保障相关配置 ####################
# Lock4j 配置项(单元测试,禁用 Lock4j
# Resilience4j 配置项
--- #################### 监控相关配置 ####################
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置
yudao:
info:
base-package: cn.iocoder.yudao

View File

@ -0,0 +1,4 @@
<configuration>
<!-- 引用 Spring Boot 的 logback 基础配置 -->
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
</configuration>

View File

@ -0,0 +1 @@
DELETE FROM "report_go_view_project";

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS "report_go_view_project" (
"id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,
"name" varchar NOT NULL,
"pic_url" varchar,
"content" varchar,
"status" varchar NOT NULL,
"remark" varchar,
"creator" varchar DEFAULT '',
"create_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updater" varchar DEFAULT '',
"update_time" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
"deleted" bit NOT NULL DEFAULT FALSE,
PRIMARY KEY ("id")
) COMMENT 'GoView 项目表';

View File

@ -30,12 +30,12 @@ public interface DeptApi {
@GetMapping(PREFIX + "/list")
@ApiOperation("获得部门信息数组")
@ApiImplicitParam(name = "ids", value = "部门编号数组", example = "1,2", required = true, allowMultiple = true)
CommonResult<List<DeptRespDTO>> getDepts(@RequestParam("ids") Collection<Long> ids);
CommonResult<List<DeptRespDTO>> getDeptList(@RequestParam("ids") Collection<Long> ids);
@GetMapping(PREFIX + "/valid")
@ApiOperation("校验部门是否合法")
@ApiImplicitParam(name = "ids", value = "部门编号数组", example = "1,2", required = true, allowMultiple = true)
CommonResult<Boolean> validDepts(@RequestParam("ids") Collection<Long> ids);
CommonResult<Boolean> validateDeptList(@RequestParam("ids") Collection<Long> ids);
/**
* Map
@ -44,7 +44,7 @@ public interface DeptApi {
* @return Map
*/
default Map<Long, DeptRespDTO> getDeptMap(Set<Long> ids) {
return CollectionUtils.convertMap(getDepts(ids).getCheckedData(), DeptRespDTO::getId);
return CollectionUtils.convertMap(getDeptList(ids).getCheckedData(), DeptRespDTO::getId);
}
}

View File

@ -20,6 +20,6 @@ public interface PostApi {
@GetMapping(PREFIX + "/valid")
@ApiOperation("校验岗位是否合法")
@ApiImplicitParam(name = "ids", value = "岗位编号数组", example = "1,2", required = true, allowMultiple = true)
CommonResult<Boolean> validPosts(@RequestParam("ids") Collection<Long> ids);
CommonResult<Boolean> validPostList(@RequestParam("ids") Collection<Long> ids);
}

View File

@ -25,8 +25,8 @@ public interface DictDataApi {
@ApiImplicitParam(name = "dictType", value = "字典类型", example = "SEX", required = true, dataTypeClass = String.class),
@ApiImplicitParam(name = "values", value = "字典数据值的数组", example = "1,2", required = true, allowMultiple = true)
})
CommonResult<Boolean> validDictDatas(@RequestParam("dictType") String dictType,
@RequestParam("values") Collection<String> values);
CommonResult<Boolean> validateDictDatas(@RequestParam("dictType") String dictType,
@RequestParam("values") Collection<String> values);
@GetMapping(PREFIX + "/get")
@ApiOperation("获得指定的字典数据")

View File

@ -16,7 +16,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@ -27,7 +26,7 @@ public interface ErrorCodeApi {
@PostMapping(PREFIX + "/auto-generate")
@ApiOperation("自动创建错误码")
CommonResult<Boolean> autoGenerateErrorCodes(@Valid @RequestBody List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs);
CommonResult<Boolean> autoGenerateErrorCodeList(@Valid @RequestBody List<ErrorCodeAutoGenerateReqDTO> autoGenerateDTOs);
@GetMapping(PREFIX + "/list")
@ApiOperation(value = "增量获得错误码数组", notes = "如果 minUpdateTime 为空时,则获取所有错误码")

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.system.api.mail;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.api.mail.dto.MailSendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.enums.ApiConstants;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Api(tags = "RPC 服务 - 邮件发送")
public interface MailSendApi {
String PREFIX = ApiConstants.PREFIX + "/mail/send";
@PostMapping(PREFIX + "/send-single-admin")
@ApiOperation(value = "发送单条邮件给 Admin 用户", notes = "在 mail 为空时,使用 userId 加载对应 Admin 的邮箱")
CommonResult<Long> sendSingleMailToAdmin(@Valid MailSendSingleToUserReqDTO reqDTO);
@PostMapping(PREFIX + "/send-single-member")
@ApiOperation(value = "发送单条邮件给 Member 用户", notes = "在 mail 为空时,使用 userId 加载对应 Member 的邮箱")
CommonResult<Long> sendSingleMailToMember(@Valid MailSendSingleToUserReqDTO reqDTO);
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.system.api.mail.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import java.util.Map;
@ApiModel("RPC 服务 - 邮件发送给 Admin 或者 Member 用户 Request DTO")
@Data
public class MailSendSingleToUserReqDTO {
@ApiModelProperty(value = "用户编号", example = "1024")
private Long userId;
@ApiModelProperty(value = "手机号", required = true, example = "15601691300")
@Email
private String mail;
@ApiModelProperty(value = "邮件模板编号", required = true, example = "USER_SEND")
@NotNull(message = "邮件模板编号不能为空")
private String templateCode;
@ApiModelProperty(value = "邮件模板参数")
private Map<String, Object> templateParams;
}

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.system.api.notify;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
import cn.iocoder.yudao.module.system.enums.ApiConstants;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import javax.validation.Valid;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Api(tags = "RPC 服务 - 站内信发送")
public interface NotifyMessageSendApi {
String PREFIX = ApiConstants.PREFIX + "/notify/send";
@PostMapping(PREFIX + "/send-single-admin")
@ApiOperation(value = "发送单条站内信给 Admin 用户")
CommonResult<Long> sendSingleMessageToAdmin(@Valid NotifySendSingleToUserReqDTO reqDTO);
@PostMapping(PREFIX + "/send-single-member")
@ApiOperation(value = "发送单条站内信给 Member 用户")
CommonResult<Long> sendSingleMessageToMember(@Valid NotifySendSingleToUserReqDTO reqDTO);
}

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