sharp-fileupload文件存储FileStore(二)

FileStore 依赖 inputStreamStore,同时实现了inputStreamStore接口。主要是处理文件文件上传。

方法

  • 处理web端文件请求
public List<? extends FileMeta> upload(List<MultipartFile> multipartFileList, String groupName);
  • 处理本地文件
public List<? extends FileMeta> storeFiles(List<File> fileList, String groupName)
  • 处理byte[]的文件
    FileMeta#data是一个字节数组
public List<? extends FileMeta> storeFileMeta(List<? extends FileMeta> fileMetaList, String groupName)

FileMeta.java

@Getter
@Setter
public class FileMeta {

    private String name;

    private String extension;

    private String contentType;

    private Long size;

    private String groupName;

    private String path;

    @Transient
    @JsonIgnore
    private byte[] data;

    @Transient
    private String url;

    /**
     * 获取完整名称
     * @return
     */
    public String getFullName() {
        if (StringUtils.isBlank(this.getExtension())) {
            return this.getName();
        }

        return this.getName() + (StringUtils.isEmpty(this.getExtension()) ? "" : "." + this.getExtension());
    }

    public String getFullPath() {
        return this.getGroupName() + "/" + getPath();
    }

    /**
     * 设置完整名称
     * @param fullName
     */
    public void setFullName(String fullName) {
        String fileName = com.rick.common.util.StringUtils.stripFilenameExtension(fullName);
        String fileExt = com.rick.common.util.StringUtils.getFilenameExtension(fullName);
        setName(fileName);
        setExtension(fileExt);
    }
}

测试

@Test
public void testStore() throws IOException {
    File file = new File("/Users/rick/jkxyx205/tmp/fileupload/demo/1.jpg");
    FileMeta fileMeta = FileMetaUtils.parse(file);
    fileStore.storeFileMeta(Lists.newArrayList(fileMeta), "upload").get(0);
}

sharp-fileupload流存储InputStreamStore(一)

简介

sharp-fileupload 可以上传文件,对图片的处理(裁剪、选装、缩放)。提供了Restfull接口上传,访问文档。底层存储功能支持:

  • 本地存储
  • 阿里云OSS
  • FastDFS

添加依赖

<dependency>
    <groupId>com.rick.fileupload</groupId>
    <artifactId>sharp-fileupload</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

配置存储策略

本地存储

默认就是本地存储。

application.xml

fileupload:
  tmp: /Users/rick/jkxyx205/tmp/fastdfs/tmp # 下载的临时目录
  local:
    server-url: http://localhost:7892/ # 映射到tmp目录
    root-path: /Users/rick/jkxyx205/tmp/fileupload

文件存储的路径是 root-path。在本地开启一个静态服务器指向存储地址。访问url是 server-url

OSS

创建 Bucketname为「sharp-fileupload」
application.xml

fileupload:
  tmp: /Users/rick/jkxyx205/tmp/fastdfs/tmp # 下载的临时目录
  oss:
    endpoint: oss-cn-beijing.aliyuncs.com
    accessKeyId: xxx
    accessKeySecret: xxx
    bucketName: sharp-fileupload

添加依赖

<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>

配置类

@Bean
public InputStreamStore ossInputStreamStore(OSSProperties ossProperties) {
    OSS ossClient = new OSSClientBuilder().build(ossProperties.getEndpoint(), ossProperties.getAccessKeyId(), ossProperties.getAccessKeySecret());
    return new OSSInputStreamStore(ossClient, ossProperties);
}

FastDFS

fdfs_client.properties

fastdfs.tracker_servers=192.168.0.117:22122
fastdfs.http_tracker_http_port=8080

添加依赖

<dependency>
    <groupId>org.csource</groupId>
    <artifactId>fastdfs-client-java</artifactId>
    <version>1.27-SNAPSHOT</version>
</dependency>

配置类

@Bean
public InputStreamStore fastDFSInputStreamStore() throws IOException, MyException {
    return new FastDFSInputStreamStore("fdfs_client.properties");
}

测试InputStreamStore

接口信息:

public interface InputStreamStore {

    StoreResponse store(String groupName, String extension, InputStream is) throws IOException;

    /**
     *
     * @param groupName
     * @param storeName 磁盘存储的文件名
     * @param extension 扩展名
     * @param is
     * @return
     * @throws IOException
     */
    StoreResponse store(String groupName, String storeName, String extension, InputStream is) throws IOException;

    /**
     * 删除文件
     * @param groupName
     * @param path
     * @return
     */
    void delete(String groupName, String path) throws IOException;

    /**
     * 获取访问地址
     * @param groupName
     * @param path
     * @return
     */
    String getURL(String groupName, String path);

    /**
     * 获取文件流
     * @param groupName
     * @param path
     * @return
     * @throws IOException
     */
    InputStream getInputStream(String groupName, String path) throws IOException;

     /**
     * 获取字节数据
     * @param groupName
     * @param path
     * @return
     * @throws IOException
     */
    byte[] getByteArray(String groupName, String path) throws IOException;
}

测试代码:

@Autowired
private InputStreamStore inputStreamStore;

private static String path;

@Test
@Order(1)
public void testPropertyStore() throws IOException {
    StoreResponse response = inputStreamStore.store("group", "jpeg",
            new FileInputStream("/Users/rick/jkxyx205/tmp/fileupload/demo/1.jpg"));
    System.out.println(response.getGroupName());
    System.out.println(response.getPath());
    System.out.println(response.getFullPath());
    System.out.println(response.getUrl());
    path = response.getPath();
}

@Test
@Order(2)
public void getURL() {
    String url = inputStreamStore.getURL("group", path);
    System.out.println(url);
}

@Test
@Order(3)
public void getInputStream() throws IOException {
    InputStream is = inputStreamStore.getInputStream("group", path);
    FileUtils.copyInputStreamToFile(is, new File("/Users/rick/jkxyx205/tmp/fileupload/download/1.png"));
    is.close();
}

@Test
@Order(Order.DEFAULT)
public void testPropertyDelete() throws IOException {
    inputStreamStore.delete("group", path);
}

sharp-database中的BaseDAOImpl使用指北

如何使用

BaseDAOImpl 的实现依赖于 SQLUtilsSharpService,主要是表的面向对象的CRUD操作。

让DAO继承 BaseDAOImpl。 实体对象继承BaseEntity

@SuperBuilder
@Getter
@Setter
@NoArgsConstructor
public class BaseEntity extends SimpleEntity {

    @Column(updatable = false)
    private Long createdBy;

    @Column(updatable = false)
    private Instant createdAt;

    private Long updatedBy;

    private Instant updatedAt;

    @Column(BaseEntityConstants.LOGIC_DELETE_COLUMN_NAME)
    private Boolean deleted;

}

`SimpleEntity.java`
```java
@SuperBuilder
@Getter
@Setter
@NoArgsConstructor
public class SimpleEntity {

    @Id
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        if (obj instanceof SimpleEntity) {
            SimpleEntity dataEntity = (SimpleEntity)obj ;
            if (dataEntity.id != null && dataEntity.id.equals(id))
                return true;
        }

        return false;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37)
                .append(id).toHashCode();
    }
}

手动指定表名和字段(字段的顺序要和entity属性的定义的顺序相同)

@Repository
public class ProjectDAO extends BaseDAOImpl<Project> {

    public ProjectDAO() {
        super("t_project", "id,title,description,cover_url,owner_id,created_by,created_at,updated_by,updated_at,is_deleted", "id");
    }
}

使用注解自动识别(推荐)

@Repository
public class ProjectGroupDAO extends BaseDAOImpl<ProjectGroup> {
}

ProjectGroup.java

@TableName("t_project_group")
@Getter
@Setter
public class ProjectGroup extends BaseEntity {

    private String title;

    private Long parentId;

    @ColumnName("group_id")
    private Long groupId;
}

如果没有注解 @ColumnName ,默认列名为驼峰名转下划线;没有注解 @TableName 默认表名为驼峰名转下划线。@Transient不参与持久化;@Id主键标识。

不指定范型

@Repository
public class MapDAO extends BaseDAOImpl {

    public MapDAO() {
        super("t_project", "id,title,description,cover_url,owner_id,created_by,created_at,updated_by,updated_at,is_deleted", "id");
    }
}

跟指定范型的区别 select 返回的对象由范型的实体变成了 Map 接收。insert update不能处理对象参数。 等价于下面的代码:

@Repository
public class MapDAO extends BaseDAOImpl<Map> {

    public MapDAO() {
        super("t_project", "id,title,description,cover_url,owner_id,created_by,created_at,updated_by,updated_at,is_deleted", "id");
    }
}

测试

添加数据insert

  • 添加数据(数组参数)
public void insert(Object[] params)
@Test
public void testInsert() {
    // id,title,description,created_by,created_at,updated_by,updated_at,is_deleted,cover_url,owner_id
    projectService.insert(new Object[]{
            null, "title-xxl", "description", 1, null, 1, null, null, "", 1
    });
}

id created_at updated_at is_deleted 会使用默认的 DefaultColumnAutoFill 填充。

DefaultColumnAutoFill.java

public class DefaultColumnAutoFill implements ColumnAutoFill {

    @Override
    public Map<String, Object> insertFill() {
        Map<String, Object> fillMap = Maps.newHashMapWithExpectedSize(4);
        LocalDateTime now = LocalDateTime.now();
        fillMap.put("id", IdGenerator.getSequenceId());
        fillMap.put("created_at", now);
        fillMap.put("updated_at", now);
        fillMap.put("is_deleted", false);
        return fillMap;
    }

    @Override
    public Map<String, Object> updateFill() {
        Map<String, Object> fillMap = Maps.newHashMapWithExpectedSize(2);
        LocalDateTime now = LocalDateTime.now();
        fillMap.put("updated_at", now);
        return fillMap;
    }
}

也可以自已定义使 created_by updated_by 也会被自动填充

@Bean
public ColumnAutoFill fill() {
    return new ColumnAutoFill() {
        @Override
        public Map<String, Object> insertFill() {
            Map<String, Object> fill = new DefaultColumnAutoFill().insertFill();
            fill.put("created_by", 0);
            fill.put("updated_by", 0);
            return fill;
        }

        @Override
        public Map<String, Object> updateFill() {
            Map<String, Object> fill = new DefaultColumnAutoFill().updateFill();
            fill.put("updated_by", 0);
            return fill;
        }
    };
}
  • 添加数据(对象参数)
    public int insert(T t) 
@Test
public void testInsert() {
    Project project = new Project();
    project.setTitle("save T");
    project.setDescription("dddd");
    projectDAO.insert(project);
    System.out.println(project.getId());
    Assert.assertNotNull(project.getId());
}
  • 批量添加数据
public void insert(List<?> paramsList)

数组参数

@Test
public void testInsertAll() {
    List<Object[]> paramsList = Lists.newArrayList(
            new Object[]{
                    IDUtils.genItemId(), "title-b", "description", 1, LocalDateTime.now(), 1, LocalDateTime.now(), 0, "", 1
            },
            new Object[]{
                    IDUtils.genItemId(), "title-c", "description", 1, LocalDateTime.now(), 1, LocalDateTime.now(), 0, "", 1
            });
    projectService.insert(paramsList);
}

对象参数

@Test
public void testInsert() {
    Project project1 = new Project();
    project1.setTitle("save TT1");
    project1.setDescription("dddd1");
    project1.setCoverUrl("http://baidu.com");

    Project project2 = new Project();
    project2.setTitle("save TT2");
    project2.setDescription("dddd2");

    List<Project> list = Lists.newArrayList(project1, project2);

    projectDAO.insert(list);
}

删除数据delete

  • 根据ID删除数据
public void deleteById(Serializable id)
@Test
public void testDeleteById() {
    projectService.deleteById(11L);
}
  • 根据ID批量删除数据
public void deleteByIds(String ids)
@Test
public void testDeleteByIds() {
    projectService.deleteByIds("1632391637675732, 1632391637678836");
}

修改数据update

  • 根据ID更新数组参数
public int update(Object[] params, Serializable id)
@Test
public void testUpdate() {
    projectService.update(new Object[]{
        "title-update", "description", 1, LocalDateTime.now(), 1, LocalDateTime.now(), 0, "", 1
    }, 1341320525040701442L);
}
  • 根据ID更新数据,指定更新列
public int update(String updateColumnNames, Object[] params, Serializable id)
@Test
public void testUpdate2() {
    int count = projectService.update("title", new Object[]{
            "title-update",
    }, 1341320525040701442L);

    Assert.assertEquals(1, count);
}
  • 根据ID更新对象参数
public int update(T t)
@Test
public void testUpdate() {
    ProjectGroup2 project = new ProjectGroup2();

    project.setId(473905081847549952L);
    project.setTitle("yes =>");
    project.setGroupId(0L);
    project.setParentId(11L);
    project.setDeleted(true);
    Assert.assertEquals(1, projectGroup2DAO.update(project));
}

查询数据select

  • 根据ID查找数据
public Optional<T> selectById(Serializable id)
@Test
public void testSelectById() {
    Optional<Project> optional = projectService.selectById(1341320525040701442L);
    Project project = optional.get();
    Assert.assertEquals("title-update", project.getTitle());

    Optional<Project> optional2 = projectService.selectById(-12);
    Assert.assertEquals(false, optional2.isPresent());
}
  • 根据ID批量查找数据
public List<T> selectByIds(String ids)
public List<T> selectByIds(Collection<?> ids)
@Test
public void testSelectByIds() {
    List<Project> list = projectService.selectByIds("1341320525040701442,1341368810614910978");
    Assert.assertEquals(2, list.size());

    List<Project> list2 = projectService.selectByIds(Arrays.asList(1341320525040701442L));
    Assert.assertEquals(1, list2.size());
}
  • 根据条件查找数据1
public List<T> selectByParams(Map<String, Object> params)
@Test
public void testParams() {
    Map<String, Object> params = Maps.newHashMapWithExpectedSize(2);
    params.put("title", "haha");
    params.put("description", "world");
    List<Project> list = projectService.selectByParams(params);
    Assert.assertEquals(1, list.size());
}
  • 根据条件查找数据2
/**
 * name=23&age=15,13 => name=:name AND age IN(:age)
 *
 * @param queryString
 * @return
 */
public List<T> selectByParams(String queryString)
@Test
public void testParams2() {
    List<Project> list = projectService.selectByParams("title=haha&created_by=156629745675451,156629745675452");
    Assert.assertEquals(4, list.size());
}
  • 根据条件查找数据3,自定义条件SQL
public List<T> selectByParams(String queryString, String conditionSQL)
@Test
public void testParams3() {
    List<Project> list = projectService.selectByParams("title=haha&created_by=156629745675452,156629745675454", "title=:title OR created_by IN(:created_by)");
    Assert.assertEquals(6, list.size());
}
  • 获取所有数据
public List<T> selectAll()
@Test
public void testSelectAll() {
    List<Project> list = projectService.selectAll();
    System.out.println(list.size());
}

sharp-common集成Spring Web

sharp-common 集成Spring Web主要体现在以下3个方面

  • 通过注解 @RestControllerAdvice 封装异常异常 ApiExceptionHandler
  • 注入国际化的工具类 MessageUtils
  • 封装 Result 返回对象

集成

  • 添加包扫描,将 ApiExceptionHandlerMessageUtils纳入Spring上下文,@EnableResultWrapped 自动包裹 Result 对象。
    @Configuration
    @Import({ApiExceptionHandler.class, MessageUtils.class})
    @EnableResultWrapped
    public class MvcConfig implements WebMvcConfigurer {}
  • 国际化文件
# messages_en_US.properties
BEAN_VALIDATE_ERROR=Params validate error:{0}
MAX_TRY_LOGIN_ERROR=The account failed to log in {0} times, please wait {1} minutes and try again

# messages_zh_CN.properties
BEAN_VALIDATE_ERROR=参数验证错误:{0}
MAX_TRY_LOGIN_ERROR=该账号{0}次登录失败后,被锁定{1}分钟
  • 添加业务异常枚举
    @Getter
    @ToString
    public enum ExceptionCode {

        BEAN_VALIDATE_ERROR(30002, "BEAN_VALIDATE_ERROR"),
        PRICE_CRAWLER_ERROR(40004, "网站价格获取失败");

        private int code;

        private String msg;

        ExceptionCode(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        public ExceptionResult&lt;String&gt; result() {
            return new ExceptionResult&lt;&gt;(getCode(), getMsg());
        }
    }

BEAN_VALIDATE_ERROR 在国际化文件中进行了语言维护。

测试

@RestController
@RequestMapping("test")
public class TestController {

    @GetMapping("1")
    public Result test1() {
        return ResultUtils.success(MessageUtils.getMessage("MAX_TRY_LOGIN_ERROR", new Object[] {5, 20}));
    }

    @GetMapping("2")
    public Result test2() {
        int a = 1 / 0;
        return ResultUtils.success();
    }

    @GetMapping("3")
    public Result test3() {
        return ResultUtils.fail();
    }

    @GetMapping("4")
    public Result test4() {
        throw new BizException(ExceptionCode.FORMULA_ERROR.result());
    }

    @GetMapping("5")
    public Result test5() {
        throw new BizException(ExceptionCode.BEAN_VALIDATE_ERROR.result(), new Object[] {"姓名不能为空"});
    }

    @GetMapping("6")
    public int test5() {
        return 1;
    }

    @GetMapping("7")
    @UnWrapped
    public int test7() {
        return 7;
    }
}

注解 @UnWrapped 表示不需要被 Result 对象包裹。

sharp-sms使用指北

简介

sharp-sms 是基于阿里云的短信配置包依赖。

如何使用

添加 pom 依赖

<dependency>
    <groupId>com.rick.sms</groupId>
    <artifactId>sharp-sms</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>

阿里云配置

application.yml

ali:
  access-key:
    id: LTAI4FkT323Aq4WU144WgZpwRA6D
    secret: U9eehrtFFsLt3SsP8E232TBhiyNh8Ghwvh

需要在阿里云的短信平台去获取

测试

@Autowired
private Sender sender;

@Test
public void testSend() {
    Map<String, String> params = new HashMap<>(3);
    params.put("name", "hello");
    params.put("value", "world");
    sender.send("18888888888", "XX公司", "SMS_202587654", params);
}

「XX公司」表示 签名,「SMS_202587654」表示 模版。 需要在阿里云的短信平台去配置。params参数就来自于 模版 中的变量。