biMap = HashBiMap.create();
+ map.forEach(biMap::forcePut);
+ return biMap.inverse();
+ }
+
+}
diff --git a/blade-digital-api/digital-wallet-api/pom.xml b/blade-digital-api/digital-wallet-api/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..520ae42e7050a133ff2e67901ccfc17c73bba86d
--- /dev/null
+++ b/blade-digital-api/digital-wallet-api/pom.xml
@@ -0,0 +1,18 @@
+
+
+
+ 4.0.0
+
+ blade-digital-api
+ org.springblade
+ 2.7.1
+
+
+ digital-wallet-api
+ ${project.artifactId}
+ ${blade.project.version}
+ jar
+
+
+
diff --git a/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/dto/WalletRecordDTO.java b/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/dto/WalletRecordDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..892f567546e4440ef4a031e9da27dfd48a022d3b
--- /dev/null
+++ b/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/dto/WalletRecordDTO.java
@@ -0,0 +1,18 @@
+package org.springblade.digital.wallet.dto;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 数据传输对象实体类
+ *
+ * @author Blade
+ * @since 2020-07-30
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WalletRecordDTO extends WalletRecord {
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/entity/WalletRecord.java b/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/entity/WalletRecord.java
new file mode 100644
index 0000000000000000000000000000000000000000..7d6d0fc2b080958f77faaea8ba5e21b4db7d4d11
--- /dev/null
+++ b/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/entity/WalletRecord.java
@@ -0,0 +1,215 @@
+package org.springblade.digital.wallet.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import org.springblade.core.mp.base.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * 实体类
+ *
+ * @author Blade
+ * @since 2020-07-30
+ */
+@Data
+@TableName("wallet_record")
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "WalletRecord对象", description = "WalletRecord对象")
+public class WalletRecord extends BaseEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键id
+ */
+ @ApiModelProperty(value = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long id;
+ /**
+ * dapp名称
+ */
+ @ApiModelProperty(value = "dapp名称")
+ private String dappName;
+ /**
+ * dapp下载地址
+ */
+ @ApiModelProperty(value = "dapp下载地址")
+ private String dappDownAddress;
+ /**
+ * dapp描述
+ */
+ @ApiModelProperty(value = "dapp描述")
+ private String dappDescribe;
+ /**
+ * 钱包名称
+ */
+ @ApiModelProperty(value = "钱包名称")
+ private String name;
+ /**
+ * 币种名称
+ */
+ @ApiModelProperty(value = "币种名称")
+ private String coinName;
+ /**
+ * 币种名称缩写
+ */
+ @ApiModelProperty(value = "币种名称缩写")
+ private String coinAbbr;
+ /**
+ * 钱包公钥地址
+ */
+ @ApiModelProperty(value = "钱包公钥地址")
+ private String puAddress;
+ /**
+ * 钱包私钥地址
+ */
+ @ApiModelProperty(value = "钱包私钥地址")
+ private String prAddress;
+ /**
+ * 钱包交易memo备注信息
+ */
+ @ApiModelProperty(value = "钱包交易memo备注信息")
+ private String memo;
+ /**
+ * 钱包钥匙备份信息
+ */
+ @ApiModelProperty(value = "钱包钥匙备份信息")
+ private String keyStore;
+ /**
+ * 钱包交易支付密码
+ */
+ @ApiModelProperty(value = "钱包交易支付密码")
+ private String payPassword;
+ /**
+ * 钱包助记词信息
+ */
+ @ApiModelProperty(value = "钱包助记词信息")
+ private String mnemonicWord;
+ /**
+ * 钱包助记词截图地址
+ */
+ @ApiModelProperty(value = "钱包助记词截图地址")
+ private String mnemonicWordPic;
+
+// public Long getId() {
+// return id;
+// }
+//
+// public void setId(Long id) {
+// this.id = id;
+// }
+//
+// public String getwDappName() {
+// return wDappName;
+// }
+//
+// public void setwDappName(String wDappName) {
+// this.wDappName = wDappName;
+// }
+//
+// public String getwDappDownAddress() {
+// return wDappDownAddress;
+// }
+//
+// public void setwDappDownAddress(String wDappDownAddress) {
+// this.wDappDownAddress = wDappDownAddress;
+// }
+//
+// public String getwDappDescribe() {
+// return wDappDescribe;
+// }
+//
+// public void setwDappDescribe(String wDappDescribe) {
+// this.wDappDescribe = wDappDescribe;
+// }
+//
+// public String getwName() {
+// return wName;
+// }
+//
+// public void setwName(String wName) {
+// this.wName = wName;
+// }
+//
+// public String getwCoinName() {
+// return wCoinName;
+// }
+//
+// public void setwCoinName(String wCoinName) {
+// this.wCoinName = wCoinName;
+// }
+//
+// public String getwCoinAbbr() {
+// return wCoinAbbr;
+// }
+//
+// public void setwCoinAbbr(String wCoinAbbr) {
+// this.wCoinAbbr = wCoinAbbr;
+// }
+//
+// public String getwPuAddress() {
+// return wPuAddress;
+// }
+//
+// public void setwPuAddress(String wPuAddress) {
+// this.wPuAddress = wPuAddress;
+// }
+//
+// public String getwPrAddress() {
+// return wPrAddress;
+// }
+//
+// public void setwPrAddress(String wPrAddress) {
+// this.wPrAddress = wPrAddress;
+// }
+//
+// public String getwMemo() {
+// return wMemo;
+// }
+//
+// public void setwMemo(String wMemo) {
+// this.wMemo = wMemo;
+// }
+//
+// public String getKeyStore() {
+// return keyStore;
+// }
+//
+// public void setKeyStore(String keyStore) {
+// this.keyStore = keyStore;
+// }
+//
+// public String getwPayPassword() {
+// return wPayPassword;
+// }
+//
+// public void setwPayPassword(String wPayPassword) {
+// this.wPayPassword = wPayPassword;
+// }
+//
+// public String getwMnemonicWord() {
+// return wMnemonicWord;
+// }
+//
+// public void setwMnemonicWord(String wMnemonicWord) {
+// this.wMnemonicWord = wMnemonicWord;
+// }
+//
+// public String getwMnemonicWordPic() {
+// return wMnemonicWordPic;
+// }
+//
+// public void setwMnemonicWordPic(String wMnemonicWordPic) {
+// this.wMnemonicWordPic = wMnemonicWordPic;
+// }
+}
diff --git a/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/vo/WalletRecordVO.java b/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/vo/WalletRecordVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..6ac6417d6034512445e0d4013a04c8264281a9a7
--- /dev/null
+++ b/blade-digital-api/digital-wallet-api/src/main/java/org/springblade/digital/wallet/vo/WalletRecordVO.java
@@ -0,0 +1,20 @@
+package org.springblade.digital.wallet.vo;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import io.swagger.annotations.ApiModel;
+
+/**
+ * 视图实体类
+ *
+ * @author Blade
+ * @since 2020-07-30
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "WalletRecordVO对象", description = "WalletRecordVO对象")
+public class WalletRecordVO extends WalletRecord {
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/blade-digital-api/pom.xml b/blade-digital-api/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..11550c78fc6705f693c6fce1601420e8de1fb968
--- /dev/null
+++ b/blade-digital-api/pom.xml
@@ -0,0 +1,74 @@
+
+
+
+ 4.0.0
+
+ org.springblade
+ SpringBlade
+ 2.7.1
+
+
+ blade-digital-api
+ ${project.artifactId}
+ 2.7.1
+ pom
+ 数字资产微服务api集合
+
+
+ digital-wallet-api
+
+
+
+ org.springblade
+ blade-core-mybatis
+ ${blade.tool.version}
+
+
+ org.springframework.cloud
+ spring-cloud-starter-openfeign
+
+
+ io.springfox
+ springfox-swagger2
+ ${swagger.version}
+
+
+ io.swagger
+ swagger-models
+
+
+
+
+ io.swagger
+ swagger-models
+ ${swagger.models.version}
+
+
+ net.dreamlu
+ mica-auto
+ ${mica.auto.version}
+ provided
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+ ${project.name}
+
+
+
+ com.spotify
+ docker-maven-plugin
+ ${docker.plugin.version}
+
+ true
+
+
+
+
+
diff --git a/blade-digital-service/digital-wallet/Dockerfile b/blade-digital-service/digital-wallet/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d92ffeead32f5eca86ea04c09cad51039233fa93
--- /dev/null
+++ b/blade-digital-service/digital-wallet/Dockerfile
@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER w1999wtw3537@sina.com
+
+RUN mkdir -p /digital/wallet
+
+WORKDIR /digital/wallet
+
+EXPOSE 8201
+
+ADD ./target/digital-wallet.jar ./app.jar
+
+ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
+
+CMD ["--spring.profiles.active=test"]
diff --git a/blade-digital-service/digital-wallet/pom.xml b/blade-digital-service/digital-wallet/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4227cc671e5725ef9399cdd1a877fd897825701e
--- /dev/null
+++ b/blade-digital-service/digital-wallet/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+ 4.0.0
+
+ blade-digital-service
+ org.springblade
+ 2.7.1
+
+
+ digital-wallet
+ ${project.artifactId}
+ ${blade.project.version}
+ jar
+
+
+
+
+ org.springblade
+ blade-core-boot
+ ${blade.tool.version}
+
+
+ org.springblade
+ digital-wallet-api
+ ${blade.tool.version}
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+
+
+ package
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/WalletApplication.java b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/WalletApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..df295c64f44ddf9c5923e1e45ccdbf294b1ca6f0
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/WalletApplication.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet;
+
+import org.springblade.core.launch.BladeApplication;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springframework.cloud.client.SpringCloudApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+/**
+ * 系统模块启动器
+ * @author Chill
+ */
+@SpringCloudApplication
+@EnableFeignClients(AppConstant.BASE_PACKAGES)
+public class WalletApplication {
+ // AppConstant.APPLICATION_SYSTEM_NAME
+ public static void main(String[] args) {
+ BladeApplication.run("digital-wallet", WalletApplication.class, args);
+ }
+
+}
+
diff --git a/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/controller/WalletRecordController.java b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/controller/WalletRecordController.java
new file mode 100644
index 0000000000000000000000000000000000000000..0c9d4dab9a3f3b66f72cebc3c5ec3cd09e3eef43
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/controller/WalletRecordController.java
@@ -0,0 +1,112 @@
+package org.springblade.digital.wallet.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import javax.validation.Valid;
+
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestParam;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+import org.springblade.digital.wallet.wrapper.WalletRecordWrapper;
+import org.springblade.digital.wallet.service.WalletRecordService;
+import org.springblade.core.boot.ctrl.BladeController;
+
+/**
+ * 控制器
+ *
+ * @author Blade
+ * @since 2020-07-30
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/walletrecord")
+@Api(value = "", tags = "接口")
+public class WalletRecordController extends BladeController {
+
+ private WalletRecordService walletRecordService;
+
+ /**
+ * 详情
+ */
+ @GetMapping("/detail")
+ @ApiOperationSupport(order = 1)
+ @ApiOperation(value = "详情", notes = "传入walletRecord")
+ public R detail(WalletRecord walletRecord) {
+ WalletRecord detail = walletRecordService.getOne(Condition.getQueryWrapper(walletRecord));
+ return R.data(WalletRecordWrapper.build().entityVO(detail));
+ }
+
+ /**
+ * 分页
+ */
+ @GetMapping("/list")
+ @ApiOperationSupport(order = 2)
+ @ApiOperation(value = "分页", notes = "传入walletRecord")
+ public R> list(WalletRecord walletRecord, Query query) {
+ IPage pages = walletRecordService.page(Condition.getPage(query), Condition.getQueryWrapper(walletRecord));
+ return R.data(WalletRecordWrapper.build().pageVO(pages));
+ }
+
+ /**
+ * 自定义分页
+ */
+ @GetMapping("/page")
+ @ApiOperationSupport(order = 3)
+ @ApiOperation(value = "分页", notes = "传入walletRecord")
+ public R> page(WalletRecordVO walletRecord, Query query) {
+ IPage pages = walletRecordService.selectWalletRecordPage(Condition.getPage(query), walletRecord);
+ return R.data(pages);
+ }
+
+ /**
+ * 新增
+ */
+ @PostMapping("/save")
+ @ApiOperationSupport(order = 4)
+ @ApiOperation(value = "新增", notes = "传入walletRecord")
+ public R save(@Valid @RequestBody WalletRecord walletRecord) {
+ return R.status(walletRecordService.save(walletRecord));
+ }
+
+ /**
+ * 修改
+ */
+ @PostMapping("/update")
+ @ApiOperationSupport(order = 5)
+ @ApiOperation(value = "修改", notes = "传入walletRecord")
+ public R update(@Valid @RequestBody WalletRecord walletRecord) {
+ return R.status(walletRecordService.updateById(walletRecord));
+ }
+
+ /**
+ * 新增或修改
+ */
+ @PostMapping("/submit")
+ @ApiOperationSupport(order = 6)
+ @ApiOperation(value = "新增或修改", notes = "传入walletRecord")
+ public R submit(@Valid @RequestBody WalletRecord walletRecord) {
+ return R.status(walletRecordService.saveOrUpdate(walletRecord));
+ }
+
+
+ /**
+ * 删除
+ */
+ @PostMapping("/remove")
+ @ApiOperationSupport(order = 7)
+ @ApiOperation(value = "逻辑删除", notes = "传入ids")
+ public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+ return R.status(walletRecordService.deleteLogic(Func.toLongList(ids)));
+ }
+
+
+}
diff --git a/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.java b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..aea19157096dcc6c4bddcfe2059dd284b33f9932
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.mapper;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import java.util.List;
+
+/**
+ * Mapper 接口
+ *
+ * @author Blade
+ * @since 2020-07-30
+ */
+public interface WalletRecordMapper extends BaseMapper {
+
+ /**
+ * 自定义分页
+ *
+ * @param page
+ * @param walletRecord
+ * @return
+ */
+ List selectWalletRecordPage(IPage page, WalletRecordVO walletRecord);
+
+}
diff --git a/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.xml b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5e32930e48485511853a963f7158cda6e1ba48c2
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select * from wallet_record where is_deleted = 0
+
+
+
diff --git a/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/service/WalletRecordService.java b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/service/WalletRecordService.java
new file mode 100644
index 0000000000000000000000000000000000000000..90231b48f39f49492ecb080c53de99703a99b80b
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/service/WalletRecordService.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.service;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+import org.springblade.core.mp.base.BaseService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+/**
+ * 服务类
+ *
+ * @author Blade
+ * @since 2020-07-30
+ */
+public interface WalletRecordService extends BaseService {
+
+ /**
+ * 自定义分页
+ *
+ * @param page
+ * @param walletRecord
+ * @return
+ */
+ IPage selectWalletRecordPage(IPage page, WalletRecordVO walletRecord);
+
+}
diff --git a/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/service/impl/WalletRecordServiceImpl.java b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/service/impl/WalletRecordServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..6bcd8d985d6aee875ccf6717472da4397e4913c2
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/service/impl/WalletRecordServiceImpl.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.service.impl;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+import org.springblade.digital.wallet.mapper.WalletRecordMapper;
+import org.springblade.digital.wallet.service.WalletRecordService;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springframework.stereotype.Service;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+/**
+ * 服务实现类
+ *
+ * @author Blade
+ * @since 2020-07-30
+ */
+@Service
+public class WalletRecordServiceImpl extends BaseServiceImpl implements WalletRecordService {
+
+ @Override
+ public IPage selectWalletRecordPage(IPage page, WalletRecordVO walletRecord) {
+ return page.setRecords(baseMapper.selectWalletRecordPage(page, walletRecord));
+ }
+
+}
diff --git a/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/wrapper/WalletRecordWrapper.java b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/wrapper/WalletRecordWrapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..d7398bc45ff42435c3430a428f3591d0dca50bf5
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/java/org/springblade/digital/wallet/wrapper/WalletRecordWrapper.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.wrapper;
+
+import org.springblade.common.constant.CommonConstant;
+import org.springblade.core.mp.support.BaseEntityWrapper;
+import org.springblade.core.tool.node.ForestNodeMerger;
+import org.springblade.core.tool.node.INode;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.SpringUtil;
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.service.WalletRecordService;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 包装类,返回视图层所需的字段
+ *
+ * @author Chill
+ */
+public class WalletRecordWrapper extends BaseEntityWrapper {
+
+ private static WalletRecordService walletRecordService;
+
+ static {
+ walletRecordService = SpringUtil.getBean(WalletRecordService.class);
+ }
+
+ public static WalletRecordWrapper build() {
+ return new WalletRecordWrapper();
+ }
+
+ @Override
+ public WalletRecordVO entityVO(WalletRecord walletRecord) {
+ WalletRecordVO walletRecordVO = BeanUtil.copy(walletRecord, WalletRecordVO.class);
+
+ return walletRecordVO;
+ }
+
+}
diff --git a/blade-digital-service/digital-wallet/src/main/resources/application-dev.yml b/blade-digital-service/digital-wallet/src/main/resources/application-dev.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a54bccc070f71271ac5760aebe68b3e966085d2e
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/resources/application-dev.yml
@@ -0,0 +1,11 @@
+#服务器端口
+server:
+ port: 8201
+
+#数据源配置
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: ${blade.datasource.dev.url}
+ username: ${blade.datasource.dev.username}
+ password: ${blade.datasource.dev.password}
diff --git a/blade-digital-service/digital-wallet/src/main/resources/application-prod.yml b/blade-digital-service/digital-wallet/src/main/resources/application-prod.yml
new file mode 100644
index 0000000000000000000000000000000000000000..25635bc41de5f77481e3d6ea0e52cf9444e30137
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/resources/application-prod.yml
@@ -0,0 +1,10 @@
+#服务器端口
+server:
+ port: 8106
+
+#数据源配置
+spring:
+ datasource:
+ url: ${blade.datasource.prod.url}
+ username: ${blade.datasource.prod.username}
+ password: ${blade.datasource.prod.password}
diff --git a/blade-digital-service/digital-wallet/src/main/resources/application-test.yml b/blade-digital-service/digital-wallet/src/main/resources/application-test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..fb5cd8f7318cf5a3ba05543b595ac6b3a600883d
--- /dev/null
+++ b/blade-digital-service/digital-wallet/src/main/resources/application-test.yml
@@ -0,0 +1,10 @@
+#服务器端口
+server:
+ port: 8106
+
+#数据源配置
+spring:
+ datasource:
+ url: ${blade.datasource.test.url}
+ username: ${blade.datasource.test.username}
+ password: ${blade.datasource.test.password}
diff --git a/blade-digital-service/pom.xml b/blade-digital-service/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..75c91630f0f0db7a4d3968b160dc4a775fcbf5ae
--- /dev/null
+++ b/blade-digital-service/pom.xml
@@ -0,0 +1,52 @@
+
+
+
+ 4.0.0
+
+ org.springblade
+ SpringBlade
+ 2.7.1
+
+
+ blade-digital-service
+ ${project.artifactId}
+ 2.7.1
+ pom
+ 数字资产微服务集合
+
+ digital-wallet
+
+
+
+ org.springblade
+ blade-common
+ ${blade.project.version}
+
+
+
+
+
+
+ com.spotify
+ docker-maven-plugin
+ ${docker.plugin.version}
+
+ ${docker.registry.url}/blade/${project.artifactId}:${project.version}
+ ${project.basedir}
+ ${docker.registry.host}
+
+
+ /
+ ${project.build.directory}
+ ${project.build.finalName}.jar
+
+
+ ${docker.registry.url}
+ ${docker.registry.url}
+ true
+
+
+
+
+
diff --git a/blade-gateway/src/main/resources/bootstrap.yml b/blade-gateway/src/main/resources/bootstrap.yml
index e875240d64ebab8a212a87e98d0ac08541bddcc8..f2ee4f22500c2a601cf6a1be48883de35384a8dd 100644
--- a/blade-gateway/src/main/resources/bootstrap.yml
+++ b/blade-gateway/src/main/resources/bootstrap.yml
@@ -13,7 +13,6 @@ spring:
loadbalancer:
retry:
enabled: true
-
# 聚合文档配置
blade:
document:
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/controller/WalletRecordController.java b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/controller/WalletRecordController.java
new file mode 100644
index 0000000000000000000000000000000000000000..86a89d6783ed9d3de37d1ed54af1abf7a01a001d
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/controller/WalletRecordController.java
@@ -0,0 +1,128 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.ApiParam;
+import lombok.AllArgsConstructor;
+import javax.validation.Valid;
+
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.RequestParam;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+import org.springblade.digital.wallet.wrapper.WalletRecordWrapper;
+import org.springblade.digital.wallet.service.IWalletRecordService;
+import org.springblade.core.boot.ctrl.BladeController;
+import java.util.List;
+
+/**
+ * 控制器
+ *
+ * @author Blade
+ * @since 2020-08-13
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/walletrecord")
+@Api(value = "", tags = "接口")
+public class WalletRecordController extends BladeController {
+
+ private IWalletRecordService walletRecordService;
+
+ /**
+ * 详情
+ */
+ @GetMapping("/detail")
+ @ApiOperationSupport(order = 1)
+ @ApiOperation(value = "详情", notes = "传入walletRecord")
+ public R detail(WalletRecord walletRecord) {
+ WalletRecord detail = walletRecordService.getOne(Condition.getQueryWrapper(walletRecord));
+ return R.data(WalletRecordWrapper.build().entityVO(detail));
+ }
+
+ /**
+ * 分页
+ */
+ @GetMapping("/list")
+ @ApiOperationSupport(order = 2)
+ @ApiOperation(value = "分页", notes = "传入walletRecord")
+ public R> list(WalletRecord walletRecord, Query query) {
+ IPage pages = walletRecordService.page(Condition.getPage(query), Condition.getQueryWrapper(walletRecord));
+ return R.data(WalletRecordWrapper.build().pageVO(pages));
+ }
+
+ /**
+ * 自定义分页
+ */
+ @GetMapping("/page")
+ @ApiOperationSupport(order = 3)
+ @ApiOperation(value = "分页", notes = "传入walletRecord")
+ public R> page(WalletRecordVO walletRecord, Query query) {
+ IPage pages = walletRecordService.selectWalletRecordPage(Condition.getPage(query), walletRecord);
+ return R.data(pages);
+ }
+
+ /**
+ * 新增
+ */
+ @PostMapping("/save")
+ @ApiOperationSupport(order = 4)
+ @ApiOperation(value = "新增", notes = "传入walletRecord")
+ public R save(@Valid @RequestBody WalletRecord walletRecord) {
+ return R.status(walletRecordService.save(walletRecord));
+ }
+
+ /**
+ * 修改
+ */
+ @PostMapping("/update")
+ @ApiOperationSupport(order = 5)
+ @ApiOperation(value = "修改", notes = "传入walletRecord")
+ public R update(@Valid @RequestBody WalletRecord walletRecord) {
+ return R.status(walletRecordService.updateById(walletRecord));
+ }
+
+ /**
+ * 新增或修改
+ */
+ @PostMapping("/submit")
+ @ApiOperationSupport(order = 6)
+ @ApiOperation(value = "新增或修改", notes = "传入walletRecord")
+ public R submit(@Valid @RequestBody WalletRecord walletRecord) {
+ return R.status(walletRecordService.saveOrUpdate(walletRecord));
+ }
+
+
+ /**
+ * 删除
+ */
+ @PostMapping("/remove")
+ @ApiOperationSupport(order = 7)
+ @ApiOperation(value = "逻辑删除", notes = "传入ids")
+ public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
+ return R.status(walletRecordService.deleteLogic(Func.toLongList(ids)));
+ }
+
+
+}
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/dto/WalletRecordDTO.java b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/dto/WalletRecordDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e0338bfd96998f91e76fe56adc5c0e88e27b80f
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/dto/WalletRecordDTO.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.dto;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 数据传输对象实体类
+ *
+ * @author Blade
+ * @since 2020-08-13
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WalletRecordDTO extends WalletRecord {
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/entity/WalletRecord.java b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/entity/WalletRecord.java
new file mode 100644
index 0000000000000000000000000000000000000000..ad4a81bbfad7c6c668598f67ca0a670d5786288d
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/entity/WalletRecord.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import org.springblade.core.mp.base.BaseEntity;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 实体类
+ *
+ * @author Blade
+ * @since 2020-08-13
+ */
+@Data
+@TableName("wallet_record")
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "WalletRecord对象", description = "WalletRecord对象")
+public class WalletRecord extends BaseEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ private Long id;
+ /**
+ * dapp名称
+ */
+ @ApiModelProperty(value = "dapp名称")
+ private String wDappName;
+ /**
+ * dapp下载地址
+ */
+ @ApiModelProperty(value = "dapp下载地址")
+ private String wDappDownAddress;
+ /**
+ * dapp描述
+ */
+ @ApiModelProperty(value = "dapp描述")
+ private String wDappDescribe;
+ /**
+ * 钱包名称
+ */
+ @ApiModelProperty(value = "钱包名称")
+ private String wName;
+ /**
+ * 币种名称
+ */
+ @ApiModelProperty(value = "币种名称")
+ private String wCoinName;
+ /**
+ * 币种名称缩写
+ */
+ @ApiModelProperty(value = "币种名称缩写")
+ private String wCoinAbbr;
+ /**
+ * 钱包公钥地址
+ */
+ @ApiModelProperty(value = "钱包公钥地址")
+ private String wPuAddress;
+ /**
+ * 钱包私钥地址
+ */
+ @ApiModelProperty(value = "钱包私钥地址")
+ private String wPrAddress;
+ /**
+ * 钱包交易memo备注信息
+ */
+ @ApiModelProperty(value = "钱包交易memo备注信息")
+ private String wMemo;
+ /**
+ * 钱包钥匙备份信息
+ */
+ @ApiModelProperty(value = "钱包钥匙备份信息")
+ private String keyStore;
+ /**
+ * 钱包交易支付密码
+ */
+ @ApiModelProperty(value = "钱包交易支付密码")
+ private String wPayPassword;
+ /**
+ * 钱包助记词信息
+ */
+ @ApiModelProperty(value = "钱包助记词信息")
+ private String wMnemonicWord;
+ /**
+ * 钱包助记词截图地址
+ */
+ @ApiModelProperty(value = "钱包助记词截图地址")
+ private String wMnemonicWordPic;
+
+
+}
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.java b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a44681647601ca8800d120728d8c825c0df0fcf
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.mapper;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import java.util.List;
+
+/**
+ * Mapper 接口
+ *
+ * @author Blade
+ * @since 2020-08-13
+ */
+public interface WalletRecordMapper extends BaseMapper {
+
+ /**
+ * 自定义分页
+ *
+ * @param page
+ * @param walletRecord
+ * @return
+ */
+ List selectWalletRecordPage(IPage page, WalletRecordVO walletRecord);
+
+}
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.xml b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0d6d73cc0f93cc0819ea747cc2cc67472dc8d9c4
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/mapper/WalletRecordMapper.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select * from wallet_record where is_deleted = 0
+
+
+
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/service/IWalletRecordService.java b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/service/IWalletRecordService.java
new file mode 100644
index 0000000000000000000000000000000000000000..af7e5688833191e65b0daf712ce4fa93e43ef539
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/service/IWalletRecordService.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.service;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+import org.springblade.core.mp.base.BaseService;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+/**
+ * 服务类
+ *
+ * @author Blade
+ * @since 2020-08-13
+ */
+public interface IWalletRecordService extends BaseService {
+
+ /**
+ * 自定义分页
+ *
+ * @param page
+ * @param walletRecord
+ * @return
+ */
+ IPage selectWalletRecordPage(IPage page, WalletRecordVO walletRecord);
+
+}
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/service/impl/WalletRecordServiceImpl.java b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/service/impl/WalletRecordServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..6081effd0092b45ab14fcf321206d5de69ef7ef0
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/service/impl/WalletRecordServiceImpl.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.service.impl;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import org.springblade.digital.wallet.vo.WalletRecordVO;
+import org.springblade.digital.wallet.mapper.WalletRecordMapper;
+import org.springblade.digital.wallet.service.IWalletRecordService;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springframework.stereotype.Service;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+/**
+ * 服务实现类
+ *
+ * @author Blade
+ * @since 2020-08-13
+ */
+@Service
+public class WalletRecordServiceImpl extends BaseServiceImpl implements IWalletRecordService {
+
+ @Override
+ public IPage selectWalletRecordPage(IPage page, WalletRecordVO walletRecord) {
+ return page.setRecords(baseMapper.selectWalletRecordPage(page, walletRecord));
+ }
+
+}
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/vo/WalletRecordVO.java b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/vo/WalletRecordVO.java
new file mode 100644
index 0000000000000000000000000000000000000000..85c12c586afc3b002ba13a9a672f13eefbef1976
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/org/springblade/digital/wallet/vo/WalletRecordVO.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2018-2028, Chill Zhuang 庄骞 (smallchill@163.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springblade.digital.wallet.vo;
+
+import org.springblade.digital.wallet.entity.WalletRecord;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import io.swagger.annotations.ApiModel;
+
+/**
+ * 视图实体类
+ *
+ * @author Blade
+ * @since 2020-08-13
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ApiModel(value = "WalletRecordVO对象", description = "WalletRecordVO对象")
+public class WalletRecordVO extends WalletRecord {
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/sql/walletrecord.menu.mysql b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/sql/walletrecord.menu.mysql
new file mode 100644
index 0000000000000000000000000000000000000000..75014d5c57c3e36d2e3b505aa72ac3b2168af211
--- /dev/null
+++ b/blade-ops/blade-develop/blade-ops/blade-develop/src/main/java/sql/walletrecord.menu.mysql
@@ -0,0 +1,11 @@
+INSERT INTO `blade_menu`(`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (0, 'walletrecord', '钱包信息记录管理', 'menu', '/wallet/walletrecord', NULL, 1, 1, 0, 1, NULL, 0);
+set @parentid = (SELECT LAST_INSERT_ID());
+INSERT INTO `blade_menu`(`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (@parentid, 'walletrecord_add', '新增', 'add', '/wallet/walletrecord/add', 'plus', 1, 2, 1, 1, NULL, 0);
+INSERT INTO `blade_menu`(`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (@parentid, 'walletrecord_edit', '修改', 'edit', '/wallet/walletrecord/edit', 'form', 2, 2, 1, 2, NULL, 0);
+INSERT INTO `blade_menu`(`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (@parentid, 'walletrecord_delete', '删除', 'delete', '/api/digital-wallet/walletrecord/remove', 'delete', 3, 2, 1, 3, NULL, 0);
+INSERT INTO `blade_menu`(`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (@parentid, 'walletrecord_view', '查看', 'view', '/wallet/walletrecord/view', 'file-text', 4, 2, 1, 2, NULL, 0);
diff --git a/blade-ops/blade-develop/pom.xml b/blade-ops/blade-develop/pom.xml
index eb93f7cc972f57f5d396f21862ac241caaf59234..d642600253fe31986fe1a63d455d5d9194ab4db2 100644
--- a/blade-ops/blade-develop/pom.xml
+++ b/blade-ops/blade-develop/pom.xml
@@ -55,6 +55,12 @@
blade-dict-api
${blade.project.version}
+
+ org.springblade
+ blade-core-test
+ ${blade.tool.version}
+ test
+
diff --git a/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeGenerator.java b/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeGenerator.java
index 6cb84af709a71479c13141fe4927b2de4216b6a1..2f994eba5fb51a1c43b522868277888182f9a3a2 100644
--- a/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeGenerator.java
+++ b/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeGenerator.java
@@ -23,66 +23,10 @@ import org.springblade.develop.support.BladeCodeGenerator;
*
* @author Chill
*/
-public class CodeGenerator {
+public interface CodeGenerator {
- /**
- * 代码生成的模块名
- */
- public static String CODE_NAME = "租户管理";
- /**
- * 代码所在服务名
- */
- public static String SERVICE_NAME = "blade-system";
- /**
- * 代码生成的包名
- */
- public static String PACKAGE_NAME = "org.springblade.system";
- /**
- * 前端代码生成所属系统
- */
- public static String SYSTEM_NAME = "sword";
- /**
- * 前端代码生成地址
- */
- public static String PACKAGE_WEB_DIR = "/Users/chill/Workspaces/product/Sword";
- /**
- * 需要去掉的表前缀
- */
- public static String[] TABLE_PREFIX = {"blade_"};
- /**
- * 需要生成的表名(两者只能取其一)
- */
- public static String[] INCLUDE_TABLES = {"blade_tenant"};
- /**
- * 需要排除的表名(两者只能取其一)
- */
- public static String[] EXCLUDE_TABLES = {};
- /**
- * 是否包含基础业务字段
- */
- public static Boolean HAS_SUPER_ENTITY = Boolean.TRUE;
- /**
- * 基础业务字段
- */
- public static String[] SUPER_ENTITY_COLUMNS = {"create_time", "create_user", "update_time", "update_user", "status", "is_deleted"};
+ // 初始化代码生成器
+ public BladeCodeGenerator initCodeGenerator(CodeModel codeModel);
- /**
- * RUN THIS
- */
- public static void main(String[] args) {
- BladeCodeGenerator generator = new BladeCodeGenerator();
- generator.setCodeName(CODE_NAME);
- generator.setServiceName(SERVICE_NAME);
- generator.setSystemName(SYSTEM_NAME);
- generator.setPackageName(PACKAGE_NAME);
- generator.setPackageWebDir(PACKAGE_WEB_DIR);
- generator.setTablePrefix(TABLE_PREFIX);
- generator.setIncludeTables(INCLUDE_TABLES);
- generator.setExcludeTables(EXCLUDE_TABLES);
- generator.setHasSuperEntity(HAS_SUPER_ENTITY);
- generator.setSuperEntityColumns(SUPER_ENTITY_COLUMNS);
- generator.run();
- }
-
}
diff --git a/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeGeneratorTest.java b/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeGeneratorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8669086a0e8e01943fa9f2a917a451627341b4ca
--- /dev/null
+++ b/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeGeneratorTest.java
@@ -0,0 +1,22 @@
+package org.springblade.test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.core.test.BladeBootTest;
+import org.springblade.core.test.BladeSpringRunner;
+import org.springblade.develop.DevelopApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@RunWith(BladeSpringRunner.class)
+@SpringBootTest(classes = DevelopApplication.class)
+@BladeBootTest(appName = AppConstant.APPLICATION_DEVELOP_NAME, profile = "dev", enableLoader = true)
+public class CodeGeneratorTest {
+
+ @Test
+ public void walletCodeGeneratorTest(){
+ WalletCode walletCode = new WalletCode();
+ walletCode.initCodeGenerator(null).run();
+ }
+
+}
diff --git a/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeModel.java b/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a34541fbf5e48d1820fa8bd26b3d4bbcc19f894
--- /dev/null
+++ b/blade-ops/blade-develop/src/test/java/org/springblade/test/CodeModel.java
@@ -0,0 +1,51 @@
+package org.springblade.test;
+
+import lombok.Data;
+
+@Data
+public class CodeModel {
+
+ /**
+ * 基础业务字段
+ */
+ public static String[] superEntityColumns = {"create_time", "create_user", "update_time", "update_user", "status", "is_deleted"};
+
+ /**
+ * 代码生成的模块名
+ */
+ private String codeName;
+ /**
+ * 代码所在服务名
+ */
+ private String serviceName;
+ /**
+ * 代码生成的包名
+ */
+ private String packageName;
+ /**
+ * 前端代码生成所属系统
+ */
+ private String systemName;
+ /**
+ * 前端代码生成地址
+ */
+ private String packageWebDir;
+ /**
+ * 需要去掉的表前缀
+ */
+ private String[] tablePrefix;
+ /**
+ * 需要生成的表名(两者只能取其一)
+ */
+ private String[] includeTables;
+ /**
+ * 需要排除的表名(两者只能取其一)
+ */
+ private String[] excludeTables;
+ /**
+ * 是否包含基础业务字段
+ */
+ private Boolean hasSuperEntity;
+
+
+}
diff --git a/blade-ops/blade-develop/src/test/java/org/springblade/test/WalletCode.java b/blade-ops/blade-develop/src/test/java/org/springblade/test/WalletCode.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1ee2f000d947a7b1c0bf286633d99971aec86fd
--- /dev/null
+++ b/blade-ops/blade-develop/src/test/java/org/springblade/test/WalletCode.java
@@ -0,0 +1,47 @@
+package org.springblade.test;
+
+import org.springblade.develop.support.BladeCodeGenerator;
+
+public class WalletCode implements CodeGenerator{
+
+ private CodeModel codeModel;
+
+ public WalletCode(){
+ CodeModel codeModel = new CodeModel();
+ codeModel.setCodeName("钱包信息记录管理");
+ codeModel.setServiceName("digital-wallet");
+ codeModel.setSystemName("sword");
+ codeModel.setPackageName("org.springblade.digital.wallet");
+ codeModel.setPackageWebDir("D://Workspaces/digital/Sword");
+ codeModel.setTablePrefix(new String[]{""});
+ codeModel.setIncludeTables(new String[]{"wallet_record"});
+// codeModel.setExcludeTables(new String[]{""});
+ codeModel.setHasSuperEntity(Boolean.TRUE);
+ this.codeModel = codeModel;
+ }
+ // 初始化代码生成器
+ public BladeCodeGenerator initCodeGenerator(CodeModel codeModel){
+
+ if(codeModel == null){
+ codeModel = this.codeModel;
+ }
+
+ BladeCodeGenerator generator = new BladeCodeGenerator();
+
+ generator.setCodeName(codeModel.getCodeName());
+ generator.setServiceName(codeModel.getServiceName());
+ generator.setSystemName(codeModel.getSystemName());
+ generator.setPackageName(codeModel.getPackageName());
+ generator.setPackageWebDir(codeModel.getPackageWebDir());
+ generator.setTablePrefix(codeModel.getTablePrefix());
+ generator.setIncludeTables(codeModel.getIncludeTables());
+ // IncludeTables 与ExcludeTables 策略不能同时存在
+// generator.setExcludeTables(codeModel.getExcludeTables());
+ generator.setHasSuperEntity(codeModel.getHasSuperEntity());
+ generator.setSuperEntityColumns(codeModel.superEntityColumns);
+
+ return generator;
+ }
+
+
+}
diff --git a/blade-service-api/blade-file-api/pom.xml b/blade-service-api/blade-file-api/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..25cf6cd124bf7b66d0d22ec4d1f006878c3001d8
--- /dev/null
+++ b/blade-service-api/blade-file-api/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ blade-service-api
+ org.springblade
+ 2.7.1
+
+ 4.0.0
+ blade-file-api
+ ${project.artifactId}
+ ${blade.project.version}
+ jar
+
+
+
+ org.springblade
+ blade-common
+ 2.7.1
+
+
+
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/constant/FileConstants.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/constant/FileConstants.java
new file mode 100644
index 0000000000000000000000000000000000000000..e29012a347acffb2b8f068d0c6af7aa7057178e1
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/constant/FileConstants.java
@@ -0,0 +1,51 @@
+package org.springblade.file.constant;
+
+
+import java.io.Serializable;
+
+/**
+ *
+ * 数据库常量
+ * 文件表
+ *
+ *
+ * @author zt
+ * @date 2020-09-10
+ */
+
+public class FileConstants implements Serializable {
+
+ /**
+ * 字段常量
+ */
+ public static final String ID = "id";
+ public static final String DATA_TYPE = "data_type";
+ public static final String SUBMITTED_FILE_NAME = "submitted_file_name";
+ public static final String TREE_PATH = "tree_path";
+ public static final String GRADE = "grade";
+ public static final String IS_DELETE = "is_delete";
+ public static final String FOLDER_ID = "folder_id";
+ public static final String URL = "url";
+ public static final String SIZE = "size";
+ public static final String FOLDER_NAME = "folder_name";
+ public static final String GROUP = "group_";
+ public static final String PATH = "path";
+ public static final String RELATIVE_PATH = "relative_path";
+ public static final String FILE_MD5 = "file_md5";
+ public static final String CONTEXT_TYPE = "context_type";
+ public static final String FILENAME = "filename";
+ public static final String EXT = "ext";
+ public static final String ICON = "icon";
+ public static final String CREATE_MONTH = "create_month";
+ public static final String CREATE_WEEK = "create_week";
+ public static final String CREATE_DAY = "create_day";
+ public static final String CREATE_TIME = "create_time";
+ public static final String CREATE_USER = "create_user";
+ public static final String UPDATE_TIME = "update_time";
+ public static final String UPDATE_USER = "update_user";
+ private static final long serialVersionUID = 1L;
+
+ private FileConstants() {
+ super();
+ }
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileAttrDO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileAttrDO.java
new file mode 100644
index 0000000000000000000000000000000000000000..289e80fbb192bb65957032cf4ecca890552bba7c
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileAttrDO.java
@@ -0,0 +1,35 @@
+package org.springblade.file.domain;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 文件部分属性
+ *
+ * @author ds
+ */
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+@Data
+public class FileAttrDO {
+ /**
+ * 路径
+ */
+ private String treePath;
+ /**
+ * 层级
+ */
+ private Integer grade;
+ /**
+ * 文件夹名称
+ */
+ private String folderName;
+ /**
+ * 文件夹id
+ */
+ private Long folderId;
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileDO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileDO.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b1ea587a6924d1f9e4aea5c3dc73c953b5c5060
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileDO.java
@@ -0,0 +1,33 @@
+package org.springblade.file.domain;
+
+
+import lombok.Builder;
+import lombok.Data;
+import org.springblade.file.enumeration.DataType;
+
+/**
+ * 文件、附件DO
+ *
+ * @author zuihou
+ * @date 2019/05/06
+ */
+@Data
+@Builder
+public class FileDO {
+ /**
+ * 原始文件名
+ */
+ private String submittedFileName;
+ /**
+ * 数据类型 IMAGE/VIDEO/AUDIO/DOC/OTHER/DIR
+ */
+ private DataType dataType;
+ /**
+ * 文件访问链接
+ */
+ private String url;
+ /**
+ * 文件大小
+ */
+ private Long size;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileDeleteDO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileDeleteDO.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a3d194961f7f7037bb519d3c6430abdc2110bdf
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileDeleteDO.java
@@ -0,0 +1,36 @@
+package org.springblade.file.domain;
+
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 文件删除
+ *
+ * @author zuihou
+ * @date 2019/05/07
+ */
+@Data
+@Builder
+public class FileDeleteDO {
+ /**
+ * fastDFS返回的组 用于FastDFS
+ */
+ private String group;
+ /**
+ * fastdfs 的路径
+ */
+ private String path;
+ /**
+ * 唯一文件名
+ */
+ private String fileName;
+ /**
+ * 文件在服务器的绝对路径
+ */
+ private String relativePath;
+ private Long id;
+ /**
+ * 是否是云盘文件删除
+ */
+ private Boolean file;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileQueryDO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileQueryDO.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6060f0fa04cb8a1e56f62e14994cbb561df0885
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileQueryDO.java
@@ -0,0 +1,17 @@
+package org.springblade.file.domain;
+
+
+import lombok.Data;
+import org.springblade.file.entity.File;
+
+/**
+ * 文件查询 DO
+ *
+ * @author zuihou
+ * @date 2019/05/07
+ */
+@Data
+public class FileQueryDO extends File {
+ private File parent;
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileStatisticsDO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileStatisticsDO.java
new file mode 100644
index 0000000000000000000000000000000000000000..6097480a61d67e800656282e5832e3650815d723
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/domain/FileStatisticsDO.java
@@ -0,0 +1,37 @@
+package org.springblade.file.domain;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springblade.file.enumeration.DataType;
+
+/**
+ * @author zuihou
+ * @create 2018-09-06 10:10
+ * @desc 文件类型数量、
+ * @Version 1.0
+ **/
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class FileStatisticsDO {
+ /**
+ * 文件类型 IMAGE、DOC等
+ */
+ private DataType dataType;
+ /**
+ * 时间类型 (按月、周、天?)
+ */
+ private String dateType;
+ /**
+ * 文件数量
+ */
+ private Integer num;
+ /**
+ * 文件大小
+ */
+ private Long size;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..08374c2cfb47287cf59068ee4d7e49c16cee1b4e
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentDTO.java
@@ -0,0 +1,43 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.springblade.file.entity.Attachment;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 实体类
+ * 附件表
+ *
+ *
+ * @author zuihou
+ * @since 2019-05-20
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = false)
+@ApiModel(value = "AttachmentDTO", description = "附件表")
+public class AttachmentDTO extends Attachment implements Serializable {
+
+ /**
+ * 在DTO中新增并自定义字段,需要覆盖验证的字段,请新建DTO。Entity中的验证规则可以自行修改,但下次生成代码时,记得同步代码!!
+ */
+ private static final long serialVersionUID = 1L;
+ @ApiModelProperty(value = "文件下载地址 根据url下载")
+ private String downloadUrlByUrl;
+ @ApiModelProperty(value = "文件下载地址 根据文件id下载")
+ private String downloadUrlById;
+ @ApiModelProperty(value = "文件下载地址 根据业务id下载")
+ private String downloadUrlByBizId;
+
+ public static AttachmentDTO build() {
+ return new AttachmentDTO();
+ }
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentRemoveDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentRemoveDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..b77096e57873049d28868b17cc7f96b092913d70
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentRemoveDTO.java
@@ -0,0 +1,27 @@
+package org.springblade.file.dto;
+
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 删除实体
+ *
+ * @param
+ * @author zuihou
+ * @date 2019-05-12 18:49
+ * @return
+ */
+@Data
+@ApiModel(value = "AttachmentRemove", description = "附件删除")
+public class AttachmentRemoveDTO implements Serializable {
+
+ @ApiModelProperty(value = "业务类型")
+ private String bizType;
+
+ @ApiModelProperty(value = "业务id")
+ private String bizId;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentResultDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentResultDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..63ec0c7a801a93121731feaac43499428f7b1d1a
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/AttachmentResultDTO.java
@@ -0,0 +1,28 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.file.entity.Attachment;
+
+import java.util.List;
+
+/**
+ * 附件返回实体
+ *
+ * @author zuihou
+ * @date 2018/12/12
+ */
+@Data
+@ApiModel(value = "AttachmentResult", description = "附件列表")
+public class AttachmentResultDTO {
+ @ApiModelProperty(value = "业务id")
+ private String bizId;
+ @ApiModelProperty(value = "业务类型")
+ private String bizType;
+ @ApiModelProperty(value = "附件列表")
+ private List list;
+}
+
+
+
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/BaseFolderDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/BaseFolderDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..70820f9cbeceba5198fc77b50b37ec6074161bb9
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/BaseFolderDTO.java
@@ -0,0 +1,36 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+/**
+ * 文件夹基础DTO
+ *
+ * @author zuihou
+ * @date 2019-04-29
+ */
+@Data
+public abstract class BaseFolderDTO {
+
+ /**
+ * 名称
+ */
+ @ApiModelProperty(value = "名称")
+ @NotEmpty(message = "文件名称不能为空")
+ protected String submittedFileName;
+
+ /**
+ * 父文件夹
+ */
+ @ApiModelProperty(value = "父文件夹id")
+ protected Long folderId;
+
+ /**
+ * 排序
+ */
+ @ApiModelProperty(value = "排序")
+ protected Integer orderNum;
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..1968b2ace9d4156c2d1a5e04764c288092396007
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileDTO.java
@@ -0,0 +1,39 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+import org.springblade.file.entity.File;
+
+import java.io.Serializable;
+
+/**
+ *
+ * 实体类
+ * 文件表
+ *
+ *
+ * @author zuihou
+ * @since 2019-06-24
+ */
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = false)
+@ApiModel(value = "FileDTO", description = "文件表")
+public class FileDTO extends File implements Serializable {
+
+ /**
+ * 在DTO中新增并自定义字段,需要覆盖验证的字段,请新建DTO。Entity中的验证规则可以自行修改,但下次生成代码时,记得同步代码!!
+ */
+ private static final long serialVersionUID = 1L;
+
+ public static FileDTO build() {
+ return new FileDTO();
+ }
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileOverviewDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileOverviewDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..682590071fb1c3b154b095015fab01a8aa60ad55
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileOverviewDTO.java
@@ -0,0 +1,48 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Builder;
+import lombok.Data;
+
+/**
+ * 文件统计 概览DTO
+ *
+ * @author zuihou
+ * @date 2019/05/08
+ */
+@Data
+@ApiModel(value = "FileOverview", description = "云盘首页概览")
+@Builder
+public class FileOverviewDTO {
+
+ @ApiModelProperty(value = "所有文件数量")
+ private Integer allFileNum;
+
+ @ApiModelProperty(value = "所有文件大小")
+ private Long allFileSize;
+
+ @ApiModelProperty(value = "文件夹数量")
+ private Integer dirNum;
+
+ @ApiModelProperty(value = "图片文件数量")
+ private Integer imgNum;
+
+ @ApiModelProperty(value = "文档文件数量")
+ private Integer docNum;
+
+ @ApiModelProperty(value = "视频文件数量")
+ private Integer videoNum;
+
+ @ApiModelProperty(value = "视音频文件数量")
+ private Integer audioNum;
+
+ @ApiModelProperty(value = "其他文件数量")
+ private Integer otherNum;
+
+ public static FileOverviewDTOBuilder myBuilder() {
+ FileOverviewDTOBuilder builder = FileOverviewDTO.builder();
+ return builder.allFileSize(0L).allFileNum(0).dirNum(0).imgNum(0).docNum(0).videoNum(0).
+ audioNum(0).otherNum(0);
+ }
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FilePageReqDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FilePageReqDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..83bf32b50616de4f0c169bfacec1b491f4e71b67
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FilePageReqDTO.java
@@ -0,0 +1,30 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.file.enumeration.DataType;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 文件分页列表请求参数
+ *
+ * @author zuihou
+ * @date 2019-04-29
+ */
+@Data
+@ApiModel(value = "FilePageReq", description = "文件分页列表请求参数")
+public class FilePageReqDTO implements Serializable {
+ @ApiModelProperty(value = "文件夹id")
+ private Long folderId;
+ @ApiModelProperty(value = "原始文件名")
+ private String submittedFileName;
+ @ApiModelProperty(value = "数据类型 null和''表示查询全部 图片:IMAGE 视频:VIDEO 音频:AUDIO 文档DOC 其他:OTHER", example = "IMAGE,VIDEO,AUDIO,DOC,OTHER")
+ private DataType dataType;
+ @ApiModelProperty(value = "开始时间")
+ private LocalDateTime startCreateTime;
+ @ApiModelProperty(value = "结束时间")
+ private LocalDateTime endCreateTime;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileStatisticsAllDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileStatisticsAllDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..b19ff5602d1125edfe8626201368d58ff50fc1ea
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileStatisticsAllDTO.java
@@ -0,0 +1,39 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 文件 按时间线统计数量和大小的 DTO
+ *
+ * @author zuihou
+ * @date 2019/05/08
+ */
+@Data
+@Builder
+@ApiModel(value = "FileStatisticsAll", description = "统计所有类型文件的数量和大小")
+public class FileStatisticsAllDTO {
+ private List dateList;
+ private List sizeList;
+ private List numList;
+
+ private List dirNumList;
+
+ private List imgSizeList;
+ private List imgNumList;
+
+ private List videoSizeList;
+ private List videoNumList;
+
+ private List audioSizeList;
+ private List audioNumList;
+
+ private List docSizeList;
+ private List docNumList;
+
+ private List otherSizeList;
+ private List otherNumList;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileUpdateDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileUpdateDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..2bb3e8e76e174db64853ddca47f7662818b3c96c
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FileUpdateDTO.java
@@ -0,0 +1,30 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * 文件修改
+ *
+ * @author zuihou
+ * @date 2019-05-06
+ */
+@Data
+@ApiModel(value = "FileUpdate", description = "文件修改")
+public class FileUpdateDTO implements Serializable {
+ @ApiModelProperty(value = "id", required = true)
+ @NotNull(message = "文件id不能为空")
+ private Long id;
+ /**
+ * 原始文件名
+ *
+ * @mbggenerated
+ */
+ @ApiModelProperty(value = "文件原始名称")
+ private String submittedFileName;
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FolderDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FolderDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..435422708228a4947ecf76200d618d8eb22eb95e
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FolderDTO.java
@@ -0,0 +1,20 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 文件夹
+ *
+ * @author zuihou
+ * @date 2019-04-29
+ */
+@Data
+@ApiModel(value = "Folder", description = "文件夹")
+public class FolderDTO extends BaseFolderDTO implements Serializable {
+ @ApiModelProperty(value = "id", notes = "文件夹id", required = true)
+ private Long id;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FolderSaveDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FolderSaveDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..a6e63efb09f8bab2c82d7e2907e7e12e0614e737
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/FolderSaveDTO.java
@@ -0,0 +1,17 @@
+package org.springblade.file.dto;
+
+import io.swagger.annotations.ApiModel;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 文件夹保存
+ *
+ * @author zuihou
+ * @date 2019-04-29
+ */
+@Data
+@ApiModel(value = "FolderSave", description = "文件夹保存")
+public class FolderSaveDTO extends BaseFolderDTO implements Serializable {
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileChunkCheckDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileChunkCheckDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..23e57d36e0eaadcbe3c8752684cc0488da60bf58
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileChunkCheckDTO.java
@@ -0,0 +1,25 @@
+package org.springblade.file.dto.chunk;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * 分片检测参数
+ *
+ * @author zuihou
+ * @date 2018/08/28
+ */
+@Data
+@ToString
+@ApiModel(value = "FileChunkCheck", description = "文件分片信息")
+public class FileChunkCheckDTO {
+
+ @ApiModelProperty(value = "文件大小")
+ private Long size;
+ @ApiModelProperty(value = "文件唯一名")
+ private String name;
+ @ApiModelProperty(value = "分片索引")
+ private Integer chunkIndex;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileChunksMergeDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileChunksMergeDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..a8b7d5a79212a6b4dca3ad23da1d57d5e1d2580b
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileChunksMergeDTO.java
@@ -0,0 +1,38 @@
+package org.springblade.file.dto.chunk;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * 封建分片合并DTO
+ *
+ * @author zuihou
+ * @date 2018/08/28
+ */
+@Data
+@ToString
+@ApiModel(value = "FileChunksMerge", description = "文件合并实体")
+public class FileChunksMergeDTO {
+
+ @ApiModelProperty(value = "文件唯一名 md5.js 生成的, 与后端生成的一致")
+ private String name;
+ @ApiModelProperty(value = "原始文件名")
+ private String submittedFileName;
+
+ @ApiModelProperty(value = "md5", notes = "webuploader 自带的md5算法值, 与后端生成的不一致")
+ private String md5;
+
+ @ApiModelProperty(value = "分片总数")
+ private Integer chunks;
+ @ApiModelProperty(value = "后缀")
+ private String ext;
+ @ApiModelProperty(value = "文件夹id")
+ private Long folderId;
+
+ @ApiModelProperty(value = "大小")
+ private Long size;
+ @ApiModelProperty(value = "类型")
+ private String contextType;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileUploadDTO.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileUploadDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..66311459d57a0717264ef351bd5a18233b5aeb30
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/dto/chunk/FileUploadDTO.java
@@ -0,0 +1,36 @@
+package org.springblade.file.dto.chunk;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.ToString;
+
+/**
+ * 文件分片上传实体
+ *
+ * @author zuihou
+ * @date 2018/08/29
+ */
+@Data
+@ApiModel(value = "FileUpload", description = "文件分片上传实体")
+@ToString
+public class FileUploadDTO {
+ @ApiModelProperty(value = "md5", notes = "webuploader 自带的md5算法值, 与后端生成的不一致")
+ private String md5;
+ @ApiModelProperty(value = "大小")
+ private Long size;
+ @ApiModelProperty(value = "文件唯一名 md5.js生成的, 与后端生成的一致")
+ private String name;
+ @ApiModelProperty(value = "分片总数")
+ private Integer chunks;
+ @ApiModelProperty(value = "当前分片")
+ private Integer chunk;
+ @ApiModelProperty(value = "最后更新时间")
+ private String lastModifiedDate;
+ @ApiModelProperty(value = "类型")
+ private String type;
+ @ApiModelProperty(value = "后缀")
+ private String ext;
+ @ApiModelProperty(value = "文件夹id")
+ private Long folderId;
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/entity/Attachment.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/entity/Attachment.java
new file mode 100644
index 0000000000000000000000000000000000000000..08d340eacdb306ee20e10f5d33d493bedc7f15cc
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/entity/Attachment.java
@@ -0,0 +1,233 @@
+package org.springblade.file.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+import lombok.experimental.Accessors;
+import org.hibernate.validator.constraints.Length;
+import org.springblade.core.mp.base.BaseEntity;
+import org.springblade.file.enumeration.DataType;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ *
+ * 实体类
+ * 附件
+ *
+ *
+ * @author zuihou
+ * @since 2019-06-24
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("f_attachment")
+@ApiModel(value = "Attachment", description = "附件")
+public class Attachment extends BaseEntity {
+
+ private static final long serialVersionUID = 1L;
+ /**
+ * 主键id
+ */
+ @ApiModelProperty(value = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long id;
+ /**
+ * 业务ID
+ */
+ @ApiModelProperty(value = "业务ID")
+ @Length(max = 64, message = "业务ID长度不能超过64")
+ @TableField("biz_id")
+ private String bizId;
+
+ /**
+ * 业务类型
+ * #AttachmentType
+ */
+ @ApiModelProperty(value = "业务类型")
+ @Length(max = 255, message = "业务类型长度不能超过255")
+ @TableField("biz_type")
+ private String bizType;
+
+ /**
+ * 数据类型
+ * #DataType{DIR:目录;IMAGE:图片;VIDEO:视频;AUDIO:音频;DOC:文档;OTHER:其他}
+ */
+ @ApiModelProperty(value = "数据类型")
+ @TableField("data_type")
+ private DataType dataType;
+
+ /**
+ * 原始文件名
+ */
+ @ApiModelProperty(value = "原始文件名")
+ @Length(max = 255, message = "原始文件名长度不能超过255")
+ @TableField("submitted_file_name")
+ private String submittedFileName;
+
+ /**
+ * FastDFS返回的组
+ * 用于FastDFS
+ */
+ @ApiModelProperty(value = "FastDFS返回的组")
+ @Length(max = 255, message = "FastDFS返回的组长度不能超过255")
+ @TableField("group_")
+ private String group;
+
+ /**
+ * FastDFS的远程文件名
+ * 用于FastDFS
+ */
+ @ApiModelProperty(value = "FastDFS的远程文件名")
+ @Length(max = 255, message = "FastDFS的远程文件名长度不能超过255")
+ @TableField("path")
+ private String path;
+
+ /**
+ * 文件相对路径
+ */
+ @ApiModelProperty(value = "文件相对路径")
+ @Length(max = 255, message = "文件相对路径长度不能超过255")
+ @TableField("relative_path")
+ private String relativePath;
+
+ /**
+ * 文件访问链接
+ * 需要通过nginx配置路由,才能访问
+ */
+ @ApiModelProperty(value = "文件访问链接")
+ @Length(max = 255, message = "文件访问链接长度不能超过255")
+ @TableField("url")
+ private String url;
+
+ /**
+ * 文件md5值
+ */
+ @ApiModelProperty(value = "文件md5值")
+ @Length(max = 255, message = "文件md5值长度不能超过255")
+ @TableField("file_md5")
+ private String fileMd5;
+
+ /**
+ * 文件上传类型
+ * 取上传文件的值
+ */
+ @ApiModelProperty(value = "文件上传类型")
+ @Length(max = 255, message = "文件上传类型长度不能超过255")
+ @TableField("context_type")
+ private String contextType;
+
+ /**
+ * 唯一文件名
+ */
+ @ApiModelProperty(value = "唯一文件名")
+ @Length(max = 255, message = "唯一文件名长度不能超过255")
+ @TableField("filename")
+ private String filename;
+
+ /**
+ * 后缀
+ * (没有.)
+ */
+ @ApiModelProperty(value = "后缀")
+ @Length(max = 64, message = "后缀长度不能超过64")
+ @TableField("ext")
+ private String ext;
+
+ /**
+ * 大小
+ */
+ @ApiModelProperty(value = "大小")
+ @TableField("size")
+ private Long size;
+ /**
+ * 组织ID
+ */
+ @ApiModelProperty(value = "组织ID")
+ @TableField("org_id")
+ private Long orgId;
+
+ /**
+ * 图标
+ */
+ @ApiModelProperty(value = "图标")
+ @Length(max = 64, message = "图标长度不能超过64")
+ @TableField("icon")
+ private String icon;
+
+ /**
+ * 创建年月
+ * 格式:yyyy-MM 用于统计
+ */
+ @ApiModelProperty(value = "创建年月")
+ @Length(max = 10, message = "创建年月长度不能超过10")
+ @TableField("create_month")
+ private String createMonth;
+
+ /**
+ * 创建时处于当年的第几周
+ * yyyy-ww 用于统计
+ */
+ @ApiModelProperty(value = "创建时处于当年的第几周")
+ @Length(max = 10, message = "创建时处于当年的第几周长度不能超过10")
+ @TableField("create_week")
+ private String createWeek;
+
+ /**
+ * 创建年月日
+ * 格式: yyyy-MM-dd 用于统计
+ */
+ @ApiModelProperty(value = "创建年月日")
+ @Length(max = 12, message = "创建年月日长度不能超过12")
+ @TableField("create_day")
+ private String createDay;
+
+
+ @Builder
+ public Attachment(Long id, Date createTime, Long createUser, Date updateTime, Long updateUser,
+ String bizId, String bizType, DataType dataType, String submittedFileName,
+ String group, String path, String relativePath, String url,
+ String fileMd5, String contextType, String filename, String ext, Long size, Long orgId, String icon,
+ String createMonth, String createWeek, String createDay) {
+ this.id = id;
+ this.setCreateTime(createTime);
+ this.setCreateUser(createUser);
+ this.setUpdateTime(updateTime);
+ this.setUpdateUser(updateUser);
+// this.createTime = createTime;
+// this.createUser = createUser;
+// this.updateTime = updateTime;
+// this.updateUser = updateUser;
+ this.bizId = bizId;
+ this.bizType = bizType;
+ this.dataType = dataType;
+ this.submittedFileName = submittedFileName;
+ this.group = group;
+ this.path = path;
+ this.relativePath = relativePath;
+ this.url = url;
+ this.fileMd5 = fileMd5;
+ this.contextType = contextType;
+ this.filename = filename;
+ this.ext = ext;
+ this.size = size;
+ this.orgId = orgId;
+ this.icon = icon;
+ this.createMonth = createMonth;
+ this.createWeek = createWeek;
+ this.createDay = createDay;
+ }
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/entity/File.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/entity/File.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a2e0c43ccf6f257e0d4f1f4098677769272eeec
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/entity/File.java
@@ -0,0 +1,254 @@
+package org.springblade.file.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.*;
+import lombok.experimental.Accessors;
+import org.hibernate.validator.constraints.Length;
+import org.springblade.core.mp.base.BaseEntity;
+import org.springblade.file.enumeration.DataType;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ *
+ * 实体类
+ * 文件表
+ *
+ *
+ * @author zuihou
+ * @since 2019-06-24
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("f_file")
+@ApiModel(value = "File", description = "文件表")
+public class File extends BaseEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 主键id
+ */
+ @ApiModelProperty(value = "主键")
+ @TableId(value = "id", type = IdType.ASSIGN_ID)
+ @JsonSerialize(using = ToStringSerializer.class)
+ private Long id;
+ /**
+ * 数据类型
+ * #DataType{DIR:目录;IMAGE:图片;VIDEO:视频;AUDIO:音频;DOC:文档;OTHER:其他}
+ */
+ @ApiModelProperty(value = "数据类型")
+ @TableField("data_type")
+ private DataType dataType;
+
+ /**
+ * 原始文件名
+ */
+ @ApiModelProperty(value = "原始文件名")
+ @Length(max = 255, message = "原始文件名长度不能超过255")
+ @TableField("submitted_file_name")
+ private String submittedFileName;
+
+ /**
+ * 父目录层级关系
+ */
+ @ApiModelProperty(value = "父目录层级关系")
+ @Length(max = 255, message = "父目录层级关系长度不能超过255")
+ @TableField("tree_path")
+ private String treePath;
+
+ /**
+ * 层级等级
+ * 从1开始计算
+ */
+ @ApiModelProperty(value = "层级等级")
+ @TableField("grade")
+ private Integer grade;
+
+ /**
+ * 是否删除
+ * #BooleanStatus{TRUE:1,已删除;FALSE:0,未删除}
+ */
+ @ApiModelProperty(value = "是否删除")
+ @TableField("is_delete")
+ private Boolean isDelete;
+
+ /**
+ * 父文件夹ID
+ */
+ @ApiModelProperty(value = "父文件夹ID")
+ @TableField("folder_id")
+ private Long folderId;
+
+ /**
+ * 文件访问链接
+ * 需要通过nginx配置路由,才能访问
+ */
+ @ApiModelProperty(value = "文件访问链接")
+ @Length(max = 255, message = "文件访问链接长度不能超过255")
+ @TableField("url")
+ private String url;
+
+ /**
+ * 文件大小
+ * 单位字节
+ */
+ @ApiModelProperty(value = "文件大小")
+ @TableField("size")
+ private Long size;
+
+ /**
+ * 父文件夹名称
+ */
+ @ApiModelProperty(value = "父文件夹名称")
+ @Length(max = 255, message = "父文件夹名称长度不能超过255")
+ @TableField("folder_name")
+ private String folderName;
+
+ /**
+ * FastDFS组
+ * 用于FastDFS
+ */
+ @ApiModelProperty(value = "FastDFS组")
+ @Length(max = 255, message = "FastDFS组长度不能超过255")
+ @TableField("group_")
+ private String group;
+
+ /**
+ * FastDFS远程文件名
+ * 用于FastDFS
+ */
+ @ApiModelProperty(value = "FastDFS远程文件名")
+ @Length(max = 255, message = "FastDFS远程文件名长度不能超过255")
+ @TableField("path")
+ private String path;
+
+ /**
+ * 文件的相对路径
+ */
+ @ApiModelProperty(value = "文件的相对路径 ")
+ @Length(max = 255, message = "文件的相对路径 长度不能超过255")
+ @TableField("relative_path")
+ private String relativePath;
+
+ /**
+ * md5值
+ */
+ @ApiModelProperty(value = "md5值")
+ @Length(max = 255, message = "md5值长度不能超过255")
+ @TableField("file_md5")
+ private String fileMd5;
+
+ /**
+ * 文件类型
+ * 取上传文件的值
+ */
+ @ApiModelProperty(value = "文件类型")
+ @Length(max = 255, message = "文件类型长度不能超过255")
+ @TableField("context_type")
+ private String contextType;
+
+ /**
+ * 唯一文件名
+ */
+ @ApiModelProperty(value = "唯一文件名")
+ @Length(max = 255, message = "唯一文件名长度不能超过255")
+ @TableField("filename")
+ private String filename;
+
+ /**
+ * 文件名后缀
+ * (没有.)
+ */
+ @ApiModelProperty(value = "文件名后缀")
+ @Length(max = 64, message = "文件名后缀长度不能超过64")
+ @TableField("ext")
+ private String ext;
+
+ /**
+ * 文件图标
+ * 用于云盘显示
+ */
+ @ApiModelProperty(value = "文件图标")
+ @Length(max = 64, message = "文件图标长度不能超过64")
+ @TableField("icon")
+ private String icon;
+
+ /**
+ * 创建时年月
+ * 格式:yyyy-MM 用于统计
+ */
+ @ApiModelProperty(value = "创建时年月")
+ @Length(max = 10, message = "创建时年月长度不能超过10")
+ @TableField("create_month")
+ private String createMonth;
+
+ /**
+ * 创建时年周
+ * yyyy-ww 用于统计
+ */
+ @ApiModelProperty(value = "创建时年周")
+ @Length(max = 10, message = "创建时年周长度不能超过10")
+ @TableField("create_week")
+ private String createWeek;
+
+ /**
+ * 创建时年月日
+ * 格式: yyyy-MM-dd 用于统计
+ */
+ @ApiModelProperty(value = "创建时年月日")
+ @Length(max = 12, message = "创建时年月日长度不能超过12")
+ @TableField("create_day")
+ private String createDay;
+
+
+ @Builder
+ public File(Long id, Date createTime, Long createUser, Date updateTime, Long updateUser,
+ DataType dataType, String submittedFileName, String treePath, Integer grade, Boolean isDelete,
+ Long folderId, String url, Long size, String folderName, String group, String path,
+ String relativePath, String fileMd5, String contextType, String filename, String ext, String icon,
+ String createMonth, String createWeek, String createDay) {
+ this.id = id;
+ this.setCreateTime(createTime);
+ this.setCreateUser(createUser);
+ this.setUpdateTime(updateTime);
+ this.setUpdateUser(updateUser);
+// this.createTime = createTime;
+// this.createUser = createUser;
+// this.updateTime = updateTime;
+// this.updateUser = updateUser;
+ this.dataType = dataType;
+ this.submittedFileName = submittedFileName;
+ this.treePath = treePath;
+ this.grade = grade;
+ this.isDelete = isDelete;
+ this.folderId = folderId;
+ this.url = url;
+ this.size = size;
+ this.folderName = folderName;
+ this.group = group;
+ this.path = path;
+ this.relativePath = relativePath;
+ this.fileMd5 = fileMd5;
+ this.contextType = contextType;
+ this.filename = filename;
+ this.ext = ext;
+ this.icon = icon;
+ this.createMonth = createMonth;
+ this.createWeek = createWeek;
+ this.createDay = createDay;
+ }
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/DataType.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/DataType.java
new file mode 100644
index 0000000000000000000000000000000000000000..a36f59f09d71b1dd53eb79c36e14cfc10e454d33
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/DataType.java
@@ -0,0 +1,74 @@
+package org.springblade.file.enumeration;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.springblade.common.baseEnum.BaseEnum;
+
+import java.util.stream.Stream;
+
+
+/**
+ *
+ * 实体注释中生成的类型枚举
+ * 文件回收站
+ *
+ *
+ * @author sp
+ * @date 2020-09-14
+ */
+@Getter
+@AllArgsConstructor
+@ApiModel(value = "DataType", description = "数据类型-枚举")
+public enum DataType implements BaseEnum {
+
+ /**
+ * DIR="目录"
+ */
+ DIR("目录"),
+ /**
+ * IMAGE="图片"
+ */
+ IMAGE("图片"),
+ /**
+ * VIDEO="视频"
+ */
+ VIDEO("视频"),
+ /**
+ * AUDIO="音频"
+ */
+ AUDIO("音频"),
+ /**
+ * DOC="文档"
+ */
+ DOC("文档"),
+ /**
+ * OTHER="其他"
+ */
+ OTHER("其他"),
+ ;
+
+ @ApiModelProperty(value = "描述")
+ private String desc;
+
+ public static DataType match(String val, DataType def) {
+ return Stream.of(values()).parallel().filter((item) -> item.name().equalsIgnoreCase(val)).findAny().orElse(def);
+ }
+
+ public static DataType get(String val) {
+ return match(val, null);
+ }
+
+
+ public boolean eq(DataType val) {
+ return val == null ? false : eq(val.name());
+ }
+
+ @ApiModelProperty(value = "编码", allowableValues = "DIR,IMAGE,VIDEO,AUDIO,DOC,OTHER", example = "DIR")
+ @Override
+ public String getCode() {
+ return this.name();
+ }
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/FileStorageType.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/FileStorageType.java
new file mode 100644
index 0000000000000000000000000000000000000000..c777f914a6fcb03afbba71236c211a0eea8301a1
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/FileStorageType.java
@@ -0,0 +1,28 @@
+package org.springblade.file.enumeration;
+
+/**
+ * 文件 存储类型 枚举
+ *
+ * @author zuihou
+ * @date 2019/05/06
+ */
+public enum FileStorageType {
+ /**
+ * 本地
+ */
+ LOCAL,
+ /**
+ * FastDFS
+ */
+ FAST_DFS,
+ ALI,
+ QINIU,
+ ;
+
+ public boolean eq(FileStorageType type) {
+ for (FileStorageType t : FileStorageType.values()) {
+ return t.equals(type);
+ }
+ return false;
+ }
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/IconType.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/IconType.java
new file mode 100644
index 0000000000000000000000000000000000000000..9312dab6d5e61fe6950f406dd4a6b1152e7c3c3d
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/enumeration/IconType.java
@@ -0,0 +1,208 @@
+package org.springblade.file.enumeration;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+/**
+ * 图标 常量
+ *
+ * @author zuihou
+ * @date 2019-06-12
+ */
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+public enum IconType {
+ /**
+ * 图标
+ */
+ DIR("application/x-director", "el-icon-folder"),
+ /**
+ * 图标
+ */
+ TXT("txt", "el-icon-document"),
+ /**
+ * 图标
+ */
+// EXE("exe", "fa-exe-o"),
+// PDF("pdf", "fa-file-pdf-o"),
+// // PSD("psd", "fa-ps-o"),
+// XLX("xls", "fa-file-excel-o"),
+// /**
+// * 图标
+// */
+// XLSX("xlsx", "fa-file-excel-o"),
+// /**
+// * 图标
+// */
+// WPS("wps", "fa-file-word-o"),
+// /**
+// * 图标
+// */
+// WPT("pwt", "fa-file-word-o"),
+// /**
+// * 图标
+// */
+// JS("js", "fa-file-code-o"),
+// /**
+// * 图标
+// */
+// JSP("js", "fa-file-code-o"),
+// /**
+// * 图标
+// */
+// PROPERTIES("properties", "fa-file-code-o"),
+
+ /**
+ * 图标
+ */
+// CSS("css", "fa-file-code-o"),
+// SWF("swf", "fa-swf-o"),
+ /**
+ * 图标
+ */
+// DOC("doc", "fa-file-word-o"),
+// /**
+// * 图标
+// */
+// DOCX("docx", "fa-file-word-o"),
+// /**
+// * 图标
+// */
+// DOTX("dotx", "fa-file-word-o"),
+// /**
+// * 图标
+// */
+// PPS("pps", "fa-file-powerpoint-o"),
+// /**
+// * 图标
+// */
+// PPT("ppt", "fa-file-powerpoint-o"),
+// /**
+// * 图标
+// */
+// POT("pot", "fa-file-powerpoint-o"),
+// /**
+// * 图标
+// */
+// PPTX("pptx", "fa-file-powerpoint-o"),
+// HTML("html", "fa-html-o"),
+// HTM("htm", "fa-html-o"),
+
+// JAR("jar", "fa-file-jar-o"),
+// WAR("war", "fa-file-war-o"),
+ /**
+ * 图标
+ */
+// GZIP("gzip", "fa-file-zip-o"),
+// /**
+// * 图标
+// */
+// TAR("tar", "fa-file-zip-o"),
+// /**
+// * 图标
+// */
+// ZIP("zip", "fa-file-zip-o"),
+// /**
+// * 图标
+// */
+// RAR("rar", "fa-file-zip-o"),
+// /**
+// * 图标
+// */
+// TAR_GZ("tar.gz", "fa-file-zip-o"),
+
+ /**
+ * 图标
+ */
+ MP3("mp3", "el-icon-microphone"),
+ /**
+ * 图标
+ */
+
+ BMP("bmp", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ JPEG("jpeg", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ JPG("jpg", "el-icon-picture"),
+
+ /**
+ * 图标
+ */
+ PNG("png", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ GIF("gif", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ COD("cod", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ IEF("ief", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ SVG("svg", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ ICO("ico", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ ICON("icon", "el-icon-picture"),
+ /**
+ * 图标
+ */
+ AVI("avi", "el-icon-video-camera"),
+ /**
+ * 图标
+ */
+ MP4("mp4", "el-icon-video-camera"),
+ /**
+ * 图标
+ */
+ MOV("mov", "el-icon-video-camera"),
+ /**
+ * 图标
+ */
+ MPG("mpg", "el-icon-video-camera"),
+ /**
+ * 图标
+ */
+ WMV("wmv", "el-icon-video-camera"),
+ /**
+ * 图标
+ */
+ WAV("wav", "el-icon-video-camera"),
+
+ BAT("wav", "el-icon-video-camera"),
+ /**
+ * 图标
+ */
+ OTHER("*", "el-icon-question"),
+ ;
+
+ private String ext;
+ private String icon;
+
+ public static IconType getIcon(String ext) {
+ if (ext == null || ext.isEmpty()) {
+ return OTHER;
+ }
+ for (IconType it : IconType.values()) {
+ if (it.getExt().equalsIgnoreCase(ext)) {
+ return it;
+ }
+ }
+ return OTHER;
+ }
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/AttachmentClient.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/AttachmentClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..e30c43c722641a3ac6bb1a6b357d1262f252a11c
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/AttachmentClient.java
@@ -0,0 +1,40 @@
+package org.springblade.file.feign;
+
+
+import org.springblade.core.tool.api.R;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springblade.file.dto.AttachmentDTO;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 文件接口
+ */
+@FeignClient(
+ value = "file-server",// AppConstant.APPLICATION_USER_NAME,
+ fallback = AttachmentClient.class
+)
+public interface AttachmentClient {
+
+ /**
+ * 通过feign-form 实现文件 跨服务上传
+ *
+ * @param file
+ * @param id
+ * @param bizId
+ * @param bizType
+ * @return
+ */
+ @PostMapping(value = "/attachment/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ R upload(
+ @RequestPart(value = "file") MultipartFile file,
+ @RequestParam(value = "isSingle", required = false, defaultValue = "false") Boolean isSingle,
+ @RequestParam(value = "id", required = false) Long id,
+ @RequestParam(value = "bizId", required = false) String bizId,
+ @RequestParam(value = "bizType", required = false) String bizType);
+
+}
diff --git a/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/AttachmentClientFallback.java b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/AttachmentClientFallback.java
new file mode 100644
index 0000000000000000000000000000000000000000..256ffafa2dd19b3fc6f6401353f60ab1af87a018
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/main/java/org/springblade/file/feign/AttachmentClientFallback.java
@@ -0,0 +1,20 @@
+package org.springblade.file.feign;
+
+import org.springblade.core.tool.api.R;
+import org.springblade.file.dto.AttachmentDTO;
+import org.springframework.stereotype.Component;
+import org.springframework.web.multipart.MultipartFile;
+
+/**
+ * 熔断
+ *
+ * @author zt
+ * @date 2020/09/10
+ */
+@Component
+public class AttachmentClientFallback implements AttachmentClient {
+ @Override
+ public R upload(MultipartFile file, Boolean isSingle, Long id, String bizId, String bizType) {
+ return R.fail("上传文件失败!!!");
+ }
+}
diff --git a/blade-service-api/blade-file-api/src/test/java/org/springblade/AppTest.java b/blade-service-api/blade-file-api/src/test/java/org/springblade/AppTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1359a54c38e922f7dddaf46462eb46cfcff2f131
--- /dev/null
+++ b/blade-service-api/blade-file-api/src/test/java/org/springblade/AppTest.java
@@ -0,0 +1,20 @@
+package org.springblade;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+{
+ /**
+ * Rigorous Test :-)
+ */
+ @Test
+ public void shouldAnswerWithTrue()
+ {
+ assertTrue( true );
+ }
+}
diff --git a/blade-service-api/blade-stock-api/doc/README.md b/blade-service-api/blade-stock-api/doc/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0dbcc1e4632c73e699ec20a16e2591ff76842d2f
--- /dev/null
+++ b/blade-service-api/blade-stock-api/doc/README.md
@@ -0,0 +1,8 @@
+选股总结:
+个股关注模型 重点看图 :
+ 个股分析思路 :
+ 一般分析股票的思路4点 依次
+ 第一 看趋势 判断空间
+ 第二 看资金 判断强弱
+第三 买票 看支撑 (缺口 年线半年线 辅助线 5日线 平台支撑等等)
+第四 卖票 看压力 (缺口 年线半年线 平台高点 M头 等等 )
\ No newline at end of file
diff --git a/blade-service-api/blade-stock-api/doc/pic/sql/sqlDesign.jpg b/blade-service-api/blade-stock-api/doc/pic/sql/sqlDesign.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..24d9afba3d10d7547d0bdaffe44d57f2ab66fde7
Binary files /dev/null and b/blade-service-api/blade-stock-api/doc/pic/sql/sqlDesign.jpg differ
diff --git "a/blade-service-api/blade-stock-api/doc/pic/sql/stock\350\241\250.eddx" "b/blade-service-api/blade-stock-api/doc/pic/sql/stock\350\241\250.eddx"
new file mode 100644
index 0000000000000000000000000000000000000000..5e9a009850d8f24bb176890670546ed52db83162
Binary files /dev/null and "b/blade-service-api/blade-stock-api/doc/pic/sql/stock\350\241\250.eddx" differ
diff --git "a/blade-service-api/blade-stock-api/doc/pic/\350\241\214\346\203\205\344\273\223\344\275\215\346\257\224\344\276\213.jpg" "b/blade-service-api/blade-stock-api/doc/pic/\350\241\214\346\203\205\344\273\223\344\275\215\346\257\224\344\276\213.jpg"
new file mode 100644
index 0000000000000000000000000000000000000000..328339be8f8d7b20af8e41396f66deac9ad3642f
Binary files /dev/null and "b/blade-service-api/blade-stock-api/doc/pic/\350\241\214\346\203\205\344\273\223\344\275\215\346\257\224\344\276\213.jpg" differ
diff --git a/blade-service-api/blade-stock-api/pom.xml b/blade-service-api/blade-stock-api/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2f4d743218b2a7876fac5d8f1267418320bd0ca5
--- /dev/null
+++ b/blade-service-api/blade-stock-api/pom.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ blade-service-api
+ org.springblade
+ 2.7.1
+
+ 4.0.0
+ blade-stock-api
+ ${project.artifactId}
+ ${blade.project.version}
+ jar
+
+
+
diff --git a/blade-service-api/pom.xml b/blade-service-api/pom.xml
index e4feba06a58da04354f57a063915e2446d95cf52..fa397593ac5ffe52f94b775647ed872cb2fce6fd 100644
--- a/blade-service-api/pom.xml
+++ b/blade-service-api/pom.xml
@@ -21,6 +21,8 @@
blade-system-api
blade-user-api
blade-demo-api
+ blade-stock-api
+ blade-file-api
diff --git a/blade-service/blade-desk/src/main/resources/application-dev.yml b/blade-service/blade-desk/src/main/resources/application-dev.yml
index 5596f7846d847743f27e2c1fae1bcf55587d0aa1..77fcf659ac89d36996440387a444a32e05d3a5ff 100644
--- a/blade-service/blade-desk/src/main/resources/application-dev.yml
+++ b/blade-service/blade-desk/src/main/resources/application-dev.yml
@@ -7,4 +7,4 @@ spring:
datasource:
url: ${blade.datasource.dev.url}
username: ${blade.datasource.dev.username}
- password: ${blade.datasource.dev.password}
\ No newline at end of file
+ password: ${blade.datasource.dev.password}
diff --git a/blade-service/blade-file/Dockerfile b/blade-service/blade-file/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..ec6b85320bebd9b659ce15e515d769a15cbeb081
--- /dev/null
+++ b/blade-service/blade-file/Dockerfile
@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER w1999wtw3537@sina.com
+
+RUN mkdir -p /blade/file
+
+WORKDIR /blade/file
+
+EXPOSE 8203
+
+ADD ./target/blade-file.jar ./app.jar
+
+ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
+
+CMD ["--spring.profiles.active=test"]
diff --git a/blade-service/blade-file/pom.xml b/blade-service/blade-file/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2d20e539884dad9ca9e242a5bda7fe25ee7b248b
--- /dev/null
+++ b/blade-service/blade-file/pom.xml
@@ -0,0 +1,78 @@
+
+
+
+
+ blade-service
+ org.springblade
+ 2.7.1
+
+
+ 4.0.0
+
+ blade-file
+ ${project.artifactId}
+ ${blade.project.version}
+ jar
+
+
+
+ org.springblade
+ blade-core-boot
+ ${blade.tool.version}
+
+
+ org.springblade
+ blade-file-api
+ ${blade.project.version}
+
+
+
+ com.aliyun.oss
+ aliyun-sdk-oss
+ 3.1.0
+
+
+
+ com.qiniu
+ qiniu-java-sdk
+ 7.2.18
+
+
+
+ com.github.tobato
+ fastdfs-client
+ 1.27.2
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+
+
+ package
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/FileServerApplication.java b/blade-service/blade-file/src/main/java/org/springblade/file/FileServerApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..d229639380c8a4b9e43679b5e4896f9c35dad882
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/FileServerApplication.java
@@ -0,0 +1,47 @@
+package com.github.zuihou;
+
+import com.github.zuihou.security.annotation.EnableLoginArgResolver;
+import com.github.zuihou.validator.annotation.EnableFormValidator;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
+import org.springframework.cloud.netflix.hystrix.EnableHystrix;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.env.Environment;
+import org.springframework.transaction.annotation.EnableTransactionManagement;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * @author zuihou
+ */
+@SpringBootApplication
+@EnableDiscoveryClient
+@EnableHystrix
+@EnableFeignClients(value = {
+ "com.github.zuihou",
+})
+@EnableTransactionManagement
+@Slf4j
+@EnableLoginArgResolver
+@EnableFormValidator
+public class FileServerApplication {
+ public static void main(String[] args) throws UnknownHostException {
+ ConfigurableApplicationContext application = SpringApplication.run(FileServerApplication.class, args);
+ Environment env = application.getEnvironment();
+ log.info("\n----------------------------------------------------------\n\t" +
+ "应用 '{}' 运行成功! 访问连接:\n\t" +
+ "Swagger文档: \t\thttp://{}:{}/doc.html\n\t" +
+ "数据库监控: \t\thttp://{}:{}/druid\n" +
+ "----------------------------------------------------------",
+ env.getProperty("spring.application.name"),
+ InetAddress.getLocalHost().getHostAddress(),
+ env.getProperty("server.port"),
+ "127.0.0.1",
+ env.getProperty("server.port"));
+
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/authScope/DataScope.java b/blade-service/blade-file/src/main/java/org/springblade/file/authScope/DataScope.java
new file mode 100644
index 0000000000000000000000000000000000000000..f66398c7ac5a085bad35c7564d9da76707b54913
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/authScope/DataScope.java
@@ -0,0 +1,135 @@
+package org.springblade.file.authScope;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class DataScope extends HashMap {
+ private String scopeName = "org_id";
+ private String selfScopeName = "create_user";
+ private Long userId;
+ private List orgIds;
+
+ public DataScope() {
+ }
+
+ public String getScopeName() {
+ return this.scopeName;
+ }
+
+ public String getSelfScopeName() {
+ return this.selfScopeName;
+ }
+
+ public Long getUserId() {
+ return this.userId;
+ }
+
+ public List getOrgIds() {
+ return this.orgIds;
+ }
+
+ public void setScopeName(String scopeName) {
+ this.scopeName = scopeName;
+ }
+
+ public void setSelfScopeName(String selfScopeName) {
+ this.selfScopeName = selfScopeName;
+ }
+
+ public void setUserId(Long userId) {
+ this.userId = userId;
+ }
+
+ public void setOrgIds(List orgIds) {
+ this.orgIds = orgIds;
+ }
+
+ @Override
+ public String toString() {
+ return "DataScope(scopeName=" + this.getScopeName() + ", selfScopeName=" + this.getSelfScopeName() + ", userId=" + this.getUserId() + ", orgIds=" + this.getOrgIds() + ")";
+ }
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ } else if (!(o instanceof DataScope)) {
+ return false;
+ } else {
+ DataScope other = (DataScope)o;
+ if (!other.canEqual(this)) {
+ return false;
+ } else if (!super.equals(o)) {
+ return false;
+ } else {
+ label61: {
+ Object this$scopeName = this.getScopeName();
+ Object other$scopeName = other.getScopeName();
+ if (this$scopeName == null) {
+ if (other$scopeName == null) {
+ break label61;
+ }
+ } else if (this$scopeName.equals(other$scopeName)) {
+ break label61;
+ }
+
+ return false;
+ }
+
+ label54: {
+ Object this$selfScopeName = this.getSelfScopeName();
+ Object other$selfScopeName = other.getSelfScopeName();
+ if (this$selfScopeName == null) {
+ if (other$selfScopeName == null) {
+ break label54;
+ }
+ } else if (this$selfScopeName.equals(other$selfScopeName)) {
+ break label54;
+ }
+
+ return false;
+ }
+
+ Object this$userId = this.getUserId();
+ Object other$userId = other.getUserId();
+ if (this$userId == null) {
+ if (other$userId != null) {
+ return false;
+ }
+ } else if (!this$userId.equals(other$userId)) {
+ return false;
+ }
+
+ Object this$orgIds = this.getOrgIds();
+ Object other$orgIds = other.getOrgIds();
+ if (this$orgIds == null) {
+ if (other$orgIds != null) {
+ return false;
+ }
+ } else if (!this$orgIds.equals(other$orgIds)) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ }
+
+ protected boolean canEqual(Object other) {
+ return other instanceof DataScope;
+ }
+ @Override
+ public int hashCode() {
+// int PRIME = true;
+ boolean PRIME = true;
+ int result = super.hashCode();
+ Object $scopeName = this.getScopeName();
+ result = result * 59 + ($scopeName == null ? 43 : $scopeName.hashCode());
+ Object $selfScopeName = this.getSelfScopeName();
+ result = result * 59 + ($selfScopeName == null ? 43 : $selfScopeName.hashCode());
+ Object $userId = this.getUserId();
+ result = result * 59 + ($userId == null ? 43 : $userId.hashCode());
+ Object $orgIds = this.getOrgIds();
+ result = result * 59 + ($orgIds == null ? 43 : $orgIds.hashCode());
+ return result;
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/biz/FileBiz.java b/blade-service/blade-file/src/main/java/org/springblade/file/biz/FileBiz.java
new file mode 100644
index 0000000000000000000000000000000000000000..fd997ef5e79def7c89ef0b7e2d2c34a9d342065b
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/biz/FileBiz.java
@@ -0,0 +1,85 @@
+package org.springblade.file.biz;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import com.github.zuihou.file.utils.ZipUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springblade.file.domain.FileDO;
+import org.springblade.file.enumeration.DataType;
+import org.springblade.file.properties.FileServerProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 文件和附件的一些公共方法
+ *
+ * @author zuihou
+ * @date 2019/05/06
+ */
+@Component
+@Slf4j
+public class FileBiz {
+
+ @Autowired
+ private FileServerProperties fileProperties;
+
+ private static String buildNewFileName(String filename, Integer order) {
+ return StrUtil.strBuilder(filename).insert(filename.lastIndexOf("."), "(" + order + ")").toString();
+ }
+
+ public void down(List list, HttpServletRequest request, HttpServletResponse response) throws Exception {
+ //获取内网前缀地址
+ String innerUriPrefix = fileProperties.getInnerUriPrefix();
+ String remoteUriPrefix = fileProperties.getUriPrefix();
+ log.info("内网前缀地址 innerUriPrefix={}", innerUriPrefix);
+ long fileSize = list.stream().filter((file) -> file != null && !DataType.DIR.eq(file.getDataType()) && StringUtils.isNotEmpty(file.getUrl()))
+ .mapToLong((file) -> Convert.toLong(file.getSize(), 0L)).sum();
+ String extName = list.get(0).getSubmittedFileName();
+ if (list.size() > 1) {
+ extName = StringUtils.substring(extName, 0, StringUtils.lastIndexOf(extName, ".")) + "等.zip";
+ }
+
+ Map map = new LinkedHashMap<>(list.size());
+ Map duplicateFile = new HashMap<>(list.size());
+ list.stream()
+ //过滤不符合要求的文件
+ .filter((file) -> file != null && !DataType.DIR.eq(file.getDataType()) && StringUtils.isNotEmpty(file.getUrl()))
+ //将外网地址转成内网地址
+ .map((file) -> {
+ String url = file.getUrl();
+ if (StringUtils.isNotEmpty(innerUriPrefix)) {
+ //转为内网渠道下载
+ url = url.replaceAll(remoteUriPrefix, innerUriPrefix);
+ log.info("文件转内网 url地址 ={}", url);
+ }
+ file.setUrl(url);
+ return file;
+ })
+ //循环处理相同的文件名
+ .forEach((file) -> {
+ String submittedFileName = file.getSubmittedFileName();
+ if (map.containsKey(submittedFileName)) {
+ if (duplicateFile.containsKey(submittedFileName)) {
+ duplicateFile.put(submittedFileName, duplicateFile.get(submittedFileName) + 1);
+ } else {
+ duplicateFile.put(submittedFileName, 1);
+ }
+ submittedFileName = buildNewFileName(submittedFileName, duplicateFile.get(submittedFileName));
+ }
+ map.put(submittedFileName, file.getUrl());
+ });
+
+
+ ZipUtils.zipFilesByInputStream(map, Long.valueOf(fileSize), extName, request, response);
+ }
+
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/AttachmentController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/AttachmentController.java
new file mode 100644
index 0000000000000000000000000000000000000000..d6df252cdce4ca509329157ed8ce31dece690d02
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/AttachmentController.java
@@ -0,0 +1,213 @@
+package org.springblade.file.controller;
+
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springblade.core.tool.api.R;
+import org.springblade.file.dto.AttachmentDTO;
+import org.springblade.file.dto.FilePageReqDTO;
+import org.springblade.file.entity.Attachment;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.stream.Collectors.groupingBy;
+
+/**
+ *
+ * 附件表 前端控制器
+ *
+ *
+ * @author zuihou
+ * @since 2019-04-29
+ */
+@RestController
+@RequestMapping("/attachment")
+@Slf4j
+@Api(value = "附件", tags = "附件")
+@Validated
+public class AttachmentController{
+
+ /**
+ * 业务类型判断符
+ */
+ private final static String TYPE_BIZ_ID = "bizId";
+
+ @Override
+ public void query(PageParams params, IPage page, Long defSize) {
+ baseService.page(page, params.getModel());
+ }
+
+ @Override
+ public R handlerDelete(List ids) {
+ return R.success(baseService.remove(ids));
+ }
+
+ /**
+ * 上传文件
+ *
+ * @param
+ * @return
+ * @author zuihou
+ * @date 2019-05-06 16:28
+ */
+ @ApiOperation(value = "附件上传", notes = "附件上传")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "isSingle", value = "是否单文件", dataType = "boolean", paramType = "query"),
+ @ApiImplicitParam(name = "id", value = "文件id", dataType = "long", paramType = "query"),
+ @ApiImplicitParam(name = "bizId", value = "业务id", dataType = "long", paramType = "query"),
+ @ApiImplicitParam(name = "bizType", value = "业务类型", dataType = "long", paramType = "query"),
+ @ApiImplicitParam(name = "file", value = "附件", dataType = "MultipartFile", allowMultiple = true, required = true),
+ })
+ @PostMapping(value = "/upload")
+ public R upload(
+ @RequestParam(value = "file") MultipartFile file,
+ @RequestParam(value = "isSingle", required = false, defaultValue = "false") Boolean isSingle,
+ @RequestParam(value = "id", required = false) Long id,
+ @RequestParam(value = "bizId", required = false) String bizId,
+ @RequestParam(value = "bizType", required = false) String bizType) {
+
+ if(StringUtils.isBlank(bizType)){
+ return R.fail("业务类型不能为空");
+ }
+ // 忽略路径字段,只处理文件类型
+ if (file.isEmpty()) {
+ return R.fail("请求中必须至少包含一个有效文件");
+ }
+ String tenant = BaseContextHandler.getTenant();
+
+ AttachmentDTO attachment = baseService.upload(file, tenant, id, bizType, bizId, isSingle);
+ return R.data(attachment);
+ }
+
+
+ @ApiOperation(value = "根据业务类型或业务id删除文件", notes = "根据业务类型或业务id删除文件")
+ @DeleteMapping(value = "/biz")
+ @SysLog("根据业务类型删除附件")
+ public R removeByBizIdAndBizType(@RequestBody AttachmentRemoveDTO dto) {
+ return R.success(baseService.removeByBizIdAndBizType(dto.getBizId(), dto.getBizType()));
+ }
+
+ @ApiOperation(value = "查询附件", notes = "查询附件")
+ @ApiResponses(
+ @ApiResponse(code = 60103, message = "文件id为空")
+ )
+ @GetMapping
+ @SysLog("根据业务类型查询附件")
+ public R> findAttachment(@RequestParam(value = "bizTypes", required = false) String[] bizTypes,
+ @RequestParam(value = "bizIds", required = false) String[] bizIds) {
+ //不能同时为空
+ BizAssert.isTrue(!(ArrayUtils.isEmpty(bizTypes) && ArrayUtils.isEmpty(bizIds)), BASE_VALID_PARAM.build("业务类型不能为空"));
+ return R.success(baseService.find(bizTypes, bizIds));
+ }
+
+ @ApiOperation(value = "根据业务类型或者业务id查询附件", notes = "根据业务类型或者业务id查询附件")
+ @GetMapping(value = "/biz/{type}")
+ @SysLog("根据业务类型分组查询附件")
+ public R>> findAttachmentByBiz(@PathVariable String type, @RequestParam("biz[]") String[] biz) {
+ SFunction sf = Attachment::getBizType;
+ if (TYPE_BIZ_ID.equalsIgnoreCase(type)) {
+ sf = Attachment::getBizId;
+ }
+ List list = baseService.list(Wrappers.lambdaQuery().in(sf, biz).orderByAsc(Attachment::getCreateTime));
+ if (list.isEmpty()) {
+ return R.success(MapUtils.EMPTY_MAP);
+ }
+ if (TYPE_BIZ_ID.equalsIgnoreCase(type)) {
+ return R.success(list.stream().collect(groupingBy(Attachment::getBizType)));
+ } else {
+ return R.success(list.stream().collect(groupingBy(Attachment::getBizId)));
+ }
+ }
+
+
+ /**
+ * 下载一个文件或多个文件打包下载
+ *
+ * @param ids 文件id
+ * @param response
+ * @throws Exception
+ */
+ @ApiOperation(value = "根据文件id打包下载", notes = "根据附件id下载多个打包的附件")
+ @GetMapping(value = "/download", produces = "application/octet-stream")
+ @SysLog("下载附件")
+ public void download(
+ @ApiParam(name = "ids[]", value = "文件id 数组")
+ @RequestParam(value = "ids[]") Long[] ids,
+ HttpServletRequest request, HttpServletResponse response) throws Exception {
+ BizAssert.isTrue(ArrayUtils.isNotEmpty(ids), BASE_VALID_PARAM.build("附件id不能为空"));
+ baseService.download(request, response, ids);
+ }
+
+ /**
+ * 根据业务类型或者业务id其中之一,或者2个同时打包下载文件
+ *
+ * @param bizIds 业务id
+ * @param bizTypes 业务类型
+ * @return
+ * @author zuihou
+ * @date 2019-05-12 21:23
+ */
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "bizIds[]", value = "业务id数组", dataType = "array", paramType = "query"),
+ @ApiImplicitParam(name = "bizTypes[]", value = "业务类型数组", dataType = "array", paramType = "query"),
+ })
+ @ApiOperation(value = "根据业务类型/业务id打包下载", notes = "根据业务id下载一个文件或多个文件打包下载")
+ @GetMapping(value = "/download/biz", produces = "application/octet-stream")
+ @SysLog("根据业务类型下载附件")
+ public void downloadByBiz(
+ @RequestParam(value = "bizIds[]", required = false) String[] bizIds,
+ @RequestParam(value = "bizTypes[]", required = false) String[] bizTypes,
+ HttpServletRequest request, HttpServletResponse response) throws Exception {
+ BizAssert.isTrue(!(ArrayUtils.isEmpty(bizTypes) && ArrayUtils.isEmpty(bizIds)), BASE_VALID_PARAM.build("附件业务id和业务类型不能同时为空"));
+ baseService.downloadByBiz(request, response, bizTypes, bizIds);
+ }
+
+ /**
+ * 根据下载地址下载文件
+ *
+ * @param url 文件链接
+ * @param filename 文件名称
+ * @return
+ * @author zuihou
+ * @date 2019-05-12 21:24
+ */
+ @ApiOperation(value = "根据url下载文件(不推荐)", notes = "根据文件的url下载文件(不推荐使用,若要根据url下载,请执行通过nginx)")
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "url", value = "文件url", dataType = "string", paramType = "query"),
+ @ApiImplicitParam(name = "filename", value = "文件名", dataType = "string", paramType = "query"),
+ })
+ @GetMapping(value = "/download/url", produces = "application/octet-stream")
+ @SysLog("根据文件连接下载文件")
+ public void downloadUrl(@RequestParam(value = "url") String url, @RequestParam(value = "filename", required = false) String filename,
+ HttpServletRequest request, HttpServletResponse response) throws Exception {
+ BizAssert.isTrue(StringUtils.isNotEmpty(url), BASE_VALID_PARAM.build("附件下载地址不能为空"));
+ log.info("name={}, url={}", filename, url);
+ baseService.downloadByUrl(request, response, url, filename);
+ }
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileChunkController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileChunkController.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b7fb8ad3750d92f349ee04e3369493f8bdd6d58
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileChunkController.java
@@ -0,0 +1,151 @@
+package org.springblade.file.controller;
+
+
+import com.github.zuihou.file.service.FileService;
+import com.github.zuihou.file.strategy.FileChunkStrategy;
+import com.github.zuihou.file.strategy.FileStrategy;
+import com.github.zuihou.file.utils.FileDataTypeUtil;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.core.tool.api.R;
+import org.springblade.file.domain.FileAttrDO;
+import org.springblade.file.dto.chunk.FileChunkCheckDTO;
+import org.springblade.file.dto.chunk.FileChunksMergeDTO;
+import org.springblade.file.dto.chunk.FileUploadDTO;
+import org.springblade.file.entity.File;
+import org.springblade.file.manager.WebUploader;
+import org.springblade.file.properties.FileServerProperties;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.nio.file.Paths;
+
+/**
+ * 注意:该类下的所有方法均需要webuploder.js插件进行配合使用。
+ * md5
+ *
+ * @author zuihou
+ * @date 2018/08/24
+ */
+@RestController
+@Slf4j
+@RequestMapping("/chunk")
+@Api(value = "文件续传+秒传", tags = "文件续传+秒传功能,所有方法均需要webuploder.js插件进行配合使用, 且4个方法需要配合使用,单核接口没有意义")
+public class FileChunkController {
+ @Autowired
+ private FileServerProperties fileProperties;
+ @Autowired
+ private FileService fileService;
+ @Resource
+ private FileStrategy fileStrategy;
+ @Resource
+ private FileChunkStrategy fileChunkStrategy;
+ @Autowired
+ private WebUploader wu;
+
+
+ /**
+ * 采用md5 上传前的验证
+ *
+ * @param md5 文件md5
+ * @return
+ */
+ @ApiOperation(value = "秒传接口,上传文件前先验证, 存在则启动秒传", notes = "前端通过webUploader获取文件md5,上传前的验证")
+ @RequestMapping(value = "/md5", method = RequestMethod.POST)
+ @ResponseBody
+ public R saveMd5Check(@RequestParam(name = "md5") String md5,
+ @RequestParam(name = "folderId", defaultValue = "0") Long folderId) {
+ Long accountId = BaseContextHandler.getUserId();
+ File file = fileChunkStrategy.md5Check(md5, folderId, accountId);
+ return R.success(file != null ? true : false);
+ }
+
+ /**
+ * 检查分片存不存在
+ *
+ * @param info
+ * @return
+ */
+ @ApiOperation(value = "续传接口,检查每个分片存不存在", notes = "断点续传功能检查分片是否存在, 已存在的分片无需重复上传, 达到续传效果")
+ @RequestMapping(value = "/check", method = RequestMethod.POST)
+ @ResponseBody
+ public R chunkCheck(@RequestBody FileChunkCheckDTO info) {
+ log.info("info={}", info);
+ String uploadFolder = FileDataTypeUtil.getUploadPathPrefix(fileProperties.getStoragePath());
+ //检查目标分片是否存在且完整
+ boolean chunkCheck = wu.chunkCheck(Paths.get(uploadFolder, info.getName(), String.valueOf(info.getChunkIndex())).toString(), info.getSize());
+ return R.success(chunkCheck);
+ }
+
+
+ /**
+ * 分片上传
+ * 该接口不能用作 单文件上传!
+ *
+ * @param info
+ * @param file
+ * @return
+ */
+ @ApiOperation(value = "分片上传", notes = "前端通过webUploader获取截取分片, 然后逐个上传")
+ @RequestMapping(value = "/upload", method = RequestMethod.POST)
+ @ResponseBody
+ public R uploadFile(FileUploadDTO info, @RequestParam(value = "file", required = false) MultipartFile file) throws Exception {
+ String uploadFolder = FileDataTypeUtil.getUploadPathPrefix(fileProperties.getStoragePath());
+ //验证请求不会包含数据上传,所以避免NullPoint这里要检查一下file变量是否为null
+ if (file == null || file.isEmpty()) {
+ log.error("请求参数不完整");
+ return R.fail("请求参数不完整");
+ }
+
+ log.info("info={}", info);
+ /*
+ 将MD5签名和合并后的文件path存入持久层,注意这里这个需求导致需要修改webuploader.js源码3170行
+ 因为原始webuploader.js不支持为formData设置函数类型参数,这将导致不能在控件初始化后修改该参数
+ 文件大小 小于 单个分片时,会执行这里的代码
+ */
+ if (info.getChunks() == null || info.getChunks() <= 0) {
+ File upload = fileStrategy.upload(file);
+
+ FileAttrDO fileAttrDO = fileService.getFileAttrDo(info.getFolderId());
+ upload.setFolderId(info.getFolderId());
+ upload.setFileMd5(info.getMd5());
+ upload.setFolderName(fileAttrDO.getFolderName());
+ upload.setGrade(fileAttrDO.getGrade());
+ upload.setTreePath(fileAttrDO.getTreePath());
+ fileService.save(upload);
+ return R.success(file.getOriginalFilename());
+ } else {
+ //为上传的文件准备好对应的位置
+ java.io.File target = wu.getReadySpace(info, uploadFolder);
+ log.info("target={}", target.getAbsolutePath());
+ if (target == null) {
+ return R.fail(wu.getErrorMsg());
+ }
+ //保存上传文件
+ file.transferTo(target);
+ return R.success(target.getName());
+ }
+
+
+ }
+
+ /**
+ * 分片通过nio合并, 合并成功后,将文件上传至fastdfs
+ * nio合并优点: 有效防止大文件的内存溢出
+ *
+ * @param info
+ * @return
+ */
+ @ApiOperation(value = "分片合并", notes = "所有分片上传成功后,调用该接口对分片进行合并")
+ @RequestMapping(value = "/merge", method = RequestMethod.POST)
+ @ResponseBody
+ public R saveChunksMerge(@RequestBody FileChunksMergeDTO info) {
+ log.info("info={}", info);
+
+ return fileChunkStrategy.chunksMerge(info);
+ }
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java
new file mode 100644
index 0000000000000000000000000000000000000000..3cd9bba5b136de7bb70c54f4e54e6a3bd1d5b3e9
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/FileController.java
@@ -0,0 +1,130 @@
+package org.springblade.file.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.zuihou.base.R;
+import com.github.zuihou.base.controller.SuperController;
+import com.github.zuihou.base.request.PageParams;
+import com.github.zuihou.file.dto.FilePageReqDTO;
+import com.github.zuihou.file.dto.FileUpdateDTO;
+import com.github.zuihou.file.dto.FolderDTO;
+import com.github.zuihou.file.dto.FolderSaveDTO;
+import com.github.zuihou.file.entity.File;
+import com.github.zuihou.file.manager.FileRestManager;
+import com.github.zuihou.file.service.FileService;
+import com.github.zuihou.log.annotation.SysLog;
+import com.github.zuihou.utils.BeanPlusUtil;
+import io.swagger.annotations.*;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ *
+ * 文件表 前端控制器
+ *
+ *
+ * @author zuihou
+ * @since 2019-04-29
+ */
+@Validated
+@RestController
+@RequestMapping("/file")
+@Slf4j
+@Api(value = "文件表", tags = "文件表")
+public class FileController extends SuperController {
+ @Autowired
+ private FileRestManager fileRestManager;
+
+ @Override
+ public void query(PageParams params, IPage page, Long defSize) {
+ fileRestManager.page(page, params.getModel());
+ }
+
+ @Override
+ public R handlerSave(FolderSaveDTO model) {
+ FolderDTO folder = baseService.saveFolder(model);
+ return success(BeanPlusUtil.toBean(folder, File.class));
+ }
+
+ /**
+ * 上传文件
+ *
+ * @param
+ * @return
+ * @author zuihou
+ * @date 2019-05-06 16:28
+ */
+ @ApiOperation(value = "上传文件", notes = "上传文件 ")
+ @ApiResponses({
+ @ApiResponse(code = 60102, message = "文件夹为空"),
+ })
+ @ApiImplicitParams({
+ @ApiImplicitParam(name = "folderId", value = "文件夹id", dataType = "long", paramType = "query"),
+ @ApiImplicitParam(name = "file", value = "附件", dataType = "MultipartFile", allowMultiple = true, required = true),
+ })
+ @RequestMapping(value = "/upload", method = RequestMethod.POST)
+ @SysLog("上传文件")
+ public R upload(
+ @NotNull(message = "文件夹不能为空")
+ @RequestParam(value = "folderId") Long folderId,
+ @RequestParam(value = "file") MultipartFile simpleFile) {
+ //1,先将文件存在本地,并且生成文件名
+ log.info("contentType={}, name={} , sfname={}", simpleFile.getContentType(), simpleFile.getName(), simpleFile.getOriginalFilename());
+ // 忽略路径字段,只处理文件类型
+ if (simpleFile.getContentType() == null) {
+ return fail("文件为空");
+ }
+
+ File file = baseService.upload(simpleFile, folderId);
+
+ return success(file);
+ }
+
+
+ @Override
+ public R handlerUpdate(FileUpdateDTO fileUpdateDTO) {
+ // 判断文件名是否有 后缀
+ if (StringUtils.isNotEmpty(fileUpdateDTO.getSubmittedFileName())) {
+ File oldFile = baseService.getById(fileUpdateDTO.getId());
+ if (oldFile.getExt() != null && !fileUpdateDTO.getSubmittedFileName().endsWith(oldFile.getExt())) {
+ fileUpdateDTO.setSubmittedFileName(fileUpdateDTO.getSubmittedFileName() + "." + oldFile.getExt());
+ }
+ }
+ File file = BeanPlusUtil.toBean(fileUpdateDTO, File.class);
+
+ baseService.updateById(file);
+ return success(file);
+ }
+
+ @Override
+ public R handlerDelete(List ids) {
+ Long userId = getUserId();
+ return success(baseService.removeList(userId, ids));
+ }
+
+ /**
+ * 下载一个文件或多个文件打包下载
+ *
+ * @param ids
+ * @param response
+ * @throws Exception
+ */
+ @ApiOperation(value = "下载一个文件或多个文件打包下载", notes = "下载一个文件或多个文件打包下载")
+ @GetMapping(value = "/download", produces = "application/octet-stream")
+ @SysLog("下载文件")
+ public void download(
+ @ApiParam(name = "ids[]", value = "文件id 数组")
+ @RequestParam(value = "ids[]") Long[] ids,
+ HttpServletRequest request, HttpServletResponse response) throws Exception {
+ fileRestManager.download(request, response, ids, null);
+ }
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/controller/StatisticsController.java b/blade-service/blade-file/src/main/java/org/springblade/file/controller/StatisticsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b1ca947c5c01de1ade39b64ae6feeaa1d7fd76e
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/controller/StatisticsController.java
@@ -0,0 +1,81 @@
+package org.springblade.file.controller;
+
+import com.github.zuihou.base.R;
+import com.github.zuihou.file.domain.FileStatisticsDO;
+import com.github.zuihou.file.dto.FileOverviewDTO;
+import com.github.zuihou.file.dto.FileStatisticsAllDTO;
+import com.github.zuihou.file.service.FileService;
+import com.github.zuihou.security.annotation.LoginUser;
+import com.github.zuihou.security.model.SysUser;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import springfox.documentation.annotations.ApiIgnore;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * 文件用户大小表 前端控制器
+ *
+ *
+ * @author zuihou
+ * @since 2019-05-07
+ */
+@Slf4j
+@RestController
+@RequestMapping("/statistics")
+@Api(value = "Statistics", tags = "统计接口")
+public class StatisticsController {
+
+ @Autowired
+ private FileService fileService;
+
+ @ApiOperation(value = "云盘首页数据概览", notes = "云盘首页数据概览")
+ @GetMapping(value = "/overview")
+ public R overview(@ApiIgnore @LoginUser SysUser user) {
+ return R.success(fileService.findOverview(user.getId(), null, null));
+ }
+
+ @ApiOperation(value = "按照类型,统计各种类型的 大小和数量", notes = "按照类型,统计当前登录人各种类型的大小和数量")
+ @GetMapping(value = "/type")
+ public R> findAllByDataType(@ApiIgnore @LoginUser SysUser user) {
+ return R.success(fileService.findAllByDataType(user.getId()));
+ }
+
+// @ApiOperation(value = "云盘首页个人文件下载数量排行", notes = "云盘首页个人文件下载数量排行")
+// @GetMapping(value = "/downTop20")
+// public R> downTop20() {
+// return success(fileService.downTop20(getUserId()));
+// }
+
+ @ApiOperation(value = "按照时间统计各种类型的文件的数量和大小", notes = "按照时间统计各种类型的文件的数量和大小 不指定时间,默认查询一个月")
+ @GetMapping(value = "")
+ public R findNumAndSizeToTypeByDate(@RequestParam(value = "startTime", required = false) LocalDateTime startTime,
+ @RequestParam(value = "endTime", required = false) LocalDateTime endTime,
+ @ApiIgnore @LoginUser SysUser user) {
+ return R.success(fileService.findNumAndSizeToTypeByDate(user.getId(), startTime, endTime));
+ }
+
+// @ApiOperation(value = "按照时间统计下载数量", notes = "按照时间统计下载数量 不指定时间,默认查询一个月")
+// @GetMapping(value = "/down")
+// public R findDownSizeByDate(@RequestParam(value = "startTime", required = false) LocalDateTime startTime,
+// @RequestParam(value = "endTime", required = false) LocalDateTime endTime) {
+// Long userId = getUserId();
+// return success(fileService.findDownSizeByDate(userId, startTime, endTime));
+// }
+
+
+ @GetMapping(value = "/test1")
+ public R test1(@RequestParam(value = "sleep", required = false, defaultValue = "10000") Long sleep) throws Exception {
+ Thread.sleep(sleep);
+ return R.success("等了" + sleep);
+ }
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/manager/FileRestManager.java b/blade-service/blade-file/src/main/java/org/springblade/file/manager/FileRestManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..7a4a86b3799100b63ddaec76997168fa42178db3
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/manager/FileRestManager.java
@@ -0,0 +1,58 @@
+package org.springblade.file.manager;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.zuihou.file.service.FileService;
+import org.apache.commons.lang3.StringUtils;
+import org.springblade.file.dto.FilePageReqDTO;
+import org.springblade.file.entity.File;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+
+/**
+ * 文件 公共代码 管理类
+ *
+ * @author zuihou
+ * @date 2019/05/21
+ */
+@Component
+public class FileRestManager {
+ @Autowired
+ private FileService fileService;
+
+ public IPage page(IPage page, FilePageReqDTO filePageReq) {
+ //查询文件分页数据
+ Long userId = BaseContextHandler.getUserId();
+
+ //类型和文件夹id同时为null时, 表示查询 全部文件
+ if (filePageReq.getFolderId() == null && filePageReq.getDataType() == null) {
+ filePageReq.setFolderId(DEF_PARENT_ID);
+ }
+
+ QueryWrapper query = new QueryWrapper<>();
+ LambdaQueryWrapper lambdaQuery = query.lambda()
+ .eq(File::getIsDelete, false)
+ .eq(filePageReq.getDataType() != null, File::getDataType, filePageReq.getDataType())
+ .eq(filePageReq.getFolderId() != null, File::getFolderId, filePageReq.getFolderId())
+ .like(StringUtils.isNotEmpty(filePageReq.getSubmittedFileName()), File::getSubmittedFileName, filePageReq.getSubmittedFileName())
+ .eq(userId != null && userId != 0, File::getCreateUser, userId);
+
+ query.orderByDesc(String.format("case when %s='DIR' THEN 1 else 0 end", FileConstants.DATA_TYPE));
+ lambdaQuery.orderByDesc(File::getCreateTime);
+
+ fileService.page(page, lambdaQuery);
+
+ return page;
+ }
+
+ public void download(HttpServletRequest request, HttpServletResponse response, Long[] ids, Long userId) throws Exception {
+ userId = userId == null || userId <= 0 ? BaseContextHandler.getUserId() : userId;
+ fileService.download(request, response, ids, userId);
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/manager/WebUploader.java b/blade-service/blade-file/src/main/java/org/springblade/file/manager/WebUploader.java
new file mode 100644
index 0000000000000000000000000000000000000000..db2652be655a5794727116974e7312d5be71ce72
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/manager/WebUploader.java
@@ -0,0 +1,168 @@
+package org.springblade.file.manager;
+
+
+import com.github.zuihou.file.dto.chunk.FileUploadDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.springblade.file.dto.chunk.FileUploadDTO;
+import org.springframework.context.annotation.Scope;
+import org.springframework.stereotype.Service;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 分片上传工具类
+ *
+ * @author zuihou
+ * @date 2019-06-14 11:50
+ */
+@Service
+@Scope("prototype")
+@Slf4j
+public class WebUploader {
+
+ /**
+ * 错误详情
+ */
+ private String msg;
+
+ /**
+ * 分片验证
+ * 验证对应分片文件是否存在,大小是否吻合
+ *
+ * @param file 分片文件的路径
+ * @param size 分片文件的大小
+ * @return
+ */
+ public boolean chunkCheck(String file, Long size) {
+ //检查目标分片是否存在且完整
+ java.io.File target = new java.io.File(file);
+ return target.isFile() && size == target.length();
+ }
+
+ /**
+ * 为上传的文件创建对应的保存位置
+ * 若上传的是分片,则会创建对应的文件夹结构和tmp文件
+ *
+ * @param info 上传文件的相关信息
+ * @param path 文件保存根路径
+ * @return
+ */
+ public java.io.File getReadySpace(FileUploadDTO info, String path) {
+ //创建上传文件所需的文件夹
+ if (!this.createFileFolder(path, false)) {
+ return null;
+ }
+
+ String newFileName; //上传文件的新名称
+
+ //如果是分片上传,则需要为分片创建文件夹
+ if (info.getChunks() > 0) {
+ newFileName = String.valueOf(info.getChunk());
+
+ String fileFolder = this.md5(info.getName() + info.getType() + info.getLastModifiedDate() + info.getSize());
+ log.info("fileFolder={}, md5={}", fileFolder, info.getMd5());
+ if (fileFolder == null) {
+ return null;
+ }
+
+ //文件上传路径更新为指定文件信息签名后的临时文件夹,用于后期合并
+ path += "/" + fileFolder;
+
+ if (!this.createFileFolder(path, true)) {
+ return null;
+ }
+ return new java.io.File(path, newFileName);
+ }
+ return null;
+ }
+
+ /**
+ * 创建存放上传的文件的文件夹
+ *
+ * @param file 文件夹路径
+ * @param hasTmp 是否有临时文件
+ * @return
+ */
+ private boolean createFileFolder(String file, boolean hasTmp) {
+ //创建存放分片文件的临时文件夹
+ java.io.File tmpFile = new java.io.File(file);
+ if (!tmpFile.exists()) {
+ try {
+ tmpFile.mkdirs();
+ } catch (SecurityException ex) {
+ log.error("无法创建文件夹", ex);
+ this.setErrorMsg("无法创建文件夹");
+ return false;
+ }
+ }
+
+ if (hasTmp) {
+ //创建一个对应的文件,用来记录上传分片文件的修改时间,用于清理长期未完成的垃圾分片
+ tmpFile = new java.io.File(file + ".tmp");
+ if (tmpFile.exists()) {
+ return tmpFile.setLastModified(System.currentTimeMillis());
+ } else {
+ try {
+ tmpFile.createNewFile();
+ } catch (IOException ex) {
+ log.error("无法创建tmp文件", ex);
+ this.setErrorMsg("无法创建tmp文件");
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * MD5签名
+ *
+ * @param content 要签名的内容
+ * @return
+ */
+ private String md5(String content) {
+ StringBuffer sb = new StringBuffer();
+ try {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+ md5.update(content.getBytes("UTF-8"));
+ byte[] tmpFolder = md5.digest();
+
+ for (int i = 0; i < tmpFolder.length; i++) {
+ sb.append(Integer.toString((tmpFolder[i] & 0xff) + 0x100, 16).substring(1));
+ }
+
+ return sb.toString();
+ } catch (NoSuchAlgorithmException ex) {
+ log.error("无法生成文件的MD5签名", ex);
+ this.setErrorMsg("无法生成文件的MD5签名");
+ return null;
+ } catch (UnsupportedEncodingException ex) {
+ log.error("无法生成文件的MD5签名", ex);
+ this.setErrorMsg("无法生成文件的MD5签名");
+ return null;
+ }
+ }
+
+ /**
+ * 获取错误详细
+ *
+ * @return
+ */
+ public String getErrorMsg() {
+ return this.msg;
+ }
+
+ /**
+ * 记录异常错误信息
+ *
+ * @param msg 错误详细
+ */
+ private void setErrorMsg(String msg) {
+ this.msg = msg;
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..7984ed44741fafbc12fc3dabacc716fd1cfb0acc
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.java
@@ -0,0 +1,54 @@
+package org.springblade.file.mapper;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import org.apache.ibatis.annotations.Param;
+import org.springblade.file.authScope.DataScope;
+import org.springblade.file.dto.AttachmentResultDTO;
+import org.springblade.file.entity.Attachment;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+/**
+ *
+ * Mapper 接口
+ * 附件
+ *
+ *
+ * @author zuihou
+ * @date 2019-06-24
+ */
+@Repository
+public interface AttachmentMapper extends BaseMapper {
+ /**
+ * 根据业务类型和业务id, 按照分组查询附件
+ *
+ * @param bizTypes
+ * @param bizIds
+ * @return
+ */
+ List find(@Param("bizTypes") String[] bizTypes, @Param("bizIds") String[] bizIds);
+
+ /**
+ * 查询不在指定id集合中的数据
+ *
+ * @param ids
+ * @param group
+ * @param path
+ * @return
+ */
+ Integer countByGroup(@Param("ids") List ids, @Param("group") String group, @Param("path") String path);
+
+ /**
+ * 按权限查询数据
+ *
+ * @param page
+ * @param wrapper
+ * @param dataScope
+ * @return
+ */
+ IPage page(IPage page, @Param(Constants.WRAPPER) Wrapper wrapper, DataScope dataScope);
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a50bdec0518ab7c0c909f31bf753fe948ab564ea
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/AttachmentMapper.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, create_time, create_user, update_time, update_user,
+ biz_id, biz_type, data_type, submitted_file_name, group_, path, relative_path, url,
+ file_md5, context_type, filename, ext, size, org_id, icon, create_month, create_week, create_day
+
+
+
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileMapper.java b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileMapper.java
new file mode 100644
index 0000000000000000000000000000000000000000..b43cad27756cae4dbb3de71e262460d396d9583a
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileMapper.java
@@ -0,0 +1,72 @@
+package org.springblade.file.mapper;
+
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import org.springblade.file.domain.FileQueryDO;
+import org.springblade.file.domain.FileStatisticsDO;
+import org.springblade.file.entity.File;
+import org.springframework.stereotype.Repository;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * Mapper 接口
+ * 文件表
+ *
+ *
+ * @author zuihou
+ * @date 2019-06-24
+ */
+@Repository
+public interface FileMapper extends BaseMapper {
+ /**
+ * 查询文件以及它的父类
+ *
+ * @param userId
+ * @param ids
+ * @return
+ * @author zuihou
+ * @date 2019-05-07 20:49
+ */
+ List findByIds(@Param("userId") Long userId, @Param("ids") Long[] ids);
+
+ /**
+ * 按照日期类型,时间区间,来查询指定用户的各种类型的 数量和大小
+ *
+ * @param userId
+ * @param dateType 日期类型
+ * @param dataType 数据类型 数据类型=ALL 按类型统计所有, =指定类型时(不等null), 统计指定类型 , =null 时不区分类型统计所有
+ * @param startTime
+ * @param endTime
+ * @return FileStatisticsDO
+ */
+ List findNumAndSizeByUserId(@Param("userId") Long userId,
+ @Param("dateType") String dateType,
+ @Param("dataType") String dataType,
+ @Param("startTime") LocalDateTime startTime,
+ @Param("endTime") LocalDateTime endTime);
+
+ /**
+ * 查询下次次数前20的文件
+ *
+ * @param userId
+ * @return
+ */
+ List findDownTop20(@Param("userId") Long userId);
+
+ /**
+ * 统计时间区间内文
+ * @param userId
+ * @param dateType 日期类型 {MONTH:按月;WEEK:按周;DAY:按日} 来统计
+ * @param startTime
+ * @param endTime
+ * @return
+ */
+ List findDownSizeByDate(@Param("userId") Long userId,
+ @Param("dateType") String dateType,
+ @Param("startTime") LocalDateTime startTime,
+ @Param("endTime") LocalDateTime endTime);
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a6bc70da9d2c2d04bec1b1e3aeea2523b6000b20
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/FileMapper.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id, create_time, create_user, update_time, update_user,
+ data_type, submitted_file_name, tree_path, grade, is_delete, folder_id, url, size, folder_name, group_, path, relative_path, file_md5, context_type, filename, ext, icon, create_month, create_week, create_day
+
+
+
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/ext/AttachmentMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/ext/AttachmentMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fe55bc4be280b85e3032db13264d4d8dd0b0bd08
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/ext/AttachmentMapper.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+ SELECT
+
+ FROM f_attachment a
+ where a.biz_id = #{bizId, jdbcType=VARCHAR} and a.biz_type = #{bizType, jdbcType=VARCHAR}
+ order by a.create_time desc
+
+
+ SELECT DISTINCT a.biz_id biz_id, a.biz_type biz_type FROM f_attachment a
+ where 1=1
+
+ and a.biz_id in
+
+ #{id, jdbcType=VARCHAR}
+
+
+
+ and a.biz_type in
+
+ #{type, jdbcType=VARCHAR}
+
+
+
+
+
+
+ select count(id) from f_file where id not in
+
+ #{id, jdbcType=BIGINT}
+
+ and group_ = #{group, jdbcType=VARCHAR}
+ and path = #{path, jdbcType=VARCHAR}
+
+
+
+
+ SELECT
+
+ FROM f_attachment f ${ew.customSqlSegment}
+
+
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/mapper/ext/FileMapper.xml b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/ext/FileMapper.xml
new file mode 100644
index 0000000000000000000000000000000000000000..533743414e1049bba73f90d44c105829c70e02e2
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/mapper/ext/FileMapper.xml
@@ -0,0 +1,156 @@
+
+
+
+
+
+
+
+
+
+
+ select
+
+ from f_file where id = #{id,jdbcType=BIGINT}
+
+
+ select
+
+ from f_file where id in
+
+ #{id}
+
+ and create_user = #{userId, jdbcType=BIGINT}
+
+
+
+
+
+
+
+
+
+
+ SELECT
+
+ max(data_type) data_type,
+
+
+ max(t.create_month) `date_type`,
+
+
+ t.create_month `date_type`,
+
+
+ t.create_week `date_type`,
+
+
+ t.create_day `date_type`,
+
+ count(id) num, sum(size) size
+ from f_file t
+ where is_delete = 0
+
+ and create_user = #{userId, jdbcType=BIGINT}
+
+
+ and data_type = #{dataType,jdbcType=VARCHAR}
+
+
+ and t.create_time >= #{startTime, jdbcType=TIMESTAMP}
+
+
+ and t.create_time #{endTime, jdbcType=TIMESTAMP}
+
+
+
+ GROUP BY data_type
+
+ ,t.create_month
+
+
+ , t.create_week
+
+
+ , t.create_day
+
+
+
+ order by t.create_month asc , data_type asc
+
+
+ order by t.create_week asc , data_type asc
+
+
+ order by t.create_day asc , data_type asc
+
+
+
+
+ GROUP BY
+
+ t.create_month
+
+
+ t.create_week
+
+
+ t.create_day
+
+
+ order by t.create_month asc
+
+
+ order by t.create_week asc
+
+
+ order by t.create_day asc
+
+
+
+
+
+
+ SELECT f.submitted_file_name date_type, count(IFNULL(ds.id, 0)) AS num
+ FROM f_file f LEFT JOIN f_down_water ds ON ds.file_id = f.id
+ where f.create_user = #{userId, jdbcType=BIGINT} and ds.id is not null
+ group by f.id
+ ORDER BY num DESC, f.create_time desc
+ LIMIT 20
+
+
+
+
+ select
+
+ water.create_month
+
+
+ water.create_week
+
+
+ water.create_day
+
+ AS dateType,
+ sum(f.size) AS size, count(water.id) AS num
+ from f_down_water water LEFT JOIN f_file f ON water.file_id = f.id
+ where 1=1
+
+ and water.create_time >= #{startTime, jdbcType=TIMESTAMP}
+
+
+ and water.create_time #{endTime, jdbcType=TIMESTAMP}
+
+ and f.create_user=1
+ group by
+
+ water.create_month
+
+
+ water.create_week
+
+
+ water.create_day
+
+
+
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/properties/FileServerProperties.java b/blade-service/blade-file/src/main/java/org/springblade/file/properties/FileServerProperties.java
new file mode 100644
index 0000000000000000000000000000000000000000..19bb14af4125ee51c6b0998437154c26d725c5a8
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/properties/FileServerProperties.java
@@ -0,0 +1,86 @@
+package org.springblade.file.properties;
+
+
+import lombok.Data;
+import lombok.Getter;
+import lombok.Setter;
+import org.springblade.file.enumeration.FileStorageType;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+import java.io.File;
+
+
+/**
+ * @author zuihou
+ */
+@Setter
+@Getter
+@ConfigurationProperties(prefix = FileServerProperties.PREFIX)
+@RefreshScope
+public class FileServerProperties {
+ public static final String PREFIX = "zuihou.file";
+ /**
+ * 为以下3个值,指定不同的自动化配置
+ * qiniu:七牛oss
+ * aliyun:阿里云oss
+ * fastdfs:本地部署的fastDFS
+ */
+ private FileStorageType type = FileStorageType.LOCAL;
+ /**
+ * 文件访问前缀
+ */
+ private String uriPrefix = "http://127.0.0.1:10000/";
+ /**
+ * 文件存储路径
+ */
+ private String storagePath = "/data/projects/uploadfile/file/";
+ /**
+ * 内网通道前缀 主要用于解决某些服务器的无法访问外网ip的问题
+ */
+ private String innerUriPrefix = "";
+ private String downByUrl = "";
+ private String downByBizId = "";
+ private String downById = "";
+
+ private Ali ali;
+
+ public String getDownByUrl(Object... param) {
+ return String.format(downByUrl, param);
+ }
+
+ public String getDownByBizId(Object... param) {
+ return String.format(downByBizId, param);
+ }
+
+ public String getDownById(Object... param) {
+ return String.format(downById, param);
+ }
+
+ public String getInnerUriPrefix() {
+ return innerUriPrefix;
+ }
+
+ public String getUriPrefix() {
+ if (!uriPrefix.endsWith(StrPool.SLASH)) {
+ uriPrefix += StrPool.SLASH;
+ }
+ return uriPrefix;
+ }
+
+ public String getStoragePath() {
+ if (!storagePath.endsWith(File.separator)) {
+ storagePath += File.separator;
+ }
+ return storagePath;
+ }
+
+ @Data
+ public static class Ali {
+ private String uriPrefix;
+ private String endpoint;
+ private String accessKeyId;
+ private String accessKeySecret;
+ private String bucketName;
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/AttachmentService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/AttachmentService.java
new file mode 100644
index 0000000000000000000000000000000000000000..859457f04d7cfa1290eb2694de53d285f1b9eff7
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/AttachmentService.java
@@ -0,0 +1,102 @@
+package com.github.zuihou.file.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.github.zuihou.base.service.SuperService;
+import com.github.zuihou.file.dto.AttachmentDTO;
+import com.github.zuihou.file.dto.AttachmentResultDTO;
+import com.github.zuihou.file.dto.FilePageReqDTO;
+import com.github.zuihou.file.entity.Attachment;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ *
+ * 业务接口
+ * 附件
+ *
+ *
+ * @author zuihou
+ * @date 2019-06-24
+ */
+public interface AttachmentService extends SuperService {
+ /**
+ * 上传附件
+ *
+ * @param file 文件
+ * @param tenant 租户
+ * @param id 附件id
+ * @param bizType 业务类型
+ * @param bizId 业务id
+ * @param isSingle 是否单个文件
+ * @return
+ */
+ AttachmentDTO upload(MultipartFile file, String tenant, Long id, String bizType, String bizId, Boolean isSingle);
+
+ /**
+ * 删除附件
+ *
+ * @param ids
+ */
+ boolean remove(List ids);
+
+ /**
+ * 根据业务id和业务类型删除附件
+ *
+ * @param bizId
+ * @param bizType
+ */
+ boolean removeByBizIdAndBizType(String bizId, String bizType);
+
+ /**
+ * 根据业务类型和业务id查询附件
+ *
+ * @param bizTypes
+ * @param bizIds
+ * @return
+ */
+ List find(String[] bizTypes, String[] bizIds);
+
+ /**
+ * 根据文件id下载附件
+ *
+ * @param request
+ * @param response
+ * @param ids
+ * @throws Exception
+ */
+ void download(HttpServletRequest request, HttpServletResponse response, Long[] ids) throws Exception;
+
+ /**
+ * 根据业务id和业务类型下载附件
+ *
+ * @param request
+ * @param response
+ * @param bizTypes
+ * @param bizIds
+ * @throws Exception
+ */
+ void downloadByBiz(HttpServletRequest request, HttpServletResponse response, String[] bizTypes, String[] bizIds) throws Exception;
+
+ /**
+ * 根据文件url下载附件
+ *
+ * @param request
+ * @param response
+ * @param url
+ * @param filename
+ * @throws Exception
+ */
+ void downloadByUrl(HttpServletRequest request, HttpServletResponse response, String url, String filename) throws Exception;
+
+ /**
+ * 查询附件分页数据,按权限
+ *
+ * @param page
+ * @param data
+ * @return
+ */
+ IPage page(IPage page, FilePageReqDTO data);
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/FileService.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/FileService.java
new file mode 100644
index 0000000000000000000000000000000000000000..50d79a2accad0ae50ea56c2fcf3b80e30f14a561
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/FileService.java
@@ -0,0 +1,131 @@
+package com.github.zuihou.file.service;
+
+import com.github.zuihou.base.service.SuperService;
+import com.github.zuihou.file.domain.FileAttrDO;
+import com.github.zuihou.file.domain.FileStatisticsDO;
+import com.github.zuihou.file.dto.FileOverviewDTO;
+import com.github.zuihou.file.dto.FileStatisticsAllDTO;
+import com.github.zuihou.file.dto.FolderDTO;
+import com.github.zuihou.file.dto.FolderSaveDTO;
+import com.github.zuihou.file.entity.File;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ *
+ * 业务接口
+ * 文件表
+ *
+ *
+ * @author zuihou
+ * @date 2019-06-24
+ */
+public interface FileService extends SuperService {
+ /**
+ * 保存文件夹
+ *
+ * @param folderSaveDto 文件夹
+ * @return
+ */
+ FolderDTO saveFolder(FolderSaveDTO folderSaveDto);
+
+ /**
+ * 根据文件id下载文件,并统计下载次数
+ *
+ * @param request 请求
+ * @param response 响应
+ * @param ids 文件id集合
+ * @param userId 用户id
+ * @throws Exception
+ */
+ void download(HttpServletRequest request, HttpServletResponse response,
+ Long[] ids, Long userId) throws Exception;
+
+ /**
+ * 根据文件id和用户id 删除文件或者文件夹
+ *
+ * @param userId 用户id
+ * @param ids 文件id集合
+ * @return
+ */
+ Boolean removeList(Long userId, List ids);
+
+ /**
+ * 根据文件夹id查询
+ *
+ * @param folderId
+ * @return
+ */
+ FileAttrDO getFileAttrDo(Long folderId);
+
+ /**
+ * 文件上传
+ *
+ * @param simpleFile 文件
+ * @param folderId 文件夹id
+ * @return
+ */
+ File upload(MultipartFile simpleFile, Long folderId);
+
+ /**
+ * 首页概览
+ *
+ * @param userId
+ * @param startTime
+ * @param endTime
+ * @return
+ */
+ FileOverviewDTO findOverview(Long userId, LocalDateTime startTime, LocalDateTime endTime);
+
+ /**
+ * 首页个人文件发展概览
+ *
+ * @param userId
+ * @param startTime
+ * @param endTime
+ * @return
+ */
+ FileStatisticsAllDTO findAllByDate(Long userId, LocalDateTime startTime, LocalDateTime endTime);
+
+
+ /**
+ * 按照 数据类型分类查询 当前人的所有文件的数量和大小
+ *
+ * @param userId
+ * @return
+ */
+ List findAllByDataType(Long userId);
+
+ /**
+ * 查询下载排行前20的文件
+ *
+ * @param userId
+ * @return
+ */
+ List downTop20(Long userId);
+
+ /**
+ * 根据日期查询,特定类型的数量和大小
+ *
+ * @param userId
+ * @param startTime
+ * @param endTime
+ * @return
+ */
+ FileStatisticsAllDTO findNumAndSizeToTypeByDate(Long userId, LocalDateTime startTime, LocalDateTime endTime);
+
+ /**
+ * 根据日期查询下载大小
+ *
+ * @param userId
+ * @param startTime
+ * @param endTime
+ * @return
+ */
+ FileStatisticsAllDTO findDownSizeByDate(Long userId, LocalDateTime startTime,
+ LocalDateTime endTime);
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ba2dbf05e6870bcebf16bab61d07fc3f9fd7672
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/AttachmentServiceImpl.java
@@ -0,0 +1,201 @@
+package com.github.zuihou.file.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.IdUtil;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.github.zuihou.base.service.SuperServiceImpl;
+import com.github.zuihou.database.mybatis.auth.DataScope;
+import com.github.zuihou.database.mybatis.conditions.Wraps;
+import com.github.zuihou.database.mybatis.conditions.query.LbqWrapper;
+import com.github.zuihou.database.properties.DatabaseProperties;
+import com.github.zuihou.exception.BizException;
+import com.github.zuihou.file.biz.FileBiz;
+import com.github.zuihou.file.dao.AttachmentMapper;
+import com.github.zuihou.file.domain.FileDO;
+import com.github.zuihou.file.domain.FileDeleteDO;
+import com.github.zuihou.file.dto.AttachmentDTO;
+import com.github.zuihou.file.dto.AttachmentResultDTO;
+import com.github.zuihou.file.dto.FilePageReqDTO;
+import com.github.zuihou.file.entity.Attachment;
+import com.github.zuihou.file.entity.File;
+import com.github.zuihou.file.enumeration.DataType;
+import com.github.zuihou.file.properties.FileServerProperties;
+import com.github.zuihou.file.service.AttachmentService;
+import com.github.zuihou.file.strategy.FileStrategy;
+import com.github.zuihou.utils.BeanPlusUtil;
+import com.github.zuihou.utils.DateUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *
+ * 业务实现类
+ * 附件
+ *
+ *
+ * @author zuihou
+ * @date 2019-06-24
+ */
+@Slf4j
+@Service
+
+public class AttachmentServiceImpl extends SuperServiceImpl implements AttachmentService {
+ @Autowired
+ private DatabaseProperties databaseProperties;
+ @Resource
+ private FileStrategy fileStrategy;
+ @Autowired
+ private FileServerProperties fileProperties;
+ @Autowired
+ private FileBiz fileBiz;
+
+ @Override
+ public IPage page(IPage page, FilePageReqDTO data) {
+ Attachment attachment = BeanPlusUtil.toBean(data, Attachment.class);
+
+ // ${ew.customSqlSegment} 语法一定要手动eq like 等 不能用lbQ!
+ LbqWrapper wrapper = Wraps.lbQ()
+ .like(Attachment::getSubmittedFileName, attachment.getSubmittedFileName())
+ .like(Attachment::getBizType, attachment.getBizType())
+ .like(Attachment::getBizId, attachment.getBizId())
+ .eq(Attachment::getDataType, attachment.getDataType())
+ .orderByDesc(Attachment::getId);
+ return baseMapper.page(page, wrapper, new DataScope());
+ }
+
+ @Override
+ public AttachmentDTO upload(MultipartFile multipartFile, String tenant, Long id, String bizType, String bizId, Boolean isSingle) {
+ //根据业务类型来判断是否生成业务id
+ if (StringUtils.isNotEmpty(bizType) && StringUtils.isEmpty(bizId)) {
+ DatabaseProperties.Id idPro = databaseProperties.getId();
+ bizId = IdUtil.getSnowflake(idPro.getWorkerId(), idPro.getDataCenterId()).nextIdStr();
+ }
+ File file = fileStrategy.upload(multipartFile);
+
+ Attachment attachment = BeanPlusUtil.toBean(file, Attachment.class);
+
+ attachment.setBizId(bizId);
+ attachment.setBizType(bizType);
+ setDate(attachment);
+
+ if (isSingle) {
+ super.remove(Wraps.lbQ().eq(Attachment::getBizId, bizId).eq(Attachment::getBizType, bizType));
+ }
+
+ if (id != null && id > 0) {
+ //当前端传递了文件id时,修改这条记录
+ attachment.setId(id);
+ super.updateById(attachment);
+ } else {
+ super.save(attachment);
+ }
+
+ AttachmentDTO dto = BeanPlusUtil.toBean(attachment, AttachmentDTO.class);
+ dto.setDownloadUrlByBizId(fileProperties.getDownByBizId(bizId));
+ dto.setDownloadUrlById(fileProperties.getDownById(attachment.getId()));
+ dto.setDownloadUrlByUrl(fileProperties.getDownByUrl(attachment.getUrl(), attachment.getSubmittedFileName()));
+ return dto;
+ }
+
+ private void setDate(Attachment file) {
+ LocalDateTime now = LocalDateTime.now();
+ file.setCreateMonth(DateUtils.formatAsYearMonthEn(now));
+ file.setCreateWeek(DateUtils.formatAsYearWeekEn(now));
+ file.setCreateDay(DateUtils.formatAsDateEn(now));
+ }
+
+ @Override
+ public boolean remove(List ids) {
+ if (CollectionUtil.isEmpty(ids)) {
+ return false;
+ }
+
+ List list = super.list(Wrappers.lambdaQuery().in(Attachment::getId, ids));
+ if (list.isEmpty()) {
+ return false;
+ }
+ boolean bool = super.removeByIds(ids);
+
+ boolean boolDel = fileStrategy.delete(list.stream().map((fi) -> FileDeleteDO.builder()
+ .relativePath(fi.getRelativePath())
+ .fileName(fi.getFilename())
+ .group(fi.getGroup())
+ .path(fi.getPath())
+ .file(false)
+ .build())
+ .collect(Collectors.toList()));
+ return bool && boolDel;
+ }
+
+ @Override
+ public boolean removeByBizIdAndBizType(String bizId, String bizType) {
+ List list = super.list(
+ Wraps.lbQ()
+ .eq(Attachment::getBizId, bizId)
+ .eq(Attachment::getBizType, bizType));
+ if (list.isEmpty()) {
+ return false;
+ }
+ return remove(list.stream().mapToLong(Attachment::getId).boxed().collect(Collectors.toList()));
+ }
+
+ @Override
+ public List find(String[] bizTypes, String[] bizIds) {
+ return baseMapper.find(bizTypes, bizIds);
+ }
+
+ @Override
+ public void download(HttpServletRequest request, HttpServletResponse response, Long[] ids) throws Exception {
+ List list = (List) super.listByIds(Arrays.asList(ids));
+ down(request, response, list);
+ }
+
+ @Override
+ public void downloadByBiz(HttpServletRequest request, HttpServletResponse response, String[] bizTypes, String[] bizIds) throws Exception {
+ List list = super.list(
+ Wraps.lbQ()
+ .in(Attachment::getBizType, bizTypes)
+ .in(Attachment::getBizId, bizIds));
+
+ down(request, response, list);
+ }
+
+ @Override
+ public void downloadByUrl(HttpServletRequest request, HttpServletResponse response, String url, String filename) throws Exception {
+ if (StringUtils.isEmpty(filename)) {
+ filename = "未知文件名.txt";
+ }
+ List list = Arrays.asList(Attachment.builder()
+ .url(url).submittedFileName(filename).size(0L).dataType(DataType.DOC).build());
+ down(request, response, list);
+ }
+
+ private void down(HttpServletRequest request, HttpServletResponse response, List list) throws Exception {
+ if (list.isEmpty()) {
+ throw BizException.wrap("您下载的文件不存在");
+ }
+ List listDO = list.stream().map((file) ->
+ FileDO.builder()
+ .url(file.getUrl())
+ .submittedFileName(file.getSubmittedFileName())
+ .size(file.getSize())
+ .dataType(file.getDataType())
+ .build())
+ .collect(Collectors.toList());
+ fileBiz.down(listDO, request, response);
+ }
+
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileServiceImpl.java b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileServiceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..920b2ef74af27d9633b035920679d77afc63b783
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/service/impl/FileServiceImpl.java
@@ -0,0 +1,432 @@
+package com.github.zuihou.file.service.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.github.zuihou.base.service.SuperServiceImpl;
+import com.github.zuihou.database.mybatis.conditions.Wraps;
+import com.github.zuihou.database.mybatis.conditions.update.LbuWrapper;
+import com.github.zuihou.file.biz.FileBiz;
+import com.github.zuihou.file.dao.FileMapper;
+import com.github.zuihou.file.domain.FileAttrDO;
+import com.github.zuihou.file.domain.FileDO;
+import com.github.zuihou.file.domain.FileDeleteDO;
+import com.github.zuihou.file.domain.FileStatisticsDO;
+import com.github.zuihou.file.dto.FileOverviewDTO;
+import com.github.zuihou.file.dto.FileStatisticsAllDTO;
+import com.github.zuihou.file.dto.FolderDTO;
+import com.github.zuihou.file.dto.FolderSaveDTO;
+import com.github.zuihou.file.entity.File;
+import com.github.zuihou.file.enumeration.DataType;
+import com.github.zuihou.file.enumeration.IconType;
+import com.github.zuihou.file.service.FileService;
+import com.github.zuihou.file.strategy.FileStrategy;
+import com.github.zuihou.utils.BeanPlusUtil;
+import com.github.zuihou.utils.BizAssert;
+import com.github.zuihou.utils.DateUtils;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.github.zuihou.exception.code.ExceptionCode.BASE_VALID_PARAM;
+import static com.github.zuihou.utils.StrPool.DEF_PARENT_ID;
+import static com.github.zuihou.utils.StrPool.DEF_ROOT_PATH;
+import static java.util.stream.Collectors.groupingBy;
+
+/**
+ *
+ * 业务实现类
+ * 文件表
+ *
+ *
+ * @author zuihou
+ * @date 2019-06-24
+ */
+@Slf4j
+@Service
+
+public class FileServiceImpl extends SuperServiceImpl implements FileService {
+
+ @Autowired
+ private FileBiz fileBiz;
+ @Resource
+ private FileStrategy fileStrategy;
+
+ @Override
+ public File upload(MultipartFile simpleFile, Long folderId) {
+ FileAttrDO fileAttrDO = this.getFileAttrDo(folderId);
+ String treePath = fileAttrDO.getTreePath();
+ String folderName = fileAttrDO.getFolderName();
+ Integer grade = fileAttrDO.getGrade();
+
+ File file = fileStrategy.upload(simpleFile);
+ file.setFolderId(folderId);
+ file.setFolderName(folderName);
+ file.setGrade(grade);
+ file.setTreePath(treePath);
+ super.save(file);
+ return file;
+ }
+
+ @Override
+ public FileAttrDO getFileAttrDo(Long folderId) {
+ String treePath = DEF_ROOT_PATH;
+ String folderName = "";
+ Integer grade = 1;
+ if (folderId == null || folderId <= 0) {
+ return new FileAttrDO(treePath, grade, folderName, DEF_PARENT_ID);
+ }
+ File folder = this.getById(folderId);
+
+ if (folder != null && !folder.getIsDelete() && DataType.DIR.eq(folder.getDataType())) {
+ folderName = folder.getSubmittedFileName();
+ treePath = StringUtils.join(folder.getTreePath(), folder.getId(), DEF_ROOT_PATH);
+ grade = folder.getGrade() + 1;
+ }
+ BizAssert.isTrue(grade <= 10, BASE_VALID_PARAM.build("文件夹层级不能超过10层"));
+ return new FileAttrDO(treePath, grade, folderName, folderId);
+ }
+
+
+ @Override
+ public FolderDTO saveFolder(FolderSaveDTO folderSaveDto) {
+ File folder = BeanPlusUtil.toBean(folderSaveDto, File.class);
+ if (folderSaveDto.getFolderId() == null || folderSaveDto.getFolderId() <= 0) {
+ folder.setFolderId(DEF_PARENT_ID);
+ folder.setTreePath(DEF_ROOT_PATH);
+ folder.setGrade(1);
+ } else {
+ File parent = super.getById(folderSaveDto.getFolderId());
+ BizAssert.notNull(parent, BASE_VALID_PARAM.build("父文件夹不能为空"));
+ BizAssert.isFalse(parent.getIsDelete(), BASE_VALID_PARAM.build("父文件夹已经被删除"));
+ BizAssert.equals(DataType.DIR.name(), parent.getDataType().name(), BASE_VALID_PARAM.build("父文件夹不存在"));
+ BizAssert.isTrue(parent.getGrade() < 10, BASE_VALID_PARAM.build("文件夹层级不能超过10层"));
+ folder.setFolderName(parent.getSubmittedFileName());
+ folder.setTreePath(StringUtils.join(parent.getTreePath(), parent.getId(), DEF_ROOT_PATH));
+ folder.setGrade(parent.getGrade() + 1);
+ }
+ if (folderSaveDto.getOrderNum() == null) {
+ folderSaveDto.setOrderNum(0);
+ }
+ folder.setIsDelete(false);
+ folder.setDataType(DataType.DIR);
+ folder.setIcon(IconType.DIR.getIcon());
+ setDate(folder);
+ super.save(folder);
+ return BeanPlusUtil.toBean(folder, FolderDTO.class);
+ }
+
+ private void setDate(File file) {
+ LocalDateTime now = LocalDateTime.now();
+ file.setCreateMonth(DateUtils.formatAsYearMonthEn(now))
+ .setCreateWeek(DateUtils.formatAsYearWeekEn(now))
+ .setCreateDay(DateUtils.formatAsDateEn(now));
+ }
+
+ public boolean removeFile(Long[] ids, Long userId) {
+ LbuWrapper lambdaUpdate =
+ Wraps.lbU()
+ .in(File::getId, ids)
+ .eq(File::getCreateUser, userId);
+ File file = File.builder().isDelete(Boolean.TRUE).build();
+
+ return super.update(file, lambdaUpdate);
+ }
+
+ @Override
+ public Boolean removeList(Long userId, List ids) {
+ if (CollectionUtil.isEmpty(ids)) {
+ return Boolean.TRUE;
+ }
+ List list = super.list(Wrappers.lambdaQuery().in(File::getId, ids));
+ if (list.isEmpty()) {
+ return true;
+ }
+ super.removeByIds(ids);
+
+ fileStrategy.delete(list.stream().map((fi) -> FileDeleteDO.builder()
+ .relativePath(fi.getRelativePath())
+ .fileName(fi.getFilename())
+ .group(fi.getGroup())
+ .path(fi.getPath())
+ .file(false)
+ .build())
+ .collect(Collectors.toList()));
+ return true;
+ }
+
+ @Override
+ public void download(HttpServletRequest request, HttpServletResponse response, Long[] ids, Long userId) throws Exception {
+ if (ids == null || ids.length == 0) {
+ return;
+ }
+ List list = (List) super.listByIds(Arrays.asList(ids));
+
+ if (list == null || list.size() == 0) {
+ return;
+ }
+ List listDo = list.stream().map((file) ->
+ FileDO.builder()
+ .dataType(file.getDataType())
+ .size(file.getSize())
+ .submittedFileName(file.getSubmittedFileName())
+ .url(file.getUrl())
+ .build())
+ .collect(Collectors.toList());
+ fileBiz.down(listDo, request, response);
+ }
+
+
+ @Override
+ public FileOverviewDTO findOverview(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
+ InnerQueryDate innerQueryDate = new InnerQueryDate(userId, startTime, endTime).invoke();
+ startTime = innerQueryDate.getStartTime();
+ endTime = innerQueryDate.getEndTime();
+
+ List list = baseMapper.findNumAndSizeByUserId(userId, null, "ALL", startTime, endTime);
+ FileOverviewDTO.FileOverviewDTOBuilder builder = FileOverviewDTO.myBuilder();
+
+ long allSize = 0L;
+ int allNum = 0;
+ for (FileStatisticsDO fs : list) {
+ allSize += fs.getSize();
+ allNum += fs.getNum();
+ switch (fs.getDataType()) {
+ case DIR:
+ builder.dirNum(fs.getNum());
+ break;
+ case IMAGE:
+ builder.imgNum(fs.getNum());
+ break;
+ case VIDEO:
+ builder.videoNum(fs.getNum());
+ break;
+ case DOC:
+ builder.docNum(fs.getNum());
+ break;
+ case AUDIO:
+ builder.audioNum(fs.getNum());
+ break;
+ case OTHER:
+ builder.otherNum(fs.getNum());
+ break;
+ default:
+ break;
+ }
+ }
+ builder.allFileNum(allNum).allFileSize(allSize);
+ return builder.build();
+ }
+
+ @Override
+ public FileStatisticsAllDTO findAllByDate(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
+ InnerQueryDate innerQueryDate = new InnerQueryDate(userId, startTime, endTime).invoke();
+ startTime = innerQueryDate.getStartTime();
+ endTime = innerQueryDate.getEndTime();
+ List dateList = innerQueryDate.getDateList();
+ String dateType = innerQueryDate.getDateType();
+
+ //不完整的数据
+ List list = baseMapper.findNumAndSizeByUserId(userId, dateType, null, startTime, endTime);
+
+ //按月份分类
+ Map> map = list.stream().collect(groupingBy(FileStatisticsDO::getDateType));
+
+ List sizeList = new ArrayList<>();
+ List numList = new ArrayList<>();
+
+ dateList.forEach((date) -> {
+ if (map.containsKey(date)) {
+ List subList = map.get(date);
+
+ Long size = subList.stream().mapToLong(FileStatisticsDO::getSize).sum();
+ Integer num = subList.stream().filter((fs) -> !DataType.DIR.eq(fs.getDataType()))
+ .mapToInt(FileStatisticsDO::getNum).sum();
+ sizeList.add(size);
+ numList.add(num);
+ } else {
+ sizeList.add(0L);
+ numList.add(0);
+ }
+ });
+
+ return FileStatisticsAllDTO.builder().dateList(dateList).numList(numList).sizeList(sizeList).build();
+ }
+
+
+ @Override
+ public List findAllByDataType(Long userId) {
+ List dataTypes = Arrays.asList(DataType.values());
+ List list = baseMapper.findNumAndSizeByUserId(userId, null, "ALL", null, null);
+
+ Map> map = list.stream().collect(groupingBy(FileStatisticsDO::getDataType));
+
+ return dataTypes.stream().map((type) -> {
+ FileStatisticsDO fs = null;
+ if (map.containsKey(type)) {
+ fs = map.get(type).get(0);
+ } else {
+ fs = FileStatisticsDO.builder().dataType(type).size(0L).num(0).build();
+ }
+ return fs;
+ }).collect(Collectors.toList());
+ }
+
+ @Override
+ public List downTop20(Long userId) {
+ return baseMapper.findDownTop20(userId);
+ }
+
+ @Override
+ public FileStatisticsAllDTO findNumAndSizeToTypeByDate(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
+ return common(userId, startTime, endTime,
+ (qd) -> baseMapper.findNumAndSizeByUserId(qd.getUserId(), qd.getDateType(), "ALL", qd.getStartTime(), qd.getEndTime()));
+ }
+
+ @Override
+ public FileStatisticsAllDTO findDownSizeByDate(Long userId, LocalDateTime startTime,
+ LocalDateTime endTime) {
+ return common(userId, startTime, endTime,
+ (qd) -> baseMapper.findDownSizeByDate(qd.getUserId(), qd.getDateType(), qd.getStartTime(), qd.getEndTime()));
+ }
+
+ /**
+ * 抽取公共查询公共代码
+ *
+ * @param userId 用户id
+ * @param startTime 开始时间
+ * @param endTime 结束时间
+ * @param function 回调函数
+ * @return
+ */
+ private FileStatisticsAllDTO common(Long userId, LocalDateTime startTime, LocalDateTime endTime, Function> function) {
+ InnerQueryDate innerQueryDate = new InnerQueryDate(userId, startTime, endTime).invoke();
+ List dateList = innerQueryDate.getDateList();
+
+ List list = function.apply(innerQueryDate);
+
+ //按月份分类
+ Map> map = list.stream().collect(groupingBy(FileStatisticsDO::getDateType));
+
+ List sizeList = new ArrayList<>(dateList.size());
+ List numList = new ArrayList<>(dateList.size());
+
+ List dirNumList = new ArrayList<>(dateList.size());
+
+ List imgSizeList = new ArrayList<>(dateList.size());
+ List imgNumList = new ArrayList<>(dateList.size());
+
+ List videoSizeList = new ArrayList<>(dateList.size());
+ List videoNumList = new ArrayList<>(dateList.size());
+
+ List audioSizeList = new ArrayList<>(dateList.size());
+ List audioNumList = new ArrayList<>(dateList.size());
+
+ List docSizeList = new ArrayList<>(dateList.size());
+ List docNumList = new ArrayList<>(dateList.size());
+
+ List otherSizeList = new ArrayList<>(dateList.size());
+ List otherNumList = new ArrayList<>(dateList.size());
+
+ dateList.forEach((date) -> {
+ if (map.containsKey(date)) {
+ List subList = map.get(date);
+
+ Function> stream = (dataType) -> subList.stream().filter((fs) -> !dataType.eq(fs.getDataType()));
+ Long size = stream.apply(DataType.DIR).mapToLong(FileStatisticsDO::getSize).sum();
+ Integer num = stream.apply(DataType.DIR).mapToInt(FileStatisticsDO::getNum).sum();
+ sizeList.add(size);
+ numList.add(num);
+
+ Integer dirNum = subList.stream().filter((fs) -> DataType.DIR.eq(fs.getDataType()))
+ .mapToInt(FileStatisticsDO::getNum).sum();
+ dirNumList.add(dirNum);
+
+ add(imgSizeList, imgNumList, subList, DataType.IMAGE);
+ add(videoSizeList, videoNumList, subList, DataType.VIDEO);
+ add(audioSizeList, audioNumList, subList, DataType.AUDIO);
+ add(docSizeList, docNumList, subList, DataType.DOC);
+ add(otherSizeList, otherNumList, subList, DataType.OTHER);
+
+ } else {
+ sizeList.add(0L);
+ numList.add(0);
+ dirNumList.add(0);
+ imgSizeList.add(0L);
+ imgNumList.add(0);
+ videoSizeList.add(0L);
+ videoNumList.add(0);
+ audioSizeList.add(0L);
+ audioNumList.add(0);
+ docSizeList.add(0L);
+ docNumList.add(0);
+ otherSizeList.add(0L);
+ otherNumList.add(0);
+ }
+ });
+
+ return FileStatisticsAllDTO.builder()
+ .dateList(dateList)
+ .numList(numList).sizeList(sizeList)
+ .dirNumList(dirNumList)
+ .imgNumList(imgNumList).imgSizeList(imgSizeList)
+ .videoNumList(videoNumList).videoSizeList(videoSizeList)
+ .audioNumList(audioNumList).audioSizeList(audioSizeList)
+ .docNumList(docNumList).docSizeList(docSizeList)
+ .otherNumList(otherNumList).otherSizeList(otherSizeList)
+ .build();
+ }
+
+ private void add(List sizeList, List numList, List subList, DataType dt) {
+ Function> stream =
+ dataType -> subList.stream().filter(fs -> dataType.eq(fs.getDataType()));
+
+ Long size = stream.apply(dt).mapToLong(FileStatisticsDO::getSize).sum();
+ Integer num = stream.apply(dt).mapToInt(FileStatisticsDO::getNum).sum();
+ sizeList.add(size);
+ numList.add(num);
+ }
+
+ @Getter
+ private static class InnerQueryDate {
+ private LocalDateTime startTime;
+ private LocalDateTime endTime;
+ private List dateList;
+ private String dateType;
+ private Long userId;
+
+ public InnerQueryDate(Long userId, LocalDateTime startTime, LocalDateTime endTime) {
+ this.userId = userId;
+ this.startTime = startTime;
+ this.endTime = endTime;
+ }
+
+ public InnerQueryDate invoke() {
+ if (startTime == null) {
+ startTime = LocalDateTime.now().plusDays(-9);
+ }
+ if (endTime == null) {
+ endTime = LocalDateTime.now();
+ }
+ endTime = LocalDateTime.of(endTime.toLocalDate(), LocalTime.MAX);
+ dateList = new ArrayList<>();
+ dateType = DateUtils.calculationEn(startTime, endTime, dateList);
+ return this;
+ }
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/storage/AliOssAutoConfigure.java b/blade-service/blade-file/src/main/java/org/springblade/file/storage/AliOssAutoConfigure.java
new file mode 100644
index 0000000000000000000000000000000000000000..02b68f05683b5f1be2298901d99c1e588dd557fb
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/storage/AliOssAutoConfigure.java
@@ -0,0 +1,254 @@
+package org.springblade.file.storage;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import com.aliyun.oss.model.CompleteMultipartUploadRequest;
+import com.aliyun.oss.model.CompleteMultipartUploadResult;
+import com.aliyun.oss.model.InitiateMultipartUploadRequest;
+import com.aliyun.oss.model.InitiateMultipartUploadResult;
+import com.aliyun.oss.model.ObjectMetadata;
+import com.aliyun.oss.model.PartETag;
+import com.aliyun.oss.model.PutObjectRequest;
+import com.aliyun.oss.model.PutObjectResult;
+import com.aliyun.oss.model.UploadPartCopyRequest;
+import com.aliyun.oss.model.UploadPartCopyResult;
+import com.aliyun.oss.model.UploadPartRequest;
+import com.aliyun.oss.model.UploadPartResult;
+import com.github.zuihou.file.domain.FileDeleteDO;
+import com.github.zuihou.file.dto.chunk.FileChunksMergeDTO;
+import com.github.zuihou.file.entity.File;
+import com.github.zuihou.file.properties.FileServerProperties;
+import com.github.zuihou.file.strategy.impl.AbstractFileChunkStrategy;
+import com.github.zuihou.file.strategy.impl.AbstractFileStrategy;
+import com.github.zuihou.utils.StrPool;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+
+import static com.github.zuihou.utils.DateUtils.DEFAULT_MONTH_FORMAT_SLASH;
+
+/**
+ * 阿里OSS
+ *
+ * @author zuihou
+ * @date 2019/08/09
+ */
+@EnableConfigurationProperties(FileServerProperties.class)
+@Configuration
+@Slf4j
+@ConditionalOnProperty(prefix = FileServerProperties.PREFIX, name = "type", havingValue = "ALI")
+public class AliOssAutoConfigure {
+
+ @Service
+ public class AliServiceImpl extends AbstractFileStrategy {
+ @Override
+ protected void uploadFile(File file, MultipartFile multipartFile) throws Exception {
+ FileServerProperties.Ali ali = fileProperties.getAli();
+ OSS ossClient = new OSSClientBuilder().build(ali.getEndpoint(), ali.getAccessKeyId(),
+ ali.getAccessKeySecret());
+ String bucketName = ali.getBucketName();
+ if (!ossClient.doesBucketExist(bucketName)) {
+ ossClient.createBucket(bucketName);
+ }
+
+ //生成文件名
+ String fileName = StrUtil.join(StrPool.EMPTY, UUID.randomUUID().toString(), StrPool.DOT, file.getExt());
+ //日期文件夹
+ String tenant = BaseContextHandler.getTenant();
+ String relativePath = tenant + StrPool.SLASH + LocalDate.now().format(DateTimeFormatter.ofPattern(DEFAULT_MONTH_FORMAT_SLASH));
+ // web服务器存放的绝对路径
+ String relativeFileName = relativePath + StrPool.SLASH + fileName;
+
+ ObjectMetadata metadata = new ObjectMetadata();
+ metadata.setContentDisposition("attachment;fileName=" + file.getSubmittedFileName());
+ metadata.setContentType(file.getContextType());
+ PutObjectRequest request = new PutObjectRequest(bucketName, relativeFileName, multipartFile.getInputStream(), metadata);
+ PutObjectResult result = ossClient.putObject(request);
+
+ log.info("result={}", JSONObject.toJSONString(result));
+
+ String url = ali.getUriPrefix() + relativeFileName;
+ file.setUrl(StrUtil.replace(url, "\\\\", StrPool.SLASH));
+ file.setFilename(fileName);
+ file.setRelativePath(relativePath);
+
+ file.setGroup(result.getETag());
+ file.setPath(result.getRequestId());
+
+ ossClient.shutdown();
+ }
+
+ @Override
+ protected void delete(List list, FileDeleteDO file) {
+ FileServerProperties.Ali ali = fileProperties.getAli();
+ String bucketName = ali.getBucketName();
+ OSS ossClient = new OSSClientBuilder().build(ali.getEndpoint(), ali.getAccessKeyId(),
+ ali.getAccessKeySecret());
+ ossClient.deleteObject(bucketName, file.getRelativePath() + StrPool.SLASH + file.getFileName());
+ ossClient.shutdown();
+ }
+ }
+
+
+ @Service
+ public class AliChunkServiceImpl extends AbstractFileChunkStrategy {
+ @Override
+ protected void copyFile(File file) {
+ FileServerProperties.Ali ali = fileProperties.getAli();
+ String sourceBucketName = ali.getBucketName();
+ String destinationBucketName = ali.getBucketName();
+ OSS ossClient = new OSSClientBuilder().build(ali.getEndpoint(), ali.getAccessKeyId(),
+ ali.getAccessKeySecret());
+
+ String sourceObjectName = file.getRelativePath() + StrPool.SLASH + file.getFilename();
+ String fileName = UUID.randomUUID().toString() + StrPool.DOT + file.getExt();
+ String destinationObjectName = file.getRelativePath() + StrPool.SLASH + fileName;
+ ObjectMetadata objectMetadata = ossClient.getObjectMetadata(sourceBucketName, sourceObjectName);
+ // 获取被拷贝文件的大小。
+
+ // 获取被拷贝文件的大小。
+ long contentLength = objectMetadata.getContentLength();
+
+ // 设置分片大小为10MB。
+ long partSize = 1024 * 1024 * 10;
+
+ // 计算分片总数。
+ int partCount = (int) (contentLength / partSize);
+ if (contentLength % partSize != 0) {
+ partCount++;
+ }
+ log.info("total part count:{}", partCount);
+
+ ObjectMetadata metadata = new ObjectMetadata();
+ metadata.setContentDisposition("attachment;fileName=" + file.getSubmittedFileName());
+ metadata.setContentType(file.getContextType());
+ // 初始化拷贝任务。可以通过InitiateMultipartUploadRequest指定目标文件元信息。
+ InitiateMultipartUploadRequest initiateMultipartUploadRequest = new InitiateMultipartUploadRequest(destinationBucketName, destinationObjectName, metadata);
+ InitiateMultipartUploadResult initiateMultipartUploadResult = ossClient.initiateMultipartUpload(initiateMultipartUploadRequest);
+ String uploadId = initiateMultipartUploadResult.getUploadId();
+
+ // 分片拷贝。
+ List partETags = new ArrayList<>();
+ for (int i = 0; i < partCount; i++) {
+ // 计算每个分片的大小。
+ long skipBytes = partSize * i;
+ long size = partSize < contentLength - skipBytes ? partSize : contentLength - skipBytes;
+
+ // 创建UploadPartCopyRequest。可以通过UploadPartCopyRequest指定限定条件。
+ UploadPartCopyRequest uploadPartCopyRequest =
+ new UploadPartCopyRequest(sourceBucketName, sourceObjectName, destinationBucketName, destinationObjectName);
+ uploadPartCopyRequest.setUploadId(uploadId);
+ uploadPartCopyRequest.setPartSize(size);
+ uploadPartCopyRequest.setBeginIndex(skipBytes);
+ uploadPartCopyRequest.setPartNumber(i + 1);
+ UploadPartCopyResult uploadPartCopyResult = ossClient.uploadPartCopy(uploadPartCopyRequest);
+
+ // 将返回的分片ETag保存到partETags中。
+ partETags.add(uploadPartCopyResult.getPartETag());
+ }
+
+ // 提交分片拷贝任务。
+ CompleteMultipartUploadRequest completeMultipartUploadRequest = new CompleteMultipartUploadRequest(
+ destinationBucketName, destinationObjectName, uploadId, partETags);
+ ossClient.completeMultipartUpload(completeMultipartUploadRequest);
+
+ String url = new StringBuilder(ali.getUriPrefix())
+ .append(file.getRelativePath())
+ .append(StrPool.SLASH)
+ .append(fileName)
+ .toString();
+ file.setUrl(StringUtils.replace(url, "\\\\", StrPool.SLASH));
+ file.setFilename(fileName);
+
+ // 关闭OSSClient。
+ ossClient.shutdown();
+ }
+
+ @Override
+ protected R merge(List files, String path, String fileName, FileChunksMergeDTO info) throws IOException {
+ FileServerProperties.Ali ali = fileProperties.getAli();
+ String bucketName = ali.getBucketName();
+ OSS ossClient = new OSSClientBuilder().build(ali.getEndpoint(), ali.getAccessKeyId(),
+ ali.getAccessKeySecret());
+
+ //日期文件夹
+ String relativePath = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
+ // web服务器存放的绝对路径
+ String relativeFileName = relativePath + StrPool.SLASH + fileName;
+
+ ObjectMetadata metadata = new ObjectMetadata();
+ metadata.setContentDisposition("attachment;fileName=" + info.getSubmittedFileName());
+ metadata.setContentType(info.getContextType());
+ //步骤1:初始化一个分片上传事件。
+ InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, relativeFileName, metadata);
+ InitiateMultipartUploadResult result = ossClient.initiateMultipartUpload(request);
+ // 返回uploadId,它是分片上传事件的唯一标识,您可以根据这个ID来发起相关的操作,如取消分片上传、查询分片上传等。
+ String uploadId = result.getUploadId();
+
+ // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
+ List partETags = new ArrayList();
+ for (int i = 0; i < files.size(); i++) {
+ java.io.File file = files.get(i);
+ FileInputStream in = FileUtils.openInputStream(file);
+
+ UploadPartRequest uploadPartRequest = new UploadPartRequest();
+ uploadPartRequest.setBucketName(bucketName);
+ uploadPartRequest.setKey(relativeFileName);
+ uploadPartRequest.setUploadId(uploadId);
+ uploadPartRequest.setInputStream(in);
+ // 设置分片大小。除了最后一个分片没有大小限制,其他的分片最小为100KB。
+ uploadPartRequest.setPartSize(file.length());
+ // 设置分片号。每一个上传的分片都有一个分片号,取值范围是1~10000,如果超出这个范围,OSS将返回InvalidArgument的错误码。
+ uploadPartRequest.setPartNumber(i + 1);
+
+ // 每个分片不需要按顺序上传,甚至可以在不同客户端上传,OSS会按照分片号排序组成完整的文件。
+ UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
+
+ // 每次上传分片之后,OSS的返回结果会包含一个PartETag。PartETag将被保存到partETags中。
+ partETags.add(uploadPartResult.getPartETag());
+ }
+
+ /* 步骤3:完成分片上传。 */
+ // 排序。partETags必须按分片号升序排列。
+ partETags.sort(Comparator.comparingInt(PartETag::getPartNumber));
+
+ // 在执行该操作时,需要提供所有有效的partETags。OSS收到提交的partETags后,会逐一验证每个分片的有效性。当所有的数据分片验证通过后,OSS将把这些分片组合成一个完整的文件。
+ CompleteMultipartUploadRequest completeMultipartUploadRequest =
+ new CompleteMultipartUploadRequest(bucketName, relativeFileName, uploadId, partETags);
+
+ CompleteMultipartUploadResult uploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
+
+ String url = new StringBuilder(ali.getUriPrefix())
+ .append(relativePath)
+ .append(StrPool.SLASH)
+ .append(fileName)
+ .toString();
+ File filePo = File.builder()
+ .relativePath(relativePath)
+ .group(uploadResult.getETag())
+ .path(uploadResult.getRequestId())
+ .url(StringUtils.replace(url, "\\\\", StrPool.SLASH))
+ .build();
+
+ // 关闭OSSClient。
+ ossClient.shutdown();
+ return R.success(filePo);
+ }
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/storage/FastDfsAutoConfigure.java b/blade-service/blade-file/src/main/java/org/springblade/file/storage/FastDfsAutoConfigure.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bbc8c1e371c98d41d56d93f1d320abff8cfddb6
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/storage/FastDfsAutoConfigure.java
@@ -0,0 +1,120 @@
+package com.github.zuihou.file.storage;
+
+
+import com.github.tobato.fastdfs.domain.fdfs.StorePath;
+import com.github.tobato.fastdfs.service.AppendFileStorageClient;
+import com.github.tobato.fastdfs.service.FastFileStorageClient;
+import com.github.zuihou.base.R;
+import com.github.zuihou.file.dao.AttachmentMapper;
+import com.github.zuihou.file.domain.FileDeleteDO;
+import com.github.zuihou.file.dto.chunk.FileChunksMergeDTO;
+import com.github.zuihou.file.entity.File;
+import com.github.zuihou.file.properties.FileServerProperties;
+import com.github.zuihou.file.strategy.impl.AbstractFileChunkStrategy;
+import com.github.zuihou.file.strategy.impl.AbstractFileStrategy;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * FastDFS配置
+ *
+ * @author zuihou
+ */
+@EnableConfigurationProperties(FileServerProperties.class)
+@Configuration
+@Slf4j
+@ConditionalOnProperty(prefix = FileServerProperties.PREFIX, name = "type", havingValue = "FAST_DFS")
+public class FastDfsAutoConfigure {
+ @Service
+
+ public class FastDfsServiceImpl extends AbstractFileStrategy {
+ @Autowired
+ private FastFileStorageClient storageClient;
+ @Autowired
+ private AttachmentMapper attachmentMapper;
+
+ @Override
+ protected void uploadFile(File file, MultipartFile multipartFile) throws Exception {
+ StorePath storePath = storageClient.uploadFile(multipartFile.getInputStream(), multipartFile.getSize(), file.getExt(), null);
+ file.setUrl(fileProperties.getUriPrefix() + storePath.getFullPath());
+ file.setGroup(storePath.getGroup());
+ file.setPath(storePath.getPath());
+ }
+
+ @Override
+ protected void delete(List list, FileDeleteDO file) {
+ if (file.getFile()) {
+ List ids = list.stream().mapToLong(FileDeleteDO::getId).boxed().collect(Collectors.toList());
+ Integer count = attachmentMapper.countByGroup(ids, file.getGroup(), file.getPath());
+ if (count > 0) {
+ return;
+ }
+ }
+ storageClient.deleteFile(file.getGroup(), file.getPath());
+ }
+
+ }
+
+ @Service
+ public class FastDfsChunkServiceImpl extends AbstractFileChunkStrategy {
+ @Autowired
+ protected AppendFileStorageClient storageClient;
+
+ @Override
+ protected void copyFile(File file) {
+ // 由于大文件下载然后在上传会内存溢出, 所以 FastDFS 不复制,删除时通过业务手段
+// DownloadByteArray callback = new DownloadByteArray();
+// byte[] content = storageClient.downloadFile(file.getGroup(), file.getPath(), callback);
+// InputStream in = new ByteArrayInputStream(content);
+// StorePath storePath = storageClient.uploadFile(file.getGroup(), in, file.getSize(), file.getExt());
+// file.setUrl(fileProperties.getUriPrefix() + storePath.getFullPath());
+// file.setGroup(storePath.getGroup());
+// file.setPath(storePath.getPath());
+ }
+
+ @Override
+ protected R merge(List files, String path, String fileName, FileChunksMergeDTO info) throws IOException {
+ StorePath storePath = null;
+
+ long start = System.currentTimeMillis();
+ for (int i = 0; i < files.size(); i++) {
+ java.io.File file = files.get(i);
+
+ FileInputStream in = FileUtils.openInputStream(file);
+ if (i == 0) {
+ storePath = storageClient.uploadAppenderFile(null, in,
+ file.length(), info.getExt());
+ } else {
+ storageClient.appendFile(storePath.getGroup(), storePath.getPath(),
+ in, file.length());
+ }
+ }
+ if (storePath == null) {
+ return R.fail("上传失败");
+ }
+
+ long end = System.currentTimeMillis();
+ log.info("上传耗时={}", (end - start));
+ String url = new StringBuilder(fileProperties.getUriPrefix())
+ .append(storePath.getFullPath())
+ .toString();
+ File filePo = File.builder()
+ .url(url)
+ .group(storePath.getGroup())
+ .path(storePath.getPath())
+ .build();
+ return R.success(filePo);
+ }
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/storage/LocalAutoConfigure.java b/blade-service/blade-file/src/main/java/org/springblade/file/storage/LocalAutoConfigure.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca6dd1b1a0c01341b7f1f6caedf69a9489640617
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/storage/LocalAutoConfigure.java
@@ -0,0 +1,171 @@
+package com.github.zuihou.file.storage;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.util.StrUtil;
+import com.github.zuihou.base.R;
+import com.github.zuihou.context.BaseContextHandler;
+import com.github.zuihou.exception.BizException;
+import com.github.zuihou.file.domain.FileDeleteDO;
+import com.github.zuihou.file.dto.chunk.FileChunksMergeDTO;
+import com.github.zuihou.file.entity.File;
+import com.github.zuihou.file.properties.FileServerProperties;
+import com.github.zuihou.file.strategy.impl.AbstractFileChunkStrategy;
+import com.github.zuihou.file.strategy.impl.AbstractFileStrategy;
+import com.github.zuihou.file.utils.FileDataTypeUtil;
+import com.github.zuihou.utils.StrPool;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.UUID;
+
+import static com.github.zuihou.utils.DateUtils.DEFAULT_MONTH_FORMAT_SLASH;
+
+
+/**
+ * 本地上传配置
+ *
+ * @author zuihou
+ * @date 2019/06/18
+ */
+
+@EnableConfigurationProperties(FileServerProperties.class)
+@Configuration
+@ConditionalOnProperty(prefix = FileServerProperties.PREFIX, name = "type", havingValue = "LOCAL", matchIfMissing = true)
+@Slf4j
+public class LocalAutoConfigure {
+
+ @Service
+ public class LocalServiceImpl extends AbstractFileStrategy {
+ @Override
+ protected void uploadFile(File file, MultipartFile multipartFile) throws Exception {
+ //生成文件名
+ String fileName = UUID.randomUUID().toString() + StrPool.DOT + file.getExt();
+
+ String tenant = BaseContextHandler.getTenant();
+
+ //日期文件夹
+ String relativePath = Paths.get(tenant, LocalDate.now().format(DateTimeFormatter.ofPattern(DEFAULT_MONTH_FORMAT_SLASH))).toString();
+ // web服务器存放的绝对路径
+ String absolutePath = Paths.get(fileProperties.getStoragePath(), relativePath).toString();
+
+ java.io.File outFile = new java.io.File(Paths.get(absolutePath, fileName).toString());
+ org.apache.commons.io.FileUtils.writeByteArrayToFile(outFile, multipartFile.getBytes());
+
+ String url = new StringBuilder(fileProperties.getUriPrefix())
+ .append(relativePath)
+ .append(StrPool.SLASH)
+ .append(fileName)
+ .toString();
+ //替换掉windows环境的\路径
+ url = StrUtil.replace(url, "\\\\", StrPool.SLASH);
+ url = StrUtil.replace(url, "\\", StrPool.SLASH);
+ file.setUrl(url);
+ file.setFilename(fileName);
+ file.setRelativePath(relativePath);
+ }
+
+ @Override
+ protected void delete(List list, FileDeleteDO file) {
+ java.io.File ioFile = new java.io.File(Paths.get(fileProperties.getStoragePath(), file.getRelativePath(), file.getFileName()).toString());
+ org.apache.commons.io.FileUtils.deleteQuietly(ioFile);
+ }
+ }
+
+ @Service
+ public class LocalChunkServiceImpl extends AbstractFileChunkStrategy {
+ /**
+ * 为上传的文件生成随机名称
+ *
+ * @param originalName 文件的原始名称,主要用来获取文件的后缀名
+ * @return
+ */
+ private String randomFileName(String originalName) {
+ String[] ext = StrUtil.split(originalName, ".");
+ return UUID.randomUUID().toString() + StrPool.DOT + ext[ext.length - 1];
+ }
+
+ @Override
+ protected void copyFile(File file) {
+ String inputFile = Paths.get(fileProperties.getStoragePath(), file.getRelativePath(),
+ file.getFilename()).toString();
+
+ String filename = randomFileName(file.getFilename());
+ String outputFile = Paths.get(fileProperties.getStoragePath(), file.getRelativePath(), filename).toString();
+
+ try {
+ FileUtil.copy(inputFile, outputFile, true);
+ } catch (IORuntimeException e) {
+ log.error("复制文件异常", e);
+ throw new BizException("复制文件异常");
+ }
+
+ file.setFilename(filename);
+ String url = file.getUrl();
+ String newUrl = StrUtil.subPre(url, StrUtil.lastIndexOfIgnoreCase(url, StrPool.SLASH) + 1);
+ file.setUrl(newUrl + filename);
+ }
+
+
+ @Override
+ protected R merge(List files, String path, String fileName, FileChunksMergeDTO info) throws IOException {
+ //创建合并后的文件
+ log.info("path={},fileName={}", path, fileName);
+ java.io.File outputFile = new java.io.File(Paths.get(path, fileName).toString());
+ if (!outputFile.exists()) {
+ boolean newFile = outputFile.createNewFile();
+ if (!newFile) {
+ return R.fail("创建文件失败");
+ }
+ try (FileChannel outChannel = new FileOutputStream(outputFile).getChannel()) {
+ //同步nio 方式对分片进行合并, 有效的避免文件过大导致内存溢出
+ for (java.io.File file : files) {
+ try (FileChannel inChannel = new FileInputStream(file).getChannel()) {
+ inChannel.transferTo(0, inChannel.size(), outChannel);
+ } catch (FileNotFoundException ex) {
+ log.error("文件转换失败", ex);
+ return R.fail("文件转换失败");
+ }
+ //删除分片
+ if (!file.delete()) {
+ log.error("分片[" + info.getName() + "=>" + file.getName() + "]删除失败");
+ }
+ }
+ } catch (FileNotFoundException e) {
+ log.error("文件输出失败", e);
+ return R.fail("文件输出失败");
+ }
+
+ } else {
+ log.warn("文件[{}], fileName={}已经存在", info.getName(), fileName);
+ }
+
+ String relativePath = FileDataTypeUtil.getRelativePath(Paths.get(fileProperties.getStoragePath()).toString(), outputFile.getAbsolutePath());
+ log.info("relativePath={}, getStoragePath={}, getAbsolutePath={}", relativePath, fileProperties.getStoragePath(), outputFile.getAbsolutePath());
+ String url = new StringBuilder(fileProperties.getUriPrefix())
+ .append(relativePath)
+ .append(StrPool.SLASH)
+ .append(fileName)
+ .toString();
+ File filePo = File.builder()
+ .relativePath(relativePath)
+ .url(StringUtils.replace(url, "\\\\", StrPool.SLASH))
+ .build();
+ return R.success(filePo);
+ }
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileChunkStrategy.java b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileChunkStrategy.java
new file mode 100644
index 0000000000000000000000000000000000000000..5f283b5110f244e780f952ed38d0c578e1278761
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileChunkStrategy.java
@@ -0,0 +1,33 @@
+package com.github.zuihou.file.strategy;
+
+
+import com.github.zuihou.base.R;
+import com.github.zuihou.file.dto.chunk.FileChunksMergeDTO;
+import com.github.zuihou.file.entity.File;
+
+/**
+ * 文件分片处理策略类
+ *
+ * @author zuihou
+ * @date 2019/06/19
+ */
+public interface FileChunkStrategy {
+
+ /**
+ * 根据md5检测文件
+ *
+ * @param md5
+ * @param folderId
+ * @param accountId
+ * @return
+ */
+ File md5Check(String md5, Long folderId, Long accountId);
+
+ /**
+ * 合并文件
+ *
+ * @param merge
+ * @return
+ */
+ R chunksMerge(FileChunksMergeDTO merge);
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileLock.java b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileLock.java
new file mode 100644
index 0000000000000000000000000000000000000000..189489a9216aac327a56b4be38cef647c29ae744
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileLock.java
@@ -0,0 +1,50 @@
+package com.github.zuihou.file.strategy;
+
+import org.springframework.stereotype.Component;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * 文件锁工具类
+ *
+ * @author zuihou
+ * @date 2019-06-14
+ */
+@Component
+public class FileLock {
+
+ private static Map LOCKS = new HashMap();
+
+ /**
+ * 获取锁
+ *
+ * @param key
+ * @return java.util.concurrent.locks.Lock
+ * @author zuihou
+ * @date 2019-06-14 11:30
+ */
+ public static synchronized Lock getLock(String key) {
+ if (LOCKS.containsKey(key)) {
+ return LOCKS.get(key);
+ } else {
+ Lock one = new ReentrantLock();
+ LOCKS.put(key, one);
+ return one;
+ }
+ }
+
+ /**
+ * 删除锁
+ *
+ * @param key
+ * @return void
+ * @author zuihou
+ * @date 2019-06-14 11:33
+ */
+ public static synchronized void removeLock(String key) {
+ LOCKS.remove(key);
+ }
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileStrategy.java b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileStrategy.java
new file mode 100644
index 0000000000000000000000000000000000000000..da0a2c79e944b4ee44b979ac59ce7538082c5ccb
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/FileStrategy.java
@@ -0,0 +1,36 @@
+package com.github.zuihou.file.strategy;
+
+import com.github.zuihou.file.domain.FileDeleteDO;
+import com.github.zuihou.file.entity.File;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * 文件策略接口
+ *
+ * @author zuihou
+ * @date 2019/06/17
+ */
+public interface FileStrategy {
+ /**
+ * 文件上传
+ *
+ * @param file 文件
+ * @return 文件对象
+ * @author zuihou
+ * @date 2019-05-06 16:38
+ */
+ File upload(MultipartFile file);
+
+ /**
+ * 删除源文件
+ *
+ * @param list 列表
+ * @return
+ * @author zuihou
+ * @date 2019-05-07 11:41
+ */
+ boolean delete(List list);
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/strategy/impl/AbstractFileChunkStrategy.java b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/impl/AbstractFileChunkStrategy.java
new file mode 100644
index 0000000000000000000000000000000000000000..607a02604b62fe64b0775d6e70025ec030bdfef7
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/impl/AbstractFileChunkStrategy.java
@@ -0,0 +1,251 @@
+package com.github.zuihou.file.strategy.impl;
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.github.zuihou.base.R;
+import com.github.zuihou.file.domain.FileAttrDO;
+import com.github.zuihou.file.dto.chunk.FileChunksMergeDTO;
+import com.github.zuihou.file.entity.File;
+import com.github.zuihou.file.enumeration.IconType;
+import com.github.zuihou.file.properties.FileServerProperties;
+import com.github.zuihou.file.service.FileService;
+import com.github.zuihou.file.strategy.FileChunkStrategy;
+import com.github.zuihou.file.strategy.FileLock;
+import com.github.zuihou.file.utils.FileDataTypeUtil;
+import com.github.zuihou.utils.DateUtils;
+import com.github.zuihou.utils.StrPool;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+
+
+/**
+ * 文件分片处理 抽象策略类
+ *
+ * @author zuihou
+ * @date 2019/06/19
+ */
+@Slf4j
+public abstract class AbstractFileChunkStrategy implements FileChunkStrategy {
+ @Autowired
+ protected FileService fileService;
+ @Autowired
+ protected FileServerProperties fileProperties;
+
+ /**
+ * 秒传验证
+ * 根据文件的MD5签名判断该文件是否已经存在
+ *
+ * @param md5 文件的md5签名
+ * @return 若存在则返回该文件的路径,不存在则返回null
+ */
+ private File md5Check(String md5) {
+ return fileService.getOne(Wrappers.lambdaQuery()
+ .eq(File::getFileMd5, md5).eq(File::getIsDelete, false), false);
+ }
+
+ /**
+ * 设置创建日期
+ *
+ * @param file
+ */
+ private void setDate(File file) {
+ LocalDateTime now = LocalDateTime.now();
+ file.setCreateMonth(DateUtils.formatAsYearMonthEn(now))
+ .setCreateWeek(DateUtils.formatAsYearWeekEn(now))
+ .setCreateDay(DateUtils.formatAsDateEn(now));
+ }
+
+ @Override
+ public File md5Check(String md5, Long folderId, Long accountId) {
+ File file = md5Check(md5);
+ if (file == null) {
+ return null;
+ }
+
+ //分片存在,不需上传, 复制一条数据,重新插入
+ copyFile(file);
+
+ file.setId(null)
+ .setCreateUser(accountId)
+ .setCreateTime(LocalDateTime.now());
+ file.setUpdateTime(LocalDateTime.now())
+ .setUpdateUser(accountId);
+ setDate(file);
+
+ FileAttrDO attr = fileService.getFileAttrDo(folderId);
+ file.setFolderId(folderId)
+ .setTreePath(attr.getTreePath())
+ .setGrade(attr.getGrade())
+ .setFolderName(attr.getFolderName());
+
+ fileService.save(file);
+ return file;
+ }
+
+ /**
+ * 让子类自己实现复制
+ *
+ * @param file
+ */
+ protected abstract void copyFile(File file);
+
+ @Override
+ public R chunksMerge(FileChunksMergeDTO info) {
+
+
+ String filename = new StringBuilder(info.getName())
+ .append(StrPool.DOT)
+ .append(info.getExt()).toString();
+ R result = chunksMerge(info, filename);
+
+ log.info("path={}", result);
+ if (result.getIsSuccess() && result.getData() != null) {
+ //文件名
+ File filePo = result.getData();
+
+ filePo.setDataType(FileDataTypeUtil.getDataType(info.getContextType()))
+ .setSubmittedFileName(info.getSubmittedFileName())
+ .setIsDelete(false)
+ .setSize(info.getSize())
+ .setFileMd5(info.getMd5())
+ .setContextType(info.getContextType())
+ .setFilename(filename)
+ .setExt(info.getExt())
+ .setIcon(IconType.getIcon(info.getExt()).getIcon());
+ setDate(filePo);
+
+ FileAttrDO attr = fileService.getFileAttrDo(info.getFolderId());
+ filePo.setTreePath(attr.getTreePath())
+ .setGrade(attr.getGrade())
+ .setFolderId(info.getFolderId())
+ .setFolderName(attr.getFolderName());
+
+ fileService.save(filePo);
+ return R.success(filePo);
+ }
+ return result;
+ }
+
+ private R chunksMerge(FileChunksMergeDTO info, String fileName) {
+ String path = FileDataTypeUtil.getUploadPathPrefix(fileProperties.getStoragePath());
+ int chunks = info.getChunks();
+ String folder = info.getName();
+ String md5 = info.getMd5();
+
+ int chunksNum = this.getChunksNum(Paths.get(path, folder).toString());
+ log.info("chunks={}, chunksNum={}", chunks, chunksNum);
+ //检查是否满足合并条件:分片数量是否足够
+ if (chunks == chunksNum) {
+ //同步指定合并的对象
+ Lock lock = FileLock.getLock(folder);
+ try {
+ lock.lock();
+ //检查是否满足合并条件:分片数量是否足够
+ List files = new ArrayList<>(Arrays.asList(this.getChunks(Paths.get(path, folder).toString())));
+ if (chunks == files.size()) {
+ //按照名称排序文件,这里分片都是按照数字命名的
+
+ //这里存放的文件名一定是数字
+ files.sort(Comparator.comparingInt(f -> Convert.toInt(f.getName(), 0)));
+
+ R result = merge(files, path, fileName, info);
+ files = null;
+
+ //清理:文件夹,tmp文件
+ this.cleanSpace(folder, path);
+ return result;
+ }
+ } catch (Exception ex) {
+ log.error("数据分片合并失败", ex);
+ return R.fail("数据分片合并失败");
+ } finally {
+ //解锁
+ lock.unlock();
+ //清理锁对象
+ FileLock.removeLock(folder);
+ }
+ }
+ //去持久层查找对应md5签名,直接返回对应path
+ File file = this.md5Check(md5);
+ if (file == null) {
+ log.error("文件[签名:" + md5 + "]数据不完整,可能该文件正在合并中");
+ return R.fail("数据不完整,可能该文件正在合并中, 也有可能是上传过程中某些分片丢失");
+ }
+ return R.success(file);
+ }
+
+
+ /**
+ * 子类实现具体的合并操作
+ *
+ * @param files 文件
+ * @param path 路径
+ * @param fileName 唯一名 含后缀
+ * @param info 文件信息
+ * @return
+ * @throws IOException
+ */
+ protected abstract R merge(List files, String path, String fileName, FileChunksMergeDTO info) throws IOException;
+
+
+ /**
+ * 清理分片上传的相关数据
+ * 文件夹,tmp文件
+ *
+ * @param folder 文件夹名称
+ * @param path 上传文件根路径
+ * @return
+ */
+ protected boolean cleanSpace(String folder, String path) {
+ //删除分片文件夹
+ java.io.File garbage = new java.io.File(Paths.get(path, folder).toString());
+ if (!FileUtils.deleteQuietly(garbage)) {
+ return false;
+ }
+ //删除tmp文件
+ garbage = new java.io.File(Paths.get(path, folder + ".tmp").toString());
+ if (!FileUtils.deleteQuietly(garbage)) {
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * 获取指定文件的分片数量
+ *
+ * @param folder 文件夹路径
+ * @return
+ */
+ private int getChunksNum(String folder) {
+ java.io.File[] filesList = this.getChunks(folder);
+ return filesList.length;
+ }
+
+ /**
+ * 获取指定文件的所有分片
+ *
+ * @param folder 文件夹路径
+ * @return
+ */
+ private java.io.File[] getChunks(String folder) {
+ java.io.File targetFolder = new java.io.File(folder);
+ return targetFolder.listFiles((file) -> {
+ if (file.isDirectory()) {
+ return false;
+ }
+ return true;
+ });
+ }
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/strategy/impl/AbstractFileStrategy.java b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/impl/AbstractFileStrategy.java
new file mode 100644
index 0000000000000000000000000000000000000000..c04d7aabbf5f68e7deca657b50c143a440b8fec5
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/strategy/impl/AbstractFileStrategy.java
@@ -0,0 +1,108 @@
+package com.github.zuihou.file.strategy.impl;
+
+import com.github.zuihou.exception.BizException;
+import com.github.zuihou.file.domain.FileDeleteDO;
+import com.github.zuihou.file.entity.File;
+import com.github.zuihou.file.enumeration.IconType;
+import com.github.zuihou.file.properties.FileServerProperties;
+import com.github.zuihou.file.strategy.FileStrategy;
+import com.github.zuihou.file.utils.FileDataTypeUtil;
+import com.github.zuihou.utils.DateUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+import static com.github.zuihou.exception.code.ExceptionCode.BASE_VALID_PARAM;
+
+
+/**
+ * 文件抽象策略 处理类
+ *
+ * @author zuihou
+ * @date 2019/06/17
+ */
+@Slf4j
+public abstract class AbstractFileStrategy implements FileStrategy {
+
+ private static final String FILE_SPLIT = ".";
+ @Autowired
+ protected FileServerProperties fileProperties;
+
+ /**
+ * 上传文件
+ *
+ * @param multipartFile
+ * @return
+ */
+ @Override
+ public File upload(MultipartFile multipartFile) {
+ try {
+ if (!multipartFile.getOriginalFilename().contains(FILE_SPLIT)) {
+ throw BizException.wrap(BASE_VALID_PARAM.build("缺少后缀名"));
+ }
+
+ File file = File.builder()
+ .isDelete(false).submittedFileName(multipartFile.getOriginalFilename())
+ .contextType(multipartFile.getContentType())
+ .dataType(FileDataTypeUtil.getDataType(multipartFile.getContentType()))
+ .size(multipartFile.getSize())
+ .ext(FilenameUtils.getExtension(multipartFile.getOriginalFilename()))
+ .build();
+ file.setIcon(IconType.getIcon(file.getExt()).getIcon());
+ setDate(file);
+ uploadFile(file, multipartFile);
+ return file;
+ } catch (Exception e) {
+ log.error("e={}", e);
+ throw BizException.wrap(BASE_VALID_PARAM.build("文件上传失败"));
+ }
+ }
+
+ /**
+ * 具体类型执行上传操作
+ *
+ * @param file
+ * @param multipartFile
+ * @throws Exception
+ */
+ protected abstract void uploadFile(File file, MultipartFile multipartFile) throws Exception;
+
+ private void setDate(File file) {
+ LocalDateTime now = LocalDateTime.now();
+ file.setCreateMonth(DateUtils.formatAsYearMonthEn(now))
+ .setCreateWeek(DateUtils.formatAsYearWeekEn(now))
+ .setCreateDay(DateUtils.formatAsDateEn(now));
+ }
+
+ @Override
+ public boolean delete(List list) {
+ if (list.isEmpty()) {
+ return true;
+ }
+ boolean flag = false;
+ for (FileDeleteDO file : list) {
+ try {
+ delete(list, file);
+ flag = true;
+ } catch (Exception e) {
+ log.error("删除文件失败", e);
+ }
+ }
+ return flag;
+ }
+
+ /**
+ * 具体执行删除方法, 无需处理异常
+ *
+ * @param list
+ * @param file
+ * @author zuihou
+ * @date 2019-05-07
+ */
+ protected abstract void delete(List list, FileDeleteDO file);
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/utils/FileDataTypeUtil.java b/blade-service/blade-file/src/main/java/org/springblade/file/utils/FileDataTypeUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..a55f735b14aab87deedcf9efa40e5896628a9eae
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/utils/FileDataTypeUtil.java
@@ -0,0 +1,82 @@
+package com.github.zuihou.file.utils;
+
+
+import com.github.zuihou.file.enumeration.DataType;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 根据类型识别工具
+ *
+ * @author zuihou
+ * @date 2019-05-06
+ */
+@Slf4j
+public class FileDataTypeUtil {
+ final static DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyy/MM");
+ private final static String IMAGE = "image";
+ private final static String VIDEO = "video";
+ private final static String DIR = "application/x-director";
+ private final static String AUDIO = "audio";
+ private final static String TEXT = "text";
+
+ /**
+ * 根据mine类型,返回文件类型
+ *
+ * @param
+ * @return
+ * @author zuihou
+ * @date 2019-05-06 13:41
+ */
+ public static DataType getDataType(String mime) {
+ if (mime == null || "".equals(mime)) {
+ return DataType.OTHER;
+ }
+ if (mime.contains(IMAGE)) {
+ return DataType.IMAGE;
+ } else if (mime.contains(TEXT)
+ || mime.startsWith("application/vnd.ms-excel")
+ || mime.startsWith("application/msword")
+ || mime.startsWith("application/pdf")
+ || mime.startsWith("application/vnd.ms-project")
+ || mime.startsWith("application/vnd.ms-works")
+ || mime.startsWith("application/x-javascript")
+ || mime.startsWith("application/vnd.openxmlformats-officedocument")
+ || mime.startsWith("application/vnd.ms-word.document.macroEnabled")
+ || mime.startsWith("application/vnd.ms-word.template.macroEnabled")
+ || mime.startsWith("application/vnd.ms-powerpoint")
+ ) {
+ return DataType.DOC;
+ } else if (mime.contains(VIDEO)) {
+ return DataType.VIDEO;
+ } else if (mime.contains(DIR)) {
+ return DataType.DIR;
+ } else if (mime.contains(AUDIO)) {
+ return DataType.AUDIO;
+ } else {
+ return DataType.OTHER;
+ }
+ }
+
+ public static String getUploadPathPrefix(String uploadPathPrefix) {
+ //日期文件夹
+ String secDir = LocalDate.now().format(DTF);
+ // web服务器存放的绝对路径
+ String absolutePath = Paths.get(uploadPathPrefix, secDir).toString();
+ return absolutePath;
+ }
+
+ public static String getRelativePath(String pathPrefix, String path) {
+ String remove = StringUtils.remove(path, pathPrefix + File.separator);
+
+ log.info("remove={}, index={}", remove, remove.lastIndexOf(java.io.File.separator));
+ String relativePath = StringUtils.substring(remove, 0, remove.lastIndexOf(java.io.File.separator));
+ return relativePath;
+ }
+
+}
diff --git a/blade-service/blade-file/src/main/java/org/springblade/file/utils/ZipUtils.java b/blade-service/blade-file/src/main/java/org/springblade/file/utils/ZipUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e3c6eb0b6902b84e17d86b10f95ef90925dc1d1
--- /dev/null
+++ b/blade-service/blade-file/src/main/java/org/springblade/file/utils/ZipUtils.java
@@ -0,0 +1,254 @@
+package com.github.zuihou.file.utils;
+
+
+import com.github.zuihou.exception.BizException;
+import com.github.zuihou.utils.StrPool;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+import static com.github.zuihou.utils.StrPool.SLASH;
+
+
+/**
+ * ZipUtils on spring-boot-filemanager
+ *
+ * @author Alex Yang
+ * @date 2016年08月25日 10:08
+ */
+@Slf4j
+public class ZipUtils {
+
+ private final static String AGENT_FIREFOX = "firefox";
+
+ private static void zipFiles(ZipOutputStream out, String path, File... srcFiles) {
+ path = path.replaceAll("\\*", SLASH);
+ if (!path.endsWith(SLASH)) {
+ path += SLASH;
+ }
+ byte[] buf = new byte[1024];
+ try {
+ for (File srcFile : srcFiles) {
+ if (srcFile.isDirectory()) {
+ File[] files = srcFile.listFiles();
+ String srcPath = srcFile.getName();
+ srcPath = srcPath.replaceAll("\\*", SLASH);
+ if (!srcPath.endsWith(SLASH)) {
+ srcPath += SLASH;
+ }
+ out.putNextEntry(new ZipEntry(path + srcPath));
+ zipFiles(out, path + srcPath, files);
+ } else {
+ try (FileInputStream in = new FileInputStream(srcFile)) {
+ out.putNextEntry(new ZipEntry(path + srcFile.getName()));
+ int len;
+ while ((len = in.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ out.closeEntry();
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.info("ZipUtils error {} ", e);
+ }
+ }
+
+ /**
+ * 通过流打包下载文件
+ *
+ * @param out
+ * @param fileName
+ * @param
+ */
+ public static void zipFilesByInputStream(ZipOutputStream out, String fileName, InputStream is) throws Exception {
+ byte[] buf = new byte[1024];
+ try {
+ out.putNextEntry(new ZipEntry(fileName));
+ int len;
+ while ((len = is.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ is.close();
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+ /**
+ * 下载指定输入流的图片
+ *
+ * @param
+ * @param
+ * @param
+ * @throws Exception
+ */
+ private static void downloadFile(InputStream is, OutputStream out) throws Exception {
+ try {
+ byte[] b = new byte[2048];
+ int length;
+ while ((length = is.read(b)) > 0) {
+ out.write(b, 0, length);
+ }
+ } catch (Exception e) {
+ throw e;
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ if (is != null) {
+ is.close();
+ }
+ }
+ }
+
+ public static void unZipFiles(File zipFile, String descDir) throws IOException {
+ if (!descDir.endsWith(SLASH)) {
+ descDir += SLASH;
+ }
+ File pathFile = new File(descDir);
+ if (!pathFile.exists()) {
+ pathFile.mkdirs();
+ }
+ try (ZipFile zip = new ZipFile(zipFile)) {
+ for (Enumeration entries = zip.entries(); entries.hasMoreElements(); ) {
+ ZipEntry entry = (ZipEntry) entries.nextElement();
+ String zipEntryName = entry.getName();
+
+ String outPath = (descDir + zipEntryName).replaceAll("\\*", SLASH);
+ //判断路径是否存在,不存在则创建文件路径
+ File file = new File(outPath.substring(0, outPath.lastIndexOf('/')));
+ if (!file.exists()) {
+ file.mkdirs();
+ }
+ //判断文件全路径是否为文件夹,如果是上面已经上传,不需要解压
+ if (new File(outPath).isDirectory()) {
+ continue;
+ }
+
+
+ try (InputStream in = zip.getInputStream(entry); OutputStream out = new FileOutputStream(outPath)) {
+ byte[] buf1 = new byte[1024];
+ int len;
+ while ((len = in.read(buf1)) > 0) {
+ out.write(buf1, 0, len);
+ }
+ }
+ }
+ } catch (ZipException e) {
+ throw e;
+ }
+ }
+
+ public static void zipFilesByInputStream(Map fileMap, Long fileSize, String extName, HttpServletRequest request, HttpServletResponse response) throws Exception {
+ HttpURLConnection connection = null;
+
+ response.setContentType("application/octet-stream; charset=utf-8");
+ String downloadFileName;
+ String agent = request.getHeader("USER-AGENT");
+ if (agent != null && agent.toLowerCase().indexOf(AGENT_FIREFOX) > 0) {
+ downloadFileName = "=?UTF-8?B?" + (new String(Base64.encodeBase64((extName).getBytes("UTF-8")))) + "?=";
+ } else {
+ //~ \ / |:"<>? 这些字符不能被替换,因为系统允许文件名有这些字符!!
+ downloadFileName = URLEncoder.encode(extName, "UTF-8")
+ .replaceAll("\\+", "%20").replaceAll("%28", "\\(")
+ .replaceAll("%29", "\\)")
+ .replaceAll("%3B", StrPool.SEMICOLON)
+ .replaceAll("%40", StrPool.AT).replaceAll("%23", "\\#")
+ .replaceAll("%26", "\\&").replaceAll("%2C", "\\,")
+ .replaceAll("%2B", StrPool.PLUS).replaceAll("%25", StrPool.PERCENT)
+ .replaceAll("%21", StrPool.EXCLAMATION_MARK).replaceAll("%5E", StrPool.HAT)
+ .replaceAll("%24", "\\$").replaceAll("%7E", StrPool.TILDA)
+
+ .replaceAll("%60", StrPool.BACKTICK).replaceAll("%5B", StrPool.LEFT_SQ_BRACKET)
+ .replaceAll("%3D", StrPool.EQUALS)
+ .replaceAll("%5D", StrPool.RIGHT_SQ_BRACKET).replaceAll("%5C", "\\\\")
+ .replaceAll("%27", StrPool.SINGLE_QUOTE).replaceAll("%2F", SLASH)
+ .replaceAll("%7B", StrPool.LEFT_BRACE).replaceAll("%7D", StrPool.RIGHT_BRACE)
+
+ .replaceAll("%7C", "\\|").replaceAll("%3A", "\\:")
+ .replaceAll("%22", "\\\"").replaceAll("%3C", "\\<")
+ .replaceAll("%3E", "\\>").replaceAll("%3F", "\\?")
+
+ ;
+ log.info("downloadFileName={}", downloadFileName);
+ }
+ response.setHeader("Content-Disposition", "attachment;fileName=" + downloadFileName);
+ if (fileSize != null && fileSize > 0) {
+ // 加了这个下载会报错?
+// response.setHeader("Content-Length", String.valueOf(fileSize));
+ }
+
+ ServletOutputStream out = response.getOutputStream();
+ if (fileMap.size() == 1) {
+ String url = null;
+ for (Map.Entry entry : fileMap.entrySet()) {
+ url = entry.getValue();
+ }
+ try {
+ connection = getConnection(url);
+ ZipUtils.downloadFile(connection.getInputStream(), out);
+ } catch (Exception e) {
+ throw new BizException("文件地址连接超时");
+ }
+ return;
+ }
+
+ try (ZipOutputStream zos = new ZipOutputStream(out); BufferedOutputStream bos = new BufferedOutputStream(zos)) {
+ for (Map.Entry entry : fileMap.entrySet()) {
+ String fileName = entry.getKey();
+ String url = entry.getValue();
+ BufferedInputStream bis = null;
+ try {
+ connection = getConnection(url);
+ bis = new BufferedInputStream(connection.getInputStream());
+ zos.putNextEntry(new ZipEntry(fileName));
+
+ int len;
+ byte[] buf = new byte[10 * 1024];
+ while ((len = bis.read(buf, 0, buf.length)) != -1) {
+ bos.write(buf, 0, len);
+ }
+ bos.flush();
+ } catch (Exception e) {
+ log.warn("打包下载多个文件异常, fileName=" + fileName + ",url=" + url, e);
+ } finally {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ if (bis != null) {
+ bis.close();
+ }
+ if (zos != null) {
+ zos.closeEntry();
+ }
+ }
+ }
+ }
+ }
+
+ private static HttpURLConnection getConnection(String url) throws Exception {
+ log.info("url={}", url);
+ URL conURL = new URL(url);
+ HttpURLConnection connection = (HttpURLConnection) conURL.openConnection();
+ connection.connect();
+ return connection;
+ }
+}
diff --git a/blade-service/blade-file/src/main/resources/application-dev.yml b/blade-service/blade-file/src/main/resources/application-dev.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e79c2ca82b2e75d0192b65354baae254c3947487
--- /dev/null
+++ b/blade-service/blade-file/src/main/resources/application-dev.yml
@@ -0,0 +1,10 @@
+#服务器端口
+server:
+ port: 8203
+
+#数据源配置
+spring:
+ datasource:
+ url: ${blade.datasource.dev.url}
+ username: ${blade.datasource.dev.username}
+ password: ${blade.datasource.dev.password}
diff --git a/blade-service/blade-file/src/main/resources/application-prod.yml b/blade-service/blade-file/src/main/resources/application-prod.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c0d8c0562a0cadfa783ac1f4e242e8e8eff1441f
--- /dev/null
+++ b/blade-service/blade-file/src/main/resources/application-prod.yml
@@ -0,0 +1,10 @@
+#服务器端口
+server:
+ port: 8102
+
+#数据源配置
+spring:
+ datasource:
+ url: ${blade.datasource.prod.url}
+ username: ${blade.datasource.prod.username}
+ password: ${blade.datasource.prod.password}
diff --git a/blade-service/blade-file/src/main/resources/application-test.yml b/blade-service/blade-file/src/main/resources/application-test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..57b842e0f9c228ee53a53e72933c6784f2a3b674
--- /dev/null
+++ b/blade-service/blade-file/src/main/resources/application-test.yml
@@ -0,0 +1,10 @@
+#服务器端口
+server:
+ port: 8102
+
+#数据源配置
+spring:
+ datasource:
+ url: ${blade.datasource.test.url}
+ username: ${blade.datasource.test.username}
+ password: ${blade.datasource.test.password}
diff --git a/blade-service/blade-file/src/main/resources/banner.txt b/blade-service/blade-file/src/main/resources/banner.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6b82083c89b24b21824d86ab7599bc5e8c4c057e
--- /dev/null
+++ b/blade-service/blade-file/src/main/resources/banner.txt
@@ -0,0 +1,10 @@
+${AnsiColor.BRIGHT_YELLOW}
+ .__.__ .___ .__ .__ .___
+__________ __|__| |__ ____ __ __ _____ __| _/_____ |__| ____ ____ | | ____ __ __ __| _/
+\___ / | \ | | \ / _ \| | \ ______ \__ \ / __ |/ \| |/ \ ______ _/ ___\| | / _ \| | \/ __ |
+ / /| | / | Y ( <_> ) | / /_____/ / __ \_/ /_/ | Y Y \ | | \ /_____/ \ \___| |_( <_> ) | / /_/ |
+/_____ \____/|__|___| /\____/|____/ (____ /\____ |__|_| /__|___| / \___ >____/\____/|____/\____ |
+ \/ \/ \/ \/ \/ \/ \/ \/
+Application Version: @project.description@ - @project.version@
+Spring Boot Version: ${spring-boot.version}${spring-boot.formatted-version}
+${AnsiColor.DEFAULT}
diff --git a/blade-service/blade-file/src/main/resources/bootstrap.yml b/blade-service/blade-file/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000000000000000000000000000000000000..061bd68dbc3472c291cd6479c2bd6be76bbfb9b3
--- /dev/null
+++ b/blade-service/blade-file/src/main/resources/bootstrap.yml
@@ -0,0 +1,56 @@
+zuihou:
+ nacos:
+ ip: ${NACOS_IP:@nacos.ip@}
+ port: ${NACOS_PORT:@nacos.port@}
+ namespace: ${NACOS_ID:@nacos.namespace@}
+ username: ${NACOS_USERNAME:@nacos.username@}
+ password: ${NACOS_PASSWORD:@nacos.password@}
+spring:
+ main:
+ allow-bean-definition-overriding: true
+ application:
+ name: @project.artifactId@
+ profiles:
+ active: @profile.active@
+ cloud:
+ nacos:
+ config:
+ server-addr: ${zuihou.nacos.ip}:${zuihou.nacos.port}
+ file-extension: yml
+ namespace: ${zuihou.nacos.namespace}
+ shared-configs:
+ - dataId: common.yml
+ refresh: true
+ - dataId: redis.yml
+ refresh: false
+ - dataId: mysql.yml
+ refresh: true
+ - dataId: rabbitmq.yml
+ refresh: false
+ enabled: true
+ username: ${zuihou.nacos.username}
+ password: ${zuihou.nacos.password}
+ discovery:
+ username: ${zuihou.nacos.username}
+ password: ${zuihou.nacos.password}
+ server-addr: ${zuihou.nacos.ip}:${zuihou.nacos.port}
+ namespace: ${zuihou.nacos.namespace}
+ metadata: # 元数据,用于权限服务实时获取各个服务的所有接口
+ management.context-path: ${server.servlet.context-path:}${spring.mvc.servlet.path:}${management.endpoints.web.base-path:}
+ grayversion: zuihou
+
+# 只能配置在bootstrap.yml ,否则会生成 log.path_IS_UNDEFINED 文件夹
+# window会自动在 代码所在盘 根目录下自动创建文件夹, 如: D:/data/projects/logs
+logging:
+ file:
+ # 为什么要用绝对路径,而非相对路径? 正式环境服务器磁盘通常是外挂到某个目录的,所以需要指定这个路径。 开发环境将所有系统日志存放在一起,便于后期清理日志文件,不用一个项目一个项目删除。
+ path: @logging.file.path@
+ name: ${logging.file.path}/${spring.application.name}/root.log
+ level:
+ com.baomidou.dynamic: debug
+
+# 用于/actuator/info
+info:
+ name: '@project.name@'
+ description: '@project.description@'
+ version: '@project.version@'
diff --git a/blade-service/blade-file/src/test/java/org/springblade/AppTest.java b/blade-service/blade-file/src/test/java/org/springblade/AppTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..1359a54c38e922f7dddaf46462eb46cfcff2f131
--- /dev/null
+++ b/blade-service/blade-file/src/test/java/org/springblade/AppTest.java
@@ -0,0 +1,20 @@
+package org.springblade;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+{
+ /**
+ * Rigorous Test :-)
+ */
+ @Test
+ public void shouldAnswerWithTrue()
+ {
+ assertTrue( true );
+ }
+}
diff --git a/blade-service/blade-stock/README.md b/blade-service/blade-stock/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..4aaa273cb76266459d80efc700a1e9db8674903b
--- /dev/null
+++ b/blade-service/blade-stock/README.md
@@ -0,0 +1,50 @@
+## balde-stock 模块
+ 股票分析、统计模块
+ 1. 行情策略筛选出自选
+ 2. 自选历史统计
+ 3. 操作模拟统计
+
+## 架构图
+
+## 工程结构
+```
+SpringBlade
+├── blade-auth -- 授权服务提供
+├── blade-common -- 常用工具封装包
+├── blade-gateway -- Spring Cloud 网关
+├── blade-ops -- 运维中心
+├ ├── blade-admin -- spring-cloud后台管理
+├ ├── blade-develop -- 代码生成
+├ ├── blade-resource -- 资源管理
+├ ├── blade-seata-order -- seata分布式事务demo
+├ ├── blade-seata-storage -- seata分布式事务demo
+├── blade-service -- 业务模块
+├ ├── blade-desk -- 工作台模块
+├ ├── blade-log -- 日志模块
+├ ├── blade-system -- 系统模块
+├ └── blade-user -- 用户模块
+├── blade-service-api -- 业务模块api封装
+├ ├── blade-desk-api -- 工作台api
+├ ├── blade-dict-api -- 字典api
+├ ├── blade-system-api -- 系统api
+└── └── blade-user-api -- 用户api
+```
+
+## 官网
+
+
+## 在线演示
+* Saber-基于Vue:[https://saber.bladex.vip](https://saber.bladex.vip)
+* Sword-基于React:[https://sword.bladex.vip](https://sword.bladex.vip)
+* Archer-全能代码生成系统:[https://archer.bladex.vip](https://archer.bladex.vip)
+* Caster-数据大屏展示系统:[https://data.avuejs.com](https://data.avuejs.com)
+
+## 技术文档
+* [SpringBlade开发手册一览](https://gitee.com/smallc/SpringBlade/wikis/SpringBlade开发手册)
+* [常见问题集锦](https://sns.bladex.vip/article-14966.html)
+
+## 项目地址
+
+# 开源协议
+Apache Licence 2.0 ([英文原文](http://www.apache.org/licenses/LICENSE-2.0.html))
+Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。
diff --git a/blade-service/blade-stock/pom.xml b/blade-service/blade-stock/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0ccf5f227ebc521293ddca54984baa8dd5b75ad6
--- /dev/null
+++ b/blade-service/blade-stock/pom.xml
@@ -0,0 +1,63 @@
+
+
+
+
+ blade-service
+ org.springblade
+ 2.7.1
+
+ 4.0.0
+ blade-stock
+ ${project.artifactId}
+ ${blade.project.version}
+ jar
+
+
+
+ org.springblade
+ blade-core-boot
+ ${blade.tool.version}
+
+
+ org.springblade
+ blade-stock-api
+ ${blade.project.version}
+
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-antrun-plugin
+
+
+ package
+
+ run
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/blade-service/blade-stock/src/main/java/org/springblade/stock/StockApplication.java b/blade-service/blade-stock/src/main/java/org/springblade/stock/StockApplication.java
new file mode 100644
index 0000000000000000000000000000000000000000..f4b314e99d1cae0519c4d916484dd7495fe6f486
--- /dev/null
+++ b/blade-service/blade-stock/src/main/java/org/springblade/stock/StockApplication.java
@@ -0,0 +1,19 @@
+package org.springblade.stock;
+
+import org.springblade.core.launch.BladeApplication;
+import org.springblade.core.launch.constant.AppConstant;
+import org.springframework.cloud.client.SpringCloudApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+
+/**
+ * stock启动器
+ */
+@SpringCloudApplication
+@EnableFeignClients(AppConstant.BASE_PACKAGES)
+public class StockApplication {
+
+ public static void main(String[] args) {
+ //AppConstant.APPLICATION_DESK_NAME
+ BladeApplication.run("blade-stock", StockApplication.class, args);
+ }
+}
diff --git a/blade-service/blade-stock/src/main/resources/application-dev.yml b/blade-service/blade-stock/src/main/resources/application-dev.yml
new file mode 100644
index 0000000000000000000000000000000000000000..429dac954d62fe9acda0768a1853caff603d6456
--- /dev/null
+++ b/blade-service/blade-stock/src/main/resources/application-dev.yml
@@ -0,0 +1,10 @@
+#服务器端口
+server:
+ port: 8201
+
+#数据源配置
+spring:
+ datasource:
+ url: ${blade.datasource.dev.url}
+ username: ${blade.datasource.dev.username}
+ password: ${blade.datasource.dev.password}
diff --git a/blade-service/blade-stock/src/main/resources/application-prod.yml b/blade-service/blade-stock/src/main/resources/application-prod.yml
new file mode 100644
index 0000000000000000000000000000000000000000..361c857e774d91dc1c0a23980c27f907d65afc0f
--- /dev/null
+++ b/blade-service/blade-stock/src/main/resources/application-prod.yml
@@ -0,0 +1,10 @@
+#服务器端口
+server:
+ port: 8201
+
+#数据源配置
+spring:
+ datasource:
+ url: ${blade.datasource.prod.url}
+ username: ${blade.datasource.prod.username}
+ password: ${blade.datasource.prod.password}
diff --git a/blade-service/blade-stock/src/main/resources/application-test.yml b/blade-service/blade-stock/src/main/resources/application-test.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9e9b257d33a4d1abacdac15f9b6be24b0b4296ad
--- /dev/null
+++ b/blade-service/blade-stock/src/main/resources/application-test.yml
@@ -0,0 +1,10 @@
+#服务器端口
+server:
+ port: 8201
+
+#数据源配置
+spring:
+ datasource:
+ url: ${blade.datasource.test.url}
+ username: ${blade.datasource.test.username}
+ password: ${blade.datasource.test.password}
diff --git a/blade-service/blade-system/src/main/resources/application-dev.yml b/blade-service/blade-system/src/main/resources/application-dev.yml
index 216bd19835ff24d91c833bf0e5263b6f9a109ff7..59c9bb9a7c874e912e582451840e22c2823ff592 100644
--- a/blade-service/blade-system/src/main/resources/application-dev.yml
+++ b/blade-service/blade-system/src/main/resources/application-dev.yml
@@ -5,6 +5,7 @@ server:
#数据源配置
spring:
datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
url: ${blade.datasource.dev.url}
username: ${blade.datasource.dev.username}
- password: ${blade.datasource.dev.password}
\ No newline at end of file
+ password: ${blade.datasource.dev.password}
diff --git a/blade-service/pom.xml b/blade-service/pom.xml
index 609d4a39ec862dc43a6d748f91ca16dc41d191a5..1fe200980f4810a618f80be254650ce01d3878a8 100644
--- a/blade-service/pom.xml
+++ b/blade-service/pom.xml
@@ -22,6 +22,8 @@
blade-system
blade-user
blade-demo
+ blade-stock
+ blade-file
diff --git "a/doc/digital/\344\273\243\347\240\201\347\224\237\346\210\220\346\226\207\344\273\266/sql/walletrecord.menu.mysql" "b/doc/digital/\344\273\243\347\240\201\347\224\237\346\210\220\346\226\207\344\273\266/sql/walletrecord.menu.mysql"
new file mode 100644
index 0000000000000000000000000000000000000000..4e778e5549dc34332f75702cd278cc22359c560b
--- /dev/null
+++ "b/doc/digital/\344\273\243\347\240\201\347\224\237\346\210\220\346\226\207\344\273\266/sql/walletrecord.menu.mysql"
@@ -0,0 +1,11 @@
+INSERT INTO `blade_menu`(`id`,`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (1293811285207400451,1293811285207400450, 'walletrecord', '钱包信息记录管理', 'menu', '/wallet/walletrecord', NULL, 1, 1, 0, 1, NULL, 0);
+set @parentid = (SELECT LAST_INSERT_ID());
+INSERT INTO `blade_menu`(`id`,`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (1293811285207400452,@parentid, 'walletrecord_add', '新增', 'add', '/wallet/walletrecord/add', 'plus', 1, 2, 1, 1, NULL, 0);
+INSERT INTO `blade_menu`(`id`,`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (1293811285207400453,@parentid, 'walletrecord_edit', '修改', 'edit', '/wallet/walletrecord/edit', 'form', 2, 2, 1, 2, NULL, 0);
+INSERT INTO `blade_menu`(`id`,`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (1293811285207400454,@parentid, 'walletrecord_delete', '删除', 'delete', '/api/digital-wallet/walletrecord/remove', 'delete', 3, 2, 1, 3, NULL, 0);
+INSERT INTO `blade_menu`(`id`,`parent_id`, `code`, `name`, `alias`, `path`, `source`, `sort`, `category`, `action`, `is_open`, `remark`, `is_deleted`)
+VALUES (1293811285207400455,@parentid, 'walletrecord_view', '查看', 'view', '/wallet/walletrecord/view', 'file-text', 4, 2, 1, 2, NULL, 0);
diff --git a/doc/docker/auth/Dockerfile b/doc/docker/auth/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..6b38f0dc82694d60019b525d6ad78afe18b0f094
--- /dev/null
+++ b/doc/docker/auth/Dockerfile
@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER w1999wtw3537@sina.com
+
+RUN mkdir -p /digital/auth
+
+WORKDIR /digital/auth
+
+EXPOSE 8010
+
+ADD blade-auth.jar app.jar
+
+ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
+
+CMD ["--spring.profiles.active=dev"]
diff --git a/doc/docker/auth/build b/doc/docker/auth/build
new file mode 100644
index 0000000000000000000000000000000000000000..918c0a4c1e4b60e0251986e90d1407351c9785f8
--- /dev/null
+++ b/doc/docker/auth/build
@@ -0,0 +1,2 @@
+手动构建镜像命令:docker build -t digital-auth:2.7.1 .
+手动启动命令:docker run -d -p 8010:8010 --name digital-auth digital-auth:2.7.1
diff --git a/doc/docker/gateway/Dockerfile b/doc/docker/gateway/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..2808345c836662c148502d41076cc0fc5bbdd366
--- /dev/null
+++ b/doc/docker/gateway/Dockerfile
@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER w1999wtw3537@sina.com
+
+RUN mkdir -p /digital/gateway
+
+WORKDIR /digital/gateway
+
+EXPOSE 80
+
+ADD blade-gateway.jar app.jar
+
+ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
+
+CMD ["--spring.profiles.active=dev"]
diff --git a/doc/docker/gateway/build b/doc/docker/gateway/build
new file mode 100644
index 0000000000000000000000000000000000000000..300b394317f76bd9890407886f560585423eecdd
--- /dev/null
+++ b/doc/docker/gateway/build
@@ -0,0 +1,2 @@
+手动构建镜像命令:docker build -t digital-gateway:2.7.1 .
+手动启动命令:docker run -d -p 80:80 --name digital-gateway digital-gateway:2.7.1
diff --git a/doc/docker/system/Dockerfile b/doc/docker/system/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..d3050247993e5ce1c993b5af3943158acb036730
--- /dev/null
+++ b/doc/docker/system/Dockerfile
@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER w1999wtw3537@sina.com
+
+RUN mkdir -p /digital/system
+
+WORKDIR /digital/system
+
+EXPOSE 8106
+
+ADD blade-system.jar app.jar
+
+ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
+
+CMD ["--spring.profiles.active=dev"]
diff --git a/doc/docker/system/build b/doc/docker/system/build
new file mode 100644
index 0000000000000000000000000000000000000000..00885ef193e567c7f6106beeff0f9981c9c6af7a
--- /dev/null
+++ b/doc/docker/system/build
@@ -0,0 +1,2 @@
+手动构建镜像命令:docker build -t digital-system:2.7.1 .
+手动启动命令:docker run -d -p 8106:8106 --name digital-system digital-system:2.7.1
diff --git a/doc/docker/user/Dockerfile b/doc/docker/user/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..7ac949e29cb39c9a779a048f16046c86618533f0
--- /dev/null
+++ b/doc/docker/user/Dockerfile
@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER w1999wtw3537@sina.com
+
+RUN mkdir -p /digital/user
+
+WORKDIR /digital/user
+
+EXPOSE 8102
+
+ADD blade-user.jar /app.jar
+
+ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
+
+CMD ["--spring.profiles.active=dev"]
diff --git a/doc/docker/user/build b/doc/docker/user/build
new file mode 100644
index 0000000000000000000000000000000000000000..eb49adbcdc58c4560880ffb84dc2974e3b956e5c
--- /dev/null
+++ b/doc/docker/user/build
@@ -0,0 +1,2 @@
+手动构建镜像命令:docker build -t digital-user:2.7.1 .
+手动启动命令:docker run -d -p 8102:8102 --name digital-user digital-user:2.7.1
diff --git a/doc/docker/wallet/Dockerfile b/doc/docker/wallet/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..346d709f3c96fb2e53cb0517a5935d977898a7e9
--- /dev/null
+++ b/doc/docker/wallet/Dockerfile
@@ -0,0 +1,15 @@
+FROM anapsix/alpine-java:8_server-jre_unlimited
+
+MAINTAINER w1999wtw3537@sina.com
+
+RUN mkdir -p /digital/wallet
+
+WORKDIR /digital/wallet
+
+EXPOSE 8201
+
+ADD digital-wallet.jar app.jar
+
+ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "app.jar"]
+
+CMD ["--spring.profiles.active=dev"]
diff --git a/doc/docker/wallet/build b/doc/docker/wallet/build
new file mode 100644
index 0000000000000000000000000000000000000000..390b9629b865bf546abad4e73bb3b1fce7d5aeb3
--- /dev/null
+++ b/doc/docker/wallet/build
@@ -0,0 +1,2 @@
+手动构建镜像命令:docker build -t digital-wallet:2.7.1 .
+手动启动命令:docker run -d -p 8201:8201 --name digital-wallet digital-wallet:2.7.1
diff --git a/doc/service/fileserver/README.md b/doc/service/fileserver/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0c09a8eddeac5abb5ebb9e849fba148f0d629117
--- /dev/null
+++ b/doc/service/fileserver/README.md
@@ -0,0 +1,29 @@
+# 文件服务系统设计
+###1. 需求说明
+ 可伸缩、通用的文件服务器.通常后端项目可能会有头像、图片、音频、视频等上传/下载需求,这些需求都可以抽象为文件服务。
+
+###2. 功能特点
+ - 支持Linux(推荐)、Windows
+
+ - 可伸缩式架构,支持部署1-N台文件服务器
+
+ - RESTful架构的API接口,支持多语言客户端
+
+ - 支持文件秒传、断点续传、远程拉取上传
+
+ - 支持为用户指定磁盘空间配额
+
+ - 支持自定义文件处理器
+
+###3. 系统架构
+
+文件的上传/下载通常由客户端直接与文件服务器交互,上传时需要提供代表用户身份token(由业务服务器生成),成功后会返回文件根地址。
+
+也可以直接由业务服务器上传返回文件根地址给客户端
+
+
+###4. 技术选型以及第三方存储服务
+
+
+
+###5. 在工程根目录的docker-compose.yml下加入配置,内容可参考如下
diff --git a/pom.xml b/pom.xml
index b98d303ff42a5b157b2ea3262a34804e4ab537f2..4f3eeb5924ebeb254b23c154fcf56d7c2605163d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,6 +42,8 @@
blade-service
blade-service-api
blade-common
+ blade-digital-service
+ blade-digital-api