|
|
|
@ -1,17 +1,31 @@ |
|
|
|
package com.ruoyi.system.controller; |
|
|
|
package com.ruoyi.web.controller.upload; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.io.File; |
|
|
|
|
|
|
|
import java.io.IOException; |
|
|
|
|
|
|
|
import java.util.Date; |
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
|
|
|
|
import java.util.concurrent.ExecutorService; |
|
|
|
|
|
|
|
import java.util.concurrent.Executors; |
|
|
|
|
|
|
|
import java.util.stream.Collectors; |
|
|
|
import javax.servlet.http.HttpServletResponse; |
|
|
|
import javax.servlet.http.HttpServletResponse; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.date.DateUtil; |
|
|
|
|
|
|
|
import cn.hutool.core.io.FileUtil; |
|
|
|
|
|
|
|
import cn.hutool.core.util.StrUtil; |
|
|
|
|
|
|
|
import com.alibaba.fastjson.JSONObject; |
|
|
|
|
|
|
|
import com.aliyun.oss.OSS; |
|
|
|
|
|
|
|
import com.github.pagehelper.PageHelper; |
|
|
|
|
|
|
|
import com.ruoyi.framework.web.ilisteners.PutProcessData; |
|
|
|
|
|
|
|
import com.ruoyi.framework.web.service.AliOSSService; |
|
|
|
|
|
|
|
import com.ruoyi.system.domain.BVideos; |
|
|
|
|
|
|
|
import com.ruoyi.system.service.IBVideosService; |
|
|
|
|
|
|
|
import com.ruoyi.web.controller.mapper.FileDao; |
|
|
|
|
|
|
|
import com.ruoyi.web.controller.mapper.VideoDao; |
|
|
|
|
|
|
|
import com.ruoyi.web.serveice.UploadService; |
|
|
|
import org.springframework.security.access.prepost.PreAuthorize; |
|
|
|
import org.springframework.security.access.prepost.PreAuthorize; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.web.bind.annotation.GetMapping; |
|
|
|
import org.springframework.util.MultiValueMap; |
|
|
|
import org.springframework.web.bind.annotation.PostMapping; |
|
|
|
import org.springframework.web.bind.annotation.*; |
|
|
|
import org.springframework.web.bind.annotation.PutMapping; |
|
|
|
|
|
|
|
import org.springframework.web.bind.annotation.DeleteMapping; |
|
|
|
|
|
|
|
import org.springframework.web.bind.annotation.PathVariable; |
|
|
|
|
|
|
|
import org.springframework.web.bind.annotation.RequestBody; |
|
|
|
|
|
|
|
import org.springframework.web.bind.annotation.RequestMapping; |
|
|
|
|
|
|
|
import org.springframework.web.bind.annotation.RestController; |
|
|
|
|
|
|
|
import com.ruoyi.common.annotation.Log; |
|
|
|
import com.ruoyi.common.annotation.Log; |
|
|
|
import com.ruoyi.common.core.controller.BaseController; |
|
|
|
import com.ruoyi.common.core.controller.BaseController; |
|
|
|
import com.ruoyi.common.core.domain.AjaxResult; |
|
|
|
import com.ruoyi.common.core.domain.AjaxResult; |
|
|
|
@ -20,6 +34,7 @@ import com.ruoyi.system.domain.BFile; |
|
|
|
import com.ruoyi.system.service.IBFileService; |
|
|
|
import com.ruoyi.system.service.IBFileService; |
|
|
|
import com.ruoyi.common.utils.poi.ExcelUtil; |
|
|
|
import com.ruoyi.common.utils.poi.ExcelUtil; |
|
|
|
import com.ruoyi.common.core.page.TableDataInfo; |
|
|
|
import com.ruoyi.common.core.page.TableDataInfo; |
|
|
|
|
|
|
|
import org.springframework.web.multipart.MultipartFile; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 【请填写功能名称】Controller |
|
|
|
* 【请填写功能名称】Controller |
|
|
|
@ -28,56 +43,218 @@ import com.ruoyi.common.core.page.TableDataInfo; |
|
|
|
* @date 2022-03-09 |
|
|
|
* @date 2022-03-09 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@RestController |
|
|
|
@RestController |
|
|
|
@RequestMapping("/system/file") |
|
|
|
@RequestMapping("/videos/file") |
|
|
|
public class BFileController extends BaseController |
|
|
|
public class BFileController extends BaseController { |
|
|
|
{ |
|
|
|
|
|
|
|
@Autowired |
|
|
|
@Autowired |
|
|
|
private IBFileService bFileService; |
|
|
|
private IBFileService bFileService; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
private IBVideosService videosService; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
private AliOSSService ossService; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
private VideoDao videoDao; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
private FileDao fileDao; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired |
|
|
|
|
|
|
|
private UploadService uploadService; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 查询【请填写功能名称】列表 |
|
|
|
* 查询【请填写功能名称】列表 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:list')") |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:list')") |
|
|
|
@GetMapping("/list") |
|
|
|
@GetMapping("/list") |
|
|
|
public TableDataInfo list(BFile bFile) |
|
|
|
public TableDataInfo list(BFile bFile) { |
|
|
|
{ |
|
|
|
|
|
|
|
startPage(); |
|
|
|
startPage(); |
|
|
|
|
|
|
|
PageHelper.orderBy("id desc"); |
|
|
|
List<BFile> list = bFileService.selectBFileList(bFile); |
|
|
|
List<BFile> list = bFileService.selectBFileList(bFile); |
|
|
|
return getDataTable(list); |
|
|
|
return getDataTable(list); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 导出【请填写功能名称】列表 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:export')") |
|
|
|
|
|
|
|
@Log(title = "【请填写功能名称】", businessType = BusinessType.EXPORT) |
|
|
|
|
|
|
|
@PostMapping("/export") |
|
|
|
|
|
|
|
public void export(HttpServletResponse response, BFile bFile) |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
List<BFile> list = bFileService.selectBFileList(bFile); |
|
|
|
|
|
|
|
ExcelUtil<BFile> util = new ExcelUtil<BFile>(BFile.class); |
|
|
|
|
|
|
|
util.exportExcel(response, list, "【请填写功能名称】数据"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 获取【请填写功能名称】详细信息 |
|
|
|
* 获取【请填写功能名称】详细信息 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:query')") |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:query')") |
|
|
|
@GetMapping(value = "/{id}") |
|
|
|
@GetMapping(value = "/{id}") |
|
|
|
public AjaxResult getInfo(@PathVariable("id") Long id) |
|
|
|
public AjaxResult getInfo(@PathVariable("id") Long id) { |
|
|
|
{ |
|
|
|
|
|
|
|
return AjaxResult.success(bFileService.selectBFileById(id)); |
|
|
|
return AjaxResult.success(bFileService.selectBFileById(id)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 新增【请填写功能名称】 |
|
|
|
|
|
|
|
*//*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:add')") |
|
|
|
|
|
|
|
@Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT) |
|
|
|
|
|
|
|
@PostMapping("/addFile") |
|
|
|
|
|
|
|
public AjaxResult addFile(@RequestParam("file") MultipartFile multipartFile, @RequestParam("remark") String remark, @RequestParam("videoName") String videoName) { |
|
|
|
|
|
|
|
BFile bFile = new BFile(); |
|
|
|
|
|
|
|
OSS client = ossService.newOss(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Date now = new Date(); |
|
|
|
|
|
|
|
String filename = now.getTime() + "." + FileUtil.getSuffix(multipartFile.getOriginalFilename()); |
|
|
|
|
|
|
|
String suffix = "/" + filename; |
|
|
|
|
|
|
|
String path = ossService.getPath(ossService.getPrefix(), suffix); |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//System.out.println(ossService.getDomain() + "/" + path);
|
|
|
|
|
|
|
|
bFile.setGmtCreate(now); |
|
|
|
|
|
|
|
bFile.setVideoName(videoName); |
|
|
|
|
|
|
|
bFile.setPercent("0%"); |
|
|
|
|
|
|
|
bFile.setMemo(remark); |
|
|
|
|
|
|
|
bFile.setUuid(path); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
|
|
return AjaxResult.error(e.getMessage()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (bFileService.insertBFile(bFile) > 0) { |
|
|
|
|
|
|
|
File f = null; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
f = File.createTempFile("tmp", null); |
|
|
|
|
|
|
|
multipartFile.transferTo(f); |
|
|
|
|
|
|
|
PutProcessData processData = new PutProcessData(); |
|
|
|
|
|
|
|
processData.setId(filename); |
|
|
|
|
|
|
|
File finalF = f; |
|
|
|
|
|
|
|
executorService2.execute(new Runnable() { |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void run() { |
|
|
|
|
|
|
|
ossService.uploadSuffix(client, processData, filename, finalF, suffix); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
executorService.execute(new Runnable() { |
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void run() { |
|
|
|
|
|
|
|
BFile bFile = new BFile(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bFile.setUuid(path); |
|
|
|
|
|
|
|
List<BFile> bFileList = bFileService.selectBFileList(bFile); |
|
|
|
|
|
|
|
BFile findFile = bFileList != null && bFileList.size() > 0 ? bFileList.get(0) : null; |
|
|
|
|
|
|
|
if (findFile == null) { |
|
|
|
|
|
|
|
logger.info("findFile is null"); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
logger.info("upload start"); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//ossService.uploadSuffix(client, multipartFile.getBytes(), suffix);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (true) { |
|
|
|
|
|
|
|
logger.info("v1 upload path:{}, success:{}, fail:{}", path, processData.isSuccess(), processData.isFail()); |
|
|
|
|
|
|
|
if (processData.isSuccess() || processData.isFail()) { |
|
|
|
|
|
|
|
if (processData.isSuccess()) { |
|
|
|
|
|
|
|
findFile.setPercent("100%"); |
|
|
|
|
|
|
|
findFile.setDone(1L); |
|
|
|
|
|
|
|
} else if (processData.isFail()) { |
|
|
|
|
|
|
|
findFile.setPercent("-1%"); |
|
|
|
|
|
|
|
findFile.setDone(2L); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bFileService.updateBFile(findFile); |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
findFile.setPercent(processData.getProcess() + "%"); |
|
|
|
|
|
|
|
bFileService.updateBFile(findFile); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Thread.sleep(1000); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
logger.info("upload success, url:{}", path); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
|
|
} finally { |
|
|
|
|
|
|
|
if (f != null) { |
|
|
|
|
|
|
|
f.delete(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return toAjax(1); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return toAjax(0); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 新增【请填写功能名称】 |
|
|
|
* 新增【请填写功能名称】 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:add')") |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:add')") |
|
|
|
@Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT) |
|
|
|
@Log(title = "【请填写功能名称】", businessType = BusinessType.INSERT) |
|
|
|
@PostMapping |
|
|
|
@PostMapping("/addFile") |
|
|
|
public AjaxResult add(@RequestBody BFile bFile) |
|
|
|
public AjaxResult addFile(@RequestParam("file") MultipartFile multipartFile, @RequestParam("remark") String remark, @RequestParam("videoName") String videoName) { |
|
|
|
{ |
|
|
|
BFile bFile = new BFile(); |
|
|
|
return toAjax(bFileService.insertBFile(bFile)); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Date now = new Date(); |
|
|
|
|
|
|
|
String filename = now.getTime() + "." + FileUtil.getSuffix(multipartFile.getOriginalFilename()); |
|
|
|
|
|
|
|
String suffix = "/" + filename; |
|
|
|
|
|
|
|
String path = ossService.getPath(ossService.getPrefix(), suffix); |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//System.out.println(ossService.getDomain() + "/" + path);
|
|
|
|
|
|
|
|
bFile.setGmtCreate(now); |
|
|
|
|
|
|
|
bFile.setVideoName(videoName); |
|
|
|
|
|
|
|
bFile.setPercent("0%"); |
|
|
|
|
|
|
|
bFile.setMemo(remark); |
|
|
|
|
|
|
|
bFile.setUuid(path); |
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
|
|
return AjaxResult.error(e.getMessage()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (bFileService.insertBFile(bFile) > 0) { |
|
|
|
|
|
|
|
File f = new File("/root/jars/tmp/"+filename); |
|
|
|
|
|
|
|
BFile findFile=null; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
if (!f.createNewFile()){ |
|
|
|
|
|
|
|
logger.info("create file fail"); |
|
|
|
|
|
|
|
return AjaxResult.error("创建文件失败"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
multipartFile.transferTo(f); |
|
|
|
|
|
|
|
BFile param = new BFile(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
param.setUuid(path); |
|
|
|
|
|
|
|
List<BFile> bFileList = bFileService.selectBFileList(param); |
|
|
|
|
|
|
|
findFile = bFileList != null && bFileList.size() > 0 ? bFileList.get(0) : null; |
|
|
|
|
|
|
|
if (findFile == null) { |
|
|
|
|
|
|
|
logger.info("findFile is null"); |
|
|
|
|
|
|
|
return AjaxResult.error("保存文件失败"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uploadService.uploadFile(findFile,filename,f,suffix); |
|
|
|
|
|
|
|
} catch (IOException e) { |
|
|
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return toAjax(1); |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return toAjax(0); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
@ -86,8 +263,7 @@ public class BFileController extends BaseController |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:edit')") |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:edit')") |
|
|
|
@Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE) |
|
|
|
@Log(title = "【请填写功能名称】", businessType = BusinessType.UPDATE) |
|
|
|
@PutMapping |
|
|
|
@PutMapping |
|
|
|
public AjaxResult edit(@RequestBody BFile bFile) |
|
|
|
public AjaxResult edit(@RequestBody BFile bFile) { |
|
|
|
{ |
|
|
|
|
|
|
|
return toAjax(bFileService.updateBFile(bFile)); |
|
|
|
return toAjax(bFileService.updateBFile(bFile)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -97,8 +273,54 @@ public class BFileController extends BaseController |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:remove')") |
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:remove')") |
|
|
|
@Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE) |
|
|
|
@Log(title = "【请填写功能名称】", businessType = BusinessType.DELETE) |
|
|
|
@DeleteMapping("/{ids}") |
|
|
|
@DeleteMapping("/{ids}") |
|
|
|
public AjaxResult remove(@PathVariable Long[] ids) |
|
|
|
public AjaxResult remove(@PathVariable Long[] ids) { |
|
|
|
{ |
|
|
|
int updRow = 0; |
|
|
|
return toAjax(bFileService.deleteBFileByIds(ids)); |
|
|
|
OSS client = ossService.newOss(); |
|
|
|
|
|
|
|
for (Long id : ids) { |
|
|
|
|
|
|
|
BFile bFile = bFileService.selectBFileById(id); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (bFile != null) { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
ossService.remove(client, ossService.getBucketName(), bFile.getUuid()); |
|
|
|
|
|
|
|
updRow += bFileService.deleteBFileById(id); |
|
|
|
|
|
|
|
BVideos bVideos = new BVideos(); |
|
|
|
|
|
|
|
bVideos.setFileId(bFile.getId()); |
|
|
|
|
|
|
|
List<BVideos> a = videosService.selectBVideosList(bVideos); |
|
|
|
|
|
|
|
if (a != null && a.size() > 0) { |
|
|
|
|
|
|
|
a.forEach(v -> { |
|
|
|
|
|
|
|
videosService.deleteBVideosById(v.getId()); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
|
|
e.printStackTrace(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return toAjax(updRow); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@PreAuthorize("@ss.hasPermi('system:file:query')") |
|
|
|
|
|
|
|
@Log(title = "【请填写功能名称】") |
|
|
|
|
|
|
|
@GetMapping("/getNoAuthFiles") |
|
|
|
|
|
|
|
public AjaxResult getNoAuthFiles(@RequestParam("videoId") Long id, @RequestParam("memo") String memo) { |
|
|
|
|
|
|
|
List<Long> ids = videoDao.findByParentId(id); |
|
|
|
|
|
|
|
if (ids.size() == 0) { |
|
|
|
|
|
|
|
ids.add(-1L); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<BFile> files = fileDao.findByNoIds(ids); |
|
|
|
|
|
|
|
if (StrUtil.isNotBlank(memo)){ |
|
|
|
|
|
|
|
files=files.stream().filter(v->{ |
|
|
|
|
|
|
|
return v.getMemo().contains(memo); |
|
|
|
|
|
|
|
}).collect(Collectors.toList()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return AjaxResult.success(files); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|