# EasyBoot **Repository Path**: rankeiot/easyboot ## Basic Information - **Project Name**: EasyBoot - **Description**: 一个易学、易用、易扩展的开发框架 - **Primary Language**: Java - **License**: MIT - **Default Branch**: 1.1 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2023-02-21 - **Last Updated**: 2026-03-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # EasyBoot [EasyBoot](https://gitee.com/rankeiot/easyboot) 是基于 **Spring Boot**、**SqlToy** 与 **Vue 3** 的全栈框架;内置 Vue / JS 的编译与资源处理,开发与部署**无需安装 Node.js**。 **本仓库**:父工程版本见根目录 `pom.xml`(当前为 `1.1-SNAPSHOT`),Spring Boot **3.5.x**,Java **17**,SqlToy 等版本由 BOM 统一管理。 ## 环境要求 | 项 | 说明 | | ----- | ----------------------------------------------------- | | 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/rankeiot/easyboot)**:核心框架与本仓库,提供平台能力与开发规范。 - **[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 youartifactId 1.0-SNAPSHOT UTF-8 UTF-8 17 17 UTF-8 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模块,js满足esm标注,前端页面通过 import xx from 'name' 方式使用 //jsModule("moduleName","js/module_a.js") //注册vue组件,注册后前端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 #测试用配置文件 ``` ### 后端指南 Easy Boot 后端采用 Spring Boot 与 SqlToy 进行开发。 #### 模块划分 Easy Boot 按业务划分 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 列表。) ``` ### 首页重写 ```vue ``` ### 数据库实体相关 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); } } ``` 前端调用验证器,引入路径为'@validator/{校验器名称}.js',如: ```vue ``` #### 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=l #[and m.operate date 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 t1on 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(string)表示调用该方法需要的权限,也可指定所需角色(role) * 当传入多个权限或者角色时,表示有当中任意一个条件满足即可调用该方法 * 以下@Api和ApiOperation注解为Swagger注解,使用Swagger注解的Controller才会出现在API文档中 * 除此外,Controller写法与常规SpringBoot Controller相同 * ClientCallable注解会自动生成前端调用的接口 * */ @Api(tags="学生信息管理") @Auth @RestController @ClientCallable @RequestMapping("demo") @RequiredArgsConstructor public class DemoController { //org.mapstruct.Mapper 类型映射接口,用于不同类的数据拷贝 final DemoMapper demoMapper; //数据接口 final DemoDao demoDao; //Sqltoy的默认DAO,可直接用数据操作,简单CRUD业务直接在controller中实现即可。复杂的或需要复用的,才提取到Service中 final LightDao dao; //Service 业务复杂或需要服用的业务再提取到service中 //final DemoService demoService; /** * 新增学生 */ @ApiOperation("新增学生") @Auth(StuMenu.STUDENT_ADD) @PostMapping("add") public Resp add(@Valid @RequestBody StudentRequest req){ //获取当前用户信息,如果该方法没有@Auth注解 UserInfo userInfo = Current.user(); Student student=demoMapper.toEntity(req); student.setAddTime(new Date()); student.setAddUser(User.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){ //获取查询排序,传入参数为前端字段与数据库字段的映射关系Map。key:前端传入的字段名,value:对应的数据库字段,可带查询语句中的表的别名,如: a.filed_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(); } } ``` 前端权限使用 ```vue ``` 前端接口调用 ```vue ``` #### 页面显示 菜单中配置上页面地址后,点击对应的菜单即可展示页面。页面及其他静态资源放在resources/static/目录下,根目录为该目录。 需要直接打开某页时,可通过 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) (菜单中路径也支持配置jsx文件) 。可通过[veaury](https://github.com/gloriasoft/veaury/blob/master/README_zhcn.md)对组件进行相互引用 ```vue ``` ```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 <> } ``` #### 系统配置项 固定配置项声明 系统配置项,通过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获取后,自行进行转换 } ``` 配置项也可在前端调用(仅front=true时),所有配置项在window._configs中,如: ```javascript var DemoName=window._configs['DemoConfig.DemoName']; ``` #### 固定字典项 调用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 filedName 转换的字段名称 * @param value 需要转换的值 * @return Object 转换后的值 */ Object translate(Object target, String filedName, Object value); } ``` 前端调用: ```vue ``` #### 定时任务 基于springboot的定时任务实现,可以在后台管理中产看和控制任务。建议用于简单低频的任务。任务高频时,推荐使用Springboot默认的方式或者XXLJob进行实现 ```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 implements SqlToyRespository{ //通过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) ExcelStreamExporter exporter = new ExcelStreamExporter(out); 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); }); } ``` 无需后端支持,前端DataTable组件自带Excel数据导出功能,配合Table组件使用。 通过 标签定义表格列,导出数据时,只选择有prop属性的列进行导出。可通过format或render传入自定义格式的格式化函数,用于定制字段输出格式。 dict,format,render三个属性只会生效一个。某些情况下,页面需要复杂显示的,可通过默认插槽进行渲染。但是导出数据不会解析插槽,这时候通过render或format的第二个参数,可以对内容进行条件处理 ```vue