小公司的悲哀,维护项目和小需求不断。。
整理一下,算作纪念
需求:第三方需要接口传输图片流到特定的摄像头下
分析:
/**
* 开发接口:
* 1.接收主要:图片(小图,大图),时间,设备id -> Base64(小图,大图),时间戳,设备id
* 2.调用平台登录,全部相机接口 -> 相机集合[相机id,相机唯一标识]
* 3.遍历相机集合根据【设备id = 唯一标识】锁定入参:[相机id,Base64(小图,大图),时间戳]
* 4.调用入库接口,传入入参
* 5.异常处理,数据包装返回
*/
重点:
- 数据格式:multipart/form-data,区别以往的application/json,text/plain请求参数格式,需要传文件啦
- 涉及到大数据,缓存起来,缩减以后的响应时间
- 日志也要好好写啦,区别以往,增加logback.xml配置文件
- 全局异常要继续hold住啊
好了,咱们代码里详聊~
``首先,实体类
/**
* Created by DQ on 2020/6/23/023.
* 请求类
*/
@Data
public class RequestDTO {
private String faceid;
private String deviceid;
private String devicename;
private String locationid;
private String locationname;
private String lng;
private String lat;
private String identification;
private String passtime;
private MultipartFile facepic;
private MultipartFile backgroudpic;
private String rect;
}
/**
* 响应类
*/
@Data
public class BaseResponse {
// Y 错误码 0:成功 其它:失败
protected Integer errcode;
// Y 错误描述
protected String errmsg;
public BaseResponse() {
this(ResponseEnum.SUCCESS);
}
public BaseResponse(ResponseEnum errorCode) {
this.errcode = errorCode.getRtn();
this.errmsg = errorCode.getMessage();
}
public BaseResponse(ResponseEnum errorCode, String message) {
this.errcode = errorCode.getRtn();
this.errmsg = message;
}
public boolean valid() {
return 0 == this.errcode;
}
}
/**
* 响应状态码枚举类
*/
public enum ResponseEnum {
SUCCESS(0, "OK"),
SERVER_ERROR(1, "服务器异常,请稍后重试"),
BAD_REQUEST_PARAMETER(-1, "图片参数错误"),
NO_MATCH_DEVICEID(-1000, "deviceid与平台无匹配"),
BAD_REQUEST(-2, "请求错误"),
MEDIA_TYPE_NOT_SUPPORTED(-3, "请求的Content-Type错误"),
METHOD_NOT_SUPPORTED(-4, "请求方法不支持"),
;
private Integer rtn;
private String message;
ResponseEnum(Integer rtn, String message) {
this.rtn = rtn;
this.message = message;
}
public Integer getRtn() {
return rtn;
}
public String getMessage() {
return message;
}
}
/**
* Created by DQ on 2020/6/24/024.
* 要被缓存的类
*/
@Data
public class Camera {
private Integer id;
//对应摄像头的唯一标识 外部deviceid 内部police_unit
private String police_unit;
public Camera(Integer id, String police_unit) {
this.id = id;
this.police_unit = police_unit;
}
public Camera() {
}
}
重头戏来咯,controller和service
/**
* Created by DQ on 2020/6/24/024.
*/
@RestController
@RequestMapping("/img")
@Api(value = "ImgFlow", tags = {"图片流入库Controller"})
public class ImgFlow {
private static Logger logger = LoggerFactory.getLogger(ImgFlow.class);
@Autowired
FlowService flowService;
@RequestMapping(value = "/flow", method = RequestMethod.POST)
@ApiOperation(value = "图片流入库接口")
public BaseResponse handleFileUpload(@RequestParam(value = "deviceid") String deviceid,
@RequestParam(value = "passtime") String passtime,
@RequestParam(value = "facepic") MultipartFile facepic,
@RequestParam(value = "backgroudpic") MultipartFile backgroudpic,
@RequestParam(value = "faceid", required = false) String faceid,
@RequestParam(value = "devicename", required = false) String devicename,
@RequestParam(value = "locationid", required = false) String locationid,
@RequestParam(value = "locationname", required = false) String locationname,
@RequestParam(value = "lng", required = false) Long lng,
@RequestParam(value = "lat", required = false) Long lat,
@RequestParam(value = "identification", required = false) String identification,
@RequestParam(value = "rect", required = false) String rect
) throws IOException, InterruptedException {
logger.info("deviceid:" + deviceid);
logger.info("passtime:" + passtime);
String faceFilename = facepic.getOriginalFilename();
String backFilename = backgroudpic.getOriginalFilename();
logger.info("facepic:" + faceFilename);
logger.info("backgroudpic:" + backFilename);
if(StrUtil.isBlank(faceFilename) || StrUtil.isBlank(backFilename)){
return new BaseResponse(ResponseEnum.BAD_REQUEST_PARAMETER);
}else {
if(faceFilename.contains(".jpg") || faceFilename.contains(".png") || faceFilename.contains(".bmp")){
if(backFilename.contains(".jpg") || backFilename.contains(".png") || backFilename.contains(".bmp")){
return flowService.FlowIt(deviceid, passtime, facepic, backgroudpic);
}
return new BaseResponse(ResponseEnum.BAD_REQUEST_PARAMETER);
}
return new BaseResponse(ResponseEnum.BAD_REQUEST_PARAMETER);
}
}
}
/**
* Created by DQ on 2020/6/23/023.
*/
@Service
public class FlowService {
private static Logger logger = LoggerFactory.getLogger(FlowService.class);
@Autowired
HttpUtils httpUtils;
public BaseResponse FlowIt(String deviceid, String passtime, MultipartFile facepic, MultipartFile backgroudpic) throws InterruptedException {
BaseResponse baseResponse = new BaseResponse();
httpUtils.loginFp();
ArrayList<Camera> cameras = httpUtils.getCameras();
int j = 0;
for (Camera camera : cameras) {
if (deviceid.equals(camera.getPolice_unit())) {
String body = JSONUtil.createObj()
.set("camera_id", camera.getId())
.set("face_image_content_base64", toBase64(facepic))
.set("picture_image_content_base64", toBase64(backgroudpic))
.set("timestamp", toTimeStamp(passtime)).toString();
logger.info(body);
baseResponse = httpUtils.flowImg(body);
} else {
j++;
}
}
if (j == cameras.size()) {
baseResponse = new BaseResponse(ResponseEnum.NO_MATCH_DEVICEID);
}
return baseResponse;
}
private String toBase64(MultipartFile someone) {
return Base64.encode(tarnsToFile(someone));
}
private long toTimeStamp(String time) {
DateTime parse = DateUtil.parse(time);
return parse.getTime()/1000;
}
/*
* MultipartFile -> File
* */
private File tarnsToFile(MultipartFile multipartFile){
File file = null;
try {
file=File.createTempFile("tmp", null);
multipartFile.transferTo(file);
file.deleteOnExit();
} catch (HttpException | IOException e) {
e.printStackTrace();
}
return file;
}
}
插个话,最近在看本书,《会说话的代码》,讲的是代码的规范方面,讲到注释只是辅助工具,好的代码不需要注释。正在往这方面努力
下面是util服务
/**
* Created by DQ on 2020/6/23/023.
*/
@Service
public class HttpUtils {
private static Logger logger = LoggerFactory.getLogger(HttpUtils.class);
private static String FP_IP = getProps("fp.ip");
private static String FP_PORT = getProps("fp.port");
private static String loginName = getProps("fp.loginName");
private static String loginPwd = getProps("fp.loginPwd");
private static String loginUrl = getProps("fp.loginUrl");
private static String flowUrl = getProps("mts.flowUrl");
private static String asyncUrl = getProps("fp.asyncUrl");
private static String cameraUrl = getProps("fp.cameraUrl");
private static String cameraCache = getProps("cameraList.cache");
private static String name = getProps("camera-device.name");
public static Cache<String, String> lruCache = CacheUtil.newLRUCache(3);
public static Cache<String, ArrayList<Camera>> listCache = CacheUtil.newLRUCache(2);
/*
* 入库接口
* POST
* */
public BaseResponse flowImg(String body) {
BaseResponse baseResponse = new BaseResponse();
String fpUrl = formatFpUrl(flowUrl, FP_IP, "21100");
HttpRequest request = HttpRequest.post(fpUrl)
.body(body)
.timeout(10000);
HttpResponse execute = request.execute();
JSONObject obj = JSONUtil.parseObj(execute.body());
Integer rtn = obj.getInt("rtn");
if (rtn != 0) {
baseResponse.setErrcode(rtn);
baseResponse.setErrmsg("入库失败");
}
return baseResponse;
}
/*
* 登录接口
* POST
* */
public void loginFp() {
if (StrUtil.isBlank(FP_IP) || StrUtil.isBlank(FP_PORT) || StrUtil.isBlank(loginName)
|| StrUtil.isBlank(loginPwd) || StrUtil.isBlank(loginUrl) || StrUtil.isBlank(flowUrl)) {
logger.error("有配置为空!");
}
if(StrUtil.isBlank(lruCache.get("cookie")) || StrUtil.isBlank(lruCache.get("cluster_id"))){
String fpUrl = formatFpUrl(loginUrl, FP_IP, FP_PORT);
String password = DigestUtil.md5Hex(loginPwd);
HttpRequest req = HttpRequest.post(fpUrl)
.header("Content-Type", "application/json;charset=UTF-8")
.body(JSONUtil.createObj()
.set("name", loginName)
.set("password", password)
.toString())
.timeout(5000);
HttpResponse execute = req.execute();
JSONObject resp = JSONUtil.parseObj(execute.body());
if (resp.getInt("rtn") == 0) {
String session_id = resp.getStr("session_id");
String cluster_id = session_id.substring(session_id.indexOf("@") + 1);
String cookie = "session_id=" + session_id;
//默认保存3分钟
lruCache.put("cookie", cookie, 60 * 3 * 1000L);
lruCache.put("cluster_id", cluster_id, 60 * 3 * 1000L);
}
}
}
/*
* post获取result_id,并get获取到cameras
* */
public ArrayList<Camera> getCameras() throws InterruptedException {
if(!listCache.containsKey("camera") || listCache.get("camera").isEmpty()){
String fpUrl = formatFpUrl(asyncUrl, FP_IP, FP_PORT);
ArrayList<Camera> list = new ArrayList<>();
String cookie = lruCache.get("cookie");
String cluster_id = lruCache.get("cluster_id");
String resultId = postResultId(cookie, cluster_id, fpUrl);
String url = fpUrl + "?result_id=" + resultId;
String camerabody = getCamera(url, cookie);
JSONObject parseObj = JSONUtil.parseObj(camerabody);
JSONArray cluster_results = parseObj.getJSONArray("cluster_results");
if (!cluster_results.isEmpty()) {
JSONObject results = cluster_results.getJSONObject(0).getJSONObject("results");
JSONArray cameras = results.getJSONArray("cameras");
for (int i = 0; i < cameras.size(); i++) {
Camera camera = new Camera();
String POLICY_CODE;
JSONObject camerai = cameras.getJSONObject(i);
Integer id = camerai.getInt("id");
if(camerai.containsKey(name)){
POLICY_CODE= camerai.getStr(name);
}else {
POLICY_CODE = camerai.getJSONObject("meta").getStr(name);
}
camera.setId(id);
camera.setPolice_unit(POLICY_CODE);
list.add(camera);
}
}
//默认缓存12小时
listCache.put("camera", list, 60 * 60 * 12 * 1000L);
return list;
}
logger.info(listCache.get("camera").toString());
return listCache.get("camera");
}
private String postResultId(String cookie, String cluster_id, String fpUrl) {
JSONObject request = JSONUtil.createObj()
.set("cluster_id", cluster_id)
.set("method", "GET")
.set("payload", JSONUtil.createObj()
.set("clusterId", cluster_id))
.set("url", "/website/face/camera-device");
JSONArray requests = JSONUtil.createArray().put(request);
String body = JSONUtil.createObj()
.set("requests", requests)
.toString();
System.out.println(body);
HttpRequest req = HttpRequest.post(fpUrl)
.header("Content-Type", "application/json;charset=UTF-8")
.header("Cookie", cookie)
.body(body)
.timeout(10000);
HttpResponse execute = req.execute();
JSONObject parseObj = JSONUtil.parseObj(execute.body());
return parseObj.getStr("result_id");
}
/*
* get平台获取Cameras
* */
private String getCamera(String url, String cookie) throws InterruptedException {
HttpRequest request = HttpRequest.get(url)
.header("Content-Type", "application/json;charset=UTF-8")
.header("Cookie", cookie)
.timeout(15000);
Thread.sleep(3000L);
return request.execute().body();
}
/*
* url处理类
* */
private static String formatFpUrl(String url, String ip, String port) {
if (StrUtil.isNotBlank(url)) {
url = url.replace("[IP]", ip);
url = url.replace("[PORT]", port);
}
return url;
}
/*
* 读取配置类
* */
private static String getProps(String key){
String path = System.getProperty("user.dir") + System.getProperty("file.separator") + "config.properties";
if(!new File(path).exists()){
logger.error("文件config.properties未找到!");
return null;
}
return new Props(path).getProperty(key);
}
}
在utils里有很多需要注意的地方,很多可能存在的异常,我并没有管,也没有trycatch,我觉得开发环境就是要大胆暴漏这些exceptions,好好测试,大不了我们下面全局处理一下嘛。。
/**
* Created by DQ on 2020/6/23/023.
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 系统异常捕获处理
*/
@ExceptionHandler(Exception.class)
// @ResponseBody
public BaseResponse exception(Exception e) {
logger.error(e.getMessage(), e.getCause());
//请求参数问题
if (e instanceof HttpMessageNotReadableException) {
return new BaseResponse(ResponseEnum.BAD_REQUEST);
}
//请求Content type不支持
if (e instanceof HttpMediaTypeNotSupportedException) {
return new BaseResponse(ResponseEnum.MEDIA_TYPE_NOT_SUPPORTED);
}
//请求方法不支持
if (e instanceof HttpRequestMethodNotSupportedException) {
return new BaseResponse(ResponseEnum.METHOD_NOT_SUPPORTED);
}
return new BaseResponse(ResponseEnum.SERVER_ERROR);
}
}
好了,非常简单处理掉MultipartFile与File的转换,调用接口还是需要多练习,最近准备学习一下spring自带的restTemplent,听说这货集成了JDK 自带的 HttpURLConnection,Apache 的 HttpClient和OKHttp3,还是蛮厉害的~~
不是hutool的http请求库不好用,就是害怕高并发和大数据情况下不保险啊。。
我看公司项目里也有人用hutool,但是httpclient还是稳啊