Migration – 永夜 https://www.shuijingwanwq.com 没有不值得去解决的问题,也没有不值得去学习的技术! Wed, 03 Jun 2026 11:44:09 +0000 zh-Hans hourly 1 https://wordpress.org/?v=7.0 Ubuntu 26.04 + Docker 搭建 Go Gin 开发环境(全记录) https://www.shuijingwanwq.com/2026/06/03/15587/ https://www.shuijingwanwq.com/2026/06/03/15587/#respond Wed, 03 Jun 2026 11:44:06 +0000 https://www.shuijingwanwq.com/?p=15587 浏览量: 37

从 Windows WSL2 无缝迁移到 Ubuntu 裸机,基于现有 MySQL/Redis 容器构建 Go 开发环境,并接入自定义网络。

📌 前言

之前一直在 Windows 10 + WSL2 中学习 go-gin-learning 项目,现在换到了全新的 Ubuntu 26.04 系统。希望继续使用 Docker 来隔离开发环境,同时复用已经部署好的 MySQL 8.0 和 Redis 7.2 容器(位于自定义网络 services_dev-network 中)。

本文记录了从零开始配置的全部步骤,包含:

  • 系统工具检查与安装
  • Docker 环境验证
  • 项目代码克隆
  • 编写 Dockerfile 预装 git
  • 编写 docker-compose.yml 接入现有网络
  • 容器内运行 Gin 应用并测试连通性
  • 常见问题与避坑建议

🧰 一、准备工作:检查系统已有工具

在 Ubuntu 中养成“先检查,后安装”的习惯,可以避免重复操作。

1.1 检查 Git

git --version

输出示例(已安装):

git version 2.53.0

若未安装,执行:

sudo apt install git -y

1.2 检查 Docker

docker --version
docker compose version

输出示例

Docker version 29.5.2, build 79eb04c
Docker Compose version v5.1.4

若未安装,请参考 Docker 官方文档 安装。

建议:将当前用户加入 docker 组,避免每次输入 sudo

sudo usermod -aG docker $USER

然后注销并重新登录


🌐 二、确认已有数据库网络环境

之前已经在 ~/docker/services 中通过 docker-compose.yml 运行了 MySQL 8.0 和 Redis 7.2,并创建了自定义网络 services_dev-network

2.1 查看网络

docker network ls | grep dev-network

输出

71f462c6ef91   services_dev-network   bridge    local

2.2 查看数据库容器状态

docker ps --filter "name=mysql80" --filter "name=redis72"

输出

CONTAINER ID   IMAGE       ...   NAMES
9ec38ca384b6   redis:7.2   ...   redis72
4e93fd2e2c3f   mysql:8.0   ...   mysql80

💡 关键信息

  • MySQL 容器名:mysql80,内部端口 3306,宿主机映射端口 3307
  • Redis 容器名:redis72,内部端口 6379
  • 网络名:services_dev-network注意前缀,由 ~/docker/services 目录名派生)

📁 三、创建代码目录并克隆项目

选择一个语义清晰的目录存放项目代码,这里使用 ~/code(全小写,符合 Linux 惯例)。

mkdir -p ~/code
cd ~/code
git clone https://github.com/shuijingwan/go-gin-learning.git
cd go-gin-learning

检查项目结构:

ls -la

🐳 四、编写 Dockerfile(预装 git)

为了不在每次进入容器时手动安装 git,我们创建一个自定义镜像。

创建 Dockerfile

FROM golang:1.26-alpine

# 安装 git(go mod 下载某些依赖时需要)
RUN apk add --no-cache git

# 设置工作目录,与后面的 docker-compose 保持一致
WORKDIR /code

# 保持容器运行(开发模式)
CMD ["tail", "-f", "/dev/null"]

📌 说明

  • 使用 Alpine 版本镜像,体积小
  • apk add --no-cache 不保留缓存,减小镜像大小
  • WORKDIR /code 后续可以省略 docker-compose 中的 working_dir,但为了明确,两边都保留也没问题

⚙️ 五、编写 docker-compose.yml(接入现有网络)

关键点:

  • 使用 build: . 而不是 image: ...
  • 加入外部网络 services_dev-network
  • 传递数据库连接所需的环境变量
  • 删除已过时的 version 字段

最终 docker-compose.yml 内容:

services:
  go:
    build: .
    container_name: go-gin-learning
    working_dir: /code
    volumes:
      - ./:/code
    ports:
      - "8080:8080"
    tty: true
    stdin_open: true
    environment:
      - GOPROXY=https://goproxy.io,direct
      - GOSUMDB=sum.golang.google.cn
      - CGO_ENABLED=0
      # 数据库连接配置(使用现有网络中的服务名)
      - DB_HOST=mysql80
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=mystrongpwd80
      - DB_NAME=go_gin
      - REDIS_HOST=redis72
      - REDIS_PORT=6379
      - REDIS_PASSWORD=myredispass
    networks:
      - services_dev-network

networks:
  services_dev-network:
    external: true

⚠️ 注意

  • DB_PASSWORDREDIS_PASSWORD 要与 ~/docker/services/docker-compose.yml 中的密码完全一致
  • 容器间通信使用内部端口 3306 和 6379,而不是宿主机映射的 3307。

🚀 六、构建并启动开发容器

# 停止并删除旧容器(如果有)
docker compose down

# 构建镜像(--no-cache 确保重新安装 git)
docker compose build --no-cache

# 启动容器(后台运行)
docker compose up -d

查看运行状态:

docker ps --filter "name=go-gin-learning"

🧪 七、进入容器并验证连接

7.1 进入容器

docker exec -it go-gin-learning sh

此时提示符变为 /code #

7.2 验证 git 已安装

git --version

应输出类似 git version 2.52.0

7.3 测试网络连通性

# 安装测试工具(仅调试用,正式镜像可不加)
apk add iputils mysql-client redis

# ping 数据库容器
ping -c 2 mysql80
ping -c 2 redis72

7.4 测试 MySQL 连接(禁用 SSL)

MySQL 8.0 默认要求 SSL,但在容器内部使用 --ssl=0 即可绕过证书验证:

mariadb -h mysql80 -P 3306 -u root -pmystrongpwd80 -e "SHOW DATABASES;" --ssl=0

预期输出:如图1

+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

💡 若使用 mysql 命令,请用 --ssl-mode=DISABLED

7.5 测试 Redis 连接

redis-cli -h redis72 -a myredispass PING

预期输出:如图2

预期输出:如图2
PONG

🏃 八、运行 Gin 应用

在容器内执行:

go run main.go

看到如下日志表示成功:如图3

看到如下日志表示成功:如图3
go: downloading github.com/go-playground/locales v0.14.1
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:	export GIN_MODE=release
 - using code:	gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /albums                   --> main.getAlbums (3 handlers)
[GIN-debug] GET    /albums/:id               --> main.getAlbumByID (3 handlers)
[GIN-debug] POST   /albums                   --> main.postAlbums (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on 0.0.0.0:8080
[GIN] 2026/06/03 - 11:19:14 | 404 |  1.219µs |      172.18.0.1 | GET      "/"
[GIN] 2026/06/03 - 11:19:14 | 404 |    778ns |      172.18.0.1 | GET      "/favicon.ico"
[GIN] 2026/06/03 - 11:19:45 | 200 | 147.412µs |      172.18.0.1 | GET      "/albums"


在宿主机测试 API

打开另一个终端,执行:

curl http://localhost:8080/albums

或直接在浏览器访问 http://localhost:8080/albums

📸 浏览器显示 JSON 数据,如图4


🧹 九、常用管理命令

操作命令
启动开发容器cd ~/code/go-gin-learning && docker compose up -d
停止容器docker compose down
重新构建镜像docker compose build --no-cache
查看日志docker compose logs -f
进入容器docker exec -it go-gin-learning sh
重启 MySQL/Redis(若需要)cd ~/docker/services && docker compose restart

⚠️ 十、避坑指南

10.1 容器内 git 缺失导致 go run 失败

现象

unable to resolve git version: exec: "git": executable file not found in $PATH

解决:使用自定义 Dockerfile 安装 git,见第四章。

10.2 MySQL 连接报 TLS/SSL 错误

现象

ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure

解决:在连接命令中添加 --ssl=0(mariadb)或 --ssl-mode=DISABLED(mysql)。

10.3 容器无法访问 MySQL/Redis

原因:未加入正确的网络,或网络名称写错。

解决

  • 确认 docker network ls 中有 services_dev-network
  • 检查 docker-compose.yml 中 networks 名称是否完全一致(包括下划线)
  • 在容器内 ping mysql80 测试

10.4 宿主机无法访问 8080 端口

原因:Gin 监听的是 127.0.0.1:8080 而不是 0.0.0.0:8080,导致端口映射失效。

解决:在代码中设置 router.Run(":8080")router.Run("0.0.0.0:8080")


📝 十一、总结

通过本次实践,我实现了:

  1. ✅ 在 Ubuntu 26.04 上搭建完整的 Go 开发环境
  2. ✅ 利用已有的 MySQL/Redis 容器(独立部署)和自定义网络
  3. ✅ 构建带 git 的 Go 1.26 Alpine 开发镜像
  4. ✅ 编写 docker-compose.yml 挂载代码并接入外部网络
  5. ✅ 在容器内成功运行 Gin 应用并验证数据库连通性

这种“数据库与代码分离容器”的模式非常适合多项目共用基础设施,既节省资源,又保持环境一致性。

下一步,便可以:

  • main.go 中编写连接 MySQL/Redis 的业务逻辑
  • 使用任意代码编辑器(如 VS Code、Sublime 等)直接编辑项目文件,保存后容器内实时同步
  • docker-compose.yml 加入版本控制,与队友共享环境配置

🔗 参考资料

]]>
https://www.shuijingwanwq.com/2026/06/03/15587/feed/ 0
从 Windows 10 迁移至 Ubuntu – 上篇:安装前的准备工作 https://www.shuijingwanwq.com/2026/05/27/13739/ https://www.shuijingwanwq.com/2026/05/27/13739/#respond Wed, 27 May 2026 08:55:55 +0000 https://www.shuijingwanwq.com/?p=13739 浏览量: 65

本文适合没有 U 盘、想彻底覆盖 Windows 系统、单装 Ubuntu 的场景
本文包含 两次尝试:第一次用 EasyBCD 失败,第二次用分区 + EasyUEFI 成功
真实踩坑:选错硬盘,把 Ubuntu 装到了较大的硬盘的 F 盘分区上

一、为什么要写这篇

我计划彻底告别 Windows,把主力系统换成 Ubuntu 26.04。手头没有 U 盘,也不想买。我在网上搜了很多教程,主流方法是用 EasyBCD 或 Rufus + U 盘。但我发现 EasyBCD 在 UEFI 模式下根本无法引导 ISO,这条路走不通。

最终我采用了另一种方法:在硬盘上划出一个 FAT32 分区,把 Ubuntu 安装文件解压进去,再用 EasyUEFI 添加一个启动项。这个方法不需要任何外部设备。

我把整个过程(包括失败的尝试和选错硬盘的坑)完整记录下来,附上 11 张截图。只要你严格按步骤操作,就能顺利完成安装前的准备。

二、准备工作(清单)

  • 一台正在运行 Windows 10 的电脑(我的机型是 ThinkPad T570)
  • Ubuntu 26.04 LTS 官方 ISO 镜像:ubuntu-26.04-desktop-amd64.iso
  • 10 GB 的空闲硬盘空间(用于临时存放安装文件)
  • 管理员权限
Ubuntu 26.04 LTS 官方 ISO 镜像:ubuntu-26.04-desktop-amd64.iso

⚠️ 重要:操作前请将 所有重要数据 备份到外部硬盘或云盘。

我是未做备份了。因为我当时的想法是C盘的数据不用保留,DEF盘的数据,直接放在较大的硬盘上,只要谨慎操作,也不会有大问题。
虽然我反复核对,还是因为误判硬盘使用情况,把 Ubuntu 装到了 F 盘(属于较小的那一块硬盘),导致 F 盘数据全部丢失。

误判硬盘使用情况:电脑自带2块硬盘,一块较小的用于F盘(磁盘 0),一块较大的用于CDE盘(磁盘 1),最开始我误以为较小的用于C盘了。
备份是唯一可靠的后悔药。

三、第一次尝试:EasyBCD(失败)

3.1 为什么先试 EasyBCD

网上很多教程说 EasyBCD 可以无 U 盘安装 Linux,我决定先试这个方案。

3.2 下载与安装

我下载了 EasyBCD 最新的免费版,安装在 C 盘。

我下载了 EasyBCD 最新的免费版,安装在 C 盘。

3.3 失败的提示

刚打开 EashBCD,结果弹出一个错误窗口:

如图3 – EasyBCD EFI 模式限制报错

EasyBCD has detected that your machine is currently booting in EFI mode. Due to limitations set by Microsoft, many of EasyBCD’s multi-booting features cannot be used in EFI mode and have been disabled.

查阅资料后发现,微软限制了 Windows 启动管理器在 UEFI 模式下的功能,EasyBCD 无法在 EFI 模式下直接引导 ISO 镜像。这条路走不通。

3.4 放弃 EasyBCD

我决定换一种方法:不依赖 Windows 启动管理器,而是直接在 UEFI 层面添加一个启动项,指向一个“假的 U 盘”(硬盘上的 FAT32 分区)。

四、第二次尝试:硬盘分区 + EasyUEFI(成功)

4.1 核心原理

在较大的硬盘(磁盘 1)的 D 盘上划出 10 GB 空间,格式化为 FAT32(UEFI 能识别),然后把 Ubuntu 26.04 ISO 解压后的所有文件复制进去。再用 EasyUEFI 这个工具,在主板 NVRAM 中添加一个启动项,指向该分区中的 grubx64.efi 文件。这样重启后,主板就会从这个“虚拟 U 盘”启动,进入 Ubuntu 安装程序。

4.2 创建一个 FAT32 分区

4.2.1 打开磁盘管理

Win + X,选择 磁盘管理

4.2.2 压缩出 10 GB 空闲空间

我选择从 D 盘(属于较大的硬盘(磁盘 1))压缩 10 GB 空间。因为 D 盘有足够的剩余空间。

右键 D 盘 → 压缩卷 → 输入压缩空间量:10240(单位 MB,即 10 GB)→ 点击压缩。

图4 – 压缩卷输入 10240 MB

压缩完成后,磁盘管理中会出现一块 10 GB 的 未分配 区域,显示为黑色条纹。

图5 – 未分配区域显示

4.2.3 配置项

  • 在新建简单卷向导的过程中
  • 分配驱动器号:我选择了 G
  • 文件系统必须选 FAT32

点击完成,等待格式化结束。最终磁盘管理中会出现一个 G 盘(FAT32,容量 9.99 GB)。

4.3 解压 Ubuntu 26.04 ISO 到 G 盘

不要使用 Rufus 或软碟通写入,只需要简单解压。

  • 用 7-Zip 或 WinRAR 打开 ubuntu-26.04-desktop-amd64.iso
  • 选中所有文件和文件夹,解压到 G 盘根目录

解压完成后,G 盘根目录下会出现 booteficasperdistspool 等文件夹,以及 boot.catalogmd5sum.txt 等文件。

如图6 – G 盘解压后的文件列表

4.4 下载并安装 EasyUEFI

EasyUEFI 是一个 UEFI 启动项管理工具,有免费版(个人使用)。

  • 搜索 “Hasleo EasyUEFI” 进入官网,选择 Hasleo EasyUEFI
  • 下载 Free Trial 版本(足够本次使用)
如图7,搜索 “Hasleo EasyUEFI” 进入官网,选择 Hasleo EasyUEFI

如图8 – EasyUEFI 官网下载 Free Trial

安装时可以装在 C 盘。

4.5 使用 EasyUEFI 创建启动项

4.5.1 打开 EasyUEFI

安装完成后启动,点击主界面第一个按钮 “管理 EFI 启动项”或者“Windows Boot Manager”。

如图9 – EasyUEFI 创建新启动项窗口

4.5.2 创建新项

在启动项管理窗口中,点击 “创建新项”

4.5.3 填写参数

  • 类型:选择 “Linux 或者其他操作系统”
  • 描述:填写 Ubuntu Installer
  • 目标分区:选择刚才创建的 FAT32 分区,即 G 盘(注意查看容量和盘符)
  • 文件路径:点击 “浏览” 按钮,导航到 G:\EFI\boot\ 目录,选中 grubx64.efi 文件

如果你的 G 盘中没有 EFI\boot\grubx64.efi,可以尝试 G:\EFI\ubuntu\grubx64.efiG:\boot\grubx64.efi。Ubuntu 26.04 的解压文件中,grubx64.efi 位于 \EFI\boot\ 下。

点击 “确定” 保存。

如图10 – EasyUEFI 创建新启动项窗口,填写参数

4.5.4 调整启动顺序

在启动项列表中找到刚创建的 Ubuntu Installer,通过右侧的 “上移” 按钮将其移到第一位。这样重启后电脑会优先从这个启动项引导。

如图11,在启动项列表中找到刚创建的 Ubuntu Installer,通过右侧的 “上移” 按钮将其移到第一位。这样重启后电脑会优先从这个启动项引导。

五、重启进入安装程序

完成以上所有步骤后,重启电脑。按 F12 键,如果启动顺序设置正确,你会直接看到 GRUB 菜单,选项包括 “Try or Install Ubuntu”、“Ubuntu (safe graphics)” 等。

注意:如果启动时黑屏或花屏,在 “Try or Install Ubuntu” 选项上按 e 键,找到 quiet splash,在后面加上 nomodeset,然后按 F10 启动。这能解决大部分显卡兼容问题。

进入 Ubuntu 桌面后,点击 “安装 Ubuntu 26.04 LTS”。

六、我踩的一个大坑:选错硬盘

在 “安装类型” 界面,我选择了 “清除整个磁盘并安装 Ubuntu”。安装程序列出了两块硬盘:

  • 第一块:238 GB(我以为是较小的硬盘,即原来的 C 盘)
  • 第二块:953 GB(我以为是较大的硬盘,即原来的 DEF 盘)

我选了 238 GB 那块。但安装完成后,进入 Ubuntu,我发现 较大的硬盘上的 F 盘(原 200 GB 分区)变成了 Ubuntu 的系统盘,里面的数据全部丢失。而原来的 C 盘(较小的硬盘)竟然还是 Windows,完好无损。

不过,说实话,虽然 F 盘被 Ubuntu 系统给覆盖了。影响倒也是不大的。因为在 Windows 上,我的 F 盘是用于虚拟机的。数据保留的价值不大的。

为什么会这样?因为 ThinkPad T570 的硬盘顺序在 Ubuntu 安装程序中不是按 C/D/E/F 显示的,而是按 物理接口顺序。我误以为容量小的就是 C 盘,但实际上容量小的那块是较大的硬盘上的一个独立分区(F 盘),而较大的固态硬盘是 1TB 的(我后来才确认)。

所以,一定不要凭容量大小判断哪块是 Windows C 盘。正确做法:

  • 在启动 EasyUEFI 之前,先在 Windows 的磁盘管理中 记下每块硬盘的型号和容量
  • 在 Ubuntu 安装界面的分区列表中,同样会显示硬盘型号(例如 “Lenovo SSD 1TB” 或 “WDC WD10JPCX…”)。
  • 根据型号选择你要覆盖的硬盘,而不是容量。

七、准备完成 – 下一步

至此,你已经完成了所有安装前的准备工作:

  • 创建了 FAT32 分区并解压了 Ubuntu 26.04 ISO
  • 用 EasyUEFI 添加了正确的 UEFI 启动项
  • 知道了如何避免我犯的选错硬盘的错误

重启后选择 Ubuntu Installer 启动,进入安装环境,然后 务必仔细核对硬盘型号,选择你真正想要覆盖的那块硬盘(通常是原 Windows 所在的固态硬盘)。

关于正式的安装步骤(分区、用户设置、引导修复等),我会在下篇详细讲解。

👉 《从 Windows 10 迁移至 Ubuntu – 下篇:安装与系统配置》(即将发布)

八、小提醒

  1. 备份!备份!备份! 我因为误判硬盘,丢了 F 盘的所有数据。
  2. EasyUEFI 添加的启动项存储在主板 NVRAM 中,即使格式化硬盘也不会消失。安装完成后如果需要删除,可以再次进入 EasyUEFI 删除。
  3. 如果你希望安装后直接进入 Ubuntu 而不出现 Windows 启动管理器,只要覆盖了 Windows 所在硬盘,GRUB 会自动接管。
  4. 如果你还是担心选错硬盘,可以在 BIOS 中暂时禁用机械硬盘,只保留目标硬盘。安装完成后再启用。

希望这篇教程能帮你避开我踩过的坑,顺利迁移到 Ubuntu 26.04。如果有任何问题,欢迎留言。

]]>
https://www.shuijingwanwq.com/2026/05/27/13739/feed/ 0
从 REST 迁移到 GraphQL 的一些思考与实践(创建资源),参考 Shopify https://www.shuijingwanwq.com/2022/03/24/6179/ https://www.shuijingwanwq.com/2022/03/24/6179/#respond Thu, 24 Mar 2022 01:36:40 +0000 https://www.shuijingwanwq.com/?p=6179 浏览量: 103 1、新增 template ,创建模板。如图1
新增 template ,创建模板。

图1

2、点击保存按钮,查看网络请求。请求网址: https://xxx.myshopify.com/admin/themes/111/assets 。请求方法: POST。这是 REST 实现。如图2
点击保存按钮,查看网络请求。请求网址: https://xxx.myshopify.com/admin/themes/111/assets 。请求方法: POST。这是 REST 实现。

图2

请求的表单数据:


asset[key]: templates/article.dawn2.json
asset[value]: {
  "sections": {
    "main": {
      "type": "main-article",
      "blocks": {
        "featured_image": {
          "type": "featured_image",
          "settings": {
          }
        },
        "title": {
          "type": "title",
          "settings": {
          }
        },
        "content": {
          "type": "content",
          "settings": {
          }
        },
        "share": {
          "type": "share",
          "settings": {
          }
        }
      },
      "block_order": [
        "featured_image",
        "title",
        "share",
        "content"
      ],
      "settings": {
      }
    }
  },
  "order": [
    "main"
  ]
}


响应数据:


{
    "asset": {
        "key": "templates\/article.dawn2.json",
        "public_url": null,
        "created_at": "2022-02-22T22:23:17-08:00",
        "updated_at": "2022-02-22T22:23:17-08:00",
        "content_type": "application\/json",
        "size": 324,
        "checksum": "125d46765a7b7f96e7f3992a8bcfe22e",
        "theme_id": 128094896334
    }
}


3、在 GraphQL 中未实现创建模板文件的 API。决定参考 productCreate ( input ProductInput!, media [CreateMediaInput!] ) ProductCreatePayload Creates a product.。如图3
在 GraphQL 中未实现创建模板文件的 API。决定参考 productCreate ( input ProductInput!, media [CreateMediaInput!] ) ProductCreatePayload Creates a product.。

图3

4、测试请求与响应结构,先测试响应失败的结构。如图4
测试请求与响应结构,先测试响应失败的结构。

图4

请求数据:


mutation {
  productCreate(
    input: {title: "title"} {
      ... product
      ... userErrors
    }
}

fragment product on ProductCreatePayload {
  product {
    status
    storefrontId
    tags
    templateSuffix
    title
    totalInventory
    totalVariants
    tracksInventory
  }
}

fragment userErrors on ProductCreatePayload {
  userErrors {
    field
    message
  }
}



响应数据:


{
  "errors": [
    {
      "message": "Parse error on \"{\" (LCURLY) at [3, 29]",
      "locations": [
        {
          "line": 3,
          "column": 29
        }
      ]
    }
  ]
}


5、测试请求与响应结构,测试响应成功的结构。但是响应失败。如图5
测试请求与响应结构,测试响应成功的结构。但是响应失败。

图5

请求数据:


mutation {
  productCreate(
    input: {
      title: "标题 20220223 2",
  	}
  ) {
    product {
      ... product
      ... userErrors
    }
}

fragment product on ProductCreatePayload {
  product {
    title
  }
}

fragment userErrors on ProductCreatePayload {
  userErrors {
    field
    message
  }
}




响应数据:


{
  "errors": [
    {
      "message": "Unexpected end of document",
      "locations": []
    }
  ]
}


6、决定在 Shopify 后台 手动添加一件产品,查看网络请求,其是请求的 GraphQL API,参考其请求参数,复制至 Altair 的请求中。点击选择 query,然后右键复制值。如图6
决定在 Shopify 后台 手动添加一件产品,查看网络请求,其是请求的 GraphQL API,参考其请求参数,复制至 Altair 的请求中。点击选择 query,然后右键复制值。

图6

7、粘贴至 Altair 的查询中。如图7
粘贴至 Altair 的查询中。

图7



mutation CreateProduct($product: ProductInput!, $media: [CreateMediaInput!]) {
  productCreate(input: $product, media: $media) {
    product {
      id
      title
      handle
      descriptionHtml
      resourceAlerts {
        content
        dismissed
        dismissibleHandle
        severity
        title
        actions {
          primary
          title
          url
          __typename
        }
        __typename
      }
      firstVariant: variants(first: 1) {
        edges {
          node {
            id
            requiresShipping
            weight
            weightUnit
            barcode
            sku
            inventoryPolicy
            fulfillmentService {
              id
              __typename
            }
            inventoryItem {
              id
              unitCost {
                amount
                __typename
              }
              countryCodeOfOrigin
              provinceCodeOfOrigin
              harmonizedSystemCode
              tracked
              __typename
            }
            ...PricingCardVariant
            __typename
          }
          __typename
        }
        __typename
      }
      ...SEOCardProduct
      ...StandardProductType
      ...CustomProductType
      __typename
    }
    userErrors {
      field
      message
      __typename
    }
    __typename
  }
}

fragment PricingCardVariant on ProductVariant {
  id
  price
  compareAtPrice
  taxable
  taxCode
  presentmentPrices(first: 2) {
    edges {
      node {
        price {
          amount
          __typename
        }
        __typename
      }
      __typename
    }
    __typename
  }
  showUnitPrice
  unitPriceMeasurement {
    quantityValue
    quantityUnit
    referenceValue
    referenceUnit
    __typename
  }
  __typename
}

fragment SEOCardProduct on Product {
  seo {
    title
    description
    __typename
  }
  __typename
}

fragment StandardProductType on Product {
  standardProductType {
    productTaxonomyNodeId
    parentProductTaxonomyNodeId
    name
    fullName
    isLeaf
    isRoot
    __typename
  }
  inferredProductMetadata {
    standardProductType {
      productTaxonomyNodeId
      parentProductTaxonomyNodeId
      name
      fullName
      isLeaf
      isRoot
      __typename
    }
    __typename
  }
  __typename
}

fragment CustomProductType on Product {
  customProductType
  __typename
}



8、点击选择 variables,然后右键复制值。如图8
点击选择 variables,然后右键复制值。

图8

9、粘贴至 Altair 的变量中。如图9
粘贴至 Altair 的变量中。

图9



{
  "media": null,
  "product": {
    "standardProductType": null,
    "customProductType": null,
    "title": "标题 20220223 1",
    "descriptionHtml": "",
    "handle": "",
    "seo": {
      "title": "",
      "description": ""
    },
    "status": "DRAFT",
    "variants": [
      {
        "compareAtPrice": null,
        "price": "0",
        "taxable": true,
        "inventoryItem": {
          "cost": null,
          "countryCodeOfOrigin": null,
          "provinceCodeOfOrigin": null,
          "harmonizedSystemCode": null,
          "tracked": true
        },
        "requiresShipping": true,
        "weight": 0,
        "weightUnit": "KILOGRAMS",
        "fulfillmentServiceId": "gid://shopify/FulfillmentService/manual",
        "sku": "",
        "barcode": "",
        "inventoryPolicy": "DENY",
        "showUnitPrice": false,
        "unitPriceMeasurement": {
          "quantityValue": 0,
          "quantityUnit": null,
          "referenceValue": 0,
          "referenceUnit": null
        },
        "taxCode": null,
        "inventoryQuantities": [
          {
            "availableQuantity": 0,
            "locationId": "gid://shopify/Location/66268496078"
          }
        ],
        "options": [
          "Default Title"
        ]
      }
    ],
    "images": [],
    "tags": [],
    "templateSuffix": "",
    "giftCardTemplateSuffix": "",
    "vendor": "",
    "giftCard": false,
    "collectionsToJoin": [],
    "workflow": "product-details-create",
    "metafields": []
  }
}


10、发送请求报错,响应:Field ‘resourceAlerts’ doesn’t exist on type ‘Product’。在查询中依次删除:resourceAlerts、showUnitPrice、unitPriceMeasurement、standardProductType。如图10
发送请求报错,响应:Field 'resourceAlerts' doesn't exist on type 'Product'。在查询中依次删除:resourceAlerts、showUnitPrice、unitPriceMeasurement、standardProductType。

图10



{
  "errors": [
    {
      "message": "Field 'resourceAlerts' doesn't exist on type 'Product'",
      "locations": [
        {
          "line": 8,
          "column": 7
        }
      ],
      "path": [
        "mutation CreateProduct",
        "productCreate",
        "product",
        "resourceAlerts"
      ],
      "extensions": {
        "code": "undefinedField",
        "typeName": "Product",
        "fieldName": "resourceAlerts"
      }
    },
    {
      "message": "Field 'showUnitPrice' doesn't exist on type 'ProductVariant'",
      "locations": [
        {
          "line": 88,
          "column": 3
        }
      ],
      "path": [
        "fragment PricingCardVariant",
        "showUnitPrice"
      ],
      "extensions": {
        "code": "undefinedField",
        "typeName": "ProductVariant",
        "fieldName": "showUnitPrice"
      }
    },
    {
      "message": "Field 'unitPriceMeasurement' doesn't exist on type 'ProductVariant'",
      "locations": [
        {
          "line": 89,
          "column": 3
        }
      ],
      "path": [
        "fragment PricingCardVariant",
        "unitPriceMeasurement"
      ],
      "extensions": {
        "code": "undefinedField",
        "typeName": "ProductVariant",
        "fieldName": "unitPriceMeasurement"
      }
    },
    {
      "message": "Field 'standardProductType' doesn't exist on type 'Product'",
      "locations": [
        {
          "line": 109,
          "column": 3
        }
      ],
      "path": [
        "fragment StandardProductType",
        "standardProductType"
      ],
      "extensions": {
        "code": "undefinedField",
        "typeName": "Product",
        "fieldName": "standardProductType"
      }
    },
    {
      "message": "Field 'inferredProductMetadata' doesn't exist on type 'Product'",
      "locations": [
        {
          "line": 118,
          "column": 3
        }
      ],
      "path": [
        "fragment StandardProductType",
        "inferredProductMetadata"
      ],
      "extensions": {
        "code": "undefinedField",
        "typeName": "Product",
        "fieldName": "inferredProductMetadata"
      }
    }
  ]
}


11、发送请求报错,响应:Variable $product of type ProductInput! was provided invalid value for standardProductType (Field is not defined on ProductInput), variants.0.inventoryItem.countryCodeOfOrigin (Field is not defined on InventoryItemInput), variants.0.inventoryItem.provinceCodeOfOrigin (Field is not defined on InventoryItemInput), variants.0.inventoryItem.harmonizedSystemCode (Field is not defined on InventoryItemInput), variants.0.showUnitPrice (Field is not defined on ProductVariantInput), variants.0.unitPriceMeasurement (Field is not defined on ProductVariantInput), workflow (Field is not defined on ProductInput)。在变量中依次删除:standardProductType、variants.0.inventoryItem.countryCodeOfOrigin、variants.0.inventoryItem.provinceCodeOfOrigin、variants.0.inventoryItem.harmonizedSystemCode、variants.0.showUnitPrice、variants.0.unitPriceMeasurement、workflow。如图11
发送请求报错,响应:Variable $product of type ProductInput! was provided invalid value

图11



{
  "errors": [
    {
      "message": "Variable $product of type ProductInput! was provided invalid value for standardProductType (Field is not defined on ProductInput), variants.0.inventoryItem.countryCodeOfOrigin (Field is not defined on InventoryItemInput), variants.0.inventoryItem.provinceCodeOfOrigin (Field is not defined on InventoryItemInput), variants.0.inventoryItem.harmonizedSystemCode (Field is not defined on InventoryItemInput), variants.0.showUnitPrice (Field is not defined on ProductVariantInput), variants.0.unitPriceMeasurement (Field is not defined on ProductVariantInput), workflow (Field is not defined on ProductInput)",
      "locations": [
        {
          "line": 1,
          "column": 24
        }
      ],
      "extensions": {
        "value": {
          "standardProductType": null,
          "customProductType": null,
          "title": "标题 20220223 3",
          "descriptionHtml": "",
          "handle": "",
          "seo": {
            "title": "",
            "description": ""
          },
          "status": "DRAFT",
          "variants": [
            {
              "compareAtPrice": null,
              "price": "0",
              "taxable": true,
              "inventoryItem": {
                "cost": null,
                "countryCodeOfOrigin": null,
                "provinceCodeOfOrigin": null,
                "harmonizedSystemCode": null,
                "tracked": true
              },
              "requiresShipping": true,
              "weight": 0,
              "weightUnit": "KILOGRAMS",
              "fulfillmentServiceId": "gid://shopify/FulfillmentService/manual",
              "sku": "",
              "barcode": "",
              "inventoryPolicy": "DENY",
              "showUnitPrice": false,
              "unitPriceMeasurement": {
                "quantityValue": 0,
                "quantityUnit": null,
                "referenceValue": 0,
                "referenceUnit": null
              },
              "taxCode": null,
              "inventoryQuantities": [
                {
                  "availableQuantity": 0,
                  "locationId": "gid://shopify/Location/66268496078"
                }
              ],
              "options": [
                "Default Title"
              ]
            }
          ],
          "images": [],
          "tags": [],
          "templateSuffix": "",
          "giftCardTemplateSuffix": "",
          "vendor": "",
          "giftCard": false,
          "collectionsToJoin": [],
          "workflow": "product-details-create",
          "metafields": []
        },
        "problems": [
          {
            "path": [
              "standardProductType"
            ],
            "explanation": "Field is not defined on ProductInput"
          },
          {
            "path": [
              "variants",
              0,
              "inventoryItem",
              "countryCodeOfOrigin"
            ],
            "explanation": "Field is not defined on InventoryItemInput"
          },
          {
            "path": [
              "variants",
              0,
              "inventoryItem",
              "provinceCodeOfOrigin"
            ],
            "explanation": "Field is not defined on InventoryItemInput"
          },
          {
            "path": [
              "variants",
              0,
              "inventoryItem",
              "harmonizedSystemCode"
            ],
            "explanation": "Field is not defined on InventoryItemInput"
          },
          {
            "path": [
              "variants",
              0,
              "showUnitPrice"
            ],
            "explanation": "Field is not defined on ProductVariantInput"
          },
          {
            "path": [
              "variants",
              0,
              "unitPriceMeasurement"
            ],
            "explanation": "Field is not defined on ProductVariantInput"
          },
          {
            "path": [
              "workflow"
            ],
            "explanation": "Field is not defined on ProductInput"
          }
        ]
      }
    }
  ]
}


12、创建产品成功。响应 200。如图12
创建产品成功。响应 200。

图12

请求查询:


mutation CreateProduct($product: ProductInput!, $media: [CreateMediaInput!]) {
  productCreate(input: $product, media: $media) {
    product {
      id
      title
      handle
      descriptionHtml
      firstVariant: variants(first: 1) {
        edges {
          node {
            id
            requiresShipping
            weight
            weightUnit
            barcode
            sku
            inventoryPolicy
            fulfillmentService {
              id
              __typename
            }
            inventoryItem {
              id
              unitCost {
                amount
                __typename
              }
              countryCodeOfOrigin
              provinceCodeOfOrigin
              harmonizedSystemCode
              tracked
              __typename
            }
            ...PricingCardVariant
            __typename
          }
          __typename
        }
        __typename
      }
      ...SEOCardProduct
      ...CustomProductType
      __typename
    }
    userErrors {
      field
      message
      __typename
    }
    __typename
  }
}

fragment PricingCardVariant on ProductVariant {
  id
  price
  compareAtPrice
  taxable
  taxCode
  presentmentPrices(first: 2) {
    edges {
      node {
        price {
          amount
          __typename
        }
        __typename
      }
      __typename
    }
    __typename
  }
  __typename
}

fragment SEOCardProduct on Product {
  seo {
    title
    description
    __typename
  }
  __typename
}

fragment CustomProductType on Product {
  customProductType
  __typename
}



请求变量:


{
  "media": null,
  "product": {
    "customProductType": null,
    "title": "标题 20220223 3",
    "descriptionHtml": "",
    "handle": "",
    "seo": {
      "title": "",
      "description": ""
    },
    "status": "DRAFT",
    "variants": [
      {
        "compareAtPrice": null,
        "price": "0",
        "taxable": true,
        "inventoryItem": {
          "cost": null,
          "tracked": true
        },
        "requiresShipping": true,
        "weight": 0,
        "weightUnit": "KILOGRAMS",
        "fulfillmentServiceId": "gid://shopify/FulfillmentService/manual",
        "sku": "",
        "barcode": "",
        "inventoryPolicy": "DENY",
        "taxCode": null,
        "inventoryQuantities": [
          {
            "availableQuantity": 0,
            "locationId": "gid://shopify/Location/66268496078"
          }
        ],
        "options": [
          "Default Title"
        ]
      }
    ],
    "images": [],
    "tags": [],
    "templateSuffix": "",
    "giftCardTemplateSuffix": "",
    "vendor": "",
    "giftCard": false,
    "collectionsToJoin": [],
    "metafields": []
  }
}


响应:


{
  "data": {
    "productCreate": {
      "product": {
        "id": "gid://shopify/Product/6956647317710",
        "title": "标题 20220223 3",
        "handle": "标题-20220223-3",
        "descriptionHtml": "",
        "firstVariant": {
          "edges": [
            {
              "node": {
                "id": "gid://shopify/ProductVariant/40929372405966",
                "requiresShipping": true,
                "weight": 0,
                "weightUnit": "KILOGRAMS",
                "barcode": "",
                "sku": "",
                "inventoryPolicy": "DENY",
                "fulfillmentService": {
                  "id": "gid://shopify/FulfillmentService/manual",
                  "__typename": "FulfillmentService"
                },
                "inventoryItem": {
                  "id": "gid://shopify/InventoryItem/43021592854734",
                  "unitCost": null,
                  "countryCodeOfOrigin": null,
                  "provinceCodeOfOrigin": null,
                  "harmonizedSystemCode": null,
                  "tracked": true,
                  "__typename": "InventoryItem"
                },
                "price": "0.00",
                "compareAtPrice": null,
                "taxable": true,
                "taxCode": null,
                "presentmentPrices": {
                  "edges": [
                    {
                      "node": {
                        "price": {
                          "amount": "0.0",
                          "__typename": "MoneyV2"
                        },
                        "__typename": "ProductVariantPricePair"
                      },
                      "__typename": "ProductVariantPricePairEdge"
                    }
                  ],
                  "__typename": "ProductVariantPricePairConnection"
                },
                "__typename": "ProductVariant"
              },
              "__typename": "ProductVariantEdge"
            }
          ],
          "__typename": "ProductVariantConnection"
        },
        "seo": {
          "title": null,
          "description": null,
          "__typename": "SEO"
        },
        "__typename": "Product",
        "customProductType": null
      },
      "userErrors": [],
      "__typename": "ProductCreatePayload"
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 20,
      "actualQueryCost": 19,
      "throttleStatus": {
        "maximumAvailable": 1000,
        "currentlyAvailable": 981,
        "restoreRate": 50
      }
    }
  }
}


13、为何 Shopify 后台调用 GraphQL API 的请求参数与最新版本的 GraphQL API 不相匹配,初步怀疑可能是版本不一致所导致。Shopify 后台调用 GraphQL API 的版本不是最新版本。现在最新版本为:2022-01。而后台调用的版本为:2020-07。如图13
为何 Shopify 后台调用 GraphQL API 的请求参数与最新版本的 GraphQL API 不相匹配,初步怀疑可能是版本不一致所导致。Shopify 后台调用 GraphQL API 的版本不是最新版本。现在最新版本为:2022-01。而后台调用的版本为:2020-07。

图13

14、当请求响应报错时的结构。如图14
当请求响应报错时的结构。

图14



mutation {
  onlineStoreThemeAssetCreate(
    input: { themeId: "vogue", content: "string", key: "string" }
  ) {
    themeAsset {
      id
      themeId
      version
      content
      key
      mimeType
      category
      schema
      createdAt
      updatedAt
      deletable
      renameable
      updatable
    }
  }
}




{
  "errors": [
    {
      "debugMessage": "Undefined index: key",
      "message": "Internal server error",
      "extensions": {
        "category": "internal"
      },
      "locations": [
        {
          "line": 5,
          "column": 5
        }
      ],
      "path": [
        "onlineStoreThemeAssetCreate",
        "themeAsset"
      ],
      "trace": [
        {
          "file": "E:\\wwwroot\\object\\Modules\\ThemeStore\\Resolver\\OnlineStoreTheme\\ThemeAssetResolver.php",
          "line": 15,
          "call": "Illuminate\\Foundation\\Bootstrap\\HandleExceptions::handleError(8, 'Undefined index: key', 'E:\\wwwroot\\object\\Modules\\ThemeStore\\Resolver\\OnlineStoreTheme\\ThemeAssetResolver.php', 15, array(5))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Directives\\FieldDirective.php",
          "line": 52,
          "call": "Modules\\ThemeStore\\Resolver\\OnlineStoreTheme\\ThemeAssetResolver::__invoke(array(1), array(1), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Directives\\RenameArgsDirective.php",
          "line": 33,
          "call": "Nuwave\\Lighthouse\\Schema\\Directives\\FieldDirective::Nuwave\\Lighthouse\\Schema\\Directives\\{closure}(array(1), array(0), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Directives\\SpreadDirective.php",
          "line": 36,
          "call": "Nuwave\\Lighthouse\\Schema\\Directives\\RenameArgsDirective::Nuwave\\Lighthouse\\Schema\\Directives\\{closure}(array(1), array(0), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Directives\\ArgTraversalDirective.php",
          "line": 29,
          "call": "Nuwave\\Lighthouse\\Schema\\Directives\\SpreadDirective::Nuwave\\Lighthouse\\Schema\\Directives\\{closure}(array(1), array(0), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Validation\\ValidateDirective.php",
          "line": 53,
          "call": "Nuwave\\Lighthouse\\Schema\\Directives\\ArgTraversalDirective::Nuwave\\Lighthouse\\Schema\\Directives\\{closure}(array(1), array(0), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Directives\\ArgTraversalDirective.php",
          "line": 29,
          "call": "Nuwave\\Lighthouse\\Validation\\ValidateDirective::Nuwave\\Lighthouse\\Validation\\{closure}(array(1), array(0), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Directives\\TrimDirective.php",
          "line": 56,
          "call": "Nuwave\\Lighthouse\\Schema\\Directives\\ArgTraversalDirective::Nuwave\\Lighthouse\\Schema\\Directives\\{closure}(array(1), array(0), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Schema\\Factories\\FieldFactory.php",
          "line": 99,
          "call": "Nuwave\\Lighthouse\\Schema\\Directives\\TrimDirective::Nuwave\\Lighthouse\\Schema\\Directives\\{closure}(array(1), array(0), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 623,
          "call": "Nuwave\\Lighthouse\\Schema\\Factories\\FieldFactory::Nuwave\\Lighthouse\\Schema\\Factories\\{closure}(array(1), array(0), instance of Nuwave\\Lighthouse\\Schema\\Context, instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 550,
          "call": "GraphQL\\Executor\\ReferenceExecutor::resolveFieldValueOrError(instance of GraphQL\\Type\\Definition\\FieldDefinition, instance of GraphQL\\Language\\AST\\FieldNode, instance of Closure, array(1), instance of GraphQL\\Type\\Definition\\ResolveInfo)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 1195,
          "call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: OnlineStoreThemeAssetCreatePayload, array(1), instance of ArrayObject(1), array(2))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 1145,
          "call": "GraphQL\\Executor\\ReferenceExecutor::executeFields(GraphQLType: OnlineStoreThemeAssetCreatePayload, array(1), array(1), instance of ArrayObject(1))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 1106,
          "call": "GraphQL\\Executor\\ReferenceExecutor::collectAndExecuteSubfields(GraphQLType: OnlineStoreThemeAssetCreatePayload, instance of ArrayObject(1), array(1), array(1))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 793,
          "call": "GraphQL\\Executor\\ReferenceExecutor::completeObjectValue(GraphQLType: OnlineStoreThemeAssetCreatePayload, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(1), array(1))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 654,
          "call": "GraphQL\\Executor\\ReferenceExecutor::completeValue(GraphQLType: OnlineStoreThemeAssetCreatePayload, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(1), array(1))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 557,
          "call": "GraphQL\\Executor\\ReferenceExecutor::completeValueCatchingError(GraphQLType: OnlineStoreThemeAssetCreatePayload, instance of ArrayObject(1), instance of GraphQL\\Type\\Definition\\ResolveInfo, array(1), array(1))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 474,
          "call": "GraphQL\\Executor\\ReferenceExecutor::resolveField(GraphQLType: Mutation, null, instance of ArrayObject(1), array(1))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 857,
          "call": "GraphQL\\Executor\\ReferenceExecutor::GraphQL\\Executor\\{closure}(array(0), 'onlineStoreThemeAssetCreate')"
        },
        {
          "call": "GraphQL\\Executor\\ReferenceExecutor::GraphQL\\Executor\\{closure}(array(0), 'onlineStoreThemeAssetCreate')"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 859,
          "function": "array_reduce(array(1), instance of Closure, array(0))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 490,
          "call": "GraphQL\\Executor\\ReferenceExecutor::promiseReduce(array(1), instance of Closure, array(0))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 263,
          "call": "GraphQL\\Executor\\ReferenceExecutor::executeFieldsSerially(GraphQLType: Mutation, null, array(0), instance of ArrayObject(1))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\ReferenceExecutor.php",
          "line": 215,
          "call": "GraphQL\\Executor\\ReferenceExecutor::executeOperation(instance of GraphQL\\Language\\AST\\OperationDefinitionNode, null)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\Executor\\Executor.php",
          "line": 156,
          "call": "GraphQL\\Executor\\ReferenceExecutor::doExecute()"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\GraphQL.php",
          "line": 162,
          "call": "GraphQL\\Executor\\Executor::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\webonyx\\graphql-php\\src\\GraphQL.php",
          "line": 94,
          "call": "GraphQL\\GraphQL::promiseToExecute(instance of GraphQL\\Executor\\Promise\\Adapter\\SyncPromiseAdapter, instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null, array(29))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
          "line": 268,
          "call": "GraphQL\\GraphQL::executeQuery(instance of GraphQL\\Type\\Schema, instance of GraphQL\\Language\\AST\\DocumentNode, null, instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null, array(29))"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
          "line": 203,
          "call": "Nuwave\\Lighthouse\\GraphQL::executeParsedQuery(instance of GraphQL\\Language\\AST\\DocumentNode, instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
          "line": 162,
          "call": "Nuwave\\Lighthouse\\GraphQL::parseAndExecuteQuery('mutation {\n  onlineStoreThemeAssetCreate(\n    input: { themeId: \"vogue\", content: \"string\", key: \"string\" }\n  ) {\n    themeAsset {\n      id\n      themeId\n      version\n      content\n      key\n      mimeType\n      category\n      schema\n      createdAt\n      updatedAt\n      deletable\n      renameable\n      updatable\n    }\n  }\n}', instance of Nuwave\\Lighthouse\\Schema\\Context, array(0), null, null)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
          "line": 121,
          "call": "Nuwave\\Lighthouse\\GraphQL::executeOperation(instance of GraphQL\\Server\\OperationParams, instance of Nuwave\\Lighthouse\\Schema\\Context)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Support\\Utils.php",
          "line": 99,
          "call": "Nuwave\\Lighthouse\\GraphQL::Nuwave\\Lighthouse\\{closure}(instance of GraphQL\\Server\\OperationParams)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\GraphQL.php",
          "line": 120,
          "call": "Nuwave\\Lighthouse\\Support\\Utils::applyEach(instance of Closure, instance of GraphQL\\Server\\OperationParams)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Support\\Http\\Controllers\\GraphQLController.php",
          "line": 32,
          "call": "Nuwave\\Lighthouse\\GraphQL::executeOperationOrOperations(instance of GraphQL\\Server\\OperationParams, instance of Nuwave\\Lighthouse\\Schema\\Context)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php",
          "line": 48,
          "call": "Nuwave\\Lighthouse\\Support\\Http\\Controllers\\GraphQLController::__invoke(instance of Illuminate\\Http\\Request, instance of Nuwave\\Lighthouse\\GraphQL, instance of Illuminate\\Events\\Dispatcher, instance of Laragraph\\Utils\\RequestParser, instance of Nuwave\\Lighthouse\\Execution\\SingleResponse, instance of Nuwave\\Lighthouse\\Execution\\ContextFactory)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
          "line": 219,
          "call": "Illuminate\\Routing\\ControllerDispatcher::dispatch(instance of Illuminate\\Routing\\Route, instance of Nuwave\\Lighthouse\\Support\\Http\\Controllers\\GraphQLController, '__invoke')"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
          "line": 176,
          "call": "Illuminate\\Routing\\Route::runController()"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
          "line": 681,
          "call": "Illuminate\\Routing\\Route::run()"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 130,
          "call": "Illuminate\\Routing\\Router::Illuminate\\Routing\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Support\\Http\\Middleware\\AttemptAuthentication.php",
          "line": 34,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Nuwave\\Lighthouse\\Support\\Http\\Middleware\\AttemptAuthentication::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\nuwave\\lighthouse\\src\\Support\\Http\\Middleware\\AcceptJson.php",
          "line": 27,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Nuwave\\Lighthouse\\Support\\Http\\Middleware\\AcceptJson::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 105,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
          "line": 683,
          "call": "Illuminate\\Pipeline\\Pipeline::then(instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
          "line": 658,
          "call": "Illuminate\\Routing\\Router::runRouteWithinStack(instance of Illuminate\\Routing\\Route, instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
          "line": 624,
          "call": "Illuminate\\Routing\\Router::runRoute(instance of Illuminate\\Http\\Request, instance of Illuminate\\Routing\\Route)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Router.php",
          "line": 613,
          "call": "Illuminate\\Routing\\Router::dispatchToRoute(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
          "line": 170,
          "call": "Illuminate\\Routing\\Router::dispatch(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 130,
          "call": "Illuminate\\Foundation\\Http\\Kernel::Illuminate\\Foundation\\Http\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\barryvdh\\laravel-debugbar\\src\\Middleware\\InjectDebugbar.php",
          "line": 67,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Barryvdh\\Debugbar\\Middleware\\InjectDebugbar::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\app\\Http\\Middleware\\ChangeAppUrlMiddleware.php",
          "line": 23,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "App\\Http\\Middleware\\ChangeAppUrlMiddleware::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php",
          "line": 21,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest.php",
          "line": 21,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Illuminate\\Foundation\\Http\\Middleware\\TransformsRequest::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize.php",
          "line": 27,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Illuminate\\Foundation\\Http\\Middleware\\ValidatePostSize::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Middleware\\CheckForMaintenanceMode.php",
          "line": 63,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Illuminate\\Foundation\\Http\\Middleware\\CheckForMaintenanceMode::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\fideloper\\proxy\\src\\TrustProxies.php",
          "line": 57,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Fideloper\\Proxy\\TrustProxies::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\dingo\\api\\src\\Http\\Middleware\\Request.php",
          "line": 111,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 171,
          "call": "Dingo\\Api\\Http\\Middleware\\Request::handle(instance of Illuminate\\Http\\Request, instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Pipeline\\Pipeline.php",
          "line": 105,
          "call": "Illuminate\\Pipeline\\Pipeline::Illuminate\\Pipeline\\{closure}(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
          "line": 145,
          "call": "Illuminate\\Pipeline\\Pipeline::then(instance of Closure)"
        },
        {
          "file": "E:\\wwwroot\\object\\vendor\\laravel\\framework\\src\\Illuminate\\Foundation\\Http\\Kernel.php",
          "line": 110,
          "call": "Illuminate\\Foundation\\Http\\Kernel::sendRequestThroughRouter(instance of Illuminate\\Http\\Request)"
        },
        {
          "file": "E:\\wwwroot\\object\\public\\index.php",
          "line": 57,
          "call": "Illuminate\\Foundation\\Http\\Kernel::handle(instance of Illuminate\\Http\\Request)"
        }
      ]
    }
  ],
  "data": {
    "onlineStoreThemeAssetCreate": {
      "themeAsset": null
    }
  }
}


15、当请求响应成功时的结构。如图15
当请求响应成功时的结构。

图15




mutation {
  onlineStoreThemeAssetCreate(
    input: { themeId: "vogue", content: "string", key: "string" }
  ) {
    themeAsset {
      id
      themeId
      content
      key
      mimeType
      category
      schema
      createdAt
      updatedAt
      deletable
      renameable
      updatable
    }
  }
}





{
  "data": {
    "onlineStoreThemeAssetCreate": {
      "themeAsset": {
        "id": "653",
        "themeId": "vogue",
        "content": "string",
        "key": "string",
        "mimeType": "text/x-php",
        "category": "unknown",
        "schema": null,
        "createdAt": "2022-02-25 01:49:53",
        "updatedAt": "2022-02-25 01:49:53",
        "deletable": false,
        "renameable": false,
        "updatable": false
      }
    }
  }
}


]]>
https://www.shuijingwanwq.com/2022/03/24/6179/feed/ 0
从 REST 迁移到 GraphQL 的一些思考与实践(响应嵌套的列表结构),参考 Shopify https://www.shuijingwanwq.com/2022/03/22/6164/ https://www.shuijingwanwq.com/2022/03/22/6164/#respond Tue, 22 Mar 2022 01:19:40 +0000 https://www.shuijingwanwq.com/?p=6164 浏览量: 171 1、参考:从 REST 迁移到 GraphQL 的一些思考与实践,参考 GitHub。在左侧为模板文件的顶级目录结构。针对每一个顶级目录,需要设置其是否允许新增文件。如图1
参考:从 REST 迁移到 GraphQL 的一些思考与实践,参考 GitHub。在左侧为模板文件的顶级目录结构。针对每一个顶级目录,需要设置其是否允许新增文件。

图1

2、本计划借鉴 Shopify 的 GraphQL 的响应结构,但是其是直接响应 html 的结构,并未使用 GraphQL。如图2
本计划借鉴 Shopify 的 GraphQL 的响应结构,但是其是直接响应 html 的结构,并未使用 GraphQL

图2

3、借鉴 Shopify 的 GraphQL 的 products,其中每一个产品皆具有其自身的 图片列表。如图3
借鉴 Shopify 的 GraphQL 的 products,其中每一个产品皆具有其自身的 图片列表

图3



{
  products(first: 3) {
    edges {
      node {
        id
        title
        images(first: 3) {
          edges {
            node {
              id
              width
            }
          }
        }
      }
    }
  }
}




{
  "data": {
    "products": {
      "edges": [
        {
          "node": {
            "id": "gid://shopify/Product/6944715342030",
            "title": "commercial kitchen quality",
            "images": {
              "edges": [
                {
                  "node": {
                    "id": "gid://shopify/ProductImage/30455717036238",
                    "width": 1920
                  }
                },
                {
                  "node": {
                    "id": "gid://shopify/ProductImage/30455718838478",
                    "width": 1920
                  }
                }
              ]
            }
          }
        },
        {
          "node": {
            "id": "gid://shopify/Product/6944715636942",
            "title": "Easter Photographing Dress-up Acessories",
            "images": {
              "edges": []
            }
          }
        },
        {
          "node": {
            "id": "gid://shopify/Product/6944715702478",
            "title": "Perfect Pet Hair Remover",
            "images": {
              "edges": []
            }
          }
        }
      ]
    }
  },
  "extensions": {
    "cost": {
      "requestedQueryCost": 20,
      "actualQueryCost": 13,
      "throttleStatus": {
        "maximumAvailable": 1000,
        "currentlyAvailable": 987,
        "restoreRate": 50
      }
    }
  }
}


4、Shopify 的 products 相当于模板文件的顶级目录列表。而 每一个产品皆具有其自身的 图片列表,相当于 每一个顶级目录皆具有其自身的 模板文件列表。最终实现了类似的响应结构。如图4
Shopify 的 products 相当于模板文件的顶级目录列表。而 每一个产品皆具有其自身的 图片列表,相当于 每一个顶级目录皆具有其自身的 模板文件列表。最终实现了类似的响应结构

图4



query{
  onlineStoreTheme(themeId: "vogue"){
    ...____
  }
}

fragment ____ on OnlineStoreTheme {
  themeFirstLevelAssets {
    name
    creatable
    themeAssets {
      id
      key
    }
  }
}





{
  "data": {
    "onlineStoreTheme": {
      "themeFirstLevelAssets": [
        {
          "name": "Assets",
          "creatable": false,
          "themeAssets": [
            {
              "id": "1",
              "key": "assets/iconfont/iconfont.css"
            },
            {
              "id": "2",
              "key": "assets/iconfont/iconfont.js"
            },
            {
              "id": "3",
              "key": "assets/iconfont/iconfont.json"
            },
            {
              "id": "4",
              "key": "assets/iconfont/iconfont.woff2"
            }
          ]
        },
        {
          "name": "Auth",
          "creatable": false,
          "themeAssets": [
            {
              "id": "5",
              "key": "auth/login.blade.php"
            },
            {
              "id": "6",
              "key": "auth/register.blade.php"
            }
          ]
        },
        {
          "name": "Components",
          "creatable": false,
          "themeAssets": [
            {
              "id": "7",
              "key": "components/carousel.blade.php"
            },
            {
              "id": "8",
              "key": "components/collections.blade.php"
            },
            {
              "id": "9",
              "key": "components/combinationRecommendation.blade.php"
            },
            {
              "id": "10",
              "key": "components/css.blade.php"
            },
            {
              "id": "11",
              "key": "components/foot.blade.php"
            },
            {
              "id": "12",
              "key": "components/footInfo.blade.php"
            },
            {
              "id": "13",
              "key": "components/footMenu.blade.php"
            },
            {
              "id": "14",
              "key": "components/footSubscribe.blade.php"
            },
            {
              "id": "15",
              "key": "components/footTextImg.blade.php"
            },
            {
              "id": "16",
              "key": "components/header.blade.php"
            },
            {
              "id": "17",
              "key": "components/hotSales.blade.php"
            },
            {
              "id": "18",
              "key": "components/image.blade.php"
            },
            {
              "id": "19",
              "key": "components/imageText.blade.php"
            },
            {
              "id": "20",
              "key": "components/megaMenu.blade.php"
            },
            {
              "id": "21",
              "key": "components/menuCollapsible.blade.php"
            },
            {
              "id": "22",
              "key": "components/publicity.blade.php"
            },
            {
              "id": "23",
              "key": "components/singlecommodity.blade.php"
            },
            {
              "id": "24",
              "key": "components/text.blade.php"
            },
            {
              "id": "25",
              "key": "components/video.blade.php"
            }
          ]
        },
        {
          "name": "Config",
          "creatable": false,
          "themeAssets": [
            {
              "id": "26",
              "key": "config/settings_data.json"
            },
            {
              "id": "27",
              "key": "config/settings_schema.json"
            }
          ]
        },
        {
          "name": "Js",
          "creatable": false,
          "themeAssets": [
            {
              "id": "28",
              "key": "js/address.js"
            },
            {
              "id": "29",
              "key": "js/app.js"
            },
            {
              "id": "30",
              "key": "js/cart.js"
            },
            {
              "id": "31",
              "key": "js/collectionItem.js"
            },
            {
              "id": "32",
              "key": "js/collections.js"
            },
            {
              "id": "33",
              "key": "js/common/banner.vue"
            },
            {
              "id": "34",
              "key": "js/common/cartFreeSpTips.vue"
            },
            {
              "id": "35",
              "key": "js/common/navbar.vue"
            },
            {
              "id": "36",
              "key": "js/common/sortselector.vue"
            },
            {
              "id": "37",
              "key": "js/custom.js"
            },
            {
              "id": "38",
              "key": "js/error404.js"
            },
            {
              "id": "39",
              "key": "js/home.js"
            },
            {
              "id": "40",
              "key": "js/index.js"
            },
            {
              "id": "41",
              "key": "js/login.js"
            },
            {
              "id": "42",
              "key": "js/order.js"
            },
            {
              "id": "43",
              "key": "js/page.js"
            },
            {
              "id": "44",
              "key": "js/productDetail.js"
            },
            {
              "id": "45",
              "key": "js/register.js"
            },
            {
              "id": "46",
              "key": "js/search.js"
            },
            {
              "id": "47",
              "key": "js/view/account/accountbodyaddress.vue"
            },
            {
              "id": "48",
              "key": "js/view/account/accountbodyorder.vue"
            },
            {
              "id": "49",
              "key": "js/view/account/accounttop.vue"
            },
            {
              "id": "50",
              "key": "js/view/account/addressfrom.vue"
            },
            {
              "id": "51",
              "key": "js/view/account/index.vue"
            },
            {
              "id": "52",
              "key": "js/view/cart/index.vue"
            },
            {
              "id": "53",
              "key": "js/view/collections/card.vue"
            },
            {
              "id": "54",
              "key": "js/view/collections/index.vue"
            },
            {
              "id": "55",
              "key": "js/view/product/descriptabs.vue"
            },
            {
              "id": "56",
              "key": "js/view/product/index.vue"
            },
            {
              "id": "57",
              "key": "js/view/product/MediaFlat.vue"
            },
            {
              "id": "58",
              "key": "js/view/product/MediaLeft.vue"
            },
            {
              "id": "59",
              "key": "js/view/product/scrollSlideVertical.vue"
            },
            {
              "id": "60",
              "key": "js/view/product/slideVertical.vue"
            },
            {
              "id": "61",
              "key": "js/view/productlist/index.vue"
            },
            {
              "id": "62",
              "key": "js/view/search/index.vue"
            }
          ]
        },
        {
          "name": "Layouts",
          "creatable": false,
          "themeAssets": [
            {
              "id": "63",
              "key": "layouts/basic.blade.php"
            }
          ]
        },
        {
          "name": "Pages",
          "creatable": false,
          "themeAssets": [
            {
              "id": "64",
              "key": "pages/address.blade.php"
            },
            {
              "id": "65",
              "key": "pages/cart.blade.php"
            },
            {
              "id": "66",
              "key": "pages/collections.blade.php"
            },
            {
              "id": "67",
              "key": "pages/collectiontitem.blade.php"
            },
            {
              "id": "68",
              "key": "pages/custom.blade.php"
            },
            {
              "id": "69",
              "key": "pages/home.blade.php"
            },
            {
              "id": "70",
              "key": "pages/index.blade.php"
            },
            {
              "id": "71",
              "key": "pages/order.blade.php"
            },
            {
              "id": "72",
              "key": "pages/product_detail.blade.php"
            },
            {
              "id": "73",
              "key": "pages/search.blade.php"
            }
          ]
        },
        {
          "name": "Sass",
          "creatable": false,
          "themeAssets": [
            {
              "id": "74",
              "key": "sass/app.scss"
            },
            {
              "id": "75",
              "key": "sass/applications.scss"
            },
            {
              "id": "76",
              "key": "sass/banner.scss"
            },
            {
              "id": "77",
              "key": "sass/collectionItems.scss"
            },
            {
              "id": "78",
              "key": "sass/collections.scss"
            },
            {
              "id": "79",
              "key": "sass/components.scss"
            },
            {
              "id": "80",
              "key": "sass/fonts.scss"
            },
            {
              "id": "81",
              "key": "sass/foot.scss"
            },
            {
              "id": "82",
              "key": "sass/global.scss"
            },
            {
              "id": "83",
              "key": "sass/header.scss"
            },
            {
              "id": "84",
              "key": "sass/hotsales.scss"
            },
            {
              "id": "85",
              "key": "sass/imagetext.scss"
            },
            {
              "id": "86",
              "key": "sass/index.scss"
            },
            {
              "id": "87",
              "key": "sass/pages.scss"
            },
            {
              "id": "88",
              "key": "sass/productDetail.scss"
            },
            {
              "id": "89",
              "key": "sass/publicity.scss"
            },
            {
              "id": "90",
              "key": "sass/text.scss"
            },
            {
              "id": "91",
              "key": "sass/video.scss"
            }
          ]
        },
        {
          "name": "Theme.json",
          "creatable": false,
          "themeAssets": [
            {
              "id": "92",
              "key": "theme.json"
            }
          ]
        }
      ]
    }
  }
}


5、由于模板文件列表,仅需要 key 字段。最后调整后的响应结构。如图5
由于模板文件列表,仅需要 key 字段。最后调整后的响应结构

图5



query{
  onlineStoreTheme(themeId: "vogue"){
    ... ____
  }
}

fragment ____ on OnlineStoreTheme {
  themeAssetList {
    themeId
    name
    key
    creatable
    themeAssetKeys
  }
}





{
  "data": {
    "onlineStoreTheme": {
      "themeAssetList": [
        {
          "themeId": "vogue",
          "name": "Assets",
          "key": "assets",
          "creatable": false,
          "themeAssetKeys": [
            "assets/iconfont/iconfont.css",
            "assets/iconfont/iconfont.js",
            "assets/iconfont/iconfont.json",
            "assets/iconfont/iconfont.woff2"
          ]
        },
        {
          "themeId": "vogue",
          "name": "Auth",
          "key": "auth",
          "creatable": false,
          "themeAssetKeys": [
            "auth/login.blade.php",
            "auth/register.blade.php"
          ]
        },
        {
          "themeId": "vogue",
          "name": "Components",
          "key": "components",
          "creatable": false,
          "themeAssetKeys": [
            "components/carousel.blade.php",
            "components/collections.blade.php",
            "components/combinationRecommendation.blade.php",
            "components/css.blade.php",
            "components/foot.blade.php",
            "components/footInfo.blade.php",
            "components/footMenu.blade.php",
            "components/footSubscribe.blade.php",
            "components/footTextImg.blade.php",
            "components/header.blade.php",
            "components/hotSales.blade.php",
            "components/image.blade.php",
            "components/imageText.blade.php",
            "components/megaMenu.blade.php",
            "components/menuCollapsible.blade.php",
            "components/publicity.blade.php",
            "components/singlecommodity.blade.php",
            "components/text.blade.php",
            "components/video.blade.php"
          ]
        },
        {
          "themeId": "vogue",
          "name": "Config",
          "key": "config",
          "creatable": false,
          "themeAssetKeys": [
            "config/settings_data.json",
            "config/settings_schema.json"
          ]
        },
        {
          "themeId": "vogue",
          "name": "Js",
          "key": "js",
          "creatable": false,
          "themeAssetKeys": [
            "js/address.js",
            "js/app.js",
            "js/cart.js",
            "js/collectionItem.js",
            "js/collections.js",
            "js/common/banner.vue",
            "js/common/cartFreeSpTips.vue",
            "js/common/navbar.vue",
            "js/common/sortselector.vue",
            "js/custom.js",
            "js/error404.js",
            "js/home.js",
            "js/index.js",
            "js/login.js",
            "js/order.js",
            "js/page.js",
            "js/productDetail.js",
            "js/register.js",
            "js/search.js",
            "js/view/account/accountbodyaddress.vue",
            "js/view/account/accountbodyorder.vue",
            "js/view/account/accounttop.vue",
            "js/view/account/addressfrom.vue",
            "js/view/account/index.vue",
            "js/view/cart/index.vue",
            "js/view/collections/card.vue",
            "js/view/collections/index.vue",
            "js/view/product/descriptabs.vue",
            "js/view/product/index.vue",
            "js/view/product/MediaFlat.vue",
            "js/view/product/MediaLeft.vue",
            "js/view/product/scrollSlideVertical.vue",
            "js/view/product/slideVertical.vue",
            "js/view/productlist/index.vue",
            "js/view/search/index.vue"
          ]
        },
        {
          "themeId": "vogue",
          "name": "Layouts",
          "key": "layouts",
          "creatable": false,
          "themeAssetKeys": [
            "layouts/basic.blade.php"
          ]
        },
        {
          "themeId": "vogue",
          "name": "Pages",
          "key": "pages",
          "creatable": false,
          "themeAssetKeys": [
            "pages/address.blade.php",
            "pages/cart.blade.php",
            "pages/collections.blade.php",
            "pages/collectiontitem.blade.php",
            "pages/custom.blade.php",
            "pages/home.blade.php",
            "pages/index.blade.php",
            "pages/order.blade.php",
            "pages/product_detail.blade.php",
            "pages/search.blade.php"
          ]
        },
        {
          "themeId": "vogue",
          "name": "Sass",
          "key": "sass",
          "creatable": false,
          "themeAssetKeys": [
            "sass/app.scss",
            "sass/applications.scss",
            "sass/banner.scss",
            "sass/collectionItems.scss",
            "sass/collections.scss",
            "sass/components.scss",
            "sass/fonts.scss",
            "sass/foot.scss",
            "sass/global.scss",
            "sass/header.scss",
            "sass/hotsales.scss",
            "sass/imagetext.scss",
            "sass/index.scss",
            "sass/pages.scss",
            "sass/productDetail.scss",
            "sass/publicity.scss",
            "sass/text.scss",
            "sass/video.scss"
          ]
        },
        {
          "themeId": "vogue",
          "name": "Theme.json",
          "key": "theme.json",
          "creatable": false,
          "themeAssetKeys": [
            "theme.json"
          ]
        }
      ]
    }
  }
}


 ]]>
https://www.shuijingwanwq.com/2022/03/22/6164/feed/ 0
从 REST 迁移到 GraphQL 的一些思考与实践(关于是否允许某个操作),参考 GitHub https://www.shuijingwanwq.com/2022/02/22/5990/ https://www.shuijingwanwq.com/2022/02/22/5990/#respond Tue, 22 Feb 2022 08:56:50 +0000 https://www.shuijingwanwq.com/?p=5990 浏览量: 134 1、左侧为一个主题素材的列表,用户每点击一个素材,其操作按钮:删除文件、重命名、保存,会随着素材的变化而变化。如图1
左侧为一个主题素材的列表,用户每点击一个素材,其操作按钮:删除文件、重命名、保存,会随着素材的变化而变化

图1

2、举例如下:ajax-loader.gif,其仅允许操作:删除文件、重命名,不允许操作:保存。如图2
举例如下:ajax-loader.gif,其仅允许操作:删除文件、重命名,不允许操作:保存

图2

3、举例如下:settings_data.json,其仅允许操作:保存,不允许操作:删除文件、重命名。如图3
举例如下:settings_data.json,其仅允许操作:保存,不允许操作:删除文件、重命名

图3

4、查看 GitHub 上的获取文件的 页面的网络请求 ,查看 README.md,请求网址: https://github.com/shuijingwan/larabbs/blob/main/README.md ,其响应为 HTML 结构,但是其允许操作:编辑文件、删除文件。如图4
查看 GitHub 上的获取文件的 页面的网络请求 ,查看 README.md,请求网址: https://github.com/shuijingwan/larabbs/blob/main/README.md ,其响应为 HTML 结构,但是其允许操作:编辑文件、删除文件

图4

5、查看 GitHub 上的获取文件的 REST API,查看 README.md,请求网址: /repos/{owner}/{repo}/readme ,其响应为 JSON 结构,但是其无是否允许操作的字段。如图5
查看 GitHub 上的获取文件的 REST API,查看 README.md,请求网址: /repos/{owner}/{repo}/readme ,其响应为 JSON 结构,但是其无是否允许操作的字段

图5

6、查看 GitHub 应用程序权限,搜索:/repos/:owner/:repo/readme,其仅标注此接口是一个读取类型。并未针对基于具体的当前用户与具体的文件,而设置一些更为具体的操作权限。此属于接口内部的细分权限。如图6
查看 GitHub 应用程序权限,搜索:/repos/:owner/:repo/readme,其仅标注此接口是一个读取类型。并未针对基于具体的当前用户与具体的文件,而设置一些更为具体的操作权限。此属于接口内部的细分权限

图6

7、查看 GitHub 上的获取文件的 GraphQL API,请求:resource ,其响应为类型: UniformResourceLocatable。如图7
查看 GitHub 上的获取文件的 GraphQL API,请求:resource ,其响应为类型: UniformResourceLocatable

图7

8、查看类型: UniformResourceLocatable,其本质上为一个接口类型。查看另一个接口类型:Updatable,针对主题素材的是否允许操作,应该参考 Updatable 实现。检查当前用户是否可以更新此对象。如图8
查看类型: UniformResourceLocatable,其本质上为一个接口类型。查看另一个接口类型:Updatable,针对主题素材的是否允许操作,应该参考 Updatable 实现。检查当前用户是否可以更新此对象

图8

9、参考使用 Explorer,我计划在 Altair 中请求。新建窗口:https://api.github.com/graphql ,点击左上角的 设置Headers。如图9
参考使用 Explorer,我计划在 Altair 中请求。新建窗口:https://api.github.com/graphql ,点击左上角的 设置Headers

图9

10、在 Key(密钥)字段中,输入 Authorization。 在 Value(值)字段中,输入 Bearer <token>,其中 <token> 是您生成的 OAuth 令牌。如图10
在 Key(密钥)字段中,输入 Authorization。 在 Value(值)字段中,输入 Bearer <token>,其中 <token> 是您生成的 OAuth 令牌。

图10

11、通过自我查询来测试您的访问,测试通过。如图11
通过自我查询来测试您的访问,测试通过

图11

请求


query {
  viewer {
    login
  }
}


响应


{
  "data": {
    "viewer": {
      "login": "shuijingwan"
    }
  }
}


12、查询资源:https://github.com/shuijingwan/larabbs/blob/main/README.md ,响应 null,无是否允许更新操作的响应字段。如图12
查询资源:https://github.com/shuijingwan/larabbs/blob/main/README.md ,响应 null,无是否允许更新操作的响应字段

图12

13、查询用户下的项目编号为 1 ,分别获取单个项目与项目列表,viewerCanUpdate 。如图13
查询用户下的项目编号为 1 ,分别获取单个项目与项目列表,viewerCanUpdate

图13

请求


query{
  user(login: "shuijingwan"){
    project(number: 1){
      viewerCanUpdate
    }
    ...projects
    
  }
}
fragment projects on User{
  projects(first:1){
    edges{
      cursor
      node{
        viewerCanUpdate
      }
    }
    totalCount
  }
}


响应


{
  "data": {
    "user": {
      "project": {
        "viewerCanUpdate": true
      },
      "projects": {
        "edges": [
          {
            "cursor": "Y3Vyc29yOnYyOpHOANZ4WQ==",
            "node": {
              "viewerCanUpdate": true
            }
          }
        ],
        "totalCount": 1
      }
    }
  }
}


14、最终决定实现 类型 themeAsset(key:String!),在响应字段中添加:deletable、renameable、updatable。如图14
最终决定实现 类型 themeAsset(key:String!),在响应字段中添加:deletable、renameable、updatable

图14

请求


query{
  onlineStoreTheme(themeId: "vogue"){
    ...themeAsset
  }
}

fragment themeAsset on OnlineStoreTheme {
  themeAsset(key: "assets/iconfont/iconfont.css") {
    id
    themeId
    version
    content
    key
    mimeType
    category
    schema
    createdAt
    updatedAt
    deletable
    renameable
    updatable
  }
}



响应


{
  "data": {
    "onlineStoreTheme": {
      "themeAsset": {
        "id": "1",
        "themeId": "vogue",
        "version": "",
        "content": "@font-face {\n  font-family: \"iconfont\"; /* Project id 2455235 */\n  src: \n       url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAABYEAAsAAAAAKLQAABWzAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACJKAq4OK4wATYCJAOBOAteAAQgBYRnB4RmG4oiVUaFjQMgJtY9yf7/ltwYMLADtO5ZMuKULZpDHcrFl0IDW6IxHg4JbghPcEf2kD51nuu0V7UaaKOf2HZxoYusNuLC9nYp/zveKgujqbTNUEqI+rXPfrD7iUDdXYB9FKCMAmIVo1DIyCgsDyQnpE5FpXZ4fpu9T6QgaZAfBAkLQRv0awMGiB8r5mGtjVpgLIxFwyJCd+mCRbOr1rW70l01UzlTbcLS4UQQX4B9IQKtdASGIuuz2/52WxaYQwZZbpO0PQgQCAr4+PXxawA0QACBeRAYXruE0vwcPc2x0wkZG42diCIofPrU2nJg+nybrdSq73lpT8tvtdeWCWyArCR7EM4i4Pq5XpsUiD/wloUhV1Un9yWFvCQHKeffFfKLKb8issNc8VLMFYBcHYLwSLrjK3WNrXC6qlMk8dmj2AGrRekx7VjOuomsKKkYMaATI1NCRF+3PRMUEGRuM70zO68Y0I/VtS8EPBY2tQLeqTrW3FgHSK/VsYqsCr/bxe7T+kEb44B+XG8Ynpyfv/qbxZEAGscAdlS7j2Y1AM+M+inOoWC+BZfXB+xYAw5ggDGxGvqauqG6ALBXOj4rQdHthFMmAFhRZ8mfMHPPPDPfzI8QTE1DR88sn90S3TZ53dtuy7//vX3myHPrbUCgmSsX/2blEfNRWZOZyrSV59agZ6xixXYyAz47ax4FvCVrEcVllpdd+d/zABMlZR0GS1yUtpSuIC1pflwUmp6GpqS6ooqqrL6BoZG8mjAxImTkFIz5COCQEALlAug4DKDHbssJBNqLXMUAoATBAsoQHKADwcuoVALIgEoCQBxCBkRBqIA2hA5IQfwBXQgLEAThAVoQPiANEcjoUIUAcEHEAAUCAzSIGtCDRAEaEA2gCdEBkpBkmfyqHgBFiElGn2oGQBWSD8hCbIA+pBgwgKAyNFQ7AEaQJYA8pFvGBHUTAMIgrwNiIG8DIiC3ABnIbUAO8geggJ9BGuOz+jYAfPArExCA34iGA+ocABJYrztCAEyP2zwE4CzA/zSE8R0Bah+4iAFvkmsad6mz9xmsSHFfBdellC4VeDRe4ENakbgkt+UShFAwQc38gznBzOqCy6X5cQMIXFqziFyigC7H0mhkpWY5YLJpfhyIjOVyk5NZ3OD1kd4M43FlOHKQcBrECqYHEwQEFhZLGW3rC1EEAj8SpFZVDzd8/giBXyQsrtoo1owZExKq63oPq/Cg8uEhWTkC3zUQf/CQgXQiHj6oExNLRw6b2KLCoSMWjdcg6HnKQ9UgyIMV389DZNfdgG/CiKJC1T5fV0xiM0NQmUqqBUxN0B+k3i7rFVLxNY2qcJUW0EBDsVShdyook0LA7Naz5f6yngMWFbPPzghQU1fK124bNAxqSShyL+sQXtZRt1T0xGJFD+n/hMiURE/0ELYfEROMxBY1mBFIj9cRW+hcPbUP+xYRn/s4sAqxWNJTF3nvTDcGwP/04wilF6plCD966wxD1xUP+srdViIK9UClgjq8K1f3unJpV6C6L5PvGthv6j0/85B7UO8ipmlJgWpZQrmkfBvVle8EGy63ENEUfUXhtzj6JsuO9CCq+y/5Cs5gUtUqCI/VtZ4XfZbIeqtvBByFb9tyWaLlLi+/rfisucbSvovjJUb20rJ+w2XoBVwxeGVSgN8a2v0qcR9M92hQN8NvIr65dQLqTdZ1W5FNnC66vcvwxMSf/N0D229/vvXm0rvuwszkY59OOT7hR29X/7ZbX+y4s+JBsOTotJOfNSUuTu+T+xTWjfOXFy835JQcAHhSMCl9pXFQHYRcEOstK1wKZWFWZj4pu68TMWagToVos8ox+yhxS7eI6L2uXwvR9yJ3ah3kQLhAMTEAfqcxH8fMvI3yHVqhkhZ71+rIoNIGC5skHjOJniqUIvyY8cqJCHsRJKtG743XvD4w7JowKIpFXOGoYAjCArtbFsOAZfquMrdCxgxeSHBOKCObJevFuUL53AzGBYM3AG5Ihc2wts4emxnL5vn8WD6RCSX/2PAmS3ixyLJ5m8/P7BlGSEiGwolmQwp/8e6T6aIlBYH64G0hy6Dxe0VntmlKllRZsyZiGLNAeUZyhj1emk1gAzkNeJzVOcqMJlJM5UFKpbIdl56ELa6UzoVv7fIOPYP6Q7MzIXdyZl1hVjJkcHSOe/Cp3Le0XkGkk+PSVwB2WJaIZkqc2SzshE3HhJ6T6XmWADy1wTQBZwgclax0GhxH+28hcdaspJDkKMCiCbC05LSUNnUY03UBGah30ZLlWAaFsqHysitSqHJvUomjBp9A88qrZmUm+JTJMKluqJMvWe2EsG/wFh/HbgSga/rorvXl6J4HB6LvdKqpmWydUptALNGRwXGw3TAKOSMLm2yN8joG1JJaraY3WmrfI0/RM/oCP5f7/LSR3ZuQYR8aAhSuMlGh7jSg063vE9dlbnmwVdTOScQhhvg+0GJLGJsVohE9U+6PM2gdFWdJrovYeMMNxSWh20D3tN/UwsEmVqNQHTKPWVdmiRUFct2oMBI9oqjajEgjIyKZLei9tVo0okWiWnO33NzT1ar0aj2K3AWbVNgiy6qiqhDKsBOrNw4EFD7a5CH56l6XKI83+6Dt5JJC9Uu77tla3lDpeh6folf1vDpy9GYZzEs1TXGm11Sr8X47P6waUs3EY0xr9ZAqJhZn+tZHYmujkXXx6IaYVkPyCQF6gjEoqYFkOW/U0lLaURRzgNdlWU8Weml/OYsydh8ZcHL4aGWwO2bXSQvJBC9nzrI1sJYy05KpIY6TE6lQdkKQUaZXOE5dRwgZOrQJb1aplTuzNDPPRxSzmNcTSQEOywZxK/VmVcdmk6Ur6lYCZ9Be15V9P5inMemnINjV77mut80LAn+977suTF7LLvTbZtv/Qu8XpThQPP8hB9jeEACH4YZxwzmV1a28bh3kEwE8sYqpdi0Xd7psnzU4ClU+FNviSRoL2OxIP+C0Lqbu3aHfCvY8WEDWX17M+enXQUEocOf9qScWPNv3egnhiSU/b7gSbLu1rtRYWtOYmWmvpHfmJOba3WR2zDlxck7csrhq+ozRM2TjPi2iD4vPaSh22Ai5f/kn/POcSuDNKv+Izsy07yqkj/+eQVmhfOvOnArAOfF89wrc18rc/0VUbyNObybTh2jTbRlEhHag4JsDxr7/YKwj+OOPzI81ty7PMGqwrXbbH38sxqmVHz2ST31FgjL/r2xq7Ai3fvFFa7ij8ak2vjX8xRfh1saOp4JX6leoadm09A/HNLUpy2rOZ33H5jvfFQxT66kMXtfZTG5KAYCxzqYsfbpBmMFXr2b8QfnjbRgePnqE6C1NCruuXv0CBs5aWG7LvIRdCz+4ds0r866/yQN4LebygQO/lj7JkrqEqvj4qoS6p0LjwT7sp+/WRDbPUaItjo4u1pZ4hV52cXSJ1uswhHk88iR5r7COtQfHbhhdXOCQ0Z367YdlsTOL9YBpj4lbvXW0LbMzJ6ldYF60ImgbstPOsJ82iwd+VzTlOF5mhJuP/TNWaGxlpaTL0UC9C6cxw5Py17xzG2KDCEfzwjNqi8Lb49tgx3wbSVacmtnCvDtMKtbPQkequ0mopIt4VB+WWWuN6FL83maBL6NM9JQbMd2Xl6ZmlNRHrVpl2IrTDCgjOmuKbOHmowTUhZxWX9iHhTBc7UUyZSXGhlX1UTQ9LnppQkj1g5DKhN+WZgtcGfyleKW06q60Uolf2izMBZmCEMJSXv5RlZgZZMpNC+DlHUZf0FwzSDnY5ZwjCSdwZRgrCuYBE7YRryLNbeT2/sXt2ThHUjXijNgGCAXETSQ8zpEzoOcvB1dHcQHfaynNBkPTckMzsixvMuArNUWwlF5lpkKRaVVkZCqtGcoRCFiUmRmKfwYD5iKC8w7/f0ee+lvIaGTsPbgUs/TgXobRCH2rDsUqVN8Bo5EpcGAC36kUmLq1lya/8jNhD1hAqoIdMGP9vjp52YM18aypRTzU9pxRFLGCgyYMN9kWxqQGFiQl9bMtcJ+Mk7p6pCSU7G6HjO5ZjrOTpHucCQIOwf6+PDCWNL81ab07cWzBgrFE9/qk1s8NGtuZtK51/rqkvKtIm6a0RNMBlke1lZW1Ra1ojyot1bSnAlRtVOXkqIxhInalROz24Hjd4uhuv5Y/AlaFTZVNHx5NUtLAyEil3ZKko6Oz6lk6PSlJfQEyyEc/pOBQchYpi4wC3wnk1AAA4iU1CVVxcVUJtU8Saoa3Kq67nr5zNSlqgxHZ+DqbxhIZadHYrmuKsoUlyqa5DpIVGLyNi7zoeSYA/j8RFXYikP3y9f9174f4vklui1PFxx2Q8W/uy7+7GzDvp2vv8ZVLtNskjN8G0v9MQ2+Gij85WH33IRAOptxAhTAbynqjfByM8w+9/u/TnAEK3vrRDIPE+HDfBL5UXajxV0FBDEyuLD6GU/tWpH4vQ18lsfT1WSR/cyzf7tZIkJrsRTbXBgJlH2955beEhk86wfeqS7sCLhAm7n3/4N9+f3mE4c0Z444oy2R9daAsdrDoenbHt6yIUHw3gfHtJtqaWItkqgvOaSfTOzvaSzPyw7f7q59VBvmxhiTmk1MWSWEJHl+rvNh2eZDDPM4ik2SsNHbsj/ca/H8gFh1qu02D9ttxeBJRnQ0wWN1Xm3+ECHJ2BjuWgCH9fYGHIZj+bct1Ij2BIBCTaPGfaCs2cz6kcPEIEc9Bxi83MRbyt31Oj0k2PlcxsEOfnXrVEUrGXw+h4slCffj5eMrrVF0h1iiySPr6JBZpUUV4zxZwZTV1LmCOOnQc7+B0KM0r7rYAAqKqNXWqznTHYsahXp0XwvJWQFfyUOdI58j681Nenc5OLssLN6rDTOHmiUB7jWGdjr+XEcapTwek/Ly32Y8Nnwu8T15ImeJOUcpF5+oB6Tuy+eNE4ul58zAAkL4KqhhSDy8ug1ILv6vQhrCyWTnVnlTa64+KgoiNvSHS7D6SDkB+DtUb4KU268p0dru2LOZDofFgV5pFjmqVdoVtrLeUlykpGig0G5cvN24X2msuhINMSVnwWE+oTWl/GJNiaGrUL0tMT9/1A9QAoq1nZm7O9p8hzl47cG1iluRVTs18Nkuc3nSsFzP4zzM8h1sI9+ijxy6tk5vZurexSlEuHx2Vlyuq3hUagX36VYp3Bb9C+m/T0NCLv15QhoD4UO3gYmS+Ia01fSF2wG8QWZyyoBVZmNGPoTWUy+2yNWtkJaHlDQOvzgqn2RDE/ufmRx/eBAkDMTQpz2Wxsm0XT8obbmy05FrMMGCxUy4zP4j45Xxi4l9oPSyVePqOXo7Zjt2aIpu+qxKBBMnXZEXX/yyBrPDcjAicG8R9op6K8XmSe+7d7/HLPWWe9wSu3eJ74GWpWY4UPEfkO7T08wI5siPiKO/lu0YiryI2N7t2ceLztq3Lt7b99nizam5nvIo/zOC//3w/I6q1aTQ2LXof6GXbNKj2fuJiL2K9egW66rdFS4eH3QKdwL1kyWefuQRSAVCIZrJn3nJVu6qugi9EL7NfvrWjegc39Ta+eyfr+3ACIMZKGPFvSes8cmbLzpPe9x8MjU5cdk927v/1x+Urt0BnXl+39dTjr0fm/jYVdKa1gLJSjtD9VPHELVJGup8onrojLkOciETGSMU/4ZUsoe8WWZk/dXeliEevwEerueHCYM4F4Xhk1P8sR9CKfP/Uj3Mp3LcDmzijH6qQQFG+faG4c/Uah9MB4q/3ujauPX7r67dQDP40bGn4xbuaSndf/mqYDPhbj407OLNsx/YD2w454FmO48AFpgRysMFbX/GGkCOIhmCGqoIG2Ba4u7udPFCCrt5ALJE2C5u4BaYsWmuLej/7lDcVed1ve2HVPlpVbL5Vt1WKv4sde3hjtf8xFuRmbuZNTIoPUa3rzWdBzdX9nv0GR3Kyw1Dr9DiBB+wXX/XU0qSAsHJJqcwuzchGEFFVcEF5eO9nY4W0JVPe+Gy43FGsLdJsEF7UKzQe7M4dm1rm1dVnphVIKpULmmsUlaEb1ykqlNXvA4FAdFGjyLW+2LOnDXd8+541bWv2gMTx6nO5YznDzoJB63Hca8Rifm5gbhx3KROX/WPOr/v3lGbzFt8uND1ziFBtfQDvxIpcRJvNt8DVOF8cc3xja4CwRvW7N4Cl6urL1cCFzoXn+4AXVUxmZDA/eJ9x4xbnIqlW5VDVMtzXr07l4z7qfPvSWt5wdtbwwvfHm43OpIufwsfO7EwdaZiycmycWXRXSm9745tiHyflzHWY8+xLiM+HHig4T78SAN/K2cCaMOjpa9Q3JCU16Bt/BxrPtBv1vye03tQaSPmlCI46bSUI9+l0B1aufOXbn1mGza1/lXFucASaXiztPCBsP2QF5laylSEHb9pBIb4BqzAzYTyPa9L+hf3WL1F9hz3BJHr6hcgLW8OUQNhjB4jFMkXIehjTE7bDH1p3ts+4OgC8leS6REwcb5V/9CWOea5iwDwuCkte62Op57AfHEIL82EHYFsM+89irO8s5heo6HUfxfEm3ZLlc2rX0cLtb9VO9ocd8n1ou8p30AZXARif85RD2Ld+oaYnTHXVPU3cEReTqQM1of43i/qnY5Pv7aW5gjkkHymnMX70/84FuzIoh1yZj42LIH9W2Aim+3ulz30FPF9fon7iBsoZGDwfHgWqnHG47ahoRMS0eBA3NRAMxoBnivWTfpjqSqErPYt1pXiUxkeJGlHKJrWQchjlEGJJXUi5KSrIyK79h0g5BRZlyQC67kKUIu5llCbKK2og5RtqIeXHKIesn9SFlP+jguxRNGchuj3uMzKCYmPczpAXDnLreV2/MdYeclqzf8yFoolZRGrirb+QMRdhU9ZxqRpMyDKYT//osO/FjFla9DpNquPrLMohspl6GQ7eMoIe4zbG7UonLxyGrdGX7xtj7SEj+X89F1dw1t0spnME/6Uwqp9Eui7ruFRHD2ZPs9NqMJ+vGLR/aSFmFJst+i/fNOUwx9dZMKyAVab6XcMoU0S/6y+7lyVUQUVDx8DEwsbBxcMnICSKkzTLi7Kqm7brh3Gal3Xbj/O6n/f7JZdZpCSpK2owVId8vk/AHdBVrMCOutjmdEdR6pmTABwvDswOd1JP9uoiEmBf5bShFdXzJNUSTuqSuCjEDMNlj0EhZ9mcFamlyrXcbAlYc6ahFsJPQRrZ8Gmq4ugUIWs6GZDrQ96b1azZkgzgk+2J0S5JJJ5gU33C44x8X04l2pXqpy2wInpcozmPtwS6tCUlWZlLz6FzbACZ1JPdyDXZxmXbENQXQE5Dv6jn1wlHjHxDjxRGqsGgx8Se6KELnbWOFB299iXogwC9zR3YYz+JnngZdxclvQp0qzxgOwPwWVMtT6/TqjRZQIodiRUzFr3XHOogwgE8OpHuNNaIC6JuSKXMhSOQ1W/S4RIAAAA=') format('woff2');\n}\n\n.iconfont {\n  font-family: \"iconfont\" !important;\n  font-size: 16px;\n  font-style: normal;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-shouqidefuben:before {\n  content: \"\\e61f\";\n}\n\n.icon-zhankai:before {\n  content: \"\\e620\";\n}\n\n.icon-guanbiicon:before {\n  content: \"\\e605\";\n}\n\n.icon-yigou:before {\n  content: \"\\e606\";\n}\n\n.icon-bofang:before {\n  content: \"\\e940\";\n}\n\n.icon-fanyeyou:before {\n  content: \"\\e61d\";\n}\n\n.icon-you1:before {\n  content: \"\\e7b9\";\n}\n\n.icon-fanyezuo:before {\n  content: \"\\e61c\";\n}\n\n.icon-diqiu:before {\n  content: \"\\e844\";\n}\n\n.icon-youtube:before {\n  content: \"\\e612\";\n}\n\n.icon-instagram:before {\n  content: \"\\f1d3\";\n}\n\n.icon-leftarrow:before {\n  content: \"\\e691\";\n}\n\n.icon-sousuo:before {\n  content: \"\\e6b9\";\n}\n\n.icon-youjiantou1:before {\n  content: \"\\e609\";\n}\n\n.icon-sanjiaodown:before {\n  content: \"\\e6b5\";\n}\n\n.icon-huobi:before {\n  content: \"\\e608\";\n}\n\n.icon-earth:before {\n  content: \"\\e9ee\";\n}\n\n.icon-menu:before {\n  content: \"\\e633\";\n}\n\n.icon-LC_icon_search_line_2:before {\n  content: \"\\e60e\";\n}\n\n.icon-gouwuche:before {\n  content: \"\\e61b\";\n}\n\n.icon-ren:before {\n  content: \"\\e6c5\";\n}\n\n.icon-youjiantoufuben:before {\n  content: \"\\e64d\";\n}\n\n.icon-bofang1:before {\n  content: \"\\e6c6\";\n}\n\n.icon-you:before {\n  content: \"\\e671\";\n}\n\n.icon-zuo:before {\n  content: \"\\e6f7\";\n}\n\n.icon-xia:before {\n  content: \"\\e63c\";\n}\n\n.icon-youjiantou2:before {\n  content: \"\\e602\";\n}\n\n.icon-zuojiantou:before {\n  content: \"\\e603\";\n}\n\n.icon-jingdiananli_wujiaoxing_shoucanghou:before {\n  content: \"\\e64a\";\n}\n\n.icon-jingdiananli_kongwujiaoxing_shoucang:before {\n  content: \"\\e64b\";\n}\n\n.icon-icontouxiang:before {\n  content: \"\\e678\";\n}\n\n.icon-gou:before {\n  content: \"\\e63b\";\n}\n\n.icon-web__bitebiyoujiantou:before {\n  content: \"\\e62f\";\n}\n\n.icon-web__bitebizuojiantou:before {\n  content: \"\\e630\";\n}\n\n.icon-cancel-1-copy:before {\n  content: \"\\e61a\";\n}\n\n.icon-shangxia:before {\n  content: \"\\e60b\";\n}\n\n.icon-gouwucheman:before {\n  content: \"\\e604\";\n}\n\n.icon-duigou:before {\n  content: \"\\e60f\";\n}\n\n.icon-xiajiantou1:before {\n  content: \"\\e62a\";\n}\n\n.icon-_pinterest:before {\n  content: \"\\e653\";\n}\n\n.icon-icomoonfacebook:before {\n  content: \"\\e654\";\n}\n\n.icon-guge1:before {\n  content: \"\\e655\";\n}\n\n.icon-twitter:before {\n  content: \"\\e656\";\n}\n\n.icon-biaoqian:before {\n  content: \"\\e718\";\n}\n\n.icon-2:before {\n  content: \"\\e601\";\n}\n\n",
        "key": "assets/iconfont/iconfont.css",
        "mimeType": "text/plain",
        "category": "unknown",
        "schema": null,
        "createdAt": "2022-02-15 09:59:41",
        "updatedAt": "2022-02-15 09:59:41",
        "deletable": false,
        "renameable": false,
        "updatable": false
      }
    }
  }
}


 ]]>
https://www.shuijingwanwq.com/2022/02/22/5990/feed/ 0
在 Yii 2.0 中创建了一个新的数据库迁移对象:添加 4 个字段的最佳实践 https://www.shuijingwanwq.com/2020/10/14/4567/ https://www.shuijingwanwq.com/2020/10/14/4567/#respond Wed, 14 Oct 2020 08:15:21 +0000 https://www.shuijingwanwq.com/?p=4567 浏览量: 119 1、表名:cpa_channel_app_source 设计,表前缀:cpa_,如图1
表名:cpa_channel_app_source 设计,表前缀:cpa_

图1

2、查看命令:yii migrate/create 的帮助文档
<pre class="wp-block-syntaxhighlighter-code">

PS E:\wwwroot\channel-pub-api> ./yii help migrate/create

DESCRIPTION
                    "
Creates a new migration.

This command creates a new migration using the available migration template.
After using this command, developers should modify the created migration
skeleton by filling up the actual migration logic.

yii migrate/create create_user_table

In order to generate a namespaced migration, you should specify a namespace before the migration's name.
Note that backslash (\) is usually considered a special character in the shell, so you need to escape it
properly to avoid shell errors or incorrect behavior.
For example:

yii migrate/create 'app\\migrations\\createUserTable'

In case [[migrationPath]] is not set and no namespace is provided, the first entry of [[migrationNamespaces]] will be used.


USAGE

yii migrate/create <name> [...options...]

- name (required): string
  the name of the new migration. This should only contain
  letters, digits, underscores and/or backslashes.

  Note: If the migration name is of a special form, for example create_xxx or
  drop_xxx, then the generated migration file will contain extra code,
  in this case for creating/dropping tables.


OPTIONS

--appconfig: string
  custom application configuration file path.
  If not set, default application configuration is used.

--color: boolean, 0 or 1
  whether to enable ANSI color in the output.
  If not set, ANSI color will only be enabled for terminals that support it.

--comment, -C: string (defaults to '')
  the comment for the table being created.

--compact, -c: boolean, 0 or 1 (defaults to 0)
  indicates whether the console output should be compacted.
  If this is set to true, the individual commands ran within the migration will not be output to the console.
  Default is false, in other words the output is fully verbose by default.

--db: Connection|array|string (defaults to 'db')
  the DB connection object or the application component ID of the DB connection to use
  when applying migrations. Starting from version 2.0.3, this can also be a configuration array
  for creating the object.

--fields, -f: array
  column definition strings used for creating migration code.

  The format of each definition is `COLUMN_NAME:COLUMN_TYPE:COLUMN_DECORATOR`. Delimiter is `,`.
  For example, `--fields="name:string(12):notNull:unique"`
  produces a string column of size 12 which is not null and unique values.

  Note: primary key is added automatically and is named id by default.
  If you want to use another name you may specify it explicitly like
  `--fields="id_key:primaryKey,name:string(12):notNull:unique"`

--help, -h: boolean, 0 or 1
  whether to display help information about current command.

--interactive: boolean, 0 or 1 (defaults to 1)
  whether to run the command interactively.

--migration-namespaces: array
  list of namespaces containing the migration classes.

  Migration namespaces should be resolvable as a [path alias](guide:concept-aliases) if prefixed with `@`, e.g. if you specify
  the namespace `app\migrations`, the code `Yii::getAlias('@app/migrations')` should be able to return
  the file path to the directory this namespace refers to.
  This corresponds with the [autoloading conventions](guide:concept-autoloading) of Yii.

  For example:

  ```php
  [
      'app\migrations',
      'some\extension\migrations',
  ]
  ```

--migration-path, -p: string|array
  the directory containing the migration classes. This can be either
  a [path alias](guide:concept-aliases) or a directory path.

  Migration classes located at this path should be declared without a namespace.
  Use [[migrationNamespaces]] property in case you are using namespaced migrations.

  If you have set up [[migrationNamespaces]], you may set this field to `null` in order
  to disable usage of migrations that are not namespaced.

  Since version 2.0.12 you may also specify an array of migration paths that should be searched for
  migrations to load. This is mainly useful to support old extensions that provide migrations
  without namespace and to adopt the new feature of namespaced migrations while keeping existing migrations.

  In general, to load migrations from different locations, [[migrationNamespaces]] is the preferable solution
  as the migration name contains the origin of the migration in the history, which is not the case when
  using multiple migration paths.

--migration-table, -t: string (defaults to '{{%migration}}')
  the name of the table for keeping applied migration information.

--template-file, -F:  (defaults to '@yii/views/migration.php')

--use-table-prefix, -P: boolean, 0 or 1 (defaults to 1)
  indicates whether the table names generated should consider
  the `tablePrefix` setting of the DB connection. For example, if the table
  name is `post` the generator wil return `{{%post}}`.


</pre>
3、添加字段,如果迁移的名称遵循 add_xxx_to_yyy 这样的格式,生成的类文件将会包含必要的 addColumn 和 dropColumn。你可以像如下这样指定多个字段:yii migrate/create add_xxx_column_yyy_column_to_zzz_table –fields=”xxx:integer,yyy:text”。如图2
添加字段,如果迁移的名称遵循 add_xxx_to_yyy 这样的格式,生成的类文件将会包含必要的 addColumn 和 dropColumn。你可以像如下这样指定多个字段:yii migrate/create add_xxx_column_yyy_column_to_zzz_table --fields="xxx:integer,yyy:text"。

图2



PS E:\wwwroot\channel-pub-api> ./yii migrate/create add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table --fields="group_name:string(64):notNull:defaultValue(''),name:string(32):notNull:defaultValue(''),avatar:string:notNull:defaultValue(''),fans_count:integer:notNull:defaultValue(0)"
Yii Migration Tool (based on Yii v2.0.35)

Create new migration 'E:\wwwroot\channel-pub-api\console/migrations\m201014_055436_add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table.php'? (yes|no) [no]:yes
New migration created successfully.


4、生成 PHP 类文件:m201014_055436_add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table.php。
<pre class="wp-block-syntaxhighlighter-code">

<?php

use yii\db\Migration;

/**
 * Handles adding columns to table `{{%channel_app_source}}`.
 */
class m201014_055436_add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table extends Migration
{
    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        $this->addColumn('{{%channel_app_source}}', 'group_name', $this->string(64)->notNull()->defaultValue(''));
        $this->addColumn('{{%channel_app_source}}', 'name', $this->string(32)->notNull()->defaultValue(''));
        $this->addColumn('{{%channel_app_source}}', 'avatar', $this->string()->notNull()->defaultValue(''));
        $this->addColumn('{{%channel_app_source}}', 'fans_count', $this->integer()->notNull()->defaultValue(0));
    }

    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        $this->dropColumn('{{%channel_app_source}}', 'group_name');
        $this->dropColumn('{{%channel_app_source}}', 'name');
        $this->dropColumn('{{%channel_app_source}}', 'avatar');
        $this->dropColumn('{{%channel_app_source}}', 'fans_count');
    }
}


</pre>
5、编辑 PHP 类文件:m201014_055436_add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table.php。以添加其他的字段选项:字段注释、字段顺序。
<pre class="wp-block-syntaxhighlighter-code">

<?php

use yii\db\Migration;

/**
 * Handles adding columns to table `{{%channel_app_source}}`.
 */
class m201014_055436_add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table extends Migration
{
    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        $this->addColumn('{{%channel_app_source}}', 'group_name', $this->string(64)->notNull()->defaultValue('')->comment('租户名称')->after('group_id'));
        $this->addColumn('{{%channel_app_source}}', 'name', $this->string(32)->notNull()->defaultValue('')->comment('名称')->after('channel_type_code'));
        $this->addColumn('{{%channel_app_source}}', 'avatar', $this->string()->notNull()->defaultValue('')->comment('头像')->after('name'));
        $this->addColumn('{{%channel_app_source}}', 'fans_count', $this->integer()->notNull()->defaultValue(0)->comment('粉丝数')->after('avatar'));
    }

    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        $this->dropColumn('{{%channel_app_source}}', 'group_name');
        $this->dropColumn('{{%channel_app_source}}', 'name');
        $this->dropColumn('{{%channel_app_source}}', 'avatar');
        $this->dropColumn('{{%channel_app_source}}', 'fans_count');
    }
}


</pre>
6、提交迁移,为了将数据库升级到最新的结构,你应该使用如下命令来提交所有新的迁移。如图3
提交迁移,为了将数据库升级到最新的结构,你应该使用如下命令来提交所有新的迁移。

图3



PS E:\wwwroot\channel-pub-api> ./yii migrate
Yii Migration Tool (based on Yii v2.0.35)

Total 1 new migration to be applied:
        m201014_055436_add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table

Apply the above migration? (yes|no) [no]:yes
*** applying m201014_055436_add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table
    > add column group_name string(64) NOT NULL DEFAULT '' COMMENT '租户名称' AFTER `group_id` to table {{%channel_app_source}} ... done (time: 0.108s)
    > add column name string(32) NOT NULL DEFAULT '' COMMENT '名称' AFTER `channel_type_code` to table {{%channel_app_source}} ... done (time: 0.097s)
    > add column avatar string NOT NULL DEFAULT '' COMMENT '头像' AFTER `name` to table {{%channel_app_source}} ... done (time: 0.170s)
    > add column fans_count integer NOT NULL DEFAULT 0 COMMENT '粉丝数' AFTER `avatar` to table {{%channel_app_source}} ... done (time: 0.158s)
*** applied m201014_055436_add_group_name_column_name_column_avatar_column_fans_count_column_to_channel_app_source_table (time: 0.551s)


1 migration was applied.

Migrated up successfully.


7、查看最新的表设计,符合预期,如图4
查看最新的表设计,符合预期

图4

 ]]>
https://www.shuijingwanwq.com/2020/10/14/4567/feed/ 0
在 Yii 2.0 中执行数据库迁移:添加 MySQL 字段的 MEDIUMTEXT 类型 https://www.shuijingwanwq.com/2020/01/02/3793/ https://www.shuijingwanwq.com/2020/01/02/3793/#respond Thu, 02 Jan 2020 09:37:20 +0000 https://www.shuijingwanwq.com/?p=3793 浏览量: 145

1、在 Yii 2.0 中执行数据库迁移:MySQL 的 MEDIUMTEXT 类型。四个 TEXT 类型是 TINYTEXT、TEXT、MEDIUMTEXT、LONGTEXT。仅支持:TEXT


    // The following are the supported abstract column data types.
    const TYPE_PK = 'pk';
    const TYPE_UPK = 'upk';
    const TYPE_BIGPK = 'bigpk';
    const TYPE_UBIGPK = 'ubigpk';
    const TYPE_CHAR = 'char';
    const TYPE_STRING = 'string';
    const TYPE_TEXT = 'text';
    const TYPE_TINYINT = 'tinyint';
    const TYPE_SMALLINT = 'smallint';
    const TYPE_INTEGER = 'integer';
    const TYPE_BIGINT = 'bigint';
    const TYPE_FLOAT = 'float';
    const TYPE_DOUBLE = 'double';
    const TYPE_DECIMAL = 'decimal';
    const TYPE_DATETIME = 'datetime';
    const TYPE_TIMESTAMP = 'timestamp';
    const TYPE_TIME = 'time';
    const TYPE_DATE = 'date';
    const TYPE_BINARY = 'binary';
    const TYPE_BOOLEAN = 'boolean';
    const TYPE_MONEY = 'money';
    const TYPE_JSON = 'json';


2、参考文件:\vendor\yiisoft\yii2\db\SchemaBuilderTrait.php,复制至 \console\db\mysql\SchemaBuilderTrait.php,参考 TEXT 类型的实现,添加对 TINYTEXT、MEDIUMTEXT、LONGTEXT 的支持

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
 
namespace console\db\mysql;
 
use yii\base\NotSupportedException;
use yii\db\Connection;
use yii\db\ColumnSchemaBuilder;
 
/**
 * SchemaBuilderTrait contains shortcut methods to create instances of [[ColumnSchemaBuilder]].
 *
 * These can be used in database migrations to define database schema types using a PHP interface.
 * This is useful to define a schema in a DBMS independent way so that the application may run on
 * different DBMS the same way.
 *
 * For example you may use the following code inside your migration files:
 *
 * ```php
 * $this->createTable('example_table', [
 *   'id' => $this->primaryKey(),
 *   'name' => $this->string(64)->notNull(),
 *   'type' => $this->integer()->notNull()->defaultValue(10),
 *   'description' => $this->text(),
 *   'rule_name' => $this->string(64),
 *   'data' => $this->text(),
 *   'created_at' => $this->datetime()->notNull(),
 *   'updated_at' => $this->datetime(),
 * ]);
 * ```
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
trait SchemaBuilderTrait
{
    /**
     * @return Connection the database connection to be used for schema building.
     */
    abstract protected function getDb();
 
    /**
     * Creates a tinytext column.
     * @return ColumnSchemaBuilder the column instance which can be further customized.
     * @throws NotSupportedException if there is no support for the current driver type
     * @since 2.0.6
     */
    public function tinyText()
    {
        return $this->getDb()->getSchema()->createColumnSchemaBuilder('tinytext');
    }
 
    /**
     * Creates a mediumtext column.
     * @return ColumnSchemaBuilder the column instance which can be further customized.
     * @throws NotSupportedException if there is no support for the current driver type
     * @since 2.0.6
     */
    public function mediumText()
    {
        return $this->getDb()->getSchema()->createColumnSchemaBuilder('mediumtext');
    }
 
    /**
     * Creates a longtext column.
     * @return ColumnSchemaBuilder the column instance which can be further customized.
     * @throws NotSupportedException if there is no support for the current driver type
     * @since 2.0.6
     */
    public function longText()
    {
        return $this->getDb()->getSchema()->createColumnSchemaBuilder('longtext');
    }
}
 

3、在数据库迁移文件中,引用新建的 Trait,以添加字段类型:MEDIUMTEXT

<?php
 
use yii\base\NotSupportedException;
use yii\db\Migration;
use console\db\mysql\SchemaBuilderTrait;
 
/**
 * Class m200102_030708_pre_pub_log
 */
class m200102_030708_pre_pub_log extends Migration
{
    use SchemaBuilderTrait;
 
    /**
     * {@inheritdoc}
     * @throws NotSupportedException if there is no support for the current driver type
     */
    public function safeUp()
    {
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            $tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB COMMENT="预发布日志"';
        }
 
        $this->createTable('{{%pre_pub_log}}', [
            'id' => $this->primaryKey(),
            'pub_log_message' => $this->mediumText()->notNull()->comment('发布日志说明'),
        ], $tableOptions);
    }
 
    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        echo "m200102_030708_pre_pub_log cannot be reverted.\n";
 
        return false;
    }
 
    /*
    // Use up()/down() to run migration code without a transaction.
    public function up()
    {
 
    }
 
    public function down()
    {
        echo "m200102_030708_pre_pub_log cannot be reverted.\n";
 
        return false;
    }
    */
}
 

4、执行数据库迁移,生成表:预发布日志,查看字段:pub_log_message,其类型为:mediumtext,符合预期,如图1

执行数据库迁移,生成表:预发布日志,查看字段:pub_log_message,其类型为:mediumtext,符合预期

图1


PS E:\wwwroot\channel-pub-api-feature-success-error-tasks> ./yii migrate
Yii Migration Tool (based on Yii v2.0.30)

Total 1 new migration to be applied:
        m200102_030708_pre_pub_log

Apply the above migration? (yes|no) [no]:yes
*** applying m200102_030708_pre_pub_log
    > create table {{%pre_pub_log}} ... done (time: 0.280s)
*** applied m200102_030708_pre_pub_log (time: 0.394s)


1 migration was applied.

Migrated up successfully.


]]>
https://www.shuijingwanwq.com/2020/01/02/3793/feed/ 0
在 Yii 2.0 中,编辑与更新租户的任务类型与步骤设置(多条记录、树形结构)的实现 (二) https://www.shuijingwanwq.com/2019/11/14/3605/ https://www.shuijingwanwq.com/2019/11/14/3605/#comments Thu, 14 Nov 2019 02:09:24 +0000 http://www.shuijingwanwq.com/?p=3605 浏览量: 78

1、编辑与更新租户的任务类型与步骤设置的需求有所变更,需要让用户可自主添加任务类型,然后从 18 个步骤中选择其中一些加入至新添加的任务类型中。上一版本的实现:https://www.shuijingwanwq.com/2019/11/06/3583/ ,在上一版本中,仅允许用户从现有的任务类型与步骤中删除或者取消勾选,但是不能够新增加任务类型。

2、在全局的任务步骤配置表中,再添加一个 手动任务 下的步骤(上传素材),后续每个租户第 1 次进入编辑页面时,默认包含 5 个任务类型,每个任务类型下的任务步骤全部为勾选状态。此时,用户可自主添加任务类型,然后从 18 个步骤中选择其中一些加入至新添加的任务类型中。任务类型可(添加、删除、重命名其名称、排序),不可(勾选、不勾选)。任务步骤可从 18 个任务步骤列表中选择(添加、删除、勾选、不勾选、排序)。

3、新建数据库迁移文件(\console\migrations\m191108_073439_config_task_steps.php),新建步骤配置表,初始化步骤配置表的记录,删除租户的任务配置表、删除租户的任务步骤配置表

<?php
 
use yii\db\Migration;
 
/**
 * Class m191108_073439_config_task_steps
 */
class m191108_073439_config_task_steps extends Migration
{
    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            $tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB COMMENT="步骤配置"';
        }
        $this->createTable('{{%config_step}}', [
            'id' => $this->primaryKey(),
            'group_id' => $this->string(32)->notNull()->comment('租户ID'),
            'code' => $this->string(32)->notNull()->comment('代码'),
            'name' => $this->string(32)->notNull()->comment('名称'),
            'category' => $this->smallInteger(1)->notNull()->defaultValue(1)->comment('类型,1:系统内置;2:用户定义'),
            'status' => $this->smallInteger()->notNull()->defaultValue(1)->comment('状态,0:禁用;1:启用'),
            'is_not_isolated' => $this->smallInteger()->notNull()->defaultValue(0)->comment('是否跨租户(不隔离),0:否;1:是'),
            'is_deleted' => $this->smallInteger()->notNull()->defaultValue(0)->comment('是否被删除,0:否;1:是'),
            'created_at' => $this->integer()->notNull()->defaultValue(0)->comment('创建时间'),
            'updated_at' => $this->integer()->notNull()->defaultValue(0)->comment('更新时间'),
            'deleted_at' => $this->integer()->notNull()->defaultValue(0)->comment('删除时间'),
        ], $tableOptions);
 
        $this->createIndex('idx_code', '{{%config_step}}', ['code'], $unique = false);
 
        $this->insert('{{%config_step}}', [
            'id' => 1,
            'group_id' => '',
            'code' => 'retrieval',
            'name' => '取稿',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 2,
            'group_id' => '',
            'code' => 'begin_shot',
            'name' => '上传素材',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 3,
            'group_id' => '',
            'code' => 'material_review',
            'name' => '素材审核',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 4,
            'group_id' => '',
            'code' => 'writing',
            'name' => '撰写稿件',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 5,
            'group_id' => '',
            'code' => 'end_shot',
            'name' => '拍摄完成',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 1,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => time(),
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 6,
            'group_id' => '',
            'code' => 'commit_doc',
            'name' => '提交稿件',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 1,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => time(),
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 7,
            'group_id' => '',
            'code' => 'manuscript_review',
            'name' => '稿件审核',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 8,
            'group_id' => '',
            'code' => 'writing_draft',
            'name' => '撰写通稿',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 9,
            'group_id' => '',
            'code' => 'newspaper_issued',
            'name' => '报纸签发',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 10,
            'group_id' => '',
            'code' => 'video_editing',
            'name' => '视频编辑',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 11,
            'group_id' => '',
            'code' => 'tandem_list',
            'name' => '串联单',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 12,
            'group_id' => '',
            'code' => 'wechat_group_reference',
            'name' => '微信组稿引用',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 13,
            'group_id' => '',
            'code' => 'finish_interview',
            'name' => '结束采访',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 14,
            'group_id' => '',
            'code' => 'program_render',
            'name' => '节目上传',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 15,
            'group_id' => '',
            'code' => 'website_release',
            'name' => '网站发布',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 16,
            'group_id' => '',
            'code' => 'program_authen',
            'name' => '节目审查',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 17,
            'group_id' => '',
            'code' => 'app_release',
            'name' => 'APP发布',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 18,
            'group_id' => '',
            'code' => 'wechat_release',
            'name' => '微信发布',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 19,
            'group_id' => '',
            'code' => 'blog_release',
            'name' => '微博发布',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
        $this->insert('{{%config_step}}', [
            'id' => 20,
            'group_id' => '',
            'code' => 'qq_release',
            'name' => '企鹅号发布',
            'category' => 1,
            'status' => 1,
            'is_not_isolated' => 0,
            'is_deleted' => 0,
            'created_at' => time(),
            'updated_at' => time(),
            'deleted_at' => 0,
        ]);
 
        $this->alterColumn('{{%config_task_step}}', 'config_task_id', $this->integer()->notNull()->comment('任务配置ID'));
        $this->addColumn('{{%config_task_step}}', 'config_task_code', $this->string(32)->notNull()->comment('任务配置代码')->after('config_task_id'));
        $this->addColumn('{{%config_task_step}}', 'config_step_id', $this->integer()->notNull()->comment('步骤配置ID')->after('config_task_code'));
        $this->update('{{%config_task_step}}', ['config_task_code' => 'tv_broadcast'], ['config_task_id' => 1]);
        $this->update('{{%config_task_step}}', ['config_task_code' => 'inter_view'], ['config_task_id' => 2]);
        $this->update('{{%config_task_step}}', ['config_task_code' => 'rich_doc'], ['config_task_id' => 3]);
        $this->update('{{%config_task_step}}', ['config_step_id' => 1], ['step_code' => 'retrieval']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 2], ['step_code' => 'begin_shot']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 3], ['step_code' => 'material_review']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 4], ['step_code' => 'writing']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 5, 'status' => 1], ['step_code' => 'end_shot']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 6, 'is_default' => 1, 'is_deleted' => 1, 'deleted_at' => time()], ['step_code' => 'commit_doc']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 7], ['step_code' => 'manuscript_review']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 8], ['step_code' => 'writing_draft']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 9], ['step_code' => 'newspaper_issued']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 10], ['step_code' => 'video_editing']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 11], ['step_code' => 'tandem_list']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 12], ['step_code' => 'wechat_group_reference']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 13], ['step_code' => 'finish_interview']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 14], ['step_code' => 'program_render']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 15], ['step_code' => 'website_release']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 16], ['step_code' => 'program_authen']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 17], ['step_code' => 'app_release']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 18], ['step_code' => 'wechat_release']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 19], ['step_code' => 'blog_release']);
        $this->update('{{%config_task_step}}', ['config_step_id' => 20], ['step_code' => 'qq_release']);
 
        $this->dropTable('{{%config_group_task}}');
        $this->dropTable('{{%config_group_task_step}}');
 
        $this->insert('{{%config_task_step}}', [
            'id' => 27,
            'group_id' => '',
            'config_task_id' => 4,
            'config_task_code' => 'other',
            'config_step_id' => 2,
            'step_code' => 'begin_shot',
            'step_name' => '上传素材',
            'sort_order' => 1,
            'is_default' => 1,
            'up_status_type' => 2,
            'status' => 1,
            'created_at' => time(),
            'updated_at' => time()
        ]);
 
        $this->dropIndex('uc_group_id_config_task_id_step_code_is_deleted_deleted_at', '{{%config_task_step}}');
        $this->createIndex('uc_group_id_config_task_code_step_code_is_deleted_deleted_at', '{{%config_task_step}}', ['group_id', 'config_task_code', 'step_code', 'is_deleted', 'deleted_at'], $unique = true);
    }
 
    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        echo "m191108_073439_config_task_steps cannot be reverted.\n";
 
        return false;
    }
 
    /*
    // Use up()/down() to run migration code without a transaction.
    public function up()
    {
 
    }
 
    public function down()
    {
        echo "m191108_073439_config_task_steps cannot be reverted.\n";
 
        return false;
    }
    */
}
 
 

4、打开表:任务配置,如图1

打开表:任务配置

图1

5、打开表:步骤配置,如图2

打开表:步骤配置

图2

6、打开表:任务步骤配置,如图3

打开表:任务步骤配置

图3

7、编辑路由配置文件,\api\config\urlManager.php


        // 基础设置 - 步骤配置
        [
            'class' => 'yii\rest\UrlRule',
            'controller' => ['v1/config-step'],
            'only' => ['index'],
        ],
        // 基础设置 - 租户设置 - 租户的任务类型与步骤设置
        [
            'class' => 'yii\rest\UrlRule',
            'controller' => ['v1/config-task-step'],
            'only' => ['view', 'edit', 'update'],
            'tokens' => ['{id}' => ''],
            'extraPatterns' => [
                'GET edit/{id}' => 'edit',
            ],
        ],
        // 移动端 - 获取租户的任务类型与步骤设置详情
        [
            'class' => 'yii\rest\UrlRule',
            'controller' => ['v1/mobile/config-task-step'],
            'only' => ['view'],
            'tokens' => ['{id}' => ''],
            'extraPatterns' => [
            ],
        ],


8、编辑控制器文件,\api\controllers\ConfigTaskStepController.php

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2018/04/04
 * Time: 15:35
 */
 
namespace api\controllers;
 
use yii\rest\ActiveController;
 
/**
 * Class ConfigTaskStepController
 * @package api\controllers
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class ConfigTaskStepController extends ActiveController
{
    /**
     * @inheritdoc
     */
    public function actions()
    {
        $actions = parent::actions();
        // 禁用"index"、"create"、"delete"、"options"动作
        unset($actions['index'], $actions['create'], $actions['delete'], $actions['options']);
        $actions['view']['class'] = 'api\rests\config_task_step\ViewAction';
        $actions['update']['class'] = 'api\rests\config_task_step\UpdateAction';
        $actions['edit'] = [
            'class' => 'api\rests\config_task_step\EditAction',
            'modelClass' => $this->modelClass,
            'checkAccess' => [$this, 'checkAccess'],
        ];
        return $actions;
    }
}
 

9、新建编辑租户的任务类型与步骤设置的方法文件(\api\rests\config_task_step\EditAction.php)

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
 
namespace api\rests\config_task_step;
 
use Yii;
use api\models\redis\cmc_console\User as RedisCmcConsoleUser;
use api\services\ConfigTaskStepService;
use yii\web\UnprocessableEntityHttpException;
 
/**
 * 编辑租户的任务类型与步骤设置:/config-task-steps/edit/my-group-id(config-task-step/edit)
 *
 * For more details and usage information on ViewAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class EditAction extends Action
{
    /**
     * Displays a model.
     * @param string $id the primary key of the model.
     * @return array
     * @throws UnprocessableEntityHttpException
     */
    public function run($id)
    {
        // 当前用户的身份实例,未认证用户则为 Null
        /* @var $identity RedisCmcConsoleUser */
        $identity = Yii::$app->user->identity;
 
        // 比对:group_id,检查其值是否等于:my-group-id
        if ($id != 'my-group-id') {
            throw new UnprocessableEntityHttpException(Yii::t('error', '226053'), 226053);
        }
 
        // 基于租户ID返回数据模型(任务步骤配置)列表
        $configTaskStepItems = ConfigTaskStepService::findModelsByGroupId($identity->group_id, false);
 
        return ['code' => 10000, 'message' => Yii::t('success', '126038'), 'data' => array_values($configTaskStepItems)];
    }
}
 
 

10、编辑租户的任务类型的服务文件(\common\services\ConfigTaskService.php)

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/11/01
 * Time: 10:19
 */
 
namespace common\services;
 
use Yii;
use common\logics\ConfigTask;
 
class ConfigTaskService extends Service
{
    /**
     * 基于租户ID返回数据模型列表
     * @param string $groupId 租户ID
     * @return array 一个 ActiveRecord 实例数组
     * 格式如下:
     * [
     *     [ // object
     *         'id' => 1, // ID
     *         'group_id' => '', // 租户ID
     *         'code' => 'tv_broadcast', // 任务代码
     *         'name' => '电视播出', // 任务名称
     *         'sort_order' => 1, // 顺序,顺序排列
     *         'category' => 1, // 任务类型,1:系统内置;2:用户定义
     *         'parameter' => '', // 自定义参数,序列化存储
     *         'status' => 1, // 状态,0:禁用;1:启用
     *         'is_deleted' => 0, // 是否被删除,0:否;1:是
     *         'created_at' => 1573439809, // 创建时间
     *         'updated_at' => 1573439809, // 更新时间
     *         'deleted_at' => 0, // 删除时间
     *     ],
     *     [
     *         'id' => 1,
     *         'group_id' => '',
     *         'code' => 'inter_view',
     *         'name' => '采访任务',
     *         'sort_order' => 2,
     *         'category' => 1,
     *         'parameter' => '',
     *         'status' => 1,
     *         'is_deleted' => 0,
     *         'created_at' => 1573439809,
     *         'updated_at' => 1573439809,
     *         'deleted_at' => 0,
     *     ],
     *     ...
     * ]
     */
    public static function findModelsByGroupId($groupId)
    {
        // 基于租户ID查找资源(任务配置)列表
        $configTaskItems = ConfigTask::find()->where(['group_id' => $groupId])->all();
 
        if (empty($configTaskItems)) {
            $groupId = '';
        }
 
        // 基于租户ID查找资源(任务配置、是否被删除:否)列表
        return ConfigTask::findAllByGroupId($groupId);
    }
}
 

11、编辑租户的任务类型与步骤设置的服务文件(\common\services\ConfigTaskStepService.php)

<?php
/**
 * Created by PhpStorm.
 * User: terryhong
 * Date: 2018/12/20
 * Time: 3:10 PM
 */
 
namespace common\services;
 
use Yii;
use common\logics\ConfigTask;
use common\logics\ConfigTaskStep;
use yii\web\NotFoundHttpException;
use yii\web\UnprocessableEntityHttpException;
 
class ConfigTaskStepService extends Service
{
    /**
     * 基于租户ID返回数据模型(任务步骤配置)列表
     * @param string $groupId 租户ID
     * @param bool $isDefaultYes 是否添加条件,默认步骤,1:是
     * @return array 一个 ActiveRecord 实例数组
     * 格式如下:
     * [
     *     1 = > [
     *         'id' => 1, // ID
     *         'code' => 'tv_broadcast', // 代码
     *         'name' => '电视播出', // 名称
     *         'sort_order' => 1, // 顺序,顺序排列
     *         'steps' => [ // 步骤列表
     *             [
     *                 'id' => 2, // ID
     *                 'code' => 'begin_shot', // 代码
     *                 'name' => '上传素材', // 名称
     *                 'sort_order' => 1, // 顺序,顺序排列
     *                 'is_default' => 1, // 是否默认步骤,1:是;0:否
     *             ],
     *             [
     *                 'id' => 4, // ID
     *                 'code' => 'writing', // 代码
     *                 'name' => '撰写稿件', // 名称
     *                 'sort_order' => 2, // 顺序,顺序排列
     *                 'is_default' => 1, // 是否默认步骤,1:是;0:否
     *             ],
     *             ...
     *         ],
     *     ],
     *     ...
     * ]
     */
    public static function findModelsByGroupId($groupId, $isDefaultYes = true)
    {
        // 基于租户ID查找资源(任务步骤配置)列表
        $configTaskStepItems = ConfigTaskStep::find()->where(['group_id' => $groupId])->all();
 
        if (empty($configTaskStepItems)) {
            $groupId = '';
        }
 
        // 基于租户ID查找资源(任务步骤配置、是否被删除:否)列表
        if ($isDefaultYes) {
            $configTaskStepIsDeletedNoItems = ConfigTaskStep::findAllIsDefaultYesByGroupId($groupId);
        } else {
            $configTaskStepIsDeletedNoItems = ConfigTaskStep::findAllByGroupId($groupId);
        }
 
        // 基于 任务配置ID 分组
        $configTaskStepGroupItems = [];
        foreach ($configTaskStepIsDeletedNoItems as $configTaskStepIsDeletedNoItem) {
            $configTaskStepGroupItems[$configTaskStepIsDeletedNoItem->config_task_id][] = [
                'id' => $configTaskStepIsDeletedNoItem->config_step_id,
                'code' => $configTaskStepIsDeletedNoItem->step_code,
                'name' => $configTaskStepIsDeletedNoItem->step_name,
                'sort_order' => $configTaskStepIsDeletedNoItem->sort_order,
                'is_default' => $configTaskStepIsDeletedNoItem->is_default,
            ];
        }
 
        // 基于租户ID返回数据模型(任务配置)列表
        $configTaskItems = ConfigTaskService::findModelsByGroupId($groupId);
 
        $data = [];
        foreach ($configTaskItems as $configTaskItem) {
            $data[$configTaskItem->id] = [
                'id' => $configTaskItem->id,
                'code' => $configTaskItem->code,
                'name' => $configTaskItem->name,
                'sort_order' => $configTaskItem->sort_order,
                'steps' => isset($configTaskStepGroupItems[$configTaskItem->id]) ? $configTaskStepGroupItems[$configTaskItem->id] : [],
            ];
        }
 
        return $data;
    }
 
 

12、在 Postman 中 GET:http://api.pcs-api.localhost/v1/config-task-steps/edit/my-group-id ,当租户未自定义时,即 任务配置表、任务步骤配置表 中无当前租户ID的记录时,默认获取 任务配置表、任务步骤配置表 中的租户ID为空的记录,响应数据与执行的 SQL 如下


{
    "code": 10000,
    "message": "编辑租户的任务类型与步骤设置成功",
    "data": {
        "items": [
            {
                "id": 1,
                "code": "tv_broadcast",
                "name": "电视播出",
                "sort_order": 1,
                "steps": [
                    {
                        "id": 2,
                        "code": "begin_shot",
                        "name": "上传素材",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 4,
                        "code": "writing",
                        "name": "撰写稿件",
                        "sort_order": 2,
                        "is_default": 1
                    },
                    {
                        "id": 7,
                        "code": "manuscript_review",
                        "name": "稿件审核",
                        "sort_order": 4,
                        "is_default": 1
                    },
                    {
                        "id": 10,
                        "code": "video_editing",
                        "name": "视频编辑",
                        "sort_order": 5,
                        "is_default": 1
                    },
                    {
                        "id": 11,
                        "code": "tandem_list",
                        "name": "串联单",
                        "sort_order": 6,
                        "is_default": 1
                    },
                    {
                        "id": 14,
                        "code": "program_render",
                        "name": "节目上传",
                        "sort_order": 7,
                        "is_default": 1
                    },
                    {
                        "id": 16,
                        "code": "program_authen",
                        "name": "节目审查",
                        "sort_order": 8,
                        "is_default": 1
                    }
                ]
            },
            {
                "id": 2,
                "code": "inter_view",
                "name": "采访任务",
                "sort_order": 2,
                "steps": [
                    {
                        "id": 2,
                        "code": "begin_shot",
                        "name": "上传素材",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 3,
                        "code": "material_review",
                        "name": "素材审核",
                        "sort_order": 2,
                        "is_default": 1
                    },
                    {
                        "id": 4,
                        "code": "writing",
                        "name": "撰写稿件",
                        "sort_order": 4,
                        "is_default": 1
                    },
                    {
                        "id": 8,
                        "code": "writing_draft",
                        "name": "撰写通稿",
                        "sort_order": 5,
                        "is_default": 1
                    },
                    {
                        "id": 7,
                        "code": "manuscript_review",
                        "name": "稿件审核",
                        "sort_order": 6,
                        "is_default": 1
                    },
                    {
                        "id": 13,
                        "code": "finish_interview",
                        "name": "结束采访",
                        "sort_order": 7,
                        "is_default": 1
                    }
                ]
            },
            {
                "id": 3,
                "code": "rich_doc",
                "name": "互联网发布",
                "sort_order": 3,
                "steps": [
                    {
                        "id": 1,
                        "code": "retrieval",
                        "name": "取稿",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 4,
                        "code": "writing",
                        "name": "撰写稿件",
                        "sort_order": 2,
                        "is_default": 1
                    },
                    {
                        "id": 7,
                        "code": "manuscript_review",
                        "name": "稿件审核",
                        "sort_order": 4,
                        "is_default": 1
                    },
                    {
                        "id": 9,
                        "code": "newspaper_issued",
                        "name": "报纸签发",
                        "sort_order": 5,
                        "is_default": 1
                    },
                    {
                        "id": 12,
                        "code": "wechat_group_reference",
                        "name": "微信组稿引用",
                        "sort_order": 6,
                        "is_default": 1
                    },
                    {
                        "id": 15,
                        "code": "website_release",
                        "name": "网站发布",
                        "sort_order": 7,
                        "is_default": 1
                    },
                    {
                        "id": 17,
                        "code": "app_release",
                        "name": "APP发布",
                        "sort_order": 8,
                        "is_default": 1
                    },
                    {
                        "id": 18,
                        "code": "wechat_release",
                        "name": "微信发布",
                        "sort_order": 9,
                        "is_default": 1
                    },
                    {
                        "id": 19,
                        "code": "blog_release",
                        "name": "微博发布",
                        "sort_order": 10,
                        "is_default": 1
                    },
                    {
                        "id": 20,
                        "code": "qq_release",
                        "name": "企鹅号发布",
                        "sort_order": 11,
                        "is_default": 1
                    }
                ]
            },
            {
                "id": 4,
                "code": "other",
                "name": "手动任务",
                "sort_order": 4,
                "steps": [
                    {
                        "id": 2,
                        "code": "begin_shot",
                        "name": "上传素材",
                        "sort_order": 1,
                        "is_default": 1
                    }
                ]
            }
        ]
    }
}



SELECT * FROM `pa_config_task_step` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC

SELECT * FROM `pa_config_task` WHERE `group_id`=''

SELECT * FROM `pa_config_task` WHERE (`group_id`='') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC


13、新建获取步骤配置列表的方法文件,\api\rests\config_step\IndexAction.php

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
 
namespace api\rests\config_step;
 
use Yii;
use api\models\ConfigStep;
use yii\base\InvalidConfigException;
use yii\data\ActiveDataProvider;
use yii\web\UnprocessableEntityHttpException;
 
/**
 * 获取步骤配置列表:/config-steps(config-step/index)
 *
 * For more details and usage information on IndexAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class IndexAction extends \yii\rest\IndexAction
{
    /**
     * Prepares the data provider that should return the requested collection of the models.
     * @return mixed|object|ActiveDataProvider
     * @throws InvalidConfigException if the configuration is invalid.
     * @throws UnprocessableEntityHttpException
     */
    protected function prepareDataProvider()
    {
        $requestParams = Yii::$app->getRequest()->getBodyParams();
        if (empty($requestParams)) {
            $requestParams = Yii::$app->getRequest()->getQueryParams();
        }
 
        $filter = null;
        if ($this->dataFilter !== null) {
            $this->dataFilter = Yii::createObject($this->dataFilter);
            if ($this->dataFilter->load($requestParams)) {
                $filter = $this->dataFilter->build();
                if ($filter === false) {
                    $firstError = '';
                    foreach ($this->dataFilter->getFirstErrors() as $message) {
                        $firstError = $message;
                        break;
                    }
                    throw new UnprocessableEntityHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '224003'), ['first_error' => $firstError])), 224003);
                }
            }
        }
 
        if ($this->prepareDataProvider !== null) {
            return call_user_func($this->prepareDataProvider, $this, $filter);
        }
 
        /* @var $modelClass ConfigStep */
        $modelClass = $this->modelClass;
 
        // 查询步骤配置
        $query = $modelClass::find()->isDeletedNo();
 
        // 设置每页资源数量为资源总数
        $count = $query->count();
        $requestParams['per-page'] = $count;
 
        return Yii::createObject([
            'class' => ActiveDataProvider::className(),
            'query' => $query,
            'pagination' => [
                'params' => $requestParams,
                'pageSizeLimit' => [1, $count],
            ],
            'sort' => [
                'params' => $requestParams,
            ],
        ]);
    }
}
 

14、新建获取步骤配置列表的序列化文件,\api\rests\config_step\Serializer.php

<?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
 
namespace api\rests\config_step;
 
use Yii;
use yii\data\DataProviderInterface;
 
/**
 * Serializer converts resource objects and collections into array representation.
 *
 * Serializer is mainly used by REST controllers to convert different objects into array representation
 * so that they can be further turned into different formats, such as JSON, XML, by response formatters.
 *
 * The default implementation handles resources as [[Model]] objects and collections as objects
 * implementing [[DataProviderInterface]]. You may override [[serialize()]] to handle more types.
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class Serializer extends \yii\rest\Serializer
{
    /**
     * Serializes a data provider.
     * @param DataProviderInterface $dataProvider
     * @return array the array representation of the data provider.
     */
    protected function serializeDataProvider($dataProvider)
    {
        if ($this->preserveKeys) {
            $models = $dataProvider->getModels();
        } else {
            $models = array_values($dataProvider->getModels());
        }
        $models = $this->serializeModels($models);
 
        if (($pagination = $dataProvider->getPagination()) !== false) {
            $this->addPaginationHeaders($pagination);
        }
 
        if ($this->request->getIsHead()) {
            return null;
        } elseif ($this->collectionEnvelope === null) {
            return $models;
        }
 
        $result = [
            $this->collectionEnvelope => $models,
        ];
 
        if ($pagination !== false) {
            return ['code' => 10000, 'message' => Yii::t('success', '126040'), 'data' => array_merge($result, $this->serializePagination($pagination))];
        }
 
        return ['code' => 10000, 'message' => Yii::t('success', '126040'), 'data' => $result];
    }
}
 

15、在 Postman 中 GET:http://api.pcs-api.localhost/v1/config-steps ,获取步骤配置列表,以便于用户选择步骤添加至对应的类型中,响应数据与执行的 SQL 如下


{
    "code": 10000,
    "message": "获取步骤配置列表成功",
    "data": {
        "items": [
            {
                "id": 1,
                "group_id": "",
                "code": "retrieval",
                "name": "取稿",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 2,
                "group_id": "",
                "code": "begin_shot",
                "name": "上传素材",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 3,
                "group_id": "",
                "code": "material_review",
                "name": "素材审核",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 4,
                "group_id": "",
                "code": "writing",
                "name": "撰写稿件",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 7,
                "group_id": "",
                "code": "manuscript_review",
                "name": "稿件审核",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 8,
                "group_id": "",
                "code": "writing_draft",
                "name": "撰写通稿",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 9,
                "group_id": "",
                "code": "newspaper_issued",
                "name": "报纸签发",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 10,
                "group_id": "",
                "code": "video_editing",
                "name": "视频编辑",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 11,
                "group_id": "",
                "code": "tandem_list",
                "name": "串联单",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 12,
                "group_id": "",
                "code": "wechat_group_reference",
                "name": "微信组稿引用",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 13,
                "group_id": "",
                "code": "finish_interview",
                "name": "结束采访",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 14,
                "group_id": "",
                "code": "program_render",
                "name": "节目上传",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 15,
                "group_id": "",
                "code": "website_release",
                "name": "网站发布",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 16,
                "group_id": "",
                "code": "program_authen",
                "name": "节目审查",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 17,
                "group_id": "",
                "code": "app_release",
                "name": "APP发布",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 18,
                "group_id": "",
                "code": "wechat_release",
                "name": "微信发布",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 19,
                "group_id": "",
                "code": "blog_release",
                "name": "微博发布",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            },
            {
                "id": 20,
                "group_id": "",
                "code": "qq_release",
                "name": "企鹅号发布",
                "category": 1,
                "status": 1,
                "is_not_isolated": 0,
                "is_deleted": 0,
                "created_at": 1573439819,
                "updated_at": 1573439819,
                "deleted_at": 0
            }
        ],
        "_links": {
            "self": {
                "href": "http://api.pcs-api.localhost/v1/config-steps?login_id=2e368664c41b8bf511bcc9c65d86dbc3&login_tid=549e02e6b704dbb437618029a711f7c8&per-page=18&page=1"
            }
        },
        "_meta": {
            "totalCount": 18,
            "pageCount": 1,
            "currentPage": 1,
            "perPage": 18
        }
    }
}



SELECT COUNT(*) FROM `pa_config_step` WHERE `is_deleted`=0

SELECT COUNT(*) FROM `pa_config_step` WHERE `is_deleted`=0

SELECT * FROM `pa_config_step` WHERE `is_deleted`=0 LIMIT 18


16、新建更新任务类型与步骤设置的方法文件(\api\rests\config_task_step\UpdateAction.php)

?php
/**
 * @link http://www.yiiframework.com/
 * @copyright Copyright (c) 2008 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
 
namespace api\rests\config_task_step;
 
use Yii;
use api\models\ConfigTask;
use api\models\ConfigTaskStep;
use api\models\redis\cmc_console\User as RedisCmcConsoleUser;
use api\services\ConfigTaskStepService;
use yii\base\Model;
use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper;
use yii\web\UnprocessableEntityHttpException;
use yii\web\ServerErrorHttpException;
 
/**
 * 更新租户的任务类型与步骤设置:/config-task-steps/{group_id}(config-task-step/update)
 *
 * For more details and usage information on CreateAction, see the [guide article on rest controllers](guide:rest-controllers).
 *
 * @author Qiang Wang <shuijingwanwq@163.com>
 * @since 1.0
 */
class UpdateAction extends Action
{
    /**
     * @var string the scenario to be assigned to the model before it is validated and updated.
     * $id {config_column_id}
     */
    public $scenario = Model::SCENARIO_DEFAULT;
 
    /**
     * Updates an existing model.
     * @param string $id the primary key of the model.
     * @return array the model being updated
     * @throws InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
     * @throws ServerErrorHttpException if there is any error when updating the model
     * @throws \Throwable
     */
    public function run($id)
    {
        // 当前用户的身份实例,未认证用户则为 Null
        /* @var $identity RedisCmcConsoleUser */
        $identity = Yii::$app->user->identity;
 
        // 比对:group_id,检查其值是否等于:my-group-id
        if ($id != 'my-group-id') {
            throw new UnprocessableEntityHttpException(Yii::t('error', '226053'), 226053);
        }
 
        /* @var $modelClass ConfigTaskStep */
        $modelClass = $this->modelClass;
 
        $requestParams = Yii::$app->getRequest()->getBodyParams();
 
        /* @var $configGroupTaskItem ConfigTask */
        // 基于租户ID查找资源(任务配置、是否被删除:否)列表
        $configTaskIsDeletedNoItems = ConfigTask::find()->where(['group_id' => $identity->group_id])->isDeletedNo()->indexBy(['code'])->all();
        // 基于租户ID查找资源(任务配置)列表
        $configTaskItems = ConfigTask::find()->where(['group_id' => $identity->group_id])->indexBy(['code'])->all();
 
        /* @var $configGroupTaskStepItem ConfigTaskStep */
        // 基于租户ID查找资源(任务步骤配置、是否被删除:否)列表
        $configTaskStepIsDeletedNoItems = $modelClass::find()->where(['group_id' => $identity->group_id])->isDeletedNo()->indexBy(function ($row) { return $row['config_task_code'] . ':' . $row['step_code']; })->all();
        // 基于租户ID查找资源(任务步骤配置)列表
        $configTaskStepItems = $modelClass::find()->where(['group_id' => $identity->group_id])->indexBy(function ($row) { return $row['config_task_code'] . ':' . $row['step_code']; })->all();
 
        if (!is_array($requestParams)) {
            return ['code' => 226010, 'message' => Yii::t('error', '226010')];
        }
 
        $configTasks = [];
        $configTaskSteps = [];
        foreach ($requestParams as $configTask) {
            // 检查 任务配置的(代码、名称、顺序) 是否存在
            if (!ArrayHelper::keyExists('code', $configTask) || !ArrayHelper::keyExists('name', $configTask) || !ArrayHelper::keyExists('sort_order', $configTask)) {
                throw new UnprocessableEntityHttpException(Yii::t('error', '226080'), 226080);
            }
 
            $configTasks[$configTask['code']] = [
                'group_id' => $identity->group_id,
                'code' => $configTask['code'],
                'name' => $configTask['name'],
                'sort_order' => $configTask['sort_order'],
            ];
 
            // 检查 步骤配置列表 是否存在
            if (ArrayHelper::keyExists('steps', $configTask)) {
                foreach ($configTask['steps'] as $configTaskStep) {
                    // 检查 步骤代码、顺序、是否默认步骤 是否存在
                    if (!ArrayHelper::keyExists('code', $configTaskStep) || !ArrayHelper::keyExists('sort_order', $configTaskStep) || !ArrayHelper::keyExists('is_default', $configTaskStep)) {
                        throw new UnprocessableEntityHttpException(Yii::t('error', '226080'), 226080);
                    }
 
                    $configTaskSteps[$configTask['code'] . ':' . $configTaskStep['code']] = [
                        'group_id' => $identity->group_id,
                        'config_task_code' => $configTask['code'],
                        'step_code' => $configTaskStep['code'],
                        'sort_order' => $configTaskStep['sort_order'],
                        'is_default' => $configTaskStep['is_default'],
                    ];
                }
 
            }
        }
 
        /* 任务配置 */
        // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 任务配置模型(MySQL) 中但是未出现在 任务配置 中的记录
        $configTaskDiffItems = array_diff_key($configTaskIsDeletedNoItems, $configTasks);
 
        $configTasks = array_values($configTasks);
 
        $configTaskModels = [];
 
        foreach ($configTasks as $configTask) {
            if (isset($configTaskItems[$configTask['code']])) {
                $configTaskItems[$configTask['code']]->scenario = ConfigTask::SCENARIO_UPDATE;
                $configTaskModels[] = $configTaskItems[$configTask['code']];
            } else {
                $configTaskModels[] = new ConfigTask([
                    'scenario' => ConfigTask::SCENARIO_UPDATE,
                ]);
            }
        }
 
        // 批量填充模型属性
        if (!empty($configTaskModels) && !Model::loadMultiple($configTaskModels, $configTasks, '')) {
            return ['code' => 226010, 'message' => Yii::t('error', '226010')];
        }
 
        // 批量验证模型
        if (!empty($configTaskModels) && !Model::validateMultiple($configTaskModels)) {
            $configTaskModelsResult = self::handleValidateMultipleError($configTaskModels);
            if ($configTaskModelsResult['status'] === false) {
                return ['code' => $configTaskModelsResult['code'], 'message' => $configTaskModelsResult['message']];
            }
        }
 
        /* 任务步骤配置 */
        // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 任务步骤配置模型(MySQL) 中但是未出现在 任务步骤配置 中的记录
        $configTaskStepDiffItems = array_diff_key($configTaskStepIsDeletedNoItems, $configTaskSteps);
 
        $configTaskSteps = array_values($configTaskSteps);
 
        $configTaskStepModels = [];
 
        foreach ($configTaskSteps as $configTaskStep) {
            $index = $configTaskStep['config_task_code'] . ':' . $configTaskStep['step_code'];
            if (isset($configTaskStepItems[$index])) {
                $configTaskStepItems[$index]->scenario = ConfigTask::SCENARIO_UPDATE;
                $configTaskStepModels[] = $configTaskStepItems[$index];
            } else {
                $configTaskStepModels[] = new $modelClass([
                    'scenario' => $modelClass::SCENARIO_UPDATE,
                ]);
            }
        }
 
        // 批量填充模型属性
        if (!empty($configTaskStepModels) && !Model::loadMultiple($configTaskStepModels, $configTaskSteps, '')) {
            return ['code' => 226010, 'message' => Yii::t('error', '226010')];
        }
 
        // 批量验证模型
        if (!empty($configTaskStepModels) && !Model::validateMultiple($configTaskStepModels)) {
            $configTaskStepModelsResult = self::handleValidateMultipleError($configTaskStepModels);
            if ($configTaskStepModelsResult['status'] === false) {
                return ['code' => $configTaskStepModelsResult['code'], 'message' => $configTaskStepModelsResult['message']];
            }
        }
 
        /* 操作数据(事务) */
 
        $configTaskStepService = new ConfigTaskStepService();
 
        $result = $configTaskStepService->update($configTaskDiffItems, $configTaskModels, $configTaskStepDiffItems, $configTaskStepModels, $identity);
        if ($result['status'] === false) {
            throw new ServerErrorHttpException($result['message'], $result['code']);
        }
 
        return ['code' => 10000, 'message' => Yii::t('success', '126039')];
    }
}
 

17、新建更新任务类型与步骤设置的服务文件(\api\services\ConfigTaskService.php)

<?php
/**
 * Created by PhpStorm.
 * User: Qiang Wang
 * Date: 2019/11/01
 * Time: 10:17
 */
 
namespace api\services;
 
use Yii;
use api\models\ConfigTask;
use yii\web\ServerErrorHttpException;
 
class ConfigTaskService extends \common\services\ConfigTaskService
{
    /**
     * 创建任务配置
     * @param object $model 对象
     *
     * @param bool $runValidation 保存记录之前是否执行验证 (调用 [[validate()]]),默认为 true
     *
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => true, // 成功
     *     'data' => [ // object
     *     ]
     * ]
     *
     * [
     *     'status' => false, // 失败
     *     'code' => 226081, // 返回码
     *     'message' => '', // 说明
     * ]
     *
     * @throws ServerErrorHttpException
     */
    public function create($model, $runValidation = true)
    {
        if ($model->save($runValidation)) {
            return ['status' => true, 'data' => $model];
        } elseif ($model->hasErrors()) {
            $firstError = '';
            foreach ($model->getFirstErrors() as $message) {
                $firstError = $message;
                break;
            }
            return ['status' => false, 'code' => 226081, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '226081'), ['first_error' => $firstError]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to create the object (task configuration) for unknown reason.');
        }
    }
 
    /**
     * 更新任务配置
     * @param object $model 对象
     *
     * @param bool $runValidation 保存记录之前是否执行验证 (调用 [[validate()]]),默认为 true
     *
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => true, // 成功
     *     'data' => [ // object
     *     ]
     * ]
     *
     * [
     *     'status' => false, // 失败
     *     'code' => 226082, // 返回码
     *     'message' => '', // 说明
     * ]
     *
     * @throws ServerErrorHttpException
     */
    public function update($model, $runValidation = true)
    {
        if ($model->save($runValidation)) {
            return ['status' => true, 'data' => $model];
        } elseif ($model->hasErrors()) {
            $firstError = '';
            foreach ($model->getFirstErrors() as $message) {
                $firstError = $message;
                break;
            }
            return ['status' => false, 'code' => 226082, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '226082'), ['first_error' => $firstError]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to update the object (task configuration) for unknown reason.');
        }
    }
 
    /**
     * 批量删除(删除任务配置)
     *
     * @param $models
     *
     * @return bool the saved model or false if saving fails
     * @throws \Throwable
     */
    public function deleteMultiple($models)
    {
        $transaction = Yii::$app->db->beginTransaction();
        try {
            foreach ($models as $model) {
                /* @var $model ConfigTask */
                if($model->softDelete() === false) {
                    throw new ServerErrorHttpException(Yii::t('error', '226083'), 226083);
                }
            }
            $transaction->commit();
 
            return true;
        } catch (\Throwable $e) {
            $transaction->rollBack();
            throw $e;
        }
    }
}
 

18、新建更新任务类型与步骤设置的服务文件(\api\services\ConfigTaskStepService.php)

<?php
/**
 * Created by PhpStorm.
 * User: terryhong
 * Date: 2018/12/20
 * Time: 3:09 PM
 */
 
namespace api\services;
 
use Yii;
use api\models\ConfigTask;
use api\models\ConfigStep;
use api\models\ConfigTaskStep;
use yii\web\ServerErrorHttpException;
 
class ConfigTaskStepService extends \common\services\ConfigTaskStepService
{
    /**
     * 创建任务步骤配置
     * @param object $model 对象
     *
     * @param bool $runValidation 保存记录之前是否执行验证 (调用 [[validate()]]),默认为 true
     *
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => true, // 成功
     *     'data' => [ // object
     *     ]
     * ]
     *
     * [
     *     'status' => false, // 失败
     *     'code' => 226084, // 返回码
     *     'message' => '', // 说明
     * ]
     *
     * @throws ServerErrorHttpException
     */
    public function create($model, $runValidation = true)
    {
        if ($model->save($runValidation)) {
            return ['status' => true, 'data' => $model];
        } elseif ($model->hasErrors()) {
            $firstError = '';
            foreach ($model->getFirstErrors() as $message) {
                $firstError = $message;
                break;
            }
            return ['status' => false, 'code' => 226084, 'message' => Yii::t('error', Yii::t('error', Yii::t('error', '226084'), ['first_error' => $firstError]))];
        } elseif (!$model->hasErrors()) {
            throw new ServerErrorHttpException('Failed to create the object (task step configuration) for unknown reason.');
        }
    }
 
    /**
     * 添加|还原|更新|删除任务配置、添加|还原|更新|删除任务步骤配置
     *
     * @param array $deletedConfigTasks 需要删除的任务配置
     * 格式如下:
     * [
     *     [ // object
     *         'id' => 32, // ID
     *         'group_id' => '015ce30b116ce86058fa6ab4fea4ac63', // 租户ID
     *         'code' => 'other', // 代码
     *         'name' => '手动任务', // 名称
     *         'sort_order' => 1, // 顺序
     *         'category' => 2, // 任务类型,1:系统内置;2:用户定义
     *         'parameter' => 's:0:"";', // 自定义参数,序列化存储
     *         'status' => 1, // 状态,0:禁用;1:启用
     *         'is_deleted' => 0, // 是否被删除,0:否;1:是
     *         'created_at' => 1573632826, // 创建时间
     *         'updated_at' => 1573632826, // 更新时间
     *         'deleted_at' => 0, // 删除时间
     *     ],
     *     ...
     * ]
     *
     * @param array $configTasks 任务配置
     * 格式如下:
     * [
     *     [ // object
     *         'group_id' => '015ce30b116ce86058fa6ab4fea4ac63', // 租户ID
     *         'code' => 'tv_broadcast', // 代码
     *         'name' => '电视播出', // 名称
     *         'sort_order' => 1, // 顺序
     *     ],
     *     [ // object
     *         'id' => 29, // ID
     *         'group_id' => '015ce30b116ce86058fa6ab4fea4ac63', // 租户ID
     *         'code' => 'tv_broadcast', // 代码
     *         'name' => '电视播出', // 名称
     *         'sort_order' => 1, // 顺序
     *         'category' => 2, // 任务类型,1:系统内置;2:用户定义
     *         'parameter' => 's:0:"";', // 自定义参数,序列化存储
     *         'status' => 1, // 状态,0:禁用;1:启用
     *         'is_deleted' => 0, // 是否被删除,0:否;1:是
     *         'created_at' => 1573632826, // 创建时间
     *         'updated_at' => 1573632826, // 更新时间
     *         'deleted_at' => 0, // 删除时间
     *     ],
     *     ...
     * ]
     *
     * @param array $deletedConfigTaskSteps 需要删除的任务步骤配置
     * 格式如下:
     * [
     *     [ // object
     *         'id' => 145, // ID
     *         'group_id' => '015ce30b116ce86058fa6ab4fea4ac63', // 租户ID
     *         'config_task_id' => 32, // 任务配置ID
     *         'config_task_code' => 'other', // 任务配置代码
     *         'config_step_id' => 2, // 步骤配置ID
     *         'step_code' => 'begin_shot', // 步骤配置代码
     *         'step_name' => '上传素材', // 步骤配置名称
     *         'sort_order' => 1, // 步骤顺序,顺序排列
     *         'is_default' => 1, // 是否默认步骤,1:是;0:否
     *         'up_status_type' => 2, // 状态更新方式,1:接口;2:人工
     *         'status' => 1, // 状态,0:禁用;1:启用
     *         'is_deleted' => 0, // 是否被删除,0:否;1:是
     *         'created_at' => 1573632826, // 创建时间
     *         'updated_at' => 1573632826, // 更新时间
     *         'deleted_at' => 0, // 删除时间
     *     ],
     *     ...
     * ]
     *
     *
     * @param array $configTaskSteps 任务步骤配置
     * 格式如下:
     * [
     *     [ // object
     *         'group_id' => '015ce30b116ce86058fa6ab4fea4ac63', // 租户ID
     *         'config_task_code' => 'tv_broadcast', // 任务配置代码
     *         'step_code' => 'begin_shot', // 步骤代码
     *         'sort_order' => 1, // 顺序
     *         'is_default' => 1, // 是否默认步骤,1:是;0:否
     *     ],
     *     [ // object
     *         'id' => 122, // ID
     *         'group_id' => '015ce30b116ce86058fa6ab4fea4ac63', // 租户ID
     *         'config_task_id' => 29, // 任务配置ID
     *         'config_task_code' => 'tv_broadcast', // 任务配置代码
     *         'config_step_id' => 2, // 步骤配置ID
     *         'step_code' => 'begin_shot', // 步骤配置代码
     *         'step_name' => '上传素材', // 步骤配置名称
     *         'sort_order' => 1, // 步骤顺序,顺序排列
     *         'is_default' => 1, // 是否默认步骤,1:是;0:否
     *         'up_status_type' => 2, // 状态更新方式,1:接口;2:人工
     *         'status' => 1, // 状态,0:禁用;1:启用
     *         'is_deleted' => 0, // 是否被删除,0:否;1:是
     *         'created_at' => 1573632826, // 创建时间
     *         'updated_at' => 1573632826, // 更新时间
     *         'deleted_at' => 0, // 删除时间
     *     ],
     *     ...
     * ]
     *
     * @param object $identity 当前用户的身份实例
     *
     * @return array
     * 格式如下:
     *
     * [
     *     'status' => true, // 成功
     * ]
     *
     * @throws \Throwable
     */
    public function update($deletedConfigTasks, $configTasks, $deletedConfigTaskSteps, $configTaskSteps, $identity)
    {
 
        /* 操作数据(事务) */
        $transaction = Yii::$app->db->beginTransaction();
        try {
 
            $configTaskService = new ConfigTaskService();
            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 任务配置模型(MySQL) 中但是未出现在 任务配置列表 中的记录
            $configTaskService->deleteMultiple($deletedConfigTasks);
 
            // 遍历模型数组,判断在 任务配置 中是否存在,如果不存在则插入,如果存在则更新(已删除则先还原)
            foreach ($configTasks as $configTask) {
                $configTaskItem = ConfigTask::find()->where(['group_id' => $identity->group_id, 'code' => $configTask->code])->one();
 
                /* @var $model ConfigTask */
                if (!isset($configTaskItem)) {
 
                    $configTask->category = $configTask::CATEGORY_CUSTOMIZE;
                    $configTask->parameter = serialize('');
                    $configTask->status = $configTask::STATUS_ENABLED;
                    $configTask->is_deleted = $configTask::IS_DELETED_NO;
                    $configTask->deleted_at = $configTask::DELETED_AT_DEFAULT;
                    $configTaskServiceCreateResult = $configTaskService->create($configTask, false);
                    if ($configTaskServiceCreateResult['status'] === false) {
                        throw new ServerErrorHttpException($configTaskServiceCreateResult['message'], $configTaskServiceCreateResult['code']);
                    }
 
                } else if (isset($configTaskItem)) {
 
                    if ($configTaskItem->is_deleted == ConfigTask::IS_DELETED_YES && $configTaskItem->restore() === false) {
                        throw new ServerErrorHttpException('Failed to restore the object (task configuration) for unknown reason.');
                    }
 
                    $configTaskItem->name = $configTask->name;
                    $configTaskItem->sort_order = $configTask->sort_order;
                    $configTaskItemServiceUpdateResult = $configTaskService->update($configTaskItem, false);
                    if ($configTaskItemServiceUpdateResult['status'] === false) {
                        throw new ServerErrorHttpException($configTaskItemServiceUpdateResult['message'], $configTaskItemServiceUpdateResult['code']);
                    }
 
                }
            }
 
            // 使用键名比较计算数组的差集,如果不为空,则删除 (软删除) 出现在 任务步骤配置模型(MySQL) 中但是未出现在 任务步骤配置列表 中的记录
            $this->deleteMultiple($deletedConfigTaskSteps);
 
            $configTaskItems = ConfigTask::find()->where(['group_id' => $identity->group_id])->indexBy(['code'])->all();
            $configStepItems = ConfigStep::find()->isDeletedNo()->indexBy(['code'])->all();
 
            // 遍历模型数组,判断在 任务步骤配置 中是否存在,如果不存在则插入,如果存在则更新(已删除则先还原)
            foreach ($configTaskSteps as $configTaskStep) {
                $configTaskStepItem = ConfigTaskStep::find()->where(['group_id' => $identity->group_id, 'config_task_code' => $configTaskStep->config_task_code, 'step_code' => $configTaskStep->step_code])->one();
 
                /* @var $model ConfigTaskStep */
                if (!isset($configTaskStepItem)) {
 
                    $configTaskStep->config_task_id = $configTaskItems[$configTaskStep->config_task_code]->id;
                    $configTaskStep->config_step_id = $configStepItems[$configTaskStep->step_code]->id;
                    $configTaskStep->step_name = $configStepItems[$configTaskStep->step_code]->name;
                    $configTaskStep->status = $configTaskStep::STATUS_ENABLED;
                    $configTaskStep->is_deleted = $configTaskStep::IS_DELETED_NO;
                    $configTaskStep->deleted_at = $configTaskStep::DELETED_AT_DEFAULT;
                    $thisCreateResult = $this->create($configTaskStep, false);
                    if ($thisCreateResult['status'] === false) {
                        throw new ServerErrorHttpException($thisCreateResult['message'], $thisCreateResult['code']);
                    }
 
                } else if (isset($configTaskStepItem)) {
 
                    if ($configTaskStepItem->is_deleted == ConfigTaskStep::IS_DELETED_YES && $configTaskStepItem->restore() === false) {
                        throw new ServerErrorHttpException('Failed to restore the object (task step configuration) for unknown reason.');
                    }
 
                    $configTaskStepItem->sort_order = $configTaskStep->sort_order;
                    $configTaskStepItem->is_default = $configTaskStep->is_default;
                    if ($configTaskStepItem->save(false)) {
                        // return ['status' => true, 'data' => $configTaskStepItem];
                    } elseif ($configTaskStepItem->hasErrors()) {
                        $firstError = '';
                        foreach ($configTaskStepItem->getFirstErrors() as $message) {
                            $firstError = $message;
                            break;
                        }
                        throw new ServerErrorHttpException(Yii::t('error', Yii::t('error', Yii::t('error', '226085'), ['first_error' => $firstError])), 226085);
                    } elseif (!$configTaskStepItem->hasErrors()) {
                        throw new ServerErrorHttpException('Failed to update the object (task step configuration) for unknown reason.');
                    }
 
                }
            }
 
            $transaction->commit();
        } catch(\Throwable $e) {
            $transaction->rollBack();
            throw $e;
        }
 
        return ['status' => true];
    }
 
    /**
     * 批量删除(删除任务步骤配置)
     *
     * @param $models
     *
     * @return bool the saved model or false if saving fails
     * @throws \Throwable
     */
    public function deleteMultiple($models)
    {
        $transaction = Yii::$app->db->beginTransaction();
        try {
            foreach ($models as $model) {
                if($model->softDelete() === false) {
                    throw new ServerErrorHttpException(Yii::t('error', '226086'), 226086);
                }
            }
            $transaction->commit();
 
            return true;
        } catch (\Throwable $e) {
            $transaction->rollBack();
            throw $e;
        }
    }
}
 

19、编辑更新任务设置的模型文件(\api\models\ConfigTask.php)

<?php
 
namespace api\models;
 
use yii\helpers\ArrayHelper;
 
class ConfigTask extends \common\logics\ConfigTask
{
    const SCENARIO_UPDATE = 'update';
 
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios[self::SCENARIO_UPDATE] = ['group_id', 'code', 'name', 'sort_order', 'is_deleted', 'deleted_at'];
 
        return $scenarios;
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        $rules = [
        ];
        $parentRules = parent::rules();
 
        return ArrayHelper::merge($rules, $parentRules);
    }
 
    /**
     * {@inheritdoc}
     * @return ConfigTaskQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new ConfigTaskQuery(get_called_class());
    }
}
 
 

20、编辑更新任务步骤设置的模型文件(\api\models\ConfigTaskStep.php)

<?php
 
namespace api\models;
 
use yii\helpers\ArrayHelper;
 
class ConfigTaskStep extends \common\logics\ConfigTaskStep
{
    const SCENARIO_UPDATE = 'update';
 
    public function scenarios()
    {
        $scenarios = parent::scenarios();
        $scenarios[self::SCENARIO_UPDATE] = ['group_id', 'config_task_code', 'step_code', 'sort_order', 'is_default', 'is_deleted', 'deleted_at'];
 
        return $scenarios;
    }
 
    /**
     * @inheritdoc
     */
    public function rules()
    {
        $rules = [
            /* 添加、更新任务步骤配置 */
            ['step_code', 'exist', 'targetClass' => '\api\models\ConfigStep', 'targetAttribute' => 'code', 'filter' => ['is_deleted' => ConfigStep::IS_DELETED_NO], 'on' => self::SCENARIO_UPDATE],
            [['is_default'], 'in', 'range' => [static::IS_DEFAULT_NO, static::IS_DEFAULT_YES], 'on' => self::SCENARIO_UPDATE],
        ];
        $parentRules = parent::rules();
 
        return ArrayHelper::merge($rules, $parentRules);
    }
 
    /**
     * {@inheritdoc}
     * @return ConfigTaskStepQuery the active query used by this AR class.
     */
    public static function find()
    {
        return new ConfigTaskStepQuery(get_called_class());
    }
}
 

21、在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据(null)与执行的 SQL (未插入数据) 如下,如图4

在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据(null)与执行的 SQL (未插入数据) 如下

图4


SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)

SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63')

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63')

Begin transaction

SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

Commit transaction


22、在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据([])与执行的 SQL (未插入数据) 如下,如图5

在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据([])与执行的 SQL (未插入数据) 如下

图5


SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)

SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63')

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63')

Begin transaction

SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

Commit transaction


23、在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据(未自定义时,编辑接口的响应数据)与执行的 SQL (分别插入 4 、24 条记录) 如下,打开表,如图6

在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据(未自定义时,编辑接口的响应数据)与执行的 SQL (分别插入 4 、24 条记录) 如下,打开表

图6


[
    {
        "code": "tv_broadcast",
        "name": "电视播出",
        "sort_order": 1,
        "steps": [
            {
                "code": "begin_shot",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "writing",
                "sort_order": 2,
                "is_default": 1
            },
            {
                "code": "manuscript_review",
                "sort_order": 4,
                "is_default": 1
            },
            {
                "code": "video_editing",
                "sort_order": 5,
                "is_default": 1
            },
            {
                "code": "tandem_list",
                "sort_order": 6,
                "is_default": 1
            },
            {
                "code": "program_render",
                "sort_order": 7,
                "is_default": 1
            },
            {
                "code": "program_authen",
                "sort_order": 8,
                "is_default": 1
            }
        ]
    },
    {
        "code": "inter_view",
        "name": "采访任务",
        "sort_order": 2,
        "steps": [
            {
                "code": "begin_shot",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "material_review",
                "sort_order": 2,
                "is_default": 1
            },
            {
                "code": "writing",
                "sort_order": 4,
                "is_default": 1
            },
            {
                "code": "writing_draft",
                "sort_order": 5,
                "is_default": 1
            },
            {
                "code": "manuscript_review",
                "sort_order": 6,
                "is_default": 1
            },
            {
                "code": "finish_interview",
                "sort_order": 7,
                "is_default": 1
            }
        ]
    },
    {
        "code": "rich_doc",
        "name": "互联网发布",
        "sort_order": 3,
        "steps": [
            {
                "code": "retrieval",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "writing",
                "sort_order": 2,
                "is_default": 1
            },
            {
                "code": "manuscript_review",
                "sort_order": 4,
                "is_default": 1
            },
            {
                "code": "newspaper_issued",
                "sort_order": 5,
                "is_default": 1
            },
            {
                "code": "wechat_group_reference",
                "sort_order": 6,
                "is_default": 1
            },
            {
                "code": "website_release",
                "sort_order": 7,
                "is_default": 1
            },
            {
                "code": "app_release",
                "sort_order": 8,
                "is_default": 1
            },
            {
                "code": "wechat_release",
                "sort_order": 9,
                "is_default": 1
            },
            {
                "code": "blog_release",
                "sort_order": 10,
                "is_default": 1
            },
            {
                "code": "qq_release",
                "sort_order": 11,
                "is_default": 1
            }
        ]
    },
    {
        "code": "other",
        "name": "手动任务",
        "sort_order": 4,
        "steps": [
            {
                "code": "begin_shot",
                "sort_order": 1,
                "is_default": 1
            }
        ]
    }
]



SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)

SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)

SELECT * FROM `pa_config_task_step` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT EXISTS(SELECT * FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task`.`code`='tv_broadcast') AND (`pa_config_task`.`is_deleted`=0) AND (`pa_config_task`.`deleted_at`=0)) // 总计 4 次执行

SELECT EXISTS(SELECT * FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task`.`code`='inter_view') AND (`pa_config_task`.`is_deleted`=0) AND (`pa_config_task`.`deleted_at`=0)) // 总计 4 次执行

SELECT EXISTS(SELECT * FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task`.`code`='rich_doc') AND (`pa_config_task`.`is_deleted`=0) AND (`pa_config_task`.`deleted_at`=0)) // 总计 4 次执行

SELECT EXISTS(SELECT * FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task`.`code`='other') AND (`pa_config_task`.`is_deleted`=0) AND (`pa_config_task`.`deleted_at`=0)) // 总计 4 次执行

SELECT EXISTS(SELECT * FROM `pa_config_step` WHERE (`pa_config_step`.`code`='begin_shot') AND (`is_deleted`=0)) // 总计 24 次执行【SELECT EXISTS(SELECT * FROM `pa_config_step` WHERE (`pa_config_step`.`code`=】

SELECT EXISTS(SELECT * FROM `pa_config_task_step` WHERE (`pa_config_task_step`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task_step`.`config_task_code`='tv_broadcast') AND (`pa_config_task_step`.`step_code`='begin_shot') AND (`pa_config_task_step`.`is_deleted`=0) AND (`pa_config_task_step`.`deleted_at`=0)) // 总计 120 次执行【SELECT EXISTS(SELECT * FROM `pa_config_task_step` WHERE (`pa_config_task_step`.`group_id`】

Begin transaction

SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`code`='tv_broadcast') // 总计 4 次执行【SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`code`=】

INSERT INTO `pa_config_task` (`group_id`, `code`, `name`, `sort_order`, `is_deleted`, `deleted_at`, `category`, `parameter`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 'tv_broadcast', '电视播出', 1, 0, 0, 2, 's:0:\"\";', 1, 1573626589, 1573626589) // 总计 4 次执行【SELECT * FROM `pa_config_task` WHERE (INSERT INTO `pa_config_task` (`group_id`, `code`, `name`, `sort_order`, `is_deleted`, `deleted_at`, `category`, `parameter`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63'】

SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_step` WHERE `is_deleted`=0

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`config_task_code`='tv_broadcast') AND (`step_code`='begin_shot') // 总计 24 次执行【SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`config_task_code`=】

INSERT INTO `pa_config_task_step` (`group_id`, `config_task_code`, `step_code`, `sort_order`, `is_default`, `is_deleted`, `deleted_at`, `config_task_id`, `config_step_id`, `step_name`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 'tv_broadcast', 'begin_shot', 1, 1, 0, 0, 17, 2, '上传素材', 1, 1573626589, 1573626589) // 总计 24 次执行【INSERT INTO `pa_config_task_step` (`group_id`, `config_task_code`, `step_code`, `sort_order`, `is_default`, `is_deleted`, `deleted_at`, `config_task_id`, `config_step_id`, `step_name`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63'】

Commit transaction


24、在 Postman 中 GET:http://api.pcs-api.localhost/v1/config-task-steps/edit/my-group-id ,当租户已自定义时,即 任务配置表、任务步骤配置表 中有当前租户的记录时,默认获取 任务配置表、任务步骤配置表 中的当前租户ID的记录,响应数据与执行的 SQL 如下


{
    "code": 10000,
    "message": "编辑租户的任务类型与步骤设置成功",
    "data": {
        "items": [
            {
                "id": 17,
                "code": "tv_broadcast",
                "name": "电视播出",
                "sort_order": 1,
                "steps": [
                    {
                        "id": 2,
                        "code": "begin_shot",
                        "name": "上传素材",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 4,
                        "code": "writing",
                        "name": "撰写稿件",
                        "sort_order": 2,
                        "is_default": 1
                    },
                    {
                        "id": 7,
                        "code": "manuscript_review",
                        "name": "稿件审核",
                        "sort_order": 4,
                        "is_default": 1
                    },
                    {
                        "id": 10,
                        "code": "video_editing",
                        "name": "视频编辑",
                        "sort_order": 5,
                        "is_default": 1
                    },
                    {
                        "id": 11,
                        "code": "tandem_list",
                        "name": "串联单",
                        "sort_order": 6,
                        "is_default": 1
                    },
                    {
                        "id": 14,
                        "code": "program_render",
                        "name": "节目上传",
                        "sort_order": 7,
                        "is_default": 1
                    },
                    {
                        "id": 16,
                        "code": "program_authen",
                        "name": "节目审查",
                        "sort_order": 8,
                        "is_default": 1
                    }
                ]
            },
            {
                "id": 18,
                "code": "inter_view",
                "name": "采访任务",
                "sort_order": 2,
                "steps": [
                    {
                        "id": 2,
                        "code": "begin_shot",
                        "name": "上传素材",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 3,
                        "code": "material_review",
                        "name": "素材审核",
                        "sort_order": 2,
                        "is_default": 1
                    },
                    {
                        "id": 4,
                        "code": "writing",
                        "name": "撰写稿件",
                        "sort_order": 4,
                        "is_default": 1
                    },
                    {
                        "id": 8,
                        "code": "writing_draft",
                        "name": "撰写通稿",
                        "sort_order": 5,
                        "is_default": 1
                    },
                    {
                        "id": 7,
                        "code": "manuscript_review",
                        "name": "稿件审核",
                        "sort_order": 6,
                        "is_default": 1
                    },
                    {
                        "id": 13,
                        "code": "finish_interview",
                        "name": "结束采访",
                        "sort_order": 7,
                        "is_default": 1
                    }
                ]
            },
            {
                "id": 19,
                "code": "rich_doc",
                "name": "互联网发布",
                "sort_order": 3,
                "steps": [
                    {
                        "id": 1,
                        "code": "retrieval",
                        "name": "取稿",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 4,
                        "code": "writing",
                        "name": "撰写稿件",
                        "sort_order": 2,
                        "is_default": 1
                    },
                    {
                        "id": 7,
                        "code": "manuscript_review",
                        "name": "稿件审核",
                        "sort_order": 4,
                        "is_default": 1
                    },
                    {
                        "id": 9,
                        "code": "newspaper_issued",
                        "name": "报纸签发",
                        "sort_order": 5,
                        "is_default": 1
                    },
                    {
                        "id": 12,
                        "code": "wechat_group_reference",
                        "name": "微信组稿引用",
                        "sort_order": 6,
                        "is_default": 1
                    },
                    {
                        "id": 15,
                        "code": "website_release",
                        "name": "网站发布",
                        "sort_order": 7,
                        "is_default": 1
                    },
                    {
                        "id": 17,
                        "code": "app_release",
                        "name": "APP发布",
                        "sort_order": 8,
                        "is_default": 1
                    },
                    {
                        "id": 18,
                        "code": "wechat_release",
                        "name": "微信发布",
                        "sort_order": 9,
                        "is_default": 1
                    },
                    {
                        "id": 19,
                        "code": "blog_release",
                        "name": "微博发布",
                        "sort_order": 10,
                        "is_default": 1
                    },
                    {
                        "id": 20,
                        "code": "qq_release",
                        "name": "企鹅号发布",
                        "sort_order": 11,
                        "is_default": 1
                    }
                ]
            },
            {
                "id": 20,
                "code": "other",
                "name": "手动任务",
                "sort_order": 4,
                "steps": [
                    {
                        "id": 2,
                        "code": "begin_shot",
                        "name": "上传素材",
                        "sort_order": 1,
                        "is_default": 1
                    }
                ]
            }
        ]
    }
}



SELECT * FROM `pa_config_task_step` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC

SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC


25、在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据(删除任务配置(采访任务)中的 6 项步骤配置;删除 1 项任务配置(手动任务)及其中的 1 项步骤配置;在 互联网发布 后添加 1 项任务配置(自定义一)及在其中添加 0 项步骤配置;在 自定义一 后添加 1 项任务配置(自定义二)及在其中添加 2 项步骤配置;更新 1 项任务配置(互联网发布)及其中的 1 项步骤配置)与执行的 SQL ( 任务配置表:2 条插入语句、2 条更新语句;任务步骤配置表:2 条插入语句、8 条更新语句) 如下,打开表,如图7

在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据(删除任务配置(采访任务)中的 6 项步骤配置;删除 1 项任务配置(手动任务)及其中的 1 项步骤配置;在 互联网发布 后添加 1 项任务配置(自定义一)及在其中添加 0 项步骤配置;在 自定义一 后添加 1 项任务配置(自定义二)及在其中添加 2 项步骤配置;更新 1 项任务配置(互联网发布)及其中的 1 项步骤配置)与执行的 SQL ( 任务配置表:2 条插入语句、2 条更新语句;任务步骤配置表:2 条插入语句、8 条更新语句) 如下,打开表

图7


[
    {
        "code": "tv_broadcast",
        "name": "电视播出",
        "sort_order": 1,
        "steps": [
            {
                "code": "begin_shot",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "writing",
                "sort_order": 2,
                "is_default": 1
            },
            {
                "code": "manuscript_review",
                "sort_order": 4,
                "is_default": 1
            },
            {
                "code": "video_editing",
                "sort_order": 5,
                "is_default": 1
            },
            {
                "code": "tandem_list",
                "sort_order": 6,
                "is_default": 1
            },
            {
                "code": "program_render",
                "sort_order": 7,
                "is_default": 1
            },
            {
                "code": "program_authen",
                "sort_order": 8,
                "is_default": 1
            }
        ]
    },
    {
        "code": "inter_view",
        "name": "采访任务",
        "sort_order": 2,
        "steps": []
    },
    {
        "code": "rich_doc",
        "name": "互联网发布更新",
        "sort_order": 4,
        "steps": [
            {
                "code": "retrieval",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "writing",
                "sort_order": 2,
                "is_default": 1
            },
            {
                "code": "manuscript_review",
                "sort_order": 3,
                "is_default": 1
            },
            {
                "code": "newspaper_issued",
                "sort_order": 5,
                "is_default": 1
            },
            {
                "code": "wechat_group_reference",
                "sort_order": 6,
                "is_default": 1
            },
            {
                "code": "website_release",
                "sort_order": 7,
                "is_default": 1
            },
            {
                "code": "app_release",
                "sort_order": 8,
                "is_default": 1
            },
            {
                "code": "wechat_release",
                "sort_order": 9,
                "is_default": 1
            },
            {
                "code": "blog_release",
                "sort_order": 10,
                "is_default": 1
            },
            {
                "code": "qq_release",
                "sort_order": 11,
                "is_default": 1
            }
        ]
    },
    {
        "code": "zdy1",
        "name": "自定义一",
        "sort_order": 5,
        "steps": []
    },
    {
        "code": "zdy2",
        "name": "自定义二",
        "sort_order": 6,
        "steps": [
            {
                "code": "begin_shot",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "program_render",
                "sort_order": 7,
                "is_default": 1
            }
        ]
    }
]



SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)

SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)

SELECT * FROM `pa_config_task_step` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT `pa_config_task`.`id` FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task`.`code`='tv_broadcast') AND (`pa_config_task`.`is_deleted`=0) AND (`pa_config_task`.`deleted_at`=0) LIMIT 2 // 总计 12 次执行【SELECT `pa_config_task`.`id` FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`=】

SELECT EXISTS(SELECT * FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task`.`code`='zdy1') AND (`pa_config_task`.`is_deleted`=0) AND (`pa_config_task`.`deleted_at`=0)) // 总计 8 次执行【SELECT EXISTS(SELECT * FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`=】

SELECT EXISTS(SELECT * FROM `pa_config_step` WHERE (`pa_config_step`.`code`='begin_shot') AND (`is_deleted`=0)) // 总计 19 次执行【SELECT EXISTS(SELECT * FROM `pa_config_step` WHERE (`pa_config_step`.`code`=】

SELECT `pa_config_task_step`.`id` FROM `pa_config_task_step` WHERE (`pa_config_task_step`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task_step`.`config_task_code`='tv_broadcast') AND (`pa_config_task_step`.`step_code`='begin_shot') AND (`pa_config_task_step`.`is_deleted`=0) AND (`pa_config_task_step`.`deleted_at`=0) LIMIT 2 // 总计 85 次执行【SELECT `pa_config_task_step`.`id` FROM `pa_config_task_step` WHERE (`pa_config_task_step`.`group_id`=】

SELECT EXISTS(SELECT * FROM `pa_config_step` WHERE (`pa_config_step`.`code`='writing') AND (`is_deleted`=0)) // 总计 19 次执行【SELECT EXISTS(SELECT * FROM `pa_config_step` WHERE (`pa_config_step`.`code`=】

Begin transaction

UPDATE `pa_config_task` SET `is_deleted`=1, `deleted_at`=1573630064 WHERE `id`=20

SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`code`='tv_broadcast') // 总计 5 次执行【SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`code`=】

UPDATE `pa_config_task` SET `name`='互联网发布更新', `sort_order`=4, `updated_at`=1573630064 WHERE `id`=19

INSERT INTO `pa_config_task` (`group_id`, `code`, `name`, `sort_order`, `is_deleted`, `deleted_at`, `category`, `parameter`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 'zdy1', '自定义一', 5, 0, 0, 2, 's:0:\"\";', 1, 1573630064, 1573630064)

INSERT INTO `pa_config_task` (`group_id`, `code`, `name`, `sort_order`, `is_deleted`, `deleted_at`, `category`, `parameter`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 'zdy2', '自定义二', 6, 0, 0, 2, 's:0:\"\";', 1, 1573630064, 1573630064)

UPDATE `pa_config_task_step` SET `is_deleted`=1, `deleted_at`=1573630064 WHERE `id`=83 // 总计 7 次执行【UPDATE `pa_config_task_step` SET `is_deleted`=1】

SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_step` WHERE `is_deleted`=0

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`config_task_code`='tv_broadcast') AND (`step_code`='begin_shot') // 总计 19 次执行【SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`config_task_code`=】

UPDATE `pa_config_task_step` SET `sort_order`=3, `updated_at`=1573630064 WHERE `id`=91

INSERT INTO `pa_config_task_step` (`group_id`, `config_task_code`, `step_code`, `sort_order`, `is_default`, `is_deleted`, `deleted_at`, `config_task_id`, `config_step_id`, `step_name`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 'zdy2', 'begin_shot', 1, 1, 0, 0, 22, 2, '上传素材', 1, 1573630064, 1573630064)

INSERT INTO `pa_config_task_step` (`group_id`, `config_task_code`, `step_code`, `sort_order`, `is_default`, `is_deleted`, `deleted_at`, `config_task_id`, `config_step_id`, `step_name`, `status`, `created_at`, `updated_at`) VALUES ('015ce30b116ce86058fa6ab4fea4ac63', 'zdy2', 'program_render', 7, 1, 0, 0, 22, 14, '节目上传', 1, 1573630064, 1573630064)

Commit transaction


26、在 Postman 中 GET:http://api.pcs-api.localhost/v1/config-task-steps/edit/my-group-id ,当租户已自定义时,即 任务配置表、任务步骤配置表 中有当前租户的记录时,默认获取 任务配置表、任务步骤配置表 中的当前租户ID的记录,响应数据与执行的 SQL 如下


{
    "code": 10000,
    "message": "编辑租户的任务类型与步骤设置成功",
    "data": {
        "items": [
            {
                "id": 17,
                "code": "tv_broadcast",
                "name": "电视播出",
                "sort_order": 1,
                "steps": [
                    {
                        "id": 2,
                        "code": "begin_shot",
                        "name": "上传素材",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 4,
                        "code": "writing",
                        "name": "撰写稿件",
                        "sort_order": 2,
                        "is_default": 1
                    },
                    {
                        "id": 7,
                        "code": "manuscript_review",
                        "name": "稿件审核",
                        "sort_order": 4,
                        "is_default": 1
                    },
                    {
                        "id": 10,
                        "code": "video_editing",
                        "name": "视频编辑",
                        "sort_order": 5,
                        "is_default": 1
                    },
                    {
                        "id": 11,
                        "code": "tandem_list",
                        "name": "串联单",
                        "sort_order": 6,
                        "is_default": 1
                    },
                    {
                        "id": 14,
                        "code": "program_render",
                        "name": "节目上传",
                        "sort_order": 7,
                        "is_default": 1
                    },
                    {
                        "id": 16,
                        "code": "program_authen",
                        "name": "节目审查",
                        "sort_order": 8,
                        "is_default": 1
                    }
                ]
            },
            {
                "id": 18,
                "code": "inter_view",
                "name": "采访任务",
                "sort_order": 2,
                "steps": []
            },
            {
                "id": 19,
                "code": "rich_doc",
                "name": "互联网发布更新",
                "sort_order": 4,
                "steps": [
                    {
                        "id": 1,
                        "code": "retrieval",
                        "name": "取稿",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 4,
                        "code": "writing",
                        "name": "撰写稿件",
                        "sort_order": 2,
                        "is_default": 1
                    },
                    {
                        "id": 7,
                        "code": "manuscript_review",
                        "name": "稿件审核",
                        "sort_order": 3,
                        "is_default": 1
                    },
                    {
                        "id": 9,
                        "code": "newspaper_issued",
                        "name": "报纸签发",
                        "sort_order": 5,
                        "is_default": 1
                    },
                    {
                        "id": 12,
                        "code": "wechat_group_reference",
                        "name": "微信组稿引用",
                        "sort_order": 6,
                        "is_default": 1
                    },
                    {
                        "id": 15,
                        "code": "website_release",
                        "name": "网站发布",
                        "sort_order": 7,
                        "is_default": 1
                    },
                    {
                        "id": 17,
                        "code": "app_release",
                        "name": "APP发布",
                        "sort_order": 8,
                        "is_default": 1
                    },
                    {
                        "id": 18,
                        "code": "wechat_release",
                        "name": "微信发布",
                        "sort_order": 9,
                        "is_default": 1
                    },
                    {
                        "id": 19,
                        "code": "blog_release",
                        "name": "微博发布",
                        "sort_order": 10,
                        "is_default": 1
                    },
                    {
                        "id": 20,
                        "code": "qq_release",
                        "name": "企鹅号发布",
                        "sort_order": 11,
                        "is_default": 1
                    }
                ]
            },
            {
                "id": 21,
                "code": "zdy1",
                "name": "自定义一",
                "sort_order": 5,
                "steps": []
            },
            {
                "id": 22,
                "code": "zdy2",
                "name": "自定义二",
                "sort_order": 6,
                "steps": [
                    {
                        "id": 2,
                        "code": "begin_shot",
                        "name": "上传素材",
                        "sort_order": 1,
                        "is_default": 1
                    },
                    {
                        "id": 14,
                        "code": "program_render",
                        "name": "节目上传",
                        "sort_order": 7,
                        "is_default": 1
                    }
                ]
            }
        ]
    }
}



SELECT * FROM `pa_config_task_step` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC

SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'

SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC


27、在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据(在 采访任务 后添加之前删除的 1 项任务配置(手动任务,调整其顺序值为 3)及其中的 1 项步骤配置)与执行的更新 SQL ( 任务配置表:2 条更新语句(先还原再更新);任务步骤配置表:1 条更新语句) 如下,打开表,如图8

在 Postman 中 PUT:http://api.pcs-api.localhost/v1/config-task-steps/my-group-id ,请求数据(在 采访任务 后添加之前删除的 1 项任务配置(手动任务,调整其顺序值为 3)及其中的 1 项步骤配置)与执行的更新 SQL ( 任务配置表:2 条更新语句(先还原再更新);任务步骤配置表:1 条更新语句) 如下,打开表

图8


[
    {
        "code": "tv_broadcast",
        "name": "电视播出",
        "sort_order": 1,
        "steps": [
            {
                "code": "begin_shot",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "writing",
                "sort_order": 2,
                "is_default": 1
            },
            {
                "code": "manuscript_review",
                "sort_order": 4,
                "is_default": 1
            },
            {
                "code": "video_editing",
                "sort_order": 5,
                "is_default": 1
            },
            {
                "code": "tandem_list",
                "sort_order": 6,
                "is_default": 1
            },
            {
                "code": "program_render",
                "sort_order": 7,
                "is_default": 1
            },
            {
                "code": "program_authen",
                "sort_order": 8,
                "is_default": 1
            }
        ]
    },
    {
        "code": "inter_view",
        "name": "采访任务",
        "sort_order": 2,
        "steps": []
    },
    {
        "code": "other",
        "name": "手动任务",
        "sort_order": 3,
        "steps": [
            {
                "code": "begin_shot",
                "sort_order": 1,
                "is_default": 1
            }
        ]
    },
    {
        "code": "rich_doc",
        "name": "互联网发布更新",
        "sort_order": 4,
        "steps": [
            {
                "code": "retrieval",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "writing",
                "sort_order": 2,
                "is_default": 1
            },
            {
                "code": "manuscript_review",
                "sort_order": 3,
                "is_default": 1
            },
            {
                "code": "newspaper_issued",
                "sort_order": 5,
                "is_default": 1
            },
            {
                "code": "wechat_group_reference",
                "sort_order": 6,
                "is_default": 1
            },
            {
                "code": "website_release",
                "sort_order": 7,
                "is_default": 1
            },
            {
                "code": "app_release",
                "sort_order": 8,
                "is_default": 1
            },
            {
                "code": "wechat_release",
                "sort_order": 9,
                "is_default": 1
            },
            {
                "code": "blog_release",
                "sort_order": 10,
                "is_default": 1
            },
            {
                "code": "qq_release",
                "sort_order": 11,
                "is_default": 1
            }
        ]
    },
    {
        "code": "zdy1",
        "name": "自定义一",
        "sort_order": 5,
        "steps": []
    },
    {
        "code": "zdy2",
        "name": "自定义二",
        "sort_order": 6,
        "steps": [
            {
                "code": "begin_shot",
                "sort_order": 1,
                "is_default": 1
            },
            {
                "code": "program_render",
                "sort_order": 7,
                "is_default": 1
            }
        ]
    }
]



Begin transaction

UPDATE `pa_config_task` SET `is_deleted`=0, `deleted_at`=0 WHERE `id`=20

UPDATE `pa_config_task` SET `sort_order`=3, `updated_at`=1573632089 WHERE `id`=20

UPDATE `pa_config_task_step` SET `is_deleted`=0, `deleted_at`=0 WHERE `id`=99

Commit transaction


28、已经有 3 个租户自定义任务类型与步骤设置,2 张表的总行数(20、97),基于 Explain 分析 SQL 如下(type:const|ref,rows 至多为表中总行数的 1/4),索引是合理的,如图9
注:
(1)type:显示的是访问类型,是较为重要的一个指标,结果值从好到坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
(2)rows:这个数表示 MySQL 要遍历多少数据才能找到,至多为表中总行数的 1/4 ,就是比较理想的情况

已经有 3 个租户自定义任务类型与步骤设置,2 张表的总行数(20、97),基于 Explain 分析 SQL 如下(type:const|ref,rows 至多为表中总行数的 1/4),索引是合理的

图9


EXPLAIN SELECT * FROM `pa_config_task` WHERE `group_id`=''
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task		ref	uc_group_id_code_is_deleted_deleted_at	uc_group_id_code_is_deleted_deleted_at	130	const	4	100.00	

EXPLAIN SELECT * FROM `pa_config_task` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task		ref	uc_group_id_code_is_deleted_deleted_at	uc_group_id_code_is_deleted_deleted_at	130	const	6	100.00	

EXPLAIN SELECT * FROM `pa_config_task` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0)
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task		ref	uc_group_id_code_is_deleted_deleted_at	uc_group_id_code_is_deleted_deleted_at	130	const	6	10.00	Using index condition

EXPLAIN SELECT * FROM `pa_config_task` WHERE (`group_id`='') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task		ref	uc_group_id_code_is_deleted_deleted_at	uc_group_id_code_is_deleted_deleted_at	130	const	4	10.00	Using index condition; Using filesort

EXPLAIN SELECT * FROM `pa_config_task_step` WHERE `group_id`='015ce30b116ce86058fa6ab4fea4ac63'
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task_step		ref	uc_group_id_config_task_code_step_code_is_deleted_deleted_at	uc_group_id_config_task_code_step_code_is_deleted_deleted_at	130	const	26	100.00		

EXPLAIN SELECT * FROM `pa_config_task_step` WHERE (`group_id`='') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task_step		ref	uc_group_id_config_task_code_step_code_is_deleted_deleted_at	uc_group_id_config_task_code_step_code_is_deleted_deleted_at	130	const	27	10.00	Using index condition; Using filesort

EXPLAIN SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`is_deleted`=0) ORDER BY `sort_order`, `id` DESC
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task_step		ref	uc_group_id_config_task_code_step_code_is_deleted_deleted_at	uc_group_id_config_task_code_step_code_is_deleted_deleted_at	130	const	26	10.00	Using index condition; Using filesort

EXPLAIN SELECT `pa_config_task`.`id` FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task`.`code`='tv_broadcast') AND (`pa_config_task`.`is_deleted`=0) AND (`pa_config_task`.`deleted_at`=0) LIMIT 2
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task		const	uc_group_id_code_is_deleted_deleted_at	uc_group_id_code_is_deleted_deleted_at	266	const,const,const,const	1	100.00	Using index

EXPLAIN SELECT EXISTS(SELECT * FROM `pa_config_task` WHERE (`pa_config_task`.`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`pa_config_task`.`code`='zdy1') AND (`pa_config_task`.`is_deleted`=0) AND (`pa_config_task`.`deleted_at`=0))
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	PRIMARY										No tables used
2	SUBQUERY	pa_config_task		const	uc_group_id_code_is_deleted_deleted_at	uc_group_id_code_is_deleted_deleted_at	266	const,const,const,const	1	100.00	Using index

EXPLAIN SELECT EXISTS(SELECT * FROM `pa_config_step` WHERE (`pa_config_step`.`code`='writing') AND (`is_deleted`=0))
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	PRIMARY										No tables used
2	SUBQUERY	pa_config_step		ref	idx_code	idx_code	130	const	1	10.00	Using where

EXPLAIN SELECT * FROM `pa_config_task_step` WHERE (`group_id`='015ce30b116ce86058fa6ab4fea4ac63') AND (`config_task_code`='tv_broadcast') AND (`step_code`='begin_shot')
id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
1	SIMPLE	pa_config_task_step		ref	uc_group_id_config_task_code_step_code_is_deleted_deleted_at	uc_group_id_config_task_code_step_code_is_deleted_deleted_at	390	const,const,const	1	100.00	



 

]]>
https://www.shuijingwanwq.com/2019/11/14/3605/feed/ 1
迁移 MySQL 数据库中的 A 表中的一列数据至 B 表中的一列数据,基于一条 SQL 的实现 https://www.shuijingwanwq.com/2019/08/15/3427/ https://www.shuijingwanwq.com/2019/08/15/3427/#comments Thu, 15 Aug 2019 03:07:23 +0000 http://www.shuijingwanwq.com/?p=3427 浏览量: 93 1、表:weibo_weibo_connect_web_app_user 中的字段:permission,准备迁移至表:channel_app_source 中的字段:permission,然后删除掉表:weibo_weibo_connect_web_app_user 中的字段:permission,如图1
表:weibo_weibo_connect_web_app_user 中的字段:permission,准备迁移至表:channel_app_source 中的字段:permission,然后删除掉表:weibo_weibo_connect_web_app_user 中的字段:permission

图1

2、表:channel_app_source 中的字段:permission,表:weibo_weibo_connect_web_app_user 中的字段:channel_app_source_id 与表:channel_app_source 中的字段:id 关联,如图2
表:channel_app_source 中的字段:permission,表:weibo_weibo_connect_web_app_user 中的字段:channel_app_source_id 与表:channel_app_source 中的字段:id 关联

图2

3、SQL 如下,更新表:channel_app_source 中的字段:permission 的值为表:weibo_weibo_connect_web_app_user 中的字段:permission 的值,条件为:(表:channel_app_source 中的字段:id 的值等于表:weibo_weibo_connect_web_app_user 中的字段:channel_app_source_id 的值),影响了 1 行,如图3。查看更新结果,表:channel_app_source 中的字段:permission 的值已经更新为:2,如图4
更新表:channel_app_source 中的字段:permission 的值为表:weibo_weibo_connect_web_app_user 中的字段:permission 的值,条件为:(表:channel_app_source 中的字段:id 的值等于表:weibo_weibo_connect_web_app_user 中的字段:channel_app_source_id 的值),影响了 1 行

图3

 
SQL 如下,更新表:channel_app_source 中的字段:permission 的值为表:weibo_weibo_connect_web_app_user 中的字段:permission 的值,条件为:(表:channel_app_source 中的字段:id 的值等于表:weibo_weibo_connect_web_app_user 中的字段:channel_app_source_id 的值),查看更新结果,表:channel_app_source 中的字段:permission 的值已经更新为:2

图4



UPDATE `cpa_channel_app_source`, `cpa_weibo_weibo_connect_web_app_user` SET `cpa_channel_app_source`.`permission` = `cpa_weibo_weibo_connect_web_app_user`.`permission` WHERE `cpa_channel_app_source`.`id` = `cpa_weibo_weibo_connect_web_app_user`.`channel_app_source_id`;


 ]]>
https://www.shuijingwanwq.com/2019/08/15/3427/feed/ 1
基于 yiisoft/yii2-app-advanced,在 GitHub 上新建仓库 yii2-app-advanced,新建接口应用(实现 RESTful 风格的 Web Service 服务的 API),调整默认字符集为:utf8mb4,接口响应格式的调整,空数组自动转换为空对象,在接口应用中收集请求日志消息(1个请求对应1条日志消息)至数据库,且实现日志功能的相应接口:日志列表(设置数据过滤器以启用筛选器处理)、日志详情(二) https://www.shuijingwanwq.com/2018/06/27/2738/ https://www.shuijingwanwq.com/2018/06/27/2738/#comments Wed, 27 Jun 2018 08:48:57 +0000 http://www.shuijingwanwq.com/?p=2738 浏览量: 303

1、在开发环境中,执行数据库迁移命令失败:1071 Specified key was too long; max key length is 767 bytes,如图17

在开发环境中,执行数据库迁移命令失败:1071 Specified key was too long; max key length is 767 bytes

图17


[root@45fdb670c7c4 /]# php /sobey/www/pcs-api/yii migrate --interactive=0
Yii Migration Tool (based on Yii v2.0.15.1)

Total 1 new migration to be applied:
        m180620_105204_update_table_options_to_log

*** applying m180620_105204_update_table_options_to_log
    > execute SQL: ALTER TABLE {{%user}} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ...Exception: SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes
The SQL being executed was: ALTER TABLE `pa_user` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci (/sobey/www/pcs-api/vendor/yiisoft/yii2/db/Schema.php:664)
#0 /sobey/www/pcs-api/vendor/yiisoft/yii2/db/Command.php(1263): yii\db\Schema->convertException(Object(PDOException), 'ALTER TABLE `pa...')
#1 /sobey/www/pcs-api/vendor/yiisoft/yii2/db/Command.php(1075): yii\db\Command->internalExecute('ALTER TABLE `pa...')
#2 /sobey/www/pcs-api/vendor/yiisoft/yii2/db/Migration.php(219): yii\db\Command->execute()
#3 /sobey/www/pcs-api/console/migrations/m180620_105204_update_table_options_to_log.php(19): yii\db\Migration->execute('ALTER TABLE {{%...')
#4 /sobey/www/pcs-api/vendor/yiisoft/yii2/db/Migration.php(114): m180620_105204_update_table_options_to_log->safeUp()
#5 /sobey/www/pcs-api/vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php(725): yii\db\Migration->up()
#6 /sobey/www/pcs-api/vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php(199): yii\console\controllers\BaseMigrateController->migrateUp('m180620_105204_...')
#7 [internal function]: yii\console\controllers\BaseMigrateController->actionUp(0)
#8 /sobey/www/pcs-api/vendor/yiisoft/yii2/base/InlineAction.php(57): call_user_func_array(Array, Array)
#9 /sobey/www/pcs-api/vendor/yiisoft/yii2/base/Controller.php(157): yii\base\InlineAction->runWithParams(Array)
#10 /sobey/www/pcs-api/vendor/yiisoft/yii2/console/Controller.php(148): yii\base\Controller->runAction('', Array)
#11 /sobey/www/pcs-api/vendor/yiisoft/yii2/base/Module.php(528): yii\console\Controller->runAction('', Array)
#12 /sobey/www/pcs-api/vendor/yiisoft/yii2/console/Application.php(180): yii\base\Module->runAction('migrate', Array)
#13 /sobey/www/pcs-api/vendor/yiisoft/yii2/console/Application.php(147): yii\console\Application->runAction('migrate', Array)
#14 /sobey/www/pcs-api/vendor/yiisoft/yii2/base/Application.php(386): yii\console\Application->handleRequest(Object(yii\console\Request))
#15 /sobey/www/pcs-api/yii(23): yii\base\Application->run()
#16 {main}
*** failed to apply m180620_105204_update_table_options_to_log (time: 0.008s)


0 from 1 migrations were applied.

Migration failed. The rest of the migrations are canceled.


2、在开发环境中的数据库中运行SQL,报错:#1071 – Specified key was too long; max key length is 767 bytes,如图18

在开发环境中的数据库中运行SQL,报错:#1071 - Specified key was too long; max key length is 767 bytes

图18

3、如果使用的是utf8mb4,并且在长度超过191个字符的varchar列上有唯一索引,则需要打开innodb_large_prefix以允许索引中的较大列,因为utf8mb4需要比utf8或latin1更多的存储空间。如果启用innodb_large_prefix(默认值),则对于使用DYNAMIC或COMPRESSED行格式的InnoDB表,索引键前缀限制为3072个字节。如果innodb_large_prefix被禁用,则任何行格式的表的索引键前缀限制为767字节。在本地环境执行迁移命令成功,是因为本地环境的MySQL版本为5.7.19(MySQL 5.7.7 默认启用innodb_large_prefix,Innodb_large_prefix在MySQL 5.7.7中已弃用,并将在未来版本中删除。),而开发环境为 5.6.16,查看网址:https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html ,如图19

如果使用的是utf8mb4,并且在长度超过191个字符的varchar列上有唯一索引,则需要打开innodb_large_prefix以允许索引中的较大列,因为utf8mb4需要比utf8或latin1更多的存储空间。

图19

4、阿里云RDS的参数配置,启用Innodb_large_prefix,如图20

阿里云RDS的参数配置,启用Innodb_large_prefix

图20

5、在开发环境中的数据库中运行SQL,成功,如图21

在开发环境中的数据库中运行SQL,成功

图21


ALTER DATABASE `pcs-api-dev` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci
ALTER TABLE pa_user ROW_FORMAT=DYNAMIC
ALTER TABLE pa_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci


注:一些数据库还可能需要运行


set global innodb_file_format = BARRACUDA;


6、编辑数据库迁移文件,\console\migrations\m180620_105204_update_table_options_to_log.php

<?php
 
use yii\db\Migration;
 
/**
 * Class m180620_105204_update_table_options_to_log
 */
class m180620_105204_update_table_options_to_log extends Migration
{
    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
            $tableOptions = 'CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE=InnoDB';
            $this->execute('ALTER TABLE {{%user}} ROW_FORMAT=DYNAMIC');
            $this->execute('ALTER TABLE {{%user}} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
            $this->execute('ALTER TABLE {{%log}} ROW_FORMAT=DYNAMIC');
            $this->execute('ALTER TABLE {{%log}} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci');
        }
 
        $this->addCommentOnTable('{{%user}}', '用户', $tableOptions);
        $this->addCommentOnTable('{{%log}}', '日志', $tableOptions);
    }
 
    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        echo "m180620_105204_update_table_options_to_log cannot be reverted.\n";
 
        return false;
    }
 
    /*
    // Use up()/down() to run migration code without a transaction.
    public function up()
    {
 
    }
 
    public function down()
    {
        echo "m180620_105204_update_table_options_to_log cannot be reverted.\n";
 
        return false;
    }
    */
}
 

7、配置请求日志允许记录的请求方法,主要是忽略 GET 请求的日志,编辑 \api\config\params.php

<?php
return [
    'adminEmail' => 'admin@example.com',
    'requestLog' => [
        'allowMethod' => ['POST', 'PUT', 'DELETE'], //请求日志允许记录的请求方法
    ],
];

8、在 Postman 中,GET http://api.pcs-api.localhost/v1/logs?login_id=e56db1b43546a110431ac38409ed8e9e&login_tid=49117dc410c491af0de08f2948aecd8f&page=2


{
    "code": 10000,
    "message": "获取日志列表成功",
    "data": {
        "items": [
            {
                "id": 21,
                "level": 4,
                "category": "api\\behaviors\\RequestLogBehavior::afterRequest",
                "log_time": 1530000329.6175,
                "prefix": "[app-api][/v1/config-column-users/1][8]",
                "message": {
                    "url": "/v1/config-column-users/1",
                    "request_query_params": {
                        "id": "1",
                        "login_id": "e56db1b43546a110431ac38409ed8e9e",
                        "login_tid": "49117dc410c491af0de08f2948aecd8f"
                    },
                    "request_body_params": {
                        "users": [
                            {
                                "user_pic": "https://cmcconsole.chinamcloud.com/user_pic/1800000005_13281105967_1529552309.png",
                                "user_mobile": "13281105967",
                                "id": "8",
                                "user_nick": "13281105967",
                                "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
                                "user_type": "1",
                                "update_time": "2018-06-26 15:37:52",
                                "add_time": "2018-04-26 10:05:28",
                                "login_name": "13281105967",
                                "user_token": "fb46626f0e71e423ca8ab4c750620a85",
                                "is_open": "1",
                                "user_email": "13281105967@chinamcloud.com"
                            },
                            {
                                "id": "299",
                                "group_id": "015ce30b116ce86058fa6ab4fea4ac63",
                                "login_name": "test11",
                                "user_token": "670d30e8d2d1f994fc0788d4ce95e0f3",
                                "user_nick": "test11",
                                "user_pic": "https://cmcconsole.chinamcloud.com/imgs/default_header.png",
                                "user_mobile": "",
                                "user_email": "",
                                "is_open": "1",
                                "add_time": "2018-05-15 18:47:43",
                                "update_time": "2018-05-18 15:08:11",
                                "user_type": "2"
                            }
                        ]
                    },
                    "user_id": "8",
                    "$_SERVER": {
                        "HTTP_ACCEPT_LANGUAGE": "zh-CN,zh;q=0.9",
                        "HTTP_ACCEPT": "application/json; version=0.0; cookie=enable",
                        "HTTP_HOST": "api.pcs-api.localhost",
                        "REMOTE_ADDR": "127.0.0.1",
                        "REQUEST_URI": "/v1/config-column-users/1",
                        "REQUEST_METHOD": "PUT",
                        "CONTENT_TYPE": "application/x-www-form-urlencoded; charset=utf-8"
                    }
                }
            },
            {
                "id": 22,
                "level": 4,
                "category": "api\\behaviors\\RequestLogBehavior::afterRequest",
                "log_time": 1530000396.3784,
                "prefix": "[app-api][/v1/config-columns][8]",
                "message": {
                    "url": "/v1/config-columns",
                    "request_query_params": {
                        "login_id": "e56db1b43546a110431ac38409ed8e9e",
                        "login_tid": "49117dc410c491af0de08f2948aecd8f"
                    },
                    "request_body_params": {
                        "status": "1",
                        "code": "wxbj",
                        "name": "无线北京"
                    },
                    "user_id": "8",
                    "$_SERVER": {
                        "HTTP_ACCEPT_LANGUAGE": "zh-CN,zh;q=0.9",
                        "HTTP_ACCEPT": "application/json; version=0.0; cookie=enable",
                        "HTTP_HOST": "api.pcs-api.localhost",
                        "REMOTE_ADDR": "127.0.0.1",
                        "REQUEST_URI": "/v1/config-columns",
                        "REQUEST_METHOD": "POST",
                        "CONTENT_TYPE": "application/x-www-form-urlencoded; charset=utf-8"
                    }
                }
            },
            {
                "id": 23,
                "level": 4,
                "category": "api\\behaviors\\RequestLogBehavior::afterRequest",
                "log_time": 1530000418.5957,
                "prefix": "[app-api][/v1/config-columns/3][8]",
                "message": {
                    "url": "/v1/config-columns/3",
                    "request_query_params": {
                        "id": "3",
                        "login_id": "e56db1b43546a110431ac38409ed8e9e",
                        "login_tid": "49117dc410c491af0de08f2948aecd8f"
                    },
                    "request_body_params": {},
                    "user_id": "8",
                    "$_SERVER": {
                        "HTTP_ACCEPT_LANGUAGE": "zh-CN,zh;q=0.9",
                        "HTTP_ACCEPT": "application/json; version=0.0; cookie=enable",
                        "HTTP_HOST": "api.pcs-api.localhost",
                        "REMOTE_ADDR": "127.0.0.1",
                        "REQUEST_URI": "/v1/config-columns/3",
                        "REQUEST_METHOD": "DELETE",
                        "CONTENT_TYPE": "application/json; charset=utf-8"
                    }
                }
            }
        ],
        "_links": {
            "self": {
                "href": "http://api.pcs-api.localhost/v1/logs?login_id=e56db1b43546a110431ac38409ed8e9e&login_tid=49117dc410c491af0de08f2948aecd8f&page=2"
            },
            "first": {
                "href": "http://api.pcs-api.localhost/v1/logs?login_id=e56db1b43546a110431ac38409ed8e9e&login_tid=49117dc410c491af0de08f2948aecd8f&page=1"
            },
            "prev": {
                "href": "http://api.pcs-api.localhost/v1/logs?login_id=e56db1b43546a110431ac38409ed8e9e&login_tid=49117dc410c491af0de08f2948aecd8f&page=1"
            }
        },
        "_meta": {
            "totalCount": 23,
            "pageCount": 2,
            "currentPage": 2,
            "perPage": 20
        }
    }
}


]]>
https://www.shuijingwanwq.com/2018/06/27/2738/feed/ 2