RoutineCI.php 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
  8. // +----------------------------------------------------------------------
  9. // | Author: CRMEB Team <admin@crmeb.com>
  10. // +----------------------------------------------------------------------
  11. namespace app\adminapi\controller\v1\application\routine;
  12. use app\adminapi\controller\AuthController;
  13. use app\services\system\NodeEnvironmentServices;
  14. use app\services\wechat\RoutineCIServices;
  15. use think\facade\App;
  16. /**
  17. * 小程序 CI 自动化上传控制器
  18. *
  19. * 功能概述:
  20. * 本控制器提供微信小程序代码自动化上传的 API 接口,基于微信官方的 miniprogram-ci 工具实现。
  21. * 通过这些接口,管理员可以在后台直接将小程序代码上传到微信服务器,无需使用微信开发者工具。
  22. *
  23. * 主要功能:
  24. * 1. 环境检测 - 检测服务器是否已安装 Node.js 和 miniprogram-ci
  25. * 2. 安装指南 - 提供不同操作系统的环境安装说明
  26. * 3. 配置管理 - 管理小程序上传密钥和 AppId 配置
  27. * 4. 代码上传 - 将小程序代码上传到微信开发版
  28. * 5. 预览功能 - 生成小程序预览二维码进行测试
  29. *
  30. * 使用前提:
  31. * - 服务器已安装 Node.js (>=14.0.0) 和 npm
  32. * - 已全局安装 miniprogram-ci (npm install miniprogram-ci -g)
  33. * - 已在微信公众平台获取小程序代码上传密钥
  34. * - 服务器 PHP 的 exec() 函数未被禁用
  35. *
  36. * @see https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html 微信官方 CI 文档
  37. * @package app\adminapi\controller\v1\application\routine
  38. */
  39. class RoutineCI extends AuthController
  40. {
  41. /**
  42. * Node.js 环境检测服务实例
  43. *
  44. * 用于检测服务器环境是否满足小程序上传的要求:
  45. * - Node.js 版本检测
  46. * - npm 可用性检测
  47. * - miniprogram-ci 安装状态检测
  48. * - 操作系统类型识别
  49. *
  50. * @var NodeEnvironmentServices
  51. */
  52. protected $envServices;
  53. /**
  54. * 小程序 CI 核心服务实例
  55. *
  56. * 处理小程序代码上传的核心业务逻辑:
  57. * - 上传密钥管理
  58. * - 项目文件准备
  59. * - 执行 miniprogram-ci 命令
  60. * - 生成预览二维码
  61. *
  62. * @var RoutineCIServices
  63. */
  64. protected $ciServices;
  65. /**
  66. * 构造方法 - 初始化服务依赖
  67. *
  68. * 通过依赖注入方式注入所需的服务类实例,
  69. * ThinkPHP 的容器会自动解析并注入这些依赖。
  70. *
  71. * @param App $app ThinkPHP 应用实例
  72. * @param NodeEnvironmentServices $envServices 环境检测服务
  73. * @param RoutineCIServices $ciServices CI 上传服务
  74. */
  75. public function __construct(App $app, NodeEnvironmentServices $envServices, RoutineCIServices $ciServices)
  76. {
  77. parent::__construct($app);
  78. $this->envServices = $envServices;
  79. $this->ciServices = $ciServices;
  80. }
  81. /**
  82. * 获取服务器运行环境状态
  83. *
  84. * 检测并返回小程序上传所需的所有环境信息,前端根据返回结果
  85. * 展示环境就绪状态或引导用户完成环境配置。
  86. *
  87. * 返回数据结构:
  88. * - os: 操作系统信息 (family, type, version)
  89. * - node: Node.js 状态 (installed, version, path, meets_requirement)
  90. * - npm: npm 状态 (installed, version)
  91. * - miniprogram_ci: CI工具状态 (installed, version)
  92. * - ready: 布尔值,环境是否完全就绪
  93. * - can_install: 是否支持自动安装
  94. * - exec_enabled: exec函数是否可用
  95. * - message: 提示信息
  96. *
  97. * @return mixed JSON 响应,包含完整的环境状态信息
  98. */
  99. public function environment()
  100. {
  101. $data = $this->envServices->getEnvironmentStatus();
  102. return app('json')->success($data);
  103. }
  104. /**
  105. * 获取环境安装指南
  106. *
  107. * 根据服务器操作系统类型返回对应的 Node.js 和 miniprogram-ci 安装步骤。
  108. * 支持的操作系统: CentOS/RHEL、Ubuntu/Debian、macOS、Windows
  109. *
  110. * 返回数据结构:
  111. * - title: 指南标题 (如 "CentOS/RHEL 安装指南")
  112. * - steps: 安装步骤数组,包含命令行指令
  113. * - script_url: 一键安装脚本的 URL 地址
  114. *
  115. * @return mixed JSON 响应,包含适合当前系统的安装指南
  116. */
  117. public function installGuide()
  118. {
  119. $guide = $this->envServices->getInstallGuide();
  120. return app('json')->success($guide);
  121. }
  122. /**
  123. * 获取小程序上传配置状态
  124. *
  125. * 返回当前的上传配置信息,用于前端展示配置状态和引导配置流程。
  126. *
  127. * 返回数据结构:
  128. * - app_id: 小程序 AppId
  129. * - app_id_configured: AppId 是否已配置
  130. * - private_key_exists: 上传密钥文件是否存在
  131. * - private_key_path: 密钥文件存储路径
  132. * - project_path: 小程序项目路径
  133. * - project_exists: 项目目录是否存在
  134. *
  135. * @return mixed JSON 响应,包含上传配置状态信息
  136. */
  137. public function uploadConfig()
  138. {
  139. $config = $this->ciServices->getUploadConfig();
  140. return app('json')->success($config);
  141. }
  142. /**
  143. * 保存小程序代码上传密钥
  144. *
  145. * 接收并保存从微信公众平台下载的小程序代码上传密钥。
  146. * 密钥用于 miniprogram-ci 工具的身份验证,确保只有授权用户才能上传代码。
  147. *
  148. * 请求参数:
  149. * - key_content: string, 必填,RSA 私钥内容 (以 -----BEGIN RSA PRIVATE KEY----- 开头)
  150. *
  151. * 密钥获取方式:
  152. * 微信公众平台 -> 开发管理 -> 开发设置 -> 小程序代码上传 -> 下载密钥
  153. *
  154. * 安全说明:
  155. * - 密钥文件保存在 config/routine_private.key
  156. * - 文件权限设置为 0600,仅所有者可读写
  157. * - 请勿将密钥文件提交到版本控制系统
  158. *
  159. * @return mixed JSON 响应,成功返回提示信息,失败返回错误原因
  160. */
  161. public function savePrivateKey()
  162. {
  163. // 获取 POST 请求中的密钥内容
  164. $keyContent = $this->request->post('key_content', '');
  165. // 验证密钥内容不能为空
  166. if (empty($keyContent)) {
  167. return app('json')->fail('请提供密钥内容');
  168. }
  169. // 调用服务层保存密钥(服务层会验证密钥格式)
  170. $this->ciServices->savePrivateKey($keyContent);
  171. return app('json')->success('密钥保存成功');
  172. }
  173. /**
  174. * 上传小程序代码到微信开发版
  175. *
  176. * 将本地小程序项目代码上传到微信服务器的开发版本。
  177. * 上传成功后,可在微信公众平台的版本管理中看到新上传的开发版本。
  178. *
  179. * 请求参数:
  180. * - version: string, 必填,版本号,格式为 x.x.x (如 1.0.0)
  181. * - desc: string, 可选,版本描述,默认为 "版本 {version}"
  182. * - is_live: int, 可选,是否开启直播功能,0=关闭 1=开启,默认关闭
  183. *
  184. * 执行流程:
  185. * 1. 验证版本号格式
  186. * 2. 检查运行环境 (Node.js、密钥文件等)
  187. * 3. 准备项目文件 (复制、替换配置)
  188. * 4. 执行 miniprogram-ci upload 命令
  189. * 5. 返回上传结果
  190. *
  191. * 返回数据结构:
  192. * - success: 是否成功
  193. * - version: 版本号
  194. * - desc: 版本描述
  195. * - message: 提示信息
  196. * - output: 命令执行输出
  197. *
  198. * @return mixed JSON 响应,包含上传结果信息
  199. */
  200. public function upload()
  201. {
  202. // 批量获取请求参数
  203. [$version, $desc, $isLive] = $this->request->postMore([
  204. ['version', ''], // 版本号
  205. ['desc', ''], // 版本描述
  206. ['is_live', 0], // 是否开启直播
  207. ], true);
  208. // 验证版本号必填
  209. if (empty($version)) {
  210. return app('json')->fail('请输入版本号');
  211. }
  212. // 验证版本号格式:必须是 x.x.x 格式 (如 1.0.0, 2.1.3)
  213. if (!preg_match('/^\d+\.\d+\.\d+$/', $version)) {
  214. return app('json')->fail('版本号格式错误,请使用 x.x.x 格式');
  215. }
  216. // 调用服务层执行上传
  217. $result = $this->ciServices->upload($version, $desc, (bool)$isLive);
  218. return app('json')->success($result);
  219. }
  220. /**
  221. * 获取小程序预览二维码
  222. *
  223. * 生成小程序预览二维码,扫码后可在手机上预览小程序效果。
  224. * 预览版本不会影响线上版本,适合开发测试使用。
  225. *
  226. * 请求参数:
  227. * - page_path: string, 可选,预览的页面路径 (如 pages/index/index)
  228. * 为空时默认预览首页
  229. *
  230. * 执行流程:
  231. * 1. 检查运行环境
  232. * 2. 准备项目文件
  233. * 3. 执行 miniprogram-ci preview 命令
  234. * 4. 生成二维码图片
  235. * 5. 返回二维码图片 URL
  236. *
  237. * 返回数据结构:
  238. * - success: 是否成功
  239. * - qrcode_url: 二维码图片的访问 URL
  240. * - message: 提示信息
  241. * - output: 命令执行输出
  242. *
  243. * 注意事项:
  244. * - 预览二维码有效期较短,过期需重新生成
  245. * - 只有小程序的开发者和体验者才能扫码预览
  246. *
  247. * @return mixed JSON 响应,包含预览二维码信息
  248. */
  249. public function preview()
  250. {
  251. // 获取预览页面路径参数
  252. $pagePath = $this->request->post('page_path', '');
  253. // 调用服务层生成预览二维码
  254. $result = $this->ciServices->preview($pagePath);
  255. return app('json')->success($result);
  256. }
  257. }