|
@@ -0,0 +1,443 @@
|
|
|
+package com.jeeplus.pubmodules.oss.service;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.CollectionUtil;
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.aliyun.oss.OSSClient;
|
|
|
+import com.aliyun.oss.model.OSSObject;
|
|
|
+import com.aliyun.oss.model.SimplifiedObjectMeta;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.service.IService;
|
|
|
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
+import com.jeeplus.core.query.QueryWrapperGenerator;
|
|
|
+import com.jeeplus.pubmodules.oss.domain.WorkAttachment;
|
|
|
+import com.jeeplus.pubmodules.oss.mapper.OssServiceMapper;
|
|
|
+import com.jeeplus.pubmodules.oss.service.dto.FileDetailDTO;
|
|
|
+import com.jeeplus.pubmodules.oss.service.dto.FileUrlDto;
|
|
|
+import com.jeeplus.pubmodules.oss.service.dto.WorkAttachmentDto;
|
|
|
+import com.jeeplus.sys.service.dto.UserDTO;
|
|
|
+import com.jeeplus.sys.utils.UserUtils;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.beans.BeanUtils;
|
|
|
+import org.springframework.beans.factory.annotation.Value;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.BufferedInputStream;
|
|
|
+import java.io.BufferedOutputStream;
|
|
|
+import java.io.IOException;
|
|
|
+import java.net.URL;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class OssService extends ServiceImpl<OssServiceMapper, WorkAttachment> {
|
|
|
+
|
|
|
+ @Value("${config.accessory.aliyun.aliyunUrl}")
|
|
|
+ private String aliyunUrl;
|
|
|
+
|
|
|
+ @Value("${config.accessory.aliyun.aliyunDownloadUrl}")
|
|
|
+ private String aliyunDownloadUrl;
|
|
|
+
|
|
|
+ @Value("${config.accessory.aliyun.endpoint}")
|
|
|
+ private String endpoint;
|
|
|
+
|
|
|
+ @Value("${config.accessory.aliyun.accessKeyId}")
|
|
|
+ private String accessKeyId;
|
|
|
+
|
|
|
+ @Value("${config.accessory.aliyun.accessKeySecret}")
|
|
|
+ private String accessKeySecret;
|
|
|
+
|
|
|
+ @Value("${config.accessory.aliyun.bucketName}")
|
|
|
+ private String bucketName;
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private OssServiceMapper ossServiceMapper;
|
|
|
+
|
|
|
+ @Value("${aliyun_directory}")
|
|
|
+ private String directory = "attachment-file/assess";
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private OSSClientService ossClientService;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存数据
|
|
|
+ * @param workAttachments
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public void saveMsg(List<WorkAttachment> workAttachments) {
|
|
|
+ if (CollectionUtil.isNotEmpty(workAttachments)) {
|
|
|
+ //获取当前登录人信息
|
|
|
+ String id = UserUtils.getCurrentUserDTO().getId();
|
|
|
+ int i = 1;
|
|
|
+ for (WorkAttachment workAttachment : workAttachments) {
|
|
|
+ //判断文件是否存在
|
|
|
+ LambdaQueryWrapper<WorkAttachment> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
+ lambdaQueryWrapper.eq(WorkAttachment::getUrl, workAttachment.getUrl());
|
|
|
+ lambdaQueryWrapper.eq(WorkAttachment::getAttachmentId, workAttachment.getAttachmentId());
|
|
|
+ List<WorkAttachment> list = ossServiceMapper.selectList(lambdaQueryWrapper);
|
|
|
+ if (CollectionUtil.isNotEmpty(list)) {
|
|
|
+ i++;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ log.info("开始执行保存操作,入参:{}" , JSONObject.toJSONString(workAttachments));
|
|
|
+ //文件后缀名赋值
|
|
|
+ List<String> strings = Arrays.asList(workAttachment.getUrl().split("\\."));
|
|
|
+ workAttachment.setType(strings.get(strings.size()-1));
|
|
|
+ workAttachment.setId(UUID.randomUUID().toString().replace("-",""));
|
|
|
+ //排序赋值
|
|
|
+ workAttachment.setSort(i);
|
|
|
+ //基础信息赋值
|
|
|
+ //workAttachment.getCreateBy().setId(id);
|
|
|
+ workAttachment.setCreateTime(new Date());
|
|
|
+ //workAttachment.getUpdateBy().setId(id);
|
|
|
+ workAttachment.setUpdateTime(new Date());
|
|
|
+ workAttachment.setDelFlag(0);
|
|
|
+ i++;
|
|
|
+
|
|
|
+ UserDTO userDTO = UserUtils.getCurrentUserDTO();
|
|
|
+ ossServiceMapper.insertWorkAttachment(workAttachment, userDTO);
|
|
|
+ }
|
|
|
+ log.info("保存操作执行完成");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据文件路径删除文件
|
|
|
+ * @param url
|
|
|
+ */
|
|
|
+ public void deleteMsgByFileName(String url) {
|
|
|
+ log.info("开始执行删除操作,入参:{}" , url);
|
|
|
+ Map<String,Object> map = new HashMap<>();
|
|
|
+ map.put("url", url);
|
|
|
+ int i = ossServiceMapper.deleteByMap(map);
|
|
|
+ log.info("删除操作完成,共删除{}条数据" , i);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据id删除数据
|
|
|
+ * @param id
|
|
|
+ */
|
|
|
+ public void deleteMsgById(String id) {
|
|
|
+ log.info("开始执行删除操作,入参:{}" , id);
|
|
|
+ Map<String,Object> map = new HashMap<>();
|
|
|
+ map.put("id", id);
|
|
|
+ int i = ossServiceMapper.deleteByMap(map);
|
|
|
+ //项目文件删除后处理项目文件状态
|
|
|
+ Integer num = ossServiceMapper.selectSaveById(id);
|
|
|
+ if (num == 0) {
|
|
|
+ ossServiceMapper.updateProjectRecord(id);
|
|
|
+ }
|
|
|
+ log.info("删除操作完成,共删除{}条数据" , i);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据附件对应父节点id查询文件列表
|
|
|
+ * @param attachmentId
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public List<WorkAttachment> findFileList(String attachmentId) {
|
|
|
+
|
|
|
+ log.info("文件查询开始,入参:{}" , attachmentId);
|
|
|
+ QueryWrapper<WorkAttachment> queryWrapper = new QueryWrapper<>();
|
|
|
+ queryWrapper.eq ("a.attachment_id", attachmentId );
|
|
|
+ List<WorkAttachment> list = ossServiceMapper.findList(queryWrapper);
|
|
|
+ //创建人和文件名称处理
|
|
|
+ if (CollectionUtil.isNotEmpty(list)) {
|
|
|
+ temporaryUrl(list);
|
|
|
+ }
|
|
|
+ log.info("文件查询结束,查询结果:{}" , JSONObject.toJSONString(list));
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成临时文件
|
|
|
+ * @param list
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public List<WorkAttachment> temporaryUrl(List<WorkAttachment> list) {
|
|
|
+ list.stream().forEach(work -> {
|
|
|
+ String url = null;
|
|
|
+ if (StringUtils.isNotEmpty(work.getUrl())) {
|
|
|
+ url = aliyunUrl + work.getUrl();
|
|
|
+ work.setTemporaryUrl(getFileTemporaryLookUrl(url));
|
|
|
+ }
|
|
|
+
|
|
|
+ //对文件大小进行处理
|
|
|
+ if(StringUtils.isBlank(work.getFileSize())){
|
|
|
+ work.setFileSize("0");
|
|
|
+ }
|
|
|
+ if(StringUtils.isNotBlank(work.getFileSize())){
|
|
|
+ Long fileSizeBytes = Long.parseLong(work.getFileSize());
|
|
|
+ //如果数据库文件大小小于等于0, 则访问阿里云获取文件大小
|
|
|
+ fileSizeBytes = 0L;
|
|
|
+ if (fileSizeBytes<=0){
|
|
|
+ fileSizeBytes = ossClientService.getSimplifiedObjectMeta(url);
|
|
|
+ }
|
|
|
+
|
|
|
+ if(null != fileSizeBytes){
|
|
|
+ Double fileSize = (double)fileSizeBytes/1024/1024;
|
|
|
+ work.setFileSize(String.format("%.2f", fileSize));
|
|
|
+ }else{
|
|
|
+ work.setFileSize("0.00");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据文件路径获取文件信息
|
|
|
+ * @param url
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ public FileDetailDTO getFileSizeByUrl(String url){
|
|
|
+ FileDetailDTO fileDetailDTO = new FileDetailDTO();
|
|
|
+ fileDetailDTO.setUrl(getFileTemporaryLookUrl(aliyunUrl + url));
|
|
|
+ Long fileSizeBytes = ossClientService.getSimplifiedObjectMeta(aliyunUrl + url);
|
|
|
+ if(null != fileSizeBytes){
|
|
|
+ Double fileSize = (double)fileSizeBytes;
|
|
|
+ fileDetailDTO.setSize(String.format("%.2f", fileSize));
|
|
|
+ }else{
|
|
|
+ fileDetailDTO.setSize("0");
|
|
|
+ }
|
|
|
+ return fileDetailDTO;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 阿里云获取临时文件查看url
|
|
|
+ * @param url
|
|
|
+ */
|
|
|
+ public String getFileTemporaryLookUrl(String url){
|
|
|
+ url = url.replace("amp;","");
|
|
|
+ String cons = "";
|
|
|
+ if (url.contains(aliyunUrl)){
|
|
|
+ cons = aliyunUrl;
|
|
|
+ }else if (url.contains("http://gangwan-app.oss-cn-hangzhou.aliyuncs.com")){
|
|
|
+ cons = "http://gangwan-app.oss-cn-hangzhou.aliyuncs.com";
|
|
|
+ }else {
|
|
|
+ cons = aliyunDownloadUrl;
|
|
|
+ }
|
|
|
+ String key = "";
|
|
|
+ String[] split = url.split(cons + "/");
|
|
|
+ if(split.length>1){
|
|
|
+ key = split[1];
|
|
|
+ }else{
|
|
|
+ key = url;
|
|
|
+ }
|
|
|
+ // 指定过期时间为24小时。
|
|
|
+ Date expiration = new Date(new Date().getTime() + 1000 * 60 * 60 * 24 );
|
|
|
+ //初始化OSSClient
|
|
|
+ OSSClient ossClient = new OSSClient(endpoint,accessKeyId,accessKeySecret);
|
|
|
+ return ossClient.generatePresignedUrl(bucketName, key, expiration).toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 附件下载
|
|
|
+ * @param key
|
|
|
+ * @param fileName
|
|
|
+ * @param response
|
|
|
+ */
|
|
|
+ public void downByStream(String key, String fileName, HttpServletResponse response, String agent){
|
|
|
+ try {
|
|
|
+ String newName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20").replaceAll("%28", "\\(").replaceAll("%29", "\\)").replaceAll("%3B", ";").replaceAll("%40", "@").replaceAll("%23", "\\#").replaceAll("%26", "\\&").replaceAll("%2C", "\\,");
|
|
|
+ // 创建OSSClient实例
|
|
|
+ //初始化OSSClient
|
|
|
+ OSSClient ossClient = new OSSClient(endpoint,accessKeyId,accessKeySecret);
|
|
|
+ OSSObject ossObject = ossClient.getObject(bucketName, key);
|
|
|
+ BufferedInputStream in = new BufferedInputStream(ossObject.getObjectContent());
|
|
|
+ BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
|
|
|
+ response.setHeader("Content-Disposition","attachment;filename*=UTF-8''"+ newName);
|
|
|
+
|
|
|
+ /*if(agent != null && agent.toLowerCase().indexOf("firefox") > 0){
|
|
|
+ response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''"+ URLEncoder.encode(fileName,"utf-8"));
|
|
|
+ }else {
|
|
|
+ response.setHeader("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName,"utf-8"));
|
|
|
+ }*/
|
|
|
+ byte[] car=new byte[1024];
|
|
|
+ int L=0;
|
|
|
+ while((L=in.read(car))!=-1){
|
|
|
+ out.write(car, 0,L);
|
|
|
+ }
|
|
|
+ if(out!=null){
|
|
|
+ out.flush();
|
|
|
+ out.close();
|
|
|
+ }
|
|
|
+ if(in!=null){
|
|
|
+ in.close();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 上传文件
|
|
|
+ * @param file
|
|
|
+ * @param dir 存放文件夹名称
|
|
|
+ * @return
|
|
|
+ * @throws Exception
|
|
|
+ */
|
|
|
+ public FileUrlDto webUpload(MultipartFile file, String dir) throws IOException {
|
|
|
+ FileUrlDto dto = new FileUrlDto();
|
|
|
+ // 文件保存路径
|
|
|
+ String fileDir =directory+"/"+dir+ossClientService.datePath()+"/"+ System.currentTimeMillis();
|
|
|
+ // 判断文件是否为空
|
|
|
+ if (!file.isEmpty()) {
|
|
|
+ String name = file.getOriginalFilename ();
|
|
|
+ ossClientService.uploadFile2OSS(file.getInputStream(), fileDir, name);
|
|
|
+ String url = fileDir +name;
|
|
|
+ String lsUrl = this.getFileTemporaryLookUrl(aliyunUrl + "/" + fileDir +name);
|
|
|
+ dto.setUrl(url);
|
|
|
+ dto.setLsUrl(lsUrl);
|
|
|
+ }
|
|
|
+ return dto;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存/修改附件
|
|
|
+ * @param fileList 要保存的附件,传空值则删除attachmentId下的所有附件
|
|
|
+ * @param attachmentId 附件的attachmentId
|
|
|
+ * @param attachmentFlag 附件的attachmentFlag
|
|
|
+ */
|
|
|
+ public void saveOrUpdateFileList(List<WorkAttachmentDto> fileList, String attachmentId, String attachmentFlag){
|
|
|
+ UserDTO userDTO = UserUtils.getCurrentUserDTO();
|
|
|
+ if (CollectionUtil.isNotEmpty(fileList)) {
|
|
|
+ List<String> ids = fileList.stream().filter(item -> {
|
|
|
+ if (StringUtils.isNotBlank(item.getId())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }).map(WorkAttachmentDto::getId).collect(Collectors.toList());
|
|
|
+ if(CollectionUtil.isNotEmpty(ids)){
|
|
|
+ ossServiceMapper.delete(new QueryWrapper<WorkAttachment>().lambda()
|
|
|
+ .eq(WorkAttachment::getAttachmentId, attachmentId)
|
|
|
+ .notIn(WorkAttachment::getId,ids));
|
|
|
+ }else{
|
|
|
+ ossServiceMapper.delete(new QueryWrapper<WorkAttachment>().lambda()
|
|
|
+ .eq(WorkAttachment::getAttachmentId, attachmentId));
|
|
|
+ }
|
|
|
+
|
|
|
+ List<WorkAttachmentDto> dtoList = fileList.stream().filter(item -> {
|
|
|
+ if (StringUtils.isNotBlank(item.getId())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+
|
|
|
+ this.saveFileList(dtoList, userDTO, attachmentId,attachmentFlag);
|
|
|
+ } else {
|
|
|
+ ossServiceMapper.delete(new QueryWrapper<WorkAttachment>().lambda().eq(WorkAttachment::getAttachmentId, attachmentId));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存/修改附件
|
|
|
+ * @param fileList 要保存的附件,传空值则删除attachmentId下attachmentFlag下的所有附件
|
|
|
+ * @param attachmentId 附件的attachmentId
|
|
|
+ * @param attachmentFlag 附件的attachmentFlag
|
|
|
+ */
|
|
|
+ public void saveOrUpdateFileListFlag(List<WorkAttachmentDto> fileList,String attachmentId,String attachmentFlag){
|
|
|
+ UserDTO userDTO = UserUtils.getCurrentUserDTO();
|
|
|
+ if (CollectionUtil.isNotEmpty(fileList)) {
|
|
|
+ List<String> ids = fileList.stream().filter(item -> {
|
|
|
+ if (StringUtils.isNotBlank(item.getId())) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }).map(WorkAttachmentDto::getId).collect(Collectors.toList());
|
|
|
+ if(CollectionUtil.isNotEmpty(ids)){
|
|
|
+ ossServiceMapper.delete(new QueryWrapper<WorkAttachment>().lambda()
|
|
|
+ .eq(WorkAttachment::getAttachmentId, attachmentId)
|
|
|
+ .eq(WorkAttachment::getAttachmentFlag, attachmentFlag)
|
|
|
+ .notIn(WorkAttachment::getId,ids));
|
|
|
+ }else{
|
|
|
+ ossServiceMapper.delete(new QueryWrapper<WorkAttachment>().lambda()
|
|
|
+ .eq(WorkAttachment::getAttachmentId, attachmentId)
|
|
|
+ .eq(WorkAttachment::getAttachmentFlag, attachmentFlag));
|
|
|
+ }
|
|
|
+
|
|
|
+ List<WorkAttachmentDto> dtoList = fileList.stream().filter(item -> {
|
|
|
+ if (StringUtils.isNotBlank(item.getId())) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+
|
|
|
+ this.saveFileList(dtoList, userDTO, attachmentId,attachmentFlag);
|
|
|
+ } else {
|
|
|
+ ossServiceMapper.delete(new QueryWrapper<WorkAttachment>().lambda().eq(WorkAttachment::getAttachmentId, attachmentId).eq(WorkAttachment::getAttachmentFlag, attachmentFlag));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存附件信息
|
|
|
+ * @param list 待保存的附件列表
|
|
|
+ * @param userDTO 当前登录用户
|
|
|
+ * @param attachmentId 关联id
|
|
|
+ */
|
|
|
+ public void saveFileList(List<WorkAttachmentDto> list, UserDTO userDTO, String attachmentId,String attachmentFlag) {
|
|
|
+ int sort = 1;
|
|
|
+ for (WorkAttachmentDto dto : list) {
|
|
|
+ WorkAttachment i = new WorkAttachment();
|
|
|
+ //包含了url、size、name
|
|
|
+ i.setId(UUID.randomUUID().toString().replace("-", ""));
|
|
|
+// i.getCreateBy().setId(userDTO.getId());
|
|
|
+ i.setCreateTime(new Date());
|
|
|
+// i.getUpdateBy().setId(userDTO.getId());
|
|
|
+ i.setUpdateTime(new Date());
|
|
|
+ i.setDelFlag(0);
|
|
|
+ i.setUrl(dto.getUrl());
|
|
|
+ //文件类型处理
|
|
|
+ List<String> strings = Arrays.asList(dto.getName().split("\\."));
|
|
|
+ if (CollectionUtil.isNotEmpty(strings)) {
|
|
|
+ i.setType(strings.get(1));
|
|
|
+ }
|
|
|
+ i.setAttachmentId(attachmentId);
|
|
|
+ i.setAttachmentName(dto.getName());
|
|
|
+ i.setAttachmentFlag(attachmentFlag);
|
|
|
+ i.setFileSize(dto.getSize());
|
|
|
+ i.setSort(sort);
|
|
|
+ ossServiceMapper.insertWorkAttachment(i, userDTO);
|
|
|
+ sort++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 保存附件信息
|
|
|
+ * @param workAttachmentDto 待保存的附件信息
|
|
|
+ * @param userDTO 当前登录用户
|
|
|
+ * @param attachmentId 关联id
|
|
|
+ * @param attachmentFlag
|
|
|
+ * @param sort
|
|
|
+ */
|
|
|
+ public String saveFile(WorkAttachmentDto workAttachmentDto, UserDTO userDTO, String attachmentId,String attachmentFlag, Integer sort) {
|
|
|
+ WorkAttachment i = new WorkAttachment();
|
|
|
+ //包含了url、size、name
|
|
|
+ i.setId(UUID.randomUUID().toString().replace("-", ""));
|
|
|
+ i.setCreateTime(new Date());
|
|
|
+ i.setUpdateTime(new Date());
|
|
|
+ i.setDelFlag(0);
|
|
|
+ i.setUrl(workAttachmentDto.getUrl());
|
|
|
+ //文件类型处理
|
|
|
+ List<String> strings = Arrays.asList(workAttachmentDto.getName().split("\\."));
|
|
|
+ if (CollectionUtil.isNotEmpty(strings)) {
|
|
|
+ i.setType(strings.get(1));
|
|
|
+ }
|
|
|
+ i.setAttachmentId(attachmentId);
|
|
|
+ i.setAttachmentName(workAttachmentDto.getName());
|
|
|
+ i.setAttachmentFlag(attachmentFlag);
|
|
|
+ i.setFileSize(workAttachmentDto.getSize());
|
|
|
+ i.setSort(sort);
|
|
|
+ ossServiceMapper.insertWorkAttachment(i, userDTO);
|
|
|
+ return i.getId();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|