T-FAST是一款基于SpringBoot+MyBatis-Plus的快速开发脚手架,拥有完整的权限管理功能,可对接Vue前端,开箱即用。
该项目为前后端分离项目的后端部分,前端项目T-FAST-WEB地址:https://gitee.com/Thmspring/t-fast-web
技术 | 版本 | 说明 |
---|---|---|
SpringBoot | 2.7.0 | 容器+MVC框架 |
SpringSecurity | 5.7.1 | 认证和授权框架 |
MyBatis | 3.5.4 | ORM框架 |
MyBatis-Plus | 3.5.2 | MyBatis增强工具 |
MyBatis-Plus Generator | 3.5.2 | 数据层代码生成器 |
Swagger-UI | 3.0.0 | 文档生产工具 |
MySql | 8.0.31 | 关系数据库 |
Redis | 6.2.7 | 分布式缓存 |
Docker | 20.10.17 | 应用容器引擎 |
Druid | 1.1.10 | 数据库连接池 |
Hutool | 5.8.8 | Java工具类库 |
JWT | 0.9.0 | JWT登录支持 |
Lombok | 1.18.24 | 简化对象封装工具 |
minio | 8.4.2 | oss对象存储 |
JDK | 17.0.4.1 | JDK |
简化依赖服务,只需安装最常用的MySql和Redis服务即可
src
├── core -- 系统核心
| ├── annotation -- 相关注解
| ├── aspect -- 相关切面
| ├── config -- 相关配置
| ├── constant -- 常量信息
| ├── entity -- 公共实体
| ├── enums -- 枚举信息
| ├── exception -- 全局异常处理相关类
| ├── generator -- MyBatis-Plus代码生成器
| ├── oss -- 对象存储
| ├── service -- 通用业务类
| ├── security -- SpringSecurity认证授权相关代码
| ├── task -- 定时任务
| | ├── base -- 抽象定时任务父类以及注册器
| | └── job -- 具体定时任务
| └── utils -- 通用工具类
├── modules -- 存放业务代码的基础包
| └── sys -- 系统管理模块业务代码
| ├── controller -- 该模块相关接口
| ├── mapper -- 该模块相关Mapper接口
| ├── model -- 该模块相关实体类
| | ├── dto -- 该模块相关请求实体
| | └── vo -- 该模块相关响应实体
| └── service -- 该模块相关业务处理类
resources
├── log -- Logback日志配置文件
├── mapper -- MyBatis中mapper.xml存放位置
├── application.yml -- SpringBoot通用配置文件
├── application-dev.yml -- SpringBoot开发环境配置文件
├── application-prod.yml -- SpringBoot生产环境配置文件
└── generator.properties -- MyBatis-Plus代码生成器配置
描述 | method | path |
---|---|---|
分页查询数据 | GET | /{控制器路由名称}/page |
获取数据列表 | GET | /{控制器路由名称}/list |
获取指定记录详情 | GET | /{控制器路由名称}/{id} |
新增数据 | POST | /{控制器路由名称}/insert |
修改数据 | PUT | /{控制器路由名称}/update |
删除数据 | DELETE | /{控制器路由名称}/delete/{id} |
直接运行启动类TFastApplication
的main
函数
示例:创建好sys模块的所有表,需要注意的是一定要写好表字段的注释,这样实体类和接口文档中就会自动生成字段说明了。
运行MyBatisPlusGenerator类的main方法来生成代码,可直接生成controller、service、mapper、model、mapper.xml的代码,无需手动创建。
sys_dict
表代码可以先输入模块名称sys
,后输入sys_dict
sys
模块代码,先输入模块名称sys
,再输入表通配sys_*
请求参数
请求参数统一定义在该模块下的
model/dto
中
新增参数格式:xxxInsertDto
@Data
@EqualsAndHashCode(callSuper = false)
public class MenuInsertDto {
@ApiModelProperty(value = "父级ID 0-表示没有父级 ")
private Long parentId;
@ApiModelProperty(value = "菜单标题", required = true)
@NotEmpty(message = "菜单标题不能为空")
@Size(max = 12, message = "菜单标题不能超过12位")
private String title;
@ApiModelProperty(value = "菜单排序")
private Integer sort;
@ApiModelProperty(value = "前端路径", required = true)
@NotEmpty(message = "菜单标题不能为空")
@Size(max = 36, message = "菜单标题不能超过36位")
private String path;
@ApiModelProperty(value = "前端图标")
private String icon;
@ApiModelProperty(value = "前端隐藏 0-隐藏 1-显示")
private Integer hidden;
}
修改参数格式:xxxUpdateDto
@Data
@EqualsAndHashCode(callSuper = false)
public class MenuUpdateDto {
@ApiModelProperty(value = "菜单ID", required = true)
@NotNull(message = "菜单ID不能为空")
private Long id;
@ApiModelProperty(value = "父级ID 0-表示没有父级 ")
private Long parentId;
@ApiModelProperty(value = "菜单标题")
@Size(max = 12, message = "菜单标题不能超过12位")
private String title;
@ApiModelProperty(value = "菜单层级")
private Integer level;
@ApiModelProperty(value = "菜单排序")
private Integer sort;
@ApiModelProperty(value = "前端路径")
@Size(max = 36, message = "前端路径不能超过36位")
private String path;
@ApiModelProperty(value = "前端图标")
private String icon;
@ApiModelProperty(value = "前端隐藏 0-隐藏 1-显示")
private Integer hidden;
@ApiModelProperty(value = "删除标识 0-删除 1-未删除")
private Integer delFlag;
}
参数校验
接口中添加@Validated注解开启参数校验功能即可。
@RestController
@Api(tags = "系统:菜单管理")
@RequestMapping("/sysMenu")
public class SysMenuController {
@Resource
private SysMenuService sysMenuService;
@ApiOperation("新增菜单信息")
@PostMapping("/insert")
public ViewResult<Long> insert(@Validated @RequestBody MenuInsertDto dto){
return ViewResult.success(sysMenuService.insert(dto));
}
@ApiOperation("修改菜单信息")
@PutMapping("/update")
public ViewResult<String> update(@Validated @RequestBody MenuUpdateDto dto) {
boolean success = sysMenuService.update(dto);
if (success) {
return ViewResult.success();
}
return ViewResult.error();
}
}
由于MyBatis-Plus提供的增强功能相当强大,单表查询几乎不用手写SQL,直接使用ServiceImpl和BaseMapper中提供的方法即可。
比如我们的菜单管理业务实现类UmsMenuServiceImpl中的方法都直接使用了这些方法。
@Service
public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {
@Resource
private SysDictItemService sysDictItemService;
@Override
public Page<SysDict> page(String keyword, Integer pageNum, Integer pageSize) {
Page<SysDict> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysDict> wrapper = Wrappers.lambdaQuery();
if (StrUtil.isNotEmpty(keyword)) {
wrapper.like(SysDict::getType, keyword)
.or()
.like(SysDict::getName, keyword);
}
wrapper.ne(SysDict::getDelFlag, DeleteFlagEnum.DELETED.getCode());
return this.page(page, wrapper);
}
@Override
@Transactional
public Long insert(DictInsertDto dto) {
SysDict sysDict = this.getOne(Wrappers
.<SysDict>lambdaQuery()
.eq(SysDict::getType, dto.getType()));
if (Objects.nonNull(sysDict)){
Asserts.fail("字典已经存在");
}
sysDict = BeanUtil.toBean(dto,SysDict.class);
SysUserDetails currentUser = SecurityUtil.getCurrentUser();
sysDict.setCreateUser(currentUser.getUserId());
sysDict.setCreateTime(currentUser.getDate());
sysDict.setUpdateUser(currentUser.getUserId());
sysDict.setUpdateTime(currentUser.getDate());
this.save(sysDict);
return sysDict.getId();
}
@Override
@Transactional
public Boolean update(DictUpdateDto dto) {
if (Objects.isNull(this.getById(dto.getId()))){
Asserts.fail("字典信息不存在");
}
SysDict sysDict = this.getOne(Wrappers
.<SysDict>lambdaQuery()
.eq(SysDict::getType, dto.getType())
.ne(SysDict::getId, dto.getId()));
if (Objects.nonNull(sysDict)){
Asserts.fail(dto.getType() + "字典已经存在");
}
sysDict = BeanUtil.toBean(dto, SysDict.class);
//修改字典类型,同时修改字典项信息
if (StrUtil.isNotEmpty(sysDict.getType())){
sysDictItemService.update(Wrappers
.<SysDictItem>lambdaUpdate()
.set(SysDictItem::getType,sysDict.getType())
.eq(SysDictItem::getDictId,sysDict.getId()));
}
SysUserDetails currentUser = SecurityUtil.getCurrentUser();
sysDict.setUpdateUser(currentUser.getUserId());
sysDict.setUpdateTime(currentUser.getDate());
//删除字典相关缓存
sysDictItemService.delDictCache(sysDict.getType());
return this.updateById(sysDict);
}
@Override
@Transactional
public Boolean delete(Long dictId) {
SysDict sysDict = this.getById(dictId);
if (Objects.isNull(sysDict)){
Asserts.fail("字典信息不存在");
}
int count = sysDictItemService.count(Wrappers
.<SysDictItem>lambdaQuery()
.eq(SysDictItem::getDictId, dictId)
.eq(SysDictItem::getDelFlag,DeleteFlagEnum.UNDELETE.getCode()));
if (count != 0){
Asserts.fail("存在字典项信息,不能删除字典信息");
}
SysUserDetails currentUser = SecurityUtil.getCurrentUser();
sysDict.setUpdateUser(currentUser.getUserId());
sysDict.setUpdateTime(currentUser.getDate());
sysDict.setDelFlag(DeleteFlagEnum.DELETED.getCode());
//删除与菜单相关缓存
sysDictItemService.delDictCache(sysDict.getType());
return this.updateById(sysDict);
}
}
对于分页查询MyBatis-Plus原生支持,不需要再整合其他插件,直接构造Page对象,然后调用ServiceImpl中的page方法即可。
@Service
public class SysDictServiceImpl extends ServiceImpl<SysDictMapper, SysDict> implements SysDictService {
@Resource
private SysDictItemService sysDictItemService;
@Override
public Page<SysDict> page(String keyword, Integer pageNum, Integer pageSize) {
Page<SysDict> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysDict> wrapper = Wrappers.lambdaQuery();
if (StrUtil.isNotEmpty(keyword)) {
wrapper.like(SysDict::getType, keyword)
.or()
.like(SysDict::getName, keyword);
}
wrapper.ne(SysDict::getDelFlag, DeleteFlagEnum.DELETED.getCode());
return this.page(page, wrapper);
}
}
@Service
public class SysLogServiceImpl extends ServiceImpl<SysLogMapper, SysLog> implements SysLogService {
@Resource
private SysUserService sysUserService;
@Override
public Page<LogVo> page(String keyword, Integer type, Integer pageNum, Integer pageSize) {
Page<LogVo> page = new Page<>(pageNum,pageSize);
List<LogVo> logVos = this.baseMapper.page(keyword, type, page);
page.setRecords(logVos);
return page;
}
}
public interface SysLogMapper extends BaseMapper<SysLog> {
/**
* 根据条件分页获取日志列表
* @param keyword 关键字:日志标题
* @param type 日志类型
* @param iPage 分页参数
* @return
*/
List<LogVo> page(@Param("keyword") String keyword,
@Param("type") Integer type,
IPage<LogVo> iPage);
}
<select id="page" resultType="com.tfast.modules.sys.model.vo.log.LogVo">
select
a.id,
a.type,
a.title,
a.ip,
a.url,
a.method,
a.agent,
a.params,
a.time,
a.exception,
a.create_time as createTime,
a.create_user as createUser,
b.user_name as createUserName
from sys_log a
join sys_user b on a.create_user = b.id
<where>
<if test="keyword != null and keyword !=''">
and a.title like concat(#{keyword},'%')
</if>
<if test="type != null">
and a.type = #{type}
</if>
</where>
</select>
对于多表查询,我们需要手写mapper.xml中的SQL实现,由于之前我们已经生成了mapper.xml文件,所以我们直接在Mapper接口中定义好方法,然后在mapper.xml写好SQL实现即可。
SysPermissionMapper
接口中添加好getPermissionByUserId
方法
public interface SysPermissionMapper extends BaseMapper<SysPermission> {
/**
* 通过用户ID获取权限集合
* @param userId 用户ID
* @return 权限集合
*/
List<SysPermission> getPermissionByUserId(Long userId);
}
SysPermissionMapper.xml
添加该方法的对应SQL实现即可。
<select id="getPermissionByUserId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select distinct a.id,
a.permission,
a.description,
a.del_flag,
a.create_time,
a.create_user,
a.update_time,
a.update_user
from sys_permission a
join sys_role_permission b on a.id = b.permission_id
join sys_user_role c on b.role_id = c.role_id and c.user_id = #{userId}
where a.del_flag != 0
</select>
项目已经提供定时任务模板,也可以自行实现
SimpleTask
并继承AbstractTask
@Slf4j
@Component
public class SimpleTask extends AbstractTask {
@Override
@PostConstruct
public void init(AbstractTask task) {
super.init(this);
}
@Override
@Scheduled(cron = "0/5 * * * * ?")
public void execute() {
try {
log.info("简单的定时任务!!!!");
status = 1;
} catch (Exception e){
status = 2;
log.error("任务运行异常,e={}",e.getMessage());
}
}
@Override
public String getTaskName() {
return SimpleTask.class.getSimpleName();
}
@Override
public String getDesc() {
return "示例定时任务";
}
@Override
public Integer getStatus() {
return status;
}
}
项目采用动态接口权限,无需编码只需要添加接口资源配置即可达到权限控制目地,灵活多变!
直接操作数据表:sys_resource
通过前端资源管理进行添加
项目接口采用restful风格,有一定要求具体参考以下示例
GET请求配置
示例接口:/sysOrganization/page
示例接口:/sysOrganization/{orgId}
请求接口带ID或者其他参数配置时只需要写前缀即可,无须写上{XXX}
POST请求配置
PUT请求配置
DELETE请求配置
项目采用docker容器化部署,非docker环境请求自行部署
docker-compose.yml
已经提供
路径:https://gitee.com/Thmspring/t-fast/blob/master/doc/docker/environment/docker-compose.yml
docker-compose -f docker-compose.yml up -d
DockerFile
部署已经编写完整的部署脚本,结果如下:
mkdir deploy
apps
文件夹并上传相关文件
cd deploy
# 上传:app.env、docker-compose.yml、Dockerfile、entrypoint.sh、global.env
mkdir apps
cd apps
# 上传:application-prod.yml、t-fast.jar
cd deploy
# 启动
docker-compose -f docker-compose.yml up -d
# 停止
docker-compose -f docker-compose.yml down
# 查看日志
docker logs -f t-fast
# 查看映射日志
cd logs
tail -f t-fast.log
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。