# cmdAdmin **Repository Path**: wusyJava/cmdAdmin ## Basic Information - **Project Name**: cmdAdmin - **Description**: cmdAdmin是基于Spring Cloud Alibaba体系微服务化分布式敏捷开发系统架构,提供整套公共微服务服务模块,其中包含用户管理、资源权限管理等多个模块,作为后端服务的开发脚手架。代码简洁,架构清晰,适合学习和直接项目中使用。 核心技术采用Spring Boot 2.2.7以及Spring Cloud 相关核心组件,采用Nacos注册和配置中心,集成流量卫兵Sentinel,dubbo远程调用,集成Activiti工作流,动态表单+动态工作流,工作流下拉选择代理人,前端采用vue-element-admin组件。 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 20 - **Created**: 2021-11-17 - **Last Updated**: 2021-11-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # cmdAdmin 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组件。 # 安装教程 1. 安装redis jdk maven 2. 安装Nacos 导入doc下文件 导入数据库sql 3. 启动网关 admin log workflow 模块 # 使用说明 有问题请加QQ群: 951379340 1. 超级管理员账号 13455555555/admin123 不参与业务只管理租户和菜单维护(操作哪些菜单租户可见) 2. 租户管理1 13466666666/123456 3. 租户管理2 13466666666/123456 ## 主要功能 - 用户中心 - 用户管理:提供用户的相关配置,新增用户后,默认密码为 cmd.c0m - 在线用户:统计登录用户 - 角色管理:对权限与菜单进行分配,可根据部门设置角色的数据权限 - 部门管理:可配置系统组织架构,树形表格展示 - 系统中心 - 菜单管理:已实现菜单动态路由,后端可配置化,支持多级菜单 - 字典管理:可维护常用一些固定的数据,如:性别等 - 存储管理: 上传附件记录 - 表单配置:根据 form-generator 集成的表单拖拽 - 数据管理: 动态表单的集中数据管理列表 - 日志中心 - 日志管理:记录用户操作日志与异常日志,方便开发人员定位排错 - ELK 日志:未完成 - 流程中心 - 流程管理:手绘工作流模型 - 待办任务:当前人审批任务 - 请假申请:动态配置,阅读表单配置 - 财务申请: 动态配置,阅读表单配置 ## 项目结构 - cmdAdmin-common 为系统的公共模块,各种工具类,公共配置存在该模块 - cmdAdmin-gateway 网关层,所有入库经过该服务 - cmdAdmin-modules 为系统的应用模块 - cmdAdmin-modules-admin 基础模块 - cmdAdmin-modules-log 日志模块 - cmdAdmin-modules-workflow 工作流模块 - cmdAdmin-rpc 远程调用 - cmdadmin-template 前端页面 ## 运行项目 - 安装 Redis - 安装 Nacos - 创建 cmdadmin 数据库,导入 db 下的 sql 脚本 - 打开 nacos,点开配置管理--配置列表 --导入配置 doc 下的 xxx.zip,会出现配置列表 - 修改 cmdAdmin-modules-admin-dev.yml master 中的数据库配置,slave 是 enabled 是 false,如果需要开启也改一下数据库链接,其他模块类似,其他配置根据需求改动, registry.address 是 nacos 地址 - 启动 cmdAdmin-gateway cmdAdmin-modules-admin 这 2 个启动就可以登录看基础功能了 日志和工作流根据需要启动 - 启动 cmdadmin-template 前端 npm install , npm run dev ![输入图片说明](https://images.gitee.com/uploads/images/2021/0319/111629_d5c6c6e5_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0312/111344_624499fd_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0312/110917_4f63fc55_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0312/110935_3eb29335_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0310/183427_abe2f6f8_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/184944_d66e4e3a_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185000_0cc26e6a_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185120_bb3bf2b4_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185139_36ddde60_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185156_56067ffe_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185218_ea15b55e_447587.png "屏幕截图.png") ## 环境配置 具体安装问题自行百度 ### 运行环境准备 - JDK1.8+ - Redis - MYSQL5.6+ - Maven3+ - Nacos1.3+ ### 开发环境 - IDEA、Eclipse、vscode - 开发插件 - Lombok:节省时间必备 - 阿里 JAVA 开发规约插件:P3C - JRebel:秒级热更新神器必备 ### 导入 SQl 文件 - MySQL 数据库新建数据库,导入 doc 下的 sql 文件 ### 配置文件导入 - 启动 nacos 后 导入 doc 下 zip 文件 - 编辑对应配置文件 比如,数据库配置 ### 访问测试 - 账号 cmdadmin/admin123 # 开发手册 ## 当前用户 获取当前登录用户对象 ```java 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 listMenus(String token) { OnlineUser user = securityAdmin.currentUser(token); Map param = Maps.newHashMap(); List vos = baseMapper.getMenus(param); return vos; } ``` ## 权限控制 本系统权限控制采用 RBAC 思想。简单地说,一个用户拥有若干角色,每一个角色拥有若干个菜单,菜单中存在菜单权限与按钮权限, 这样,就构造成“用户-角色-菜单” 的授权模型。在这种模型中,用户与角色、角色与菜单之间构成了多对多的关系,如下图 ![avatar](/img/1.jpg) ### 权限控制 本系统安全框架使用的是 Jwt Token, 访问后端接口需在请求头中携带 token 进行访问,请求头格式如下: ```yml Authorization: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XCJjcmVhdGVUaW1lXCI6MTU5ODE3OTc4NDAwMCxcImRlbGV0ZWRcIjp0cnVlLFwiZGVwdElkXCI6XCIwZjY4NjZiZTcwYTJmNjkwZTY3YWQyYjZiNzExMjZiYlwiLFwiZGVwdE5hbWVcIjpcIumZleilv1wiLFwiZW5hYmxlZFwiOnRydWUsXCJpZFwiOlwid3d3XCIsXCJuaWNrTmFtZVwiOlwiY21kYWRtaW5cIixcInBhc3N3b3JkXCI6XCIyMmJiZmM0NmZlNzEwOTlkMTM5ZTJjYWEwOGQ1NWJlN1wiLFwicGhvbmVcIjpcIjEzNDU1NTU1NTU1XCIsXCJzYWx0XCI6XCIxMTFcIixcInVwZGF0ZVRpbWVcIjoxNTk4MTc5Nzg2MDAwLFwidXNlck5hbWVcIjpcImNtZGFkbWluXCJ9IiwianRpIjoiN2UzNzU3ZjUtZmU0MC00NWRhLTk2OTctN2ZmNTk3NGE1Y2RjIn0.zM1UafO-UDVrnmsBTTtQPAThVOddR5cz0gDKvjCAlrSfqj5rw-lU4MQmYFvM4k5NNy6NbRQEw5YER3k19C1ARg ``` ![avatar](/img/2.png) ### 权限注解 提供 EL 表达式,允许在定义接口访问的方法上面添加注解,来控制访问权限,常用的 EL 如下 | 表达式 | 描述 | | -------------------------------- | --------------------------------------------------------------------------------------- | | @CmdAdmin({"user:list"}) | 当前用户是否拥有指定角色。 | | @CmdAdmin({"admin","user:list"}) | 多个角色是一个以逗号进行分隔的字符串。如果当前用户拥有指定角色中的任意一个则返回 true。 | 下面的接口表示用户拥有 admin、user:add、user:update create 如果方法不加@CmdAdmin 注解,意味着所有用户都需要带上有效的 token 后能访问 create 方法 ```java @Log("用户新增或编辑") @CmdAdmin({"admin","user:add","user:update"}) @PutMapping public ResponseEntity create(@RequestBody User entity) throws Exception { userService.create(entity); return ResponseEntity.ok(new CmdResponse()); } ``` 由于每个接口都需要给超级管理员放行,而使用 CmdAdmin('admin','user:list') 每次都需要重复的添加 admin 权限 ### 匿名访问 在我们使用的时候,有些接口是不需要验证权限的,这个时候就需要我们给接口放行,在网关配置中添加,使用方式如下 ```yml cmd: filter: #需要进行过滤的白名单 allowPaths: - /admin/cmd/v1/auth/login - /admin/cmd/v1/auth/code ``` ## 远程调用 本系统使用的是Dubbo远程调用,当接口对外需要提供访问时候,在当前接口实现上加入dubbo注解 例如: ```java @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) 生成器策略部分也可以配置! ```java /** * 创建时间 */ // 注意!这里需要标记为填充字段 @TableField(value = "create_time",fill = FieldFill.INSERT) private Date createTime; ``` - 自定义实现类 MyMetaObjectHandler ```java /** * 自动注入 * @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()); } } ``` - FieldFill类型 ```java public enum FieldFill { /** * 默认不处理 */ DEFAULT, /** * 插入填充字段 */ INSERT, /** * 更新填充字段 */ UPDATE, /** * 插入和更新填充字段 */ INSERT_UPDATE } ``` ## 字典翻译 在后台字典管理中新增字典数据 参考前端代码views\system\menu\index.vue 使用步骤: ```javascript - import { getDic } from '@/utils/auth' - created() { this.dicType = getDic('字典编码') } - formatterType({ cellValue }) { const item = this.dicType.find(item => item.code === cellValue) return item ? item.value : '' } - ``` ## 异常处理 我们开发项目的时候,数据在请求过程中发生错误是非常常见的事情。 如:权限不足、数据唯一异常、数据不能为空异常、义务异常等。 这些异常如果不经过处理会对前端开发人员和使用者造成不便,因此我们就需要统一处理他们。 代码位置: cmdAdmin-common 工程中com.ninong.ker.common.exception包中 处理异常在每个模块 conf 包 LogAspect ### 异常封装 #### 异常实体 ```java /** * 错误类 * * @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,用于处理通用的异常 ```java /** * @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 属性可过滤拦截的条件 ```java /\*\* - 异常处理类 - - @author cmd - @date 2020 年 11 月 28 日 下午 11:22:48 \*/ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理所有不可知的异常 */ @ExceptionHandler(Throwable.class) public ResponseEntity handleException(Throwable e) { // 打印堆栈信息 log.error(ThrowableUtil.getStackTrace(e)); return buildResponseEntity(CmdError.error(e.getMessage())); } /** * 处理自定义异常 */ @ExceptionHandler(value = CmdException.class) public ResponseEntity 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 methodArgumentNotValidException(MethodArgumentNotValidException e) { List 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 buildResponseEntity(CmdError CmdError) { return new ResponseEntity<>(CmdError, HttpStatus.valueOf(CmdError.getStatus())); } } ``` ### 具体使用 ```java // 通用异常 throw new CmdException("系统提醒"); // 通用异常,使用自定义状态码 throw new CmdException(402, "系统提醒"); ``` ## 系统日志 本系统使用 AOP 方式记录用户操作日志,只需要在 controller 的方法上使用 @Log("") 注解,就可以将用户操作记录到数据库 模块具体使用如下: ```java @Log("用户删除") @CmdAdmin({"admin","user:delete"}) @DeleteMapping public ResponseEntity delete(@RequestBody String[] ids) { userService.delete(ids); CmdResponse response = new CmdResponse(); response.setMessage(BusinessEnum.DELETE_SUCCESS.getMsg()); return ResponseEntity.ok(response); } ``` 页面日志中心可以看到 操作日志和异常日志 ![输入图片说明](https://images.gitee.com/uploads/images/2021/0312/111344_624499fd_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0312/110917_4f63fc55_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0312/110935_3eb29335_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0310/183427_abe2f6f8_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/184944_d66e4e3a_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185000_0cc26e6a_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185120_bb3bf2b4_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185139_36ddde60_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185156_56067ffe_447587.png "屏幕截图.png") ![输入图片说明](https://images.gitee.com/uploads/images/2021/0204/185218_ea15b55e_447587.png "屏幕截图.png")