1.导依赖以及上传依赖到服务器

1.1、打包到本地,来使用依赖

  • 创建 maven 工程空项目,来放置自己的工具类

    • xml 中添加 plugin
    <build>
      <finalName>${project.version}</finalName>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <configuration>
            <encoding>UTF-8</encoding>
            <source>1.8</source>
            <target>1.8</target>
            <showWarnings>true</showWarnings>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-source-plugin</artifactId>
          <version>3.0.1</version>
          <configuration>
            <attach>true</attach>
          </configuration>
          <executions>
            <execution>
              <phase>compile</phase>
              <goals>
                <goal>jar</goal>
              </goals>
            </execution>
          </executions>
        </plugin>
      </plugins>
    </build>
    
    • 打包命令:mvn clean install

    会吧打包在本地磁盘上,命令后会有显示位置在哪里,之后的项目就可以直接引入这个maven的依赖来使用里面的一些工具类

  • 创建 springboot 项目工程,来引入打包后的 maven 依赖

    • xml 中添加依赖
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
    
      <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
      </dependency>
      <dependency>
        <groupId>com.mhn</groupId>
        <artifactId>mhn-commons</artifactId>
        <version>1.0-SNAPSHOT</version>
      </dependency>
    </dependencies>
    

1.2、上传到 nexus,可以随后部署到服务器

  • 同上创建 maven 项目,进行打包

    • settings.xml 中添加设置

    ```xml

    mirror central,jcenter,!rdc-releases,!rdc-snapshots mirror https://maven.guanweiming.com:8081/repository/maven-tulan

home-releases admin 12345678Aa home-snapshots admin 12345678Aa home home-releases::default::https://maven.guanweiming.com:8081/repository/tulan-release/ home-snapshots::default::https://maven.guanweiming.com:8081/repository/tulan-snapshot/ home-releases</releases.id> maven</releases.name> https://maven.guanweiming.com:8081/repository/tulan-release/</releases.url>

    <snapshots.id>home-snapshots</snapshots.id>
    <snapshots.name>maven</snapshots.name>
    <snapshots.url>https://maven.guanweiming.com:8081/repository/tulan-snapshot/</snapshots.url>
  </properties>
  <repositories>
    <repository>
      <id>central</id>
      <url>https://maven.guanweiming.com:8081/repository/maven-tulan</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>snapshots</id>
      <url>https://maven.guanweiming.com:8081/repository/maven-tulan</url>
      <releases>
        <enabled>false</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>home-releases</id>
      <url>https://maven.guanweiming.com:8081/repository/tulan-release/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </repository>
    <repository>
      <id>home-snapshots</id>
      <url>https://maven.guanweiming.com:8081/repository/tulan-snapshot/</url>
      <releases>
        <enabled>false</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </repository>
  </repositories>
  <pluginRepositories>
    <pluginRepository>
      <id>central</id>
      <url>https://maven.guanweiming.com:8081/repository/maven-tulan</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </pluginRepository>
    <pluginRepository>
      <id>snapshots</id>
      <url>https://maven.guanweiming.com:8081/repository/maven-tulan</url>
      <releases>
        <enabled>false</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </pluginRepository>
    <pluginRepository>
      <id>home-releases</id>
      <url>https://maven.guanweiming.com:8081/repository/tulan-release/</url>
      <releases>
        <enabled>true</enabled>
      </releases>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
    </pluginRepository>
    <pluginRepository>
      <id>home-snapshots</id>
      <url>https://maven.guanweiming.com:8081/repository/tulan-snapshot/</url>
      <releases>
        <enabled>false</enabled>
      </releases>
      <snapshots>
        <enabled>true</enabled>
      </snapshots>
    </pluginRepository>
  </pluginRepositories>
</profile>

</profiles> home


  - pom.xml 中添加配置服务器地址

  ```xml
  <repositories>
    <repository>
      <id>home</id>
      <url>https://maven.guanweiming.com:8081/repository/maven-public</url>
    </repository>
  </repositories>
  • 不需要打包,直接进行发行到服务器:mvn deploy

同上创建 springBoot 项目,来直接导入相应依赖就可以使用该依赖中存在的工具类

2.Optional

2.1、概述

Optional(java.util.Optional)类是一个容器类,可以保存类型 T 的值,代表这个值存在。或仅仅保存 null,表示这个值不存在。原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念,并且可以避免空指针异常。Optional 提供了很多方法,可以不用显式进行空值检测

2.2、用法

  • 实例提前准备代码
@Data
@AllArgsConstructor
@NoArgsConstructor
class Student{
  private String name;
  private Integer age;
}
  • 创建 Optional 类对象的方法

    • Optional.empty():创建一个空的 Optional 实例
    • Optional.of(T t):创建一个 Optional 实例,t 必须非空
    • Optional.ofNullable(T t):t 可以为 null
    public void test(){
      // 声明空的 Optional
      Optional<Object> empty = Optional.empty();
      // 依据一个非空值创建 Optional
      Student student = new Student();
      Optional<Student> o1 = Optional.of(student);
      // 可接受 null 的 Optional
      Student student1 = null;
      Optional<Student> o2 = Optional.ofNullable(student1);
    }
    
  • 判断 Optional 容器中是否包含对象

    • boolean isPresent():判断是否包含对象
    • void ifPresent(Consumer'<? super T>' consumer):如果有值,就执行 Consumer 接口的视线代码,并且该值会作为参数传递给它
    public void test(){
      Student studnet = new Student();
      boolean isNull = Optional.ofNullable(student).isPresent();
      System.out.println(isNull); // fasle
    
      // 如果不为空,则set值
      Optioanl.ofNullable(student).isPresnet(item -> item.setName("浩楠"));
    }
    
  • 获取 Optional 容器的对象

    • T get():如果调用对象包含值,返回该值,否则抛异常
    • T orElse(T other):如果有值则将其返回,否则返回指定的 other 对象
    • T orElseGet(Supplier'<? extends T>' other):如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象
    • T orElseThrow(Supplier'<? extends X> exceptionSupplier):如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常
    public void test() throws Excpetion{
      Student student = null;
      Optional<Student> o1 = Optional.ofNullable(student);
      // 使用get时注意,如果为空,则会报错
      Student student1 = o1.get();
    
      // 当 student 为空的时候,返回新建的这个对象
      Student student2 = o1.orElse(new Student("浩楠",22));
    
      // 当 student 为空的时候,返回指定对应的函数结果
      Student sutdent3 = o1.orElseGet(() -> new Student("浩楠",22));
    
      // 当 student 为空的时候,抛出指定异常
      o1.orElseThrow(() -> new NullPointException());
    }
    
  • 过滤

    • Optional'<'T>' filter(Predicate'<? Super T>' predicate'):如果值存在,并且这个值匹配给定的 predicate,返回一个 Optional 用以描述这个值,否则返回一个空的 Optional
    public void test(){
      Student student = new Student("浩楠",22);
      Optional.ofNullable(student).filter(item -> item.getName().equals("heihei").ifPresent(item -> System.out.println("ok")));
    }
    
  • 映射

    • Optional map(Function<? super T,? extends U> mapper):如果有值,则对其执行调用映射函数得到返回值,如果返回值不为 null,则创建包含映射返回值的 Optional 作为 map 方法的方绘制,否则返回空的 Optional
    • Optional flatMap(Function<? Super T, Optional<U'> mapper):如果值存在,就对该值执行提供的 mapping 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象
    Optional.ofNullable(CategoEnum.getItem(projectVO.getHPcategory())).map(CategoEnum::getLabel).ifPresent(projectVO::setHPcategoryStr);
    

3.Word导出

  • 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    <version>2.6.13</version>
</dependency>
  • yml配置
freemarker:
  cache: false #是否启用缓存,开发环境不建议启动因为涉及经常修改模板调试
  settings:
    classic_compatible: true
  suffix: .xml #一般格式tpl居多
  charset: UTF-8
  template-loader-path: classpath:/templates/ #模板路径,一般都是这个
  • controller
@Slf4j
@Api(tags = "doc 导出")
@RequiredArgsConstructor
@RestController
@RequestMapping(Const.ADMIN + "export")
public class ExportController {
    private final IExportService exportService;

    @ApiModelProperty("doc文件导出")
    @RequestMapping(value = "/doc", method = RequestMethod.GET)
    public void exportWord(@ApiParam(required = true)@RequestParam Long applyId,
                           @ApiParam(required = true)@RequestParam  Long type) throws Exception {
        String fileName = null; //文件名称
        // 设置头部数据
        Map<String, Object> dataMap = null;
        String templateName = null;

        ApproveRuleTypeEnum typeEnum = ApproveRuleTypeEnum.getItem(type);
        switch (typeEnum) {
            case CAR:
                fileName = LocalDateTime.now() + "11.doc";
                templateName = "车辆.xml";
                dataMap = getCarDate(applyId);
                break;
            case SEAL:
                fileName = LocalDateTime.now() + "印章使用审批单.doc";
                templateName = "印章使用审批单.xml.ftl";
                dataMap = getSealDate(applyId);
                break;
        }
        exportService.exportDocToClient(fileName, templateName, dataMap);
    }
    // 车辆审批数据
    private Map<String, Object> getCarDate(long applyId) {
        Map<String, Object> dataMap = new HashMap<>();

        // 设置表格数据
        List<String> list = new ArrayList<>();
        list.add("123");
        list.add("456");
        String reason = "回家看病人";
        dataMap.put("name", "小明");
        dataMap.put("reason", reason);
        dataMap.put("regAddress", "苏州");
        dataMap.put("courseList", list);
        return dataMap;
    }
}
  • Service
public interface IExportService {
    void exportDocToClient(String fileName, String templateName, Map<String, Object> dataMap) throws Exception;
}
  • ServiceImpl
@Slf4j
@RequiredArgsConstructor
@Service
public class IExportServiceImpl implements IExportService {

    private final FreeMarkerConfigurer freeMarkerConfigurer;

    @Override
    // 参数一:导出文件的名字(注意要存在后缀) 参数二:xml 模板文件名字(存在后缀)  参数三:映射数据的 map
    public void exportDocToClient(String fileName, String tplName, Map<String, Object> data) throws Exception {
        HttpServletResponse response = HttpKit.getHttpResponse();
        Writer out = null;
        try {
            response.reset();
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/msword");
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
            // 把本地文件发送给客户端
            out = response.getWriter();
            Template template = getTemplate(tplName);
            template.process(data, out);
            CloseUtil.closeQuietly(out);
        } finally {
            CloseUtil.closeQuietly(out);
        }
    }

    public Template getTemplate(String name) throws Exception {
        return freeMarkerConfigurer.getConfiguration().getTemplate(name);
    }
}
  • 遇见到的问题

    • 数据怎么进行渲染的

    根据 freemarker 提供的接口传入的 map 集合,集合与 xml 中的 ${} 数据进行映射渲染

    • 导出文档信息导致打不开

    .docx 转为 doc 格式,再转为 xml 格式(WPS转换会导致错误,当前使用 office 2013)

    • 存在多选框,怎么解决

    freemarker 存在表达式,可以在 xml 中进行流程控制的判断

4.Word批量导出为压缩包

  • ServiceImpl 封装为返回字节
@Service
@Slf4j
@RequiredArgsConstructor
public class WordExportServiceImpl implements WordExeportService {

    private final FreeMarkerConfigurer freeMarkerConfigurer;


    @Override
    public byte[] wordExport(String fileName, String templateName, Map<String, Object> dataMap) throws Exception{
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(bos);
        try {
            Template template = getTemplate(templateName);
            template.process(dataMap, osw);
          // 返回字节数组
            return bos.toByteArray();
        } finally {
            CloseUtil.closeQuietly(osw);
            CloseUtil.closeQuietly(bos);
        }
    }
    public Template getTemplate(String name) throws Exception {
        return freeMarkerConfigurer.getConfiguration().getTemplate(name);
    }
}
  • 调用时,通过相应流转为解压包形式
public void wordBatchExport(long statFeaturePlanId) {
  List<StatFeaturePlanTask> tasks = statFeaturePlanTaskDao.findTaskIdsByPlanId(statFeaturePlanId);
  HttpServletResponse response = HttpKit.getHttpResponse();
  ZipOutputStream zos = null;
  try {
    // 解压流
    zos = new ZipOutputStream(response.getOutputStream());
    response.reset();
    response.setCharacterEncoding("UTF-8");
    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(LocalDateTime.now() + "批量导出.zip", "UTF-8"));
    for (StatFeaturePlanTask task : tasks) {
      byte[] bytes = wordExport(task.getStatFeaturePlanTaskId(), false);
      // 将doc写入压缩包中
      zos.putNextEntry(new ZipEntry(sciDisciplineDao.findBySubKey(task.getSubKey()).getSubName()+".doc"));
      zos.write(bytes,0,bytes.length);
      zos.flush();
      zos.closeEntry();
    }
    zos.flush();
  } catch (Exception e) {
    log.info("批量导出有误:{}", e.getMessage());
  } finally {
    CloseUtil.closeQuietly(zos);
  }
}

5.Excel导出

  • 依赖
<!--导出excel-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>3.3.2</version>
</dependency>
<dependency>
  <groupId>cn.afterturn</groupId>
  <artifactId>easypoi-spring-boot-starter</artifactId>
  <version>4.4.0</version>
</dependency>
  • 封装为工具类
public static void doExport(Workbook workbook, String name) {
  HttpServletResponse response = HttpKit.getHttpResponse();
  ServletOutputStream outputStream = null;
  try {
    String filename = URLEncoder.encode(name + ".xls", "UTF-8");
    response.setContentType("application/vnd.ms-excel;charset=gb2312");
    response.setHeader("Content-Disposition", "attachment; filename=" + filename);
    outputStream = response.getOutputStream();
    workbook.write(outputStream);
  } catch (Exception e) {
    log.error("导出报错", e);
  } finally {
    CloseUtil.closeQuietly(outputStream);
  }
}
  • 实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class HProjectDTO {
    @Excel(name = "合同负责人姓名")
    private String name;

    @Excel(name = "合同状态")
    private String hstateStr;

    private String hadmin;

    @Excel(name = "合同成员")
    private String hnum;

    @Excel(name = "合同金额")
    private BigDecimal hamount;

    @Excel(name = "立项时间")
    private LocalDate hbegin;
    }
}
  • 调用
// 参数意义:ExcelExportUtils.exportExcel(ExportParams对象,实体类.class,数据集合);
// 参数意义:ExportParams(标题,sheetName);
ExcelUtil.doExport(ExcelExportUtil.exportExcel(new ExportParams(null,"数据"),ZProjectDTO.class,voList),"纵向项目数据");

6.Excel导入

public R<String> sciPlatImport(MultipartFile file) {
  ImportParams importParams = new ImportParams();
  //设置标题的行数,有标题时一定要有
  importParams.setTitleRows(0);
  //设置表头的行数
  importParams.setHeadRows(1);
  try{
    // 获取到当前导入的内容
    // 参数一:获取文件流,参数二:实体类(也就是表明了 Excel 注解的),参数三:ImportParams参数
    List<SciPlatDTO> sciPlatDTOS = ExcelImportUtil.importExcel(file.getInputStream(), SciPlatDTO.class, importParams);
    for (SciPlatDTO sciPlatDTO : sciPlatDTOS) {
      com.zhenghang.entity.SciPlat sciPlat = SciPlatDTO.toSciPlat(sciPlatDTO);
      log.info("导入数据:{}", JsonUtil.toJson(sciPlatDTO,true));
      Optional.ofNullable(natPlatnormalDao.findDateByGrade1Str(sciPlatDTO.getPlatGrade1Str())).map(com.zhenghang.entity.NatPlatnormal::getPlatKey).ifPresent(data -> sciPlat.setPlatGrade1(data.toString()));
      log.info("插入数据:{}",JsonUtil.toJson(sciPlat,true));
      int result = sciPlatDao.insert(sciPlat);
      if(result == 0){
        R.createByErrorMessage("导入失败,数据可能导致有误");
      }
    }
    return R.createBySuccess("导入成功");
  }catch(Exception e){
    log.info("导入报错:{}",e.getMessage(),e);
  }
  return R.createByErrorMessage("导入失败");
}

7.获取当前操作的 IP 地址

@Slf4j
public class RequestUtils {

    public static String getRemoteHost(javax.servlet.http.HttpServletRequest request) {
        List<String> keyList = Lists.newArrayList();
        keyList.add("x-original-forwarded-for");
        keyList.add("x-forwarded-for");
        keyList.add("x-real-ip");
        keyList.add("Proxy-Client-IP");
        keyList.add("WL-Proxy-Client-IP");

        for (String key : keyList) {
            String content = request.getHeader(key);
            if (StringUtils.isBlank(content)) {
                continue;
            }
            if ("unknown".equals(content)) {
                continue;
            }
            if (content.contains(",")) {
                String[] arr = content.split(",");
                for (String temIp : Lists.newArrayList(arr)) {
                    temIp = temIp.trim();
                    if (StringUtils.isBlank(temIp)) {
                        continue;
                    }
                    if ("unknown".equals(temIp)) {
                        continue;
                    }
                    return temIp;
                }
            }
            return content;
        }
      // 获取到请求头的所有名字信息,通过request.getHeader(名字信息)来获取到要的信息
      // 原有:可以通过这样的方式来查看自己想要的请求头数据,当下就是检验上面逻辑未能找到ip的原因
        Enumeration<String> headerNames = request.getHeaderNames();
        Map<String, Object> headerMap = Maps.newHashMap();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            if (StringUtils.isNotBlank(key)) {
                headerMap.put(key, request.getHeader(key));
            }
        }
        log.warn("找不到真实ip:{}", JsonUtil.toJson(headerMap));
        return "unknown";
    }
}

8.根据 IP 地址获取当前登录地区

  • 依赖
<dependency>
    <groupId>com.jthinking.common</groupId>
  <artifactId>ip-info</artifactId>
  <version>2.2.0</version>
</dependency>
  • 调用
IPInfo ipInfo = IPInfoUtils.getIpInfo(参数为ip地址);
System.out.println("---start");
System.out.println(ip); // 国家中文名称
System.out.println(ipInfo.getCountry()); // 国家中文名称
System.out.println(ipInfo.getProvince()); // 中国省份中文名称
System.out.println(ipInfo.getAddress()); // 详细地址
System.out.println(ipInfo.getIsp()); // 互联网服务提供商
System.out.println(ipInfo.isOverseas()); // 是否是国外
System.out.println(ipInfo.getLat()); // 纬度
System.out.println(ipInfo.getLng()); // 经度
System.out.println("---end");

9.加密解密密码

存在包:org.springframework.security.crypto.password.PasswordEncoder

  • 加密
@Autowired
private PasswordEncoder passwordEncoder;

@Test
void setDefaultRole() {
  for (int i = 0; i < 3; i++) {
    System.out.println(passwordEncoder.encode("123456"));
  }
}
  • 解密
// 参数一:前端接受密码参数。参数二:获取用户密码
passwordEncoder.matches(param.getUserPassword(), user.getUserPassword());//【true/false】

10.Jackson详解及常用注解

  • JsonProperty

该注解用于竖向上,作用是把该属性的名称序列化为另一个名称,【比如把 trueName 属性序列化为 true_name,这样前端接受的属性名称就为 true_name】

import com.fasterxml.jackson.annotation.JsonProperty;

public class User {

    @JsonProperty(value = "true_name")
    private String trueName;
}
  • @JsonIgnore —— @JsonIgnoreProperties(与前者相似)

该注解用在属性或方法上(最好是属性),用来完全忽略被注释的字段和方法对应的属性,即便这个字段或方法可以被自动检测到或者还有其他注解,一般标记在属性上,返回给前端的 json 数据即不包含该属性【使用场景:通常为返回的密码】

import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@ApiModel(value = "User", description = "用户实体,用户相关属性信息")
public class User {

    @JsonIgnore
    @ApiModelProperty(value = "密码", name = "password", required = false)
    private String password;
}
  • Spring 中 @JsonFormat

该注解用来设置时间字段的格式,但不能直接使用,在中国会和北京时间相差 8 小时,所以格式化的时候需要指定时区(timezone)

import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;

public class MenuStatisticsDetail {

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
}
  • Spring 中 @DateTimeFormat

@JsonFormat 注解用于定义属性的序列化和反序列化格式,而 DateTimeFormat 用于解析和格式化日期事件类型的属性,通常与 @RequestMapping 来一起使用

public class Event {
    private String name;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date eventDate;

    // Getter and setter methods
}
  • @JsonInclude

    前后端分离的项目,框架中封装了返回给前端的结果,如果属性存在该注解并且值为 null 的情况下,返回给前端的时候会过滤掉这个属性不返回【如果放在类上,则类的全部属性都会起作用】

    • @JsonInclude(JsonInclude.Include.ALWAYS):默认值,返回全部字段
    • @JsonInclude(JsonInclude.Include.NON_DEFAULT):属性为默认值不序列化【未有变动的字段】
    • @JsonInclude(JsonInclude.Include.NON_EMPTY):属性为空或者为 null 都不序列化
    • @JsonInclude(JsonInclude.Include.NON_NULL):属性为 null 不序列化
    • @JsonInclude(JsonInclude.Include.NON_ABSENT):如果实例对象汇总存在 Optional 或 AtomicReference 类型的成员变量时,如果该两种类型引用的实例为 null,也可以使该字段不做序列化,同事也可以排除值为 null 的字段
import com.fasterxml.jackson.annotation.JsonInclude;

public class User {

    @JsonInclude(JsonInclude.Include.NON_NULL) //当电话为null时就不传
    private String phone;
}
  • @JsonPropertyOrder

在将 java 对象序列化为 json 字符串的时候,使用该注解可以指定属性在 json 字符串中的顺序

@JsonPropertyOrder(value={"userName","userId"})
public class User {
    private String userId;
    private String userName;
}

results matching ""

    No results matching ""