# 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的配置