# mcdull
**Repository Path**: dqcer/mcdull
## Basic Information
- **Project Name**: mcdull
- **Description**: 麦兜框架:依赖管理 基类 超类 效验参数 架构规则库 操作稽查组件 缓存组件 多级缓存 多数据源 动态数据源 加解密 MQ组件
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 12
- **Forks**: 3
- **Created**: 2022-10-21
- **Last Updated**: 2025-10-19
## Categories & Tags
**Categories**: distributed-service
**Tags**: None
## README
# mcdull
### 框架介绍
mcdull 为企业级环境中提炼出来的cloud微服务版本
没有最好的框架,只有最合适的
     
    
        
             
        
        
             
        
        
             
        
        
             
        
    
> 徽章生产网站 https://shields.io/
### 快速链接
* 在线文档: [点击前往](https://dqcer.gitee.io/mcdull)
#### 介绍
麦兜框架:依赖管理 基类/超类 效验参数 架构规则库 操作稽查组件 缓存组件 多级缓存 多数据源 动态数据源 加解密 MQ组件
### 架构图

> 画图工具 [excalidraw](https://excalidraw.com/)
### 项目结构 
```
mcdull
├──doc                           文档
│    ├─db                        sql
│    └─yaml                      配置文件
│ 
├─mcdull-bussiness               业务模块
│   
├─mcdull-framework                              基础框架
│    ├─mcdull-framework-agent                     探针模块
│    ├─mcdull-framework-base                      底层定义
│    ├─mcdull-framework-config                    配置定义
│    ├─mcdull-framework-dependencies              依赖管理
│    ├─mcdull-framework-enforcer                  框架规则
│    └─mcdull-framework-starters               组件starters  
│       ├─mcdull-framework-starter-feign          feign组件
│       ├─mcdull-framework-starter-feign          流程编排
│       ├─mcdull-framework-starter-mongodb        mongodb组件
│       ├─mcdull-framework-starter-mysql          mysql组件
│       ├─mcdull-framework-starter-nacos          nacos组件
│       ├─mcdull-framework-starter-oss            对象存储
│       ├─mcdull-framework-starter-redis          redis组件
│       └─mcdull-framework-starter-web            web组件
│ 
├─mcdull-support                              支撑模块(包含技术中台和业务中台)
│    └─mcdull-geteway                             统一网关
│    ├─mcdull-generator                           代码生成器  
│    ├─mcdull-mdc                                 元数据中心  
│    └─mcdull-uac                                 用户中心     
```
### 功能特性
- [x]  - [x] 网关动态路由
- [x] 日志打印
  - [x] 基于网关
  - [x] 基于controller
  - [x] 基于数据库表
- [x] RBAC权限控制
- [x] 参数效验
- [x] 枚举封装
- [x] 动态数据源
- [x] 多级缓存
- [x] VO/DO/DTO定义
- [x] 异常拦截
- [x] 码表自动翻译
- [x] 代码生成器
- [x] 分布式锁
- [x] 基于IP的雪花算法
- [x] 基于用户/物品协同的推荐算法
- [x] 支持csv文件解析并支持模糊搜索和指定显示的字段
### 框架优势
1. 模块化架构设计,层次清晰,方便升级。
2. 最小依赖原则,杜绝循环重复依赖。
3. 从真实线上环境中提炼而来。
4. 前沿技术与技术成熟度的平衡选型。
5. 代码洁癖者。
6. 有节制的使用第三方开源组件,最大程度上实现自主可控。
7. 提供了静态检查机制(配合单元测试使用),避免线上出现不合规范的使用。
8. 四层架构(controller, service, manager, dao) 
### 快速开始
#### 文档部署
- 全局安装``docsify`` ,安装之前必须要安装``nodeJS``
```shell
npm i docsify-cli -g
```
- 进入到文档目录下
```shell
cd doc
```
- 启动文档系统
```shell
docsify serve .
```
### 编码常识
#### 基本类型与包装类型使用标准
关于基本数据类型与包装数据类型的使用标准如下:
- 所有的POJO类属性必须使用包装数据类型。 
- RPC方法的返回值和参数必须使用包装数据类型。 
- 所有的局部变量推荐使用基本数据类型。 
> 说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
#### @SuppressWarnings注解
- all	抑制所有警告
- boxing	抑制装箱、拆箱操作时候的警告
- cast	抑制映射相关的警告
- dep-ann	抑制启用注释的警告
- deprecation	抑制过期方法警告
- fallthrough	抑制在 switch 中缺失 breaks 的警告
- finally	抑制 finally 模块没有返回的警告
- hiding	抑制相对于隐藏变量的局部变量的警告
- incomplete-switch	忽略不完整的 switch 语句
- nls	忽略非 nls 格式的字符
- null	忽略对 null 的操作
- rawtypes	使用 generics 时忽略没有指定相应的类型
- restriction	抑制禁止使用劝阻或禁止引用的警告
- serial	忽略在 serializable 类中没有声明 serialVersionUID 变量
- static-access	抑制不正确的静态访问方式警告
- synthetic-access	抑制子类没有按最优方法访问内部类的警告
- unchecked	抑制没有进行类型检查操作的警告
- unqualified-field-access	抑制没有权限访问的域的警告
- unused	抑制没被使用过的代码的警告
### 闭坑指南
##### feign 传参服务端无法接收
- 解决方案
```xml
        
        
            io.github.openfeign
            feign-httpclient
        
```
##### 上传文件 子线程无法获取文件
```java
    private void biz(MultipartFile[] attachmentFiles) {
        this.前置逻辑();
        EmailUtil.sendEmailAsyn(
                () -> {
                    // 模拟复现条件 Thread.sleep(25000);
                    // 可能存在的场景:主线程结束,临时文件被清空,导致子线程业务类无法获取到临时文件而报错(系统找不到指定的文件)。
                    mailApi.sendEmailToMultipleReceiverWithAttachment(attachmentFiles, sendTo, ccs, subject, html(text));
                });
        this.后续逻辑();
    }
```
- 解决方案
> 改成主线程、或者使用file.getInputStream(),以文件流信息存储在内存中
##### feign get传对象
- 解决方案
```java
    /**
     * 单个详情(使用 @SpringQueryMap)
     *
     * @param dto dto
     * @return {@link Result < DictVO >}
     */
    @GetMapping("/dict/detail")
    Result detail(@SpringQueryMap @Validated(value = ValidGroup.One.class) DictLiteDTO dto);
```
##### 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,因为它的add/remove/clear方法会抛出UnsupportedOperationException异常
> asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数据
```java
        // 反例
        String[] str = new String[]{"1", "2"};
        List list = Arrays.asList(str);
        // 第一种情况运行时异常
        list.add(3);
        // 第二种情况也会随之修改,反之亦然
        str[0] = "1-1";
        System.out.println(list);
        
```
#### 禁止使用bean进行copy
```java
// 反例
BeanUtil.copyProperties(source, target)
// 正例
get/set
```
> 说明:使用反射效率低,其次增加可维护性成本增高,后面接手的小伙伴不知道程序哪些是需要的字段. 
> 使用get/set是最直接,也是最简单的。后续二开/维护排错倍倍香!!!
#### 避免过多冗余、过少的日志
- 打印必要的日志
- 合并打印
- 简化:只打印其id
- 缩写:如write 简化为 w, read 简化为 r
- 压缩:文件压缩处理
> 多说一句: 日志用时方恨少,但切记避免过于冗余
### 常见问题
#### 是否使用swagger
- 结论:尽量避免使用``swagger``
- 原因:代码侵入性强,部分存在兼容问题
#### 是否lombok
- 结论:科学使用``lombok``,不要一上来就使用``@Data``注解
- 原因:有时候bean只需要``get``、``set`` 和 ``toString``,但这哥们给我们生成太多东西了。另外因为是在编译的时候生成的,导致编译速度下降
### 自动化测试组件
> 用于检查 Java 代码的体系结构,命名,层级调用是否满足规约
##### 快速开始
- 导入maven
```xml
        
            io.gitee.dqcer
            mcdull-framework-enforcer
            test
        
```
- 编码
```java
    private JavaClasses classes;
    @BeforeEach
    public void setUp() {
        classes = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_PACKAGE_INFOS)
                .importPackages("io.gitee");
    }
    @Test
    public void requiredRules() {
        for (ArchRule rule : ArchitectureEnforcer.requiredRules) {
            rule.check(classes);
        }
    }
```
##### 内置规约如下
##### 命名规则
    
1. mapper 层下的类应该以'Mapper'结尾
2. 实现DO的类名应该以'DO'结尾
3. 实现DTO的类名应该以'DTO'结尾
4. 实现VO的类名应该以'VO'结尾
5. 枚举类应该以'Enum'结尾
    
##### 层级调用
1. Mapper数据库访问层仅被Repository数据库包装层调用
2. Repository数据库包装层仅被Service业务层、Manager通用逻辑层调用
3. Manager数据库访问层仅被Service业务层调用
4. Service业务层仅被Controller层和ServerFeign层调用
- 异常处理反例
```java
// 反例
     throw new Exception(); 
     throw new RuntimeException("error"); 
     throw new Throwable("error"); 
class CustomException extends Throwable {
    
}
```
- 控制台打印反例
```java
System.out.println("foo");
System.err.println("bar");
OutputStream out = System.out; 
out.write(bytes)
try {
     // ...
} catch (Exception e) {
     e.printStackTrace(); 
 }
```
### 插件推荐
#### GenerateAllSetter
> 通过alt+enter对变量类生成对类的所有setter方法的调用
#### Free Mybatis plugin
>快速从代码跳转到mapper及从mapper返回代码
#### RestfulTool
>一套 Restful 服务开发辅助工具集
#### Easy Javadoc
>自动生成javadoc文档注释
### 后期规划
- 翻译转换支持批量提高效率
- 文件上传下载
- pdf生成器
- transmittable-thread-local
>动态表单: https://segmentfault.com/q/1010000009146625 
> https://www.jianshu.com/p/b2f4ad0ec396
>log.error("xxxxx", ThrowableUtil.getStackTraceAsString(e));
>如有需求请联系作者(dqcer@sina.com)
### 编码建议
#### 控制流语句“if”、“for”、“while”、“switch”和“try”不应该嵌套得太深
```java
// 反例
if (condition1) {                  // Compliant - depth = 1
  /* ... */
  if (condition2) {                // Compliant - depth = 2
    /* ... */
    for(int i = 0; i < 10; i++) {  // Compliant - depth = 3, not exceeding the limit
      /* ... */
      if (condition4) {            // Noncompliant - depth = 4
        if (condition5) {          // Depth = 5, exceeding the limit, but issues are only reported on depth = 4
          /* ... */
        }
        return;
      }
    }
  }
}
// 正例
if (!condition1) {                 
  /* ... */
    return;
}
if (!condition2) {                
   /* ... */
    return;
}
for(int i = 0; i < 10; i++) {  
 /* ... */
 if (condition4) {            
   if (condition5) {          
     /* ... */
   }
   return;
 }
}
```
#### 禁止导入包import * 和允许import内部类(idea默认是允许导入*的)
- 打开设置,找到 File | Settings | Editor | Code Style | Java 界面的 imports 页签,导入数量设置为999
#### 进行权限的拦截时,优先使用``OncePerRequestFilter``而不是``Filter``
> OncePerRequestFilter 确保其 doFilter() 方法在每个请求中只被调用一次,即使在多个线程并发处理请求的情况下
#### 替换``LoadBalancer``用默认的缓存
> 生产环境中使用Caffeine 缓存以获得更好的性能和内存管理
- 1、``pom``添加依赖
```xml
        
            com.github.ben-manes.caffeine
            caffeine
        
```
- 2、``application.yml``添加配置
```yml
spring:
  cache:
    type: caffeine
```
#### 服务间内部调用避免存在当前登录会话凭证, 能避免head传参的尽量避免,除非是公用属性
> 需要兼容,定时任务、无需登录的接口和场景
#### 对于相对复杂且持续迭代的业务,避免使用所谓的`validation`的组进行分组效验
> 后期谁维护了才只知道,可维护性差
#### commit 信息
> 背景:测试提交了一个bug,开发人员无法确认是哪个版本有这个问题?当前测试环境部署的是某个版本吗?生产环境会不会也有这个问题?
>公司内部的项目,总共几十、几百个服务,每天都有服务的生产环境部署,一个服务甚至一天上线好几次,对于项目管理来说无法清晰了解某一时刻某个服务的版本
如何验证我的代码是否已经上线?
持续更新中... 欢迎点``star``, 避免下次迷路
- [x] 网关动态路由
- [x] 日志打印
  - [x] 基于网关
  - [x] 基于controller
  - [x] 基于数据库表
- [x] RBAC权限控制
- [x] 参数效验
- [x] 枚举封装
- [x] 动态数据源
- [x] 多级缓存
- [x] VO/DO/DTO定义
- [x] 异常拦截
- [x] 码表自动翻译
- [x] 代码生成器
- [x] 分布式锁
- [x] 基于IP的雪花算法
- [x] 基于用户/物品协同的推荐算法
- [x] 支持csv文件解析并支持模糊搜索和指定显示的字段
### 框架优势
1. 模块化架构设计,层次清晰,方便升级。
2. 最小依赖原则,杜绝循环重复依赖。
3. 从真实线上环境中提炼而来。
4. 前沿技术与技术成熟度的平衡选型。
5. 代码洁癖者。
6. 有节制的使用第三方开源组件,最大程度上实现自主可控。
7. 提供了静态检查机制(配合单元测试使用),避免线上出现不合规范的使用。
8. 四层架构(controller, service, manager, dao) 
### 快速开始
#### 文档部署
- 全局安装``docsify`` ,安装之前必须要安装``nodeJS``
```shell
npm i docsify-cli -g
```
- 进入到文档目录下
```shell
cd doc
```
- 启动文档系统
```shell
docsify serve .
```
### 编码常识
#### 基本类型与包装类型使用标准
关于基本数据类型与包装数据类型的使用标准如下:
- 所有的POJO类属性必须使用包装数据类型。 
- RPC方法的返回值和参数必须使用包装数据类型。 
- 所有的局部变量推荐使用基本数据类型。 
> 说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
#### @SuppressWarnings注解
- all	抑制所有警告
- boxing	抑制装箱、拆箱操作时候的警告
- cast	抑制映射相关的警告
- dep-ann	抑制启用注释的警告
- deprecation	抑制过期方法警告
- fallthrough	抑制在 switch 中缺失 breaks 的警告
- finally	抑制 finally 模块没有返回的警告
- hiding	抑制相对于隐藏变量的局部变量的警告
- incomplete-switch	忽略不完整的 switch 语句
- nls	忽略非 nls 格式的字符
- null	忽略对 null 的操作
- rawtypes	使用 generics 时忽略没有指定相应的类型
- restriction	抑制禁止使用劝阻或禁止引用的警告
- serial	忽略在 serializable 类中没有声明 serialVersionUID 变量
- static-access	抑制不正确的静态访问方式警告
- synthetic-access	抑制子类没有按最优方法访问内部类的警告
- unchecked	抑制没有进行类型检查操作的警告
- unqualified-field-access	抑制没有权限访问的域的警告
- unused	抑制没被使用过的代码的警告
### 闭坑指南
##### feign 传参服务端无法接收
- 解决方案
```xml
        
        
            io.github.openfeign
            feign-httpclient
        
```
##### 上传文件 子线程无法获取文件
```java
    private void biz(MultipartFile[] attachmentFiles) {
        this.前置逻辑();
        EmailUtil.sendEmailAsyn(
                () -> {
                    // 模拟复现条件 Thread.sleep(25000);
                    // 可能存在的场景:主线程结束,临时文件被清空,导致子线程业务类无法获取到临时文件而报错(系统找不到指定的文件)。
                    mailApi.sendEmailToMultipleReceiverWithAttachment(attachmentFiles, sendTo, ccs, subject, html(text));
                });
        this.后续逻辑();
    }
```
- 解决方案
> 改成主线程、或者使用file.getInputStream(),以文件流信息存储在内存中
##### feign get传对象
- 解决方案
```java
    /**
     * 单个详情(使用 @SpringQueryMap)
     *
     * @param dto dto
     * @return {@link Result < DictVO >}
     */
    @GetMapping("/dict/detail")
    Result detail(@SpringQueryMap @Validated(value = ValidGroup.One.class) DictLiteDTO dto);
```
##### 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,因为它的add/remove/clear方法会抛出UnsupportedOperationException异常
> asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数据
```java
        // 反例
        String[] str = new String[]{"1", "2"};
        List list = Arrays.asList(str);
        // 第一种情况运行时异常
        list.add(3);
        // 第二种情况也会随之修改,反之亦然
        str[0] = "1-1";
        System.out.println(list);
        
```
#### 禁止使用bean进行copy
```java
// 反例
BeanUtil.copyProperties(source, target)
// 正例
get/set
```
> 说明:使用反射效率低,其次增加可维护性成本增高,后面接手的小伙伴不知道程序哪些是需要的字段. 
> 使用get/set是最直接,也是最简单的。后续二开/维护排错倍倍香!!!
#### 避免过多冗余、过少的日志
- 打印必要的日志
- 合并打印
- 简化:只打印其id
- 缩写:如write 简化为 w, read 简化为 r
- 压缩:文件压缩处理
> 多说一句: 日志用时方恨少,但切记避免过于冗余
### 常见问题
#### 是否使用swagger
- 结论:尽量避免使用``swagger``
- 原因:代码侵入性强,部分存在兼容问题
#### 是否lombok
- 结论:科学使用``lombok``,不要一上来就使用``@Data``注解
- 原因:有时候bean只需要``get``、``set`` 和 ``toString``,但这哥们给我们生成太多东西了。另外因为是在编译的时候生成的,导致编译速度下降
### 自动化测试组件
> 用于检查 Java 代码的体系结构,命名,层级调用是否满足规约
##### 快速开始
- 导入maven
```xml
        
            io.gitee.dqcer
            mcdull-framework-enforcer
            test
        
```
- 编码
```java
    private JavaClasses classes;
    @BeforeEach
    public void setUp() {
        classes = new ClassFileImporter()
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
                .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_PACKAGE_INFOS)
                .importPackages("io.gitee");
    }
    @Test
    public void requiredRules() {
        for (ArchRule rule : ArchitectureEnforcer.requiredRules) {
            rule.check(classes);
        }
    }
```
##### 内置规约如下
##### 命名规则
    
1. mapper 层下的类应该以'Mapper'结尾
2. 实现DO的类名应该以'DO'结尾
3. 实现DTO的类名应该以'DTO'结尾
4. 实现VO的类名应该以'VO'结尾
5. 枚举类应该以'Enum'结尾
    
##### 层级调用
1. Mapper数据库访问层仅被Repository数据库包装层调用
2. Repository数据库包装层仅被Service业务层、Manager通用逻辑层调用
3. Manager数据库访问层仅被Service业务层调用
4. Service业务层仅被Controller层和ServerFeign层调用
- 异常处理反例
```java
// 反例
     throw new Exception(); 
     throw new RuntimeException("error"); 
     throw new Throwable("error"); 
class CustomException extends Throwable {
    
}
```
- 控制台打印反例
```java
System.out.println("foo");
System.err.println("bar");
OutputStream out = System.out; 
out.write(bytes)
try {
     // ...
} catch (Exception e) {
     e.printStackTrace(); 
 }
```
### 插件推荐
#### GenerateAllSetter
> 通过alt+enter对变量类生成对类的所有setter方法的调用
#### Free Mybatis plugin
>快速从代码跳转到mapper及从mapper返回代码
#### RestfulTool
>一套 Restful 服务开发辅助工具集
#### Easy Javadoc
>自动生成javadoc文档注释
### 后期规划
- 翻译转换支持批量提高效率
- 文件上传下载
- pdf生成器
- transmittable-thread-local
>动态表单: https://segmentfault.com/q/1010000009146625 
> https://www.jianshu.com/p/b2f4ad0ec396
>log.error("xxxxx", ThrowableUtil.getStackTraceAsString(e));
>如有需求请联系作者(dqcer@sina.com)
### 编码建议
#### 控制流语句“if”、“for”、“while”、“switch”和“try”不应该嵌套得太深
```java
// 反例
if (condition1) {                  // Compliant - depth = 1
  /* ... */
  if (condition2) {                // Compliant - depth = 2
    /* ... */
    for(int i = 0; i < 10; i++) {  // Compliant - depth = 3, not exceeding the limit
      /* ... */
      if (condition4) {            // Noncompliant - depth = 4
        if (condition5) {          // Depth = 5, exceeding the limit, but issues are only reported on depth = 4
          /* ... */
        }
        return;
      }
    }
  }
}
// 正例
if (!condition1) {                 
  /* ... */
    return;
}
if (!condition2) {                
   /* ... */
    return;
}
for(int i = 0; i < 10; i++) {  
 /* ... */
 if (condition4) {            
   if (condition5) {          
     /* ... */
   }
   return;
 }
}
```
#### 禁止导入包import * 和允许import内部类(idea默认是允许导入*的)
- 打开设置,找到 File | Settings | Editor | Code Style | Java 界面的 imports 页签,导入数量设置为999
#### 进行权限的拦截时,优先使用``OncePerRequestFilter``而不是``Filter``
> OncePerRequestFilter 确保其 doFilter() 方法在每个请求中只被调用一次,即使在多个线程并发处理请求的情况下
#### 替换``LoadBalancer``用默认的缓存
> 生产环境中使用Caffeine 缓存以获得更好的性能和内存管理
- 1、``pom``添加依赖
```xml
        
            com.github.ben-manes.caffeine
            caffeine
        
```
- 2、``application.yml``添加配置
```yml
spring:
  cache:
    type: caffeine
```
#### 服务间内部调用避免存在当前登录会话凭证, 能避免head传参的尽量避免,除非是公用属性
> 需要兼容,定时任务、无需登录的接口和场景
#### 对于相对复杂且持续迭代的业务,避免使用所谓的`validation`的组进行分组效验
> 后期谁维护了才只知道,可维护性差
#### commit 信息
> 背景:测试提交了一个bug,开发人员无法确认是哪个版本有这个问题?当前测试环境部署的是某个版本吗?生产环境会不会也有这个问题?
>公司内部的项目,总共几十、几百个服务,每天都有服务的生产环境部署,一个服务甚至一天上线好几次,对于项目管理来说无法清晰了解某一时刻某个服务的版本
如何验证我的代码是否已经上线?
持续更新中... 欢迎点``star``, 避免下次迷路