# EasyBoot **Repository Path**: ApeCoder/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-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # EasyBoot ## 介绍 [EasyBoot](https://gitee.com/rankeiot/easyboot)为 SpringBoot + Sqltoy + Vue3 全栈框架。内置vue3与js的编译处理插件。开发与部署均无需node环境。 ## 相关技术 - 后端基础框架 [SpringBoot](https://spring.io/projects/spring-boot) - ORM框架 [Sqtoy](https://gitee.com/sagacity/sagacity-sqltoy) - Bean拷贝 [MapStruct](https://mapstruct.org/) - 前端框架 [Vue3](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)简单的示例启动项目 ### EasyBoot项目结构 - easy-core 核心模块,不带业务逻辑,包含了一些基础功能扩展,如配置项,菜单配置,权限认证等功能 - easy-platform 基础平台模块,包含了用户管理,权限管理,等平台业务功能,带相关界面 - easy-dev 开发工具模块,包括代码生成和一些小工具 - easy-vue vue模板加载模板,包含了vue的主文件。提供了vue文件,mjs文件,less文件的页面端支持 ### 系统功能 - 用户管理:提供用户的相关配置,新增用户后,默认密码为123456 - 角色管理:对权限与菜单进行分配,角色可绑定用户和菜单/按钮权限 - 部门管理:可配置系统组织架构,树形表格展示。部门有一个带级连关系的内部ID。 - 字典管理:可维护常用一些固定的数据,如:状态,性别等 - 配置项管理:管理各种常用可变配置 - 用户日志:记录用户操作日志,可自定义日志 - 慢SQL日志:记录运行慢的日志,方便维护和调优,阈值可设置 - 定时任务:方便定时任务,有任务执行记录 - 报表管理:XMReport集成,在线模板设计器,pdf导出 - 代码生成:高灵活度生成前后端代码,减少大量重复的工作任务 - API接口:可以导出api.json和在线调试 ## 快速开始 1. 新建maven项目,然后修改pom.xml ```xml 4.0.0 easyboot com.rankeiot.easy 1.1-SNAPSHOT yourgroupId youartifactId 1.0-SNAPSHOT 17 17 UTF-8 rankeiot-public-central central https://maven.cnb.cool/rankeiot/public/-/packages/ true true always rankeiot-public-central central 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.springframework.boot spring-boot-maven-plugin org.projectlombok lombok true ``` 2. 编写启动类 ```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); } } ``` 启动后打开浏览器进入 http://localhost:8080 ![安装](doc/install.png) 目前只支持了mysql,当然sqltoy本身是适配了多数据库的。填写完数据库信息后点确定即可进行数据库的初始化。初始化完成后会在项目目录下生成application-dev.yml文件。 可将application-dev.yml移动到src/main/resources目录下面,这样打包后运行不需要重新配置。也可以将application-dev.yml放置与jar包同一目录(工作目录) 初始化完成后不出意外的化,我们会看到下面的界面 ![安装完成](doc/installed.png) 进入系统后我们会看到一个非常干净的首页。可通过新建文件 src/main/resources/static/home.vue 来覆盖首页,其他页面也可以通过这种方式覆盖。 ![首页](doc/index.png) 到这里基本的系统已经跑起来了。 ## 开发指南 建议使用IDEA进行开发,根据Maven的profile配置,IDEA会在项目打开的时候会显示对于的配置选项。EasyBoot开发依赖数据库设计,当设计好数据库后,我们可以从系统中直接导入表模型进行代码生成。 ![IDEA](doc/idea.png) 开发的时候,maven默认选择了dev这个profile。在打包的时候应该选择prod,maven会根据参数替换application.yml中的配置,并去掉开发和接口api模块进行打包 ### 代码生成 在开发模式下(profile=dev),进入http://localhost:8080,选择代码生成器进入下面菜单。通过导入数据库中以及设计好的数据库表的方式,我们可以导入模型,生成对应代码。数据库表设计时,请添加必要的表注释及字段注释。 ![代码生成器](doc/designer.png) ### 项目目录结构说明 ``` /project_root ├── pom.xml ├── src │ ├── main │ │ ├── java │ │ │ └── yourpackage #应用基础包 │ │ │ ├── moduleA #应用模块A │ │ │ │ ├── controller #controller目录 │ │ │ │ ├── dao #DAO接口目录 │ │ │ │ ├── domain #Domain目录 │ │ │ │ │ ├── dto #DTO目录 │ │ │ │ │ ├── entity #数据库实体目录 │ │ │ │ │ ├── vo #VO目录 │ │ │ │ │ └── DemoMapper.java #MapStruct的数据映射接口 │ │ │ │ ├── service #service目录 │ │ │ │ ├── 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后端采用SpringBoot和sqltoy进行开发 #### 模块划分 Easy Boot按照业务划分包模块,可在模块中配置模块的业务菜单,固定配置项,字典项等 对于需要独立出来的模块,需要进行下面的定义 ```java //可参考com.rankeiot.platform.PlatformModule @Configuration public class DemoModule implements Module { public void start(ApplicationContext context) { //注册菜单 //regMenu(TestMenu.class); } } //按spring自动组件的要求,新建文件 /sources/META-INF/spring.factories,加入: //org.springframework.boot.autoconfigure.EnableAutoConfiguration=模块完整类名,如:com.rankeiot.platform.PlatformModule ``` ### 数据库实体相关 1. 通过[SqlToy](https://gitee.com/sagacity/sagacity-sqltoy)定义数据库实体类, 2. 通过Sqltoy的注解@Entity来标明后端实体, 3. 通过@Column绑定数据库字段,当查询用于输出VO时,输出字段无需使用@Column标注,输出会自动映射到同名字段。带下划线的字段名会按驼峰规则转换后映射,如:user_name->userName。如仍需@Cloumn映射字段的情况下,实体类需要标注@Entity注解(tableName可留空),否则@Column不生效。 4. 通过@ClientValidator生成前端实体验证器,名字与类同名,可以通过注解@ClientValidator("otherName")改写前端名字,可用于entity,dto等。与数据库、sqltoy无直接关系。验证器通过hibernate字段验证注解,如:@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/目录下,根目录为该目录。 当需要直接显示页面时,可以使用 http://localhost:8080/?v=path/to/page.vue直接访问页面。同时支持vue3的单文件组件文件和基于Preact(完全兼容react语法)的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