微服务应用使用分布式方式进行部署,并且有可能随时随地部署多个副本,所以必须有一个独立的文件系统来管理用户上传和使用的资源文件,包括图片和视频等。
在模块goods-web 的设计中,我们是使用FastDFS这个轻量级的分布式文件系统来设计的。如果使用云服务商提供的对象存储服务来设计,如OSS服务等,则可以参照服务提供商的使用说明,并结合本实例进行设计。
下面针对库存管理中,商品创建和编辑时使用的图片,实现在FastDFS上进行存储和管理的设计。
有关FastDFS的安装、集群构建和相关配置等,将在运维部署部分的相关章节中进行介绍。
分布式文件系统客户端开发
FastDFS 提供了Java语言使用的客户端开发包,但在Spring Boot中使用时还需要进行二次开发。为了简化开发过程,我们使用tobato在GitHub上开源的一个专为Spring Boot开发者提供的封装。
首先,在goods-web模块中,增加如下依赖引用:
<dependency>
<groupId>com.github.tobato</groupId><artifactId>fastdfs-client</artifactId><version>1.26.4-RELEASE</version>
</dependency>
然后,在模块的配置文件application.yml 中增加如下配置:
fdfs:
soTimeout: 1501
connectTimeout:601thumbImage:
width: 150height: 150trackerList:
- 192.168.1.214:22122-192.168.1.215:22122spring.jmx.enabled: false
file.path.head:http://192.168.1.214:8080/
这个配置假设 FastDFS 的TrackerServer安装了两台服务器,它们的P地址分别为“192.168.1.214”和“192.168.1.215”,并且可以通过链接“http:/192.168.1.214:8080/”使用文件。
接着,在工程的启动文件中增加注解@Import和@EnableMBeanExport,即导入fastdfs-client的相关配置,代码如下所示:
@SpringBootApplication@EnableDiscoveryClient
@EnableFeignClients (basePackages = "com.demo")@componentScan(basePackages = "com.demo")
R Import(FdfsClientConfig.class)
@EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)public class GoodswebApplication {
public static void main(String[]args) {
SpringApplication.run (GoodsWebApplication.class, args);
}
}
为了确认上面的引用和配置都已经准备就绪,可以启动应用验证一下。如果启动应用正常,则说明上面的配置是正确的。
现在,我们就可以创建一个“FastefsClient”实现文件的上传功能了,代码如下所示:
@service
public class FastefsClient {
@Autowired
protected FastFileStorageClient storageClient;
public String uploFile (MultipartFile file){
String fileType=
FilenameUtils.getExtension(file.get0riginalFilename ()).toLowerCase();
StorePath path =null;
try {
path = storageclient.uploadFile(file.getInputstream(),
file.getsize(, fileType, nul1);
Jcatch (IOException e){
e.printstackTrace();
}
if(path !=null) {
return path.getFu1lPath(;}else {
return null;
}
public string uploile(Inputstream inputstream,Long size, String type)(
StorePath path = null;
try {
path = storageclient.uploadFile(inputStream, size, type, null);}catch(Exception e){
e.printStackTrace();
}
if(path!=null) {
return path. getFullPath();}else i
return null;
public boolean deleteFile(string fullPath)
try {
storageClient.deleteFile(fullPath);return true;
}catch(Exception e){
e-printstackTrace(;
}
return false;
}
}
这里,设计了一个多态的uploFile方法,可以使用不同的参数通过调用FastFileStorageClient实现文件上传,同时设计了一个deleteFile方法,实现文件的删除操作。
商品图片上传设计
商品图片上传步骤如下。
首先,设计一个控制器PicUtilController;然后,在这个控制器中实现文件上传的功能,代码如下所示:
@Controller
@RequestMapping("/pic")
public class PicUtilController {
@value("${file.path.head:http://192.168.1.214:84/}")private String pathHead;
GAutowired
private FastefsClient fastefsClient;
//可缩放图片上传
CRequestMapping ("/upload")public String upload() {
return "pic/upload-pic";
}
/*★
*上传图片* @return大/
@RequestMapping(value = "/uploadPic", method =RequestMethod. POST)
public void uploadPic(@RequestParam ("pictureFile") MultipartFile
multipartFile,HttpServletRequest request,HttpServletResponse response)
try{
String filename = fastefsclient.uploFile (multipartFile);Long shopid =1L;
AsyncThreadPool.getInstance().execute(new Runnable()(
Goverride
public void run(){
try{
savePic(multipartFile, filename ,shopid);]catch (Exception e){
e.printStackTrace();
});
BufferedImage image = ImageIO.read(multipartFile.getInputStream());
Map<String,0bject> data = new HashMap<String,0bject>();
data.put("pathInfo", pathHead+filename);
data.put("width", image.getWidth());
data.put ("height", image.getHeight());
ObjectMapper mapper = new ObjectMapper();
String ret = mapper.writeValueAsString(data);
response.setContentType("text/html; charset=utf8");response.getOutputStream().write(ret. getBytes());response.flushBuffer();
}catch (IOException e){
e.printStackTrace();
}
}
...
}
这个控制器设计了一个链接“/upload”,用来打开上传文件的操作界面。另外,另一个链接“/uploadPic”通过调用前面设计的文件客户端“FastefsClient”实现文件上传。上传后再将图片的路径和文件大小等信息返回给调用者。
在上面的代码中,文件上传的操作界面在视图设计“upload-pic.html”(源代码文件代码行30~33行)中实现,主要使用一个“input”控件从操作者的机器上选取文件进行上传,代码如下所示:
<div class="upload-box">
//点击上传
<input id="pictureFile" name="pictureFile" type="file" class="file"onchange="uploadPic submit (this)"/>
</div>
通过“upload-page.,js”(源代码文件代码行432~437行)设计一个文件上传的请求方法,即可在视图界面上调用上面的控制器设计中上传文件的链接“/uploadPic”了。调用成功后再取出文件信息,代码如下所示:
//上传图片
function ajaxFileUpload(id){
var url = '/pic/uploadPic';$.ajaxFileUpload({
url :url,//需要链接到服务器地址
fileElementId : id, //文件选择框的id属性
dataType : 'json',//服务器返回的格式,可以是jsonsuccess:function(data){
if(data.errorMsg){
showMsg(data.errorMsg,"错误");}else{
page.upload.finish(data.pathInfo, data.width, data.height);
}
}
}};
}
通过上述方法获取到文件信息之后,再通过“upload-pic.html”(源代码文件代码行53~80行)视图展现出来,这部分的设计代码如下所示:
<div class="img-select">
<div class="up-tit">选择图片后可以在下框中调整您所需的部分。</div><div class=" operateBox">
<span>上传图片后预览</span>
<div class=" operate" id="operate">
<img/>
<div class="isee">
<div class="b top"></div>
<div class="b_right"></div><div class="bbottom" ></div><div class="b left"></div>
<div class="handle h_top_left"></div><div class="handle h top"></div>
<div class="handle h_top right"></div><div class="handle h_right"></div>
<div class="handle hbottom right"></div><div class="handle h_bottom"></div>
<div class="handle h bottom_left"></div><div class="handle h_left"></div>
<div class="s_c"></div>
</div>
<div class="o_bg bg_top"></div><div class="o_bg bg_right"></div><div class="o_bg bg_bottom"></div><div class="o_bg bg_left"></div></div>
</div></div>
上面的设计完成后,最后显示的效果如图7-5所示。
其中,在进行图片选取时,还可以对图片进行裁剪,有关这部分的功能请查看项目的源代码。
注意,在进行上面的整个调试时,必须保证有分布式文件系统服务可以访问。
富文本编辑器上传图片设计
在库存管理中,对商品内容的编辑建议使用富文本编辑器,这样可以编辑出图文并茂的内容。使用富文本编辑器上传图片的原理与7.7.2节中的图片上传的设计基本相同。
这里以使用开源的ueditor富文本编辑器为例进行说明。
在控制器PicUtilController的设计中,使用如下所示的uploadimg方法设计:
@controller
@RequestMapping("/pic")
public class PicUtilController {
evalue("${file.path.head:http://192.168.1.214:84/}")private String pathHead;
@Autowired
private FastefsClient fastefsClient;
//ueditor 图片上传
@RequestMapping (value = "/uploadimg", method= RequestMethod. POST,produces="text /html; charset=UTE-8")
public void uploadimg(CRequestParam ( "upfile") MultipartFile upfile,HttpServletRequest request,HttpServletResponse response){
try {
String filename= fastefsclient. uploFile(upfile);
Long shopid =1L;
AsyncThreadPool.getInstance() .execute(new Runnable(){
coverride .
public void run() {
try {
savePic(upfile,filename , shopid);}catch(Exception e){
e-printStackTrace();
}
}
}};
Map<String,0bject> data = new HashMap<String,0bject>();data.put ("original",upfile.getOriginalFilename());
data.put("url",pathHead+filename);
data.put("title","");
data.put ("state", "sUCCESS");
ObjectMapper mapper =new 0bjectMapper();
String ret = mapper.writevalueAsString (data);
response.setContentType ("text/html; charset=utf8");response.getOutputStream().write(ret.getBytes());response.flushBuffer(;
}catch (Exception e){
e.printStackTrace();
}
}
}
与商品图片上传设计不同的地方是返回参数有点不一样,主要是根据ueditor插件的需要进行调整和设定。
在新增商品new.html和编辑商品edit.html的页面上增加如下所示代码,引用ueditor插件:
<script type="text/javascript" charset="utf-8">
window.UEDITOR_HOME_ URL = "/ueditor/";</script>
<script type="text/javascript" charset="utf-8"th:src="@{/ueditor/editor_config.js} "></script>
<script type="text/javascript" charset="utf-8"th:src="@{/ueditor/editor_all.js} "></script>
<script type="text/javascript">
var ME=UE .getEditor('contents',
{
wordCount:false,
initialFramewidth: 406,maximumwords:50000,
wordoverFlOwMsg:'最多输入50000个字符,toolbars:[
[ ' fullscreen', 'undo', 'redo', 'l','bold','underline',
'strikethrough', 'superscript', 'subscript', 'removeformat',
'formatmatch', 'autotypeset', 'I', 'forecolor',
'backcolor', 'cleardoc', 'T','rowspacingtop', 'rowspacingbottom ' ,
' lineheight','I','justifyleft', 'justifycenter',
'justifyright', 'justifyjustify','I', 'imagenone', 'imageleft',
' imageright', 'imagecenter','l','customstyle',
'paragraph', 'fontsize','l', 'emotion', 'date', 'time', 'spechars',
'L','searchreplace', 'insertimage', 'wordimage',
'link ']
]});
</script>
最后,必须在ueditor 插件的配置文件“editor_config.js”(源代码文件代码行38~41行)中,更改如下所示的几行配置:
//图片上传配置区
, imageUrl: " /pic/uploadimg"//图片上传提交地址
, imagePath:""//图片修正地址,引用了fixedImagePath,如有特殊需求,可自行配置
,imageFieldName: "upfile"//图片数据的 key,若此处修改,需要在后台对应文件修改对应参数
这样,就可以使用富文本编辑器上传图片文件了。
设计完成后,显示的效果如图7-6所示。
建立本地文件信息库
当一个文件上传之后,为了方便以后可以继续使用这个文件,我们可以在本地建立一个文件信息库,用来保存一个文件的简要信息。实现方法如下。
首先,有关数据服务的设计部分在模块goods-restapi 中。本地信息的图片数据对象:
@Table(name = "t picture")@Data
public class Picture{
@Id
@Generatedvalue(strategy =GenerationType.IDENTITY)private Long id;//图片编号
@column(name = "path info")
private String pathInfo;//文件路径Ccolumn(name = "file name")
private String fileName;//文件名private int width;//宽度
private int height;//高度private String flag;//标志
@DateTimeFormat (pattern = "yyyy-MM-dd HH:mm:ss")
ecolumn (name = "created",columnDefinition = "timestamp defaultcurrent_timestamp")
@Temporal (TemporalType.TIMESTAMP)private Date created;//创建时间private Long merchantid;//商家编号
}
对应的数据库表结构t _picture的定义:
CREATE TABLE 'tpicture'(
'id' bigint (20) NOT NULL AUTO_INCREMENT,
'created'timestamp NOT NULL DEFAULT CURRENT TIMESTAMP,'file_name' varchar(255)COLLATE utf8 bin DEFAULT NULL,'flag'varchar(255)COLLATE utf8 bin DEFAULT NULL,
'height' int(11) DEFAULT NULL,
'merchantid' bigint (20) DEFAULT NULL,
'path info' varchar(255)COLLATE utf8_bin DEEAULT NULL,'width' int(11) DEFAULT NULL,
PRIMARY KEY ('id')
ENGINE=InnoDB AUTO INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
有关本地图片信息库的数据服务部分的实现,可以参照商品数据服务的设计方法,不再赘述。成功上传文件之后,如何将文件的路径和简要信息保存到本地文件信息库中呢?
文件信息保存在控制器PicUtilController 的savePic方法中,代码如下所示:
@controller
@RequestMapping("/pic")
public class PicUtilController {
Value("${file.path.head:http://192.168.1.214:84/}")private string pathHead;
CAutowired
private PictureRestService pictureRestService;
private String savePic(MultipartFile multipartFile,String filename,Longshopid) throws Exception{
BufferedImage image = ImageI0.read (multipartFile.getInputStream ());
PictureQo picture= new PictureQ0();
picture.setFileName (filename);
picture.setHeight (image. getHeight ());picture.setWidth(image.getwidth());picture.setPathInfo(pathHead);
picture.setMerchantid(shopid);
return pictureRestService.create(picture);
}
}
在使用了本地文件信息库之后,在库存管理中创建和编辑商品时就可以使用已经上传的文件了。
为了能够更好地使用本地文件信息库,我们需要在控制器PicUtilController中创建一个分页查询本地文件信息库的方法 listPic,代码如下所示:
@controller
@RequestMapping("/pic")
public class PicUtilController{
@value("${file.path.head:http://192.168.1.214:84/}")private String pathHead;
@Autowired
private PictureRestService pictureRestService;
@RequestMapping(value = "/listPic",method = RequestMethod.POST)
@ResponseBody
public Page<Map<String,0bject>>listPic(PictureQo pictureQo){
String json = pictureRestService.findPage (pictureQo);
Gson gson = TreeMapConvert.getGson();
TreeMap<String, 0bject> page = gson.fromJson(json,new TypeToken<
TreeMap<String, Object>>(){}.getType());
Pageable pageable = PageRequest.of(pictureQo.getPage(),
pictureQo.getSize( , null);
java.util.List<PictureQ0> list = new ArrayList<>();
if(page != null && page.get ("list") !=null) {
list = gson.fromJson (page.get ("list").toString(), new
TypeToken<List<PictureQ0>>() {
}.getType());
String count - page.get( "total").toString();
return new PageImpl(list, pageable,new Long ( count));
}
}
其中,控制器使用链接“/listPic”为页面设计提供调用方法,返回本地文件信息的分页列表。
在页面上使用脚本定义upload-page.,js(源代码文件代码行501~525行),实现文件信息库分页查询结果的处理方法,代码如下所示:
function getDataHtml (pageNo, pagesize) {
$.ajax(i
url: "/pic/listPic",dataType: "json",type: "POST",
cache: false,
data: {page: pageNo-1,size: pagesize ll 8},success:function (data){
var $list =$('#upload-list').empty();$.each (data.content, function (i, v) {
var html ="";
html +='<div class="upload-item">'+
'<div class="img"><img src="'+ v.pathInfo + v.fileName +
"/></div>'+
'<p>'+v.width+'x'+ v.height+'</p>'+'<div class="selected"></div>'+
'</div>';
$list.append($ (html));
})
page.photos.setPosition(;
document.getElementById('pagebar').innerHTML=
PageBarNumList.getPageBar (data.number+1, data.totalPages,3,'getDataHtml',pagesize ll8,true);
},
errOr: function (e) {}});
}
设计完成之后,展示的效果如图7-7所示。
在库存管理项目整体开发完成之后,可以进行一个总体测试。这个测试需要调用类目管理的微服务接口,所以在进行测试时,可按下列顺序启动各个模块。
(1)启动类目接口服务:catalog-restapi模块。
(2)启动库存管理微服务API应用:goods-restapi模块。
(3)启动库存管理PC端Web应用:goods-web模块。
上面几个模块启动成功之后,可在浏览器打开如下链接地址:
http://localhost:8092
如果各个模块都能正常运行,则可以在库存管理首页中显示已有的商品列表,如图7-8所示。
在这个页面中,我们可以新增或者编辑商品。编辑商品的操作界面如图7-9所示。
本章介绍了库存管理的微服务接口和一个相关的Web应用微服务的开发。在这个项目的开发过程中,我们使用了半自动的数据库开发框架MyBatis,体验了与使用JPA不同的开发实践。在生产应用中,读者可以根据实际情况选择使用。
同时,本章的Web应用开发也演示了使用分布式文件系统的方法,不管是使用DFS,还是使用OSS,其设计思路和实现方法基本一致,所以我们只需掌握一种开发方法,就能够在实际应用中应用自如。
SpringCloud微服务架构实战:库存管理与分布式文件系统,使用分布式文件系统DFS、总体测试
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。