|
|
22 시간 전 | |
|---|---|---|
| config | 7 달 전 | |
| ffi | 5 달 전 | |
| frontend | 22 시간 전 | |
| route | 6 달 전 | |
| src | 5 달 전 | |
| .gitignore | 7 달 전 | |
| README.md | 5 달 전 | |
| build.sh | 7 달 전 | |
| composer.json | 7 달 전 | |
| config.php | 7 달 전 | |
| info.php | 7 달 전 |
本模块为 SixShop 系统提供资讯管理功能。其核心业务逻辑(数据库 CRUD)完全由 Go 语言实现,并通过 PHP FFI 进行调用。这种方式可以有效保护核心业务逻辑代码,同时利用 Go 的高性能特性。
.so) 中执行。backend/extension/news/
├── config/
│ ├── install.sql # 数据库表结构
│ └── uninstall.sql # 卸载表结构
├── ffi/
│ ├── main.go # Go FFI 业务逻辑和导出函数
│ ├── go.mod # Go 依赖管理
│ ├── Makefile # 一键构建 Go 共享库的脚本
│ ├── model/
│ │ ├── category.go # 资讯分类 GORM 模型
│ │ └── news.go # 资讯文章 GORM 模型
│ ├── lib_news.so # [构建产物] Go 编译的共享库
│ └── lib_news.h # [构建产物] Go 编译的头文件
├── route/
│ ├── admin.php # 后台路由
│ └── api.php # 前端API路由
├── src/
│ ├── Controller/
│ ├── Model/ # ThinkPHP 模型 (仅用于框架集成, 不含业务逻辑)
│ ├── Service/
│ │ ├── NewsCategoryService.php # 分类服务的 FFI 调用封装
│ │ ├── NewsService.php # 资讯服务的 FFI 调用封装
│ │ └── NewsFfiService.php # [核心] FFI 单例服务, 负责加载和调用 .so 文件
│ └── Hook/
├── news.info.yml # 模块信息
└── README.md # 模块说明文档
config/install.sql 文件,以创建 cy_news_category 和 cy_news 两张表。Makefile 中的构建命令)以下步骤以宝塔面板安装的 PHP 8.3 为例,其他版本仅需替换路径中的版本号。
cd /www/server/php/83/src/ext/ffi
若提示目录不存在,请先在宝塔面板的 PHP 设置中执行一次“重新安装源码”。
phpize
执行成功后会生成 configure 等构建文件。
./configure --with-php-config=/www/server/php/83/bin/php-config
make && make install
完成后系统会在 /www/server/php/83/lib/php/extensions/ 下生成 ffi.so。
在宝塔面板中打开 PHP 8.3 的 php.ini,或直接修改 /www/server/php/83/etc/php.ini,追加:
extension=ffi
ffi.enable=true
如果使用 PHP-FPM,请同步在 php.d 或额外的配置文件中开启上述参数,确保 CLI 与 FPM 环境都能加载 FFI。
在宝塔面板中重启对应 PHP 版本的服务,或执行:
bt restart php 83
/www/server/php/83/bin/php -m | grep FFI
若输出包含 FFI,说明扩展已启用。也可使用 php -i | grep -i ffi 查看详细配置。
完成以上步骤后,即可在 news 插件中通过 PHP FFI 加载 Go 动态库。
所有 Go 源代码的编译都通过 Makefile 完成。
# 进入 ffi 目录
cd backend/extension/news/ffi
# 执行构建
make build
该命令会使用 Docker 容器来编译 Go 代码,确保构建环境的一致性。成功后,会在当前目录下生成 lib_news.so 和 lib_news.h 两个文件。
NewsFfiService 通过单例模式加载 lib_news.so 文件,并根据 lib_news.h 的内容定义 C 函数签名。NewsService 和 NewsCategoryService 在实例化时会获取 NewsFfiService 的单例。NewsService 的方法时(如 getList),NewsService 会将请求参数(如查询条件、分页信息)打包成 JSON 字符串。NewsService 调用 NewsFfiService 的相应方法,将 JSON 字符串作为参数传递给 Go 函数。NewsFfiService 接收返回的 JSON 字符串,并将其解码为 PHP 数组,最终返回给控制器。所有业务逻辑都已封装在以下 Go 函数中,并通过 FFI 导出:
GetCategoryList(char* whereJson)GetCategoryByID(int id)CreateCategory(char* dataJson)UpdateCategory(int id, char* dataJson)DeleteCategory(int id)GetNewsList(char* paramsJson)GetNewsByID(int id)CreateNews(char* dataJson)UpdateNews(int id, char* dataJson)DeleteNews(int id)NewsFfiService.php 中硬编码了 lib_news.so 的相对路径。如果移动文件,需要同步更新。ffi/ 目录下 .go 文件的修改,都必须重新执行 make build 才能生效。error 键的 JSON 字符串形式返回,PHP 服务层应进行相应的检查和处理。在 news 模块的控制器中,存在两种不同的服务层依赖注入(或实例化)方式。这两种方式在设计上存在差异,体现了不同的架构思路。
此方式应用于 src/Controller/Admin/ 目录下的所有控制器。
实现: 控制器在其构造函数中,通过 new 关键字直接实例化一个适配器 (NewsServiceAdapter 或 NewsCategoryServiceAdapter)。
// 文件: backend/extension/news/src/Controller/Admin/NewsController.php
use SixShop\News\Service\NewsServiceAdapter;
class NewsController
{
protected $service;
public function __construct()
{
// 直接实例化适配器
$this->service = new NewsServiceAdapter();
}
// ...
}
优点:
结论: 这是项目推荐的、更健壮、更灵活的设计模式。
此方式应用于 src/Controller/Api/ 目录下的控制器。
实现: 控制器在其构造函数的参数中,声明需要注入的具体服务 (NewsService),由框架的依赖注入容器自动实例化并传入。
// 文件: backend/extension/news/src/Controller/Api/NewsController.php
use SixShop\News\Service\NewsService;
class NewsController
{
private $service;
public function __construct(NewsService $service)
{
// 由框架注入具体的 NewsService 实例
$this->service = $service;
}
// ...
}
缺点:
NewsService 类紧密绑定。改进建议: 为了保持整个模块架构的一致性,建议未来将 Api 控制器的实现方式重构为方式一(使用适配器)。
本插件提供两组符合 RESTful 规范的资源路由,分别用于后台管理和前端调用。
路由前缀: /admin/extension/news
所有后台接口均会通过 Auth 中间件进行权限验证。
/category)GET /category
name 字段模糊查询。GET /admin/extension/news/category?name=技术GET /category/{id}
POST /category
{ "name": "新分类", "sort": 100, "status": 1 }PUT /category/{id}
{ "name": "更新后的分类", "sort": 99 }DELETE /category/{id}
/news)GET /news
title 和 category_id 查询。GET /admin/extension/news/news?title=六店&category_id=1&page=1&limit=10GET /news/{id}
POST /news
{ "category_id": 1, "title": "文章标题", "content": "文章内容...", "status": 1 }PUT /news/{id}
{ "title": "更新后的标题" }DELETE /news/{id}
路由前缀: /api/extension/news
/category)GET /category
/news)GET /news
category_id 筛选和分页。GET /api/extension/news/news?category_id=1&page=1&limit=10GET /news/{id}
config/install.sql,自动创建资讯分类表(cy_news_category)和资讯文章表(cy_news)。config/uninstall.sql,自动删除本模块相关表结构。推荐在模块安装、升级、卸载时自动调用对应 SQL,便于独立管理和维护。
本模块内置 Go FFI 动态库(ffi/ 目录),用于高性能、可闭源的资讯业务处理,PHP 可通过 FFI 调用。
安装依赖:
cd ffi
go mod tidy
config/db.yaml推荐用 Makefile 一键构建:
cd ffi
make build
# 生成 lib_news.so 和 lib_news.h
也可用 Dockerfile 构建
GetList() 获取资讯列表(JSON)GetById(id) 获取单条资讯详情(JSON)Create(json) 新增资讯,参数为 JSONUpdate(id, json) 编辑资讯Delete(id) 删除资讯GenerateSummary(content) 生成摘要CheckContent(content) 内容审核ExtractKeywords(content) 关键词提取$ffi = FFI::cdef(file_get_contents('lib_news.h'), 'lib_news.so');
$list = json_decode(FFI::string($ffi->GetList()), true);
$info = json_decode(FFI::string($ffi->GetById(1)), true);
$id = $ffi->Create(json_encode($data));
$ok = $ffi->Update($id, json_encode($data));
$ok = $ffi->Delete($id);
$summary = FFI::string($ffi->GenerateSummary($content));
$isIllegal = $ffi->CheckContent($content);
$keywords = explode(',', FFI::string($ffi->ExtractKeywords($content)));
推荐使用 shell 脚本(需 bash 环境):
cd backend/extension/news
./build.sh
或使用 PHP 脚本(适合 PHP-only 环境):
cd backend/extension/news
php build.php
build/build.log,便于排查问题。build/ 目录内容打包为 build/package.zip,方便分发和备份。build/lib_news.so、build/lib_news.h:Go FFI 动态库及头文件build/NewsServiceFFI.php:PHP FFI 适配层,控制器/接口直接调用build/package.zip:自动打包的分发包build/build.log:编译和打包日志require_once __DIR__ . '/../build/NewsServiceFFI.php';
$service = new NewsService();
$list = $service->getList();
为实现开发模式(PHP Service)与分发模式(FFI 适配层)的无缝切换,所有控制器均依赖适配层(如 NewsServiceAdapter、NewsCategoryServiceAdapter),而不直接依赖具体 Service 实现。
$service = new NewsServiceAdapter();
$list = $service->getList();
适配层代码位于 src/Service/NewsServiceAdapter.php、src/Service/NewsCategoryServiceAdapter.php,可根据实际业务扩展更多方法。