QQ群:951379340
基于SpringBoot+SpringMVC+MybatisPlus分布式敏捷开发系统架构,默认支持SAAS化,努力为中小型企业打造全方位J2EE企业级开发解决方案。
cmdAdmin是基于Spring Cloud Alibaba体系微服务化分布式敏捷开发系统架构,支持SAAS化,提供整套公共微服务服务模块,其中包含用户管理、资源权限管理等多个模块,作为后端服务的开发脚手架。代码简洁,架构清晰,适合学习和直接项目中使用。 核心技术采用Spring Boot 2.2.7以及Spring Cloud 相关核心组件,采用Nacos注册和配置中心,集成流量卫兵Sentinel,dubbo远程调用,集成Activiti工作流,动态表单+动态工作流,工作流下拉选择代理人,前端采用vue-element-admin组件。
有问题请加QQ群: 951379340
用户中心
系统中心
日志中心
流程中心
cmdAdmin-common 为系统的公共模块,各种工具类,公共配置存在该模块
cmdAdmin-gateway 网关层,所有入库经过该服务
cmdAdmin-modules 为系统的应用模块
cmdAdmin-modules-admin 基础模块
cmdAdmin-modules-log 日志模块
cmdAdmin-modules-workflow 工作流模块
cmdAdmin-rpc 远程调用
cmdadmin-template 前端页面
创建 cmdadmin 数据库,导入 db 下的 sql 脚本
打开 nacos,点开配置管理--配置列表 --导入配置 doc 下的 xxx.zip,会出现配置列表
修改 cmdAdmin-modules-admin-dev.yml master 中的数据库配置,slave 是 enabled 是 false,如果需要开启也改一下数据库链接,其他模块类似,其他配置根据需求改动, registry.address 是 nacos 地址
具体安装问题自行百度
获取当前登录用户对象
OnlineUser user = securityAdmin.currentUser(token);
例如:
@GetMapping(value = "/menus")
public ResponseEntity menus(HttpServletRequest request, @RequestHeader(value = Constants.TOKEN) String token) {
CmdResponse response = new CmdResponse();
response.setData(loginService.listMenus(token));
return ResponseEntity.ok(response);
}
@Override
public List<MenuVO> listMenus(String token) {
OnlineUser user = securityAdmin.currentUser(token);
Map<String, Object> param = Maps.newHashMap();
List<MenuVO> vos = baseMapper.getMenus(param);
return vos;
}
本系统权限控制采用 RBAC 思想。简单地说,一个用户拥有若干角色,每一个角色拥有若干个菜单,菜单中存在菜单权限与按钮权限, 这样,就构造成“用户-角色-菜单” 的授权模型。在这种模型中,用户与角色、角色与菜单之间构成了多对多的关系,如下图
本系统安全框架使用的是 Jwt Token, 访问后端接口需在请求头中携带 token 进行访问,请求头格式如下:
Authorization: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJjcmVhdGVUaW1lXCI6MTU5ODE3OTc4NDAwMCxcImRlbGV0ZWRcIjp0cnVlLFwiZGVwdElkXCI6XCIwZjY4NjZiZTcwYTJmNjkwZTY3YWQyYjZiNzExMjZiYlwiLFwiZGVwdE5hbWVcIjpcIumZleilv1wiLFwiZW5hYmxlZFwiOnRydWUsXCJpZFwiOlwid3d3XCIsXCJuaWNrTmFtZVwiOlwiY21kYWRtaW5cIixcInBhc3N3b3JkXCI6XCIyMmJiZmM0NmZlNzEwOTlkMTM5ZTJjYWEwOGQ1NWJlN1wiLFwicGhvbmVcIjpcIjEzNDU1NTU1NTU1XCIsXCJzYWx0XCI6XCIxMTFcIixcInVwZGF0ZVRpbWVcIjoxNTk4MTc5Nzg2MDAwLFwidXNlck5hbWVcIjpcImNtZGFkbWluXCJ9IiwianRpIjoiN2UzNzU3ZjUtZmU0MC00NWRhLTk2OTctN2ZmNTk3NGE1Y2RjIn0.zM1UafO-UDVrnmsBTTtQPAThVOddR5cz0gDKvjCAlrSfqj5rw-lU4MQmYFvM4k5NNy6NbRQEw5YER3k19C1ARg
提供 EL 表达式,允许在定义接口访问的方法上面添加注解,来控制访问权限,常用的 EL 如下
表达式 | 描述 |
---|---|
@CmdAdmin({"user:list"}) | 当前用户是否拥有指定角色。 |
@CmdAdmin({"admin","user:list"}) | 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回 true。 |
下面的接口表示用户拥有 admin、user:add、user:update create 如果方法不加@CmdAdmin 注解,意味着所有用户都需要带上有效的 token 后能访问 create 方法
@Log("用户新增或编辑")
@CmdAdmin({"admin","user:add","user:update"})
@PutMapping
public ResponseEntity<CmdResponse> create(@RequestBody User entity) throws Exception {
userService.create(entity);
return ResponseEntity.ok(new CmdResponse());
}
由于每个接口都需要给超级管理员放行,而使用 CmdAdmin('admin','user:list') 每次都需要重复的添加 admin 权限
在我们使用的时候,有些接口是不需要验证权限的,这个时候就需要我们给接口放行,在网关配置中添加,使用方式如下
cmd:
filter: #需要进行过滤的白名单
allowPaths:
- /admin/cmd/v1/auth/login
- /admin/cmd/v1/auth/code
本系统使用的是Dubbo远程调用,当接口对外需要提供访问时候,在当前接口实现上加入dubbo注解
例如:
@Service
@org.apache.dubbo.config.annotation.Service(version = "1.0.0")
public class FwLogServiceImpl
如果不对外提供访问,只是内部容器调用则只使用spring的 @Service 即可
原理:
实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
注解填充字段 @TableField(.. fill = FieldFill.INSERT) 生成器策略部分也可以配置!
/**
* 创建时间
*/
// 注意!这里需要标记为填充字段
@TableField(value = "create_time",fill = FieldFill.INSERT)
private Date createTime;
/**
* 自动注入
* @author cmd
*
*/
@Component
public class BasicMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.fillStrategy(metaObject, "createTime", new Date());
this.fillStrategy(metaObject, "enabled", true);
this.fillStrategy(metaObject, "deleted", true);
}
@Override
public void updateFill(MetaObject metaObject) {
this.fillStrategy(metaObject, "updateTime", new Date());
}
}
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
在后台字典管理中新增字典数据 参考前端代码views\system\menu\index.vue
使用步骤:
- import { getDic } from '@/utils/auth'
- created() {
this.dicType = getDic('字典编码')
}
- formatterType({ cellValue }) {
const item = this.dicType.find(item => item.code === cellValue)
return item ? item.value : ''
}
- <vxe-table-column field="type" title="菜单类型" :formatter="formatterType" />
我们开发项目的时候,数据在请求过程中发生错误是非常常见的事情。
如:权限不足、数据唯一异常、数据不能为空异常、义务异常等。 这些异常如果不经过处理会对前端开发人员和使用者造成不便,因此我们就需要统一处理他们。
代码位置: cmdAdmin-common 工程中com.ninong.ker.common.exception包中
处理异常在每个模块 conf 包 LogAspect
/**
* 错误类
*
* @author cmd
* @date 2020年11月28日 下午11:16:13
*/
@Data
public class CmdError {
private Integer status = 400;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime timestamp;
private String message;
private CmdError() {
timestamp = LocalDateTime.now();
}
public static CmdError error(String message) {
CmdError apiError = new CmdError();
apiError.setMessage(message);
return apiError;
}
public static CmdError error(Integer status, String message) {
CmdError apiError = new CmdError();
apiError.setStatus(status);
apiError.setMessage(message);
return apiError;
}
}
封装了 CmdException,用于处理通用的异常
/**
* @author cmd
* @date 2020-11-23 统一异常处理
*/
@Getter
public class CmdException extends RuntimeException{
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer status = BAD_REQUEST.value();
public CmdException(String msg){
super(msg);
}
public CmdException(Integer status,String msg){
super(msg);
this.status = status;
}
public CmdException(BusinessEnum bus){
super(bus.getMsg());
this.status = bus.getStatus();
}
}
使用全局异常处理器 @RestControllerAdvice 处理请求发送的异常
@RestControllerAdvice:默认会扫描指定包中所有@RequestMapping 注解
@ExceptionHandler:通过@ExceptionHandler 的 value 属性可过滤拦截的条件
/\*\*
- 异常处理类
-
- @author cmd
- @date 2020 年 11 月 28 日 下午 11:22:48
\*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有不可知的异常
*/
@ExceptionHandler(Throwable.class)
public ResponseEntity<CmdError> handleException(Throwable e) {
// 打印堆栈信息
log.error(ThrowableUtil.getStackTrace(e));
return buildResponseEntity(CmdError.error(e.getMessage()));
}
/**
* 处理自定义异常
*/
@ExceptionHandler(value = CmdException.class)
public ResponseEntity<CmdResponse> badRequestException(CmdException e) {
CmdResponse response = new CmdResponse();
response.setCode(e.getStatus());
response.setMessage(e.getMessage());
return ResponseEntity.ok(response);
}
/**
* 接口参数校验不通过异常
*
* @param e
* @return
*/
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseEntity<CmdResponse> methodArgumentNotValidException(MethodArgumentNotValidException e) {
List<FieldError> bindingResult = e.getBindingResult().getFieldErrors();
StringBuilder sb = new StringBuilder();
for (FieldError fieldError : bindingResult) {
sb.append(fieldError.getDefaultMessage()).append(";");
}
CmdResponse response = new CmdResponse();
response.setCode(100);
response.setMessage(sb.toString());
return ResponseEntity.ok(response);
}
/**
* 统一返回
*/
private ResponseEntity<CmdError> buildResponseEntity(CmdError CmdError) {
return new ResponseEntity<>(CmdError, HttpStatus.valueOf(CmdError.getStatus()));
}
}
// 通用异常
throw new CmdException("系统提醒");
// 通用异常,使用自定义状态码
throw new CmdException(402, "系统提醒");
本系统使用 AOP 方式记录用户操作日志,只需要在 controller 的方法上使用 @Log("") 注解,就可以将用户操作记录到数据库 模块具体使用如下:
@Log("用户删除")
@CmdAdmin({"admin","user:delete"})
@DeleteMapping
public ResponseEntity<CmdResponse> delete(@RequestBody String[] ids) {
userService.delete(ids);
CmdResponse response = new CmdResponse();
response.setMessage(BusinessEnum.DELETE_SUCCESS.getMsg());
return ResponseEntity.ok(response);
}
页面日志中心可以看到 操作日志和异常日志
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
1. 开源生态
2. 协作、人、软件
3. 评估模型