# 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``, 避免下次迷路