# EasyBoot **Repository Path**: qoder/easy-boot ## Basic Information - **Project Name**: EasyBoot - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: 1.1 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-05-10 - **Last Updated**: 2026-03-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # EasyBoot [EasyBoot](https://gitee.com/qoder/easy-boot/)(代码库默认分支版本 `1.1`)是基于 **Spring Boot**、**SqlToy** 与 **Vue 3** 的全栈框架;内置 Vue / JS 的编译与资源处理,开发与部署**无需安装 Node.js**。 **本仓库**:父工程版本见根目录 `pom.xml`(当前发布包版本为 `1.1-SNAPSHOT`),Spring Boot **3.5.x**,Java **17**,SqlToy 等版本由 BOM 统一管理。 > 版本口径说明:`1.1` 表示代码库分支版本;`1.1-SNAPSHOT` 表示发布包版本。两者用于不同场景,不是同一个版本号。 ## 环境要求 | 项 | 说明 | | ----- | ----------------------------------------------------- | | JDK | 17+ | | Maven | 3.6+(建议 3.8+) | | 数据库 | 平台与示例以 **MySQL** 为主(SqlToy 本身支持多数据库) | | IDE | 建议使用 IntelliJ IDEA,便于识别 Maven Profile(`dev` / `prod`) | ## 文档目录 - [环境要求](#环境要求) - [介绍](#介绍) - [相关技术](#相关技术) - [项目说明](#项目说明) - [快速开始](#快速开始) - [开发指南](#开发指南) > 阅读建议:首次接触请先看“快速开始”;准备落地业务时再通读“开发指南”。 ## 介绍 配合下方 [项目说明](#项目说明) 的模块能力,你可以快速搭建具备管理后台、权限控制、代码生成与报表能力的业务系统;也可以仅依赖 **easy-core** 进行轻量化扩展。 ## 相关技术 - 后端基础框架 [Spring Boot](https://spring.io/projects/spring-boot) - ORM 框架 [SqlToy](https://gitee.com/sagacity/sagacity-sqltoy) - Bean 映射 [MapStruct](https://mapstruct.org/) - 前端框架 [Vue 3](https://cn.vuejs.org/) · [Preact(可选)](https://preact.nodejs.cn/) - 前端 UI 组件库 [HeyUI](https://v2.heyui.top/) - 前端 UI 组件库 [ElementPlus](https://element-plus.org/zh-CN/) ## 项目说明 - **[EasyBoot](https://gitee.com/qoder/easy-boot/)**(默认分支版本 `1.1`):核心框架与本仓库,提供平台能力与开发规范。 - **[EasyPlugins](https://gitee.com/rankeiot/easyplugins)**:扩展插件集合。 - **[EasyStarter](https://gitee.com/rankeiot/easystarter)**:更简的示例启动工程(若与本仓库 `easy-demo` 二选一即可)。 ### EasyBoot 项目结构 - **easy-core**:核心模块,无业务代码;配置项、菜单、权限、缓存、注解切面等扩展能力 - **easy-platform**:基础平台;用户/角色/部门、字典、系统配置、日志、定时任务、报表(XMReport)等,含管理端页面与初始化 SQL(如 `easy-platform/src/main/resources/META-INF/platform_mysql.sql`) - **easy-dev**:开发工具;代码生成器与辅助能力(通常 `dev` Profile 下启用) - **easy-vue**:前端资源与编译链路相关依赖(业务工程通过 Maven 依赖引入;可与本仓库分开发布) - **easy-boot-application**:轻量启动/打包壳模块,入口为 `com.rankeiot.boot.loader.BootApplication`,用于组装依赖并生成可运行产物 - **easy-demo**:示例工程(成员管理等),含内置 Vue 页面与可选独立前端,详见 [easy-demo/README.md](easy-demo/README.md) - **easy-maven-plugin**:Maven 插件(与 `easy-maven-plugin.ver` 对应),负责前端资源处理等构建能力 ### 系统功能 - 用户管理:提供用户基础配置;新增用户后默认密码为 `123456` - 角色管理:用于分配权限与菜单;角色可绑定用户及菜单/按钮权限 - 部门管理:可配置系统组织架构,树形表格展示;部门带有**级联关系**的内部 ID。 - 字典管理:维护常用固定数据(如状态、性别等) - 配置项管理:维护系统中常用的可变配置 - 用户日志:记录用户操作日志,支持自定义日志 - 慢 SQL 日志:记录慢查询日志,便于维护和调优,阈值可配置 - 定时任务:提供任务配置与执行记录能力 - 报表管理:集成 XMReport,支持在线模板设计与 PDF 导出 - 代码生成:高灵活度生成前后端代码,减少重复工作 - API 接口:支持导出 `api.json` 与在线调试 ## 快速开始 ### 克隆本仓库并运行示例(推荐上手) 1. 创建 MySQL 数据库,并执行初始化脚本:[easy-platform/src/main/resources/META-INF/platform_mysql.sql](easy-platform/src/main/resources/META-INF/platform_mysql.sql)。 2. 编辑 `[easy-demo/src/main/resources/application-dev.yml](easy-demo/src/main/resources/application-dev.yml)`,填写数据源等连接信息。 3. 在仓库根目录执行:`mvn clean install`(首次构建耗时较长属正常现象)。 4. 启动 `easy-demo` 模块中的主类 `com.rankeiot.easy.Main`,浏览器访问 [http://localhost:8080](http://localhost:8080)。更多说明见 [easy-demo/README.md](easy-demo/README.md)。 ### 新建 Maven 工程集成 EasyBoot 1. 新建 Maven 项目,修改 `pom.xml`(父工程坐标与仓库需与当前 EasyBoot 版本一致;仓库 ID 可与根工程一致使用 `rankeiot-public`): ```xml 4.0.0 easyboot com.rankeiot.easy 1.1-SNAPSHOT yourgroupId yourartifactId 1.0-SNAPSHOT UTF-8 UTF-8 17 17 rankeiot-public rankeiot-public https://maven.cnb.cool/rankeiot/public/-/packages/ true true always rankeiot-public rankeiot-public https://maven.cnb.cool/rankeiot/public/-/packages/ true true com.rankeiot.easy easy-vue com.rankeiot.easy easy-dev runtime true com.rankeiot.easy easy-platform org.projectlombok lombok compile true mysql mysql-connector-java org.apache.maven.plugins maven-compiler-plugin full true org.projectlombok lombok ${lombok.version} org.mapstruct mapstruct-processor ${mapstruct.version} com.rankeiot.easy easy-core ${easy.ver} org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok true ``` > **提示**:`annotationProcessorPaths` 中的 `${easy.ver}` 由父工程 `easyboot` 的 `` 注入;若使用自定义父 POM,请自行定义 `easy.ver` 或与所用 EasyBoot 版本一致。 1. 编写启动类 ```java package yourpackage; import com.rankeiot.core.Module; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ApplicationContext; /** * 基于 EasyBoot 的库存管理系统启动类。 */ @SpringBootApplication public class Application implements Module { @Override public String name() { return "系统名称"; } /** * 框架初始化入口 */ @Override public void start(ApplicationContext applicationContext) { // 注册框架功能 // 注册用户菜单 //regMenu(demoMenu.class); // 注册会员菜单:通常不需要,只有同时启用会员与用户菜单时才使用 //regMemberMenu(demoMemberMenu.class); // 注册系统配置项 //regConfigs(config.class) // 注册固定字典项 //regFixedDict(DictEnum.class) // 注册前端 JS 模块(需满足 ESM 规范),前端可通过 import xx from 'name' 使用 // jsModule("moduleName","js/module_a.js") // 注册 Vue 组件,注册后可在前端页面中直接通过注册名使用 // vueComponent("VueCom1","coms/VueCom1.vue") // 添加全局 CSS 样式,会在启动页自动加载 //addGlobalStyle(String path) // 添加全局 JS 文件(非 ESM 模块),会在启动页自动加载 //setGlobalScripts(path) // 其他自定义初始化逻辑 } public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 1. 添加 `src/main/resources/application.yml`: ```yaml spring: application: name: 系统名称 profiles: active: dev ``` 1. 添加 `src/main/resources/application-dev.yml`(开发环境数据源等敏感信息建议仅放在本地或工作目录,勿提交仓库): ```yaml spring: datasource: dynamic: primary: master datasource: master: url: jdbc:mysql://数据库地址:数据库端口/数据库名?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false&allowPublicKeyRetrieval=true&autoReconnect=true&rewriteBatchedStatements=true&useInformationSchema=true username: 数据库用户名 password: 数据库密码 ``` 1. 启动应用后,在浏览器打开 [http://localhost:8080](http://localhost:8080)。 #### 首次启动与安装向导 目前平台初始化向导主要面向 **MySQL**(SqlToy 可支持其他数据库类型)。在向导中填写数据库连接并确认后,系统会完成库表初始化;完成后可能在项目目录下生成 `application-dev.yml`。 可将 `application-dev.yml` 放到 `src/main/resources`(随 jar 打包),或放在与 jar **同一路径的工作目录**下,避免每次部署重新配置。 初始化成功后即可进入系统。若需自定义首页,可在业务工程中新增 `src/main/resources/static/home.vue` 对默认首页进行**覆盖**;其他页面同样可通过在 `static` 下放置同名路径资源实现覆盖。 至此,一个可登录、可配置的基础环境即已就绪。 ## 开发指南 **本章主要小节**:代码生成 · 目录约定 · 后端(SqlToy / 菜单权限等)· 前端(Vue / ESM)· 登录签名 · 插件机制。 建议使用 IntelliJ IDEA:根据 Maven Profile 配置,项目打开后会显示对应环境选项。EasyBoot 强调**先完成数据库设计**,在表和字段注释完善后,再通过代码生成器导入模型并生成前后端代码。 IDEA 使用说明: 开发阶段 Maven 默认选择 `dev` Profile。打包阶段建议切换到 `prod`,Maven 会按参数替换 `application.yml` 中的配置,并在打包时移除开发与接口 API 相关模块。 ### 代码生成 在开发模式下(`profile=dev`)访问 [http://localhost:8080](http://localhost:8080),进入**代码生成器**:通过已连接库中的表导入模型并生成代码。请为表与字段补充**注释**,以便生成更友好的页面与文档。 代码生成器说明:建议先确认数据表命名、字段注释与字典取值,再执行生成,能显著减少后续手动调整工作量。 ### 项目目录结构说明 ``` /project_root ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── yourpackage #应用基础包 │ │ │ ├── moduleA #应用模块A,按应该模块分包 │ │ │ │ ├── controller #controller目录 │ │ │ │ ├── dao #DAO接口目录 │ │ │ │ ├── dto #DTO目录 │ │ │ │ ├── domain #数据库实体目录 │ │ │ │ ├── service #service目录 │ │ │ │ │ └── DemoMapper.java #MapStruct的数据映射接口 │ │ │ │ ├── config #配置项目录 │ │ │ │ ├── dict #固定字典项目录 │ │ │ │ └── task #定时任务目录 │ │ │ ├── moduleB #应用模块B │ │ │ └── Application.java #启动文件 │ │ └── resources │ │ ├── models #使用代码生成器时,自动生成的实体模型 │ │ │ └── moduledir1 #模块1 │ │ │ └── xxtable.json #实体模型,描述了模型名字,字段名,字段类型,页面显示等信息 │ │ ├── sql #初始化sql目录 │ │ │ └── xxtable_init.sql #业务初始化sql │ │ ├── static #前端模块 │ │ │ ├── coms #自定义前端组件,每个组件为单个vue或jsx文件 │ │ │ ├── css #css 放通用css和less文件 │ │ │ ├── js #js目录,支持ESM标准 │ │ │ └── moduledir1 #某个功能的页面目录 │ │ │ └── xxlist.vue #包含页面,可以是vue组件文件或jsx的React组件 │ │ ├── application.yml #项目配置 │ │ ├── application-dev.yml #项目DEV配置 │ │ └── application-prod.yml #项目PROD配置 │ └── test │ ├── java │ │ └── yourpackage │ │ └── MainTestApp.java │ └── resources │ └── application.yml #测试用配置文件 ``` ### 后端指南 EasyBoot 后端基于 Spring Boot 与 SqlToy 开发。 #### 模块划分 EasyBoot 按业务维度拆分 Java 包模块;在对应 `Module` 实现中可注册本模块菜单、固定配置项、固定字典等能力。 对于需要独立拆分的模块,可按下面方式定义: ```java //可参考com.rankeiot.platform.PlatformModule @Configuration public class DemoModule implements Module { public void start(ApplicationContext context) { //注册菜单 //regMenu(TestMenu.class); } } // Spring Boot 3:在 src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports // 中写入一行模块完整类名,例如:com.rankeiot.platform.PlatformModule // (Spring Boot 2 仍可使用 META-INF/spring.factories 的 EnableAutoConfiguration 列表。) ``` ### 数据库实体相关 1. 通过 [SqlToy](https://gitee.com/sagacity/sagacity-sqltoy) 定义数据库实体类; 2. 使用 SqlToy 的 `@Entity` 标明持久化实体。 3. 使用 `@Column` 绑定数据库字段。查询结果映射到 VO 时,同名字段可自动映射;下划线命名会按驼峰转换(如 `user_name` → `userName`)。若仍需用 `@Column` 显式映射,实体类须带 `@Entity`(`tableName` 可留空),否则 `@Column` 不生效。 4. `@ClientValidator` 用于生成前端校验逻辑,默认与类名一致,可用 `@ClientValidator("otherName")` 覆盖;适用于 entity、dto 等,与 SqlToy 无直接耦合。校验规则来自 Bean Validation / Hibernate Validator 等注解(如 `@NotNull`、`@Length`)。 以 `Student` 为例: ```java import java.io.Serializable; import java.math.BigDecimal; import java.util.Date; import java.sql.Types; import lombok.Data; import lombok.experimental.FieldNameConstants; import io.swagger.v3.oas.annotations.media.Schema; import org.sagacity.sqltoy.config.annotation.Entity; import org.sagacity.sqltoy.config.annotation.Id; import org.sagacity.sqltoy.config.annotation.Column; import jakarta.validation.constraints.*; import jakarta.validation.constraints.Email; import org.hibernate.validator.constraints.*; import com.rankeiot.core.validation.*; import com.rankeiot.core.anno.ClientValidator; /** * 学生信息 */ @Schema(description="学生信息") @Data @ClientValidator @FieldNameConstants(asEnum = true) @Entity(tableName=Student.TABLE) public class Student implements Serializable{ public static final String TABLE="student_table_name"; /** * ID */ @Schema(description="ID") @Id(strategy = "identity") @Column(name="ID",length=64L,type=Types.BIGINT,nullable=false,autoIncrement=true) private Long id; /** * 发票号码 */ @Schema(description="发票号码") @Length(max=50) @Column(name="user_name",length=50L,type=Types.VARCHAR) private String userName; } ``` 后端调用示例: ```java @Service @RequiredArgsConstructor public class StudentService{ final LightDao dao; public void createStudent(){ var student=new Student(); student.setUserName("testName"); dao.save(student); } } ``` #### DAO DAO基于[SqlToy](https://gitee.com/sagacity/sagacity-sqltoy)实现,用于数据库数据查询。 通过编写DAO接口的方式,编译时框架自动生成对应的实现类。DAO编写步骤: 1. 编写一个接口,需要继承SqlToyRepository接口,然后添加类注解@com.rankeiot.core.sqltoy.anno.Dao或@org.springframework.stereotype.Repository。二者区别在于,Repository会通过Spring自动处理Transaction 2. 编写SQL查询或执行的方法,添加一个方法,然后通过@Query注解添加对应的执行SQL语句。Query会自动判断执行的sql类型(QUERY或UPDATE),自动处理参数映射和返回类型 3. 查询SQL遵循SqlToy的语法规则,基础的两个规则为:paramName 表示参数占位,用#[]包裹时,如:#[user_name like :UserName] ,会自动判断包裹的参数是否为空,从而进行动态剔除,支持其余SqlToy的高级方法 4. 方法注解增加了@DateFormat,@NumberFormat,@Translate,@LLike,@RLike,@Blank。功能对应SqlToy中XML的同名指令 ```java import com.rankeiot.core.sqltoy.StreamFetcher; import java.util.List; import com.rankeiot.core.sqltoy.OrderBy; import com.rankeiot.core.sqltoy.SqlToyRepository; import com.rankeiot.core.sqltoy.anno.Dao; import com.rankeiot.core.sqltoy.anno.Query; import org.sagacity.sqltoy.model.Page; import java.util.Map; import com.rankeiot.core.sqltoy.anno.filters.LLike; import com.rankeiot.core.sqltoy.anno.filters.RLike; /** * 演示DAO */ @Dao public interface DemoDao extends SqlToyRepository { /** * 简单分页查询 * 1. 当参数列表中,除去Page和OrderBY类型的参数,只剩下一个 Map类型参数时,语句中的查询占位名与map中的key值名相对应 * 2. 当参数列表中,除去Page和OrderBY类型的参数,只剩下一个实现Serializable接口类型的参数时,语句中的查询占位名与该bean中的字段名相对应 * 3. 其余情况直接对应参数名本身 * @param pageable * @param params * @param order * @return */ @Query("SELECT * FROM demo_table WHERE param like :param") Page list(Page> pageable, Map params, OrderBy order); /** * 占位直接对应到参数名 * @param userName * @return */ @Query("SELECT * FROM demo_table WHERE param like :userName") int queryCountByName(String userName); /** * 列表查询,通过返回StreamFetcher的方式,可以流式获取数据,也可以直接返回List */ @Query("SELECT * FROM demo_table WHERE param like :param") StreamFetcher list(Map params); /** * like查询示例,默认情况下 like :param 会自动在参数值两端拼接 %,无需手动concat("%") * 当前仅需左侧补%时,使用@LLike注解进行声明,需右侧补%进行匹配时,使用@RLike进行声明 * @param name * @return */ @LLike({"name"}) // 可对多个字段生效 @Query("SELECT * FROM demo_table WHERE param like :name") DemoVO findByName(String name); /** * in查询使用()包裹占位名后直接查 * @param ids id列表 * @return */ @Query("SELECT * FROM table WHERE id in (:ids)") List findDemos(List ids); /** * 快速分页。@fast, @fastPage与@fast等效 * Sqltoy的分页不是简单的包裹sql,根据sql情况智能剔除了sql中的order by, * 同时select count(1) 模式是根据sql逻辑决定是直接切除掉原sql的from 之前的语句, * 替换成select count(1) from 避免不必要的sql函数运算。 * 如下示例中,计算总数时,只用执行@fast包裹住的部分,而不需要额外join其他的表。 */ @Query(""" select tmp.*,d.rpt name file_name from @fast( select m.* from nebula_rpt_subscribe_mail m where 1=1 #[and m.operate_time between :beginDate and :endDate] #[and m.status=:status] #[and m.subscribe_id=:subscribeId] order by m.operate_time desc ) tmp left join nebula_rpt_subscribe_file f on f.id=tmp.id left join nebula_rpt_deploy d on d.rpt_id=f.rpt_id """) Page fastPageList(Date beginDate,Date endDate,Integer status,Long subscribeId); /** * sql内条件处理 * SQL内@if(),@elseif() @else * */ @Query(""" select * from table where name='测试' -- 非计逻辑场景下,内部动态参数为null,最终为andstatus=1也要自动剔除 #[and status=1 #[and type=:type] #[and orderName like :orderName] ] -- flag==1时成立,因为内容存在功态参数,所以继续变成#[and status=:status]参数为nuLL剔除,不为nuLL则保留 #[@if(:flag==1) and status=:status] -- flag==1时成立,因为and status=1没有动态参数,则保留and status=1 #[@if(:flag==1) and status=1 ] #[@elseif(:flag==2) and status in (2,4) ] #[@else and status in (1,2)] """) List findByType(String type,String orderName,int status,int flag); /** * 1.使用@blank()目的是为非条件部分的语句虚构出一个参数,让其符合sqltoy的sql组织规则复杂场景请结合@if和@value使用来解决问题,比如@value(:sqlScript) 直接嵌入组织好的sql。 * 2.@value(:paramName) 类似于@blank(:paramName),唯一的区别是 @value(:paramName) 会直接显示paramName对应的值。 */ @Query(""" select t.STAFF_ID,t.STAFF_CODE,t.STAFF_NAME,t.POST, -- 当sexType参数为空时,结果字段中不会输出 t.SEX_TYPE #[@blank(:sexType) t.SEX_TYPE, ]t.ORGAN_ID,t1.ORGAN_NAME from sys_staff_info t left join sys_organ_info t1 on t.ORGAN_ID=t1.ORGAN_ID where 1=1 #[and t.STAFF_NAME like :staffName] #[and t.sex_type=:sexType] """) List findStaffByNameAndType(String staffName,String sexType); /** * 循环 @loop()、@secure-loop()、@loop-full()、@secure-loop-full()的用法 * @loop()用于sql动态循环拼接字段、条件,一般针对数组或集合,且不适用于in的场景。 * @secure-loop():跟@loop的区别在于参数不会直接拼接到sql中,而是采用?形式入参,防止sql注入 * 分三种格式 * @secure-loop(:loopParam,loopContent) * @loop(:loopParam,loopContent) 不推荐 * @secure-loop(:loopParam,loopContent,linkSign) * @loop(:loopParam,loopContent,linkSign) 不推荐 * @secure-loop(:loopParam,loopContent,linkSign,startIndex,endIndex) * @loop(:loopParam,loopContent,linkSign,startIndex,endIndex) 不推荐 * 推荐使用:@@secure-loop * 1、为了确保@loop宏参数用逗号切割的准确性,支持参数上用{},’’,”” 三种方式包裹 * 2. @secure-loop(:loopParams,{loopContent},or) 默认跳过null和空,不参与循环,如不跳过则请使用@secure-loop-full(:loopParams,{loopContent},or) * 下面语句中,当ids=[1,2,3]时,实际的语句为: SELECT * FROM table WHERE id=1 or id=2 or id=3 */ @Query(""" SELECT * FROM table WHERE #[@if(size(:ids)>0) @loop(:ids,"id=:ids[i]"," or ")] """) List listByIds(List ids); /** * 使用@include(:partSql)可以通过动态参数形式传入 SQL 片段,适用于需要动态拼接语句的场景。因可读性较差且维护成本较高,不推荐常规使用 * @param sqlPart 传入的sql,可以带占位,如: and field_a=:paramA * @param paramA 参数A */ @Query(""" SELECT * FROM table WHERE status=1 @include(:sqlPart) """) List findWithSqlPart(String sqlPart,String paramA); /** * 数据更新 * @param demo */ @Query("UPDATE demo_table SET field_a=:fieldA where id=:id") void update(DemoEntity demo); /** * 批量更新,返回受影响数据条数,可返回void * @param demos */ @Query("UPDATE demo_table SET field_a=:fieldA where id=:id") int update(List demos); } ``` StreamFetcher ```java package com.rankeiot.core.sqltoy; /** * 用于 DAO 接口中返回流式处理结果。 * @param */ public interface StreamFetcher { /** * 获取数据。 * @param handler 数据处理 Handler */ void fetch(StreamResultHandler handler); /** * 获取数据。 * @param consumer BiConsumer[Data,rowIndex] */ void fetch(BiConsumer consumer); } ``` StreamResultHandler ```java package org.sagacity.sqltoy.callback; /** * @project sagacity-sqltoy * @description 定义基于流模式获取查询结果的回调 * @author zhongxuchen * @version v1.0, Date:2022-7-23 */ public interface StreamResultHandler { /** * 开始处理。 * @param columnsLabels 查询结果列标题 * @param columnsTypes 查询结果列对应的数据类型 */ public default void start(String[] columnsLabels, String[] columnsTypes) { } /** * 消费单行数据。 * @param row * @param rowIndex */ public default void consume(Object row, int rowIndex) { } /** * 支持条件终止的消费方法。 * @param row * @param rowIndex * @return 是否继续消费后续数据 */ public default boolean doNextConsume(Object row, int rowIndex) { return true; } /** * 流数据提取完成。 */ public default void end() { } } ``` ### 业务菜单和权限 Easy Framework使用注解方式来声明权限,新增菜单或者权限后,重新启动项目,页面菜单会自动更新。如果修改后没有更新,在后端管理页面菜单中点击重置内部菜单按钮,可以刷新菜单 ```java import com.rankeiot.core.anno.Menu; import com.rankeiot.core.anno.Permission; //这里声明了菜单以及权限 @Menu(value = "测试", icon = "icon-file", id = TestMenu.ID) public interface TestMenu { String ID = "Demo"; //配置菜单,也支持jsx(React)组件路径 @Menu(value = "示例", path = "test/demo.vue", icon = "icon-file") String DEMO = ID + ".demo"; // demo增删改权限 @Permission(value = "新增", parent = DEMO) String DEMO_ADD = DEMO + ".add"; @Permission(value = "修改", parent = DEMO) String DEMO_EDIT = DEMO + ".edit"; @Permission(value = "删除", parent = DEMO) String DEMO_DELETE = DEMO + ".delete"; } ``` 框架中的权限注解说明 ```java @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Auth { /** * 调用方法需要的用户权限,默认不需权限,仅登录即可。 */ String[] value() default {}; /** * 调用方法需要的用户角色。 */ String[] role() default {}; /** * 调用方法限定的用户类型,默认为Current.TYPE_USER,即后台用户,可选值为:Current.TYPE_USER(后台用户),Current.TYPE_MEMBER(前台会员)。 */ char userType() default Current.TYPE_USER; /** * 用户子类型,备用字段,可用于用户业务类型判断 */ char[] subType() default Current.SUB_TYPE_DEFAULT; /** * 是否需要登录,Controller上标注时,全部方法都需要进登录,可以用@Auth(login=false)单独注解一个方法,用来表示其不需要登录 */ boolean login() default true; } ``` 后端接口代理注解说明 ```java package com.rankeiot.core.anno; /** * 标注于Controller上,生成同名前端调用接口代理 */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ClientCallable { } /** * 方法注解,非必须 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ClientMethod { //映射的方法名,默认情况下,使用java代码中原本的方法名 String value() default ""; //是否需要排除该方法 boolean exclude() default false; //该方法是否为文件下载处理方法 boolean download() default false; } ``` 后端权限使用,以学生信息管理为例 ```java import com.rankeiot.core.anno.ClientCallable; import com.rankeiot.core.anno.ClientMethod; /** * 示例说明: * @Auth 表示需要登录;@Auth("perm") 表示调用该方法需要指定权限,也可指定角色(role) * 当传入多个权限或角色时,满足任意一个条件即可调用该方法 * @Api 与 @ApiOperation 为 Swagger 注解,使用后对应 Controller 才会出现在 API 文档中 * 除此之外,Controller 写法与常规 Spring Boot Controller 一致 * @ClientCallable 注解会自动生成前端调用接口 */ @Api(tags="学生信息管理") @Auth @RestController @ClientCallable @RequestMapping("demo") @RequiredArgsConstructor public class DemoController { // org.mapstruct.Mapper 类型映射接口,用于不同对象之间的数据拷贝 final DemoMapper demoMapper; // DAO 接口 final DemoDao demoDao; // SqlToy 默认 DAO,可直接做数据操作。简单 CRUD 可在 Controller 中实现,复杂或需复用逻辑再提取到 Service final LightDao dao; // 业务复杂或需要复用时,再提取到 Service //final DemoService demoService; /** * 新增学生 */ @ApiOperation("新增学生") @Auth(StuMenu.STUDENT_ADD) @PostMapping("add") public Resp add(@Valid @RequestBody StudentRequest req){ // 获取当前用户信息(通常要求方法已具备登录校验能力) UserInfo userInfo = Current.user(); Student student=demoMapper.toEntity(req); student.setAddTime(new Date()); student.setAddUser(userInfo.getUsername()); dao.save(student); // Resp 说明: // Resp.ok() 返回成功消息 // Resp.ok("message") 返回成功消息 // Resp.ok(data,"message") 携带数据返回成功消息 // Resp.of(data) 携带数据返回成功消息 // Resp.fail("message") 抛出 new BusinessException(message) 异常 return Resp.ok(); } /** * 删除学生 */ @ApiOperation("删除学生") @Auth(StuMenu.STUDENT_DELETE) @Transactional @PostMapping("delete") public Resp delete(List ids){ // dao.loadAll(students); List students = CollectionUtil.map(ids,Student::new); dao.deleteAll(students); return Resp.ok(); } /** * 更新学生信息 * 使用 dao.update 更新数据时,直接使用update会根据实体中@Id标注的字段,对其余有值的字段进行更新,如果@Id字段没有值,会抛出异常 * 如果需要空值也需要更新,则需指定强制更新字段:dao.update(student,"field1","field2",...) * 如果仅需更新指定字段使用updateFields: * 单个实体:dao.update().updateFields("field1"...).one(student) * 更新实体列表:dao.update().updateFields("field1"...).many(studentList) * 也可通过自定义Dao接口,写SQL语句对数据进行精确修改 */ @ApiOperation("更新学生") @Auth(StuMenu.STUDENT_EDIT) @PostMapping("save") public Resp save(@Valid @RequestBody Student student){ dao.update(student); return Resp.ok(); } /** * 获取学生 */ @ApiOperation("获取学生详细") @Auth(StuMenu.STUDENT) @GetMapping("detail") public Resp detail(Integer id){ Student student=dao.load(new Student(id)); return Resp.of(student); } /** * 获取学生列表 */ @ApiOperation("列出学生") @Auth(StuMenu.STUDENT) @PostMapping("list") public Resp list(QueryPage query){ // 获取查询排序。参数为前端字段到数据库字段的映射:key=前端字段,value=数据库字段(可带表别名,如 a.field_a) var orderBy=query.getOrderBy(Map.of("studentName","student_name")); Page result = demoDao.list(query.page,query.params(),orderBy); return Resp.of(result); } /** * 导出学生信息:需要特定模板时,使用后端导出;否则推荐使用前端自带的所见即所得导出能力 */ @ApiOperation("导出学生") @Auth(StuMenu.STUDENT) @PostMapping(value="export",produces = "application/vnd.ms-excel") public ResponseEntity export(QueryParams query){ return Resp.export("学生" + DateUtil.format(new Date(), "yyMMdd") + ".xlsx" , out -> { // 流式导出 ExcelStreamExporter exporter = new ExcelStreamExporter(out); // 自定义导出可在此设置字段翻译 // 调用 demoDao 中的流式查询方法 demoDao.list(query.params).fetch(exporter); }); } /** * 导入学生,通过Excel文件进行导入 */ @ApiOperation("导入学生") @Auth(StuMenu.STUDENT_ADD) @Transactional @PostMapping("import") public Resp importData(MultipartFile file) throws IOException { if (file == null) { // 未上传文件,抛出业务异常 Resp.fail("未上传文件"); } // 设置导入源 ExcelBatchImporter importer=new ExcelBatchImporter(file.getInputStream(), Student.class); importer.start((batchData,lastIndex)->{ dao.saveAll(batchData); }); return Resp.ok(); } } ``` #### 系统配置项 固定配置项声明 系统配置项,通过enum+@Item注解的方式声明配置项,配置项的key值为enum类名+条目名, 比如下面这个DemoName的Key值为DemoConfig.DemoName。该值可以作为数据库中t_config表的sn列 或者在前端调用时使用,例如:`_config[key]` 当front=true时,表示可以在前端页面中直接使用_config[key]获取对应key的配置值 固定配置项需要在模块配置中注册,可在任意初始化代码中调用注册方法,常见位置为 `main` 方法或 `Configuration` 类。 ConfigManager.register("配置所属分组",DemoConfig.class); 也可在Module的start 方法中调用 regConfigs(DemoConfig.class); ```java import com.rankeiot.core.config.ControlType; import com.rankeiot.core.config.IConfigItem; import com.rankeiot.core.config.Item; import com.rankeiot.core.Config; /** *测试配置项 */ public enum DemoConfig implements IConfigItem { @Item(title="测试配置",defaultValue = "Hello",front = true) DemoName } ``` 在后端获取配置项的值 ```java public static void demo(){ //通过配置枚举类引用 String str= DemoConfig.DemoName.value().asString(); Date date= DemoConfig.DemoName.value().asDate(); // ... //通过Config直接获取 Config.getStr("DemoConfig.DemoName"); //获取配置值,自动转换为Boolean类型 Config.getBool("otherName"); //获取配置值,自动转换为Long Config.getLong("otherName"); //其他类型需通过getStr获取后,自行进行转换 } ``` #### 固定字典项 调用 `FixedDictManager.regFixedDict(Gender.class)`。 注册后,该固定字典会写入系统,并可在字典菜单中查看。 也可在 `Module#start` 方法中调用 `regFixedDict(Gender.class)` 进行注册。 ```java import com.rankeiot.core.dict.IFixedDict; import com.rankeiot.core.dict.Title; /** * 字典常规写法,表示 * * 性别:Gender * 男:M * 女:F */ @Title("性别") @Getter @RequiredArgsConstructor public enum Gender implements ITitleFixedDict { M("男"), F("女"); final String title; } /** * 字典,可以指定特定值,但是无法直接通过字面量转换为Enum,如下:表示 * 性别:Gender * 男:1 * 女:0 */ @Title("性别") @Getter @RequiredArgsConstructor public enum Gender implements ITitleFixedDict { M("1","男"), F("0","女"); final String value; final String title; } /** * 字典常规写法,效果同示例1,表示 * * 性别:Gender * 男:M * 女:F */ @Title("性别") public enum Gender implements IFixedDict { @Title("男") //Male M, @Title("女")//Female F } ``` 后端使用字典,通过枚举或者CacheDataService直接引用 ```java @Component @RequiredArgsConstructor public class DemoService{ final CacheDataService cacheDataService; public void dictDemo(){ //通过枚举获取固定字典 String genderM = Gender.M.getValue(); var gender=Gender.valueOf("M"); //通过CacheDataService获取配置,这里还可以获取到通过后台页面添加到数据库中的动态字典 //获取字典Map Map genderMap=cacheDataService.getByType("Gender"); //获取字典列表 List dictList=cacheDataService.getDictList("Gender"); //获取字典翻译器 Translator translator=cacheDataService.getDictTranslator("Gender"); //反向翻译,如把"男"翻译为"M" Translator translator=cacheDataService.getDictTranslator("Gender",true); } } //com.rankeiot.core.translator.Translator的定义 public interface Translator { /** * 把给出的value进行转换 * @param target 转换的数据对象 * @param fieldName 转换的字段名称 * @param value 需要转换的值 * @return Object 转换后的值 */ Object translate(Object target, String fieldName, Object value); } ``` #### 定时任务 基于 Spring Boot 的定时任务实现,可在后台管理中查看并控制任务。建议用于简单、低频任务;高频任务建议使用 Spring Boot 原生方式或 XXL-Job。 ```java import com.rankeiot.core.anno.Job; @Job(cron = "0 0 0 0 0", title = "定时任务", desc = "定时任务描述") public class DemoTask implements Runnable { @Override public void run() { //任务内容 } } ``` #### Excel导入导出 后端通过定义Excel Bean的方式来实现Excel的导入导出 - Excel文件大量数据流式导出 ```java import com.rankeiot.core.anno.Excel; @Data public class ExportVo{ @Excel("姓名") String name; @Excel("出生日期") @JsonFormat(pattern="yyyy年MM月dd日") Date brith; //也可通过 @Translate() 注解处理格式 } public interface StudentDao extends SqlToyRepository{ //通过StreamFetcher返回流式获取结果,用于导出或者流式处理 @Query("SELECT * FROM demo_table WHERE param like :param") StreamFetcher list(Map params); } public class ExportImportController{ // 在 Spring 的 Controller 中通过 ResponseEntity 导出 public ResponseEntity exportExcel(QueryParams query){ return Resp.export("导出文件名.xlsx",out->{ ExcelStreamExporter exporter=new ExcelStreamExporter(out); //可设置数据转换翻译 //exporter.translate("field",Translator) studentDao.list(params).fetch(exporter); }); } } ``` 后端通过Excel模板进行数据导出 ```java import com.rankeiot.core.excel.XlsTemplateExporter; public ResponseEntity exportExcel(QueryParams query){ return Resp.export("导出文件名.xlsx",out->{ //起始填充行 int startRow=2; XlsTemplateExporter exporter=new XlsTemplateExporter("模板位置.xls",startRow); exporter .setFieldMap(CollectionUtil.asMap("A","idx","B","name","C","age"))//定义字段与列的映射,如果ListData类中有@Excel定义,也可以不需显示定义列映射 .setCellData("C1","这是一个测试")//指定单元格填充数据 .setListData(dataList) .export(out); }); } ``` - Excel 文件导入 前端定义 ```vue 导入文件 ``` 后端接收文件并导入 ```java //简单导入 public void importExcel(MultipartFile mf){ ExcelBatchImporter bi=new ExcelBatchImporter(mf.getInputStream(),ExportVo.class); //可设置数据转换翻译 //exporter.translate("field",Translator) //默认500条一批数据读入,如果需要全部读入内存后处理,可在下面缓存后处理 //bi.setBatchSize(500); bi.start((batch,index)->{ // }); } //文件上传方法 @PostMapping("xx/import") public SseEmitter importFile(@RequestParam("file") MultipartFile file) { UserInfo user=Current.user(); return FrontendProcess.create(p -> { p.info("文件上传完成,导入中..."); //使用VO或者Entity进行数据导入 ExcelBatchImporter bi = new ExcelBatchImporter(file.getInputStream(), ExpendRecordImportVo.class); bi.start((list, idx) -> { //list就是处理好的数据列表 //dao.saveAll(list) }); p.info("导入完成..."); }); } //数据模板 @Data //指定起始行 @ExcelTemplate(startRow = 2) public static class ExpendRecordImportVo { //指定字段列 @Excel(columnName = "A") private String projectName; } ``` #### 短信集成 在 `pom.xml` 中加入依赖,并在后台配置界面完成阿里云短信服务配置后,即可通过 `com.rankeiot.platform.service.SmsService` 发送短信。 ```xml com.aliyun dysmsapi20170525 ``` #### 小程序支持 easy-platform.jar 中的uni/api目录下内置了两个uni-app的后端接口文件。把它放入uni项目中,配置后端路径。 支持前端用户登录,用户信息获取,ajax请求自动处理token 同时在后端配置设置中配置好对应的小程序ID和secretKey可使用小程序的登录和手机快捷登录 ### 前端指南 EasyBoot 前端开发框架基于 Vue 3,并通过 `es-module-shims` 加载 Vue、JS、CSS 资源。 在调试模式下,loader 会直接加载 `.vue` 模板文件,并动态编译为 JS 模块。 在打包发布时,`.vue` 模板会被编译为压缩混淆后的 JS 文件,由 loader 直接加载,以减少响应体积并提升加载效率。 前端资源文件需统一放在 `resources/static/` 目录下,该目录对应 Web 请求根路径。 本文默认读者已掌握 [ES6](ES6.md) 与 Vue 3 基础知识。 #### 前端框架支持 EasyBoot 使用 [es-module-shims](https://github.com/guybedford/es-module-shims) 实现 ES Modules 加载。 在 UI 组件库方面,内置 [HeyUI](https://www.heyui.top/) 与 [ElementPlus](https://element-plus.org/zh-CN/component/overview),框架示例默认使用 HeyUI。 若引入第三方库,需满足 ESM 规范。EasyBoot 支持加载 `.mjs` 模块与 `.less` 样式文件。 其中 `.mjs` 文件用于满足 [ESM](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules) 模块要求;语法支持范围受浏览器兼容性影响。 除压缩与混淆外,EasyBoot 不提供额外语法转换。 Vue 组件页面和 React 组件页面可通过 [veaury](https://github.com/gloriasoft/veaury/blob/master/README_zhcn.md) 互相引用。 直接支持的文件类型如下: mjs 库 ```javascript // hello.js,满足 ESM 语法 function hello(){ } export default{ hello } // 引用 import {hello} from './hello' ``` less 样式 ```less /** demo.less **/ .some{ .color{ color:red; } } /** * HTML 中引用 * */ /** * JS 文件中引用 * require('./demo.css') * mjs 或 Vue 模块中引用 * require('./demo.css') 或 import './demo.css' */ ``` Vue 页面 ```vue ``` React 页面 ```jsx // demo.jsx // 在 React 中使用 Vue 组件 import React,{useState,Component} from 'react'; import Demo from './demo.vue' import {applyPureVueInReact,VueContainer} from 'veaury' const DemoComponent=applyPureVueInReact(Demo) export default function (props) { // 如果 'vue-router' 存在,则渲染 '' 可以使用 '' return <> {/* 使用applyPureVueInReact定义的组件 */} {/* 或者使用VueContainer组件 */} > } ``` #### 页面与菜单路径 通过在后端代码中配置菜单页面路径的方式,指定页面路径。或在管理后台中手动新增菜单方式指定页面路径。页面及其他静态资源放在resources/static/目录下,这也是前端资源的根目录。 一般情况下,菜单路径直接指定为带后缀的静态资源路径,例如:/path/to/page.vue,/path/to/page.jsx。 当需要不通过菜单直接打开某个组件页面时,可通过 URL 参数 `v` 指定静态路径,例如 [http://localhost:8080/?v=path/to/page.vue](http://localhost:8080/?v=path/to/page.vue)。支持 **Vue 3 单文件组件**(`.vue`)以及基于 **Preact**、兼容 React 语法的 **JSX**(`.jsx`)。 如 [http://localhost:8080/?v=path/to/page.jsx](http://localhost:8080/?v=path/to/page.jsx) 菜单路径规则如下: 1. _blank: 开头的菜单,表示在新窗口打开后面的链接 2. url: 开头的菜单会将路径嵌入iframe内进行显示,无法显示组件,仅支持html页面显示 3. top: 开头的菜单,支持vue,jsx页面组件,表示该页面显示为全局页面,不会被嵌入到页面框架内(即不被框架的Header,侧边栏,菜单等) 4. 其他路径:表示该页面为普通页面,会嵌入到页面框架内显示,以.vue结尾为vue组件,以.jsx结尾为react组件 ### 首页重写 如需重写首页,可在 `resources/static/` 目录下创建 `home.vue` 文件,即可覆盖默认首页。 ```vue 欢迎使用EasyBoot ``` #### 引入三方库的方法 可从前端 CDN 站点(如 [jsDelivr](https://www.jsdelivr.com/?docs=esm))直接下载编译好的 ESM 文件。若 CDN 无现成产物,可在 [npm](https://www.npmjs.com/) 搜索对应模块,自行在前端工程中构建并导出 ESM 文件,再放入 `static/js/lib` 目录。完成后可通过以下三种方式引入三方 JS: 1. 在后端使用jsModule注册模块别名,如: jsModule("moduleName","js/lib/module_a.js") ,前端直接通过别名引入,如: import xx from 'moduleName' 2. 或者前端直接使用相对路径导入,如: import xxx from '../js/lib/xx.js' 3. 如果没有esm格式的js文件,常规格式的也能支持,通过 import '../xxx.js' 方式导入,如:import './js/lib/jquery.js',然后可以通过window引用其全局变量,如:window.jQuery。 该方式容易造成命名污染,不推荐。 ##### 前后端集成相关 在前端调用配置项(仅front=true时),所有配置项在window._configs中,如: ```javascript var DemoName=window._configs['DemoConfig.DemoName']; ``` 前端权限使用: ```vue 具有Demo Add 权限 ``` 前端调用后端接口时,除了直接使用 ajax 请求,也可以通过自动生成的 controller 代理接口调用。 ```vue ``` 前端可使用后端生成的校验器进行表单校验。引入路径为 `@validator/{校验器名称}.js`,校验器由后端 VO 上的 `@ClientValidator` 注解生成。 如: ```vue ``` 前端获取字典: ```vue {{it.value}}:{{it.title}} ``` ##### client.js `client.js` 位于 `easy-platform` 模块的 `static/js` 目录下,用于提供前端 ajax 请求能力,自动处理认证 token,并为非 EasyBoot 集成前端提供 controller 获取方式。`framework.js` 中的 ajax 方法基于 `client.js` 扩展。`client.js` 未注册模块别名,只能通过相对路径导入;通常不建议在普通业务代码中直接引用。 ```javascript // ajax 调用,推荐从 framework.js 引入 import {ajax} from '../js/client.js' ``` ##### framework.js `framework` 中封装了常用方法: ```javascript // ajax 相关:除特殊说明外,均返回 Promise import {ajax} from 'framework' // GET 请求 let promise=ajax.get(url,{...params}) // POST 请求 let promise=ajax.post(url,{...params}) // PUT 请求 let promise=ajax.put(url,{...params}) // DELETE 请求 let promise=ajax.delete(url,{...params}) // POST 表单提交 ajax.formPost(url,{...params}) // POST 上传单个文件 let params={ file:File } // 或多个文件 let params={ file:[...files] } // 或命名的多个文件(需与后端参数名对应) let params={ file:file, file1:file1 } ajax.upload(url,{...params}) /** * 文件下载(也可使用 window.open(url) 等方式下载文件)。 * 若需要登录用户授权,可添加 URL 参数:_token=sessionStorage._token */ ajax.download(url,params={},method="POST") // ajax 方式的文件下载 downloadA(path, fileName = "", params = {}, method = 'GET') /** * 加载带缓存的数据,可直接用于 Vue 数据绑定。 * @param url * @param defaultValue * @returns {VueRef} */ ajax.data(url, defaultValue) /** * 通过 URL 创建数据集,可用于 DataTable、Select 等组件数据绑定。 */ ajax.createDs(uri, pageAble = false) // 数据集使用示例 const ds=ajax.createDs('someurl',true) // 数据集中的数据,可直接绑定到 DataTable ds.data // 设置附加查询参数,也可直接绑定到 Vue 查询控件 ds.params.param1='value1' // 加载数据(也可用于重新加载) ds.load() // 设置数据转换:每次加载后都会经过该转换函数 ds.transformer((v)=>v); // 清空数据 ds.clean() // 设置自定义数据 ds.putData([...data]) // 在数据加载前修改查询参数 ds.on('beforeLoad',(params)=>undefined) // 在数据加载后修改响应数据 ds.on('load',(data)=>undefined) // 数据加载错误时回调 ds.on('error',(error)=>undefined) ``` ```javascript // 其他用法 import {uuid,format,hasPermission,hasRole,mapState,mapActions} from 'framework' // 生成随机 ID,例如:'1gk2o6462g94p92' uuid() // 数据格式化,两种调用方式均可得到 '1.00' format(1,"####.00") const fmt=format("####.00"); fmt(1) // 日期格式化,两种调用方式均可得到 '2022-12-12' format(Date.now(),"yyyy-MM-dd") const fmt=format("yyyy-MM-dd"); fmt(Date.now()) // 判断用户是否拥有某个权限/角色 hasPermission('some.priv') hasRole('some.role') // 引用 Vue Store(也可直接使用 Vuex) const [user,theme]=mapState(['user','theme']) const loadUserInfo = mapActions(['loadUserInfo']) ``` ```javascript // framework util 中包含常用数据处理方法 import {util} from 'framework' /** * 将数组转换为 map。 * @param list 数组 * @param getId * @param getValue * @returns {Object} */ util.asMap(list, getId = (it, idx) => idx, getValue = it => it) /** * 将数组转换为树。parentId 为 0、false、null,或找不到 parent 的节点会作为根节点。 * @param list * @param getId * @param getParentId * @param getValue * @returns {Array} */ util.asTree(list, getId = it => it, getParentId = it => null, getValue = it => it) /** * 获取一个对象中的多个值 */ util.fields(obj, fields) let obja={a:1,b:2,c:3} //obj2={a:1,b:2} let obj2=util.fields(obj,"a,b") //折叠/展开字段。由于现在后台本身支持类似的解码,所以本方法使用较少 // 输出 {a:"1,2,3",b:"1,2,3"} util.fold({a:[1,2,3],b:[1,2,3]}, "a,b") // 输出 {a:[1,2,3],b:[1,2,3]} util.unfold({a:"1,2,3",b:"1,2,3"}, "a,b") //清除对象的数据,输出: {a:null,b:null} util.clean({a:1,b:1}) /** * 导入Excel文件到浏览器 * * Excel导入浏览器 * */ function doImport(file){ //是否作为对象数组导出,以第一行为key值,默认为false,导出为二维数组 let asObj=true util.importExcel(file,asObj).then(r =>{ console.log(r) }) } import {useEvent,useStore,toRef,useDlg,getToken} from 'framework' // 事件总线 // 定义事件总线,其中 server 表示服务器推送事件 let events=userEvent("server") // 派发事件:server 总线中的事件由后端 com.rankeiot.platform.service.ServerEventPushService 派发 events.dispatchEvent("eventname",data) // 添加监听 events.addEventListener("eventname",listener) // 全局状态:第二个参数为默认值,可为直接值、函数或异步函数;返回值为 ref let theme=useStore('theme','green') // 将直接值、函数返回值或异步函数响应值转换为 ref let data=toRef(ajax.get('xxxx')) /** * 封装内联对话框需要数据和方法 * dlg.isOpen * dlg.data * dlg.open(opendata=null) * dlg.close() * dlg.submit() */ let dlg=useDlg({},function onSubmit(){ }) // 获取用户认证所需 token let token = getToken() // 设置菜单红点:需在前端菜单初始化完成后调用,建议放在 layout 或顶部工具扩展栏逻辑中 import {setMenuDot} from 'framework' // 显示数字角标 setMenuDot("test.menu",'66') // 仅显示红点 setMenuDot("test.menu") // 清除菜单红点 setMenuDot("test.menu",'') ``` #### 扩展组件 EasyBoot 提供了部分扩展 Vue 组件: ```vue 把图片文件拖入到这里 ``` 前端DataTable组件自带Excel数据导出功能,配合Table组件使用。 通过标签定义表格列,导出数据时,只选择有prop属性的列进行导出。可通过format或render传入自定义格式的格式化函数,用于定制字段输出格式。 `dict`、`format`、`render` 三个属性同一列只会生效一个。若页面需要复杂渲染,可使用默认插槽;但导出数据不会解析插槽,此时可通过 `render` 或 `format` 的第二个参数进行导出场景下的条件处理。 ```vue ``` ##### DataSet 组件 用于获取后端数据的封装组件。 ```vue ``` 参数 | 参数 | 类型 | 默认值 | 说明 | |---------------|--------------------------| ------------ |-------------------------------| | loader | function(params):Promise | | 获取数据的方法 | | url | string | | 获取数据的URL地址 | | pageable | boolean | false | 是否分页 | | pageSize | number | 10 | 每页数据长度 | | method | string | post | 数据提交方式 | | lazy | boolean | false | lazy 为 true 时,需要手动调用 `load` 方法载入数据 | | transform | Function | (data)=>data | 数据转换方法,用于在显示前处理数据 | 事件 | 事件 | 参数 | 说明 | | ---------- | ----- | -------------------------------------- | | beforeLoad | param | 在请求前被调用,传入请求参数 `param`,可通过修改 `param` 的值调整请求 | | load | data | 在请求完成后、数据展示前被调用,传入参数为响应数据,可在这里修改展示数据 | | afterLoad | data | 在数据转换完成后被调用,传入转换处理过的数据 | 属性方法通常通过 `ref` 获取引用,同时在 `slot` 中也提供同名属性。 | 方法/属性 | 类型 | 说明 | | ---------- | ----------------- | ----------------------------------- | | load | ()=>{} | 载入数据 | | reload | ()=>{} | 重新载入数据,相较于load,reload会把分页设置为第一页 | | setData | (data)=>{} | 手动设置其数据 | | setSort | ({prop,type})=>{} | 设置排序,prop为排序的字段,type为 desc/asc 排序类型 | | params | Object | 当前查询参数,在 `slot` 中可用于绑定查询条件 | | pagination | {page,size} | 当前的分页状态,可用于绑定分页条 | | loading | ref(bool) | 当前数据加载状态,可用于绑定数据加载装状态 | ##### Dialog 对话框组件 用于打开对话框,基于 HeyUI Modal 实现,并继承 [Modal弹出框组件](https://v2.heyui.top/component/message/modal) 的全部属性。 不同之处在于,这里 `url` 对应的组件为懒加载。 ```vue 对话框标题 取消 确认 ``` dlg.vue ```vue ``` ##### 图表(Chart) [G2Plot](http://g2plot-v1.antv.vision/zh/docs/manual/introduction/)是一套简单、易用、并具备一定扩展能力和组合能力的统计图表库,基于图形语法理论搭建而成, 可通过 `import '@antv/g2plot'` 引用纯 JS 版图表,也可以直接使用封装好的 `G2Plot` 组件。 推荐用法: ```vue ``` Chart 基于[Chart.js](https://www.chartjs.org/) v4.4.0 Simple yet flexible JavaScript charting library for the modern web 直接引用js使用import 'chart',组件为Chart ```vue ``` [ECharts](https://echarts.apache.org/handbook/zh/get-started/) 是一个基于 JavaScript 的开源可视化库,用于创建各种静态、动态和交互式的图表。 js引用使用 import * as echarts from 'echarts'; EChart ```vue ``` ##### 前端导入Excel ```vue 测试Excel导入 ``` ### 前端Excel表格 [vue3-excel-editor](https://www.jsdelivr.com/package/npm/vue3-excel-editor?path=src) ```vue import { VueExcelEditor,VueExcelColumn} from 'vue-excel-editor' ``` ### 登录签名(token)算法 ```js //过期时间(s) 默认1800s let expire = 1800; //到期时间 let time = parseInt(Date.now() / 1000) + expire; //到期时间转换为7位36进制的字符串,不足位数以0补全 let t = leftPad(time.toString(36), '0', 7); //后端应用签名,一般为启动类类名,可通过配置 spring.application.name 这个配置项/环境变量来设置 let appSign='com.ranke.xxx.xxApplication' //userType为一位字符串一般为U或M,表示用户类型为用户或者会员,用户后端寻找用户 let value=userType+username //盐,用于验证签名,md5以hex形式展示 let salt=md5hex(username + "$" + password) + appSign let v = value + time + salt; // 最终 token 长度为 7+24+n;其中 base64 采用 urlBase64,结果中的 '+'、'/' 会变为 '-'、'_' let token=t + md5base64(v) + urlbase64(value) ``` ### 应用插件系统(次要) 仅插件需要通过安装卸载模式运行时使用。一般业务功能模块插件使用spring的自动扫描配置,打包为常规spring可自动扫描的jar包即可,如有默认配置, 添加resources/plugin_config.yml,按spring配置文件规则编写配置,应用启动时会自动加载为默认配置,其中配置项可被主应用的application.yml 中配置项,启动参数等同名配置值覆盖。 #### 基座平台 ```xml 4.0.0 easyboot com.rankeiot.easy ${lastVersion} com.your.groupid yourartifact 1.0-SNAPSHOT rankeiot-public-central central https://rankeiot-maven.pkg.coding.net/repository/public/central/ true true always rankeiot-public-central central https://rankeiot-maven.pkg.coding.net/repository/public/central/ true true always com.rankeiot.easy easy-vue com.rankeiot.easy easy-platform mysql mysql-connector-java org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok ``` ```java public static void main(String[] args) { new PlatformApplication().start(args); } ``` #### 应用插件 应用插件配置说明 插件目录结构,将插件的jar文件及依赖jar(主框架已经包含的不需要再次添加)文件和插件描述文件plugin.json一起打为zip包(没有二级目录) ``` / ├── plugin.json ├── lib1.jar ├── lib2.jar ├── ... ``` 描述文件,其中id,version,framework为必填项,其他为选填项 ```json5 { "id":"plugin-id", //插件ID,必填 "version":"1.0-SNAPSHOT",//插件版本号,必填 "framework": "1.1-SNAPSHOT", //主框架版本,必填 "name":"插件名称", "author":"EasyBoot", "description":"一个插件", "homepage":"https://gitee.com/desire1/app_manager", "depends":[ {"id":"other-plugin","version":"1.0.0"} //其他插件依赖 ] } ``` 应用功能文件结构: ``` /root ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── yourpackage │ │ │ └── YourModule.java │ │ └── resources │ │ ├── META-INF │ │ │ └── spring │ │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports │ │ ├── static │ │ │ └── moduledir │ │ │ └── somefunction.vue │ │ ├── application-plugin.yml // 可选的插件默认配置,按 Spring Boot 配置规范编写 │ │ └── plugin.json │ └── test │ ├── java │ │ └── yourpackage │ │ └── MainTestApp.java │ └── resources │ └── application.yml //测试用配置文件 ``` MainTestApp.java ```java //MainTestApp.java public static void main(String[] args) { new PlatformApplication().start(args); } ``` plugin.json ```json { "id": "${artifactId}", "version": "${version}", "framework": "${easy.ver}", "author": "EasyBoot", "name": "${name}", "description": "${description}", "depends": [ ] } ``` pom ```xml 4.0.0 easyboot com.rankeiot.easy ${lastVersion} com.your.groupid yourartifact 1.0-SNAPSHOT DemoAPP demo app是一个测试接口应用 rankeiot-public-central central https://rankeiot-maven.pkg.coding.net/repository/public/central/ true true always rankeiot-public-central central https://rankeiot-maven.pkg.coding.net/repository/public/central/ true true always com.rankeiot.easy easy-vue com.rankeiot.easy easy-platform mysql mysql-connector-java org.projectlombok lombok compile true org.apache.maven.plugins maven-resources-plugin copy-resources generate-resources copy-resources ${project.build.outputDirectory}/ src/main/resources true plugin.json ${*} ``` 应用模块配置类 ```java package com.your.groupid; //配置类的包名尽量不要与其他模块及框架的包名冲突 @Configuration public class YourModule implements Module { public void start(ApplicationContext applicationContext) { //注册所需的各种功能 //regFixedDict(AppPlatform.class); //regFixedDict(ResourceType.class); //regMenu(AppMenu.class); //regConfigs(AppMgConfig.class); } } ``` 插件自动扫描配置 - 新增文件:`src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` - 每行一个自动配置类全限定名,例如: ```properties com.your.groupid.YourModule ``` 插件默认配置文件: - 可在 `src/main/resources` 下提供插件用 Spring Boot 配置(如示例中的 `application-plugin.yml`);多插件若存在相同配置项,后加载的可能覆盖先加载的,请注意键名冲突。 ### 相关文档 - 本仓库示例模块说明:[easy-demo/README.md](easy-demo/README.md) - 数据库表结构说明:[数据库设计.md](数据库设计.md) - [EasyBoot 源码(Gitee)](https://gitee.com/qoder/easy-boot/)(默认分支版本 `1.1`)— 远端目录 `easy-demo` 与本地示例模块对应 - [SqlToy 文档](https://gitee.com/sagacity/sagacity-sqltoy) - [Vue3 文档](https://cn.vuejs.org/) - [HeyUI 文档](https://v2.heyui.top/) - [ElementPlus 文档](https://element-plus.org/zh-CN/)