# wujies-hello-plugin **Repository Path**: wujies/wujies-hello-plugin ## Basic Information - **Project Name**: wujies-hello-plugin - **Description**: Hello 示例插件 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-23 - **Last Updated**: 2026-10-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Hello 示例插件 > **最小可运行的插件模板。** 新插件的起点,也是"沙箱能不能用"的对照组。 > > **想写一个业务 CRUD(列表 + 查询 + 新增 + 编辑 + 删除)?直接照第五组演示抄** —— > 后端 `controller/HelloItemController` + `service/impl/HelloItemServiceImpl` + `domain/{bo,vo}/HelloItem*` > + `mapper/HelloItemMapper`,前端 `frontend/src/views/DemoCrud.vue` > + `src/api/hello/item/`。这是**平台的标准写法**(与 `wujies-gallery` 逐条同形), > 别自创。详细对照见下面〈五组演示分别在哪几个文件〉。 --- ## 它是什么 一个刻意做到最小的插件:**一张表(`hello_item`)、五组演示、前后端同一个包**。 | 位置 | 里面有什么 | |---|---| | `app.json` | 应用清单(编码 / 版本 / 菜单树 / 产物路径)—— 打包器读的那份真相来源 | | `pom.xml` | 只依赖 7 个平台基础库(`wujies-common-core` / `-web` / `-mybatis` / `-excel` / `-tenant` / `-satoken` / `-plugin-sdk`),**不依赖 `wujies-system`、也没有自己的 `*-api`**;另加兄弟插件 `wujies-member-plugin` 与 SnailJob 客户端,一律由父 POM 统一为 `provided`(`-tenant` 是 `HelloItem extends TenantEntity` 需要的,`-excel` 只给 VO 上的 `@ExcelProperty` 用) | | `src/main/java/cn/wujies/hello/` | **15 个 Java 文件**:基础 controller + 3 个演示 controller + **1 个标准 CRUD controller** + 1 个 open controller + 2 个 service 接口 + 2 个 impl + mapper + 实体 + **Bo/Vo** + 2 个定时任务 | | `migrations/` | **4 个脚本**:`01` 建表 / `02` 种子数据 / `03` 站点配置 / `04` CRUD 演示补充种子数据(随包走,安装时自动执行) | | `frontend/` | **前端工程与后端同仓**:`src/index.ts` + **4 个 `.vue` 页面** + **`src/api/hello/item/`**;依赖是已发布的 `@wujies/*` npm 包,**不引用宿主源码**,`npm install && npm run build` 可独立构建 | 它刻意**不**用 MapStruct 转换器以外的机制、不写自定义 SQL。 理由:模板的价值在于"一条命令就能验通",不该掺任何需要额外解释的东西。 > ⚠️ 严格说,标准 CRUD 那一组用到了 `@AutoMapper` + `MapstructUtils.convert` > (MapStruct-Plus 的注解处理器已在父 POM 里配好)。这不是"额外的机制"—— > **它是平台 CRUD 的标准写法**,其它插件(gallery / mall / member …)全都这么写, > 绕开它反而要手写 `BeanUtils.copyProperties`,那才是自创。 ⚠️ **但它必须继承 `TenantEntity`**(1.0.1 起)—— 这不是"额外的机制",而是**平台默认设计的组成部分**: 平台的租户行拦截器会给每一条未列入 `security.tenant.excludes` 的表的 SQL 补 `tenant_id = '000000'`,实体不继承、表里也没有 `tenant_id` 列的组合,症状是只有**真去查那张表**的接口报 `Unknown column 'tenant_id' in 'where clause'`(同一插件的其它接口完全正常,极难定位)。 详见下面〈插件自己的表:要么带 tenant_id,要么进 tenant.excludes〉。 ⚠️ **Mapper XML 随插件 jar 走,平台里已经没有 `*-api` 契约模块了** —— 见下面〈与平台约定的对照〉。 --- ## 五组演示分别在哪几个文件 后端源码全在 `src/main/java/cn/wujies/hello/` 下,前端页面对应 `frontend/src/views/`,菜单里的 `component` 是 `remote:hello/<视图键>`。 | 演示 | 后端源码 | 前端页面 | 打什么验 | |---|---|---|---| | **1. 调用宿主系统服务**(`ConfigService` / `OssService` / `LoginHelper`) | `controller/HelloDemoHostController` | `demo/home`(运行时探针) | `GET /hello/demo/host/whoami` | | **2. 跨插件调用**(**类可见 vs bean 可见** 的对照,本项目最容易踩的点) | `controller/HelloDemoCrossPluginController` | `demo/plugin-dev` 分区 2 | `GET /hello/demo/cross/service?userId=1` | | **3. 从插件开放 `/open` 接口**(`@SaIgnore` + `/open/hello/**`,**宿主零改动**) | `open/HelloDemoOpenController` | `demo/plugin-dev` 分区 3 | `GET /open/hello/greet`(**不带 token**) | | **4. 定时任务两种写法** | `task/HelloDemoScheduledTask`(`@Scheduled`,cron 在**代码**里)+ `task/HelloDemoSnailJobTask`(`@JobExecutor(name="helloDemoTask")`,cron 在 `/snail-job` **控制台**配) | `demo/plugin-dev` 分区 4(无端点,看源码) | 服务端日志 / SnailJob 控制台 | > 演示是**五组**,但 demo 类是 **6 个**(定时任务那一组给了两种写法); > 前端 `DemoPluginDev.vue` 把两种定时任务并成一个分区,所以那里写"四个演示分区"。 > 第 5 组**不在** `DemoPluginDev.vue` 里 —— 它有自己的一页(`DemoCrud.vue`)与自己的菜单项。 ### ⭐ 第 5 组(标准 CRUD)才是**写业务时该抄的那一份** 前四组演示的是"插件能拿到什么运行时能力"(宿主服务 / 跨插件 / 开放接口 / 定时任务), **第 5 组演示的是"一个普通业务页面长什么样"** —— 而且它与别的插件**逐条同形**, 照着抄不会跑偏: | 层 | hello 的文件 | 抄的是哪份"标准答案" | |---|---|---| | Controller | `controller/HelloItemController` | `wujies-gallery` 的 `GalleryImageTagController` | | Service 接口 | `service/IHelloItemService` | `IGalleryImageTagService` | | Service 实现 | `service/impl/HelloItemServiceImpl` | `GalleryImageTagServiceImpl` | | Bo / Vo | `domain/bo/HelloItemBo` + `domain/vo/HelloItemVo` | `GalleryImageTagBo` + `GalleryImageTagVo` | | Mapper | `mapper/HelloItemMapper`(`extends BaseMapperPlus<实体, Vo>`) | `GalleryImageTagMapper` | | 前端接口层 | `frontend/src/api/hello/item/index.ts` + `types.ts` | `wujies-gallery-plugin` 仓库 `frontend/src/api/gallery/imageTag/index.ts` | | 前端页面 | `frontend/src/views/DemoCrud.vue` | `wujies-gallery-plugin` 仓库 `frontend/src/views/gallery/imageTag/index.vue` | 约定速记(**每一条都在上面那些文件里**,别自创): - 路径 `/hello/demo/item`,五个端点 `list` / `getInfo` / `add` / `edit` / `remove`; - 权限标识 `hello:item:{list,query,add,edit,remove}`,**与 `app.json` 里 F 按钮的 `perms` 逐字一致**; - `list` 返回 `TableDataInfo`(字段是 `rows` / `total`,**不是** `data`); - 写接口带 `@RepeatSubmit`,增删改带 `@Log`,查询不带; - 返回用 `BaseController#toAjax(...)`,不要自己拼 `R.ok`; - 删除是**逻辑删除**(`del_flag`),批量靠 `DELETE /{ids}` 的逗号分隔主键串。 > `HelloItemBo` 里的名称唯一性校验演示了"保存前查库校验"的写法, > 其中 `.ne(entity.getId() != null, ...)` 那个**判空条件最容易漏** > (漏了新增时唯一性校验会静默失效)—— 见 `HelloItemServiceImpl#checkNameUnique` 的注释。 **先打下面这两个,确认"装载"与"数据"两条链都通了**(`controller/HelloController`,刻意不带权限注解,沙箱里免 token): | 接口 | 验的是什么 | 期望 | |---|---|---| | `GET /hello/greet?name=无界` | **路由挂上了、依赖注入通了**(不碰数据库) | `{"code":200,"msg":"操作成功","data":"你好,无界!这是 Hello 示例插件。"}` | | `GET /hello/item/list` | **建表脚本执行了、Mapper 装配了、SQL 真打到库上了** | `code=200`,`data` 里有 8 条种子数据 | 如果第一个通、第二个不通 → 问题在数据侧(建表脚本 / Mapper / 表名)。 如果两个都不通 → 问题在装载侧(看 `/sandbox` 的 `pluginLoaded` 与 controllers)。 --- ## 怎么跑起来 ### 前端:本仓库内独立构建 ```powershell cd frontend npm install # 依赖全是 npm 上已发布的 @wujies/*(当前 5.6.5),不引用宿主源码 npm run build # 产出 frontend/dist/index.js npm run typecheck ``` 产物里只有 `vue`、`element-plus`、`@wujies/ui-shared` 三个裸说明符(由宿主 Import Map 提供单例), `@wujies/api-client` 与 `@wujies/components` **打进包内**,所以插件包不依赖宿主的额外垫片。 一眼确认 externals: ```powershell Get-Content dist\index.js -TotalCount 3 # 应当只有上面那三个裸说明符 ``` ### 后端:能构建,但**前提是本机 `~/.m2` 里已有平台构件** `pom.xml` 的 parent 是 `cn.wujies.plugins:wujies-plugins-parent:1.0.0`,`` 已留空 ⇒ Maven 直接去仓库找父 pom,不再依赖"它在单体仓库里的相对位置"。 父 pom、`wujies-common-*`、兄弟插件构件目前**只有本地 `mvn install` 出来的版本**,还没发布到 Maven 仓库 (那是后端重构阶段的活),所以: - **本机**(平台构件已 `mvn install` 过):直接构建得通。实测 `-o` 离线 23 秒、15 个源文件、产出 `target/wujies-hello-plugin-1.0.0.jar`。 - **陌生人 clone 后**:会卡在找不到 `wujies-plugins-parent` —— 阶段4 把父 pom/BOM 与平台构件发布出去才能自助。 ```powershell # 在本仓库根目录。没有 mvn 用 IDEA 自带的也行;JAVA_HOME 必须 >= 17 $env:JAVA_HOME = 'C:\Users\\.jdks\ms-17.0.16' mvn -B package -DskipTests # 用沙箱跑起来(H2 内存库,不连 MySQL / Redis / RabbitMQ;沙箱仍在 wujies-plugins 仓库) cd \wujies-plugins mvn -B -pl sandbox spring-boot:run ` "-Dspring-boot.run.arguments=--sandbox.plugin-jar=<本仓库绝对路径>/target/wujies-hello-plugin-1.0.0.jar" ``` > ⚠️ Maven 构件版本与应用版本(`app.json` 的 `version`)是**两件事**: > `target/` 下那个 jar 名带的是**父 POM 的构件版本**(这里仍是 `1.0.0`), > 而 `assets.backend` 写的是**应用版本**(`1.0.1`)。`package.ps1` 会把 jar 按构建产物名找出来、 > 在包里重命名成 `backend/wujies-hello-plugin-<应用版本>.jar`,所以两处不一致是**正常**的。 然后: ``` http://localhost:18080/hello/greet?name=无界 ← 先打这个 http://localhost:18080/hello/item/list ← 再打这个 http://localhost:18080/sandbox ← 插件装上没有、挂了几条路由 http://localhost:18080/h2-console ← 直接查沙箱库(用户 sa,空密码) ``` ### 第三层:装进宿主点菜单(前端真正跑起来的验收) 打包用本仓库自带的**完整** `package.ps1`(母本在 `wujies-plugins` 仓库根,各仓用 `node scripts/sync-package-script.mjs` 同步过来),在本仓库根执行就能体检、构建、签名、出包,不需要回到单体仓库、也不需要手工传产物路径: ```powershell # 都在本仓库根执行 powershell -NoProfile -ExecutionPolicy Bypass -File package.ps1 -CheckOnly # ① 打包前检查:清单/产物/迁移/菜单/密钥,几秒返回 powershell -NoProfile -ExecutionPolicy Bypass -File package.ps1 -Dev # ② 开发包:不签名,落 ..\.plugin-dist\hello\dev powershell -NoProfile -ExecutionPolicy Bypass -File package.ps1 # ③ 正式包:构建 + 用 keys\private.pem 签名 ``` 判据是「脚本所在仓库根有没有 `app.json`」,所以在本仓库里**不要传 `-Plugin`**(它按子目录找清单,会报「找不到插件目录」);`-FrontendProject` / `-FrontendDist` 也不用传 —— 独立布局的前端工程固定就是本仓库 `frontend/`、产物 `frontend/dist`,脚本自己跑 `npm run build`(首次先 `cd frontend && npm install`,缺依赖时脚本会把这条命令直接报给你)。只想复用现成产物就加 `-SkipBuild`;`-SkipFrontend` 只用于**没有前端**的纯后端应用,带前端时它会连复制产物一起跳过,包里没有 `frontend/`。 `-CheckOnly` 打出的 `CHECK|级别|项|说明` 行就是管理端「应用上架 → 插件开发 → 打包」抽屉里那份体检清单,两边跑的是同一套检查;有 ERROR 时正式打包会被直接拦下。签名密钥在本仓库 `keys/`(`private.pem` + `public.pem`,已在 `.gitignore` 里),生成或更换用 `-Keygen`(覆盖已有密钥要显式加 `-KeygenForce`,旧钥先整份备份进 `keys\backup\<时间戳>\`)。参数全表与产物结构见文档站 docs.wujies.cn 的「打包应用」与「密钥与签名」两页。 生成的 zip 在管理端「应用市场 → 本地安装」上传,装完逐个点开 **Hello 示例** 下的 4 个菜单 (运行时探针 / 插件开发范例 / 关于示例应用 / 标准 CRUD 示范)。 **验收点**:页面能出、控制台没有 `Failed to resolve module specifier "vue"` 之类的裸说明符报错、 探针页的「平台公共 API」那一行显示 `sys_normal_disable 共 N 项`(走的是打进包内的 `@wujies/api-client`)。 > ⚠️ 本插件**不需要** `--sandbox.extra-classpath` —— 它是**自包含**的 > (契约与实现在同一个模块里;2026-09-30 起平台里 `*-api` 模块数量为 **0**)。 > 那个参数是给"契约放在**另一个**模块里"的插件补类路径用的:不挂就会 > `NoClassDefFoundError`。沙箱用法详见 `wujies-plugins` 仓库的 `sandbox/README.md` §4。 --- ## 清单 `app.json` 里写了什么 **本仓库只有根目录这一份 `app.json`**(前端工程里那份已删)—— 它是打包器读的**唯一事实来源**, `code` / `version` / `menus` / `assets.frontend` 都只改这里;前端构建入口的 `appCode` 在 `frontend/vite.config.ts` 里声明,改 `code` 时**两处要一起改**。 > ℹ️ 本仓库根就是**完整**的 `package.ps1`,按独立仓库布局认本仓库的 `frontend/`(不需要 `pnpm --filter`, > 也不用手工传 `-FrontendDist`)。所以下面 `assets.*` 描述的是**包内的目标路径**,不是本仓库的目录结构 > (本仓库前端就在 `frontend/`,构建产物在 `frontend/dist/`,打包时被复制成包里的 `frontend/`)。 | 字段 | 值 | |---|---| | `code` / `name` / `version` | `hello` / `Hello 示例` / `1.0.2` | | `type` / `engine` | `plugin` / `>=5.6.2 <6.0.0` | | `author.name` / `license` | `无界创镜` / `MIT` | | `summary` | 一句话简介(应用市场列表里显示的就是这句) | | `dependencies` | `[{ "code": "member" }]` —— 跨插件调用那条依赖的**声明**(要先装 member,见 `AGENTS.md` §2.5) | | `assets.backend` | `backend/wujies-hello-plugin-1.0.2.jar` | | `assets.frontend` | `frontend/index.js` | | `menus` | 4 个 `type:"C"` 页面(运行时探针 / 插件开发范例 / 关于示例应用 / **标准 CRUD 示范**),**每个下面挂 `type:"F"` 按钮**(前三个各 1 个;CRUD 那个挂 4 个:`hello-item-{query,add,edit,remove}`) | ⚠️ **`assets.frontend` 必须带 `frontend/` 前缀**(不能写成裸 `index.js`):落位时 `AppPackageDeployService.dirPrefix(...)` 取的是**目录前缀**,整棵前端目录按它剥掉前缀后 放到 `/remote-apps/hello/`。裸 `index.js` 得到空前缀,于是**一个前端文件都不会落位** —— 包里看着什么都有,菜单点开却是「远程视图不可用」(踩过)。 ⚠️ `menus` 里的视图键 `demo/home`、`demo/plugin-dev`、`demo/about`、**`demo/crud`** 是**刻意的前端命名空间** (与"远程应用目录名"无关,别顺手改名):它必须与 [`frontend/src/index.ts`](frontend/src/index.ts) 的 `views` 键**逐字**对上。 ⚠️ 本插件的版本是 **`1.0.2`**(`1.0.1` 未发布)。`1.0.1 → 1.0.2` 这一版加了 第 5 组演示(标准 CRUD)与 `migrations/04-*.sql`;`01` / `02` / `03` **一个字节都没动**, 所以存量部署**不需要彻底删除重装** —— 正常升级一次即可(`04` 是 `INSERT IGNORE`, 对已经装过 `1.0.1` 的库只会补上 id 3..8 那 6 行)。 > 上一次改已执行脚本的是 `1.0.0 → 1.0.1`(给 `01` 补 `tenant_id`),那一版的口径是 > **存量那台要彻底删除重装** —— 因为被改的是 `01-create-hello-tables.sql`。 > 这一次**没有**动任何已执行脚本,两种口径别混。 --- ## 照它建一个新插件 **一个插件一个仓库**,仓库名 `wujies--plugin`(与 Maven `artifactId` 一致),后端在仓库根、前端在 `frontend/`。 ``` 1. 复制本仓库: wujies-hello-plugin → wujies--plugin 2. 改 pom.xml: wujies-hello-plugin → wujies--plugin 3. 改 app.json: code / name / version / menus(含 type:"F" 的按钮)—— 只有这一份 4. 改包名与目录: src/main/java/cn/wujies/hello → cn/wujies/ 5. 改 migrations 里的表名:hello_item → _xxx ⚠️ 新表**要么**实体 extends TenantEntity 且脚本里带 tenant_id + BaseEntity 的公共列, **要么**把表名加进宿主 application.yml 的 security.tenant.excludes(见上面对照表) 6. 改前端身份: frontend/vite.config.ts 的 appCode、frontend/package.json 的 name、 frontend/src/index.ts 的 manifest.code ⚠️ 前端只依赖 npm 上已发布的 @wujies/*,不要再引宿主源码路径 ``` 更省事的路子是 `wujies-plugins` 仓库的脚手架 `node scripts/new-plugin.mjs `(它生成的是单体仓库里的插件目录,拆成独立仓时按上面 6 步对齐即可)。 完整说明与验收标准见文档站「插件开发」章节(docs.wujies.cn)。 --- ## 与平台约定的对照 模板里每一处"为什么这么写"都写在对应文件的注释里,速查: | 约定 | 在哪体现 | |---|---| | 平台依赖用 `provided`(由宿主类加载器提供) | 父 POM 的 `dependencyManagement`,插件 pom 里**不写** `` | | **标准 CRUD 骨架**(Controller / Service / Bo / Vo / Mapper 五件套 + 前端 api + 页面) | `controller/HelloItemController` 等(见上面〈第 5 组〉那张对照表),**照它抄** | | **启动类**(Halo 式:宿主装载完成后调 `start()`、卸载前调 `stop()`,一个插件一个) | `HelloPluginApplication`(包根,`@Configuration` + `implements PluginApplication`);`create-plugin` 脚手架会一起生成 | | Mapper 用 `BaseMapperPlus<实体, Vo>`(白拿 `selectVoById` / `selectVoPage`) | `mapper/HelloItemMapper` | | Bo / Vo 用 `@AutoMapper` + `MapstructUtils.convert`(不手写 `BeanUtils.copyProperties`) | `domain/bo/HelloItemBo` / `domain/vo/HelloItemVo` | | 校验分组(新增不校验主键、编辑必校验) | `HelloItemBo` 的 `groups = {AddGroup.class, EditGroup.class}` | | 权限标识与清单 F 按钮 `perms` **逐字一致** | `HelloItemController#add` 的 `@SaCheckPermission("hello:item:add")` ↔ `app.json` 的 `hello-item-add` | | **自定义 SQL 的 Mapper XML 随插件 jar 走**,由 `PluginMapperRegistrar` 在插件加载时注册进宿主 | 本插件**没有** XML(只用 MyBatis-Plus 内置方法,见 `HelloItemMapper`);规矩见 `wujies-platform/AGENTS.md` §2.2 | | 用构造注入,不用字段注入 | `HelloServiceImpl` / `HelloItemServiceImpl` 类注释 | | 菜单含 `type:"F"` 按钮 | `app.json` 的 `menus[].children[].children[]`(`hello-crud` 下 4 个) | | 迁移脚本幂等、编号即顺序、**不可改已执行内容** | `migrations/*.sql` 头部注释;`04` 是"新增而不是改 02"的实例 | | 表结构随包走(否则卸载时平台不知道该删哪些表) | `migrations/01-create-hello-tables.sql` | ### ⚠️ 插件自己的表:要么带 `tenant_id`,要么进 `tenant.excludes` **每个插件作者都会踩这个坑,而且它的报错极难定位。** 两条路只能选一条: | 这条路 | 什么时候走 | 要做什么 | |---|---|---| | **业务表**(每租户各一份数据) | 绝大多数情况 —— 这是平台的默认设计 | 实体 `extends TenantEntity`,建表脚本里带 `tenant_id` **加上 `BaseEntity` 的全部公共列**(见下表) | | **全局表**(平台级、中心一份) | 这表**不按租户分**:像 `app_info` / `app_version` / `app_version_route` 那样只存一份 | 把表名加进宿主 `wujies-admin/src/main/resources/application.yml` 的 `security.tenant.excludes` | ⚠️ **不这么做的症状**(原样记下来,便于以后搜到): ``` {"code":500,"msg":"Unknown column 'tenant_id' in 'where clause'","data":null} ``` 而且它**只在这个表真的被查的接口上出现** —— 同一个插件的其它接口可以完全正常 (不查库的、或查的是平台已有 `tenant_id` 的表),所以第一反应总是"接口写错了", 而不是"表结构不对"。hello 1.0.0 线上就是这么炸的: | 接口 | 结果 | 为什么 | |---|---|---| | `GET /open/hello/config` | ✅ 200 | 查的是平台的 `sys_config`(本来就有 `tenant_id`) | | `GET /open/hello/greet` | ✅ 200 | 不查库 | | `GET /open/hello/item/list` | ❌ 500 `Unknown column 'tenant_id'` | **只有它真查了 `hello_item`** | ⚠️ **别以为匿名接口就没事**:`/open/**` 在宿主的 `open-api.tenant.path-patterns` 里, `OpenApiTenantInterceptor` 会给**未登录**请求设上默认租户 `000000` (`X-Tenant-Id` 头或 `?tenantId=` 可覆盖)—— 这是刻意的,**匿名请求同样会触发租户拦截**。 ⚠️ 反向的坑同样存在:**表在排除清单里、实体却继承了 `TenantEntity`** 时, `tenant_id` 不会被自动填充(列可能是空的)—— 全局表请用 `BaseEntity` 或不继承, 并套 `TenantHelper.ignore(...)` 读写。 **`hello_item` 的逐列对齐(照它抄)**: | 列 | 来自 | 类型 / 默认值 | |---|---|---| | `tenant_id` | `TenantEntity` | `varchar(20) DEFAULT '000000'` | | `create_dept` / `create_by` | `BaseEntity`(`FieldFill.INSERT`) | `bigint DEFAULT NULL` | | `create_time` | `BaseEntity`(`FieldFill.INSERT`) | `datetime DEFAULT CURRENT_TIMESTAMP` | | `update_by` / `update_time` | `BaseEntity`(`FieldFill.INSERT_UPDATE`) | `bigint` / `datetime DEFAULT NULL` | | `del_flag` | **实体自己声明** + `@TableLogic` | `int DEFAULT 0`(删除 = 1) | | `id` / `name` / `remark` | 业务字段(实体自己声明) | 见 `migrations/01-create-hello-tables.sql` | > `BaseEntity` 里另有 `searchValue` 与 `params`,两者都是 `@TableField(exist = false)` ⇒ **不落列**。 > 审计字段的类型以基类为准(`Date`),实体里**不要**再手写 `createTime` / `LocalDateTime`。 > 写法照别的插件(`wujies-gallery` 的 `GalleryImageCategory`),别自创。 > 完整推导见 `wujies-plugins` 仓库 `docs/插件开发详细教程.md` §11.7。 ### ⚠️ 关于 Mapper XML:旧规则已作废 「**Mapper XML 必须放平台 `*-api` 模块**」是 **2026-09-30 之前**的旧规则,**已不成立**。 现在平台里 `*-api` 契约模块数量为 **0**:`domain` / `BO` / `VO` / `Mapper` 接口 / **Mapper XML** / `Service` 全在**插件模块自己的 `src/` 下**。 XML 由 `PluginMapperRegistrar` 在插件加载时**只读传入的那一个 jar**、动态注册进宿主 `SqlSessionFactory`。它刻意**不用** `classpath*:mapper/**/*Mapper.xml` —— 那种写法会拿 **宿主**类加载器扫 classpath、把宿主的映射文件也捞一遍,再注册就撞 `Result Maps collection already contains value`,**宿主直接起不来**。 ⚠️ 顺带一条:**无 XML 的 Mapper 也能正常用 MyBatis-Plus 内置方法**(`selectList` / `selectById` / `insert` …)—— 本插件就是这么写的(`HelloItemMapper` 只继承 `BaseMapper`, 一行自定义 SQL 都没有)。2026-10-04 之前"卸载再重载会丢内置方法"是个真缺陷,现已修 (`PluginMapperRegistrar` 三样一起镜像 + 清 MyBatis-Plus 的注入缓存), 细节见 `wujies-platform/AGENTS.md` §2.2。 --- ## 要看详细文档去哪 这份 README 只回答"这是什么 / 怎么跑起来 / 五组演示在哪几个文件"。再往下: | 想知道 | 去哪 | |---|---| | 从零建插件到上架的全流程(可复制命令 + 验收标准) | `wujies-plugins` 仓库 `docs/插件开发指南.md`;线上版在文档站(docs.wujies.cn)「插件开发」章节 | | 跟着 hello 逐行读源码(问题 → 文件 对照表) | `wujies-site` 仓库的文档站源码 `.../plugin-dev/demo`(线上同 docs.wujies.cn) | | 平台必须遵守的不变量与踩过的坑 | `wujies-platform/AGENTS.md`(尤其 §2.2 契约随插件走、§2.5 上下文方向) | | 本地沙箱怎么用、有哪些配置项 | `wujies-plugins` 仓库 `sandbox/README.md` | | 前端 SDK(`@wujies/ui-shared` / `components` / `api-client` / `ui-plugin-bundler-kit`)有哪些、怎么用 | **`wujies-ui` 仓库**(npm 上发布的 `@wujies/*` 就来自这里),线上版见文档站 SDK 页 | | 打包、签名与上架 | `wujies-plugins` 仓库 `docs/应用市场-插件打包与部署教程.md` | --- ## 许可 MIT。