Java Restful API Best Practices
API 是后端工作的主要工作之一, 开发难度低, 但是比较繁杂。 经过几个月的学习,总结一下自己对接口开发的一些套路。
接口首先,需要熟悉业务,熟悉数据库表结构,列出接口与表的对应关系。
确定接口需求与数据表
如某单位接口界面与表需求:
线路统计界面需要展示的数据 <==> 数据表
按照这种关系, 制作一个对应的Excel表,将初始需求确定下来,以便后面快速进行开发。
后台项目结构
user
---> controller ---> service ---> dao
---> cache ---> 返回
---> mapper ---> DB ---> 返回
---> 管道 ---> 数据处理 ---> 返回
按照这种结构可以更好地扩展,
dao层负责数据获取与接受,方式不定,可能从DB, Cache, MQ之类的结构获取或者提交数据。
pojo 分为
dto 数据传输对象, 当model数据需要处理时返回使用
vo 最后返回给前端的对象封装
model 从DB直接出来的数据,未经过逻辑加工
项目构建
结构定下来后, 直接使用spring initializr, 加入开发中需要的技术项。
加入swagger-ui,交代清楚接口事宜。
package com.loyotech.bigscreenbackend;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.RequestHandler;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @Auther:chalide
* @Date:2018/8/30 11:45
* @Description:
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket api(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("loyotech")
.apiInfo(apiInfo())
.select()
.apis(SwaggerConfig.basePackage("com.loyotech.bigscreenbackend"))
.paths(PathSelectors.regex("/*.*"))
.build();
}
/**
* Predicate that matches RequestHandler with given base package name for the class of the handler method.
* This predicate includes all request handlers matching the provided basePackage
*
* @param basePackage - base package of the classes
* @return this
*/
public static Predicate<RequestHandler> basePackage(final String basePackage) {
return input -> declaringClass(input).transform(handlerPackage(basePackage)).or(true);
}
/**
* 处理包路径配置规则,支持多路径扫描匹配以逗号隔开
*
* @param basePackage 扫描包路径
* @return Function
*/
private static Function<Class<?>, Boolean> handlerPackage(final String basePackage) {
return input -> {
for (String strPackage : basePackage.split(",")) {
boolean isMatch = input.getPackage().getName().startsWith(strPackage);
if (isMatch) {
return true;
}
}
return false;
};
}
/**
* @param input RequestHandler
* @return Optional
*/
private static Optional<? extends Class<?>> declaringClass(RequestHandler input) {
return Optional.fromNullable(input.declaringClass());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
//标题
.title("Demo-Api")
//联系人
.contact(new Contact("", "", ""))
//版本号
.version("1.0")
//描述
.description("Demo-api")
.build();
}
}
引入swagger-ui,减少交互成本
环境
上下文环境在这里指的就是Spring容器上下文,也就是在环境中定义一些对象,存储进去。
添加spring 上下文环境, spring-dao, spring-service, spring-web, web.xml
或者是使用Java Config类去定义上下文环境(推荐)。
其他环境,mybatis-config.xml, logback.xml
当部署的时候只需要在命令行中调用对应的环境
启用对应的propertiesjava -jar xxx.jar --spring.profiles.active=test
这样就可以启动那个对应环境的配置
相似的,下面是会根据对应的关系启用对应的properties/yaml文件
开发流程
user
---> controller ---> service ---> dao
---> cache ---> 返回
---> mapper ---> DB ---> 返回
---> 管道 ---> 数据处理 ---> 返回
规则1.所有的类都必须有接口和实现,以便后期扩展。
规则2.所有的单条接口流程命名一致,如有其他业务则另外扩展业务类
规则3.写上业务注释,使用IDEA全局搜索辅助查找需求
规则4.所有传参统一使用Map,所有返参统一使用Object
规则5.一切都往规则靠拢,不要魔法数,命名表达意思...
以上是为了开发时可以更好利用搜索,统一命名可能不太妥当,所以如有其他业务则直接扩展业务类,重新定义业务方法名。
最外层开始 -->
@SpringBootApplication
@ComponentScan
@EnableAutoConfiguration
@EnableSwagger2
@EnableCaching
public class BigScreenBackendApplication(){}
Controller
@RestController
@Api("Ice-Module-Api")
@Log
public class IceModuleController implements IceModuleRemoteApi {
@Autowired
IceModuleService iceModuleService;
@Override
public Object selectIceMonitorDeviceCountEveryProvince(HttpServletRequest request, HttpServletResponse response) {
// 获取传输参数, 删除无效参数, 加上分页逻辑.....
Map<String,String> parameterMap = MapUtil.getParameterMap(request);
log.info("方法-selectIceMonitorDeviceCountEveryProvince 参数-" + parameterMap.toString());
return iceModuleService.selectIceMonitorDeviceCountEveryProvince(parameterMap);
}
}
Service
@Service
@Transactional
@Log
public class IceModuleServiceImpl implements IceModuleService {
// 有可能会用到redisTemplate, 如果没有使用redis,
// 则这个对象为null, 使用时在最外层做一下空指针判定
@Autowired(required = false)
RedisTemplate redisTemplate;
@Autowired
IceModuleDao iceModuleDao;
// #TODO 截至编码位置, 利用TODO来标示任务项,可快速定位开发位置
@Override
public Object selectIceMonitorDeviceCountEveryProvince(Map<String, String> paramterMap) {
List<String> recCompanies = Arrays.asList("南网超高压", "广东电网", "广西电网", "云南电网", "贵州电网");
List<IceMonitorDeviceCount> iceMonitorDeviceCounts = iceModuleDao.selectIceMonitorDeviceCountEveryProvince(paramterMap);
List<IceMonitorDeviceCount> result = new LinkedList<>();
// 初始化数据
recCompanies.stream().distinct().forEach(
recCompany -> {
result.add(new IceMonitorDeviceCount(){{
setRecCompany(recCompany);
}});
}
);
result.stream().forEach(iceMonitorDeviceCount -> {
for (IceMonitorDeviceCount data : iceMonitorDeviceCounts) {
if (iceMonitorDeviceCount.getRecCompany().equalsIgnoreCase(data.getRecCompany())) {
iceMonitorDeviceCount.setCount(data.getCount());
}
}
});
return prepareGoodResult(result);
}
}
Dao
@Repository
public class IceModuleDaoImpl implements IceModuleDao {
@Autowired
IceModuleMapper iceModuleMapper;
@Override
@Cacheable(value = "demo", key = "'IMDC:' + #parameterMap.get(id)")
public List<IceMonitorDeviceCount> selectIceMonitorDeviceCountEveryProvince(Map<String, String> parameterMap) {
List<IceMonitorDeviceCount> result = iceModuleMapper.selectIceMonitorDeviceCountEveryProvince(parameterMap);
return result;
}
}
Mapper
@Mapper
@Repository
public interface IceModuleMapper {
List<IceMonitorDeviceCount> selectIceMonitorDeviceCountEveryProvince(Map<String, String> parameterMap);
}
IceModuleMapper.xml
<resultMap id="IceMonitorDeviceCountMap" type="com.loyotech.bigscreenbackend.model.IceMonitorDeviceCount">
<result column="COUNT" jdbcType="INTEGER" property="count" />
<result column="REC_COMPANY" jdbcType="VARCHAR" property="recCompany" />
</resultMap>
<select id="selectIceMonitorDeviceCountEveryProvince" resultMap="IceMonitorDeviceCountMap">
SELECT COUNT(DISTINCT BSID) AS COUNT, COMPANY
FROM ICE_MONITOR_DATA
GROUP BY REC_COMPANY
</select>
以上,构建了最主要的API主要的轮廓,还可以往上添加很多其他的内容,如:
Servlet, Listener, Filter, Bean, ApplicationContext, CronTask, InitRunner, Exception, Cache....
这些这里暂且不做说明