# cloud
**Repository Path**: wanghehe/cloud
## Basic Information
- **Project Name**: cloud
- **Description**: Spring Cloud框架的封装,整合了常用的第三方组件
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2021-11-01
- **Last Updated**: 2021-11-01
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 开发工具规范
- ##### 建议统一使用开发工具IDEA,为避免代码格式化后合并出现冲突,保持默认的Code Style即可,并安装如下必备开发插件:
```
提高开发效率插件:Lombok
阿里巴巴编码规范插件:Alibaba Java Coding Guidelines
```
# 项目工程规范
- ##### 如果项目对外提供接口,则项目工程结果规范如下:
```
cloud-sample
cloud-sample-api
cloud-sample-impl
```
cloud-sample-api工程用于服务接口的定义,cloud-sample-imp工程用于服务的实现,cloud-sample作为整体项目通过maven的modules包含它们即可,如下所示:
```
4.0.0
com.cloud
cloud-sample
1.0.0
pom
cloud-sample-api
cloud-sample-impl
```
- ##### 每个项目工程统一使用maven管理工程,提交到GIT仓库中不允许出现开发工具生成的文件,只允许如下目录和文件:
```
src //用于存放源代码
pom.xml //maven的项目管理文件
readme.md //工程的介绍文件
.gitignore //git的忽略定义文件
```
- ##### 项目的pom.xml文件继承cloud-dependencies,并添加cloud-common依赖,如下所示:
```xml
4.0.0
com.cloud
cloud-demo
${build.time}
com.cloud
cloud-dependencies
1.0.0
1.x.x
1.x.x
org.springframework.boot
spring-boot-starter-web
com.cloud
cloud-common
${cloud-common-version}
com.cloud
cloud-sample-api
${cloud-sample-api-version}
org.springframework.boot
spring-boot-maven-plugin
org.apache.maven.plugins
maven-source-plugin
attach-sources
jar
```
- ##### 配置文件目录src/main/resources主要有如下文件
application.yml文件作为应用的核心配置,在注册中心nacos中创建一份,配置内容保存一致
bootstrap.yml文件作为启动引导配置,主要配置应用名和应用环境
bootstrap-local.yml文件作为本地环境的配置,用于本地开发启动使用
bootstrap-dev.yml文件作为开发环境的配置
bootstrap-test.yml文件作为测试环境的配置
bootstrap-prod.yml文件作为生产环境的配置
logging.xml文件是日志配置文件,保持默认即可
smartdoc.json接口文档工具配置
- ##### .gitignore作为git忽略配置文件,例子如下:
```
/*.iml
/.idea/
/target/
/.project
/.classpath
/.settings/
```
# 包的划分规范
- ##### 对包划分与命名统一,提高代码的可维护性:
```
bean //存放业务逻辑内部处理用到的类
cache //存放缓存操作的类(类名按业务域命名并以Cache结尾)
config //存放配置相关的类(类名以Config结尾)
constant //存放常量定义的类(包括枚举,枚举类名后缀以Enum结尾)
controller //存放对外接口的类(类名以Controller结尾)
dto //存放对外接口的参数类(类名以DTO结尾)
dao //存放数据库访问的类(类名以Dao结尾)
entity //存放数据库对应的实体类
job //定时任务处理类(类名以Job结尾)
service //存放业务逻辑处理的类(接口类名以Service结尾,实现类以ServiceImpl结尾)
support //存放业务逻辑实现需要支持的辅助类
util //存放通用型的工具类(类名以Utils结尾)
```
# 对外接口规范
- 请求方法统一使用POST(减少不统一引起的沟通成本)
- 请求路径格式为:/业务域/操作,如下所示:
```
/user/get* //获取单个
/user/query* //获取多个
/user/insert* //添加
/user/update* //更新
/user/delete* //删除
```
- 定义的接口路径不允许有占位符的用法,这种方式不利于接口监控,例如:/user/get/{id}
- 入参数量大于1个时,需要封装为DTO对象,并且统一放到dto包,如果接口的DTO比较多,按需根据业务创建子包
- Controller定义的接口使用标准的Java代码注释,使用maven插件smart-doc自动生成接口文档,接口定义例子如下:
```
/**
* 产品接口
*/
@Slf4j
@RestController
public class ProductController {
/**
* 查询产品
*
* @param paramDTO
* @return ResultInfo
*/
@PostMapping("/product/query")
public ResultInfo> query(@Valid @RequestBody ProductQueryParamDTO paramDTO) {
// TODO
return ResponseInfo.success();
}
}
```
接口返回对象统一使用ResultInfo类,并且通过泛型指定返回的业务数据类型,ResultInfo定义如下:
```
@Data
public class ResultInfo {
/**
* 状态码
*/
private int code;
/**
* 消息
*/
private String message;
/**
* 数据
*/
private T data;
}
```
- 接口入参DTO对象考虑到和返回值对象的区分,命名上做如下规范:
```
DTO入参对象类:业务域+操作+ParamDTO //例如:ProductQueryParamDTO
DTO返回对象类:业务域+DTO //例如:ProductDTO
```
入参验证数据有效性统一使用Validation验证框架,它采用注解的方式简化代码开发,如下所示:
```java
import lombok.Data;
import javax.validation.constraints.NotEmpty;
/**
* 产品查询参数
*/
@Data
public class ProductQueryParamDTO {
/**
* 名称
*/
@NotEmpty(message = "名字不能为空")
private String name;
/**
* 类型
*/
@NotEmpty(message = "类型不能为空")
private String type;
}
```
# 业务开发规范
### Session规范
- 业务模块读取登录的Session,统一使用cloud-common封装好的SessionContext类,例子如下:
```
SessionInfo sessionInfo = SessionContext.get();
```
- 登录模块保存Session,统一使用cloud-common封装好的SessionCache类,例子如下:
```
SessionInfo sessionInfo = new SessionInfo();
sessionInfo.setAppName(AppContext.getAppName());
sessionInfo.setUserId(userId);
sessionInfo.setSecret(secret);
sessionInfo.setAuthResources(authResources);
sessionCache.save(sessionInfo, appConfig.getSessionValidSeconds());
```
### Service 规范
- Mybatis-Plus提供的IService和ServiceImpl其内部已经实现基本的增删改查功能,通过继承它们从而提高开发效率,如下所示:
```java
public interface ProductService extends IService {
}
@Slf4j
@Service
public class ProductServiceImpl extends ServiceImpl implements ProductService {
}
```
- 如果某个Service调用其他业务的读写数据库接口,合理的做法是注入对方的Service,而不是注入对方的Dao
- 由于Mybatis-Plus的IService提供了通用的查询接口,通过指定查询对象QueryWrapper就可以进行各种条件查询,所以如果某个Service调用另外Service时,遵循由被调用方封装查询逻辑的原则,这样做的好处是代码高内聚低耦合并且提高了复用性。
- 本地事务控制使用Spring提供的事务注解,并设置rollbackFor=Exception.class,例子如下:
```
@Transactional(rollbackFor = Exception.class)
public void update(User user) {
updateById(user);
importAccount(user);
}
```
### Dao规范
- maven的pom文件添加数据库jdbc驱动和mybatis-plus依赖,例子如下:
```
mysql
mysql-connector-java
${mysql-connector-version}
com.baomidou
mybatis-plus-boot-starter
```
- Dao类继承BaseMapper,基本的增删改查功能已在基类实现,不必要写原始SQL,如下所示:
```
@Component
public interface ProductDao extends BaseMapper {
}
```
- 数据库表规范,表编码格式统一utf8mb4,表名采用t_作为前缀,表字段包含创建人/创建时间/更新人/更新时间,如下所示:
```
CREATE TABLE `t_discount_use`
(
`id` INT NOT NULL AUTO_INCREMENT COMMENT 'ID',
`activity_id` INT NOT NULL COMMENT '活动ID',
`product_id` INT NOT NULL COMMENT '产品ID',
`spec_id` INT NOT NULL COMMENT '规格ID',
`user_id` INT NOT NULL COMMENT '用户ID',
`create_user` INT DEFAULT 0 COMMENT '创建人',
`update_user` INT DEFAULT 0 COMMENT '更新人',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='折扣使用';
```
备注:ON UPDATE CURRENT_TIMESTAMP不要漏掉,避免程序中忘记设置更新时间引起字段值不对
### Cache规范
- 操作Redis缓存使用Spring提供的RedisTemplate,不同业务封装缓存类放到cache包下,类名后缀为Cache,例子如下:
```
@Slf4j
@Sentinel
@Component
public class ChatCache {
@Autowired
private RedisTemplate redisTemplate;
/**
* 保存对话
*
* @param imUserId
* @param imKeeperId
*/
public void save(String imUserId, String imKeeperId) {
String key = getKeyPrefix() + imUserId;
redisTemplate.opsForValue().set(key, imKeeperId, Duration.ofSeconds(60));
}
}
```
- 业务方法是单一查询场景时,采用注解的方式读写缓存,cloud-common提供了CacheConfigurer简化使用步骤,如下所示:
```
@Configuration
public class CacheConfig extends CacheConfigurer {
public static final String USER = "cloud-im:user:";
public static final String GROUP = "cloud-im:group:";
@Bean
@Override
public CacheManager cacheManager() {
setTimeout(USER, 600);
setTimeout(GROUP, 600);
return createCacheManager();
}
}
@Cacheable(cacheNames = CacheConfig.USER, key = "#userId")
public User getByUserId(Integer userId) {
// TODO
}
```
- Redis缓存Key的命名规范为:服务名:业务域,例如:cloud-im:users
### Feign规范
```
@FeignClient(name = "cloud-im", contextId = "cloud-im-user")
public interface ImUserProvider {
/**
* 根据imUserId获取查询
*
* @param imUserId IM账号
* @return ResultInfo 响应结果
*/
@FeignInvoke
@PostMapping("/user/getByImUserId")
ResultInfo getByImUserId(@RequestParam String imUserId);
/**
* 添加IM账号
*
* @param paramDTO 参数对象
* @return ResultInfo 响应结果
*/
@FeignInvoke
@PostMapping("/user/insert")
ResultInfo insertUser(@Valid @RequestBody UserInsertParamDTO paramDTO);
}
```
备注:@FeignClient中的name表示服务名,contextId用于一个服务需要拆分为多个类时用于避免bean的ID冲突,@FeignInvoke是自定义注解并且是必须的,可以拦截请求自动输出入参和返回值,方便开发和测试环境排查问题,同时还支持mock转发请求,提高微服务的本地调式效率。
### 配置规范
- 业务逻辑用到配置参数,统一定义放到config包下,如定义AppConfig类,配置例子如下:
```
app:
sessionValidSeconds: 1800
```
### 常量规范
- 业务实体有多个状态则使用枚举类定义,例子如下:
```
public enum ProductStatusEnum {
DELETED(0, "已删除"),
AVAILABLE(1, "可用"),
UNAVAILABLE(2, "不可用");
@Getter
private int code;
@Getter
private String description;
ProductStatusEnum(int code, String description) {
this.code = code;
this.description = description;
}
public static ProductStatusEnum of(int code) {
for (ProductStatusEnum statusEnum : values()) {
if (statusEnum.code == code) {
return statusEnum;
}
}
return null;
}
}
```
- 错误信息定义在ErrorEnum类,例子如下:
```
public enum ErrorEnum {
USER_EXIST(5001, "用户已存在"),
USER_NOT_EXIST(5002, "用户不存在");
@Getter
private int code;
@Getter
private String message;
public ResultInfo result() {
return ResultInfo.failure().setCode(code).setMsg(message);
}
ErrorEnum(int code, String message) {
this.code = code;
this.message = message;
}
}
```
- 公用常量统一定义在Constants类,例子如下
```
public class Constants {
public static final int OK = 1;
public static final int NO = 0;
}
```
### 异常规范
- 框架中提供了全局异常处理,非必须情况不要捕获异常,定义方法时统一抛出异常即可
- 如果捕获了异常想继续往上抛则无需输出异常信息,统一由全局异常处理输出
- 如果内部逻辑需要抛出自定义异常,统一使用AppException,该异常指定错误码和消息给全局异常处理输出返回值,如下所示:
```
log.error(e.getMessage(), e);
ErrorEnum errorEnum = ErrorEnum.TIM_IMPORT_USER_FAILURE;
throw new AppException(errorEnum.getCode(), errorEnum.getMessage());
```
### 日志规范
- 使用@Slf4j注解自动创建log对象,输出日志使用占位符,如下所示:
```java
@Slf4j
@Service
public class ProductServiceImpl {
public Product get(Integer id) {
log.info("product id={}", id);
}
}
```
- log的日志级别debug、info、warn、error要使用正确,原则如下:
debug:用于输出调试信息
info:用于输出业务的关键信息
warn:用于系统出现不正常的情况,但又不影响到核心逻辑
error:系统处理过程中出现重要错误,要把异常堆栈一起输出,如下所示:
```
log.error(e.getMessage(), e); //不允许少这个e异常对象
```
- log的配置文件统一使用默认的即可,logging.xml已经做好对接ELK的配置