Halo 2.0 插件开发快速开始模板(WIP)
.
├── LICENSE
├── README.md
├── admin-frontend
│ ├── README.md
│ ├── env.d.ts
│ ├── package.json
│ ├── pnpm-lock.yaml
│ ├── src
│ │ ├── assets
│ │ │ └── logo.svg
│ │ ├── components
│ │ │ └── HelloWorld.vue
│ │ ├── index.ts # Admin Frontend Entry file
│ │ ├── styles
│ │ │ └── index.css
│ │ └── views
│ │ └── DefaultView.vue # Views Component
│ ├── tsconfig.app.json
│ ├── tsconfig.config.json
│ ├── tsconfig.json
│ ├── tsconfig.vitest.json
│ └── vite.config.ts
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lib
│ └── halo-2.0.0-SNAPSHOT-plain.jar
├── settings.gradle
└── src
└── main
├── java
│ └── run
│ └── halo
│ └── template
│ ├── Apple.java
│ ├── ApplesController.java
│ └── TemplatePlugin.java # Main Class
└── resources
├── admin
│ ├── main.js # Admin Frontend Entry file(production build)
│ └── style.css
├── extensions
│ ├── apple.yaml
│ ├── reverseProxy.yaml # Reverse Proxy Config
│ ├── roleTemplate.yaml # Role Template Config
│ └── settings.yaml # Settings Config
├── plugin.yaml # Plugin Config
└── static
├── image.jpeg
├── some.txt
└── test.html
对于以上目录树:
-
admin-frontend
: 插件前端项目目录,为一个 Vue 项目,技术栈为 Vue 3 + Vite,其中已经预配置好了构建策略。 -
build
:插件后端构建目录,build/libs
下的 jar 包为最终插件产物。 -
lib
:为临时的 Halo 依赖,为了使用 Halo 中提供的类在build.gradle
中作为编译时依赖引入compileOnly files("lib/halo-2.0.0-SNAPSHOT-plain.jar")
,待2.0
正式发布会将其发布到maven
中央仓库,便可通过gradle
依赖。 -
src
: 为插件后端源码目录。 -
Apple.java
: 为自定义模型。@GVK(group = "apple.guqing.xyz", kind = "Apple", version = "v1alpha1", singular = "apple", plural = "apples") @Data @EqualsAndHashCode(callSuper = true) public class Apple extends AbstractExtension { }
关键在于标注
@GVK
注解和extends AbstractExtension
,当如此定义了一个模型后,插件启动时会根据@GVK
配置自动生成CRUD
的RESTful API
,以此为例子会生成如下APIs
GET /apis/apple.guqing.xyz/v1alpha1/apples POST /apis/apple.guqing.xyz/v1alpha1/apples GET /apis/apple.guqing.xyz/v1alpha1/apples/{name} PUT /apis/apple.guqing.xyz/v1alpha1/apples/{name} DELETE /apis/apple.guqing.xyz/v1alpha1/apples/{name}
生成规则见:Halo extension RFC
-
TemplatePlugin.java
:插件生命周期入口,它继承BasePlugin
,可以通过getApplicationContext()
方法获取到SchemeManager
,然后在start()
方法中注册自定义模型,这一步必不可少,所有定义的自定义模型都需要在此注册,并在stop()
生命周期方法中清理资源。public class TemplatePlugin extends BasePlugin { // ... @Override public void start() { schemeManager.register(Apple.class); } @Override public void stop() { Scheme scheme = schemeManager.get(Apple.class); schemeManager.unregister(scheme); } // ... }
注意:该类不能标注
@Component
等能将其声明为Spring Bean
的注解 -
ApplesController.java
:如果根据模型自动生成的CURD RESTful APIs
无法满足业务需要,可以写常规Controller
来自定义APIs
,示例:@ApiVersion("v1alpha1") @RestController @RequestMapping("colors") public class ApplesController { @GetMapping public Mono<String> hello() { return Mono.just("Hello world"); } }
插件定义
Controller
必须要标注@ApiVersion
注解,否则启动时会报错。如果定义了这样的Controller
,插件启动后会生成如下的APIs
:GET /api/v1alpha1/plugins/apples/colors
生成规则为
/api/{version}/plugins/{plugin-name}/{mapping-in-class}/{mapping-in-method}
其中:
version
:来自@ApiVersion("v1alpha1")
的 value,详情参考:Halo plugin API compositionplugin-name
:值来自plugin.yaml
中的metadata.name
属性mapping-in-class
:来自标注在类上的@RequestMapping("colors")
mapping-in-method
:来自标注在方法上的@GetMapping
插件还允许使用
@Service
、@Component
注解将其声明为一个Spring Bean
,便可通过依赖注入使用Spring Bean
,示例:@Service public class ColorService { public String getColor(String appleName) { return "red"; } } @ApiVersion("v1alpha1") @RestController @RequestMapping("colors") public class ApplesController { private final ColorService colorService; // 构造器注入,当然也同样允许 @Autowired 注入 和 setter 方法注入 public ApplesController(ColorService colorService) { this.colorService = colorService; } }
-
resources
:目录为插件资源目录admin
目录下为插件前端打包后的产物存放目录,固定为main.js
和style.css
两个文件extensions
存放自定义模型资源配置plugin.yaml
为插件描述配置static
为静态资源示例目录
插件启动时会加载
extensions
目录的yaml
保存到数据库,apple.yaml
为本项目自定义的模型的数据示例,当启用插件后调用GET /apis/apple.guqing.xyz/v1alpha1/apples
便可查询到
apple.yaml
中定义的记录[ { "spec": { "varieties": "Fuji", "color": "red", "size": "middle", "producingArea": "China" }, "apiVersion": "apple.guqing.xyz/v1alpha1", "kind": "Apple", "metadata": { "name": "Fuji-apple", "labels": { "plugin.halo.run/plugin-name": "apples" }, "version": 0, "creationTimestamp": "2022-06-24T04:03:22.890741Z" } } ]
reverseProxy.yaml
为 Halo 中提供的模型,表示反向代理,允许插件配置规则代理到插件目录apiVersion: plugin.halo.run/v1alpha1 kind: ReverseProxy metadata: name: reverse-proxy-template rules: - path: /static/** file: directory: static
它表示访问
GET /assets/{plugin-name}/static/**
时代理访问到插件的resources/static
目录,本插件便可访问到一下静态资源GET /assets/apples/static/image.jpeg GET /assets/apples/static/some.txt GET /assets/apples/static/test.html
roleTemplate.yaml
文件中kind: Role
表示插件允许提供角色模版,定义格式如下:apiVersion: v1alpha1 kind: Role metadata: name: a name here labels: plugin.halo.run/role-template: "true" annotations: plugin.halo.run/module: "module name" plugin.halo.run/alias-name: "display name" rules: # ...
必须带有
plugin.halo.run/role-template: "true"
labels,表示该角色为角色模版,当用户启用插件后,创建角色或修改角色时会将其列在权限列表位置。插件如果不提供角色模版除非是超级管理员否则其他账号没有权限访问,因为 Halo 规定
/api
和/apis
开头的api
都需要授权才能访问,因此插件不提供角色模版的自定义资源,就无法将其分配给用户。
更多详情参考:Halo security RFC
- OpenJDK 17
- NodeJS 16+
- pnpm 7+
mkdir ./halo-dev
mkdir ./halo-dev/dev-plugins # 存放插件源码
cd ./halo-dev
git clone https://github.com/halo-dev/halo --branch next
git clone https://github.com/halo-dev/halo-admin --branch next
cd ./dev-plugins
git clone https://github.com/halo-sigs/plugin-template
修改 halo/src/main/resources/application-dev.yaml
halo:
security:
initializer:
super-admin-username: admin
super-admin-password: P@88w0rd
oauth2:
jwt:
jwsAlgorithm: rs512
public-key-location: classpath:app.pub
private-key-location: classpath:app.key
plugin:
runtime-mode: development # development, deployment
classes-directories:
- "build/classes"
- "build/resources"
lib-directories:
- "libs"
plugins-root: /Users/ryanwang/Workspace/github/ruibaby/halo-dev/dev-plugins # 修改为上方存放插件源码的实际目录
initial-extension-locations: # 初始化资源加载配置,需要配置当前开发中插件的资源目录或者文件
- "/Users/ryanwang/Workspace/github/ruibaby/halo-dev/dev-plugins/plugin-template/src/main/resources/plugin.yaml"
下载前端依赖:
cd ./halo-dev/dev-plugins/plugin-template
./gradlew.bat pnpmInstall
# or macOS/Linux
./gradlew pnpmInstall
构建:
./gradlew.bat build
# or macOS/Linux
./gradlew build
cd ./halo-dev/halo
./gradlew.bat bootRun --args="--spring.profiles.active=dev"
# or macOS/Linux
./gradlew bootRun --args="--spring.profiles.active=dev"
或者在 IntelliJ IDEA 中运行 Application 启动类。但注意需要配置好 spring.profiles.active
为 dev。
cd ./halo-dev/halo-admin
pnpm install
pnpm build:packages
pnpm dev
在浏览器中访问 https://localhost:3000 即可,登录用户名和密码为上方 application-dev.yaml
配置中的 super-admin-username
和 super-admin-password
。
然后在左侧菜单中选择 插件
,即可查看所有插件的状态。
修改前端代码或者后端代码,然后运行 ./gradlew.bat build
或者 ./gradlew build
(macOS/Linux)即可构建插件,无需重启 Halo。但修改配置文件后需要 build 插件以及重启 Halo。
./gradlew -x build
然后只需复制例如build/libs/plugin-template-0.0.1-SNAPSHOT-plain.jar
的 jar
包即可使用。