# anno-admin
**Repository Path**: hxj0x/anno-admin
## Basic Information
- **Project Name**: anno-admin
- **Description**: Anno-Admin是一个开源项目,旨在通过注解生成后台管理系统。它可以帮助开发者更轻松地创建功能强大的后台管理系统,无需手动编写大量代码。使用Anno-Admin,只需在代码中添加注解,就可以自动生成各种功能,包括用户管理、权限管理、数据管理等。这个项目适合需要快速开发后台管理系统的开发者,也适合想学习注解和代码生成技术的开发者。
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 24
- **Created**: 2023-08-17
- **Last Updated**: 2023-11-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
中文 | [English](./README-EN.md)
Anno Admin
零前端代码,注解驱动,轻松拓展实现界面化CRUD
Solon / SpringBoot 3.x
---
码云仓库 |
Github 仓库 |
📕 使用文档
---
作者微信(添加请备注 anno-admin):

---
## 🚀 简介 | Intro
Anno-Admin是一个新兴的开源项目,旨在通过注解生成后台管理系统,在原项目上轻松拓展实现界面化CRUD。
零前端代码、零 CURD、自动建表,仅需 **一个类文件** + 简洁的注解配置,快速开发企业级 Admin 管理后台。
高扩展性,支持CURD自由扩展、自定义数据源、逻辑删除、OSS。
提供企业级中后台管理系统的全栈解决方案,大幅压缩研发周期,专注核心业务。
> 完美取代 **代码生成器**,开发后台管理系统更优解!
## 🌈 特性 | Features
+ **适配主流框架**:支持**Solon** / **Spring Boot**
+ **自动建表**:表结构自动生成,无需手动建表
+ **虚拟表 / 列**:支持虚拟表 / 虚拟列,轻松对接拓展
+ **易于上手**:会简单的 **Spring Boot / Solon** 基础知识即可
+ **使用简单**:仅需了解 **@AnnoMain** 与 **@AnnoField** 两个注解即可上手开发
+ **代码简洁**:仅需一个 `.java` 文件, template、controller、service、dao 都不需要创建
+ **功能强大**:动态条件处理,逻辑删除等
+ **数据源**:支持:MySQL,持续拓展中。
+ **高扩展性**:支持自定义数据源实现、动态权限管理、**自定义 OSS**
+ **大量组件**:时间选择、**一对多**、图片上传、代码编辑器、自动完成、树、**多对多**、地图等多类组件
+ **丰富展示**:普通文本、**二维码**、链接、图片、HTML、代码段等
---
+ **低侵入性**:几乎所有功能都围绕注解而展开,不影响Spring Boot / Solon其他功能或三方库库的使用
+ **前后端分离**:后端与前端可分开部署
+ **前端零代码**:前端布局自动构建,一行前端代码都不用写
+ **无需二次开发**:仅需引用 jar 包即可 !
## 演示
系统菜单:

系统菜单代码:
```java
package site.sorghum.anno.modular.menu.entity.ao;
import com.alibaba.fastjson2.annotation.JSONField;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.noear.wood.annotation.Table;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoMain;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoPermission;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoTree;
import site.sorghum.anno.modular.anno.annotation.field.AnnoEdit;
import site.sorghum.anno.modular.anno.annotation.field.AnnoField;
import site.sorghum.anno.modular.anno.annotation.field.AnnoSearch;
import site.sorghum.anno.modular.anno.annotation.field.type.AnnoOptionType;
import site.sorghum.anno.modular.anno.enums.AnnoDataType;
import site.sorghum.anno.modular.preinstall.model.BaseMetaModel;
/**
* 系统菜单
*
* @author Sorghum
* @since 2023/05/19
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AnnoMain(name = "菜单管理",
annoTree = @AnnoTree(label = "title", parentKey = "parentId", key = "id", displayAsTree = true),
annoPermission = @AnnoPermission(enable = true, baseCode = "sys_anno_menu", baseCodeTranslate = "菜单管理"))
@Table("sys_anno_menu")
public class SysAnnoMenu extends BaseMetaModel {
@JSONField(name = "parentId")
@AnnoField(title = "父菜单", tableFieldName = "parent_id", edit = @AnnoEdit)
private String parentId;
@AnnoField(title = "菜单名称", tableFieldName = "title", edit = @AnnoEdit)
@JSONField(name = "title")
private String title;
@AnnoField(title = "菜单类型", tableFieldName = "type", search = @AnnoSearch(),
dataType = AnnoDataType.OPTIONS,
optionType = @AnnoOptionType(value = {
@AnnoOptionType.OptionData(label = "页面", value = "1"),
@AnnoOptionType.OptionData(label = "目录", value = "0")
}),
edit = @AnnoEdit(placeHolder = "请选择菜单类型", notNull = true))
@JSONField(name = "type")
private Integer type;
@AnnoField(title = "菜单排序", tableFieldName = "sort", edit = @AnnoEdit)
@JSONField(name = "sort")
private Integer sort;
@AnnoField(title = "打开方式", tableFieldName = "open_type", edit = @AnnoEdit, optionType = @AnnoOptionType(value = {
@AnnoOptionType.OptionData(label = "内置页", value = "_iframe"),
}), dataType = AnnoDataType.OPTIONS)
@JSONField(name = "openType")
private String openType;
@AnnoField(title = "菜单图标", tableFieldName = "icon", edit = @AnnoEdit)
@JSONField(name = "icon")
private String icon;
@AnnoField(title = "菜单链接", tableFieldName = "href", edit = @AnnoEdit)
@JSONField(name = "href")
private String href;
@AnnoField(title = "权限标识", tableFieldName = "permission_id", edit = @AnnoEdit,
dataType = AnnoDataType.OPTIONS,
optionType = @AnnoOptionType(sql = "select id as value, name as label from sys_permission where del_flag = 0 and parent_id is null order by id desc"))
@JSONField(name = "permissionId")
private String permissionId;
}
```
系统组织:

系统组织代码:
```java
package site.sorghum.anno.modular.preplugin.ao;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.noear.wood.annotation.Table;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoMain;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoPermission;
import site.sorghum.anno.modular.anno.annotation.field.AnnoButton;
import site.sorghum.anno.modular.anno.annotation.field.AnnoEdit;
import site.sorghum.anno.modular.anno.annotation.field.AnnoField;
import site.sorghum.anno.modular.anno.annotation.field.AnnoSearch;
import site.sorghum.anno.modular.preinstall.model.BaseMetaModel;
/**
* 系统组织
*
* @author sorghum
* @since 2023/05/27
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AnnoMain(name = "组织管理",
annoPermission = @AnnoPermission(enable = true, baseCode = "sys_org", baseCodeTranslate = "组织管理"))
@Table("sys_org")
public class SysOrg extends BaseMetaModel {
/**
* 部门名字
*/
@AnnoField(title = "部门名字", tableFieldName = "org_name",
search = @AnnoSearch(),
edit = @AnnoEdit(editEnable = true, addEnable = true, placeHolder = "请输入部门名字"))
private String orgName;
@AnnoButton(name = "组织用户",
o2mJoinButton = @AnnoButton.O2MJoinButton(joinAnnoMainClazz = SysUser.class, joinThisClazzField = "id", joinOtherClazzField = "orgId", enable = true))
private Object userButton;
@AnnoButton(name = "跳去百度", jumpUrl = "https://www.baidu.com/?tn=${clazz}&props=${props}")
private Object jump2BaiduButton;
@AnnoButton(name = "简单的JS命令", jsCmd = "alert('点击了按钮'); console.log(props);")
private Object jsCmd;
}
```
系统角色:

系统角色代码:
```java
package site.sorghum.anno.modular.preplugin.ao;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.noear.wood.annotation.Table;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoMain;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoPermission;
import site.sorghum.anno.modular.anno.annotation.field.AnnoButton;
import site.sorghum.anno.modular.anno.annotation.field.AnnoEdit;
import site.sorghum.anno.modular.anno.annotation.field.AnnoField;
import site.sorghum.anno.modular.anno.annotation.field.AnnoSearch;
import site.sorghum.anno.modular.anno.annotation.field.type.AnnoOptionType;
import site.sorghum.anno.modular.anno.enums.AnnoDataType;
import site.sorghum.anno.modular.preinstall.model.BaseMetaModel;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = true)
@AnnoMain(name = "角色管理",
annoPermission = @AnnoPermission(enable = true, baseCode = "sys_role", baseCodeTranslate = "角色管理"))
@Table("sys_role")
public class SysRole extends BaseMetaModel implements Serializable {
/**
* 角色名称
*/
@AnnoField(title = "角色名称", tableFieldName = "role_name", search = @AnnoSearch(),
edit = @AnnoEdit(placeHolder = "请输入角色名称", notNull = true))
String roleName;
/**
* 排序
*/
@AnnoField(title = "排序", tableFieldName = "sort", search = @AnnoSearch(),
edit = @AnnoEdit(placeHolder = "请输入排序", notNull = true))
Integer sort;
/**
* 状态 1 正常 0 封禁
*/
@AnnoField(title = "状态", tableFieldName = "enable", search = @AnnoSearch(),
dataType = AnnoDataType.OPTIONS,
optionType = @AnnoOptionType(value = {
@AnnoOptionType.OptionData(label = "正常", value = "1"),
@AnnoOptionType.OptionData(label = "封禁", value = "0")
}),
edit = @AnnoEdit(placeHolder = "请选择状态", notNull = true))
Integer enable;
/**
* 角色按钮
*/
@AnnoButton(name = "用户", m2mJoinButton = @AnnoButton.M2MJoinButton(
joinAnnoMainClazz = SysUser.class,
mediumTable = "sys_user_role",
mediumTableClass = SysUserRole.class,
mediumOtherField = "role_id",
mediumThisFiled = "user_id",
joinThisClazzField = "id"
))
private Object userButton;
/**
* 角色按钮
*/
@AnnoButton(name = "权限", m2mJoinButton = @AnnoButton.M2MJoinButton(
joinAnnoMainClazz = SysPermission.class,
mediumTableClass = SysRolePermission.class,
mediumOtherField = "role_id",
mediumThisFiled = "permission_id",
joinThisClazzField = "id"
))
private Object roleButton;
}
```
系统用户:

系统用户代码:
```java
package site.sorghum.anno.modular.preplugin.ao;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.noear.wood.annotation.PrimaryKey;
import org.noear.wood.annotation.Table;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoMain;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoPermission;
import site.sorghum.anno.modular.anno.annotation.clazz.AnnoProxy;
import site.sorghum.anno.modular.anno.annotation.field.AnnoButton;
import site.sorghum.anno.modular.anno.annotation.field.AnnoEdit;
import site.sorghum.anno.modular.anno.annotation.field.AnnoField;
import site.sorghum.anno.modular.anno.annotation.field.AnnoSearch;
import site.sorghum.anno.modular.anno.annotation.field.type.AnnoImageType;
import site.sorghum.anno.modular.anno.annotation.field.type.AnnoOptionType;
import site.sorghum.anno.modular.anno.enums.AnnoDataType;
import site.sorghum.anno.modular.auth.service.AuthService;
import site.sorghum.anno.modular.preinstall.model.BaseOrgMetaModel;
import proxy.site.sorghum.anno.pre.plugin.SysUserProxy;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = true)
@AnnoMain(name = "用户管理",
annoPermission = @AnnoPermission(enable = true, baseCode = "sys_user", baseCodeTranslate = "用户管理"),
annoProxy = @AnnoProxy(value = SysUserProxy.class))
@Table("sys_user")
public class SysUser extends BaseOrgMetaModel implements Serializable {
/**
* 用户头像
*/
@AnnoField(title = "用户头像",
tableFieldName = "avatar",
dataType = AnnoDataType.IMAGE,
edit = @AnnoEdit(placeHolder = "请上传用户头像"),
imageType = @AnnoImageType(thumbMode = AnnoImageType.ThumbMode.COVER, thumbRatio = AnnoImageType.ThumbRatio.RATE_ONE))
private String avatar;
/**
* 手机号
*/
@AnnoField(title = "手机号", tableFieldName = "mobile", search = @AnnoSearch(),
edit = @AnnoEdit(placeHolder = "请输入手机号", notNull = true))
private String mobile;
/**
* 密码
*/
@AnnoField(title = "密码", tableFieldName = "password",
edit = @AnnoEdit(placeHolder = "请输入密码", notNull = true, editEnable = false), show = false)
private String password;
/**
* 用户名
*/
@AnnoField(title = "用户名", tableFieldName = "name", search = @AnnoSearch(),
edit = @AnnoEdit(placeHolder = "请输入用户名", notNull = true))
private String name;
/**
* 状态 1 正常 0 封禁
*/
@AnnoField(title = "状态", tableFieldName = "enable", search = @AnnoSearch(),
dataType = AnnoDataType.OPTIONS,
optionType = @AnnoOptionType(value = {
@AnnoOptionType.OptionData(label = "正常", value = "1"),
@AnnoOptionType.OptionData(label = "封禁", value = "0")
}),
edit = @AnnoEdit(placeHolder = "请选择状态", notNull = true))
private Integer enable;
/**
* 角色按钮
*/
@AnnoButton(name = "角色", m2mJoinButton = @AnnoButton.M2MJoinButton(
joinAnnoMainClazz = SysRole.class,
mediumTable = "sys_user_role",
mediumTableClass = SysUserRole.class,
mediumOtherField = "user_id",
mediumThisFiled = "role_id",
joinThisClazzField = "id"
))
private Object roleButton;
/**
* 重置密码按钮
*/
@AnnoButton(name = "重置密码", javaCmd = @AnnoButton.JavaCmd(beanClass = AuthService.class, methodName = "resetPwd"))
private Object resetPwdButton;
}
```
## 初步文档
### 全局注解:
@AnnoScan
启动类上增加
### 类注解:
#### 主要类:
@AnnoMain
- name:类名称
- annoProxy:前置代理类
- @AnnoProxy
- value**: **代理类,Class extends AnnoBaseProxy>
- annoPermission:权限启用类
- @AnnoPermission
- baseCode:基础权限名称(即查看权限) 如:sys_org
- baseCodeTranslate:基础权限翻译,入:系统组织
- enable: 是否启用,默认false
- annoLeftTree:左树右表配置
- @AnnoLeftTree
- catKey:分类关键词,当前类的树列的名称
- treeClass:左边的树类
- enable:是否启用,默认为true
- annoTree:当前类树的配置
- @AnnoTree
- label: 标签 树展示字段,一般为name
- parentKey:父节点的的关键词,一般为:pid
- key: 当前节点关键词,一般为id
- displayAsTree:是否展示为树
- enable:是否启用
@Table
- value: 声明表名
#### 删除配置类:
@AnnoRemove
- removeType:删除类型 0 物理删除 1 逻辑删除
- removeValue:逻辑删除值 默认 1
- notRemoveValue:逻辑删除值 默认 0
- removeField:逻辑删除字段 默认 del_flag
#### 前置代理类:
@AnnoPreProxy
- value:代理类,Class extends AnnoPreBaseProxy> value()
### 列注解
主要类:
@AnnoField
- title:标题
- tableFieldName:表字段名
- show:显示
- search:搜索信息
- @AnnoSearch
- notNull:是否必填
- placeHolder:提示信息
- enable:是否启用,默认启用
- edit: 编辑信息
- @AnnoEdit
- notNull:是否必填
- placeHolder:提示信息
- addEnable:启用新增
- editEnable:编辑启用
- dataType:数据类型
- **_STRING _**字符串
- **_FILE _**文件
- **_IMAGE _**图片
- **_NUMBER _**数字
- **_DATE _**日期
- **_DATETIME _**日期时间
- **_OPTIONS _**下拉框
- **_RICH_TEXT _**富文本
- **_EDITOR _**编辑器
- optionType:如果是options,则需要完成:选择类型
- @AnnoOptionType
- sql:比如 select value, label from table where del_flag = 0 order by id desc
- value:@OptionData[]
- imageType:如果是iamge,则需要完成:图像类型
- @AnnoImageType
- thumbRatio:预览图比率.
- thumbMode:图片模式
- enlargeAble:点击可放大展示
- width:宽度 px
- height:高度 px
@PrimaryKey
- value:主键名称
#### 按钮注解
> 自定义逻辑 一对多 多对多的连表逻辑
@AnnoButton
- name:按钮名称
- size:按钮大小 'xs' | 'sm' | 'md' | 'lg'
- ----以下为按钮事件----
- jsCmd: 按下按钮后的js命令
- jumpUrl:跳转的url
- o2mJoinButton:一对多关联按钮
- m2mJoinButton:多对多关联按钮
- javaCmd:java命令
## 自动生成表结构
项目启动后,会对`@AnnoMain`注解标记的实体类,做以下处理:
1. 如果实体类对应的表不存在,自动创建表;
2. 如果表存在,且实体类上有新增字段,会将新增字段添加到已有的表上;
3. 对于实体类上已经删除的字段,不会将已有的字段删除,需要手动处理;
4. 如果实体类上的字段信息(类型,长度,备注等)和数据库中不一致,不会做任何处理,需要手动处理;
如果想要关闭改功能,可以添加配置:
```yaml
anno-admin:
isAutoMaintainTable: false
```
## 预置数据
项目启动后,会扫描 `resources/init-data/*.sql` 的所有文件,将依次执行文件中的 sql。
对于 insert 语句:
- 如果当前行的数据不存在,则执行 insert 语句;
- 按照主键(`id`)查询数据库已有的数据,将每个字段,和当前的`insert`语句中的值进行比较,如果不一致,则生成 `update` 语句;
- 预置数据升级时,会忽略系统字段(`create_time`、`create_by`、`update_time`、`update_by`、`del_flag`)的比较;
如果想要关闭改功能,可以添加配置:
```yaml
anno-admin:
isAutoMaintainInitData: false
```
## 贡献
如果您发现了任何问题或有任何建议,请随时提交issue或pull request。我们非常欢迎您的贡献!
开发环境准备:
```
# 1. 打前端包到本地仓库(idea 启动项目前,需要先打包前端)
mvn clean install -pl anno-admin-ui -am
# 2. 打包后端代码
mvn clean package -pl \!anno-admin-ui -Dmaven.test.skip=true
```
## 许可证
Anno-Admin使用Apache 2.0许可证。详情请参阅LICENSE文件。