目前主流的公司和开发团队都比较关注前后端分离模式的开发。该模式明确了前后端的作用和职责,一定程度上降低了前后端人员的开发时间成本,但是技术却有一定的要求。前后端分离模式极大解耦了前后端服务的关系,避免了前端严重依赖后端,解决了前后端接口交互不及时,易变更等问题。使得前后端开发人员能更高效的专注自己的领域。
前后端分离后,关键的桥梁就是接口。通过接口将前后端联系在一起。而接口交互一直以来是前后端人员交互容易产生冲突的地方。而随着swagger等接口信息自动化生成测试的技术出现,极大减少了这种冲突。swagger由后端开发人员集成到项目中,使用相应的注解来描述接口,参数,返回模型等各类信息。后端开发人员如果变更了接口,接口可以及时反映到前端。
首先一个springboot项目集成swagger需要依赖以下包:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${version.swagger}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${version.swagger}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-bean-validators</artifactId> <version>${version.swagger}</version> </dependency><dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
将以上版本改为自己使用的版本即可,引入依赖包后还需要将swagger整合到项目中,整合后在启动类加上注解即可。swagger整合到springboot中网上有很多相关资料,这里不再贴出代码。本人的swagger整合和网上不同,本人将swagger中的核心类进行了重构,开发了自己的注解,采用配置的方式整合。如下:
@EnableAutoSwagger2 @SpringBootApplication(scanBasePackages = {"com.yougu.core","com.yougu.server.common"}) public class ServerApplication { public static void main(String[] args) { SpringApplication.run(ServerApplication.class, args); } }
## Swagger configuration swagger: enabled: true title: ${info.build.name} description: ${info.build.description} version: ${info.build.version} base-package: com.yougu.core base-path: /**
需要注意@EnableAutoSwagger2不能直接用,是本人自己开发的注解。这里给出思路,大家可以根据自己的情况也进行相应的重构。swagger的效果在下面结合代码给出图片。
beetlsql的使用比mybatis效率有很大的提升。遵循约定优于配置的原则,这正好和springboot的理念相符。beetlsql通过反射机制,将数据库的表结构和对应的javabean直接进行关联,大大减少开发人员的无关配置。开发效率得到了很大提升。在springboot中引入beetlsql,这里以mysql数据库为例:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetl</artifactId> </dependency> <dependency> <groupId>com.ibeetl</groupId> <artifactId>beetlsql</artifactId> </dependency>
由于本人代码对数据库连接做了多数据源和工具的重构,这里也不在贴出整合代码, 网上可自行查找相关简单的集成。下面只给出数据库的配置:
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/csic_722_mis?useunicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: xxxx password: xxxxx sql-root: /sql dao-suffix: Repository product-mode: false offset-start-zero: true debug-interceptor: true base-package: com.yougu.core.repository druid: enable: true initial-size: 5 min-idle: 8 max-active: 15 connection-init-sqls: 'SET NAMES utf8mb4' validation-query: 'SELECT 1' test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-open-prepared-statements: 20 use-global-data-source-stat: true filters: stat,slf4j,wall,config stat-view-servlet: enabled: true url-pattern: /druid/* reset-enable: true
下面结合具体的例子来说明该模式的开发。
首先将前后端的交互接口进行抽象,抽象成一个接口对象,而不提供具体实现,然后由Controller进行具体的实现,抽象的接口使用swagger注解对接口进行描述,Controller中只需实现接口,避免代码直接污染Controller
@Api(value = "dept Api Client", description = "部门-API(幽谷)", protocols = "application/json") public interface DeptApi { String BASE_PATH = "/server/dept"; @ApiOperation(value = "通过ID查询部门信息 #幽谷/2019-05-03#", notes = "通过ID查询部门信息", nickname = "dept-get") @ApiImplicitParams({@ApiImplicitParam(name = "id", value = "查询对象", paramType = "path", dataType = "int", required = true)}) @RequestMapping(value = BASE_PATH + "/get/{id}" ,method = RequestMethod.GET) DeptDto get(@PathVariable("id") int id); @ApiOperation(value = "通过条件分页查询部门信息 #幽谷/2019-05-03#", notes = "通过条件分页查询部门信息", nickname = "dept-pageDept") @ApiImplicitParams({@ApiImplicitParam(name = "deptParamDto", value = "查询对象", paramType = "body", dataType = "DeptParamDto", required = true)}) @RequestMapping(value = BASE_PATH + "/pageDept" ,method = RequestMethod.POST) PageData<DeptDto> pageDept(@RequestBody DeptParamDto deptParamDto); @ApiOperation(value = "新增部门信息 #幽谷/2019-05-03#", notes = "新增部门信息", nickname = "dept-create") @ApiImplicitParams({@ApiImplicitParam(name = "deptInsertDto", value = "新增部门对象", paramType = "body", dataType = "DeptInsertDto", required = true)}) @RequestMapping(value = BASE_PATH + "/create" ,method = RequestMethod.POST) String create(@RequestBody DeptInsertDto deptInsertDto); @ApiOperation(value = "修改部门信息 #幽谷/2019-05-03#", notes = "修改部门信息", nickname = "dept-update") @ApiImplicitParams({@ApiImplicitParam(name = "deptModifyDto", value = "修改部门对象", paramType = "body", dataType = "DeptModifyDto", required = true)}) @RequestMapping(value = BASE_PATH + "/update" ,method = RequestMethod.POST) String update(@RequestBody DeptModifyDto deptModifyDto); @ApiOperation(value = "删除部门信息 #幽谷/2019-05-03#", notes = "删除部门信息", nickname = "dept-delete") @ApiImplicitParams({@ApiImplicitParam(name = "idsDto", value = "删除部门ID集", paramType = "body", dataType = "IdsDto", required = true)}) @RequestMapping(value = BASE_PATH + "/delete" ,method = RequestMethod.POST) String delete(@RequestBody IdsDto idsDto); }
由于代码较多,下面只贴出删除方法的实现:
@Override public String delete(@RequestBody IdsDto idsDto) { DeptUnit deptUnit = new DeptUnit(); deptUnit.setUpdatedAt(LocalDateTime.now()); deptUnit.setUpdatedBy(MyRequestContextHolder.getUserId()); deptUnit.setDelFlag(Boolean.TRUE); //代码尽量避免罗嗦,该行代码至少将5个操作融为一体,1、三目运算2、字符串截取3、字符串转字符串数组4、字符串数组转字符串集合5、字符串集合转整型集合 List<Integer> idList = commonMapper.toIntList( Arrays.asList( StringUtils.commaDelimitedListToStringArray( idsDto.getIds().endsWith(",")?idsDto.getIds().substring(0,idsDto.getIds().length() - 1):idsDto.getIds()))); if(!ObjectUtils.isEmpty(idList))//java的开发在于使用具体,而不是重复造轮子,也尽量自己少写if(null == obj),可以直接使用工具类。 repository.myDeleteByIds( idList, deptUnit); return CommonConstant.DELETE_SUCCESS_MSG; }
下面是使用beetlsql的dao层
@Component @SqlResource("dept") public interface DeptRepository extends BaseMapper<DeptUnit> { void pageDept(PageQuery<DeptDto> pageQuery); int myDeleteByIds(@Param("idList") List<Integer> idList, @Param("deptUnit") DeptUnit deptUnit); }
sql文件则只需要写sql语句,不需要进行数据结构配置
myDeleteByIds === *根据ID批量软删除信息 UPDATE csic_mis_dept SET del_flag = #deptUnit.delFlag#,updated_At = #deptUnit.updatedAt#,updated_By = #deptUnit.updatedBy# WHERE id IN (#join(idList)#)
beetlsql更强大的地方在于其已经自动生成了大量基础sql,对于上面四个接口,只需写两个sql即足够,比如查询,无需写sql,可以直接引用:
@Override public DeptDto get(@PathVariable("id") int id) { return mapper.toDeptDto(repository.createQuery().andEq("id", id).andEq("del_flag", Boolean.FALSE).select()); }
一句代码解决该服务,是不是开发效率大大提升。
前端只需要更具swagger生成的接口图进行调用和测试即可:
通过swagger可以直接明了的查看接口信息,数据模型,字段属性等。
以上即是一个简单的后端模式开发过程。后端开发往往较为复杂,使用一个好的架构模式,可以很好的提升开发效率,减少不必要的麻烦。后续再给出后端开发的其他架构设计。