编写和维护接口文档是每个程序员的职责,根据Swagger2可以快速帮助我们编写最新的API接口文档,再也不用担心开会前仍忙于整理各种资料了,间接提升了团队开发的沟通效率。
swagger通过注解表明该接口会生成文档,包括接口名、请求方法、参数、返回信息的等等。
@Api:修饰整个类,描述Controller的作用
@ApiOperation:描述一个类的一个方法,或者说一个接口
@ApiParam:单个参数描述
@ApiModel:用对象来接收参数
@ApiModelProperty:用对象接收参数时,描述对象的一个字段
@ApiImplicitParam:一个请求参数
@ApiImplicitParams:多个请求参数
在common模块pom.xml引入Swagger2依赖
io.springfox
springfox-swagger2
io.springfox
springfox-swagger-ui
在service-util模块添加 com.atguigu.yygh.common.config.Swagger2Config 配置类
package com.atguigu.yygh.common.config;
/**
* Swagger2 配置信息
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
public Docket webApiConfig() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
// 只显示api路径下的页面
.paths(Predicates.and(PathSelectors.regex("/api/.*")))
.build();
}
@Bean
public Docket adminApiConfig() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("adminApi")
.apiInfo(adminApiInfo())
.select()
// 只显示 admin 路径下的页面
.paths(Predicates.and(PathSelectors.regex("/admin/.*")))
.build();
}
private ApiInfo webApiInfo() {
return new ApiInfoBuilder()
.title("网站-API文档")
.description("本文档描述了网站微服务接口定义")
.version("1.0")
.contact(new Contact("atguigu","http://atguigu.com","[email protected]"))
.build();
}
private ApiInfo adminApiInfo() {
return new ApiInfoBuilder()
.title("后台管理系统——API文档")
.description("本文档描述了后台管理系统微服务接口定义")
.version("1.0")
.contact(new Contact("atguigu", "http://atguigu.com", "[email protected]"))
.build();
}
}
因为Swagger2Config 和启动类不在一个模块下,引入进来之后,需要添加组件扫描。否则不能访问SwaggerUI界面
@SpringBootApplication
@ComponentScan(basePackages = "com.atguigu")
public class ServiceHospApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceHospApplication.class, args);
}
}
医院设置主要是用来保存开通医院的一些基本信息,每个医院一条信息,保存了医院编号(平台分配,全局唯一)和接口调用相关的签名key等信息,是整个流程的第一步,只有开通了医院设置信息,才可以上传医院相关信息。
我们所开发的功能就是基于单表的一个CRUD、锁定/解锁和发送签名信息这些基本功能。
表结构:
hosname: 医院名称
hoscode: 医院编号(平台分配,全局唯一,api接口必填信息)
api_url: 医院回调的基础url(如:预约下单,我们要调用该地址去医院下单)
sign_key: 双方api接口调用的签名key,有平台生成
contacts_name: 医院联系人姓名
contacts_phone: 医院联系人手机
status: 状态(锁定/解锁)
#
# Database "yygh_hosp"
#
CREATE DATABASE IF NOT EXISTS `yygh_hosp` CHARACTER SET utf8mb4;
USE `yygh_hosp`;
#
# Structure for table "hospital_set"
#
CREATE TABLE `hospital_set` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`hosname` varchar(100) DEFAULT NULL COMMENT '医院名称',
`hoscode` varchar(30) DEFAULT NULL COMMENT '医院编号',
`api_url` varchar(100) DEFAULT NULL COMMENT 'api基础路径',
`sign_key` varchar(50) DEFAULT NULL COMMENT '签名秘钥',
`contacts_name` varchar(20) DEFAULT NULL COMMENT '联系人',
`contacts_phone` varchar(11) DEFAULT NULL COMMENT '联系人手机',
`status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '状态',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint(3) NOT NULL DEFAULT '0' COMMENT '逻辑删除(1:已删除,0:未删除)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_hoscode` (`hoscode`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='医院设置表';
在service下创建service_hosp模块
com.atguigu.yygh
service
0.0.1-SNAPSHOT
com.atguigu.yygh
service_hosp
0.0.1-SNAPSHOT
jar
service-hosp
service-hosp
service-hosp
org.springframework.boot
spring-boot-maven-plugin
# 服务端口
server:
port: 8201
spring:
application:
# 服务名
name: service-hosp
# 环境设置:dev、test、prod
profiles:
active: dev
# 数据库连接
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/yygh_hosp?characterEncoding=utf-8&useSSL=false
username: root
password: 0903he0419
# 返回json的全局时间格式
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
mybatis-plus:
#配置mapper xml文件的路径
mapper-locations: com/atguigu/yygh/hosp/mapper/xml/*.xml
在com.atguigu.yygh.hosp.config 创建HospConfig 配置类
将mapper扫描放在配置类上
@Configuration
@MapperScan("com.atguigu.yygh.hosp.mapper")
public class HospConfig {
// Mybatis-Plus 分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
BaseEntity
@Data
public class BaseEntity implements Serializable {
@ApiModelProperty(value = "id")
@TableId(type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("create_time")
private Date createTime;
@ApiModelProperty(value = "更新时间")
@TableField("update_time")
private Date updateTime;
@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
@TableLogic
@TableField("is_deleted")
private Integer isDeleted;
@ApiModelProperty(value = "其他参数")
@TableField(exist = false)
private Map param = new HashMap<>();
}
com.atguigu.yygh.model.hosp.HospitalSet
@Data
@ApiModel(description = "医院设置")
@TableName("hospital_set")
public class HospitalSet extends BaseEntity {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "医院名称")
@TableField("hosname")
private String hosname;
@ApiModelProperty(value = "医院编号")
@TableField("hoscode")
private String hoscode;
@ApiModelProperty(value = "api基础路径")
@TableField("api_url")
private String apiUrl;
@ApiModelProperty(value = "签名秘钥")
@TableField("sign_key")
private String signKey;
@ApiModelProperty(value = "联系人姓名")
@TableField("contacts_name")
private String contactsName;
@ApiModelProperty(value = "联系人手机")
@TableField("contacts_phone")
private String contactsPhone;
@ApiModelProperty(value = "状态")
@TableField("status")
private Integer status;
}
HospitalSetVo
@Data
public class HospitalSetQueryVo {
@ApiModelProperty(value = "医院名称")
private String hosname;
@ApiModelProperty(value = "医院编号")
private String hoscode;
}
Result
/**
* 全局统一返回结果类
*/
@Data
@ApiModel(value = "全局统一返回结果")
public class Result {
@ApiModelProperty(value = "返回码")
private Integer code;
@ApiModelProperty(value = "返回消息")
private String message;
@ApiModelProperty(value = "返回数据")
private T data;
public Result(){}
protected static Result build(T data) {
Result result = new Result();
if (data != null)
result.setData(data);
return result;
}
public static Result build(T body, ResultCodeEnum resultCodeEnum) {
Result result = build(body);
result.setCode(resultCodeEnum.getCode());
result.setMessage(resultCodeEnum.getMessage());
return result;
}
public static Result build(Integer code, String message) {
Result result = build(null);
result.setCode(code);
result.setMessage(message);
return result;
}
public static Result ok(){
return Result.ok(null);
}
/**
* 操作成功
* @param data
* @param
* @return
*/
public static Result ok(T data){
Result result = build(data);
return build(data, ResultCodeEnum.SUCCESS);
}
public static Result fail(){
return Result.fail(null);
}
/**
* 操作失败
* @param data
* @param
* @return
*/
public static Result fail(T data){
Result result = build(data);
return build(data, ResultCodeEnum.FAIL);
}
public Result message(String msg){
this.setMessage(msg);
return this;
}
public Result code(Integer code){
this.setCode(code);
return this;
}
public boolean isOk() {
if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {
return true;
}
return false;
}
}
ResultCodeEnum
/**
* 统一返回结果状态信息类
*/
@Getter
public enum ResultCodeEnum {
SUCCESS(200,"成功"),
FAIL(201, "失败"),
PARAM_ERROR( 202, "参数不正确"),
SERVICE_ERROR(203, "服务异常"),
DATA_ERROR(204, "数据异常"),
DATA_UPDATE_ERROR(205, "数据版本异常"),
LOGIN_AUTH(208, "未登陆"),
PERMISSION(209, "没有权限"),
CODE_ERROR(210, "验证码错误"),
// LOGIN_MOBLE_ERROR(211, "账号不正确"),
LOGIN_DISABLED_ERROR(212, "改用户已被禁用"),
REGISTER_MOBLE_ERROR(213, "手机号已被使用"),
LOGIN_AURH(214, "需要登录"),
LOGIN_ACL(215, "没有权限"),
URL_ENCODE_ERROR( 216, "URL编码失败"),
ILLEGAL_CALLBACK_REQUEST_ERROR( 217, "非法回调请求"),
FETCH_ACCESSTOKEN_FAILD( 218, "获取accessToken失败"),
FETCH_USERINFO_ERROR( 219, "获取用户信息失败"),
//LOGIN_ERROR( 23005, "登录失败"),
PAY_RUN(220, "支付中"),
CANCEL_ORDER_FAIL(225, "取消订单失败"),
CANCEL_ORDER_NO(225, "不能取消预约"),
HOSCODE_EXIST(230, "医院编号已经存在"),
NUMBER_NO(240, "可预约号不足"),
TIME_NO(250, "当前时间不可以预约"),
SIGN_ERROR(300, "签名错误"),
HOSPITAL_OPEN(310, "医院未开通,暂时不能访问"),
HOSPITAL_LOCK(320, "医院被锁定,暂时不能访问"),
;
private Integer code;
private String message;
private ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
com.atguigu.yygh.model.hosp.mapper
@Mapper
public interface HospitalSetMapper extends BaseMapper {
}
com.atguigu.yygh.model.hosp.mapper.xml
com.atguigu.yygh.model.hosp.service
public interface HospitalSetService extends IService {
}
com.atguigu.yygh.model.hosp.service.impl
@Service
public class HospitalSetServiceImpl extends ServiceImpl implements HospitalSetService {
@Autowired
private HospitalSetMapper hospitalSetMapper;
}
com.atguigu.yygh.model.hosp.controller
package com.atguigu.yygh.hosp.controller;
@Api(tags = "医院设置管理")
@RestController
@RequestMapping("/admin/hosp/hospitalSet")
public class HospitalSetController {
@Autowired
private HospitalSetService hospitalSetService;
/*
* 1、查询医院设置表所有信息
* */
@ApiOperation(value = "获取所有医院设置")
@GetMapping("findAll")
public Result findAllHospitalSet() {
// 调用 service的方法
List list = hospitalSetService.list();
return Result.ok(list);
}
/*
* 2、逻辑删除医院设置
* */
@ApiOperation(value = "逻辑删除医院设置")
@DeleteMapping("{id}")
public Result removeHospSet(@PathVariable Long id) {
boolean flag = hospitalSetService.removeById(id);
if (flag) {
return Result.ok();
} else {
return Result.fail();
}
}
/*
* 3、条件查询带分页
* */
@ApiOperation(value = "条件查询带分页")
@PostMapping("findPageHospSet/{current}/{limit}")
public Result findPageHospSet(@PathVariable long current,
@PathVariable long limit,
@RequestBody(required = false)
HospitalSetQueryVo hospitalSetQueryVo
) {
// 创建 page 对象,传递当前页,每页记录数
Page page = new Page<>(current, limit);
QueryWrapper wrapper = new QueryWrapper<>();
String hosname = hospitalSetQueryVo.getHosname();//医院名称
String hoscode = hospitalSetQueryVo.getHoscode();//医院编号
if (!StringUtils.isEmpty(hosname)) {
wrapper.like("hosname", hospitalSetQueryVo.getHosname());
}
if (!StringUtils.isEmpty(hoscode)) {
wrapper.eq("hosocde", hospitalSetQueryVo.getHoscode());
}
// 调用方法实现分页查询
Page pageHospitalSet = hospitalSetService.page(page, wrapper);
// 返回结果
return Result.ok(pageHospitalSet);
}
/*
* 4、添加医院设置
* */
@ApiOperation(value = "添加医院设置")
@PostMapping("saveHospitalSet")
public Result saveHospitalSet(@RequestBody HospitalSet hospitalSet) {
// 设置状态:1 使用,0 不能使用
hospitalSet.setStatus(1);
// 签名秘钥
Random random = new Random();
hospitalSet.setSignKey(MD5.encrypt(System.currentTimeMillis() + "" + random.nextInt(1000)));
//调用service
boolean save = hospitalSetService.save(hospitalSet);
if (save) {
return Result.ok();
} else {
return Result.fail();
}
}
/*
* 5、根据id获取医院设置
* */
@ApiOperation(value = "根据id获取医院设置")
@GetMapping("getHospSet/{id}")
public Result getHospSet(@PathVariable Long id) {
HospitalSet hospitalSet = hospitalSetService.getById(id);
return Result.ok(hospitalSet);
}
/*
* 6、修改医院设置
* */
@ApiOperation(value = "修改医院设置")
@PostMapping("updateHospitalSet")
public Result updateHospitalSet(@RequestBody HospitalSet hospitalSet) {
boolean flag = hospitalSetService.updateById(hospitalSet);
if (flag) {
return Result.ok();
} else {
return Result.fail();
}
}
/*
* 7、批量删除医院设置
* */
@ApiOperation(value = "批量删除医院设置")
@DeleteMapping("batchRemove")
public Result batchRemoveHospitalSet(@RequestBody List idList) {
boolean flag = hospitalSetService.removeByIds(idList);
if (flag) {
return Result.ok();
} else {
return Result.fail();
}
}
/*
* 8、医院设置锁定与解锁
* */
@ApiOperation(value = "医院设置锁定与解锁")
@PutMapping("lockHospitalSet/{id}/{status}")
public Result lockHospitalSet(@PathVariable Long id,
@PathVariable Integer status) {
// 根据id查询医院设置信息
HospitalSet hospitalSet = hospitalSetService.getById(id);
// 设置状态
hospitalSet.setStatus(status);
// 调用方法
hospitalSetService.updateById(hospitalSet);
return Result.ok();
}
/*
* 9、发送签名秘钥
* */
@PutMapping("sendKey/{id}")
public Result lockHospitalSet(@PathVariable Long id) {
HospitalSet hospitalSet = hospitalSetService.getById(id);
String signKey = hospitalSet.getSignKey();
String hoscode = hospitalSet.getHoscode();
// TODO 发送短信
return Result.ok();
}
}
在common-util模块添加自定义异常处理类——YyghException
@Data
@ApiModel(value = "自定义全局异常类")
public class YyghException extends RuntimeException {
@ApiModelProperty(value = "异常状态码")
private Integer code;
/**
* 通过状态码和错误消息创建异常对象
* @param message
* @param code
*/
public YyghException(String message, Integer code) {
super(message);
this.code = code;
}
/**
* 接收枚举类型对象
* @param resultCodeEnum
*/
public YyghException(ResultCodeEnum resultCodeEnum) {
super(resultCodeEnum.getMessage());
this.code = resultCodeEnum.getCode();
}
@Override
public String toString() {
return "YyghException{" +
"code=" + code +
", message=" + this.getMessage() +
'}';
}
}
在common-util模块添加全局异常处理类——GlobalExceptionHandler
/*
* 全局异常处理类
* */
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result error(Exception e) {
e.printStackTrace();
return Result.fail();
}
/*
* 自定义异常处理
* */
@ExceptionHandler(YyghException.class)
public Result error(YyghException e) {
e.printStackTrace();
System.out.println(e.getCode() + " : " + e.getMessage());
return Result.build(e.getCode(), e.getMessage());
}
}
在controller中测试全局异常是否生效:访问根据id获取医院设置
此接口测试自定义异常
/*
* 5、根据id获取医院设置
* */
@ApiOperation(value = "根据id获取医院设置")
@GetMapping("getHospSet/{id}")
public Result getHospSet(@PathVariable Long id) {
try {
// 模拟异常
int a = 1 / 0;
} catch (Exception e) {
throw new YyghException("失败了呀", 2002);
}
HospitalSet hospitalSet = hospitalSetService.getById(id);
return Result.ok(hospitalSet);
}
日志记录器(Logger)的行为是分等级的。如下表所示:分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
默认情况下,spring boot从控制台打印出来的日志级别只有INFO及以上级别,可以配置日志级别
# 设置日志级别
logging:
level:
root: debug
resources/logback-spring.xml
logback
INFO
${CONSOLE_LOG_PATTERN}
UTF-8
${log.path}/log_info.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log
100MB
15
INFO
ACCEPT
DENY
${log.path}/log_warn.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log
100MB
15
warn
ACCEPT
DENY
${log.path}/log_error.log
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
UTF-8
${log.path}/error/log-error-%d{yyyy-MM-dd}.%i.log
100MB
15
ERROR
ACCEPT
DENY