项目实训12
1. 背景
由于本项目算法部分为使用python编写,所以需要在springboot中调用外部的python程序进行计算本项目算法评分模块。
2. 实现
业务流程:前端从后端获取推荐的图片,前端用户在进行描绘之后上传描绘后的灰度图片至后端,后端存储图片并调用python算法模块对该图片进行评分,并将评分返回给前端,并更新后端的中心图片。
由于项目本地开发和部署之后的服务器环境的配置和图片保存的地方也不相同,所以使用被两个property文件进行配置
# 本地
python.command=/Users/nyq/anaconda3/envs/pytorch/bin/python
python.code = /Users/nyq/Desktop/algorythm/code.py
# 服务器
python.command=/root/anaconda3/envs/guke/bin/python
python.code = /usr/local/src/spring/GuKe/algorythm/code.py
前端获取后端推荐图片代码:
@UserLoginToken
@PostMapping("/getRecommend")
public Result getRecommend(HttpServletRequest request)
{
String openid = openidUtils.getOpenidFromRequest(request);
try
{
Source source = sourceService.getRecommend(openid);
Map<String, Object> map = new HashMap<>();
map.put("source", source);
return ResultFactory.success().data(map);
} catch (Exception e)
{
throw new CustomException(exceptionUtils.getErrorInfoFromException(e) + "获取推荐图片失败");
}
}
这边调用了service中的方法,被推荐的图片共有1145张,所以需要在后端设置一个图片推荐的调度算法,本项目采用了一个简单的调用算法,雨露均沾式,哪些图片被描绘的少就推荐哪些,同时必须是用户没有描绘过的图片。
@Override
public Source getRecommend(String openid)
{
// 获取待推荐的图片,描绘次数少的图片在前面
List<Source> sourceList = sourceMapper.getAll();
// for(int i = 0; i < 10; i++){
// System.out.println(sourceList.get(i).getImageUrl() + " " +sourceList.get(i).getImageUrl());
// }
// 获取用户已上传的图片列表
List<Integer> upList = uploadMapper.getUploadByOpenid(openid);
// System.out.println("upList" + upList);
// 获取推荐图片的id
int count = 0;
boolean found = false;
while (!found)
{
// 如果用户没有描绘过该图片
if (!upList.contains(sourceList.get(count).getId()))
{
found = true;
break;
}
count++;
}
// 根据id获取源图片信息
return sourceList.get(count);
}
后端接口中,返回的图片地址为在线地址,在服务器端采用的是nginx静态资源存储。
存储完图片后调用服务器端的python程序:
部分代码如下:
@UserLoginToken
@ResponseBody
@PostMapping("/upload")
public Result uploadPicture(@RequestParam("file") MultipartFile file,
int id,
HttpServletRequest request) throws IOException, InterruptedException
{
if (file == null)
throw new CustomException("图片文件为空");
String openid = openidUtils.getOpenidFromRequest(request);
// 根据源图片类型创建响应的文件夹
String path = base_path;
path += Integer.toString(id);
path += "/";
//将文件保存到服务器指定位置
try
{
//获取文件在服务器的储存位置
File filePath = new File(path);
log.debug("文件的保存路径" + path);
if (!filePath.exists() && !filePath.isDirectory())
{
log.debug("目录不存在,创建目录" + filePath);
filePath.mkdir();
}
Upload uploadImage = new Upload();
//获取原始文件名称(包括格式)
String originalFileName = file.getOriginalFilename();
//获取文件类型,以最后一个‘.’为标识
String type = originalFileName.substring(originalFileName.lastIndexOf(".") + 1);
// 以用户的openid对图片进行命名
String name = openid;
// 设置文件新名称:用户的openid
Date d = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String date = sdf.format(d);
String fileName = date + name + "." + type;
// 在指定路径下创建文件
File targetFile = new File(path, fileName);
file.transferTo(targetFile);
System.out.println("上传成功");
// 保存upload的信息到数据库
uploadImage.setName(id);
uploadImage.setAddress(path + fileName);
uploadImage.setTime(d);
uploadImage.setOpenid(openid);
// TODO: 这里还有一个需要做的工作是调用python脚本获取评分 (根据图片的本地地址) 最后将评分返回给前端,也得保存至数据库
// 由于获取到的数据为string类型,所以这边先转为double
// 然后得分 *100
// 然后再转化为整数
String result = this.getScore(path + fileName, id);
double res = Double.parseDouble(result) * 100;
int score = (int) res;
uploadImage.setScore(score);
// 更新source表相应图片的描绘次数
sourceService.updateCountById(id);
// 更新user表的总评分
userService.updateUserScore(uploadImage.getScore(), openid);
// 插入upload表信息
uploadService.insertUpload(uploadImage);
Map<String, Object> map = new HashMap<>();
map.put("score", score);
return ResultFactory.success().message("上传成功").data(map);
} catch (Exception e)
{
// System.out.println("上传失败");
// result.put("code",400);
log.error("保存图片失败");
e.printStackTrace();
return ResultFactory.error().message("上传失败");
}
}
String getScore(String file, int id) throws IOException, InterruptedException
{
String result = "";
try
{
//这个方法是类似隐形开启了命令执行器,输入指令执行python脚本
Process process = Runtime.getRuntime()
.exec(command + " " + code + " " + file + " " + id);
// 这种方式获取返回值的方式是需要用python打印输出,然后java去获取命令行的输出,在java返回
System.out.println(command + " " + code + " " + file + " " + id);
InputStreamReader ir = new InputStreamReader(process.getInputStream());
LineNumberReader input = new LineNumberReader(ir);
result = input.readLine();//中文的话这里可能会有乱码,可以尝试转一下不过问题不大
// result1 = new String(result.getBytes("iso8859-1"),"utf-8");
input.close();
ir.close();
int re = process.waitFor();
System.out.println("以下为结果");
System.out.println(result);
} catch (IOException | InterruptedException e)
{
System.out.println("调用python脚本并读取结果时出错:" + e.getMessage());
}
return result;
}
这边利用的是exec调用来执行外部程序【开启了命令执行器】,并使用输入输出流读取python程序打印到标准输出流的输出,然后再输入到java的变量中进行评分的获取。
到此,便完成了外部程序的调用。